-
Гильмуллин Руслан Ниязович создал
feat: showcase panel overhaul — execution-trace table, lesson notes, single-snippet orchestration (#112) (#113) * feat: showcase panel overhaul — execution-trace table, lesson notes, single-snippet orchestration (#112) The landing-page snippet showcases used to be a single column with a {frameIndex} counter under each animation. Reworked around stepped- equivalent learning artifacts: a per-iter trace table, prose lesson notes beside the player, and Landing-orchestrated playback so only one snippet runs at a time. Execution-trace table (src/components/ExecutionTraceTable.svelte) - Six columns — Step | State | Head reads | Write | Move | Goto — same shape as the engine README's trace example, minus the Tape after column (the live tape strip already covers that). - Highlighted row carries aria-current="step" and the --graph-highlight-soft-fill background so the trace matches the graph's "you are here" feel. - Sticky thead inside max-height: 280px overflow-y: auto. - Scroll-into-view is scoped to the trace's wrap container via manual scrollTop math — Element.scrollIntoView walks every overflow ancestor (including the page) and would yank the whole landing layout each time a partially-visible panel ticked. - Multi-tape cells render as bracketed arrays ([a, _] / [*, b] / [R, L]). Blank cells use the .blank dimmed class instead of UI substitution (matches Tape.svelte policy). - Halt resolves toId === 0 → 'halt' directly so the engine graph node's literal "id:0" placeholder name doesn't leak through. Two-column SnippetPanel + per-showcase lesson notes - Below the caption, a CSS grid: left column is the player content (graph + tapes + trace + controls) on a framed surface (background: var(--bg) + border + radius — reads as an embedded screen within the outer card); right column carries lesson prose. Stacks to a single column at <=768px. - Landing's snippet grid switched from a multi-column auto-fill track to one panel per row so the internal two-column layout has room. Showcase Tape viewport widened to 17 / 13 / 9 cells across the desktop / tablet / phone breakpoints. - New Example.lessonNotes?: string field carries the prose. All six showcases got multi-paragraph notes — what the program does, the role of each state or instruction, and (for callable-subtree and call-subroutine) the subroutine boundary, the call/return arrows, and the wrapper-vs-bare distinction. - src/lib/lessonMarkdown.ts renders a tight subset (paragraphs, bullet lists, inline backtick code) — HTML-escapes input then emits the formatted block. Injected with {@html} under the demo's existing build-time-author-content policy (same policy the SVG icons use). Renderer tests at src/lib/lessonMarkdown.test.ts. Single-snippet playback orchestration - Landing.svelte owns a single IntersectionObserver across all rendered panels (one observer; ratios tracked across callbacks since IO reports only what changed). A Svelte use:panelSlot action registers each panel's slot wrapper with the parent registry on mount. The panel with the highest visible ratio becomes active. Engine switch clears the activeSnippetId + ratios. Under prefers-reduced-motion the IO is never created. - SnippetPanel becomes a controlled playback consumer. The internal IO is gone. Receives an active: boolean prop and dispatches a 4-state playback machine through one $effect: state | becomes active | becomes inactive ---------|------------------------------------|------------------- idle | reset to frame 0 + start timer → | stays idle | playing | playing | (already playing — no-op) | clear timer → paused paused | resume timer from current frame → | stays paused | playing | done | no auto-replay — Replay only | stays done First viewing plays from the top; scroll-away pauses without reset; scroll-back resumes. Finished snippets sit at end-state until the visitor clicks Replay — re-scrolling past a completed snippet doesn't restart it. Reduced motion pins to the final frame on mount and ignores active changes. - The active-toggle $effect's body wraps in untrack() so $state reads inside dispatch helpers (machineGraphRef, tapesStackRef, graphReady) don't become effect deps and form an infinite loop. replayTimerId demoted from $state to plain let to break a separate read/write feedback loop on the timer handle. - Trace-row highlight clears at natural playback end too — parallels the graph-highlight clear at the same moment (#108). Under reduced motion the last row stays highlighted to mirror the graph holding the final-frame snapshot. Stepped-shaped graph highlight during playback - recordSnippet emits after-iter-shaped highlights (strong: 'from', paused: false, fromId = bareIdOf(m.state.id) — already canonicalized). For wrapper-entry iters that canonicalization discards the wrapper info, so the callable-subtree showcase's wrapper node + the bold "call" arrow stayed dim during playback even though the cluster lit up via §9. - Fix lives entirely in the demo, no upstream visuals change. src/vite-plugins/snippets.ts wraps machine.runStepByStep BEFORE calling recordSnippet so the wrapper rides the same iteration and shares the same State id space the graph was built from. Captures m.state.id / m.nextState.id per iter into rawStateIds / rawNextStateIds, attached to each snippet as extension fields. - SnippetPanel.applyGraph now builds the highlight from rawStateIds: fromId = bareIdOf(rawStateIds[step - 2], graph) (or 'idle' for the first iter), toId = rawStateIds[step - 1] raw, strong: 'to', paused: true — the before-pause shape live stepped execution emits, so §2 expansion in applyHighlight lights up [wrapper, bare] and §10 lights the call edge during the wrapper-entry iter. The recorded Frame.highlight stays untouched (the trace table reads it as-is). - A first attempt re-eval'd the example to capture raw ids in a separate machine — that minted fresh State runtime ids that didn't match graph.nodes keys, and the symptom was "only idle highlighted, nothing else." Wrapping the same machine fixed it. Layout plumbing fixes that surfaced along the way - src/app.css body gets overflow: hidden + the flex chain (body → main → .landing) gains explicit min-height: 0 so internal containers bound correctly and .landing's overflow-y: auto owns the only vertical scroll. Without it, Safari's rubber-band overscroll exposes empty page-background area below the footer when content is short. Tests + tooling - SnippetPanel.test.ts rewritten: FakeIntersectionObserver harness gone, tests drive the new active prop directly. New cases: inactive-pauses-preserves-highlight (pause-and-resume keeps frame position), done-no-autoreplay-on-scroll (finished snippets need explicit Replay even after re-scrolling in). Trace-row assertions go through [aria-current="step"] + data-testid="trace-row" + data-step. - ExecutionTraceTable.test.ts covers row count, current-row marking, multi-tape brackets, blank-cell class, halt rendering, unknown-node fallback. - Stub Snippet.graph.nodes entries now include the full GraphNode shape (transitions, isWrapper, isHaltMarker, tags) because the embedded MachineGraph's summariseGraph walks them — happy-dom no longer trips on missing fields. - 281 tests across 27 files; Playwright landing.spec.ts unchanged (still 18 e2e green). Docs - CLAUDE.md component tree gains Landing.svelte / SnippetPanel.svelte / ExecutionTraceTable.svelte / lessonMarkdown.ts entries with their new responsibilities (IO orchestration, 4-state machine, manual scrollTop, lesson-prose policy). - README.md intro opens with the landing page; layout tree gets the three new components + lessonMarkdown.ts; e2e section lists all three spec files. - docs/execution-model.md gains §1.1 "Scope: engine pages only" — SnippetPanel playback is orthogonal to MachineView's DebugSession modes. - docs/machine-graph-palette.md highlight-tokens table notes ExecutionTraceTable reuses --graph-highlight-soft-fill so the trace and graph stay in lockstep when re-tuning. Closes #112. * chore(release): 1.0.0-alpha.25 Showcase panel overhaul shipped in the preceding commit (#112): execution-trace table replacing the {frameIndex} counter, two-column panel with per-showcase lesson notes, Landing-orchestrated playback (4-state machine: idle / playing / paused / done — finished snippets need explicit Replay), stepped-shaped graph highlight that lights up the wrapper + call edge during wrapper-entry iters.a207d177