## Summary
Test-side fixes for ten desktop E2E flake clusters and six long-tail
rows from the 21–28 Oct 2026 CI audit. Each spec now waits for the state
it assumes (a rendered row, a mounted listener, a finished animation, a
held boot read), not a fixed delay or a one-shot probe. Two mock-bridge
seams support this.
The PR changes test code and mock-bridge seams only, apart from one
product constant extracted with its value unchanged. It is one of seven
PRs split out of the `flakey-tests` branch (siblings below).
### Related issue
None found.
- #8622, #8244 and #8386 touch some of these spec files, but not the
tests fixed here.
- §7 mode A (`navigation:472`) is left to open PR #6852, which already
sets the width and measures in one polled evaluate.
- Draft #8279 queues cold-launch nostr-bind links in the product. Its
mock returns null, so the §12 listener seam is still needed.
### Mock-bridge seams
- `__BUZZ_E2E_EVENT_LISTENER_COUNT__(event)` counts live **app**
listeners per Tauri event. It wraps the mocked
`plugin:event|listen`/`unlisten`. The spec-facing
`__TAURI_INTERNALS__.listen` passes a marker target that the wrapper
skips, so a spec's own subscription can't satisfy the wait. Tauri
2.11.1's mock ignores targets, so delivery is unchanged.
- `deferFirstChannelsRead` arms the existing channels-read hold at boot.
`__BUZZ_E2E_RELEASE_CHANNELS_READ__` releases it.
- `CREATE_PROJECT_REFOCUS_DELAY_MS` moves to `createProjectFormFocus.ts`
with its value unchanged (50 ms). Both `CreateProjectFormContent` and
`project-commit-detail.spec` import it.
### Flaky tests fixed
| Test | Failing runs | Cause | Change |
|---|---|---|---|
| §2 `overscroll-boundary.spec.ts:34` (and its sibling) | 39601763602,
40367495044, 40583830861, 40597484657, 40373404267 | The
`message-timeline` test id first lands on the loading skeleton, which
doesn't overflow, so the scroll lock rightly cancels the wheel. | Wait
for a rendered row, and content taller than the viewport, before
dispatching wheels. |
| §4 `project-conversation-load-failure.spec.ts:73` | 40367495044 (all 3
attempts), 40608383847, 40583830861; PRs 39541263507, 40509460168 | The
seeded root and reply were dated `now`. The panel's live REQ (`since =
floor(now)`) still replayed the reply, so the error card never appeared.
| Date both events a minute in the past, and correct the comment. |
| §6 `workflows.spec.ts` `createWorkflow` cluster (:222, :365, :297,
:525, :471, :406, :614, :997) | 14 of 50 main jobs; :222 ×4, e.g.
40353619362 | `AnimatePresence mode="wait"` keeps the trigger body
mounted through its exit, so "Message text" could fill the trigger's
field (:222 and the callers). For :297, `isVisible()` read "closed" one
frame before the dialog opened its own channel list. | Wait for the step
body's "Step details" control before filling. Create dialogs wait for
the list; only the duplicate dialog clicks it. |
| §12 `nostr-bind.spec.ts` (:156, :198, :228, :247, :260, :335, :417,
:447) | 8 main jobs, each test once | The spec emitted
`deep-link-nostr-bind` before the app subscribed, and the mock drops
events that have no listener. | Wait for
`__BUZZ_E2E_EVENT_LISTENER_COUNT__` to report the app listener before
emitting. |
| §15 `profile.spec.ts:1128` (integration) | 7 of 50 main jobs | The 320
ms motion-sampling window started before the click. | Start the window
when `activeTab` reads "runtime". |
| §17 `agent-availability.spec.ts:602`, both shutdown-failure variants
(integration) | 6 jobs (4 disconnected-online, 2 failed-online) | The
"shutdown refused" toast sits over the Delete row and pauses while the
pointer is on it, so the retry click is intercepted. | Assert the toast,
then retry with focus + Enter. The toaster has no close button. |
| §19 `virtualization.spec.ts:247` "08 — cascading older pages…" | 4 of
50 main jobs; PR 40605736093 (all 3 attempts) | The 120 ms and 400 ms
windows were timed from before the wheel bursts, and CDP can deliver
wheels late. | Count the wheels the page receives. Each window opens at
the first wheel and closes only after all have landed, then waits for
still frames. **A second, product-side mode remains** (see below). |
| §20 `message-copy-link.spec.ts:160` | 4 of 50 main jobs (5 failed
attempts), e.g. 40595483966 | The huddle row reused `"d".repeat(64)`,
which is a seeded ordinary `#general` message. | Use ids that no seed
uses, and emit only after `waitForMockChannelHeadReady`. |
| §21 `navigation.spec.ts:687` | 4 of 50 main jobs | The pending chip's
`boundingBox()` was read once, and can be null while the row mounts. |
Poll until the chip has a width. |
| §23 `sidebar-snapshot.spec.ts:358`, `:388`, `:438` | PR 40427822003
(all 3 attempts); never on main | The 500 ms loading-state budget
counted app boot, so a slow runner finished the channels read first. |
Hold the first channels read with `deferFirstChannelsRead`, assert the
loading state, then release it. |
| `project-commit-detail.spec.ts:259` | 1–2 CI failures (audit long
tail) | A 50 ms refocus timer can land mid-fill and type the description
into the name field. | Once the name field is focused, wait out a page
timer of `CREATE_PROJECT_REFOCUS_DELAY_MS`; equal-delay timers fire in
order. |
| `project-pr-review.spec.ts:3520` | 1–2 CI failures | The same timer
pattern in `CreateProjectWorkItemDialog`. | Wait for `toBeFocused()` on
the title, which only that timer can satisfy. |
| `thread-load-failure.spec.ts:79` | 1–2 CI failures |
`openWelcomeThread` branched on a one-shot `count()` and fell back to a
hover-only Reply click. | Wait for the summary row, then click it. |
| `virtualization.spec.ts:201` | 1–2 CI failures | The drop point sat 2
px above the target's bottom edge, after two fixed frames. | Drop at the
centre, after dnd-kit's live region names the target. |
| `channel-composer-overflow.spec.ts:217` | 1–2 CI failures |
Same-second emits sort by id, and the clearance was checked once. |
`createdAt: base + i` and `expect.poll`. |
| `scroll-history.spec.ts:1289` | 1–2 CI failures | A fixed 250 ms wait
before idle sampling. | Poll for coverage first. |
Local before/after counts are from the original `flakey-tests` commits
(e286a6ae0, 816c7c35e, 67185df6a). Each used a forcing that made
`main`'s copy fail with CI's signature:
| Test | Before | After |
|---|---|---|
| §2 | 3/15 | 15/15 |
| §4 | 0/10 | 10/10 |
| §6 :222 | 0/3 | 3/3 |
| §6 :297 | 0/3 | 3/3 |
| §12 | 0/3 | 3/3 |
| §15 | 0/3 | 3/3 |
| §17 | 1/4 | 4/4 |
| §19 late-wheel modes | 0/3 | 3/3 and 2/3 |
| §23 | 2/6 | 6/6 |
| project-commit-detail | 0/3 | 3/3 |
| project-pr-review | 0/3 | 3/3 |
§20, §21, `thread-load-failure:79`, `virtualization:201`,
`channel-composer-overflow:217` and `scroll-history:1289` were not
reproduced; they get the fix that matches their CI signature.
Not fixed (follow-up): `virtualization:247` also has a product-side
mode. In one frame after a prepend, `scrollHeight` can drop 458–639 px
while `scrollTop` moves only 50–114 px, so the anchor row jumps 382–457
px. That is lost anchor compensation, and this test can still fail on
it.
### Verification
This branch was pushed without local re-verification, by the user's
choice: local test runs were overloading the machine and failing for
that reason. **Hosted CI is the check.** It was pushed with `git push
--no-verify`, because the pre-push hook runs typecheck and the unit
suites. #8613's flake-check lane will also repeat the changed specs.
- Every file except `virtualization.spec.ts` is byte-identical to its
final version on `flakey-tests` (67185df6a). That file carries only this
PR's hunks; see "Merge order".
- Checked for this PR: `git diff --check`, biome on the partial spec (no
changes, the same as on `main`), and a conflict-free merge with all six
siblings onto `main`.
### Readiness
Test and mock-bridge change; the one product edit is a constant
extraction with its value unchanged. AGENTS.md's readiness steps (agent
review, then human confirmation) are still outstanding, so
`buzz-review-completed` is not set.
### Merge order
No required order. `desktop/tests/e2e/virtualization.spec.ts` is also
edited by **flakey-tests-desktop-races**, which adds one test after line
929; this PR's hunks are all above line 551. They don't touch, so the
two merge cleanly in either order, as checked with `git merge-tree`.
### Sibling PRs
Split from `flakey-tests` (one PR per area): #8676 (PostgreSQL lease +
lane), #8677 (relay E2E helper + Windows shell), #8678 (mobile), #8679
(Tauri apt cache + Windows Rust cap), #8680 (desktop product races),
#8682 (desktop E2E composer + row actions).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Jitter <2b2e6415e748c35180847a37aadae06a11e30dddc76b784e7fd9354c4eb42e7a@buzz.block.builderlab.xyz>
## Summary
This fixes the Desktop Smoke E2E failure that turned `main` red at
af7979e ([run](https://github.com/block/buzz/actions/runs/39295686771)).
The failing test was `video-attachment.spec.ts` "playback speed persists
across videos and reloads". The cause is a real timeline bug, not a
problem in the test.
When the reader leaves the bottom, `MessageTimeline` freezes the live
tail and sets a one-shot flag. The flag tells it to ignore the next "at
bottom" report, because freezing the tail can shorten Virtua's model and
produce a false one. If nothing is buffered, the freeze doesn't change
the model, so that false report never arrives. The flag then stays set
and swallows the reader's real return to the bottom. If the reader gets
back to the bottom in one scroll event, the tail stays frozen and new
messages wait behind a "1 new message" pill even though the reader is
already at the bottom. A scrollbar jump, the End key, or Playwright's
scroll-into-view retry all return in one event.
In CI, the Play click was intercepted, so Playwright retried it with
different scroll alignments. That moved the timeline away from the
bottom (scrollTop 430 → 289 → 430, seen in the run's trace), and the
second video message was buffered and never rendered.
Fix: once the freeze commits with nothing buffered, clear the flag. If
the reader is already back at the bottom, release the tail.
### Related issue
None found. No open PR touches this.
### Testing
- New smoke test `virtualization.spec.ts` "a single scroll back to the
bottom releases the live tail": failed 10/10 before the fix, passed
10/10 after (`--repeat-each=10`).
- `video-attachment.spec.ts` "playback speed persists…": 20/20 passed
with `--repeat-each=20`.
- Smoke runs of `virtualization`, `scroll-history`, `video-attachment`,
`messaging`, `smoke`, `file-attachment` and `thread-follow`: 188 passed.
3 failed with a blank page before the first click, and all 3 passed on
re-run. Unmodified `main` shows the same failure locally, so it's a
local preview-server flake and unrelated to this change.
- `pnpm typecheck`, `pnpm check`, and `pnpm test` (6829 + 160 tests) all
pass.
Signed-off-by: Fizz <6be7be23ec5bba868f3e1db5ec86013d76bd1a293dce5b435a192158fec13641@buzz.block.builderlab.xyz>
Co-authored-by: Fizz <6be7be23ec5bba868f3e1db5ec86013d76bd1a293dce5b435a192158fec13641@buzz.block.builderlab.xyz>
## Summary
- Makes Projects repository-aware across browsing, branch and tag
selection, local and remote source management, work items, commits, and
contextual repository actions.
- Refines project details into consistent single-column reading surfaces
with a resizable, section-aware context pod, while keeping project
conversations available in the attached chat panel.
- Adds persistent sidebar project navigation, direct entity links,
repository discussion channels, contributor identity matching, and
consistent loading and activity presentation.
- Keeps branch-specific controls on code-oriented sections while Tasks,
Reviews, and Channels remain repository-scoped, reducing misleading
context and actions.
Replacement for #5981 with an identical final tree flattened into one
signed-off commit because the required DCO check suite remained stalled.
### Related issue
N/A
### Testing
- [x] Desktop pre-push checks, TypeScript typecheck, and unit tests
- [x] E2E production build with `pnpm build:e2e`
- [x] Focused Playwright smoke coverage for project workspace, task,
review, commit, sidebar, and contextual-panel behavior
- [ ] Attach final before/after screenshots after design review
---------
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Wintermute <165f0c871dd2586bb18b6aa109eeaf57bb2132ff4d27b10120f4368a0f627022@buzz.block.builderlab.xyz>
Co-authored-by: Wintermute <165f0c871dd2586bb18b6aa109eeaf57bb2132ff4d27b10120f4368a0f627022@buzz.block.builderlab.xyz>
## Summary
- retire Virtua prepend reconciliation on every ordinary reader wheel
event, including events suppressed from its separate wheel-timing
heuristic
- keep Ctrl+wheel browser zoom excluded from reader ownership
- arm the Ctrl+wheel prepend probe before pagination begins so it cannot
miss the commit
- preserve ESM/CJS patch parity and update the patch lock hash
## Why main was red
PR #2855 added the reader-wheel retirement action after Virtua's
existing suppression guard. Once the first event set that guard, later
events in the same wheel burst returned before retiring prepend mode. A
late ResizeObserver correction could then pull the viewport backward by
20–40px. The same test had failed twice on #2855 but passed its final
retry, so the PR job appeared green; the merge commit lost all three
retries.
The separate Ctrl+wheel failure was a test race: its MutationObserver
was registered after the request had already been triggered and could
miss the prepend commit.
## Validation
- desktop full unit suite: 3,515 passed
- pre-push desktop check: passed
- `git diff --check`: passed
- CI is the E2E verification; no local E2E was run
Fixes the main-branch failure in CI run 30180099007.
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Summary
- make Virtua the sole prepend geometry/correction owner by accumulating
active prepend ResizeObserver corrections from the live DOM offset
- retire prepend reconciliation on ordinary reader wheel input, while
preserving it for Ctrl+wheel browser zoom
- remove Buzz's competing three-second semantic-anchor watcher and
corrective `scrollBy` loop
- keep ESM/CJS Virtua patch behavior equivalent and update the patch
lock hash
## Why
Buzz admitted prepended rows using seeded estimates, then Virtua
received multiple measurement corrections for the same transaction. Each
correction was based on the same stale model offset, so a later write
replaced an earlier correction instead of accumulating it. In the
reproduced first page, that resurrected 452px of anchor drift; the
app-level watcher merely corrected the lost virtualizer write afterward.
This fixes the correction inside Virtua and deletes the competing app
writer, following the single-owner geometry invariant used by Berd
rather than copying its spacer implementation.
## Validation
- watcher-off desktop virtualization matrix: 11/11 passed, including 15
cascading prepends, continued wheel input, detached rich-row growth,
channel switching, bottom follow, and buffered live arrivals
- focused cascading prepend/Ctrl+wheel regression passed
- desktop typecheck passed
- desktop unit suite passed: 3,495 tests
- Biome passed on changed desktop files
- `git diff --check` clean
## Manual behavior
Load older history repeatedly while scrolling upward, then wheel
downward during/after a prepend. The visible anchor should stay within
the existing 5px contract during reconciliation, and deliberate reader
movement should not be pulled back. Ctrl+wheel during the prepend commit
must not cancel reconciliation.
---------
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>