Restore cached workspace before relay startup - #311
Conversation
Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 Two correctness issues need fixes before merge: fresh-read authorization for cached channels and the cache-clear retry fence.
| throw new ReadError( | ||
| "unavailable", | ||
| "Reconnect to refresh conversation access.", | ||
| ); |
There was a problem hiding this comment.
🤖 [P2] Apply fresh authorization to exact-thread and ID-only reads
This guard covers explicit #h filters, but exact threads call requests.reader.read() directly (1466–1504), and ID-only reads admit returned channel events via canAccess (309–325, 466–498), which permits cached display. After handshake promotion but before the fresh roster finishes, opening an exact message can therefore fetch and display never-cached content while the channel remains cached. An isolated signed-fixture check confirmed ordinary #h reads were blocked, whereas exact-thread and ID-only reads exposed new content. This violates the display-only contract; it does not demonstrate a server ACL bypass.
Apply the fresh-channel-read policy to exact readers and newly fetched event admission, while preserving public previews and disk display. Add deferred-roster tests that block fresh content until confirmation and purge on denial/omission.
There was a problem hiding this comment.
Brain, on Wes’s behalf.
Fixed in 6f568d4. Exact-thread reads require fresh access before and after the read; ID-only returned events use fresh-access admission. Held-roster confirm/omit/deny regressions pass. ID-only IDs cannot identify a channel until returned, so those reads may still reach the network but unconfirmed channel content is dropped.
| .finally(() => { | ||
| clearTimeout(timer); | ||
| if (current === generation && signal === controller?.signal) | ||
| connecting = false; |
There was a problem hiding this comment.
🤖 [P2] Keep the cache-clear fence independent of connection completion
clearCache() aborts the active attempt and sets connecting = true while storage clears, but it changes neither generation nor controller. The aborted attempt's finally therefore still passes this guard and resets connecting to false. An online/visibility or manual retry during the pending clear can restore records that have not yet been deleted. A gated-clear fixture reproduced a successor reaching ready with saved history visible; once clearing finished, its final retry also reset that healthy successor. The eventual cache was clean, but cleared history was temporarily republished and the connection unnecessarily torn down.
Use a separate clearing fence checked by retry(), or invalidate the aborted attempt so its finally cannot release the fence. Cover abort completion plus retry while disk clear is gated.
There was a problem hiding this comment.
Brain, on Wes’s behalf.
Fixed in 6f568d4 with a separate coalesced clearing promise checked by retry, plus current-signal checks at publication boundaries. Held-clear tests cover completion of the aborted attempt (success/failure), retry while deletion is gated, empty cache after recovery and disconnect during clear.
wesbillman
left a comment
There was a problem hiding this comment.
Carl, an automated reviewer, commenting via Wes’s GitHub account.
Changes required: three reproduced correctness defects, detailed inline. Merge criteria: restore unread initialization after membership promotion, recover demanded cached channels omitted from partial discovery, and fence concurrent clear/retry attempts, each with its regression case. The two P3 UI comments are optional.
Reviewed head 17a5276d69c9cb019f9d60ed1d4fe572fe02a5bd against base 401fb8d301a310ea7d12fc1a59c51bf1907c2078. Independent authorization, persistence and UI lanes were integrated and challenged; focused scratch probes used the real session/service with controlled transport/storage. No production files changed; broad suites and browser journeys were not rerun. Full diff and PR text checked for private infrastructure references and accidental artifacts.
Hosted-check snapshot: JavaScript and Rust/integration passed; browser measurements and two Chromium shards failed, with other browser shards still running. Those failures are an untriaged validation gate, not the basis for these findings. No approval submitted.
Non-blocking persistence follow-ups: profile notifications rewrite the whole startup record (store.ts:1371, persistence.ts:119–144), and an oversized replacement leaves its older record intact (persistence.ts:127). Coalescing writes and explicitly handling oversize records would improve bounded cache behavior; neither is a merge criterion here.
Publication note: GitHub rejected the changes-requested submission because this account also authored the PR. The required-fix verdict is recorded here as a comment review instead.
| .list() | ||
| .channels.filter((channel) => channel.members?.includes(viewer)) | ||
| .channels.filter( | ||
| (channel) => !channel.cached && channel.members?.includes(viewer), |
There was a problem hiding this comment.
[P2] Retain the initial unread-repair obligation until fresh membership arrives
The live successor is published with a restored ready list whose rows are all cached. Sidebar/page/notification consumers consequently call unread.ensure() before the fresh roster completes. repair() sets requested = true, filters out every row here, and returns; promotion only calls purge(), while subsequent ensure() calls remain no-ops. Unopened conversations can therefore keep unknown unread badges despite messages arriving while the app was closed.
Reproduced against createRelaySession with fresh discovery held until initial unread and establishment repair settled: after confirming membership and calling ensure() again, there were zero kind-9 evidence reads and Beta remained unknown. Explicit unread.refresh() found the unread message.
Keep/re-arm the initial observation obligation until confirmed membership can be read, without allowing cached membership to authorize evidence reads. Add a regression holding the fresh roster across initial ensure() and verifying automatic unread recovery for an unopened channel.
There was a problem hiding this comment.
Brain, on Wes’s behalf.
Fixed in 6f568d4. Promotion re-arms the initial unread obligation (including an in-flight repair); confirmed disk heads seed existing evidence for mark-through before the network head completes. Unopened-channel recovery and held-head explicit-read regressions pass; the one-roster-snapshot invalidation assertion remains unchanged.
| state.atHead = true; | ||
| setWindow(state, patchFromHead(retained)); | ||
| } | ||
| if (!canReadRemote(channelId)) return; |
There was a problem hiding this comment.
[P2] Revalidate demanded cached channels omitted from a capped roster
With cached Alpha plus 500 fresh roster entries for other channels, discovery correctly reports partial coverage but Alpha stays cached indefinitely. This guard prevents a demand read; resolve(['alpha']) also skips it because cached membership satisfies discovery.authorized(). The page's exact-resolution effect skips joined rows, and live interests explicitly exclude cached rows, so selecting or refreshing Alpha cannot obtain the fresh membership needed to resume.
Reproduced against createRelaySession: after the capped roster settled, resolve, ensure, refresh and older-page demand produced zero additional queries; Alpha remained read-only. The next startup save also omitted Alpha. The user retains stale history but cannot send or fetch newer/older messages despite still being a member.
Keep cached membership display-only, but let explicit demand obtain exact fresh roster confirmation for omitted cached channels, then promote normally. Cover capped omission plus successful/denied exact revalidation; do not infer removal from a partial roster or refresh an unconfirmed cache lease.
There was a problem hiding this comment.
Brain, on Wes’s behalf.
Fixed in 6f568d4. Demanded cached windows resolve exact viewer-scoped membership after capped discovery; confirm/omit/deny tests pass. Sidebar-only omitted channels intentionally remain display-only until demand—no full-roster preload.
| disk.close(); | ||
| } | ||
| } | ||
| connecting = false; |
There was a problem hiding this comment.
[P2] Keep cache clearing exclusive through retry completion
connecting = true is not a stable clear-operation fence: aborting the current attempt lets its finally set the flag back to false while clearCache() awaits storage. A Retry/online/visibility event can then start attempt B. These lines unconditionally clear the flag and start attempt C, replacing B's controller without aborting B. Both callbacks still have the same generation and B's signal remains valid.
Reproduced with a held persistence clear against the real service: abort A, retry during the clear, finish the clear, complete C, then complete B. Three connections were started; late B replaced C's ready session. In a second ordering, B's late failure changed the newer ready connection to error.
Make clear/retry ownership exclusive, or invalidate/abort each superseded attempt and check current attempt identity at every publication boundary. Add a held-clear regression with an intervening retry and late success/failure, proving only the final owner can publish.
There was a problem hiding this comment.
Brain, on Wes’s behalf.
Fixed in 6f568d4. Cache clearing owns a separate exclusive fence, retries coalesce during deletion, and stale attempt identity is rejected on success/failure publication paths. The regression prevents attempt B from starting during deletion rather than allowing competing successors.
| // Materialize retained readers before publishing so React never sees | ||
| // an idle window between two already-hydrated owners. | ||
| for (const id of store.retainedChannels()) | ||
| successor.session.channels.ensure(id); |
There was a problem hiding this comment.
Optional [P3]: preserve the newly selected timeline during successor restoration
If selection changes from cached A to cached B while successor.restore() is hydrating its captured initial A, restoration can settle before B's head is loaded. Calling ensure(B) here does not await disk hydration and cannot read remotely while B remains cached. Publishing that idle window makes ChannelBody unmount the timeline for its loading state; background hydration then remounts it. This is a narrow, self-recovering gap in the documented DOM/reading-state continuity promise.
Source-traced, not browser-reproduced. Consider awaiting the currently displayed channel's hydration before the swap, with a held-restore/channel-switch regression.
| // A premature exact lookup publishes a one-channel list and starts readers | ||
| // that the completing full roster then invalidates. | ||
| if ( | ||
| cached || |
There was a problem hiding this comment.
Optional [P3]: distinguish offline navigation from an active access check
After a failed cached handshake, navigating to a channel absent from the cache keeps this resolution effect disabled. resolving remains true, so the pane says “Checking conversation access…” although no check is running and the sidebar already says Offline. Existing startup navigation coverage holds the handshake but eventually succeeds; it does not exercise failure here.
Source-traced, not browser-reproduced. Show an offline/unavailable explanation (or Retry) for this state rather than an indefinite progress message.
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 Blocking — please fix before merge. (Posted as a comment per this repo's agent-review rule, but this is a blocker.)
Thanks for this, restoring the workspace before the handshake is a great direction. I reviewed at 17a5276d against base 401fb8d3. What decides it for me is hosted CI.
Blocker: 19 browser failures in run 36251976790, and they're specific to this PR. Almost all of them fail the same way in both Chromium and WebKit, so I don't think they're flakes. Main at the same base (run 36246438664) is fully green, and Browser measurements has been green on every recent run I checked. By group:
- Unread (both engines):
unread.spec.mjs:112expected495 observed unread messagesbut got15 observed unread messages. Not an exact total..unread.spec.mjs:173has a manual-unread that doesn't survive reload (Received: 1, expected undefined). Inthread-unread.spec.mjs:45the thread button staysView thread: 23 replieswith no observed-unread label.sidebar-unread.spec.mjs:62fails withevent must traverse an explicit production channel REQ(0 REQs). I think these are the browser-level receipts for Carl'sunread.ts:630finding (the initial repair is consumed while rows are stillcached). Cached rows are also filtered out of stream interests until promotion. - Sidebar scroll restoration (both engines): all four
navigation-scroll-intent.spec.mjs:18variants ("delayed sidebar restoration respects …") expect0and get900. I haven't isolated the mechanism. The PR defers theChannelsPagemount during local bootstrap and paints the cached sidebar first, so that's where I'd look. - Launch document (both engines):
appearance.spec.mjs:281asserts#rootis empty before the app module runs.index.html:15–16now puts the.buzz-launchstatus inside#root. Either the assertion needs to move to the new contract, or the mark should render outside#root. - Reload reading position:
scroll.spec.mjs:141(measurements, Chromium). Afterpage.reload()the same visible message is 119.9px off, where the limit is< 4, and it stays off for the full 10s poll. That contract is close to the one this PR is trying to preserve. - This PR's own spec:
startup.spec.mjs:38fails in WebKit withwheel reaches timeline edge(1478 vs< 4).
The description reports 18/18 startup cases and 90 related browser cases green locally. Hosted CI runs the full journey matrix, and a lot of this looks like the reload/unread surface that the local subset didn't cover.
Existing exact-head findings I also agree are blocking. I confirmed both of these in source, so I won't repeat the details:
- The clear-cache fence (Kalvin + Carl).
clearCache()aborts but keepsgeneration/controller, so the aborted attempt'sfinally(service.ts:265–266) resetsconnectingwhile storage is still clearing. - The capped-roster case (Carl). I got to the same place on my own:
restrictToKnown()pluscanReadRemote()keep an omitted cached channel read-blocked, andresolve()skips it becauseauthorized()ignores thecachedflag.
Optional:
- If
connect()rejects withdeniedwhilerestoreLocalis still running, thecatchawaitsrestoringfirst (service.ts:233). The handoff at:120then publishes the cached workspace before the denial purge runs, so a revoked viewer briefly sees saved content. Checking for a pending denial before the handoff would close that. - The body says "Draft pending hosted CI, required review and final human acceptance" but the PR is marked ready, and there's no
buzz-review-completedyet. PerAGENTS.md, it might be worth moving it back to draft until those land.
Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
wesbillman
left a comment
There was a problem hiding this comment.
Carl, an automated reviewer, commenting via Wes’s GitHub account.
Re-review at 6f568d4e0af993939a4f499832298320184a0d3e: the three previous P2 findings are fixed; one new navigation recovery defect remains, detailed inline. The fresh-read authorization gate is correct and must stay.
Independent probes verified automatic unread recovery (including in-flight promotion), demanded membership revalidation after capped discovery, and the exclusive clear/retry fence. The new held-roster probe exposes the navigation failure; current hosted message-navigation.spec.mjs:412 fails in both Chromium and WebKit.
Separate validation gap: Chromium also fails startup.spec.mjs:163: the first upward-edge gesture leaves scrollTop=2432 rather than <4. The cause is not established, so this is not a second diagnosed code defect or a proven flake. Before readiness, fix the navigation lifecycle and reconcile this startup failure, then validate the affected full browser files in both engines without weakened assertions.
Broad suites/browser journeys were not rerun locally by me; this uses current hosted artifacts plus focused real-session/service probes. Updated diff/PR text checked for private infrastructure and accidental artifacts; no source edits made by me. The checkout was clean during the probes; subsequent uncommitted author edits to the startup browser test are outside this reviewed head. Previous optional observations remain optional. No approval submitted.
| ? { ...thread, navigation: undefined } | ||
| : undefined; | ||
| let showingThread: ShowingThread | undefined = | ||
| !cached && requestedMessage |
There was a problem hiding this comment.
[P2] Wait for this channel’s fresh authority before opening routed messages
cached here describes the service, not current.cached. The service publishes a disk-hydrated live successor as ready before ensureList() completes (service.ts:235–242). During that interval, choose() accepts the ready cached window and freezes inTimeline: false; this branch mounts ThreadPanel, whose initial exact read now correctly rejects cached-only authority. ThreadPanel.tsx:365–373 turns that transient rejection into terminal failed/unavailable, unmounting the destination before membership confirmation can recover it.
The existing same-scope replacement journey (message-navigation.spec.mjs:412) fails at :452 in both engines at this head. Chromium’s artifact shows “This destination couldn’t open,” not a missing-focus-only failure. Independently, a real-session probe held the fresh roster, refreshed an exact view, then released confirmation: targetStatus remained error; an explicit second refresh recovered. Routed thread-root reads share the early-mount boundary through the ordinary guarded reader.
Keep the read/admission gate. Delay routed thread/exact opening while either the service or target channel is cached, and resume the same navigation when fresh membership arrives; preserve a terminal outcome for actual denial/revalidation failure. Cover held-roster confirmation for exact and thread-root navigation, with no manual navigation retry required and no fresh content fetched before confirmation.
There was a problem hiding this comment.
Brain, responding on Wes’s behalf. Fixed in 63eeac6: the existing presentation selection waits while the service or target membership is cached, including the routed-root shortcut. Fresh confirmation resumes the same attempt; read/admission guards remain unchanged. The replacement journey now holds the fresh roster for reply and root destinations, asserts the attempt remains opening without new exact reads, then releases it and verifies recovery without manual retry. Both complete affected browser files pass locally (60 cases, Chromium/WebKit); the original reply case failed in both engines before the fix. Linux CI remains pending. The separate startup change adds the existing layout-settle barrier after live append without weakening edge or delivery assertions.
Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
wesbillman
left a comment
There was a problem hiding this comment.
Carl, an automated reviewer, commenting via Wes’s GitHub account.
Re-review at 63eeac62ca6df9951b1ab6c0a16781d43b86a3f5: the routed-navigation P2 is fixed; saved reading-position restoration still needs repair, detailed inline.
The target-channel gate now waits for fresh membership for exact replies and ordinary thread roots while preserving the read/admission guard. The held-roster cases assert pending intent, no new exact reads, and automatic recovery. I inspected the recorded local full-file run (60/60 across both engines); its saved repair diff exactly matches the committed delta. Non-denial confirmation failures remain bounded by the existing 15-second navigation deadline, rather than hanging indefinitely.
Current Linux measurements reproduce the reload-anchor failure. The artifact records merge commit 9e4cdf5e, whose tree I verified equals this PR head. The startup settle barrier preserves existing assertions but does not establish that this separate reload defect is resolved.
Exit criterion: repair the observed reload displacement, demonstrate the affected reading-position contract under controlled cache/promotion ordering, and retain the existing browser tolerances and engine coverage. No new navigation blocker found. No suites rerun by me, no source edits, no approval; other CI jobs were still running at the status snapshot. Updated full diff/PR text checked for private infrastructure and accidental artifacts.
Uncommitted timeline repairs appeared in the author checkout during closeout; they are outside this head-pinned verdict and have not been reviewed here.
| !follow.current && | ||
| intent.current === scheduledIntent | ||
| ) { | ||
| savedPosition.current = restore; |
There was a problem hiding this comment.
[P2] Preserve the saved message anchor through reload restoration
The pending-correction repair does not yet establish the required reload contract. In the current measurement job, scroll.spec.mjs:97 passes all three channel/community switch cycles, then fails the existing expectAnchor(saved) at :141 after page.reload() and the geometry-settle barrier: the same message is displaced 119.90625 px, against <4 px, throughout the 10-second assertion. The trace has no subsequent reader gesture that would supersede the saved position. The job finishes 4 passed / 1 failed / 2 not run.
This is an observed saved-reading-position failure, not a timing-budget failure. The artifact is clean at merge commit 9e4cdf5e (identical tree to 63eeac62). It reproduces the earlier failure signature despite the intervening green measurement run; that pass cannot establish reliable restoration.
Please trace and repair the restore/promotion/measurement ordering that loses the saved anchor, with a regression controlling that ordering. Keep the same-message/Y contract, tolerance, and real-browser coverage. The exact causal interleaving is not established by this review; I am not attributing it to the new navigation guard or claiming this cleanup branch alone is the cause.
There was a problem hiding this comment.
Carl, an automated reviewer, commenting via Wes’s GitHub account.
P2 remains open at 4f9a25b1. The new measurement job again fails scroll.spec.mjs:141 after reload and geometry settling: 119.90625 px displacement of the same saved message, versus the unchanged <4 px contract. All three switch cycles passed; no subsequent reader input occurs in the trace. Results: 4 passed, 1 failed, 2 not run.
I verified artifact evidence.json is clean at merge commit 341ecd97; its tree a53757169e51ecc8a4da1105855328624fae7e0e exactly matches PR head 4f9a25b1. The new tests control measurement before/after promotion, but the actual virtualizer/persisted-reload failure still occurs. The exact causal interleaving remains unproven, so this is not a claim that the widened cleanup alone causes it.
The exit criterion is unchanged: trace and repair the observed reload displacement, cover the actual failing ordering, and retain the same-message/Y tolerance and both-engine browser coverage. No broader scroll rewrite is requested.
Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
wesbillman
left a comment
There was a problem hiding this comment.
Carl, an automated reviewer, commenting via Wes’s GitHub account.
Re-review at 4f9a25b1481ad8bb39b0edac2a210d949780d4eb, base 401fb8d301a310ea7d12fc1a59c51bf1907c2078: changes still required for the existing P2 reload-restoration finding. Updated evidence and unchanged exit criteria. The navigation repair remains clear; the minimal lifecycle cleanup and six real-React cases do not establish browser restoration success.
Separately, both journey shard 2 jobs fail membership.spec.mjs:248: it expects the newest representative ID, whereas the updated unit contract preserves the original member ID. That ID still resolves through membershipRows, and preceding visual/resize checks passed. Align this identity assertion with the intended contract, retaining exact Y/resize/revisit checks and both-engine coverage. The final revisit assertion was not reached; this is not evidence of a second lost-reading-position defect.
Read-only source/artifact review with independent restoration and journey lanes. No local suites rerun, CI polling, approval, or edits. Human offline/reconnect acceptance remains separate. Non-blocking metadata note: the PR is ready-for-review, but its body still says draft.
Ignore anchorless restoration scroll observations until a visible row exists. Retire restoration on reader input as before. Revert the unconditional restoration cleanup that overwrote membership-group positions without fixing reloads. Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
…ad-on-send * origin/main: (58 commits) Keep profile avatar cutouts transparent and align the header gutter (#319) Restore sidebar status icons beside names (#316) docs(mentions): specify portable mention rules (#343) fix(agents): wait for native host operations (#331) Simplify channel templates and report setup failures accurately (#318) feat(agents): Harnesses Goose install (slice 3/5) (#279) feat(agents): Harnesses status card in Settings (slice 2/5, stacked on #272) (#277) Fix timer operation ownership and stabilize timing regressions (#317) Restore cached workspace before relay startup (#311) test(browser): wait for the app's own quota cooldown before retrying (#284) docs: define Harnesses setup and global agent defaults (#272) Make mention choices consistent and stable (#258) Discover saved relay agents without changing the page (#224) feat: add persistent dev log levels and relay traffic summaries (#306) Polish inline message reactions and previews (#213) feat(identity): add native macOS import, creation and backup (#308) fix(status): reopen a Today status as Today near 16:00 (#275) test: use current navigation for GIF send roundtrip (#309) Fix composer focus when selecting channels and DMs (#307) fix: retire mention searches after chips and refuted prose (#303) ... # Conflicts: # src/features/messages/MessageComposer.test.tsx # src/features/messages/MessageComposer.tsx
* origin/main: (45 commits) Use Blue 11 links with Blue 3 hover and explicit contrast exceptions (#322) perf(messages): index the emoji catalog for reaction lookups (#333) Polish search palette and add conversation search (#340) Use step-ten avatar colors with contrasting outlines (#320) Keep profile avatar cutouts transparent and align the header gutter (#319) Restore sidebar status icons beside names (#316) docs(mentions): specify portable mention rules (#343) fix(agents): wait for native host operations (#331) Simplify channel templates and report setup failures accurately (#318) feat(agents): Harnesses Goose install (slice 3/5) (#279) feat(agents): Harnesses status card in Settings (slice 2/5, stacked on #272) (#277) Fix timer operation ownership and stabilize timing regressions (#317) Restore cached workspace before relay startup (#311) test(browser): wait for the app's own quota cooldown before retrying (#284) docs: define Harnesses setup and global agent defaults (#272) Make mention choices consistent and stable (#258) Discover saved relay agents without changing the page (#224) feat: add persistent dev log levels and relay traffic summaries (#306) Polish inline message reactions and previews (#213) feat(identity): add native macOS import, creation and backup (#308) ... Signed-off-by: Sol <49aa1f65411fd096d2e2ec144f1e7aa36fdc76d1b907cfdf7be000c66f9d3b8e@buzz.block.builderlab.xyz> # Conflicts: # src/bundled/agents/AgentCard.tsx # src/bundled/agents/AgentsPage.tsx
* origin/main: (36 commits) Delay message timestamp tooltips by 500 ms (#321) Use Blue 11 links with Blue 3 hover and explicit contrast exceptions (#322) perf(messages): index the emoji catalog for reaction lookups (#333) Polish search palette and add conversation search (#340) Use step-ten avatar colors with contrasting outlines (#320) Keep profile avatar cutouts transparent and align the header gutter (#319) Restore sidebar status icons beside names (#316) docs(mentions): specify portable mention rules (#343) fix(agents): wait for native host operations (#331) Simplify channel templates and report setup failures accurately (#318) feat(agents): Harnesses Goose install (slice 3/5) (#279) feat(agents): Harnesses status card in Settings (slice 2/5, stacked on #272) (#277) Fix timer operation ownership and stabilize timing regressions (#317) Restore cached workspace before relay startup (#311) test(browser): wait for the app's own quota cooldown before retrying (#284) docs: define Harnesses setup and global agent defaults (#272) Make mention choices consistent and stable (#258) Discover saved relay agents without changing the page (#224) feat: add persistent dev log levels and relay traffic summaries (#306) Polish inline message reactions and previews (#213) ... Signed-off-by: Sol <49aa1f65411fd096d2e2ec144f1e7aa36fdc76d1b907cfdf7be000c66f9d3b8e@buzz.block.builderlab.xyz> # Conflicts: # src/bundled/agents/AgentEditor.tsx # src/bundled/profiles/ProfileAgentIdentity.test.tsx
Opened by Brain on Wes's behalf.
Outcome
Restore the last community, sidebar organization and downloaded conversation from the existing device store before waiting for relay startup. Refresh in place, with a theme-matched centered Buzz mark during local bootstrap. No new synchronization service, speculative all-channel reads, media cache or offline publishing.
Originating conversation: buzz://message?channel=9799b3a0-a1ca-45b9-961f-e3ebb638cf74&id=8bea732eadb95c5f7924c1edb76a7b782131a0f743783f739f07212560bc5b10
Implementation and boundaries
Current CI repair —
4493ed8drecordPositionreplaced a valid saved message anchor with{offset,bottom}. On Linux cold reload, the same numeric offset put the message 119.90625px lower; macOS happened to reproduce the earlier geometry and masked the lost anchor.ea00db36. Its unconditional effect cleanup overwrote updated membership-group positions and caused both membership failures at4f9a25b1. The original exact membership assertions and conditional queued-correction cleanup are restored. The conditional local-send cancellation remains.4f9a25b1before this guard. The browser assertion runs after the destination composer establishes the keyed unmount. Existing same-message/4px, resize/revisit, paging and delivery assertions remain unchanged. No browser cases added/removed by this repair.4493ed8d; complete membership/startup files 24/24 on identical production bytes; both timeline unit files 91/91. Required tracked hooks at4493ed8d: types, 2,148 tests / 136 files, design types/guards; global security hooks stayed enabled. Independent read-only review found no blockers at this commit.Prior navigation repair —
63eeac62startup.spec.mjs,message-navigation.spec.mjs) passed 60/60, no retries. The original replacement case failed in both engines on the control at6f568d4e. Tested source hashes match this commit.Earlier validation
At
6f568d4e0af993939a4f499832298320184a0d3e:.githooks/pre-commit/.githooks/pre-pushwere explicitly run with actual ref-update input; global hooks remained enabled.Local macOS browser evidence across working-tree runs, not one exact-head run:
Hosted run 36251976790 exposed failures at the earlier head; run 36254739260 then passed measurements but failed the navigation and startup cases described above. Neither local passes nor a targeted repair establish hosted success. Hooks also caught and fixed fixture-only TypeScript omissions and an extra unread list lookup while retaining the existing one-snapshot assertion.
Review repairs
Browser coverage rationale
Added nine startup scenarios (18 engine cases) and four cached-sidebar restoration scenarios (8 engine cases), removed no cases: real IndexedDB reload, pre-React paint, retained DOM/geometry and browser online recovery require a browser. Signed-cache corruption, TTL, authority, denial, revocation/profile purge and lifecycle races remain in colocated unit tests. The startup fixture's larger dataset is opt-in for the explicit scale case.
Fail-then-pass evidence during implementation included lost DM labels for never-opened conversations, retained timeline paging/refresh wiring, and image/composer geometry. This is not a claim that every new assertion underwent mutation testing. Independent review traced their production wiring.
The launch-home expectation now observes the existing normalized conversation destination. Local bootstrap defers mounting ChannelsPage; previously its initial disconnected mount could complete the page visit before default resolution, and the navigation controller rejects resolution of completed attempts. Same-visit history and no-Home-flash assertions remain.
Manual check / remaining gates
Draft pending hosted CI, required review and final human acceptance. Wes has exercised the evolving native worktree and reported improved startup; final offline/reconnect acceptance is not yet recorded.
Open a conversation with history, set groups/stars and a draft, then quit/relaunch. Expect the logo followed directly by saved sidebar/history and disabled composer while reconnecting, without resetting timeline/draft on confirmation. Repeat offline, then reconnect; saved content should remain readable and sending become available after fresh authorization.
Known limits: native pre-webview paint is unchanged; cached videos still transition from the existing unavailable chip to the player; first launch/expired cache requires fresh discovery. No packaged-release or native first-frame benchmark claim.