Skip to content

v7 release: composition-representation overhaul#167

Merged
mellonis merged 168 commits into
masterfrom
v7
Jun 3, 2026
Merged

v7 release: composition-representation overhaul#167
mellonis merged 168 commits into
masterfrom
v7

Conversation

@mellonis

@mellonis mellonis commented May 20, 2026

Copy link
Copy Markdown
Owner

DRAFT — do not review, do not merge. Open early for CI + mergeability watch only. The actual release PR will branch off this work once the v7 scope closes.

Purpose of opening this PR early

  • Mergeability watch — surfaces conflicts the moment master moves (e.g., when a v6.5 patch lands and touches code v7 is also refactoring).
  • Integration CI — runs the test/lint/build suite against the merged tree (v7 + master), not just v7 in isolation. PRs landing on v7 already get CI against the v7 base; what this PR adds is the cross-branch picture.
  • Branch-protection / required-check sanity — if master's required-check set drifts away from what v7 produces, we'd rather find out now than on release day.

v7 scope

Scope-based release. Ships when every box below is checked.

Original scope (opened with this PR)

  • #149 — rename withOverrodeHaltStatewithOverriddenHaltState (#166, shipped in v7.0.0-alpha.1)
  • #148 — paren-based wrapped-state naming (A(B) instead of A>B) (shipped in v7.0.0-alpha.1)
  • #138 — cleaner toMermaid emit for wrapped states (shipped in v7.0.0-alpha.1 as collapsed-bare; refined to function-call model in #174)
  • #139 — bytewise toMermaid round-trip regression test (shipped in v7.0.0-alpha.1)
  • #102 — debugger step-in / step-out / step-over

Added to scope during the v7 alpha cycle

Composition / graph emit:

  • #174 — callable-subtree toMermaid emit: separate wrapper/bare nodes, union-find frames, ==> call / -. return .-> / -. halt .-> arrows
  • #175withOverriddenHaltState memoization (same (bare, override) pair returns the same wrapper instance)
  • #176 — nested .wohs() chain collapse
  • #186 — first-class State tags (state.tag(...), GraphNode.tags, <br>-embedded labels + classDef/class lines in toMermaid) — shipped in v7.0.0-alpha.3

Graph utilities + internals:

  • #180 — extract State.toGraph / State.fromGraph to utilities/stateGraph.ts; STATE_INTERNAL Symbol-keyed package-private accessor — shipped in v7.0.0-alpha.4
  • #195State.collectStates(state, tapeBlock) → id-keyed Map<number, {state, transitionSymbols}> — shipped in v7.0.0-alpha.4

Per-iter matched transition + halt-BP redesign (v7.0.0-alpha.5):

  • #205MachineState.matchedTransition + .-separator GraphTransition.id (was -)
  • #207haltState.debug collapsed to boolean; halt-imminent pause moved to AFTER side of the halt-triggering iter
  • #209 — docs: chained-form haltState.debug.before silently no-ops in non-strict mode

Fixes:

  • #194toMermaid HTML-entity-escapes user content in labels (fixes parse errors on alphabets containing ", <, >)
  • #196runStepByStep halt-stack scoped to the call rather than the TuringMachine instance (fixes memory leak / ghost iteration on machine reuse)
  • #210 — coverage: patternKinds defensive 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:

  1. Carry closes #N keywords for every scope item. v7-branch merges don't auto-close v7-milestone issues — that only fires on master merges. Add closes #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.
  2. README Versioning notes — turn **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.
  3. CHANGELOG.md — add the [7.0.0] - YYYY-MM-DD entry (individual v7 PRs defer CHANGELOG per repo convention; this is its first home).
  4. lerna version bumpnpx lerna version 7.0.0 --no-git-tag-version --no-push --yes in a release-bump commit.
  5. Publishnpx lerna publish from-package --yes after merge.
  6. GH releasegh release create v7.0.0 --target master --title v7.0.0 --notes-file <path> (stable, not prerelease).
  7. Trigger post-machine-js v7 work — engine v7 must exist on npm before post-machine-js can widen its peer dep. Surfaces post-machine-js#82.

Coordination

  • v7 work lands on the v7 integration branch via standalone PRs (each with --base v7). Each PR also defers its own CHANGELOG entry to this release PR.
  • Cross-repo (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.)

mellonis added 2 commits May 20, 2026 15:25
…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)
mellonis added 15 commits May 20, 2026 16:21
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).
cleaner `toMermaid` emit for wrapped states (#138, #139)
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
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.
mellonis added 2 commits May 21, 2026 00:29
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
mellonis added 7 commits May 21, 2026 00:53
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)
mellonis added 18 commits May 30, 2026 20:50
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)
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.
mellonis added 2 commits June 3, 2026 06:11
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.
@mellonis mellonis mentioned this pull request Jun 3, 2026
6 tasks
mellonis added 2 commits June 3, 2026 06:34
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).
chore(visuals): add LICENSE for stable v7.0.0
@mellonis mellonis marked this pull request as ready for review June 3, 2026 03:43
@mellonis mellonis merged commit 5a34afa into master Jun 3, 2026
3 checks passed
@github-project-automation github-project-automation Bot moved this from Ready to Done in @mellonis's machines Jun 3, 2026
@mellonis mellonis deleted the v7 branch June 3, 2026 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

v7: rename withOverrodeHaltStatewithOverriddenHaltState

2 participants