Repository navigation
Add complete reaction toggles to the message menu - #185
Conversation
b3c474c to
b618613
Compare
Co-authored-by: Carl <c217fe6b9d958f41c3a5e030dccc7f626775a923089cb6491305eade75ea1f1b@buzz.block.builderlab.xyz> Signed-off-by: Carl <c217fe6b9d958f41c3a5e030dccc7f626775a923089cb6491305eade75ea1f1b@buzz.block.builderlab.xyz>
Signed-off-by: Carl <c217fe6b9d958f41c3a5e030dccc7f626775a923089cb6491305eade75ea1f1b@buzz.block.builderlab.xyz>
Signed-off-by: Carl <c217fe6b9d958f41c3a5e030dccc7f626775a923089cb6491305eade75ea1f1b@buzz.block.builderlab.xyz>
Signed-off-by: Carl <c217fe6b9d958f41c3a5e030dccc7f626775a923089cb6491305eade75ea1f1b@buzz.block.builderlab.xyz>
Signed-off-by: Carl <c217fe6b9d958f41c3a5e030dccc7f626775a923089cb6491305eade75ea1f1b@buzz.block.builderlab.xyz>
Signed-off-by: Carl <c217fe6b9d958f41c3a5e030dccc7f626775a923089cb6491305eade75ea1f1b@buzz.block.builderlab.xyz>
Signed-off-by: Carl <c217fe6b9d958f41c3a5e030dccc7f626775a923089cb6491305eade75ea1f1b@buzz.block.builderlab.xyz>
Signed-off-by: Carl <c217fe6b9d958f41c3a5e030dccc7f626775a923089cb6491305eade75ea1f1b@buzz.block.builderlab.xyz>
b618613 to
37b5107
Compare
Signed-off-by: Carl <c217fe6b9d958f41c3a5e030dccc7f626775a923089cb6491305eade75ea1f1b@buzz.block.builderlab.xyz>
wesbillman
left a comment
There was a problem hiding this comment.
Carl, an automated reviewer, commenting via Wes’s GitHub account.
Source review of 665a8c3b7899dd23ec80958b70257d49585a5ff5 against 222605777f8808617026ea33a0aad266bdaff4c6: no substantiated code blockers found. Reviewed reaction toggles, own-duplicate removal, author checks, event-local emoji identity, failure/retry and channel/DM/thread synchronization, with independent deletion-boundary review.
This is not merge readiness or approval. Exact-head CI still fails the unchanged warm-switch budget: 120.5 ms versus <100 ms. All four browser journey shards passed, but that does not resolve the performance gate. Resolve or explicitly disposition the measurement failure with causal/equivalent-state evidence; do not weaken the budget merely to make this review green.
Source-only on Blox; no PR code, local tests, packaged app, or live-relay workflow was executed. Existing CI was read, not rerun.
Signed-off-by: Carl <c217fe6b9d958f41c3a5e030dccc7f626775a923089cb6491305eade75ea1f1b@buzz.block.builderlab.xyz>
…o-player-polish * origin/main: (38 commits) Fix diff content fallback, keyboard scrolling and edit selection (#205) Standardize form controls and field feedback across Buzz (#174) Keep image review downloads and external opens distinct (#144) Verify media review comments (#166) Follow system appearance (#210) Add rich composer formatting and spoiler rendering (#203) feat: show roster-backed channels and managed instances in profiles (#188) Add new direct message flow (#156) Remove Home, start in Messages, and keep Channels enabled (#194) fix: restore avatar presence controls and active-input sensing (#198) Add legacy diff messages with inline and expanded viewing (#202) Edit the latest own message with Up in the existing composer (#192) Add complete reaction toggles to the message menu (#185) feat: add persistent community navigation rail (#191) test: add margin to warm-switch performance gate (#195) Add composer attachments and compatible media preparation (#183) Add reply and copying to the shared message menu (#182) fix: avoid idle workspace re-renders from activity and label churn (#186) feat: add devtools trace capture to web profiling (#180) Add optional channel templates, teams and personal group defaults (#181) ... Signed-off-by: Zach Marley <[email protected]>
…-content-compat * origin/main: (38 commits) Fix diff content fallback, keyboard scrolling and edit selection (#205) Standardize form controls and field feedback across Buzz (#174) Keep image review downloads and external opens distinct (#144) Verify media review comments (#166) Follow system appearance (#210) Add rich composer formatting and spoiler rendering (#203) feat: show roster-backed channels and managed instances in profiles (#188) Add new direct message flow (#156) Remove Home, start in Messages, and keep Channels enabled (#194) fix: restore avatar presence controls and active-input sensing (#198) Add legacy diff messages with inline and expanded viewing (#202) Edit the latest own message with Up in the existing composer (#192) Add complete reaction toggles to the message menu (#185) feat: add persistent community navigation rail (#191) test: add margin to warm-switch performance gate (#195) Add composer attachments and compatible media preparation (#183) Add reply and copying to the shared message menu (#182) fix: avoid idle workspace re-renders from activity and label churn (#186) feat: add devtools trace capture to web profiling (#180) Add optional channel templates, teams and personal group defaults (#181) ... Signed-off-by: Zach Marley <[email protected]> # Conflicts: # docs/channels.md
Activity moves out of the dock and back into a row at the top of the Messages sidebar, where it was before #2626. Drafts & sent sits below it when that plugin is on. - **Hover preview:** resting the pointer on the row opens the same Activity card as before (#2347). The shell already supports previews on sidebar rows, so the card works unchanged. - **Reaction emoji:** the row's bell swaps for the latest reaction emoji, as the dock tile did. `ActivityNavigationIcon` is now sized for the row's 16px icon slot, and the dock's Activity-only icon code is gone. - **Selection:** opening Activity keeps Messages selected in the dock and keeps its sidebar, like other pages listed there (#2779). The change is host policy in `pagePlacement()` (`src/app/shell/presentation.ts`): `buzz.inbox/inbox` resolves to `"sidebar"` whatever placement it registers, and it's dropped from the dock order. No FOUNDATION files are touched. Docs (`docs/inbox.md`, `docs/buzz-help.md`, `docs/shell-design.md`, the Activity README) and Inbox code comments now say "Messages sidebar" where they said "app dock". ## Tradeoffs - **Fewer places to see it.** The row lives in the Messages sidebar. With another dock app open, the sidebar collapsed (below 650px), or a plugin narrowing the sidebar, Activity takes an extra step (Messages, Show navigation, or Search/shortcut), and the reaction-emoji swap isn't visible until the sidebar is. - Activity is no longer a dock tab. This reverses that part of #2626, so the dock's owner should sign off. ## Screenshots Synthetic browser-fixture data only. Hovering the Activity row, light:  Dark:  The row's bell swapped for a new reaction (✅):  ## Tests - New `PageNavigation.test.tsx`: the sidebar Activity row swaps its bell for the emoji and keeps its accessible name. - `presentation.test.ts`: bundled Activity resolves to the sidebar whatever placement it registers. - `navigation-owner.test.ts`: Activity now belongs to Messages. - `entity-navigation.test.tsx` also checks that Activity has no dock tab. - AppShell and entity-navigation tests that used Activity as an example dock app now use Bestie, Reminders or Workflows. Entity-navigation tests find the Activity row only after the sidebar's one startup remount, so they never click a detached row. - Browser specs find Activity with a shared `activityRow()` helper in `tests/browser/navigation.mjs`. No browser cases were added or removed. `inbox-preview.spec.mjs` now covers hovering the sidebar row; on a selected row it checks for the row's title hint where it used to check for the dock's tooltip. `inbox-tabs.spec.mjs` expects the 16px row emoji instead of the 18px dock one. Validation on this branch: - `pnpm check` (biome, typecheck, design checks) passed. - Full `vitest run`: 20,129 passed, 1 failed. The failure, `MemoryPanel.test.tsx > copies a memory from its context menu`, passes when run on its own on both this branch and main. - Chromium browser specs (`--project chromium`) covering Activity or the dock (41 files): after the fixes, every case touched passes. Three cases fail identically on the base commit without this change, so they're out of scope here: `layout.spec.mjs` "Alt+ArrowUp from a message link preserves native scroll ownership", and both `typing.spec.mjs` "a growing … composer lifts history" cases (React error #185). - **Narrow windows:** below 650px the Messages sidebar is collapsed, so getting to Activity takes Show navigation or Search. `inbox.spec.mjs`'s 390px case opens Activity through Show navigation and checks that the drawer closes. - After review fixes (`916e24d3e`): the vitest files touched (30 files, 689 tests) and `tsc` pass; the Chromium specs rerun with 45 passed and 1 failed, the Alt+ArrowUp case that also fails on main. `layout.spec.mjs` now uses Workflows as its non-Messages dock destination. - tulsi tried the running desktop app: the row, hover preview and emoji swap all work. Not run locally: WebKit browser projects and native builds. CI covers those. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: tulsi <[email protected]> Co-authored-by: BasilBot <399581b6165ab01343e3a1324774b1024d76942aea563468f93c975e5ee5193c@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <[email protected]>
…e covers it (#2942) ## Summary In a narrow window a thread, profile, Channel settings or link pane slides over the conversation, but the covered conversation stayed live. Shift+Tab from the pane walked into the hidden `Message #Alpha` composer, Enter posted to a channel you couldn't see, screen readers could still browse it, and messages arriving underneath were marked read. The same happened on the Activity page (`PageTabs`) and when the window narrowed while you were typing. This makes the covered conversation `inert` and `aria-hidden` while the dock floats over it, and keeps keyboard focus somewhere live. - **`useCoveredByDock(board, open)`** (new) reports whether the open dock covers the conversation by reading its computed `position: absolute`. `Channels.module.css` keeps owning the breakpoints (`@media` and `@container` both set it; comments there name the contract). It measures in a layout effect and on `ResizeObserver` for the board and the dock, and resets when the dock closes. - **`ChannelsPage` and `PageTabs`** mark the conversation `inert` + `aria-hidden` while covered, including the visible strip beside the 420 px card (that strip is already treated as covered by the pane-focus shortcuts). `PageTabs`' close-all guard checks the board instead of the now-inert page, so the native close-all-tabs command still works while a tab covers it. - **Focus handoff when the cover starts.** If focus was inside the conversation when it becomes covered (narrowing while typing, a pane opening without taking focus), it moves to the dock: the top visible pane's own landing (`<aside tabIndex=-1>`) or its selected tab, in document order. Never a button. Same precedent as narrow Settings. Todos and Huddles sections get `tabIndex={-1}` so they have a landing. - **Focus return on close.** `afterClose` in `useConversationTabs` and `hide()` in `useChannelPanels` used to focus the opener synchronously, before the close committed, while it was still inert. They now resolve and focus the target in the post-commit layout effect, and fall back to the dock if something else (e.g. the companion) still covers the opener. This fixes Close from Channel settings, Escape from a profile opened from Manage members, and Escape from Todos/Huddles at narrow widths. - **Read state** falls out of `inert`: `use-reading` already skips anything under `[inert]`, so messages that arrive while covered aren't read until the conversation is uncovered. Not in this PR: Bestie and the shell companion frame (`Panels.module.css`). Those float the card at a fixed 1000 px breakpoint with plenty of conversation still usable, so they need a different rule (inert only on full cover). Follow-up. Heads-up for #2609: it changes the same `afterClose` target line. Whichever lands second swaps one expression in the resolver (`successor(trigger) ?? fallbackFocus?.current`); the deferral already runs after the commit, so `successor()` sees a live history. ## Before / after Chromium 600×800, same scripted journey on both sides (left: `main` at `3bcf1c4bb1`, right: this branch). The badge at the bottom shows `document.activeElement`, whether it is hit-testable, and the live text of the `Message #Alpha` composer. Part 1: seven Shift+Tab presses from the thread tab, type "Wrong channel!", Enter, close the thread. On the left every stop is "HIDDEN under the thread pane" and Enter posts to #Alpha. On the right focus stays on visible chrome and the draft is untouched. Part 2 (from 22 s): typing at 1000 px, then narrowing to 600 px. Left keeps typing into the hidden composer; right hands focus to the "Thread root 1" tab and later keystrokes go nowhere. https://github.com/user-attachments/assets/68c47044-b9e7-4584-bd3d-8f6eb7b18501 Part 2 at full resolution: https://github.com/user-attachments/assets/0b86a9bb-413f-44e3-b07a-f7b58db0e47d ## Testing - New browser journeys in `tests/browser/focus-navigation.spec.mjs` (browser-only: jsdom evaluates neither media nor container queries): - a thread covering the conversation keeps typing out of it (1100 → 600 → 900 px; fails on `main` at the narrowing step and at the Shift+Tab walk, passes here, Chromium and WebKit); - covered panes at text scale 1 and 2, with `covered` derived from geometry (dock box intersects conversation box), asserting both branches ran; - narrowing hands reading to the thread tab until the pane closes; - an Activity tab covering the page: inert, Shift+Tab never enters it, close-all still works, narrowing focuses the selected tab; - companion overlays: Close under the companion lands on the companion's `<aside>`, Space leaves it open; - escaping a side panel over the conversation returns focus to its launcher; - a dock that still covers the conversation keeps focus in page order. - collapsing the tab pane beside a surviving companion at 600 px keeps focus on the companion; Escape from a Todos panel restored after Messages → Activity → Messages lands on Channel actions once uncovered (both fail without the inert-aware deferral, Chromium and WebKit); - close-all with Todos and a thread tab at 600 px keeps Todos open and the conversation inert, and hands the closed tab's focus to Todos (fails at `ce1ee40a5` with focus lost, passes at `ff0817585`, Chromium and WebKit). - Each new test was shown to fail when the code it guards is removed (inert, the handoff, each deferral, the dock order, the close-all guard). - Nine existing tests that query covered content by role now pass `includeHidden: true` (`inbox`, `layout`, `thread-resize`, `live-status`, `channel-details`, two in `focus-navigation`). - `channel-settings.spec.mjs:117` and `profiles.spec.mjs:1297` fail without the `afterClose` deferral and pass with it. - Final runs, Chromium + WebKit: focus-navigation, channel-settings, profiles, inbox, layout, thread-resize, live-status, todos, huddles all pass (one intentional WebKit skip in huddles). Vitest for `ChannelsPage*`, `PageTabs*`, `useConversationTabs*`, `useChannelPanels*`: 170 tests pass. `tsc --noEmit`, Biome, `design:check` clean. - Sweep of the 80 narrow-viewport specs in both engines: 1099 passed, 1 failed (`typing.spec.mjs:290`, React #185; fails 8–10 of 15 runs on `main` too). - Not verified: the desktop WKWebView build and VoiceOver. No styles change. 🤖 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 Opus 5.5 <[email protected]>
## Why People with no agents need a clear place to start without being sent into setup at launch. The pinned Bestie destination should introduce agents first, then explain Bestie after creation. ## What - Show a rotating **Create an agent** shortcut and a **Create your first agent** landing page. Keep setup opt-in. - After first-agent creation, show a local welcome with that agent’s avatar and the normal incoming-message bubble. Explain the sidebar shortcut and Buzz help. - Keep agents from another account or community out of the shared inventory choices. Filter saved rows before deduplication in both host readers. - Center rotating and assigned Bestie avatars in the sidebar row. - Enable Bestie by default only on fresh local installs. Preserve existing explicit On/Off and legacy implicit Off in both browser and native storage. ## How Reuse the shared agent creation form, saved Bestie choice and canonical DM. A device-local receipt binds the welcome to the first verified DM for that account, community and agent. A measured timeline intro holds the welcome outside real message rows. It publishes no message, starts no model turn and adds no unread state or message actions. Existing-agent choices and replacements do not create a welcome. Reuse the plugin settings stores and existing local-install detection. Save resolved Bestie choices through those owners; native default snapshots remain best effort. A deliberate settings reset uses the current On default. A new device, profile, or cleared browser storage counts as fresh, even for an older account. ## Risk The inventory correction affects all consumers of the shared saved-agent library on native and development-broker hosts. It changes no stored identities, membership or credentials and adds no relay reads. The welcome adds a presentation slot to the shared timeline; ordinary history, drafts, sending and threads keep their existing owners. **Main integration:** merge commit `f06f8d8e` brings in main at `345d938c9` without rewriting feature commits. It preserves the local welcome alongside main’s shared Bestie DM launchers, Huddle, actions menu, settings and member controls. Both file conflicts are resolved, and GitHub reports the pushed head as mergeable. Human acceptance of this integrated/defaults build remains pending. ## Testing - Before the latest main integration and default change, the human exercised the real native feature build: first-agent setup, community-scoped choices, sidebar appearance and the local welcome. They accepted the result and requested the final wording refinement. - Agent re-showed the local welcome in the same native build and verified its visible heading and closing text. No message or agent turn was triggered. Temporary replay code was removed. - Independent feature, host-scoping, presentation, combined-main and fresh-defaults and follow-up fixture reviews completed with no remaining blockers (9/10 or better). - One browser case was added and none removed. It controls an avatar rotation between native pointer-down and pointer-up to prove dock activation survives artwork replacement. That native targeting contract cannot be proved in jsdom. The new case failed in both engines before the six-line CSS fix; the full sidenav file then passed 20/20 in Chromium and WebKit with two workers and no retries. The existing colocated test still owns rotation, visibility and reduced-motion policy. The existing cold-start journey now proves fresh Bestie activation and opt-in setup; the upgrade journey proves implicit Off is preserved. The saved-settings journey covers enabling and disabling across reload. State permutations remain in the storage owners, with fail-before evidence for fresh On and legacy Off. Existing Chromium/WebKit journeys exercised the integrated dock, opt-in setup, retained drafts, thread/profile history and close behavior. Existing fixtures/selectors were updated for the creation invitation and Personal space fallback. No screenshot sweeps were run. - Historical browser baseline before this integration: the unchanged **Alt+ArrowUp from a message link preserves native scroll ownership** case fails in Chromium and WebKit. The same failure reproduces on the exact incoming main `d3d69aaa2` in an isolated worktree. Main now binds that chord to Previous conversation; this PR does not change either contract. Across the initial five-file run and a full rerun of the two corrected files, 82 of 84 distinct cases pass. This was not one fully green 84-case run. No test retries or exclusions were added. - Focused local checks for the current defaults snapshot passed: `bin/pnpm exec vitest run src/plugins/storage.test.ts src/plugins/storage.desktop.test.ts src/plugins/storage-imports.test.ts src/plugins/manager.test.ts` (50 tests); `bin/cargo test -p buzzodz-plugins` (133 tests); `bin/pnpm typecheck`; and `bin/pnpm test:browser tests/browser/plugin-defaults.spec.mjs --project chromium --project webkit --no-deps` (10 cases). This is focused macOS evidence, not Linux CI parity. - Normal pre-push hooks passed on `b16c75099`: TypeScript, all selected unit tests, design checks and whole-workspace Clippy. DCO passed; hosted CI for this head remains pending. - Follow-up `b16c75099` keeps rotating decorative artwork out of pointer hit testing, so its stable dock tab receives the press. Independent review found no blockers. Focused command: `bin/pnpm exec playwright test --config tests/browser/playwright.ci.config.mjs tests/browser/sidenav-polish.spec.mjs --project chromium --project webkit --no-deps --workers=2` (20/20, macOS; not Linux CI parity). No navigation assertions, retries or timeout limits were weakened. - CI run `42690878853` on `f06f8d8e` tested synthetic merge `6988d0f` against incoming main `ac8d5fe1`. The Bestie lost-click failure is addressed above. Both Chromium composer-growth React #185 errors also reproduce on a clean exact-base `ac8d5fe1` and on `6988d0f`, using the unchanged two cases in both engines: Chromium fails both; WebKit passes both. This is focused macOS baseline evidence, not Linux parity or a fix. The separate composer defect remains outside this PR; current-head hosted CI is pending. - The latest conflict resolution passed an independent review with no blockers and 258 focused tests across 17 files: `bin/pnpm exec vitest run src/bundled/bestie src/features/messages/ChannelTimeline.viewport.test.tsx src/app/shell/BestieNavigation.test.tsx src/app/entity-navigation.test.tsx src/plugins/storage.test.ts src/bundled/readmes.test.ts --maxWorkers=2`. This covers both parents’ welcome/history and DM-control behavior; it is not browser/native acceptance. - Follow-up `60abd17c0` makes Bestie explicitly On in active-navigation fixtures and updates the declared-default expectation. All 51 tests passed with `bin/pnpm exec vitest run src/app/entity-navigation.test.tsx src/app/pages.integration.test.mjs src/bundled/readmes.test.ts src/plugins/storage.test.ts`. A stale Vitest file-module cache caused the separate README failure; documented `--clearCache` restored the unchanged contract. No assertions or hook checks were bypassed. - Before the default change, the integrated snapshot passed the native scoped identity-library and isolated IPC tests, plus 678 tests in 16 focused frontend/broker files. A controlled older-history gate now proves the welcome stays hidden until verified history reaches its start; a deliberate mutation demonstrated the timeline regression test fails when the intro is removed. - Deferred: human acceptance of the integrated build, welcome-specific geometry in both themes at narrow/enlarged-text sizes, protected custom-avatar rendering, and packaged/cross-platform acceptance. The existing Bestie workspace journey exercised both themes, narrow widths and enlarged text, without first-agent welcome provenance. Required CI on the pushed head, DCO and code-owner approval remain merge gates. No current native launch or live-data test was performed. Generated with Goose --------- Signed-off-by: Clay Delk <[email protected]> Co-authored-by: goose <[email protected]> Co-authored-by: Luke Tornquist <[email protected]>
## Summary
Lets a Solo region source start its chats in the host's New chat editor,
and fixes how a row menu action gets focus.
### New Chat entry point for region sources (`45408d79c`)
A region source can now open the New chat editor for its region,
optionally in a workspace, with `handle.openDraft(binding, { workspace?,
lead? })`.
- **Draft:** keeps its own prompt, files, model and effort.
- **Send:** works like an ordinary New chat. The create goes out
unguarded with the region label before the first message, so the Outbox
retries both after a restart. A region draft adds no durable acceptance
step.
- **Agents:** a source can limit the agents the draft may address with
`draftAgent`.
- **Refusal:** Send is refused while the source is not running and
ready. The prompt and files go back to the region's draft.
- **Start record:** before the create, the host saves on this device how
the chat started. `started(binding, channelId)` returns it only to that
region's source until `forget(binding, channelId)`. The source
reconciles its own records from it by channel ID.
Hosts and plugins that do not call these APIs see no change.
`docs/solo-regions.md` documents the contract.
### Row menu action focus (`a778be9bd`)
A contributed Solo row action used to run on click, while its menu was
still open. The menu then returned focus to the row and took it from the
dialog or composer the action had just opened.
- Now the action runs once the menu's close completes. The menu first
focuses the row, so the action can move focus from there.
- The queued action is dropped if the menu reopens, the row unmounts or
rebinds, or the user moves focus elsewhere first.
- Escape and outside dismissal still return focus to the row and run
nothing.
### Compared with the previous version of this PR
The durable task chat flow that this PR used to hold is gone: task
drafts, task submission saved before the source claim, and the task-mode
Outbox. Task sources now reconcile from the region draft start.
buzz-review-completed
## Testing
Local runs on macOS, on the stack rebased onto main `9596cd3db`:
- At this PR's head `a778be9bd`: `pnpm check` (Biome, TypeScript and the
design checks) and the icon check passed. Vitest for the files the flake
check selects for this PR: 497 passed.
- At the stack tip `adbdf424e`: full Vitest, 20556 passed and 23 skipped
(includes `browser-host/channel-kit.test.mjs`).
- Browser journeys at the stack tip, Chromium and WebKit: the full
`tests/browser/solo-rename.spec.mjs`, repeated 5 times, passed 120/120.
The 34 other specs that the flake check relates to the stack, run once
in each engine: 343 passed, 3 skipped by their own conditions. The full
browser suite was not run locally.
- The Nexus plugin's connected browser harness ran on the actual Buzz
web shell and plugin UI, with a simulated relay and generated
identities, before the rebase. Full run at host pin `049a9b57e` (now
`1fbd49c49`), which includes this PR: 53 passed. Targeted New task and
workspace tests at pin `797040559` (now `fc6749bda`): 4 passed.
**Native testing:** Gil ran the native desktop app on this stack at
`adbdf424e`, with the matching Nexus plugin build. Gil confirmed that
these native-only paths passed:
- `!` folder listing through the Tauri `repositories_folder_list`
command (#2789);
- `/` SKILL.md listing through `local_skills_list` (#2790);
- the Claude `--add-dir` folder grant (#2789);
- relay signing and Outbox retry after a native restart.
Agent testing did not reach these paths: the agent ran no native app, no
real relay and no real agent.
## Known gaps
- On the plugin side, a new task chat shows briefly as a "More tasks"
placeholder, with an "Incomplete task information" notice, before its
record lands. This lasted about 80 ms on the simulated relay; on a real
relay it lasts at least one record read and one write.
Hosted or local failures that this stack does not cause:
- `tests/browser/typing.spec.mjs:290` ("a growing channel/thread
composer lifts history with it at the bottom") intermittently fails with
React error #185 on main too. Locally it failed 12 of 12 runs at
`9596cd3db` and 9 of 12 at a later main commit.
- On macOS, `cargo test -p Buzz` crashes the library test binary
(SIGSEGV), and some `host_command` deadline tests fail. Both happen the
same way at base `9596cd3db`, before this stack. Hosted Linux CI is the
reference for these.
## Test changes
Titles are compared only in the test files each range changes. Tests
that moved or were renamed are matched by title.
<details><summary>Removed by this PR's diff (0)</summary>
- none
</details>
<details><summary>Added by this PR's diff (17)</summary>
- `src/features/channel-navigation/SoloRegions.test.tsx`: cancels
selected %s %s after %s selection moves to %s during exit
- `src/features/channel-navigation/SoloRegions.test.tsx`: does not let
late %s completion consume a new %s selection
- `src/features/channel-navigation/SoloRegions.test.tsx`: drops a queued
%s destination action after %s before late close completion
- `src/features/channel-navigation/SoloRegions.test.tsx`: drops a queued
%s destination action when its menu reopens and then closes with Escape
- `src/features/channel-navigation/SoloRegions.test.tsx`: hands the %s
destination action focus after %s menu close completes
- `src/features/channel-navigation/SoloRegions.test.tsx`: keeps ordinary
%s Escape and outside dismissal focus without running a contribution
- `src/features/channel-navigation/SoloRegions.test.tsx`: keeps the %s
row origin and visible error after a deferred contribution rejects
- `src/features/channel-navigation/SoloRegions.test.tsx`: resets the
chosen %s handoff on keyboard reopen before Escape
- `src/features/channel-navigation/SoloRegions.test.tsx`: runs selected
%s Pin once from body and returns its non-navigation focus to the row
- `src/features/channel-navigation/routes.test.ts`: keeps a region
draft's label and refuses a malformed or linked one
- `src/features/channel-navigation/solo-navigation-draft.test.ts`: names
a region's draft owner only while its source is running and ready
- `src/features/channel-navigation/solo-navigation-draft.test.ts`: opens
New chat for its region, and only that region reads and forgets how its
chats started
- `src/features/solo/SoloDraft.test.tsx`: a refused region start forgets
its link and gives its prompt and files back to the region's draft
- `src/features/solo/SoloDraft.test.tsx`: a region draft can only
address agents its source accepts
- `src/features/solo/SoloDraft.test.tsx`: a region draft whose link
can't be saved starts nothing and keeps its prompt and files
- `src/features/solo/SoloDraft.test.tsx`: a region draft whose source
stops while its files upload keeps the draft and can't send
- `src/features/solo/SoloDraft.test.tsx`: a region's New chat starts a
labeled chat its source can find, without filing it
</details>
<details><summary>In the previous version of this PR, not carried over
(78)</summary>
- `src/features/channel-navigation/solo-navigation-draft.test.ts`:
persists one copied workspace on separate new draft identities before
navigating
- `src/features/channel-navigation/solo-navigation-draft.test.ts`:
preserves ordinary no-workspace seed %j
- `src/features/channel-navigation/solo-navigation-draft.test.ts`:
rechecks binding after synchronous draft-save subscribers invalidate it
- `src/features/channel-navigation/solo-navigation-draft.test.ts`:
rechecks binding when workspace validation re-enters the host
- `src/features/channel-navigation/solo-navigation-draft.test.ts`:
refuses an existing UUID draft rather than overwrite it
- `src/features/channel-navigation/solo-navigation-draft.test.ts`:
refuses an old binding before writing
- `src/features/channel-navigation/solo-navigation-draft.test.ts`:
refuses combined workspace and primary seeds before allocating, writing
or navigating
- `src/features/channel-navigation/solo-navigation-draft.test.ts`:
refuses malformed workspace %j without saving or navigating
- `src/features/channel-navigation/solo-navigation-draft.test.ts`:
refuses storage %s without navigation
- `src/features/messages/MessageComposer.test.tsx`: a task with a
missing provider stays read-only and retains its draft
- `src/features/messages/MessageComposer.test.tsx`: an ineligible pasted
task identity blocks the send instead of falling back to primary
- `src/features/messages/MessageComposer.test.tsx`: keeps task repair
visible with revoked historical A, then sends only to eligible primary B
- `src/features/messages/MessageComposer.test.tsx`: queued task
follow-ups preserve captured primary identity (explicit=%s)
- `src/features/messages/MessageComposer.test.tsx`: rechecks task policy
at queued publication after the provider is %s
- `src/features/messages/MessageComposer.test.tsx`: refuses captured
task-draft workspace controls while %s
- `src/features/messages/MessageComposer.test.tsx`: task primary A
survives explicit B; later Make primary B changes untagged routing
without prefill
- `src/features/messages/MessageComposer.test.tsx`: task sends reject
multiple or revoked explicit targets before admission and never fall
back
- `src/features/messages/MessageComposer.test.tsx`: taskDraft without
submission cannot bypass %s own-Solo send admission
- `src/features/messages/composer-chat-cards.test.ts`: stages a card
from document data another build saved and keeps it unsupported
- `src/features/messages/follow-up-persistence.test.ts`: keeps a saved
follow-up with task details this build cannot send, unsent and unchanged
- `src/features/messages/mention-draft.test.ts`: keeps a task draft's
workspace and marks document data another build saved as unsupported
- `src/features/relay/channel-details.test.ts`: keeps region and
task-mode labels during label replacement, including rename=%s
- `src/features/relay/channel-writes.test.ts`: confirms a restored Solo
create with a lost acknowledgement before attempting any resend
- `src/features/relay/channel-writes.test.ts`: resolves missing metadata
for an authorized roster-only task before confirming refresh
- `src/features/relay/channel-writes.test.ts`: task member additions
keep current policy through signing and require a fresh gate on restored
retry
- `src/features/relay/outbox-chat.test.ts`: holds the submission lock
through publication and refreshes a waiting stale owner
- `src/features/relay/outbox-chat.test.ts`: keeps peer cleanup after a
former owner receives a late verified echo
- `src/features/relay/outbox-chat.test.ts`: refuses a failed pair write
before claim and restores no partial submission
- `src/features/relay/outbox-chat.test.ts`: renews admission for saved
rows and cannot use a released lock gate
- `src/features/relay/outbox-chat.test.ts`: reserves room for both rows
before claim and permits retry after capacity is free
- `src/features/relay/outbox-chat.test.ts`: restores the same pair
without automatic replay after %s
- `src/features/relay/outbox-chat.test.ts`: saves the entire pair before
any signer, observer or publisher runs
- `src/features/relay/task-outbox.test.ts`: a verified echo settles a
queued whenPrepared wait without claiming local binding
- `src/features/relay/task-outbox.test.ts`: keeps a refused preparation
signed and retries after reload with the same content and event ID
- `src/features/relay/task-outbox.test.ts`: persists the signed event
before binding it, and binds before local hearing or publication
- `src/features/relay/task-outbox.test.ts`: restores the guarded create
and first message with fresh policy and their original signed bytes
- `src/features/relay/task-outbox.test.ts`: whenPrepared ends after
durable binding and synchronous policy, without waiting for relay ACK
- `src/features/relay/task-outbox.test.ts`: whenPrepared rejects %s
failure and retains the saved operation
- `src/features/solo/SoloDraft.test.tsx`: keeps another build's task
draft with a staged chat link unchanged and unsent across mount, send
and remount (%s)
- `src/features/solo/SoloDraft.test.tsx`: persists first-message intent
before waiting for task creation (%s)
- `src/features/solo/SoloDraft.test.tsx`: prepares a task before
starting it, keeps a refused draft and signs only its primary recipient
- `src/features/solo/SoloDraft.test.tsx`: rejects multiple task targets
before preparation or creation and retains the draft
- `src/features/solo/SoloDraft.test.tsx`: restores workspace-only
openDraft and never reseeds a removal, which undo restores
- `src/features/solo/SoloDraft.test.tsx`: resumes the saved task pair
before prepare and transfers edits; keeps another build's task
references unchanged and unsent=%s
- `src/features/solo/SoloDraft.test.tsx`: same-ID attachment recovery
transfers only actual file edits (changed=%s)
- `src/features/solo/SoloDraft.test.tsx`: shows pending accepted-message
recovery and cancels binding when its draft region ends
- `src/features/solo/task-mode.test.ts`: admits verified owner
information through only the current source policy
- `src/features/solo/task-mode.test.ts`: applies task policy to every
signed member addition, including a direct outbox retry
- `src/features/solo/task-mode.test.ts`: binds the exact preparation
identity and revision before exposing the first event
- `src/features/solo/task-mode.test.ts`: checks creation readiness at
the durable binding boundary (%s)
- `src/features/solo/task-mode.test.ts`: checks the whole membership and
exact explicit target before local delivery
- `src/features/solo/task-mode.test.ts`: does not classify a three-field
destination
- `src/features/solo/task-mode.test.ts`: does not grant informational
admission to an event with %s
- `src/features/solo/task-mode.test.ts`: does not trust a copied
proof-bearing event
- `src/features/solo/task-mode.test.ts`: fails closed after disposal or
a missing owning region while preserving legacy region chats
- `src/features/solo/task-mode.test.ts`: keeps %s outside informational
policy
- `src/features/solo/task-mode.test.ts`: keeps an ordinary addressed
owner message when the source predicate returns false
- `src/features/solo/task-mode.test.ts`: keeps foreign task-reference
and live task-member guards ahead of informational policy
- `src/features/solo/task-mode.test.ts`: keeps recipient-free
publication closed when the source policy is %s
- `src/features/solo/task-mode.test.ts`: keeps unsigned saved draft
validation on the ordinary path
- `src/features/solo/task-mode.test.ts`: never binds an informational
notice as the first task event
- `src/features/solo/task-mode.test.ts`: rechecks an informational
notice before publication after %s
- `src/features/solo/task-mode.test.ts`: refuses a proof-bearing
addressed owner message classified as information
- `src/features/solo/task-mode.test.ts`: refuses recognized information
carrying a recipient even if ordinary admission would allow it
- `src/features/solo/task-mode.test.ts`: refuses task references another
build signed, on send and first-event retry, and ignores unknown
versions
- `src/features/solo/task-mode.test.ts`: refuses unknown roster keys,
mismatched channel evidence and unreadable historical policy
- `src/features/solo/task-mode.test.ts`: retains and retries the same
signed first message after a creation wait %s
- `src/features/solo/task-mode.test.ts`: retains confirmed historical
members without authorizing revoked recipients or additions
- `src/features/solo/task-submission.test.tsx`: hands off a %s draft
after a shared submission completes
- `src/features/solo/task-submission.test.tsx`: keeps a completed task
draft when its %s changes during handoff
- `src/features/solo/task-submission.test.tsx`: keeps accepted first
delivery recoverable through failed %s cleanup
- `src/features/solo/task-submission.test.tsx`: keeps an incomplete
claim in its draft without a saved pair
- `src/features/solo/task-submission.test.tsx`: keeps failed staging
editable and retries without consuming a source claim
- `src/features/solo/task-submission.test.tsx`: refuses a corrupt saved
pair before another source claim
- `src/features/solo/task-submission.test.tsx`: refuses live eligibility
revoked while the staged claim is pending
- `src/features/solo/task-submission.test.tsx`: resumes the staged
identity after exit %s the source claim commits
- `tests/browser/outbox-chat.spec.mjs`: a waiting chat owner keeps
accepted event identities from the shared journal
- `tests/browser/outbox-chat.spec.mjs`: an unrelated save cannot restore
a task pair that another page completes
</details>
---------
Signed-off-by: Gil Birman <[email protected]>
Co-authored-by: Codex <[email protected]>
Co-authored-by: Claude <[email protected]>
## Summary
Exposes the bound user's personal Solo Workspaces to plugin regions
(`2cbf04b6d`).
`ctx.soloNavigation.workspaces` takes the current region binding and
offers:
- `snapshot`: the workspace IDs and names, the catalog status, whether
creation is available, and any read error;
- `subscribe` and `refresh`;
- `create({ id, name })`.
It uses the existing Solo preference writer, so that writer's
authorization, capacity and revision checks apply. An ended binding
cannot read or write another community. Ordinary chat filing stays
host-owned. `docs/solo-regions.md` documents the contract.
### Compared with the previous version of this PR
The PR no longer contains "add primary controls and the workspace
header". It is based on #2784's branch instead of #2785's.
buzz-review-completed
## Testing
Local runs on macOS, on the stack rebased onto main `9596cd3db`:
- At this PR's head `2cbf04b6d`: `pnpm check` (Biome, TypeScript and the
design checks) and the icon check passed. Vitest for the files the flake
check selects for this PR: 436 of 438 passed in a run that shared the
machine with a Rust build; the 2 tests that timed out
(`App.profile.test.tsx`, `entity-navigation.test.tsx`) passed on rerun,
52/52 in their files.
- At the stack tip `adbdf424e`: full Vitest, 20556 passed and 23 skipped
(includes `browser-host/channel-kit.test.mjs`).
- Browser journeys at the stack tip, Chromium and WebKit: the full
`tests/browser/solo-rename.spec.mjs`, repeated 5 times, passed 120/120.
The 34 other specs that the flake check relates to the stack, run once
in each engine: 343 passed, 3 skipped by their own conditions. The full
browser suite was not run locally.
- The Nexus plugin's connected browser harness ran on the actual Buzz
web shell and plugin UI, with a simulated relay and generated
identities, before the rebase. Full run at host pin `049a9b57e` (now
`1fbd49c49`), which includes this PR: 53 passed.
**Native testing:** Gil ran the native desktop app on this stack at
`adbdf424e`, with the matching Nexus plugin build. Gil confirmed that
these native-only paths passed:
- `!` folder listing through the Tauri `repositories_folder_list`
command (#2789);
- `/` SKILL.md listing through `local_skills_list` (#2790);
- the Claude `--add-dir` folder grant (#2789);
- relay signing and Outbox retry after a native restart.
Agent testing did not reach these paths: the agent ran no native app, no
real relay and no real agent.
## Known gaps
- None specific to this PR.
Hosted or local failures that this stack does not cause:
- `tests/browser/typing.spec.mjs:290` ("a growing channel/thread
composer lifts history with it at the bottom") intermittently fails with
React error #185 on main too. Locally it failed 12 of 12 runs at
`9596cd3db` and 9 of 12 at a later main commit.
- On macOS, `cargo test -p Buzz` crashes the library test binary
(SIGSEGV), and some `host_command` deadline tests fail. Both happen the
same way at base `9596cd3db`, before this stack. Hosted Linux CI is the
reference for these.
## Test changes
Titles are compared only in the test files each range changes. Tests
that moved or were renamed are matched by title.
<details><summary>Removed by this PR's diff (0)</summary>
- none
</details>
<details><summary>Added by this PR's diff (1)</summary>
- `src/features/channel-navigation/solo-workspaces.test.ts`: projects
the existing Solo catalog and writer, preserving uncertainty and fencing
ended bindings
</details>
<details><summary>In the previous version of this PR, not carried over
(2)</summary>
- `src/features/solo/SoloDraft.test.tsx`: permits one start across
already-open drafts and preserves unresolved payload (%s)
- `src/features/solo/SoloDraft.test.tsx`: shows pending accepted-message
recovery and cancels binding when its draft region ends
</details>
Signed-off-by: Gil Birman <[email protected]>
Co-authored-by: Codex <[email protected]>
## Summary Keeps plugin-owned chat deletion inside Solo (`02f9c0277`). When a plugin's row leaves the roster after a delete, Solo keeps its successor navigation, and a newer visit the user already made wins. ### Compared with the previous version of this PR The commit is rebased onto the rebuilt stack. Its behavior is unchanged. buzz-review-completed ## Testing Local runs on macOS, on the stack rebased onto main `9596cd3db`: - The pushed heads differ from the tested `adbdf424e` chain only in `tests/browser/solo-rename.spec.mjs`. `02f9c0277` makes the new plugin-owned lifecycle test wait for the chat's row before filing it, after the hosted flake check caught it racing the roster load. With that fix, the full `solo-rename.spec.mjs` passed 180/180 at `02f9c0277` (10 repeats, Chromium and WebKit) and 120/120 at `c99d0ce51` (5 repeats). - At this PR's head `12b06a7b1`: `pnpm check` (Biome, TypeScript and the design checks) and the icon check passed. Vitest for the files the flake check selects for this PR: 349 passed. - At the stack tip `adbdf424e`: full Vitest, 20556 passed and 23 skipped (includes `browser-host/channel-kit.test.mjs`). - Browser journeys at the stack tip, Chromium and WebKit: the full `tests/browser/solo-rename.spec.mjs`, repeated 5 times, passed 120/120. The 34 other specs that the flake check relates to the stack, run once in each engine: 343 passed, 3 skipped by their own conditions. The full browser suite was not run locally. - The Nexus plugin's connected browser harness ran on the actual Buzz web shell and plugin UI, with a simulated relay and generated identities, before the rebase. Full run at host pin `049a9b57e` (now `1fbd49c49`), which includes this PR: 53 passed. **Native testing:** Gil ran the native desktop app on this stack at `adbdf424e`, with the matching Nexus plugin build. Gil confirmed that these native-only paths passed: - `!` folder listing through the Tauri `repositories_folder_list` command (#2789); - `/` SKILL.md listing through `local_skills_list` (#2790); - the Claude `--add-dir` folder grant (#2789); - relay signing and Outbox retry after a native restart. Agent testing did not reach these paths: the agent ran no native app, no real relay and no real agent. ## Known gaps - None specific to this PR. Hosted or local failures that this stack does not cause: - `tests/browser/typing.spec.mjs:290` ("a growing channel/thread composer lifts history with it at the bottom") intermittently fails with React error #185 on main too. Locally it failed 12 of 12 runs at `9596cd3db` and 9 of 12 at a later main commit. - On macOS, `cargo test -p Buzz` crashes the library test binary (SIGSEGV), and some `host_command` deadline tests fail. Both happen the same way at base `9596cd3db`, before this stack. Hosted Linux CI is the reference for these. ## Test changes Titles are compared only in the test files each range changes. Tests that moved or were renamed are matched by title. <details><summary>Removed by this PR's diff (0)</summary> - none </details> <details><summary>Added by this PR's diff (2)</summary> - `src/features/channel-navigation/SoloRegions.test.tsx`: %s of a plugin-owned chat preserves Solo and newer navigation (%s) - `tests/browser/solo-rename.spec.mjs`: deleting a plugin-owned chat keeps the shell in Solo </details> <details><summary>In the previous version of this PR, not carried over (0)</summary> - none </details> Signed-off-by: Gil Birman <[email protected]> Co-authored-by: Codex <[email protected]>
## Summary
Restores workspace selection (`bc4996eb3`), and leaves workspace Leads
to the region source (`e23433e56`). It also lets you choose and show a
chat's workspace (`96a096a50`, `fa06b6d8f`).
- **Selection:** a workspace selects from its title or from blank row
space. The region source decides what selection opens through the
existing `onSelect`, `selected` and `leadWorkspaceId` props. For
example, Nexus opens the workspace's Lead task, or a New chat draft for
a Lead when it has none.
- **Header:** task creation shows on hover. A header too narrow for both
controls keeps only its actions menu, which offers New task at every
width.
- **Leads belong to the region source:** the source stores Leads with
its own records and chooses them. The host stores no Leads, and
workspace snapshots stay `{ id, name }`. So Leads exist only while their
source, such as Nexus, is enabled.
- **Lead drafts:** a region's lead draft opens in its workspace with
`openDraft(..., { lead: true })`. The draft start records `lead` only
while the draft still targets that workspace. The source reads it with
`started`, saves the Lead in its own records, and calls `forget`. While
the lead draft is open, the host highlights the workspace row named by
`leadWorkspaceId`.
### Compared with the previous version of this PR
- `e23433e56` removes the host's Lead storage and APIs. Removed: the
private Leads record and its native and browser admission, the startup
read, the Lead field in workspace snapshots, the handle's two Lead
methods, the selection gate with its error footer, the post-delete
cleanup, and the host's single-Lead conflict check. The Lead rules now
belong to the source that owns the Lead.
- In `solo-navigation-contract.ts`, the change is only these removals
and one comment reword. New chat and Outbox delivery are unchanged.
- Earlier, rebased onto main: `bc4996eb3` also restores the workspace
header's blank-space target and its narrow-width rule. On the shared
section heading, the strips above and below the label and the trailing
space did not select the workspace, and a narrow header kept New task.
`tests/browser/solo-rename.spec.mjs` caught it on hosted CI.
### Choose and show a chat's workspace (`96a096a50`, `fa06b6d8f`)
- **`#` in New chat:** `#` lists the workspaces the composer's workspace
control offers, above channels. Choosing one removes the `#query` and
sets the control, leaving no text or chip; Send files the chat as
before. Only host code can make the workspace edit, so a plugin row that
looks like one is dropped.
- **Region adapter:** a Solo region can offer an optional `workspace`
adapter for its chats. The composer's workspace control then shows the
region's filing and files through it, instead of hiding. Until the chat
has a message, `#` offers the same workspaces and files the chat at
once.
- **Focus while saving:** the control stays focusable and ignores
activation (`aria-disabled`), so a pointer or keyboard choice keeps
focus on it.
#### Test changes for these two commits
<details><summary>Removed by this PR's diff (0)</summary>
- none
</details>
<details><summary>Added by this PR's diff (10)</summary>
- `src/bundled/channels/ChannelCompletion.test.tsx`: lets workspace rows
take their place within the row limit
- `src/bundled/channels/ChannelCompletion.test.tsx`: lists a new chat's
workspaces first, by the channel name rule
- `src/bundled/channels/ChannelCompletion.test.tsx`: offers no workspace
rows outside a new chat
- `src/features/conversation/completion.test.ts`: keeps a host workspace
edit and drops a provider's look-alike
- `src/features/solo/SoloDraft.test.tsx`: `#` chooses New chat's
workspace without leaving text, and Send files the chat there
- `src/features/solo/WorkspaceControl.test.tsx`: keeps focus on the
control while a keyboard choice saves, and ignores it until then
- `src/features/solo/WorkspaceControl.test.tsx`: keeps hiding a region
chat whose region offers no workspace
- `src/features/solo/WorkspaceControl.test.tsx`: shows a region chat's
workspace and files it through its region
- `src/features/solo/chat-workspace.test.ts`: calls a chat new only
while its verified, complete history has no message
- `src/features/solo/chat-workspace.test.ts`: never calls a chat new
before its history is known
</details>
## Testing
Local runs on macOS for the Lead removal, at this PR's head `e23433e56`:
- `tsc --noEmit` passed. `pnpm run lint` (Biome with
`--error-on-warnings` and the icon check) passed.
- Vitest for channel-navigation, channel-templates, solo-preferences,
relay session, bundled channels, PrimarySidebar, NewMenu, features/solo,
solo-agents and `browser-host/channel-kit.test.mjs`: 117 files, 2147
passed.
- Rust: `cargo fmt --check` and `cargo clippy -p Buzz --all-targets --
-D warnings` passed. `relay::kit` tests: 7 passed. `kit.rs` is now
identical to this PR's base.
At the stack tip `e65fa4da7`, which is the stack rebased onto
`e23433e56`:
- `tsc`, lint, `cargo fmt --check` and clippy passed. `relay::kit`
tests: 7 passed.
- Vitest for the suites above, plus features/messages, bundled/mentions
and every test file the stack changes: 226 files, 5334 passed.
- `tests/browser/solo-rename.spec.mjs` in Chromium and WebKit: 50
passed.
- The full Vitest suite, the full browser suite and the full `cargo
test` were not run locally for this change.
Earlier local runs on macOS, on the stack rebased onto main `9596cd3db`,
before the Lead removal:
- The heads pushed then differed from the tested `adbdf424e` chain only
in `tests/browser/solo-rename.spec.mjs`. `02f9c0277` makes the new
plugin-owned lifecycle test wait for the chat's row before filing it,
after the hosted flake check caught it racing the roster load. With that
fix, the full `solo-rename.spec.mjs` passed 180/180 at `02f9c0277` (10
repeats, Chromium and WebKit) and 120/120 at `c99d0ce51` (5 repeats).
- At this PR's earlier head `6d474514c`: `pnpm check` (Biome, TypeScript
and the design checks) and the icon check passed. Vitest for the files
the flake check selects for this PR: 817 passed.
- Rust at `6d474514c`: `cargo fmt --check` and `cargo clippy -p Buzz
--all-targets -- -D warnings` passed. `cargo-deny check` passed at the
stack tip.
- At the stack tip `adbdf424e`: full Vitest, 20556 passed and 23 skipped
(includes `browser-host/channel-kit.test.mjs`).
- Browser journeys at the stack tip, Chromium and WebKit: the full
`tests/browser/solo-rename.spec.mjs`, repeated 5 times, passed 120/120.
The 34 other specs that the flake check relates to the stack, run once
in each engine: 343 passed, 3 skipped by their own conditions. The full
browser suite was not run locally.
- The Nexus plugin's connected browser harness ran on the actual Buzz
web shell and plugin UI, with a simulated relay and generated
identities, before the rebase. Full run at host pin `049a9b57e` (now
`1fbd49c49`): 53 passed. Targeted New task, workspace New task and Solo
Workspaces tests at pin `797040559`, before its header fix: 4 passed.
**Native testing:** Gil ran the native desktop app on this stack at
`adbdf424e`, with the matching Nexus plugin build. Gil confirmed that
these native-only paths passed:
- `!` folder listing through the Tauri `repositories_folder_list`
command (#2789);
- `/` SKILL.md listing through `local_skills_list` (#2790);
- the Claude `--add-dir` folder grant (#2789);
- relay signing and Outbox retry after a native restart.
Agent testing did not reach these paths: the agent ran no native app, no
real relay and no real agent. That native run predates the Lead removal,
and the Lead flow with source-owned storage has not been run in the app
yet.
## Known gaps
- Workspace Leads now depend on the region source's own storage. The
matching Nexus plugin change must ship with its re-pin to this head.
- The "Removed" list has two tests that look reworded rather than
dropped. Their reworded versions are under "Added": ordinary workspace
draft after session replacement, and ordinary drafts not marked as a
region draft.
Hosted or local failures that this stack does not cause:
- `tests/browser/typing.spec.mjs:290` ("a growing channel/thread
composer lifts history with it at the bottom") intermittently fails with
React error #185 on main too. Locally it failed 12 of 12 runs at
`9596cd3db` and 9 of 12 at a later main commit.
- On macOS, `cargo test -p Buzz` crashes the library test binary
(SIGSEGV), and some `host_command` deadline tests fail. Both happen the
same way at base `9596cd3db`, before this stack. Hosted Linux CI is the
reference for these.
## Test changes
Titles are compared only in the test files each range changes. Tests
that moved or were renamed are matched by title.
<details><summary>Removed by this PR's diff (2)</summary>
- `src/app/shell/PrimarySidebar.test.tsx`: does not navigate to a
replaced session after project creation from %s completes
- `src/features/channel-navigation/SoloInventory.test.tsx`: shows and
selects the ordinary New chat draft row for %s
</details>
<details><summary>Added by this PR's diff, outside the two
workspace-choice commits (11)</summary>
- `src/app/shell/PrimarySidebar.test.tsx`: keeps an ordinary chat editor
and pending save through task-source recovery
- `src/app/shell/PrimarySidebar.test.tsx`: keeps ordinary Buzz creation
controls until a workspace source opts in and restores them when it is
removed
- `src/app/shell/PrimarySidebar.test.tsx`: opens an ordinary workspace
draft from %s and ignores later completion after session replacement
- `src/app/shell/PrimarySidebar.test.tsx`: selects the created workspace
through its source from %s and retains selection failures for retry
- `src/bundled/channels/SoloWorkspaces.test.tsx`: shares a workspace
frame while keeping selection, disclosure and chat creation separate
- `src/features/channel-navigation/SoloInventory.test.tsx`: selects
ordinary drafts without marking a region draft as New chat (%s)
- `src/features/channel-navigation/routes.test.ts`: keeps a region lead
draft only with its region and workspace
- `src/features/channel-navigation/solo-navigation-draft.test.ts`: opens
a workspace's lead draft only in that workspace and returns the lead
with its start
- `src/features/solo/SoloDraft.test.tsx`: a region lead draft starts its
workspace's lead only while it still targets that workspace
- `tests/browser/solo-rename.spec.mjs`: task-source recovery retains an
ordinary chat editor, caret and unsaved name
- `tests/browser/solo-rename.spec.mjs`: workspace blank space selects
independently of its controls ${variant.name}
</details>
<details><summary>In the previous version of this PR, not carried over
(14)</summary>
These tested host Lead storage, which `e23433e56` removes. One was
renamed rather than dropped: "shares a workspace frame while keeping
lead selection, disclosure and chat creation separate" is now "…keeping
selection, disclosure and chat creation separate".
- `browser-host/channel-kit.test.mjs`: workspace Leads admission: $name
- `src/bundled/channels/SoloWorkspaces.test.tsx`: selects a Lead
workspace only once its separate Leads record is read, showing the
owner's failure with Retry
- `src/bundled/channels/SoloWorkspaces.test.tsx`: shares a workspace
frame while keeping lead selection, disclosure and chat creation
separate
- `src/features/channel-navigation/solo-workspaces.test.ts`: keeps a
Lead to the owner's own Solo channel in the source's region
- `src/features/channel-navigation/solo-workspaces.test.ts`: lets a
running registered source without a task contract set its own Solo task
as Lead
- `src/features/channel-navigation/solo-workspaces.test.ts`: refuses a
Lead from a registered source that is not running or is unregistered
- `src/features/channel-navigation/solo-workspaces.test.ts`: resolves
archived lead history and replaces only an authoritatively absent lead,
fencing source ownership and binding
- `src/features/relay/solo-preferences.test.ts`: isolates an unreadable
Leads record from workspaces and the catalog, and never writes over it
- `src/features/relay/solo-preferences.test.ts`: keeps Leads in their
own record, fencing conflicting replacements and leaving Groups at
main's fields
- `src/features/relay/solo-preferences.test.ts`: keeps unknown Leads
fields, hides and prunes orphans, and best-effort removes a deleted
workspace's Lead
- `src/features/relay/solo-preferences.test.ts`: loads Leads beside
Groups, and session recovery retries a transient failure
- `src/features/relay/solo-preferences.test.ts`: never lets a refresh
begun before a confirmed Lead write show the older Leads
- `src/features/relay/solo-preferences.test.ts`: prunes Leads against
Groups read after them, so another device's Lead survives
From the version before the rebase onto main:
- `src/features/channel-navigation/SoloInventory.test.tsx`: selects
ordinary drafts without marking a task draft as New chat (%s)
</details>
buzz-review-completed
---------
Signed-off-by: Gil Birman <[email protected]>
Co-authored-by: Codex <[email protected]>
Co-authored-by: Claude <[email protected]>
## Summary Adds host-internal semantic chips to the composer (`070ad3b96`). The folder (#2789) and skill (#2790) layers build on them. - **Chips:** a chip is a token node with a bounded `ComposerToken`, a `folder` or a `skill`. Extra JSON fields round-trip without interpretation. - **Editing:** the editor inserts a chip over an open completion query as one undo step, names it for assistive technology, keeps it through saved drafts, and copies it as its label text. A decoration or link never adopts it. - **Who can make one:** only a provider that a bundled plugin registered. The plugin runtime marks the owner it creates for a host-shipped module, and `completionResult` keeps a token edit only for a marked entry. An id, revision, title or flag that a provider claims is not provenance. - **Public API:** the plugin completion contract, `MentionDraft` and the generated public declarations are unchanged. - **Unsent in this layer:** every composer keeps a draft with chips unchanged and refuses to send it. Saved chip data that does not restore as a chip restores no chip. ## Testing Local runs on macOS, on the stack rebased onto main `9596cd3db`: - The pushed heads differ from the tested `adbdf424e` chain only in `tests/browser/solo-rename.spec.mjs`. `02f9c0277` makes the new plugin-owned lifecycle test wait for the chat's row before filing it, after the hosted flake check caught it racing the roster load. With that fix, the full `solo-rename.spec.mjs` passed 180/180 at `02f9c0277` (10 repeats, Chromium and WebKit) and 120/120 at `c99d0ce51` (5 repeats). - At this PR's head `eff434bd0`: `pnpm check` (Biome, TypeScript and the design checks) and the icon check passed. Vitest for the files the flake check selects for this PR: 632 passed. - At the stack tip `adbdf424e`: full Vitest, 20556 passed and 23 skipped (includes `browser-host/channel-kit.test.mjs`). - Browser journeys at the stack tip, Chromium and WebKit: the full `tests/browser/solo-rename.spec.mjs`, repeated 5 times, passed 120/120. The 34 other specs that the flake check relates to the stack, run once in each engine: 343 passed, 3 skipped by their own conditions. The full browser suite was not run locally. - The Nexus plugin's connected browser harness ran on the actual Buzz web shell and plugin UI, with a simulated relay and generated identities, before the rebase. Full run at host pin `049a9b57e` (now `1fbd49c49`), which includes this PR: 53 passed. **Native testing:** Gil ran the native desktop app on this stack at `adbdf424e`, with the matching Nexus plugin build. Gil confirmed that these native-only paths passed: - `!` folder listing through the Tauri `repositories_folder_list` command (#2789); - `/` SKILL.md listing through `local_skills_list` (#2790); - the Claude `--add-dir` folder grant (#2789); - relay signing and Outbox retry after a native restart. Agent testing did not reach these paths: the agent ran no native app, no real relay and no real agent. ## Known gaps - None specific to this PR. Hosted or local failures that this stack does not cause: - `tests/browser/typing.spec.mjs:290` ("a growing channel/thread composer lifts history with it at the bottom") intermittently fails with React error #185 on main too. Locally it failed 12 of 12 runs at `9596cd3db` and 9 of 12 at a later main commit. - On macOS, `cargo test -p Buzz` crashes the library test binary (SIGSEGV), and some `host_command` deadline tests fail. Both happen the same way at base `9596cd3db`, before this stack. Hosted Linux CI is the reference for these. ## Test changes Titles are compared only in the test files each range changes. Tests that moved or were renamed are matched by title. <details><summary>Removed by this PR's diff (0)</summary> - none </details> <details><summary>Added by this PR's diff (10)</summary> - `src/features/conversation/ComposerCompletions.test.tsx`: hands a chip edit to the composer only from a bundled provider (bundled: %s) - `src/features/conversation/ComposerCompletions.test.tsx`: opens no query at a trigger inside a resolved chip - `src/features/conversation/completion.test.ts`: keeps chip edits only from a provider a bundled plugin registered, for that registration's lifetime - `src/features/messages/MessageComposer.test.tsx`: inserts a bundled provider's chip as one atom and keeps it unsent until chips ship - `src/features/messages/composer-token.test.tsx`: bounds token JSON by UTF-8 bytes, including multibyte identities - `src/features/messages/composer-token.test.tsx`: copies a bounded, frozen token of a known kind and nothing else - `src/features/messages/composer-token.test.tsx`: copies a chip as its label text, never as a link its label resembles - `src/features/messages/composer-token.test.tsx`: refuses an unknown kind, a stale or literal range, and edits beyond the chip or length bounds - `src/features/messages/composer-token.test.tsx`: replaces an open query with one chip: named, separate from recipients, one undo step, and restored from a saved draft - `src/features/messages/composer-token.test.tsx`: restores a chip only from chip data its snapshot keeps </details> <details><summary>In the previous version of this PR, not carried over (3)</summary> - `src/features/messages/MessageComposer.test.tsx`: inserts a bundled provider's chip as one atom and keeps it unsent until task references ship (task chat: %s) - `src/features/messages/composer-token.test.tsx`: refuses a workspace, a stale or literal range, and edits beyond the chip or length bounds - `src/features/messages/composer-token.test.tsx`: restores a chip, and marks chip data any snapshot rejection would drop as another build's data </details> buzz-review-completed --------- Signed-off-by: Gil Birman <[email protected]> Co-authored-by: Codex <[email protected]> Co-authored-by: Claude <[email protected]>
## Summary
Offers and sends `!` folder chips (`828571619`).
- **Listing:** in the desktop app, `!` in any composer lists the
immediate folders of the community's repositories folder (Local
settings, or the default `~/.buzz/REPOS`).
- It opens at a word start once a character follows `!`.
- A bare `!`, `!` before a space, a mid-word `!` and `![` stay prose.
- One open query lists once. Reopening it, a changed preference, or
Retry after a failed read lists again.
- **Sending:** chips now send. A chip is its label as literal text, and
the host composer signs the message's chips as one `buzz-tokens-v1` tag.
- Sends, replies, queued follow-ups after a restart, and the first
message of a new Solo chat all sign.
- The public message API cannot add the tag. A detached window refuses
to send chips.
- **Native listing:** `repositories_folder_list` serves the core
renderer, and `plugin_host_repository_folders` serves
`ctx.host.repositoryFolders` after checking the plugin's manager id,
revision and file grant. The listing:
- follows a linked root folder but never a linked child;
- is capped at 4096 entries and 1 MiB;
- runs on the directory listing semaphore with the read deadline.
- **Agent grant:** a local Claude or Codex agent is told about a folder
only when the message's author is the agent's owner and the folder is in
the owner's listing at that turn. Claude also gets the folder through
`--add-dir` for the rest of the conversation; Codex gets the prompt
section only.
### Compared with the previous version of this PR
The previous version ("send task references, `#` workspaces and `@`
folders") depended on the removed task mode. The `#` workspace choice is
now in its own commits (see the stack), and folders use `!` instead of
`@`.
## Testing
Local runs on macOS, on the stack rebased onto main `9596cd3db`:
- The pushed heads differ from the tested `adbdf424e` chain only in
`tests/browser/solo-rename.spec.mjs`. `02f9c0277` makes the new
plugin-owned lifecycle test wait for the chat's row before filing it,
after the hosted flake check caught it racing the roster load. With that
fix, the full `solo-rename.spec.mjs` passed 180/180 at `02f9c0277` (10
repeats, Chromium and WebKit) and 120/120 at `c99d0ce51` (5 repeats).
- At this PR's head `1fbd49c49`: `pnpm check` (Biome, TypeScript and the
design checks) and the icon check passed. Vitest for the files the flake
check selects for this PR: 984 passed, 28 skipped.
- Rust at `1fbd49c49`: `cargo fmt --check` and `cargo clippy -p Buzz
--all-targets -- -D warnings` passed. `host_files` and
`repositories_folder` tests at the stack tip: 24 and 4 passed.
`cargo-deny check` passed at the stack tip.
- At the stack tip `adbdf424e`: full Vitest, 20556 passed and 23 skipped
(includes `browser-host/channel-kit.test.mjs`).
- Browser journeys at the stack tip, Chromium and WebKit: the full
`tests/browser/solo-rename.spec.mjs`, repeated 5 times, passed 120/120.
The 34 other specs that the flake check relates to the stack, run once
in each engine: 343 passed, 3 skipped by their own conditions. The full
browser suite was not run locally.
- The Nexus plugin's connected browser harness ran on the actual Buzz
web shell and plugin UI, with a simulated relay and generated
identities, before the rebase. Full run at this PR's pre-rebase head as
the pin (`049a9b57e`): 53 passed. The simulated host lists folders; the
native listing did not run there.
**Native testing:** Gil ran the native desktop app on this stack at
`adbdf424e`, with the matching Nexus plugin build. Gil confirmed that
these native-only paths passed:
- `!` folder listing through the Tauri `repositories_folder_list`
command (#2789);
- `/` SKILL.md listing through `local_skills_list` (#2790);
- the Claude `--add-dir` folder grant (#2789);
- relay signing and Outbox retry after a native restart.
Agent testing did not reach these paths: the agent ran no native app, no
real relay and no real agent.
## Known gaps
- Gil exercised the folder listing and the `--add-dir` grant natively,
and they passed. Agent testing did not reach them.
Hosted or local failures that this stack does not cause:
- `tests/browser/typing.spec.mjs:290` ("a growing channel/thread
composer lifts history with it at the bottom") intermittently fails with
React error #185 on main too. Locally it failed 12 of 12 runs at
`9596cd3db` and 9 of 12 at a later main commit.
- On macOS, `cargo test -p Buzz` crashes the library test binary
(SIGSEGV), and some `host_command` deadline tests fail. Both happen the
same way at base `9596cd3db`, before this stack. Hosted Linux CI is the
reference for these.
## Test changes
Titles are compared only in the test files each range changes. Tests
that moved or were renamed are matched by title.
<details><summary>Removed by this PR's diff (2)</summary>
- `src/features/conversation/service.test.tsx`: bundled plugin registers
its own completion provider
- `src/features/messages/MessageComposer.test.tsx`: inserts a bundled
provider's chip as one atom and keeps it unsent until chips ship
</details>
<details><summary>Added by this PR's diff (21)</summary>
- `src/bundled/claude-code/runtime.test.ts`: grants a listed folder its
owner referenced by chip for the rest of the conversation, never an
unlisted one
- `src/bundled/mentions/FolderCompletion.test.tsx`: a `!` query reopened
at the same position, after a preference change, or retried after a
failed read lists the folders again; a closed query's late listing never
publishes
- `src/bundled/mentions/FolderCompletion.test.tsx`: matches `!` only at
a word start before a query character, never in image syntax or mid-word
- `src/bundled/mentions/FolderCompletion.test.tsx`: offers the
repositories folder's folders as chips, listed once per query; a browser
build never lists it
- `src/features/agent-harness/referenced-folders.test.ts`: grants
nothing without chips, for malformed chips, or when the listing fails
- `src/features/agent-harness/referenced-folders.test.ts`: grants only
folders the owner referenced that their repositories folder lists now
- `src/features/agent-harness/referenced-folders.test.ts`: quotes a
folder path so it cannot close the prompt section
- `src/features/communities/repositories.test.ts`: lists the partition's
configured folder and refuses a listing that outlived its preference or
caller
- `src/features/conversation/service.test.tsx`: bundled plugin registers
its own completion providers
- `src/features/host/service.test.ts`: lists the binding's configured
repositories folder as the calling plugin, never a path it names
- `src/features/messages/MessageComposer.test.tsx`: inserts a bundled
provider's chip as one atom and signs it into the sent message
- `src/features/messages/follow-up-persistence.test.ts`: keeps saved
messages with invalid or oversized chips unsent and unchanged: %j
- `src/features/messages/follow-up-persistence.test.ts`: restores a
queued message's chips and signs them on the recovered send
- `src/features/messages/message-tokens.test.ts`: gives the chips being
signed only to the first send inside it
- `src/features/messages/message-tokens.test.ts`: rejects duplicate tags
or chips, malformed shapes and oversized payloads
- `src/features/messages/message-tokens.test.ts`: round-trips exact
chips with extra metadata in one tag
- `src/features/messages/message-tokens.test.ts`: sends chips as their
labels, literally and in authored order
- `src/features/messages/message-tokens.test.ts`: serializes syntax-like
labels literally, including under rich formatting
- `src/features/relay/send-to-channel.test.ts`: a host reply signs its
chips once and retries the exact bytes; the public API cannot add chips
- `src/features/relay/send-to-channel.test.ts`: only the host's own send
carries its chips, not a send an outbox subscriber makes while it is
queued
- `src/features/solo/SoloDraft.test.tsx`: starts a New chat from a saved
folder chip and signs the chip into its first message
</details>
<details><summary>In the previous version of this PR, not carried over
(24)</summary>
- `src/bundled/channels/ChannelCompletion.test.tsx`: in a task draft,
`#` offers the region's workspaces instead of channels; a task chat
keeps channel links
- `src/bundled/mentions/team-mentions.test.tsx`: a host task composer
offers its repositories folder's folders as chips, listed once per
query; other composers never list it
- `src/bundled/mentions/team-mentions.test.tsx`: an `@` query reopened
at the same position, after a preference change, or retried after a
failed read lists the folders again; a closed query's late listing never
publishes
- `src/features/conversation/completion.test.ts`: keeps chip and
workspace edits only from a provider a bundled plugin registered, for
that registration's lifetime
- `src/features/messages/MessageComposer.test.tsx`: a bundled `#`
workspace choice files a task draft in one undo step, and nothing else
(task draft: %s)
- `src/features/messages/MessageComposer.test.tsx`: a plugin Composer
seeded with chip data never gets it signed (%s)
- `src/features/messages/MessageComposer.test.tsx`: inserts a bundled
provider's chip as one atom and signs it only in a task chat (task chat:
%s)
- `src/features/messages/MessageComposer.test.tsx`: inside a host task
draft, only its own composer gets the draft's completion context (task
draft: %s)
- `src/features/messages/follow-up-persistence.test.ts`: keeps saved
messages with invalid or oversized task tokens unsent and unchanged: %j
- `src/features/messages/follow-up-persistence.test.ts`: restores a task
chat's queued chips and signs them on the recovered send, never in
another chat
- `src/features/messages/mention-draft.test.ts`: keeps a task draft's
workspace, lifts one saved project into it, and marks other data another
build saved as unsupported
- `src/features/messages/task-tokens.test.ts`: keeps personal workspace
filing out of signed task tags
- `src/features/messages/task-tokens.test.ts`: keeps repository and
folder references in authored order, inline and without a footer
- `src/features/messages/task-tokens.test.ts`: keeps unknown versions
inert and fallback labels literal
- `src/features/messages/task-tokens.test.ts`: rejects duplicate tags,
duplicate identities, malformed shapes and oversized UTF-8 payloads
- `src/features/messages/task-tokens.test.ts`: round-trips exact
identities and extra metadata without interpreting display verification
- `src/features/messages/task-tokens.test.ts`: serializes syntax-like
token labels literally in place, including under rich formatting
- `src/features/relay/send-to-channel.test.ts`: a host task reply signs
its chips once and retries the exact bytes; the public API cannot add
chips
- `src/features/solo/SoloDraft.test.tsx`: gives the task draft's own
region binding to its composer's completions
- `src/features/solo/SoloDraft.test.tsx`: restores %s chip saved beside
a staged chat link across remount; starts the task with it inline and
signed only if it is for the primary agent
- `src/features/solo/SoloDraft.test.tsx`: resumes the saved task pair
before prepare and transfers edits, including a chip-only change;
refuses malformed signed chips (%s)
- `src/features/solo/task-mode.test.ts`: checks signed chips against the
task chat's recipients, refuses them outside a task chat and malformed
ones on send and first-event retry, and ignores unknown versions
- `src/features/solo/task-mode.test.ts`: keeps malformed task-token and
live task-member guards ahead of informational policy
- `src/features/solo/task-mode.test.ts`: refuses recognized information
carrying %s even if ordinary admission would allow it
</details>
buzz-review-completed
---------
Signed-off-by: Gil Birman <[email protected]>
Co-authored-by: Codex <[email protected]>
Co-authored-by: Claude <[email protected]>
## Summary Offers `/` skill chips for the addressed local agent (`c99d0ce51`). - **Listing:** in the desktop app, `/` at a word start lists the skills of the one local Claude or Codex agent that the viewer's message addresses. - Claude's skills come from `~/.claude/skills`; Codex's from `~/.codex/skills` and `~/.agents/skills`. - They show under a "Skills for <agent>" heading. Completion rows gain an optional group. - Several addressed agents make the menu ask for one. A remote agent, another person's agent, a person, or a browser build lists nothing. - **The chip:** a chosen skill is a chip for that agent. It opens its short description and location in place, as a nested popover. It sends as literal text plus the signed `buzz-tokens-v1` tag, and no skill content is copied into the message. - **Native listing:** `local_skills_list` reads only SKILL.md front matter, through the bounded listing helper. It skips front matter over 16 KiB and refuses more than 512 skills. - **Agent grant:** a local agent gets one prompt line naming each SKILL.md, only when the message's author is the agent's owner and the chip names that agent. ### Compared with the previous version of this PR The previous version offered session skills through the removed task mode's skills source. Skills now come from the agent's own folders on this machine, for any chat that addresses one local agent. ## Testing Local runs on macOS, on the stack rebased onto main `9596cd3db`: - The pushed heads differ from the tested `adbdf424e` chain only in `tests/browser/solo-rename.spec.mjs`. `02f9c0277` makes the new plugin-owned lifecycle test wait for the chat's row before filing it, after the hosted flake check caught it racing the roster load. With that fix, the full `solo-rename.spec.mjs` passed 180/180 at `02f9c0277` (10 repeats, Chromium and WebKit) and 120/120 at `c99d0ce51` (5 repeats). - At this PR's head `adbdf424e`: `pnpm check` (Biome, TypeScript and the design checks) and the icon check passed. Vitest for the files the flake check selects for this PR: 1485 passed, 16 skipped. - Rust at `adbdf424e`: `cargo fmt --check` and `cargo clippy -p Buzz --all-targets -- -D warnings` passed. `local_skills` and `host_files` tests at this head: 4 and 24 passed. `cargo-deny check` passed at the stack tip. - At the stack tip `adbdf424e`: full Vitest, 20556 passed and 23 skipped (includes `browser-host/channel-kit.test.mjs`). - Browser journeys at the stack tip, Chromium and WebKit: the full `tests/browser/solo-rename.spec.mjs`, repeated 5 times, passed 120/120. The 34 other specs that the flake check relates to the stack, run once in each engine: 343 passed, 3 skipped by their own conditions. The full browser suite was not run locally. - No plugin harness pin includes this commit, so the plugin harness did not exercise it. **Native testing:** Gil ran the native desktop app on this stack at `adbdf424e`, with the matching Nexus plugin build. Gil confirmed that these native-only paths passed: - `!` folder listing through the Tauri `repositories_folder_list` command (#2789); - `/` SKILL.md listing through `local_skills_list` (#2790); - the Claude `--add-dir` folder grant (#2789); - relay signing and Outbox retry after a native restart. Agent testing did not reach these paths: the agent ran no native app, no real relay and no real agent. ## Known gaps - Gil exercised skill listing natively, and it passed. Agent testing did not reach skill listing or the SKILL.md prompt line. Hosted or local failures that this stack does not cause: - `tests/browser/typing.spec.mjs:290` ("a growing channel/thread composer lifts history with it at the bottom") intermittently fails with React error #185 on main too. Locally it failed 12 of 12 runs at `9596cd3db` and 9 of 12 at a later main commit. - On macOS, `cargo test -p Buzz` crashes the library test binary (SIGSEGV), and some `host_command` deadline tests fail. Both happen the same way at base `9596cd3db`, before this stack. Hosted Linux CI is the reference for these. ## Test changes Titles are compared only in the test files each range changes. Tests that moved or were renamed are matched by title. <details><summary>Removed by this PR's diff (0)</summary> - none </details> <details><summary>Added by this PR's diff (6)</summary> - `src/bundled/claude-code/runtime.test.ts`: names a skill its owner chose for this agent by chip, never one chosen for another agent - `src/bundled/mentions/SkillCompletion.test.tsx`: lists nothing unless one addressed agent is the viewer's own local Claude or Codex agent - `src/bundled/mentions/SkillCompletion.test.tsx`: matches `/` and name characters at a word start, never mid-word or past a space - `src/bundled/mentions/SkillCompletion.test.tsx`: offers the one addressed local agent's skills as chips for that agent, listed once per query - `src/bundled/mentions/SkillCompletion.test.tsx`: shows a failed listing with Retry, which lists again - `src/features/agent-harness/referenced-folders.test.ts`: names only skills the owner chose for this agent, quoted </details> <details><summary>In the previous version of this PR, not carried over (8)</summary> - `src/features/host/service.test.ts`: inspects paths with the calling plugin grant and preserves missing/dangling/error results - `src/features/messages/MessageComposer.test.tsx`: shows a task chat's skill details through its source, without a failing or malformed report - `src/features/messages/composer-token.test.tsx`: opens a skill chip's details in place by pointer or key, and dismisses only them - `src/features/messages/composer-token.test.tsx`: returns the caret right after a skill chip when its details close toward it - `src/features/solo/task-skill-completion.test.tsx`: matches `/` only after a boundary, up to the next slash - `src/features/solo/task-skill-completion.test.tsx`: offers a task chat's skills for its one tagged agent or primary and rechecks them on selection - `src/features/solo/task-skill-completion.test.tsx`: refuses a task source whose skills cannot be called - `src/features/solo/task-skill-completion.test.tsx`: reports an empty or failed load, and offers nothing outside a task chat or without a skills source </details> buzz-review-completed --------- Signed-off-by: Gil Birman <[email protected]> Co-authored-by: Codex <[email protected]> Co-authored-by: Claude <[email protected]>
## Why `typing.spec.mjs:290`, "a growing channel composer lifts history with it at the bottom", and its thread variant fail intermittently on main and on unrelated PRs. The failure is an unexplained page error: `Minified React error #185` (maximum update depth exceeded). It started showing up in CI with the move to larger runners (#3090), but the bug is in the product. Fast typing in a composer can throw this for real users too. This unblocks CI on #3084 and the other PRs that hit the chromium 4/9 failure. ## Root cause The loop is in `MessageComposer`'s `Composer`, in the effect added by #2271 that retires oversized-paste feedback: ```ts useEffect(() => { setPasteTooLong(false); }, [draft, editing.target]); ``` 1. The effect runs on every keystroke, because `draft` changes. 2. React only skips a same-value `setState` early when the component has no pending work. While typing it always has some, so `false → false` still queues a render. 3. Keystrokes commit synchronously, and a synchronous commit flushes passive effects inside the commit. So every keystroke's commit ends with that render still pending. 4. React counts a commit that leaves render work pending as a nested update. It resets the count only after a commit that leaves none. 5. When keys arrive faster than the pending render can run by itself, the count climbs across keystrokes. After 50 it throws #185 at the next `setState`, which is `onDraftChange` in `EditableInput`'s `dispatchTransaction`. **How it was found.** A development React build never reproduced it (0/16): it is slow enough that the pending render runs between keys. So I patched `react-dom-client.production.js` temporarily in an unminified production browser build. The patch logged each `scheduleUpdateOnFiber` call (component, lane, execution context) and each commit that increments the nested-update count. Every one of the 46 counted commits had the same shape: - `Composer`'s keystroke updates, at the synchronous lane. - One more `Composer` update at the default lane, scheduled during commit from `commitPassiveMountOnFiber`. This is the effect above. ## Fix Clear the flag only while the feedback is shown: `if (pasteTooLong) setPasteTooLong(false);`. Behaviour is unchanged. A changed draft or edit target still retires shown feedback, and hidden feedback no longer queues a render on each keystroke. Deleting the effect's body passed every existing test, so the effect had no coverage. I added `retires feedback when an edit replaces the draft`. It passes with this change and fails if the effect stops clearing. There are no allowlist entries, retries or skips. ## Evidence All runs are local: macOS, Chromium, `bin/pnpm test:browser --project=chromium --no-deps tests/browser/typing.spec.mjs -g "a growing" --repeat-each=10`. | Snapshot | Result | |---|---| | main `b037c470b` | **19 failed, 1 passed**. Every failure is `Minified React error #185` | | this branch | **20 passed** | Other checks: - With the instrumented React build and the fix, 10/10 passed, and the nested-update count never reached the logging threshold of 45. Before the fix it reached it on every failing run. - Full `typing.spec.mjs`, Chromium and WebKit: 10/10 passed. - `vitest run src/features/messages/`: 99 files and 2,995 tests passed, including the new test. - `biome check --error-on-warnings` on the changed files and `pnpm typecheck` pass. Hooks were skipped (`--no-verify`), so the checks above were run by hand. ## Not done - I didn't try this in the desktop app. The behaviour change is the absence of a crash under fast typing, which the journey covers. - No human has tested this yet, so `buzz-review-completed` is not set. 🤖 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 Opus 5.5 <[email protected]>
What this does
Adds three personalized quick reactions and the full emoji picker to the message menu. Reactions show a count of people; selecting your reaction again removes it instead of adding another copy. Custom emoji keep the image that was attached to the original reaction.
This is PR 2 of 3. The menu foundation (#182) has merged; this branch is now rebased onto
mainat222605777f8808617026ea33a0aad266bdaff4c6. Message editing and other management controls remain in PR 3.Why it matters
People can react from channels, DMs, and threads without leaving the conversation. Failed changes expose a retry action, including when the last reaction disappears. Counts and your selected state stay synchronized between the timeline and thread.
How it works
Reuses the existing emoji contribution, design-system controls, session delivery queue, and retry/reconnect behavior. Grouped reactions retain authors and event IDs so removal deletes only your events, including duplicates. A shared author-only deletion operation also supports PR 3's message management; the broker admits its bounded shape without replacing workflow-deletion validation.
Quick choices use this viewer/community's local usage history, with three defaults and unavailable custom emoji filtered out. Mounted shortcuts do not reshuffle on each selection; new row mounts, catalog changes, reloads, or cross-window preference changes can refresh them. The menu stays visible while its picker is open, using the trigger's existing expanded state rather than adding another popup owner.
Latest shared-picker update
At
7c925972aca0001ff58d0e601554cc1317d6e145, reaction buttons remain eagerly available, while the emoji plugin coordinates one active picker. Inactive rows no longer mount picker state/popover machinery. Switching targets replaces the picker; Escape restores focus only to the active, connected, enabled trigger. Removed/disabled rows release the picker. Composer behavior and quick-choice ranking are unchanged.Validation on this clean commit: complete reactions/emoji/GIF browser files 12/12 passed across Chromium and WebKit; full Vitest 2,486/2,486 passed in 239 files; mandatory push hooks passed (types, 1,348 related tests, design-system guards, formatting/lint and security checks). First push was blocked by the unchanged MessageActionBar keyboard-focus unit test; the full suite and subsequent unmodified hook run passed. This is an observed intermittent failure, not a claimed repair.
One browser scenario was added (two engine cases), no cases removed: real pointer/keyboard retargeting, single visible portal, Escape focus return, remount and archive cleanup. Browser-only justification is actual popup positioning, pointer interaction and focus lifecycle. The shared-owner exclusivity assertion has not been mutation-tested against the parent; no fail-then-pass claim for that new assertion. Independent read-only review cleared the bounded lifecycle changes.
Local pre-commit probe on the same production delta: three serial runs per engine retained immediately present hover buttons; warm switching medians were 39.0 ms Chromium / 63.5 ms WebKit. This does not establish a hosted-CI performance fix. Main now independently contains #195, which retains a 100 ms target but uses a 200 ms failure ceiling; this PR does not edit that gate. A green hosted result must not be attributed solely to the picker change.
Hosted checks for this new head are pending. PR is ready for review, not asserted merge-ready. No merge performed. Live relay and packaged-app acceptance remain unverified.
Historical verification (previous head)
Previous pushed head:
665a8c3b7899dd23ec80958b70257d49585a5ff5; the following results predate the shared-picker update.At this head, mandatory hooks passed: TypeScript, 1,348 related tests in 91 files, design-system types/guards, staged formatting/lint, and sadscan. Hosted run https://github.com/block/buzz-app/actions/runs/35936296156 completed: JavaScript (2,486 tests in 239 files), Rust/tool integration, all four Chromium/WebKit journey shards, and DCO passed. Browser measurements failed on the second warm Beta switch: 120.5 ms against the unchanged <100 ms requirement. The dependent CI-required check therefore failed. That run was not merge-ready; the PR is now ready for review with a newer head.
At its direct parent
37b5107e3eb4fa2e66085b48a6db704029e4f4f5, the complete messages/presence/reactions/message-actions browser files passed 28/28 in Chromium and WebKit; isolated channel-opening measurements passed 4/4 across both engines. The only subsequent change is Biome formatting in the thread navigation fixture, repairing the hosted formatting failure. Browser behavior was not changed by that formatting commit.The rebased fixture now advertises reaction removal, scopes the media-comment picker to the exact reply, and selects an unowned reaction while retaining signed event/target and canonical thread-root assertions. The modified-link test observes that Buzz leaves the browser default uncanceled and does not route internally. It deliberately does not test loading a modified-click background tab: an app-free cross-origin Chromium crash was reproduced. Real unmodified external popup navigation remains covered. Independent read-only review cleared both bounded repairs. No timeout or assertion-count relaxation.
The previous hosted run also exposed intermittent large-thread presence timing; that production/test path remains unchanged. Both hosted WebKit shards now pass, but one green run does not establish deterministic timing.
Performance diagnosis: the base main run (
35934216275, base22260577) measured warm switches at 42.5–50.6 ms; this PR run measured 83.7/120.5 ms before failing. Cross-run numbers alone do not isolate causality. A local research-only comparison on the same checkout, with 3x CPU throttling and 12 switches, measured median warm paint 92.25 ms with quick reaction controls versus 76.75 ms with just those controls omitted (first-visible medians 87.85/72.60 ms). This implicates control rendering as additional work, but does not prove removing that work alone would meet hosted CI. Temporary probes/omissions were removed; no assertions or time limits were changed in committed code. At that historical head, no production performance fix had been made.Previous UI follow-up evidence at
b3c474ca:Review follow-up: quick emoji now use the existing small icon buttons, matching adjacent actions; removing a focused last reaction returns focus to that message’s persistent menu trigger; empty reaction rows add no spacing while retry remains mounted and unclipped. No custom control styling or new browser cases. The spacing and keyboard-removal assertions failed before the fixes in both engines. At this head, all 8 cases in the full reactions/message-actions browser files passed in Chromium/WebKit, including failed first-reaction retry and failed-removal rollback; mandatory types, 1,345 related tests and design-system checks passed. Princess Donut’s targeted read-only review found no remaining issue.
The broader verification below was run at the preceding head
fc187bc; it is not a claim that the full suite was rerun for this UI-only follow-up.message-actions,reactions,emoji, andtypeaheadbrowser files. Covers real menu/picker wiring, channel/thread synchronization, DM add/remove, actual signed reaction/deletion publication, custom emoji, failed-removal retry after remount, focus return, touch, and narrow/intermediate/wide layouts.Browser coverage changes
Before the shared-picker update above, no browser cases were added or removed. Extended the existing reactions journey for first reactions, true removal, retry, historic custom-emoji identity, picker visibility and responsive bounds. Extended the existing channel/thread and DM message-action journeys for representative full-app wiring. Browser-only justification: real portal/pointer/focus/layout behavior and integration through the production broker; author/retry/reconnect matrices remain in lower-layer tests. No replacement coverage was deleted.
Fail-then-pass evidence: the picker visibility assertion failed in both engines before the CSS fix; the row-identity regression failed when reaction event identity was not compared. Both pass at the checked head. Initial full-app reactions failed because the fixture only admitted messages/read-state; the fixture now strictly validates signed reaction/removal targets, channel, and ownership before retention/live echo. Assertions and error allowlists were not relaxed.
Local macOS timing, two workers, Chromium + WebKit: the affected menu/reaction files passed in 11.2s (browser test durations 0.7–3.7s each). Before fixture admission they aborted on unexpected kind 7, so there is no meaningful passing baseline or speedup claim. Emoji/typeahead files passed in 34.2s. These are local functional-run timings including fixture setup, not isolated setup measurements or hosted CI performance results.
Remaining gates
The latest hosted checks and required human/code-owner review remain gates; the historical warm-switch failure is not claimed fixed by local measurements. Full browser suite and native builds were not run locally; existing CI owns broader automated validation. Packaged-app and live-relay acceptance remain unverified. No merge performed.
Visual check
Synthetic fixture: personalized shortcuts, grouped custom reaction, and the existing picker remaining open beside the message.
Narrow full-app layout: