diff --git a/CLAUDE.md b/CLAUDE.md index 464b256..be30f42 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,12 +16,23 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Architecture -This is an npm-workspaces + Lerna monorepo (`packages/*`, single shared version managed by `lerna.json`). Four published packages: +This is an npm-workspaces + Lerna monorepo (`packages/*`). Versioning is **lockstep across the four engine/library packages** via `lerna.json` (engine + builder + library-binary-numbers + library-binary-numbers-bare share one version); **`visuals` versions independently** — additive consumer-package patches don't require coordinated peer-dep widening (see Versioning convention below). Five published packages: - **`@turing-machine-js/machine`** — the core engine (no runtime deps). - **`@turing-machine-js/builder`** — declarative state-table → machine builder; depends on `machine`. Soft-deprecated; see its README. - **`@turing-machine-js/library-binary-numbers`** — prebuilt states for `^…$`-delimited binary arithmetic on a 5-symbol alphabet; depends on `machine`. - **`@turing-machine-js/library-binary-numbers-bare`** — same operations on a 3-symbol alphabet (no markers, single number per tape, much smaller state graphs); depends on `machine`. Side-by-side with the marker-based library to make the alphabet-vs-graph-size trade-off visible. +- **`@turing-machine-js/visuals`** (v7-only, [#204](https://github.com/mellonis/turing-machine-js/issues/204)) — pure highlight + graph-indexing logic for the engine `Graph`. Peer dep on `machine`, no runtime deps. Exposes `applyHighlight` / `applyIndicator` / `indexGraph` / `bareIdOf` / `highlightExpand` / `equivalentIds` / `recordingOps` (renderer-agnostic ops contract via `HighlightOps` / `IndicatorOps`), the `recordSnippet` artifact recorder + `SnippetPlayer` class (`Snippet` / `Frame` / `StepCommand`), and the engine-edge-label formatter primitives (`formatStepNotation` / `tokenizeStep` / `formatTape`, shipped in alpha.6.1). **`TapeSnapshot` + `tapeViewport` are re-exported from `@turing-machine-js/machine`** ([#227](https://github.com/mellonis/turing-machine-js/issues/227), v7.0.0-alpha.8 onward) — they live next to the `Tape` class in the engine; visuals re-exports for consumer-import stability. 16-rule contract for `applyHighlight` lives at `packages/visuals/docs/graph-highlight-and-breakpoints.md` (moved there from machines-demo). Used by machines-demo for graph highlight + step-log rendering; designed to be reusable by article embeds and any future Graph consumer that wants byte-identical edge-label notation to `toMermaid`. **Engine + libraries are at `7.0.0-alpha.8`; visuals at `7.0.0-alpha.8`** (re-aligned to lockstep after alpha.7.1 visuals-only patch). + +### Versioning convention + +Engine + builder + libraries bump in lockstep (`lerna version X.Y.Z` from the repo root edits all four `package.json`s + `lerna.json`). This is the right shape when an engine API breaks and peer-dep ranges in dependents need to widen together — that's the whole reason a single coordinated bump exists. + +**Visuals breaks lockstep deliberately for additive consumer-package patches** (e.g., the `alpha.6.1` formatter-primitives bump). Bumping engine + libraries to alpha.6.1 with no functional changes would create ghost releases (identical tarballs republished under a new version). Visuals's peer `@turing-machine-js/machine: ^7.0.0-alpha.6` accepts alpha.6.1+ via semver-prerelease caret semantics, so consumers keep matching peer-dep versions without a coordinated upgrade. For the actual publish: + +- `lerna publish from-package --dist-tag next` walks `package.json` versions and publishes whatever's not yet on the registry. After bumping visuals alone to `7.0.0-alpha.6.1` and leaving the other 4 packages at alpha.6, only visuals publishes; the engine/builder/lib tarballs at alpha.6 are already on the registry and `from-package` skips them. +- `lerna.json`'s `version` field can stay at the prior lockstep version (`7.0.0-alpha.6` after a visuals-only bump) — it's informational when not running in `independent` mode, and `from-package` doesn't read it for publish decisions. +- When the next engine alpha needs to ship, bump back to lockstep (engine + builder + libs + visuals all to alpha.7 via `lerna version`). ### Documentation parity for the two binary libraries @@ -39,7 +50,7 @@ Key shapes that take reading multiple files to grasp: - **`State` is keyed by `symbol` (the JS primitive), not by string.** A state definition is `{ [tapeBlock.symbol([...])]: { command, nextState } }`. `tapeBlock.symbol(...)` interns a *pattern* over all tapes in the block and returns a unique JS `Symbol`. `State.getSymbol(tapeBlock)` then matches the current head against those interned patterns; if nothing matches, the special `ifOtherSymbol` key is used as a fallback. This is why everything goes through `tapeBlock.symbol(...)` — it's both a symbol factory and a multi-tape pattern compiler. See `TapeBlock.#getSymbolForPatternList` and `State.getSymbol`. -- **`State.withOverrodeHaltState(next)` is a composition primitive.** It returns a copy of the state whose `haltState` transition is replaced by a *continuation* pushed onto `TuringMachine`'s internal stack. When the machine would halt inside that subgraph, it instead pops and resumes. `library-binary-numbers/src/index.ts` uses this heavily (e.g. `minusOne` chains `invertNumber → plusOne → invertNumber → normalizeNumber`). When you see `.withOverrodeHaltState(...)`, read it as "subroutine call, then continue with the argument." +- **`State.withOverriddenHaltState(next)` is a composition primitive.** It returns a **`CallFrame`** — a `State` subclass (so `instanceof State` holds; it flows anywhere a `State` does) that carries its own `bare` (the wrapped state) and `override` (`next`), *delegating* transition lookups and `debug` to the bare rather than aliasing the bare's private `#symbolToDataMap`/`#debugRef` (the v6 mechanism; reworked in [#213](https://github.com/mellonis/turing-machine-js/issues/213)). At run time the machine pushes the override onto `TuringMachine`'s internal stack; when it would halt inside that subgraph, it pops and resumes. `instanceof CallFrame` is the wrapper discriminator. `library-binary-numbers/src/index.ts` uses this heavily (e.g. `minusOne` chains `invertNumber → plusOne → invertNumber → normalizeNumber`). When you see `.withOverriddenHaltState(...)`, read it as "subroutine call, then continue with the argument." - **`Reference`** is a forward-declaration handle. `new Reference()` then `.bind(state)` later — this lets you build cyclic state graphs where a state's `nextState` is itself or a not-yet-constructed peer. The `builder` package relies on this to wire arbitrary state-name graphs in a single declarative pass; user code rarely needs `Reference` directly. @@ -47,10 +58,10 @@ Key shapes that take reading multiple files to grasp: - **`TapeBlock` has a `Lock`** that `TuringMachine.run` grabs for the duration of a run, asserting the block isn't being mutated by another machine. Calls to `applyCommand` from outside a run must pass the matching capture symbol. -- **`state.debug` (v4+)** — runtime-mutable breakpoint cell with `{ before, after }` symbol filtering. Shared across `withOverrodeHaltState` wrappers via a private `Ref` so an assignment on the original is visible from every wrapper instance — useful when the same primitive is reused in composition chains. Pauses dispatch via the optional `onPause` hook on `run()` (awaited; without the hook, breaks fire-and-resume invisibly). `haltState.debug.before = true` pauses on every halt entry (program exit + subroutine pop). See `packages/machine/README.md` "Debugging breakpoints (v4+)" for the full API. +- **`state.debug` (v4+)** — runtime-mutable breakpoint cell with `{ before, after }` symbol filtering. Shared across `withOverriddenHaltState` wrappers via a private `Ref` so an assignment on the original is visible from every wrapper instance — useful when the same primitive is reused in composition chains. **As of v7 #102, breakpoint *detection* and *reaction* are split**: `runStepByStep` is the pure-iteration primitive (evaluates no filters, stamps no pause field); `run()` is sync + callback-free; an opt-in **`DebugSession`** (`new DebugSession(machine, {initialState})`) is the only thing that evaluates `state.debug` and turns a match into an awaited `pause` event. The pause descriptor is `m.pause: { side: 'before' | 'after', cause: 'breakpoint' | 'step' | 'manual' }` (replaced the per-yield `m.debugBreak: { before?, after? }`). **As of v7 #207, `haltState.debug` is a `boolean`** (not `DebugConfig`) — halt is terminal with one meaningful pause moment (post-triggering-iter, before halt processing). `haltState.debug = true` pauses on every halt entry (program exit + subroutine pop); the pause fires on the AFTER side of the iter whose transition leads to halt (`m.state` is the triggering state, `m.pause.side === 'after'`). Any object-shaped write (`{ before: true }`, `{ after: true }`, etc.) throws at write time. The `HaltState` typed alias on the singleton export narrows `debug` to `boolean` for compile-time safety at the canonical access path. **Chained-form `haltState.debug.before = true` does NOT throw in non-strict mode** — it's a primitive-boolean property assignment, silent in non-strict, `TypeError` in strict; the engine never sees the write so can't intercept. Always use whole-object form. See `packages/machine/README.md` "Debugging" for the full API. Cross-version notes: - - **v5**: hook renamed `onDebugBreak` → `onPause` (#110). `haltState.debug.after = true` (or `{ before, after }` together) now throws at write-time — halt is terminal, no iteration-after-halt to anchor on (#108 part 2). Halting iter's after-fire stopped being silently lost (#108 part 1). New `run({ debug: boolean })` master switch suppresses all `onPause` dispatches without editing `state.debug` assignments (#106). + - **v5**: hook renamed `onDebugBreak` → `onPause` (#110). `haltState.debug.after = true` (or `{ before, after }` together) now throws at write-time — halt is terminal, no iteration-after-halt to anchor on (#108 part 2). *Superseded in v7 by [#207](https://github.com/mellonis/turing-machine-js/issues/207): `haltState.debug` is now a boolean and ALL object writes throw — the per-side shape never modeled anything for halt.* Halting iter's after-fire stopped being silently lost (#108 part 1). New `run({ debug: boolean })` master switch suppresses all `onPause` dispatches without editing `state.debug` assignments (#106). - **v6**: `onPause(after, K)` now fires on iter K's *own* yield, alongside `onPause(before, K)` and `onStep(K)` — per-iter lifecycle is `before → step → after` (#119). Previously `after` fired on iter K+1's tick with a `prevYield` substitution dance; that substitution is gone. Implication: tests asserting cross-hook ordering at the lifecycle level need v6-aware shape. - **v6.1**: `state.debug` is now always a non-null `DebugConfig` (lazy-initialized on first read), so chained writes like `state.debug.before = true` work on a fresh state without a prior whole-object assignment. The instance is `Object.seal`-ed — typos throw `TypeError`. `state.debug = null` continues to work but now means "reset filters" (next read returns a fresh empty config). Type signature narrowed `DebugConfig | null` → `DebugConfig` on the getter; setter still accepts `null`. (#150) - **v6.2** *(superseded by v6.3)*: briefly widened `onStep` to `(m) => void | Promise` and added an inline `await onStep(m)` in the run loop, motivated by a downstream throttle use case. That overturned the docstring-stated sync contract for `onStep` and was reverted in v6.3.0. Don't reach for that shape — the v6.4 `onIter` hook is the proper place for per-iter awaited coordination. @@ -59,8 +70,32 @@ Key shapes that take reading multiple files to grasp: ### Visualization & round-trip -`packages/machine` ships `State.toGraph(state, tapeBlock)` → `Graph` and `State.fromGraph(graph)` → `{start, tapeBlock, states}` for serialization. `toMermaid(graph)` and `fromMermaid(text)` round-trip the same Graph through Mermaid flowchart syntax. The round-trip is **behaviorally** lossless (rebuilt graph runs to same outputs on same inputs — covered by `test/round-trip.spec.ts`); not bytewise lossless because state IDs auto-reassign, and for `withOverrodeHaltState` wrappers the composite name accumulates `>${override.name}` suffixes on each pass. Tracked in [#138](https://github.com/mellonis/turing-machine-js/issues/138) (cleaner emit for wrapped states) + [#139](https://github.com/mellonis/turing-machine-js/issues/139) (regression test that fails until #138 is fixed). +`packages/machine` ships `State.toGraph(state, tapeBlock)` → `Graph` and `State.fromGraph(graph)` → `{start, tapeBlock, states}` for serialization. `toMermaid(graph)` and `fromMermaid(text)` round-trip the same `Graph` through [Mermaid flowchart](https://mermaid.js.org/syntax/flowchart.html) syntax (renderer: [mermaid-js/mermaid](https://github.com/mermaid-js/mermaid)). The parser is strict to the dialect `toMermaid` emits — hand-edited Mermaid with different arrow styles or shapes won't round-trip. + +**`packages/visuals` is the Graph consumer for runtime visualization.** Given the engine's `Graph` (from `toGraph`), visuals provides: per-rule highlight derivation (`applyHighlight` over a renderer-agnostic `HighlightOps` interface — consumers implement DOM mutation / ANSI escapes / span wrapping / etc.), breakpoint-indicator placement (`applyIndicator`), wrapper/bare-id canonicalization (`bareIdOf`, `highlightExpand`, `equivalentIds`), pre-built index lookups (`indexGraph` → `GraphIndexes`), and a `recordingOps` factory for snapshot-test assertions. The 16-rule contract `applyHighlight` satisfies lives in the visuals package (`packages/visuals/docs/graph-highlight-and-breakpoints.md`). Same input shape as `toMermaid`, but emits operations against a typed renderer interface instead of a string. + +**`packages/visuals` is also the canonical edge-label formatter.** `formatStepNotation(reads, commands, blanks, matchKinds?)` produces strings byte-identical to `toMermaid`'s edge labels (`[reads] → [writes]/[moves]` with `'X'` / `B` / `*='X'` / `K='X'` / `K=B` / `E` per cell, multi-tape comma-separated within one outer bracket per role, manual-Apply path collapsing to `[writes]/[moves]`). Use this when log rendering needs to line up byte-for-byte with the rendered state graph. Consumers wanting non-string rendering (HTML spans with CSS classes, ANSI-colored terminal output, clickable cells) call `tokenizeStep` (returns `StepTokens` — discriminated-union `ReadToken` / `WriteToken` / move-letters) and walk the tokens themselves. `formatStepNotation` is a thin string renderer over `tokenizeStep`. Tape rendering: `formatTape(snapshot)` → `a[b]c` (head bracketed in place). + +**`packages/visuals` records snippets for embeds.** `recordSnippet({ machine, initialState, graph, alphabets, name?, maxSteps?, log? }): Snippet` runs `machine.runStepByStep` and captures one `Frame` per iter plus a frame-0 initial-state snapshot. Frame shape: `{ step, tape: TapeSnapshot[], commands?: { movement, read, write }[], highlight: GraphHighlight | null, log? }` — `commands` carries both read and write so consumers can step bi-directionally without recomputing deltas from neighbouring frames. Engine-agnostic — caller supplies a pre-built `machine` + `initialState` (works with `@turing-machine-js/machine` or `@post-machine-js/machine`). + +**`State.collectStates(state, tapeBlock)`** (v7, #195) — returns a `Map` keyed by engine `GraphNode.id`. For each id: the live `State` instance and the per-pattern `Symbol[]` from `#symbolToDataMap` in insertion order. The K-th `transitionSymbols` slot is positionally aligned with the GraphTransition whose id is `${stateId}.${K}` (separator was `-` in alpha.4; #205 changes it to `.` for v7), so consumers holding `(stateId, patternIx)` from a rendered graph reach the firing Symbol with no walk. `ifOtherSymbol` is included at its natural slot. Wrappers and the halt singleton get empty `transitionSymbols`; synthetic halt markers (`isHaltMarker: true`, id `= -frameId`) are excluded — they all collapse to `haltState` at runtime. Halt-singleton entry at id `0` is the process-wide singleton — JSDoc warns toggling its `debug` affects every machine in the runtime. + +**Per-iter `matchedTransition`** (v7, [#205](https://github.com/mellonis/turing-machine-js/issues/205) / [PR #206](https://github.com/mellonis/turing-machine-js/pull/206)) — every `MachineState` yielded by `run` / `runStepByStep` carries `matchedTransition: { id: string, matchKinds: ('wildcard'|'literal')[] }`. `id` is `${stateId}.${transitionIx}` and resolves in `toGraph`'s output via `graph.nodes[stateId].transitions.find(t => t.id === ...)`. For wrapper-entry iters (composite source from `withOverriddenHaltState`), `id` references the **bare's** transition — wrappers' own `transitions` arrays are empty because the pattern lives on the bare; consumers detect delegation by comparing `id.split('.')[0]` against `state.id`. `matchKinds` is per-tape, length = tape count; `'wildcard'` iff the winning alternative held `ifOtherSymbol` at that position (catch-all), `'literal'` otherwise. Halt-bound transitions still get a valid id (both real halt `nextStateId === 0` and in-frame halt markers `nextStateId === -frameId`). The field eliminates ambiguity in `(source, nextState)` resolution when multiple transitions on the same source share a destination, and lets consumers do exact-edge highlighting / per-transition coverage maps / wildcard-aware log formatting without re-parsing pattern strings from `toGraph`. The separator change from `-` (alpha.4) to `.` propagates to `toGraph`'s `GraphTransition.id` — see `collectStates` note above. + +**`STATE_INTERNAL` Symbol accessor** (v7, #180) — `state[STATE_INTERNAL]()` returns a getter/setter view onto `State`'s private fields (`id`, `name`, `bareState`, `overriddenHaltState`, `symbolToDataMap`, `tags`). `@internal`-marked. Exported from `classes/State.ts` for sibling-module use in `packages/machine/src` (currently `utilities/stateGraph.ts`); NOT re-exported from the package's public `index.ts`. The `name` setter is the one mutation site, used by `fromGraph` to assign graph-sourced composite names (e.g. `A(target)`) that the constructor's paren-validator would reject. New utility modules in `packages/machine/src` that need private-field access route through this accessor rather than growing per-module bypasses. `toGraph`/`fromGraph`/`collectStates` all live in `utilities/stateGraph.ts` (extracted from `classes/State.ts` in #180); `State.toGraph` / `.fromGraph` / `.collectStates` statics remain as thin delegates for backwards compat. + +**State tags (v7 alpha.3, #186)** — `state.tag(...tags) / state.untag(...tags) / state.tags` API for attaching string metadata to a State. Tags are out-of-band — they don't affect transition lookup, `equivalentOn`, or runtime semantics. Live on the State *instance* (not on `#symbolToDataMap`), so engine #175 memoization doesn't leak tags across wrappers sharing a bare: `A.wohs(t1).tag('hot')` and `A.wohs(t2)` carry independent tag sets. `GraphNode` gains `tags: string[]` field — survives `toGraph`/`fromGraph` round-trip. `toMermaid` emits tags two ways: inline in node label via universal `
` (`sN["name
tag1, tag2"]`) AND as `classDef tag_` + `class sN tag_` lines (6-color hash-based palette). `fromMermaid` splits the label on `
` as source of truth; `class` lines are decorative. + +**v7 callable-subtree emit shape** (#174, layered on the v7 alpha.1 framing from #138/#139): each `withOverriddenHaltState` wrapper produces TWO `GraphNode`s — a wrapper node (`isWrapper: true`, `[[composite-name]]` shape, no transitions, `bareStateId` points to the bare's GraphNode) and a bare node (regular `["name"]` shape inside its callable subtree subgraph, holds the bare's transitions). Frames are computed via union-find on bare-reachability: each unique bare's forward-reachable set defines its candidate frame; overlapping reach sets merge into a union frame. Frame id = smallest bare-id in the component. Halt marker per frame (id = `-frameId`, `isHaltMarker: true`, maps back to singleton `haltState` in `fromGraph`). Halt-bound transitions of in-frame states retarget to the frame's halt marker. Subgraph label: `"callable subtree of NAME"` (single bare) or `"callable scope: A ∪ B"` (union). The always-emitted `idle([idle])` stadium sentinel + `idle -. enter .-> sN` arrow marks the initial state. **No per-context duplication** — shared bares like `library-binary-numbers`'s `invertNumber` (in `minusOne`) appear once with `& `-joined call arrows from each wrapper. + +**Edge label vocabulary** — `[reads] → [writes]/[moves]`, each role wrapped in `[…]` (the tape-block indicator, one entry per tape; brackets always present, even single-tape). Read cells: literal-quoted (`'X'`), `*` (ASCII, ifOtherSymbol catch-all; literal `*` in the alphabet is quoted as `'*'`), `B` (the tape's blank). Write cells: literal-quoted, `K` (keep), `E` (erase = write blank). Move cells: `L` / `R` / `S`. **Alternation is always per-pattern bracket** (`['^']|['1']|['0']` for single-tape, `['0','a']|['1','b']` for multi-tape); the compact in-bracket form `['^'|'1']` is rejected by `fromMermaid` to prevent the cross-product reading trap in multi-tape (`['0'|'1','a'|'b']` would read as 4 combinations rather than 2 paired alternatives, so the format avoids the shape entirely). + +**Edge arrow styles** — solid `-->` for regular state-to-state transitions AND for the wrapper's post-return `--> override` (just an ordinary transition under the call/return mental model); bold `==> "call"` is RESERVED for the wrapper-to-bare call arrow (source: wrapper, target: its bare; `&` ribbon collapses multiple wrappers sharing a bare); dotted `-.->` for frame-level dispatch — `w_N -. "return" .-> wrapper` (demand-emit: only when the frame's halt marker has incoming edges AND a wrapper calls into the frame), `w_N -. "halt" .-> s0` (demand-emit: only when the frame has a non-wrapper entry path), and `idle -. enter .-> sN` for execution-start. **The `-. onHalt .->` keyword from v7 alpha.1 is retired** — wrapper-to-override is now just a regular solid arrow, and the dotted `-.->` style is reserved for frame-level dispatch. + +**Round-trip** is **bytewise stable for simple wrappers** (regression test in `test/round-trip.spec.ts` — #139). The wrapper's composite name (e.g. `scanToX(eraseHere)`) does NOT appear as a graph node label; only the bare's name does, so `fromGraph` recomputes the composite fresh on reconstruction — no accumulation. **Shared-bare cases** (same `State` instance used as the bare of multiple wrappers, e.g. `library-binary-numbers`'s `minusOne` where `invertNumber` is both the outermost bare AND wrapper-W1's bare) do **not** duplicate: `toGraph`'s BFS de-dups by `State` id, so a shared bare emits a **single** node and the wrappers fan in via `&`-joined call arrows; `fromGraph` rebuilds one shared instance feeding all wrappers, so the sharing survives the round-trip. What isn't bytewise stable is the serialization itself — `GraphNode` ids are runtime `State` ids, reassigned on every `fromGraph` rebuild. For a simple wrapper the second `toGraph` matches the first up to id renaming; for a shared bare the post-rebuild id assignment no longer follows the original emission order, so the byte stream differs even though the graph describes the same machine. Reconstruction yields behaviorally-equivalent State instances (verify with `equivalentOn` → `allAgree: true`), never the same runtime `#id`. + +**Stats helpers** — `summarize(state, tapeBlock)` returns `{stateCount, transitionCount, compositionEdgeCount, maxCompositionDepth, selfLoopCount, hasCycles, tapeCount, alphabetCardinalities}`. `stateCount` filters out `isHaltMarker` sentinels (they're visualization-only, all map to the singleton `haltState` at runtime); matches the per-algorithm header in `library-binary-numbers/states.md` by construction. `equivalentOn(reference, candidate, cases)` is the separate behavioral-equivalence checker — runs both machines, compares outputs and per-step snapshots; lives in `./utilities/equivalence.ts`, unaffected by the visualization-layer changes above. ### Builder package -`buildMachine({ alphabetString, initialState, finalStateList, states })` (`packages/builder/src/index.ts`) takes a string-keyed state table where each transition is `{ symbol, movement: 'L'|'R'|'S', state }`. It constructs an `Alphabet` from `alphabetString.split('')`, makes a `Reference` per state name (so forward references work), then materializes every `State` and binds the references. Returns `[machine, initialState, statesByName]`. Use this when you have a textual/tabular spec; use the raw `machine` API when you need composition primitives (`withOverrodeHaltState`, custom symbol patterns, multi-tape). +`buildMachine({ alphabetString, initialState, finalStateList, states })` (`packages/builder/src/index.ts`) takes a string-keyed state table where each transition is `{ symbol, movement: 'L'|'R'|'S', state }`. It constructs an `Alphabet` from `alphabetString.split('')`, makes a `Reference` per state name (so forward references work), then materializes every `State` and binds the references. Returns `[machine, initialState, statesByName]`. Use this when you have a textual/tabular spec; use the raw `machine` API when you need composition primitives (`withOverriddenHaltState`, custom symbol patterns, multi-tape). diff --git a/README.md b/README.md index 8f40c35..506cc53 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ This repository contains the following packages: * [@turing-machine-js/library-binary-numbers](https://github.com/mellonis/turing-machine-js/tree/master/packages/library-binary-numbers) — binary arithmetic on a 5-symbol alphabet (` ^$01`) supporting multiple numbers per tape, with `plusOne`, `minusOne`, `minusOneFast`, `invertNumber`, `normalizeNumber`, and inter-number navigation. * [@turing-machine-js/library-binary-numbers-bare](https://github.com/mellonis/turing-machine-js/tree/master/packages/library-binary-numbers-bare) — the same arithmetic on a 3-symbol alphabet (` 01`), single-number-per-tape. Side-by-side with the marker-based library to make the alphabet-size vs state-graph-size trade-off visible. * [@turing-machine-js/builder](https://github.com/mellonis/turing-machine-js/tree/master/packages/builder) — declarative state-table construction. Not actively developed; the same pattern is shown inline in `@turing-machine-js/machine`'s README. +* [@turing-machine-js/visuals](https://github.com/mellonis/turing-machine-js/tree/master/packages/visuals) (v7+) — pure highlight + graph-indexing logic for the engine `Graph`: `applyHighlight` / `applyIndicator` against a renderer-agnostic `HighlightOps` interface, `bareIdOf` / `highlightExpand` for wrapper/bare canonicalization, `formatStepNotation` / `tokenizeStep` / `formatTape` for engine-edge-label rendering (byte-identical to `toMermaid`), `recordSnippet` for prerecorded-playback artifacts. No DOM, no Svelte, no Mermaid — consumers bring their own renderer. # An example @@ -74,35 +75,29 @@ console.log(tape.symbols.join('').trim()); // a*c*a ## How it runs -Just one state — call it **S** — that loops on every cell, writing `*` whenever the head reads `b` and stopping at the trailing blank: - -```mermaid -flowchart LR - S(("**S**")) - H((("**halt**"))) - S -- "b → *, R" --> S - S -- "_ → keep, L" --> H - S -- "any other → keep, R" --> S -``` - -*Reading the labels: `read → write, move`. `_` is the blank symbol.* - -
-📊 Same diagram, generated by toMermaid() (the engine's actual output) +Just one state — call it `replaceB` — that loops on every cell, writing `*` whenever the head reads `b` and stopping at the trailing blank. The engine emits its state graph via `toMermaid(toGraph(replaceB, tapeBlock))`: ```mermaid flowchart TD %% alphabets: [[" ","a","b","c","*"]] s0(((halt))) - s1(("replaceB")) - s1 -- "b → */R" --> s1 - s1 -- "- → ·/L" --> s0 - s1 -- "* → ·/R" --> s1 + s1["replaceB"] + idle([idle]) + idle -. enter .-> s1 + s1 -- "['b'] → ['*']/[R]" --> s1 + s1 -- "[B] → [K]/[L]" --> s0 + s1 -- "[*] → [K]/[R]" --> s1 ``` -Engine notation: `read → write/move`; `·` = keep, `*` = `ifOtherSymbol` catch-all, `-` = the blank symbol. +Quick legend for the diagram above — full table at [packages/machine/README.md § Diagram conventions](packages/machine/README.md#diagram-conventions): -
+- **Edge format**: `[reads] → [writes]/[moves]` (each `[…]` is a tape-block reading; brackets always, even single-tape). +- **Read cells**: `'X'` (literal, single-quoted), `*` (`ifOtherSymbol` catch-all), `B` (the tape's blank). +- **Write cells**: `'X'` (literal), `K` (keep), `E` (erase = write blank). +- **Movement cells**: `L` / `R` / `S` (left / right / stay). +- **Node shapes**: `(((halt)))` = halt, `["square"]` = regular state or callable-subtree bare, `[[double-walled]]` = `withOverriddenHaltState` wrapper (call site), `idle([idle])` = pre-execution sentinel. +- **Edges**: `-->` regular transition (and wrapper → override target), `==> "call"` = bold call arrow from wrapper to bare (`&` ribbons collapse multi-wrapper-shares-bare), `-. "return" .->` / `-. "halt" .->` from subgraph = frame-level dispatch, `-. enter .->` from `idle` = where execution starts. +- **Subgraph `w_N["callable subtree of NAME"]`** wraps a bare + its body + a halt marker — see [§Subroutine composition](packages/machine/README.md#subroutine-composition-with-withoverriddenhaltstate) in the engine README. Trace on the input tape `abcba`: diff --git a/docs/superpowers/plans/2026-05-25-step-control.md b/docs/superpowers/plans/2026-05-25-step-control.md new file mode 100644 index 0000000..372d956 --- /dev/null +++ b/docs/superpowers/plans/2026-05-25-step-control.md @@ -0,0 +1,1354 @@ +# Step-control via `run()` / `debugRun()` Split — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Close #102 (v7-stable gating issue) by splitting `TuringMachine`'s execution API into two non-overlapping entry points: a synchronous `run()` with no observation overhead, and a `debugRun()` that returns a `DebugSession` carrying the full interactive-debugger surface (events, breakpoints, step-in/over/out, click-pause, throttle). + +**Architecture:** `TuringMachine` stays minimal. `DebugSession` lives in a separate module and is constructed directly: + +```ts +class TuringMachine { + run({initialState, stepsLimit?}): void; // pure execution, sync + *runStepByStep({initialState, stepsLimit?}): Generator; // unchanged, sync per-iter observation + // NO debugRun method — debugger is an external opt-in +} + +// Separate module — packages/machine/src/classes/DebugSession.ts +class DebugSession { + constructor(machine: TuringMachine, parameter: DebugSessionParameter); + start(): Promise; + stop(): void; + continue(): void; + stepIn(): void; + stepOver(): void; + stepOut(): void; + pause(): void; + setRunInterval(ms: number): void; + on/off events: pause, step, iter, halt; +} + +// Usage: +import {TuringMachine, DebugSession} from '@turing-machine-js/machine'; +const session = new DebugSession(machine, {initialState}); +await session.start(); +``` + +`DebugSession` owns everything that was previously layered on top of `run({onPause, onStep, onIter, debug})` plus all the coordination logic (step-mode tracking, click-pause, throttle, pause/resume plumbing, event fan-out) that every demo / IDE extension would otherwise reimplement. Internally `DebugSession` consumes the engine's public `runStepByStep` generator and uses a Symbol-keyed `MACHINE_STATE_INTERNAL` accessor on yielded machine states to read the halt-stack for step-over/step-out endpoint detection — same sibling-module pattern as `STATE_INTERNAL` from #180. **No new private access needed beyond the Symbol.** + +**Tech Stack:** TypeScript, Vitest, the existing `@turing-machine-js/machine` engine code. + +**Scope:** Engine-only. `@post-machine-js/machine` adopts the new shape in a follow-up issue/PR (`pm.run()` + `pm.debugRun()`). `machines-demo` worker migration is a downstream follow-up. CHANGELOG entry lands in the alpha.6 release PR. + +**Breaking changes vs v6:** `run({onPause, onStep, onIter, debug})` removed. Consumers wanting hooks call `debugRun()` and use the session's event interface. Documented in the v7 migration section of `packages/machine/README.md`. v7 is already the breaking version, so this fits cleanly with the existing composition renames and `haltState.debug` boolean change. + +**Design choices locked in:** + +1. **`DebugSession` is the public surface for interactive debugging.** The `ResumeDirective` primitive that powers it stays internal (used inside `DebugSession`'s loop but not part of the package's `index.ts` exports). One canonical recommended API, one internal mechanism. +2. **`run()` becomes sync.** Without `await onPause / onIter`, no async work remains in the loop. Return type `Promise → void`. Symmetric reversal of the v4-era `run` → `async run` change. +3. **`onStep` removed entirely.** `runStepByStep` (the generator) already covers sync per-iter observation; keeping `onStep` is YAGNI duplication. Consumers needing tracing without breakpoint-driven flow iterate the generator. +4. **`DebugBreak.cause: 'breakpoint' | 'step' | 'manual'`.** `'breakpoint'` for `state.debug` matches, `'step'` for step-mode natural endpoints, `'manual'` for `session.pause()` click-pauses. +5. **`DebugSession` uses a tiny built-in listener registry**, not `node:events`. Library is environment-agnostic (Node, browsers, Workers); ~20 LOC of `on/off/emit` methods. +6. **`session.start()` returns `Promise` resolved on halt.** Async because consumer-controlled pauses await consumer action. Symmetric with how `run()` previously behaved. +7. **One-shot step-mode rule preserved:** any pause-event dispatch (breakpoint, step endpoint, manual) drops the active step-mode. Re-engaging requires another `session.stepIn/Over/Out()` call. +8. **Step-out from empty halt-stack throws.** IDE convention; explicit error beats silent no-op. +9. **Halt-stack snapshot via Symbol-keyed `MACHINE_STATE_INTERNAL`.** Same pattern as `STATE_INTERNAL` from #180. Not re-exported from the package's public `index.ts`. + +--- + +## File Structure + +- **Modify** `packages/machine/src/classes/TuringMachine.ts` — strip callbacks from `run`, make sync, add `MACHINE_STATE_INTERNAL` Symbol + accessor in `runStepByStep`, extend `MachineState.debugBreak.cause`. **Do NOT add `debugRun` method** — `DebugSession` is constructed directly. +- **Create** `packages/machine/src/classes/DebugSession.ts` — the new debugger-session class (~300 LOC). +- **Create** `packages/machine/src/classes/DebugSession.spec.ts` — focused tests for session lifecycle + events + step controls + throttle + click-pause. +- **Modify** `packages/machine/src/index.ts` — export `DebugSession` + its event/option types. Do NOT export `MACHINE_STATE_INTERNAL` or the internal `ResumeDirective`. +- **Modify existing test files** that used the removed `run({onPause, onStep, onIter})` API — migrate to `debugRun()`. + - `packages/machine/src/classes/TuringMachine.debug.spec.ts` + - `packages/machine/src/classes/TuringMachine.matchedTransition.spec.ts` +- **Modify** `packages/machine/README.md` — rewrite "Debugging breakpoints" section around `debugRun()`; add v7 migration subsection. + +--- + +## Task 1: Type scaffolding + +**Files:** +- Modify: `packages/machine/src/classes/TuringMachine.ts` + +- [ ] **Step 1: Extend `DebugBreak` on `MachineState` with `cause`** + +In `TuringMachine.ts` near the existing `MachineState` type: + +```ts +export type DebugBreak = { + before?: true; + after?: true; + cause: 'breakpoint' | 'step' | 'manual'; +}; + +// Update MachineState.debugBreak field type to use this: +debugBreak?: DebugBreak; +``` + +- [ ] **Step 2: Define the internal `ResumeDirective` type (NOT exported from package index)** + +```ts +/** @internal — used by DebugSession's loop coordination. NOT part of public API. */ +export type ResumeDirective = 'continue' | 'step-in' | 'step-over' | 'step-out'; +``` + +- [ ] **Step 3: Typecheck** + +Run: `npm run typecheck` +Expected: PASS + +- [ ] **Step 4: Commit** + +```bash +git add packages/machine/src/classes/TuringMachine.ts +git commit -m "types: add DebugBreak.cause + internal ResumeDirective (#102)" +``` + +--- + +## Task 2: `MACHINE_STATE_INTERNAL` accessor for halt-stack snapshot + +**Files:** +- Modify: `packages/machine/src/classes/TuringMachine.ts` + +- [ ] **Step 1: Define the Symbol and accessor shape** + +Near the top of `TuringMachine.ts`: + +```ts +/** @internal — used by DebugSession to read halt-stack at yield time. Same pattern as STATE_INTERNAL (#180). */ +export const MACHINE_STATE_INTERNAL = Symbol('machineState/internal'); + +export type MachineStateInternal = { + /** Halt-stack at yield time (BEFORE applyCommand / pop / push for this iter). Frozen. */ + stack: readonly State[]; +}; +``` + +- [ ] **Step 2: Attach the accessor on every yielded `MachineState` in `runStepByStep`** + +In `runStepByStep`, before the existing `yield yielded` (line 257), add: + +```ts +Object.defineProperty(yielded, MACHINE_STATE_INTERNAL, { + value: (): MachineStateInternal => ({stack: Object.freeze(stack.slice())}), + enumerable: false, +}); +``` + +- [ ] **Step 3: Test that the accessor returns a frozen snapshot** + +In a new file `packages/machine/src/classes/DebugSession.spec.ts` (we'll add more tests here in later tasks): + +```ts +import {describe, it, expect} from 'vitest'; +import {Alphabet, Tape, TapeBlock, State, TuringMachine, haltState} from '../index.js'; +import {MACHINE_STATE_INTERNAL} from './TuringMachine.js'; + +describe('MACHINE_STATE_INTERNAL', () => { + it('exposes a frozen halt-stack snapshot on every yielded machine state', () => { + const alphabet = new Alphabet({symbolList: ['_', '0']}); + const tapeBlock = TapeBlock.fromTapes([new Tape({alphabet})]); + const halt = new State({[tapeBlock.symbol([['_']])]: {nextState: haltState}}); + const inner = new State({[tapeBlock.symbol([['_']])]: {nextState: halt}}); + const outer = inner.withOverriddenHaltState(halt); + const machine = new TuringMachine(tapeBlock); + + const yields = [...machine.runStepByStep({initialState: outer})]; + expect(yields.length).toBeGreaterThan(0); + for (const m of yields) { + const internal = (m as any)[MACHINE_STATE_INTERNAL](); + expect(Array.isArray(internal.stack)).toBe(true); + expect(Object.isFrozen(internal.stack)).toBe(true); + } + }); +}); +``` + +- [ ] **Step 4: Run test** + +Run: `npx vitest run packages/machine/src/classes/DebugSession.spec.ts -t "MACHINE_STATE_INTERNAL"` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add packages/machine/src/classes/TuringMachine.ts packages/machine/src/classes/DebugSession.spec.ts +git commit -m "feat(engine): MACHINE_STATE_INTERNAL halt-stack accessor (#102)" +``` + +--- + +## Task 3: `DebugSession` class skeleton + +**Files:** +- Create: `packages/machine/src/classes/DebugSession.ts` +- Modify: `packages/machine/src/classes/DebugSession.spec.ts` + +- [ ] **Step 1: Write a failing test for session creation + halt event** + +Add to `DebugSession.spec.ts`: + +```ts +import {DebugSession} from './DebugSession.js'; + +describe('DebugSession skeleton', () => { + it('creates a session and emits halt on completion', async () => { + const alphabet = new Alphabet({symbolList: ['_', '0']}); + const tapeBlock = TapeBlock.fromTapes([new Tape({alphabet, symbolList: ['0']})]); + const halt = new State({[tapeBlock.symbol([['0']])]: {nextState: haltState}}); + const machine = new TuringMachine(tapeBlock); + + const session = machine.debugRun({initialState: halt}); + let haltFired = false; + session.on('halt', () => { haltFired = true; }); + await session.start(); + expect(haltFired).toBe(true); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `npx vitest run packages/machine/src/classes/DebugSession.spec.ts -t "skeleton"` +Expected: FAIL — `DebugSession` doesn't exist yet, `machine.debugRun` doesn't exist yet. + +- [ ] **Step 3: Implement `DebugSession.ts` skeleton** + +Create the file with: + +```ts +import {State} from './State.js'; +import {TuringMachine, MACHINE_STATE_INTERNAL} from './TuringMachine.js'; +import type {MachineState, MachineStateInternal} from './TuringMachine.js'; + +export type DebugSessionEvent = 'pause' | 'step' | 'iter' | 'halt'; +export type DebugSessionListener = + E extends 'halt' ? () => void : (machineState: MachineState) => void; + +export type DebugSessionParameter = { + initialState: State; + stepsLimit?: number; +}; + +export class DebugSession { + readonly #machine: TuringMachine; + readonly #parameter: DebugSessionParameter; + readonly #listeners = { + pause: [] as Array<(m: MachineState) => void>, + step: [] as Array<(m: MachineState) => void>, + iter: [] as Array<(m: MachineState) => void>, + halt: [] as Array<() => void>, + }; + #started = false; + + constructor(machine: TuringMachine, parameter: DebugSessionParameter) { + this.#machine = machine; + this.#parameter = parameter; + } + + on(event: E, listener: DebugSessionListener): this { + (this.#listeners[event] as any[]).push(listener); + return this; + } + + off(event: E, listener: DebugSessionListener): this { + const arr = this.#listeners[event] as any[]; + const ix = arr.indexOf(listener); + if (ix >= 0) arr.splice(ix, 1); + return this; + } + + async start(): Promise { + if (this.#started) throw new Error('DebugSession already started'); + this.#started = true; + + for (const machineState of this.#machine.runStepByStep(this.#parameter)) { + // Hooks for events to be wired in subsequent tasks. + void machineState; + } + for (const fn of this.#listeners.halt) fn(); + } +} +``` + +- [ ] **Step 4: Add `debugRun()` method to `TuringMachine`** + +In `TuringMachine.ts`: + +```ts +import {DebugSession} from './DebugSession.js'; +import type {DebugSessionParameter} from './DebugSession.js'; + +// inside the class, alongside run() and runStepByStep(): +debugRun(parameter: DebugSessionParameter): DebugSession { + return new DebugSession(this, parameter); +} +``` + +- [ ] **Step 5: Re-export from `packages/machine/src/index.ts`** + +Add: `export {DebugSession} from './classes/DebugSession.js';` +And: `export type {DebugSessionEvent, DebugSessionListener, DebugSessionParameter} from './classes/DebugSession.js';` + +- [ ] **Step 6: Run test to verify PASS** + +Run: `npx vitest run packages/machine/src/classes/DebugSession.spec.ts -t "skeleton"` +Expected: PASS + +- [ ] **Step 7: Commit** + +```bash +git add packages/machine/src/classes/DebugSession.ts packages/machine/src/classes/TuringMachine.ts packages/machine/src/classes/DebugSession.spec.ts packages/machine/src/index.ts +git commit -m "feat(engine): DebugSession skeleton + debugRun() (#102)" +``` + +--- + +## Task 4: `step` event + +**Files:** +- Modify: `packages/machine/src/classes/DebugSession.ts` +- Modify: `packages/machine/src/classes/DebugSession.spec.ts` + +- [ ] **Step 1: Failing test** + +```ts +describe('DebugSession: step event', () => { + it('emits step on every iter, in order', async () => { + const alphabet = new Alphabet({symbolList: ['_', '0']}); + const tapeBlock = TapeBlock.fromTapes([new Tape({alphabet, symbolList: ['0', '0', '0']})]); + const halt = new State({[tapeBlock.symbol([['_']])]: {nextState: haltState}}); + const ref = new Reference(); + const move = new State({ + [tapeBlock.symbol([['0']])]: {command: [{movement: movements.right}], nextState: ref}, + [tapeBlock.symbol([['_']])]: {nextState: halt}, + }); + ref.bind(move); + + const steps: number[] = []; + const machine = new TuringMachine(tapeBlock); + const session = machine.debugRun({initialState: move}); + session.on('step', (m) => steps.push(m.step)); + await session.start(); + expect(steps).toEqual([1, 2, 3, 4]); + }); +}); +``` + +- [ ] **Step 2: Run test to verify FAIL** (no step emission yet). + +- [ ] **Step 3: Wire step emission inside `DebugSession.start()`** + +Replace the loop body in `start()`: + +```ts +for (const machineState of this.#machine.runStepByStep(this.#parameter)) { + for (const fn of this.#listeners.step) fn(machineState); +} +``` + +- [ ] **Step 4: Run test to verify PASS** + +Run: `npx vitest run packages/machine/src/classes/DebugSession.spec.ts -t "step event"` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add packages/machine/src/classes/DebugSession.ts packages/machine/src/classes/DebugSession.spec.ts +git commit -m "feat(DebugSession): step event (#102)" +``` + +--- + +## Task 5: `pause` event + `continue()` resume + +**Files:** +- Modify: `packages/machine/src/classes/DebugSession.ts` +- Modify: `packages/machine/src/classes/DebugSession.spec.ts` + +- [ ] **Step 1: Failing test** + +```ts +describe('DebugSession: pause + continue', () => { + it('emits pause on debugBreak match and resumes when continue() is called', async () => { + const alphabet = new Alphabet({symbolList: ['_', '0']}); + const tapeBlock = TapeBlock.fromTapes([new Tape({alphabet, symbolList: ['0', '0']})]); + const halt = new State({[tapeBlock.symbol([['_']])]: {nextState: haltState}}); + const ref = new Reference(); + const move = new State({ + [tapeBlock.symbol([['0']])]: {command: [{movement: movements.right}], nextState: ref}, + [tapeBlock.symbol([['_']])]: {nextState: halt}, + }); + ref.bind(move); + move.debug = {before: ['0']}; + + const machine = new TuringMachine(tapeBlock); + const session = machine.debugRun({initialState: move}); + const causes: ('breakpoint' | 'step' | 'manual' | undefined)[] = []; + session.on('pause', (m) => { + causes.push(m.debugBreak?.cause); + session.continue(); + }); + await session.start(); + expect(causes).toEqual(['breakpoint', 'breakpoint']); + }); +}); +``` + +- [ ] **Step 2: Run test to verify FAIL.** + +- [ ] **Step 3: Implement pause/resume coordination in `DebugSession.start()`** + +Add private state and method: + +```ts +#pauseResolver: (() => void) | null = null; +#activeStepMode: ResumeDirective | null = null; +#clickTimeTopFrame: State | null = null; + +continue(): void { + this.#activeStepMode = null; + this.#clickTimeTopFrame = null; + this.#pauseResolver?.(); + this.#pauseResolver = null; +} +``` + +Rewrite the loop in `start()`: + +```ts +async start(): Promise { + if (this.#started) throw new Error('DebugSession already started'); + this.#started = true; + + for (const machineState of this.#machine.runStepByStep(this.#parameter)) { + const dispatchBeforePause = machineState.debugBreak?.before === true; + const dispatchAfterPause = machineState.debugBreak?.after === true; + + if (dispatchBeforePause) { + await this.#dispatchPause(machineState, {before: true, cause: 'breakpoint'}); + } + for (const fn of this.#listeners.step) fn(machineState); + if (dispatchAfterPause) { + await this.#dispatchPause(machineState, {after: true, cause: 'breakpoint'}); + } + } + for (const fn of this.#listeners.halt) fn(); +} + +async #dispatchPause(machineState: MachineState, debugBreak: DebugBreak): Promise { + this.#activeStepMode = null; // one-shot drop BEFORE dispatch + this.#clickTimeTopFrame = null; + const machineStateWithCause: MachineState = {...machineState, debugBreak}; + for (const fn of this.#listeners.pause) fn(machineStateWithCause); + await new Promise((resolve) => { + this.#pauseResolver = resolve; + }); +} +``` + +- [ ] **Step 4: Import `ResumeDirective` + `DebugBreak` types into `DebugSession.ts`** + +- [ ] **Step 5: Run test PASS** + +- [ ] **Step 6: Commit** + +```bash +git add packages/machine/src/classes/DebugSession.ts packages/machine/src/classes/DebugSession.spec.ts +git commit -m "feat(DebugSession): pause event + continue() (#102)" +``` + +--- + +## Task 6: `stepIn()` method + +**Files:** +- Modify: `packages/machine/src/classes/DebugSession.ts` +- Modify: `packages/machine/src/classes/DebugSession.spec.ts` + +- [ ] **Step 1: Failing test** + +```ts +describe('DebugSession: stepIn', () => { + it('forces a pause on the next iter regardless of debugBreak', async () => { + const alphabet = new Alphabet({symbolList: ['_', '0']}); + const tapeBlock = TapeBlock.fromTapes([new Tape({alphabet, symbolList: ['0', '0', '0']})]); + const halt = new State({[tapeBlock.symbol([['_']])]: {nextState: haltState}}); + const ref = new Reference(); + const move = new State({ + [tapeBlock.symbol([['0']])]: {command: [{movement: movements.right}], nextState: ref}, + [tapeBlock.symbol([['_']])]: {nextState: halt}, + }); + ref.bind(move); + move.debug = {before: ['0']}; + + let pauseCount = 0; + const pauseSteps: number[] = []; + const machine = new TuringMachine(tapeBlock); + const session = machine.debugRun({initialState: move}); + session.on('pause', (m) => { + pauseCount += 1; + pauseSteps.push(m.step); + if (pauseCount === 1) session.stepIn(); + else session.continue(); + }); + await session.start(); + expect(pauseSteps[0]).toBe(1); // initial breakpoint + expect(pauseSteps[1]).toBe(2); // step-in forced (no debugBreak on iter 2... wait, breakpoint still matches; test below for non-breakpoint case) + }); + + it('emits cause: step when the pause is from step-in', async () => { + // Set up a state that only matches debug on iter 1, then step-in forces an iter-2 pause. + const alphabet = new Alphabet({symbolList: ['_', '0', '1']}); + const tapeBlock = TapeBlock.fromTapes([new Tape({alphabet, symbolList: ['0', '1']})]); + const halt = new State({[tapeBlock.symbol([['_']])]: {nextState: haltState}}); + const ref = new Reference(); + const move = new State({ + [tapeBlock.symbol([['0']])]: {command: [{movement: movements.right}], nextState: ref}, + [tapeBlock.symbol([['1']])]: {command: [{movement: movements.right}], nextState: ref}, + [tapeBlock.symbol([['_']])]: {nextState: halt}, + }); + ref.bind(move); + move.debug = {before: ['0']}; // only iter 1 matches (it sees '0') + + const causes: string[] = []; + const machine = new TuringMachine(tapeBlock); + const session = machine.debugRun({initialState: move}); + let first = true; + session.on('pause', (m) => { + causes.push(m.debugBreak!.cause); + if (first) { + first = false; + session.stepIn(); + } else { + session.continue(); + } + }); + await session.start(); + expect(causes).toEqual(['breakpoint', 'step']); + }); +}); +``` + +- [ ] **Step 2: Run test FAIL.** + +- [ ] **Step 3: Implement `stepIn()`** + +Add to `DebugSession`: + +```ts +stepIn(): void { + this.#activeStepMode = 'step-in'; + this.#clickTimeTopFrame = null; + this.#pauseResolver?.(); + this.#pauseResolver = null; +} +``` + +Modify the `start()` loop to honor `#activeStepMode === 'step-in'`: + +```ts +for (const machineState of this.#machine.runStepByStep(this.#parameter)) { + const dispatchBeforePause = + machineState.debugBreak?.before === true + || this.#activeStepMode === 'step-in'; + const dispatchAfterPause = machineState.debugBreak?.after === true; + + if (dispatchBeforePause) { + const cause: 'breakpoint' | 'step' = + machineState.debugBreak?.before ? 'breakpoint' : 'step'; + await this.#dispatchPause(machineState, {before: true, cause}); + } + for (const fn of this.#listeners.step) fn(machineState); + if (dispatchAfterPause) { + const cause: 'breakpoint' | 'step' = + machineState.debugBreak?.after ? 'breakpoint' : 'step'; + await this.#dispatchPause(machineState, {after: true, cause}); + } +} +``` + +- [ ] **Step 4: Run tests PASS.** + +- [ ] **Step 5: Commit** + +```bash +git add packages/machine/src/classes/DebugSession.ts packages/machine/src/classes/DebugSession.spec.ts +git commit -m "feat(DebugSession): stepIn() (#102)" +``` + +--- + +## Task 7: `stepOver()` with click-time frame snapshot + +**Files:** +- Modify: `packages/machine/src/classes/DebugSession.ts` +- Modify: `packages/machine/src/classes/DebugSession.spec.ts` + +- [ ] **Step 1: Failing tests** + +```ts +describe('DebugSession: stepOver', () => { + it('pauses at first iter after click-time top-frame is no longer on the stack', async () => { + const alphabet = new Alphabet({symbolList: ['_', '0']}); + const tapeBlock = TapeBlock.fromTapes([new Tape({alphabet, symbolList: ['0']})]); + const halt = new State({[tapeBlock.symbol([['0']])]: {nextState: haltState}}); + const innerBare = new State({[tapeBlock.symbol([['0']])]: {nextState: halt}}); + const wrapper = innerBare.withOverriddenHaltState(halt); + innerBare.debug = {before: ['0']}; // pause on first iter inside wrapper + + let pauses = 0; + let resumedAtStep = -1; + const machine = new TuringMachine(tapeBlock); + const session = machine.debugRun({initialState: wrapper}); + session.on('pause', (m) => { + pauses += 1; + if (pauses === 1) session.stepOver(); + else { + resumedAtStep = m.step; + session.continue(); + } + }); + await session.start(); + expect(resumedAtStep).toBeGreaterThan(1); + }); + + it('with empty click-time stack, behaves like stepIn', async () => { + const alphabet = new Alphabet({symbolList: ['_', '0']}); + const tapeBlock = TapeBlock.fromTapes([new Tape({alphabet, symbolList: ['0', '0']})]); + const halt = new State({[tapeBlock.symbol([['_']])]: {nextState: haltState}}); + const ref = new Reference(); + const move = new State({ + [tapeBlock.symbol([['0']])]: {command: [{movement: movements.right}], nextState: ref}, + [tapeBlock.symbol([['_']])]: {nextState: halt}, + }); + ref.bind(move); + move.debug = {before: ['0']}; + + const pauseSteps: number[] = []; + const machine = new TuringMachine(tapeBlock); + const session = machine.debugRun({initialState: move}); + session.on('pause', (m) => { + pauseSteps.push(m.step); + if (pauseSteps.length === 1) session.stepOver(); + else session.continue(); + }); + await session.start(); + expect(pauseSteps[1]).toBe(2); + }); +}); +``` + +- [ ] **Step 2: Run tests FAIL.** + +- [ ] **Step 3: Implement `stepOver()` + endpoint detection** + +Add method: + +```ts +stepOver(): void { + this.#activeStepMode = 'step-over'; + this.#clickTimeTopFrame = this.#capturedTopFrame; // captured at pause-dispatch time (see below) + this.#pauseResolver?.(); + this.#pauseResolver = null; +} +``` + +Add `#capturedTopFrame` field, snapshot it in `#dispatchPause` before listeners fire (consumers may call `stepOver()` synchronously from the listener): + +```ts +#capturedTopFrame: State | null = null; + +async #dispatchPause(machineState: MachineState, debugBreak: DebugBreak): Promise { + this.#activeStepMode = null; + this.#clickTimeTopFrame = null; + // Snapshot the pre-iter stack top so stepOver/stepOut have it available. + const internal: MachineStateInternal = (machineState as any)[MACHINE_STATE_INTERNAL](); + this.#capturedTopFrame = internal.stack.length > 0 ? internal.stack[internal.stack.length - 1] : null; + const machineStateWithCause: MachineState = {...machineState, debugBreak}; + for (const fn of this.#listeners.pause) fn(machineStateWithCause); + await new Promise((resolve) => { this.#pauseResolver = resolve; }); +} +``` + +Extend the loop's `dispatchBeforePause` predicate: + +```ts +const readStack = (m: MachineState): readonly State[] => + (m as any)[MACHINE_STATE_INTERNAL]?.().stack ?? []; + +const stepOverEndpoint = + this.#activeStepMode === 'step-over' + && (this.#clickTimeTopFrame === null || !readStack(machineState).includes(this.#clickTimeTopFrame)); + +const dispatchBeforePause = + machineState.debugBreak?.before === true + || this.#activeStepMode === 'step-in' + || stepOverEndpoint; +``` + +- [ ] **Step 4: Run tests PASS.** + +- [ ] **Step 5: Commit** + +```bash +git add packages/machine/src/classes/DebugSession.ts packages/machine/src/classes/DebugSession.spec.ts +git commit -m "feat(DebugSession): stepOver() with frame snapshot (#102)" +``` + +--- + +## Task 8: `stepOut()` + empty-stack error + +**Files:** +- Modify: `packages/machine/src/classes/DebugSession.ts` +- Modify: `packages/machine/src/classes/DebugSession.spec.ts` + +- [ ] **Step 1: Failing tests** + +```ts +describe('DebugSession: stepOut', () => { + it('pauses at first iter after click-time top-frame is popped', async () => { + const alphabet = new Alphabet({symbolList: ['_', '0']}); + const tapeBlock = TapeBlock.fromTapes([new Tape({alphabet, symbolList: ['0']})]); + const halt = new State({[tapeBlock.symbol([['0']])]: {nextState: haltState}}); + const innerBare = new State({[tapeBlock.symbol([['0']])]: {nextState: halt}}); + const wrapper = innerBare.withOverriddenHaltState(halt); + innerBare.debug = {before: ['0']}; + + let pauses = 0; + let resumedAtStep = -1; + const machine = new TuringMachine(tapeBlock); + const session = machine.debugRun({initialState: wrapper}); + session.on('pause', (m) => { + pauses += 1; + if (pauses === 1) session.stepOut(); + else { resumedAtStep = m.step; session.continue(); } + }); + await session.start(); + expect(resumedAtStep).toBeGreaterThan(1); + }); + + it('throws when called with an empty click-time stack', async () => { + const alphabet = new Alphabet({symbolList: ['_', '0']}); + const tapeBlock = TapeBlock.fromTapes([new Tape({alphabet, symbolList: ['0']})]); + const halt = new State({[tapeBlock.symbol([['_']])]: {nextState: haltState}}); + const top = new State({[tapeBlock.symbol([['0']])]: {nextState: halt}}); + top.debug = {before: ['0']}; + + const machine = new TuringMachine(tapeBlock); + const session = machine.debugRun({initialState: top}); + const errors: Error[] = []; + session.on('pause', () => { + try { session.stepOut(); } catch (e) { errors.push(e as Error); session.continue(); } + }); + await session.start(); + expect(errors.length).toBe(1); + expect(errors[0].message).toMatch(/step-out.*empty/i); + }); +}); +``` + +- [ ] **Step 2: Run tests FAIL.** + +- [ ] **Step 3: Implement `stepOut()` + empty-stack throw** + +```ts +stepOut(): void { + if (this.#capturedTopFrame === null) { + throw new Error('Cannot step-out from an empty click-time halt-stack — there is no enclosing frame to exit.'); + } + this.#activeStepMode = 'step-out'; + this.#clickTimeTopFrame = this.#capturedTopFrame; + this.#pauseResolver?.(); + this.#pauseResolver = null; +} +``` + +Add step-out endpoint to the loop's predicate (same shape as step-over): + +```ts +const stepOutEndpoint = + this.#activeStepMode === 'step-out' + && this.#clickTimeTopFrame !== null + && !readStack(machineState).includes(this.#clickTimeTopFrame); + +const dispatchBeforePause = + machineState.debugBreak?.before === true + || this.#activeStepMode === 'step-in' + || stepOverEndpoint + || stepOutEndpoint; +``` + +- [ ] **Step 4: Run tests PASS.** + +- [ ] **Step 5: Commit** + +```bash +git add packages/machine/src/classes/DebugSession.ts packages/machine/src/classes/DebugSession.spec.ts +git commit -m "feat(DebugSession): stepOut() + empty-stack guard (#102)" +``` + +--- + +## Task 9: One-shot rule lock-in test + +**Files:** +- Modify: `packages/machine/src/classes/DebugSession.spec.ts` + +- [ ] **Step 1: Test that an inner breakpoint drops the step-mode (one-shot)** + +```ts +describe('DebugSession: one-shot rule', () => { + it('drops active step-mode when an inner breakpoint fires before the natural endpoint', async () => { + const alphabet = new Alphabet({symbolList: ['_', '0']}); + const tapeBlock = TapeBlock.fromTapes([new Tape({alphabet, symbolList: ['0']})]); + const halt = new State({[tapeBlock.symbol([['0']])]: {nextState: haltState}}); + const innerBare = new State({[tapeBlock.symbol([['0']])]: {nextState: halt}}); + const wrapper = innerBare.withOverriddenHaltState(halt); + innerBare.debug = {before: ['0']}; + halt.debug = {before: ['0']}; + + const causes: string[] = []; + const machine = new TuringMachine(tapeBlock); + const session = machine.debugRun({initialState: wrapper}); + let first = true; + session.on('pause', (m) => { + causes.push(m.debugBreak!.cause); + if (first) { first = false; session.stepOver(); } + else session.continue(); + }); + await session.start(); + // Both pauses are breakpoints — the inner halt-bound break dropped step-over. + expect(causes).toEqual(['breakpoint', 'breakpoint']); + }); +}); +``` + +- [ ] **Step 2: Run test PASS** (already implemented correctly by Tasks 5-8 — this locks the behavior). + +- [ ] **Step 3: Commit** + +```bash +git add packages/machine/src/classes/DebugSession.spec.ts +git commit -m "test(DebugSession): lock in one-shot step-mode rule (#102)" +``` + +--- + +## Task 10: `iter` event + `setRunInterval()` throttle + +**Files:** +- Modify: `packages/machine/src/classes/DebugSession.ts` +- Modify: `packages/machine/src/classes/DebugSession.spec.ts` + +- [ ] **Step 1: Failing test** + +```ts +describe('DebugSession: iter event + throttle', () => { + it('emits iter at end of each iter and respects setRunInterval', async () => { + const alphabet = new Alphabet({symbolList: ['_', '0']}); + const tapeBlock = TapeBlock.fromTapes([new Tape({alphabet, symbolList: ['0', '0']})]); + const halt = new State({[tapeBlock.symbol([['_']])]: {nextState: haltState}}); + const ref = new Reference(); + const move = new State({ + [tapeBlock.symbol([['0']])]: {command: [{movement: movements.right}], nextState: ref}, + [tapeBlock.symbol([['_']])]: {nextState: halt}, + }); + ref.bind(move); + + const iters: number[] = []; + const machine = new TuringMachine(tapeBlock); + const session = machine.debugRun({initialState: move}); + session.setRunInterval(5); + session.on('iter', (m) => iters.push(m.step)); + const start = Date.now(); + await session.start(); + const elapsed = Date.now() - start; + expect(iters).toEqual([1, 2, 3]); + expect(elapsed).toBeGreaterThanOrEqual(10); // at least 2 throttle waits of 5ms + }); +}); +``` + +- [ ] **Step 2: Run test FAIL.** + +- [ ] **Step 3: Implement `setRunInterval()` + iter emission + throttle** + +```ts +#runIntervalMs = 0; + +setRunInterval(ms: number): void { + if (ms < 0 || !Number.isFinite(ms)) throw new Error('runInterval must be a non-negative finite number'); + this.#runIntervalMs = ms; +} +``` + +In the `start()` loop, at the end of each iter: + +```ts +for (const fn of this.#listeners.iter) fn(machineState); +if (this.#runIntervalMs > 0) { + await new Promise((resolve) => setTimeout(resolve, this.#runIntervalMs)); +} +``` + +- [ ] **Step 4: Run test PASS.** + +- [ ] **Step 5: Commit** + +```bash +git add packages/machine/src/classes/DebugSession.ts packages/machine/src/classes/DebugSession.spec.ts +git commit -m "feat(DebugSession): iter event + setRunInterval throttle (#102)" +``` + +--- + +## Task 11: External `pause()` (click-pause from outside) + +**Files:** +- Modify: `packages/machine/src/classes/DebugSession.ts` +- Modify: `packages/machine/src/classes/DebugSession.spec.ts` + +- [ ] **Step 1: Failing test** + +```ts +describe('DebugSession: external pause()', () => { + it('triggers a pause event with cause: manual on the next iter', async () => { + const alphabet = new Alphabet({symbolList: ['_', '0']}); + const tapeBlock = TapeBlock.fromTapes([new Tape({alphabet, symbolList: ['0', '0', '0']})]); + const halt = new State({[tapeBlock.symbol([['_']])]: {nextState: haltState}}); + const ref = new Reference(); + const move = new State({ + [tapeBlock.symbol([['0']])]: {command: [{movement: movements.right}], nextState: ref}, + [tapeBlock.symbol([['_']])]: {nextState: halt}, + }); + ref.bind(move); + + const causes: string[] = []; + const machine = new TuringMachine(tapeBlock); + const session = machine.debugRun({initialState: move}); + session.setRunInterval(2); + let iterCount = 0; + session.on('iter', () => { + iterCount += 1; + if (iterCount === 1) session.pause(); // request pause from outside + }); + session.on('pause', (m) => { + causes.push(m.debugBreak!.cause); + session.continue(); + }); + await session.start(); + expect(causes).toEqual(['manual']); + }); +}); +``` + +- [ ] **Step 2: Run test FAIL.** + +- [ ] **Step 3: Implement `pause()` + manual-pause detection in loop** + +```ts +#pauseRequested = false; + +pause(): void { + this.#pauseRequested = true; +} +``` + +In the loop, before the existing `dispatchBeforePause` calculation, intercept the manual flag: + +```ts +const manualPause = this.#pauseRequested; +if (manualPause) { + this.#pauseRequested = false; +} + +const dispatchBeforePause = + machineState.debugBreak?.before === true + || this.#activeStepMode === 'step-in' + || stepOverEndpoint + || stepOutEndpoint + || manualPause; + +// Inside the dispatchBeforePause block, derive cause: +const beforeCause: 'breakpoint' | 'step' | 'manual' = + manualPause ? 'manual' + : machineState.debugBreak?.before ? 'breakpoint' + : 'step'; +``` + +- [ ] **Step 4: Run test PASS.** + +- [ ] **Step 5: Commit** + +```bash +git add packages/machine/src/classes/DebugSession.ts packages/machine/src/classes/DebugSession.spec.ts +git commit -m "feat(DebugSession): external pause() with cause: manual (#102)" +``` + +--- + +## Task 12: `stop()` (immediate termination) + +**Files:** +- Modify: `packages/machine/src/classes/DebugSession.ts` +- Modify: `packages/machine/src/classes/DebugSession.spec.ts` + +- [ ] **Step 1: Failing test** + +```ts +describe('DebugSession: stop', () => { + it('terminates the session and resolves start() without halt event', async () => { + const alphabet = new Alphabet({symbolList: ['_', '0']}); + const tapeBlock = TapeBlock.fromTapes([new Tape({alphabet, symbolList: ['0', '0', '0', '0', '0']})]); + const halt = new State({[tapeBlock.symbol([['_']])]: {nextState: haltState}}); + const ref = new Reference(); + const move = new State({ + [tapeBlock.symbol([['0']])]: {command: [{movement: movements.right}], nextState: ref}, + [tapeBlock.symbol([['_']])]: {nextState: halt}, + }); + ref.bind(move); + + let haltFired = false; + const machine = new TuringMachine(tapeBlock); + const session = machine.debugRun({initialState: move}); + session.on('halt', () => { haltFired = true; }); + session.on('iter', (m) => { + if (m.step === 2) session.stop(); + }); + await session.start(); + expect(haltFired).toBe(false); // stopped before halt + }); +}); +``` + +- [ ] **Step 2: Run test FAIL.** + +- [ ] **Step 3: Implement `stop()`** + +```ts +#stopped = false; + +stop(): void { + this.#stopped = true; + this.#pauseResolver?.(); + this.#pauseResolver = null; +} +``` + +In the loop, check `#stopped` at the top of each iter and break: + +```ts +for (const machineState of this.#machine.runStepByStep(this.#parameter)) { + if (this.#stopped) break; + // ... existing iter body ... + if (this.#stopped) break; // also check after listeners +} +if (!this.#stopped) { + for (const fn of this.#listeners.halt) fn(); +} +``` + +- [ ] **Step 4: Run test PASS.** + +- [ ] **Step 5: Commit** + +```bash +git add packages/machine/src/classes/DebugSession.ts packages/machine/src/classes/DebugSession.spec.ts +git commit -m "feat(DebugSession): stop() (#102)" +``` + +--- + +## Task 13: Strip callbacks from `run()`, make sync, migrate existing tests + +**Files:** +- Modify: `packages/machine/src/classes/TuringMachine.ts` +- Modify: `packages/machine/src/classes/TuringMachine.debug.spec.ts` +- Modify: `packages/machine/src/classes/TuringMachine.matchedTransition.spec.ts` +- Modify: any other test files using `run({onPause | onStep | onIter | debug})` + +- [ ] **Step 1: Grep for all consumers of the removed callback API** + +Run: `grep -rn "onPause\|onStep\|onIter" packages/machine/src/ packages/builder/src/ packages/library-binary-numbers/src/ packages/library-binary-numbers-bare/src/ test/` + +Expected: a list of all files using callbacks. Each must be migrated to `debugRun()`. + +- [ ] **Step 2: Update `TuringMachine.run()` to be sync, callback-free** + +Replace the existing `run(...)` method with: + +```ts +run({initialState, stepsLimit}: RunParameter): void { + for (const _ of this.runStepByStep({initialState, stepsLimit})) { + // pure execution, no observation + } +} +``` + +Remove `onPause`, `onStep`, `onIter`, `debug` fields from `RunParameter` (or rename it `RunParameter` for clarity that this is now the small surface). Remove unused imports. + +- [ ] **Step 3: Migrate each test file from `grep` results** + +For each `run({onPause: ..., ...})` call site, transform: + +Before: +```ts +await machine.run({ + initialState, + onPause: (m) => { observed.push(m); }, +}); +``` + +After: +```ts +const session = machine.debugRun({initialState}); +session.on('pause', (m) => { + observed.push(m); + session.continue(); +}); +await session.start(); +``` + +Similarly for `onStep` → `session.on('step', ...)`, `onIter` → `session.on('iter', ...)`, `debug: false` → simply use `run()` instead of `debugRun()`. + +- [ ] **Step 4: Run full test suite** + +Run: `npm test` +Expected: PASS — every test migrated. + +- [ ] **Step 5: Run lint + typecheck** + +Run: `npm run lint && npm run typecheck` +Expected: PASS + +- [ ] **Step 6: Commit** + +```bash +git add packages/machine/src/ packages/builder/src/ packages/library-binary-numbers/src/ packages/library-binary-numbers-bare/src/ test/ +git commit -m "refactor(engine): run() is sync + callback-free; migrate tests to debugRun() (#102)" +``` + +--- + +## Task 14: Coverage + edge-case tests + +**Files:** +- Modify: `packages/machine/src/classes/DebugSession.spec.ts` + +- [ ] **Step 1: Add tests for these edge cases** + +```ts +describe('DebugSession: edge cases', () => { + it('throws when start() is called twice', async () => { + // ... + }); + + it('removes a listener via off()', async () => { + // ... + }); + + it('start() resolves on natural halt with no listeners attached', async () => { + // ... + }); + + it('handles state.debug.after pauses + step controls together', async () => { + // ... + }); + + it('haltState.debug = true fires pause with cause: breakpoint', async () => { + // ... + }); +}); +``` + +Fill in concrete test bodies based on the patterns above. + +- [ ] **Step 2: Run coverage** + +Run: `npm run test:coverage` +Expected: All v7 floors met (97/90/95/97 per `vitest.config.ts`). + +- [ ] **Step 3: Commit** + +```bash +git add packages/machine/src/classes/DebugSession.spec.ts +git commit -m "test(DebugSession): edge cases + coverage (#102)" +``` + +--- + +## Task 15: README rewrite + migration guide + +**Files:** +- Modify: `packages/machine/README.md` + +- [ ] **Step 1: Find and rewrite the "Debugging breakpoints" section** + +Replace with a new "Debugging" section structured around `debugRun()`: + +```markdown +## Debugging + +For pure execution with no observation overhead: + +\`\`\`ts +machine.run({initialState}); // synchronous; returns when halted +\`\`\` + +For per-iter tracing without breakpoint-driven flow: + +\`\`\`ts +for (const m of machine.runStepByStep({initialState})) { + trace(m); +} +\`\`\` + +For interactive debugging — breakpoints, step-in/over/out, throttle, click-pause: + +\`\`\`ts +const session = machine.debugRun({initialState}); +session.on('pause', (m) => { + console.log('paused at', m.state.name, 'cause:', m.debugBreak.cause); + session.stepIn(); // or stepOver(), stepOut(), continue(), stop() +}); +session.on('halt', () => console.log('done')); +await session.start(); +\`\`\` + +### Breakpoints + +`state.debug = {before: [...], after: [...]}` sets a per-symbol breakpoint filter. `haltState.debug = true` pauses on every halt (program exit + subroutine return). [Existing breakpoint docs...] + +### Step controls + +| Method | Behavior | +|---|---| +| `session.continue()` | Resume until next breakpoint or halt | +| `session.stepIn()` | Pause on the very next iter | +| `session.stepOver()` | Pause at first iter after click-time top halt-frame is no longer on the stack; with an empty click-time stack, collapses to `stepIn` | +| `session.stepOut()` | Pause at first iter after click-time top halt-frame is popped; throws if click-time stack is empty | +| `session.pause()` | Request a pause from outside the loop; fires on the next iter with `cause: 'manual'` | +| `session.stop()` | Terminate immediately; no `halt` event fires | + +**One-shot rule:** any `pause` event (breakpoint, step endpoint, manual) drops the active step-mode. To continue stepping, call `stepIn/Over/Out` again from the new pause. + +### Throttle + +`session.setRunInterval(ms)` inserts an awaited delay at the end of every iter — useful for visualizing execution. + +### Events + +| Event | Argument | Fires | +|---|---|---| +| `pause` | `MachineState` (with `debugBreak.cause`) | Breakpoint match, step endpoint, or manual `pause()` | +| `step` | `MachineState` | Once per iter, between any before-pause and after-pause | +| `iter` | `MachineState` | Once per iter, at end (after any after-pause) | +| `halt` | (none) | Once, on natural halt (not on `stop()`) | +``` + +- [ ] **Step 2: Add a v7 migration subsection** + +```markdown +### v7 migration — `run({onPause, ...})` removed + +v7 splits the execution API into three non-overlapping entry points: + +\`\`\`ts +// v6 +await machine.run({ + initialState, + onPause: (m) => { ... }, + onStep: (m) => { ... }, + onIter: (m) => { ... }, +}); + +// v7 +const session = machine.debugRun({initialState}); +session.on('pause', (m) => { ... session.continue(); }); +session.on('step', (m) => { ... }); +session.on('iter', (m) => { ... }); +await session.start(); +\`\`\` + +If you weren't using callbacks, `machine.run({initialState})` still works — it's now synchronous and returns `void`. Drop the `await`. + +If you only used `onStep` for tracing, the generator `machine.runStepByStep({initialState})` is the equivalent shape: + +\`\`\`ts +for (const m of machine.runStepByStep({initialState})) { + trace(m); +} +\`\`\` +``` + +- [ ] **Step 3: Commit** + +```bash +git add packages/machine/README.md +git commit -m "docs: rewrite Debugging section around debugRun() + v7 migration (#102)" +``` + +--- + +## Task 16: Final verification + PR + +- [ ] **Step 1: Run full quality gate** + +```bash +npm run lint && npm run typecheck && npm test && npm run test:coverage +``` + +Expected: all PASS. Coverage floors met. + +- [ ] **Step 2: Open the PR targeting `v7`** + +```bash +gh pr create --base v7 --title "feat(engine): run() + debugRun() + DebugSession (#102)" --body "$(cat <<'EOF' +## Summary + +Closes #102 — the v7-stable gating issue. Splits `TuringMachine`'s execution API into three non-overlapping entry points: + +- `machine.run({initialState})` — pure execution, synchronous, no observation overhead. +- `machine.runStepByStep({initialState})` — sync generator, per-iter observation (unchanged). +- `machine.debugRun({initialState})` — returns a `DebugSession` with events (pause/step/iter/halt), step controls (continue/stepIn/stepOver/stepOut), external `pause()`/`stop()`, and `setRunInterval()` throttle. + +The v6 `run({onPause, onStep, onIter, debug})` shape is removed (breaking change, fits cleanly in v7's major version). + +## Breaking changes + +- `run()` is now synchronous (`void`, not `Promise`). +- `run({onPause | onStep | onIter | debug})` removed. Use `debugRun()`. + +Migration documented in the README. + +## Out of scope (follow-ups) + +- `@post-machine-js/machine` adoption (`pm.run()` + `pm.debugRun()`) — separate issue. +- `machines-demo` worker migration — separate issue. +- CHANGELOG entry lands in the alpha.6 release PR per `project_v7_release_checklist` convention. +EOF +)" +``` + +- [ ] **Step 3: After CI green, manually close #102** with a comment pointing to this PR + the eventual alpha.6 release. + +--- + +## Self-review notes + +- All four step controls (continue / stepIn / stepOver / stepOut) plus pause / stop have dedicated tasks with tests. +- Throttle (`setRunInterval`) is a first-class session method, not bolted onto another API. +- External `pause()` introduces the third `DebugBreak.cause` value ('manual') and has its own task. +- One-shot rule has a dedicated lock-in test. +- Empty-stack `stepOut` throws (IDE convention). +- `run()` becomes sync; `runStepByStep` unchanged; `debugRun()` is async via `start()`. +- v7 migration documented in README. +- Engine-only scope; post-machine-js adoption and machines-demo migration are separate follow-ups. +- No placeholders: every step has actual code or actual commands with expected outputs. diff --git a/docs/superpowers/plans/2026-05-29-callframe-extends-state.md b/docs/superpowers/plans/2026-05-29-callframe-extends-state.md new file mode 100644 index 0000000..904a399 --- /dev/null +++ b/docs/superpowers/plans/2026-05-29-callframe-extends-state.md @@ -0,0 +1,380 @@ +# CallFrame extends State — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the implicit wrapper-State produced by `withOverriddenHaltState` (a plain `State` whose private `#symbolToDataMap`/`#debugRef` are aliased to its bare) with a first-class `CallFrame extends State` that *delegates* to its bare, with zero public behavior change. + +**Architecture:** `CallFrame` is a subclass of `State` defined inline in `State.ts` (after the `State` class, to avoid the `extends State` module-init TDZ cycle). It holds its own `#bare`/`#override`, overrides the transition-lookup methods + `debug` + `STATE_INTERNAL` to forward to `#bare`, and inherits `#id`/`#name`/`#tags`/`isHalt` unchanged. `withOverriddenHaltState` constructs a `CallFrame` instead of mutating a fresh `State`. Because `CallFrame instanceof State` stays true, no consumer migration is needed; `instanceof CallFrame` becomes the explicit wrapper discriminator. + +**Tech Stack:** TypeScript (project references), Vitest, npm workspaces. Run a single spec: `npx vitest run packages/machine/src/classes/State.spec.ts -t "name"`. + +**Branch:** `feat/213-callframe-extends-state` (off `v7`). Issue: mellonis/turing-machine-js#213. + +**Coverage floors (must hold):** 97% statements / 90% branches / 95% functions / 97% lines. + +--- + +## File Structure + +- `packages/machine/src/classes/State.ts` — add `export class CallFrame extends State` after the `State` class; rewrite `withOverriddenHaltState` to build a `CallFrame`; route `State.inspect` through `STATE_INTERNAL` so it works for `CallFrame`; widen `#wrapperCache` value type to `WeakRef`. +- `packages/machine/src/classes/TuringMachine.ts` — simplify the `resolvableStateId` derivation (`:197-198`) to a `CallFrame` instanceof check; import `CallFrame`. +- `packages/machine/src/index.ts` — export `CallFrame`. +- `packages/machine/src/classes/State.spec.ts` — add CallFrame-identity / delegation tests. +- Verification only (expected unchanged): `utilities/stateGraph.ts` (toGraph/fromGraph/collectStates), `TuringMachine.spec.ts`, `library-binary-numbers` specs, `test/round-trip.spec.ts`. + +--- + +## Task 1: `CallFrame extends State` (inline in State.ts) + +**Files:** +- Modify: `packages/machine/src/classes/State.ts` +- Test: `packages/machine/src/classes/State.spec.ts` + +- [ ] **Step 1: Write failing tests** (append to `State.spec.ts`) + +```ts +import State, {CallFrame, haltState, ifOtherSymbol} from './State'; +import TapeBlock from './TapeBlock'; + +describe('CallFrame', () => { + const tb = TapeBlock.fromSymbols([['a', 'b']]); + const sA = new State({[tb.symbol([['a']])]: {nextState: haltState}}, 'A'); + const sB = new State({[tb.symbol([['b']])]: {nextState: haltState}}, 'B'); + + it('withOverriddenHaltState returns a CallFrame that is also a State', () => { + const w = sA.withOverriddenHaltState(sB); + expect(w).toBeInstanceOf(State); + expect(w).toBeInstanceOf(CallFrame); + }); + + it('composite name is bare(override)', () => { + expect(sA.withOverriddenHaltState(sB).name).toBe('A(B)'); + }); + + it('is not the halt state', () => { + expect(sA.withOverriddenHaltState(sB).isHalt).toBe(false); + }); + + it('exposes the override via overriddenHaltState', () => { + expect(sA.withOverriddenHaltState(sB).overriddenHaltState).toBe(sB); + }); + + it('delegates transition lookups to the bare', () => { + const w = sA.withOverriddenHaltState(sB); + const sym = sA.getSymbol(tb); + expect(w.getSymbol(tb)).toBe(sym); + expect(w.getNextState(sym)).toBe(sA.getNextState(sym)); + expect(w.getMatchedTransition(sym).ix).toBe(sA.getMatchedTransition(sym).ix); + }); + + it('memoizes by (bare, override) — #175', () => { + expect(sA.withOverriddenHaltState(sB)).toBe(sA.withOverriddenHaltState(sB)); + }); + + it('collapses a wohs chain — #176', () => { + const sC = new State({[tb.symbol([['a']])]: {nextState: haltState}}, 'C'); + expect(sA.withOverriddenHaltState(sB).withOverriddenHaltState(sC)) + .toBe(sA.withOverriddenHaltState(sC)); + }); + + it('keeps tags independent across wrappers sharing a bare — #186', () => { + const sC = new State({[tb.symbol([['a']])]: {nextState: haltState}}, 'C'); + sA.withOverriddenHaltState(sB).tag('hot'); + expect(sA.withOverriddenHaltState(sC).tags).not.toContain('hot'); + }); + + it('shares debug with the bare both ways', () => { + const w = sA.withOverriddenHaltState(sB); + const sym = sA.getSymbol(tb); + sA.debug = {before: [sym]}; + expect(w.debug.before).toEqual([sym]); + w.debug = {after: [sym]}; + expect(sA.debug.after).toEqual([sym]); + sA.debug = null; + }); +}); +``` + +- [ ] **Step 2: Run, verify failure** + +Run: `npx vitest run packages/machine/src/classes/State.spec.ts -t "CallFrame"` +Expected: FAIL — `CallFrame` is not exported from `./State`. + +- [ ] **Step 3: Add the `CallFrame` class** at the bottom of `State.ts`, after `export const haltState`: + +```ts +export class CallFrame extends State { + readonly #bare: State; + + readonly #override: State; + + constructor(bare: State, override: State) { + super(null); + this.#bare = bare; + this.#override = override; + // Composite name bypasses the constructor's paren-validator by going + // through the STATE_INTERNAL name setter (writes the inherited #name). + this[STATE_INTERNAL]().name = `${bare.name}(${override.name})`; + } + + get bare(): State { + return this.#bare; + } + + get overriddenHaltState(): State { + return this.#override; + } + + getSymbol(tapeBlock: TapeBlock) { + return this.#bare.getSymbol(tapeBlock); + } + + getCommand(symbol: symbol) { + return this.#bare.getCommand(symbol); + } + + getNextState(symbol: symbol) { + return this.#bare.getNextState(symbol); + } + + getMatchedTransition(symbol: symbol) { + return this.#bare.getMatchedTransition(symbol); + } + + get debug(): DebugConfig { + return this.#bare.debug; + } + + set debug( + value: DebugConfig | { before?: symbol[] | readonly symbol[] | true; after?: symbol[] | readonly symbol[] | true } | null, + ) { + this.#bare.debug = value; + } + + [STATE_INTERNAL]() { + // Own id/name/tags come from the inherited State fields (via super's + // view); bareState/override/transition-map delegate to #bare/#override. + const own = super[STATE_INTERNAL](); + const bare = this.#bare; + const override = this.#override; + + return { + get id(): number { return own.id; }, + get name(): string { return own.name; }, + set name(v: string) { own.name = v; }, + get bareState(): State | null { return bare; }, + get overriddenHaltState(): State | null { return override; }, + get symbolToDataMap() { return bare[STATE_INTERNAL]().symbolToDataMap; }, + get tags(): ReadonlySet { return own.tags; }, + }; + } +} +``` + +- [ ] **Step 4: Run, verify pass** + +Run: `npx vitest run packages/machine/src/classes/State.spec.ts -t "CallFrame"` +Expected: PASS (all CallFrame tests). If the `#wrapperCache`/`withOverriddenHaltState` still returns a plain `State`, the `instanceof CallFrame` test fails — that's fixed in Task 2; it's acceptable for Step 4 to leave `instanceof CallFrame` red until Task 2. Re-run after Task 2. + +> Note: the `instanceof CallFrame`, memoization, chain-collapse, tag, and debug tests depend on `withOverriddenHaltState` building a `CallFrame` (Task 2). Implement Task 2 immediately, then this whole block goes green. + +- [ ] **Step 5: Commit** (after Task 2 green — see Task 2 Step 6) + +--- + +## Task 2: Build `CallFrame` in `withOverriddenHaltState`; route `inspect`; drop aliasing + +**Files:** +- Modify: `packages/machine/src/classes/State.ts` + +- [ ] **Step 1: Widen the cache value type** (`State.ts:106`) + +```ts + static #wrapperCache = new WeakMap>>(); +``` + +- [ ] **Step 2: Rewrite `withOverriddenHaltState`** (replace the body, `State.ts:441-484`) + +```ts + withOverriddenHaltState(overriddenHaltState: State): CallFrame { + // Chain-collapse (#176): an inner override is dead at runtime, so a + // wrapped `this` unwraps to its bare before re-wrapping. + const bare = this instanceof CallFrame ? this.bare : this; + + let innerCache = State.#wrapperCache.get(bare); + + if (innerCache !== undefined) { + const ref = innerCache.get(overriddenHaltState); + + if (ref !== undefined) { + const cached = ref.deref(); + + if (cached !== undefined) { + return cached; + } + } + } else { + innerCache = new WeakMap(); + State.#wrapperCache.set(bare, innerCache); + } + + const frame = new CallFrame(bare, overriddenHaltState); + + innerCache.set(overriddenHaltState, new WeakRef(frame)); + + return frame; + } +``` + +This removes the four field-aliasing assignments (`#symbolToDataMap`, `#overriddenHaltState`, `#debugRef`, `#bareState`) — they no longer exist on a wrapper. + +- [ ] **Step 3: Route `State.inspect` through `STATE_INTERNAL`** so it reads the bare's transitions/override for a `CallFrame`. Replace the direct `state.#symbolToDataMap` / `state.#overriddenHaltState` / `state.#id` / `state.#name` reads in `inspect` (`State.ts:534-579`) with the internal view: + +```ts + static inspect(state: State): { + id: number; + name: string; + isHalt: boolean; + overriddenHaltState: { id: number; name: string } | null; + transitions: Array<{ + rawPatternDescription: string | undefined; + command: Array<{ symbol: string; movement: string }>; + nextState: { id: number; name: string } | null; + }>; + } { + const internal = state[STATE_INTERNAL](); + const transitions: Array<{ + rawPatternDescription: string | undefined; + command: Array<{ symbol: string; movement: string }>; + nextState: { id: number; name: string } | null; + }> = []; + + for (const [sym, {command, nextState}] of internal.symbolToDataMap) { + let target: State | null = null; + + try { + target = nextState instanceof State ? nextState : nextState.ref; + } catch { + target = null; + } + + transitions.push({ + rawPatternDescription: sym.description, + command: command.tapesCommands.map((tc) => ({ + symbol: decodeWriteSymbol(tc.symbol), + movement: decodeMovement((tc.movement as symbol).description), + })), + nextState: target ? {id: target.id, name: target.name} : null, + }); + } + + const override = internal.overriddenHaltState; + + return { + id: internal.id, + name: internal.name, + isHalt: state.isHalt, + overriddenHaltState: override + ? {id: override.id, name: override.name} + : null, + transitions, + }; + } +``` + +- [ ] **Step 4: Run the full State spec + the matchedTransition spec** + +Run: `npx vitest run packages/machine/src/classes/State.spec.ts packages/machine/src/classes/State.debug.spec.ts` +Expected: PASS, including the Task 1 `instanceof CallFrame` / memo / chain / tags / debug tests. + +- [ ] **Step 5: Typecheck** + +Run: `npm run typecheck` +Expected: no errors. (Watch for: `withOverriddenHaltState` return type vs `WeakRef`; forward-reference of `CallFrame` as a type in the `#wrapperCache` annotation — allowed since types hoist.) + +- [ ] **Step 6: Commit** + +```bash +git add packages/machine/src/classes/State.ts packages/machine/src/classes/State.spec.ts +git commit -m "Extract CallFrame as a State subclass; delegate instead of field-aliasing" +``` + +(Commit only with the user's explicit go-ahead — see CLAUDE.md.) + +--- + +## Task 3: Simplify run-loop stateId resolution + +**Files:** +- Modify: `packages/machine/src/classes/TuringMachine.ts` + +- [ ] **Step 1: Import `CallFrame`** (`TuringMachine.ts:1`) + +```ts +import State, {CallFrame, haltState, STATE_INTERNAL, type DebugConfig} from './State'; +``` + +- [ ] **Step 2: Replace the `resolvableStateId` derivation** (`TuringMachine.ts:189-198`). The `STATE_INTERNAL` peek for `bareState` becomes a direct `CallFrame` check: + +```ts + const resolvableStateId = state instanceof CallFrame ? state.bare.id : state.id; +``` + +Remove the now-unused `const stateInternal = state[STATE_INTERNAL]();` line and its comment block. If `STATE_INTERNAL` is no longer referenced anywhere else in `TuringMachine.ts`, drop it from the import (keep if `MACHINE_STATE_INTERNAL` plumbing or other sites still use it — grep first: `grep -n STATE_INTERNAL packages/machine/src/classes/TuringMachine.ts`). + +- [ ] **Step 3: Run the TuringMachine specs** + +Run: `npx vitest run packages/machine/src/classes/TuringMachine.spec.ts packages/machine/src/classes/TuringMachine.matchedTransition.spec.ts packages/machine/src/classes/TuringMachine.debug.spec.ts packages/machine/src/classes/DebugSession.spec.ts` +Expected: PASS. + +- [ ] **Step 4: Commit** + +```bash +git add packages/machine/src/classes/TuringMachine.ts +git commit -m "Use CallFrame instanceof for run-loop stateId resolution" +``` + +--- + +## Task 4: Export `CallFrame`; full verification gate + +**Files:** +- Modify: `packages/machine/src/index.ts` + +- [ ] **Step 1: Export `CallFrame`** (`index.ts:4`) + +```ts +export { default as State, CallFrame, DebugConfig, haltState, ifOtherSymbol } from './classes/State'; +``` + +- [ ] **Step 2: Full suite** + +Run: `npm test` +Expected: PASS, all packages. Pay attention to `library-binary-numbers` (the `minusOne` 4-deep `wohs` chain), `stateGraph.spec.ts` (toGraph/fromGraph/collectStates), and `test/round-trip.spec.ts` (bytewise stability for simple wrappers + shared-bare cases). These are the regression net for the delegation change. + +- [ ] **Step 3: Lint + typecheck + coverage** + +Run: `npm run lint && npm run typecheck && npm run test:coverage` +Expected: clean lint, no type errors, coverage ≥ 97/90/95/97. + +- [ ] **Step 4: Build (smoke the Rollup repackage)** + +Run: `npm run build` +Expected: success — confirms the new export + class compile through the project-references + Rollup post-step. + +- [ ] **Step 5: Commit** + +```bash +git add packages/machine/src/index.ts +git commit -m "Export CallFrame from the public surface" +``` + +--- + +## Self-Review notes + +- **Spec coverage:** CallFrame identity (T1), delegation of lookups/debug/STATE_INTERNAL (T1), memoization #175 (T1/T2), chain-collapse #176 (T1/T2), independent tags #186 (T1), inspect correctness (T2), run-loop simplification (T3), graph round-trip + collectStates (T4 verification), public discriminator export (T4). All design points covered. +- **`isHalt`:** inherited; `super(null)` → fresh nonzero `#id` → false. No override. +- **`validateDebugFilter`:** never runs on a CallFrame because `set debug` forwards to the bare, whose own validator uses the real transition map. +- **Out of scope:** halt-stack stays `State[]` (no `CallFrame[]` rework); no naming changes; binary-numbers libraries untouched. +- **Decision (locked):** CallFrame inline in State.ts (no cross-file `extends` cycle); debug delegates to bare (preserves shared contract). diff --git a/docs/superpowers/plans/2026-05-30-visuals-alpha7-formatters.md b/docs/superpowers/plans/2026-05-30-visuals-alpha7-formatters.md new file mode 100644 index 0000000..c777213 --- /dev/null +++ b/docs/superpowers/plans/2026-05-30-visuals-alpha7-formatters.md @@ -0,0 +1,434 @@ +# `@turing-machine-js/visuals` 7.0.0-alpha.6.1 — formatter enhancements + +> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:executing-plans or superpowers:subagent-driven-development. + +**Goal:** Extend visuals's formatter surface so it (a) covers the richness machines-demo's `format.ts` already provides — the upcoming demo cleanup PR can drop `format.ts` and call visuals's primitives directly — AND (b) ships a renderer-agnostic structured-token surface so UIs beyond the demo (article embeds, snippet panels, terminal tools) can render the same per-step data in their own format without re-implementing the per-cell encoding logic. Pure additive change to visuals. + +**Tracks:** Unblocks the `format.ts` portion of the [machines-demo visuals-cleanup plan](../../../../machines-demo/docs/superpowers/plans/2026-05-30-visuals-cleanup.md). Owns two gaps I missed when implementing the alpha.6 formatters (Task 10 of #220): +1. I designed minimal primitives in a vacuum when demo's `format.ts` was the de facto requirement spec. +2. I made `formatStepNotation`-equivalent monolithic (returns one specific string format), so UIs wanting different rendering had to re-implement the per-cell logic from scratch. The `tokenizeStep` + `ReadToken`/`WriteToken` surface fixes that — string rendering is now ONE of many possible presentations. + +**Branch:** `feat/visuals-alpha7-formatters` (off `v7`). Branch name reflects the initial alpha.7 draft; the version shipped is `7.0.0-alpha.6.1` after a mid-draft rename. Branch identifier doesn't matter; the version that ships is what counts. + +**Architecture:** `tokenizeStep` is the primary primitive — takes `(reads, commands, blanks, matchKinds?)` and returns `StepTokens` (per-tape discriminated-union tokens for reads/writes/moves). `formatStepNotation` is a thin string renderer that consumes `tokenizeStep`'s output and joins it with the engine edge-label vocabulary (`[reads] → [writes]/[moves]`). UIs wanting different presentations call `tokenizeStep` and walk tokens themselves. alpha.6's `formatStep(m)` and `formatCommand(tapeCommand)` stay untouched (backward-compat for any caller). Mirror demo's `format.ts:formatStepNotation` byte-for-byte at the rendering layer — same encoding rules, same multi-tape join, same null-`reads` manual-Apply handling, same `K='X'` / `B` / `E` shortcuts. The demo-side migration in the cleanup PR becomes: drop local `formatStepNotation`, drop local `formatTape`, change two internal imports to consume from `@turing-machine-js/visuals`. + +--- + +## Decisions (locked) + +- **Visuals-only bump to `7.0.0-alpha.6.1`.** Engine + builder + library-binary-numbers + library-binary-numbers-bare stay at `7.0.0-alpha.6` — they have no changes; bumping them would create ghost releases (identical tarballs published under a new version). The workspace's lockstep convention exists for coordinated peer-dep widening when engine APIs break; an additive visuals-only release doesn't trigger it. Peer-dep coherence holds: visuals's `@turing-machine-js/machine: ^7.0.0-alpha.6` accepts alpha.6.1+ via semver-prerelease caret, so consumers don't need a coordinated upgrade. +- **Version name `alpha.6.1` (not `alpha.7`).** Communicates "additive follow-up patch on alpha.6," not "new alpha milestone." Reserves `alpha.7` for the next real engine bump. Semver ordering is correct (`alpha.6 < alpha.6.1 < alpha.7`) and caret ranges `^7.0.0-alpha.6` accept it. +- **alpha.6 surface stays intact.** `formatStep(m: MachineState): string` and `formatCommand(tapeCommand: TapeCommand): string` remain exactly as published. New primitives are added alongside. No deprecation in alpha.6.1. +- **Primary primitive: `tokenizeStep(reads, commands, blanks, matchKinds?)`** — renderer-agnostic structured-token output. Returns `StepTokens = { reads: ReadToken[] | null; writes: WriteToken[]; moves: ('L'|'R'|'S')[] }` where: + - `ReadToken = { kind: 'literal'; symbol: string } | { kind: 'blank' } | { kind: 'wildcard'; symbol: string }` + - `WriteToken = { kind: 'literal'; symbol: string } | { kind: 'erase' } | { kind: 'keep'; readContext?: { symbol: string; isBlank: boolean } }` + UIs that want non-string rendering (HTML spans with CSS classes, ANSI escape codes, clickable cells, alternative move vocabulary, JSON for embeds) walk the tokens themselves. Same input contract as `formatStepNotation` — same engine vocabulary, same null-`reads` manual-Apply handling, same wildcard suppression of the blank shortcut. +- **`formatStepNotation` is now a thin string renderer over `tokenizeStep`.** Output is byte-identical to a from-scratch port of demo's `formatStepNotation` (verified — all 24 existing format-spec cases pass unchanged after the refactor). String rendering is one of many possible presentations of the same structured data. +- **New primitive: `formatTape(tape: TapeSnapshot)`** — verbatim port of demo's `formatTape`. No tokenizer variant (single-tape rendering is structurally trivial; no per-cell discrimination needed beyond head-vs-other, which the simple string form captures fine). +- **`StepCommand` parameter type defined locally in visuals.** Plain `{ movement: 'L'|'R'|'S'; symbol: string | null }` — distinct from engine's `TapeCommand` class. Coupling formatters/tokenizers to a specific engine class adds zero value; consumers passing plain data from snippet frames / log lines / worker boundaries don't have to construct engine class instances just to format a string. +- **Demo migration is NOT in this PR.** That happens in the machines-demo cleanup PR, after alpha.6.1 publishes. + +--- + +## File Structure + +``` +packages/visuals/ +├── src/ +│ ├── format.ts # MODIFY — add StepCommand + tokenizeStep + token types +│ │ + formatStepNotation (delegates to tokenizeStep) +│ │ + formatTape; keep alpha.6 fns intact +│ ├── format.spec.ts # MODIFY — test cases for tokenizeStep, formatStepNotation, formatTape +│ └── index.ts # MODIFY — export all new symbols +├── CHANGELOG.md # MODIFY — new [7.0.0-alpha.6.1] entry +└── package.json # MODIFY — version bump 7.0.0-alpha.6 → 7.0.0-alpha.6.1 + +package-lock.json # MODIFY — auto-resync (visuals's lock entry only) +``` + +Note: `lerna.json`'s `version` field stays at the engine's `7.0.0-alpha.6` — this release intentionally diverges from lerna's single-version model. Per-package versions are the authoritative source for `lerna publish from-package`; the `lerna.json` version is informational when `independent` mode isn't set, and is fine to leave stale for this kind of single-package release. (Reconsider if lerna emits a warning at publish time.) + +Public API delta after this PR: + +```ts +// Existing (alpha.6) — unchanged +export function formatCommand(tapeCommand: TapeCommand): string; +export function formatStep(m: MachineState): string; + +// New (alpha.6.1) — structured-token surface (primary primitive) +export type StepCommand = { + movement: 'L' | 'R' | 'S'; + symbol: string | null; // null = keep +}; + +export type ReadToken = + | { kind: 'literal'; symbol: string } + | { kind: 'blank' } + | { kind: 'wildcard'; symbol: string }; + +export type WriteToken = + | { kind: 'literal'; symbol: string } + | { kind: 'erase' } + | { kind: 'keep'; readContext?: { symbol: string; isBlank: boolean } }; + +export type StepTokens = { + reads: readonly ReadToken[] | null; // null = manual Apply + writes: readonly WriteToken[]; + moves: readonly ('L' | 'R' | 'S')[]; +}; + +export function tokenizeStep( + reads: readonly string[] | null, + commands: readonly StepCommand[], + blanks: readonly string[], + matchKinds?: readonly ('wildcard' | 'literal')[] | null, +): StepTokens; + +// New (alpha.6.1) — string rendering (thin renderer over tokenizeStep) +export function formatStepNotation( + reads: readonly string[] | null, + commands: readonly StepCommand[], + blanks: readonly string[], + matchKinds?: readonly ('wildcard' | 'literal')[] | null, +): string; + +// New (alpha.6.1) — tape rendering +export function formatTape(tape: TapeSnapshot): string; +``` + +--- + +## Task 1: Add tokens + tokenizer + renderers + tape formatter to `format.ts` + +**Files:** `packages/visuals/src/format.ts` + +- [ ] **Step 1: Read demo's `format.ts` as the source spec** + +`../machines-demo/src/lib/format.ts:33-84` — the `formatStepNotation` function body + its JSDoc + the `formatTape` function. The port is a refactor (not a verbatim copy): demo's monolithic string function splits into `tokenizeStep` (structured output) + `formatStepNotation` (thin string renderer over tokens). Final string output is byte-identical. + +- [ ] **Step 2: Define `StepCommand` + token types** + +Add to `packages/visuals/src/format.ts`: + +```ts +import type { TapeSnapshot } from './types'; + +export type StepCommand = { + movement: 'L' | 'R' | 'S'; + symbol: string | null; +}; + +export type ReadToken = + | { kind: 'literal'; symbol: string } + | { kind: 'blank' } + | { kind: 'wildcard'; symbol: string }; + +export type WriteToken = + | { kind: 'literal'; symbol: string } + | { kind: 'erase' } + | { kind: 'keep'; readContext?: { symbol: string; isBlank: boolean } }; + +export type StepTokens = { + reads: readonly ReadToken[] | null; + writes: readonly WriteToken[]; + moves: readonly ('L' | 'R' | 'S')[]; +}; +``` + +`StepCommand` is the plain per-tape command shape (NOT engine's `TapeCommand` class). Lets callers pass data from snippet frames / log lines / worker boundaries without constructing engine class instances. + +- [ ] **Step 3: Add `tokenizeStep`** + +```ts +export function tokenizeStep( + reads: readonly string[] | null, + commands: readonly StepCommand[], + blanks: readonly string[], + matchKinds?: readonly ('wildcard' | 'literal')[] | null, +): StepTokens { + const writes: WriteToken[] = commands.map((c, i) => { + if (c.symbol === null) { + if (reads !== null) { + const r = reads[i]; + if (r !== undefined) { + return { kind: 'keep', readContext: { symbol: r, isBlank: r === blanks[i] } }; + } + } + return { kind: 'keep' }; + } + if (c.symbol === blanks[i]) return { kind: 'erase' }; + return { kind: 'literal', symbol: c.symbol }; + }); + + const moves = commands.map((c) => c.movement); + + if (reads === null) return { reads: null, writes, moves }; + + const readTokens: ReadToken[] = reads.map((r, i) => { + if (matchKinds?.[i] === 'wildcard') return { kind: 'wildcard', symbol: r }; + if (r === blanks[i]) return { kind: 'blank' }; + return { kind: 'literal', symbol: r }; + }); + + return { reads: readTokens, writes, moves }; +} +``` + +- [ ] **Step 4: Add `formatStepNotation` as a thin renderer over `tokenizeStep`** + +```ts +function renderReadToken(t: ReadToken): string { + if (t.kind === 'wildcard') return `*='${t.symbol}'`; + if (t.kind === 'blank') return 'B'; + return `'${t.symbol}'`; +} + +function renderWriteToken(t: WriteToken): string { + if (t.kind === 'erase') return 'E'; + if (t.kind === 'literal') return `'${t.symbol}'`; + if (!t.readContext) return 'K'; + if (t.readContext.isBlank) return 'K=B'; + return `K='${t.readContext.symbol}'`; +} + +export function formatStepNotation( + reads: readonly string[] | null, + commands: readonly StepCommand[], + blanks: readonly string[], + matchKinds?: readonly ('wildcard' | 'literal')[] | null, +): string { + const tokens = tokenizeStep(reads, commands, blanks, matchKinds); + const writesStr = tokens.writes.map(renderWriteToken).join(','); + const movesStr = tokens.moves.join(','); + const writesPart = `[${writesStr}]/[${movesStr}]`; + if (tokens.reads === null) return writesPart; + const readsStr = tokens.reads.map(renderReadToken).join(','); + return `[${readsStr}] → ${writesPart}`; +} +``` + +`renderReadToken` / `renderWriteToken` are internal helpers — NOT exported. Consumers wanting custom rendering call `tokenizeStep` directly and write their own per-cell renderers (HTML spans, ANSI escape codes, etc.). + +- [ ] **Step 5: Add `formatTape`** + +```ts +export function formatTape(tape: TapeSnapshot): string { + return tape.symbols + .map((sym, i) => (i === tape.position ? `[${sym}]` : sym)) + .join(''); +} +``` + +Verbatim port of demo's `formatTape`. + +- [ ] **Step 6: Typecheck** + +```sh +npm --prefix /Users/mellonis/Developer/mellonis-workspace/machines/turing-machine-js run typecheck +``` + +Expected: clean. + +--- + +## Task 2: Add tests + +**Files:** `packages/visuals/src/format.spec.ts` + +Three test groups: `formatStepNotation` (string output assertions), `tokenizeStep` (structured-token assertions), `formatTape` (string output). + +- [ ] **Step 1: `formatStepNotation` cases (10 cases)** + +Each asserts the exact output string. Mirrors demo's encoding rules byte-for-byte; serves as the regression net that catches any drift in the token→string render path. + +- Single-tape literal: `formatStepNotation(['a'], [{symbol: 'b', movement: 'R'}], [' '], ['literal'])` → `"['a'] → ['b']/[R]"` +- Single-tape blank read (non-wildcard): `([' '], [{...}], [' '], ['literal'])` → `[B] → ...` +- Single-tape wildcard read: `(['a'], [...], [' '], ['wildcard'])` → `"[*='a'] → ..."` (wildcard marker, NOT `B` shortcut) +- Single-tape wildcard read with blank: `([' '], [...], [' '], ['wildcard'])` → `"[*=' '] → ..."` (wildcard preserves literal even when blank) +- Single-tape keep with read: `(['a'], [{symbol: null, movement: 'S'}], [' '], ['literal'])` → `"['a'] → [K='a']/[S]"` +- Single-tape keep with blank read: `([' '], [{symbol: null, ...}], [' '], ['literal'])` → `"[B] → [K=B]/[S]"` +- Single-tape erase: `(['a'], [{symbol: ' ', movement: 'L'}], [' '], ['literal'])` → `"['a'] → [E]/[L]"` +- Manual-Apply (no reads): `formatStepNotation(null, [{symbol: 'b', movement: 'R'}], [' '], null)` → `"['b']/[R]"` (no `[reads] →` prefix) +- Manual-Apply with keep: `formatStepNotation(null, [{symbol: null, ...}], [' '], null)` → `"[K]/[S]"` (bare K — no read context) +- Multi-tape mixed wildcard + literal: `(['a','x'], [...,...], [' ',' '], ['wildcard','literal'])` → `"[*='a','x'] → ['b','y']/[R,S]"` +- Omitted matchKinds: defaults to literal everywhere, no wildcards. + +- [ ] **Step 2: `tokenizeStep` cases (10 cases)** + +Each asserts the structured shape. These prove the tokenizer is correct independently of the string renderer; consumers who skip the string renderer rely on these. + +- Literal read + literal write: full `StepTokens` object with `{ kind: 'literal', symbol: ... }` variants. +- Blank read (non-wildcard) → `{ kind: 'blank' }`. +- Wildcard read → `{ kind: 'wildcard', symbol: ' ' }` (no blank shortcut). +- Keep with non-blank read → `{ kind: 'keep', readContext: { symbol: 'a', isBlank: false } }`. +- Keep with blank read → `{ kind: 'keep', readContext: { symbol: ' ', isBlank: true } }`. +- Erase → `{ kind: 'erase' }`. +- Manual Apply (`reads === null`): `tokens.reads === null`, `keep` writes carry no `readContext`. +- Omitted matchKinds: reads are all `{ kind: 'literal' | 'blank' }` (no wildcards). +- Multi-tape: arrays line up per index across reads/writes/moves; mixed wildcard + literal + blank tokens. +- Consistency invariant: `formatStepNotation(args)` for given args equals the manual string render of `tokenizeStep(args).{reads,writes,moves}` — confirms the two surfaces describe the same step. (One sentinel case is enough.) + +- [ ] **Step 3: `formatTape` cases (5 cases)** + +- Head in middle: `formatTape({symbols: ['a','b','c'], position: 1})` → `"a[b]c"` +- Head at start: `{symbols: ['a','b'], position: 0}` → `"[a]b"` +- Head at end: `{symbols: ['a','b'], position: 1}` → `"a[b]"` +- Single-cell: `{symbols: ['x'], position: 0}` → `"[x]"` +- Blank glyph rendered literally: `{symbols: [' ','a',' '], position: 0}` → `"[ ]a "` (spaces pass through literally, joined with empty string — NOT space-separated) + +- [ ] **Step 4: Run the spec + full verify** + +```sh +npx --prefix /Users/mellonis/Developer/mellonis-workspace/machines/turing-machine-js vitest run packages/visuals/src/format.spec.ts +npm --prefix /Users/mellonis/Developer/mellonis-workspace/machines/turing-machine-js test +npm --prefix /Users/mellonis/Developer/mellonis-workspace/machines/turing-machine-js run typecheck +npm --prefix /Users/mellonis/Developer/mellonis-workspace/machines/turing-machine-js run lint +``` + +All green. Total visuals format spec count = 8 (alpha.6 `formatCommand` + `formatStep`) + 10 (`formatStepNotation`) + 10 (`tokenizeStep`) + 5 (`formatTape`) = **33**. Full repo test count goes from 616 → **642**. + +--- + +## Task 3: Wire exports + +**Files:** `packages/visuals/src/index.ts` + +- [ ] **Step 1: Merge into the existing format export line** + +Replace: +```ts +export { formatCommand, formatStep } from './format'; +``` + +With: +```ts +export { + formatCommand, + formatStep, + formatStepNotation, + formatTape, + tokenizeStep, + type StepCommand, + type ReadToken, + type WriteToken, + type StepTokens, +} from './format'; +``` + +- [ ] **Step 2: Verify** + +Re-run typecheck + a build to confirm `dist/` includes the new symbols: + +```sh +npm --prefix /Users/mellonis/Developer/mellonis-workspace/machines/turing-machine-js run build +``` + +Expected: clean. `dist/format.d.ts` exports all new functions + types; `dist/index.{cjs,mjs,d.ts}` re-export them. `Built @turing-machine-js/visuals Node entries` confirms Rollup runs cleanly. + +--- + +## Task 4: Bump visuals + CHANGELOG + commit + +**Files:** `packages/visuals/package.json`, `packages/visuals/CHANGELOG.md`, `package-lock.json`. + +- [ ] **Step 1: Bump version** + +Edit `packages/visuals/package.json`: +```diff +- "version": "7.0.0-alpha.6", ++ "version": "7.0.0-alpha.6.1", +``` + +Peer-dep range stays `"@turing-machine-js/machine": "^7.0.0-alpha.6"` — semver-prerelease caret already accepts alpha.6.1+. + +Resync `package-lock.json`: +```sh +npm --prefix /Users/mellonis/Developer/mellonis-workspace/machines/turing-machine-js install --package-lock-only +``` + +Only the visuals workspace entry in `package-lock.json` should change. + +- [ ] **Step 2: Add the CHANGELOG entry** + +`packages/visuals/CHANGELOG.md` — add above `[7.0.0-alpha.6]`: + +```markdown +## [7.0.0-alpha.6.1] - 2026-05-30 + +### Added + +- `formatStepNotation(reads, commands, blanks, matchKinds?)` — engine edge-label format primitive, matches `toMermaid` emit byte-for-byte. Per-cell encoding: literal `'X'`, blank shortcut `B`, wildcard `*='X'` (shows what `ifOtherSymbol` caught), keep-with-concrete-symbol `K='X'` / `K=B`, erase `E`. Multi-tape comma-separated within one outer bracket per role. Pass `reads === null` for the manual-Apply path — output collapses to `[writes]/[moves]`. Folds in the richness machines-demo's local `format.ts` had so demo can drop the local helper and call visuals's primitive directly. +- `tokenizeStep(reads, commands, blanks, matchKinds?)` + `ReadToken` / `WriteToken` / `StepTokens` types — renderer-agnostic structured form of one step. Same input contract as `formatStepNotation`; returns discriminated-union tokens per cell. Consumers wanting custom rendering (HTML spans with CSS classes, ANSI-colored terminal output, alternative move vocabulary, clickable cells) walk the tokens themselves. `formatStepNotation` is refactored to be a thin string renderer over `tokenizeStep` (output byte-identical). +- `formatTape(tape)` — inline tape rendering with the head bracketed in place (`a[b]c`). +- `StepCommand` — plain per-tape command shape (`{ movement: 'L' | 'R' | 'S'; symbol: string | null }`) consumed by `formatStepNotation` and `tokenizeStep`. Distinct from engine's `TapeCommand` class; matches the shape machines-demo's worker boundary exposes. + +### Compatibility + +- alpha.6's `formatCommand(tapeCommand)` and `formatStep(m)` unchanged. Additive release. +- Engine + builder + library-binary-numbers + library-binary-numbers-bare stay at `7.0.0-alpha.6` — no functional changes there. Visuals-only follow-up patch; the workspace's lockstep convention is for coordinated peer-dep widening when engine APIs break, not for additive consumer-package enhancements. +- Peer dep `@turing-machine-js/machine: ^7.0.0-alpha.6` unchanged (semver-prerelease caret already accepts `alpha.6.1`). +``` + +- [ ] **Step 3: Verify, then commit** + +`git -C ... status` — should show: `packages/visuals/package.json`, `packages/visuals/CHANGELOG.md`, `package-lock.json`, plus the Task 1-3 source/test/index changes. + +Commit in two or three focused commits for a clean bisect. The actual PR (#221) used: + +1. `feat(visuals): add formatStepNotation + formatTape primitives (#204)` — Task 1 (partial: types, formatStepNotation, formatTape) + Task 2 (tests for those) + Task 3 (export) + plan doc +2. `release(visuals): 7.0.0-alpha.6.1 — formatter enhancements` — Task 4 (version + CHANGELOG + lockfile) +3. `feat(visuals): add tokenizeStep + ReadToken/WriteToken/StepTokens for renderer-agnostic step rendering (#204)` — Task 1 (remainder: tokens, tokenizeStep, refactored formatStepNotation to delegate) + Task 2 (tokenizer tests) + Task 3 (extended export) + CHANGELOG amend + +The 3rd commit is the tokenizer fold — added mid-execution after the design discussion landed on Option B (structured tokens) as the right shape. A clean redo of this plan would land all of it as commit (1), but per the user's no-amend rule the fold became its own commit. + +- [ ] **Step 4: Push** + +```sh +git -C /Users/mellonis/Developer/mellonis-workspace/machines/turing-machine-js push -u origin feat/visuals-alpha7-formatters +``` + +--- + +## Task 5: Open PR + +- [ ] **Step 1: Open PR against `v7`** + +`gh pr create --repo mellonis/turing-machine-js --base v7 --head feat/visuals-alpha7-formatters --title "feat(visuals): alpha.6.1 — formatStepNotation + tokenizeStep + formatTape"` with a body covering: summary (formatter richness + structured-token surface), public API delta (the full block from this plan's File Structure section), versioning (visuals-only bump, alpha.6.1 over alpha.7), test plan, publish + follow-up notes pointing at the machines-demo cleanup plan. + +The actual PR landed at https://github.com/mellonis/turing-machine-js/pull/221. + +--- + +## Self-review checklist + +- [ ] `formatStepNotation` output is byte-identical to a from-scratch port of demo's helper — verified by running demo's existing format-spec assertions against the refactored implementation (24/24 pass unchanged). +- [ ] `tokenizeStep` returns the expected discriminated-union shape for every encoding branch (literal, blank, wildcard reads; literal, erase, keep writes; keep carries `readContext` when reads available, omits when manual Apply). +- [ ] `formatTape` body matches demo's verbatim. +- [ ] alpha.6 surface (`formatCommand`, `formatStep`) unchanged. +- [ ] CHANGELOG entry under `[7.0.0-alpha.6.1]`, dated `2026-05-30`, lists all four new public symbols (`formatStepNotation`, `tokenizeStep`, `formatTape`, `StepCommand`) plus the three token types. +- [ ] package.json at `7.0.0-alpha.6.1`; peer dep unchanged at `^7.0.0-alpha.6`. +- [ ] No Claude attribution footers in commits. +- [ ] Tests cover both string output (10 `formatStepNotation` cases + 5 `formatTape`) AND structured tokens (10 `tokenizeStep` cases including a consistency invariant that ties the two surfaces together). + +--- + +## After this lands + +1. **Publish** — `cd turing-machine-js && npx lerna publish from-package --dist-tag next --yes`. Only visuals publishes (engine + builder + libs are already on the registry at alpha.6, lerna skips them). Same catch-up-publish flow as alpha.6's initial visuals publish. +2. **Tagging:** **no new GH release** for `v7.0.0-alpha.6.1`. The git tag scheme tracks engine versions; visuals-only releases ship under the existing engine tag (alpha.6 in this case). Optionally edit the `v7.0.0-alpha.6` GH release body to add a short follow-up note pointing at visuals's CHANGELOG. +3. **Resume the machines-demo cleanup PR plan.** With alpha.6.1 published, the cleanup PR additionally: + - Bumps the demo's visuals dep to `^7.0.0-alpha.6.1` (via `npm install @turing-machine-js/visuals@next`). + - Deletes demo's local `formatStepNotation` (was internal) — call sites in `commandsEntry` switch to importing from `@turing-machine-js/visuals`. + - Deletes demo's local `formatTape` export — callers switch to visuals's. + - Keeps `tapesEntry`, `commandsEntry`, `CommandsApplication` in demo (LogEntry assembly is demo-specific). + - Could also adopt `tokenizeStep` for any demo paths that want richer rendering than the default string format — purely opportunistic, not required for the cleanup. + +--- + +## Out of scope + +- **Engine bump.** No engine changes in this alpha. +- **Backwards-compat shims for the alpha.6 formatters.** `formatCommand(tapeCommand)` and `formatStep(m)` stay live, unchanged. Future deprecation, if any, is a separate decision. +- **GH release for `v7.0.0-alpha.6.1`.** Skipped — tag scheme tracks engine versions, and the engine isn't bumping. Optionally edit the existing `v7.0.0-alpha.6` release body to note the visuals follow-up. +- **`renderReadToken` / `renderWriteToken` as public exports.** They're internal helpers backing `formatStepNotation`'s string rendering. Consumers wanting custom rendering call `tokenizeStep` and walk tokens themselves — exposing the per-cell string renderers would just be a confusing partial-customization API ("you can change the join but not the tokens? or the tokens but not the join?"). Add later only if a real consumer asks. +- **Demo migration.** That's the cleanup PR's job, after this publishes. diff --git a/docs/superpowers/plans/2026-05-30-visuals-package-extract.md b/docs/superpowers/plans/2026-05-30-visuals-package-extract.md new file mode 100644 index 0000000..871332c --- /dev/null +++ b/docs/superpowers/plans/2026-05-30-visuals-package-extract.md @@ -0,0 +1,588 @@ +# `@turing-machine-js/visuals` package extraction — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Extract machines-demo's pure highlight/graph-indexing modules + rules doc into a new lockstep-published package `@turing-machine-js/visuals`, AND ship the initial `recordSnippet` + `Snippet`/`Frame` schema so v1 publishes with a complete API. + +**Scope split:** Originally drafted as extraction-only (issue step 1) with `recordSnippet` as a follow-up PR (step 3). **Revised mid-implementation to fold step 3 in** — so the first published alpha carries the complete v1 API ready to feed the landing-page work (#79). Step 2 (machines-demo cleanup) remains a separate follow-up in the machines-demo repo. See Tasks 10–13 below. + +**Architecture:** New `packages/visuals/` workspace, peer dep on `@turing-machine-js/machine` (`Graph` type only — no runtime dependency). Pure TypeScript, no DOM, no Svelte. Modules are line-for-line moves: `TuringGraph` local alias becomes `import { Graph } from '@turing-machine-js/machine'`; everything else unchanged. The rules doc moves under `packages/visuals/docs/`. machines-demo's local copies + the doc-source are deleted in a follow-up demo PR (issue step 2, **not** this PR — keeps the extract PR self-contained and reviewable against engine tests only). + +**Tech Stack:** TypeScript (project references), Vitest, npm workspaces, Lerna. Standard repo commands (`npm test`, `npm run lint`, `npm run typecheck`, `npm run build`) automatically pick up the new workspace. + +**Branch:** `feat/204-visuals-extract` (off `v7`). Issue: [mellonis/turing-machine-js#204](https://github.com/mellonis/turing-machine-js/issues/204). + +**Coverage floor (must hold):** 100% statements / 100% branches / 100% functions / 100% lines. The extracted modules are pure logic with no I/O; demo's existing test suites already hit 100% on these files. Drop is unexpected and indicates an extraction error. + +--- + +## Decisions (locked in this plan) + +- **Package name: `@turing-machine-js/visuals`.** Issue #204 lists this as an open question between `visuals` / `highlight` / `graph-visuals`. Going with `visuals` — broadest, room for `recordSnippet` + future visual primitives without renaming. +- **No DOM applier sub-export in v1.** The package stays purely renderer-agnostic. A future `@turing-machine-js/visuals/dom` sub-path can be added non-breakingly when there's a concrete consumer asking for one. Out of scope here. +- **Snippet schema versioning policy.** Additive fields don't bump `version`; breaking shape changes bump the integer. Documented in the README addition that ships with Task 13. +- **`Frame` shape (locked).** Per-iter capture: `{ step: number; tape: TapeSnapshot[]; highlight: GraphHighlight | null; log?: string }`. `step` is 0-indexed iter (frame 0 = initial state, before any transition). `tape` is per-tape array (single-tape: length 1). `highlight` is the GraphHighlight to render for this frame (null = no active highlight). `log` is optional pre-formatted text; consumers can render it as a caption / status line. Users wanting a different shape pass `log` callback for the string only — Frame's structural fields stay fixed (consumers' renderers depend on the shape). +- **`Snippet` shape (locked, no `engine` field per #204 comment).** `{ version: 1; name?: string; graph: Graph; alphabets: string[][]; frames: Frame[] }`. `frames.length === stepsApplied + 1` — frame 0 is the initial state. `alphabets` is per-tape (single-tape: length 1). +- **`recordSnippet` signature (locked).** `recordSnippet({ machine, initialState, graph, alphabets, name?, maxSteps?, log? }): Snippet`. Caller supplies pre-built `machine`, `initialState`, pre-computed `graph` (via `State.toGraph`), and `alphabets`. Caller chooses which engine (turing-machine-js or post-machine-js) — recorder is engine-agnostic, just runs `machine.runStepByStep({ initialState, stepsLimit: maxSteps })` and captures per iter. `log` callback (optional) receives `(machineState, prevMachineState | null)` and returns the line text. Default frame has `log: undefined`. +- **Default formatter primitives.** Ship `formatCommand(command)` + `formatStep(machineState, prev?)` as composable helpers, exported alongside `recordSnippet`. Format mirrors the engine's edge-label notation (`[reads] → [writes]/[moves]`) so a logged step lines up with the rendered graph's edge. Users compose their own `log` callback by combining these or write their own from scratch. +- **`TapeSnapshot` type moves into visuals.** `{ symbols: string[]; position: number }` — pure data, renderer-agnostic, lives in `packages/visuals/src/types.ts` alongside `GraphHighlight`. Demo's local copy stays during this PR (cleaned up in the step-2 follow-up). +- **`graphHighlightDerivation.ts` does NOT extract.** Its `ExecutionMode` union mirrors machines-demo's MachineView state machine (`'DEMO' | 'MANUAL' | 'RUNNING_AUTO' | …`) — demo orchestration, not engine semantics. Stays in machines-demo and imports `bareIdOf` from the published package post-extract. +- **`Snippet.engine` field is dropped (per #204 comment 2026-05-29).** Pinned here so PR B inherits the constraint. Engine identity lives at the caller bucket level (`{ turing: Snippet[], post: Snippet[] }`), not on the artifact. +- **No content rewrites during the move.** Rules doc moves verbatim — any rewording is a separate edit later. Keeps this PR's diff a clean move. +- **machines-demo cleanup (issue step 2) is a separate follow-up PR**, in the machines-demo repo, after this lands and lockstep-publishes with the next engine v7 alpha. This PR does NOT touch machines-demo. + +--- + +## File Structure + +``` +packages/visuals/ +├── package.json # NEW — peer dep on @turing-machine-js/machine +├── tsconfig.json # NEW — IDE/typecheck config (mirrors machine's) +├── tsconfig.build.json # NEW — composite-build config (mirrors machine's) +├── README.md # NEW — what this package is, public API summary +├── src/ +│ ├── index.ts # NEW — re-exports public surface +│ ├── highlightOps.ts # MOVED from machines-demo/src/lib/highlightOps.ts +│ ├── graphUtils.ts # MOVED from machines-demo/src/lib/graphUtils.ts +│ ├── graphUtils.spec.ts # MOVED + .test.ts → .spec.ts (colocated next to source per repo convention) +│ ├── graphIndexes.ts # MOVED from machines-demo/src/lib/graphIndexes.ts +│ ├── applyHighlight.ts # MOVED from machines-demo/src/lib/applyHighlight.ts +│ ├── applyHighlight.spec.ts # MOVED + colocated +│ ├── graphFixtures.spec.ts # MOVED + colocated +│ └── fixtures/ # MOVED — fixture JSONs (graphs/*.json) used by graphFixtures.spec.ts +└── docs/ + └── graph-highlight-and-breakpoints.md # MOVED from machines-demo/docs/ + +tsconfig.build.json # MODIFY — references packages/visuals/tsconfig.build.json (per repo convention — all entries reference the build variant) +package.json # MODIFY — extend "typecheck" script to include packages/visuals/tsconfig.json +``` + +**Convention notes (verified against `packages/machine`):** +- **Dual tsconfig** per package: `tsconfig.json` (extends root, `rootDir: ".."`, includes `paths` alias map) for IDE / standalone typecheck; `tsconfig.build.json` (extends local `./tsconfig.json`, `rootDir: "src"`, `composite: true`, `exclude: ["**/*.spec.ts", "dist"]`) for the project-references build. +- **Tests colocated** as `*.spec.ts` next to source under `src/` (per CLAUDE.md). No top-level `tests/` dir. Fixtures live wherever the colocated spec needs them — under `src/fixtures/` if the test reads from it. + +Public API exported from `packages/visuals/src/index.ts`: + +```ts +// Types +export type { + NodeKey, HighlightClass, HighlightOps, IndicatorOps, RecordedOp, +} from './highlightOps'; +export type { GraphIndexes } from './graphIndexes'; + +// Functions +export { recordingOps } from './highlightOps'; +export { bareIdOf, highlightExpand, equivalentIds } from './graphUtils'; +export { indexGraph } from './graphIndexes'; +export { applyHighlight, applyIndicator } from './applyHighlight'; +``` + +Note: machines-demo's local `TuringGraph` alias resolves to `Graph` from `@turing-machine-js/machine`. The move replaces every `import { …, type TuringGraph } from './types.ts'` with `import { type Graph } from '@turing-machine-js/machine'` and renames the type at use-sites. `TuringGraph` is NOT re-exported from visuals — callers import `Graph` directly from `@turing-machine-js/machine`. + +--- + +## Task 1: Scaffold `packages/visuals/` + +**Files:** +- Create: `packages/visuals/package.json` +- Create: `packages/visuals/tsconfig.json` +- Create: `packages/visuals/README.md` +- Create: `packages/visuals/src/index.ts` +- Modify: `tsconfig.build.json` +- Modify: `package.json` (root — extend `typecheck` script) + +- [ ] **Step 1: Write `packages/visuals/package.json`** + +```json +{ + "name": "@turing-machine-js/visuals", + "version": "7.0.0-alpha.6", + "description": "Pure highlight + graph-indexing logic for @turing-machine-js/machine — no DOM, no renderer.", + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" + } + }, + "files": ["dist", "src", "docs", "README.md", "CHANGELOG.md", "LICENSE"], + "scripts": { + "prepublishOnly": "npm run --workspaces=false -w @turing-machine-js/visuals build" + }, + "peerDependencies": { + "@turing-machine-js/machine": "^7.0.0-alpha.6" + }, + "publishConfig": { + "access": "public" + } +} +``` + +> **Verify before committing:** version field matches the current engine alpha tag. Cross-check `packages/machine/package.json`'s version. Lerna's `version` command keeps these in lockstep on the next bump. + +- [ ] **Step 2: Write `packages/visuals/tsconfig.json`** + +Mirror `packages/machine/tsconfig.json`'s structure. Read it first, then create: + +```json +{ + "extends": "@tsconfig/recommended/tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "declarationMap": true, + "composite": true, + "module": "esnext", + "moduleResolution": "bundler", + "target": "es2022", + "strict": true + }, + "references": [ + { "path": "../machine" } + ], + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "tests"] +} +``` + +> Adjust to match `packages/machine/tsconfig.json` exactly if it differs — repo convention overrides the example above. + +- [ ] **Step 3: Write `packages/visuals/README.md`** + +```markdown +# @turing-machine-js/visuals + +Pure highlight + graph-indexing logic for [`@turing-machine-js/machine`](../machine). No DOM, no Svelte, no Mermaid — consumers bring their own renderer and DOM applier. + +## Scope + +Types and pure functions for: +- Indexing an engine `Graph` for wrapper/bare lookup (`indexGraph`, `bareIdOf`, `highlightExpand`). +- Applying highlight + indicator operations against a renderer-agnostic `HighlightOps` interface (`applyHighlight`, `applyIndicator`). + +See [`docs/graph-highlight-and-breakpoints.md`](./docs/graph-highlight-and-breakpoints.md) for the full set of rules these functions satisfy. + +## Versioning + +Lockstep with `@turing-machine-js/machine`. + +## Install + +\`\`\`sh +npm install @turing-machine-js/visuals @turing-machine-js/machine +\`\`\` +``` + +- [ ] **Step 4: Write `packages/visuals/src/index.ts` (empty for now)** + +```ts +// Public API — populated as modules are moved in Tasks 2–5. +export {}; +``` + +- [ ] **Step 5: Modify `tsconfig.build.json` — add reference** + +Read the file first; then append `{ "path": "packages/visuals" }` to its `references` array. + +- [ ] **Step 6: Modify root `package.json` — extend `typecheck` script** + +Read the current value: +``` +"typecheck": "tsc --noEmit -p packages/machine/tsconfig.json && tsc --noEmit -p packages/builder/tsconfig.json && tsc --noEmit -p packages/library-binary-numbers/tsconfig.json && tsc --noEmit -p packages/library-binary-numbers-bare/tsconfig.json" +``` + +Add `&& tsc --noEmit -p packages/visuals/tsconfig.json` at the end. + +- [ ] **Step 7: Verify scaffolding compiles** + +Run: `npm install && npm run typecheck && npm run build` +Expected: green. No new tests yet; existing engine tests still pass. + +- [ ] **Step 8: Commit** + +```sh +git add packages/visuals tsconfig.build.json package.json package-lock.json +git commit -m "feat(visuals): scaffold @turing-machine-js/visuals package (#204)" +``` + +--- + +## Task 2: Move `highlightOps.ts` + +**Files:** +- Read: `../machines-demo/src/lib/highlightOps.ts` (source — outside this repo; copy contents, do NOT modify the source from this repo) +- Create: `packages/visuals/src/highlightOps.ts` +- Modify: `packages/visuals/src/index.ts` + +- [ ] **Step 1: Read source** + +Read `../machines-demo/src/lib/highlightOps.ts` in full. It has no external imports beyond TypeScript stdlib types — a pure types + recordingOps module. Direct move. + +- [ ] **Step 2: Create the file at the new location** + +Write `packages/visuals/src/highlightOps.ts` with the source contents verbatim. No import changes needed (it has none). + +- [ ] **Step 3: Add to public exports** + +In `packages/visuals/src/index.ts`: +```ts +export type { NodeKey, HighlightClass, HighlightOps, IndicatorOps, RecordedOp } from './highlightOps'; +export { recordingOps } from './highlightOps'; +``` + +- [ ] **Step 4: Typecheck** + +Run: `npm run typecheck` +Expected: green. + +- [ ] **Step 5: Commit** + +```sh +git add packages/visuals/src +git commit -m "feat(visuals): move highlightOps from machines-demo (#204)" +``` + +--- + +## Task 3: Move `graphUtils.ts` + test + +**Files:** +- Read: `../machines-demo/src/lib/graphUtils.ts`, `../machines-demo/src/lib/graphUtils.test.ts` +- Create: `packages/visuals/src/graphUtils.ts` +- Create: `packages/visuals/tests/graphUtils.spec.ts` +- Modify: `packages/visuals/src/index.ts` + +- [ ] **Step 1: Read source files** + +Read both `graphUtils.ts` and `graphUtils.test.ts` from machines-demo. Note imports: +- Source imports `type TuringGraph` from local `./types.ts` (demo-local alias). +- Test imports the source + may import test fixtures. + +- [ ] **Step 2: Create `packages/visuals/src/graphUtils.ts`** + +Copy source contents. Replace the line: +```ts +import type { TuringGraph } from './types.ts'; +``` +with: +```ts +import type { Graph } from '@turing-machine-js/machine'; +``` +Then rename `TuringGraph` → `Graph` at every use-site in this file. + +- [ ] **Step 3: Create `packages/visuals/tests/graphUtils.spec.ts`** + +Copy test contents. Update import paths: +- `from '../src/lib/graphUtils.ts'` → `from '../src/graphUtils'` +- Any `TuringGraph` → `Graph` from `@turing-machine-js/machine` + +If the test references fixtures, defer their move to Task 5 (graphFixtures) — for now, gate any fixture-dependent test on the file existing or comment it out with a `// TODO Task 5: restore after fixtures move` marker. If no fixture deps, the test is self-contained. + +- [ ] **Step 4: Add to public exports** + +In `packages/visuals/src/index.ts`, append: +```ts +export { bareIdOf, highlightExpand, equivalentIds } from './graphUtils'; +``` + +- [ ] **Step 5: Run the moved test** + +Run: `npx vitest run packages/visuals/tests/graphUtils.spec.ts` +Expected: same passing count as the original in machines-demo (or skipped count for fixture-deferred tests). + +- [ ] **Step 6: Typecheck** + +Run: `npm run typecheck` +Expected: green. + +- [ ] **Step 7: Commit** + +```sh +git add packages/visuals +git commit -m "feat(visuals): move graphUtils + test (#204)" +``` + +--- + +## Task 4: Move `graphIndexes.ts` (+ test if present) + +**Files:** +- Read: `../machines-demo/src/lib/graphIndexes.ts` +- Create: `packages/visuals/src/graphIndexes.ts` +- Modify: `packages/visuals/src/index.ts` + +- [ ] **Step 1: Read source** + +Read `graphIndexes.ts`. Note the imports — likely `TuringGraph` from local types. + +- [ ] **Step 2: Create `packages/visuals/src/graphIndexes.ts`** + +Copy contents, replace `TuringGraph` import with `import type { Graph } from '@turing-machine-js/machine'`, rename use-sites. + +- [ ] **Step 3: Add to public exports** + +```ts +export type { GraphIndexes } from './graphIndexes'; +export { indexGraph } from './graphIndexes'; +``` + +- [ ] **Step 4: Verify** + +Run: `npm run typecheck` +Expected: green. + +> **Note:** machines-demo does NOT ship a standalone `graphIndexes.test.ts` (only an `applyHighlight.test.ts` and `graphUtils.test.ts`). `indexGraph` is exercised transitively by the applyHighlight tests in Task 6. No dedicated test added in this PR — extraction parity is preserved. + +- [ ] **Step 5: Commit** + +```sh +git add packages/visuals +git commit -m "feat(visuals): move graphIndexes (#204)" +``` + +--- + +## Task 5: Move `graphFixtures.test.ts` + fixture JSONs + +**Files:** +- Read: `../machines-demo/src/lib/graphFixtures.test.ts` + any `tests/fixtures/graphs/*.json` referenced +- Create: `packages/visuals/tests/graphFixtures.spec.ts` +- Create: `packages/visuals/tests/fixtures/graphs/*.json` (one file per fixture) + +- [ ] **Step 1: Read test + identify fixture files** + +Read `graphFixtures.test.ts`. Find every `import` or `readFileSync` / `JSON.parse` of a fixture path. List them. + +- [ ] **Step 2: Copy each fixture JSON file verbatim** + +For each fixture path discovered in Step 1, copy the file from machines-demo to `packages/visuals/tests/fixtures/graphs/.json`. Do not modify the JSON — these are engine-emit snapshots and must roundtrip byte-for-byte. + +- [ ] **Step 3: Create the test file** + +Copy `graphFixtures.test.ts` contents to `packages/visuals/tests/graphFixtures.spec.ts`. Update: +- Source import paths (`from '../lib/...'` → `from '../src/...'`). +- Fixture path strings (`./fixtures/graphs/...` should already resolve from the new test location since fixtures are colocated under `tests/fixtures/`). +- Any `import * as turing from '@turing-machine-js/machine'` already correct. +- Type rename: `TuringGraph` → `Graph`. + +- [ ] **Step 4: Run the fixture test** + +Run: `npx vitest run packages/visuals/tests/graphFixtures.spec.ts` +Expected: same passing count as in machines-demo. Fixture roundtrip MUST pass — failure here indicates engine-emit drift OR an extraction-time corruption of the JSON. + +- [ ] **Step 5: Commit** + +```sh +git add packages/visuals/tests +git commit -m "feat(visuals): move graphFixtures test + fixture JSONs (#204)" +``` + +--- + +## Task 6: Move `applyHighlight.ts` + test + +**Files:** +- Read: `../machines-demo/src/lib/applyHighlight.ts`, `../machines-demo/src/lib/applyHighlight.test.ts` +- Create: `packages/visuals/src/applyHighlight.ts` +- Create: `packages/visuals/tests/applyHighlight.spec.ts` +- Modify: `packages/visuals/src/index.ts` + +- [ ] **Step 1: Read source files** + +Read both. Note all imports — likely: +- `import type { TuringGraph } from './types.ts'` +- `import { ... } from './graphUtils.ts'` +- `import { ... } from './graphIndexes.ts'` +- `import { ... } from './highlightOps.ts'` + +- [ ] **Step 2: Create `packages/visuals/src/applyHighlight.ts`** + +Copy source. Replace: +- `TuringGraph` import → `import type { Graph } from '@turing-machine-js/machine'` + rename uses. +- Local imports from `./graphUtils.ts` / `./graphIndexes.ts` / `./highlightOps.ts` → drop the `.ts` extension only if needed for the package's resolution (match the convention used in Tasks 2–4). + +- [ ] **Step 3: Create `packages/visuals/tests/applyHighlight.spec.ts`** + +Copy test. Update import paths to point at `../src/...`. Rename `TuringGraph` → `Graph`. + +- [ ] **Step 4: Add to public exports** + +```ts +export { applyHighlight, applyIndicator } from './applyHighlight'; +``` + +- [ ] **Step 5: Run the test** + +Run: `npx vitest run packages/visuals/tests/applyHighlight.spec.ts` +Expected: all original tests pass at new location. Same count as in machines-demo. + +- [ ] **Step 6: Restore any fixture-deferred tests from Task 3** + +If Task 3 commented out fixture-dependent graphUtils tests, uncomment them now and verify they pass. Fixtures are in place since Task 5. + +- [ ] **Step 7: Full test + typecheck + lint** + +Run: `npm test && npm run typecheck && npm run lint` +Expected: green across all four packages (machine, builder, library-binary-numbers, library-binary-numbers-bare) + the new visuals. + +- [ ] **Step 8: Commit** + +```sh +git add packages/visuals +git commit -m "feat(visuals): move applyHighlight + applyIndicator + test (#204)" +``` + +--- + +## Task 7: Move the rules doc + +**Files:** +- Read: `../machines-demo/docs/graph-highlight-and-breakpoints.md` +- Create: `packages/visuals/docs/graph-highlight-and-breakpoints.md` + +- [ ] **Step 1: Read the source doc** + +Read `../machines-demo/docs/graph-highlight-and-breakpoints.md` fully (~272 lines). + +- [ ] **Step 2: Copy verbatim** + +Create `packages/visuals/docs/graph-highlight-and-breakpoints.md` with identical contents. NO rewrites, NO scope changes, NO reformatting. If any in-doc cross-link points to a machines-demo path (e.g., `../src/components/MachineGraph.svelte`), leave it intact — fixing those links is part of the follow-up machines-demo cleanup PR (issue step 2). A `git mv`-shaped change preserves blame across the move (run `git log --follow` on the new path to verify). + +- [ ] **Step 3: Verify the README link resolves** + +The `packages/visuals/README.md` from Task 1 links to `./docs/graph-highlight-and-breakpoints.md` — confirm the file is at that path. + +- [ ] **Step 4: Commit** + +```sh +git add packages/visuals/docs +git commit -m "docs(visuals): move highlight + breakpoints rules doc (#204)" +``` + +--- + +## Task 8: Coverage verify + CHANGELOG + +**Files:** +- Create: `packages/visuals/CHANGELOG.md` + +- [ ] **Step 1: Run coverage** + +Run: `npm run test:coverage` +Expected: visuals package at **100% / 100% / 100% / 100%**. If any file is below 100%, identify the missing branch — likely a defensive `null` check from the demo's original code. Either: +- Add a missing test case if the branch is reachable. +- Document the unreachable branch in the file (one-line comment) and verify reviewers accept it. + +> **Do NOT lower the floor.** A drop below 100% means extraction-time scope mismatch; investigate before committing. + +- [ ] **Step 2: Write `packages/visuals/CHANGELOG.md`** + +Per repo convention, every release-bound package needs a CHANGELOG entry up front: + +```markdown +# Changelog + +All notable changes to this package will be documented in this file. + +## [Unreleased] + +### Added + +- Initial extraction from machines-demo: `Highlight`/`HighlightOps`/`GraphIndexes` types, `indexGraph`, `applyHighlight`, `applyIndicator`, `bareIdOf`, `highlightExpand`, `equivalentIds`, `recordingOps`. Rules doc moved into `docs/graph-highlight-and-breakpoints.md`. Closes mellonis/turing-machine-js#204 (extraction step; recordSnippet follows in a separate PR). +``` + +> The `[Unreleased]` heading converts to `[X.Y.Z] - YYYY-MM-DD` during the release-PR pass (per workspace `CLAUDE.md` "CHANGELOG.md is part of every v-bump PR" rule). Not bumped in this PR — version bump is part of the next engine alpha release PR that includes visuals. + +- [ ] **Step 3: Commit** + +```sh +git add packages/visuals/CHANGELOG.md +git commit -m "docs(visuals): add CHANGELOG with extraction entry (#204)" +``` + +--- + +## Task 9: Open PR + +- [ ] **Step 1: Push the branch** + +```sh +git push -u origin feat/204-visuals-extract +``` + +- [ ] **Step 2: Open the PR against `v7`** + +```sh +gh pr create \ + --repo mellonis/turing-machine-js \ + --base v7 \ + --head feat/204-visuals-extract \ + --title "feat(visuals): extract @turing-machine-js/visuals (#204, step 1)" \ + --body "$(cat <<'EOF' +## Summary + +Extracts pure highlight + graph-indexing modules from machines-demo into a new lockstep-published package `@turing-machine-js/visuals`. Step 1 of [#204](https://github.com/mellonis/turing-machine-js/issues/204) — extraction only. \`recordSnippet\` + Snippet/Frame schema lands in a follow-up PR (step 3). machines-demo consumption (step 2) lands in machines-demo as a separate PR after this publishes. + +What's in: + +- New \`packages/visuals/\` workspace (peer dep on \`@turing-machine-js/machine\`, no runtime dep, no DOM). +- Moves: \`highlightOps\`, \`graphUtils\`, \`graphIndexes\`, \`applyHighlight\` + their tests. +- Rules doc moves to \`packages/visuals/docs/graph-highlight-and-breakpoints.md\` verbatim. +- Public API exported from \`packages/visuals/src/index.ts\`. + +What's NOT in (deliberate): + +- \`graphHighlightDerivation.ts\` stays in machines-demo — its \`ExecutionMode\` union is demo-coupled, not engine semantics. +- \`recordSnippet\`, \`Snippet\`, \`Frame\` types — follow-up PR. +- machines-demo cleanup — separate PR in machines-demo. + +Decisions locked in [\`docs/superpowers/plans/2026-05-30-visuals-package-extract.md\`](docs/superpowers/plans/2026-05-30-visuals-package-extract.md): package name = \`visuals\`; no DOM applier sub-export in v1; \`Snippet.engine\` field dropped (per #204 comment 2026-05-29). + +## Test plan + +- [x] \`npm test\` — all packages including new visuals. +- [x] \`npm run typecheck\` — clean across all 5 packages. +- [x] \`npm run lint\` — clean. +- [x] \`npm run test:coverage\` — visuals at 100% / 100% / 100% / 100%. +- [x] \`npm run build\` — clean. +- [x] Fixture roundtrip test passes — engine emit shape unchanged. + +Note: v7-branch PRs do not run CI per repo convention; checks run on the eventual v7 → master integration PR. +EOF +)" +``` + +- [ ] **Step 3: Verify PR URL returned** + +Capture the PR URL and report back. Done. + +--- + +## Self-review checklist + +Before opening the PR, walk this list one more time: + +- [ ] Every moved file's `TuringGraph` references replaced with `Graph` from `@turing-machine-js/machine`. +- [ ] No source files still reference `./types.ts` (machines-demo-local). +- [ ] `packages/visuals/src/index.ts` exports the full public surface (5 types, 7 functions). +- [ ] No machines-demo source files were modified — this PR is engine-side only. +- [ ] Coverage at 100/100/100/100 for the visuals package. +- [ ] CHANGELOG entry under `[Unreleased]` exists and references the issue. +- [ ] All `Task N` commits have clear, scope-prefixed messages (`feat(visuals):` or `docs(visuals):`). +- [ ] No commit includes `🤖 Generated with Claude Code` or any Claude attribution footer (per global CLAUDE.md). +- [ ] No `--no-verify` or signature-skip flags used. + +--- + +## What happens after this lands + +1. **Lockstep publish** with the next engine v7 alpha (bump `7.0.0-alpha.6` → `7.0.0-alpha.7`, etc.) via `lerna publish from-package --dist-tag next`. Same release PR that bumps engine packages bumps visuals. +2. **Follow-up plan** for `recordSnippet` + Snippet/Frame schema (issue #204 step 3). Will be saved at `docs/superpowers/plans/-visuals-recordsnippet.md`. +3. **Follow-up PR in machines-demo** (issue #204 step 2) — drop the local copies of the moved modules + rules doc; depend on `@turing-machine-js/visuals` from npm `next`. +4. **Tiny edit to merged machines-demo spec** (`docs/superpowers/specs/2026-05-27-landing-page-design.md`) — remove the stray `engine` field reference from the snippet-schema decision now that the upstream schema dropped it. diff --git a/docs/superpowers/specs/2026-05-20-tomermaid-wrapper-emit-design.md b/docs/superpowers/specs/2026-05-20-tomermaid-wrapper-emit-design.md new file mode 100644 index 0000000..ab661e7 --- /dev/null +++ b/docs/superpowers/specs/2026-05-20-tomermaid-wrapper-emit-design.md @@ -0,0 +1,373 @@ +# `toMermaid` wrapped-state emit — design comparison + +**Status:** decided — **Variant X with `subgraph` overlay + `idle` entry sentinel + bracketed-tape-block edge labels**. See [Final locked design](#final-locked-design-variant-x-with-subgraph-overlay) at the bottom for the exact diagrams and reader's contract. Each wrapper gets a `subgraph` rectangle labeled `"halt frame"` containing the `[[bare]]` (double-walled wrapper-node) and a halt-marker `(((halt)))`. The dotted `onHalt` edge originates from the `[[bare]]` and crosses the subgraph border to the override target. A stadium-shaped `idle([idle])` sentinel + labeled dotted arrow `idle -. enter .-> sN` is always emitted to mark the initial state. Edge labels: `[reads] → [writes]/[moves]` with each role wrapped in `[…]` (tape-block indicator, always present even single-tape); read cells use `'X'` literal-quoted, `*` (ASCII) for ifOtherSymbol, `B` for the tape's blank; write cells use `K`/`E` plus literal-quoted; movements `L`/`R`/`S`. Alternation is per-pattern-bracket (`['^']|['1']`); compact `['^'|'1']` form is rejected by `fromMermaid` (would read as cross-product in multi-tape). Stack-pushing transitions emit thick `==>` arrows. Decision rationale posted on [#138](https://github.com/mellonis/turing-machine-js/issues/138#issuecomment-4499377933). Implementation in PR [#169](https://github.com/mellonis/turing-machine-js/pull/169). + +**Context.** [#138](https://github.com/mellonis/turing-machine-js/issues/138) — clean up the visually-confusing Mermaid output for `withOverriddenHaltState`-wrapped states. [#139](https://github.com/mellonis/turing-machine-js/issues/139) — bytewise round-trip regression for the wrapper name accumulation, naturally fixed by whichever design we pick. + +Three live variants below — paste the fenced ` ```mermaid ` blocks into anything that renders Mermaid (GitHub preview, mermaid.live, IDE plugin). + +--- + +## Baseline: current v7 emit + +The shape we'd be replacing. Composite name flipped to paren form in [#168](https://github.com/mellonis/turing-machine-js/pull/168), still has all the readability problems #138 calls out: wrapper duplicates the bare's edges, dotted-edge attached to the wrong node visually, three non-halt nodes for what's conceptually a 2-step composition. + +Single wrapper — `scanToX.withOverriddenHaltState(eraseHere)`: + +```mermaid +flowchart TD +%% alphabets: [[" ","a","b","X"]] + s0(((halt))) + s1["scanToX"] + s2["eraseHere"] + s3(("scanToX(eraseHere)")) + s1 -- "X → ·/S" --> s0 + s1 -- "* → ·/R" --> s1 + s2 -- "* → ⌫/S" --> s0 + s3 -- "X → ·/S" --> s0 + s3 -- "* → ·/R" --> s1 + s3 -. onHalt .-> s2 +``` + +Nested — `A.withOverriddenHaltState(B.withOverriddenHaltState(C))` (placeholder transitions): + +```mermaid +flowchart TD + s0(((halt))) + s1["A"] + s2["B"] + s3["C"] + s4["B(C)"] + s5(("A(B(C))")) + s1 -- "..." --> s0 + s2 -- "..." --> s0 + s3 -- "..." --> s0 + s4 -- "..." --> s2 + s5 -- "..." --> s1 + s4 -. onHalt .-> s3 + s5 -. onHalt .-> s4 +``` + +--- + +## Variant X — shape on the bare, no extra wrapper node + +The wrapped state is signalled by **shape only**: `[[name]]` (subroutine, double-walled rectangle) on the bare. The wrapper node is not emitted at all — its identity collapses into the bare's. A dotted `onHalt` edge runs directly from the bare to the override target. + +Single wrapper: + +```mermaid +flowchart TD +%% alphabets: [[" ","a","b","X"]] + s0(((halt))) + s1[["scanToX"]] + s2["eraseHere"] + s1 -- "X → ·/S" --> s0 + s1 -- "* → ·/R" --> s1 + s2 -- "* → ⌫/S" --> s0 + s1 -. onHalt .-> s2 +``` + +Nested — each wrapped state's bare gets `[[…]]`: + +```mermaid +flowchart TD + s0(((halt))) + s1[["A"]] + s2[["B"]] + s3["C"] + s1 -- "..." --> s0 + s2 -- "..." --> s0 + s3 -- "..." --> s0 + s1 -. onHalt .-> s2 + s2 -. onHalt .-> s3 +``` + +**Shared-bare case** — `minusOne` in `library-binary-numbers` is `invertNumber.with(plusOne.with(invertNumber.with(normalizeNumber)))`. The same `invertNumber` instance is the bare of two wrappers. In Variant X this is fine — `invertNumber` is one node, its `[[…]]` shape says "I'm wrapped in this context", and the dotted edge from it points at the relevant override: + +```mermaid +flowchart TD + s0(((halt))) + s1[["invertNumber"]] + s2["plusOne"] + s3["normalizeNumber"] + s1 -- "..." --> s0 + s2 -- "..." --> s0 + s3 -- "..." --> s0 + s1 -. onHalt .-> s2 +``` + +(Only the outermost wrapper is shown — `minusOne = invertNumber.with(W2)` — because that's what the caller passed to `toGraph`. Nested wrappers inside `W2` get their own `[[…]]` shapes and dotted edges as the walk descends.) + +**Round-trip.** Graph carries only the bare's name; wrapper's composite name (`scanToX(eraseHere)`) does **not** appear in any graph node. `fromGraph` reconstructs via `bare.withOverriddenHaltState(override)` which recomputes the composite name fresh — no name accumulation, fixes [#139](https://github.com/mellonis/turing-machine-js/issues/139) automatically. + +**Pros.** Minimal change. No extra nodes. Handles shared-bare cleanly (one node, one shape). Round-trip trivially stable. +**Cons.** Keeps the dotted-edge convention (some find it non-obvious). The wrapped-vs-not distinction lives in the node SHAPE rather than as a tangible halt-redirect joint. + +--- + +## Variant Y₁ — pseudo-halt node per wrapper, with per-context duplication + +The wrapper becomes a real node in the graph (sentinel label `~halt`, shape `[[…]]`). The bare's halt-bound transitions are **rewritten in the emit** to point at the pseudo-halt instead of at real halt. The pseudo-halt has a solid outgoing edge to the override. Most faithful to the runtime semantics — the wrapper IS the halt-redirect joint, and the graph makes that tangible. + +Single wrapper: + +```mermaid +flowchart TD +%% alphabets: [[" ","a","b","X"]] + s0(((halt))) + s1(("scanToX")) + ph1[["~halt"]] + s2["eraseHere"] + s1 -- "X → ·/S" --> ph1 + s1 -- "* → ·/R" --> s1 + ph1 --> s2 + s2 -- "* → ⌫/S" --> s0 +``` + +Nested: + +```mermaid +flowchart TD + s0(((halt))) + s1(("A")) + ph1[["~halt"]] + s2["B"] + ph2[["~halt"]] + s3["C"] + s1 -- "..." --> ph1 + ph1 --> s2 + s2 -- "..." --> ph2 + ph2 --> s3 + s3 -- "..." --> s0 +``` + +**Shared-bare case — the problem.** `minusOne` again: the same `invertNumber` instance is bare of W3 (outermost) and bare of W1 (innermost). In W3's wrapper context, its halt-bound transitions must rewrite to `ph_W3`; in W1's context, they must rewrite to `ph_W1`. Different rewrites in different contexts → **the state must be emitted twice as different nodes** (`s1` and `s4` below), or we lose the distinct halt-rewrite per wrapper. + +```mermaid +flowchart TD + s0(((halt))) + s1(("invertNumber")) + ph_W3[["~halt"]] + s4["invertNumber (duplicate, in W1 context)"] + ph_W1[["~halt"]] + s2["plusOne"] + ph_W2[["~halt"]] + s3["normalizeNumber"] + s1 -- "..." --> ph_W3 + ph_W3 --> s2 + s2 -- "..." --> ph_W2 + ph_W2 --> s4 + s4 -- "..." --> ph_W1 + ph_W1 --> s3 + s3 -- "..." --> s0 +``` + +`fromGraph` needs to recognize the duplication and merge the two `invertNumber` copies back into one State instance at reconstruction. Possible but non-trivial. + +**Pros.** Runtime-faithful — the wrapper appears as a concrete redirect step in the graph. No dotted-edge convention. +**Cons.** State duplication for shared-bare cases (common in the library). Significantly more parser + reconstruction logic in `fromMermaid` and `fromGraph` to handle duplication + merge. Strictly more nodes in the emit. + +--- + +## Variant Y₂ — pseudo-halt as an additional node (no rewriting; keeps dotted edge) + +A compromise: the pseudo-halt appears as a node with shape `[[~halt]]` and a **solid** outgoing edge to the override, but the bare's halt-bound transitions are **not** rewritten — they still point at real halt. The dotted `onHalt` edge runs from the bare to the pseudo-halt (replacing the current dotted-to-override edge). The pseudo-halt visualizes "the wrapper's redirect joint" without requiring per-context rewriting. + +Single wrapper: + +```mermaid +flowchart TD +%% alphabets: [[" ","a","b","X"]] + s0(((halt))) + s1(("scanToX")) + s2["eraseHere"] + ph1[["~halt"]] + s1 -- "X → ·/S" --> s0 + s1 -- "* → ·/R" --> s1 + s1 -. onHalt .-> ph1 + ph1 --> s2 + s2 -- "* → ⌫/S" --> s0 +``` + +Nested: + +```mermaid +flowchart TD + s0(((halt))) + s1(("A")) + s2["B"] + s3["C"] + ph1[["~halt"]] + ph2[["~halt"]] + s1 -- "..." --> s0 + s2 -- "..." --> s0 + s3 -- "..." --> s0 + s1 -. onHalt .-> ph1 + ph1 --> s2 + s2 -. onHalt .-> ph2 + ph2 --> s3 +``` + +**Shared-bare case** — no duplication needed; `invertNumber` is one node, with one dotted `onHalt` edge to one pseudo-halt: + +```mermaid +flowchart TD + s0(((halt))) + s1(("invertNumber")) + s2["plusOne"] + s3["normalizeNumber"] + ph_outer[["~halt"]] + s1 -- "..." --> s0 + s2 -- "..." --> s0 + s3 -- "..." --> s0 + s1 -. onHalt .-> ph_outer + ph_outer --> s2 +``` + +(Inner wrappers handled by recursion — each gets its own pseudo-halt node, dotted edge from its bare.) + +**Pros.** Pseudo-halt visualized as a tangible step. No state duplication. Modest implementation cost. +**Cons.** Keeps the dotted-edge convention, just shifts what the dotted edge points at (bare → pseudo, instead of bare → override). One extra node per wrapper. + +--- + +## Comparison table + +| Concern | Baseline (current) | X (shape-on-bare) | Y₁ (rewriting pseudo) | Y₂ (additional pseudo) | +|---|---|---|---|---| +| Wrapper node duplicates bare's edges | ❌ yes | n/a (no wrapper node) | ✅ no | ✅ no | +| Dotted-edge convention | ✅ used | ✅ used (bare → override) | ❌ removed | ✅ used (bare → pseudo) | +| Extra nodes per wrapper level | 1 (wrapper) | 0 | 1 (pseudo) | 1 (pseudo) | +| Shared-bare handling | ✅ single node | ✅ single node | ❌ duplicate per context | ✅ single node | +| Round-trip stability ([#139](https://github.com/mellonis/turing-machine-js/issues/139)) | ❌ accumulates `(override)` | ✅ trivially stable | ✅ stable if dedup works | ✅ stable | +| Implementation surface | n/a | small | large (per-context walk, dedup) | medium | +| Faithfulness to runtime semantics | medium (composite name embedded) | medium (shape conveys it) | ✅ high (pseudo IS the joint) | medium-high (pseudo visible but no rewrite) | + +--- + +## Recommendation + +**Y₂ if you want the pseudo-halt visualized as a node**; **X if you want the smallest possible change.** + +Y₁ is the most faithful but the cost of per-context state duplication in `fromGraph` is high for limited additional clarity over Y₂. + +--- + +## Final locked design (Variant X with `subgraph` overlay) + +After iteration, the locked shape evolves Variant X (collapse the wrapper into the bare's representation, no extra "wrapper node" in the graph data) with two visualization-only enhancements that make the wrapper's runtime semantics tangible without mutating the graph structure: + +1. A Mermaid **`subgraph` rectangle labeled `"halt frame"`** around each wrapper — the visual scope for "the wrapper's stack frame for halt handling." +2. A **halt-marker `(((halt)))` node inside that subgraph** — visualization of "halt-bound transitions land here, *inside* the wrapper's scope." `haltState` is a runtime singleton; the halt marker is a teaching aid (one per wrapper context on the diagram, all corresponding to the single runtime instance). + +### Visual contract (what a reader sees) + +- **`subgraph wN["halt frame"]`** = wrapper's runtime stack frame. While execution is "inside" the rectangle, the wrapper's override target sits on the runtime stack waiting to catch a halt. Visual-only — does not mutate the graph's edges. +- **`[[bare]]`** (Mermaid subroutine / double-walled rectangle, "two lines on sides") = the wrapper-node. Both: + - the wrapper's runtime entry point (execution starts here on entering the wrapper), and + - the source of the dotted `onHalt` redirect (since the wrapper-node *is* the catcher). +- **Cloned `(((halt)))` inside the subgraph** = the halt entry point within this wrapper's scope. Halt-bound transitions from the bare terminate here, not at the real halt. +- **Solid arrows from `[[bare]]` to halt marker** = the bare's structural halt-bound transitions. All stay inside the subgraph rectangle. +- **Dotted `onHalt` arrow from `[[bare]]` out of the subgraph to the override target** = the wrapper's catch-and-redirect. Exactly one per wrapper. Solid arrows from `[[bare]]` to non-halt targets can ALSO cross the rectangle border (when the bare's transitions reach external states — common in compositions like `library-binary-numbers`'s `minusOne`); those are just regular runtime transitions, not wrapper machinery. Only the dotted `onHalt` carries wrapper-machinery meaning. +- **Real `(((halt)))` outside any subgraph** = the actual run terminus. Reached only by states that are *not* inside a wrapper's halt-frame (the unwrapped tail of the chain). + +### Single wrapper + +`scanToX.withOverriddenHaltState(eraseHere)`: + +```mermaid +flowchart TD + s0(((halt))) + subgraph wA["halt frame"] + s1[["scanToX"]] + h_A(((halt))) + end + s2["eraseHere"] + s1 -- "X → ·/S" --> h_A + s1 -- "* → ·/R" --> s1 + s2 -- "* → ⌫/S" --> s0 + s1 -. onHalt .-> s2 +``` + +### Nested + +`A.withOverriddenHaltState(B.withOverriddenHaltState(C))`: + +```mermaid +flowchart TD + s0(((halt))) + subgraph wA["halt frame"] + s1[["A"]] + h_A(((halt))) + end + subgraph wB["halt frame"] + s2[["B"]] + h_B(((halt))) + end + s3["C"] + s1 -- "..." --> h_A + s2 -- "..." --> h_B + s3 -- "..." --> s0 + s1 -. onHalt .-> s2 + s2 -. onHalt .-> s3 +``` + +### Round-trip ([#139](https://github.com/mellonis/turing-machine-js/issues/139)) + +The wrapper's composite name (e.g. `scanToX(eraseHere)`) does **not** appear as any graph node's label — only the bare's name does. `fromGraph` reconstructs the wrapper via `bareStates[id].withOverriddenHaltState(getFinal(overriddenHaltStateId))`, which recomputes the composite name fresh on the reconstructed State instance. No round-trip name accumulation — fixes #139 automatically. + +### Shared-bare handling + +`library-binary-numbers`'s `minusOne` = `invertNumber.with(plusOne.with(invertNumber.with(normalizeNumber)))` — same `invertNumber` instance is the bare of two distinct wrappers (outermost and innermost). Each wrapper context implies its own `subgraph` membership + its own halt marker + its own dotted `onHalt` edge. + +Plan: emit the bare as a separate graph node per wrapper context (per-context duplication in `toGraph`). The shared State instance is preserved at runtime; the graph and Mermaid emit are per-context. `fromGraph` reconstructs equivalent State instances (not necessarily the same runtime `#id` as the original — just behaviorally equivalent). + +### Implementation outline + +1. Add `#bareState` field on `State`; populate in `withOverriddenHaltState` so `toGraph` can recover the bare from a wrapper instance. +2. `GraphNode` gains `isWrapped: boolean`. +3. `State.toGraph`: + - Detect wrapper-States (those with `#overriddenHaltState !== null`). + - Substitute with the bare; mark the bare's graph node `isWrapped: true`. + - Synthesize a per-wrapper halt-marker graph node (a node with `isHalt: true` whose role is "halt marker for this wrapper"). + - Rewrite the bare's halt-bound transitions to target the halt marker rather than the real one. +4. `toMermaid`: + - `isWrapped: true` node → `s${id}[["${name}"]]` (subroutine shape). + - Halt-marker node → `s${id}(((halt)))` (triple-paren, identical to real halt). + - Wrap each `[[bare]]` + its halt marker in `subgraph wN["halt frame"] … end`. + - Dotted onHalt edge `s${bareId} -. onHalt .-> s${overrideId}` (from `[[bare]]`, crossing the subgraph border). +5. `fromMermaid`: + - Parse Mermaid `subgraph wN["..."] … end` blocks. + - Recognize `s(\d+)\[\["([^"]*)"\]\]$` as wrapped-bare nodes; mark `isWrapped: true`. + - Track subgraph membership for the round-trip. +6. `State.fromGraph`: + - For `isWrapped: true` nodes, reconstruct via `bareStates[id].withOverriddenHaltState(getFinal(overriddenHaltStateId))`. + - Halt-marker graph nodes don't get separate State instances — they all map back to the singleton `haltState`. +7. `#139`'s round-trip test added; should pass after this design. +8. `states.md` regenerates with the new shape (both binary libraries). +9. README "Subroutine composition" section rewritten to use the new visual + reader's contract above. + +### Downstream support (for `machines-demo` [#9](https://github.com/mellonis/machines-demo/issues/9) / [#10](https://github.com/mellonis/machines-demo/issues/10) / [#37](https://github.com/mellonis/machines-demo/issues/37)) + +Five design choices in #138's implementation that keep the demo's render + highlight + click-to-breakpoint features unblocked: + +1. **Stable per-node ids in `Graph`.** Every node has a deterministic id: + - Bare nodes: `node.id = bareState.id` (the engine's `State.#id`). + - Halt-marker nodes: synthesized but deterministic from `(bareNodeId, wrapper-depth)`. + - Per-context bare duplicates: synthesized similarly. + + Mermaid emits `s${id}` for each; downstream can find the SVG node for any engine `state.id` directly. + +2. **Halt-marker marker on `GraphNode`.** `isHaltMarker: boolean` (additional to `isHalt: true`). Real halt has `isHalt: true, isHaltMarker: false`; halt markers have both `true`. Downstream uses this to: + - **#9** — emit halt-markers with a different CSS class (`.halt-marker` vs `.halt`) for styling. + - **#10** — skip halt-markers when computing "current state highlight" (they're visualization aids, not runtime states). + - **#37** — skip halt-markers when wiring click-to-toggle breakpoint handlers. + +3. **Edge identity on `GraphTransition`.** Add `id: string` field, deterministic from `(fromNodeId, patternIndex)` where `patternIndex` is the index of that transition in the bare's symbol map. Mermaid emit injects the id via a CSS-class directive that downstream can target. This is what #10 needs to highlight "the edge that will fire next" precisely. + +4. **Deterministic subgraph names in Mermaid emit.** Each wrapper's subgraph is `subgraph w_${bareNodeId}["halt frame"] … end`. Stable across rebuilds; downstream can target the rendered SVG group. + +5. **`isWrapped: boolean` on `GraphNode`** (already in the locked design above) — gives [#37](https://github.com/mellonis/machines-demo/issues/37) the surface to know "this node is a wrapper, click sets a breakpoint that triggers when the wrapper would catch a halt." + +**Out of scope for #138, deferred to #10 implementation:** exposing the runtime wrapper-context on `MachineState` (so downstream can disambiguate which duplicate-of-the-same-bare to highlight in shared-bare cases like `minusOne`). For the first cut of #10, lighting up all duplicates with the same `state.id` is acceptable; precision-tightening is a follow-up engine-API change. This is the only known shared-bare case in the library; user code rarely hits it. diff --git a/docs/superpowers/specs/2026-05-21-halt-frame-transitive-closure.md b/docs/superpowers/specs/2026-05-21-halt-frame-transitive-closure.md new file mode 100644 index 0000000..f3ddd0c --- /dev/null +++ b/docs/superpowers/specs/2026-05-21-halt-frame-transitive-closure.md @@ -0,0 +1,824 @@ +# Callable-subtree visualization for `withOverriddenHaltState` + +**Status:** IMPLEMENTED 2026-05-21 — design captured in PR #181 (Phase 1: data model) and the current PR (Phases 2–7: toGraph un-collapse + union-find + toMermaid + fromMermaid + fromGraph + tests + docs). Tracks [#174](https://github.com/mellonis/turing-machine-js/issues/174). Lands on the `v7` integration branch before v7 stable cut. + +**Relation to [#173](https://github.com/mellonis/turing-machine-js/issues/173):** #173 closed (2026-05-21) as the literal complaint (orphan `c_N` + onHalt anchor) doesn't apply to the new design — there's no `c_N` per-wrapper concept anymore. The new design has per-bare `c_A` halt sinks and per-wrapper call/return/onHalt edges, which together visualize runtime semantics directly. + +## The reframing + +`withOverriddenHaltState` is, structurally, **a function call**. When you write `W = A.wohs(target)`: + +- `W` is a **call site** — invoking it pushes `target` onto the halt-stack and delegates to `A`'s transitions. +- `A` (or rather, the subtree forward-reachable from `A`) is the **callable body**. +- The "halt" at the end of `A`'s execution is a **return point** — the stack pop kicks the override into action. + +So the runtime model is "graph of callable subtrees, dispatched from a top-level driver." The diagram should reflect that directly. + +## Mental model & vocabulary + +| Concept | Visualization | +|---|---| +| **Callable subtree** of a bare `A` | `subgraph subtree_A["callable subtree of A"]` block containing `A`, A's body states, and a local halt sink `c_A` | +| **Wrapper** `W = A.wohs(target)` | A `[[A(target)]]` node OUTSIDE the subtree | +| **Call** (wrapper → bare) | Bold `==>` arrow labeled `call`. **Reserved**: only wrappers emit bold arrows, and only to their bare. Other transitions whose target happens to be a wrapper (e.g., dispatcher → W1) stay as regular solid `-->`. Multiple wrappers sharing a bare collapse into one `==>` ribbon via Mermaid `&` syntax: `s_W1 & s_W2 == call ==> s_A` | +| **Return** (subtree halt → wrapper) | Dotted `-.->` arrow from the SUBGRAPH back to each wrapper, labeled `return`. Multiple wrappers collapse via `&` on the target side: `subtree_A -. return .-> s_W1 & s_W2`. **Demand-emit** — only emitted when `c_A` has at least one incoming edge AND the wrapper actually calls this subtree | +| **Halt** (subtree halt → real halt) | Dotted `-.->` arrow from the SUBGRAPH to `s0`, labeled `halt`. **Demand-emit** — only emitted when `c_A` has incoming edges AND there's a non-wrapper entry path (solid `-->`) into any state in the subtree. Fires when the subtree is entered without a wrapper on the stack | +| **Wrapper's outgoing** (post-return continuation) | Solid `-->` arrow from wrapper to its override target. Just a regular transition — the wrapper "transitions to" its override after the return fires | +| **Idle sentinel** | `idle([idle])` + dotted `idle -. enter .-> initial` (unchanged from v7 alpha.1) | +| **Real halt** | `s0(((halt)))` at top level (unchanged from v7 alpha.1) | +| **Local subtree halt** | `c_A(((halt)))` inside the subtree's subgraph block — body halts visually land here | + +## Arrow style summary + +| Style | Used for | +|---|---| +| Solid `-->` | Regular state-to-state transitions, including (a) wrapper → override target, and (b) any non-wrapper state's transitions, even when their target is a wrapper | +| Bold `==>` | **Only** the wrapper-to-bare `call` arrow. Source is always a wrapper; target is its bare | +| Dotted `-.->` | Frame-level dispatch (subtree return, subtree halt, idle enter) | + +Bold `==>` is reserved — it's the visual signature of "a wrapper calling into its callable subtree." Counting bold arrows in the diagram tells you exactly how many wrappers are in play. (Departs from v7 alpha.1's "bold-into-any-wrapper" rule; under that older rule, dispatcher → W1 was also bold. Under the new rule, it's solid — the reader sees W1's `[[…]]` shape and infers "wrapper" without needing redundant arrow styling on every transition into it.) + +## Examples + +### Example 1 — simple wrapper + +`A.withOverriddenHaltState(target)`, no shared bare: + +```mermaid +--- +config: + layout: elk +--- +flowchart TB + s0((("halt"))) + idle([idle]) + s_target["target"] + s_W[["A(target)"]] + + subgraph subtree_A["callable subtree of A"] + s_A["A"] + s_body["body"] + c_A((("halt"))) + end + + s_W == call ==> s_A + subtree_A -. return .-> s_W + s_W --> s_target + + idle -. enter .-> s_W + + s_A -- "[*]" --> s_body + s_body -- "['*']" --> c_A + s_target -- "[*]" --> s0 +``` + +Reading the runtime: idle → enter → W → call → subtree → body halts at c_A → return to W → W's `--> s_target` → s_target → halt. + +**No `halt` arrow** from the subtree to `s0`: there's no non-wrapper entry path into the subtree (only `s_W == call ==>` enters it), so the empty-stack case never fires. The `halt` arrow is demand-emit and omitted here. + +### Example 1b — PostMachine subroutine (the canonical motivating case) + +`PostMachine` program `{ 1: call('rightToBlank'); 2: mark; 3: stop; rightToBlank: { 1: right; 2: check(1,3); 3: stop } }`. PostMachine constructs internally: + +- A **hopper state** named `rightToBlank` whose only transition is `[ifOtherSymbol]: nextState = rightToBlank::1` (forward to the first body state). +- **Body states** `rightToBlank::1` (the `right` command) and `rightToBlank::2` (the `check` command). +- A **continuation state** `1~2` (where control resumes after the subroutine returns). +- A **wrapper** `W = rightToBlank.withOverriddenHaltState(continuation_1~2)`. +- A **top-level instruction 2** (`mark`), reached via the continuation. + +```mermaid +--- +config: + layout: elk +--- +flowchart TB + s0((("halt"))) + idle([idle]) + s_W[["rightToBlank(1~2)"]] + s_continuation["1~2"] + s_inst2["2"] + + subgraph subtree_rightToBlank["callable subtree of rightToBlank"] + s_hopper["rightToBlank"] + s_body1["rightToBlank::1"] + s_body2["rightToBlank::2"] + c_rightToBlank((("halt"))) + end + + s_W == call ==> s_hopper + subtree_rightToBlank -. return .-> s_W + s_W --> s_continuation + + idle -. enter .-> s_W + + s_hopper -- "[*]" --> s_body1 + s_body1 -- "[*] → [K]/[R]" --> s_body2 + s_body2 -- "['*'] → [K]/[S]" --> s_body1 + s_body2 -- "[B] → [K]/[S]" --> c_rightToBlank + + s_continuation -- "[*] → [K]/[S]" --> s_inst2 + s_inst2 -- "[*] → ['*']/[S]" --> s0 +``` + +**What this resolves vs. alpha.1's emit:** + +- Body states `rightToBlank::1` and `rightToBlank::2` are INSIDE the subtree (alpha.1 put them outside). +- The check's halt-bound transition (`s_body2 -- "[B]" -->`) retargets to `c_rightToBlank` instead of `s0` (alpha.1 emitted `→ s0`, misleading about runtime). +- No orphan halt marker — `c_rightToBlank` has incoming from `s_body2`. The condition that prompted #173 doesn't arise. +- The wrapper `s_W[["rightToBlank(1~2)"]]` sits OUTSIDE the subtree as a separate node — it's the call site, not the bare. + +Runtime trace reads off the diagram: `idle → W → call → hopper → body_1 → body_2 → c_rightToBlank → return W → continuation → instruction_2 → s0`. Each arrow corresponds to a runtime step. + +### Example 2 — multi-wrapper sharing a bare + +Two wrappers `W1 = A.wohs(target_B)` and `W2 = A.wohs(target_C)`, dispatcher routes: + +```mermaid +--- +config: + layout: elk +--- +flowchart TB + s0((("halt"))) + idle([idle]) + s_disp["dispatcher"] + s_targetB["target_B"] + s_targetC["target_C"] + + subgraph subtree_A["callable subtree of A"] + s_A["A"] + s_body["body"] + c_A((("halt"))) + end + + s_W1[["A(target_B)"]] & s_W2[["A(target_C)"]] == call ==> s_A + subtree_A -. return .-> s_W1 & s_W2 + subtree_A -. halt .-> s0 + s_W1 --> s_targetB + s_W2 --> s_targetC + + idle -. enter .-> s_disp + + s_disp -- "['*']" --> s_W1 + s_disp -- "['+']" --> s_W2 + s_disp -- "['#']" --> s_A + + s_A -- "[*]" --> s_body + s_body -- "['*']" --> c_A + s_targetB -- "[*]" --> s0 + s_targetC -- "[*]" --> s0 +``` + +Dispatcher's outgoing arrows to W1, W2, and A are all solid `-->` (regular transitions). The reader sees W1 and W2 are wrappers because of the `[[…]]` shape. Two bold `call` arrows in the diagram (the wrappers' calls into A's subtree) — exactly the count of wrappers in play. The subtree's `-. return .->` arrows go back to both wrappers (collapsed via `&`); the `-. halt .->` arrow handles the dispatcher's direct entry path (`['#']`). + +### Example 3 — self-wrapping (`A.wohs(A)`) + +```mermaid +--- +config: + layout: elk +--- +flowchart TB + s0((("halt"))) + idle([idle]) + s_W[["A(A)"]] + + subgraph subtree_A["callable subtree of A"] + s_A["A"] + c_A((("halt"))) + end + + s_W == call ==> s_A + s_W --> s_A + subtree_A -. return .-> s_W + subtree_A -. halt .-> s0 + + idle -. enter .-> s_W + + s_A -- "[*]" --> c_A +``` + +Legal API call (no self-reference validation). Runtime: W push A → A halts → pop A → control to A → A halts again → real halt. The diagram captures both arrows from W to A: bold `call` (entering A under W's stack frame) and solid `-->` (W's "post-return continuation IS A again"). Pattern is "run A twice in sequence." + +### Example 4 — nested `.wohs()` chain + +Two cases worth comparing: (4a) the chain `.wohs().wohs()` with only the outer entered, and (4b) two independently-referenced wrappers around the same bare. + +#### 4a. Chain construction, only outer entered + +```ts +const W1 = A.withOverriddenHaltState(target_1); +const W2 = W1.withOverriddenHaltState(target_2); +// Only W2 is referenced from the entry — W1 is never independently entered. +``` + +```mermaid +--- +config: + layout: elk +--- +flowchart TB + s0((("halt"))) + idle([idle]) + s_target2["target_2"] + s_W2[["A(target_1)(target_2)"]] + + subgraph subtree_A["callable subtree of A"] + s_A["A"] + c_A((("halt"))) + end + + s_W2 == call ==> s_A + subtree_A -. return .-> s_W2 + s_W2 --> s_target2 + + idle -. enter .-> s_W2 + + s_A -- "[*]" --> c_A + s_target2 -- "[*]" --> s_target2 + s_target2 -- "[*]" --> s0 +``` + +**Only ONE wrapper appears in the graph: `s_W2`.** `W1` is structurally absorbed — at runtime, only W2's override (`target_2`) is pushed onto the stack; `target_1` is never on the stack so it never fires. The composite name `A(target_1)(target_2)` is the engine's `state.#name`; it shows the construction chain textually but is technically misleading about runtime behavior (only `target_2` actually fires). Sibling-style emission (no nested frames) is the right rendering because the runtime is single-level. + +**Runtime trace:** idle → W2 → call subtree_A → A halts at c_A → return to W2 → W2 transitions to `target_2` → target_2 halts → s0. + +`halt` arrow omitted (no non-wrapper entry to subtree_A). + +#### 4b. Two independent wrappers, both referenced + +```ts +const W1 = A.withOverriddenHaltState(target_1); +const W2 = A.withOverriddenHaltState(target_2); +const dispatcher = new State({ + [sym_a]: { nextState: W1 }, + [sym_b]: { nextState: W2 }, +}, 'dispatcher'); +``` + +```mermaid +--- +config: + layout: elk +--- +flowchart TB + s0((("halt"))) + idle([idle]) + s_disp["dispatcher"] + s_target1["target_1"] + s_target2["target_2"] + + subgraph subtree_A["callable subtree of A"] + s_A["A"] + c_A((("halt"))) + end + + s_W1[["A(target_1)"]] & s_W2[["A(target_2)"]] == call ==> s_A + subtree_A -. return .-> s_W1 & s_W2 + s_W1 --> s_target1 + s_W2 --> s_target2 + + idle -. enter .-> s_disp + s_disp -- "['a']" --> s_W1 + s_disp -- "['b']" --> s_W2 + + s_A -- "[*]" --> c_A + s_target1 -- "[*]" --> s0 + s_target2 -- "[*]" --> s0 +``` + +**Two wrappers, both as siblings.** Dispatcher chooses which via input symbol — solid arrows from dispatcher to each wrapper (regular transitions; the `[[…]]` shape on W1 and W2 tells the reader they're wrappers). Each wrapper has its own bold `call` arrow into the subtree. + +This is how `library-binary-numbers/minusOne` is built (five sibling wrappers around two distinct bares). + +The visual distinction between 4a and 4b: **count the bold `==>` arrows**. 4a has one (W2's call); 4b has two (W1's and W2's). One bold arrow per wrapper in the graph, by construction. + +### Example 5 — Reference cycle (dead wrapper) + +`A` loops to itself via `Reference`; `W = A.wohs(target)`: + +```mermaid +--- +config: + layout: elk +--- +flowchart TB + s0((("halt"))) + idle([idle]) + s_target["target"] + s_W[["A(target)"]] + + subgraph subtree_A["callable subtree of A"] + s_A["A"] + c_A((("halt"))) + end + + s_W == call ==> s_A + s_W --> s_target + + idle -. enter .-> s_W + + s_A -- "[*]" --> s_A + s_target -- "[*]" --> s0 +``` + +`c_A` is **orphan** (no `--> c_A` edge): `s_A` loops back to itself via the Reference and never halts. Per the demand-emit rule, `subtree_A -. return .->` and `subtree_A -. halt .->` are BOTH omitted because `c_A` has no incoming edges — neither dispatch path is structurally reachable. Reader sees: "the subtree's `c_A` is unreachable. Whatever the wrapper redirects to (`target`) is also unreachable. Dead wrapper." + +The orphan `c_A` alone IS the dead-wrapper signal — no need for orphan return/halt arrows. + +## minusOne as a worked example + +`library-binary-numbers/minusOne` is a 4-deep wrapper composition that exercises shared bares + override chains. Probed under the union-find rule (2026-05-21): five wrappers, three subtrees. + +**Wrapper instances** (constructed via `.withOverriddenHaltState`): + +- `W10 = goToNumberStart.wohs(invertNumberGoToNumberWithInversion)` +- `W14 = goToNumberStart.wohs(normalizeNumberMoveNumberStart)` — same bare, different override +- `W20 = invertNumber.wohs(normalizeNumber)` +- `W22 = invertNumber.wohs(W21)` — wraps invertNumber, override is plusOne wrapper +- `W21 = plusOne.wohs(W20)` — wraps plusOne, override is invertNumber wrapper + +**Subtree decomposition** (3 unique bares): + +- `subtree_goToNumberStart`: contains `goToNumberStart` bare only (no body; halts directly). +- `subtree_invertNumber`: contains `invertNumber` bare only (no body; transitions delegate to `goToNumberStart` wrapper externally). +- `subtree_plusOne`: contains `plusOne` bare + body states (`plusOneFillZeros`, `plusOneAddNumberStart`, `plusOneCaryOne`). + +**Compared to alpha.1's emit** (5 subgraph blocks, per-context-duplicated bares): the new emit has **3 subgraph blocks** and de-duplicates the shared bares (`goToNumberStart` and `invertNumber` each appear once instead of twice). + +```mermaid +--- +config: + layout: elk +--- +flowchart TB + s0((("halt"))) + idle([idle]) + s_minusOne["minusOne"] + s_invNumGoTo["invertNumberGoToNumberWithInversion"] + s_norm["normalizeNumber"] + s_normMove["normalizeNumberMoveNumberStart"] + s_normPut["normalizeNumberPutNewStartSymbol"] + s_goToNum["goToNumber"] + + subgraph subtree_goToNumberStart["callable subtree of goToNumberStart"] + s_goToNumberStart["goToNumberStart"] + c_goToNumberStart((("halt"))) + end + + subgraph subtree_invertNumber["callable subtree of invertNumber"] + s_invertNumber["invertNumber"] + c_invertNumber((("halt"))) + end + + subgraph subtree_plusOne["callable subtree of plusOne"] + s_plusOne["plusOne"] + s_plusOneFillZeros["plusOneFillZeros"] + s_plusOneAddNumberStart["plusOneAddNumberStart"] + s_plusOneCaryOne["plusOneCaryOne"] + c_plusOne((("halt"))) + end + + s_W10[["goToNumberStart(invertNumberGoToNumberWithInversion)"]] + s_W14[["goToNumberStart(normalizeNumberMoveNumberStart)"]] + s_W20[["invertNumber(normalizeNumber)"]] + s_W21[["plusOne(W20)"]] + s_W22[["invertNumber(W21)"]] + + s_W10 == call ==> s_goToNumberStart + s_W14 == call ==> s_goToNumberStart + s_W20 == call ==> s_invertNumber + s_W22 == call ==> s_invertNumber + s_W21 == call ==> s_plusOne + + subtree_goToNumberStart -. return .-> s_W10 + subtree_goToNumberStart -. return .-> s_W14 + subtree_invertNumber -. return .-> s_W20 + subtree_invertNumber -. return .-> s_W22 + subtree_plusOne -. return .-> s_W21 + + s_W10 --> s_invNumGoTo + s_W14 --> s_normMove + s_W20 --> s_norm + s_W21 --> s_W20 + s_W22 --> s_W21 + + idle -. enter .-> s_minusOne + s_minusOne --> s_W22 + + s_invertNumber --> s_W10 + s_norm --> s_W14 + s_normMove --> s_normPut + s_normPut --> s_goToNum +``` + +Reading the override chain: `minusOne → W22 → W21 → W20 → normalizeNumber → W14 → normalizeNumberMoveNumberStart → … → real halt`. Each wrapper's solid `-->` to its override target chains the runtime call sequence. + +Reading the bare-sharing: both `W10` and `W14` call into the SAME `subtree_goToNumberStart`. Both `W20` and `W22` call into the SAME `subtree_invertNumber`. This de-duplication is invisible in alpha.1's emit (it emits two `goToNumberStart` and two `invertNumber` subgraphs). + +**Union-find didn't trigger** for minusOne because each bare's forward-reachable set is just itself (the bares transition either to halt or to other wrappers, never directly into each other's body states). Union-find applies to scenarios like the dispatcher+shared-X case, not chained compositions. + +## Reachability rules (what's inside the subtree) + +For each wrapped bare `A`, the callable subtree contains: +- `A` itself. +- Every state forward-reachable from `A` via its transitions, following `Reference#ref` transparently. +- The synthesized halt-marker `c_A` (always emitted — see Edge cases for the dead-wrapper rationale). + +**Each state is rendered exactly once. Containers are computed via union-find on wrapper reachability:** + +1. **For each wrapped bare** `B`, compute `reach(B)` = forward-reachable set starting from B (following transitions and `Reference#ref` transparently). +2. **Compute connected components** of wrappers by overlap: wrappers `W_i` and `W_j` are in the same component iff `reach(bare(W_i)) ∩ reach(bare(W_j)) ≠ ∅` (their reachable sets share at least one state). Transitive closure of the overlap relation. +3. **Each connected component** becomes one **callable scope** (a subgraph frame in the emit). The frame contains: every state in the union of `reach(bare(W))` for all wrappers in the component, plus a single halt marker `c_union`. +4. **States outside any wrapper's reach** stay at top level (e.g., dispatcher, override targets, real halt singleton, `idle`). + +Frame name: `callable subtree of ` for a single-bare component; `callable scope: A ∪ B ∪ …` for multi-bare components. + +**Demand-emit rules** (refined): + +- `c_union` always present (orphan marker is the dead-wrapper signal — see Edge cases). +- `frame -. return .-> W` emitted iff `c_union` has incoming edges AND wrapper W has a `call` arrow into the frame. +- `frame -. halt .-> s0` emitted iff `c_union` has incoming edges AND there is **at least one solid `-->` arrow** entering any state inside the frame (a non-wrapper entry path). + +Cross-subgraph arrows are allowed and natural — Mermaid supports arrows whose source and target sit in different subgraph blocks. A state in the union frame may be the target of an arrow from outside (dispatcher → X, e.g.), or the source of an arrow to outside (frame member → wrapper override target). + +**No per-context duplication.** Today's v7 alpha.1 duplicates shared bares (e.g., `library-binary-numbers/minusOne` shows `invertNumber` as both `s20` and `s22` in its emit). Under the callable-subtree model, there's a single `subtree_invertNumber` and both wrappers `call ==>` into it via the `&` syntax. The diagram is smaller and the runtime "same instance, multiple call sites" semantic is visualized exactly. No same-instance marking is needed because no node is duplicated. + +## Edge cases + +### `c_A` always emitted + +Every wrapped bare gets a `c_A(((halt)))` inside its subtree subgraph, even when: +- The bare has no halt-bound transitions in its reachable set (dead wrapper — Reference cycle case). +- The body has halt-bound transitions but none happen to land on `c_A` directly (e.g., halts via intermediate paths that exit the subtree). + +The orphan `c_A` (no incoming `--> c_A` edge) is a meaningful signal: "this wrapper's runtime scope never produces a halt — wrapper is dead code." + +### Self-wrapping (`A.wohs(A)`) + +Legal. Wrapper has two arrows to A: bold `call` + solid `-->` (override target). Runtime: "run A twice in sequence." See Example 3. + +### Override target is itself a wrapper + +`W = A.wohs(W2)` where `W2 = B.wohs(target)`. W's outgoing to its override `W2` is a regular solid `--> s_W2` (the new convention reserves bold for wrapper-to-bare only). The reader sees W2's `[[…]]` shape and knows it's a wrapper. W2 in turn has its own bold `s_W2 == call ==> s_B` arrow into its own subtree. So the chain reads as: regular transition into a wrapper (solid), then that wrapper's bold call into its bare. Two bold arrows total (one per wrapper), regardless of how composition chains together. + +### Nested `.wohs()` chain (`A.wohs(t1).wohs(t2)`) + +Single wrapper emitted (outermost). Composite name accumulates the chain (`A(t1)(t2)`). Per probe finding, only `t2` actually fires at runtime; `t1` is overwritten. The composite name is technically misleading but matches engine v7 alpha.1's `state.#name` value. + +### Shared body state (reachable from multiple subtrees) + +When X is reachable from multiple wrapped bares whose reach sets overlap, those wrappers are in the same connected component → one **union frame** containing both bares + the shared state + a single halt marker. + +**Worked example 1: no direct entry to X.** `dispatcher` reads `1` → `W1`, reads `2` → `W2`. `A → X`. `B → X`. `X` halts. `W1 = A.wohs(target_1)`, `W2 = B.wohs(target_2)`. + +`reach(A) = {A, X}` and `reach(B) = {B, X}` overlap on X → union component {W1, W2} → one frame containing {A, B, X, c_union}. + +```mermaid +--- +config: + layout: elk +--- +flowchart TB + s0((("halt"))) + idle([idle]) + s_disp["dispatcher"] + s_target1["target_1"] + s_target2["target_2"] + s_W1[["A(target_1)"]] + s_W2[["B(target_2)"]] + + subgraph union_AB["callable scope: A ∪ B"] + s_A["A"] + s_B["B"] + s_X["X"] + c_union((("halt"))) + end + + s_W1 == call ==> s_A + s_W2 == call ==> s_B + union_AB -. return .-> s_W1 + union_AB -. return .-> s_W2 + + s_W1 --> s_target1 + s_W2 --> s_target2 + + idle -. enter .-> s_disp + s_disp -- "['1']" --> s_W1 + s_disp -- "['2']" --> s_W2 + + s_A --> s_X + s_B --> s_X + s_X -- "[*]" --> c_union + + s_target1 -- "[*]" --> s0 + s_target2 -- "[*]" --> s0 +``` + +X is inside the union frame. Both wrappers have `call` arrows to their respective bare entry points (W1 → A, W2 → B) — both bares live in the same scope. `c_union` has X's halt-bound transition landing on it. + +No `halt` arrow emitted — there's no solid `-->` entry into the union frame (only bold `call` from wrappers). The runtime never reaches `c_union` with an empty stack, so falling through to `s0` is structurally impossible. + +**Worked example 2: dispatcher adds direct entry to X (`['3'] → X`).** Now there's a non-wrapper entry into the union frame. + +```mermaid +--- +config: + layout: elk +--- +flowchart TB + s0((("halt"))) + idle([idle]) + s_disp["dispatcher"] + s_target1["target_1"] + s_target2["target_2"] + s_W1[["A(target_1)"]] + s_W2[["B(target_2)"]] + + subgraph union_AB["callable scope: A ∪ B"] + s_A["A"] + s_B["B"] + s_X["X"] + c_union((("halt"))) + end + + s_W1 == call ==> s_A + s_W2 == call ==> s_B + union_AB -. return .-> s_W1 + union_AB -. return .-> s_W2 + union_AB -. halt .-> s0 + + s_W1 --> s_target1 + s_W2 --> s_target2 + + idle -. enter .-> s_disp + s_disp -- "['1']" --> s_W1 + s_disp -- "['2']" --> s_W2 + s_disp -- "['3']" --> s_X + + s_A --> s_X + s_B --> s_X + s_X -- "[*]" --> c_union + + s_target1 -- "[*]" --> s0 + s_target2 -- "[*]" --> s0 +``` + +Cross-subgraph entry `s_disp -- ['3'] --> s_X` triggers the `halt` arrow to emit. All three runtime paths from dispatcher are now traceable: `1` → W1 → call → halt at c_union → return W1 → target_1 → s0; `2` → W2 analogously; `3` → directly to X (inside union) → halt at c_union → empty-stack `halt` arrow → s0. + +The union model degrades gracefully — adding direct entry to a state inside the union just flips the `halt` arrow on. No new structural concept needed. + +### Worked union shapes — engine-emitted Mermaid + +Real `toMermaid` output (post-#174) for three increasingly merged union shapes. Each test machine has a dispatcher routing by tape symbol into per-bare wrappers `W = bare.withOverriddenHaltState(target)`. The bares' transitions land on shared body states that drive the union-find merge. + +#### Case 1: `A ∪ B` (two bares, one shared state) + +```text +reach(A) = {A, X} +reach(B) = {B, X} +A ∩ B = {X} → union(A, B) +``` + +```mermaid +flowchart TD +%% alphabets: [[" ","1","2"]] + s0(((halt))) + s4["t1"] + s5["t2"] + s8["dispatcher"] + s6[["A(t1)"]] + s7[["B(t2)"]] + idle([idle]) + subgraph w_2["callable scope: A ∪ B"] + s1["X"] + s2["A"] + s3["B"] + c2(((halt))) + end + idle -. enter .-> s8 + s6 == "call" ==> s2 + s7 == "call" ==> s3 + w_2 -. "return" .-> s6 & s7 + s6 --> s4 + s7 --> s5 + s1 -- "[*] → [K]/[S]" --> c2 + s2 -- "[*] → [K]/[R]" --> s1 + s3 -- "[*] → [K]/[R]" --> s1 + s4 -- "[*] → [K]/[S]" --> s0 + s5 -- "[*] → [K]/[S]" --> s0 + s8 -- "['1'] → [K]/[S]" --> s6 + s8 -- "['2'] → [K]/[S]" --> s7 +``` + +Two `==> call` arrows into the same frame; one `& `-joined `return` ribbon back to both wrappers. + +#### Case 2: `A ∪ B ∪ C` (three bares, all share X directly) + +```text +reach(A) = {A, X} +reach(B) = {B, X} +reach(C) = {C, X} +Every pair intersects on X → all merge. +``` + +```mermaid +flowchart TD +%% alphabets: [[" ","1","2","3"]] + s0(((halt))) + s13["t1"] + s14["t2"] + s15["t3"] + s19["dispatcher"] + s16[["A(t1)"]] + s17[["B(t2)"]] + s18[["C(t3)"]] + idle([idle]) + subgraph w_10["callable scope: A ∪ B ∪ C"] + s9["X"] + s10["A"] + s11["B"] + s12["C"] + c10(((halt))) + end + idle -. enter .-> s19 + s16 == "call" ==> s10 + s17 == "call" ==> s11 + s18 == "call" ==> s12 + w_10 -. "return" .-> s16 & s17 & s18 + s16 --> s13 + s17 --> s14 + s18 --> s15 + s9 -- "[*] → [K]/[S]" --> c10 + s10 -- "[*] → [K]/[R]" --> s9 + s11 -- "[*] → [K]/[R]" --> s9 + s12 -- "[*] → [K]/[R]" --> s9 + s13 -- "[*] → [K]/[S]" --> s0 + s14 -- "[*] → [K]/[S]" --> s0 + s15 -- "[*] → [K]/[S]" --> s0 + s19 -- "['1'] → [K]/[S]" --> s16 + s19 -- "['2'] → [K]/[S]" --> s17 + s19 -- "['3'] → [K]/[S]" --> s18 +``` + +Three `call` arrows, one three-way `& `-joined `return` ribbon. Same frame, one halt marker. + +#### Case 3: `(A ∪ B) ∪ C` — transitive (B and C don't share anything direct) + +```text +reach(A) = {A, X, Y} +reach(B) = {B, X} +reach(C) = {C, Y} +A ∩ B = {X} → union(A, B) +A ∩ C = {Y} → union(A, C) ← C joins {A, B} via A +B ∩ C = ∅ ← but they end up in the same frame anyway, transitively +``` + +```mermaid +flowchart TD +%% alphabets: [[" ","1","2","3"]] + s0(((halt))) + s25["t1"] + s26["t2"] + s27["t3"] + s31["dispatcher"] + s28[["A(t1)"]] + s29[["B(t2)"]] + s30[["C(t3)"]] + idle([idle]) + subgraph w_22["callable scope: A ∪ B ∪ C"] + s20["X"] + s21["Y"] + s22["A"] + s23["B"] + s24["C"] + c22(((halt))) + end + idle -. enter .-> s31 + s28 == "call" ==> s22 + s29 == "call" ==> s23 + s30 == "call" ==> s24 + w_22 -. "return" .-> s28 & s29 & s30 + s28 --> s25 + s29 --> s26 + s30 --> s27 + s20 -- "[*] → [K]/[S]" --> c22 + s21 -- "[*] → [K]/[S]" --> c22 + s22 -- "['1'] → [K]/[R]" --> s20 + s22 -- "['2'] → [K]/[R]" --> s21 + s23 -- "[*] → [K]/[R]" --> s20 + s24 -- "[*] → [K]/[R]" --> s21 + s25 -- "[*] → [K]/[S]" --> s0 + s26 -- "[*] → [K]/[S]" --> s0 + s27 -- "[*] → [K]/[S]" --> s0 + s31 -- "['1'] → [K]/[S]" --> s28 + s31 -- "['2'] → [K]/[S]" --> s29 + s31 -- "['3'] → [K]/[S]" --> s30 +``` + +Two shared body states (X, Y). B reaches X only; C reaches Y only. They never see each other directly — yet they live in the same frame because A bridges them. The union-find transitive-closure step is what bundles them. + +**Reading rule encoded in the emit:** one subgraph = one union component. The label tells you how many bares share the scope. `& `-joined `call` / `return` arrows tell you how many wrappers are in play (always one per wrapper; the union collapses bares, never wrappers). + +## Data model changes + +The callable-subtree model **un-collapses what v7 alpha.1 collapsed**. In alpha.1, each `withOverriddenHaltState` wrapper produces ONE `GraphNode` representing both the wrapper and its bare (the bare is "collapsed onto" the wrapper's id, with `isWrapped: true`). Under the new model, **wrappers and bares are separate `GraphNode` instances**: + +- **Bare node** (`isWrapper: false`, regular `["…"]` shape) — lives inside its callable subtree. There's exactly one bare node per unique wrapped `State` instance. +- **Wrapper node** (`isWrapper: true`, `[[…]]` shape, has `bareStateId: number`) — lives outside any subtree. There's one wrapper node per `withOverriddenHaltState` call. Multiple wrappers can share the same `bareStateId` if they wrap the same bare with different override targets. +- **Halt marker** (`isHaltMarker: true`, `(((halt)))` shape inside subtree) — one per subtree, retargets the bare's halt-bound transitions. + +`GraphNode` field changes (proposed names; subject to implementation): + +| Field | alpha.1 | New model | +|---|---|---| +| `isWrapped: boolean` | True for collapsed-wrapper nodes | **Renamed/repurposed:** drop in favor of `isWrapper: boolean` | +| `isWrapper: boolean` | n/a | True for external `[[…]]` wrapper nodes | +| `bareStateId: number \| null` | n/a | Set on wrappers; points to the bare's `GraphNode` id | +| `frameId: number \| null` | n/a (P1 added it; superseded) | Set on bare, body states, and halt marker — the id of the containing subtree | +| `isHaltMarker: boolean` | True for synthesized halt markers | Same | +| `overriddenHaltStateId: number \| null` | Set on collapsed-wrapper nodes (= override target's id) | Set on wrapper nodes | + +**`State.toGraph` second pass** (the reachability + frame-assignment + halt-retargeting work): + +- Pass 1 enumerates `State`s reachable from the initial state. For each State: + - If the State is wrapped (`#overriddenHaltState !== null` AND `#bareState !== null`): produce TWO nodes — a wrapper node (`isWrapper: true`, with the wrapper's id and composite name) AND a bare node (regular, with the bare's id and name) IF the bare doesn't already have a graph node from another wrapper context. + - If the State is not wrapped: produce one regular node. +- Pass 2 assigns `frameId` via union-find on bare reachability: each unique bare's forward-reachable set (following the bare's transitions and `Reference#ref` transparently) defines its candidate subtree. When two bares' reach sets overlap, they merge into one union frame. Each frame gets a single halt marker. +- Halt-bound transitions of any in-frame state retarget to the frame's halt marker. + +`Graph` itself: no structural change beyond the per-node field additions. + +## Emit changes + +### `toMermaid` + +- Subgraph emission unchanged in shape (`subgraph w_N["callable subtree of NAME"]` with bare + body + halt marker inside). +- **Frame-level outgoing arrows** added: for each wrapped subtree, emit: + - One `subtree_N -. return .-> wrapper_id` per wrapper that calls this subtree. + - One `subtree_N -. halt .-> s0` always. +- **Wrapper's `onHalt` edge removed.** Replaced by a solid `--> override_id` regular transition — always solid, regardless of whether the override is a wrapper (the new convention reserves bold for wrapper-to-bare `call` arrows only; the reader identifies the override as a wrapper, if it is one, by its `[[…]]` shape). +- **`call` label** added to bold arrows entering a wrapped subtree. The `&` multi-source syntax groups multiple wrappers sharing a bare into one ribbon. +- **Same-instance class assignments** if Option B/C selected. + +### `fromMermaid` + +- Parse subgraph membership → set `frameId` on nodes inside (already done in current draft). +- Parse `-. return .->` and `-. halt .->` arrows → no Graph data change needed (these are derivable from frame membership and structure at emit time, OR stored as a separate field). **TBD.** +- Parse `== call ==>` arrows: regular transitions whose target is a wrapper. Already handled. + +### `fromGraph` + +Unchanged. Frame membership and return/halt arrows are visualization concerns; runtime reconstruction reads `transitions` and `overriddenHaltStateId` from each node and builds State instances. + +## Round-trip + +`toGraph → toMermaid → fromMermaid → toGraph`: +- Data model survives if `frameId` is preserved across round-trip (already done). +- **TBD:** verify `library-binary-numbers/minusOne` round-trips byte-stable. Since the new model eliminates per-context duplication, the previous "shared-bare ordering" caveat is gone — `minusOne` should be cleaner under the new emit. + +## Implementation outline + +1. **Revert previous implementation** (the exclusive-only `frameId` pass in `State.toGraph` and the toMermaid grouping). Keep the `frameId` field on `GraphNode`; reuse it under the new semantics. +2. **Two-pass `State.toGraph`:** + - Pass 1: build raw nodes (current). + - Pass 2 (new): for each wrapper, compute the full forward-reachable set from its bare. For each in-set node, assign `frameId = wrapper.id`. For each in-set state's halt-bound transition, retarget to the wrapper's halt marker. + - Each `State` → exactly one `GraphNode`. When a state would belong to multiple subtrees, assign it to the innermost (deepest-nested) containing subtree. External references become cross-subgraph arrows at emit time. +3. **`toMermaid` rewrite of the subgraph emission:** + - Group nodes by `frameId`. + - Emit `subgraph w_${id}["callable subtree of NAME"]` per group. + - Emit `subtree_N -. return .-> wrapper_id` per wrapper that calls the subtree (derive from which wrappers' bareState produces this subtree). + - Emit `subtree_N -. halt .-> s0`. + - Wrapper's outgoing → override: solid `-->`, always. (Bold `==>` is reserved for the wrapper's `call` into its own bare.) +4. **`fromMermaid`:** track subgraph membership (already done); parse `return` and `halt` arrows as needed; parse `call` arrows as regular bold transitions. +5. **Tests:** Examples 1–5 above as regression cases. Existing wrapper-emission tests will need updates. +6. **Regenerate `library-binary-numbers*/states.md`** and verify visual sanity, especially `minusOne`. +7. **Update docs:** engine `CLAUDE.md`, `README.md`, this spec to IMPLEMENTED status. + +## Resolved questions + +### `return`/`halt` arrow storage + +**Decision: derive at emit time.** `toMermaid` computes `return` and `halt` arrows from frame membership + transitions; no new fields on `Graph` or `GraphNode` beyond `frameId`. + +Rationale: +- Data model stays lean — single source of truth (frame structure + transitions) drives the arrows. +- Round-trip stability is automatic: as long as `toGraph` is deterministic on `frameId` assignment (which it is per the union-find rule), `toGraph → toMermaid → fromMermaid → toGraph` produces bytewise-identical Mermaid on the second `toMermaid` call. The arrows are recomputed from the (identical) underlying structure. +- `fromMermaid` parses `return`/`halt` arrows but doesn't need to persist them in graph data — they're re-derived on the next emit. (Minor parse-then-discard cost; offset by the smaller data model.) +- Hand-edited Mermaid with inconsistent arrows is already explicitly unsupported per the engine's strict-format policy (`fromMermaid` is strict to the dialect `toMermaid` emits). + +### `onHalt` keyword retirement + +**Decision: retire the `onHalt` label and dotted style for wrapper-to-override edges.** The new model uses a regular solid `-->` arrow from wrapper to its override target — it's just a transition under the function-call mental model ("what fires after the call returns"). No special label needed. + +The dotted `-.->` style is now exclusively reserved for **frame-level dispatch arrows**: `return` (subtree → wrapper), `halt` (subtree → real halt), and `enter` (idle → initial state). + +Migration notes for the next prerelease: +- **CHANGELOG entry** — note the format change. Mermaid strings emitted by v7 alpha.1 (containing `-. onHalt .->` edges) will not parse with the new `fromMermaid`. One-way migration. +- **Engine `README.md`** — remove the "Edge arrow styles" reference to `-. onHalt .->`; replace with the new convention table. +- **Engine `CLAUDE.md`** — update the "v7 emit shape" paragraph to reflect the callable-subtree model + new arrow conventions. +- **`packages/library-binary-numbers*/states.md`** — regenerate via `npm run docs:states` after implementation lands; visual check on `minusOne` confirms the new shape reads well. + +## Open questions + +(None remaining; all design decisions resolved. Implementation details for `State.toGraph` un-collapsing, backward-compat strategy for alpha.1 Mermaid strings, and PR-shape decisions are captured in the [Implementation outline](#implementation-outline) and the related issues turing#175, turing#176, post-machine-js#85.) diff --git a/lerna.json b/lerna.json index e573776..e9b371b 100644 --- a/lerna.json +++ b/lerna.json @@ -3,6 +3,6 @@ "packages": [ "packages/*" ], - "version": "6.4.0", + "version": "7.0.0", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ecca00f..21e0729 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,14 +12,14 @@ ], "devDependencies": { "@tsconfig/recommended": "^1.0.13", - "@types/node": "^25.6.2", - "@vitest/coverage-v8": "4.1.5", - "eslint": "^10.2.1", + "@types/node": "^25.9.1", + "@vitest/coverage-v8": "^4.1.8", + "eslint": "^10.4.1", "lerna": "^9.0.7", - "rollup": "^4.60.2", + "rollup": "^4.61.0", "typescript": "^6.0.3", - "typescript-eslint": "^8.59.1", - "vitest": "4.1.5" + "typescript-eslint": "^8.60.1", + "vitest": "^4.1.8" }, "engines": { "npm": ">=7.0.0" @@ -179,9 +179,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", - "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -215,9 +215,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", - "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -980,19 +980,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/arborist/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@npmcli/fs": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", @@ -1006,19 +993,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@npmcli/git": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.2.tgz", @@ -1079,19 +1053,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/git/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@npmcli/git/node_modules/which": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", @@ -1223,19 +1184,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/metavuln-calculator/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@npmcli/name-from-folder": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-3.0.0.tgz", @@ -1363,19 +1311,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/package-json/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@npmcli/promise-spawn": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", @@ -1537,23 +1472,10 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@nx/devkit/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@nx/nx-darwin-arm64": { - "version": "22.7.1", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-22.7.1.tgz", - "integrity": "sha512-m00ZmBn39VUgb0Ahhu5iY6D56ETdXjDbVnOz0XF3DacJrcLtq9sZ+cg1bj6PshqtvRWVg+zJRrZBU6vL7hGuFQ==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-22.7.5.tgz", + "integrity": "sha512-eoPtwx0qZqvRUD+VVOHm150AlSYwYoPxkDHBBGqKCn5nzPspb0lLWw8q83crM/L1M928YgK0WmGf3C++7eqsTA==", "cpu": [ "arm64" ], @@ -1565,9 +1487,9 @@ ] }, "node_modules/@nx/nx-darwin-x64": { - "version": "22.7.1", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-22.7.1.tgz", - "integrity": "sha512-DmD8Qow+Yt7Yrmjlz1AsfiwxW+0kRzg+6MY70+d7qChtD2bTzvA/k0ut8SMy+CxU3kxgUbKhGOtml5JDXoX2ww==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-22.7.5.tgz", + "integrity": "sha512-VLOn/ZoEn3HfjSj+yIHLCM56/el79r+9I28CkZNHaSXJQWZ3edSkcgcfYjVxCurpN2VEwDQHLBeFCH8M+lQ7wQ==", "cpu": [ "x64" ], @@ -1579,9 +1501,9 @@ ] }, "node_modules/@nx/nx-freebsd-x64": { - "version": "22.7.1", - "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-22.7.1.tgz", - "integrity": "sha512-HboVrUCHcuYTXtuX3dMyRszP7JO90ZVBLWgnmaM7jUM7jnllZjmezUMtpNHfN1GQbVFafJf/NBShDWsu9LuaUA==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-22.7.5.tgz", + "integrity": "sha512-LEVer/E2xfGvK9Go+imMQoEninOoq/38Z2bhV1SD3AThXrp1xaLFVkW5jQ6juebeVkAeztEoMLFlr576egS0vw==", "cpu": [ "x64" ], @@ -1593,9 +1515,9 @@ ] }, "node_modules/@nx/nx-linux-arm-gnueabihf": { - "version": "22.7.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-22.7.1.tgz", - "integrity": "sha512-5Gm8Y7L8WXMLUjHhiy1eqGz5/PiRw1YLanFg5audBNkZvH6Jkwzdpoz0dbeKjwMDHz4NmniUV1s76Th8VLWmiQ==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-22.7.5.tgz", + "integrity": "sha512-NP27EFGpmFJM6RL1Ey/AFJ7gA2xuqtIHaw6jjSNGvfrnZRUNaway30GrVaGGeODf0DsvAty/unqoBMPy6kDHbw==", "cpu": [ "arm" ], @@ -1607,9 +1529,9 @@ ] }, "node_modules/@nx/nx-linux-arm64-gnu": { - "version": "22.7.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-22.7.1.tgz", - "integrity": "sha512-GdgPYMfbijBRFJs1absL/9QdSNLsTAGdyKykDf9CaVxEMZ92VB+pncpX9Vn/ZBCSeeWTLF+bSK3UM5v+loIObQ==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-22.7.5.tgz", + "integrity": "sha512-QLnkJl3HkHsPfpLiNiAiMfpfAeFpic0U1diAxF8RqChOkCpQ7ulvyBVgE1UrQxvhd+gFQ3ed5RNDxtCRw8nTiw==", "cpu": [ "arm64" ], @@ -1621,9 +1543,9 @@ ] }, "node_modules/@nx/nx-linux-arm64-musl": { - "version": "22.7.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-22.7.1.tgz", - "integrity": "sha512-HyBgPtY1hyNTk8683nt7F29jh3lVdS/zul9vS0NgKeCSoYL3GRM3nLoTPynoHUxyVP/tWYOE3ymvnk92qYwL4Q==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-22.7.5.tgz", + "integrity": "sha512-cEP6KmwBgnb38+jTTaibWCjwXcHmigqhTfy0tN1be7WZr6bHxbqNLsXqKRN70PSNA3HouZcxw1cdRL8tqbPBBA==", "cpu": [ "arm64" ], @@ -1635,9 +1557,9 @@ ] }, "node_modules/@nx/nx-linux-x64-gnu": { - "version": "22.7.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-22.7.1.tgz", - "integrity": "sha512-bQBgRiEsanNvKcDOjVAUPjvcp0iDLofYYUL2af2iuCDxreLOej+J6MeA5bWTLNly5ly1d4voKGTqa+OsouVyLg==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-22.7.5.tgz", + "integrity": "sha512-tbaX1tZCSpGifDNBfDdEZAMxVF3Yg4bhFP/bm1needc0diqb+Zflc0u5tM5/6BWDMITQDwenJVsNiQ8ZdtJURA==", "cpu": [ "x64" ], @@ -1649,9 +1571,9 @@ ] }, "node_modules/@nx/nx-linux-x64-musl": { - "version": "22.7.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-22.7.1.tgz", - "integrity": "sha512-gcco2GjcAztF/fRcAgFxtWxrWDnQdNmPaAN9FTt1+qQ9RUSLvdL8bQxKx4Kd9N9T+gXPlrWhMkBkKbbV09+X1Q==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-22.7.5.tgz", + "integrity": "sha512-H0M7csOZIgPT822LqjxSXzf4MXRND15vIkAQe3F3Jlr3Si8LC3tzbL52aVcRfgb8MF/xOB5U47mSwxWt1M2bPQ==", "cpu": [ "x64" ], @@ -1663,9 +1585,9 @@ ] }, "node_modules/@nx/nx-win32-arm64-msvc": { - "version": "22.7.1", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-22.7.1.tgz", - "integrity": "sha512-IT9oEn0YQ83iPH7666aoPyTRsUzBIBJdBLMXeLX4I60fHPXWhUSGpfiLtIsgU2OfeOVb9hU9idwNh1wc4u9rWQ==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-22.7.5.tgz", + "integrity": "sha512-JTcZch9YAnDL1gbhqePz3DZ4x7iYemLn1yJzrjbbXAmXju2eiiJiZvJJHbV06+SP9HKXDT8RjTKuAWTdVxnHug==", "cpu": [ "arm64" ], @@ -1677,9 +1599,9 @@ ] }, "node_modules/@nx/nx-win32-x64-msvc": { - "version": "22.7.1", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-22.7.1.tgz", - "integrity": "sha512-P2zeSKXVH2Eiwsb8UfP2rMMS7//cHWpiO4M9zt6q0c4lI/hN1vXBciRKVWruGk9ZrWLHuhaMAhG94+MJtzKuRQ==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-22.7.5.tgz", + "integrity": "sha512-ngcMyHdBJ9FSz2nHdbZ7gtJlFq0O2b05sPAsVMkZ18CKzdaA1qrBDJfsMO49hPCny505eiT766+CkKdaCDl5kA==", "cpu": [ "x64" ], @@ -1865,9 +1787,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.128.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.128.0.tgz", - "integrity": "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", "dev": true, "license": "MIT", "funding": { @@ -1875,9 +1797,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.18.tgz", - "integrity": "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", "cpu": [ "arm64" ], @@ -1892,9 +1814,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.18.tgz", - "integrity": "sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", "cpu": [ "arm64" ], @@ -1909,9 +1831,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.18.tgz", - "integrity": "sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", "cpu": [ "x64" ], @@ -1926,9 +1848,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.18.tgz", - "integrity": "sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", "cpu": [ "x64" ], @@ -1943,9 +1865,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.18.tgz", - "integrity": "sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", "cpu": [ "arm" ], @@ -1960,9 +1882,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.18.tgz", - "integrity": "sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", "cpu": [ "arm64" ], @@ -1977,9 +1899,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.18.tgz", - "integrity": "sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", "cpu": [ "arm64" ], @@ -1994,9 +1916,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.18.tgz", - "integrity": "sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", "cpu": [ "ppc64" ], @@ -2011,9 +1933,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.18.tgz", - "integrity": "sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", "cpu": [ "s390x" ], @@ -2028,9 +1950,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.18.tgz", - "integrity": "sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", "cpu": [ "x64" ], @@ -2045,9 +1967,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.18.tgz", - "integrity": "sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", "cpu": [ "x64" ], @@ -2062,9 +1984,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.18.tgz", - "integrity": "sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", "cpu": [ "arm64" ], @@ -2079,9 +2001,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.18.tgz", - "integrity": "sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", "cpu": [ "wasm32" ], @@ -2162,9 +2084,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.18.tgz", - "integrity": "sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", "cpu": [ "arm64" ], @@ -2179,9 +2101,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.18.tgz", - "integrity": "sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", "cpu": [ "x64" ], @@ -2196,16 +2118,16 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.18.tgz", - "integrity": "sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", - "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.0.tgz", + "integrity": "sha512-dnxczajOqt0gesZlN5pGQ1s1imQVrsmCw5G2Ci4oM+0WvNz3pyRnlWrT7McoZIb8VlFwCawdmbWRmxRn7HI+VQ==", "cpu": [ "arm" ], @@ -2217,9 +2139,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", - "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.0.tgz", + "integrity": "sha512-Bp3JpGP00Vu3f238ivRrjf7z3xSzVPXqCmaJYA9t2c+c8vKYvOzmXF7LkkeUalTEGd6cZcSWe+PFIP3Vy48fRg==", "cpu": [ "arm64" ], @@ -2231,9 +2153,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", - "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.0.tgz", + "integrity": "sha512-zaYIpr670mUmmZ1tVzUFplbQbG7h3Gugx3L5FoqhsC2m/YnLlR1a7zVLmXNPy+iY1tFPEbNG+HHBXZGyId0G5w==", "cpu": [ "arm64" ], @@ -2245,9 +2167,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", - "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.0.tgz", + "integrity": "sha512-+P49fvkv2dSoeevUW+lgZ/I2JHSsJCK1Lyjj7Cu6E4UHG4tS9XIefzIjo5qhgELjAclnen1rLzK2PMKJdo+Dyg==", "cpu": [ "x64" ], @@ -2259,9 +2181,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", - "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.0.tgz", + "integrity": "sha512-l3FAAOyKJXH2ea6KNFN+MMgC/rnE94YGLXs2ehYqDcCoHt1DpvgWX75BhUJxN38XojP7Ul+4H8PRn7EdyqSDrw==", "cpu": [ "arm64" ], @@ -2273,9 +2195,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", - "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.0.tgz", + "integrity": "sha512-VokPN3TSctKj65cyCNPaUh4vMFA8awxOot/0sp+4J7ZlNRKQEhXhawqPwajoi8H5ZFt61i0ugZJuTKXBjGJ17Q==", "cpu": [ "x64" ], @@ -2287,9 +2209,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", - "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.0.tgz", + "integrity": "sha512-DxH0P3wxm+Yzs/p3zrk9dw1rURu8p0Nv5+MRK/L7OtnLNg5rLZraSBFZ8iUXOd9f2BlhJyEpIZUH/emjq4UJ4g==", "cpu": [ "arm" ], @@ -2301,9 +2223,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", - "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.0.tgz", + "integrity": "sha512-T6ZvMNe84kAz6TBWHC7hGAoEtzP1LWYw/AqayGWEF6uISt3Abk/st06LqRD9THd7Xz3NxzurUpzAuEAUbZf+nw==", "cpu": [ "arm" ], @@ -2315,9 +2237,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", - "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.0.tgz", + "integrity": "sha512-q/4hzvQkDs8b4jIBab1pnLiiM0ayTZsN2amBFPDzuyZxjEd4wDwx0UJFYM3cOZzSf5Kw8fnWSprJzIBMkcR44Q==", "cpu": [ "arm64" ], @@ -2329,9 +2251,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", - "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.0.tgz", + "integrity": "sha512-vvYWX3akdEAY6km+9wAqFDnk6pQsbJKVnj7xawcvs/+fdlYBGp+U+Qq/lLfpIxYIZvZLHMAKD9HLdacSx/r3dw==", "cpu": [ "arm64" ], @@ -2343,9 +2265,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", - "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.0.tgz", + "integrity": "sha512-DePa5cqOxDP/Zp0VOXpeWaGew5iIv5DXp9NYbzkX5PFQyWVX9184WCTh3hvr/7lhXo8ZVlbFLkz8+o/q1dU6gA==", "cpu": [ "loong64" ], @@ -2357,9 +2279,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", - "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.0.tgz", + "integrity": "sha512-LV8aWMB8UChglMCEzs7RkN0GsH29RJaLLqwm9fCIjlqwxQTiWAqNcc7wjBkH31hV0PU/yVxGYvrYsgfea2qw6g==", "cpu": [ "loong64" ], @@ -2371,9 +2293,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", - "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.0.tgz", + "integrity": "sha512-QoNSnwQtaeNu5grdBbsL0tt1uyl5EnS8DA8Mr3nluMXbhdQNyhN+G4tBax7VCdxLKj8YJ0/4OO9Ho84jMnJtKA==", "cpu": [ "ppc64" ], @@ -2385,9 +2307,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", - "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.0.tgz", + "integrity": "sha512-/zZp5MKapIIApE8trN8qLGNSiRN9TUoaUZ1cmVu4XnVdd5LQLOXTtyi+vtfUbNnT3iyjzpPqYeKXmvJ+gJGYWw==", "cpu": [ "ppc64" ], @@ -2399,9 +2321,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", - "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.0.tgz", + "integrity": "sha512-RbrzcD3aJ1k3UbtMRRBNwojdVVyXjuVAFTfn/xPa6EEl6GE9Sm/akPgFTb9aAC9pMKGJ6CtWxaGrqWcabH+ySg==", "cpu": [ "riscv64" ], @@ -2413,9 +2335,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", - "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.0.tgz", + "integrity": "sha512-ZF+onDsBso8PJf1XaG9lB+O9RnBpKGnY6OrzC4CSHrtC1jb6jWLTKK4bRqdoCXHd22gyr2hiYmEAm8Wns/BOCw==", "cpu": [ "riscv64" ], @@ -2427,9 +2349,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", - "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.0.tgz", + "integrity": "sha512-Atk0aSIk5Zx2Wuh9dgRQgLP0Koc8hOeYpbWryMXyk8G8/HmPkwPPkMqIIDhrXHHYqfUzSJA/I7IWSBv8xSmRBA==", "cpu": [ "s390x" ], @@ -2441,9 +2363,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", - "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.0.tgz", + "integrity": "sha512-0uMOcf3eZ5K+K4cYHkdxShFMPlPXCOdfDFEFn9dNYAEEd2cVvmOfH7zFgRVoDgmtQ1m9k5q7qfrHzyMAubKYUA==", "cpu": [ "x64" ], @@ -2455,9 +2377,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", - "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.0.tgz", + "integrity": "sha512-mvFtE4A/t/7hRJ7X8Ozmu8FsIkAUat2nzl12pgU337BRmq87AQUJztwHz2Zv5/tjo9/C95E66CK03SI/ToEDJw==", "cpu": [ "x64" ], @@ -2469,9 +2391,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", - "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.0.tgz", + "integrity": "sha512-z9b9+aTxvt8n2rNltMPvyaUfB8NJ+CVyOrGK/MdIKHx7B+lXmZpm/XbRsU7Rpf3fRqJ2uS6mBJiJveCtq8LHDg==", "cpu": [ "x64" ], @@ -2483,9 +2405,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", - "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.0.tgz", + "integrity": "sha512-jXaXFqKMehsOc+g8R6oo33RRC6w07G9jDBxAE5eAKX7mOcCbZloYIPNhfG9Wl+P9O9IWHFO4OJgPi1Ml2qkt7w==", "cpu": [ "arm64" ], @@ -2497,9 +2419,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", - "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.0.tgz", + "integrity": "sha512-OXNWVFocS2IA4+QplhTZZ2a+8hPZR7T8KuozsNmJKK8y7cp83StHvGksfHzPG3wczWTczyWHVQuqeiTUbjiyBg==", "cpu": [ "arm64" ], @@ -2511,9 +2433,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", - "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.0.tgz", + "integrity": "sha512-AlAbNtBO637LxSldqV43z0FfXoGfl2TW1DgAg/bs7aQswFbDewz2SJm3BUhiGfbOVtW571xbc9p+REdxhyN/Eg==", "cpu": [ "ia32" ], @@ -2525,9 +2447,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", - "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.0.tgz", + "integrity": "sha512-QRSrQXyJ1M4tjNXdR0/G/IgV6lzfQQJYBjlWIEYkY2Xs86DRl/iEpQ4blMDjJxSl7n19eDKKXMg0AmuBVYy8pQ==", "cpu": [ "x64" ], @@ -2539,9 +2461,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", - "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.0.tgz", + "integrity": "sha512-tkuFxhvKO/HlGd0VsINF6vHSYH8AF8W0TcNxKDK6JZmrehngFj78pToc8iemtnvwilDjs2G/qSzYFhe9U8q+fw==", "cpu": [ "x64" ], @@ -2781,6 +2703,10 @@ "resolved": "packages/machine", "link": true }, + "node_modules/@turing-machine-js/visuals": { + "resolved": "packages/visuals", + "link": true + }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -2838,13 +2764,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.2.tgz", - "integrity": "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==", + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.19.0" + "undici-types": ">=7.24.0 <7.24.7" } }, "node_modules/@types/normalize-package-data": { @@ -2855,17 +2781,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.2.tgz", - "integrity": "sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz", + "integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.59.2", - "@typescript-eslint/type-utils": "8.59.2", - "@typescript-eslint/utils": "8.59.2", - "@typescript-eslint/visitor-keys": "8.59.2", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/type-utils": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -2878,7 +2804,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.59.2", + "@typescript-eslint/parser": "^8.60.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -2894,16 +2820,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.2.tgz", - "integrity": "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz", + "integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.59.2", - "@typescript-eslint/types": "8.59.2", - "@typescript-eslint/typescript-estree": "8.59.2", - "@typescript-eslint/visitor-keys": "8.59.2", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", "debug": "^4.4.3" }, "engines": { @@ -2919,14 +2845,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.2.tgz", - "integrity": "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz", + "integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.59.2", - "@typescript-eslint/types": "^8.59.2", + "@typescript-eslint/tsconfig-utils": "^8.60.1", + "@typescript-eslint/types": "^8.60.1", "debug": "^4.4.3" }, "engines": { @@ -2941,14 +2867,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.2.tgz", - "integrity": "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz", + "integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.2", - "@typescript-eslint/visitor-keys": "8.59.2" + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2959,9 +2885,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.2.tgz", - "integrity": "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz", + "integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==", "dev": true, "license": "MIT", "engines": { @@ -2976,15 +2902,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.2.tgz", - "integrity": "sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz", + "integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.2", - "@typescript-eslint/typescript-estree": "8.59.2", - "@typescript-eslint/utils": "8.59.2", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -3001,9 +2927,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.2.tgz", - "integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz", + "integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==", "dev": true, "license": "MIT", "engines": { @@ -3015,16 +2941,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.2.tgz", - "integrity": "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz", + "integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.59.2", - "@typescript-eslint/tsconfig-utils": "8.59.2", - "@typescript-eslint/types": "8.59.2", - "@typescript-eslint/visitor-keys": "8.59.2", + "@typescript-eslint/project-service": "8.60.1", + "@typescript-eslint/tsconfig-utils": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -3042,23 +2968,10 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/typescript-estree/node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -3073,16 +2986,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.2.tgz", - "integrity": "sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz", + "integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.59.2", - "@typescript-eslint/types": "8.59.2", - "@typescript-eslint/typescript-estree": "8.59.2" + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3097,13 +3010,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.2.tgz", - "integrity": "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz", + "integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/types": "8.60.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -3115,14 +3028,14 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.5.tgz", - "integrity": "sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz", + "integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.5", + "@vitest/utils": "4.1.8", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -3136,8 +3049,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.5", - "vitest": "4.1.5" + "@vitest/browser": "4.1.8", + "vitest": "4.1.8" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -3156,16 +3069,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", - "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", + "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -3174,13 +3087,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", - "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", + "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.5", + "@vitest/spy": "4.1.8", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -3201,9 +3114,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", - "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", + "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", "dev": true, "license": "MIT", "dependencies": { @@ -3214,13 +3127,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", - "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", + "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.5", + "@vitest/utils": "4.1.8", "pathe": "^2.0.3" }, "funding": { @@ -3228,14 +3141,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", - "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", + "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/pretty-format": "4.1.8", + "@vitest/utils": "4.1.8", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -3244,9 +3157,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", - "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", + "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", "dev": true, "license": "MIT", "funding": { @@ -3254,13 +3167,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", - "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", + "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.5", + "@vitest/pretty-format": "4.1.8", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -3473,13 +3386,13 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", - "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", "dev": true, "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", + "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } @@ -3733,19 +3646,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/cacache/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/cacache/node_modules/ssri": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", @@ -4184,19 +4084,6 @@ "node": ">=14" } }, - "node_modules/conventional-changelog-writer/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/conventional-commits-filter": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-3.0.0.tgz", @@ -4718,18 +4605,18 @@ } }, "node_modules/eslint": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.3.0.tgz", - "integrity": "sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz", + "integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.5.5", + "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.1", + "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -5076,9 +4963,9 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "dev": true, "funding": [ { @@ -5429,19 +5316,6 @@ "node": ">=14" } }, - "node_modules/git-semver-tags/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/git-up": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", @@ -5813,19 +5687,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/init-package-json/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/inquirer": { "version": "12.9.6", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.9.6.tgz", @@ -6628,19 +6489,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/libnpmpublish/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", @@ -7013,19 +6861,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/make-fetch-happen": { "version": "15.0.2", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.2.tgz", @@ -7590,19 +7425,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/node-gyp/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-gyp/node_modules/which": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", @@ -7677,19 +7499,6 @@ "node": ">=10" } }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/normalize-package-data/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -7723,19 +7532,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm-install-checks/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/npm-normalize-package-bin": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", @@ -7762,19 +7558,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm-package-arg/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/npm-packlist": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.3.tgz", @@ -7838,19 +7621,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm-pick-manifest/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/npm-registry-fetch": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.0.tgz", @@ -7885,9 +7655,9 @@ } }, "node_modules/nx": { - "version": "22.7.1", - "resolved": "https://registry.npmjs.org/nx/-/nx-22.7.1.tgz", - "integrity": "sha512-SadJUQY57MiwRIetm9rhZhdpFeOe1Csib2Vg9C423Pw/h0fZE14qUo6+OBby9vLh5QCkRfRZ0WaHkeO5q6yNtA==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/nx/-/nx-22.7.5.tgz", + "integrity": "sha512-zoxsJabb33jl1QYnalDn0bicryrEBgSzdKp90d7VGGv/jDgzKrcLg/hw2ZxeYiOjWPIT/o8QNT9G9vTs4dv3AQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -7905,11 +7675,11 @@ "ansi-styles": "4.3.0", "argparse": "2.0.1", "asynckit": "0.4.0", - "axios": "1.15.0", + "axios": "1.16.0", "balanced-match": "4.0.3", "base64-js": "1.5.1", "bl": "4.1.0", - "brace-expansion": "5.0.2", + "brace-expansion": "5.0.6", "buffer": "5.7.1", "call-bind-apply-helpers": "1.0.2", "chalk": "4.1.2", @@ -7938,7 +7708,7 @@ "escape-string-regexp": "1.0.5", "figures": "3.2.0", "flat": "5.0.2", - "follow-redirects": "1.15.11", + "follow-redirects": "1.16.0", "form-data": "4.0.5", "fs-constants": "1.0.0", "function-bind": "1.1.2", @@ -7966,7 +7736,7 @@ "mime-db": "1.52.0", "mime-types": "2.1.35", "mimic-fn": "2.1.0", - "minimatch": "10.2.4", + "minimatch": "10.2.5", "minimist": "1.2.8", "npm-run-path": "4.0.1", "once": "1.4.0", @@ -7990,7 +7760,7 @@ "strip-bom": "3.0.0", "supports-color": "7.2.0", "tar-stream": "2.2.0", - "tmp": "0.2.4", + "tmp": "0.2.6", "tree-kill": "1.2.2", "tsconfig-paths": "4.2.0", "tslib": "2.8.1", @@ -7999,7 +7769,7 @@ "wrap-ansi": "7.0.0", "wrappy": "1.0.2", "y18n": "5.0.8", - "yaml": "2.8.0", + "yaml": "2.9.0", "yargs": "17.7.2", "yargs-parser": "21.1.1" }, @@ -8008,16 +7778,16 @@ "nx-cloud": "dist/bin/nx-cloud.js" }, "optionalDependencies": { - "@nx/nx-darwin-arm64": "22.7.1", - "@nx/nx-darwin-x64": "22.7.1", - "@nx/nx-freebsd-x64": "22.7.1", - "@nx/nx-linux-arm-gnueabihf": "22.7.1", - "@nx/nx-linux-arm64-gnu": "22.7.1", - "@nx/nx-linux-arm64-musl": "22.7.1", - "@nx/nx-linux-x64-gnu": "22.7.1", - "@nx/nx-linux-x64-musl": "22.7.1", - "@nx/nx-win32-arm64-msvc": "22.7.1", - "@nx/nx-win32-x64-msvc": "22.7.1" + "@nx/nx-darwin-arm64": "22.7.5", + "@nx/nx-darwin-x64": "22.7.5", + "@nx/nx-freebsd-x64": "22.7.5", + "@nx/nx-linux-arm-gnueabihf": "22.7.5", + "@nx/nx-linux-arm64-gnu": "22.7.5", + "@nx/nx-linux-arm64-musl": "22.7.5", + "@nx/nx-linux-x64-gnu": "22.7.5", + "@nx/nx-linux-x64-musl": "22.7.5", + "@nx/nx-win32-arm64-msvc": "22.7.5", + "@nx/nx-win32-x64-msvc": "22.7.5" }, "peerDependencies": { "@swc-node/register": "^1.11.1", @@ -8059,19 +7829,6 @@ "node": "20 || >=22" } }, - "node_modules/nx/node_modules/brace-expansion": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", - "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/nx/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -8105,22 +7862,6 @@ "node": ">= 4" } }, - "node_modules/nx/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/nx/node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -8610,19 +8351,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/pacote/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/pacote/node_modules/which": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", @@ -8831,9 +8559,9 @@ } }, "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -8851,7 +8579,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -9370,14 +9098,14 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz", - "integrity": "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.128.0", - "@rolldown/pluginutils": "1.0.0-rc.18" + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" @@ -9386,31 +9114,31 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.18", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.18", - "@rolldown/binding-darwin-x64": "1.0.0-rc.18", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.18", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.18", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.18", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.18", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.18", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.18", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.18", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.18", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.18", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.18", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.18", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18" + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" } }, "node_modules/rollup": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", - "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.0.tgz", + "integrity": "sha512-T9mWdbWfQtp0B5lv/HX+wrhYsmXRlcWnXXmJbXqKJhlRaoS6KMhq0gpyzW4UJfclcxrEdLnTgjT2NjruLONu0g==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@types/estree": "1.0.9" }, "bin": { "rollup": "dist/bin/rollup" @@ -9420,41 +9148,34 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.3", - "@rollup/rollup-android-arm64": "4.60.3", - "@rollup/rollup-darwin-arm64": "4.60.3", - "@rollup/rollup-darwin-x64": "4.60.3", - "@rollup/rollup-freebsd-arm64": "4.60.3", - "@rollup/rollup-freebsd-x64": "4.60.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", - "@rollup/rollup-linux-arm-musleabihf": "4.60.3", - "@rollup/rollup-linux-arm64-gnu": "4.60.3", - "@rollup/rollup-linux-arm64-musl": "4.60.3", - "@rollup/rollup-linux-loong64-gnu": "4.60.3", - "@rollup/rollup-linux-loong64-musl": "4.60.3", - "@rollup/rollup-linux-ppc64-gnu": "4.60.3", - "@rollup/rollup-linux-ppc64-musl": "4.60.3", - "@rollup/rollup-linux-riscv64-gnu": "4.60.3", - "@rollup/rollup-linux-riscv64-musl": "4.60.3", - "@rollup/rollup-linux-s390x-gnu": "4.60.3", - "@rollup/rollup-linux-x64-gnu": "4.60.3", - "@rollup/rollup-linux-x64-musl": "4.60.3", - "@rollup/rollup-openbsd-x64": "4.60.3", - "@rollup/rollup-openharmony-arm64": "4.60.3", - "@rollup/rollup-win32-arm64-msvc": "4.60.3", - "@rollup/rollup-win32-ia32-msvc": "4.60.3", - "@rollup/rollup-win32-x64-gnu": "4.60.3", - "@rollup/rollup-win32-x64-msvc": "4.60.3", + "@rollup/rollup-android-arm-eabi": "4.61.0", + "@rollup/rollup-android-arm64": "4.61.0", + "@rollup/rollup-darwin-arm64": "4.61.0", + "@rollup/rollup-darwin-x64": "4.61.0", + "@rollup/rollup-freebsd-arm64": "4.61.0", + "@rollup/rollup-freebsd-x64": "4.61.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.0", + "@rollup/rollup-linux-arm-musleabihf": "4.61.0", + "@rollup/rollup-linux-arm64-gnu": "4.61.0", + "@rollup/rollup-linux-arm64-musl": "4.61.0", + "@rollup/rollup-linux-loong64-gnu": "4.61.0", + "@rollup/rollup-linux-loong64-musl": "4.61.0", + "@rollup/rollup-linux-ppc64-gnu": "4.61.0", + "@rollup/rollup-linux-ppc64-musl": "4.61.0", + "@rollup/rollup-linux-riscv64-gnu": "4.61.0", + "@rollup/rollup-linux-riscv64-musl": "4.61.0", + "@rollup/rollup-linux-s390x-gnu": "4.61.0", + "@rollup/rollup-linux-x64-gnu": "4.61.0", + "@rollup/rollup-linux-x64-musl": "4.61.0", + "@rollup/rollup-openbsd-x64": "4.61.0", + "@rollup/rollup-openharmony-arm64": "4.61.0", + "@rollup/rollup-win32-arm64-msvc": "4.61.0", + "@rollup/rollup-win32-ia32-msvc": "4.61.0", + "@rollup/rollup-win32-x64-gnu": "4.61.0", + "@rollup/rollup-win32-x64-msvc": "4.61.0", "fsevents": "~2.3.2" } }, - "node_modules/rollup/node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, "node_modules/run-async": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.6.tgz", @@ -9503,6 +9224,19 @@ "dev": true, "license": "MIT" }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -9916,9 +9650,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", - "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", "dev": true, "license": "MIT", "engines": { @@ -9953,9 +9687,9 @@ } }, "node_modules/tmp": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz", - "integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.6.tgz", + "integrity": "sha512-5sJPdPjfI5Kx+qbrDesxkglRBxW//g7hCsqspEjwkewGvBMGIKMOTKzLt1hFVJzyadba3lDUN20O9qhvbQUSTA==", "dev": true, "license": "MIT", "engines": { @@ -10087,16 +9821,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.2.tgz", - "integrity": "sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.1.tgz", + "integrity": "sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.59.2", - "@typescript-eslint/parser": "8.59.2", - "@typescript-eslint/typescript-estree": "8.59.2", - "@typescript-eslint/utils": "8.59.2" + "@typescript-eslint/eslint-plugin": "8.60.1", + "@typescript-eslint/parser": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10135,9 +9869,9 @@ } }, "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, "license": "MIT" }, @@ -10208,17 +9942,17 @@ } }, "node_modules/vite": { - "version": "8.0.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.11.tgz", - "integrity": "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==", + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", - "postcss": "^8.5.14", - "rolldown": "1.0.0-rc.18", - "tinyglobby": "^0.2.16" + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" @@ -10286,9 +10020,9 @@ } }, "node_modules/vite/node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -10303,19 +10037,19 @@ } }, "node_modules/vitest": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", - "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", + "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.5", - "@vitest/mocker": "4.1.5", - "@vitest/pretty-format": "4.1.5", - "@vitest/runner": "4.1.5", - "@vitest/snapshot": "4.1.5", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/expect": "4.1.8", + "@vitest/mocker": "4.1.8", + "@vitest/pretty-format": "4.1.8", + "@vitest/runner": "4.1.8", + "@vitest/snapshot": "4.1.8", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -10343,12 +10077,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.5", - "@vitest/browser-preview": "4.1.5", - "@vitest/browser-webdriverio": "4.1.5", - "@vitest/coverage-istanbul": "4.1.5", - "@vitest/coverage-v8": "4.1.5", - "@vitest/ui": "4.1.5", + "@vitest/browser-playwright": "4.1.8", + "@vitest/browser-preview": "4.1.8", + "@vitest/browser-webdriverio": "4.1.8", + "@vitest/coverage-istanbul": "4.1.8", + "@vitest/coverage-v8": "4.1.8", + "@vitest/ui": "4.1.8", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -10393,9 +10127,9 @@ } }, "node_modules/vitest/node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -10559,9 +10293,9 @@ } }, "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "dev": true, "license": "ISC", "bin": { @@ -10569,6 +10303,9 @@ }, "engines": { "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yargs": { @@ -10656,44 +10393,55 @@ }, "packages/builder": { "name": "@turing-machine-js/builder", - "version": "6.4.0", + "version": "7.0.0", "license": "GPL-3.0-or-later", "engines": { "npm": ">=7.0.0" }, "peerDependencies": { - "@turing-machine-js/machine": "^6.0.0" + "@turing-machine-js/machine": "^7.0.0" } }, "packages/library-binary-numbers": { "name": "@turing-machine-js/library-binary-numbers", - "version": "6.4.0", + "version": "7.0.0", "license": "GPL-3.0-or-later", "engines": { "npm": ">=7.0.0" }, "peerDependencies": { - "@turing-machine-js/machine": "^6.0.0" + "@turing-machine-js/machine": "^7.0.0" } }, "packages/library-binary-numbers-bare": { "name": "@turing-machine-js/library-binary-numbers-bare", - "version": "6.4.0", + "version": "7.0.0", "license": "GPL-3.0-or-later", "engines": { "npm": ">=7.0.0" }, "peerDependencies": { - "@turing-machine-js/machine": "^6.0.0" + "@turing-machine-js/machine": "^7.0.0" } }, "packages/machine": { "name": "@turing-machine-js/machine", - "version": "6.4.0", + "version": "7.0.0", "license": "GPL-3.0-or-later", "engines": { "npm": ">=7.0.0" } + }, + "packages/visuals": { + "name": "@turing-machine-js/visuals", + "version": "7.0.0", + "license": "GPL-3.0-or-later", + "engines": { + "npm": ">=7.0.0" + }, + "peerDependencies": { + "@turing-machine-js/machine": "^7.0.0" + } } } } diff --git a/package.json b/package.json index 9ebb1be..201c783 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "test:coverage": "vitest run --coverage", "docs:states": "node ./scripts/build-states-md.mjs", "lint": "eslint .", - "typecheck": "tsc --noEmit -p packages/machine/tsconfig.json && tsc --noEmit -p packages/builder/tsconfig.json && tsc --noEmit -p packages/library-binary-numbers/tsconfig.json && tsc --noEmit -p packages/library-binary-numbers-bare/tsconfig.json" + "typecheck": "tsc --noEmit -p packages/machine/tsconfig.json && tsc --noEmit -p packages/builder/tsconfig.json && tsc --noEmit -p packages/library-binary-numbers/tsconfig.json && tsc --noEmit -p packages/library-binary-numbers-bare/tsconfig.json && tsc --noEmit -p packages/visuals/tsconfig.json" }, "author": "Ruslan Gilmullin ", "workspaces": [ @@ -21,13 +21,13 @@ ], "devDependencies": { "@tsconfig/recommended": "^1.0.13", - "@types/node": "^25.6.2", - "@vitest/coverage-v8": "4.1.5", - "eslint": "^10.2.1", + "@types/node": "^25.9.1", + "@vitest/coverage-v8": "^4.1.8", + "eslint": "^10.4.1", "lerna": "^9.0.7", - "rollup": "^4.60.2", + "rollup": "^4.61.0", "typescript": "^6.0.3", - "typescript-eslint": "^8.59.1", - "vitest": "4.1.5" + "typescript-eslint": "^8.60.1", + "vitest": "^4.1.8" } } diff --git a/packages/builder/CHANGELOG.md b/packages/builder/CHANGELOG.md index 3ef8afc..5055de6 100644 --- a/packages/builder/CHANGELOG.md +++ b/packages/builder/CHANGELOG.md @@ -4,6 +4,46 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [7.0.0] - 2026-06-03 + +Stable v7. Lockstep release with `@turing-machine-js/machine` 7.0.0. See the machine package CHANGELOG for the cumulative v7 trajectory. + +### Changed + +- Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.8` → `^7.0.0`. + +## [7.0.0-alpha.8] - 2026-06-02 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.8 — lifts `TapeSnapshot` + `tapeViewport` from `@turing-machine-js/visuals` into the engine ([#227](https://github.com/mellonis/turing-machine-js/issues/227)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.7` → `^7.0.0-alpha.8`. + +## [7.0.0-alpha.7] - 2026-05-30 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.7 — adds `CallFrame` as a first-class `State` subclass ([#213](https://github.com/mellonis/turing-machine-js/issues/213)) and fixes a `toMermaid` framed-wrapper emit asymmetry ([#223](https://github.com/mellonis/turing-machine-js/issues/223)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.6` → `^7.0.0-alpha.7`. + +## [7.0.0-alpha.6] - 2026-05-28 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.6 — adds the `DebugSession` interactive-debugging class and reshapes the engine debug surface: `run()` becomes synchronous + callback-free, `runStepByStep` becomes the pure-iteration primitive (no breakpoint detection), and the per-yield `m.debugBreak` is replaced by a one-sided `m.pause: { side, cause }` carried on `DebugSession` `pause` events ([#102](https://github.com/mellonis/turing-machine-js/issues/102)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.5` → `^7.0.0-alpha.6`. + +## [7.0.0-alpha.5] - 2026-05-25 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.5 — adds per-iter `MachineState.matchedTransition` + renames `GraphTransition.id` separator `-` → `.` ([#205](https://github.com/mellonis/turing-machine-js/issues/205)), collapses `haltState.debug` to a `boolean` with halt-imminent pause on the AFTER side of the halt-triggering iter ([#207](https://github.com/mellonis/turing-machine-js/issues/207)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.4` → `^7.0.0-alpha.5`. + +## [7.0.0-alpha.4] - 2026-05-23 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.4 — adds `State.collectStates` ([#195](https://github.com/mellonis/turing-machine-js/issues/195)), extracts graph serialization into `utilities/stateGraph.ts` ([#180](https://github.com/mellonis/turing-machine-js/issues/180)), fixes `toMermaid` label escape ([#194](https://github.com/mellonis/turing-machine-js/issues/194)) and `runStepByStep` halt-stack scope ([#196](https://github.com/mellonis/turing-machine-js/issues/196)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.3` → `^7.0.0-alpha.4`. + +## [7.0.0-alpha.3] - 2026-05-21 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.3 — first-class out-of-band State tags ([#186](https://github.com/mellonis/turing-machine-js/issues/186)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.2` → `^7.0.0-alpha.3`. + +## [7.0.0-alpha.2] - 2026-05-21 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.2 — `toMermaid` callable-subtree emit refinement ([#174](https://github.com/mellonis/turing-machine-js/issues/174)), `withOverriddenHaltState` memoization ([#175](https://github.com/mellonis/turing-machine-js/issues/175)), nested `.wohs()` chain collapse ([#176](https://github.com/mellonis/turing-machine-js/issues/176)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.1` → `^7.0.0-alpha.2`. + +## [7.0.0-alpha.1] - 2026-05-21 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.1 — composition-representation overhaul: `withOverrodeHaltState` → `withOverriddenHaltState` ([#149](https://github.com/mellonis/turing-machine-js/issues/149)), paren-based wrapped-state naming `A(B)` ([#148](https://github.com/mellonis/turing-machine-js/issues/148)), `toMermaid` callable-subtree emit alpha.1 collapsed-bare shape ([#138](https://github.com/mellonis/turing-machine-js/issues/138), [#139](https://github.com/mellonis/turing-machine-js/issues/139)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^6.0.0` → `^7.0.0-alpha.1`. + ## [6.4.0] - 2026-05-19 Released in lockstep with `@turing-machine-js/machine` 6.4.0. No source or behavior changes in this package. diff --git a/packages/builder/LICENSE b/packages/builder/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/packages/builder/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/packages/builder/README.md b/packages/builder/README.md index 9f3e9a5..f7a8415 100644 --- a/packages/builder/README.md +++ b/packages/builder/README.md @@ -43,7 +43,7 @@ The state-table format is intentionally minimal. It does **not** support: - **OR-patterns** (matching multiple current symbols with one transition row). For `tapeBlock.symbol('^10$')` style patterns, use the raw `@turing-machine-js/machine` API. - **Multi-tape machines** (`buildMachine` is single-tape only). -- **`withOverrodeHaltState` composition** (the subroutine-call mechanism). For composed machines like `library-binary-numbers`'s `minusOne`, use the raw API. +- **`withOverriddenHaltState` composition** (the subroutine-call mechanism). For composed machines like `library-binary-numbers`'s `minusOne`, use the raw API. If you need any of the above, the inline state-table example in [`@turing-machine-js/machine`'s README](../machine/README.md) shows how to write your own `buildMachine`-equivalent in ~30 lines, and you can extend it to fit your case. diff --git a/packages/builder/package.json b/packages/builder/package.json index 138af98..f92bf6c 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@turing-machine-js/builder", - "version": "6.4.0", + "version": "7.0.0", "description": "A turing machine builder — declarative state-table construction. Not actively developed by the author; the same state-table pattern is also shown as an inline example in @turing-machine-js/machine's README. Contributions welcome.", "engines": { "npm": ">=7.0.0" @@ -25,7 +25,7 @@ "builder" ], "peerDependencies": { - "@turing-machine-js/machine": "^6.0.0" + "@turing-machine-js/machine": "^7.0.0" }, "scripts": { "build": "tsc --build --verbose tsconfig.build.json && node ../../scripts/build-node-entries.mjs --package=@turing-machine-js/builder", diff --git a/packages/builder/src/builder.spec.ts b/packages/builder/src/builder.spec.ts index b1dfb20..ea96975 100644 --- a/packages/builder/src/builder.spec.ts +++ b/packages/builder/src/builder.spec.ts @@ -1,4 +1,4 @@ -import {haltState, Tape} from '@turing-machine-js/machine'; +import {DebugSession, haltState, Tape} from '@turing-machine-js/machine'; import buildMachine, {States} from './index'; describe('buildMachine', () => { @@ -113,10 +113,12 @@ describe('buildMachine — debug config (#101)', () => { })); const pausedSymbols: string[] = []; - await machine.run({ - initialState: init, - onPause: (m) => { pausedSymbols.push(m.currentSymbols[0]); }, + const session = new DebugSession(machine, {initialState: init}); + session.on('pause', (m) => { + pausedSymbols.push(m.currentSymbols[0]); + session.continue(); }); + await session.start(); // Trajectory: A (pause) → A (pause) → B (no pause; B not in filter) → halt. expect(pausedSymbols).toEqual(['A', 'A']); @@ -124,7 +126,7 @@ describe('buildMachine — debug config (#101)', () => { test('debug.after symbol-list fires on the halting iter\'s own yield', async () => { // 'B' triggers the halting transition; the after-fire for B reaches - // onPause on B's own yield (post-#119 dispatch model). + // onPause on B's own yield. const [machine, init] = buildLoopMachine({Q0: {after: ['B']}}); machine.tapeBlock.replaceTape(new Tape({ alphabet: machine.tapeBlock.alphabets[0], @@ -132,10 +134,12 @@ describe('buildMachine — debug config (#101)', () => { })); let afterCount = 0; - await machine.run({ - initialState: init, - onPause: (m) => { if (m.debugBreak?.after) afterCount += 1; }, + const session = new DebugSession(machine, {initialState: init}); + session.on('pause', (m) => { + if (m.pause.side === 'after') afterCount += 1; + session.continue(); }); + await session.start(); expect(afterCount).toBe(1); }); @@ -152,13 +156,13 @@ describe('buildMachine — debug config (#101)', () => { let beforeCount = 0; let afterCount = 0; - await machine.run({ - initialState: init, - onPause: (m) => { - if (m.debugBreak?.before) beforeCount += 1; - if (m.debugBreak?.after) afterCount += 1; - }, + const session = new DebugSession(machine, {initialState: init}); + session.on('pause', (m) => { + if (m.pause.side === 'before') beforeCount += 1; + if (m.pause.side === 'after') afterCount += 1; + session.continue(); }); + await session.start(); expect(beforeCount).toBeGreaterThan(0); expect(afterCount).toBeGreaterThan(0); diff --git a/packages/library-binary-numbers-bare/CHANGELOG.md b/packages/library-binary-numbers-bare/CHANGELOG.md index 28c4dc6..7cb2a5b 100644 --- a/packages/library-binary-numbers-bare/CHANGELOG.md +++ b/packages/library-binary-numbers-bare/CHANGELOG.md @@ -4,6 +4,46 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [7.0.0] - 2026-06-03 + +Stable v7. Lockstep release with `@turing-machine-js/machine` 7.0.0. See the machine package CHANGELOG for the cumulative v7 trajectory. + +### Changed + +- Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.8` → `^7.0.0`. + +## [7.0.0-alpha.8] - 2026-06-02 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.8 — lifts `TapeSnapshot` + `tapeViewport` from `@turing-machine-js/visuals` into the engine ([#227](https://github.com/mellonis/turing-machine-js/issues/227)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.7` → `^7.0.0-alpha.8`. + +## [7.0.0-alpha.7] - 2026-05-30 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.7 — adds `CallFrame` as a first-class `State` subclass ([#213](https://github.com/mellonis/turing-machine-js/issues/213)) and fixes a `toMermaid` framed-wrapper emit asymmetry ([#223](https://github.com/mellonis/turing-machine-js/issues/223)). No source or behavior changes in this package — `states.md` is unaffected (no diagram here uses an inner-wrapper-call pattern). Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.6` → `^7.0.0-alpha.7`. + +## [7.0.0-alpha.6] - 2026-05-28 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.6 — adds the `DebugSession` interactive-debugging class and reshapes the engine debug surface: `run()` becomes synchronous + callback-free, `runStepByStep` becomes the pure-iteration primitive (no breakpoint detection), and the per-yield `m.debugBreak` is replaced by a one-sided `m.pause: { side, cause }` carried on `DebugSession` `pause` events ([#102](https://github.com/mellonis/turing-machine-js/issues/102)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.5` → `^7.0.0-alpha.6`. + +## [7.0.0-alpha.5] - 2026-05-25 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.5 — adds per-iter `MachineState.matchedTransition` + renames `GraphTransition.id` separator `-` → `.` ([#205](https://github.com/mellonis/turing-machine-js/issues/205)), collapses `haltState.debug` to a `boolean` with halt-imminent pause on the AFTER side of the halt-triggering iter ([#207](https://github.com/mellonis/turing-machine-js/issues/207)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.4` → `^7.0.0-alpha.5`. + +## [7.0.0-alpha.4] - 2026-05-23 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.4 — adds `State.collectStates` ([#195](https://github.com/mellonis/turing-machine-js/issues/195)), extracts graph serialization into `utilities/stateGraph.ts` ([#180](https://github.com/mellonis/turing-machine-js/issues/180)), fixes `toMermaid` label escape ([#194](https://github.com/mellonis/turing-machine-js/issues/194)) and `runStepByStep` halt-stack scope ([#196](https://github.com/mellonis/turing-machine-js/issues/196)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.3` → `^7.0.0-alpha.4`. + +## [7.0.0-alpha.3] - 2026-05-21 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.3 — first-class out-of-band State tags ([#186](https://github.com/mellonis/turing-machine-js/issues/186)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.2` → `^7.0.0-alpha.3`. + +## [7.0.0-alpha.2] - 2026-05-21 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.2 — `toMermaid` callable-subtree emit refinement ([#174](https://github.com/mellonis/turing-machine-js/issues/174)), `withOverriddenHaltState` memoization ([#175](https://github.com/mellonis/turing-machine-js/issues/175)), nested `.wohs()` chain collapse ([#176](https://github.com/mellonis/turing-machine-js/issues/176)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.1` → `^7.0.0-alpha.2`. + +## [7.0.0-alpha.1] - 2026-05-21 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.1 — composition-representation overhaul: `withOverrodeHaltState` → `withOverriddenHaltState` ([#149](https://github.com/mellonis/turing-machine-js/issues/149)), paren-based wrapped-state naming `A(B)` ([#148](https://github.com/mellonis/turing-machine-js/issues/148)), `toMermaid` callable-subtree emit alpha.1 collapsed-bare shape ([#138](https://github.com/mellonis/turing-machine-js/issues/138), [#139](https://github.com/mellonis/turing-machine-js/issues/139)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^6.0.0` → `^7.0.0-alpha.1`. + ## [6.4.0] - 2026-05-19 Released in lockstep with `@turing-machine-js/machine` 6.4.0. No source or behavior changes in this package. diff --git a/packages/library-binary-numbers-bare/LICENSE b/packages/library-binary-numbers-bare/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/packages/library-binary-numbers-bare/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/packages/library-binary-numbers-bare/package.json b/packages/library-binary-numbers-bare/package.json index 2efd624..6a70b47 100644 --- a/packages/library-binary-numbers-bare/package.json +++ b/packages/library-binary-numbers-bare/package.json @@ -1,6 +1,6 @@ { "name": "@turing-machine-js/library-binary-numbers-bare", - "version": "6.4.0", + "version": "7.0.0", "description": "Single-number binary arithmetic on a 3-symbol alphabet (blank, 0, 1) — same operations as @turing-machine-js/library-binary-numbers but without ^/$ markers. Side-by-side with the marker-based library for learning the trade-off.", "engines": { "npm": ">=7.0.0" @@ -28,7 +28,7 @@ "teaching" ], "peerDependencies": { - "@turing-machine-js/machine": "^6.0.0" + "@turing-machine-js/machine": "^7.0.0" }, "scripts": { "build": "tsc --build --verbose tsconfig.build.json && node ../../scripts/build-node-entries.mjs --package=@turing-machine-js/library-binary-numbers-bare", diff --git a/packages/library-binary-numbers-bare/states.md b/packages/library-binary-numbers-bare/states.md index 6ac5d63..1e440f3 100644 --- a/packages/library-binary-numbers-bare/states.md +++ b/packages/library-binary-numbers-bare/states.md @@ -2,62 +2,70 @@ ## plusOne -*3 states (including `haltState`)* +*3 states; 5 transitions; has cycles* ```mermaid flowchart TD %% alphabets: [[" ","0","1"]] s0(((halt))) s1["plusOneCarry"] - s2(("plusOne")) - s1 -- "1 → 0/L" --> s1 - s1 -- "0 → 1/S" --> s0 - s1 -- "- → 1/S" --> s0 - s2 -- "0|1 → ·/R" --> s2 - s2 -- "- → ·/L" --> s1 + s2["plusOne"] + idle([idle]) + idle -. enter .-> s2 + s1 -- "['1'] → ['0']/[L]" --> s1 + s1 -- "['0'] → ['1']/[S]" --> s0 + s1 -- "[B] → ['1']/[S]" --> s0 + s2 -- "['0']|['1'] → [K]/[R]" --> s2 + s2 -- "[B] → [K]/[L]" --> s1 ``` ## minusOne -*3 states (including `haltState`)* +*3 states; 5 transitions; has cycles* ```mermaid flowchart TD %% alphabets: [[" ","0","1"]] s0(((halt))) s3["minusOneBorrow"] - s4(("minusOne")) - s3 -- "0 → 1/L" --> s3 - s3 -- "1 → 0/S" --> s0 - s3 -- "- → ·/S" --> s0 - s4 -- "0|1 → ·/R" --> s4 - s4 -- "- → ·/L" --> s3 + s4["minusOne"] + idle([idle]) + idle -. enter .-> s4 + s3 -- "['0'] → ['1']/[L]" --> s3 + s3 -- "['1'] → ['0']/[S]" --> s0 + s3 -- "[B] → [K]/[S]" --> s0 + s4 -- "['0']|['1'] → [K]/[R]" --> s4 + s4 -- "[B] → [K]/[L]" --> s3 ``` ## invertNumber -*2 states (including `haltState`)* +*2 states; 3 transitions; has cycles* ```mermaid flowchart TD %% alphabets: [[" ","0","1"]] s0(((halt))) - s5(("invertNumber")) - s5 -- "0 → 1/R" --> s5 - s5 -- "1 → 0/R" --> s5 - s5 -- "- → ·/S" --> s0 + s5["invertNumber"] + idle([idle]) + idle -. enter .-> s5 + s5 -- "['0'] → ['1']/[R]" --> s5 + s5 -- "['1'] → ['0']/[R]" --> s5 + s5 -- "[B] → [K]/[S]" --> s0 ``` ## normalizeNumber -*2 states (including `haltState`)* +*2 states; 3 transitions; has cycles* ```mermaid flowchart TD %% alphabets: [[" ","0","1"]] s0(((halt))) - s6(("normalizeNumber")) - s6 -- "0 → ⌫/R" --> s6 - s6 -- "1 → ·/S" --> s0 - s6 -- "- → 0/S" --> s0 + s6["normalizeNumber"] + idle([idle]) + idle -. enter .-> s6 + s6 -- "['0'] → [E]/[R]" --> s6 + s6 -- "['1'] → [K]/[S]" --> s0 + s6 -- "[B] → ['0']/[S]" --> s0 ``` diff --git a/packages/library-binary-numbers/CHANGELOG.md b/packages/library-binary-numbers/CHANGELOG.md index 1310a02..98c8c74 100644 --- a/packages/library-binary-numbers/CHANGELOG.md +++ b/packages/library-binary-numbers/CHANGELOG.md @@ -4,6 +4,46 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [7.0.0] - 2026-06-03 + +Stable v7. Lockstep release with `@turing-machine-js/machine` 7.0.0. See the machine package CHANGELOG for the cumulative v7 trajectory. + +### Changed + +- Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.8` → `^7.0.0`. + +## [7.0.0-alpha.8] - 2026-06-02 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.8 — lifts `TapeSnapshot` + `tapeViewport` from `@turing-machine-js/visuals` into the engine ([#227](https://github.com/mellonis/turing-machine-js/issues/227)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.7` → `^7.0.0-alpha.8`. + +## [7.0.0-alpha.7] - 2026-05-30 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.7 — adds `CallFrame` as a first-class `State` subclass ([#213](https://github.com/mellonis/turing-machine-js/issues/213)) and fixes a `toMermaid` framed-wrapper emit asymmetry ([#223](https://github.com/mellonis/turing-machine-js/issues/223)). The `states.md` file is regenerated under the new emit: the `minusOne` diagram's `goToNumberStart(invertNumberGoToNumberWithInversion)` wrapper now renders INSIDE `invertNumber`'s callable subtree (was outside, visually disconnected from its owner frame). No source or behavior changes. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.6` → `^7.0.0-alpha.7`. + +## [7.0.0-alpha.6] - 2026-05-28 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.6 — adds the `DebugSession` interactive-debugging class and reshapes the engine debug surface: `run()` becomes synchronous + callback-free, `runStepByStep` becomes the pure-iteration primitive (no breakpoint detection), and the per-yield `m.debugBreak` is replaced by a one-sided `m.pause: { side, cause }` carried on `DebugSession` `pause` events ([#102](https://github.com/mellonis/turing-machine-js/issues/102)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.5` → `^7.0.0-alpha.6`. + +## [7.0.0-alpha.5] - 2026-05-25 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.5 — adds per-iter `MachineState.matchedTransition` + renames `GraphTransition.id` separator `-` → `.` ([#205](https://github.com/mellonis/turing-machine-js/issues/205)), collapses `haltState.debug` to a `boolean` with halt-imminent pause on the AFTER side of the halt-triggering iter ([#207](https://github.com/mellonis/turing-machine-js/issues/207)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.4` → `^7.0.0-alpha.5`. + +## [7.0.0-alpha.4] - 2026-05-23 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.4 — adds `State.collectStates` ([#195](https://github.com/mellonis/turing-machine-js/issues/195)), extracts graph serialization into `utilities/stateGraph.ts` ([#180](https://github.com/mellonis/turing-machine-js/issues/180)), fixes `toMermaid` label escape ([#194](https://github.com/mellonis/turing-machine-js/issues/194)) and `runStepByStep` halt-stack scope ([#196](https://github.com/mellonis/turing-machine-js/issues/196)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.3` → `^7.0.0-alpha.4`. + +## [7.0.0-alpha.3] - 2026-05-21 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.3 — first-class out-of-band State tags ([#186](https://github.com/mellonis/turing-machine-js/issues/186)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.2` → `^7.0.0-alpha.3`. + +## [7.0.0-alpha.2] - 2026-05-21 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.2 — `toMermaid` callable-subtree emit refinement ([#174](https://github.com/mellonis/turing-machine-js/issues/174)), `withOverriddenHaltState` memoization ([#175](https://github.com/mellonis/turing-machine-js/issues/175)), nested `.wohs()` chain collapse ([#176](https://github.com/mellonis/turing-machine-js/issues/176)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.1` → `^7.0.0-alpha.2`. + +## [7.0.0-alpha.1] - 2026-05-21 + +Released in lockstep with `@turing-machine-js/machine` 7.0.0-alpha.1 — composition-representation overhaul: `withOverrodeHaltState` → `withOverriddenHaltState` ([#149](https://github.com/mellonis/turing-machine-js/issues/149)), paren-based wrapped-state naming `A(B)` ([#148](https://github.com/mellonis/turing-machine-js/issues/148)), `toMermaid` callable-subtree emit alpha.1 collapsed-bare shape ([#138](https://github.com/mellonis/turing-machine-js/issues/138), [#139](https://github.com/mellonis/turing-machine-js/issues/139)). No source or behavior changes in this package. Peer dep `@turing-machine-js/machine` widened `^6.0.0` → `^7.0.0-alpha.1`. + ## [6.4.0] - 2026-05-19 Released in lockstep with `@turing-machine-js/machine` 6.4.0. No source or behavior changes in this package. diff --git a/packages/library-binary-numbers/package.json b/packages/library-binary-numbers/package.json index d938e47..bcb8fb7 100644 --- a/packages/library-binary-numbers/package.json +++ b/packages/library-binary-numbers/package.json @@ -1,6 +1,6 @@ { "name": "@turing-machine-js/library-binary-numbers", - "version": "6.4.0", + "version": "7.0.0", "description": "A standard library for working with binary numbers", "engines": { "npm": ">=7.0.0" @@ -27,7 +27,7 @@ "numbers" ], "peerDependencies": { - "@turing-machine-js/machine": "^6.0.0" + "@turing-machine-js/machine": "^7.0.0" }, "scripts": { "build": "tsc --build --verbose tsconfig.build.json && node ../../scripts/build-node-entries.mjs --package=@turing-machine-js/library-binary-numbers", diff --git a/packages/library-binary-numbers/src/graphs.spec.ts b/packages/library-binary-numbers/src/graphs.spec.ts index 291fde6..71becea 100644 --- a/packages/library-binary-numbers/src/graphs.spec.ts +++ b/packages/library-binary-numbers/src/graphs.spec.ts @@ -1,11 +1,10 @@ -import {State, fromMermaid, toMermaid} from '@turing-machine-js/machine'; +import {State, fromMermaid, summarizeGraph, toMermaid} from '@turing-machine-js/machine'; import binaryNumbers from './index'; -// Per-state node counts pinned from the source comments above each declaration -// in `index.ts`. Each count includes haltState (`State.toGraph` walks the full -// reachable graph, and every algorithm transitions to halt). Regressions caught: -// a refactor that accidentally grows or shrinks an algorithm's state graph -// fails this table. +// Per-state counts pinned from the source comments above each declaration in +// `index.ts`. Runtime state counts (per `summarize().stateCount`) — excludes +// `isHaltMarker` visualization sentinels synthesized inside `halt frame` +// subgraphs. Matches the states.md per-algorithm header by construction. const expectedNodeCount: Record = { goToNumber: 2, goToNextNumber: 3, @@ -15,7 +14,7 @@ const expectedNodeCount: Record = invertNumber: 5, normalizeNumber: 7, plusOne: 5, - minusOne: 17, + minusOne: 18, minusOneFast: 10, }; @@ -28,7 +27,7 @@ describe('library-binary-numbers state graphs', () => { const tapeBlock = binaryNumbers.getTapeBlock(); const graph = State.toGraph(binaryNumbers.states[name], tapeBlock); - expect(Object.keys(graph.nodes)).toHaveLength(expectedNodeCount[name]); + expect(summarizeGraph(graph).stateCount).toBe(expectedNodeCount[name]); }, ); @@ -38,11 +37,14 @@ describe('library-binary-numbers state graphs', () => { const tapeBlock = binaryNumbers.getTapeBlock(); const graph = State.toGraph(binaryNumbers.states[name], tapeBlock); - const haltNodes = Object.values(graph.nodes).filter((node) => node.isHalt); + // Every algorithm has exactly one REAL halt node (the singleton's id is + // shared across all states' graphs). v7's wrapper-emit synthesizes one + // `isHaltMarker: true` node per wrapper context as a visualization aid — + // those are filtered out here. + const realHaltNodes = Object.values(graph.nodes) + .filter((node) => node.isHalt && !node.isHaltMarker); - // Every algorithm has exactly one halt node (the singleton's id is shared - // across all states' graphs). - expect(haltNodes).toHaveLength(1); + expect(realHaltNodes).toHaveLength(1); }, ); diff --git a/packages/library-binary-numbers/src/index.ts b/packages/library-binary-numbers/src/index.ts index 3762080..59ec43b 100644 --- a/packages/library-binary-numbers/src/index.ts +++ b/packages/library-binary-numbers/src/index.ts @@ -85,11 +85,11 @@ const goToNumbersStart = new State({ }, }, 'goToNumberStart'); -// deleteNumber — 5 nodes +// deleteNumber — 4 nodes // // Composition: go to the number's '^', then sweep right erasing every cell // (digits, '^', '$') until the number is gone. Implemented as -// goToNumbersStart.withOverrodeHaltState(deleteNumberInternal): when +// goToNumbersStart.withOverriddenHaltState(deleteNumberInternal): when // goToNumbersStart would halt at '^', it falls through to the eraser instead. const deleteNumberInternal = new State({ [symbol('$')]: { @@ -108,17 +108,17 @@ const deleteNumberInternal = new State({ const deleteNumber = new State({ [symbol('^10$')]: { - nextState: goToNumbersStart.withOverrodeHaltState(deleteNumberInternal), + nextState: goToNumbersStart.withOverriddenHaltState(deleteNumberInternal), }, [ifOtherSymbol]: { nextState: haltState, }, }, 'deleteNumber'); -// invertNumber — 5 nodes +// invertNumber — 4 nodes // // Composition: go to '^', then sweep right flipping each bit until '$'. -// Same shape as deleteNumber (goToNumbersStart.withOverrodeHaltState(...)) but +// Same shape as deleteNumber (goToNumbersStart.withOverriddenHaltState(...)) but // the inner state writes the complement instead of erasing. const invertNumberGoToNumberWithInversion = new State({ [symbol('^')]: { @@ -145,14 +145,14 @@ const invertNumberGoToNumberWithInversion = new State({ const invertNumber = new State({ [symbol('^10$')]: { - nextState: goToNumbersStart.withOverrodeHaltState(invertNumberGoToNumberWithInversion), + nextState: goToNumbersStart.withOverriddenHaltState(invertNumberGoToNumberWithInversion), }, [ifOtherSymbol]: { nextState: haltState, }, }, 'invertNumber'); -// normalizeNumber — 7 nodes +// normalizeNumber — 6 nodes // // Strips leading zeros by erasing them and re-planting '^' just before the first // '1' (or before '$' if the entire number was zero — preserving "0" as "^$"). @@ -184,7 +184,7 @@ const normalizeNumberMoveNumberStart = new State({ const normalizeNumber = new State({ [symbol('^10$')]: { - nextState: goToNumbersStart.withOverrodeHaltState(normalizeNumberMoveNumberStart), + nextState: goToNumbersStart.withOverriddenHaltState(normalizeNumberMoveNumberStart), }, [ifOtherSymbol]: { nextState: haltState, @@ -268,16 +268,18 @@ const plusOne = new State({ }, }, 'plusOne'); -// minusOne — 17 nodes (the largest in this library) +// minusOne — 18 nodes (the largest in this library, per `summarize().stateCount`) // // Computes x − 1 via the two's-complement identity: x − 1 == ~(~x + 1) // (every step is a state we already have), composed with three nested -// withOverrodeHaltState calls to chain invert → plusOne → invert → normalize. +// withOverriddenHaltState calls to chain invert → plusOne → invert → normalize. +// The chain has 4 state names but 3 wrapper hops — `normalizeNumber` at the +// inner end is the terminal override target, not another wrapper level. // // This is *deliberately* the heavy version. It exists side-by-side with // minusOneFast (10 nodes, direct borrow) to make the cost of "compose existing // pieces" vs "write a dedicated algorithm" visible. See ../states.md for the -// dotted onHalt edges that show the four-deep subroutine chain. +// dotted onHalt edges that show the three-deep wrapper chain. const minusOne = new State({ [symbol('^10')]: { command: { @@ -286,11 +288,11 @@ const minusOne = new State({ }, [symbol('$')]: { nextState: invertNumber - .withOverrodeHaltState( + .withOverriddenHaltState( plusOne - .withOverrodeHaltState( + .withOverriddenHaltState( invertNumber - .withOverrodeHaltState(normalizeNumber), + .withOverriddenHaltState(normalizeNumber), ), ), }, @@ -307,7 +309,7 @@ const minusOne = new State({ // // Same algorithm as minusOne in @turing-machine-js/library-binary-numbers-bare // (which is 3 nodes there). The extra 7 nodes here are the cost of: scanning -// past '^' on entry, the goToNumberStart path and its withOverrodeHaltState +// past '^' on entry, the goToNumberStart path and its withOverriddenHaltState // wrapper for normalize, and normalizeNumber's own marker-relocation chain. const minusOneFastBorrow = new State({ [symbol('1')]: { @@ -337,7 +339,7 @@ const minusOneFast = new State({ command: { movement: movements.left, }, - nextState: minusOneFastBorrow.withOverrodeHaltState(normalizeNumber), + nextState: minusOneFastBorrow.withOverriddenHaltState(normalizeNumber), }, [ifOtherSymbol]: { nextState: haltState, diff --git a/packages/library-binary-numbers/states.md b/packages/library-binary-numbers/states.md index 81eb3a6..faf056f 100644 --- a/packages/library-binary-numbers/states.md +++ b/packages/library-binary-numbers/states.md @@ -2,139 +2,162 @@ ## goToNumber -*2 states (including `haltState`)* +*2 states; 2 transitions; has cycles* ```mermaid flowchart TD %% alphabets: [[" ","^","$","0","1"]] s0(((halt))) - s1(("goToNumber")) - s1 -- "$ → ·/S" --> s0 - s1 -- "* → ·/R" --> s1 + s1["goToNumber"] + idle([idle]) + idle -. enter .-> s1 + s1 -- "['$'] → [K]/[S]" --> s0 + s1 -- "[*] → [K]/[R]" --> s1 ``` ## goToNextNumber -*3 states (including `haltState`)* +*3 states; 3 transitions; has cycles* ```mermaid flowchart TD %% alphabets: [[" ","^","$","0","1"]] s0(((halt))) s1["goToNumber"] - s2(("goToNextNumber")) - s1 -- "$ → ·/S" --> s0 - s1 -- "* → ·/R" --> s1 - s2 -- "* → ·/R" --> s1 + s2["goToNextNumber"] + idle([idle]) + idle -. enter .-> s2 + s1 -- "['$'] → [K]/[S]" --> s0 + s1 -- "[*] → [K]/[R]" --> s1 + s2 -- "[*] → [K]/[R]" --> s1 ``` ## goToPreviousNumber -*3 states (including `haltState`)* +*3 states; 3 transitions; has cycles* ```mermaid flowchart TD %% alphabets: [[" ","^","$","0","1"]] s0(((halt))) s3["goToPreviousNumberInternal"] - s4(("goToPreviousNumber")) - s3 -- "$ → ·/S" --> s0 - s3 -- "* → ·/L" --> s3 - s4 -- "* → ·/L" --> s3 + s4["goToPreviousNumber"] + idle([idle]) + idle -. enter .-> s4 + s3 -- "['$'] → [K]/[S]" --> s0 + s3 -- "[*] → [K]/[L]" --> s3 + s4 -- "[*] → [K]/[L]" --> s3 ``` ## deleteNumber -*5 states (including `haltState`)* +*5 states; 6 transitions; 1 wrapper (max nesting depth 1); has cycles* ```mermaid flowchart TD %% alphabets: [[" ","^","$","0","1"]] s0(((halt))) - s5["goToNumberStart"] s6["deleteNumberInternal"] - s7["goToNumberStart>deleteNumberInternal"] - s8(("deleteNumber")) - s5 -- "^ → ·/S" --> s0 - s5 -- "* → ·/L" --> s5 - s6 -- "$ → ⌫/S" --> s0 - s6 -- "* → ⌫/R" --> s6 - s7 -- "^ → ·/S" --> s0 - s7 -- "* → ·/L" --> s5 - s7 -. onHalt .-> s6 - s8 -- "^|1|0|$ → ·/S" --> s7 - s8 -- "* → ·/S" --> s0 + s8["deleteNumber"] + s7[["goToNumberStart(deleteNumberInternal)"]] + idle([idle]) + subgraph w_5["callable subtree of goToNumberStart"] + s5["goToNumberStart"] + c5(((halt))) + end + idle -. enter .-> s8 + s7 == "call" ==> s5 + w_5 -. "return" .-> s7 + s7 --> s6 + s5 -- "['^'] → [K]/[S]" --> c5 + s5 -- "[*] → [K]/[L]" --> s5 + s6 -- "['$'] → [E]/[S]" --> s0 + s6 -- "[*] → [E]/[R]" --> s6 + s8 -- "['^']|['1']|['0']|['$'] → [K]/[S]" --> s7 + s8 -- "[*] → [K]/[S]" --> s0 ``` ## goToNumbersStart -*2 states (including `haltState`)* +*2 states; 2 transitions; has cycles* ```mermaid flowchart TD %% alphabets: [[" ","^","$","0","1"]] s0(((halt))) - s5(("goToNumberStart")) - s5 -- "^ → ·/S" --> s0 - s5 -- "* → ·/L" --> s5 + s5["goToNumberStart"] + idle([idle]) + idle -. enter .-> s5 + s5 -- "['^'] → [K]/[S]" --> s0 + s5 -- "[*] → [K]/[L]" --> s5 ``` ## invertNumber -*5 states (including `haltState`)* +*5 states; 8 transitions; 1 wrapper (max nesting depth 1); has cycles* ```mermaid flowchart TD %% alphabets: [[" ","^","$","0","1"]] s0(((halt))) - s5["goToNumberStart"] s9["invertNumberGoToNumberWithInversion"] - s10["goToNumberStart>invertNumberGoToNumberWithInversion"] - s11(("invertNumber")) - s5 -- "^ → ·/S" --> s0 - s5 -- "* → ·/L" --> s5 - s9 -- "^ → ·/R" --> s9 - s9 -- "1 → 0/R" --> s9 - s9 -- "0 → 1/R" --> s9 - s9 -- "$ → ·/S" --> s0 - s10 -- "^ → ·/S" --> s0 - s10 -- "* → ·/L" --> s5 - s10 -. onHalt .-> s9 - s11 -- "^|1|0|$ → ·/S" --> s10 - s11 -- "* → ·/S" --> s0 + s11["invertNumber"] + s10[["goToNumberStart(invertNumberGoToNumberWithInversion)"]] + idle([idle]) + subgraph w_5["callable subtree of goToNumberStart"] + s5["goToNumberStart"] + c5(((halt))) + end + idle -. enter .-> s11 + s10 == "call" ==> s5 + w_5 -. "return" .-> s10 + s10 --> s9 + s5 -- "['^'] → [K]/[S]" --> c5 + s5 -- "[*] → [K]/[L]" --> s5 + s9 -- "['^'] → [K]/[R]" --> s9 + s9 -- "['1'] → ['0']/[R]" --> s9 + s9 -- "['0'] → ['1']/[R]" --> s9 + s9 -- "['$'] → [K]/[S]" --> s0 + s11 -- "['^']|['1']|['0']|['$'] → [K]/[S]" --> s10 + s11 -- "[*] → [K]/[S]" --> s0 ``` ## normalizeNumber -*7 states (including `haltState`)* +*7 states; 9 transitions; 1 wrapper (max nesting depth 1); has cycles* ```mermaid flowchart TD %% alphabets: [[" ","^","$","0","1"]] s0(((halt))) s1["goToNumber"] - s5["goToNumberStart"] s12["normalizeNumberPutNewStartSymbol"] s13["normalizeNumberMoveNumberStart"] - s14["goToNumberStart>normalizeNumberMoveNumberStart"] - s15(("normalizeNumber")) - s1 -- "$ → ·/S" --> s0 - s1 -- "* → ·/R" --> s1 - s5 -- "^ → ·/S" --> s0 - s5 -- "* → ·/L" --> s5 - s12 -- "- → ^/S" --> s1 - s13 -- "^|0 → ⌫/R" --> s13 - s13 -- "1|$ → ·/L" --> s12 - s14 -- "^ → ·/S" --> s0 - s14 -- "* → ·/L" --> s5 - s14 -. onHalt .-> s13 - s15 -- "^|1|0|$ → ·/S" --> s14 - s15 -- "* → ·/S" --> s0 + s15["normalizeNumber"] + s14[["goToNumberStart(normalizeNumberMoveNumberStart)"]] + idle([idle]) + subgraph w_5["callable subtree of goToNumberStart"] + s5["goToNumberStart"] + c5(((halt))) + end + idle -. enter .-> s15 + s14 == "call" ==> s5 + w_5 -. "return" .-> s14 + s14 --> s13 + s1 -- "['$'] → [K]/[S]" --> s0 + s1 -- "[*] → [K]/[R]" --> s1 + s5 -- "['^'] → [K]/[S]" --> c5 + s5 -- "[*] → [K]/[L]" --> s5 + s12 -- "[B] → ['^']/[S]" --> s1 + s13 -- "['^']|['0'] → [E]/[R]" --> s13 + s13 -- "['1']|['$'] → [K]/[L]" --> s12 + s15 -- "['^']|['1']|['0']|['$'] → [K]/[S]" --> s14 + s15 -- "[*] → [K]/[S]" --> s0 ``` ## plusOne -*5 states (including `haltState`)* +*5 states; 10 transitions; has cycles* ```mermaid flowchart TD @@ -143,124 +166,142 @@ flowchart TD s16["plusOneFillZeros"] s17["plusOneAddNumberStart"] s18["plusOneCaryOne"] - s19(("plusOne")) - s16 -- "1 → 0/R" --> s16 - s16 -- "$ → ·/S" --> s0 - s17 -- "- → ^/R" --> s17 - s17 -- "1 → ·/R" --> s16 - s18 -- "0 → 1/R" --> s16 - s18 -- "1 → ·/L" --> s18 - s18 -- "^ → 1/L" --> s17 - s19 -- "^|1|0 → ·/R" --> s19 - s19 -- "$ → ·/L" --> s18 - s19 -- "* → ·/S" --> s0 + s19["plusOne"] + idle([idle]) + idle -. enter .-> s19 + s16 -- "['1'] → ['0']/[R]" --> s16 + s16 -- "['$'] → [K]/[S]" --> s0 + s17 -- "[B] → ['^']/[R]" --> s17 + s17 -- "['1'] → [K]/[R]" --> s16 + s18 -- "['0'] → ['1']/[R]" --> s16 + s18 -- "['1'] → [K]/[L]" --> s18 + s18 -- "['^'] → ['1']/[L]" --> s17 + s19 -- "['^']|['1']|['0'] → [K]/[R]" --> s19 + s19 -- "['$'] → [K]/[L]" --> s18 + s19 -- "[*] → [K]/[S]" --> s0 ``` ## minusOne -*17 states (including `haltState`)* +*18 states; 28 transitions; 5 wrappers (max nesting depth 3); has cycles* ```mermaid flowchart TD %% alphabets: [[" ","^","$","0","1"]] s0(((halt))) s1["goToNumber"] - s5["goToNumberStart"] - s9["invertNumberGoToNumberWithInversion"] - s10["goToNumberStart>invertNumberGoToNumberWithInversion"] s12["normalizeNumberPutNewStartSymbol"] s13["normalizeNumberMoveNumberStart"] - s14["goToNumberStart>normalizeNumberMoveNumberStart"] s15["normalizeNumber"] - s16["plusOneFillZeros"] - s17["plusOneAddNumberStart"] - s18["plusOneCaryOne"] - s19["plusOne"] - s20["invertNumber>normalizeNumber"] - s21["plusOne>invertNumber>normalizeNumber"] - s22["invertNumber>plusOne>invertNumber>normalizeNumber"] - s23(("minusOne")) - s1 -- "$ → ·/S" --> s0 - s1 -- "* → ·/R" --> s1 - s5 -- "^ → ·/S" --> s0 - s5 -- "* → ·/L" --> s5 - s9 -- "^ → ·/R" --> s9 - s9 -- "1 → 0/R" --> s9 - s9 -- "0 → 1/R" --> s9 - s9 -- "$ → ·/S" --> s0 - s10 -- "^ → ·/S" --> s0 - s10 -- "* → ·/L" --> s5 - s10 -. onHalt .-> s9 - s12 -- "- → ^/S" --> s1 - s13 -- "^|0 → ⌫/R" --> s13 - s13 -- "1|$ → ·/L" --> s12 - s14 -- "^ → ·/S" --> s0 - s14 -- "* → ·/L" --> s5 - s14 -. onHalt .-> s13 - s15 -- "^|1|0|$ → ·/S" --> s14 - s15 -- "* → ·/S" --> s0 - s16 -- "1 → 0/R" --> s16 - s16 -- "$ → ·/S" --> s0 - s17 -- "- → ^/R" --> s17 - s17 -- "1 → ·/R" --> s16 - s18 -- "0 → 1/R" --> s16 - s18 -- "1 → ·/L" --> s18 - s18 -- "^ → 1/L" --> s17 - s19 -- "^|1|0 → ·/R" --> s19 - s19 -- "$ → ·/L" --> s18 - s19 -- "* → ·/S" --> s0 - s20 -- "^|1|0|$ → ·/S" --> s10 - s20 -- "* → ·/S" --> s0 - s20 -. onHalt .-> s15 - s21 -- "^|1|0 → ·/R" --> s19 - s21 -- "$ → ·/L" --> s18 - s21 -- "* → ·/S" --> s0 - s21 -. onHalt .-> s20 - s22 -- "^|1|0|$ → ·/S" --> s10 - s22 -- "* → ·/S" --> s0 - s22 -. onHalt .-> s21 - s23 -- "^|1|0 → ·/R" --> s23 - s23 -- "$ → ·/S" --> s22 - s23 -- "* → ·/S" --> s0 + s23["minusOne"] + s14[["goToNumberStart(normalizeNumberMoveNumberStart)"]] + s20[["invertNumber(normalizeNumber)"]] + s21[["plusOne(invertNumber(normalizeNumber))"]] + s22[["invertNumber(plusOne(invertNumber(normalizeNumber)))"]] + idle([idle]) + subgraph w_5["callable subtree of goToNumberStart"] + s5["goToNumberStart"] + c5(((halt))) + end + subgraph w_11["callable subtree of invertNumber"] + s9["invertNumberGoToNumberWithInversion"] + s10[["goToNumberStart(invertNumberGoToNumberWithInversion)"]] + s11["invertNumber"] + c11(((halt))) + end + subgraph w_19["callable subtree of plusOne"] + s16["plusOneFillZeros"] + s17["plusOneAddNumberStart"] + s18["plusOneCaryOne"] + s19["plusOne"] + c19(((halt))) + end + idle -. enter .-> s23 + s10 & s14 == "call" ==> s5 + s20 & s22 == "call" ==> s11 + s21 == "call" ==> s19 + w_5 -. "return" .-> s10 & s14 + w_11 -. "return" .-> s20 & s22 + w_19 -. "return" .-> s21 + s10 --> s9 + s14 --> s13 + s20 --> s15 + s21 --> s20 + s22 --> s21 + s1 -- "['$'] → [K]/[S]" --> s0 + s1 -- "[*] → [K]/[R]" --> s1 + s5 -- "['^'] → [K]/[S]" --> c5 + s5 -- "[*] → [K]/[L]" --> s5 + s9 -- "['^'] → [K]/[R]" --> s9 + s9 -- "['1'] → ['0']/[R]" --> s9 + s9 -- "['0'] → ['1']/[R]" --> s9 + s9 -- "['$'] → [K]/[S]" --> c11 + s11 -- "['^']|['1']|['0']|['$'] → [K]/[S]" --> s10 + s11 -- "[*] → [K]/[S]" --> c11 + s12 -- "[B] → ['^']/[S]" --> s1 + s13 -- "['^']|['0'] → [E]/[R]" --> s13 + s13 -- "['1']|['$'] → [K]/[L]" --> s12 + s15 -- "['^']|['1']|['0']|['$'] → [K]/[S]" --> s14 + s15 -- "[*] → [K]/[S]" --> s0 + s16 -- "['1'] → ['0']/[R]" --> s16 + s16 -- "['$'] → [K]/[S]" --> c19 + s17 -- "[B] → ['^']/[R]" --> s17 + s17 -- "['1'] → [K]/[R]" --> s16 + s18 -- "['0'] → ['1']/[R]" --> s16 + s18 -- "['1'] → [K]/[L]" --> s18 + s18 -- "['^'] → ['1']/[L]" --> s17 + s19 -- "['^']|['1']|['0'] → [K]/[R]" --> s19 + s19 -- "['$'] → [K]/[L]" --> s18 + s19 -- "[*] → [K]/[S]" --> c19 + s23 -- "['^']|['1']|['0'] → [K]/[R]" --> s23 + s23 -- "['$'] → [K]/[S]" --> s22 + s23 -- "[*] → [K]/[S]" --> s0 ``` ## minusOneFast -*10 states (including `haltState`)* +*10 states; 15 transitions; 2 wrappers (max nesting depth 1); has cycles* ```mermaid flowchart TD %% alphabets: [[" ","^","$","0","1"]] s0(((halt))) s1["goToNumber"] - s5["goToNumberStart"] s12["normalizeNumberPutNewStartSymbol"] s13["normalizeNumberMoveNumberStart"] - s14["goToNumberStart>normalizeNumberMoveNumberStart"] s15["normalizeNumber"] - s24["minusOneFastBorrow"] - s25["minusOneFastBorrow>normalizeNumber"] - s26(("minusOneFast")) - s1 -- "$ → ·/S" --> s0 - s1 -- "* → ·/R" --> s1 - s5 -- "^ → ·/S" --> s0 - s5 -- "* → ·/L" --> s5 - s12 -- "- → ^/S" --> s1 - s13 -- "^|0 → ⌫/R" --> s13 - s13 -- "1|$ → ·/L" --> s12 - s14 -- "^ → ·/S" --> s0 - s14 -- "* → ·/L" --> s5 - s14 -. onHalt .-> s13 - s15 -- "^|1|0|$ → ·/S" --> s14 - s15 -- "* → ·/S" --> s0 - s24 -- "1 → 0/S" --> s0 - s24 -- "0 → 1/L" --> s24 - s24 -- "^ → ·/S" --> s0 - s25 -- "1 → 0/S" --> s0 - s25 -- "0 → 1/L" --> s24 - s25 -- "^ → ·/S" --> s0 - s25 -. onHalt .-> s15 - s26 -- "^|1|0 → ·/R" --> s26 - s26 -- "$ → ·/L" --> s25 - s26 -- "* → ·/S" --> s0 + s26["minusOneFast"] + s14[["goToNumberStart(normalizeNumberMoveNumberStart)"]] + s25[["minusOneFastBorrow(normalizeNumber)"]] + idle([idle]) + subgraph w_5["callable subtree of goToNumberStart"] + s5["goToNumberStart"] + c5(((halt))) + end + subgraph w_24["callable subtree of minusOneFastBorrow"] + s24["minusOneFastBorrow"] + c24(((halt))) + end + idle -. enter .-> s26 + s14 == "call" ==> s5 + s25 == "call" ==> s24 + w_5 -. "return" .-> s14 + w_24 -. "return" .-> s25 + s14 --> s13 + s25 --> s15 + s1 -- "['$'] → [K]/[S]" --> s0 + s1 -- "[*] → [K]/[R]" --> s1 + s5 -- "['^'] → [K]/[S]" --> c5 + s5 -- "[*] → [K]/[L]" --> s5 + s12 -- "[B] → ['^']/[S]" --> s1 + s13 -- "['^']|['0'] → [E]/[R]" --> s13 + s13 -- "['1']|['$'] → [K]/[L]" --> s12 + s15 -- "['^']|['1']|['0']|['$'] → [K]/[S]" --> s14 + s15 -- "[*] → [K]/[S]" --> s0 + s24 -- "['1'] → ['0']/[S]" --> c24 + s24 -- "['0'] → ['1']/[L]" --> s24 + s24 -- "['^'] → [K]/[S]" --> c24 + s26 -- "['^']|['1']|['0'] → [K]/[R]" --> s26 + s26 -- "['$'] → [K]/[L]" --> s25 + s26 -- "[*] → [K]/[S]" --> s0 ``` diff --git a/packages/machine/CHANGELOG.md b/packages/machine/CHANGELOG.md index 43e71b1..1e951ae 100644 --- a/packages/machine/CHANGELOG.md +++ b/packages/machine/CHANGELOG.md @@ -4,6 +4,392 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [7.0.0] - 2026-06-03 + +Stable v7. The composition-representation overhaul. See alpha.1 through alpha.8 entries below for the detailed step-by-step trajectory; this entry summarizes the cumulative API changes from v6.4.0. + +### Added + +- **`DebugSession`** — `new DebugSession(machine, { initialState })`. The sole interactive-debugging surface (there is no `debugRun()` factory on `TuringMachine`). Emits `pause` / `step` / `iter` / `halt` events via `session.on(event, listener)`; `iter` listeners are awaited (the per-iter throttle / coordination point), `pause` / `step` / `halt` are fire-and-forget. Drive controls: `continue()`, `stepIn()`, `stepOver()`, `stepOut()`, external `pause()`, `stop()`, and `setRunInterval(ms)` for a per-iter throttle. Breakpoint detection (`state.debug` filters, `haltState.debug`) lives entirely here. Concurrent sessions on one machine are rejected (the underlying `TapeBlock` lock is single-active-run). Depth-based step controls mirror Chrome DevTools: `stepIn` = next iter at any depth; `stepOver` = next iter at `depth ≤ click-time depth`; `stepOut` = next iter at `depth < click-time depth` (throws at depth 0). +- **`CallFrame extends State`** — `withOverriddenHaltState`'s wrapper is now a first-class `State` subclass instead of an instance aliasing the bare's private `#symbolToDataMap` / `#debugRef`. Transition lookups and `debug` access delegate to the bare. `instanceof State` is preserved; `instanceof CallFrame` is the wrapper discriminator. Exported additively from the package root. +- **`TapeSnapshot` type + `tapeViewport` helper** — the per-tape wire-data shape `{ symbols: string[]; position: number }` and a pure helper providing a fixed-width centered window over it. Live next to the `Tape` class; `@turing-machine-js/visuals` re-exports both for consumer-import stability. +- **First-class State tags** — `state.tag(...) / .untag(...) / .tags` for attaching string metadata. Out-of-band — no effect on transition lookup, `equivalentOn`, or runtime semantics. Live on the State instance, so memoized wrappers don't leak tags across sharers of a bare. `GraphNode.tags: string[]` survives `toGraph` / `fromGraph` round-trip; `toMermaid` emits tags inline (`
`) AND as `classDef` + `class` color-group lines. +- **`withOverriddenHaltState` memoization** — calls with the same `(bare, override)` pair return the literally-same `State` instance. Backed by a `WeakMap` keyed by the bare with `WeakRef`-valued entries. +- **`MachineState.matchedTransition`** — every yielded `MachineState` carries `{ id: string, matchKinds: ('wildcard' | 'literal')[] }`. `id` is `${stateId}.${transitionIx}` and resolves directly in `toGraph` output. `matchKinds` is per-tape; `'wildcard'` iff the winning alternative held `ifOtherSymbol` at that position. Eliminates `(source, nextState)` resolution ambiguity for exact-edge highlighting and per-transition coverage maps. +- **`State.getMatchedTransition(symbol)`** — returns `{ nextState, matchedSymbol, ix }`, exposing the index used by `matchedTransition.id`. +- **`State.collectStates(initialState, tapeBlock)`** — returns `Map` keyed by engine `GraphNode.id`. The K-th `transitionSymbols` slot is positionally aligned with the GraphTransition whose id is `${stateId}.${K}`. `StateMap` and `StateMapEntry` types exported. +- **`TapeBlock.patternKinds(symbol, currentSymbols?)`** — per-tape `'wildcard' | 'literal'` for the matched alternative's selector. +- **`HaltState` typed alias** — narrows `haltState.debug` to `boolean` at the canonical access path. +- **`@turing-machine-js/visuals` companion package** — pure highlight + graph-indexing logic for the engine `Graph` (peer dep on `machine`, no runtime deps). Includes `applyHighlight` / `applyIndicator` / `indexGraph` / `bareIdOf` / `highlightExpand` / `equivalentIds` / `recordingOps`, the engine-edge-label formatter primitives (`formatStepNotation` / `tokenizeStep` / `formatTape`), the snippet-recording surface (`recordSnippet` + `SnippetPlayer`), and a 16-rule contract doc. + +### Changed + +- **`State.prototype.withOverrodeHaltState` renamed to `withOverriddenHaltState`** — grammar fix. Hard cutover, no deprecated alias. Renames in lockstep: public method, `state.overriddenHaltState` getter, the `#overriddenHaltState` private field, and the serialized `Graph` field `node.overriddenHaltStateId`. +- **Wrapped-state composite name format flipped from `bare>override` to `bare(override)`** — paren-nested keeps structurally-distinct wrap-trees distinct (`A.with(B.with(A))` is `A(B(A))`; `A.with(B).with(A)` is `A(B)(A)`). As a consequence, **user-provided state names must not contain `(` or `)`** (the constructor throws). `>` is no longer reserved. +- **`run()` is synchronous and callback-free** — signature is `run({ initialState, stepsLimit? }): void` (was `async … : Promise` with `onPause` / `onStep` / `onIter` callbacks). All callback machinery has moved to `DebugSession`. **Breaking** for callers awaiting `run()` or passing callbacks. +- **`runStepByStep` is the minimal pure-iteration primitive** — advances the machine and yields a plain `MachineState`. Evaluates no `state.debug` filters and stamps no pause field. Breakpoint detection moved to `DebugSession`. **Breaking** for consumers that read a pause / breakpoint field off raw yields. +- **`haltState.debug` is a `boolean`** — was `DebugConfig` with per-side `{ before?, after? }`. `haltState.debug = true` enables (pauses on every halt entry); `false` / `null` disables. Object-shaped writes throw. The pause fires on the AFTER side of the iter whose transition leads to halt — `m.state` is the triggering state. **Breaking** — pre-v7 consumers using `haltState.debug = { before: true }` / `{ after: true }` must switch to `haltState.debug = true`. ⚠️ Chained-form `haltState.debug.before = true` silently no-ops in non-strict mode (primitive-property assignment). Use the whole-object form. +- **`toMermaid` callable-subtree emit** — wrapper and bare are separate graph nodes. Wrapper sits OUTSIDE any subgraph as `[[composite-name]]` (call site). Bare lives INSIDE its callable subtree subgraph as a regular `[name]` node. Subgraph label is `"callable subtree of NAME"` (single bare) or `"callable scope: A ∪ B"` (multi-bare union frame computed via union-find on bare-reachability). Halt marker is per-frame. Arrow vocabulary: solid `-->` for regular transitions AND wrapper's post-return `--> override`; bold `==> "call"` is RESERVED for wrapper-to-bare (`&` ribbon collapses multiple wrappers sharing a bare); dotted `-.->` is reserved for frame-level dispatch (`w_N -. "return" .-> wrapper`, `w_N -. "halt" .-> s0`, `idle -. enter .-> sN`). `GraphNode` field changes: `isWrapper: boolean`, `bareStateId: number | null`, `frameId: number | null`; `overriddenHaltStateId` lives on wrapper nodes only. Bytewise round-trip stability holds for all wrapped states, including shared-bare cases. +- **`toMermaid` framed-wrapper emit fix** — `toGraph`'s reach-set now pushes the wrapper itself AND tunnels through `overriddenHaltStateId`, so framed-wrapper-continuations join the caller's frame instead of emitting at the top level. +- **`toMermaid` user-content escape** — alphabet symbols, state names, tag names, and frame bare names are HTML-entity-escaped at the leaf (`&`, `"`, `<`, `>` to named entities; statement terminators, C0 controls, DEL, bidi controls, lone surrogates to numeric entities). `fromMermaid` mirrors with a single-pass entity decoder. Fixes parse errors on alphabets containing literal `"`, `<`, `>`, etc. +- **Edge-label vocabulary rewritten** — `[reads] → [writes]/[moves]`, each role wrapped in `[…]` (tape-block indicator; always present, even single-tape). Read cells: `'X'` literal-quoted, `*` (ifOtherSymbol catch-all), `B` (tape's blank). Write cells: literal-quoted, `K` (keep), `E` (erase). Move cells: `L` / `R` / `S`. Alternation per-pattern bracket (`['^']|['1']`, never compact in-bracket form). +- **`GraphTransition.id` separator: `-` → `.`** — was `${stateId}-${patternIx}`, now `${stateId}.${patternIx}`. Aligns with `matchedTransition.id` and makes the id splittable without colliding with user-defined state names containing `-`. +- **Nested `.wohs()` chain collapse** — `A.wohs(t1).wohs(t2)` is equivalent to `A.wohs(t2)` (the inner override is dead at runtime). Composite name now reflects runtime behavior: `A(t2)`, not the misleading `A(t1)(t2)`. +- **`runStepByStep` halt-stack is run-scoped, not machine-scoped** — fixes a "ghost iteration" / memory leak on consecutive `runStepByStep` calls when the previous generator wasn't drained to halt. +- **Graph serialization extracted to `utilities/stateGraph.ts`** — `State.toGraph` / `State.fromGraph` / `State.collectStates` live in a sibling module. Public surface preserved via thin static delegates. +- **`Tape.viewport` getter shares its centering loop with `tapeViewport`** via an internal `tapeViewportFromAccess` core — the centering math lives once. +- **`summarize().stateCount` filters `isHaltMarker` nodes** — halt markers are visualization sentinels (all map back to singleton `haltState` at runtime). + +### Removed + +- **`MachineState.debugBreak`** — the per-yield `{ before?, after?, cause }` descriptor from the v4–v6 debugger. Replaced by the one-sided `m.pause: { side: 'before' | 'after', cause: 'breakpoint' | 'step' | 'manual' }` carried ONLY on a `DebugSession` `pause` event (`PausedMachineState`); raw `runStepByStep` yields carry no pause field. +- **`withOverrodeHaltState` (the misspelled alias)** — no deprecated alias retained. +- **The `-. onHalt .->` Mermaid arrow** — wrapper-to-override is now an ordinary solid `--> override` arrow; dotted `-.->` is reserved for frame-level dispatch. +- **`GraphNode.isWrapped`** — replaced by `isWrapper: boolean`, `bareStateId: number | null`, `frameId: number | null`. +- **`run()` callbacks (`onPause`, `onStep`, `onIter`)** — moved to `DebugSession` events. +- **Hand-drawn pedagogical Mermaid blocks in READMEs** — the v7 `toMermaid` output is the primary illustration; no more vocabulary mismatch between hand-drawn and engine-rendered diagrams. + +### Migration + +Mechanical identifier rename: + +```sh +git grep -l 'OverrodeHaltState\|overrodeHaltState' | xargs sed -i '' \ + -e 's/OverrodeHaltState/OverriddenHaltState/g' \ + -e 's/overrodeHaltState/overriddenHaltState/g' +``` + +`await`-on-`run()` and callback users move to `DebugSession`: + +```ts +// before +await machine.run({ initialState, onPause: (m) => { ... } }); + +// after +const session = new DebugSession(machine, { initialState }); +session.on('pause', (m) => { /* m.pause.side, m.pause.cause */ }); +await session.continue(); +``` + +`m.debugBreak.before` / `.after` / `.cause` consumers move to `m.pause.side` / `m.pause.cause` on `DebugSession` `pause` events. + +`haltState.debug = { after: true }` writes throw — use `haltState.debug = true` (always after-side). + +Code that pattern-matches `state.name` for wrapper composites switches from `>`-split to paren-parse: `'A>B'` is now `'A(B)'`. + +If you render `toMermaid` output programmatically, the edge-label vocabulary changed completely — see the engine README's §Diagram conventions. + +If you store serialized `Graph` JSON: `node.overrodeHaltStateId` → `node.overriddenHaltStateId`, `GraphNode.isWrapped` → `isWrapper` + `bareStateId` + `frameId`, `GraphTransition.id` separator `-` → `.`. + +### Compatibility + +- Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.8` → `^7.0.0` on `@turing-machine-js/builder`, `@turing-machine-js/library-binary-numbers`, `@turing-machine-js/library-binary-numbers-bare`, `@turing-machine-js/visuals`. + +## [7.0.0-alpha.8] - 2026-06-02 + +Eighth v7 pre-release. Lifts `TapeSnapshot` (the per-tape wire-data shape `{symbols, position}`) and `tapeViewport` (fixed-width centered window over a `TapeSnapshot`) from `@turing-machine-js/visuals` into the engine, next to the `Tape` class. Resolves [#227](https://github.com/mellonis/turing-machine-js/issues/227). Published under the `next` dist-tag: `npm install @turing-machine-js/machine@next`. + +**Pre-release — the API surface may still shift before stable v7.0.0.** Pin to a specific alpha for reproducibility: `@turing-machine-js/machine@7.0.0-alpha.8`. + +### Added + +- **`TapeSnapshot` type** ([#227](https://github.com/mellonis/turing-machine-js/issues/227)) — `{ symbols: string[]; position: number }`. The per-tape wire-data shape — what callers serialize a live `Tape` into for transmission (worker boundaries, snippet recording, snapshot tests). Previously lived in `@turing-machine-js/visuals`'s `types.ts` and was re-imported by the engine for `Frame.tape`-style fields. Now sits in the engine next to the live `Tape` class; `@turing-machine-js/visuals` re-exports it for consumer-import stability (so `import { TapeSnapshot } from '@turing-machine-js/visuals'` keeps working). +- **`tapeViewport(snapshot, width, blank): { cells, headIndex }`** ([#227](https://github.com/mellonis/turing-machine-js/issues/227)) — pure helper: fixed-width window over a `TapeSnapshot`, centered on the head, padded with `blank` for out-of-bounds cells. `headIndex` is deterministic at `Math.floor(width / 2)`. Throws `RangeError` on non-positive or non-integer width. Visuals re-exports. + +### Changed + +- **`Tape.viewport` getter now shares its centering loop with `tapeViewport`** via an internal `tapeViewportFromAccess(position, width, cellAt)` core. The public `tapeViewport(snapshot, width, blank)` signature is unchanged — it passes a snapshot-bounds-checking `cellAt`; `Tape.viewport` passes its own `(i) => alphabet.get(this.#cellAt(i))`. The two public surfaces serve different inputs (wire-data `TapeSnapshot` vs live int-encoded `Tape`) but the centering math lives once. + +### Compatibility + +- **Consumers importing `TapeSnapshot` or `tapeViewport` from `@turing-machine-js/visuals` are unaffected** — visuals re-exports both from the engine. +- `Tape.viewport`'s public contract (returns a fresh `string[]` of length `viewportWidth`, head at `Math.floor(viewportWidth/2)`) is unchanged. +- The internal `tapeViewportFromAccess` helper is `@internal` — not exported from the package root. + +## [7.0.0-alpha.7] - 2026-05-30 + +Seventh v7 pre-release. Lands the `CallFrame` extraction ([#213](https://github.com/mellonis/turing-machine-js/issues/213), [PR #218](https://github.com/mellonis/turing-machine-js/pull/218)) and a `toMermaid` framed-wrapper emit fix ([#223](https://github.com/mellonis/turing-machine-js/issues/223), [PR #224](https://github.com/mellonis/turing-machine-js/pull/224)). Published under the `next` dist-tag: `npm install @turing-machine-js/machine@next`. + +**Pre-release — the API surface may still shift before stable v7.0.0.** Pin to a specific alpha for reproducibility: `@turing-machine-js/machine@7.0.0-alpha.7`. + +### Added + +- **`CallFrame extends State`** ([#213](https://github.com/mellonis/turing-machine-js/issues/213)) — `withOverriddenHaltState`'s wrapper is now a first-class `State` subclass instead of a `State` instance aliasing the bare's private `#symbolToDataMap` / `#debugRef`. Transition lookups and `debug` access delegate to the bare. `instanceof State` is preserved (no breaking surface change), and `instanceof CallFrame` becomes the wrapper discriminator. `CallFrame` is exported additively from the package root. + +### Fixed + +- **`toMermaid` framed-wrapper emit asymmetry** ([#223](https://github.com/mellonis/turing-machine-js/issues/223)) — `toGraph`'s reach-set previously tunneled through wrappers to their continuation but left the wrappers themselves outside the caller's frame, so framed-wrapper-continuations (e.g. `library-binary-numbers/minusOne`'s `goToNumberStart(invertNumberGoToNumberWithInversion)` inside `invertNumber`'s callable subtree) emitted at the top level, visually disconnected from their owner frame. Fix in `utilities/stateGraph.ts`'s `resolveAndPush` pushes the wrapper itself AND tunnels through `overriddenHaltStateId`, so both join the caller's frame; `utilities/graphFormats.ts` renders framed wrappers inside the owner subgraph with the same `[[…]]` shape. Unframed top-level wrappers still emit outside any subgraph. `library-binary-numbers/states.md` regenerated — only the `minusOne` diagram shape changed. + +## [7.0.0-alpha.6] - 2026-05-28 + +Sixth v7 pre-release. Lands the debugger step controls ([#102](https://github.com/mellonis/turing-machine-js/issues/102), [PR #214](https://github.com/mellonis/turing-machine-js/pull/214)) and, in doing so, **reshapes the entire debug surface** into three clearly-separated entry points: `run()` is now pure synchronous execution (no callbacks), `runStepByStep` is the minimal pure-iteration primitive (no breakpoint detection), and a new **`DebugSession`** class owns all interactive debugging — events, step controls, throttle, and pause coordination. With #102 the **v7 milestone is feature-complete**; the stable v7.0.0 cut is the only remaining step. Published under the `next` dist-tag: `npm install @turing-machine-js/machine@next`. + +**Pre-release — the API surface may still shift before stable v7.0.0.** Pin to a specific alpha for reproducibility: `@turing-machine-js/machine@7.0.0-alpha.6`. + +### Added + +- **`DebugSession`** ([#102](https://github.com/mellonis/turing-machine-js/issues/102)) — `new DebugSession(machine, { initialState })`. The sole interactive-debugging surface (there is no `debugRun()` factory on `TuringMachine`). Emits `pause` / `step` / `iter` / `halt` events via `session.on(event, listener)`; `iter` listeners are **awaited** (the per-iter throttle / coordination point), `pause` / `step` / `halt` are fire-and-forget. Drive controls: `continue()`, `stepIn()`, `stepOver()`, `stepOut()`, external `pause()`, `stop()`, and `setRunInterval(ms)` for a per-iter throttle. Breakpoint detection (`state.debug` filters, `haltState.debug`) lives entirely here — `runStepByStep` no longer evaluates it. Concurrent sessions on one machine are rejected with a clear error (the underlying `TapeBlock` lock is single-active-run). + +- **`PauseInfo` / `PausedMachineState`** ([#102](https://github.com/mellonis/turing-machine-js/issues/102)) — the `pause` event payload is a `MachineState & { pause: PauseInfo }` where `PauseInfo = { side: 'before' | 'after'; cause: 'breakpoint' | 'step' | 'manual' }`. Exactly one side per descriptor — `DebugSession` dispatches the two timings as separate `pause` events, so the v6 "both timings on one yield" shape no longer exists. `cause` precedence when an iter satisfies more than one trigger: `breakpoint > step > manual`; `'step'` / `'manual'` only ever fire on the `'before'` side. + +- **Depth-based step controls mirroring Chrome DevTools** ([#102](https://github.com/mellonis/turing-machine-js/issues/102)) — `stepIn` = next iter at any depth; `stepOver` = next iter at `depth ≤ click-time depth` (nested `.withOverriddenHaltState(...)` subroutines run to completion, pause back at the starting level); `stepOut` = next iter at `depth < click-time depth` (current subroutine frame popped; throws at depth 0, mirroring DevTools' top-frame Step Out). + +- **`matchFilter` export** ([#102](https://github.com/mellonis/turing-machine-js/issues/102), `@internal`) — predicate evaluating a `DebugConfig` side-filter against a matched symbol. Exported for sibling-module use in `DebugSession`; NOT re-exported from the package root. + +### Changed + +- **`run()` is synchronous and callback-free** ([#102](https://github.com/mellonis/turing-machine-js/issues/102)) — signature is now `run({ initialState, stepsLimit? }): void` (was `async … : Promise` with `onPause` / `onStep` / `onIter` callbacks). Pure execution, no debug overhead. Reverses v4's `run` → `async run` change now that all callback machinery has moved to `DebugSession`. **Breaking** for callers awaiting `run()` or passing callbacks — construct a `DebugSession` instead. + +- **`runStepByStep` is the minimal pure-iteration primitive** ([#102](https://github.com/mellonis/turing-machine-js/issues/102)) — it advances the machine and yields a plain `MachineState`; it evaluates no `state.debug` filters and stamps no pause field. All breakpoint detection moved to `DebugSession`. **Breaking** for consumers that read a pause / breakpoint field off raw yields. + +- **`haltState.debug` pause is delivered via `DebugSession`** ([#102](https://github.com/mellonis/turing-machine-js/issues/102)) — still fires on the AFTER side of the halt-triggering iter (alpha.5 semantics: `m.state` is the triggering state, not `haltState`), but now arrives as a `pause` event with `{ side: 'after', cause: 'breakpoint' }` instead of a `debugBreak` field on the yield. + +### Removed + +- **`MachineState.debugBreak`** ([#102](https://github.com/mellonis/turing-machine-js/issues/102)) — the per-yield `{ before?, after?, cause }` descriptor from the v4–alpha.5 debugger is **removed**. Its replacement is the one-sided `m.pause: { side, cause }` carried ONLY on a `DebugSession` `pause` event (`PausedMachineState`); raw `runStepByStep` yields carry no pause field. **Breaking from alpha.5** — consumers reading `m.debugBreak.before` / `.after` / `.cause` must move to a `DebugSession` and read `m.pause.side` / `m.pause.cause`. + +## [7.0.0-alpha.5] - 2026-05-25 + +Fifth v7 pre-release. Adds per-iter `matchedTransition` to `MachineState` so consumers can resolve the firing transition by graph id without re-deriving from `(source, nextState)` ambiguous pairs ([#205](https://github.com/mellonis/turing-machine-js/issues/205)), collapses `haltState.debug` to a `boolean` and moves the halt-imminent pause to the AFTER side of the halt-triggering iter so the wording + diagram cursor agree with the just-fired transition ([#207](https://github.com/mellonis/turing-machine-js/issues/207)), and renames the `GraphTransition.id` separator from `-` to `.` for parser-friendly splitting. Published under the `next` dist-tag: `npm install @turing-machine-js/machine@next`. + +**Pre-release — the API surface may still shift before stable v7.0.0.** Pin to a specific alpha for reproducibility: `@turing-machine-js/machine@7.0.0-alpha.5`. + +### Added + +- **`MachineState.matchedTransition`** ([#205](https://github.com/mellonis/turing-machine-js/issues/205)) — every yielded `MachineState` from `run` / `runStepByStep` now carries `{ id: string, matchKinds: ('wildcard' | 'literal')[] }`. `id` is `${stateId}.${transitionIx}` and resolves directly in `toGraph`'s output via `graph.nodes[stateId].transitions.find(t => t.id === ...)`. For wrapper-entry iters, `id` references the BARE's transition (wrappers delegate via `bareState`). `matchKinds` is per-tape, length = tape count; `'wildcard'` iff the winning alternative held `ifOtherSymbol` at that position. Eliminates `(source, nextState)` resolution ambiguity for tools doing exact-edge highlighting, per-transition coverage maps, or wildcard-aware log formatting. + +- **`State.getMatchedTransition(symbol)`** ([#205](https://github.com/mellonis/turing-machine-js/issues/205)) — public method returning `{ nextState, matchedSymbol, ix }` for the symbol's transition. Same lookup `getNextState` does, plus the index — exposes the K used by `MachineState.matchedTransition.id` so callers needing both don't pay for the lookup twice. + +- **`TapeBlock.patternKinds(symbol, currentSymbols?)`** ([#205](https://github.com/mellonis/turing-machine-js/issues/205)) — public method returning per-tape `'wildcard' | 'literal'` for the matched alternative's selector. The engine uses it internally to populate `matchedTransition.matchKinds`; exposed for consumers wiring custom dispatch. + +- **`HaltState` typed alias** ([#207](https://github.com/mellonis/turing-machine-js/issues/207)) — the exported `haltState` singleton now carries a narrower TypeScript type (`State & { debug: boolean; ... }`) so `haltState.debug = true` is type-safe at the call site. Generic `State` references still see `DebugConfig` — runtime branches on `this.isHalt`. + +### Changed + +- **`GraphTransition.id` separator: `-` → `.`** ([#205](https://github.com/mellonis/turing-machine-js/issues/205)). Was `${stateId}-${patternIx}`, now `${stateId}.${patternIx}`. Aligns with `matchedTransition.id` and makes the id splittable without colliding with user-defined state names that may contain `-`. **Breaking** for any consumer parsing the old separator; alpha-channel only. + +- **`haltState.debug` is `boolean`** ([#207](https://github.com/mellonis/turing-machine-js/issues/207)). Was `DebugConfig` with per-side `{ before?, after? }` filters; under v7 it's a single `boolean` flag. `haltState.debug = true` enables; `false` / `null` disables. **Object-shaped writes throw** (`{ before: true }`, `{ after: true }`, etc.). The pause fires on the AFTER side of the iter whose transition leads to halt — `m.state` is the triggering state (not haltState), `m.debugBreak.after === true`. Diagram + log narratives read naturally: the halt-bound transition has fired when the pause lands, and halt is the next thing. **Breaking** — pre-alpha.5 consumers using `haltState.debug = { before: true }` must switch to `haltState.debug = true`. + + ⚠️ **Chained-form `haltState.debug.before = true` silently no-ops in non-strict mode.** The getter returns the boolean; assigning `.before` to a primitive is a JS no-op outside strict mode (`TypeError` in strict). The engine setter never sees the write. Use the whole-object form (`haltState.debug = true` / `= false` / `= null`). + +- **`State` internal: single `#getEntry` helper for `getCommand` / `getNextState` / `getMatchedTransition`** ([#206](https://github.com/mellonis/turing-machine-js/pull/206)). Internal refactor — no public API change. All three accessors now share one `.get()` lookup + unified throw message (`"No transition for symbol at state named ..."` — was per-method messages). + +- **Internal: dead-code typeof check on `state.debug` in `runStepByStep` removed; `patternKinds` defensive paths now have dedicated test coverage** ([#210](https://github.com/mellonis/turing-machine-js/pull/210)). Engine-internal cleanup that bumps overall coverage to 99.35% statements / 96.58% branches / 100% functions / 99.48% lines. + +### Docs + +- ⚠️ **README + CLAUDE.md** ([#209](https://github.com/mellonis/turing-machine-js/pull/209)) — warn that chained-form `haltState.debug.before = true` silently no-ops in non-strict mode (`new Function(...)`, `