Skip to content

fix(sidebar): draw a finish opened here as ready, as herdr's own client does - #1342

Open
devswha wants to merge 12 commits into
mainfrom
fix/sidebar-opened-done-ready
Open

devswha wants to merge 12 commits into
mainfrom
fix/sidebar-opened-done-ready

Conversation

@devswha

@devswha devswha commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

Change

Fixes #1279.

Replace the browser's state_change_seq acknowledgement heuristic with server-issued finish identities.

  • Each PC's bridge increments a pane's finish_id when its final client-visible status transitions into DONE, after the completion tracker, OmO, GJC and launched-agent overrides. Authoritative recovery observations feed the tracker before an asynchronous refresh, preserving the resync's newer exclusions. Snapshots and pane-status events carry the optional fields; local machine rosters and remote relays preserve them.
  • A random finish_epoch identifies the bridge incarnation. It changes on web server startup and on observed replacement of herdr's socket identity (Windows: its pid:start marker). Failed reads and reconnects alone do not prove a restart. The browser never derives finish epochs from counter rollback; the legacy ordering-only reset remains separate.
  • A finish is acknowledged only when the selected pane's content is actually presented on a visible page and no drawer, popup viewer, Settings, New session, Files, machine dialog, descendant dialog or menu covers it. Composer slash/file suggestions and the failed-suggestions popup count as coverage. Desktop popups count as covered too; a docked file/web panel does not.
  • Readiness belongs to the rendered content lifetime: terminal output must have been parsed for its current pane/terminal, without a held, missing or failed attachment; native chat must successfully read the displayed finish and load its timeline. Crashed subtrees, loading/failed chats, cached pre-finish responses, zoom redirects, pending deep links and restored-pane errors cannot acknowledge the finish.
  • Store the greatest viewed finish ID under a separate localStorage key for each (PC, epoch, pane), synchronize tabs through storage events, remove the old herdr-web-ui:seen:<pc> records, and cap retained keys at 2,000, oldest first. A tab's roster never prunes another tab's pane records.

This follows herdr's own opened-DONE behavior and the Codex app's unread completion dot, with intentionally browser-local rather than cross-device read state. There is no new setting.

Why this is safe

  • Native status overrides and recovery: a view while WORKING records nothing. An OmO, GJC or completion-tracker finish gets a new ID even when herdr's state_change_seq is unchanged. A WORKING explicitly observed during recovery also reaches the finish tracker, so a DONE overtaking the refresh cannot reuse the preceding finish's ID. Panes that became newer or ended during the resync read are excluded.
  • Restarts and stale tabs: equality is meaningful only within the same PC and server epoch. An old tab can write only its old epoch, not acknowledge a new incarnation's finish. Status events also carry the epoch; a changed epoch clears other panes' cached finish IDs until their next snapshot.
  • Covered or unavailable content: acknowledgement uses one app-level presentation gate, including dialogs and composer popups mounted by descendants. A readiness marker is committed with the actual content and removed with its error boundary. A fresh chat response is tied to the finish identity it was requested for; an older response cannot acknowledge a newer finish. Selection alone is not a view.
  • Storage races: different panes use different keys, so concurrent saves cannot overwrite each other. Same-pane events converge toward the maximum; delayed write events do not restore deleted keys. A tab closed before reconciliation, eviction or unavailable storage can restore a read dot, not hide a newer unread finish.
  • Conservative cleanup adjustment: only an epoch this tab actually left is known to be obsolete. An unfamiliar epoch on an older initial roster could belong to a newer tab, so it is ignored and bounded by the key cap rather than eagerly deleted. This avoids recreating the stale-tab cleanup bug.
  • Older remote bridges without finish fields degrade to keeping DONE dots. The fields are additive and optional; generated herdr wire types are untouched.
  • Raw status and herdr recency still determine Agents order. Counter rollback clears synthetic recency on older bridges without epochs, solely for ordering, never acknowledgement. Only matching DONE draws ready; working, blocked and question do not, and background-wait display takes precedence.
  • No browser pane.focus or other herdr call was added. A transition the server never observes cannot be counted. Socket identity evidence, not a failed connection or PID alone, governs herdr restart detection.

Merged origin/main into the existing branch first, without a rebase or force push.

Validation

  • bun run typecheck and bun run check fast: passed. Final run: 4,522 passed, 0 failed, 8 existing skips across 348 files, including the direct CSS-selector regression tests.
  • Focused isolated tests cover completion-tracker, OmO, GJC and launched-agent finish counters; server startup/restart epochs; HTTP and remote status propagation; exact matching; hidden pages; overlays; background waits; stale tabs/rosters; concurrent pane writes; same-pane event reconciliation; old-format cleanup and oldest-first retention.
  • Negative controls: temporarily removing finish increments, epoch replacement, the WORKING guard and overlay gate caused 8 relevant tests to fail. Restored behavior passed. These were deliberate regression mutations, not a claim that every new test was run unchanged against the previous implementation.
  • Review follow-up fail-before evidence: the real CompletionTracker/Finishes held-refresh reproduction returned ID 1 instead of 2 before the fix, and the legacy rollback regression ordered the old stand-in first. Removing newer exclusions separately failed the ended-pane regression (2 instead of 1). Browser cases against the pre-fix build reproduced acknowledgement under a crashed pane, loading/failed/refreshing conversation, held or absent terminal attachment, slash suggestions and failed file suggestions; all eight now pass. The duplicate no-op ordering assertion was removed and the meaningful rollback regression added; no prose or test-source meta-test was introduced.
  • Composer coverage was also isolated from content readiness: removing only the composer selector failed both direct HTML/CSS matching tests (ordinary listbox and role-less failure popup), and the strengthened browser regression failed after waiting for the current finish's conversation to arrive. This prevents a temporarily loading chat from masking a broken coverage gate.
  • bun run check run bun test --timeout 15000 server/api.contract.test.ts server/collector.contract.test.ts server/pending-input.contract.test.ts: passed, 201 tests. HTTP snapshots and WS status frames agree on finish IDs and epoch.
  • bun run build: passed.
  • bun scripts/sidebar-activity-demo-regression.ts: passed, including crashed/loading/failed/refreshing content, held and absent terminal attachments, both phone composer popup variants, phone drawer, full-screen file viewer while the selected pane finishes, zoom redirect, reload with an unread control, storage-loss control, Activity order and pins.
  • bun scripts/droplet-demo-regression.ts: passed, including server finish acknowledgement, portrait/landscape, keyboard geometry and opening a finished pane.
  • bun run check run --build bun scripts/gjc-status-browser-regression.ts: passed, all 6 native-status scenarios on an isolated test session, with current-source build provenance.
  • bun scripts/release-guards.ts --base origin/main: passed: this is not a release. REMOTE_BUNDLE_VERSION stays at 31; server/shared changes need a newly published remote bundle in the release that ships them. Until then, older bridges keep their dots rather than guessing acknowledgements.

All herdr-backed checks used isolated test sessions and disposable state. No merge or GitHub comment was made. Native Windows execution and an independent reviewer were not run in this task; Windows marker identity is covered with fixtures. The session's nested-agent limit prevented a separate reviewer, so the implementation review and screenshot inspection were performed directly.

…nt does

herdr keeps DONE until one of its own clients shows the pane, so a finish
read only in the web UI kept its dot until someone visited the pane in the
TUI. Bring back the per-browser seen record #1120 removed with Quiet opened
finishes, without the setting: the pane on screen while the page is visible
is recorded at herdr's state_change_seq, and a DONE still at that counter
draws as ready in the Agents list and the workspace roll-up. Its next
finish moves the counter and draws the dot again.

The first-use seed that started the old setting quiet is gone: a PC with no
record starts with nothing opened, so no finish is hidden that was never
opened here. Restart handling, stand-in carry and storage rules are the
ones the setting had. Nothing calls pane.focus.

Fixes #1279

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: devswha/herdr-web-ui/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f60342b6-cd4c-4050-a4a4-7ad62ac1b0f8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: devswha/herdr-web-ui/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3fee4d1c-07fd-4cb6-b781-3ea4021e0901
📥 Commits

Reviewing files that changed from the base of the PR and between c4c842b and 135cb15.

📒 Files selected for processing: 11
  • DESIGN.md
  • changelog.d/sidebar-opened-finish-ready.md
  • docs/development.md
  • scripts/droplet-demo-regression.ts
  • scripts/gjc-status-browser-regression.ts
  • scripts/sidebar-activity-demo-regression.ts
  • scripts/ui-regression.ts
  • src/components/MachineSidebar.tsx
  • src/lib/sidebarActivity.tsx
  • src/lib/sidebarOrder.test.ts
  • src/lib/sidebarOrder.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.

📝 Summary

Summary by CodeRabbit

  • New Features
    • Finished agents now appear ready in the Agents list and workspace row after you open them while the page is visible. Their next finish restores the DONE indicator.
    • This read state is saved per browser and machine, so it doesn’t affect other browsers or devices.
  • Behavior Notes
    • Reloads during the polling window may briefly show the DONE indicator again. Saved read state is cleared when a machine is removed or its session restarts.

The sidebar now displays a DONE pane as idle after it is opened while the page is visible. The browser stores seen state per machine. A later finish restores the DONE status.

Changes: Sidebar seen-finish tracking

Layer / File(s) Summary
Seen-state records and status mapping
src/lib/sidebarOrder.ts, src/lib/sidebarOrder.test.ts
Sequence tracking and per-machine seen records determine when a DONE pane displays as idle. Tests cover counter promotion, restart handling, pruning, and storage behavior.
Visible-pane tracking and status integration
src/components/MachineSidebar.tsx, src/lib/sidebarActivity.tsx
The sidebar passes the selected machine and pane to the activity hook. The hook records a selected pane as seen while the page is visible, manages stored records, and provides sequence-aware displayed statuses.
Regression checks and behavior documentation
scripts/*regression.ts, DESIGN.md, changelog.d/sidebar-opened-finish-ready.md, docs/development.md
Regression scripts check displayed statuses and persistence of opened finishes. Documentation describes the browser-specific behavior.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: phirschybar, haeminway1

Merge Risk: 🔵 Low · up to 135cb

After a herdr restart while the browser is closed, a new finish could appear ready instead of showing its DONE indicator. This narrow case warrants owner awareness but does not, on the available evidence, block merging.

Pre-merge checks: Passed 3 · Failed 2

Failed Failed checks: 2 warnings

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 63.16% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 19 functions across 8 files. (3 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check Warning The description includes the required Change and Validation sections and provides detailed test results. However, it describes a server-issued finish ID and finish epoch implementation that conflicts … Rewrite the description to match the actual changeset. Describe per-browser state_change_seq tracking, visibility-based acknowledgement, localStorage persistence, restart and cleanup handling, and the tests that validate those changes. Remo…
Passed Passed checks: 3 passed
Check name Status Explanation
Linked Issues check Passed Issue #1279 requires a per-browser seen record for a finished pane, matching state_change_seq, with READY display until a later finish. sidebarOrder.ts adds exact-sequence seen checks, counter car…
Out of Scope Changes check Passed The source changes implement issue #1279 directly. The tests, regression scripts, DESIGN.md, development documentation, and changelog document or verify the same browser-local seen behavior. No unrela…
Title check Passed The title clearly identifies the main change: a finish opened in the sidebar is displayed as ready. It is concise and related to the pull request objectives.
Full details: Linked Issues check

Explanation

Issue #1279 requires a per-browser seen record for a finished pane, matching state_change_seq, with READY display until a later finish. sidebarOrder.ts adds exact-sequence seen checks, counter carry across stand-ins, restart clearing, and browser storage. sidebarActivity.tsx records the selected pane only while the page is visible. MachineSidebar.tsx supplies the selection context to both sidebar views. The changed regression scripts and sidebarOrder.test.ts cover READY display, reloads, later finishes, ordering, stand-ins, restarts, and persistence.

Full details: Out of Scope Changes check

Explanation

The source changes implement issue #1279 directly. The tests, regression scripts, DESIGN.md, development documentation, and changelog document or verify the same browser-local seen behavior. No unrelated product behavior or unrelated source area is shown in the reviewed change summary.

Full details: Docstring Coverage

Explanation

Docstring coverage is 63.16% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 19 functions across 8 files. (3 skipped: 3 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Full details: Description check

Explanation

The description includes the required Change and Validation sections and provides detailed test results. However, it describes a server-issued finish ID and finish epoch implementation that conflicts with the summarized changes, which implement browser-local tracking using state_change_seq.

Resolution

Rewrite the description to match the actual changeset. Describe per-browser state_change_seq tracking, visibility-based acknowledgement, localStorage persistence, restart and cleanup handling, and the tests that validate those changes. Remove unsupported claims about server-issued finish IDs, finish epochs, bridge changes, and related server-side validation.

✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@devswha

devswha commented Oct 18, 2026

Copy link
Copy Markdown
Owner Author

Ran the browser checks on a MacBook Air (macOS 27.0.1, arm64, Chrome, its own isolated test herdr session; fresh clones of this branch at 135cb15 and of main at b5cdbdb):

  • scripts/ui-regression.ts: the Agents-row check this PR changed ("delayed machines poll preserves newer streamed pane status") passes. The script then stops at "returning from Agents paints the current pane". main stops at the same step on this Mac, so that failure is not from this PR.
  • scripts/sidebar-activity-demo-regression.ts: on this Mac, main and this branch both stop at the same untouched step. Line 195 waits for the workspace menu that Shift+F10 should open, and on macOS Chrome it never opens. With that key replaced by a right-click in the local clone only (and its keyboard-focus assert dropped), all 8 steps pass, including the new ones: an opened DONE reads as ready, through a reload, until the next finish, and the workspace roll-up drops it too.
  • scripts/gjc-status-browser-regression.ts: 6/6 pass.
  • scripts/droplet-demo-regression.ts: pass.

Keep finishes unread behind the phone drawer or a zoomed pane, and drop ambiguous stand-in carry across counter advances and connection gaps. Invalidate loaded records on proven restarts and merge marks from other browser tabs. Cover the review cases with real-hook and demo regressions, including persistent raw DONE on reload.
A chat scrolled back to older turns, or showing New messages, no longer
reports its content as presented, so the answer a finish lands at the end
is not acknowledged until the reader returns to it. Adds a sidebar demo
regression that fails without the gate.

This branch has not been deployed

No deployments
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.

Sidebar: opening a finished pane in the web UI should clear its DONE, as the TUI does

1 participant