fix(desktop): preserve archived observer history#1752
Merged
Conversation
… stabilize session-boundary keys Archive events now route to a per-channel uncapped store instead of the 3,000-event live-relay cap. Session-boundary React keys use firstItemId (stable across prepend) instead of runIndex (unstable on archive load). Closes data-loss regression: >3,000 archived frames were silently discarded. Closes key-churn regression: archive page loads caused unnecessary boundary node remounts. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…age ingest Archive page ingest now calls notifyListeners() once after the batch, so useSyncExternalStore subscribers rerender after every page load. Previously a full 50-row page wrote to the store silently. The live/archive merge now happens at the raw ObserverEvent[] level: mergeObserverEventWindows() deduplicates by (seq, timestamp) and ManagedAgentSessionPanel calls buildTranscriptState() once over the combined sorted window. Previously two independent state machines derived TranscriptItem[] separately, losing stateful aggregates (tool start/update, permission request/response, plan replacement) that straddle the 3,000-event live/archive boundary. The same combined raw window feeds both the transcript and the raw event rail / header count, so the count no longer shows 0 after archive-only reload. Removes archiveTranscriptByChannel (derived), getArchivedChannelTranscript, useArchivedChannelTranscript, and mergeTranscriptWindows. Replaces with getArchivedChannelEvents (raw), useArchivedChannelEvents, and mergeObserverEventWindows. Adds 5 regression tests: subscription notification on full page, no notification on pure-duplicate page, tool start+update across boundary, permission request+response across boundary, raw-rail count after archive-only ingest. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Three pass-3 corrections: - Assert exactly one batched subscriber notification (notifyCount === 1) for a full 50-row archive page, proving the batch contract rather than just liveness. - Reshape the permission cross-boundary fixture to use production-shaped optionId/kind fields (allow_once) and outcome:"selected", and assert the final row label is exactly "Approved (allow_once)" rather than non-null. - AgentSessionThreadPanel header: feed useArchivedChannelEvents + mergeObserverEventWindows into the latest-timestamp computation so the header shows "Last updated …" rather than "No updates yet" after a reload where only archived events exist. Drop the live-only useAgentTranscript dependency; raw combined events fully determine the header timestamp. Add a focused regression that proves the merged window yields a finite timestamp after archive-only ingest. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
9fb46e2 to
8b4a1df
Compare
The prior copy claimed current-context knowledge that the frontend cannot reliably determine across rotate, crash, and reload transitions.
tlongwell-block
pushed a commit
that referenced
this pull request
Jul 11, 2026
Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz> * origin/main: fix(desktop): preserve archived observer history (#1752) fix(dev): install Lefthook hooks into shared .git/hooks dir (#1751) fix(desktop): surface codex config-parse failures and -32603 internal errors clearly (#1745) fix(desktop): fix workspace rail badge disappearance and persist mark-as-unread (#1747) chore(desktop): remove container-only npm-preflight E2E harness (#1749) fix(desktop): preflight npm prefix writability in doctor installs (#1732)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
After a app restart or Command+R reload, the observer panel body showed
archived rows while the thread header said "No updates yet", and the
raw event rail showed an empty count. Scrolling back through older history
triggered archive page loads that were stored but never signaled to React
subscribers, so the UI did not rerender until an unrelated live event
arrived.
Design
Keeps a channel-keyed uncapped raw archive journal (
archiveEventsByChannelin
observerRelayStore). All display state is derived once from a singlemerged raw-event window:
mergeObserverEventWindows(liveScoped, archived)deduplicates by(seq, timestamp)and sorts ascending — same identity and ordering ascompareObserverEvents.buildTranscriptState(combined)runs once over the combined window sostateful aggregates (tool start/update, permission request/response,
plan replacement, message chunks) are never split across two independent
state machines.
event count in
ManagedAgentSessionPanel.AgentSessionThreadPanelheader feeds the same merged window into itslatest-timestamp computation so "Last updated …" reflects the full loaded
history, not only the capped live window.
useSyncExternalStoresubscribers once perbatch that added new events; duplicate-only pages do not notify.
Stable boundary identity
Session-group boundary keys use
sessionId:firstItemIdinstead ofrunIndex:firstItemId, eliminating key churn when archive pages prependevents that shift earlier run indices.
Behavior coverage
outcome label (
Approved (allow_once)).archive-only ingest.