Repository navigation
fix(chat): prevent stale conversation frames when switching sessions - #422
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review. 📝 SummarySummary by CodeRabbit
WalkthroughPaneTerminal now remounts ChatView when the pane changes. Product-mode browser checks exercise pane and machine switches, delayed success and failure responses, and unavailable conversations. ChangesPane chat history isolation
Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix · Severity of issue fixed: Low Merge Risk: ⚪ Minimal · up to No actionable issue is established for this incremental change; normal checks and the separately noted Safari validation remain appropriate. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to Pane switches now discard the previous chat view while preserving the terminal. No new production access path or weakened access control was identified. Immediate isolation when a conversation is replaced within the same pane remains unverified. Retained concerns Security review detailsSecurity Blast Radius
Security Findings and Attack Paths
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 5 files. (1 skipped: 1 unsupported.)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Maintainer follow-up to the review of devswha#422. - ChatView is keyed by the pane, not by the pane and the session herdr reports. That report is a hint: the shared Codex daemon reports every TUI's thread to the pane that started it (codex-binding.contract.test.ts), so the key changed while the conversation did not, and a remount drops the scroll position, the earlier pages already loaded and open tool output. The stale frame comes from a pane switch, which the pane key covers; another conversation in the same pane is still taken through history_id. - The browser regression answers each conversation request as it arrives unless its pane is held, so a poll left by an abandoned mount cannot be answered in place of the active one. It reads the commit log once the expected chat is up, and waits for a late answer to end in the page before asserting that nothing changed. - The earlier hook and ChatView checks run under StrictMode again; only the real PaneTerminal is mounted outside it.
# Conflicts: # CHANGELOG.md
# Conflicts: # CHANGELOG.md
|
Merged, thank you. What changed on the branch before the merge (
Checked here: the script passes with the key and fails without it ("stale … in intermediate commits"), and it passed in the CI browser lane. The one red CI run on the last head was a submit deadline test in |
…inal and layout clips (#435) ## What Six feature clips under the README's intro, re-shot on the current UI. Each clip is one live take of the web UI on desktop and phone, in real time with no cuts. herdr's own TUI appears only in the hero (#425). | Cell | Clip | Length | Size | Upload name | |---|---|---|---|---| | 1 | **Switch to the live terminal** (new): a click on Terminal turns the chat into the pane's real terminal, showing Claude Code's own TUI with its edit. On the phone, in the tests tab's terminal, ↑ on the key bar brings back `bun test` and the input line's Enter button runs it again | 11.4 s | 2.9 MB | `switch-to-the-live-terminal.mp4` | | 2 | **Your herdr layout, in the browser** (new): herdr's workspaces, tabs and split panes, each with its agent's state. A click on a tab shows its pane; the tab's menu lists the split's panes and switches between them; on the phone the drawer shows the same workspaces and opens one | 12.6 s | 2.5 MB | `your-herdr-layout-in-the-browser.mp4` | | 3 | **Know when an agent needs you** (new): the phone sits on another workspace; when Claude asks, the app's in-app alert drops in, a tap opens the question card, and the desktop lists the pane under "Needs you" | 11.3 s | 2.4 MB | `know-when-an-agent-needs-you.mp4` | | 4 | **Send a screenshot from your phone**: the paperclip uploads a receipt screenshot into the pane's folder and adds its `@path`, and the message lands on the desktop | 11.3 s | 0.9 MB | `send-a-screenshot-from-your-phone.mp4` | | 5 | **Open what the agent made**: a click on a path in the browser's terminal (#266) opens Claude's SVG chart in the file viewer, then a tap on the same path in the phone's chat opens it too | 10.9 s | 1.5 MB | `open-what-the-agent-made.mp4` | | 6 | **Branch off a second agent** (new): on the phone, ⋯ → New worktree opens with a branch already filled in; Claude Code is picked and the new workspace starts nested under the first | 13.9 s | 1.7 MB | `branch-off-a-second-agent.mp4` | Changes: - **WebP loops** in `docs/media/readme/`, 800×450 and taken from each clip: - `terminal.webp`, `layout.webp`, `alerts.webp` and `worktree.webp` are new; `attach.webp` and `open.webp` are replaced. - `browse.webp` and `ssh.webp` are deleted. - The six together are 2.6 MB; the old four were 4.1 MB. - **All four READMEs:** - the grid, now 3×2 with terminal | layout as its first row: links, alt text, titles and captions; - the footnote under the grid; - the "Know when you're needed" bullet, which now mentions the in-app alert; - a sentence on tabs and worktrees in "Keep your workflow". - **`site/index.html`:** the same six cells and the footnote. Dropped: - **Browse a pane's files:** the file viewer is already in "Open what the agent made". - **Every PC in one sidebar:** herdr itself already shows several machines in one window, so it was the weakest contrast. The bullets and `docs/remote-pcs.md` still cover remote PCs. This PR touches docs and the site only. No CHANGELOG, no version, nothing in `src/` or `server/`. ## How they were made **All six** - **Recorded live:** alerts, attach, open and worktree on 2026-10-04; terminal and layout on 2026-10-05. One take per clip. - The desk is 1280×800 and the phone 390×844 with touch, both at 2x, recorded at the same time on one clock. - The app is herdr web ui in headless Chromium, `dist/` rebuilt: - v0.3.49 (32a3c85) for alerts, attach, open and worktree; - main at 7c5fe4e, four commits after v0.3.49 (#422, #423, #424, #426), for terminal and layout. - No app change was made for any clip. - The phone is that page inside a drawn frame, with no on-screen keyboard. - **Session:** the dedicated `herdr-web-ui-demo` session on the installed herdr 0.9.0, never the default session. - **Time:** every clip plays at 1×, with no cuts and nothing reordered. Times use the machine's own time zone, and "Worked for" is the app's own count. - **Composited:** - the wallpaper, the window frame with its drawn `localhost:7317` address, and the phone frame; - the camera moves; - the macOS arrow, drawn at the recorded pointer; - the touch disc, drawn at each recorded tap (from 0.12 s before it). - There is no push banner and no interpolated frame. - **Staged:** - the session and `~/code/checkout-api`, written by `scripts/readme-media/stage.ts`; - the `web-dashboard`, `infra` and `release` rows, whose agent states were reported with no agent running in them; - terminal and layout: the extra tabs (and layout's split), made through herdr's API. New tabs get a clean shell from the film's herdr config (no profile, prompt `checkout-api $`); - a clean Claude Code: project and local settings only, no MCP servers, and the account's publishing tools denied. - **Real:** Claude Code 2.1.289 on the maintainer's account. Turns per clip: - terminal 3, off camera; - layout 1, off camera; - open 2, off camera; - attach 1; - alerts 1, plus one unrecorded probe; - worktree 0. - **Scrubbed:** the account's name, email and organization, the machine's names, the login and the home path. They show as `~` and "workstation". - **OCR-checked:** every frame of each render, and the source frames in its window, were checked with OCR against those strings. Nothing was found. - **About herdr:** herdr does much of this itself: its agents list, toasts, `prefix+o`, its own worktree form, and a phone layout over SSH. This app is another surface on the same session, attached at the same time. It does not replace herdr. **Switch to the live terminal** (take K1) - **Turns:** three real turns ran off camera, sent through herdr. Measured back from the first frame: - "What do the tests in src/server.test.ts cover? One line." sent 28.6 s before. - "List the files in src, one per line." sent 21.1 s before. It was there only to scroll Claude Code's start header off the desk's screen. - The task, sent 14.4 s before, done 8.6 s before: "Add a test named "unknown refund is 404" to src/server.test.ts: refunding a payment id that doesn't exist returns 404. Don't run the tests; I'll run them." - **Claude's setup:** acceptEdits mode, Bash denied, bundled skills off. It changed only `src/server.test.ts` (+4 lines). - **Sizes:** - The desk opened the pane in Terminal before the turns, so its grid set the pane's size (104×35). It switched to Chat off camera. - The switch to Terminal on camera caused no pty resize. Sizes were checked every 250 ms and against the app's `pane-geometry` messages. - **The tests tab:** `clear` and `bun test` (4 pass) were sent through herdr about 36 s before. The phone's ↑ brings back that real command from the shell's history. - **The taps:** both phone taps are real touches sent by the script. A separate `bun test` afterwards gave 5 pass, 0 fail. - **Pointer side effect:** on its way to Terminal, the arrow passed over two chat turns, so their time and copy buttons faded in as on any hover. - **Zoom:** the desk is at z 1.45, under the film's usual 1.6, so the toggle and the edit both stay in frame. - **As it came:** Claude Code's grey "commit this" suggestion, and its "Baked for 3s" and "Sautéed for 5s" lines. **Your herdr layout, in the browser** (take L1) - **Staged through herdr's API:** - the tabs: "payments" renamed, "dev" created; - dev split to the right (`pane.split`), its panes labelled "tests" and "git" (`pane.rename`, as prefix+shift+p does); - release's repo and its history; - the side rows' states (web-dashboard Codex working, infra Claude idle), reported with no agent running in them. Those rows are never opened. - **Commands sent through herdr, 21.3–13.1 s before frame 0:** - tests: `clear`, `bun test`; - git: `clear`, `git --no-pager log --oneline --decorate --stat`, with git told to ignore any user config; - release: `clear`, `git --no-pager log --oneline --graph --decorate -n 12`. - **One real turn, off camera:** "What does this repo do? Answer in one line.", sent 8.3 s before frame 0. Claude ran in default mode with Bash denied, and the repo was unchanged afterwards. - **Pane sizes:** - Before recording, the desk opened tests and git in Terminal once, so they kept its size. - Every pane held its size through the take, checked every 250 ms; nothing was resized on camera. - **Input:** - the desk's 3 clicks and the phone's 2 taps were real input events sent by the script; - nothing is typed; - the arrow never crossed a terminal, so no mouse reports reached the shells. - **The sidebar title:** a workspace's sidebar row takes the name of the pane on screen, so the checkout-api row reads "tests" or "git" while those panes are open. - **As it came:** - each pane switch shows an empty grid for about 0.12 s; - the phone briefly shows "Loading conversation…". **Know when an agent needs you** (take N1) - **Task:** sent through herdr 0.34 s before recording, and shown in the bubble: "Add per-IP rate limiting to POST /payments. Ask me one question first: which limit to use." Recording began while Claude was working. - **Phone setup:** the phone sits on `release`'s terminal. That repo's history is staged, and its `git log --graph` was sent off camera 0.90 s before recording. - **Alert:** the island is the app's own in-app alert. It drops in 3.40 s into the clip. The tap on it is real, 1.39 s later, and the phone's card painted 0.10 s after the tap. - **Recommended tag:** Claude set "(Recommended)" itself. - **After the clip:** - The question was answered off camera through the app's API, 0.73 s after the last frame, with "10 / minute". - A later Bash permission was approved once, also off camera. - Claude finished 46 s after the task, and `bun test` passed. - **Pointer:** the desk pointer was parked off the page, so no arrow is drawn. **Send a screenshot from your phone** (take U1) - **Screenshot:** rendered off camera from the staged repo's own receipt page (1170×1170). The stage planted the $NaN bug. - **Picker:** the photo picker is not shown. The take answered the page's file chooser 0.055 s after the tap, and the thumbnail and path appeared 0.109 s after it. - **Typing:** "Fix this, with a test." was typed into the page, with no on-screen keyboard. - **Before recording:** Claude was started 8.5 s before. A local command (`/color default`, no model call) created its session, so both chats show the real empty state. - **Mode:** acceptEdits. - **On camera:** the message reached the desktop 0.74 s after the Send tap, and Claude's first row came 4.0 s later. - **After the clip:** - Two Bash permission prompts were approved off camera. - The fix plus a new test passed `bun test`, and the receipt then read $12.50. - Claude's transcript shows the image arrived as an attachment. - **The `~` path:** it is a stand-in from the page scrub. The composer inserted, and Claude received, `@~/code/…`; the real app inserts the absolute path. **Open what the agent made** (take O1) - **Before recording:** both turns ran off camera and were sent through herdr. "What's in bench/latency.csv?" also scrolled Claude Code's header away. Then: "Chart p95 per release as an SVG at bench/p95.svg that I can read on my phone. Keep the reply short." - **Setup:** Bash was denied, the bundled skills were off, the mode was acceptEdits, and the terminal font size was 15. - **The chart:** Claude's own SVG, unedited: 49 lines, and the only file it changed. - **Click (#266):** the click on the path in the browser's terminal is real. herdr's attach stream reported mouse moves to the pane, but no click, key or text reached Claude, and its input row was unchanged. - **Opening times:** the desk viewer's chart painted 0.24 s after the click, and the phone's 0.18 s after the tap. - **As it came:** Claude's grey suggestion "commit the chart". **Branch off a second agent** (take W1) - **Before recording:** Claude was started in checkout-api 7.2 s before. A local command (no model call) created its session. - **herdr film config:** it sets the worktree folder to `~/code/.worktrees`. New panes get a clean shell, and a `claude` wrapper that adds `--setting-sources project,local --strict-mcp-config`. - **Settings:** the checkout carries the film's Claude settings, committed in the staged repo. - **Trust:** `~/code/.worktrees` was trusted in Claude Code in advance. - **The dialog:** the branch `worktree/clear-forest-3580` is the app's own random suggestion, highlighted by its own focus. - **No turn:** the second Claude got no turn. It shows READY 4.2 s after Create. - **The phone leaves:** it leaves the frame 0.09 s after its dialog closes. Off frame, it then opened the new workspace. - **Cleanup:** afterwards the worktree, its branch folder and the staged session were all removed. **herdr's TUI and where it lives in the web UI** | herdr's TUI | The web UI | |---|---| | spaces list, prefix+w | one sidebar row per workspace | | agents list and state dots | row badges and tab dots; "Needs you" for blocked panes | | prefix+o, prefix+g | "Needs you", the in-app alert, the command palette | | prefix+c | the strip's +, or New tab | | prefix+shift+t | double-click a tab, or F2 | | prefix+x / prefix+shift+x | the tab's ×, or Delete | | prefix+1..9 / n / p | click a tab | | prefix+shift+g | ⋯ → New worktree | | prefix+shift+n | the PC header's + (New workspace) | 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: e2e <e2e@example.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Bumps package.json and herdr-plugin.toml to 0.3.50 and moves the Unreleased notes under [0.3.50] - 2026-10-06, with the compare link. Adds the PR link and credit the #471 entry was missing; every other entry already carries them. Raises the remote PC bundle to 17: #470 changes the prompt answer route on the bridge, so a remote PC on bundle 16 keeps the old behaviour until its bridge is updated. Push the `remote-v17` tag and let the bundle workflow publish before running release.yml. Known limits carried into the notes: Japanese and Chinese text uses the device's fonts beside Pretendard; #469 stays open for what #470 does not cover (answers after the first committing key, identical twin prompts, screens no reader knows). Carries #422 #423 #424 #425 #426 #435 #437 #439 #446 #447 #448 #452 #454 #455 #456 #457 #458 #459 #460 #461 #462 #463 #465 #466 #467 #468 #471 #473 #474 #475 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Upstream brings the Codex chat paneRead fix (devswha#393: ANSI snapshot instead of TUI-scrolling text reads, 1.1s -> ~0ms per Codex chat poll), queued follow-ups, paged settings, More menu, agents sidebar, new palettes and 160+ other commits. Fork features kept: Devin chat, attention inbox (NeedsInput restored, its UI check re-wired), dashboard sidebar, usage top panel/placement and fork defaults, Claude/Codex marks, Dynamic Island droplet + alert history bell, instant send blended with upstream's pending messages, work-progress line, prompt retry on the same card, omo intent line, native push routing. Session switching uses upstream devswha#422's single mechanism. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
Switching session tabs can briefly render the previous pane's chat/IRC headers or unavailable state before the new pane's conversation loads. A focused browser regression using the real PaneTerminal reproduces an A → B commit containing A's chat against upstream main 6992d0a.
Change
Verification
bun scripts/chat-history-browser-qa.ts: passed.bun run typecheck: passed.git diff --check: passed.Full suites were also attempted and are not reported as passing: the full unit suite hits the unchanged voice filename assertion (
audio.webmexpected,clip.webmobserved), and the broader UI suite times out waiting for a notification card. These unrelated failures were left unchanged to keep this PR scoped.The browser fixture uses the actual PaneTerminal without React StrictMode, due to xterm's temporary-unmount error in that fixture. No production agent sessions or private conversation content are used. Physical iPhone Safari validation is still separate from this Chromium regression.