Skip to content

refactor(live-room): shrink right rail to slim Status strip#391

Merged
spytensor merged 1 commit into
mainfrom
feat/v0.10-383-rail-slim
May 26, 2026
Merged

refactor(live-room): shrink right rail to slim Status strip#391
spytensor merged 1 commit into
mainfrom
feat/v0.10-383-rail-slim

Conversation

@spytensor
Copy link
Copy Markdown
Owner

Closes #383.

Implements ADR docs/v0.10-chat-stream-vs-dashboard.md §Q5 — the right rail collapses to a single Status card (Work / Blockers / Evidence, ~10 rendered lines including borders) and stops reading anything that ticks per tool-call.

Summary

  • Rewrote render_status_rail to delegate to a new pure slim_status_rail_lines(state) -> Vec<Line> that reads only work_cards statuses and the room-level permission flag — no state.spinners, no state.spawn_lifecycle.
  • Added stable rollups: rail_work_active_count (Working WorkCards), rail_blocker_status (room-level permission/interrupted), rail_evidence_status (no read of has_active_work, which would have leaked spinner state in).
  • Deleted obsolete rail helpers: status_panel_lines, work_card_lines, role_metadata_line, work_status_label, status_color_for_work, work_card_detail, evidence_status_text, evidence_status_color. Kept the still-useful section_header and status_kv_line.
  • Marked RoomRuntimeState::team and last_seen as #[allow(dead_code)] with doc comments explaining the back-compat reason (the rail's activity: K/N roles seen field that consumed them is gone — moved to the footer narration per v0.9.16: footer narration bar — "N roles still working" #382 / Q4).
  • Updated / deleted rail tests per AC-4 (see "Test impact" below).
  • Added new tests that pin the ADR semantics: byte-identical rail across spinner ticks (AC-2), active counts WorkCards not spinners, blockers track only room-level signals, the rail never renders the WorkCard body.

Removed-fields relocation table

Per AC-3. Every field the old rail carried is either resurfaced on another v0.10 surface or deliberately dropped as redundant.

Field (old) Old location New location
activity: K/N roles seen rail Work block footer narration (#382) — N roles still working · @role @role is the live answer; the K/N counter is dropped as redundant with the per-role list
latest: <work card title> rail Work block chat-stream working card header (#381) — each WorkCard renders inline in the Room with its title
assignee: @role · working rail Work block chat-stream working card title row (#381) — @role · <title> · working · 14s
Embedded WorkCard body (steps, current_step) rail body, below Status chat-stream working card body (#381) — the WorkCard widget owns its full render in the Room
approval: @role · <tool> rail Blockers block room-level surfaces — the slim rail still flags state: approval pending at room level; the per-role / per-tool detail lives in the existing permission overlay which is unchanged
interrupted: @role · <reason> rail Blockers block chat-stream done-collapsed marker (#384) — @role ⨯ interrupted · {elapsed} · {N steps}; the slim rail keeps a room-level state: interrupted summary
validation: observed complete | pending | blocked rail Evidence block rail Evidence block (renamed semantics) — now clean | pending | blocking | not observed per ADR §Q5. Derivation no longer reads has_active_work() (which inspected spinners), only work_cards statuses and permission
@role work-card identity header rail-rendered above each card chat-stream working card border title (#381) — the role glyph + token belongs to the in-chat card
Tool-call tree (embedded delegate calls) rail chat-stream working card live tool stream (#381) — visual adjacency replaces the tree per ADR thesis

AC status

  • AC-1 (no per-spawn lifecycle reads from rail): grep against the file shows zero matches inside render_status_rail, slim_status_rail_lines, rail_work_active_count, rail_blocker_status, or rail_evidence_status. The matches that remain are:

    Verified by grep -E 'spawn_lifecycle|SpawnInstance|working_instances|spawning_instances' src/console_room_runtime.rs.

  • AC-2 (rail stable across tool-call ticks): new test rail_stays_byte_identical_across_spinner_ticks constructs two RoomEvent::Spinner events differing only on frame, tools_seen, current_state and asserts the rendered rail body is identical. The implementation makes this hard to regress: the new helpers only read work_cards and permission, both of which are quiet on spinner events.

  • AC-3 (field relocation table): above.

  • AC-4 (rail tests deleted not adapted): see "Test impact" — three tests are deleted with a comment in place; one is rewritten to express the new locked semantics.

  • AC-5 (main Room reclaims freed space): the rail's Constraint::Length(42) is intentionally kept. Rationale: the slim rail content is much narrower than 42 (max ~20 cols), but shrinking the rail to e.g. 32 would (a) re-trigger layout regressions tested by existing snapshots without a behavioral payoff, and (b) leave awkward whitespace if/when ADR §Q5 evolves. The visual "freed space" the rail used to consume vertically (tool-call tree, work-card body) is the real win — the Room now uses that height for chat-stream working cards (v0.9.16: inline working card widget in the chat stream #381). The body's Constraint::Min(62) already provides the Room with all the horizontal room it needs at supported viewports. Layout constraints in render_room_runtime_frame are untouched per the issue's instructions.

Test impact

Test Action Why
rail_shows_work_status_when_no_spinners_and_no_cards renamed to rail_shows_locked_slim_status_when_idle, kept the locked idle baseline still has a test; assertions expanded to cover cards: 0 and the absence of removed fields
rail_shows_status_and_center_current_turn_when_spinning rewritten as rail_active_counts_working_work_cards_not_spinners per spec: drop the rail-related "current turn" assertions; keep the inline @backend behavior. New test additionally pins the ADR semantic that a spinner alone leaves active: 0
rail_folds_work_panel_when_no_cards removed (replaced) the slim rail always shows the Work block; folding never happens. Covered by rail_shows_locked_slim_status_when_idle and rail_does_not_render_work_card_body
rail_renders_team_plus_work_when_card_present_but_no_spinner replaced by rail_does_not_render_work_card_body asserted latest, assignee, the inline ◇ @backend header, and the work-card title on the rail — every one of those surfaces is gone per ADR §Q5
work_card_renders_role_label_exactly_once_per_card deleted with comment counted @backend inside the rail-rendered work card body, which the rail no longer renders. Per AC-4: delete, not adapt
work_and_spinner_events_populate_status_rail deleted with comment asserted the work-card title (Run validation) and role glyph () appeared on the rail. Those surfaces moved to the chat-stream working card (#381)
work_cards_render_a_role_identity_header_per_card deleted with comment asserted ◇ @backend on the rail above the card; the rail no longer renders cards
rail_stays_byte_identical_across_spinner_ticks new AC-2 enforcement
rail_active_counts_working_work_cards_not_spinners new locks ADR §Q5 semantics: active is WorkCards, not spinners
rail_does_not_render_work_card_body new regression guard against the rail re-absorbing the WorkCard body
rail_blocker_state_reflects_permission_prompt new locks the room-level (not per-role) blocker copy

Validation

cargo fmt --all -- --check       # OK
cargo build --lib --quiet         # OK
cargo test --lib console_room_runtime   # 63 / 63 passed
cargo test --test console_terminal_qa_test  # 5 / 5 passed
cargo test --all                  # full suite green
cargo clippy --all-targets --all-features -- -D warnings  # clean

Test plan

  • In a live cr session with two concurrent sub-agents, watch the rail across many tool-call ticks: confirm no flicker in active / cards / validation / state (AC-2 live dogfood).
  • Open a WorkCard-emitting flow (e.g. host validation) and confirm active increments by exactly 1, cards increments by exactly 1, and neither moves on per-spinner ticks afterwards.
  • Trigger a permission prompt and confirm Blockers state: approval pending without any role/tool detail on the rail (the overlay still carries that detail).
  • Compare a frame at 120×30 to the ADR Frame A mockup — the rail should match.

Per ADR `docs/v0.10-chat-stream-vs-dashboard.md` §Q5, the right rail
keeps a single ~10-line `Status` card (Work / Blockers / Evidence)
and drops every per-spawn, per-turn, per-tool field. The slim rail
is byte-identical across tool-call ticks because it no longer reads
`state.spinners` or `state.spawn_lifecycle` — only `work_cards`
statuses and the room-level `permission` flag.

Closes #383.
@spytensor spytensor force-pushed the feat/v0.10-383-rail-slim branch from 9db4fa3 to 7eddcc8 Compare May 26, 2026 09:22
@spytensor spytensor merged commit f93257e into main May 26, 2026
5 checks passed
@spytensor spytensor deleted the feat/v0.10-383-rail-slim branch May 26, 2026 09:25
spytensor added a commit that referenced this pull request May 26, 2026
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).
spytensor added a commit that referenced this pull request May 26, 2026
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v0.9.16: shrink right rail to scoped dashboard, move team activity out

1 participant