Repository navigation
Conversation
joahg
force-pushed
the
joah/solo-fold-inactive
branch
from
November 11, 2026 08:05
2c95e08 to
e35928e
Compare
Solo chats whose last message is more than 72 hours old move to a collapsed "Inactive for 72 hours (n)" section below the workspaces. The open chat, chats with a working agent, unread chats and a chat being renamed stay in their workspace; a new message moves a chat back. A deadline timer folds a chat that crosses the window while the sidebar is idle, and the section remembers whether it was opened. Solo holds an activity demand scoped to Solo chats, so the relay session reads their latest message time without a Recent sort or Active filter. AGNTOPS-732 Co-authored-by: Hertz <c6741012416ef5c49a865db79682113385c5fb998e8c890234fb442b59e22748@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Joah Gerstenberg <[email protected]>
joahg
force-pushed
the
joah/solo-fold-inactive
branch
from
November 12, 2026 00:07
e35928e to
7dbcbb8
Compare
matt2e
added a commit
that referenced
this pull request
Nov 14, 2026
…es (#3442) ## Why Two browser journeys fail intermittently in CI for the same underlying reason. Both jump the page clock far enough to expire the live stream's heartbeat, and the stream restart that follows was not accounted for. | Test | Sightings | |---|---| | `agent-outcomes.spec.mjs`, "a remote failure shares its expandable history and preserves its message anchor" | WebKit 1/18, Browser journeys: [10 Nov, main](https://github.com/block/buzz-app/actions/runs/42923245792/job/130206998334), [10 Nov, PR](https://github.com/block/buzz-app/actions/runs/42920092542/job/130193154955), [11 Nov, PR](https://github.com/block/buzz-app/actions/runs/43110752944/job/130826317616), [11 Nov, main](https://github.com/block/buzz-app/actions/runs/42952461094/job/130300041605) | | `relay-recovery.spec.mjs`, "relay reads recover automatically after the network returns" | WebKit 5/18, Browser journeys: [10 Nov, PR, passed on re-run](https://github.com/block/buzz-app/actions/runs/42918607668/job/130187892464) | ## Root cause The live broker stream arms a 45 s heartbeat on the page clock. The broker's 15 s keepalive runs on real time. A page-clock jump longer than 45 s therefore always expires the heartbeat, and the page aborts and restarts its stream. `presence.spec.mjs` already records this hazard. **`agent-outcomes`** pauses the page clock, freezes wall time, and runs 120 s of browser time. Two failures follow from the restart: - In three of the four jobs the test published its signed replies before the restarted stream had re-issued a channel REQ. The policy relay rejected the publish with "event must traverse an explicit production channel REQ". In each trace the publish came about 0.9 s after the clock resumed, and the new stream opened 0.2 to 0.4 s later. - In the fourth job (11 Nov, main) the 120 s jump took 85.6 s of real time instead of the usual 4 to 12 s. The still-frozen wall clock drifted more than 60 s from the broker, so read-state signing answered 400. **`relay-recovery`** fast-forwards the page clock 30 s twice. The second jump expires the heartbeat. In the same jump, session recovery's scheduled retry posts `/stream-retry` with the old stream id. The broker has already handled the stream close and answers 404 "Live stream no longer available". The app drops the error for a superseded generation, but the browser still logs "Failed to load resource … 404". The fixture only excused retired-stream 404s for `/stream-observer` and `/stream-priority`, so the unexplained-console-error check failed the test. The trace shows the fast-forward, the 404 ten milliseconds later, and the new stream 0.7 s after that. Neither is a product defect. The restart only happens under a fake-clock jump, and the app already ignores the late control response. ## Fix - **`agent-outcomes.spec.mjs`**: re-anchor the page wall clock to real time right after resuming, so signed read-state events follow the broker's clock again. Then poll for the primary/alpha route before publishing, mirroring the readiness wait the test already does at startup. - **`fixture.mjs`**: widen the retired-stream 404 match to the six broker control routes that answer 404 once a stream is retired: observer, priority, retry, interests, artifact-sync and artifact-sync-close. The exact `retiredStreams.has(streamId)` guard stays, so a 404 for a live or unknown stream still fails. Presence controls answer 200 for a missing stream and stay out of the list. No timeouts, retries or assertions were relaxed. No browser cases were added or removed. ## Evidence Local runs on Linux, WebKit, `bin/pnpm test:browser:ci --project webkit --no-deps --workers=1 --repeat-each=8 --reporter=list`. | Scope | Result | |---|---| | `agent-outcomes.spec.mjs -g "a remote failure shares"` | 8/8 passed, 17.8 to 20.2 s each | | `relay-recovery.spec.mjs relay-refusal.spec.mjs -g "relay reads recover automatically\|an upload is the first request"` | 16/16 passed | ## Not reproduced, not run - The late `/stream-retry` 404 did not reproduce locally. No retired controls were recorded in the local evidence, so the widened fixture branch is only exercised under CI timing. The Flake check lane on this PR is the real test of it. - The unchanged `agent-outcomes` test also passed 8/8 locally, with two or three stream restarts during each jump. The local run guards against regression rather than reproducing the CI ordering. - The read-state re-anchor addresses a failure seen once, when the jump ran unusually slowly. That slowness was not reproduced. - Chromium was not run for these two changes. - No human has tested this yet, so `buzz-review-completed` is not set. ## Overlap with open PRs Several open PRs also edit `tests/browser/fixture.mjs` (for example #3332, #3239, #3186 and #3093). None touches the retired-stream match, and a trial merge of each against this change is clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: mtoohey <[email protected]> Signed-off-by: Matt Toohey <[email protected]> Co-authored-by: Claude Fable 5.1 <[email protected]>
This branch has not been deployed
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.
Why
Solo lists every chat the viewer has, so the sidebar fills up with chats nobody has touched in days. Chats that have gone quiet should get out of the way without being archived.
What
Solo chats with no messages in the last 72 hours move into one Inactive for 72 hours (N) section below the workspaces.
solo:inactive-open), the same way workspace collapse is saved.useSidebarFilters.Activity loading (
relay/session.ts, FOUNDATION)Today Solo only knows a chat's last-message time when a Team section sorts by Recent or filters by Active. Otherwise the time comes from live events alone, so after a reload most chats have no known activity.
session.channels.retainActivity(include)loads the latest message time for the listed channels thatincludeaccepts, for as long as the handle is held. Solo holds it for its own chats only, so a community with many channels doesn't read all of them. A Recent sort or Active filter still reads every channel, as before. Releasing the last handle cancels the read, the same as turning those preferences off.Test fixtures
The browser fixture dates seeded history to November 2023. Every seeded Solo chat would fold, which hides the rows many specs click.
signnow moves seeded times in Solo chats to the last day and keeps their order. Times that are already current, such as live appends, are left alone. Other channels keep their 2023 times, which global search's date filters depend on. The vitest sidebar fixture's "newer" chats get current timestamps for the same reason.Testing
solo-inactivity.test.ts: the 72-hour boundary, exempt chats, unknown activity, and the next deadline.SoloInventory.test.tsx: a quiet chat folds while the open and unread chats stay listed. Reading the unread chat folds it. Opening Inactive shows its rows and saves that it's open. A chat crossing 72 hours while the sidebar is idle folds when the timer fires.channel-activity-session.test.ts:retainActivityreads only the channels it names, without a Team Recent or Active section. Its release is idempotent, the read cancels after the last release, and a Recent sort still reads every channel.tests/browser/solo-inactive.spec.mjs, in Chromium and WebKit: after the clock moves forward 72 hours, three chats fold. The section starts closed, opening a chat takes it out of Inactive, and a reload keeps the section open.navigation-sorting,relay-recoveryandsidebar-unread. Those three failed on the first CI run, when Solo read activity for every channel. They also pass in WebKit.media-preparationfails on main too. Thevite-configwarning test passes when run on its own.navigation-sorting,relay-recoveryandsidebar-unread. The Solo, channels, relay activity and composer vitest suites pass too (2,099 tests).biome checkpass. Rendered in light and dark themes at 1280×800.This replaces the plugin approach in #2483 (a
registerChatListhook), which is closed.🤖 Generated with Claude Code