Repository navigation
Conversation
jawspeak
force-pushed
the
design/popout-window
branch
from
November 7, 2026 03:17
d1ff354 to
8ee3b9d
Compare
jawspeak
force-pushed
the
design/popout-window
branch
5 times, most recently
from
November 10, 2026 23:04
4a130fb to
8eecc7e
Compare
jawspeak
marked this pull request as ready for review
November 10, 2026 23:05
Author
|
🤖 @block/buzz-oss-team Could a core maintainer pick up code-owner review for this PR and approve the pending fork CI run? The author cannot add the team through GitHub’s reviewer picker due to permissions. CI run: https://github.com/block/buzz-app/actions/runs/42920275467 |
Proposal only; no behavior change. Compares Slack's Open in new window, records Buzz's Huddle companion precedent and main-only native authority, and recommends a sliced presentation-companion approach. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
Adopt the composer's existing draft-conflict contract, make Open in main a focus (not a move), add per-window binding/incarnation, a real thread read projection, reading-lease based unread, and cite Slack's official help. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
…exits, delayed reading IPC Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
Presentation-only companion windows (popout-<uuid>) modeled on the Huddle window: main owns identity, relay, signing and read state; each window is bound to one target and session incarnation; watch/action/download are gated per label; guarded close asks the companion; Cmd+W closes a focused pop-out; a main renderer reload detaches companions instead of leaving them able to act. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
Main-window owner projects a session-owned thread view into a pop-out companion, reusing the Huddle composer bridge (now with thread reply and thread draft keys) and a shared companion row projection/renderer. The companion mounts the real composer, reports held work for native close guards, and measures reading with useReading over a lease proxy whose leases live in main. Entry point: message menu → Open thread in new window. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
…d S1 deviations Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
- Reading moves to a request/response BroadcastChannel bridge: a companion observation completes only when main's read-state save is durable, so the earned lease outlives the save; a disposed lease answers no late request. - Replies are offered and admitted only once the exact verified root of the thread is loaded in its channel; channel availability stays separate. - A window is owned before native creation: scope change or disposal during the await retires it and destroys what native created; failed creation releases the reader and composer. - Web links in a pop-out go to the OS opener; only buzz: links go to main. - Image review hides Copy image (no media_copy_image authority). - Linux/Windows: closing main destroys unguarded companions and asks guarded ones first; main destruction closes every companion. - Native tests: caller gating, per-label download isolation, guarded close interception, capability contents. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
… before closing any companion A companion lease request carries the read-intent revision its event saw; main grants it only while that revision is current, so intent formed before a manual unread cannot become a new read when it arrives late. Views carry main's revision and refresh on unread changes. On Linux/Windows main close asks guarded companions first and closes nothing until accepted. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
… thread tabs
FOUNDATION edit authorized by jaw (thread buzz-feature-popout-window,
2026-11-04): conversation.registerTabAction adds an entry to a conversation
tab's menu after the host's own actions, shown while its matcher passes for
that tab's {channelId, threadRootId?} target. The Pop-out windows plugin
registers Open in new window for thread tabs.
Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz>
Signed-off-by: Jonathan Andrew Wolter <[email protected]>
…; pop-out windows register theirs FOUNDATION edits authorized by jaw (thread buzz-feature-popout-window, 2026-11-04). The communities service owns exit holds: select() waits as a pending selection while a registered feature holds work a community exit would discard, and runs its continuation only once selected. The shell shows one Switch community? dialog for pending selections and, through the existing single-slot departure guard composed with unsaved Settings, for navigations that would change community before their route commits; Keep working preserves route and community. Navigation-driven selection forces because the guard already asked; the invite path sequences its open after selection; the sign-out confirmation lists the holds. Pop-out windows report held companions (native now forwards the guard action) and register them through communityReader.holdExit. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
…vigation; join keeps selection pending; organization sign-out asks Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
…ts session revision Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
…enabled by default The desktop catalog comes from the native plugin manager, not the frontend bundled list; without this the plugin never activated on desktop. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
…app resolves The dark window background used the hover step (#2e2e2e); dark --bg-app is --neutral-2 (#101010). The pinning test now resolves --bg-app's alias chain per mode from tokens.css instead of searching for one token anywhere. The command-capability test gains popout.json, which it had omitted since the pop-out commands were added. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
…nce too No second copy of the colour literals: a view reporting only dark builds a bare Appearance and asks it, so the pinned test covers that path as well. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
The header shows the icon the conversation's tab has (hash, lock, the DM's person, the Solo bubble; the thread mark for a thread frame) with an accessible kind label, and a DM is named by its people as tabs and Inbox name it. The native title carries the kind where no icon can: "#design", "Ada (DM)", "Planning (Solo chat)", "Thread in …". Main projects kind and the DM picture; the native DTO validates both. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
…lugin chat icons Signed-off-by: Jonathan Andrew Wolter <[email protected]>
Main projects a bounded member preview (count, up to three to picture — agents for a Solo chat, people otherwise, pictures first, names through the identity service) and requests their profiles as its own header does. The companion renders the same avatar stack; choosing it opens the conversation's members in the main window (the conversation itself for a Solo chat, which has no Members surface). New companion action openMembers. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
…ed before filtering AvatarStack takes an optional total so a sample of a larger set shows the right overflow without placeholder items. The projection requests the bounded roster's profiles as main's header does, so a late agent flag or picture changes the preview and count. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
Main projects the agents working in a frame's conversation from the same live evidence its indicator reads (current activity, stop controls, step and status text, thread scope), bounded for the native DTO. The companion renders the same incoming-message placeholder with the current step, a row per agent (View activity opens in main) and Stop. Stop is a composer-bridge operation admitted by the top frame, which re-derives the controls at that moment and refuses work that has already ended. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
…main's state owner
A conversation frame now includes work in its threads, as main's channel
indicator does (only a row's own live bubble takes it over there, which
companion rows never have). The stop operation answers as main's interrupt
does ({lifecycle, running}) and the companion drives useWorkStops with it:
lifecycle stops complete, hosted work waits the bounded retry window with
main's notices, nothing-running is reported.
Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz>
Signed-off-by: Jonathan Andrew Wolter <[email protected]>
A plugin's chat icon can now describe itself in data (ChatIcon.describer: describe + watch), so a host can draw it where the component cannot run. registerChatIcon takes the describer as a second argument and validates it. The GitHub plugin describes its linked pull request's state; the pop-out projects a Solo chat's first describing contribution, watches it while the frame lives, and the companion draws the design-system artwork with the sidebar's tone colours. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
…n; watches and lookup made safe Only the first chat icon contribution is shown, as in the sidebar; one that describes nothing leaves the default. Watches are reconciled on every projection, so a Solo chat converted to a channel releases its watch at once. The catalog lookup is an own-property check, so a prototype key in a projected token draws the default instead of failing the renderer. Merged pull requests take the sidebar's purple. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
…ontract now requires Post-rebase fixup: main's ReadStateStorage gained capture(). Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
…ut confirmation too While server discovery is pending or failed, the root account's own row is the only one shown; it now receives the exit holds like the listed rows, so a pop-out's unsent reply is named before signing out. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
…indow The channel header menu shared the sidebar's action builder and already had it; the Solo header built its own list. Contributed conversation actions now render through one shared ConversationActionItems in the Solo header and the Solo sidebar rows, right after Copy link in each. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
… too Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
… as its header does The Solo access dialog lives in the main-view header, not on a route. The pop-out shows the chat in main, then hands the header a session-fenced request (soloAccessRequests, like message-action handoffs) that only the header showing that chat in that session takes. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
… it, as main's row does Main presents work tied to a request message as a live bubble under that row (step, status, Stop) and keeps its footer indicator for agents no row presents; the pop-out projected only the footer, so a hosted agent's work on a request showed as "Only typing updates" with no Stop. Rows a live turn or local request names as trigger now carry live[], computed with main's requestWork/workStop; the companion renders the same bubble under the row; the footer leaves those agents to their rows; Stop admits row controls too. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
stopWork searched only the replies for row controls; a hosted turn whose retained trigger is the thread root showed Stop but refused it once its raw records were trimmed. The root is now searched with the rows; test covers a hosted turn with a retained trigger and no raw records. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
…channel window; the page no longer scrolls past its content Main's channel view shows a thread's live work on the thread root; the pop-out matched only the exact request message, which is not a channel row, so a request made in a thread had no bubble or Stop there. Live requests are now mapped to the shown root in conversation frames, with Stop scoped to the exact request. The companion document is pinned to the viewport (html/body/#root overflow hidden) so only the message list scrolls. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
With a waiting and a working request in one thread, the bubble showed the working one while Stop addressed the first local entry. requestScope now prefers working, then waiting, then failed, as requestWork does. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
… it, as main does Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
…editor once it connects The companion composer connects to main asynchronously, so a focus moved when the thread frame appeared landed on the log. The thread frame now rests focus on its log and passes that owner through MessageComposer's autoFocus handoff, which focuses the editor when it is ready and yields to any focus move meanwhile; covered with the real owner/client bridge. Co-authored-by: Jaw-fable51-pi <27b9430246cf506d04fedbbb39d0126ad17496a0e41bd82d39e35860de80f3d0@buzz.block.builderlab.xyz> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
Main saves each open pop-out's target, drilled frame and screen frame per origin:viewer partition and reopens them unfocused where they were once the relay session is ready and the roster still has the conversation. Closing a window forgets it; quitting keeps it. Native reports moves and resizes after they settle and sends no closure notice while the app exits, so a quit never clears the saved list. Co-authored-by: Jaw-fable51-claude <7b068d2e5c4ec472d16a8b1241b9d1431d28caef5fa40cd5c0f9c12344b9a08d@buzz.block.builderlab.xyz> Co-Authored-By: Claude Fable 5.1 <[email protected]> Signed-off-by: Jonathan Andrew Wolter <[email protected]> Co-authored-by: jaw-gpt56sol <ea4d48fe3c0a6de67bdc4128679b983e511642f2579429941cfcdf0eede927fb@buzz.block.builderlab.xyz>
jawspeak
force-pushed
the
design/popout-window
branch
from
November 11, 2026 22:24
8eecc7e to
7792ded
Compare
The pop-out showed every working agent's row and Stop in an always-visible card under the response preview. Main shows only the compact step cue and opens its "Working now" chooser on hover or click. Move that chooser out of TypingIndicator into a shared AgentChooser and render it from CompanionWorking, so both windows share one implementation and the same rows, Stop controls and dismissal. Co-authored-by: Jaw-fable51-claude <7b068d2e5c4ec472d16a8b1241b9d1431d28caef5fa40cd5c0f9c12344b9a08d@buzz.block.builderlab.xyz> Co-Authored-By: Claude Fable 5.1 <[email protected]> Signed-off-by: Jonathan Andrew Wolter <[email protected]>
kazemek21
added a commit
that referenced
this pull request
Nov 12, 2026
…ments (#3322) ## TL;DR Plugins can now register a headless composer annotator with `ctx.conversation.registerAnnotator`. The host gives it the focused draft, validates the ranges it publishes, draws the marks, and replaces text only when the person picks one of the offered replacements, as one undo step. Checking, language, scheduling, timeouts and wording all stay in the plugin, so a spelling, grammar or style checker can ship as a standalone plugin. ## API ```ts ctx.conversation.registerAnnotator({ id, title, component }); // component: ComponentType<CompletionContext & { // observation: { revision, text, prose: { start, end }[] }; // frozen, one per document // publish(annotations: ComposerAnnotation[]): (() => void) | false; // }> // ComposerAnnotation = { start, end, text, label, kind?: "spelling" | "grammar", replacements?: { text, label? }[] } ``` The types are exported from `@buzz/author`. The full contract is in `docs/plugin-architecture.md` under "Composer annotators". **Who owns what.** | Host | Plugin | |---|---| | Mounting per focused composer, destination and enablement; revoking `publish` on change, removal or render failure | What to check, language, ignore rules, tokenization | | Draft projection, prose ranges and UTF-16 mapping | Debounce, timeouts, retries, failure policy | | Validating revision, expected text, prose containment, grapheme boundaries, labels and replacements; caps | Results, labels, replacement choice and ranking | | Underline, `aria-invalid`, right-click / ⌘. menu, focus return, one-step Undo, IME and read-only refusal | — | **Disclosure.** An annotator sees what completion providers already see, under the same eligibility (focused, enabled, outbox-capable composer), plus the prose ranges. It never sees the caret, and no observation is offered while unfocused, disabled, read-only or composing. **Publishing.** `publish` is bound to one observation. It returns `false` once the draft changed, the destination or enablement changed, the annotator was removed or failed to render, or the value is not an array. Otherwise it replaces that annotator's marks and returns a disposer that withdraws only them. A disposer for an earlier draft leaves the mapped underlines in place until the next publication, so per-observation cleanup does not flicker. The menu closes when its annotation is withdrawn or replaced, even for the same draft. **Validation and bounds.** - An annotation must match its text exactly, lie inside one prose range, not split a surrogate pair or grapheme cluster, and carry a 1–120 unit label. - Replacements: at most 5, distinct, at most 64 units, different from the marked text. An empty replacement deletes the range and needs a label. - No control, bidi or line-separator characters and no lone surrogates. - At most 200 annotations per publication and 400 marks per editor; earlier annotators win overlaps; at most one redraw per animation frame. **Applying a replacement.** The host rechecks revision, range, text, editability and composition; refuses mixed formatting; keeps the range's formatting; and closes history on both sides so typing, the replacement and further typing undo as three steps. ## Review notes - Adds an extension point to three `FOUNDATION` files: `conversation/contracts.ts`, `conversation/service.tsx` and `plugins/author.ts`. The change is additive and needs FOUNDATION review. - The native spellcheck default and autocorrection preference are unchanged. - Related open work this does not overlap: native spellcheck default (#2201, #2546), a generic draft read/replace API (#2656), other composer work (#2983, #3312). Other open PRs touching the same composer files: #2988, #3260, #3257, #2896. ## Tests - `composer-annotations.test.ts` (Node): prose-range exclusions, same-text revisions, range/grapheme/label/replacement validation and caps, overlap and merge, mark-preserving replacement and deletion with Undo/Redo, stale and mixed-format refusal, composition mapping, the hidden mark on the word being typed. - `ComposerAnnotators.test.tsx` (jsdom, annotator registered through the real `PluginRuntime` and `ConversationService`): observation shape and context, emoji offsets, keyboard and right-click menus, Escape and focus return, replacement with Undo/Redo, `aria-invalid` kinds, stale and invalid publications, disposer ownership, menu closing on same-draft withdrawal or replacement, earlier underlines kept across a stale disposer, an immediately publishing annotator surviving a destination change, same-text structural change, retirement on disable, destination change, plugin removal and render failure, IME composition, disabled and read-only editors, unmount. - `MessageComposer.test.tsx`: the real composer wiring, including retirement on a destination change. - No browser journey was added; jsdom covers the contract and the remaining gaps are native-only. On a Linux remote box against this head rebased on current `main`: `pnpm check` (Biome, `tsc`, design typecheck and design checks) passed, and `vitest run` over `src/features/conversation`, `src/plugins`, `EditableInput`, `MessageComposer` and `composer-annotations` passed (35 files, 1374 tests). The four new lifecycle tests fail against the previous source and pass with the fix. The local pre-push hook was skipped because CI runs the same checks. **Not yet verified:** - right-click selection and context-menu behavior in native WebKit; - whether ⌘. reaches the webview on macOS; - screen-reader announcement of `aria-invalid`; - real IME composition in the desktop app; - both themes, narrow and wide widths, and enlarged text, with a real annotator installed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Kyle Zemek <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
|
Just stopping by to say I would love this feature and hope it gets approved. :-) |
matt2e
added a commit
that referenced
this pull request
Nov 14, 2026
## Why `user-status.spec.mjs`, "statuses edit, synchronize, clear, reject stale traffic and retain failed drafts", fails intermittently on WebKit with an unexplained page error: `Unhandled Promise Rejection: TypeError: Importing a module script failed.` | Sighting | Lane | |---|---| | [9 Nov, PR](https://github.com/block/buzz-app/actions/runs/42573912834/job/129017542663), and again on [its re-run](https://github.com/block/buzz-app/actions/runs/42573912834/job/129030209036) | Flake check, WebKit 2/11 | | [10 Nov, main](https://github.com/block/buzz-app/actions/runs/42766553248/job/129664645706) | Browser journeys, WebKit 9/18 | | [11 Nov, PR](https://github.com/block/buzz-app/actions/runs/42981535351/job/130393321764) | Flake check, WebKit 2/12 | The spec's check for unexplained page errors is deliberate and correct. The bug is in the product. ## Root cause `useReactionName` and `ReactionEmoji`'s `revealName` both ran `void import("./reaction-name").then(...)` with no rejection handler. `reaction-name.ts` statically imports the emoji-mart native table, so the first Unicode status or reaction name starts a large chunk load. When a navigation lands while that load is in flight, WebKit cancels the request, the `import()` rejects, and the page reports an unhandled rejection. In the journey, the click that opens a free-text status starts the load and the next `page.goto` cancels it. The traces show the chunk request failing with "Load request cancelled" and the page error 4 ms after the navigation. The unguarded import arrived with #2384 and moved into `useReactionName` in #2774. ## Fix Handle the rejection at both sites. - A failed load leaves the name unresolved. In the hook the next mount tries again. `ReactionEmoji` keeps the raw content and the next hover or focus tries again. - Both handlers `console.warn` with the error, so a chunk that keeps failing, such as a 404 after a deploy with a stale shell, stays visible. The comments note that only a new document fetches the chunk again, because some engines keep a failed module fetch for the document's life. The warning does not affect browser checks: the fixture records only error-level console messages, and the page-error helper watches `pageerror`. There is no spec or fixture change, and the page-error check stays as it is. The second commit is the review follow-up that replaced the first commit's empty handlers with the warning and made the two unit tests wait for it. ## Tests - `use-reaction-name.test.tsx` (new) and a `MessageReactions.test.tsx` case mock `./reaction-name` to reject. They assert the name stays unresolved, that the warning is logged, and that no unhandled rejection reaches the process. They then unmock and show the next mount or reveal resolves. - On the unfixed code both fail with the unhandled rejection. ## Evidence | Scope | Result | |---|---| | This branch's head, macOS: `bin/pnpm exec vitest run src/features/messages/use-reaction-name.test.tsx src/features/messages/MessageReactions.test.tsx` | 2 files, 29 passed | | Before the split from a combined branch, Linux: the same two files | 27 passed in 8 of 8 runs, no stderr output | | Linux, WebKit: `user-status.spec.mjs`, `--workers=1 --repeat-each=8` | 8/8 passed, 11.7 to 12.7 s each | | Triage, WebKit, with the native table request held across the navigation | unfixed 3/3 failed with the CI error, fixed 3/3 passed | | Before the split, Linux: Biome on the touched files, `tsc --noEmit` | clean | The count is 29 here and 27 in the commit messages because #3397 has since added tests to `MessageReactions.test.tsx`. ## Not reproduced, not run - The cancelled chunk did not reproduce on an idle host without holding the request, so the WebKit repeat guards against regression rather than reproducing CI timing. - Not tried in the desktop app. The user-visible change is the absence of an unhandled rejection, plus a console warning when the chunk fails. - The local pre-push checks for this branch (whole-project type check, related unit tests, design-system check) were not run. The two-file run in the first row is the only check run on this branch's head. The type check and Biome results are from the combined branch, where the same change passed. - No human has tested this yet, so `buzz-review-completed` is not set. ## Left alone - A second, unrelated failure of the same journey ("Duration: Today" read as "8 hours" at exactly 16:00 UTC) is fixed in its own PR. - In the persistent-failure case each hover or new mount logs another warning. That is bounded and only visible in developer tools. - Open PRs #2896, #2120, #2085 and #2009 edit other parts of `MessageReactions.tsx`, and #2794, #2154 and #2120 edit its test file. A trial merge of each against this change is clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: mtoohey <[email protected]> Signed-off-by: Matt Toohey <[email protected]> Co-authored-by: Claude Fable 5.1 <[email protected]>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
tl;dr - carefully implemented to be very similar to slack pop-out windows that people expect. Video demo buzz://message?channel=ce6ec97e-b85e-4c84-8992-938a40185053&id=78a1f2ce060ccaecc049a6a1863f6dff2b5914ab757e7ce63389820ee13fe439&thread=a664e10a8bf8b84272b465aa8b17e7fccdf3a985852639ece895d31faf5933d4
Pop-out windows (Slack-style) — channel, DM, Solo and thread windows
Opens a channel, DM, Solo chat or thread in its own desktop window ("#channel — Buzz", "Thread in #channel — Buzz"), with shared drafts, message actions, member avatars, and live agent previews with Stop. A conversation window drills into one of its threads in place with a
<back control, as Slack's does. Design and scope checkpoints:docs/popout-windows-design.md(§10 S1, §11 S2, §12 S3; review logs in §9, §11, and §12). Help text:docs/buzz-help.md→ "Pop-out windows".Model
The pop-out is a companion projection (the Huddle companion-window precedent generalized): the main window is the sole signer, outbox, unread and session owner. The companion renders what main projects and asks main to perform bounded operations against the rows it shows — reply, react, edit/delete/send-to-channel, read state, follow, report, plugin-action handoffs. Every operation ends in explicit terminal evidence acknowledged by the companion; absence is never success.
What's in
src-tauri/src/popout_window.rs,capabilities/popout.json,popout.html): per-window binding, bounded DTOs, guarded close (unsent work), ⌘W, focus-main, microphone allowed for pop-out labels on the app origin (Linux/Windows hook).⋯→ "Open thread in new window".<back); files kept across the drill; a recording in progress asks before leaving.buzz://links labelled as in main (Links plugin renderer, channel names, "‹author› in #‹channel›" bylines resolved by main and projected per row) in messages and composer decorations.Screenshots (native tryout)
Also in this PR
describeonregisterChatIcon.buzz://links label as in main; dark windows open dark; the Solo header's and side pane's menus; organization sign-out names held pop-out drafts.Not in a pop-out (documented)
The members/access dialog opens in the main window. Pop-outs do not restore after restart. Canvas, pages, Settings, and browser/mobile pop-outs are outside this change. Plugin chat icons apply to Solo chats; the community name is not shown in the header/title.
Verification
docs/popout-windows-design.md§9. Reviewer notes under the reviewer's workspace.8eecc7e, rebased ontomain741f3a8. The branch owner reported passing pre-push checks (types-and-related-unit-tests,design-system,rust-clippy) and targeted Rust window/microphone tests. Related-unit-test hooks do not establish a full Vitest-suite pass. The owner reportednative_mcp::tests::test_overlay_preserves_normal_capability_selectionalso fails locally on cleanorigin/main; that check remains unresolved locally.Pre-PR checklist (
AGENTS.md)docs/popout-windows-design.md.buzz://link formatting, dark-mode open, member avatars, agents at work + Stop, plugin chat icon, menus, sign-out with a held draft.buzz-review-completed
Generated with Codex