Repository navigation
perf(messages): cut main-thread work while a busy channel is open - #2729
Conversation
the channel-list callback filtered snapshot items through allowed(), which scans the whole list and members.includes(viewer) per item on every list notification (one per live message). reuse the set already built for the callback. Co-authored-by: Claude Opus 5.5 <[email protected]>
the inset box-shadow ring added for reactions you have made costs hover frames on macos; a 1px outline with a -1px offset draws the same ring. forced colors keep a highlight ring through outline-color. Co-Authored-By: Claude Opus 5.5 <[email protected]>
ae25b6f to
e1b4bca
Compare
the reading effect depended on latestMessageId, so every message appended to the open list tore down its observers and listeners, disposed queued observe leases, and remeasured synchronously in the passive effect, forcing layout. read the latest id at measurement time instead; the list mutation already remeasures in the next frame. Co-Authored-By: Claude Opus 5.5 <[email protected]>
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord’s automated source review via Wes’s account, with independent Rocket, Mantis and Groot coverage.
Reviewed head 465dad8c0466b374d84cd3c4f2c2a72484d3ffb6 against base 8c0cb0c1efb68068b89c813ad6f890d8b8f07401.
No actionable findings. The source preserves dwell/catch-up target checks and lease cancellation on lifecycle changes, reuses the same allowed-channel predicate for reaction pruning, and retains the reaction self-ring tokens with separate button focus styling. The added test checks observer continuity and catch-up after the new row is measured.
Existing limitation: continuous list mutations less than 300 ms apart still restart dwell. This change removes redundant effect restarts; it does not eliminate that starvation case.
Source-only review: no tests, app workflows, CI checks or performance measurements were run. The reported benchmark improvements, rendered outline equivalence and forced-colors behavior remain unverified.
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 bottomId reads the latest id at measure time through useEffectEvent, so a new message no longer tears down observation, and session/channel/root changes still restart it. the reaction prune now checks the same allowedChannel set it just built, and the outline keeps the self-reaction tokens and the forced-colors ring. LGTM. not a blocker, just worth knowing: mutations less than 300 ms apart still restart the dwell, this removes the extra effect restarts on top of that
…leanup * origin/main: test(mentions): serve the mentions fixture as a build so cold loads fit (#2776) test(browser): let a late publication land after its page closed (#2565) test(relay): wait for the verified owner before a guest edits an agent (#2413) test: wait for the companion default width measurement (#2750) test(browser): preserve conversation scroll while revealing actions (#2715) test(browser): wait for link hit-testing after thread close (#2792) perf(messages): skip micromark for plain prose bodies (#2780) perf(inbox): load the emoji name table on first use (#2774) perf(messages): cut main-thread work while a busy channel is open (#2729) Refine agent Attention cards, watch details, and status controls (#2717) fix(bestie): remove a Bestie from its agent card on any device (#2720) feat(agents): preview Builderlab agent card links with Add to Buzz (#2719)
…h-up dwell (#2837) ## Problem `ThreadPanel.test.tsx` › "observes a visible reply before its root resolves, then enables thread catch-up" fails deterministically on main since fca0629 (#2729). Bisect: passes at `fca0629fb^`, fails at `fca0629fb` and at the current main tip. ## Cause #2729 reads `latestMessageId` through `useEffectEvent` instead of restarting the observation effect. When the root resolves, its row measures in the next frame. That list mutation triggers `reflow`, which cancels and re-arms the 300 ms dwell one frame later. Catch-up still fires exactly once, one frame later than the test expected. ## Change - The test advances the extra measurement frame before asserting the 299 ms / 300 ms dwell boundary. No product code changes. ## Checks - `vitest run src/features/messages/ThreadPanel.test.tsx`: 91/91 pass, 3 runs. - `biome check` clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Kalvin Chau <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]> * origin/main: test(messages): wait for the row measurement frame before thread catch-up dwell (#2837) Keep agent coordination history readable after work finishes (#2454) test(mentions): serve the mentions fixture as a build so cold loads fit (#2776) test(browser): let a late publication land after its page closed (#2565) test(relay): wait for the verified owner before a guest edits an agent (#2413) test: wait for the companion default width measurement (#2750) test(browser): preserve conversation scroll while revealing actions (#2715) test(browser): wait for link hit-testing after thread close (#2792) perf(messages): skip micromark for plain prose bodies (#2780) perf(inbox): load the emoji name table on first use (#2774) perf(messages): cut main-thread work while a busy channel is open (#2729) Refine agent Attention cards, watch details, and status controls (#2717) fix(bestie): remove a Bestie from its agent card on any device (#2720) feat(agents): preview Builderlab agent card links with Add to Buzz (#2719) Restore macOS Cmd-W window closing when no tab handles it (#2443) Make agent transcripts quieter and link finished replies to activity (#2424)
…ve-enter * origin/main: (21 commits) Make drafts and sent part of Messages (#2728) fix(canvas): render every Canvas within the save limit (#2839) test(messages): wait for the row measurement frame before thread catch-up dwell (#2837) Keep agent coordination history readable after work finishes (#2454) test(mentions): serve the mentions fixture as a build so cold loads fit (#2776) test(browser): let a late publication land after its page closed (#2565) test(relay): wait for the verified owner before a guest edits an agent (#2413) test: wait for the companion default width measurement (#2750) test(browser): preserve conversation scroll while revealing actions (#2715) test(browser): wait for link hit-testing after thread close (#2792) perf(messages): skip micromark for plain prose bodies (#2780) perf(inbox): load the emoji name table on first use (#2774) perf(messages): cut main-thread work while a busy channel is open (#2729) Refine agent Attention cards, watch details, and status controls (#2717) fix(bestie): remove a Bestie from its agent card on any device (#2720) feat(agents): preview Builderlab agent card links with Add to Buzz (#2719) Restore macOS Cmd-W window closing when no tab handles it (#2443) Make agent transcripts quieter and link finished replies to activity (#2424) fix(channels): wrap unread conversation stepping around the sidebar (#2682) fix(navigation): let bare buzz:// open Buzz without a broken-link notice (#2718) ...
Co-authored-by: BuzzPRReviewAssistant1 <a581a7e8115222acdc32ffb14bfce55d48c24478d98e155f8eba126b69b83c7b@buzz.block.builderlab.xyz> * origin/main: (71 commits) test(activity): scope preview assertions to the request row (#2843) perf(search): fold people names once across palette keystrokes (#2570) fix(github): keep PR watches running when localStorage is full (#2699) Make drafts and sent part of Messages (#2728) fix(canvas): render every Canvas within the save limit (#2839) test(messages): wait for the row measurement frame before thread catch-up dwell (#2837) Keep agent coordination history readable after work finishes (#2454) test(mentions): serve the mentions fixture as a build so cold loads fit (#2776) test(browser): let a late publication land after its page closed (#2565) test(relay): wait for the verified owner before a guest edits an agent (#2413) test: wait for the companion default width measurement (#2750) test(browser): preserve conversation scroll while revealing actions (#2715) test(browser): wait for link hit-testing after thread close (#2792) perf(messages): skip micromark for plain prose bodies (#2780) perf(inbox): load the emoji name table on first use (#2774) perf(messages): cut main-thread work while a busy channel is open (#2729) Refine agent Attention cards, watch details, and status controls (#2717) fix(bestie): remove a Bestie from its agent card on any device (#2720) feat(agents): preview Builderlab agent card links with Add to Buzz (#2719) Restore macOS Cmd-W window closing when no tab handles it (#2443) ... Signed-off-by: Ravneet Arora <[email protected]>
…ime-hint * upstream/main: (26 commits) Make drafts and sent part of Messages (block#2728) fix(canvas): render every Canvas within the save limit (block#2839) test(messages): wait for the row measurement frame before thread catch-up dwell (block#2837) Keep agent coordination history readable after work finishes (block#2454) test(mentions): serve the mentions fixture as a build so cold loads fit (block#2776) test(browser): let a late publication land after its page closed (block#2565) test(relay): wait for the verified owner before a guest edits an agent (block#2413) test: wait for the companion default width measurement (block#2750) test(browser): preserve conversation scroll while revealing actions (block#2715) test(browser): wait for link hit-testing after thread close (block#2792) perf(messages): skip micromark for plain prose bodies (block#2780) perf(inbox): load the emoji name table on first use (block#2774) perf(messages): cut main-thread work while a busy channel is open (block#2729) Refine agent Attention cards, watch details, and status controls (block#2717) fix(bestie): remove a Bestie from its agent card on any device (block#2720) feat(agents): preview Builderlab agent card links with Add to Buzz (block#2719) Restore macOS Cmd-W window closing when no tab handles it (block#2443) Make agent transcripts quieter and link finished replies to activity (block#2424) fix(channels): wrap unread conversation stepping around the sidebar (block#2682) fix(navigation): let bare buzz:// open Buzz without a broken-link notice (block#2718) ... Signed-off-by: Cheng Peng <[email protected]> Co-authored-by: Cursor <[email protected]>
## Failing test `src/app/navigation.test.tsx` "keeps a return path for addressed plugin settings and retires it on Close" (added by #2491) fails intermittently in the Vitest flake-check lane: ``` Error: Test timed out in 5000ms. ``` It is one long journey on a full `App` mount with the default 5000 ms timeout: open the Plugins list, open Pair mobile, Back, Forward, open Appearance, open Pair mobile, Close, Back, Back. ## Why this is a timeout flake, not a race The test is slow all the time, and every failure is the same timeout. No assertion has failed. - **Main:** 25 green `JavaScript` shard runs since #2491 took 2148–4414 ms (median 3921 ms). That leaves under 1.2 s of headroom. - **Flake-check failures:** each one was a cold iteration 1 that ran past 5000 ms. The same test then passed iterations 2–5 at its usual speed, with no product or test change between them. | Run | Iter 1 | Iters 2–5 | |---|---|---| | #2718 [42217828904](https://github.com/block/buzz-app/actions/runs/42217828904), Flake check 1/2 | **× 5045 ms** | 4061–4133 ms | | #2718, Flake check 2/2 | 3796 ms | 3377–3581 ms | | #2729 [42194107421](https://github.com/block/buzz-app/actions/runs/42194107421) attempt 1 | **× 5055 ms** | 3973–4399 ms | | #2729 attempt 2 | **× 5224 ms** | 4016–4423 ms | - **Neither PR touches this test's path.** #2718 changes deep-link ingress and #2729 changes messages and relay code. The flake check selected this file only through imports (`navigation.test.tsx` → `services.ts` → `deep-links.ts`). #2729's attempt 3 passed. - **Local near-miss:** in the local baseline below, one iteration on unmodified `main` took 4920 ms. ## What this changes The test is split where its title already splits. Each new test mounts its own `App` with the file's usual per-test setup. The 5000 ms timeout is unchanged. 1. **"keeps a return path for addressed plugin settings through Back and Forward":** open the list, open Pair mobile, Back, Forward. These are the original's first-half assertions, verbatim. 2. **"retires every visit to addressed plugin settings on Close":** open the list, open Pair mobile, open Appearance, open Pair mobile, Close, Back, Back. These are the original's second-half assertions, verbatim. - The test keeps one earlier Pair mobile visit behind Appearance. `dismissPluginDetails` retires *every* matching visit, and the final Back landing on the list is what proves it. - Without the earlier visit, that last assertion is vacuous. With Close mutated to retire only the current visit (`navigation.dismiss([navigation.snapshot().entry.id], settings)`): | Version | Result with the mutated Close | |---|---| | Original test | fails | | Split without the earlier visit | **passes** (misses the regression) | | This split | fails | Every assertion in the original test is kept in one of the two tests. Production code is unchanged. ## Verification All runs were local, on macOS (16 cores). The flake-check runs happened while the machine was under heavy unrelated load (load average 35–40). - `tsc --noEmit` and `biome check --error-on-warnings src/app/navigation.test.tsx` pass. - Each new test passes run alone (`-t`), and the whole file passes under `--sequence.shuffle` with seeds 1–3. Seed 2 runs the Close test first. **Per-test timings (ms).** The command was `BUZZ_TEST_WORKERS=2 ./bin/node scripts/flake-check.mjs vitest --iterations=5 <files>`. The file list is the same 15-file selection as #2718's failing lane. | Test | 1 | 2 | 3 | 4 | 5 | |---|---|---|---|---|---| | Before: original (on `3bcf1c4bb`) | 2407 | 1994 | 2487 | 2037 | 4920 | | After: Back and Forward | 1399 | 2071 | 1538 | 906 | 1384 | | After: Close | 1951 | 2966 | 2131 | 1496 | 1801 | Running `navigation.test.tsx` alone the same way: Back and Forward took 848–1595 ms and Close took 1352–2114 ms, with 0 of 5 iterations failing. **Headroom.** The local worst case drops from 4920 to 2966 ms. The Close half is the longer one, at about 0.75–0.8× the original's local time. Scaled to CI's 3921 ms median, it should land near 3 s, about 1.8–2 s under the timeout instead of about 1.1 s. That is an estimate. This PR's own flake-check lane will repeat the edited file and give the real CI numbers. **Unrelated local failures.** Each 15-file run had one failure in `src/app/entity-navigation.test.tsx`, which this PR does not touch, and both happened under the load above: - Before: "opens Inbox for a malformed launch link when the catalog settles after the client" failed a `findByRole` wait. - After: "normalizes old Bestie links … retaining { …(2) } scope" timed out at 5608 ms. No browser journeys were added or removed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Matt Toohey <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
Problem
Three costs add up while a busy channel is open and new messages arrive.
useReadinglistslatestMessageIdas an effect dependency, so each new latest message tears down the effect and runs it again. That disposes the reading lease, the observers and the listeners, then measures rows synchronously in the passive-effect phase (getClientRects/getBoundingClientRect). The result is a forced layout per message, and a dwell that new messages arriving faster than 300 ms never let complete.allowed(item.channelId). Each call rescans the list and runsmembers.includes(viewer), even though the callback has just built the same allowed set.box-shadow, which adds paint-bound long-animation-frame blocking while the pointer rests on a message.Change
This PR combines three changes:
useReadingreads the latest message id throughuseEffectEventat measurement time, and it is no longer an effect dependency. A new latest message already mutates the list, and the existing mutation observer remeasures it in the next frame.next.has(item.channelId), the set built a few lines above from the same list snapshot and predicate.outline: 1px solidandoutline-offset: -1pxinstead of the inset shadow. It uses the same color variables, and in forced-colors mode onlyoutline-color: Highlightis set.No behavior changes
catchUpitself still skips when a newer row exists and revalidates its target.rootId,channelId,sessionandenabledstill restart the effect.nextholds exactly the idsallowedwould accept, built from the same snapshot with noawaitbefore the filter.loadandeventVisibilitystill callallowed.border-radiusfrom Safari 16.4 / WebKitGTK 2.40.Evidence
Main
8c0cb0c1eand this PR were interleaved for 6 rounds on 4 Linux and 5 macOS workstations. Each workstation's median is taken first, and the table shows the mean of those medians.Lower is better. Blocking is long-animation-frame blocking time. Every workstation moved the same way on both blocking rows (per box: Linux −77.0% to −77.9% and −88.1% to −91.9%; macOS −100% on all five). Neither variant blocks in the at-rest run.
Checks:
messagesvitest suites pass (96 files, 2,835 tests).inbox-reactionspasses (10 tests).tsc,biomeandcheck-contrastpass.🤖 Generated with Claude Code