You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Closes chunk 10. ChatBubble (bottom-right) now renders the live
token stream from the WebSocket `token.received` events, with
PipelineState.generatedTokens as a fallback. Chunk-10 checkbox
flips to [x]; all four persistent UI surfaces are now wired.
ChatBubble component:
- New props: tokens (string[]) + isFinal (boolean).
- Empty → "Tokens stream in here as the model generates."
- Populated → "Response" header + token count + concatenated text
with whitespace preserved.
- " · complete" appended to the count when isFinal=true.
- Blinking cursor (1px-wide animated bar) trails the text while
streaming; hidden when isFinal.
- data-final attribute mirrors the prop for test assertions.
CinematicViz wiring:
- events prop now consumed (was inert since chunk-1 type slot).
Default = []; pages pass through from useRunStream.
- Filter events for token.received via discriminated-union
type narrowing; map to tokenEvents.
- chatTokens = events-derived when any present, else
state.pipelineState.generatedTokens.map(t => t.string).
- chatIsFinal = last event's is_final, OR (state fallback)
Scene 20 flourish phase (sceneId === 'detokenize' && t >= 0.9).
- ChatBubble mounts with the derived props.
11 new Vitest tests in ChatBubble.test.tsx:
- Empty placeholder gated to no tokens.
- Concatenated text renders.
- Token count visible.
- "complete" suffix gated to isFinal=true.
- Blinking cursor gated to !isFinal.
- data-final attribute mirrors prop.
- Whitespace + newlines preserved in concatenated text.
Full suite: 86 files / 867 tests pass; tsc --noEmit clean.
phase1.md: chunk-10 checkbox [x], status row "chunks 1-10 done",
chunk-10b decisions block (10 bullets) landed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| M13 | Cinematic Inference Visualization | M12 | 12 days | 🟡 In progress (chunks 1–9 done) | Replaces the M8 live viz with a 20-scene narrative |
30
+
| M13 | Cinematic Inference Visualization | M12 | 12 days | 🟡 In progress (chunks 1–10 done) | Replaces the M8 live viz with a 20-scene narrative |
31
31
| M14 | Deployment | M13 | 5 days | ⚪ Not started ||
32
32
| M15 | Launch Prep | M14 | 5 days | ⚪ Not started ||
33
33
||**Total**||**~80 engineer-days**|||
@@ -1027,7 +1027,7 @@ Bonus M12 work beyond the SPEC's two exit criteria:
1027
1027
1028
1028
- [x] **Chunk 9 — Scenes 18-20 (autoregressive loop + KV cache + detokenization).** **Scene 18 (variable)**: meta-loop. Full sequence (input + generated so far) becomes new input. Compress Scenes 5-17 into ~2s for token #2, ~1.5s for #3, accelerating to ~200ms/token (5/sec) by token #10+. The real run drives token timing via the WebSocket; if the run finishes before the viz catches up, viz keeps playing at its accelerated pace while the chat bubble already has the full text. **Scene 19 (2s, one-time reveal during first loop iteration)**: KV cache. Pause briefly; show K + V matrices from previous tokens appearing in a dimmed "cache drawer" with a small lock/disk icon. For the new token, only its row of K + V is fresh-computed + added. Attention matrix gains a single new bottom row per step instead of recomputing the whole grid. Subsequent loop iterations skip the explanatory beat; cache drawer just fills. **Scene 20 (continuous during Scene 18)**: detokenization — each chosen token ID briefly transforms back to its string fragment via reverse-lookup highlight in the vocab sidebar, then flies into the chat bubble + concatenates. End-of-sequence token: chat bubble glows, full pipeline canvas dims, completion flourish.
1029
1029
1030
-
-[]**Chunk 10 — Persistent UI completion.** Wire the four sections to live data:
1030
+
-[x]**Chunk 10 — Persistent UI completion.** Wire the four sections to live data:
1031
1031
-**Vocabulary sidebar (left)**: scrolling list of `(id, string)` populated during Scene 3's tokenization. Persists through all 20 scenes. Highlight + scroll-into-view on lookup events from Scene 3 (forward) + Scene 20 (reverse).
1032
1032
-**Chat bubble (bottom-right)**: mock chat-message UI. Starts empty; grows as Scene 17 appends each token's string. Driven directly by the real `token.received` WebSocket event so the user sees the response coming in even if the visualization hasn't reached Scene 17 yet for that token.
1033
1033
-**Layer counter HUD (top-right)**: integer counter + tower progress mini-bar. Visible during scenes 5-12. Auto-hides during scenes 0-4 + 13-20.
@@ -1066,7 +1066,19 @@ Bonus M12 work beyond the SPEC's two exit criteria:
1066
1066
-**Real run continues independently.** The M6 streaming pipeline + the M8 `useRunStream` hook keep doing exactly what they do. The chat bubble subscribes to the same WebSocket events and displays tokens as they arrive. Scenes 18-20 use the same event timeline to drive the autoregressive-loop pacing, but the viz lagging behind is expected + acceptable.
1067
1067
-**WebSocket → Scene coupling.** Scenes 5-17 fire once per *generated* token. The current `RunEvent` schema (M6) emits one `token.received` per token; the SceneRunner subscribes + advances. For the input tokens (the prompt), the runner enters Scenes 0-4 at submit time without waiting for the WebSocket.
-**ChatBubble accepts `tokens: string[]` + `isFinal: boolean`** — the simplest contract that matches both data sources. CinematicViz does the events-vs-state selection upstream; ChatBubble itself doesn't know which source produced the array.
1071
+
-**Events-primary, state-fallback selection in CinematicViz.** When the `events` prop contains any `token.received` entries, the bubble reads from those. When empty (chunk-10 isolated testing, replay without a stream), it falls back to `state.pipelineState.generatedTokens.map(t => t.string)`. This delivers the spec's "ahead-of-viz" property automatically: live events stream from the real run independent of which scene the viz is on; chunk-10 tests + replays without a stream still show something.
1072
+
-**`is_final` derived from the last token event's payload, or scene-based fallback.** When events stream, `is_final` is the `tokenEvents[N-1].payload.is_final` flag. When no events (state fallback), the bubble enters "complete" mode when Scene 20's flourish is active (`sceneId === 'detokenize' && t >= 0.9`). Mirrors the in-canvas "Inference complete" badge timing.
1073
+
-**Blinking cursor only when `!isFinal`.** Subtle pulse on a 1px-wide bar at the end of the text. CSS `animate-pulse` (Tailwind's keyframes). Hidden once the run is final — matches the "stop talking" beat. Aria-hidden because the cursor is purely decorative.
1074
+
-**Token count rendered alongside "Response" header.** "12 tokens" / "12 tokens · complete" — quick orientation for the viewer about how much output has streamed in. The chunk-9 in-canvas tray uses the same count format.
1075
+
-**`whitespace-pre-wrap break-words` on the text.** Real tokens include leading spaces (`' the'`, `' a'`); preserving them gives natural word-boundary rendering. `break-words` keeps unusually long single tokens from horizontally overflowing the 288px bubble.
1076
+
-**CinematicViz's events prop defaults to `[]`.** Existing pages (Threads/Show, Runs/Replay, Share/Replay) already pass an events array from the M6 streaming wiring; the default is for chunk-10 isolated testing only. The page mounts continue to work without changes.
1077
+
-**Type narrowing via `Extract<RunEvent, { event: 'token.received' }>`.** TypeScript's discriminated-union narrowing inside the `filter` predicate gives us typed access to `e.payload.token` + `e.payload.is_final` without a cast. Documented in the inline comment.
1078
+
-**No `useRunStream` import in `CinematicViz`.** The hook stays at the page level (Threads/Show passes its events through to CinematicViz). Keeps CinematicViz a pure render component — no live data subscription, easier to test in isolation. Chunk 11 will keep the same separation when adding playback controls.
1079
+
-**Chunk 10 complete with all four surfaces wired.** PipelineProgressBar (chunk 1), VocabSidebar (chunks 3c + 10a), LayerCounterHud (chunk 10a), ChatBubble (chunk 10b). The off-canvas persistent UI now reflects scene + run state continuously.
-**PipelineProgressBar was already wired in chunk 1.** The 21-segment control + `onSelectScene` click-to-jump existed since the chunk-1 stub plus the chunk-3a integration. No work needed in chunk 10. Status table line in the chunk-10 task list now reads "✅ already wired (chunk 1)".
1071
1083
-**LayerCounterHud derives `currentLayer` from viz state, not real `layer.advanced` telemetry.** Spec line says "visible during scenes 5-12" — the HUD reflects *where the viz is*, not raw run progress. During scenes 5-11 we show the representative layer (= 1) since those scenes visualize a single layer's worth of computation. During Scene 12 (tower view) the HUD's value matches Scene 12's in-canvas counter exactly via the same five-phase math. Real `layer.advanced` events stay out of scope; chunk 12 (perf) or chunk 14 may add a "real layer" indicator if needed.
1072
1084
-**Phase math for Scene 12 inlined in `CinematicViz`, not re-imported from `lib/towerCamera`.** Considered importing `counterValue` from chunk 7's lib. Rejected because the HUD already mirrors the in-canvas counter exactly; re-importing would tightly couple the persistent HUD to the scene-internal math. The 10-line inline equivalent is its own contract — chunk 7's `towerCamera` is testable + the HUD is testable; both can evolve independently without circular dependency.
0 commit comments