mirror of
https://github.com/block/buzz.git
synced 2026-10-30 22:11:36 +00:00
Slice A of the focus-mode agent conversation. Agent activity gets the same
full-cover presentation threads already had in focus mode, because its
transcript is tool calls, diffs, and command output — content a 380px side
pane cannot show usefully. It deliberately offers no split/focus toggle: at
any viewport wide enough for two panes it always covers.
The drawer surface is extracted from `FocusThreadDrawer` into a
presentation-only `CoverDrawer` that knows nothing about what it covers, so
the thread drawer and the activity drawer are one surface with two contents.
Motion, scrim, Escape ownership, and focus capture/restore all live there.
"Which panel" and "which presentation" now resolve together in
`channelAuxiliarySurface`, which is what makes the two cover drawers mutually
exclusive: there is one covered slot and the resolved surface owns it.
Two ordering hazards the surface has to handle, both covered by tests:
- Escape during a replacement. `AnimatePresence` keeps the outgoing drawer
mounted through its exit, capture-phase listeners fire in registration
order, and the outgoing one registered first — so it would swallow the press
via `stopImmediatePropagation` and the user would have to press Escape
twice. The drawer stands down for its whole exit, gated on presence rather
than the focus slot: a successor whose content takes focus never claims the
slot, which would leave the outgoing claim current for exactly the drawer
that should stand down.
- `StrictMode` replaying the focus-capture effect. By the replayed setup the
drawer has already focused itself, so an unconditional
`document.activeElement` read records the drawer as its own opener and a
real close focuses a detached node, stranding the keyboard on `<body>`. A
one-shot ref captures the opener exactly once.
Reconciled against main, where the idle auxiliary panel gained its own
full-cover presentation (#6590):
- `FocusThreadDrawer` keeps main's `label` prop and passes it through as
`CoverDrawer`'s `ariaLabel`; the idle panel reuses the presentation with its
own name, which is why the label is a prop and not the thread's constant.
- `outline-hidden` carries onto the extracted surface with the reason it
exists recorded — the drawer is `tabIndex={-1}` and focuses itself, so the
ring is noise.
- `useFocusIdleDrawer` sits outside `auxiliarySurface` on purpose: the idle
panel is caller-owned, is not in the last-opened-wins set, and
`idleAuxiliaryOverridesThread` lets it render ahead of a thread. It still
cannot contend for the covered slot, because `shouldUseFocusIdleDrawer`
requires every other candidate to be absent.
- main's `mode="wait"` on the presence boundary is kept. It closes the window
where two drawers are mounted together, but the drawer's presence and
focus-slot guards stay: they protect the surface, not this host's choice of
mode.
Squashed from ss-dev-00/agent-activity-cover (#6542) at 105a3654d.
Co-authored-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: ss-dev-00 <a02c4e0850e5e612b4ddf95dbe2f5c56467cf27c6552203bc833ff438fb31971@buzz.block.builderlab.xyz>