feat(live-room): inline working card widget in the chat stream#392
Merged
Conversation
Closes #381. Renders each `Working`-state spawn instance as an inline ASCII card spliced into the chat scrollback at the spawning event's chat-time position, per ADR Frame B. - New `working_card` module wraps the visual locked in the chat-stream-demo (#379) and reads only from `SpawnInstance`. Markers: `✓` done, `∴` in-progress, `⨯` failed. Identity color comes from `tui_style::role_color` — no hard-coded literals. - `SpawnInstance` gains a `title` field (populated from `WorkTitle` events the tracker now consumes) and a `chat_position` field (stamped by `RoomRuntimeState` at `TurnDispatched` time). - `render_scrollback` builds a merged scrollback that interleaves card lines at each spawn's `chat_position`, then applies the existing scroll-offset / wrap logic. Cards therefore scroll with history (ADR Q1 — no pinning) and the v0.9.16 `↓ N new` follow indicator continues to work. - Elapsed time renders at whole-second granularity so sub-second re-renders never change the rendered string (AC-5 flicker guard). - Long tool-call summaries middle-truncated to fit `card_width`. - Hotkey hint `[e]xpand [i]nterrupt [f]ocus` is rendered as a non-functional string per AC-7; `handle_key` is untouched (#385 will wire it). Addresses @Reviewer must-fix items on the original draft: 1. Rebased onto current main (which now has #390 footer narration and #391 rail slim). Tests verify the working card, footer narration row, and slim rail coexist at 120×30. 2. `push_scrollback` now calls `spawn_lifecycle.shift_chat_positions` after the 1000-row drain so every Working card's anchor index shifts left by `overflow` rows (saturating at 0). Without this the card would silently slip out of place after long sessions, which would have broken AC-2. Two new regression tests pin both: - `working_card_position_survives_scrollback_drain` floods 1500 rows while one card is Working, then asserts `chat_position` shifted and the card still precedes the latest line in the merged scrollback. - `build_merged_scrollback_is_idempotent` rerenders the same state twice and asserts byte-for-byte identical output. Validation: cargo fmt clean, cargo clippy --all-targets -- -D warnings clean, cargo test --lib console_room_runtime (77 pass — 65 existing + 6 footer narration + 6 working card integration), cargo test --lib working_card (21 pass), cargo test --lib spawn_lifecycle (17 pass).
6c71226 to
2510ce2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #381.
Summary
Implements the v0.10
WorkingCardwidget per the ADR (docs/v0.10-chat-stream-vs-dashboard.md) Frame B mockup. EachWorking-state spawn instance now renders as a multi-line ASCII card spliced into the chat scrollback at the spawning event's chat-time position. The widget reads fromSpawnInstanceonly — no kernel access.Design choices that needed picking
Card title source. The issue offered two options. I picked adding a
title: Stringfield toSpawnInstance(default empty) and wiringCrepEvent::WorkTitlethroughSpawnLifecycleTracker::apply_eventto populate it. Rationale: the tracker is already the single ingest point for the kernel event stream, so this stays consistent with the rest of #380's model and avoids a parallelpending_titlesmap on the renderer. When noWorkTitlehas landed yet the card top border shows the locked(no title)placeholder.Chat position. Added a
chat_position: usizefield toSpawnInstance.RoomRuntimeState::apply_eventstamps it via a newSpawnLifecycleTracker::set_chat_position(SpawnId, usize)setter atTurnDispatchedtime, using the currentscrollback.len()as the splice index. This keeps the tracker renderer-agnostic — it owns the storage but never reads the value.Splice rather than overlay.
render_scrollbacknow builds a mergedVec<Line>that interleaves card lines intostate.scrollbackat each working spawn'schat_position. The existing slicing /scroll_offset/↓ N newfollow indicator (v0.9.16 #371) all continue to work because cards live INSIDE the merged scrollback rather than being rendered as a separate overlay.Elapsed time. Rendered with
Duration::as_secs()granularity. Sub-second drift cannot change the displayed string, so AC-5 (at most once per second) holds without an explicit timestamp gate.Acceptance criteria
WorkingCardrenders one Working spawn per the visual. ReadsSpawnInstanceonly.tui_style::role_color— no hard-coded literals for role identity.[e]xpand [i]nterrupt [f]ocusrenders but pressing the keys does nothing — v0.9.16: per-card hotkeys for working cards — [e]xpand [i]nterrupt [f]ocus #385's surface.Validation
Required evidence — three rendered scenes
Scene A (AC-6a) — zero tool calls, card shows just title:
Scene B (AC-6b) — one done + one in-progress:
Scene C (AC-6c) — 5 tool calls (>N=3), oldest 2 dropped:
(Scenes captured from
cargo run --exampleagainstRoomRuntimeStatedriven through realCrepEvents — the example file itself is not committed; the equivalent test fixtures live insrc/console_room_runtime.rs::testsandsrc/working_card.rs::tests.)Files touched
src/working_card.rs(new) — the widget plus 14 renderer unit tests (state gating, zero/one+/overflow tool calls, identity color, elapsed label, middle-truncation, marker glyphs).src/spawn_lifecycle.rs— addedtitleandchat_positionfields toSpawnInstance; routedCrepEvent::WorkTitlethroughapply_event; addedset_chat_positionsetter; 3 new lifecycle tests.src/console_room_runtime.rs— stampedchat_positiononTurnDispatched; addedbuild_merged_scrollback; 6 new integration tests.src/lib.rs— registered the new module.Out of scope (per #381)
#385).DoneandReportedcollapsed/report dual emission (#384).#382).#386).#383).