Skip to content

fix(tui): first-class shells on the work strip - #5699

Merged
Hmbown merged 1 commit into
mainfrom
codex/tideline-shells-first-class-20260828
Aug 29, 2026
Merged

fix(tui): first-class shells on the work strip#5699
Hmbown merged 1 commit into
mainfrom
codex/tideline-shells-first-class-20260828

Conversation

@Hmbown

@Hmbown Hmbown commented Aug 28, 2026

Copy link
Copy Markdown
Owner

Summary

  • Background shells are now the same class of work as subagents: a navigable ▾ Shells N group on the work strip. Open a row to watch output; the shell_* id on the row is the cancel target (/jobs cancel <id>).
  • /jobs cancel all (space) and /jobs cancel-all both cancel running shells. The old parse treated all as a task id and printed Command cancel failed: Task all not found.
  • The composer hourglass crumb no longer stands in for a shell surface. Subagent visibility is unchanged.

This is a new PR, not a ride on #5698. The #5698 startup-hero lane was left alone (no underwater.rs, composer, or prompts/text.rs edits).

What was ripped

  • Shells as a footer/status crumb (⏳ 1 shell — cd …) as the only operator surface
  • /jobs cancel all looking up a TaskManager/shell-owner id named all
  • Session-scoped /jobs misses saying Task {id} not found (now Job {id} not found)

Test plan

  • Local: jobs parse + shell-strip + crumb exclusion 8/8 (parses_job_actions, handler_is_pure_and_argument_only ×3 filter hits, list_shows_controls_and_stale_state, pending_work_from_app_picks_up_shell_and_agent_entries, live_shells_are_first_class_work_strip_rows, durable_tasks_are_not_promoted_as_shell_rows)
  • Local: tui:: suite 3140 passed / 8184 skipped / 0 failed (includes work-strip subagent regression tests)
  • Hosted exact-head CI at this SHA before merge (b1b7fda40eedcff2251bcf862d458c7f9cea5b91)
  • In cw operate with a live background shell: strip shows ▾ Shells 1 + the shell_* id; Enter opens output; /jobs cancel all cancels that job; subagents group still works

Made with Cursor

No-Issue: founder operate fix for first-class shells and /jobs cancel all; tracked in the 0.9.12 ledger, not a standalone GitHub issue.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-29T00:07:09.740429Z b1b7fda PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error after 1m 51s —— View job


Claude is reviewing this PR

Todos

  • Gather context (diff, changed files, CLAUDE.md/AGENTS.md rules)
  • Review correctness (logic errors, panics, error handling, exhaustive matches)
  • Review provider/model/route safety implications (likely N/A for this PR)
  • Review reuse/simplification/dead code
  • Review test coverage
  • Review security considerations
  • Post final review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1b7fda40e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +2095 to +2097
primary_action: Some(SidebarRowAction::InspectWork {
title: format!("Shell {}", entry.id),
body: shell_inspector_body(app, entry, command, status),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Load shell output only when the inspector opens

For any verbose running shell, constructing every work-strip projection now calls shell_inspector_body, which locks the manager, calls inspect_job_for_session, and copies the shell's full retained stdout/stderr into the row action. A live shell can retain up to 16 MiB per stream (tools/shell/output.rs), while visible_rows_for_panel is called during layout, rendering, and input, so ordinary redraws can repeatedly allocate and copy tens of MiB per shell and make the TUI unresponsive. Defer output retrieval until the row is activated, or project only a bounded tail.

Useful? React with 👍 / 👎.

Comment on lines +2061 to +2064
if !matches!(entry.status.as_str(), "running" | "queued") {
return false;
}
entry.prompt_summary.starts_with("shell: ") || entry.id.starts_with("shell_")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep shell lifecycle typed through the projection

Classifying shell liveness from the presentation strings "running" and "queued" makes row visibility depend on English status spelling rather than the existing typed ShellStatus; any new or renamed live state will silently disappear from the strip. Preserve a typed shell discriminator/status in TaskPanelEntry, or classify the typed ShellJobSnapshot before flattening it for display.

AGENTS.md reference: crates/tui/AGENTS.md:L13-L14

Useful? React with 👍 / 👎.

Comment on lines +168 to +171
// Live shells belong on the work strip (`▾ Shells N`), not this
// composer crumb. A dual surface hid the PTY behind hourglasses.
if entry.prompt_summary.starts_with("shell: ") || entry.id.starts_with("shell_") {
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve a shell indicator when the Context panel is selected

When rail_panel=context, work_surface/render/mod.rs renders the context fact list directly and visible_rows_for_panel returns no work rows, so skipping shells here removes their only persistent indicator: neither the composer crumb nor the new Shells rows are painted. A user with the persisted Context panel can therefore start a background shell and see no running state or cancellation id until manually invoking /jobs; retain a fallback indicator or include shell work alongside that panel.

AGENTS.md reference: crates/tui/AGENTS.md:L8-L10

Useful? React with 👍 / 👎.

Comment on lines +2136 to +2137
label: format!("Shells {}", shells.len()),
detail: "Watch live output · cancel by the id on the row".to_string(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Localize the new shell work-strip prose

The new heading detail and inspector copy—including “Watch live output,” “Running shells,” and the status/output instructions—are hard-coded English, so every non-English locale displays an untranslated block. Add typed MessageId entries and render these strings through tr(locale, ...), leaving only command names, ids, and glyphs composed in code.

AGENTS.md reference: crates/tui/AGENTS.md:L22-L23

Useful? React with 👍 / 👎.

Comment on lines +2103 to +2105
agent: Some(AgentRowFacts {
role_label: "shell".to_string(),
status: status.to_string(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the shell id visible in compact side rails

At the supported 26-column side-rail width, the shared agent identity cap is only about 10 columns, so the 14-column shell_<8 hex> label is dropped and this fallback paints only shell; because agent-style rows do not render detail, the exact cancellation id disappears from the row. This makes the new id-on-row contract unusable in compact Left/Right layouts, especially with multiple jobs, so preserve the full shell id ahead of optional role/status chrome or use a shell-specific layout.

AGENTS.md reference: crates/tui/AGENTS.md:L19-L21

Useful? React with 👍 / 👎.

Comment on lines +2164 to +2170
if !detail.stdout.is_empty() {
body.push_str("\nSTDOUT:\n");
body.push_str(&detail.stdout);
}
if !detail.stderr.is_empty() {
body.push_str("\nSTDERR:\n");
body.push_str(&detail.stderr);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Declare truncated output in shell inspectors

When a running shell exceeds the raw stream's 16 MiB retention ceiling, inspect_job_for_session returns only the retained tail while snapshot.stdout_len/stderr_len still describe the full stream; this new inspector prints that tail under plain STDOUT/STDERR headings without any omission marker or byte counts. Users can therefore copy or review a clipped log as if it were complete, so propagate the omitted-byte metadata or explicitly label the retained tail before displaying it.

Useful? React with 👍 / 👎.

Shells were a composer crumb and `/jobs cancel all` looked up a task
named "all". Project live shells beside subagents so you can open,
watch, and cancel by the shell_* id on the row.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@Hmbown
Hmbown force-pushed the codex/tideline-shells-first-class-20260828 branch from b1b7fda to 1e663e5 Compare August 29, 2026 00:15
@claude

claude Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 2m 12s —— View job


Review: first-class shells on the work strip

I verified the diff (git diff origin/main...HEAD) and cross-checked the Codex review findings against the actual code paths (call sites, buffer sizing, and the identity-column renderer). Summary: the parse fix for /jobs cancel all and the crumb/strip consolidation are correct and well-tested, but there are two real regressions worth fixing before merge, plus a few lower-priority follow-ups.

Confirmed issues

1. Full stdout/stderr copied into every row on every render/layout/input passcrates/tui/src/tui/work_surface/model.rs:2067 (shell_work_rows) unconditionally calls shell_inspector_body (L2154) for every live shell entry, which locks the shell manager and copies detail.stdout/detail.stderr into the row's body string. visible_rows_for_panel (which this feeds) is invoked from render/mod.rs, render/layout.rs, and input.rs — i.e. every frame and every keystroke, not just when a row is opened. Each raw stream can retain up to RAW_STREAM_MAX_BYTES = 16 MiB (crates/tui/src/tools/shell/output.rs:368), so a single verbose running shell can mean tens of MiB copied per redraw. This should be deferred to when the row is actually opened (lazy body via the InspectWork action), not precomputed for every row in the projection.

2. Shell visibility disappears entirely when the Context rail panel is selected — this PR removes shells from the composer crumb (background_indicator.rs:165-171, now filtered out unconditionally) on the premise that the work-strip rows replace it. But render/mod.rs:83 short-circuits RailPanel::Context before calling visible_rows_for_panel, so Context-panel users get neither surface. Before this PR the crumb was panel-independent, so this is a net-new regression, not a pre-existing gap. Needs either a fallback indicator on the Context panel or excluding it from the panel that drops shell visibility.

3. The row's own shell_* cancel-id contract breaks in compact side railsagent_identity (render/rows.rs:121) picks row.label only if it fits agent_identity_cap (width * 2/5); at the documented 26-column rail width that's a 10-column cap, but shell_<8 hex> labels are 14 columns, so it silently falls back to role_label = "shell". Agent-style rows never paint detail (only role/status/objective/receipt via AgentRowText, render/rows.rs:46-61), so the actual cancel id vanishes with multiple concurrent shells in Left/Right placement — directly undermining the PR's stated contract ("the shell_* id on the row is the cancel target").

Lower-priority, worth a follow-up

  • Truncated output has no marker (model.rs:2154): ShellJobSnapshot already carries stdout_len/stderr_len for the full stream size, distinct from the retained tail inspect_job returns, but shell_inspector_body never compares them, so a clipped 16 MiB log prints under a plain STDOUT:/STDERR: heading as if complete.
  • String-typed status matching (is_live_shell_entry, model.rs:2057): matches on "running" | "queued" string literals rather than a typed ShellStatus. This mirrors the pre-existing pattern already used elsewhere in this same file/background_indicator.rs (not a new convention), so it's a real fragility but not a regression this PR introduces.
  • Hard-coded English strings in the new heading/inspector copy ("Watch live output", "Running shells", etc.): true, but every other section heading in this file ("Subagents N", "Archived", "needs input", etc.) is already hard-coded English with no tr() call — this PR is consistent with existing file convention rather than a new deviation. Worth a broader localization pass, not a blocker for this PR specifically.

Good choices

  • The /jobs cancel all parse fix (jobs.rs:78) and the Task {id}Job {id} error message rename (tools/shell.rs:1745,3042) are correct and directly address the described bug (all misparsed as a literal task id).
  • Test coverage is solid: live_shells_are_first_class_work_strip_rows and durable_tasks_are_not_promoted_as_shell_rows (model.rs new tests) correctly assert the row shape, cancel-command wiring, and that non-shell task_panel entries aren't misclassified; the crumb-exclusion test in background_indicator.rs mirrors it on the other side.
  • No provider/model/route logic touched — EPIC v0.8.65 EPIC: Separate provider facts, model facts, offerings, and route resolution #2608 invariant not implicated.
  • No unwrap/expect added on new fallible paths; the try_lock() + let-else/if let chain in shell_inspector_body degrades gracefully when the manager is contended.

Fix perf + Context-panel + compact-rail issues →

@Hmbown
Hmbown merged commit 0fa9b2c into main Aug 29, 2026
27 checks passed
@Hmbown
Hmbown deleted the codex/tideline-shells-first-class-20260828 branch August 29, 2026 01:27
Hmbown pushed a commit that referenced this pull request Aug 29, 2026
Version drift wants release-note receipts for the #5698/#5699
references; the dead-code budget grows by the 64 standalone Tideline
items that lose their allows the moment frame wiring lands (the next
Tideline slice must bring the number back down).

No-Issue: gate receipts for the Tideline component family
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
yanghucai pushed a commit to yanghucai/CodeWhale that referenced this pull request Aug 29, 2026
- Spec for all three approved screens: component inventory with slot
  lineage, layout contract, color contract (ChromeInk only), motion
  table (wall-clock keyed, still frames), clickability registry,
  moment map. Impossible-in-Ratatui elements resolved explicitly.
- Topbar component (tui/topbar.rs): deterministic widget, shed order,
  per-segment hitboxes, ascii-safe, meter warn ink; golden-buffer
  harness + 12 goldens at BLOCKER_SIZES. 6/6 via dev-test.sh tui topbar.
- Not wired into frame.rs: collides with Hmbown#5698/Hmbown#5699 writers.

No-Issue: design scaffolding on the ratified tideline direction
yanghucai pushed a commit to yanghucai/CodeWhale that referenced this pull request Aug 29, 2026
Standalone whale table (spec §2/§5a/§5b): WHALE 10 (never truncates),
ASSIGNMENT remainder (truncate with …, never wrap), STATE 12 glyph+word,
then ELAPSED/RECEIPTS/LAST UPDATE 8-wide time columns shedding
LAST UPDATE → RECEIPTS+ELAPSED per the §5b ladder — at 80 columns the
ledger is exactly WHALE │ ASSIGNMENT │ STATE. Selected row ▶ marker,
state marks carry words (color never invents state), failed rows are
the only Failure ink, row rects → inspector.

Deterministic injected rows (subagent_cache projection is the landing
slice's job); ASCII-safe ▶>/●./✓Y/✗X/│|. Goldens
ledger_{w}x{h} at the four blocker sizes; the 80-wide golden proves
the shed. Hmbown#5699 shell semantics untouched; not wired into ui/frame.rs
(Hmbown#5698 gate).

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
yanghucai pushed a commit to yanghucai/CodeWhale that referenced this pull request Aug 29, 2026
Receipt stream (spec §5a, in history.rs/tideline_stream.rs): user ▎
and assistant ▏ turns, the pod-formation ├──/└── tree (same object as
the ledger below — continuity), state-marked receipt rows with words,
timestamps and ×N counts, indented conclusion block, and the legend row
teaching the marks in place. Row rects span pod trees; marks pair with
words so color never invents state.

Rail (work_surface/tideline.rs, additive per spec — Hmbown#5699 shell
semantics untouched): five groups RUNS/WHALES/POD/WORK/CONTEXT, context
meter, help/settings meta rows, « collapse with » expander; width
ladder 22/16/0 per §5b (rail sheds at 80). Group rects follow the
painted rows.

The composite renders rail │ stream; ledger and composer dock into it
in the live shell and carry their own goldens. Goldens
work_{w}x{h} at the four blocker sizes prove both components; the
80-wide golden proves the rail shed. Not wired into ui/frame.rs
(Hmbown#5698 gate).

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
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.

1 participant