Skip to content

fix(ui): replay stopped Worker history at recorded PTY grid, copy transcript selections, link bare web hosts - #16

Merged
guilhermexp merged 9 commits into
mainfrom
fix/worker-terminal-copy-links
Sep 28, 2026
Merged

guilhermexp merged 9 commits into
mainfrom
fix/worker-terminal-copy-links

Conversation

@guilhermexp

@guilhermexp guilhermexp commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Intent

The developer wanted to ship three pending fixes to main in the comet fork. The first makes stopped Worker terminal tabs opened from the chat panel replay their history at the PTY grid size where they originally ran, with frozen width and horizontal scroll, so the log no longer comes out garbled. The second ports upstream zeronsh#599 so Cmd/Ctrl+C copies a text selection in the chat transcript even when the composer isn't focused, since partial selections couldn't be copied before. The third makes a bare web host written as inline code (like www.jkdistribuicao.com.br) render as a real clickable link that opens the https URL, instead of being treated as a file path relative to the project folder. They asked to commit everything, send it to main through the repo's normal no-mistakes gate flow, and then clean up the branch.

What Changed

  • Worker terminal replay: the vendored unpeel session host now writes a journal of each PTY size change (cols × rows at an output offset). Stopped Worker tabs opened from the chat replay their history at those recorded grids, with the width frozen and horizontal scroll. Sessions recorded before the journal existed fall back to the width of their full-row ─ rules. Covered by an OpenSpec change (replay-worker-history-at-recorded-geometry) and an updated third_party/unpeel-upstream.toml.
  • Transcript and markdown: Cmd/Ctrl+C now copies a chat transcript selection even when the composer doesn't have focus (port of upstream Copy transcript selections with Cmd/Ctrl+C again zeronsh/zeron#599). A bare web host in inline code, such as www.example.com.br, now renders as a link that opens https://… instead of resolving as a path in the project.
  • Worktrees and restart recovery: in the Workers sidebar, a worktree that has sessions no longer gets its own header row. Its sessions sit at the parent project's level, each with a green branch icon. Worked-projects rows in Details also use the branch icon for worktrees. When the engine recovers after a restart, it now also settles orphaned subagents: spawn chips still marked running flip to failed, and the subagent docs get their abandoned streams stamped aborted. New tests in restart_resume.rs cover this.

🤖 Generated with Claude Code

Risk Assessment

🚨 High: The code fixes are sound (marks are re-read on each catch-up poll, and the web-host rule is narrowed as the user chose), but the fix round committed unrelated tooling config that would ship to main with hook paths pointing at a worktree that is about to be deleted, plus an unpinned npx -y MCP server.

Testing

I ran the targeted tests for each fix and all passed: the zeron-ui worker-terminal journal replay tests, the transcript Cmd/Ctrl+C gpui VisualTestContext tests (including the guard that a terminal keeps Ctrl+C), the inline-code web-host detection tests (including rejecting README.org, COMMAND.COM and example.com), and the workers-unpeel legacy-geometry test. The unpeel-core geometry journal test could not run because that crate is outside the workspace. No screenshot or video was captured. The app is a native gpui window with no headless render harness, and the only capture available was the user's live desktop, so the live checks (seeing the terminal render, pressing Cmd+C in the transcript, clicking the link) could not be driven without taking over the user's session.

  • Live validation: ⚠️ inconclusive - 0 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Opening a stopped Worker's terminal tab from the chat panel replays its history at the recorded PTY grid (frozen width, horizontal scroll), not garbled ⏸️ untested no Needs the native gpui app (scripts/dev-demo.sh) running on the user's active macOS desktop, plus a real stopped Worker session with a recorded geometry journal. To validate, run scripts/dev-demo.sh on…
Selecting part of the chat transcript and pressing Cmd/Ctrl+C with the composer unfocused copies the selection to the clipboard ⏸️ untested no Needs synthetic mouse drag and keystrokes in the native app window on the user's live desktop, which would take over their session. Needs an unattended macOS display with Accessibility permission for…
Adversarial: Ctrl+C inside a terminal panel still goes to the terminal when a transcript selection exists ⏸️ untested no Same blocker: needs keystroke automation in the native gpui window on the user's active desktop.
Inline code www.jkdistribuicao.com.br renders as a link that opens https://www.jkdistribuicao.com.br ⏸️ untested no Needs a transcript message rendered in the native app plus a click. There is no headless gpui render harness, and the only available screen capture is the user's live desktop.
Adversarial: inline-code filenames such as README.org, COMMAND.COM and brain.md still open as project files, not web links ⏸️ untested no Same blocker as the link scenario: this needs the rendered native app.
Evidence: Targeted zeron-ui test run
test markdown::inline_chips::tests::bare_hosts_in_code_open_as_web_links ... ok
test workers::terminal::tests::journal_replay_switches_grid_at_the_mark_across_chunk_boundaries ... ok
test workers::terminal::tests::journal_replay_decodes_history_at_the_grid_it_was_drawn_for ... ok
test shell::navigation_tests::copy_shortcut_prefers_the_focused_composer_selection ... ok
test shell::navigation_tests::ctrl_c_in_a_terminal_is_not_taken_by_a_transcript_selection ... ok
test shell::navigation_tests::copy_shortcut_copies_transcript_selection_after_clicking_the_transcript ... ok
test shell::navigation_tests::copy_shortcut_copies_side_chat_selection_from_the_right_pane ... ok
test result: ok. 11 passed; 0 failed
- Outcome: ⚠️ 2 warnings across 1 run (1m58s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

⚠️ **Rebase** - 1 warning
  • ⚠️ crates/engine/AGENTS.md - branch carries 2 commit(s) that exist on your local main branch but were never pushed to origin/main; these may be unintended bundled work (proposed PR changes 24 file(s)):
  • c47aa41 feat(ui): worked projects mark worktrees with the branch icon
  • bd4d40a fix: subagent docs settle with their chips on restart; flat worktree sessions

Confirm these commits belong in this PR before approving, or manually separate the intended work onto origin/main before gating.

⚠️ **Review** - 1 error
  • ⚠️ crates/ui/src/workers/terminal.rs:772 - During catch-up the PTY geometry marks are read only once (load_marks fires only while geometry_marks.is_none(), then state.geometry_marks = Some(marks) at line 857 caches them for the rest of catch-up). The proposal says the same replay also applies to a live Worker's history. On a live session, the first on_grid_metrics (line ~984) sends client.resize(panel cols/rows), and the host writes a new mark at offset X (session_host.rs:7570). If that mark lands after the marks read in the first poll but before backlog_end (the first viewport's output_offset), then feed_at_recorded_geometry decodes every byte in [X, backlog_end) at the old grid. The same happens to a chunk that crosses backlog_end with bytes past X. Those bytes are the TUI's SIGWINCH redraw at the new size, so the screen comes out garbled — the very defect this change fixes — until the TUI does another full redraw. Concrete sequence: a live Claude Code Worker whose PTY is 120x48 is opened in a 100x40 panel. The poll reads marks [..,120x48], the resize writes 100x40@X, and the output read returns the backlog plus the redraw, all fed at 120x48. Fix: re-read the journal on every catch-up poll instead of caching it once. read_pty_geometry is a small local file read, already on the background executor; for example, set load_marks whenever is_catching_up() and replace geometry_marks each time.
  • ⚠️ crates/ui/src/markdown/inline_chips.rs:64 - web_host_url counts any label after the first that equals com/net/org/gov/edu as proof of a web host, and file_target now returns None whenever it matches (line 74). So a bare filename in inline code, such as notes.org, README.org or todo.org (Org-mode files), and also COMMAND.COM, stops opening as a project file. It renders blue with a favicon and opens https://README.org instead. Paths with a directory (docs/notes.org) are unaffected, because only the text before the first / is tested. The docstring already rejects bare TLDs because .md, .sh and .rs collide with file extensions, but .org has the same collision. Stated intent only needs hosts like www.jkdistribuicao.com.br and site.com/loja. A narrower rule would cover it: a www. prefix, a generic label that is not the last label (x.com.br), or a generic last label only when followed by /path. This changes which strings link, so it needs your decision.

🔧 Fix applied.
1 error still open:

  • 🚨 .cursor/hooks.json:7 - Fix round 1 (commit da2c632) committed editor and agent tooling config that has nothing to do with either finding it was fixing. It looks like the graft indexer rewrote these files inside the pipeline worktree. (1) .cursor/hooks.json:7,12,17: the hook paths changed from the user's real checkout (~/Documents/Projetos/SelfHosting/comet/...) to this temporary no-mistakes worktree (~/.no-mistakes/worktrees/6fec5bb7ba85/01M3MYAQS768G5B47VRFZQJD6V/...). Once this merges to main and the worktree is cleaned up (which the user asked for), every Cursor postToolUse, afterMCPExecution and sessionEnd hook will point at a file that no longer exists. (2) .mcp.json:4-7 and .cursor/mcp.json:4-7: the graft MCP server switched from the local graft binary to npx -y @nanonets/graft, which fetches and runs whatever npm version is latest, unpinned, on every session. (3) New opencode.json with the same npx setup, new .claude/skills/graft/SKILL.md (156 lines), and statusLine/subagentStatusLine entries in .claude/settings.json:69-76. The user wanted three UI fixes; nothing asked for these. Fix: restore these files to their 45197b0 state (git checkout 45197b07 -- .cursor/hooks.json .cursor/mcp.json .mcp.json .claude/settings.json, then git rm opencode.json .claude/skills/graft/SKILL.md).

🔧 Fix applied.
1 error still open:

  • 🚨 .cursor/hooks.json:7 - Fix round 1 (commit da2c632) committed editor and agent tooling config that has nothing to do with either finding it was fixing. It looks like the graft indexer rewrote these files inside the pipeline worktree. (1) .cursor/hooks.json:7,12,17: the hook paths changed from the user's real checkout (~/Documents/Projetos/SelfHosting/comet/...) to this temporary no-mistakes worktree (~/.no-mistakes/worktrees/6fec5bb7ba85/01M3MYAQS768G5B47VRFZQJD6V/...). Once this merges to main and the worktree is cleaned up (which the user asked for), every Cursor postToolUse, afterMCPExecution and sessionEnd hook will point at a file that no longer exists. (2) .mcp.json:4-7 and .cursor/mcp.json:4-7: the graft MCP server switched from the local graft binary to npx -y @nanonets/graft, which fetches and runs whatever npm version is latest, unpinned, on every session. (3) New opencode.json with the same npx setup, new .claude/skills/graft/SKILL.md (156 lines), and statusLine/subagentStatusLine entries in .claude/settings.json:69-76. The user wanted three UI fixes; nothing asked for these. Fix: restore these files to their 45197b0 state (git checkout 45197b07 -- .cursor/hooks.json .cursor/mcp.json .mcp.json .claude/settings.json, then git rm opencode.json .claude/skills/graft/SKILL.md).
⚠️ **Test** - 2 warnings
  • ⚠️ None of the three UI fixes (Worker terminal replay, transcript Cmd/Ctrl+C, inline-code web links) was checked live. comet is a native gpui macOS app with no headless render harness, and the only live path, scripts/dev-demo.sh, opens a window on the user's active desktop. Driving it would take synthetic mouse and keyboard input there, which needs macOS Accessibility permission for the automation process and an unattended display. The targeted tests all pass, but the repo's own CLAUDE.md says a green suite is not proof the UI is correct. Please run scripts/dev-demo.sh and check each of the three behaviors by eye before merging.
  • ⚠️ live validation verdict: inconclusive (0 of 5 scenarios were driven live against the product); untested: Opening a stopped Worker's terminal tab from the chat panel replays its history at the recorded PTY grid (frozen width, horizontal scroll), not garbled, Selecting part of the chat transcript and pressing Cmd/Ctrl+C with the composer unfocused copies the selection to the clipboard, Adversarial: Ctrl+C inside a terminal panel still goes to the terminal when a transcript selection exists, Inline code www.jkdistribuicao.com.br renders as a link that opens https://www.jkdistribuicao.com.br, Adversarial: inline-code filenames such as README.org, COMMAND.COM and brain.md still open as project files, not web links
  • Live validation: ⚠️ inconclusive - 0 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Opening a stopped Worker's terminal tab from the chat panel replays its history at the recorded PTY grid (frozen width, horizontal scroll), not garbled ⏸️ untested no Needs the native gpui app (scripts/dev-demo.sh) running on the user's active macOS desktop, plus a real stopped Worker session with a recorded geometry journal. To validate, run scripts/dev-demo.sh on…
Selecting part of the chat transcript and pressing Cmd/Ctrl+C with the composer unfocused copies the selection to the clipboard ⏸️ untested no Needs synthetic mouse drag and keystrokes in the native app window on the user's live desktop, which would take over their session. Needs an unattended macOS display with Accessibility permission for…
Adversarial: Ctrl+C inside a terminal panel still goes to the terminal when a transcript selection exists ⏸️ untested no Same blocker: needs keystroke automation in the native gpui window on the user's active desktop.
Inline code www.jkdistribuicao.com.br renders as a link that opens https://www.jkdistribuicao.com.br ⏸️ untested no Needs a transcript message rendered in the native app plus a click. There is no headless gpui render harness, and the only available screen capture is the user's live desktop.
Adversarial: inline-code filenames such as README.org, COMMAND.COM and brain.md still open as project files, not web links ⏸️ untested no Same blocker as the link scenario: this needs the rendered native app.
  • cargo test -p zeron-ui --lib -- journal_replay bare_hosts_in_code copy_shortcut ctrl_c_in_a_terminal inline_chips (11 passed)
  • cargo test -p zeron-workers-unpeel -- the_row_wide_rule (legacy geometry estimate, passed)
  • cargo test -p unpeel-core --lib -- pty_geometry_journal (could not run: the crate is not a workspace member, so its dev-dependencies are unavailable)
  • screencapture -x probe: screen capture works, but it only captures the user's active desktop; probe deleted because it held private content
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

guilhermexp and others added 8 commits September 28, 2026 12:50
…sessions

Engine: a dead run never finished its subagent sinks, so after a restart
the subagent doc kept a `streaming` last entry (the subagent tab spun
forever) while the parent chip was already settled `failed` - the Details
widget and the tab disagreed. Boot recovery now settles both sides
together: chips still `running` flip to `failed`, and every chip settled
by the recovery has its subagent doc's abandoned streams stamped
`aborted`. A subagent that genuinely resumes reopens both as `running`.

UI: a Workers worktree with sessions no longer draws its own header row;
its sessions sit at the parent project's level, each led by a green
branch mark (tooltip: the branch). Empty worktrees keep their header.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122ykZEFtNWWut4VkghZ1DQ
The Details "Projects worked" rows wear the green branch mark for
Workers worktree projects (same rule as the Workers sidebar/titlebar),
the folder otherwise.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122ykZEFtNWWut4VkghZ1DQ
Differential TUIs (Claude Code, pi, codex) redraw by moving up the rows
their last frame took and rewriting only changed cells. Decoding
output.bin at the panel's grid, or at 300 columns for a stopped Worker,
lands those moves on the wrong rows: new words fill in over stale ones.

The vendored session host now journals every PTY grid change with its
output offset. The Workers terminal decodes history at the recorded grid,
estimates it for sessions recorded before the journal (row-wide rule
width, 24 rows), and keeps a stopped Worker at its recorded width with
horizontal scroll instead of reflowing it into a narrower panel. The chat
side pane now tells the terminal when its Worker has stopped.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122ykZEFtNWWut4VkghZ1DQ
…ronsh#599)

Clicking the transcript blurs the composer, whose Copy action was the
only handler, so the shortcut copied nothing. Ports upstream e13b18d
unchanged: the main chat and right pane copy the markdown selection from
a bubble-phase key-down listener.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122ykZEFtNWWut4VkghZ1DQ
`www.site.com.br` in backticks matched the filename heuristic (`.br` as
an extension) and opened a missing file under the project root. Hosts
with a `www.` prefix or a generic label (com/net/org/gov/edu) now render
as web links with their favicon.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122ykZEFtNWWut4VkghZ1DQ
@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 635526dc-7374-444b-9632-dcec2cf919ce


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

[Medium risk] Fixes Worker terminal replay and adds transcript copy, web link support.

Not safe to merge until populated worktrees retain their project actions and launch controls. The transcript-copy concern is non-blocking.

Findings

  1. P1 Populated worktrees lose project actions ▶
  2. P2 Terminal copy uses stale text ▶

Summary

The PR adds Worker terminal geometry replay, transcript copying, inline web links, and a flatter Workers sidebar. A populated worktree still loses its project actions and launch controls.

Reviews (2) · Last reviewed commit: "no-mistakes(ci): I fixed the three CI fa..."

Comment thread third_party/unpeel/crates/unpeel-core/src/session_host.rs
Comment on lines +1390 to +1397
if flatten {
return div()
.w_full()
.flex()
.flex_col()
.children(rows)
.children(reveal_control)
.into_any_element();

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 Populated worktrees lose project actions

When a worktree has a visible session, this return removes its project row before the project menu and launch controls are built. The replacement rows offer only session actions. Users cannot reach that worktree’s checkout actions or start another Worker through its sidebar controls while sessions are present.

Artifacts

Source-extracted worktree control probe

  • The authored command extracts the changed Rust return, compiles a focused before-and-after runtime harness, and records each run.

Parent-revision runtime harness

  • The generated Rust harness exercises the parent revision’s path past the project-row boundary with zero and one worktree session.

Parent revision: project row remains with a session

  • The compiled parent-revision probe ran successfully and showed a project row alongside the session row.

Current-code runtime harness

  • The generated Rust harness includes the early-return expression extracted from the changed source.

Current code: populated worktree has only a session row

  • The compiled current-code probe ran successfully and showed that the project row is absent when a worktree has one session.

View artifacts

T-Rex Ran code and verified through T-Rex

Comment thread crates/ui/src/shell.rs
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
this.capture_navigation_focus(true, false, window, cx);
}))
.on_key_down(|event, _, cx| navigation_focus::copy_transcript_selection(event, cx))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Terminal copy uses stale text

A transcript selection can remain after focus moves to the right pane. When the focused terminal has no selection and lets Cmd+C pass through, this pane handler copies the old transcript selection instead. This non-blocking concern can unexpectedly replace the clipboard with unrelated text.

Artifacts

Source-extracted transcript-copy check

  • The authored script extracts the production copy handler and generates a Rust check using the production selection module.

Executed Rust source for transcript copy

  • The generated source checks clipboard behavior with a settled transcript selection and a modeled focused terminal without a selection.

Clipboard before the pane copy handler

  • The before run left the clipboard unchanged and allowed the copy keystroke to propagate.

Clipboard after the pane copy handler

  • The changed handler copied the earlier transcript selection and stopped propagation.

View artifacts

T-Rex Ran code and verified through T-Rex

@greptile-apps

greptile-apps Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Comments Outside Diff

These findings could not be posted inline.

  • P1 Live redraw uses stale marks crates/ui/src/workers/terminal.rs:820 ▶

    If a live Worker resizes between the geometry read and the output read, the output can contain a redraw for the new grid while apply_refresh receives only the earlier marks. It decodes those bytes at the old width, leaving stale text or erasing content on the opening screen. A later poll cannot repair bytes already decoded.

…rates/ui/src/markdown/inline_chips.rs::render`. I couldn't run those three tests here, because they only compile on Linux and Windows. The full `zeron-ui` suite passes on this Mac (2011 passed, 0 failed), and `cargo fmt --check` is clean. **Cause:** since the Codex-links commit, a paragraph containing a web link renders through `inline_chips::render`. That function gave every segment a derived key. So the first segment's text was registered as `link-fixture:0-inline-0:<nested ix>` instead of `link-fixture:0`, and the tests' lookup of `link-fixture:0` panicked with "painted text". The rule that was broken: a paragraph's leading text must keep the paragraph's own key, `{row_key}:{ix}`, whether or not it has inline chips. That key is what selection lookup and ownership use. **Fix:** only the non-chip segment whose range starts at 0 keeps the paragraph's identity: - It uses `part_ix = ix` and keeps `opts.row_key`, so it paints as `{row_key}:{ix}`. - `selection_group` is still set on it. - All later segments and every chip, including a chip at offset 0, keep their derived keys (`{group}-inline-{start}` and `nested_ix(ix, usize::MAX, start)`). Each element index stays unique, so the RowVeil concern in the existing comment still holds. The tests are unchanged and the favicon grouping is untouched. Chips only take their key from this one computation, so there was no other place to fix. The fix was checked by tracing the fixture: `[Docs](https://example.com/docs)` is a single non-chip web-link segment at 0, so it now paints as `link-fixture:0`
@guilhermexp
guilhermexp merged commit 8306f01 into main Sep 28, 2026
10 checks passed
@guilhermexp
guilhermexp deleted the fix/worker-terminal-copy-links branch September 28, 2026 22:58
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