Skip to content

fix(web): mark new background completions unread - #10077

Closed
juliusmarminge wants to merge 7 commits into
mainfrom
fix/audit-background-completions-unread
Closed

juliusmarminge wants to merge 7 commits into
mainfrom
fix/audit-background-completions-unread

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Sep 5, 2026 •

Copy link
Copy Markdown
Member

Closes #3131.

New background threads can finish without showing an unread completion indicator. The current selector treats a missing visit timestamp as read, even when the client received the completion without opening the thread.

This observes each environment's live shell snapshots. The first live snapshot establishes a baseline without creating unread markers for previously untracked completions. Later new or changed completions receive the existing unread timestamp only when no visit entry exists. Existing unread markers, real visits and manual read/unread state remain unchanged. Updates use one lazy visit-map copy and one store notification per snapshot, with the existing persistence debounce.

Closes #3131. This is a credited replacement for Wout Stiens's #3235, which the author closed and maintainers cannot edit. It adapts that proposal to the current live/cached shell lifecycle rather than restoring the removed visit-seeding code.

Human decision required

Keep this unmerged until the bootstrap/read-state policy is approved. The first live snapshot does not retroactively mark previously untracked completions unread; existing unread markers survive reload. Ordinary disconnect/reconnect retains the observer's baseline, so later completion changes can still become unread, including in a full resynchronization snapshot. Recreating the observer through page reload, environment removal/re-addition or disabled-to-enabled auth state establishes a fresh historical baseline. Observation remains mounted across eligible hosted connect routes, but stays disabled for unauthenticated and hosted-pairing states. This PR does not change the default unread selector, persisted schema, mobile status policy, providers, or wire protocol, and does not close the issue automatically.

Verification

  • 158 focused tests pass across the observer, UiState, sidebar selectors and shell state. They cover running-to-completed, completed discovery, initial history, cached-to-live synchronization, reconnect, environment removal/re-addition, independent environments, hosted remote-only catalogs, real/manual visits, reload and disposal. Actual shared shell queues and applied-state receipts cover both modern synchronization markers and older-server fallback.
  • The review follow-up moves the hook into RootRouteView before its early returns and retains the existing auth eligibility. Actual React hook, Registry and memory-router lifecycle controls reproduce the old shell-only layout losing a completion during a connect route, then pass with persistent observation across connect and welcome routes. Disabled cleanup and fresh history on re-enable are covered. Node EventTarget host stubs supply router browser plumbing; these tests do not render the complete root component or access a deployed hosted account.
  • Two existing importer integration tests pass using the real engine, SQLite projection and reactor receipts with controlled scanner/provider inputs. Imported Codex and Claude history has no latest turn and does not start the provider before a new prompt. The web lifecycle control confirms that such settled history stays unmarked while a different thread's live completion becomes unread.
  • Batch controls assert one notification for 100 or 1,000 new completions and none for unchanged history. With 10,000 existing visit entries, a scratch callback measurement fell from about 5.3 seconds and 1,000 notifications to about 8 milliseconds and one notification for 1,000 completions. These are local synthetic measurements, not browser latency claims. Persistence was already debounced before this change.
  • Web typecheck, scoped lint, formatting and whitespace checks pass on 429a878d, based on main 09aac715. Lint reports the upstream root-route effect-dependency and welcome-ref render warnings. At September 5, 2026, 09:35 UTC, all executed CI jobs and Macroscope correctness/convention checks passed, and Macroscope approved this head. The maintainer's bootstrap/read-state policy hold remains. Cursor Bugbot has no current-head review; CodeRabbit reports automatic reviews disabled, so its successful status is not a completed review.
  • Linux Chromium 152, 1280×900, real client and command/receipt flow with a disposable local deterministic Codex fixture. No paid model, provider update, app-store action or direct database write was used. A never-opened background thread showed Done after completion, survived reload, and cleared when opened. Mark unread restored it. The Legacy sidebar rendered Completed for the same manual-unread state and cleared it when opened.

The native-before used main eced382b. The after integrated candidate 2e1a60c, based on 4631000f, into the retained test app with backend 5a4287cd. The relevant root, store, shell and sidebar behavior matched those bases. Unrelated held audit integrations were present. The normal merge of main 25cbcd62 initially left all four proposal files byte-identical to that tested candidate. The later route-lifetime correction changes hook ownership and auth gating, not the visit-map or ordinary completion policy.

Before and after use separate fresh threads following the same flow, case 2 before and case 3 after. They are not the same thread replayed. Electron, macOS, mobile and a deployed hosted client were not exercised. Hosted and multi-environment lifecycle coverage is at the shared runtime/Registry boundary.

An additional ordinary-client control integrated the exact e36ed081 route correction. A fresh case 4 completed in the background at September 5, 2026,09:20:13.637UTC; Done appeared with a persisted visit timestamp one millisecond earlier, survived reload, and cleared on actual open. Historical and previously visited threads remained unchanged. Its local capture contains a fixture-health banner and is not published. The later main merge preserves the upstream welcome wizard and changes the root only by adding the observer import/call; welcome-route verification is the lifecycle test above, not a native wizard walkthrough.

Before: the newly completed background thread has no Done indicator.

Before: completed background thread without an unread indicator

After: a new background completion shows Done while another thread remains selected.

After: new background completion shows Done while another thread is open

Control: opening the completed thread clears Done normally.

Control: opening the completed thread clears the unread indicator

Continuous four-second client clip showing the background thread's arrival and Done indicator. This clip does not show the separate reload or open controls and is not a latency benchmark.

Background thread arrival and Done indicator

GPT 6 Astra via Codex in T3 Code.

Note

Mark newly completed background turns as unread in web UI

  • Adds subscribeToUnvisitedCompletions in useUnvisitedCompletionsUnread.ts which subscribes to each catalog environment's shell atom, takes a live snapshot as the baseline, and persists unread markers for threads that complete after that baseline
  • Adds the useUnvisitedCompletionsUnread React hook and wires it into __root.tsx for authenticated or hosted-static sessions; subscriptions are disposed on unmount or disable
  • Extracts getUnreadVisitedAt in uiStateStore.ts to validate completion timestamps and compute a visited timestamp one millisecond before completion; invalid or missing timestamps return null and skip marking
  • Adds a large integration test suite in useUnvisitedCompletionsUnread.test.ts covering baseline handling, navigation, reconnects, remote environments, batching, and edge cases
  • Behavioral Change: threads that completed before the observer's first live snapshot are not marked unread; only completions observed after the baseline are persisted

Macroscope summarized 429a878.

juliusmarminge and others added 4 commits September 5, 2026 01:35
Preserve historical first-live snapshots and existing visits while observing later completions across environment reconnects. Adapt the initial proposal from closed [#3235](#3235) to the current shell lifecycle.

Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 5, 2026
Comment thread apps/web/src/routes/__root.tsx Outdated
@github-actions

github-actions Bot commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.5 KiB 13.6 KiB +55 B (+0.4%) 15.1 KiB ✅
Codex Thread snapshot wire 7.0 KiB 7.0 KiB −3 B (−0.0%) 7.3 KiB ✅
Codex Live turn WebSocket wire 6.5 KiB 6.6 KiB +58 B (+0.9%) 7.8 KiB ✅
Codex Live turn WebSocket decoded 57.0 KiB 57.1 KiB +88 B (+0.2%) 66.4 KiB ✅
Codex Live turn messages 8 10 +2 (+25.0%) 21 ✅
Claude Total thread wire 13.5 KiB 13.6 KiB +67 B (+0.5%) 15.1 KiB ✅
Claude Thread snapshot wire 7.0 KiB 7.0 KiB −3 B (−0.0%) 7.3 KiB ✅
Claude Live turn WebSocket wire 6.5 KiB 6.6 KiB +70 B (+1.1%) 7.8 KiB ✅
Claude Live turn WebSocket decoded 57.8 KiB 57.9 KiB +88 B (+0.1%) 66.4 KiB ✅
Claude Live turn messages 8 10 +2 (+25.0%) 21 ✅

Baseline: 09aac71 · PR result: 429a878 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.8 KiB
  • Claude decoded thread snapshot: 114.5 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

macroscopeapp Bot commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds an always-on observer that changes and persists unread state for background completions across environments and route transitions. Although the focused test coverage is extensive, the new lifecycle and bootstrap read-state policy represent nontrivial user-visible behavior that warrants human review.

Notes:

  • Diff unchanged. Approvability was decided on eligibility alone.

You can add or adjust custom eligibility rules. Learn more.

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Sep 5, 2026
@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 1, 2026 — with ChatGPT Codex Connector
@macroscopeapp
macroscopeapp Bot dismissed their stale review October 1, 2026 16:02

Dismissing prior approval to re-evaluate 429a878

@juliusmarminge

Copy link
Copy Markdown
Member Author

Thanks for working on this. We merged the orchestrator V2 rewrite in #2829, and we are closing this PR as part of that transition.

This PR adds live completion detection based on thread.latestTurn.state and completedAt. V2 completion state lives on latestRun/runs, so the new unread observer is built against the retired snapshot shape.

Sorry for the extra work this creates. If the change is still needed on V2, please rebuild it on current main, verify it there, and open a new PR linking back here. We're closing the current implementation without assuming the underlying request is resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sidebar can miss unread Completed status for newly finished threads

1 participant