mirror of
https://github.com/block/buzz.git
synced 2026-11-06 17:31:19 +00:00
Problem:
Second review of the branch (warning, regression vs main) on a2a9767de
("fix(desktop): end an own send's freeze hold when its row lands"),
useAnchoredScroll.ts and ChannelPane.tsx. The hold can still go stale
when a send never adds a row to this timeline.
`ChannelPane.handleSendMessage` armed the pin and the hold before it
awaited `onSendMessage`, for every main-composer send. That includes a
DM send that `useMentionSendFlow`'s `onPrepareSendChannel` moves to
another DM (the message mentions an agent who isn't in this one). Its
optimistic row goes to that channel's window, and `goChannel` runs only
after the send succeeds. If the publish is rejected or times out, the
user stays on the original DM with both flags armed. A same-channel send
whose `onMutate` can't resolve the channel also adds no row. Then the
reader scrolls up, the hold skips the freeze, the next arrival from the
other person becomes the tail, and the stale pin moves the reader to the
bottom with no pill. The reviewer's jsdom repro: at HEAD, no pill and
scrollTop 660 -> 956. At the merge base, "1 new message" and 660 stays.
A failed send to the same channel was fine: its optimistic row lands in
`onMutate`, an append that clears the hold, and is then reverted.
Fix:
- `ChannelPane.handleSendMessage` arms only when the send targets this
channel (`!channelId || channelId === activeChannelId`). A send
prepared into another channel lands its row there, and the pane
navigates to it once the send succeeds.
- `MessageTimelineHandle.cancelScrollToBottomOnNextUpdate` disarms a
send. `useAnchoredScroll` clears the pin and the pending-send flag.
The hold skipped every freeze while the send was pending, including
one queued before the send that the arm cancelled. So if the send was
still pending and Virtua's last report has the reader off the floor,
the cancel queues that freeze, as the report would have without the
hold. Later arrivals then wait behind the pill. If the send's row
already landed, the hold had ended and the cancel only clears the
pin.
- `ChannelPane` arms through `armOwnSend`, which returns the disarm. The
main composer calls it when `onSendMessage` rejects, and the Welcome
"create in chat" flow when its `mutateAsync` rejects. Its `beforeSend`
option now returns the disarm. A generation counter keeps a late
failure from disarming a pin that a later send armed.
Validation:
- `MessageTimeline.ownSend.jsdom-test.mjs` adds the reviewer's no-row
case in three orders: fail before the reader scrolls up, after, and
with the freeze queued before the send. Each expects "1 new message"
and the reader's offset unchanged after a new tail arrives. With the
cancel a no-op (HEAD's behavior), all three fail: no pill, and the
reader moves from 660 to 956. With the cancel clearing the flags but
not queueing the freeze, the two orders where the reader left the
floor during the send fail with "Jump to latest". A fourth case, a
same-channel send whose row lands and is then reverted, still shows
"1 new message" and keeps the offset. It passes on HEAD as well,
because it pins behavior that must not change. 7/7 pass.
- `useAnchoredScroll.lifecycle.test.mjs` adds a hook case: arm, cancel,
the reader leaves the floor, a new tail arrives without a jump, and a
later send still pins. It fails if the cancel is a no-op, or if it
clears only the pending flag. useAnchoredScroll unit tests: 45/45.
- Full desktop `pnpm test`: 6934 unit and 183 jsdom tests pass.
- `tsc --noEmit`, `biome check --error-on-warnings` on the changed files
and `git diff --check` are clean.
- No test renders `ChannelPane`, so its arm condition, the rejection
handlers and the generation counter are covered only by tsc and
review. No browser E2E was run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
(cherry picked from commit 159a87b411947e390e39c5bf94376bea9400a4fd)
Buzz
Desktop chat shell with:
- Tauri + React + TypeScript + Vite
- Tailwind CSS
- shadcn/ui-ready shared components
- Biome (lint/format/check)
- Feature-driven frontend structure
Scripts
pnpm dev- run the web frontendpnpm tauri dev- run the desktop apppnpm build- typecheck and build frontendpnpm typecheck- TypeScript checkspnpm lint- Biome lintpnpm format- Biome format (write)pnpm check- Biome check
Structure
src/shared- reusable app-wide code (ui,lib,styles)src/features- feature modules (vertical slices)src/app- top-level app composition