• Гильмуллин Руслан Ниязович's avatar
    adopt(demo): engine + post v7.0.0-alpha.6 (engine #102 DebugSession reshape) (#80)
    · 6e0e13e5
    * feat(adopt): worker + scenarioRunner adopt DebugSession (engine #102)
    
    Adopts the v7 alpha.6 engine API (run sync + callback-free; new
    DebugSession class for interactive debugging):
    
    - scenarioRunner: replaces `machine.run({onStep, onPause})` with a
      `new turing.DebugSession(machine, ...)` + `.on('step'/'pause')` + start().
    - machineWorker: at the run() call site, constructs either a Turing
      DebugSession (for TuringMachine) or a PostDebugSession via
      pm.debugRun() (for PostMachine), wires the existing
      onPauseFn / onIterFn / onStep handlers via session.on(...), and
      drives via session.start(). Pause listeners call session.continue()
      after dispatchPause resolves.
    
    Caveat — iter-synthesized step pauses: DebugSession iter listeners fire
    fire-and-forget (Node EventEmitter semantics, established in engine #102
    PR discussion). The existing onIterFn calls dispatchPause + awaits a
    worker-side resume; with fire-and-forget iter, that await no longer
    blocks the engine. The UX of "Step button = run one iter, then pause"
    may need a follow-up tweak (use session.pause() inside iter listener
    instead of dispatchPause) — left for the dedicated demo migration PR.
    Breakpoint-driven pauses (the main use case) work cleanly via the
    pause event listener.
    
    Builds via the local npm link to the v7-alpha.6 engine and the
    post-machine-js adopt branch — when those publish, the peer-dep pin in
    machines-demo's package.json widens to ^7.0.0-alpha.6.
    
    * fix(worker): return iter listener's Promise to the engine (engine #102)
    
    The iter listener was wrapped in 'void onIterFn(m)' — the listener
    returned undefined, so the engine's 'await fn(m)' awaited nothing.
    RUNNING_STEP and RUNNING_AUTO broke: onIterFn's internal awaits
    (synthetic dispatchPause, throttle setTimeout) ran in parallel with
    the engine, which advanced freely through all iters in one click.
    
    Drop the void and let the listener return the Promise. The engine's
    awaited dispatch then properly blocks on the demo's per-iter
    coordination — Step pauses after one iter, Run-with-pause throttles
    between iters.
    
    * fix(worker): halt breakpoint toggle uses boolean directly; drop ugly casts (#102)
    
    Two fixes:
    
    1. **Halt breakpoint couldn't be unchecked.** `entry.state.debug` for
       haltState is a single boolean (turing-machine-js#207), not a
       DebugConfig. Reading `.before` / `.after` on a boolean returns
       undefined, so `current` was always `{before:false, after:false}` and
       `mergeDebugKinds` flipped the kind back ON every click. Branch out
       to the haltState path BEFORE the per-side bit read and toggle the
       boolean directly.
    
    2. **Drop `m as unknown as OnPausePayload` double casts** on the
       pause / iter listener wiring. Root cause: the local `OnPausePayload`
       had a `nextState.debug` field whose shape didn't match the engine's
       `nextState` (a State with `DebugConfig | boolean` debug). That field
       was never actually read by the worker — purely vestigial.
    
       Cleanup: redefine `OnPausePayload` as `Omit<MachineYield, 'state'> & {
       state: MachineYield['state'] & { name?: string }; debugBreak?: ... }`.
       The result is structurally assignable FROM MachineYield, so engine
       yields flow into the local handlers as-is — no casts at the call
       sites.
    
    Verified: npm run check (0 errors), npm test (176 pass), npm run lint
    (clean), npm run test:e2e (8/8 cold-start scenarios pass).
    
    * adopt(demo): engine pause descriptor m.pause {side,cause} (engine #102)
    
    Follows the engine reshape (raw runStepByStep yields carry no pause
    field; DebugSession's `pause` event carries `m.pause: {side, cause}`,
    replacing `m.debugBreak: {before?, after?, cause}`):
    
    - machineWorker OnPausePayload: `debugBreak?` → `pause?: {side, cause}`
      (optional — iter events have no pause field). onPauseFn reads
      `m.pause?.side === 'after'` and translates pause.side into the
      worker→main {before?, after?} protocol (unchanged; MachineView reads it).
    - imminentHalt: gate reads `m.pause?.side === 'after'`.
    - scenarioRunner: emitPauseLine takes PausedMachineState; translates
      pause.side for formatPauseLine (the DebugSession migration was already
      in place).
    
    Verified against linked alpha.6 engine + post: check (0 errors), lint
    clean, 176 unit tests, and 8/8 Playwright cold-start e2e against a FRESH
    build (the prior e2e ran on a stale dist).
    
    * refactor(demo): rename worker→main `debugBreak` → `pause {side?, cause}` (engine #102)
    
    Completes the rename through the demo's internal worker↔main protocol so
    it matches the engine's `m.pause` shape end-to-end (no more
    `debugBreak`).
    
    - types.ts `PausedResponse`: `debugBreak: {before?, after?}` →
      `pause: {side?: 'before'|'after'; cause: 'breakpoint'|'step'|'manual'}`.
      `side` is optional: worker-synthesized boundaries (Step button, click-
      pause) have no before/after timing.
    - worker: onPauseFn forwards the engine's `m.pause` as-is (no more
      before/after translation); the two synthetic dispatches now carry
      `{cause: 'step'}` (Step boundary) and `{cause: 'manual'}` (click-pause)
      instead of `{}` — the side-less shape still renders the generic
      "after applying command" line.
    - pauseLineFormat + MachineView: read `paused.pause.side`.
    - scenarioRunner: forwards `m.pause` straight into the PausedResponse.
    - machineRunner.test fixtures + e2e comment updated.
    
    check (0 errors), lint, 176 unit, 8/8 Playwright e2e on a fresh build.
    
    * docs(demo): worker protocol pause descriptor — debugBreak → pause {side?, cause}
    
    Aligns the live docs with the renamed worker↔main `paused` payload:
    - README "Crosses the boundary" + CLAUDE.md worker-contract row now name
      the `pause: {side?, cause}` field.
    - graph-highlight-and-breakpoints §13a: after-fire detection reads
      `m.pause?.side === 'after'`.
    
    NOTE: docs/execution-model.md is intentionally NOT touched here — its
    sequence diagrams predate the DebugSession adoption (they still show
    `machine.run({onPause})` + the arm/restore Step mechanic), so a piecemeal
    debugBreak→pause edit would leave it incoherent. It needs a holistic
    refresh for the DebugSession worker, best done when finalizing the demo
    adoption PR.
    
    * chore(deps): bump engine + post to 7.0.0-alpha.6
    
    * refactor(worker): drive Step/Pause through the engine DebugSession (engine #102)
    
    Stop hand-rolling pauses in onIter. The Step button now calls the engine's
    stepIn() and the Pause button its external pause(); every pause — breakpoint,
    step, manual — flows through the single engine `pause` event. The listener
    awaits main's resume decision and resumes via stepIn()/continue()/stop().
    
    Consequences:
    - Step / click-Pause are now before-side (engine semantics), so the graph
      highlight + pause line show the about-to-fire transition. The consumers
      needed no change — `pauseBefore = pause.side === 'before'` routes them
      automatically.
    - onIter collapses to just the RUNNING_AUTO throttle (skipped while stepping).
    - Removed stepRequested / pauseRequested / dispatchedAfterThisIter and the
      now-dead `decideOnIter` (+ its tests) — Step is before-side and never
      collides with an after-side breakpoint, so the dedup is unnecessary.
    - session hoisted to module scope so the `pause` request can reach it.
    - e2e + CLAUDE.md updated for the before-side wording.
    
    Verified: check, lint, 171 unit tests, 8 Playwright e2e.
    
    * chore(release): v1.0.0-alpha.17
    6e0e13e5