Commit Graph
1318 Commits
Author SHA1 Message Date
1d3b810ad7 fix(desktop): paint community rail full height (#3382)
## Summary

- paint the community rail across the full app height instead of
exposing the parent background through external margins
- preserve the existing community-button alignment and balanced
horizontal gutters by moving vertical spacing inside the rail
- update the rail geometry coverage to require full-height paint
ownership

## Root cause

PR #2972 aligned the rail box with the inset content by adding top and
bottom margins to the `bg-sidebar` element. Margins are outside the
painted box, so flat light and dark themes exposed a differently colored
app background above and below the rail.

## Validation

- pre-push `desktop-check`
- pre-push desktop unit suite: 3,751 passed
- `git diff --check`

Local Playwright/E2E was not run; CI owns the full browser matrix.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-28 20:17:01 +00:00
Will PflegerandGitHub b0503d80c2 feat(desktop): add custom harness inline from agent dialogs (#3252)
Registering a custom ACP harness works today, but only from Settings →
Agents. Anyone whose first touchpoint is "New agent" has no way to
discover the custom path — the dropdown just lists the baked-in presets
plus whatever was registered earlier. This adds an inline "Add custom
harness…" entry to the harness dropdown in all three agent surfaces:
create, edit-definition (`AgentDefinitionDialog`), and instance edit
(`AgentInstanceEditDialog`).

The entry is a sentinel option (`ADD_CUSTOM_HARNESS_VALUE`, NUL-prefixed
so it can never collide with a real harness id — backend ids match
`[a-z0-9_][a-z0-9_-]*`), mirroring the `CUSTOM_ENTRY_ID` trick already
used in `HarnessCatalogDialog`. Picking it never writes into form state;
it opens `AddCustomHarnessDialog`, a thin modal wrapper hosting the
existing `CustomHarnessForm` in `chromeless` mode. `CustomHarnessForm`'s
`onSaved` now carries the saved `definition.id` (the form may rewrite
it); the two existing call sites ignore the argument, so their behavior
is unchanged.

Selection after save is deferred rather than immediate.
`usePendingHarnessSelection` holds the saved id until the runtime
catalog actually publishes it via discovery, then selects it exactly
once — so the dialog never selects an id it cannot render, and
back-to-back registrations resolve correctly. The wait is scoped to the
owning dialog's `open` state: both host dialogs stay mounted when
closed, so an unpublished id is dropped on close rather than selecting
into reset form state when discovery later catches up. Selection is
routed through each dialog's normal dropdown change handler, so
provider/model reset (and command pinning in the instance dialog) behave
identically to a hand-picked harness. Dismissing the modal leaves the
previous selection untouched. `AgentInstanceEditDialog`'s existing
"Custom command" option is a different feature (ad-hoc command override
vs. a registered reusable harness) and is untouched.

Coverage is 16 unit tests in `addCustomHarness.test.mjs` (real React
mount, following the existing `.test.mjs` pattern) plus 4 Playwright
specs in `inline-custom-harness.spec.ts` covering all three surfaces
end-to-end. Both suites were mutation-verified: treating the sentinel as
a real selection, selecting before the catalog publishes, never clearing
the pending id, ignoring the dialog's open state, and reversing
latest-save-wins each turn the unit tests red; reverting the two dialog
diffs turns all four e2e specs red. The `check-file-sizes.mjs` overrides
for the two dialogs are ratcheted to their exact new counts (1048 and
1229) — verified tight in both directions, N passes and N−1 fails, so no
headroom is introduced.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-28 16:01:41 -04:00
a35771fc44 feat(desktop): refine agent catalog sharing (#2439)
## Summary

- add custom-agent catalog sharing and hide built-ins from discovery
- let owners publish later catalog updates from Share or while saving
edits — the save always persists locally, and the publish reports
`published` or `queued` (flushed automatically once the relay is
reachable again)
- preserve agent type, model, and runtime across snapshot import/export
- simplify agent and team entry points and tighten catalog layout
- migrate the legacy global retention queue into the owner's active
scope so pending catalog publishes survive the upgrade
- keep the agent list and edits usable in recovery mode by degrading to
unshared projections when scope resolution or the retention DB fails
- track catalog provenance on copied personas, so adding an
already-added foreign agent resolves to the existing copy instead of
creating a duplicate
- scope inbound persona events to the community relay they arrived on
- page the catalog read past the relay's 1,000-row query clamp
- unify the share dialog's memory-level choice into a single "What's
included" selector that drives both DM-send and copy-link delivery (all
six combinations preserved), group the delivery rows above the option
rows, and label the catalog toggle "Not shared" / "Shared"
- keep emoji avatars on catalog entries — they persist as inline
percent-encoded SVG, which the catalog projection's http(s)-only URL
guard used to drop, so a shared agent showed initials instead of its
avatar
- drop the "Active in communities" card from Agents settings, superseded
by the per-channel runtime controls in the members sidebar

## Screenshots

### Agent actions

![Agent
actions](https://raw.githubusercontent.com/block/buzz/4643581cd0882d8b101b04e3d8be290ea7e39f08/pr-2439--01-agent-menu.png)

### Team avatar stack

![Team avatar
stack](https://raw.githubusercontent.com/block/buzz/4643581cd0882d8b101b04e3d8be290ea7e39f08/pr-2439--02-team-menu.png)

### Catalog sharing

![Catalog
sharing](https://raw.githubusercontent.com/block/buzz/648d6eaf6df7d4daa5b7375948ed221f723793d6/pr-2439--03-share-to-catalog.png)

### Publish while editing

![Publish while
editing](https://raw.githubusercontent.com/block/buzz/bd4eb473ff456f6e665173054dc5a0f764594d1e/pr-2439--01-edit-agent-publish-updates.png)

### Publish from Share

![Publish from
Share](https://raw.githubusercontent.com/block/buzz/648d6eaf6df7d4daa5b7375948ed221f723793d6/pr-2439--02-share-dialog-publish-updates.png)

### Catalog details

![Catalog
details](https://raw.githubusercontent.com/block/buzz/4643581cd0882d8b101b04e3d8be290ea7e39f08/pr-2439--04-agent-catalog.png)

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-28 15:01:03 -04:00
thomaspblockandGitHub 3afa129ee7 fix(desktop): keep drafts out of the Inbox All view (#3217)
## Summary

Drafts were showing up in the Home Inbox **All** view, mixed in with
messages and reminders (reported in `#buzz-bugs`). Drafts are private
composer state, not inbox activity — they now appear only under the
dedicated **Drafts** filter.

## Changes

- **`inboxListRows.ts`** — drop the `draft` row variant from
`buildInboxListRows`; the mixed view builds only `inbox` + `reminder`
rows.
- **`InboxListPane.tsx`** — remove the draft branch of the All-view
render path; `PersonalItemRow` now renders reminders only.
- **`useHomePersonalInbox.ts`** — stop enabling draft selection (and its
root-status relay probing) for the mixed view; draft selection is scoped
to the Drafts filter.
- Drafts filter behavior is unchanged: the filter badge count,
`DraftsPanel` list, and `DraftDetailPane` all still work.

## Testing

- `pnpm test` (desktop unit suite): 3697 passed, 0 failed.
- `pnpm exec biome check src/features/home tests`: clean.
- Updated `inboxListRows.test.mjs` for the two-variant row model.
- Updated the e2e test (`channels.spec.ts`) to assert All never lists
drafts and that the draft is still reachable under the Drafts filter.
- Added `drafts-all-fix-screenshots.spec.ts` capturing both states
(screenshots below).

### All view — draft is gone, messages/reminders unaffected


![01-all-view-no-drafts](https://raw.githubusercontent.com/block/buzz/12c97624832cef40df951c403982994fea58dd80/pr-3217--01-all-view-no-drafts.png)

### Drafts filter — the draft is still listed and editable


![02-drafts-filter-still-lists](https://raw.githubusercontent.com/block/buzz/12c97624832cef40df951c403982994fea58dd80/pr-3217--02-drafts-filter-still-lists.png)

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
2026-07-28 19:37:12 +01:00
00ede2e7aa fix(desktop): restore the inbox icon in the sidebar (#3341)
## Why

The Inbox surface was briefly renamed to **Activity** during #2045 and
picked up a bell icon to match. The name was reverted to **Inbox**
before merge, but the icon was not.

A bell says "notification tray." Inbox is a destination — a focused,
conversation-oriented place to catch up on work relevant to you,
including drafts and reminders that have nothing to do with
notifications. The glyph should say that.

## What changed

- Swap the sidebar entry from Lucide `Bell` to Lucide `Inbox`.
- Assert the icon in `inbox-refactor-screenshots.spec.ts`. Nothing
pinned it before, which is exactly how it drifted through a rename.

This also brings desktop back in line with mobile, which already uses
`LucideIcons.inbox300` / `inbox500` for the same destination.

## Deliberately unchanged

The bell on **reminder** rows in the list pane (`InboxListPane.tsx`,
reminders → bell, drafts → file) stays. A bell is the right glyph for a
reminder; that one was never about the surface's identity.

## Verification

- The new assertion is a real guard, not a no-op: with `Bell` restored
the test fails with `Expected: 1, Received: 0` on `svg.lucide-inbox`.
Confirmed before committing.
- `biome` and `tsc` clean.
- Playwright smoke: `inbox-refactor-screenshots` 4 passed; `smoke`,
`navigation`, `channels`, `sidebar-more-unread-overlap`,
`home-collapsed-top-chrome`, `workspace-rail` — 107 passed, 1 skipped.
- Screenshot below is the regenerated `02-current-controls` shot from
the spec.

Signed-off-by: Clay Delk <clay.delk@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 14:01:18 -04:00
Will PflegerandGitHub 4e3998f36e fix(desktop): gate codex-acp on a minimum supported version (#3254)
The codex adapter version gate accepted any `major >= 1`, so a 1.x
`codex-acp` older than the version that fixes outbound relay access for
`buzz` CLI subprocesses classified as `Available` and was never offered
a reinstall. Only the 0.16.x `@zed-industries/codex-acp` adapter — which
fails `--version` outright — was caught.

`probe_codex_acp_version` now returns the full `(major, minor, patch)`
triple and `codex_adapter_availability` compares it against a new
`MIN_CODEX_ACP_VERSION` floor of `1.1.7`, the current npm latest. An
adapter below the floor classifies as `AdapterOutdated`, which routes it
through the existing uninstall-then-install reinstall plan.

The parse requires exactly three numeric dot-separated components.
Partial versions (`1.2`) and prerelease tags (`1.2.0-rc1`) return `None`
and therefore classify as `AdapterOutdated` — a version Buzz cannot
compare against the floor fails closed, offering a reinstall rather than
running an adapter of unknown vintage. Both the floor's bump policy and
the strict-parse behavior are stated in doc comments rather than left
implicit.

Supersedes [#3097](https://github.com/block/buzz/pull/3097) by
@Bharathchinneni, whose semver floor and behavior tests this carries.
That PR could not land as written: the two
`probe_codex_acp_major_version` compatibility wrappers it kept had no
non-test callers, which is a hard `clippy -D warnings` failure. The
wrappers are deleted here and their call sites collapsed onto
`probe_codex_acp_version`.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-28 13:56:59 -04:00
5457c947a7 fix(composer): scope multiline block formatting (#3246)
**Category:** fix
**User Impact:** Composer block formatting now applies to the intended
line or selection without collapsing multiline content.

**Problem:** Block formatting from a Shift+Enter line could convert the
entire draft, selected visual lines could collapse into one list item,
and code conversion could lose line breaks. **Solution:** Scope caret
formatting to its hard-break-delimited line and normalize explicit
selections for the destination block type while preserving neighboring
content and visual line boundaries.

<details>
<summary>File changes</summary>

**desktop/src/features/messages/lib/selectionBlockFormatting.ts**
Scopes collapsed-caret block actions to the active visual line and
normalizes multiline selections for lists and code blocks.

**desktop/src/features/messages/lib/selectionBlockFormatting.test.mjs**
Adds unit coverage for caret-line isolation across line positions and
selection directions.

**desktop/src/features/messages/ui/FormattingToolbar.tsx**
Routes list, quote, and code-block actions through the selection-aware
formatting transaction.

**desktop/tests/e2e/composer-selection-formatting.spec.ts**
Covers caret-only formatting, multiline list conversion, list-to-code
conversion, preserved hard breaks, Markdown output, and backward
selections.

</details>

## Reproduction steps

1. In the desktop composer, enter several lines using Shift+Enter and
place the caret on one line.
2. Apply a bullet list, ordered list, quote, or code block; only the
caret line should change.
3. Select several Shift+Enter lines and apply a list; each visual line
should become its own item.
4. Select several list items and apply Code block; they should become
one multiline code block while unselected neighbors remain intact.
5. Select several Shift+Enter lines and apply Code block; each line
break should remain visible.

## Screenshots/Demos
<img width="508" height="222" alt="Screen Recording 2026-07-27 at 5 29
19 PM"
src="https://github.com/user-attachments/assets/35640dea-0cfb-44f1-9b0b-a993c69cb55f"
/>

Expected multiline code-block result:
https://buzz.block.builderlab.xyz/media/d2e2668093af3b67d896a32e9799daccd236da9fc9e24ec56ddb4ebf7d01dd96.png

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
2026-07-28 13:31:27 -04:00
WesandGitHub 4a977c588a chore(release): release Buzz Desktop version 0.5.0 (#3213)
## Buzz Desktop release v0.5.0

### Changes since v0.4.26:

- feat(invites): add use-limited invite links
([#3141](https://github.com/block/buzz/pull/3141))
([`d500c2d5c`](https://github.com/block/buzz/commit/d500c2d5cf5d9aabe0ca4ebebfcafdbe5f5b7fd3))
- fix(node): bump Buzz-supplied Node runtimes past OpenClaw's >=24.15.0
floor ([#3218](https://github.com/block/buzz/pull/3218))
([`98a7b1334`](https://github.com/block/buzz/commit/98a7b1334823ee0be3e3fa5cab7a2e349e438dab))
- fix(desktop): preserve thread anchor through layout reflow
([#3212](https://github.com/block/buzz/pull/3212))
([`9810d8545`](https://github.com/block/buzz/commit/9810d8545937329f229ff40d8a19edc9e3e325c1))
- feat(search): parse from:/in:/after:/before: and pass them in the
filter ([#2871](https://github.com/block/buzz/pull/2871))
([`cb2a265b5`](https://github.com/block/buzz/commit/cb2a265b5399426e808461c1a16713754c593258))
- fix(desktop): fetch join policies through native networking
([#2862](https://github.com/block/buzz/pull/2862))
([`0019f8076`](https://github.com/block/buzz/commit/0019f80765e96f056e81b57789b8b5fb80936f72))
- fix(desktop): republish agent identity records when a persona rename
propagates ([#2607](https://github.com/block/buzz/pull/2607))
([`7ca0bbd94`](https://github.com/block/buzz/commit/7ca0bbd946fd82a7008132f94d069a97bb53f94b))
- fix(desktop): keep project Inbox previews compact
([#3193](https://github.com/block/buzz/pull/3193))
([`de1396050`](https://github.com/block/buzz/commit/de13960505fd798070e177cb33b1663100ac06bb))
- Inbox refactor ([#2045](https://github.com/block/buzz/pull/2045))
([`2bd4c24b7`](https://github.com/block/buzz/commit/2bd4c24b71335e7ce272ec6de6491f7f37f4b20d))
- Fix composer selection formatting and drop overlay
([#3172](https://github.com/block/buzz/pull/3172))
([`99da5b7eb`](https://github.com/block/buzz/commit/99da5b7ebb19e26453e075bfb949672122b31be3))
- Refine pending message status
([#3153](https://github.com/block/buzz/pull/3153))
([`75588eaff`](https://github.com/block/buzz/commit/75588eaff2354d620e554c055b80ec83735ddb0a))
- fix(desktop): recover full local storage on startup
([#3182](https://github.com/block/buzz/pull/3182))
([`174c38e4b`](https://github.com/block/buzz/commit/174c38e4bd1ed8498641546bc4fcb6d5a4c9cede))
- fix(desktop): keep collapsed table separators out of spoilers
([#3169](https://github.com/block/buzz/pull/3169))
([`4d8b676bb`](https://github.com/block/buzz/commit/4d8b676bb283a1917cec5850c3b7327fe122b0c1))
- feat(desktop): redesign agent runtime settings
([#3093](https://github.com/block/buzz/pull/3093))
([`d98da7389`](https://github.com/block/buzz/commit/d98da7389e60cfbd79b219aa411449fe2e53a18a))
- fix(desktop): use forward slashes for git credential.helper on Windows
([#3023](https://github.com/block/buzz/pull/3023))
([`899531684`](https://github.com/block/buzz/commit/8995316844f7ad50552fbae67fbd35119262796f))
- chore(desktop): add AgentCreationPreview file-size override to unblock
main CI ([#3154](https://github.com/block/buzz/pull/3154))
([`b92a1f4bf`](https://github.com/block/buzz/commit/b92a1f4bf400e7da5ab7a010cdd81a69497d8191))
- fix(desktop): make the test loader work on Windows
([#2758](https://github.com/block/buzz/pull/2758))
([`8bb43d519`](https://github.com/block/buzz/commit/8bb43d51912894553f2670b2d285a96cf09cd472))
- fix(desktop): make lint and unit-test gates work on Windows
([#2943](https://github.com/block/buzz/pull/2943))
([`545bb46b8`](https://github.com/block/buzz/commit/545bb46b824a3fbf4401062f03b72531d832ebb9))
- feat(desktop): add search to agent emoji picker
([#2630](https://github.com/block/buzz/pull/2630))
([`313f793c8`](https://github.com/block/buzz/commit/313f793c8753d413c22ff8edfe420d5ee78708bc))
- fix(desktop): keep identity key help dialog readable in dark mode
([#2854](https://github.com/block/buzz/pull/2854))
([`be275cfc6`](https://github.com/block/buzz/commit/be275cfc6c7b80fe43e9d66c6d14b6d2bbe58a10))
- feat(acp): title agent sessions from the agent and channel name
([#3028](https://github.com/block/buzz/pull/3028))
([`f2fe3b63c`](https://github.com/block/buzz/commit/f2fe3b63c21be55907175715c076cd3a9195b74d))
- feat(git): use agent display name as git author name
([#3040](https://github.com/block/buzz/pull/3040))
([`18eef633d`](https://github.com/block/buzz/commit/18eef633d88ac465c61d98f12655fbf51dc3ca44))
- fix(deps): bump nostr to 0.44.6 for RUSTSEC-2026-0216 (NIP-44 remote
DoS) ([#3135](https://github.com/block/buzz/pull/3135))
([`31e2de196`](https://github.com/block/buzz/commit/31e2de1966672e73e026af3c54f3a1a9a2f5e103))
- fix(desktop): read the newest pair-scoped harness log
([#3134](https://github.com/block/buzz/pull/3134))
([`654f38490`](https://github.com/block/buzz/commit/654f384906b5c720a60a199d85031a6f1cb6efc9))
- feat(desktop): handle project work from Inbox
([#3117](https://github.com/block/buzz/pull/3117))
([`c5c4f390b`](https://github.com/block/buzz/commit/c5c4f390b6713256e2efb8394c59823ebad73db6))
- fix(desktop): clarify identity key button when key exists
([#2357](https://github.com/block/buzz/pull/2357))
([`87b3fcd3c`](https://github.com/block/buzz/commit/87b3fcd3c0131683569dd4268b099d18b25dcd5e))
- Restore Goose and Buzz Agent to onboarding harness selection
([#2731](https://github.com/block/buzz/pull/2731))
([`7fc0cc82d`](https://github.com/block/buzz/commit/7fc0cc82db4d9dced9c258bbe8b530164a832a77))
- fix(desktop): render rich project work item content
([#3100](https://github.com/block/buzz/pull/3100))
([`afb272bb7`](https://github.com/block/buzz/commit/afb272bb7b8d7d45d7de676fa97dcd5a8eefacc7))
- feat(acp): bring your own harness (BYOH) — generic ACP runtime seam +
settings gallery ([#2773](https://github.com/block/buzz/pull/2773))
([`95fdf9788`](https://github.com/block/buzz/commit/95fdf978800982389b120c66ff5e766d785419c7))
- feat(desktop): use collective mesh routing for Auto
([#2825](https://github.com/block/buzz/pull/2825))
([`16d4ec335`](https://github.com/block/buzz/commit/16d4ec335e210295a9d9f77f36c1e85a18b6814a))
- fix(desktop): strip legacy baked team instructions from stored prompts
([#3035](https://github.com/block/buzz/pull/3035))
([`aee631448`](https://github.com/block/buzz/commit/aee63144843854ee32ed9d36a2e7511c82ddc6b0))
- feat(agents): lower default agent parallelism from 24 to 10
([#3038](https://github.com/block/buzz/pull/3038))
([`5d8ede446`](https://github.com/block/buzz/commit/5d8ede446f8fdc48146fe56d389cab6bf3500f92))
- Polish community rail and mobile pairing
([#2972](https://github.com/block/buzz/pull/2972))
([`e6c90bb7c`](https://github.com/block/buzz/commit/e6c90bb7c430d1b2af16508b634f9a5283b7fa3b))
- fix(desktop): remove bundled libsystemd from AppImage
([#2353](https://github.com/block/buzz/pull/2353))
([`a31fc4d2f`](https://github.com/block/buzz/commit/a31fc4d2f35d51cdf45ff8c61fc3a07f49c665e8))
- fix(desktop): make agent definition authoritative for
model/provider/prompt ([#1968](https://github.com/block/buzz/pull/1968))
([`8c0e8cb16`](https://github.com/block/buzz/commit/8c0e8cb1656b04ad269bce3c2deeda2a943ae78a))
- chore(desktop): delete dead persona catalog UI cluster
([#2886](https://github.com/block/buzz/pull/2886))
([`8e67cf399`](https://github.com/block/buzz/commit/8e67cf399d0291bcdbc69cd0402983ca030f05bb))
- fix(desktop): surface install failures hidden by curl-pipe exit codes
([#2892](https://github.com/block/buzz/pull/2892))
([`166c6655e`](https://github.com/block/buzz/commit/166c6655e8bca87d83ad60c087fb70a32a026baf))
- Refactor managed-agent runtime into cohesive modules
([#2974](https://github.com/block/buzz/pull/2974))
([`74b63e184`](https://github.com/block/buzz/commit/74b63e1846212af6e6751a62cfc631f74b1dfe07))
- fix(desktop): make Linux AppImage GStreamer work on non-Debian distros
([#2176](https://github.com/block/buzz/pull/2176))
([`cc6c4d347`](https://github.com/block/buzz/commit/cc6c4d3471629fad018bcf645f9471a01b9ffe2f))
- refactor(desktop): remove Agent directory section from Agents page
([#2290](https://github.com/block/buzz/pull/2290))
([`5d1233e84`](https://github.com/block/buzz/commit/5d1233e841b0efa91470bb45467b2c8e4284ebf6))
- fix(desktop): enable arboard Wayland backend so Linux copies reach the
Wayland clipboard ([#2904](https://github.com/block/buzz/pull/2904))
([`ab7aa8b12`](https://github.com/block/buzz/commit/ab7aa8b1200710dbc2d7a8661ed5aab95c4199c1))
- fix(desktop): supervise and re-arm relay-mesh runtime
([#2823](https://github.com/block/buzz/pull/2823))
([`aa51dab9d`](https://github.com/block/buzz/commit/aa51dab9da5fef7054d03cf1a1207986d0000684))
- fix(agents): run live Databricks discovery instead of the fallback
list ([#2890](https://github.com/block/buzz/pull/2890))
([`8eb6e3eb6`](https://github.com/block/buzz/commit/8eb6e3eb601174249642373a6a367262fa476753))
- fix(desktop): retire prepend mode on every reader wheel
([#2913](https://github.com/block/buzz/pull/2913))
([`07d0265cf`](https://github.com/block/buzz/commit/07d0265cfc212ef02e1c26153bf58ff46ce5ffe6))
- fix(desktop): consolidate prepend scroll correction
([#2855](https://github.com/block/buzz/pull/2855))
([`25e7864b3`](https://github.com/block/buzz/commit/25e7864b35f4dfd1c0ff31304a38555230a85f8d))
- fix(desktop): track concurrent agent turns up to the harness maximum
([#2882](https://github.com/block/buzz/pull/2882))
([`20bff5910`](https://github.com/block/buzz/commit/20bff591023daffc5ee1032cff02b54b75da3567))
- fix(relay): preserve reconnect backoff
([#2759](https://github.com/block/buzz/pull/2759))
([`499c5d349`](https://github.com/block/buzz/commit/499c5d349dab13bc906b1af5fe1fcb09ce2afa81))
- refactor(relay): expose reconnect timing policy
([#2310](https://github.com/block/buzz/pull/2310))
([`2f0041595`](https://github.com/block/buzz/commit/2f0041595d72529c06885680d2bd07ddb6a0beb4))
- fix(desktop): clear stale working badges on agent stop/restart
([#2803](https://github.com/block/buzz/pull/2803))
([`a64cc71f6`](https://github.com/block/buzz/commit/a64cc71f6c1605279b1a6fbd0fe904a2984cbdb0))
- fix(desktop): surface agent rename relay profile sync failure as a
warning toast ([#2279](https://github.com/block/buzz/pull/2279))
([`5e3d2e484`](https://github.com/block/buzz/commit/5e3d2e4849c0f2512330801d804fb96f4ab72d28))
- fix(discovery): inject PATH into Codex adapter planning
([#2767](https://github.com/block/buzz/pull/2767))
([`6ab3835f3`](https://github.com/block/buzz/commit/6ab3835f3fe89ee215819fe8d193463c0ae7472b))

**To release:** merge this PR. The tag and build will happen
automatically.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
2026-07-28 07:21:38 -07:00
Will PflegerandGitHub be13b4bb9c fix(desktop): probe legacy Goose install dir on Windows (#3248)
Goose's pre-[#2680](https://github.com/block/buzz/pull/2680) Windows
installer unpacked the CLI to `%USERPROFILE%\goose\goose.exe`. That
directory is on no standard `PATH`, and `common_binary_paths()` never
probed it, so users who installed Goose with the legacy installer stayed
permanently undiscovered — the residual half of #2239.

`resolve_command_uncached` finds binaries outside `PATH` only by
scanning `common_binary_paths()`, so adding the directory there is the
whole fix: Windows basename expansion already supplies
`goose.exe`/`.cmd`/`.bat`, and discovery, readiness probes, and spawn
all route through the same shared resolver. No Goose-specific resolution
path is introduced. The entry sits beside the existing Codex
`%LOCALAPPDATA%\Programs\OpenAI\Codex\bin` probe in the same
`#[cfg(windows)]` block.

The regression test is `#[cfg(windows)]` and is CI-reachable, not dead
code — the `desktop-build-windows` job runs `cargo test --manifest-path
desktop/src-tauri/Cargo.toml --target $env:TARGET` on `windows-latest`.
It asserts the probe list rather than planting a binary:
`common_binary_paths` is a process-lifetime `OnceLock`, so a test cannot
deterministically re-seed `USERPROFILE`, and planting an executable
under the real user profile is not an acceptable side effect. Verified
locally by widening the `cfg` to build on macOS — the test passes with
the probe and fails without it.

The `check-file-sizes.mjs` override for `managed_agents/discovery.rs`
moves 1835 → 1841, the exact post-`cargo fmt` gate count. Verified both
directions: 1841 passes, 1840 fails.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-27 21:30:21 -04:00
Will PflegerandGitHub 94675e0d25 refactor(desktop): extract install command execution into install_exec (#3251)
`commands/agent_discovery.rs` was pinned at its 2167-line file-size
ceiling with zero headroom, blocking the install-supervision and
install-log work queued behind it. Install command *execution* is a
clean seam and moves into `commands/agent_discovery/install_exec.rs`
together with its tests, matching the existing `managed_node.rs` /
`post_install_verification.rs` split under the same module.

Moved: `INSTALL_MAX_ATTEMPTS`, `run_install_command_with_retry`,
`run_install_with_retry`, `install_failure_is_retryable`,
`install_retry_backoff`, `annotate_retry_attempts`,
`run_install_command`, `truncate_output`, `floor_char_boundary`, and the
install-retry test block. Command *construction*
(`install_shell_command`, `install_powershell_command`,
`build_install_command`) stays in the parent — the new module owns only
what happens once a `Command` exists. Public surface is exactly one
`pub(super) fn run_install_command_with_retry`.

The extraction is behavior-preserving, verified by diffing the moved
text against the original line ranges: the parent is original-minus-cuts
plus the intended edits, and the moved code is byte-identical except for
the `pub(super)` marker, the `build_install_command` →
`prepare_install_command` call site, and the new function described
below. Two parent imports (`std::io::Read`, `InstallStepResult`) became
unused and were dropped.

### Install working directory (#2245)

Absorbed from #3090. A packaged desktop launch inherits `/` as its
working directory, so installers that write relative to the CWD fail on
a read-only root. The new `prepare_install_command` builds the command
and applies `default_agent_workdir()`, and it is the only builder
`run_install_command` calls — so no spawn path can bypass the workdir.

This differs from #3090 in the test: that version spawned `pwd` through
the real install shell and deleted
`test_install_shell_command_returns_ok_on_unix` to make room. Here the
prepared `Command` is asserted directly via `get_current_dir()` —
hermetic, no shell spawn — and the existing test is kept.

### Tests

Four new, on top of the moved retry block:

- `test_prepared_install_command_uses_default_workdir` — every install
child carries `default_agent_workdir()`.
- `test_truncate_output_leaves_short_output_untouched` — under the cap,
byte-for-byte passthrough.
- `test_truncate_output_keeps_head_and_tail_with_marker` — over the cap,
both ends survive and the marker names the omitted byte count.
- `test_truncate_output_does_not_split_multibyte_characters` — the
boundary floor prevents a mid-codepoint cut.

`truncate_output` had no coverage anywhere before this.

### File-size gate

`check-file-sizes.mjs` override for `agent_discovery.rs` moves 2167 →
1808, the exact post-`cargo fmt` gate count — verified both directions
(1808 passes, 1807 fails). `install_exec.rs` is 458 lines and needs no
override; the default 1000-line limit covers it.

Related: #3090, #2245

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-27 21:29:44 -04:00
morgmartandGitHub d8f9d87c17 Polish composer activity layout and transitions (#3151)
## Summary
- Replace the permanently reserved composer activity row with a
conditional, content-driven accessory for channel and thread composers.
- Keep the composer dock geometrically stable while activity appears, so
the composer’s bottom edge resizes smoothly without shifting the
conversation.
- Preserve translucent backdrop blur during the resize with a stable
dock-level blur layer, and keep thread overlay/focus-mode alignment
consistent.
- Keep bottom-pinned virtualized conversations fully visible as composer
content, zoom, or viewport height changes without repinning readers who
have scrolled into history.
- Refine the activity lockup with aligned avatars/text and a subtle,
reduced-motion-safe shimmer.
- Centralize the dock’s quiet inset, activity rail, released space, and
activity offset in one CSS-variable geometry contract.

This takes a different, systemic route from the spacing reduction
proposed in #2602 and supersedes that approach.

### Related issue
Related PR: #2602

### Testing
- `./scripts/check-branch-skew.sh`
- `just desktop-check`
- `just desktop-test` — 3,638 passing
- `useAnchoredScroll.test.mjs` — virtualized viewport resize follows the
explicit bottom state
- Focused desktop smoke E2E — 6 passing across stable dock geometry,
multiline growth, viewport resize, reduced motion, blur ownership, and
thread overlay alignment
- Pre-push hooks passed for organization safety, branch skew, desktop
checks/tests, Rust tests, workspace tests, and desktop Tauri tests
- Mobile pre-push is independently red on latest `main`; the exact
`activity_page_test.dart` compiler failure reproduces on untouched
`origin/main`
- Visually tested channel and thread composers across quiet/activity
states, multiline composer growth, and thread overlay/focus mode


https://github.com/user-attachments/assets/ed0b08d9-18e0-4061-b272-ab509dbcd8ee

---------

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
2026-07-27 15:47:04 -07:00
d500c2d5cf feat(invites): add use-limited invite links (#3141)
## Summary

- add database-backed v2 invite links with optional maximum-use limits
and atomic final-slot redemption
- preserve v1 invite compatibility while adding
exhausted/expired/invalid client handling across desktop, web, and
mobile
- emit structured claim-outcome logs with community, invite ID, outcome,
maximum uses, and post-claim count

## Verification

- `cargo fmt --all -- --check`
- `cargo test -p buzz-db` (85 passed, 134 Postgres-dependent ignored)
- `cargo clippy -p buzz-db --all-targets -- -D warnings`
- desktop `npm run typecheck`
- push hook: desktop checks/tests, desktop Tauri tests, Rust tests, and
branch-skew passed
- Postgres integration tests were previously reviewed green at the
pre-rebase tree; local rerun on this session was unavailable because
Postgres/Docker were not running
- mobile push-hook check could not start because Flutter is unavailable
locally

---------

Signed-off-by: Kalvin Chau <kalvin@block.xyz>
Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
Co-authored-by: npub1c4alndp82zyt9veaklm5d965quss79vlhk9awv7qu5erwhmf42qqlvc25c <c57bf9b4275088b2b33db7f746975407210f159fbd8bd733c0e532375f69aa80@buzz.block.builderlab.xyz>
Co-authored-by: npub122y0pqkertljmedu303rl0aqrj3w8pvu43t6jxm6875lzg6f2pwqegc3xc <5288f082d91aff2de5bc8be23fbfa01ca2e3859cac57a91b7a3fa9f12349505c@buzz.block.builderlab.xyz>
Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
2026-07-27 15:19:39 -07:00
98a7b13348 fix(node): bump Buzz-supplied Node runtimes past OpenClaw's >=24.15.0 floor (#3218)
## Problem

User report:

```
Buzz Node mismatch: Buzz supplies Node 24.14.0; OpenClaw requires >=24.15.0. All 10 ACP workers immediately crash.
```

Buzz supplies Node to agent processes from two places, and both were
below OpenClaw's floor:

| Supply path | Was | Now |
|---|---|---|
| hermit dev env (`bin/.node-*.pkg`) — the 24.14.0 in the report |
24.14.0 | **24.15.0** (newest hermit publishes; satisfies `>=24.15.0`) |
| Desktop managed runtime (`managed_node.rs` / `managed_node_paths.rs`)
| v24.11.0 | **v24.18.0** (current latest v24) |

The managed runtime sits **first** on the worker PATH
(`managed_agents/runtime/path.rs`), so a user-installed newer Node can't
mask a stale managed one — the pin itself has to move.

## Verification

- SHA-256 digests for all six platform artifacts taken from
`https://nodejs.org/dist/v24.18.0/SHASUMS256.txt`; darwin-arm64
independently re-verified by downloading the tarball (hash match),
extracting, and running `bin/node --version` → `v24.18.0`.
- All artifacts within `MANAGED_NODE_MAX_BYTES` (largest linux-x64 at 57
MB < 90 MB cap); tar.gz layout keeps the `node-vX-platform/bin/node`
shape `verify_node_tree` expects.
- `cargo test --lib` in `desktop/src-tauri`: **1801 passed, 0 failed**
at this commit; `cargo fmt --check` + `cargo clippy --lib -D warnings`
clean.
- Existing readiness check (`node --version == MANAGED_NODE_VERSION`)
makes upgrade automatic: installed v24.11.0 trees fail readiness and the
installer stages v24.18.0 atomically (rename with `.old` rollback —
existing logic, unchanged).

Note: CI `node-version: 24.14.1` pins in
`release.yml`/`windows-canary.yml` are build-env only (already `>=`
nothing OpenClaw touches) and left alone to keep this minimal.

Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
2026-07-27 22:18:02 +00:00
9810d85459 fix(desktop): preserve thread anchor through layout reflow (#3212)
## Summary
- keep a thread presentation-switch anchor pinned while focus/split
width reflow settles
- retire the temporary anchor only after resize correction and a
following paint confirm the row is visible
- preserve the existing external-target resolution behavior and viewport
E2E contract

## Root cause
The focus and split wrappers intentionally retain the same thread
surface, but switching wrappers also changes the message column width.
`useAnchoredScroll` centered the captured message once and immediately
cleared the one-shot layout target. A later text reflow could then move
that message outside the viewport with no remaining target to correct
it.

## Verification
- `pnpm check`
- `pnpm typecheck`
- `pnpm test` — 3,699 passed
- `pnpm build:e2e`
- `pnpm exec playwright test tests/e2e/thread-focus-mode.spec.ts
--project=smoke --repeat-each=10` — 20 passed
- push hook: branch-skew, Desktop check, and Desktop full unit suite
passed

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-27 14:54:52 -07:00
cb2a265b53 feat(search): parse from:/in:/after:/before: and pass them in the filter (#2871)
## Summary
- Adds a pure-TS operator parser (`from:` / `in:` / `after:` /
`before:`) with unit tests. Invalid date tokens stay in the FTS text.
- Extends `search_messages` so the desktop can send `authors` / `since`
/ `until` (and existing `#h`) on the filter the relay already
understands.
- Wires topbar search to strip operators from the prefix query, resolve
`in:` against local channels and `from:` against hex pubkeys / known
agents, then pass the structured fields through.

This is part 1 of #2853 (parser + command plumbing). Autocomplete chips
/ richer `from:@name` resolution can follow in a second PR.

## Test plan
- [x] `node --import ./test-loader.mjs --experimental-strip-types --test
src/features/search/lib/parseSearchOperators.test.mjs`
- [x] Added `search_messages_filter_emits_operator_fields` unit test
(full `buzz-desktop` crate build needs local sidecar binaries in this
environment)
- [ ] Manual: topbar `deploy from:<hex> after:2024-01-01` emits
authors/since on the bridge filter and returns narrowed hits

Made with [Cursor](https://cursor.com)

---------

Signed-off-by: Jatinder Mahajan <jatinder.mahajan@certifyos.com>
Co-authored-by: Jatinder Mahajan <jatinder.mahajan@certifyos.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-27 21:52:56 +00:00
John Matthew TennantandGitHub 0019f80765 fix(desktop): fetch join policies through native networking (#2862)
## Context

Adding an existing community by relay URL could fail with `Community
rejected: Load failed` even when its WebSocket endpoint was reachable.
The Add Community flow fetched `/api/join-policy` from the WebView, so a
relay without a matching CORS allowance blocked the policy request
before the app could join it.

## Summary

This bug fix fetches join policies through Tauri's native networking
layer for direct URL joins. Invite-code discovery, policy acceptance,
and signed invite claims remain on the WebView path so those operations
can migrate together later.

## Changes

- Uses native networking for Add Community and first-community direct
URL join-policy requests.
- Validates relay schemes, rejects URLs containing credentials, and
refuses redirects.
- Bounds declared and chunked native responses before JSON parsing.
- Preserves existing `404`, non-success status, malformed JSON, and
absent-policy behavior.
- Requires every join-policy caller to choose its transport explicitly.

Public relays using Buzz's default permissive CORS configuration are not
known to be affected.

### Related issue

Related to #2872.

### Testing

#### Reviewer-reproducible examples

End-to-end red/green requires a relay with restrictive CORS and a Buzz
identity authorized to join it.

##### Red: `main`

From a clean checkout of `main`:

```bash
. ./bin/activate-hermit
just staging
```

In Buzz Desktop:

1. Add another community so the restrictive-CORS relay can be removed.
2. Remove that relay.
3. Open Add Community and enter the relay's WebSocket URL.
4. Select Add Community.

Observed result:

```text
Community rejected: Load failed
```

##### Green: this PR

From a clean checkout of this branch:

```bash
. ./bin/activate-hermit
just staging
```

Repeat the same steps above.

Observed result:

```text
The community rejoins successfully.
```

Supporting checks:

- Six native join-policy tests, including oversized declared and chunked
responses.
- Four TypeScript API tests, including the native command contract.
- E2E build and four focused onboarding and sidebar Playwright tests.
- Full `just ci` and pre-push suites.
- Builderbot, Kalvin, and minimize-diff review fanout found no
actionable issues after the final rebase.
2026-07-27 17:52:36 -04:00
7ca0bbd946 fix(desktop): republish agent identity records when a persona rename propagates (#2607)
## Problem

Part of #2423 (renaming personal agents desynchronises identity).

Renaming an agent definition (persona) propagates the new display name
to its
linked agent instances (`propagate_persona_name_rename` in
`desktop/src-tauri/src/commands/personas/mod.rs`) and saves
`managed-agents.json` — but, unlike the instance-rename path
(`update_managed_agent`), it never re-retains the renamed instances'
kind:30177
managed-agent identity records. `record.name` is part of the published
identity
projection (`agent_event_content`), so after a persona rename:

- `managed-agents.json` says the NEW name,
- the retained kind:30177 row (retention.db → relay flush loop) still
carries
  the OLD name, with the OLD `created_at`.

The stale identity record stays live on the relay until the next app
launch,
when the boot-time reconcile (`reconcile_agents_in_dir`) finally notices
the
content diff and republishes. Until that restart, any surface that
resolves
agents from kind:30177 records (second desktop of the same owner, CLI,
other
NIP-AP clients) sees the OLD name bound to the agent pubkey while the
kind:0
profile already shows the NEW one — the name→identity binding desync
described
in #2423, and consistent with the report's observation that repairing
state
required "a separate restart".

## Fix

- Extract the per-record retain body of the boot reconcile into
`managed_agents::reconcile::retain_agent_record(conn, keys, record) ->
Result<bool, String>`
— one shared content-diff + monotonic-`created_at`-bump engine (returns
whether a row was rewritten). `reconcile_agents_in_dir` now calls it per
  record (behavior unchanged; existing reconcile tests still pass).
- `commands::agents::retain_managed_agent_pending` delegates to the
shared
  engine instead of carrying a duplicate implementation (same semantics:
  projection-equality no-op guard, monotonic bump, `pending_sync = 1`).
- `update_persona` (Phase 1, still under the store lock, after
`save_managed_agents`): call `retain_managed_agent_pending` for every
record
the rename propagated to — mirroring `update_managed_agent`. Avatar-only
edits are deliberately excluded (the avatar is not part of the
kind:30177
  projection; retaining would be a guaranteed no-op).

No new events, kinds, or APIs — this uses the existing signed-event
retention
and flush pipeline, per CONTRIBUTING's guidance to prefer a signed Nostr
event
and the existing ingest path over endpoint-specific JSON APIs.

## Out of scope (deliberately)

- Rename → runtime restart is #1823, fixed by open PR #2507
(spawn_hash).
- Surfacing kind:0 relay profile-sync failures on rename is PR
#2302/#2279
  territory (and largely superseded by the merged rollback in #2258).
- Mention-picker UX (owner/status disambiguation) and channel-membership
repair for stale identities: TS-side, noted in #2423, not touched here.

## Test evidence

Two new unit tests in
`desktop/src-tauri/src/managed_agents/reconcile/tests.rs`
(same harness as the existing reconcile tests — tempdir + retention.db +
fresh
keys, no AppHandle):

- `rename_re_retains_identity_record_with_new_name` — retain "Fizz",
confirm
  flush, rename to "Spark", re-retain: row keeps the pubkey coordinate,
  carries the new name only, is `pending_sync`, and its `created_at` is
  strictly past the retained head (replaceable-event acceptance).
- `retain_agent_record_is_noop_when_unchanged` — an unchanged projection
does
  not rewrite the row and produces zero `pending_sync` churn.

Ran scoped per CONTRIBUTING build discipline (from `desktop/src-tauri`):

```
cargo fmt -p buzz-desktop                                  # applied, clean
cargo clippy -p buzz-desktop --lib --tests -- -D warnings  # exit 0, no warnings
cargo test -p buzz-desktop --lib                           # full lib suite
```

Full `buzz-desktop` lib suite: **1562 passed, 0 failed, 13 ignored** —
including all 12 `managed_agents::reconcile` tests (10 pre-existing, all
unmodified in behavior, plus the 2 new regression tests above).

## Links

- Issue: https://github.com/block/buzz/issues/2423
- Adjacent (no overlap): PR #2507 (rename-restart, #1823), PRs
#2302/#2279
(kind:0 sync-failure surfacing), merged #2258 (instance-rename
rollback).

---------

Signed-off-by: Sean Gearin <sgearin@gmail.com>
Co-authored-by: Sean Gearin <sgearin@gmail.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-27 17:44:08 -04:00
thomaspblockandGitHub de13960505 fix(desktop): keep project Inbox previews compact (#3193)
## Summary

Project pull request and issue previews in Inbox no longer expand when
their content begins with a Markdown heading. Heading-form titles now
match neighboring Inbox typography while preserving the existing
two-line truncation.

### Related issue

Related: #3117

### Testing

- `pnpm build:e2e`
- `pnpm exec playwright test tests/e2e/project-inbox.spec.ts
--project=smoke --retries=0` — passed
- `pnpm exec biome check tests/e2e/project-inbox.spec.ts
src/shared/styles/globals/markdown.css` — passed
- `pnpm check:px-text` — passed
- Captured `desktop/test-results/inbox-preview/01-project-preview.png`

The repository-wide desktop file-size gate remains blocked by the
pre-existing 1,026-line `AgentCreationPreview.tsx` on `main`; the change
itself introduces no file-size regression.

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
2026-07-27 13:54:54 -07:00
2bd4c24b71 Inbox refactor (#2045)
## Why

The Inbox mixed overlapping feed categories with personal work queues,
so **All** was not actually comprehensive and several filters did not
make it clear why an item appeared. Threads and DMs could produce one
row per event instead of one row per conversation, drafts were hidden
until selected, and reminders appeared through multiple competing
presentations.

This refactor makes the **Inbox** a focused, conversation-oriented place
to catch up on work relevant to you. It is intentionally not a mirror of
every unread event in every channel.

## What changed

- Keep the destination named **Inbox** and use the standard Lucide bell
icon.
- Refocus **All** on DMs, mentions, thread replies, needs-action items,
replies from agents the user owns or controls, due reminders, and active
drafts.
- Exclude generic top-level channel traffic and updates from agents the
user does not own or control.
- Group each thread or DM into one row, sorted by latest activity.
- Resume an unread conversation at its oldest unread message while
opening the full thread or DM in the detail pane.
- Reuse the existing **New** divider at the unread boundary.
- Make the detail title a direct link to the canonical conversation.
- Give Reminders and Drafts the same list/detail interaction and
location metadata as conversation rows.
- Separate Reminders and Drafts from message filters with a subtle
divider, without adding another labeled section.
- Put reminder and draft counts beside their corresponding filter labels
instead of on the generic filter button.
- Preserve the selected conversation when switching filters if it
remains valid; otherwise select a valid replacement without flashing
stale detail.
- Use filter-specific empty states and rename the options toggle to
**Show unread only**.
- Ship the focused behavior directly. The earlier experiment gate,
Custom view, and default-view controls have been removed from this PR to
keep the first pass focused.

## Filter model

| Filter | What appears |
| --- | --- |
| **All** | One row per personally relevant conversation, plus due
reminders and active drafts. Includes DMs, mentions, thread replies,
explicit needs-action items, and replies from agents the current user
owns or controls. Excludes generic top-level channel traffic, other
agents' updates, and reminders that are not due yet. |
| **Mentions** | Conversations containing a direct mention. Each
conversation appears once and opens with full context. |
| **Threads** | Conventional threaded replies, grouped to one row per
thread. Broadcast replies are not treated as conventional thread
replies. |
| **Needs action** | Feed items explicitly classified as requiring
action. |
| **Agents** | Conversations whose representative response was authored
by an agent the current user owns or controls, including top-level DM
responses. If a human replies afterward, the conversation leaves this
filter until an owned agent responds again. |
| **Reminders** | All pending reminders, including upcoming reminders
that stay out of **All** until they are due. |
| **Drafts** | Active drafts, ordered by their last real edit time. |

## Grouping, ordering, and state

- A thread or DM creates one Inbox row rather than one row per event.
- An unread conversation resumes at its oldest unread message so
intervening context is not skipped.
- Conversation rows still sort by their latest activity.
- The detail pane opens the full available conversation and shows the
shared **New** divider before the first unread message.
- Upcoming reminders appear only in **Reminders**.
- When a reminder becomes due, it enters **All** at its trigger time. If
its source conversation is already represented, the reminder state
merges into that row instead of creating a duplicate; otherwise it
appears as a standalone reminder row.
- A due reminder can enrich a row in another relative filter when that
conversation already qualifies for the filter. Reminder lifecycle
remains separate from message read state.
- Drafts appear in **All** by their last real edit time. Opening an
unchanged draft does not move it to the top.
- Reminder and draft rows show their location as `In #channel` or `In DM
with <name>`.
- **Show unread only** hides reminder and draft work queues because they
do not share message unread semantics.

## Removed or narrowed

- **Remove the old Activity filter.** It overlapped with All while still
omitting items All now includes.
- **Narrow Agents.** It no longer gathers every agent participating in a
shared thread or subsequent human follow-ups.
- **Remove duplicate reminder presentations.** The aggregate
pending-reminders jump and duplicate generic feed rows are replaced by
one list/detail model.
- **Remove Custom and default-view settings from this pass.** They added
considerable state and UI before the core model had been validated.
- **Do not add section labels for Reminders and Drafts.** A divider
communicates the distinction without creating another hierarchy in the
menu.

## Risk assessment

Medium implementation risk because this changes composition, grouping,
ordering, read behavior, and personal queues in a primary desktop view.
The implementation is scoped to the desktop UI and its local feed
projection; it does not change relay schemas or public APIs.

## Testing

- Desktop formatting, lint, file-size, text-size, and TypeScript checks
passed.
- Desktop unit suite: **3,663 passed, 0 failed**.
- Desktop E2E production build passed.
- Playwright smoke coverage across every spec touching this surface
(`channels`, `smoke`, `profile`, `project-inbox`, `community-rail`,
`integration`, `drafts-screenshots`): **118 passed, 0 failed**.
- Full Playwright smoke project: **732 passed, 1 skipped**. Three local
failures were investigated and cleared — `community-rail` keyboard
reorder passed on re-run (flaky), while `relay-reconnect:97` and
`video-attachment:223` are untouched by this commit (the only change to
shared `tests/helpers/bridge.ts` is a comment) and pass in CI.
- Unit coverage includes focused All matching, owned-agent filtering,
conversation grouping, oldest-unread selection, selection stability,
chronological reminder/draft composition, trigger-time reminder
ordering, and duplicate reminder suppression.

## Update: July 27, 2026

The naming decision is settled: the surface stays **Inbox**. An earlier
pass in this branch had renamed it to **Activity**; that rename has been
reverted in `9c00d2d6e`, which is naming-only and changes no behavior.

The revert covers file names, component/hook/type/constant identifiers,
the sidebar label and tooltip, the `Inbox options` and `Filter inbox:`
aria-labels, and the corresponding test names, test ids, and fixture
ids.

Three things were deliberately left as `activity`:

- **The feed API contract** — the `activity` / `agent_activity`
categories, the `feed.activity` and `feed.agentActivity` keys, and the
`types=` query parameter. These are the server's names, not the
surface's.
- **Plain-noun usage** — empty states such as "No activity yet", plus
`latestActivityAt` and `PROJECT_ACTIVITY_KINDS`.
- **Pre-existing agent, project, and profile activity code**, which
refers to a different concept entirely.

The earlier experiment-gate approach has also been dropped, so
`tests/helpers/bridge.ts` no longer claims that an Activity preview
feature exists — `preview-features.json` has no such entry and the seed
helper enables every desktop feature.

Generated with Codex

---------

Signed-off-by: Clay Delk <clay.delk@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 16:45:34 -04:00
99da5b7ebb Fix composer selection formatting and drop overlay (#3172)
## Summary
- scope code blocks and list formatting to the selected composer text
- use the Buzz primary color for the selection formatter
- extend the channel drop overlay over the composer with matching
corners, blur, and accessible contrast across themes

## Validation
- `just ci`
- composer selection formatting E2E tests
- file attachment and all-theme drop contrast E2E tests

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-27 20:39:44 +00:00
klopez4212andGitHub 75588eaff2 Refine pending message status (#3153)
## Summary
- Replace the all-caps, widely tracked pending-message label with
sentence-case `Sending`
- Match the surrounding timestamp and metadata spacing

## Why
The status briefly appeared as `SENDING`, unlike nearby message
metadata.

## Validation
- Desktop typecheck
- Focused Biome and text guards
- 3,637 desktop unit tests

Signed-off-by: kenny lopez <klopez4212@gmail.com>
2026-07-27 21:06:37 +01:00
174c38e4bd fix(desktop): recover full local storage on startup (#3182)
## Summary
- recover already-full Buzz installs before desktop initialization
without deleting healthy caches
- enforce a global 2 MiB UTF-16 byte budget across disposable message,
channel, timeline-skeleton, and sidebar-skeleton caches, regardless of
relay count
- route all disposable cache writes through quota recovery and reserve
roughly 3 MiB of WebKit's observed ~5 MiB quota for durable state
- preserve communities, identities, preferences, drafts, and read state;
match only delimiter-qualified disposable namespaces

## Context
WebKit enforces an approximately 5 MiB per-origin localStorage quota and
Tauri does not expose an app-level knob to raise it to 50 MiB. Buzz
0.4.26 shipped reactive recovery for selected durable writes, but
disposable writers swallowed quota failures and their existing limits
were count-based per relay rather than byte-based per origin.

This PR handles both halves: upgrade recovery for already-wedged origins
and proactive global headroom so disposable snapshots cannot drive the
origin back to the cliff.

## Safety
- startup first probes a one-byte marker; healthy installs retain their
caches
- only if the marker write fails are the four relay-rehydratable cache
namespaces removed
- namespace matching requires the `v1:` delimiter, preventing future
`v10` or similarly named durable keys from matching
- oversized individual snapshots are rejected; crossing the global
budget evicts disposable snapshots only
- failed recovery leaves the marker absent, so the next launch retries

## Verification
- `pnpm test` — 3,670 passed
- `pnpm check`
- `pnpm typecheck`
- push hooks: branch-skew, desktop-check, desktop-test passed
- byte-budget tests cover UTF-16 accounting, multiple relays, oversized
writes, durable-state preservation, healthy startup, full startup,
marker retry, and namespace near misses

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-27 12:49:07 -07:00
4d8b676bb2 fix(desktop): keep collapsed table separators out of spoilers (#3169)
## Summary

- preserve collapsed GFM table separator rows as literal text
- prevent `||---:|---|---||` from becoming an animated spoiler canvas
- leave normal spoilers and valid multiline tables unchanged

## Root cause

When table newlines are lost, adjacent row pipes become `||`. The
spoiler remark plugin interpreted the delimiter row between those pairs
as a hidden spoiler, so the reported "static" was the spoiler particle
animation rather than table layout churn.

## Safety

The guard only applies to a paragraph span made entirely of text that
exactly matches a multi-column GFM delimiter row. The narrow syntax
collision is that an intentional spoiler containing only a delimiter row
such as `||---|---||` now renders literally.

## Verification

- desktop pre-push checks passed (3,662 tests)
- desktop TypeScript typecheck passed
- independent review found no blocking issues

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-27 11:40:24 -07:00
d98da7389e feat(desktop): redesign agent runtime settings (#3093)
**Category:** improvement
**User Impact:** Users can understand, install, authenticate, and manage
agent runtimes from one progressively disclosed Agents settings
experience.

**Problem:** Runtime health and custom harness management were split
across overlapping settings surfaces, exposing low-level configuration
too early while leaving setup and authentication states hard to
understand. **Solution:** Consolidate those operations into one stable
runtime list and an Add runtimes catalog, with task-oriented state
labels, direct setup actions, and custom configuration contained in a
dedicated form.

<details>
<summary>File changes</summary>

**desktop/playwright.config.ts**
Registers the visual coverage needed for the redesigned runtime catalog.

**desktop/public/harness-logos/CREDITS.md**
Documents bundled runtime-mark provenance and the decision not to ship
the withdrawn OpenAI mark.

**desktop/public/runtime-icons/codex.png**
Removes the obsolete Codex bitmap in favor of the neutral fallback.

**desktop/public/runtime-icons/goose.svg**
Removes the old Goose asset now replaced by the theme-adaptive mark.

**desktop/src-tauri/src/managed_agents/discovery.rs**
Aligns runtime discovery guidance with the new task-oriented setup
language.

**desktop/src-tauri/src/managed_agents/discovery/runtime_metadata.rs**
Updates runtime metadata used by the redesigned settings states.

**desktop/src/features/agents/ui/runtimeAvailabilityWarning.test.mjs**
Updates availability-warning expectations for the revised runtime
guidance.

**desktop/src/features/onboarding/assets/harness-logos/chatgpt.png**
Removes the redundant bitmap from the unified runtime icon pipeline.

**desktop/src/features/onboarding/assets/harness-logos/goose.png**
Removes the redundant Goose bitmap.

**desktop/src/features/onboarding/ui/HarnessMarks.tsx**
Adds theme-adaptive bundled runtime marks with safe fallbacks.

**desktop/src/features/onboarding/ui/RuntimeIcon.tsx**
Centralizes runtime logo rendering so settings and catalog rows cannot
drift.

**desktop/src/features/onboarding/ui/SetupStep.tsx**
Aligns onboarding runtime setup copy with the settings terminology.

**desktop/src/features/onboarding/ui/presetLogos.test.mjs**
Guards bundled-logo behavior and prevents the withdrawn Codex mark from
returning.

**desktop/src/features/settings/ui/CustomHarnessForm.tsx**
Reworks custom runtime creation and editing into a clear, dedicated
catalog form.

**desktop/src/features/settings/ui/HarnessCatalogDialog.tsx**
Introduces the Add runtimes master-detail catalog, grouped setup states,
loading treatment, and pinned actions.

**desktop/src/features/settings/ui/HarnessManagementCard.tsx**
Removes the superseded standalone custom-harness management surface.

**desktop/src/features/settings/ui/HarnessRow.tsx**
Provides stable operational runtime rows with install, update,
authentication, edit, and delete behavior.

**desktop/src/features/settings/ui/HarnessesSettingsPanel.tsx**
Consolidates runtime health and custom management into one Agents
settings panel.

**desktop/src/features/settings/ui/SettingsPanels.tsx**
Wires the consolidated panel into Agents settings.

**desktop/src/features/settings/ui/harnessCatalogCopy.ts**
Adds restrained, source-annotated runtime descriptions and setup
guidance.

**desktop/src/features/settings/ui/harnessCatalogLogic.test.mjs**
Covers grouping, state labels, stable row order, actions, and adapter
warnings.

**desktop/src/features/settings/ui/harnessCatalogLogic.ts**
Centralizes catalog grouping, actions, status labels, and runtime-safe
warning copy.

**desktop/src/features/settings/ui/harnessGalleryLogic.test.mjs**
Removes obsolete gallery-only tests after consolidation.

**desktop/src/features/settings/ui/harnessGalleryLogic.ts**
Retains only the shared custom-runtime safety logic needed by the new
surface.

**desktop/src/shared/ui/config-nudge-attachment.tsx**
Points configuration nudges to Agent runtimes with matching terminology.

**desktop/src/testing/e2eBridge.ts**
Adds deterministic runtime states for authentication and catalog E2E
coverage.

**desktop/tests/e2e/doctor-states.spec.ts**
Verifies ready, setup-required, node-gated, authentication, loading, and
error contracts.

**desktop/tests/e2e/harness-catalog-screenshots.spec.ts**
Captures whole-pane visual states for the runtime catalog experience.

**desktop/tests/e2e/harness-management.spec.ts**
Exercises catalog actions and custom runtime create, edit,
authentication, and deletion flows.

**desktop/tests/e2e/onboarding-agent-defaults.spec.ts**
Updates default-runtime expectations for the consolidated experience.

**desktop/tests/e2e/profile.spec.ts**
Aligns profile navigation assertions with the new settings surface.

</details>

## Reproduction steps

1. Open Settings → Agents and inspect Agent runtimes; ready, signed-out,
installable, and setup-required runtimes should have stable rows and
explicit actions.
2. Open Add runtimes and browse the Setup and Installed groups; select
entries to see sourced guidance and a pinned Install or Setup guide
action.
3. Select Custom harness, create a runtime, then edit and delete it;
verify required-field gating and the blast-radius confirmation.
4. Exercise a signed-out runtime and connect it; the row should move
from Sign-in needed to Ready without reordering.
5. Resize the window and switch themes to verify responsive layout and
adaptive bundled marks.

## Screenshots

| Mixed runtime states | Goose not installed | Sign-in needed |
|---|---|---|
| <img width="980" height="1000" alt="image"
src="https://github.com/user-attachments/assets/28705b39-1d91-440a-a954-fb2d7d8ee759"
/> | <img width="980" height="1000" alt="image"
src="https://github.com/user-attachments/assets/e65ce8e1-8c85-4272-afb5-e6001b94d560"
/> | <img width="980" height="1000" alt="image"
src="https://github.com/user-attachments/assets/3987f355-960d-4744-8c26-b3746944a33c"
/> |

| Add runtimes catalog | Custom runtime | Setup guide |
|---|---|---|
| <img width="896" height="672" alt="image"
src="https://github.com/user-attachments/assets/a2f32c8a-4dff-4929-af96-b07346933335"
/> | <img width="896" height="672" alt="image"
src="https://github.com/user-attachments/assets/b2e85a0c-3940-4875-a138-4035423c9de7"
/> | <img width="896" height="672" alt="image"
src="https://github.com/user-attachments/assets/5a690c1c-368e-4ee3-98b9-ffefdc3cf804"
/> |

Full 13-state screenshot matrix and review evidence:
https://buzz.block.builderlab.xyz/channels/c5af9e3e-4317-4853-b3e3-ed9c15bc511d?event=ec88f6e472f2b08d6318ae76fd754fd4f218385f67cfe59b73034c4bc34c9252

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
2026-07-27 11:01:42 -07:00
8995316844 fix(desktop): use forward slashes for git credential.helper on Windows (#3023)
## Problem

On Windows, Projects **Remote** view shows an empty file tree and
Sync/Clone fails with a mangled credential-helper path, for example:

```
C:\Users\<user>\AppData\Local\Buzz\git-credential-nostr.exe get: line 1:
C:Users<user>AppDataLocalBuzzgit-credential-nostr.exe: command not found
fatal: could not read Username for 'https://<relay>/git/...': terminal prompts disabled
```

Buzz injects an absolute path into `credential.helper` via
`Path::display()`. On Windows that yields backslashes. Git for Windows
runs credential helpers through MinGW bash, which treats `\` as escapes
and destroys the path, so NIP-98 auth never runs and the blobless temp
clone behind Remote view fails.

macOS/Linux are unaffected (paths already use `/`).

This is unrelated to shipping a stub helper - the bundled
`git-credential-nostr.exe` is a real binary. User `~/.gitconfig`
workarounds also cannot help here because Projects git sets
`GIT_CONFIG_GLOBAL=/dev/null` and injects its own helper.

Closes #3025

## Fix

Normalize the helper path to forward slashes before writing
`GIT_CONFIG_VALUE_*`:

- `desktop/src-tauri/src/commands/project_git_exec.rs` (Projects Remote
/ Sync)
- `desktop/src-tauri/src/managed_agents/runtime.rs` (agent spawn git
auth)

Forward slashes are accepted by Git on every platform; on macOS/Linux
the replace is a no-op. No `cfg(windows)`, packaging, or libgit2
changes.

## How to reproduce (before)

1. Install Buzz on Windows with Git for Windows
2. Connect to a relay that has a repository with at least one pushed
branch
3. Open **Projects** -> select the repo -> **Remote**
4. Observe empty tree; Sync/Clone shows the mangled-path / `command not
found` error above

## Test plan

- [x] Unit: `cargo test --manifest-path desktop/src-tauri/Cargo.toml
credential_helper_config_value` (formatter covered on all platforms;
no-op for Unix-style paths)
- [x] Local Windows NSIS build + install of this branch
- [x] Projects -> Remote / Sync against a Buzz relay repo succeeds on
Windows after the fix

---------

Signed-off-by: Bjorn de Jong <bcrdejong@users.noreply.github.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Bjorn de Jong <bcrdejong@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-27 13:56:34 -04:00
Will PflegerandGitHub b92a1f4bf4 chore(desktop): add AgentCreationPreview file-size override to unblock main CI (#3154)
`Desktop Core` is currently red on `main`, and every open PR that picks
up current main inherits the failure.

[#2630](https://github.com/block/buzz/pull/2630) added a shadow-root
search-input autofocus effect to `AgentCreationPreview.tsx`, taking the
file from 999 to 1026 lines. It sat one line under the 1000-line default
beforehand, so that PR's own CI was green while the merged file crossed
the cap with no override entry in
`desktop/scripts/check-file-sizes.mjs`.

This adds the missing entry at 1026, following the pattern the rest of
the overrides list uses. The split stays queued along with the others.

```
- src/features/agents/ui/AgentCreationPreview.tsx: 1026 lines (limit 1000)
```

The override is tight in both directions: at `1026` the gate passes, and
at `1025` it reproduces the failure above.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-27 13:18:44 -04:00
8bb43d5191 fix(desktop): make the test loader work on Windows (#2758)
The resolve hook hands nextResolve absolute filesystem paths. Node's ESM
resolver requires URLs or relative specifiers: POSIX absolute paths
happen to be coerced, but a Windows path like C:\... parses as a URL
with protocol 'c:', so every desktop unit-test run on Windows dies
immediately with ERR_UNSUPPORTED_ESM_URL_SCHEME - on a clean tree,
before any test executes. CI never sees it (Linux runners).

Convert absolute paths to file:// URLs (pathToFileURL) at the three
nextResolve call sites. On POSIX the resulting URL is identical to what
node coerced before; on Windows the loader now works.

With this change the full desktop suite (318 files, 3487 tests) passes
on Windows 11 / node 24.14.1. Independently reported by another Windows
contributor in #2634's testing notes.


Claude-Session: https://claude.ai/code/session_01YFkHsUe1UUBBuvL81Zoe3n

---------

Signed-off-by: technicallybrantley <77166260+technicallybrantley@users.noreply.github.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-27 13:00:55 -04:00
545bb46b82 fix(desktop): make lint and unit-test gates work on Windows (#2943)
## Summary

On a Windows checkout the desktop quality gate does not work. This fixes
four defects in it. Two checks report success without examining
anything, one fails on every file, and one reports violations that its
own allowlist already covers.

**1. `pnpm test` finds no tests and still exits 0.** The `test` script
quotes the glob with single quotes. On Windows pnpm runs scripts through
`cmd.exe`, which does not strip single quotes, so node receives them as
part of the pattern and matches nothing. The run prints `# tests 0` and
exits 0 — a silent green. Double quotes are stripped by `cmd.exe` and by
POSIX shells alike, so Linux CI behaviour is unchanged.

**2. Every text file is checked out as CRLF.** There is no
`.gitattributes`, and `core.autocrlf=true` is the Git for Windows
default. Biome formats with LF, so `biome check .` fails on 1632 of 1633
files. `desktop/src/features/messages/ui/virtuaWheelModePatch.test.mjs`
fails too, because it matches `patches/*.patch` with `\n`-joined
patterns. The stored blobs are already LF, so `eol=lf` adds no
renormalisation churn — `git status` stays clean after the change.

**3. `check:px-text` never finds its own allowlist.**
`scripts/check-px-text-core.mjs` builds the key from `path.relative`,
which returns `\` separators on Windows, while the allowlist in
`desktop/scripts/check-px-text.mjs` is written with `/`. Nothing
matches, so the check reports 5 false violations on a clean tree.

**4. `check:file-sizes` examines nothing at all.** `findRule` compares
against `` `${rule.root}${path.sep}` ``. The roots are multi-segment
(`src/app`, `src/features`, `src-tauri/src`), so on Windows `src/app\`
never matches `src\app\...`. No rule matches any file: the check walks 0
of 1097 files and exits 0.

`scripts/check-pubkey-truncation-core.mjs` already normalises paths this
way (`relativePath.split(path.sep).join("/")`). This applies the same
idiom to the other two.

### Related issue

None found — no open issue covers this. The closest open PR is #2758,
which fixes a fifth Windows defect in `desktop/test-loader-hooks.mjs`;
it is required before the desktop unit tests can pass here, and it does
not overlap with these files. I checked the changed-file list of every
open PR: none touch `.gitattributes`, `desktop/package.json`,
`scripts/check-px-text-core.mjs` or `scripts/check-file-sizes-core.mjs`.

### Testing

Windows 11 (10.0.26200), node 22.17.1, pnpm 11.4.0, clean checkout with
the default `core.autocrlf=true`.

| Command | Before | After |
| --- | --- | --- |
| `pnpm test` | `# tests 0`, exit 0 | 374 test files discovered, exit 1
|
| `biome check .` | 1632 of 1633 files fail | 1633 checked, 0 errors |
| `pnpm check:px-text` | 5 false violations | passes |
| `pnpm check:file-sizes` | 0 of 1097 files examined, exit 0 | 1097
examined |

`check:file-sizes` now reports `src-tauri/src/managed_agents/runtime.rs:
2220 lines (limit 2216)`. That violation is pre-existing and not
introduced here — `main` currently fails on the same line in CI (Desktop
Core, run 30185213010, commit c2a4ee7). Before this change Windows
reported success while CI was red; now the Windows result agrees with
CI.

Desktop unit tests still fail on Windows until #2758 lands. With #2758
applied on top of this branch the full suite passes: 3515 tests, 0
failures. This change stops hiding those failures rather than fixing
them.

Signed-off-by: Seydi Charyyev <seydi.charyev@gmail.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-27 13:00:25 -04:00
Luke TornquistandGitHub 313f793c87 feat(desktop): add search to agent emoji picker (#2630)
## Why
The agent avatar picker disables Emoji Mart search, making emojis
difficult to find when creating or editing an agent.

## What
- Enable sticky search in the shared agent avatar picker
- Focus the search field when the Emoji tab opens
- Add end-to-end coverage for search, focus, and selection

## Risk Assessment
Low — this is limited to the desktop agent avatar picker and does not
change avatar persistence or agent configuration.

## References
- `pnpm --dir desktop test` — 3,448 passed
- `pnpm --dir desktop typecheck` — passed
- `pnpm --dir desktop exec playwright test tests/e2e/agents.spec.ts` —
20 passed

---
**Update Jul 24, 16:28 EDT:** Completed `CONTRIBUTING.md` validation.

### Manual test
1. Create an agent and open Add avatar → Emoji.
2. Confirm the search field is focused and filters results.
3. Select an emoji and confirm it becomes the avatar.
4. Repeat while editing an existing agent.

### Validation
- `just ci` — passed
- `just test` — passed

### Follow-up work
None.

Generated with Codex
2026-07-27 09:47:31 -07:00
mikeyandGitHub be275cfc6c fix(desktop): keep identity key help dialog readable in dark mode (#2854)
## Problem

With macOS in dark mode (the default for fresh profiles is to follow the
system scheme), the onboarding "What's an identity key?" help dialog
renders
its title in near-white on the always-white textured card, making it
unreadable. The body paragraphs stay readable because they use the fixed
olive `--buzz-onboarding-backup-ink`; only the `text-foreground` title
(and
the close button's hover color) flip with the theme.

## Cause

The dialog's `DialogContent` carries `buzz-onboarding-neutral-theme` but
is
portaled outside the `buzz-startup-shell` subtree, so in dark mode it
matches
`.dark .buzz-onboarding-neutral-theme:not(.buzz-startup-shell)`
(`components.css`), which flips `--foreground` to `0 0% 98%`. The
textured
powder card (`buzz-card-textured`) has no dark variant — it is baked
light —
so the near-white title disappears against it.

## Fix

One attribute: pin the dialog to the light neutral theme with
`data-system-color-scheme="light"`. This is the established pattern for
always-light onboarding dialogs (`HostedCommunityOnboarding.tsx`, the
`CommunityOnboardingFlow.tsx` avatar dialog), and the pinned-light CSS
rule
already exists and out-specifies the dark-mode flip. No new CSS.

## Testing

- Added a dark-mode regression test to
`tests/e2e/identity-key-help.spec.ts`
  (already registered in the Playwright smoke project): emulates
  `prefers-color-scheme: dark`, opens the dialog, and asserts the title
resolves to the pinned light-neutral ink `rgb(23, 23, 23)`. Before the
fix
  it rendered `rgb(250, 250, 250)`.
- Manual repro: macOS appearance set to Dark → fresh profile → machine
  onboarding → click "What's an identity key?".

Before/after screenshots are in the comment below.

Signed-off-by: Michael Pfister <pfista@gmail.com>
2026-07-27 09:47:02 -07:00
Will PflegerandGitHub f2fe3b63c2 feat(acp): title agent sessions from the agent and channel name (#3028)
ACP harnesses that name a session from the first text they receive all
land in the same place: every managed Buzz agent opens with the
identical `[Base] You are operating inside the Buzz platform…` framing,
so the harness session list shows a wall of indistinguishable rows.
Because sessions are keyed per channel, one agent active in several
channels produces several of them.

This sends the name out of band instead. `session/new` carries
`_meta.sessionTitle` with `Agent · #channel`, composed from the agent's
`display_name` (or its unique `name` handle) and the channel it is
serving. The prompt is untouched — no tokens spent, no perturbation of
the prompt contract, and nothing new for the desktop observer's section
parsing to handle.

The mechanism is harness-agnostic: Buzz sends the field on every ACP
`session/new` regardless of which harness is behind it, and adapters
that don't read it ignore it per spec.

## Inert until a consuming adapter ships

ACP adapters ignore `_meta` members they do not recognize, so against an
adapter with no reader a Buzz session gets no title and nothing else
changes. Three adapter halves consume it — Codex, Goose, and Claude Code
(linked below); this half and each reader are only useful together, and
each reader lands independently.

No version floor is added. `codex_adapter_is_outdated_with_path` already
gates codex-acp on major version `>= 1`
(`desktop/src-tauri/src/managed_agents/discovery.rs:1276-1284`) and this
feature needs nothing above that — an older adapter is not broken by the
extra member, it simply ignores it.

## What changes

**`crates/buzz-acp`** owns sanitization and composition.
`sanitize_session_title` collapses whitespace, drops control characters,
and caps at `SESSION_TITLE_MAX_CHARS` (80) by character, not byte, so a
multi-byte character cannot be split. `compose_session_title` truncates
only the channel part against that cap, so the agent name always
survives; when the agent name alone fills the cap the channel is dropped
rather than the name. `session_new_full` sets `_meta.sessionTitle` when
a title exists and omits `_meta` entirely when it does not, since an
adapter may distinguish an absent member from a null one.

**`desktop/src-tauri`** only resolves and exports.
`resolve_session_title` picks `display_name` or falls back to `name`,
and `spawn_agent_child` writes it to `BUZZ_ACP_SESSION_TITLE` — or
removes the variable when neither candidate yields anything printable.

DMs, unresolved channels, and heartbeat sessions get the bare agent name
with no channel suffix.

## Four properties that are easy to remove by accident

**Control characters are stripped at the desktop boundary, not in the
harness.** An interior NUL cannot cross the environment boundary at all
— `Command::env` fails the entire spawn rather than passing it through.
Deferring the strip to `buzz-acp` would let a corrupted display name
turn display chrome into a spawn failure. A display name that is *only*
control characters falls back to `name`.

**The title is hashed into `spawn_config_hash`.** Without it, renaming
an agent left the running process with a stale title and no restart
badge. The hash runs the same `resolve_session_title` the spawn writes,
and skips it when a user env override shadows `BUZZ_ACP_SESSION_TITLE` —
spawn writes the title *before* the layered user env, so the override is
what actually runs, and it already reaches the hash through
`descriptor.env`. Hashing the record-derived value under an override
would badge a rename that changes nothing.

**One channel resolve serves both consumers.**
`resolve_new_session_channel_context` returns `(is_dm, title_channel)`
from a single metadata lookup, feeding both the canvas block's DM check
and the title. `ChannelInfoResolver` caches only `Some`, so two
independent calls against an unresolvable channel pay the full
`fetch_channel_info` retry sequence twice — two timeouts plus a retry
delay each — directly in front of `session/new`, precisely when the
relay is already degraded.

**The `"unknown"` channel name is treated as absent.**
`fetch_channel_info` substitutes the literal `"unknown"` for a metadata
event with no `name` tag. Composing that sentinel would title every
unnamed channel `Agent · #unknown`, reintroducing the exact collision
the suffix exists to remove while naming a channel something it isn't.
The startup cache already refuses `channel_type == "unknown"` for the
same reason.

Closes #2334

Related — the adapter halves that consume `_meta.sessionTitle`:

-
[codex-acp#338](https://github.com/agentclientprotocol/codex-acp/pull/338)
— Codex
-
[aaif-goose/goose#10712](https://github.com/aaif-goose/goose/pull/10712)
— Goose
-
[claude-agent-acp#920](https://github.com/agentclientprotocol/claude-agent-acp/pull/920)
— Claude Code

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-27 12:46:43 -04:00
31e2de1966 fix(deps): bump nostr to 0.44.6 for RUSTSEC-2026-0216 (NIP-44 remote DoS) (#3135)
## Summary

`cargo-deny` started failing on **every** PR and on `main` when
**RUSTSEC-2026-0216** was published mid-afternoon today. Nothing in the
tree changed — cargo-deny fetches the advisory DB at run time, so main's
own `Security` job passed at `00ecf2c` and then began failing on the
same commit.

```
error[vulnerability]: Remote Denial of Service via malformed NIP-44 v2 payload
  Cargo.lock:432  nostr 0.44.3  —  RUSTSEC-2026-0216
advisories FAILED, bans ok, licenses ok, sources ok
```

The `nostr` NIP-44 v2 decrypt path reads a 2-byte unpadded-length prefix
via `buffer[0..2]` **after** the HMAC check passes, without verifying
the decrypted buffer holds 2 bytes. A sender who holds the conversation
key — i.e. any DM sender — can craft a payload that decrypts to 0 or 1
bytes and panic the receiver. Remote DoS through any relay that delivers
the event. No key material, plaintext, or memory corruption.

Affects `0.26.0` through `0.44.4`. Fixed in `0.44.5`.

## The change

Lockfiles only, 6 insertions / 6 deletions. The manifest already
declares `nostr = "0.44"` — a caret range — so `0.44.6` needs no
`Cargo.toml` edit.

| Lockfile | Before | After |
|---|---|---|
| `Cargo.lock` | 0.44.3 | 0.44.6 |
| `desktop/src-tauri/Cargo.lock` | **0.44.4** | 0.44.6 |

**The desktop lockfile is the part worth reviewing.**
`desktop/src-tauri` is excluded from the root workspace
(`Cargo.toml:31`), and the `Security` job runs `cargo-deny check` at the
repo root — so it never sees that lockfile. It was pinning a vulnerable
*and* yanked `0.44.4` that no CI check would ever have flagged. Desktop
calls `nip44::decrypt` at `commands/identity.rs:495`. Credit to @Eva for
catching this; I'd have shipped the root-only fix and left it sitting
there.

**This isn't optional maintenance.** `0.44.0` through `0.44.4` are all
yanked on crates.io. `0.44.5` and `0.44.6` are the only live versions in
our range — staying put isn't an available option.

### On the two extra lines in the desktop lockfile

The desktop bump also repoints two existing dependency edges:

```
nostr-derive: syn 2.0.118 -> syn 1.0.109
tempfile:     getrandom 0.4.3 -> getrandom 0.3.4
```

I checked these rather than waving them through: **no packages are added
or removed** — both versions were already present in the graph, so only
which edge points where changed. The resolution is stable across
repeated re-resolves, and a plain re-resolve without the bump produces
zero diff, so this isn't pre-existing lockfile staleness leaking in.

## Verification

At this commit, in a clean worktree off `origin/main`:

- `cargo-deny check advisories` → **`advisories ok`**, exit 0. The same
tree before the bump reported `advisories FAILED` with this advisory, so
the check is doing real work, not passing vacuously.
- `./scripts/run-tests.sh unit` → all five packages pass.
- `cargo test -p buzz-core` 229/229, `-p buzz-cli` 250/250, `-p
buzz-relay --lib` 750 pass / 1 fail — the sole failure
`api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo` is
pre-existing and reproduces identically at unmodified `00ecf2c`.
- `desktop-tauri-test` passed in the pre-push hook, which exercises the
crate whose lockfile changed.

## Why not a `deny.toml` ignore

Considered and rejected. This is a reachable panic triggerable by any DM
sender, and buzz-acp agents decrypt DMs from arbitrary senders.
Suppressing it would ship a live remote-DoS to every agent and client in
order to make a dashboard green.

## Note on `spin`

The yanked `spin 0.9.8` / `0.10.0` warnings in the same job are **not**
what fails CI — the log has exactly one hard error, this one. They're
`warning[yanked]`, and warnings don't fail the build. `spin` is also
three levels transitive (`mesh-llm-host-runtime → mdns-sd → flume →
spin`) under a dev-dependency, so it isn't ours to bump. Left alone
deliberately.

## Follow-up

Unblocks #3128 (relay-admin ban gate), which has a zero dependency-file
delta and will inherit this cleanly once main is merged in.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>

Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1jmc9dt2lyvzu3h0kxlwxt5zg4fxp9476awyxw6gwxn72g6cw7exqs64whm <96f056ad5f2305c8ddf637dc65d048aa4c12d7daeb8867690e34fca46b0ef64c@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
2026-07-27 12:04:23 -04:00
654f384906 fix(desktop): read the newest pair-scoped harness log (#3134)
Harnesses became per (agent, relay) pair in #2122 and now write
`agents/logs/{pubkey}__{sha256(relay_url)}.log` via
`managed_agent_runtime_log_path`. `get_managed_agent_log` was never
updated and still read the legacy `agents/logs/{pubkey}.log`, so agent
profile → Runtime → Harness Log froze at each agent's last
single-runtime line while live output accumulated in files the reader
never opened.

The reader now resolves the log through `latest_managed_agent_log_path`,
which picks the most recently modified file belonging to the agent —
pair-scoped `{pubkey}__*.log` or legacy `{pubkey}.log` — and falls back
to the legacy path when the agent has no log on disk at all. Agents that
have not restarted since the update keep working, and the panel follows
whichever harness is currently writing. The response already carried
`log_path`, so the panel header names the file being shown.

Selection is deterministic: equal mtimes break toward the higher
filename, and files belonging to other agents or without a `.log`
extension are never candidates.

`storage.rs`'s inline test module moves to a `#[path]`-included sibling
`storage_tests.rs`, matching the existing pattern in `teams.rs` and
`archive/mod.rs`. This drops both halves under the desktop file-size
limit (1383 → 826 / 701), so the ratchet entries tighten instead of
growing.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-07-27 11:45:03 -04:00
thomaspblockandGitHub c5c4f390b6 feat(desktop): handle project work from Inbox (#3117)
## Summary

Pull requests and issues that mention you now appear as
repository-scoped Inbox conversations, so project work can be reviewed
without first navigating to Projects.

Opening a project item resolves its current canonical state and reuses
the existing review, comment, merge, and issue actions. Repository-aware
grouping keeps identical event IDs from different repositories separate,
while loading, missing-data, and partial-query states avoid exposing
stale actions.

### Related issue

None found.

### Testing

- `node --import ./test-loader.mjs --experimental-strip-types --test
src/features/home/lib/projectInbox.test.mjs` — 6 tests passed
- `CI=1 pnpm exec playwright test tests/e2e/project-inbox.spec.ts
--project=smoke` — passed
- Pre-push desktop, mobile, Tauri, and Rust checks — passed

---------

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
2026-07-27 15:14:22 +01:00
87b3fcd3c0 fix(desktop): clarify identity key button when key exists (#2357)
## Summary
- backing out of the backup step and clicking the CTA again reuses the
existing key
- the button still said "Create a new identity key", which is misleading
for a security-sensitive action
- when a key is already loaded, label it "Continue with existing
identity key"

Closes #2318

## Test plan
- [ ] first-run: button still says "Create a new identity key"
- [ ] create key → backup → back: button says "Continue with existing
identity key" and shows the same key


Made with [Cursor](https://cursor.com)

---------

Signed-off-by: Taksh <takshkothari09@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 08:47:39 -04:00
morgmartandGitHub 7fc0cc82db Restore Goose and Buzz Agent to onboarding harness selection (#2731)
> [!NOTE]
> **Part 1 of a multi-PR onboarding rework.** This PR only restores the
hidden harnesses, adjusts the card layout, and gates onboarding
completion on valid config. A follow-up PR (stacked on this branch)
restructures the flow: the harness page becomes a single-choice "pick
your default harness" step, and install/sign-in/provider setup moves to
the following page. Review this one on its own terms — the flow rework
is intentionally not here.

## Summary

- Restore **Goose** and **Buzz Agent** to onboarding harness selection,
reverting the launch-only restriction from #2233
- The restore is the single centralized allowlist
(`ONBOARDING_RUNTIME_ORDER` in `onboardingRuntimeSelection.ts`) that
#2233 deliberately set up for this moment — setup cards, readiness
handoff, and the defaults harness picker all derive from it
- Lay the four harness cards out as a single row at `lg` and above
(`lg:grid-cols-4`); below 1024px (including the app's 800px minimum
window width) the grid is 2×2, and 1-up on narrow viewports
- **Gate onboarding Finish on actual config validity** (review finding):
the defaults page rendered provider/model/credential fields for
provider-required harnesses but Finish only checked that a harness was
selected — a fresh user picking Buzz Agent could persist a default that
fails at first spawn. The Finish gate now consumes `AgentConfigFields`'
existing `onValidityChange` signal. Baked build env and runtime-file
config satisfy the gate, so internal builds and existing Goose users are
never blocked
- Update the unit + E2E specs that pinned the hidden behavior, plus two
new E2E cases pinning the Finish gate (blocked-until-configured, and
baked-env never blocked)
- AGENTS.md rule 7 updated to document the completion gate

## Testing

- `onboardingRuntimeSelection.test.mjs` — 4 passed
- `pnpm typecheck` — clean
- `pnpm exec playwright test tests/e2e/onboarding-agent-defaults.spec.ts
--project=smoke` — 21 passed
- `onboarding-docked-cta-screenshots.spec.ts` — 3 passed
- Biome — clean

## Known cosmetic issue (deferred to PR 2)

At the app's minimum window size (800×500) the 2×2 grid extends past the
visible area and the footer CTA overlaps card space. Next still
hit-tests correctly. PR 2 redesigns this page entirely (cards become a
single-choice chooser with no inline setup), so this is deferred rather
than patched twice.

---------

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
2026-07-27 07:56:40 -04:00
thomaspblockandGitHub afb272bb7b fix(desktop): render rich project work item content (#3100)
## Summary

Project issues, pull requests, reviews, and commit details no longer
flatten rich content into inert or plain text. They now share the
message markdown and media pipeline, preserving NIP-92 `imeta` metadata
so links, images, and videos render consistently.

Commit bodies are fetched only when a single-commit detail view is
opened, keeping list queries lightweight while exposing full context
beside the diff.

### Related issue

None found.

### Testing

- Pre-push suite: `desktop-check`, `desktop-test`, `desktop-tauri-test`,
`rust-tests`, and `mobile-test`
- Project issue and pull request regression tests cover preserving
attachment metadata on roots, updates, and comments
- The project commit detail smoke scenario verifies linked text, images,
and video in commit bodies

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
2026-07-27 12:22:05 +02:00
95fdf97880 feat(acp): bring your own harness (BYOH) — generic ACP runtime seam + settings gallery (#2773)
## What

Implements a "bring your own harness" (BYOH) generic ACP mechanism —
replacing per-harness backend code with a data-driven 3-tier system:

- **Tier 1 (compiled-in builtins):** goose, claude, codex, buzz-agent —
unchanged behavior
- **Tier 2 (bundled presets):** cursor, omp, grok, opencode, kimi, amp,
hermes, openclaw, and any future additions — defined in
`PRESET_HARNESSES`, no code duplication, icons stay
TerminalSquare/bundled-asset-only
- **Tier 3 (user-defined custom):** JSON definitions saved to
`custom_harnesses/` under app data; managed via Settings → Agents UI

## Changes

### Core data model
- `HarnessDefinition` — id, label, command, args, env, install URL/hint
- `PRESET_HARNESSES` static table — single source of truth for all
presets; `preset_harness_ids()` derives reserved IDs (D-11: no
hand-maintained copy)
- `source: "builtin" | "preset" | "custom"` tagging on every catalog
entry

### Persistence (B-4, B-6)
- `save_custom_harness_to_dir(dir, definition, rename_old_id)` —
backup-swap atomic write (backs up target → .bak, commits temp → target,
restores .bak on failure, removes .bak on success); safe on Windows
where `fs::rename` over an existing file is "access denied"
- `save_and_warm` / `delete_and_warm` — hold `PERSIST_MUTEX` for the
write + registry-warm pair, eliminating the lost-update race (B-6) where
two concurrent saves could interleave their warm calls and leave a stale
registry snapshot
- Validate-before-mutate: both IDs and env validated before any
filesystem mutation

### Env validation boundary (B-3)
- `validate_harness_definition_pub` calls `validate_user_env_keys` on
definition env at save AND load
- Rejects malformed keys (BUZZ_AUTH_TAG=x forgery shape), reserved keys
(BUZZ_MANAGED_AGENT etc.), NUL bytes, oversized values

### TypeScript boundary (B-2 / Thufir CRITICAL)
- `RawAcpRuntimeCatalogEntry` now declares `definition_env?:
Record<string,string>` and `source: "builtin" | "preset" | "custom"`
- `fromRawAcpRuntimeCatalogEntry` maps `definition_env → definitionEnv`
(camelCase); absent field defaults to `{}`
- Edit form reads `entry.definitionEnv` — env no longer erased on
save-then-edit cycle

### Unified descriptor (Phase A / Thufir F4)
- `EffectiveHarnessDescriptor { command, args, env }` in `readiness.rs`
- `resolve_effective_harness_descriptor()` — single resolver used by
spawn, spawn_hash, summary, get_agent_models (both saved and unsaved),
and readiness
- No competing arg-resolution forms

### Other fixes
- B-5: stop freezing `runtime.defaultArgs` into `record.agent_args` on
normal create paths
- B-7: readiness exec-check — `MissingBinary` variant for custom
commands not found on PATH
- B-8: onboarding transition — `setTimeout(0)` removed, parent-owned
route intent via `navigateAfterComplete` prop
- C-9: collector-discriminating sweep tests with injectable filters
- C-10: `HarnessManagementCard` uses `harnessGalleryLogic` helpers
(killed duplicate filter/sort)
- D-11: `BUILTIN_IDS` derived from `PRESET_HARNESSES` (no
hand-maintained copy)
- D-12: `mobile/pubspec.lock` churn reverted
- D-13: false ownership fast-path comment fixed
- D-14: URL scheme validation for `installInstructionsUrl`
- D-15: OpenClaw Gateway env-locus README line

### Tests added
**B-4 persistence (6 tests):**
`save_to_dir_create_writes_file_and_loads_back`,
`save_to_dir_same_id_edit_replaces_content`,
`save_to_dir_backup_is_cleaned_up_after_same_id_edit`,
`save_to_dir_rename_removes_old_file_and_creates_new`,
`save_to_dir_rename_nonexistent_old_id_is_non_fatal`,
`save_to_dir_roundtrip_with_env_preserves_values`

**B-3 env validation (6 tests):**
`validate_rejects_malformed_key_with_equals_sign`,
`validate_rejects_reserved_key_buzz_managed_agent`,
`validate_rejects_reserved_key_case_insensitive`,
`validate_rejects_nul_byte_in_value`,
`validate_rejects_value_over_per_value_size_limit`,
`validate_accepts_well_formed_env`

**B-2 API boundary (4 TS tests in tauri.test.mjs):**
`fromRawAcpRuntimeCatalogEntry maps definition_env to definitionEnv`,
`defaults definitionEnv to {} when absent`, `preserves source preset`,
`env round-trips through edit payload shape`

## Preset catalog

| ID | Label | Command |
|----|-------|---------|
| `cursor` | Cursor | `cursor-agent acp` |
| `omp` | Oh My Pi | `omp acp` |
| `grok` | Grok Build | `grok agent --always-approve stdio` |
| `opencode` | OpenCode | `opencode acp` |
| `kimi` | Kimi Code | `kimi acp` |
| `amp` | Amp | `amp-acp` |
| `hermes` | Hermes Agent | `hermes-acp` |
| `openclaw` | OpenClaw | `openclaw acp` |

## Review-fix pass (2026-07-26, Eva)

Fixes from the three-way review (Wren / Dawn / Eva) in the
buzz-generic-acp-harnesses thread, pushed as new commits (no rewrite):

1. **installHint edit round-trip** — form seeding extracted to
`formValuesFromCatalogEntry` (single source of truth), input rendered,
full-definition lossless round-trip regression.
2. **Dangling-delete coherence** — delete allowed; confirm counts
referencing agents (direct pin + persona-inherited); summary rows render
`harness (deleted): <id>`; spawn errors become actionable sentences
(`user_facing_harness_error`); composed delete→summary→start test.
3. **Comma-in-args** — rejected at `validate_harness_definition` (shared
by save AND disk load), mirrored inline in the form.
4. **Registry publish race** — collision/dup filtering moved into
`load_custom_harnesses` (both loaders inherit shadowing rules);
discovery publishes by re-reading the dir under `persist_mutex` (lock
scoped to publish only); deterministic interleaving regressions for
save-during-discovery and delete-during-discovery.
5. **Mechanical** — discarded `belongs_to_us` sweep arg deleted,
`load_global_agent_config` hoisted out of the per-record summary loop,
duplicated doc paragraph + stray SAFETY comment removed.
6. **PGID test de-flaked** — leader kept alive through the assertion.

Known follow-up (filed in review, not blocking): file-size split-outs
queued in `check-file-sizes.mjs` entries.

## Gate table — head `bf53f1d60`

| Gate | Result |
|------|--------|
| `cargo test --lib` (desktop/src-tauri) | **1701 passed**, 0 failed, 14
ignored |
| desktop JS suite (`pnpm test`) | **3605 passed**, 0 failed |
| `tsc --noEmit` | clean |
| `biome check` + file-size/px/pubkey checks | clean |
| `cargo clippy --lib -- -D warnings` | clean |
| `cargo fmt --check` | clean |

PR head: `bf53f1d60e3cbd07392e1287b83bb37ba90d0d33` — includes merge of
origin/main (`c2a4ee711`, conflicts in agent_models composed with
#2890's live Databricks discovery)

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-07-26 20:06:20 -04:00
16d4ec335e feat(desktop): use collective mesh routing for Auto (#2825)
Uses MeshLLM built-in `mesh` collective intelligence / Mixture of Agents
when Buzz Auto sees two or more distinct physical models. With zero or
one model, Auto remains ordinary `auto`. This is an alternative way to
improve tool responses, accuracy, and resistance to hallucination when a
high-latency distributed mesh contains diverse models.

Models and members may come and go: collective routing enables only
after stable capacity, drops on confirmed contraction, and can recover
later. Mesh-specific failures retry once through ordinary Auto.

This update also pins MeshLLM to a v0.73.1-compatible backport of
[MeshLLM #1074](https://github.com/Mesh-LLM/mesh-llm/pull/1074), so
client-only Buzz nodes cannot enter model election or download a remote
provider model. Buzz preserves the selected local sharing model and
switches an existing client to sharing across a controlled app restart,
retaining one runtime and one `:9337` / `:3131` pair per machine.

Validation:
- Full local `just ci` passes on the cleaned branch.
- MeshLLM host-runtime suite: 1,568 passed, 0 failed; strict Clippy
passes.
- Buzz desktop Tauri suite with `mesh-llm`: 1,721 passed, 0 failed;
strict feature Clippy passes.
- Playwright covers client-to-share using the saved local model and no
destructive stop.
- Packaged two-machine testing proved single-model routing, dual-model
collective routing, tool-markup fallback, and runtime reuse.
- Packaged client-only recheck routed a real Mini Buzz turn through M5
while Mini stayed `is_client=true`, `is_host=false`, hosted no models,
and created no Gemma cache.

Builds on the recovery work merged in #2823; this PR does not duplicate
it.

---------

Signed-off-by: Michael Neale <michael.neale@gmail.com>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: Michael Neale <michael.neale@gmail.com>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
2026-07-26 19:46:13 -04:00
aee6314484 fix(desktop): strip legacy baked team instructions from stored prompts (#3035)
Agents in a team receive two `Team Instructions` blocks per turn, and
the observer feed renders two Team Instructions cards for them.

There are two producers. `with_team()` in `crates/buzz-acp/src/pool.rs`
appends the LIVE `[Team Instructions]` block from the runtime
`TeamRecord` — that one is correct. The second is baked into the stored
`system_prompt` itself: records written before the runtime framing
landed were composed by the now-removed `compose_prompt()` in
`buzz-persona`, which appended `"\n\n---\n# Team Instructions\n"` plus a
frozen copy of the team instructions. So an affected agent is fed a
stale roster ahead of the current one, and the transcript parser —
correctly — reports both.

Fixing this in `parseSystemPromptSections` would hide the symptom while
the agent kept receiving the stale bytes, so the suffix is removed at
rest by a boot migration.

`strip_baked_team_instructions` splits each stored `system_prompt` at
the LAST occurrence of the exact delimiter and keeps the text before it.
Last-occurrence matches the parser's own `lastIndexOf` guard: a persona
body may quote a delimiter-shaped passage, and only the final one is the
producer boundary. The match is byte-exact — a bare `---`, a `# Team
Instructions` heading at a different position, or a single preceding
newline are author content and are left alone. It applies to every
record regardless of `team_id` / `persona_id` / `pubkey`: the key-less
definition records carry the suffix exactly as the instances minted from
them do. A prompt that was nothing but the suffix becomes `None`, not
`Some("")`, matching the absent-prompt convention in
`AgentDefinition::into_agent_record`.

Stripping a definition's prompt changes its `persona_content_hash`,
which is the drift basis behind the Agents-menu "out of date" badge.
Left alone, every linked instance would light up stale for a change the
user never made. The migration therefore advances the pin of instances
whose `persona_source_version` still equals the definition's PRE-strip
hash — the same conditional `refresh_builtin_agent_avatars` already
uses. An instance that had genuinely drifted keeps its stale pin, and
its badge.

The migration runs after `fold_personas_into_agent_store` so definitions
lifted out of the legacy `personas.json` are cleaned in the same boot,
and before `backfill_standalone_agents` so a manufactured definition
never snapshots a suffix about to be removed. It writes only when at
least one record changed, so a second boot is a true no-op, and takes a
create-if-absent backup at
`managed-agents.json.pre-team-suffix-strip.bak` following the
`pre-backfill.bak` contract — a re-run after a partial failure cannot
replace the pristine backup with a half-migrated snapshot. An
unparseable store errors without writing and without taking a backup,
leaving the file for manual recovery.

Pass 5's legacy branch in `agentSessionTranscriptHelpers.ts` is
deliberately untouched: un-migrated installs and snapshot imports still
need it.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-07-26 19:33:43 -04:00
5d8ede446f feat(agents): lower default agent parallelism from 24 to 10 (#3038)
## What

One-line default change: `DEFAULT_AGENT_PARALLELISM` 24 → 10
(`desktop/src-tauri/src/managed_agents/types.rs`), requested by Tyler in
[buzz-generic-acp-harnesses](buzz://message?channel=d6d84fdc-47fe-4e41-acf5-a08a4a013e6b&id=a047ead3a34ba804c376bf79f29e355d304162a88c7b2a7121e092547c572022).

## Why

Each unit of parallelism is a **full agent subprocess** per agent ×
community pair, spawned eagerly and serially at create/start before the
harness connects to the relay:

- At 24, a desktop with a few dozen agent×community pairs carries >1,000
potential idle subprocesses.
- Slow-starting harnesses (OpenClaw measured at 4–10s per worker) take
minutes to create, and messages sent during that window can be
permanently missed (the startup watermark is captured only after eager
pool init).
- Parallelism is per-channel concurrency — a channel never uses more
than one worker — so 10 still covers concurrent-channel fan-out for
typical agents.

Context: verified RCA + live 1-worker/3-mention experiment in the same
thread. Demand-driven pool growth (seed 1, grow on contention up to the
cap) is agreed as a follow-up; this is the interim risk reduction.

## Scope / blast radius

- Existing agent records keep their persisted `parallelism` value — only
new records and unset defaults change.
- Per-agent override unchanged (1..=32 via agent editor /
`BUZZ_ACP_AGENTS`).
- Readers of the constant (`team_snapshot`, `personas/snapshot/import`,
`commands/agents` mint path) all take the new default transitively; no
other code pins 24.
- `activeAgentTurnsStore.test.mjs` comments updated so the 24-turn
eviction case is framed against the harness cap (32) instead of the
default — coverage unchanged, no assertion touched.

## Verification

At 63bb0c1c4, same shell: desktop Rust `cargo test --lib` 1693/0 (14
ignored) · JS suite 3523/3523 · tsc clean · biome clean on touched file
· cargo fmt clean.

Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
2026-07-26 18:05:53 -04:00
klopez4212andGitHub e6c90bb7c4 Polish community rail and mobile pairing (#2972)
## Summary
- align the multi-community rail with the content surface and balance
its visible 10px side gutters
- center Mobile pairing, start sessions on demand, and keep retry states
inside the QR area
- reveal the QR code and copy action with 250ms motion and use the
standard loading spinner

The rail was centered within its own box, but the adjacent sidebar added
another 11px to the visible right gap. Mobile pairing also started
before user intent, which could leave an idle session waiting for EOSE.

## Validation
- `pnpm -C desktop build:e2e`
- `pnpm -C desktop test` — 3,516 passed
- `pnpm -C desktop exec playwright test tests/e2e/community-rail.spec.ts
--project=smoke` — 19 passed
- `pnpm -C desktop exec playwright test
tests/e2e/mobile-pairing-qr.spec.ts --project=smoke` — 1 passed

`pnpm -C desktop check` is currently blocked by the existing
`src-tauri/src/managed_agents/runtime.rs` file-size baseline (2,220
lines; limit 2,216).
2026-07-26 13:20:26 -07:00
Taksh KothariandGitHub a31fc4d2f3 fix(desktop): remove bundled libsystemd from AppImage (#2353)
## Summary
- AppImage bundles an older `libsystemd.so.0` that shadows the system
copy after `libmount` is already removed
- on systemd ≥ 251 (Arch, Fedora 41+, etc.) launch fails with
`LIBSYSTEMD_251` not found
- add `libsystemd.so*` to the existing removal list in `fix-appimage.sh`

Closes #2335

## Test plan
- [ ] rebuild / repack an AppImage with the updated script
- [ ] launch on a distro with systemd ≥ 251 without `LD_PRELOAD`
workaround


Made with [Cursor](https://cursor.com)

Signed-off-by: Taksh <takshkothari09@gmail.com>
2026-07-26 13:54:59 -04:00
8c0e8cb165 fix(desktop): make agent definition authoritative for model/provider/prompt (#1968)
## Summary

Introduces a single effective-config resolver that makes agent
definitions authoritative for model, provider, system prompt, and
relay-mesh routing on linked instances. Stale materialized record bytes
are never consulted at spawn, deploy, readiness, hash, card summary, or
mesh preflight.

**Resolution semantics (field-specific):**
- **model/provider (linked):** definition → global. `None` = inherit
global default. Record value never consulted.
- **model/provider (definition-less):** instance → global.
- **system_prompt (linked):** strictly from definition; blank = no
prompt. No global tier for prompt.
- **system_prompt (definition-less):** from the instance.
- **relay-mesh preflight (both):** driven by the same
`resolve_effective_config` resolution spawn's mesh env consults. For a
linked instance the record's own `provider`/`model`/`relay_mesh` bytes
are never consulted; for a definition-less instance with no provider of
its own, the legacy marker and env preset are the last fallback (see
below).
- **Orphaned (linked record, definition missing):** spawn, deploy, and
mesh preflight blocked with actionable user-facing error.

**Changes:**
- New `effective_config` resolver module with `ConfigSource` metadata
(`definition`, `global`, `instance_legacy`)
- All consumers routed through the single resolver: local spawn, deploy,
readiness, spawn hash, card summary, relay-mesh preflight (interactive
start and restore-on-launch)
- `apply_persona_snapshot` no longer preserves stale record
model/provider when definition is blank
- Backend `update_managed_agent` blocks model/provider/prompt writes for
linked records
- Frontend omits model/provider/systemPrompt submission for linked
instances; system prompt override hidden
- `model_source` field added to `ManagedAgentSummary` so card labels
distinguish inherited (`Default model (X)`) from explicit
- Spawn hash now digests resolved model/provider (not raw record fields)
so global default changes trip the restart badge even for runtimes
without `model_env_var`
- Orphan spawn/deploy/mesh-preflight blocked with jargon-free error
("This agent's configuration is missing — it may still be syncing or was
deleted on another device")
- `EffectiveAgentConfig::relay_mesh_model_id()` and
`resolve_effective_relay_mesh_model_id()` added; both mesh preflights
(`start_local_agent_with_preflight`, `restore_managed_agents_on_launch`)
call this instead of the deleted
`relay_mesh_config`/`relay_mesh_model_id` record-byte sniffs
- Legacy relay-mesh records keep their mesh routing. Two shipped
generations predate `provider: "relay-mesh"` and are never rewritten on
load: the typed `relay_mesh` marker (added when `ManagedAgentRecord` had
no `provider` field), and before it the mesh preset written directly
into `env_vars`. `resolve_definition_less` falls back to the marker,
then to the env preset, so these records still resolve to mesh instead
of silently misrouting to an unrelated provider while their stale env
bytes reach the child. The fallback is skipped when the record carries
an explicit `provider` — that states current intent, including a switch
away from mesh — and `resolve_linked` has no legacy fallback at all
- The env discriminator accepts both spellings of the two sentinels
renamed in the Jun-11 window without a record migration: the provider
env key (`BUZZ_AGENT_PROVIDER`, previously `SPROUT_AGENT_PROVIDER`) and
the api-key value (`buzz-mesh-local`, previously `sprout-mesh-local`).
Each is independent, since a record can straddle the window; the current
provider-key spelling wins when both are present
- Dead `persona_field_with_record_fallback` and wrapper
`persona_snapshot_with_agent_config_fallback` deleted; callers use
`persona_snapshot` directly
- New resolver/deploy/hash/write-guard/mesh-preflight tests, including
switch-away and global-inheritance regressions for both mesh preflight
call sites, per-class legacy-mesh resolution (typed marker and env
preset, in every rename-window spelling combination), and the paired
assertions that a linked instance's legacy mesh bytes stay inert

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-07-26 12:30:33 -04:00
Will PflegerandGitHub 8e67cf399d chore(desktop): delete dead persona catalog UI cluster (#2886)
## Summary

- Deletes 6 orphaned files in `desktop/src/features/agents/ui/` (556
lines) that formed a closed cluster with zero imports from the reachable
module graph — orphaned by the 1B dialog consolidation
(`PersonaCatalogSurface`, `PersonaCatalogSection`,
`PersonaCatalogDetailsSheet`, `PersonaCatalogSelectionBadge`,
`PersonaIdentity`, `PersonaLibraryEntryPoints`).
- Removes a stale `check-file-sizes.mjs` override entry for the
no-longer-existent `PersonaDialog.tsx`.

Verified dead two ways: import-graph reachability walk from all entry
points puts all six outside the reachable set, and `tsc --noEmit` passes
clean after deletion. Their `data-testid`s have zero references outside
the cluster. `PersonaCatalogDialog.tsx` is alive (`AgentsView` imports
it) and stays.

Independent of #1968 — pure dead code removal, no behavioral change.
2026-07-26 12:30:13 -04:00
166c6655e8 fix(desktop): surface install failures hidden by curl-pipe exit codes (#2892)
Ubuntu Doctor reports `Install failed at verify: The installer finished,
but Buzz still could not use claude-code (observed: CLI missing)` while
the `cli` step shows success. The `cli` step is lying.

## The masking

Every CLI install command is a pipe — `curl -fsSL
https://claude.ai/install.sh | bash`
(`managed_agents/discovery.rs:109`), `… | sh` for Codex (`:141`), `… |
CONFIGURE=false bash` for Goose (`:75`). `install_shell_command` ran
them through `bash -l -c` with no `pipefail`, so the pipeline's exit
status was the **right-hand** side's. A `curl` that fails — or that
isn't on the child's PATH at all — feeds `bash` an empty stdin, and
`bash` with nothing to run exits 0:

```
$ /bin/bash -l -c 'curl -fsSL https://nonexistent.invalid/x.sh | bash'; echo $?
curl: (6) Could not resolve host: nonexistent.invalid
0
$ PATH=/tmp/empty /bin/bash -l -c 'curl -fsSL https://claude.ai/install.sh | bash'; echo $?
bash: line 1: curl: command not found
0
```

`run_install_command` records exit 0 as `success: true`, the adapter
step then installs fine (it uses Buzz's own bundled Node, no system PATH
needed), and `post_install_verification` correctly reports the CLI is
absent. The user is handed a `verify` riddle instead of curl's error,
which is why diagnosing this required three rounds of guessing.

Install commands now run under `set -o pipefail`, so the left-hand
side's failure is the step's failure and `InstallStepResult.stderr`
carries the vendor's own message. `SHELLOPTS` is not exported by either
shell, so the piped-to vendor script still runs with its default
options. The Windows PowerShell install path
(`install_powershell_command`) bypasses this shell and is untouched.

## The PATH collapse it was hiding

`install_shell_command` composes the child's PATH and calls
`cmd.env("PATH", …)`, which **replaces** rather than extends.
`should_use_inherited` was `is_windows && !had_shell_path &&
has_local_context`, so on Unix the inherited process PATH was never
appended. When `login_shell_path()` returns `None` — a login shell that
exits non-zero or prints nothing, which a GUI-launched process can
easily hit via `~/.profile` — the child's entire PATH becomes Buzz's two
managed Node dirs. There is no `curl`, `sh`, `sha256sum`, or `tar` in
either, so every curl-pipe install fails, and before this PR it failed
invisibly.

The `is_windows` requirement is dropped: the inherited PATH is the floor
whenever no login-shell PATH was obtained, on every OS. Both existing
suppressions are kept — a login-shell PATH present still suppresses it
(no doubling), and no home/exe context still suppresses it (never
manufacture a PATH from ambient state alone). Inherited entries stay
**last**, so managed dirs keep precedence.

The other caller, `build_augmented_path` (`runtime/path.rs:148`, feeding
agent spawns and CLI probes), reads correctly under the new rule for the
same reason: it only gains the inherited PATH in the case where it would
otherwise hand a child a PATH with no native entries. When a login-shell
PATH exists — the normal case on macOS and Linux — its output is
unchanged, which `unix_shell_path_suppresses_inherited_fallback` pins.

## Scope

This fixes the reporting defect and the PATH floor. The specific
environment failure on the affected Ubuntu box is still being diagnosed
and is deliberately not addressed here; the point of this change is that
the next attempt produces the real error instead of a `verify` riddle.

One interaction worth noting: `install_failure_is_retryable` retries any
failure that carries an exit code, so a pipefail-surfaced curl failure
now gets 3 attempts with backoff — correct for transient network blips,
and harmless for hard failures.

`desktop/scripts/check-file-sizes.mjs` ratchets the `agent_discovery.rs`
ceiling 1836 → 1895 for the added tests.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-07-26 12:28:30 -04:00
74b63e1846 Refactor managed-agent runtime into cohesive modules (#2974)
## Summary

- split the managed-agent runtime warehouse into cohesive modules for
process ownership/termination, orphan sweeping, dead-instance reaping,
lifecycle synchronization, and runtime metadata
- preserve the existing `runtime` API through narrow re-exports; helper
bodies and platform `cfg` branches are unchanged apart from
module-qualified visibility
- reduce `runtime.rs` from 2,220 lines on `main` to 908 lines and remove
its temporary file-size override, restoring the standard 1,000-line
ceiling

## Why

`main` failed after stale successful PR checks allowed independent
growth to combine above `runtime.rs`'s 2,216-line override. The earlier
fix in #2974 extracted only 55 lines and left the monolith on a special
ratchet. This replacement includes that extraction but establishes
responsibility boundaries and removes the exception entirely.

## Module boundaries

- `process.rs` — process identity, ownership markers, receipt
validation, and termination primitives
- `orphan_sweep.rs` — same-instance orphan discovery and cleanup
- `instance_reaper.rs` — foreign/dead desktop instance detection and
agent reaping
- `lifecycle.rs` — tracked runtime synchronization and stale record
cleanup
- `metadata.rs` — model/provider metadata resolution
- `runtime.rs` — summary/config/spawn orchestration and composition

## Validation

At `a824fda31eff6ecc0d39ca1b8ea5602a108897e6`:

- pre-push `desktop-check`
- pre-push `desktop-test`
- pre-push full `desktop-tauri-test`: 1,637 passed, 0 failed, 14
ignored; integration + doc tests passed
- `cargo check --manifest-path desktop/src-tauri/Cargo.toml --lib`
- `cargo fmt --manifest-path desktop/src-tauri/Cargo.toml --all --
--check`
- `node desktop/scripts/check-file-sizes.mjs`

Supersedes #2974 and #2930.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Princess Donut <68157ebd23b3897c1991015c3038658ea916200c67d3a54620b0754d1b92f6e0@buzz.block.builderlab.xyz>
2026-07-26 15:34:00 +00:00
Daniel CadenasandGitHub cc6c4d3471 fix(desktop): make Linux AppImage GStreamer work on non-Debian distros (#2176) 2026-07-25 19:49:30 -07:00
WesandGitHub 5d1233e841 refactor(desktop): remove Agent directory section from Agents page (#2290) 2026-07-25 19:46:07 -07:00
Shawn YeagerandGitHub ab7aa8b120 fix(desktop): enable arboard Wayland backend so Linux copies reach the Wayland clipboard (#2904)
Fixes #2896

**Root cause:** `arboard = "3"` compiles with default features only
(`image-data`), so the Linux build has no Wayland backend. In a Wayland
session every copy lands on the XWayland clipboard; the compositor only
mirrors X11 selections while an XWayland window holds focus, and
arboard's clipboard client is windowless, so Wayland-native apps read
nothing. `set_text` still returns `Ok`, which is why the invite dialog
shows "Copied" with an empty clipboard.

**Fix:** enable arboard's `wayland-data-control` feature. Under Wayland
it uses the `zwlr-data-control` protocol; when `WAYLAND_DISPLAY` is
absent it falls back to X11 as before. The `wl-clipboard-rs` dependency
is target-gated inside arboard, so macOS and Windows builds are
unchanged.

**Verified:** `cargo check` passes on the desktop crate; wl-clipboard-rs
now resolves in the lockfile.

Signed-off-by: Shawn Yeager <shawn@shawnyeager.com>
2026-07-26 02:29:50 +00:00