Conversation
…closes #149) Grammar fix on a name introduced in 2019. `overridden` (past participle) fits the "with a halt-state that has been ___" naming idiom; `overrode` (simple past) didn't. Hard cutover — no deprecated alias. Renames in lockstep: - public method `State.prototype.withOverrodeHaltState` → `withOverriddenHaltState` - getter `state.overrodeHaltState` → `state.overriddenHaltState` - private field `#overrodeHaltState` → `#overriddenHaltState` - serialized `Graph` field `node.overrodeHaltStateId` → `node.overriddenHaltStateId` Consumer migration: global find/replace `OverrodeHaltState` → `OverriddenHaltState` and `overrodeHaltState` → `overriddenHaltState`. Persisted `State.toGraph` JSON dumps would need the same field-rename treatment, but persistence isn't a known consumer pattern. CHANGELOG entry deferred to the v7 release PR per repo convention (entries are dated at release time, no Unreleased section). README "Versioning notes" section gets a v7 (in progress) entry now. Cross-repo follow-ups (post-machine-js, machines-demo) wait until engine v7 publishes to npm — their peer-dep ranges can't widen to `^7` until `^7` exists.
rename `withOverrodeHaltState` → `withOverriddenHaltState` (#149)
Composition-name format change in `State#withOverriddenHaltState`: flat `bare>override` → nested `bare(override)`. The old `>`-flat notation collided structurally-distinct wrap-trees into the same string — e.g., `A.with(B.with(A))` and `A.with(B).with(A)` both rendered as "A>B>A". The paren form keeps them distinct: `A(B(A))` vs `A(B)(A)`. Changes: - `State#withOverriddenHaltState` emits paren-format names. - `State` constructor now rejects user-provided names containing `(` or `)` — they're reserved as wrapper-composition delimiters. `>` is no longer reserved and is valid in user-provided names again. - Internal composition paths (`withOverriddenHaltState` and `fromGraph`'s wrapper reconstruction) bypass the validation by constructing a State with no name and assigning `#name` directly. `#name` lost its `readonly` modifier to allow this; access stays inside the class. - `inspect()`, `toGraph`, `toMermaid` outputs carry the new format transparently — name is just a string, no shape change. - Round-trip name accumulation (#138/#139) still applies in paren form: `A(B)` becomes `A(B)(B)` on second round-trip. Tracked separately. Tests added: - Distinguishes the two A.with(B).with(A) vs A.with(B.with(A)) constructions that flat `>` notation collided. - Constructor throws on names containing `(` or `)`. Generated artifacts: - `states.md` for both binary libraries regenerated. Marker library gets the new paren format. Bare library has cosmetic ID renumbering (no composite names — it doesn't use composition primitives) per the build-states-md.mjs header's renumber-is-safe note. Docs: - README "Subroutine composition" section + Mermaid example use paren format; "Reading guide" gains a one-line note explaining why. - README "Versioning notes" v7 entry gains a sub-bullet for #148. - Engine CLAUDE.md narrative on round-trip name accumulation reflects the new format. Out of scope (cross-repo follow-ups, deferred to engine v7 publish): - post-machine-js — its `Path` parser embeds `>` as the engine's wrapper separator (`'foo>10~30'` shape). Needs flip to parens. New v7 issue to be opened, separate from #82 (the rename adoption). - machines-demo — no composition usage; rides peer-dep bump.
Spawn one child Node process per library so each starts from a fresh `State` id counter. Without isolation, the second-imported library's IDs depend on how many states the first-imported one constructed — adding a state to one library would shift every ID label in the other library's `states.md`, creating cross-coupled diff churn. Net effect on this PR's diff: - `packages/library-binary-numbers-bare/states.md` reverts to its v3.0.0 numbering (s1–s6). The "cosmetic ID renumbering" called out in the prior commit's body is undone here — the bare library now has zero net change from master. - `packages/library-binary-numbers/states.md` unchanged (marker library imports as it always did at top of dispatcher; isolation doesn't change its numbering). Single-file structure: argv-gated dispatcher / worker pattern. No top-level library imports — only the worker branch's dynamic import loads a library, and it loads only the one it was asked to render.
paren-based wrapped-state naming `A(B)` (#148)
#139) v7 emit overhaul for `withOverriddenHaltState`-wrapped states. Per design discussion in #138 and the spec at docs/superpowers/specs/2026-05-20-tomermaid-wrapper-emit-design.md. Visual contract: - `subgraph w_${bareId}["halt frame"]` around each wrapper = the wrapper's runtime stack frame for halt handling. Visual-only — no edge mutations. - `[[bare]]` (Mermaid subroutine / double-walled rectangle) = the wrapper-node. Both the runtime entry point AND the source of the dotted `onHalt` edge. - Cloned `(((halt)))` inside the subgraph = where halt-bound transitions land within the wrapper's scope. `haltState` is a runtime singleton; the cloned node is a teaching aid. - Solid edges from `[[bare]]` to cloned halt stay inside the subgraph. Dotted `onHalt` from `[[bare]]` crosses the subgraph border to the override target — exactly one per wrapper. - Real `(((halt)))` outside any subgraph = actual run terminus. Graph data model changes: - `GraphNode` gains `isWrapped: boolean` and `isClonedHalt: boolean`. - `GraphTransition` gains a stable per-edge `id: string` (`${fromNodeId}-${patternIx}`) for downstream rendering (e.g. `machines-demo` #10's edge-highlighting). - `State` gains a private `#bareState` ref populated in `withOverriddenHaltState` so `toGraph` can recover the bare from a wrapper. - Cloned-halt graph nodes use negative ids (one per wrapper); in Mermaid output they emit as `c${absId}` to satisfy Mermaid's id syntax. `fromGraph` maps them back to the singleton `haltState`. Round-trip: - Closes #139: bytewise-stable round-trip for simple wrappers (test added in test/round-trip.spec.ts). The wrapper's composite name no longer appears as a graph node label — only the bare's name does — so `fromGraph` recomputes the composite fresh on reconstruction; no accumulation. - Shared-bare cases (e.g. `minusOne`'s repeated `invertNumber`) use per-context duplication in the graph emit. Reconstruction produces behaviorally-equivalent State instances. Bytewise round-trip for shared-bare cases is a known limitation outside #139's scope. Mermaid emit is sorted deterministically (nodes by id ascending, transitions per-node in stored order) to support the round-trip test and reduce diff churn in `states.md`. Downstream support for `machines-demo` #9 (graph render) / #10 (next- edge highlight) / #37 (click-to-toggle breakpoints): stable per-node ids, `isWrapped` / `isClonedHalt` flags so downstream can style or skip cloned halts, edge ids for SVG targeting. Other updates: - README "Subroutine composition" section rewritten with the new diagram + reader's contract. - README "Versioning notes" v7 entry gains a sub-bullet for this PR. - `states.md` regenerated for `library-binary-numbers`. - Existing pinned-Mermaid-output assertions in graph.spec.ts / introspection.spec.ts / State.spec.ts updated for the new shape.
…ial shape
Adds an explicit pre-execution marker to every `toMermaid` output:
idle([idle])
idle -. enter .-> sN
`idle` is a stadium-shaped sentinel — not a graph node — emitted on every
diagram regardless of whether the initial state is wrapped. The labeled
dotted enter arrow is symmetric with the `onHalt` dotted convention used
by wrapper redirects.
The `((round))` shape on non-wrapped initials is dropped — non-wrapped
initials now render as plain `[name]` square, and the `idle -. enter .->`
arrow is the sole "start here" signal. Avoids the redundant double signal
of the previous (always-emit) version.
`fromMermaid` recovers `initialId` exclusively from the `idle -. enter .->`
arrow. The no-initial error message updates accordingly.
Affects:
- `packages/library-binary-numbers/states.md` regenerated — every algorithm
picks up the `idle([idle])` + enter arrow lines.
- `packages/library-binary-numbers-bare/states.md` regenerated for the same.
- README's Mermaid examples (Quick Start, Reference cycle, Subroutine
composition before/after, name-state) all updated to show the new shape;
notation block at the start of the engine README rewritten.
- graph.spec.ts assertions updated for the new shape.
- Design spec status line updated.
Downstream win: `idle` becomes a stable, predictable anchor for
`machines-demo` rendering. The arrow's `id`-targetable shape lets the
demo highlight "execution is about to start" or visualize entry.
Folds into PR #169 — no breaking-behavior change beyond the emit-format
flip (already in the v7 breaking set).
…te count Cloned-halt nodes (one per wrapper context) are visualization-only sentinels — at runtime they all map to the singleton `haltState`, so they shouldn't inflate the "N states (including haltState)" header. Affects only the count text; the rendered Mermaid graph below the header still shows the cloned halts inside each `halt frame` subgraph (that's their whole purpose — visualizing where halt-bound transitions land within a wrapper's scope). Header counts after the fix (binary-numbers): - deleteNumber: 5 → 4 - invertNumber: 5 → 4 - normalizeNumber: 7 → 6 - minusOne: 20 → 15 - minusOneFast: 10 → 8 (All others unchanged — no wrappers, no cloned halts.)
Replace the Unicode/raw-literal edge-label encoding with a vocabulary that reads cleanly without out-of-band reference: Write commands (right of `→`, left of `/`): - `·` (keep) → `K` - `⌫` (erase) → `E` Literal alphabet symbols (in both read patterns and write positions): - previously bare: `0`, `X`, `$` - now quoted: `'0'`, `'X'`, `'$'` Unquoted markers (unchanged semantics, unchanged emit): - `*` per-cell ifOtherSymbol (any symbol) - `-` the tape's blank symbol Example before/after: - `s1 -- "X → ·/S" --> s0` → `s1 -- "'X' → K/S" --> s0` - `s2 -- "* → ⌫/S" --> s0` → `s2 -- "* → E/S" --> s0` - `s1 -- "1 → 0/R" --> s1` → `s1 -- "'1' → '0'/R" --> s1` - `s1 -- "0,a → 0/R,a/L" --> s2` → `s1 -- "'0','a' → '0'/R,'a'/L" --> s2` Why the change: - `K` / `E` are mnemonic command names (Keep, Erase) that a reader unfamiliar with the diagram can guess from the letter alone. The Unicode `·` / `⌫` look pretty but require a legend to interpret. - Quoting literal alphabet symbols visually distinguishes them from the convention markers `*` / `-` / `K` / `E`. Eliminates ambiguity when an alphabet contains a character that would otherwise collide with a marker (e.g. a literal `K` in the alphabet renders as `'K'`, unambiguous from the keep command `K`). Engine changes: - `decodeWriteSymbol` wraps literal symbols in `'…'`; returns `K` / `E` for the keep/erase commands. - `decodePatternDescription` wraps literal cells in `'…'`; returns `*` / `-` for the markers (no quoting). - `parseWriteSymbolLabel` strips surrounding quotes from literal symbols; recognizes `K` / `E` as keep/erase. - `parsePatternString` strips surrounding quotes from literal cells. - `escapeAlphabetSymbol` simplified — only escapes `\` and `'` now (the other reserved chars are distinguishable via quote presence). Affects: - `packages/library-binary-numbers/states.md` regenerated with the new vocabulary. - `packages/library-binary-numbers-bare/states.md` regenerated. - README "Quick Start" + Subroutine composition Mermaid blocks and their narrative legends updated. - Decoder unit tests + per-state graph fixture tests updated for the new label format. - Spec status line updated. Round-trip stability (#139) preserved — the new encoding is deterministic and reversible.
…⇹ for movements
Two readability tweaks to the edge-label vocabulary:
1. The `ifOtherSymbol` catch-all marker changes from ASCII `*` (U+002A)
to `∗` (U+2217 ASTERISK OPERATOR). A taller, thinner asterisk that
is visually distinct from a quoted-literal `'*'`, so an alphabet
containing literal `*` (rendered as `'*'`) is unambiguous from the
catch-all marker.
2. Movement labels swap their letter codes for directional Unicode
arrows:
- `R` → `→` (U+2192 RIGHTWARDS ARROW)
- `L` → `←` (U+2190 LEFTWARDS ARROW)
- `S` → `⇹` (U+21F9 LEFT RIGHT OPEN-HEADED ARROW)
The arrows read at a glance and reinforce the spatial intuition
of head movement. The right-arrow `→` is the same glyph used by
the read-to-write separator, but the parser keys on the spaced
` → ` (the separator) versus unspaced `→` (the movement) so
parsing stays unambiguous.
Example edge labels:
Before: s1 -- "X → ·/R" --> s0
s1 -- "* → ⌫/S" --> s0
After: s1 -- "'X' → K/→" --> s0
s1 -- "∗ → E/⇹" --> s0
Engine changes:
- `decodePatternDescription` returns `∗` (not `*`) for the
ifOtherSymbol marker.
- `parsePatternString` recognizes `∗` (not `*`) as the marker.
- `decodeMovement` returns `←` / `→` / `⇹` (not `L` / `R` / `S`).
- `parseMovementLabel` recognizes the three arrows.
- `escapeAlphabetSymbol` no longer needs to escape `*` / `,` / `|`
(quoting is what disambiguates literals from markers now); only
`\` and `'` need escaping inside the quotes.
Affects:
- `states.md` regenerated for both binary libraries.
- README "Quick Start" + Subroutine composition Mermaid blocks +
legends updated.
- Decoder / parser unit tests + per-state graph fixture tests
updated for the new vocabulary.
Round-trip stability (#139) preserved.
…, dogfooded `summarize`
Iterative UX refinements on top of the v7 emit. The five highlights:
1. **Edge-label vocabulary refined further.**
- Catch-all marker: `*` (ASCII) → `🞰` (U+1F7B0 HEAVY EIGHT
BALLOON-SPOKED ASTERISK) — distinct from a quoted-literal `'*'`
so an alphabet containing literal `*` stays unambiguous from the
ifOtherSymbol marker.
- Blank-symbol shorthand (read patterns): `-` → `B` — read-side
noun for "the cell is blank"; write-side action stays `E` (Erase).
Verb/noun split mirrors the read/write role.
- Movements stay `L` / `R` / `S` (briefly tried arrows `←` / `→` /
`⇹` and reverted — the letter codes read cleanly without
overloading the read-to-write `→` separator).
2. **Stack-pushing transitions now visually distinct.**
`toMermaid` emits a thick `==>` arrow when a transition's target is
a wrapped state AND target ≠ source (= stack-push happens at
runtime per `TuringMachine.run` line 220's
`if (state !== nextState && nextState.overriddenHaltState) push(...)`).
Self-loops on wrappers stay `-->` because they don't push. The
reader can now scan an execution path counting thick arrows to see
how deep the wrapper stack grows.
3. **Removed 4 hand-drawn pedagogical Mermaid blocks** from root and
engine READMEs (Quick Start replaceB ×2, Reference cycle, Subroutine
composition). The v7 engine emit is clear enough to stand on its
own; the hand-drawn versions used different vocabulary and shapes
and forced the reader to context-switch between two visual styles.
Promoted the engine `toMermaid()` outputs out of `<details>` to
primary illustration. Root README's stale v6 emit also updated to
v7 vocabulary.
4. **`summarize().stateCount` filters cloned-halt sentinels** —
matches the per-algorithm header in `library-binary-numbers/states.md`
by construction. All three sources (source-comment counts, test
fixture `expectedNodeCount`, states.md header) now agree.
`build-states-md.mjs` dogfoods `summarizeGraph()` for its richer
stats line (`N states; N transitions; N wrappers (max nesting
depth N); has cycles`), replacing the manually-computed count.
5. **Docs realigned with what the emit actually does.**
- README and spec drop the overstated "only dotted crosses the
subgraph border" invariant — true for simple wrappers, false for
compositions like `minusOne` whose bare's transitions reach
helpers outside the halt frame. Solid arrows can cross; only the
dotted `onHalt` carries wrapper-machinery meaning.
- Engine CLAUDE.md's `toMermaid` description rewritten for v7
reality (subgraph + `[[bare]]` + cloned halt + idle sentinel +
edge-label vocabulary + thick-arrow convention + per-context
duplication for shared-bare).
- Mermaid links added (mermaid.js.org syntax docs + mermaid-js repo).
- `library-binary-numbers/src/index.ts` source-comment fixes:
minusOne "17 nodes" → "15 nodes (per `summarize().stateCount`)",
"four-deep subroutine chain" → "three-deep" (3 wrapper hops + 1
terminal target, not 4 wrappers). Per-algorithm counts updated
where v7 emit changed them (deleteNumber/invertNumber 5→4,
normalizeNumber 7→6, minusOneFast 10→8).
`equivalentOn` verified unaffected — pure-runtime checker, doesn't
touch the visualization or introspection surface.
All 420 tests still pass.
…lt-marker rename Iterative UX refinements on top of the v7 emit, all in this branch since the last commit. Several themes: **Bracketed-tape-block edge labels** — `[reads] → [writes]/[moves]` where each role wraps in `[…]`, always (even single-tape). Brackets are the "tape-block" indicator. Cell content inside the brackets: literal-quoted (`'X'`), `🞰` (U+1F7B0 ifOtherSymbol catch-all), `B` (tape's blank shorthand), `K`/`E` (keep/erase write commands), `L`/`R`/`S` (movements). Alternation is per-pattern bracket: `['^']|['1']|['0']` for single-tape, `['0','a']|['1','b']` for multi-tape. Compact `['^'|'1']` form rejected by `fromMermaid` (two new strict-rejection tests added) to prevent the cross-product trap in multi-tape (`['0'|'1','a'|'b']` would read as 4 combinations rather than 2 paired alternatives). **Thick `==>` arrows for stack-pushing transitions** — when a transition's target is a wrapped state AND target ≠ source (i.e. the engine's runtime push fires per `TuringMachine.run` line ~220). Self-loops on wrappers don't push, stay regular `-->`. Reader can scan an execution path counting thick arrows to see worst-case wrapper-stack growth. **"halt marker" replaces "cloned halt"** — the per-wrapper halt sentinel inside each `subgraph w_N["halt frame"]` was previously called a "clone", which misleadingly implied a real second copy at runtime. It's a visualization-only marker that maps back to the singleton `haltState` in `fromGraph`. Renamed `isClonedHalt` → `isHaltMarker` on `GraphNode` (public-API type) to align the implementation with the prose. **Multi-tape example added** — a 2-tape "copier" machine in §Diagram conventions illustrates the bracket-format with N=2 tapes. Previously the engine had no documented multi-tape example. **Restructured README sections**: - Quick Start trimmed back to: code + diagram + one-paragraph diagram-specific read-out. No legend dump. - New §Diagram conventions section near the end (right before §Versioning notes) holds the full reference: node shapes table, edge styles table, edge label format + cell vocabulary table, alternation rule + cross-product trap explanation, multi-tape example. Reader hits it as a reference once familiar with the library's concepts. - 4 hand-drawn pedagogical Mermaid blocks removed from root + engine READMEs; engine `toMermaid()` output is now the primary illustration. Vocabulary mismatch between hand-drawn and engine diagrams is gone. **Engine `CLAUDE.md`** rewritten to reflect v7 emit reality: subgraph + `[[bare]]` + halt-marker + idle sentinel + edge-label vocabulary + thick-arrow convention + per-context duplication for shared-bare cases. `summarize().stateCount` filters halt-markers (consistent with `states.md` header by construction). Added link to Mermaid syntax docs + repo. **Source-comment cleanups in `library-binary-numbers/src/index.ts`**: - minusOne "17 nodes" → "15 nodes (per `summarize().stateCount`)"; "four-deep subroutine chain" → "three-deep" (3 wrapper hops + 1 terminal target — the previous "four-deep" was counting state names in the chain, not wrapper levels). - Per-algorithm node counts updated where v7 emit changed them (deleteNumber/invertNumber 5→4, normalizeNumber 7→6, minusOneFast 10→8). **`build-states-md.mjs` dogfoods `summarizeGraph`** for the per- algorithm header line: `N states; N transitions; N wrappers (max nesting depth N); has cycles`. No more manual counting in the script. **Pre-existing anchor fix** — `### Throttle pattern (v6.4.0+)` heading → `### Throttle pattern`. Two cross-links elsewhere in the README targeted `#throttle-pattern` and didn't resolve; dropping the version suffix from the heading makes them resolve. All 422 tests pass (420 prior + 2 new strict-rejection regression tests for the compact alternation form). `equivalentOn` verified unaffected — pure-runtime checker, doesn't touch any of the visualization/introspection surface.
U+1F7B0 didn't render in GitHub Mermaid (tofu) or most monospace fonts. Switched to ASCII `*` — a literal `*` in the alphabet stays unambiguous because it's quoted (`'*'`), parallel to literal `'B'` vs the `B` blank-marker. Also renamed internal `clonedHalt*` identifiers to `haltMarker*` for consistency with the public `GraphNode.isHaltMarker` flag and the docs terminology — both `State.toGraph` and `toMermaid`/`fromMermaid` now match. Prose mentions of "cloned halt" in the README and design spec updated in lockstep. Regenerated `library-binary-numbers/states.md` (27 sites).
First v7 pre-release. Consolidates the composition-representation overhaul landed across #149 / #148 / #138 / #139 on the v7 branch. Publishes to npm under the `next` dist-tag. - Bump all 4 packages 6.4.0 → 7.0.0-alpha.1 (lerna lockstep) - Widen peer dep `@turing-machine-js/machine` on the three dependent packages: `^6.0.0` → `^7.0.0-alpha.1` - CHANGELOG entry for [7.0.0-alpha.1] with full migration walkthrough - README versioning notes: v7 (in progress) → v7 (alpha 1, 2026-05-21) with install snippet and #102 status callout Outstanding for stable v7.0.0: #102 (debugger step-in/over/out). v7-branch merges don't auto-close — `closes` keywords carried here fire on the eventual v7 → master release PR, not on this merge. closes #138 closes #139 closes #148 closes #149
chore(release): 7.0.0-alpha.1
Three new tests in the existing `fromMermaid error paths` block: - Read label with no bracketed list (graphFormats.ts:379) - Write/move cell-count mismatch (graphFormats.ts:396) - Backslash-escape branch in `stripBrackets` (graphFormats.ts:355-356) Coverage: statements 98.29 → 98.72 (+0.43), branches 95.35 → 96.13 (+0.78), graphFormats.ts 96.42/90.62 → 98.8/94.79. Addresses the Coveralls "coverage fell" status on PR #167 (v7 → master draft) after v7-alpha.1 work landed. Remaining uncovered branches in v7 code are defensive fallbacks (graph.ts:173/209, State.ts:463-464, introspection.ts:121) — not worth synthetic tests.
4 tasks
test(graphFormats): cover fromMermaid parser error paths
Three more tests for the v7 lines #171 didn't reach: - `State.toGraph` unbound-Reference catch in WRAPPER context (State.ts:463-464) — the existing test covered the non-wrapper branch; this one wraps the unbound-Ref state via `withOverriddenHaltState(...)` to hit the other try/catch - `parseWriteSymbolLabel` fallback return (graph.ts:209) — defensive return-as-is for unrecognized labels - `parsePatternString` blank-marker `?? cell` fallback (graph.ts:164) — defensive return when `alphabets[tapeIx]` is missing Plus the parser-fallback test in `parsePatternString` describe block (graph.ts:173) was already added in #171's first round; this PR finishes the symmetric `parseWriteSymbolLabel` describe block. Coverage: statements 98.72 → 99.14 (+0.42), branches 96.13 → 96.71 (+0.58), lines 98.32 → 99.21 (+0.89), State.ts 99.11 → 100% statements. Should clear Coveralls' remaining -0.5% drop on PR #167 by pulling v7 total above master. Remaining uncovered (not worth synthetic tests): - graphFormats.ts:344 — defensive throw inside `stripBrackets` closure, unreachable via fromMermaid's slice logic - introspection.ts:121 — `if (node)` guard in cycle detection
4 tasks
test(coverage): cover remaining reachable v7 branches
Draft design spec at docs/superpowers/specs/2026-05-21-halt-frame-transitive-closure.md. Captures the reframing of `withOverriddenHaltState` visualization as a function-call model: wrapper = call site, callable subtree = body, halt = return. Replaces v7 alpha.1's halt-frame subgraph emit (closes the design phase of #174). Key decisions documented: - Bold `==>` reserved for wrapper-to-bare `call` arrows only. - Dotted `-.->` reserved for frame-level dispatch (return, halt, enter). - Wrappers and bares as separate GraphNodes (un-collapsing alpha.1's collapse). Each unique bare gets ONE subtree; multiple wrappers share via `bareStateId`. - Union-find merges subtrees only when bare reach sets overlap (rare in practice; minusOne probe confirms 3 components instead of alpha.1's 5 subgraphs). - Halt marker `c_X` always emitted; orphan signals dead wrapper. - `onHalt` keyword retired; wrapper outgoing is a regular solid `-->`. - `return`/`halt` arrows derived at emit time, not stored as Graph fields. Includes 7 worked examples with Mermaid diagrams: - simple wrapper, PostMachine subroutine, multi-wrapper sharing bare, self-wrapping `A.wohs(A)`, nested `.wohs()` chain (4a/4b), Reference cycle dead wrapper. Plus 6 edge case discussions and the minusOne worked example showing the new emit shape vs alpha.1's per-context duplication. Implementation pending (tasks #30 revert P1, #31 reimplement per the spec). Related issues filed: #175 memoization, #176 nested-wohs name simplification, post-machine-js#85 subroutine hopper evaluation.
…ure-174 docs(spec): callable-subtree visualization design for #174
`A.wohs(t1).wohs(t2)` now produces a wrapper with name `A(t2)` and bare = A (not "A(t1)(t2)" with bare = W1). The chain's inner overrides are dead at runtime — only the outermost `.wohs()`'s override is pushed onto the halt-stack on entry (verified empirically by probe). The composite name now reflects runtime behavior, not construction history. Implementation is a single-line change: `withOverriddenHaltState` unwraps `this` to its bare before composing the new wrapper. Tests: - New: `A.wohs(t1).wohs(t2).name === 'A(t2)'`, structural equivalence to `A.wohs(t2)` (same name + same override target). - New: 3-deep chain `A.wohs(t1).wohs(t2).wohs(t3) → 'A(t3)'`. - Updated: the existing "paren-naming distinguishes nestings" test now exercises only override-side wrapping (`outer1 = A.wohs(B.wohs(A))`), since `this`-side chain construction collapses under the new rule. Composes with #175 (memoization): with both shipped, `A.wohs(t1).wohs(t2) === A.wohs(t2)` (literal reference equality via cache lookup). states.md for library-binary-numbers libraries unchanged — those compositions use independent wrappers, not chained `.wohs()` calls. closes #176
fix(State): nested `.wohs()` collapses inner overrides (#176)
Cache `(bare, override) → wrapper` so identical arguments return the same JS instance. `A.wohs(t) === A.wohs(t)` — referential equality holds for repeated wrap calls. Composes with #176 (chained-`wohs()` collapse): - #176 unwraps `this` to its bare on each `.wohs()` call. - This cache then keys by `(bare, override)`. - Result: `A.wohs(t1).wohs(t2) === A.wohs(t2)` — chained construction hits the same cache slot as direct construction. Implementation: - Static private cache `State.#wrapperCache: WeakMap<bare, WeakMap<override, WeakRef<wrapper>>>`. - Two-level WeakMap so the outer entry is GC'd when the bare is collected; WeakRef values let wrappers themselves be GC'd when nothing else references them, with subsequent calls reconstructing fresh wrappers. - Cache lookup happens BEFORE constructing a new wrapper; cache miss falls through to the existing construction path. Tooling: - Bumped `lib` to `ES2021` (was implied `ES2020`) for `WeakRef`. Target stays `ES2020` — emit is unchanged. Tests: - `A.wohs(t) === A.wohs(t)` (same instance, same id). - `A.wohs(t1).wohs(t2) === A.wohs(t2)` (composes with #176). - Different override → different instance (cache key sanity). - Different bare → different instance (cache key sanity). Full suite: 437/437 (was 433 + 4 new). Coverage above floors. closes #175
feat(State): memoize withOverriddenHaltState (#175)
The scanToX example bullet and the toMermaid node-shape legend both claimed wrappers were drawn "outside any subgraph" — true before the #223 fix, but framed wrappers (continuation chain participates in a caller's frame) now render INSIDE the owner frame's subgraph using the same [[…]] shape. Soften both statements with a pointer to #223.
…-continuations fix(machine): toGraph reach-set tunnels through wrappers to their continuation (#223)
chore(release): 7.0.0-alpha.7
…er (7.0.0-alpha.7.1)
DebugSession is the engine's interactive-playback class; SnippetPlayer is the analogous driver for prerecorded runs. The codebase convention is stateful drivers are classes (State, TapeBlock, TuringMachine, CallFrame, PostMachine, DebugSession), pure transforms are functions. Use 'new SnippetPlayer(snippet)' instead of a factory.
…Snapshot Equivalent of the engine's Tape.viewport getter, but for wire-data TapeSnapshot. Pads with the caller-supplied blank symbol so the result always has exactly width entries. Frame stays lossless data; the helper windows on demand so consumers can pick their viewport width per render context.
…e / buttons explicitly Previous example had render(ops, renderTape) magically receiving args that were never defined in the example scope. Replaced with applyFrame() that closes over explicit declare-typed stubs for ops, renderTape, and the illustrative buttons — caller sites become render-free.
…down
Replace .onclick assignments with addEventListener({signal}); scope the
setInterval cleanup through the same signal. One controller.abort() in
the component's teardown tears down both the auto-play timer and the
click listeners.
…ayer feat(visuals): createSnippetPlayer — pure-state playback driver (7.0.0-alpha.7.1)
…e (7.0.0-alpha.8, #227) TapeSnapshot is a wire-data shape derived from Tape — its logical home is next to the live Tape class in the engine package. Visuals re-exports both type and helper, so consumers importing from @turing-machine-js/visuals are unaffected. Engine's Tape.viewport getter is unchanged — it operates on the live int-encoded tape via Alphabet.get (different data shape from the wire-data TapeSnapshot). The two surfaces are not direct duplicates; the move is about locality, not internal dedup. Closes #227.
…ort via internal core tapeViewportFromAccess(position, width, cellAt) — internal helper for the centering loop. Public tapeViewport(snapshot, width, blank) is a thin wrapper passing a snapshot-bounds-checking cellAt; Tape.viewport calls the core directly with its own cellAt that does Alphabet.get(#cellAt(i)). The two surfaces serve different inputs (wire-data TapeSnapshot vs live int-encoded Tape) but the centering math now lives once. Public APIs unchanged. Tape.viewport's caching (#viewportDirty, #viewportBuffer) is preserved.
feat(machine): lift TapeSnapshot + tapeViewport to engine (7.0.0-alpha.8, #227)
… TapeSnapshot home) Engine README: - Add TapeSnapshot type + tapeViewport helper to the Tape section - Versioning notes section catches up alpha.5/6/7/8 (was at alpha.4); v7 milestone marked feature-complete with #102 Engine CLAUDE.md: - visuals package bullet: TapeSnapshot listed as re-exported from engine (not native); version mentions updated to 7.0.0-alpha.8 Visuals README: - TapeSnapshot type def line annotated 're-exported from @turing-machine-js/machine (alpha.8+); canonical home is engine'
docs: catch up engine + visuals docs to v7.0.0-alpha.8 (#227 closure, TapeSnapshot home)
…ounts Refine the round-trip stability claim across CLAUDE.md, the engine README, and test/round-trip.spec.ts. Previous wording said "stable for all wrapped states including shared-bare cases" — that overstated the guarantee. The accurate model: - Simple wrappers (scanToX(eraseHere)-style): bytewise stable across toMermaid → fromMermaid → toGraph → toMermaid. The #139 regression test pins this. - Shared-bare cases (library-binary-numbers/minusOne where invertNumber backs both the outermost bare and wrapper-W1's bare): emit a SINGLE de-duped node with &-joined call arrows; sharing survives the round-trip. But GraphNode ids are runtime State ids reassigned on every fromGraph rebuild, and a shared bare's post-rebuild id no longer follows the original emission order, so the byte stream reorders. Verify behavioural identity with equivalentOn → allAgree:true instead. Also syncs in-source state-count comments for minusOne (15 → 18) and minusOneFast (8 → 10) — the graphs.spec.ts test fixture already pins these counts via summarizeGraph(graph).stateCount, so the comments were lagging behind the source of truth.
docs: round-trip byte-stability scope + minusOne/minusOneFast state counts
devDep bumps (all patch/minor): - vitest + @vitest/coverage-v8 4.1.5 → 4.1.8 - rollup 4.60.3 → 4.61.0 - eslint 10.3.0 → 10.4.1 - typescript-eslint 8.59.2 → 8.60.1 - @types/node 25.6.2 → 25.9.1 `npm audit fix` clears 6 dev-only vulns (3 moderate / 3 high) under nx/lerna toolchain — no production-runtime exposure. Verified locally: build, test (664/664), lint, typecheck, coverage baseline (99/94/100/99) all green. Preparation for the v7.0.0 stable cut on PR #167.
7 tasks
chore(deps): bump root devDeps + npm audit fix for v7.0.0 cut
Stable v7 release. Lockstep bump of all 5 packages from 7.0.0-alpha.8 to 7.0.0. CHANGELOG entry summarizes the cumulative v7 trajectory across alphas 1-8 (debugger reshape, CallFrame, callable-subtree mermaid emit, state tags, TapeSnapshot to engine, visuals companion package). Closes the v7 milestone — composition-representation overhaul.
release: v7.0.0
The visuals package was created in 7.0.0-alpha.6 without a LICENSE file and shipped without one through alpha.8. Stable v7.0.0 fixes this by copying the MIT LICENSE that the other 4 sibling packages already ship (byte-identical to packages/machine/LICENSE). Tarball before: 39.5 kB packed / 130.9 kB unpacked, 16 files. Tarball after: 51.4 kB packed / 166.0 kB unpacked, 17 files (+LICENSE).
3 tasks
chore(visuals): add LICENSE for stable v7.0.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose of opening this PR early
v7 scope
Scope-based release. Ships when every box below is checked.
Original scope (opened with this PR)
withOverrodeHaltState→withOverriddenHaltState(#166, shipped in v7.0.0-alpha.1)A(B)instead ofA>B) (shipped in v7.0.0-alpha.1)toMermaidemit for wrapped states (shipped in v7.0.0-alpha.1 as collapsed-bare; refined to function-call model in #174)toMermaidround-trip regression test (shipped in v7.0.0-alpha.1)Added to scope during the v7 alpha cycle
Composition / graph emit:
toMermaidemit: separate wrapper/bare nodes, union-find frames,==> call/-. return .->/-. halt .->arrowswithOverriddenHaltStatememoization (same(bare, override)pair returns the same wrapper instance).wohs()chain collapsestate.tag(...),GraphNode.tags,<br>-embedded labels +classDef/classlines intoMermaid) — shipped in v7.0.0-alpha.3Graph utilities + internals:
State.toGraph/State.fromGraphtoutilities/stateGraph.ts;STATE_INTERNALSymbol-keyed package-private accessor — shipped in v7.0.0-alpha.4State.collectStates(state, tapeBlock)→ id-keyedMap<number, {state, transitionSymbols}>— shipped in v7.0.0-alpha.4Per-iter matched transition + halt-BP redesign (v7.0.0-alpha.5):
MachineState.matchedTransition+.-separatorGraphTransition.id(was-)haltState.debugcollapsed toboolean; halt-imminent pause moved to AFTER side of the halt-triggering iterhaltState.debug.beforesilently no-ops in non-strict modeFixes:
toMermaidHTML-entity-escapes user content in labels (fixes parse errors on alphabets containing",<,>)runStepByStephalt-stack scoped to the call rather than theTuringMachineinstance (fixes memory leak / ghost iteration on machine reuse)patternKindsdefensive paths + drop dead typeof check (unblocks Coveralls on this PR)Release-PR bookkeeping (when this PR is marked ready)
To be done in a final dressing commit before flipping out of draft:
closes #Nkeywords for every scope item. v7-branch merges don't auto-close v7-milestone issues — that only fires on master merges. Addcloses #149, #148, #138, #139, #102, #174, #175, #176, #186, #180, #195, #205, #207, #194, #196(and any newly added v7 issues) to the PR description so they auto-close on this PR's merge to master.**v7** *(in progress)*→**v7**and add the release date in the prose. Audit that every scope item has its own sub-bullet under the v7 entry.[7.0.0] - YYYY-MM-DDentry (individual v7 PRs defer CHANGELOG per repo convention; this is its first home).npx lerna version 7.0.0 --no-git-tag-version --no-push --yesin a release-bump commit.npx lerna publish from-package --yesafter merge.gh release create v7.0.0 --target master --title v7.0.0 --notes-file <path>(stable, not prerelease).Coordination
v7integration branch via standalone PRs (each with--base v7). Each PR also defers its own CHANGELOG entry to this release PR.mellonis/post-machine-js) v7 work happens AFTER engine v7 publishes. Tracked in post-machine-js#82 and post-machine-js#72.Closes (auto-close on merge to master)
Closes #149, #148, #138, #139, #102, #174, #175, #176, #186, #180, #195, #205, #207, #194, #196, #213, #223, #227
(v7-branch merges did NOT auto-close these issues — the close keywords need to be on the merge-to-master PR.)