Commit Graph

1767 Commits

Author SHA1 Message Date
Salman Mohammed
5621006bcf refactor: move agent Git bootstrap into ACP harness (#7819)
discovered in
https://github.com/block/buzz/issues/7742#issuecomment-5783512985

## Summary

Goose uses its native shell, so it bypassed the Git identity and signing
setup inside `buzz-dev-mcp`. Agent commits could use the host Git
identity, while MCP shells had a separate config. This moves Git setup
into `buzz-acp`, where both paths receive the same agent identity, Nostr
signer, and relay-scoped credential helper.

Before:

```text
Buzz Desktop
  └─ buzz-acp                 ← Desktop supplied relay Git authentication
      ├─ Goose → native shell ← no agent Git author or signing setup
      └─ Buzz Agent
          └─ buzz-dev-mcp
              └─ shell         ← MCP created the keyfile and Git signing config
```

After:

```text
Buzz Desktop or Sprig
  └─ buzz-acp                 ← owns the keyfile, identity, signing, and scoped credentials
      ├─ Goose → native shell ← inherits the complete Git environment
      └─ Buzz Agent
          └─ buzz-dev-mcp
              └─ shell         ← receives the same Git environment via mcpServers[].env
```

The harness keeps the signing key in a private temporary file and
removes it on normal exit, startup failure, and graceful termination. It
composes inherited `GIT_CONFIG_*` entries, forwards the complete block
to MCP servers, and bundles the helpers through the ACP multicall
entrypoint. For standard `buzz-acp` launches, Desktop and Sprig no
longer write Git config. A custom Desktop ACP command bypasses the
harness, so Desktop preserves its prior relay-scoped credential helper
for that supported override.

This changes Git settings for every ACP runtime. The startup signal path
also changes so a termination during adapter initialization removes the
keyfile. The temporary file can remain after SIGKILL or a machine crash.

### Related issue

Related: #6177 covers stricter commit identity enforcement and touches
the same Git paths. That PR and this one need to be reconciled before
either lands. No issue was found for this runtime bootstrap gap.

### Testing

- Ran the ignored `git_runtime_tests` with real Goose 1.50.0 and Buzz
Agent ACP sessions. `real_goose_native_git_shell` passed again on this
PR branch with `BUZZ_TEST_BIN_DIR` pointing to built binaries. Goose
made a signed commit and tag through its native shell; Buzz Agent did
the same through its MCP shell. Both verified signatures, identity,
credential scoping, and keyfile cleanup.
- Built the Linux Sprig image and ran `scripts/test-sprig-image.sh
buzz-sprig:acp-git-bootstrap`. The container made and verified a signed
commit and tag, then confirmed keyfile removal after SIGTERM.
- `just ci` passed on the original branch. After rebasing onto `main`
and addressing review feedback, the ACP Git bootstrap integration tests,
focused desktop smoke test, custom-command regression test, formatting,
and ACP/Desktop Clippy passed. The pre-push hook hit two
timing-sensitive ACP deadline tests under parallel load; both passed
alone and the complete ACP package passed serially (964 library, 2 Git
bootstrap, 9 pool lifecycle tests). The push used `--no-verify` after
those checks; GitHub CI is rerunning on the new head.
- Under injected `BUZZ_ACP_ALLOWED_RESPOND_TO`, `BUZZ_ACP_LAZY_POOL`,
and `BUZZ_ACP_IDLE_POOL_SLEEP`, the clean-env serial package command in
`crates/buzz-acp/TESTING.md` passed: 964 active library tests, 2 Git
bootstrap tests, and 9 pool lifecycle tests. Both named Databricks OAuth
tests passed locally; another reviewer reproduced their
environment-specific failures on `main`.
- No authenticated relay clone or push was run. The local probes used
disposable repositories and did not contact a relay.

Generated with Codex

---------

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
2026-09-23 14:41:00 -04:00
Wes
0cc63fe3e6 fix(agent): preserve Databricks Opus UC reasoning and tool continuation (#7840)
## Summary
Fix `reasoning_effort: Extra inputs are not permitted` for Databricks v2
model `data_workflow_tools.goose.goose-claude-opus-5-5` without dropping
explicit High.

- Add one provider-qualified exact capability record: Anthropic
Messages, adaptive thinking, `output_config.effort`, medium upstream
default. Mirror exact-record precedence in Rust/TS; keep uncurated UC
services neutral and existing GPT routing unchanged.
- Preserve ordered native content, including signed/empty thinking and
redacted blocks, across tool results. Do not cache-stamp thinking blocks
or leak native state into Chat reasoning details. Discard truncated
native state; reject more than 64 native calls before any tool executes
instead of altering signed content.
- Add production-dispatch/ACP regressions and six shared capability
vectors. No relay/ACP protocol, schema, settings, inheritance, retries,
or other harness changes.

## Validation
At final head `82f43f8d07bfcc3f4af492afc0a58a02f8e549c4`, clean tree:
- Rebuilt real `buzz-agent`, drove ACP with a harmless MCP fixture
against the actual service. Two POSTs to
`/ai-gateway/anthropic/v1/messages`, both HTTP 200, exact FQN, High via
`output_config`, adaptive/summarized thinking, no `reasoning_effort`.
First response contained signed thinking + tool use; second request
preserved signature/content order and paired result; final ACP stop
`end_turn`. No credentials, signature contents, or conversation data
included here.
- Mandatory pre-commit/pre-push checks passed: formatting/lint,
file-size gate, Rust test lanes, desktop typechecking and full JS tests,
Tauri clippy/tests.
- Full `cargo test -p buzz-agent` passed on the pre-commit tree (only
hook formatting followed), including the new native truncation/65-call
ACP regressions, ordered interleaved signed/redacted two-tool capture,
nearby-ID negatives, and existing provider regressions.
- Independent API/replay, regression-boundary, and Rust/TS capability
reviews cleared.

Full `just ci`, packaging and manual desktop UI testing were not run
locally; repository CI remains the merge gate. Cross-harness internals
and other UC service contracts are outside scope.

## References
- [Databricks model
services](https://docs.databricks.com/aws/en/ai-gateway/query-model-services)
- [Anthropic thinking
preservation](https://platform.claude.com/docs/en/build-with-claude/thinking#thinking-block-preservation-by-model)
- [Anthropic
effort](https://platform.claude.com/docs/en/build-with-claude/effort)
- Originating Buzz channel: `fccdea96-0f31-4343-ba6a-324214dc8d73`
- Originating thread:
buzz://message?channel=fccdea96-0f31-4343-ba6a-324214dc8d73&id=33f242923a317e269c1ff7b960cacd6ec1f3371eff2f97592857804c50fb100c

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
2026-09-23 09:44:13 -06:00
Will Pfleger
b6a26556ec feat(canvas): add version history with atomic restore (#6780)
🤖 Adds append-only channel canvas history, restore, and
relay-authoritative optimistic concurrency so stale edits cannot
silently replace a newer canvas.

## What this adds

- **Desktop history and restore** — browse revisions with author,
timestamp, preview, and diff; restore an older revision as a new signed
head after explicit confirmation. Persisted empty canvases remain
reachable, mutation outcomes are announced accessibly, and focus moves
to a stable destination after save or restore.
- **CLI history and restore** — `buzz canvas history --channel …`, `buzz
canvas get --channel … --revision <id>`, and `buzz canvas restore
--channel … --revision <id>` use keyset pagination over the retained
revision stream.
- **SDK canvas builders** — optional `expected-revision` tags, bounded
ancestry classification, and shared timestamp discipline keep
first-party writes ahead of the head they read without propagating
poisoned future timestamps.

## Authoritative concurrency contract

Tagged canvas writes use compare-and-swap semantics in the relay and
database:

- `expected-revision=none` succeeds only when no live canvas exists.
- An event ID succeeds only when it names the canonical live head.
- A byte-identical replay succeeds only when that event is already the
canonical live head.
- A successful candidate must sort strictly ahead of the current head
under `created_at DESC, id ASC`.
- Malformed preconditions and stale or non-advancing writes are rejected
before event rows, mentions, or fan-out are committed.

The database serializes tagged writes, unconditional kind `40100`
writes, and canvas soft deletion on the same `(community, kind,
channel)` advisory key. Untagged `buzz canvas set` remains an
unconditional write, but participates in that serialization boundary so
it cannot race a tagged save or deletion. Deletion classification is
derived inside the datastore transaction; unrelated event kinds do not
take the canvas lock.

## Read and timestamp consistency

Precondition and post-write ancestry reads request strong consistency
from `/query`, pinning them to the writer when replica routing is
enabled. Display-only history remains replica-eligible.

First-party canvas writers stamp `created_at = max(now, head + 1)` with
a conservative client skew limit. The relay applies a canvas-specific
future-timestamp ceiling before persistence, preventing an accepted
boundary head from making subsequent first-party writes invalid while
preserving the broader ingest limit for other event kinds.

## Durable outcomes and recovery

After an accepted publish, a failed verification read is reported as
durable success with `verified: false` rather than a failed save.
Desktop and CLI preserve the new event ID and direct users to History
when verification is unavailable. A successful read walks the bounded,
cycle-guarded `expected-revision` ancestry chain so transitive
descendants are not misclassified as supersession. Canvas caches
invalidate for every settled mutation outcome, including a retained but
superseded revision.

The 409 reconciliation path in `cmd_restore_canvas` now separately
establishes persistence evidence from ancestry classification.
`canvas_write_survived` checks reachability only; a secondary
writer-pinned IDs lookup distinguishes "A absent" from "A present but
superseded by a legacy write." The four-way outcome (survived /
persisted-but-superseded / genuinely-absent / read-fail) is now correct
for all lost-response shapes including unconditional legacy writes.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
2026-09-23 11:03:54 -04:00
Wes
ec7ea38f62 chore(release): release Buzz Desktop version 0.5.24 (#7817)
## Buzz Desktop release v0.5.24

- **Frozen main:** `fe56d311a7156802408afb04023c3f75d4015007`
- **Reviewed candidate:** `3befaf16002d802a97aa79007be29b23623ceb3f`
- **Previous desktop release:** `desktop-v0.5.23`
- **Proposed immutable tag:** `desktop-v0.5.24`

This PR may be **squash merged** after the Desktop Release Candidate
check and all protected-branch checks pass. Merging authorizes
publication of the exact reviewed candidate; later or unrelated changes
on `main` cannot alter it.

The checked-in changelog accounts for every non-merge commit in the
release range. The Desktop tag points to the reviewed candidate commit,
not the later squash commit. Publication remains bound to that immutable
candidate tag.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
2026-09-22 19:54:44 -06:00
Wes
fe56d311a7 test(desktop): stabilize unread and audio release smoke fixtures (#7821)
## Summary
Repair the mock-backed desktop smoke fixtures blocking release PR #7817.
No application unread logic, styling, audio component, scheduler, or
release-tool changes.

- Require an exact channel-scoped kind-9 subscription before injecting
unread messages, including after navigation. The old readiness check
could accept a global-only, wrong-channel, or wrong-kind consumer. The
extracted predicate is used by the real mock bridge and has regression
coverage for those false positives and legacy behavior.
- Seed the 24 audio messages offscreen with distinct timestamps, then
open the channel with all cards in the load margin. Same-second random
event IDs previously reordered the settling timeline, causing different
visible cards to start loads. This was viewport churn, not a component
restart defect.
- Expose the existing real audio scheduler snapshot through the E2E
bridge. Assert 3 active / 21 queued before navigation and 0 active / 0
queued afterward. Preserve the peak-3 cap, exact 3 fetches /
cancellations / releases, zero object URLs, and existing unread/bold
assertions. No sleeps, retries, skipped tests, or relaxed limits were
added.

## Validation
- Before commit, all 30 tests in `channel-activity-popover.spec.ts`,
`channel-mute.spec.ts`, and `voice-note.spec.ts` passed five repetitions
with retries disabled: **150/150**. This was the working repair on base
`312cf674f`, before final formatting/cleanup, not the entire smoke
suite.
- At committed head `1b126afbe75c2a2afbdfe13744f0938fa584764e`, normal
pre-push hooks passed: **6,591/6,591 desktop unit tests**, desktop
checks, TypeScript typechecking, differential file-size gate, and
branch-skew check. Pre-commit Biome formatting passed; working tree is
clean.
- Independent sidebar and audio reviews found no substantive blocker.
Their formatting findings are resolved in this commit.
- Full repository `just ci`, native release packaging, and the entire
Playwright smoke suite were not run locally. GitHub CI is the remaining
broader gate; this PR is not a release certification.

## Release follow-up
After this repair is merged and required checks pass, regenerate #7817
through the desktop release tooling and validate the new candidate's
exact head. This PR does not merge or publish a release.

Originating discussion:
buzz://message?channel=3428ec3f-a58b-429b-afbc-dc6a77918ce8&id=3303565b14334696e9bbf433ef3ad1aea4307acc69834f7f685a412e81354493

Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
2026-09-23 01:07:00 +00:00
Diem Nguyen
312cf674fa fix(desktop): remember Inbox unread-only choice (#7672)
## Summary

Inbox forgets the **Show unread only** choice after navigation or
restart. Save the toggle with the existing safe storage helpers and
restore it when Inbox opens.

The preference stays on this installation and applies across accounts
and communities. Fresh installations keep the current off default. If
storage fails, the current view remains usable, but the choice may not
survive navigation.

### Related issue

Related report: #3669.

Overlaps with #6105 and #3682. #6151 also persists category filters and
pane widths. This draft keeps the change to the unread toggle and reuses
the existing storage helpers.

### Testing

The mock Desktop regression passed: enable the toggle, navigate to
Agents and back, reload, then repeat with the toggle disabled. Both
choices were restored. This exercises the UI with a synthetic fixture; a
native app restart was not tested.

The full local repository CI gate is pending. Keep this PR in draft
until validation completes.

### Before and after behavior

Same synthetic Desktop fixture in Chromium, 1280×720, on macOS.
**Before:** production source from
`41c5ace93740261ee5a5d962c2a17e8d846c2c1b`. **After:** PR head
`b50c1a06d53fdc080e7aae6ee9aa2e9ad6b60a82`. The capture test asserted
the displayed toggle state before taking each screenshot.

1. Enable **Show unread only**, open **Agents**, go back to **Inbox**,
and reopen **Inbox options**.
2. Enable the toggle again, reload the page, and reopen **Inbox
options**.

Before the fix the toggle is off after both journeys; after the fix it
stays on. These are mock-backed UI captures, not a native Desktop
restart or live relay test.

| Journey | Before: resets to off | After: stays on |
| --- | --- | --- |
| Inbox → Agents → back |
![before-navigation](https://raw.githubusercontent.com/block/buzz/8a4d4845a51ab096d573f96bd528aa0e7abb1dc9/pr-7672--before-navigation.png)
|
![after-navigation](https://raw.githubusercontent.com/block/buzz/8a4d4845a51ab096d573f96bd528aa0e7abb1dc9/pr-7672--after-navigation.png)
|
| Enable → page reload |
![before-reload](https://raw.githubusercontent.com/block/buzz/8a4d4845a51ab096d573f96bd528aa0e7abb1dc9/pr-7672--before-reload.png)
|
![after-reload](https://raw.githubusercontent.com/block/buzz/8a4d4845a51ab096d573f96bd528aa0e7abb1dc9/pr-7672--after-reload.png)
|

Generated with Codex

Signed-off-by: Diem Nguyen <diem@squareup.com>
2026-09-22 15:55:55 -07:00
Wes
a024b83d54 fix(desktop): refresh channels after access-revoked closure (#7784)
Pinky is updating this PR on Wes’s behalf.

## Summary
- Treat exact `CLOSED restricted: channel access revoked` on a known
channel-scoped live subscription as a hint to refresh authoritative
channel state.
- Reuse `useLiveChannelUpdates`’ existing debounced, idle-aware query
refresh. Keep terminal retirement, explicit membership denial, and
polling unchanged.
- Cover archived, unchanged, removed, and failed authoritative results;
burst coalescing; irrelevant/stale frames; exact-reason rejection; and
unmount cleanup with seven production-path regression cases.

The hint does **not** establish archive or membership state. This closes
a missed-update recovery gap without changing the relay or blindly
retrying subscriptions.

Related symptom report: https://github.com/block/buzz/issues/7026 (not
claimed fully resolved; archived-channel delete policy is separate).
Related new-client fix: https://github.com/block/buzz-app/pull/106
Originating conversation:
buzz://message?channel=cb38cd5a-c920-40a1-b770-e9b89c289476&id=d22c1eed473016cf1f73c889a4cc88f1491ee092cfe3794575f8fea8003aa19e

## Review follow-up and main integration
At **`51ec7482ad1e600bd02ea6b9ed265db8a70e7266`**, based on main
**`c213d90d5c450e2e579505da4a4145f528ecbcf2`**:
- Addressed Jude’s P2: a known channel-scoped live subscription closed
with `restricted: not a channel member` stays terminal but must not
refresh channels.
- Mutation verification: replacing the exact reason predicate with
`startsWith("restricted:")` produced **6,588 passes / 1 expected
failure**, specifically the new no-refresh assertion (`actual: 1`,
`expected: 0`). Restored the exact predicate; the final full suite
passed **6,589/6,589**.
- Resolved both conflicts with #7790, retaining main’s paced/cancellable
subscriptions and this PR’s hint after terminal cleanup/drain
cancellation. The fixture advances the production drain with a
consistent virtual clock; it does not stub or bypass admission.
- Integration crossed the session file’s 1,200-line ceiling (1,207).
Extracted only this PR’s exact predicate/listener set into
`relayChannelAccessRevocations.ts`, with unchanged session ownership and
cleanup. No limit change, new timer, retry policy, or unrelated
refactor. Final feature diff: 46 production additions across three
files, plus the 333-line regression fixture.
- Independent read-only review of the integration and final extraction
found no blocking findings. This is not GitHub approval.

## Validation
At exact pushed head **`51ec7482ad1e600bd02ea6b9ed265db8a70e7266`**:
- Mandatory hooks passed without bypasses: desktop lint/policy checks,
TypeScript, repository file-size gate, branch-skew check, and full
desktop suite **6,589/6,589**, zero failed/cancelled/skipped.
- Working tree clean after push. DCO passed on GitHub.
- Fresh hosted CI and Desktop Release Candidate checks are running;
prior-head green checks are not evidence for this head. Re-review
requested from `jedwards27`; the changes-requested review and required
approval remain for the reviewer to update.

## Evidence boundaries
- Native IPC is stubbed; the fixture exercises actual subscription
creation, raw CLOSED dispatch, mounted hook, authoritative query
refresh, and IPC result conversion. It does not exercise native
discovery or rendered sidebar behavior. Existing in-flight refresh
retention remains source/helper-test coverage rather than a new
integrated case.
- No native app launch, live archive mutation, or release certification.
This fixes a demonstrated recovery mechanism, not proof of the exact
original incident ordering.
- The initial broad local `just ci` attempt hit a 300-second timeout
compiling `buzz-voice`; full local `just ci` remains incomplete. Current
delivery uses mandatory path-scoped hooks and fresh hosted CI; no
unrelated native/mobile lane was bypassed or claimed rerun locally.

---------

Signed-off-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz>
Co-authored-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz>
2026-09-22 15:55:49 -06:00
Wes
c213d90d5c fix(desktop): bound startup request bursts and recover quota refusals (#7790)
Prepared by Brain on Wes’s behalf.

## Summary

Reduce startup request amplification and let quota-refused background
work recover without changing relay quotas or bypassing the existing
Send gate.

- Coalesce nest regeneration into one worker plus the latest pending
generation. Keep the stale-write fence; bound the archive read so an old
workspace cannot strand newer work.
- Claim pending channel subscriptions before awaiting setup; cancel
removed/unmounted entries and preserve still-owned subscriptions across
reconnect handoff.
- Recheck extended cooldown deadlines before retry dispatch; distinguish
an explicit zero-second hint from a missing hint.
- Recover native finite/unread queries within their original caller
deadline and a three-retry budget, preserving verified unique events,
complete filters and terminal errors.
- Pace renderer initial/live-CLOSED REQs at one per 250ms: visible
channel and explicitly interactive TTS/read-state/observer owners first,
then live-only auxiliaries, then background history. Publication and
finite queries bypass this queue. Reconnect replay now promotes explicit
interactive owners alongside the visible channel with stable
registration-order ties; its existing capped batches and cooldown remain
intact.

**Scope at `867495426`:** 39 source/test files, including 15 production
files (+665/-235) and 24 test/fixture files. The diagnostic recorder,
HTTP instrumentation, trace launcher and raw captures are deliberately
excluded. No profile reset, grouping, quota increase or new global
scheduler.

## Before / after evidence

These are observations from instrumented, human-operated development
Tauri/Vite runs against a production relay, plus explicitly identified
controlled regressions—not a packaged-release startup benchmark.

| Measurement | Before | After | Evidence boundary |
| --- | --- | --- | --- |
| Archive snapshot requests (kind 13535) | 290 | 3 | Recorder-only
baseline → nest-only intermediate candidate; unequal 67.436s / 39.273s
captures and hydration differences |
| Archive responses: HTTP 200 / 429 | 277 / 13 | 3 / 0 | Same
baseline/nest-only captures; not a claim that all quota pressure
disappeared |
| Successful archive responses’ advertised content-length sum |
21,208,505 bytes | 229,695 bytes | Header values, not actual transferred
bytes |
| 154-channel StrictMode replay | 308 REQs | 154 REQs | Controlled real
React hook → real session → mocked Tauri transport regression |
| Add one channel while 154 setups are pending | 309 REQs | 155 REQs |
Same production-boundary regression; ordinary mount remains 154 → 154 |
| Broad renderer request burst / spacing | 110 broad REQs within
10.766ms in pre-drain capture | 250.061ms minimum / 252.131ms median
between broad REQ writes in final longer capture | Different attended
runs; pacing rather than a universal request-rate cap |
| Native unread query outcomes | Baseline: 8 EOSE / 146 quota CLOSED out
of 154 queries | Final longer run: all 156 observed queries reach EOSE;
eight refused queries each recover after two refusals | Different
runs/workloads; query completion, not badge rendering |

### Timing metrics

| Observation | Measured value |
| --- | ---: |
| Baseline’s two accepted chat EVENT socket writes → positive OK |
173.340ms / 85.556ms |
| Nest-only intermediate candidate’s two accepted writes → positive OK |
277.854ms / 78.775ms |
| Final candidate, short run, accepted chat writes → positive OK |
80.636ms / 77.506ms |
| Final candidate, longer run, accepted chat writes → positive OK |
120.712ms / 86.895ms |
| Last initial EOSE among 163 observed broad channel registrations,
longer run | 64.550s after trace start |
| Last EOSE among 156 observed native unread queries, longer run |
73.972s after trace start |
| Native retry waits after 3s then 0s hints, longer run |
3000.7–3002.8ms, then 2001.5–2002.6ms |
| Renderer finite-query retry after a 2s hint, longer run | 2061.365ms |

Both Sends in each final run were accepted, with no recorded negative
OK. Every quota-refused query in the longer run subsequently completed
the same query. Its two Sends happened early (17.878s and 21.655s), not
near the end. These small samples do not support a percentage latency
improvement.

**Tradeoffs and limits**
- 296 background subscriptions imply about 74s of paced registration
before extra cooldowns/high-priority work. The longer capture observed
163 registrations; it did not independently enumerate the expected
membership set or record a queue-empty marker.
- This is not a global 4-REQ/s cap: finite queries, native requests and
reconnect replay are outside the drain and still share relay quota.
Quota refusals recover rather than disappear.
- Socket-write → OK starts after composer submission and any publisher
gate wait. No click-to-visible, launch-to-usable or badge-paint latency
is claimed.
- Native EOSE is not proof of command return, classification, React
state application or visible unread badges. Existing per-channel kinds,
since floors, limits, h-less routing, unread lease/scope guard and
eight-query concurrency remain intact.
- The attended measurements belong to the instrumented candidate, not a
fresh release run of this fixes-only commit. At the initial packaging
checkpoint (`1bf3d060`), eighteen of twenty files were byte-identical;
the other two removed diagnostic gate calls and isolated the nest test’s
temporary paths. The subsequent interactive priority/reconnect and
readiness fixes have controlled hook/session/browser evidence, not
another attended voice/performance run.

### Evidence provenance (captures retained locally, not uploaded)

| Capture | SHA-256 |
| --- | --- |
| Baseline `STARTUP_20260921T194814Z_98811.jsonl` |
`f4f0a89ad9aea91084bd6a7432510488166de4cbc906600d88c26d557f380994` |
| Nest-only `STARTUP_20260921T202610Z_28642.jsonl` |
`ce517b02a3f717c36d96b36bb8616e8fa80b03b12b356bacfd40475925ea3f3e` |
| Final short `STARTUP_20260922T013628Z_13506.jsonl` |
`bfea6f3cf80cd7b7e4e676632a3df2f16271fade5bfa0edec72b7bcbbe7839ff` |
| Final longer `STARTUP_20260922T014637Z_15846.jsonl` |
`f4abece613d108c4c2fe3cb808e633a0486bdc9993ca92323822dc00c3fae6ec` |

Final instrumented build fingerprint:
`b0b4fc27d793c3e643f4e10289b5afdb4620ee2e13a8b33fa0595dd3417fd5ca`. All
observed dropped-record counters in these final captures were zero.
Source manifests and offline correlation analyses are retained locally;
no raw captures, profile data or agent sessions are part of this PR.

## Validation

Current head: `86749542657a60abdfe82eed5cc576d3e162fe5d`, based on
`77729abfb692b25a0f4ec4a69add86af2e32c0dd`.

Review follow-ups:
- `8842dbd8`: explicit interactive TTS admission using the existing
main-window owner, independent of companion/timeline selection.
Real-hook/session/IPC coverage with 296 cold owners proves initial and
CLOSED priority, unchanged filters/cooldown/pacing, background progress
and deduplicated speech.
- `8cb088106`: reconnect preserves interactive priority even with no
visible channel. Stable ordering among foreground ties; real
hook/session reconnect regressions cover cooldown, capped batching, all
297 owners, deduplication and unmount cancellation. Independent reviewer
mutation restored the prior replay implementation, failed both reconnect
rows, then passed the restored candidate.
- `68e76d7c`: read-state and observer reuse interactive priority. Stop
and ModelPicker wait on the existing observer owner inside send
callbacks, after installing result listeners/deadlines. No new owner,
timer or scheduler; inherited CLOSED/fallback/error semantics remain,
rather than a new strict EOSE guarantee. The real Stop panel browser
probe holds observer IPC across the click and checks
observer-before-control order and correlated outcome. ModelPicker
component ordering is source-reviewed only.
- Mock live admission now replays matching stored history with
per-filter time/limit constraints, deduplication, untagged auxiliary
channel fallback and authoritative explicit h-tags. Kind-specific
fixture admission waits and retry-resolving hover actions fix
startup-dependent test setup without weakening event/badge assertions.
- `48e9c0dad` and `867495426`: steady-state scroll-prepend setup
observes live admission, subsequent head dispatch, successful/idle query
completion and initial bottom geometry. Phase-2 genuine-growth and ≤32px
anchor assertions are unchanged. This isolates steady-state prepending:
it **does not fix or certify production early-scroll/head-reset
behavior**.

Current validation:
- Normal push hooks at `867495426`: **6,582/6,582 renderer tests**, zero
failed/skipped/cancelled; lint/static checks, typecheck and differential
file-size gate passed. Native source is unchanged from the earlier
checkpoint; no new native run is claimed for these renderer/test
follow-ups.
- E2E build passed at `48e9c0dad` plus the exact subsequently committed
test-only delta (spec blob `469be2eed868c677ba80b859cc871208000fee60`).
The completed affected-smoke run passed **153/153** across
agent-control, badge, entity links, messaging, profile status and scroll
history; the full scroll file additionally passed twice (**36/36**). No
full CI-smoke pass is claimed from this six-file local run.
- Pinky independently reviewed production and test follow-ups, including
the final scroll spec blob `469be2eed868c677ba80b859cc871208000fee60`,
with no blocking finding. Review is source evidence, not an independent
test run.
- **Not merge-ready:** new-head required CI must complete, exact-range
security review requires authorization, and GitHub currently reports
REVIEW_REQUIRED; fresh exact-head reviewer approval is still needed. No
approval, dismissal or merge performed. No new packaged/native audio,
launch-to-usable or badge-paint observation is claimed.

Initial fixes-only checkpoint validation
(`1bf3d06024d284a63326fdd353baced0842aeb81`):

- Pre-commit formatting hooks passed; worktree clean.
- Renderer package hooks: 6,570 tests passed, zero
failed/skipped/cancelled; lint/static checks, TypeScript and
differential file-size gate passed.
- Native package hook passed: default and mesh-enabled all-target
Clippy, then the complete native workspace test suite. App library:
3,222 passed / 0 failed / 19 ignored; terminal/integration suites
passed. The real nest trigger/stalled-body test passed on macOS.
- Independent whole-change source review: no blocking findings at this
commit; all 20 file hashes matched the committed blobs and
review/validation manifests.
- Regression coverage includes real hook/session cancellation,
StrictMode/membership re-entry, reconnect ownership, extended/zero
cooldowns, burst pacing, auxiliary readiness (including EOSE before IPC
settles), native socket refusal/retry/deadline/cancellation, unread
fetch/classification and real nest trigger/HTTP/file persistence.
- Earlier isolated renderer mutations that started readiness before
enqueue or removed auxiliary priority each failed the real presence
regression; restored controls passed. No native mutation claim.
- Nest fixture uses explicit app-data/nest paths instead of assuming
Windows known-folder APIs obey HOME/APPDATA. Windows runtime execution
is not claimed; CI/build results remain separate.

Originating conversation:
buzz://message?channel=46ced308-71df-495d-a144-4ebf08334703&id=bc43c3422a3b0b2141268d0fa149b898421c824992b662aed8b73c4eee50404f

No approval or merge recommendation is implied by opening this PR.

---------

Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
2026-09-22 18:31:44 +00:00
Wes
a3117a4762 fix(desktop): register macOS badges for new and existing installs (#7783)
## Summary
- Request macOS badge authorization alongside alerts and sounds.
- Repair existing bundled installations at startup only when native
status is `Authorized` and `badgeSetting` is `NotSupported` (the
omitted/unregistered interaction). Leave user-disabled badges, denied
notifications, new users, and provisional/ephemeral authorization
untouched.
- Reuse the native query/request path in one background startup
operation. No frontend changes, read-state resets, persisted migration
flags, timers, or in-process retries.

## Validation
- Full `just ci` passed at implementation head `e6c4bc640`, before the
test-seam extraction and additional regression coverage.
- Native patch head `8ab8cf6d6`: normal pre-commit/pre-push hooks
passed, including macOS Tauri clippy (default and mesh-llm), complete
Tauri test suite (3,208 passed, 19 ignored), and repository file-size
gate.
- Regression coverage: all five native authorization statuses × all
three badge states; request exactly once only for Authorized +
NotSupported; settings/request failure propagation; startup wiring;
complete authorization options.
- Mutation verification: removing the startup task and removing the
Badge option each fail the corresponding regression.
- Isolated bundled native app on macOS 26.6.2: requesting Alert | Sound
produced Authorized + badge NotSupported; requesting Alert | Sound |
Badge then produced badge Enabled with no error. A direct AppKit Dock
label of 7 was visibly rendered in the isolated app.
- Independent review found no remaining code blocker at final head.

## CI repair (`f9a6ff2b0`)
- The original browser smoke failure was a 500ms startup assertion
racing loaded-runner rendering. Hosted failure snapshots contained all
14 expected rows; the unchanged test also reproduced the failure locally
with 6× CPU throttling.
- Reuse the existing one-shot mock channel response gate in the affected
test. Wait for startup settlement, defer a second real revalidation,
mutate display state while pending, release, then assert after query
settlement. No production changes in this follow-up.
- All 10 sidebar snapshot E2Es pass at clean head `f9a6ff2b0`; the
repaired case also passed 3/3 CPU-throttled runs before commit.
Temporarily removing display preservation or persisting the display
mutation each caused the corresponding assertion to fail; both mutants
were restored.
- Normal pre-commit/pre-push gates passed, including desktop
checks/typechecking, all 6,508 desktop tests, and the file-size gate.
Independent artifact/source review found no blocker in the repair.
- New hosted CI is running:
https://github.com/block/buzz/actions/runs/35633308957. This is not a
claim that hosted CI is green. Exact-range security review still
requires organization-member authorization.

## Runtime limitation
This fixes a verified missing authorization registration and provides an
automatic existing-install path. It is **not yet proof that the reported
blank Dock badge in the released Buzz app is fully resolved**: patched
bundled Buzz startup and unread-to-Dock recovery have not been
exercised. The isolated app is native API evidence, not a replacement
for that release smoke test. Installed Buzz and its read state were not
modified.

Before release, verify an existing Alert/Sound-only install gains the
badge setting on launch and renders an unread Dock badge; verify a
user-disabled badge remains disabled.

---------

Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
2026-09-21 18:12:38 +00:00
Wes
5079c770fe fix(workflows): make deletion persistent and retryable (#7735)
## Summary

Fix workflow deletion leaving its definition queryable, and make public
deletion acceptance agree atomically with executable/definition state.

- Delete the executable workflow and soft-delete its definition under
the existing coordinate lock, in the same transaction as kind-5
acceptance and mention indexing. Failed deletion leaves no public
accepted event; identical retries can succeed. Legacy already-stored
failed requests still repair.
- Choose one committed dispatch/audit owner for concurrent identical
requests. Completed duplicates remain silent. Preserve owner/community
isolation, newer revisions, orphan cleanup, and intentional backdated
recreation.
- Align Desktop confirmation and architecture documentation with
recreation semantics. Add deterministic PostgreSQL, live WebSocket, and
Desktop regressions. No schema migration or new retry/delivery
subsystem.

## Current validation

Head: `40d86ddab0853ffb292316653b22c3fd4f983f0c`.

- **Exact clean head:** live relay workflow-deletion E2E **4/4 passed**.
All applicable normal pre-push hooks passed: Rust unit suites, full
Desktop unit suite, Desktop lint/typecheck, Tauri clippy/tests, and
file-size/branch checks. No hook bypass.
- **Pre-commit implementation tree, then committed at this head:** full
discovered DB/relay PostgreSQL lane **351/351 passed**; built Desktop
deletion Playwright smoke **3/3 passed** (success, failure preserving
editor/draft, corrected confirmation copy).
- **Deterministic mutation evidence:** unchanged previous relay
`659b79282` fails both new regressions: concurrent identical requests
produce two normal/dispatch-owner responses, and rejected deletion
leaves one public event. Both controls pass. Fixed relay passes all
four.
- **Independent source review:** transaction/authorization/lock-order
and regression/cleanup lanes clear. Reviewers did not run tests
independently.

## Honest limits / merge gates

The broad pre-commit DB/relay/test-client selection passed
**1,215/1,216**; the untouched mesh-demo echo test returned HTTP 504
instead of 200. It remains outside this repair and is not counted green.
Initial hook attempts also exposed an untouched Desktop inventory
ordering assertion and unidentified ACP/agent test failures; standalone
package reruns and the final complete hooks passed without changing
those packages. Causes are not established.

The previous Desktop CI shard failed before tests on a Hermit `just`
download (HTTP 500). New-head CI/security review and reviewer
disposition remain required. No deployment, fresh interactive Desktop
session, full local `just ci`, or multi-replica runtime validation
claimed. Postgres/Redis validation used task-owned local containers,
fresh identities, and disposable fixtures.

## Compatibility

No automatic cleanup of existing workflows: repeating deletion repairs
definitions left by the old path. Canonical UUID coordinates and legacy
name fallback (`LIMIT 1` for duplicate names) are unchanged. Backdated
recreation remains intentional per [Tyler’s
decision](https://github.com/block/buzz/pull/7735#issuecomment-5729881295),
not an irreversible tombstone. Mention-index failure now rolls back the
complete deletion rather than best-effort accepting partial work.

Detailed reviewer disposition and evidence:
https://github.com/block/buzz/pull/7735#issuecomment-5737367825

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
Co-authored-by: Mordecai <f314b6033b6e13df94c8e9e1187d3666b6b9ce54e66ee986e31cdaa909e8dcbc@buzz.block.builderlab.xyz>
2026-09-21 08:37:14 -06:00
Jordan Mecom
f18ea45698 Replace personal and internal data in desktop test fixtures (#7748)
Replace personal contributor identities, copied internal image content,
and production Databricks hostnames in desktop tests and mocks with
synthetic fixtures. The related path and mock-profile fixtures use the
same example identity, and the image regression still verifies reserved
layout space while its mocked request remains pending. Contributor
matching and configuration decoding behavior are preserved.

Checked on Blox: 36 targeted JavaScript tests, 30 focused Rust tests,
the Playwright image-height regression, TypeScript/Biome, formatting,
security/file-size gates, 6,508 desktop tests, the full Tauri test
workspace (including 3,204 main-crate tests), and desktop/web builds.

The full `just ci` run could not finish mobile checks because Blox
connections to `pub.dev` were reset and a locked dependency was missing
from its offline cache. The broader Rust unit run passed 16 lanes but
hit an unrelated catalog timeout in `buzz-agent`; that exact test passed
in isolation.

Signed-off-by: jm <jm@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-18 22:26:19 +00:00
Wes
8953cbfff5 fix(desktop): keep managed agent avatars usable across communities (#7732)
## Summary

Fix Desktop-managed agents losing avatars after joining another
community. The saved persona/instance stores one desired source URL, but
authenticated media belongs to a community. Republishing community A’s
URL in B does not make that image accessible in B.

- Before comparing or publishing kind-0, reuse or copy
configured-community avatar media into the pinned destination. Keep
exact image bytes, including animation, and leave the saved source
unchanged.
- Keep the previous profile on transfer/publication failure so existing
reconciliation can retry. Reuse destination bytes during source outages;
after source removal, reuse only an already-published signed destination
picture with the same original content hash.
- Refresh source permissions through narrow, serialized IPC without
reconnecting or resetting the active community. Startup waits until the
latest permission update completes before restoring agents.

### Related issue

Refs #2366 (avatar portion only, not runtime availability). Related
#2659 deliberately excludes avatar/media mirroring; this change
addresses that media boundary without introducing runtime fan-out.
Searched existing issues/PRs for `avatar community` before opening.

## Contract and safety

Desktop-managed profiles still follow the saved persona source; this
does not introduce community-specific persona editing. Agent-managed
profiles still opt out of automatic reconciliation.

Authenticated transfer requests use the fixed agent key/NIP-OA tag,
origin-scoped Blossom auth, and the pinned destination, never mutable
active-workspace owner credentials. They refuse redirects, enforce
30-second request timeouts and bounded bodies, verify SHA-256 and
detected image MIME, and validate the returned descriptor’s
origin/hash/MIME/size. Source permission is rechecked after an awaited
destination HEAD miss. The removed-source fallback verifies the
destination profile’s signature, kind, signer, origin, and
original-media hash without contacting the removed source.

Ordinary external images retain passthrough behavior and receive no
community credentials. No public-media policy change, image-reader URL
rewriting, new persistence, background service, or global profile merge.

## Testing

Normal commit and push hooks passed on exact head
`925781eece66d5f6a4b084b3b3abd6309cda357e`; working tree clean before
and after:

- Desktop formatting/lint, typecheck, and **6,508 tests**.
- Full native workspace suite, including **3,204 desktop-library tests**
(19 ignored), integration tests, terminal crates, and doc tests.
- Native Clippy on default and `mesh-llm` workspace/all-target
configurations with warnings denied; repository file-size and
branch-skew gates.

Regression coverage exercises production reconciliation/shared
publication against two local HTTP fixtures: add/edit/restart, upload
and rejected-publication retry, removed-source local-image preservation,
fixed signer/destination, redirect/hash/descriptor/size failures,
revocation during pending HEAD, and opt-out no-I/O. Mounted
initialization tests cover inactive-list changes without workspace
reset, two deferred permission updates, and rejection of either update.

Earlier working-tree validation at base
`4ab4f786085a23fe6126529861840eff6048ceee` also passed the E2E frontend
build and four Chromium mock-bridge community flows (boot, switch,
active removal/fallback, leave-final/setup). Subsequent production
changes only serialize permission writes and wait for a stable latest
update; the complete suites above cover the final commit. Mutations
independently removing serialization or stable waiting failed the new
regression.

## Remaining validation and limitations

- No live production avatar was changed. Native GUI rendering against
two hosted communities remains to be verified with a build containing
this fix; local HTTP fixtures and the browser mock bridge do not
establish that outcome.
- A removed/unreachable source with no existing destination copy cannot
be recovered automatically. Choose a reachable image/source in that
case.
- This is a publication/reconciliation fix, not a visual redesign. No
before/after native GUI screenshots are claimed. Broad repository CI
remains required; it was not duplicated locally.

## Review

Independent security/scope and caller/ordering reviews are clear after
the bounded fixes. Final hook-ordering reread covered the staged tree
that became this commit. Commit authorship/sign-off use the implementing
Carl identity and configured Carl signing key.

Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
2026-09-18 06:14:28 -06:00
Brad Seiler
6d1f488d22 refactor(desktop): share relay request construction (#7667)
## Summary
This is a behavior-preserving refactor that gives the desktop relay HTTP
bridge one helper for authenticated request construction. It moves the
repeated `Authorization`, JSON content type, optional `x-auth-tag`,
body, and timeout setup into `build_authenticated_relay_request`.

The helper uses the final URL, auth header, and body bytes passed by the
caller. It does not sign, reserialize, normalize, send, parse, classify
errors, choose a client, or handle rate limits.

Excluded paths: media, WebSocket, Git, agents, mobile, and NIP-FI. The
query send helper only routes through the new request builder so the
timeout and `x-auth-tag` behavior are covered by the same seam.

### Related issue
None found. This prepares for the enterprise identity follow-up to be
smaller than #7663.

### Testing
- `cargo fmt --manifest-path desktop/src-tauri/Cargo.toml --check`
passed at `356676a2f70f6bdf80cfdcb9084f1f7548359ecb`.
- `cargo test --manifest-path desktop/src-tauri/Cargo.toml
relay::tests::authenticated -- --nocapture` passed at
`356676a2f70f6bdf80cfdcb9084f1f7548359ecb`.
- `just desktop-tauri-check` passed at
`356676a2f70f6bdf80cfdcb9084f1f7548359ecb`.
- `just desktop-tauri-test` passed at
`356676a2f70f6bdf80cfdcb9084f1f7548359ecb`.
- `just desktop-tauri-clippy` passed at
`356676a2f70f6bdf80cfdcb9084f1f7548359ecb`.

Generated with Codex

Signed-off-by: coder 0 <d97ebdbb198c7237c94f84ea8bb8a73583ea067407eebd0062abbb3962527fb1@buzz.block.builderlab.xyz>
Co-authored-by: coder 0 <d97ebdbb198c7237c94f84ea8bb8a73583ea067407eebd0062abbb3962527fb1@buzz.block.builderlab.xyz>
2026-09-16 09:48:06 -04:00
klopez4212
a9dd4d3bd7 Fix desktop onboarding regressions (#7659)
## Summary
- restore neutral identity guidance icons and reveal-first private-key
copying
- keep runtime availability sections contiguous and restore primary
onboarding colors
- route API-key setup directly to Buzz provider configuration, including
slow discovery and signed-out states

## Testing
- `pnpm check`
- `pnpm test` (6,501 passed)
- focused Playwright onboarding coverage (API routing 3/3; visual flow
1/1)

Signed-off-by: kenny lopez <klopez4212@gmail.com>
2026-09-15 10:30:05 -04:00
klopez4212
81fba7bda4 Refresh desktop onboarding flow (#7528)
## Summary
- apply the refreshed card layout across desktop onboarding
- update identity backup, recovery, and key presentation flows
- clarify AI connection choices and default model settings
- simplify the backup ceremony from creation through optional
verification
- bring the harness and provider setup flow forward with validation,
preserved state, and consistent navigation
- fix action alignment and stray card scrolling in the refreshed layout

## Validation
- pre-push desktop lint, typecheck, frontend tests, and native tests
passed
- focused onboarding Playwright coverage passed
- manually reviewed the refreshed onboarding and backup flow in the
native app
- just ci passed all sections except one unchanged process-cleanup
timing test; its exact rerun passed

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Signed-off-by: Clay Delk <clay.delk@gmail.com>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Clay Delk <clay.delk@gmail.com>
2026-09-15 05:58:16 -04:00
Tom Brow
7c789dee09 Fix Apple Silicon iOS simulator builds (#7646)
Apple Silicon simulator builds fail because MLImage and MLKit contain
device-labelled ARM64 objects.

Enable the Flutter ML Kit package's upstream compatibility helper, which
restores device labels for iPhone builds. This preserves the MLKit-based
functionality (avatar background removal) and complements the
notification-extension linker isolation in #7187.

Adds a complete iOS simulator build to CI to catch this build
regression.

---------

Signed-off-by: Tom Brow <tomb@block.xyz>
2026-09-14 22:14:06 +00:00
Salman Mohammed
6c35e82bd5 fix(acp): improve hints in pi agent setup guide (#7594)
## Summary

Handle missing Pi adapter setup and update its install guidance.

### Related issue

None found.

### Testing

No manual testing.

Generated with Amp

---------

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
Co-authored-by: Amp <amp@ampcode.com>
2026-09-12 17:40:15 +00:00
Salman Mohammed
78618804ec Configure ACP session scope per agent (#7578)
## Summary

PR #6732 taught the ACP harness to honor `BUZZ_ACP_SESSION_POLICY`, but
the desktop exposed that policy as one global experiment. Turning it on
changed every managed agent at once, even though conversation scope is
part of how an individual agent should behave. Some agents need
continuity across a channel, while agents such as validators may need
clean context for each thread.

This moves the choice onto the agent definition under Advanced as
**Conversation context**, with **Entire channel** and **Each thread**
options. **Entire channel** remains the default, so existing definitions
keep their current behavior.

The policy now travels through create and update IPC, returned agent
summaries, persona events, snapshots, teams, and community catalog
copies. Launches pass the effective definition value to the harness.
Editing a running agent uses the same lifecycle as a model edit: it
shows the restart-required state, leaves the deployed process untouched,
and applies the new policy on restart.

The global Thread Scoped ACP Sessions experiment and its desktop
command/state have been removed because the setting no longer has a
useful global meaning. There is intentionally no migration for the old
preview override. Users who enabled it can choose **Each thread** for
the agents that need it.

Unknown or null policy values degrade to **Entire channel** across
stored agents, persona events, and catalog projections. This keeps
forward-version data from dropping complete agent records.

### Related issue

No matching issue found. Builds on #6732.

### Testing

- Focused Playwright create flow selected **Each thread** and verified
`create_persona` received `sessionPolicy: "thread"`.
- Focused Playwright catalog flow added a thread-scoped agent and
verified the local copy stored `session_policy: "thread"`.

Generated with Codex

---------

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
2026-09-11 20:38:41 +00:00
Taylor Ho
ec11f8e2f3 fix(avatars): scale agent squircles from normalized paths (#7307)
## Overview

**Category:** fix

**User Impact:** Agent avatars and empty agent-team placeholders now
keep the same intentional squircle silhouette at every size on desktop,
while human avatars remain circular.

**Problem:** Desktop agent avatars used percentage-rounded rectangles,
which made the silhouette vary by surface and required each avatar
implementation to recreate the shape. Empty team placeholders also drew
a regular CSS border inside the clip, leaving straight inset edges that
looked like a cropped square. **Solution:** Reuse one normalized SVG
clip path across desktop artwork. Empty team placeholders use a
separately clipped squircle outline behind the clipped surface, so both
the fill and outline follow the canonical silhouette.

> **Scope correction:** Mobile is deferred from this PR because a real
Flutter capture could not be obtained reliably in the current build
environment. Previously attached mobile images were hand-composed
synthetic layouts, not captures from the running app; they have been
removed and must not be treated as validation. The mobile implementation
is being reverted in one follow-up commit so it can be reapplied and
verified separately.

### Related issue

Follow-up to #7106.

## Changes

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

**desktop/src/features/agents/ui/AgentCreationPreview.tsx**
Applies the shared squircle clip to agent creation preview images and
fallbacks.


**desktop/src/features/agents/ui/AgentSessionToolItem/CompactMessageSummary.tsx**
Uses the shared agent-avatar silhouette in compact session summaries.

**desktop/src/features/agents/ui/IdentityInitialsAvatar.tsx**
Clips agent initials with the normalized squircle instead of a
percentage radius.

**desktop/src/features/agents/ui/TeamIdentityCard.tsx**
Uses the shared squircle for agent identities and gives empty teams a
separately clipped squircle outline instead of a regular inset border.


**desktop/src/features/agents/ui/activityRenderClasses/UserMessageBubble.tsx**
Keeps agent avatars in activity message bubbles on the canonical
silhouette.

**desktop/src/features/home/ui/InboxListPane.tsx**
Applies the shared clip to agent avatars in the inbox list.

**desktop/src/features/home/ui/InboxMessageRow.tsx**
Applies the shared clip to agent avatars in inbox message rows.

**desktop/src/features/huddle/components/ParticipantList.tsx**
Uses the normalized squircle for agent participants while preserving
circular human avatars.

**desktop/src/features/messages/ui/MessageRow.tsx**
Clips agent message avatars through the shared definition.

**desktop/src/features/messages/ui/SystemMessageAvatars.tsx**
Uses the canonical agent shape in system-message avatar groups.

**desktop/src/features/messages/ui/TypingIndicatorRow.tsx**
Uses the canonical agent shape in typing indicators.

**desktop/src/features/profile/ui/ProfileAvatar.tsx**
Scales the same squircle to the larger agent profile avatar.

**desktop/src/features/profile/ui/SelectedRecipientChip.tsx**
Uses the shared shape for selected agent recipients.

**desktop/src/features/projects/ui/IssueAssigneesRow.tsx**
Applies the agent squircle to issue assignees without changing human
avatar geometry.

**desktop/src/features/projects/ui/ProjectCards.tsx**
Uses the canonical shape for agent avatars on project cards.

**desktop/src/features/projects/ui/ProjectEntityListRow.tsx**
Uses the canonical shape for agent identities in project entity lists.

**desktop/src/features/projects/ui/ProjectsActivityFeed.tsx**
Uses the shared clip for agent avatars in project activity.

**desktop/src/features/projects/ui/ProjectsOverviewRail.tsx**
Uses the shared clip for agent avatars in the project overview rail.

**desktop/src/main.tsx**
Mounts the single shared SVG clip-path definition once for the desktop
application.

**desktop/src/shared/lib/cn.ts**
Registers `rounded-squircle` in the border-radius class group so
shadcn-style `cn(...)` composition resolves it against `rounded-full` by
normal last-class precedence.

**desktop/src/shared/styles/globals/utilities.css**
Registers the reusable Tailwind `rounded-squircle` utility and
references the normalized SVG clip path without agent-specific CSS
selectors.

**desktop/src/shared/ui/AvatarClipPaths.tsx**
Owns the reusable normalized object-bounding-box `rounded-squircle` path
so all desktop sizes reuse one definition.

**desktop/src/shared/ui/UserAvatar.tsx**
Routes the existing squircle shape option through the shared SVG clip.

**desktop/tests/e2e/agents.spec.ts**
Verifies team facepile and empty-team placeholder surfaces/outlines use
the canonical clip without a rectangular border.

**desktop/tests/e2e/messaging.spec.ts**
Verifies agent message and profile avatars reuse one clip definition and
scale across rendered sizes.

</details>

## Reproduction steps

1. Run the desktop app and open a channel containing both agent and
human messages.
2. Compare agent avatars in the message list, inbox, huddle
participants, projects, and profile panel. Agent images and fallbacks
should share one smooth squircle silhouette at each size; human avatars
should remain circular.
3. Open **Agents** and inspect an empty agent-team card. Its placeholder
fill and outline should both be smooth squircles, with no straight inset
cropped-square edges.
4. Open an agent profile from a message and confirm the larger profile
avatar keeps the same proportions as the smaller message avatar.

## Testing

- Desktop Biome check passed for the changed empty-team component and
Agents E2E spec.
- Desktop typecheck and differential file-size check passed.
- Full desktop unit suite passed: 6,453 tests.
- Full Agents integration spec passed: 37/37, including the empty-team
outline regression.
- Post-rebase pre-push gates passed at `304b159a2`: desktop checks,
TypeScript typecheck, differential file-size gate, and all 6,453 desktop
unit tests.
- The shadcn-aligned `rounded-squircle` registration passed changed-file
Biome, desktop TypeScript/build, and pre-push desktop checks at
`27e0c12d3`; the full desktop unit suite passed 6,459/6,459.

## Screenshots

Screenshots are temporarily omitted. The previous captures used the
desktop E2E mock bridge and were not evidence from a live running app,
so they were removed.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
2026-09-11 06:08:44 -10:00
Wes
f3408fc620 fix(desktop): isolate quota backoff and reuse channel discovery rosters (#6998)
Pinky, an AI agent, is updating this PR on Wes's behalf.

## Targeted repair, not a startup rewrite

This replaces the previous readiness/replay candidate. Current head:
**`83ac81ff6ae439b0e68f4a3dafd4b2af6dbadcdb`**, two independent commits
on main base `3c7f288c60d67df78577b237e27c3dfc8831aaa1`:

1. **`0b6681de2` — keep quota backoff on its transport.** HTTP 429s
retain native HTTP backoff without arming the renderer WebSocket gate.
WebSocket quota/concurrency CLOSED retains its retry deadline without
damping HTTP's separate quota. The explicit `rate-limited: shared
admission unavailable` signal still damps HTTP too. Reuse existing
all-channel live streams for Home mention invalidation instead of a
second per-channel mention subscription; retain kind, recipient, self,
membership, replay-deduplication, notification and unread boundaries.
2. **`83ac81ff6` — reuse complete membership rosters within each channel
fetch.** Discovery already returns full paginated rosters. Retain those
results and request only uncovered directory/pending-owner channel IDs.
Skip empty fallback filters and preserve covered counts/pubkeys if that
tolerant fallback fails. The next fetch reads current membership again;
there is no persistent cache or new invalidation state.

No send-route, AUTH-readiness, replay-ordering, scheduler, pacing, relay
policy, dependency, or authorization changes. Refresh triggers and
activity batching stay unchanged. Independent tracing did not establish
a safe-to-delete duplicate normal-startup refresh; recovery
invalidations at different replay stages are not interchangeable.

**Size:** 18 files, +1,066/-350 overall; nine production paths +76/-205
(**129 fewer production lines**). The roster follow-up itself touches
only `fetch.rs` (+31/-14) and its new 378-line regression file. Test
bulk is not new startup machinery.

## Comparative roster measurement

Actual native HTTP fetch code against a synthetic Axum loopback fixture:
265 channels × 100 members, deterministic signed events, debug build.
Seven iterations per condition; medians below exclude the first warm-up.
Baseline is `0b6681de2` production fetch, candidate is `83ac81ff6`
production fetch, with the same temporary benchmark harness.

| Measure | Baseline | Candidate |
| --- | ---: | ---: |
| HTTP reads per fetch | 7 | 6 |
| JSON response-body bytes per fetch | 4,803,136 | 2,453,380 |
| Median, no artificial request delay | 262.9 ms | 149.3 ms |
| Median, 25 ms artificial delay per request | 376.7 ms | 282.7 ms |

Channel/member assertions and output hash (`fa7cc551bc855e4d`) match
throughout. This removes **one batched roster request**, not 265
requests. The measured ~43%/~25% reductions describe this large-roster
synthetic fixture, **not production startup latency**. Activity requests
run concurrently with roster enrichment, so real savings vary. Counts
now use the discovery snapshot rather than a later reread within that
same non-atomic fetch.

## Verification

At pushed head **`83ac81ff6`**:

- All enabled pre-push gates passed without bypass: `push-head-scope`,
`branch-skew`, `desktop-check`, `file-size-check`, `desktop-typecheck`,
`desktop-test`, `desktop-tauri-checks` (native fmt/Clippy/tests).
- Full Desktop frontend suite: **6,457 passed**, zero failures; separate
tooling suite: **10 passed**.
- Full native Desktop workspace: **3,276 passed**, zero failures, 20
ignored, across unit/integration/doc bins. The main native lib accounts
for 3,175 passed / 19 ignored; this is not 3,276 lib tests.
- `pnpm build` passed the protected-feature artifact matrix; `pnpm
build:e2e` also passed. Existing chunk-size/dynamic-import warnings
remain. No full repository `just ci` run locally.
- **130 selected headless mock-bridge smoke cases passed across two
runs**: messaging, reconnect, reconnect affordances, inbox live updates,
sidebar snapshots, send-channel binding, boot splash. The first
invocation hit the shell's 300-second limit after 117 passes and no
failures; all remaining 13 passed in a separate invocation. This is not
a claim the interrupted invocation completed.
- Six new tests invoke production `fetch_channels` through the real
native HTTP client and cover 265/501-channel pagination (including
same-timestamp continuation), exact requests, complete/deduplicated
rosters, directory/pending fallback, hidden DMs, repeated
membership/identity/relay changes, empty lists and errors.
- Falsification in an isolated source copy: restoring the redundant
baseline fetch fails four roster regressions; removing missing-roster
fallback fails two. Restored candidate passes all six. Earlier transport
mutations fail HTTP→WS coupling, WS→HTTP coupling, omitted shared-outage
damping and duplicate-mention regressions. No mutation was applied to
the committed worktree.
- Brain's independent static reviews found no blockers in the transport
repair or immutable roster patch. Source tracing confirms relay
discovery serializes complete rosters, not projected matching p-tags.
This was source review, not independent test execution or approval.

### Hosted CI

[CI run
34185670504](https://github.com/block/buzz/actions/runs/34185670504)
**completed successfully** for this head. GitHub checked out merge ref
`25bcf09627e129887481d4441a1af3c6cb0af1ff`; its Git tree is identical to
`83ac81ff6` (`49a01f3c2ebf4e547bbe3cd31406f984ac6cefe8`), verified
through the GitHub Git API. The final PR rollup has 33 successful checks
and 28 skipped, with none failing or pending.

- Desktop Core passed frontend/native suites and compiled-feature
verification. Windows Rust tests, Windows Desktop build, macOS
`.app`/`.dmg` build, all four smoke shards and both relay-backed E2E job
shards passed, as did Rust lint, DCO, Semgrep and zizmor.
- Browser job totals: **1,620 passed, 13 flaky cases passing automatic
retry #1, five skipped, zero terminal failures**. Smoke accounts for
1,389 passed / 12 flaky / three skipped; integration jobs report 231
passed / one flaky / two skipped. No manual CI reruns or source changes
to obtain green.
- The retries cover channel-browser, empty-edit, huddle menu,
runtime-install, overscroll, onboarding avatar, video attachment and
workflow UI assertions. They are disclosed, not collectively declared
unrelated. The video case reaches the changed real TS all-channel hook
despite mocking native I/O: independent tracing found no regression
mechanism or blocker, but the missing second-video player remains
**unexplained**, and an indirect timing effect is not excluded. The
self-authored, unmentioned event cannot use either the new mention
branch or the removed old mention handler as a delivery path. A passing
retry does not diagnose its cause.
- The separate “Desktop Release Candidate” check skipped its validation
step; it is not additional release validation. Codex security review was
skipped, not passed.

## Remaining acceptance limits

Wes's earlier manual trial of the transport-only candidate reported
improvement; relay readback found both a top-level message and a thread
reply. That was not a fingerprinted or timed trial of the new roster
commit. The remaining few-second top-level send wait is
**unattributed**, and this PR does not claim to resolve every startup
delay.

No visible app launch, relaunch, laptop input automation, or production
test messages during this follow-up. Local native tests use fixtures;
browser suites do not run the Tauri backend. CI building an app does not
validate bundled macOS alerts or inactive-channel notification delivery.
Those bundled real-workflow checks remain outstanding. No visual UI
changes or screenshots; no approval or merge performed.

Originating conversation:
buzz://message?channel=5b130804-d759-40ad-a564-d64cc907fa8e&id=cb432417e290e06a1842639cfe0268f9d43d186defbcb59260930d129a26af83

---------

Signed-off-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz>
Co-authored-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz>
2026-09-11 09:11:39 -06:00
Wes
9847b0967d test(desktop): isolate login-shell probe measurements (#7570)
Pinky, acting on Wes’s behalf.

## Summary

Fix a pre-existing false-failure mechanism in the login-shell discovery
tests, independently of the thread-window work.

- Run the two process-global-counter assertions in separate, bounded
libtest processes. The global counter still sees all of each test’s
auth/worker threads; no production discovery or cache behavior changes.
- Require exact test selection, successful child exit, and a completion
receipt after the assertion body. Reject counter resets outside
isolation.
- Keep the cold/repeated cheap-discovery zero assertions and
forced-discovery positive control unchanged. Add parent-probe exclusion
and own-worker controls.

Three test-only files, +103/-4. Base
`00209076c7a10d9e4a475466c313e8ebecf041f5`; head
`07f8e86c58a0aa5378537a3daa4610361fdbad55`.

## Reproduction and causal checks

On the main-based source, cheap discovery alone counted 0. Running the
existing missing-binary version test on another thread inside the
measurement interval made the repeated-cheap assertion fail with 1. This
reproduces interference, not the exact scheduling/caller behind the
original full-suite failure. The counter measures `run_in_login_shell`
invocations, not exact OS process counts.

Mutation checks:
- Removing isolation fails the measurement guard.
- Removing the real production `record()` call fails the original forced
positive control (0).
- A reviewer-identified early-return mutation initially survived; the
final helper owns the assertion closure instead. Deleting that
invocation now fails the external worker assertion (0 vs 1).

Independent read-only review by Brain found no remaining blocking
finding at the final head.

## Validation at the final head

- **Full `just ci`: passed.** Desktop Tauri: 3,175 passed, 19 ignored;
desktop JS: 6,488 passed; mobile: 2,098 passed. Remaining Rust unit,
formatting/lint/static, and desktop/web build lanes passed. An initial
five-minute tool timeout interrupted compilation; the subsequent
complete run used a longer deadline, not altered gates.
- **Normal pre-commit and pre-push hooks: passed.** Push includes
default + mesh Tauri Clippy, complete Tauri workspace tests, size and
branch-scope checks. No bypass.
- **Real local relay, following `TESTING.md`: passed.** Built release
`buzz-relay`, `buzz`, and `buzz-admin` from this head. Fresh isolated
Postgres/Redis, additive migrations, dedicated loopback ports,
disposable NIP-98 identity; health, create channel, signed root/reply
send, exact-ID get, and thread retrieval passed. Relay and both owned
containers stopped afterward. Dev auth-token mode follows the guide; not
a production-auth or thread-window/native UI acceptance claim.

## Scope and limits

This is process/cache isolation, not a filesystem sandbox. The existing
bounded-command helper bounds child runtime/output; its Unix
process-group behavior does not guarantee ownership of nested probe
groups. Existing individual probe deadlines remain unchanged.

No UI change, screenshots, thread-window commits, feature enablement,
merge, or deployment included.

Originating conversation:
buzz://message?channel=0b73400d-6922-4fe3-9b97-454da04d011b&id=984528c883faa8829971d19394870f0f2d543d5c1950565fd55ffddc1b8d8022

Signed-off-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz>
Co-authored-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz>
2026-09-11 09:11:05 -06:00
Salman Mohammed
813bbd1412 fix(acp): integrate the Buzz Pi adapter fork (#7552)
## Summary

PR #7335 worked around missing Pi adapter support by generating a
private Pi launcher and injecting Buzz's standing prompt and skills at
process launch. The Buzz Pi fork now carries the required adapter
extensions, so this removes that launcher and returns prompt
construction to the normal ACP session path while retaining the
base-prompt composition introduced by #7335.

The Pi preset now installs `salman1993/pi-acp` and launches its renamed
`buzz-pi-acp` binary. Buzz adds `-- --skill
<harness-cwd>/.agents/skills` only when launching that binary, sends the
complete composed prompt as the `_meta.systemPrompt` replacement string
on `session/new` only when `initialize.agentInfo.name` is `buzz-pi-acp`,
and sends the scoped title alongside it as `_meta.sessionTitle`. The
fork identity is treated as system-prompt capable regardless of its
reported ACP protocol version, which prevents duplicate legacy
user-message framing. Upstream `pi-acp` does not receive either
fork-specific behavior. Observer transcript projection accepts the
string, `{ replace }`, and `{ append }` metadata forms.

The fork now stores restore metadata in one atomic file per session
under `~/.pi/buzz-pi-acp/sessions/`. This prevents concurrent Buzz
workers from overwriting another session's prompt or title. The fix
landed in
[salman1993/pi-acp#9](https://github.com/salman1993/pi-acp/pull/9).

This supersedes the closed #7508. No agent-configuration rules changed;
this changes the Buzz Pi adapter contract and launch arguments.

### Related issue

#7329

### Testing

Installed fork commit `09cf07e436b8f18e52401558f988f31a15702313` through
the documented Git URL. The installed bundle matched the committed
`dist/index.js` byte for byte and contained the `~/.pi/buzz-pi-acp`
metadata path. The fork's 106 non-skipped tests, typecheck, and lint
pass.

Ran the ignored real-Pi integration test through Buzz's production
session composer. The test exercised the renamed package,
`agentInfo.name`, and the new per-session metadata store. Base, persona,
team, core-memory, huddle, canvas, and skill markers each appeared once
after switching sessions and again after restarting the adapter, while
the other session and Pi's native default prompt were absent.

Added regression coverage proving `buzz-pi-acp` receives fork-specific
system-prompt metadata and managed skills while upstream `pi-acp` does
not. `just ci` passes.

Generated with Codex

---------

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
2026-09-10 21:38:42 -04:00
Taylor Ho
092c6a7277 fix(markdown): align mention chip wrapping (#7501)
**Category:** fix
**User Impact:** Human and agent mentions now break across lines with
the same cloned chip treatment as repository and permalink chips while
preserving the conversation text rhythm.

**Problem:** Profile-backed rendered mentions sat inside an
`inline-flex` popover trigger, unlike entity chips, so the wrapper
interfered with true inline fragmentation. The browser-layout test
measured text-range rows rather than the painted chip rectangles,
allowing touching decorations to pass as “separate” fragments.

**Solution:** Keep the profile trigger interactive but override its
layout to true `inline`, then give mention fragments 18px computed
leading inside the message’s 20px prose rhythm. Chromium paints each
fragment at 17px and advances it by 20px, leaving a visible gap between
cloned rounded rectangles. The browser test now measures the chip’s own
`getClientRects()` and asserts fragment count, height, gap, and step;
entity links retain their existing 22px leading.

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

**desktop/src/features/profile/ui/UserProfilePopover.tsx**
Allows inline consumers to override the trigger wrapper’s layout without
changing other profile-popover call sites.

**desktop/src/shared/styles/globals/markdown.css**
Keeps one shared wrapping-chip mechanic and gives mention decorations
enough room to separate visibly within 20px prose.

**desktop/src/shared/ui/markdown.test.mjs**
Pins both rendered mentions and entity links to the shared wrapping-chip
contract.

**desktop/src/shared/ui/markdown/MarkdownMention.tsx**
Makes the profile-popover trigger truly inline so the nested mention
chip can fragment with surrounding prose.

**desktop/src/shared/ui/mentionChip.ts**
Keeps `wrapping-inline-chip` as the single contract for fragmenting
decorated chips.

**desktop/tests/e2e/mentions.spec.ts**
Measures the painted chip rectangles, requires a positive fragment gap,
and verifies the inline trigger remains mouse- and keyboard-operable.

**desktop/tests/e2e/navigation.spec.ts**
Keeps a wrapped repository chip as the control, asserting its existing
22px line height and fragment advance.

</details>

## Reproduction steps

1. Open a channel in Buzz Desktop using dark theme.
2. Send a message containing a human mention and another containing an
agent mention; both chips should remain aligned with adjacent text on a
20px line.
3. Render a collision-qualified mention in a narrow message width; it
should break into separately decorated fragments exactly like another
wrapping chip, while each fragment follows the 20px prose rhythm.
4. Render a long repository or permalink chip in the same constrained
width; it should retain its roomier 22px fragment spacing.

## Screenshot

The dark-theme production renderer shows the real qualified label (`bob
(npub1hv3…tpuc)`) at an 8rem width. The two lines now paint as visibly
separate rounded fragments rather than one continuous rectangle.

![Qualified human mention wrapping into two visibly separate rounded
fragments in the dark-theme Buzz
timeline](https://github.com/user-attachments/assets/ee6e7be5-937e-4022-9c82-cf71f8203470)

## Validation

At commit `2b063e1b4ade30e11f1616269ad4ba4190366885`:

- Pre-push desktop gates — file-size check, Biome/checks, typecheck, and
6,483 unit tests passed
- `pnpm --dir desktop build` — passed
- Focused Playwright coverage for single-line agent mention, single-line
human mention, wrapped qualified mention including keyboard profile
activation, and timeline mention click — 4 passed
- `git diff --check` — passed

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Rizz <rizz@agents.buzz>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
2026-09-10 22:42:10 +00:00
cynfria
cec5c8fd92 fix(desktop): let inbox title and message author names truncate under narrow panes (#7550)
## Summary

Fixes two instances of the same dead-truncate pattern in the desktop
app, where a flex item's implicit `min-width: auto` prevented `truncate`
from engaging, so long text painted over adjacent controls instead of
ellipsizing:

- **Inbox detail title** (`InboxDetailPane.tsx`): the clickable
context-title button sized to its text instead of shrinking with the
pane, overlapping the header controls (open-in-channel, members, huddle,
more menu). Fixed by adding `max-w-full`.
- **Message author names** (`MessageHeader.tsx` /
`UserProfilePopover.tsx`): the `UserProfilePopover` inline-flex trigger
wrapper refused to shrink below the name's nowrap width, running long
author names under the hover action bar and off the pane edge. Fixed by
adding a `triggerClassName` prop to `UserProfilePopover` and passing
`min-w-0 max-w-full` at the author call site.

Two other suspected instances (project file breadcrumb, drafts pane
title) were stress-tested and already truncate correctly — no change.

### Related issue

N/A — none found.

### Testing

- New Playwright regression tests for both fixes
(`inbox-title-overlap.spec.ts`, `message-author-overlap.spec.ts`,
registered in the smoke project), each proven to discriminate: they fail
with the fix reverted (real measured overlap) and assert the ellipsis
actually engages with non-zero title width, so they can't pass
vacuously.
- Typecheck, lint, and full desktop unit suite green (pre-push hooks);
full desktop e2e smoke suite run earlier: 1402 passed, 3 pre-existing
unrelated failures (each fails identically with the fix reverted).

**Inbox title — before** (long title paints under the header controls):

![Inbox title before: title text overlaps the header control
icons](https://github.com/user-attachments/assets/d5a10f98-114f-4466-8012-468616b82807)

**Inbox title — after** (truncates with ellipsis, controls stay clear):

![Inbox title after: title truncates with an ellipsis before the
controls](https://github.com/user-attachments/assets/80520f7c-1120-4dbc-929d-1ef5eceb874b)

**Author name — before** (long name runs past the header row edge):

![Author name before: name glyphs bleed past the action
bar](https://github.com/user-attachments/assets/be9743ae-ef51-4db0-8c46-a0649e98f0d5)

**Author name — after** (clean cutoff):

![Author name after: name truncates cleanly inside the header
row](https://github.com/user-attachments/assets/24cc0412-ca99-40c4-add6-13380b3ae065)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: cynfria <yescynthia@gmail.com>
Signed-off-by: Tree Trunks <6ba22921d9dc2ad0aa6ecdf63787ddd24726e266d866da31af69f2e4e146ace5@buzz.block.builderlab.xyz>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Tree Trunks <6ba22921d9dc2ad0aa6ecdf63787ddd24726e266d866da31af69f2e4e146ace5@buzz.block.builderlab.xyz>
2026-09-10 10:00:15 -07:00
Stephen DeLorme
12023a3cba fix(desktop): require a Codex adapter with Astra support (#7427)
## Summary

Buzz considers codex-acp 1.6.2 current because the supported adapter
floor is still 1.1.7. That adapter bundles Codex 0.148.0, so updating a
separate Codex CLI to 0.153.4 leaves managed agents on the older runtime
and unable to use GPT-6 Astra.

Raise the supported adapter floor to the published 1.10.0 release, which
depends on `@openai/codex ^0.153.3`. Existing discovery and installation
code then classifies older adapters as outdated and offers the managed
reinstall path. Update the availability and install-plan regressions to
cover the observed 1.6.2 installation and the new minimum.

This follows the existing version-floor policy. It does not
automatically update a running installation: the user must complete
Buzz’s offered adapter upgrade. Future upstream compatibility changes
may require another floor update.

### Related issue

No exact duplicate found in searches for Astra, CODEX_PATH, bundled
Codex, outdated runtime, and codex-acp 1.10. Related: #3097 raised the
older floor to 1.1.7 (already present on main); #2422 covers lost error
details for runtime mismatches. Neither resolves this version gap.

Originating conversation:
buzz://message?channel=3286cd76-f83e-4c7d-8317-10a16580744d&id=8b79a73078217222b870fff144c27e7d27bcd5a67c966869c18fe726db716898

### Testing

- Isolated npm install of codex-acp 1.10.0 resolved bundled Codex
0.153.4, with no CODEX_PATH override.
- Live macOS ACP probe: initialize protocol v1 → session/new → select
gpt-6-astra[medium] → prompt. Received `OK` and `stopReason: end_turn`;
usage metadata confirms gpt-6-astra.
- Existing adapter 1.6.2 initialized but advertised no Astra model in
the same probe.
- Desktop Rust formatting and `git diff --check` pass.
- `just desktop-tauri-test`: 3,266 passed, 20 ignored, zero failures
across the Desktop workspace and integration tests.
- Workspace and Desktop Clippy, frontend static checks, and `just
file-size-check` pass.
- Repository `just ci`: still running the remaining
mobile/build/workspace-test stages.

The installed Buzz app and managed adapter were not replaced or
restarted. The live check validates the new adapter/runtime path; a
complete packaged Desktop upgrade workflow remains untested.

Signed-off-by: Stephen DeLorme <stephen@d.elor.me>
2026-09-09 16:32:37 -06:00
Logan Johnson
ad9591c43b fix(desktop): order unnamed roster members by full canonical npub (#7503)
🤖

## Summary

- Channel members appear in the Members sidebar. A member who has never
set a display name is listed under an abbreviated form of their public
key (npub), and the sidebar previously sorted those unnamed members by
that short label. Short labels are not unique — different keys can share
one — so the order of unnamed members could look arbitrary or unstable.
Unnamed members now sort by their full public key, so the order is
deterministic.
- When two members display the same name, the previous tiebreak was
membership order (who joined first), which is not visible to a reader
and can shift as roster data loads in. The tiebreak is now the full
public key, so identical display names always land in the same order.
- Nothing gets noisier on screen: the full key is used only for sorting,
and the sidebar still shows the compact abbreviated form. Priorities are
unchanged — authored (custom) names still outrank fallback labels, and
role/current-user grouping still applies.
- Scope is the desktop app's Members sidebar and member management: the
two existing sort comparators. Mobile and other lists in the app are
untouched.

### Related issue

Based on #7495 (introduced the abbreviated npub labels this follows up
on). The original five presentation PRs remain independently reviewable.
No closer duplicate found.

### Testing

- 6469 desktop unit tests, typecheck, and check pass.
- The 3 existing consumer-seam E2E tests still pass; a new E2E test
asserts the sidebar lists unnamed members in full-key order, with
fixture members deliberately inserted in the opposite order so incoming
membership order cannot mask the sort.
- Negative check: reverting only this change makes the new ordering
assertion fail, so it genuinely binds the new sort.
- CI has not run on this PR yet.

Buzz provenance: channel 1f0e4a3d-7e01-4efe-bb16-843b357f85c9 / task
340c3de9b27dbedb8453c0c7652220f9080d30fcc70a7c4f6e27fdd4fa378056

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
2026-09-09 13:32:10 -07:00
Logan Johnson
2226b6f95b fix(desktop): npub identity controls across profile, agents, and workflows (#7489)
🤖
## Summary

Building on #7488's npub foundation, this PR finishes the identity
display change for the controls where you actually manage people and
keys: profile, settings, agent access, and workflows. Everywhere in
these surfaces, an identity key shows — and copies — as its canonical
npub (npub is the human-readable encoding of a Nostr public key: the
compact `npub1j57...fjmv` form where space is tight, the full npub where
the whole key matters), and accepts npub as input.

After this change:

- Profile panel: the public-key row and the managed-by / declared-owner
copies show the full npub. If a key can't be encoded, you see
"Unavailable" with no copy button — never a raw or partial key.
- Settings: the identity card shows and copies the npub. The
hosted-communities account identity derives from the bound key
(`pubkey_hex`) — the same authority as the mismatch gate and hosted
operations — so the display can never disagree with what the app acts
on; an unusable hex falls back to a neutral label instead of rendering
the unverified server npub. The connected claim and a community's
Connect action require that same usable bound key to match the local one
— with no usable binding the card cannot claim connected or start
Connect, while the community list, linking, and delete/rebind recovery
stay available.
- Hosted create/onboarding: the account and device identity rows in the
create flow and owner onboarding derive from the same authoritative
fields (bound key / local key), with the same neutral fallback;
readiness requires a usable bound key that matches the local one.
- Respond-to allowlist (controls who may respond to an agent): entries
can be typed or pasted as hex or npub; both spellings of the same key
are recognized as one entry and dedupe. Search results, chips, and
remove buttons use the compact npub.
- Workflow key fields: to/from keys display as npubs in the form and
save back as canonical hex. Templates like `{{trigger.author}}`, roles,
and free text pass through untouched; placeholders accept both
spellings.
- Recipient and agent dialogs: the verify popover is npub-only (the
raw-hex line is gone); denied-membership screens never show a raw key.
- The Rust-side truncated display name (used for native surfaces) shows
the same compact npub, so those surfaces match the web UI.

Internal representation is unchanged: keys are still stored, sent, and
matched as canonical 64-character hex — npub is a display and input
spelling, normalized to hex at the boundary, so existing data and
integrations keep working. Bound-key usability and comparison use one
normalized form (trimmed, lowercased, 64 hex characters; npub rejected),
so padded or mixed-case spellings of the same key match. Display names,
private keys, and event IDs are untouched.

## Details

- `respondToAllowlist` / `RespondToField`: npub entries normalize to
canonical hex; cross-form dedupe; compact npub in rows and chips;
direct-add accepts npub and stores canonical hex.
- `workflowFormTypes` / `WorkflowStepCard`: hex → npub for display, npub
→ canonical hex on save; templates, roles, and free text pass through in
both directions (roundtrip-tested).
- `UserProfilePanelFields`, `ProfileSettingsCard`,
`HostedCommunitiesSettingsCard`, `MembershipDenied`,
`SelectedRecipientChip`, `AddAgentToChannelDialog`: npub display and
copy; invalid keys → "Unavailable" with no copy; hosted identity rows
derive from the bound `pubkey_hex` (create/onboarding rows from the
bound and local keys), never the unverified server npub;
connected/readiness/Connect gates use the same usable-bound-key
predicate, and the settings Connect invocation callback re-checks it
before starting.
- `src-tauri/src/commands/identity.rs`: `truncated_display_name`
compacts to the first 8 + last 4 characters of the npub (above a 12-char
threshold), mirroring `truncateNpub`.
- e2e: profile key rows and clipboard polls assert npub forms and
raw-hex suppression; the display-screenshots spec pins the npub-only
popover; hosted specs drive the real settings card, create flow, and
onboarding rows through their real providers, and the unlinked/npub-only
identity cases assert no connected claim and no Connect action.

### Related issue

- Fixes: N/A. No separate issue; the related work is the stack below.
- Stack: builds on #7488 (shared npub foundation), now merged; this PR
is rebased onto main and stands on its own.

### Testing

At head `303c90ffa` (base: main `bfc38485`; 24 files, +1125/−146):

- Focused unit suites (respondToAllowlist, workflowFormTypes,
hostedCommunityApi bound-key helpers) green; mutation-checked — dropping
allowlist canonicalization fails the dedupe case, and dropping bound-key
normalization fails the npub-in-hex and padded same-key cases.
- Full desktop unit suite 6,477/6,477, `desktop-typecheck`,
`desktop-check` (formatting fixed narrowly with `biome check --write` on
the touched files only), and a fresh E2E build at the current head; the
add-community + hosted-communities-settings specs 18/18 and onboarding
integration 69/69 on a fresh dedicated port, with focused new-case runs
4+4 covering padded same-key (ready, Connect kept — no false rebind) and
npub-in-hex (neutral label, recovery, no Connect) across the settings
card, create flow, and first-community onboarding, plus the
unlinked-account settings regression asserting Connect cannot occur.
- `cargo fmt`/clippy (both feature sets) and `cargo test identity` (71
pass) passed at the earlier full-change head; since then, the only
production changes in this PR's delta are the hosted identity display
authority and its fail-closed bound-key gating/normalization above
(base-side fixes carry #7488's receipts) — every other change is
test-only.

### Task provenance

Buzz channel: `1f0e4a3d-7e01-4efe-bb16-843b357f85c9`

Task:
buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
2026-09-09 14:08:01 -04:00
Logan Johnson
ad2a84131e fix(desktop): npub identity displays for mention, member, and workflow surfaces (#7495)
🤖
## Summary

Every Buzz account is identified by a long public key. Before this
change, when someone had no display name, surfaces fell back to
inconsistent labels — mostly raw hex fragments like `abcd1234…wxyz`,
sometimes a generic role label with no key — so the same person looked
different from surface to surface, and nothing looked like an npub
address. This PR applies the npub identity foundation from #7488 to the
everyday surfaces: a person without a display name now falls back to the
same compact npub everywhere — `npub1xxxx…yyyy`, the human-readable
spelling of their public key (first 8 + last 4 characters of the full
npub) — across messages and mentions, reactions, huddles, member and
participant lists, the sidebar and channel activity, search, projects,
tray, notifications, and workflow surfaces.

- **Mentions and messages**: key-only mention chips render the compact
npub. Pasting a copied mention back still re-binds it byte-exactly to
the identity it declares, for both the new npub chips and legacy
hex-truncated chips copied by older clients — wrong, missing, or
tampered key qualification is rejected instead of silently degrading to
plain text.
- **Reactions and huddles**: huddle reaction events and the huddle
roster/participants render the compact npub for unnamed participants;
workflow reaction triggers describe authors with the same form.
- **Members and sidebar**: channel and community member lists,
add-member results and invites, the members sidebar, the
channel-activity popover, search, projects (assignees/reviewers/PR
panels), the tray menu, and desktop notifications all fall back to the
compact npub; titles and aria labels keep the machine-readable full
labels.
- **Profile labels**: panel/popover display names and owner handles fall
back to the compact npub (never raw hex) when there is no name;
linked-event (nevent) message metadata shows the npub-shaped author
fallback while the event lookup and event IDs are unchanged.
- **Workflows**: author-picker secondary labels, step destination keys,
and trigger-author references render compact npubs; event and blob IDs
keep their existing hex compacts (they are not identities).
- **Avatars stay distinct**: fallback avatars for key-only identities
derive initials from the key's tail, so prefixed role labels like
"Participant npub1…" no longer collapse every unnamed participant onto
the same initials; people with names keep their name initials.

Preserved exactly: display names and distinct avatars, internal hex keys
(storage/API forms unchanged), clipboard identity roundtrips, event/blob
ID compaction, private keys (no nsec path is touched), and nevent link
handling.

Scope: this PR changes what identity labels **display**, not identity
controls — profile/settings copy controls, the respond-to allowlist,
workflow key fields, and agent dialogs are the sibling slice #7489, and
the shared primitives (`canonicalNpub`, `truncateNpub`, the `<PubKey>`
gate, strict input parsing) come from the foundation #7488.

### Related issue

- Fixes: N/A. Searched existing issues/PRs for duplicates — none found;
the related work is the npub identity stack this slice belongs to.
- Base/dependency: stacks on #7488 (foundation) — this PR does not stand
alone on main.
- #7489 is a sibling slice on the same #7488 base
(profile/agent/workflow controls), not a dependency: this PR does not
require #7489, and #7489 does not require this PR — both only require
#7488.

### Testing

At exact head `4763cbeae1dd521309755e6d61f657324cb98667` (base:
`fix/desktop-npub-identity-d1a` @
`5f3a4a8111998c8aa41ad77cf66992bd1c85343c`; 71 files, +656/−189 —
production +277/−136, test support +379/−53):

- At this head: targeted `mentions.spec.ts` (1/1), the e2e build,
typecheck, and biome — green.
- 9 changed/related unit files: 100/100 green; typecheck, e2e build,
biome, and px text/truncation checks clean; huddle-roster focused run
green; channel-activity e2e 11/11; mutation checks confirm the fallback
wiring (removing it collapses shared initials and drops fallback rows).
- Known pre-existing local e2e failures, unchanged by this PR and
reproduced identically at the upstream merge-base: huddle-transcription
voice-menu attribution (25 pass / 1 fail) and the
`workflow-local-controls` 438px caret drift. Not claimed green locally.
- Update at head `236af9e6137386737e84d3a474d6bc808a704c50` (test-only
follow-ups `1143af345` + `236af9e6`): the `workflow-local-controls`
races were fixed in the test drivers, and the 438px diff was shown to be
a stale Darwin snapshot baseline (name-row enable switch already absent
and `message_posted` already MessageSquare at recording commit
`9390e11c9`) and refreshed — the focused screenshot test, including
keyboard/caret assertions, now passes locally (twice). The full spec was
not rerun after the snapshot refresh; the huddle-transcription item
above is unchanged.

Label/copy text changes are asserted by the e2e specs (`mentions`,
`mention-recipients`, `pubkey-display-screenshots`,
`huddle-transcription`, `channel-activity-popover`,
`workflow-local-controls`) rather than new screenshots; the screenshot
spec pins the compact npub text forms.

### Task provenance

Buzz channel: `1f0e4a3d-7e01-4efe-bb16-843b357f85c9`

Task:
buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
2026-09-09 14:07:24 -04:00
Logan Johnson
bfc3848558 fix(desktop): shared npub identity foundation (canonicalNpub, PubKey gate, strict parser) (#7488)
🤖
## Summary

Identity keys in the desktop app are displayed as raw 64-character hex.
A person's key shows up as something like `953d3363…` — unreadable,
impossible to recognize as the same identity on another screen, and a
hazard when copied by hand. Nostr (the protocol Buzz runs on) has a
human-readable spelling for identity keys — the `npub1…` form — but the
desktop app did not use it consistently.

This is the foundation of the desktop npub changes: it adds the shared
pieces every identity surface builds on, and two follow-up slices stack
directly on this branch — #7489 converts the identity controls (profile,
settings, allowlist, workflow key fields) and #7495 converts the
everyday display surfaces (mentions, member lists, sidebar, and other
name fallbacks).

After this change:

- The shared identity widget shows the compact npub form —
`npub1j57...fjmv` — instead of a hex prefix, everywhere it renders (for
example the owned-agent public-key row on a profile). Copying it puts
the full npub on the clipboard.
- Copy is a real interaction, verified end-to-end: both popover variants
put the exact canonical npub on the actual clipboard — never the raw hex
the popover also lists, never a truncation — and a portaled popover's
clicks no longer steal focus from the new-DM To-field mid-copy. Pointer
copy, a natural Space-then-Enter path, and inner/outer Escape are
covered.
- Anything that isn't a valid identity key fails neutrally: short or
corrupt values — including degenerate values that technically encode to
a checksum-valid npub but aren't real identity keys — show "Unavailable"
with no copy button, instead of a misleading value.
- Both valid npub spellings display: all-lowercase `npub1…` and
all-uppercase `NPUB1…` (Bech32, npub's encoding, permits either casing)
both render the same canonical lowercase npub. Mixed case is rejected by
the display path as written — `canonicalNpub` and the widget don't
case-normalize input — while input parsing (`parsePubkeyInput`) keeps
its trim-and-lowercase normalization and accepts mixed-case npubs; both
paths require the decoded payload to be exactly a 64-character identity
key.
- Identity-key input is strict on payload: an npub whose decoded payload
isn't exactly a 64-character identity key is rejected, matching the
validation the app's Rust side already applies to agent allowlists.

Intentional scope boundary: only surfaces that render through the shared
widget change here. Outer profile copy, settings identity cards, the
respond-to allowlist, and workflow key fields still show hex — they move
to npub in the controls follow-up (#7489). Nothing else changes identity
representation: display names, private keys, event IDs, and the hex the
app stores, sends, and matches internally are untouched; only the
user-facing spelling of an identity key changes.

## Details

- `desktop/src/shared/lib/pubkey.ts` — `canonicalNpub()`: strict
canonical full-npub helper (64-char hex in any case, or a
checksum-validated npub, returns the canonical npub; anything else
returns `null`); `truncateNpub()`: the compact display form; existing
exports unchanged.
- `desktop/src/shared/ui/PubKey.tsx` — the shared widget's identity gate
validates through `canonicalNpub`; the popover copies the npub only.
- `desktop/src/shared/lib/nostrUtils.ts` — `parsePubkeyInput` rejects
npubs whose payload is not exactly a 64-character identity key.
- `desktop/src/features/messages/ui/NewMessageScreen.tsx` — the To-field
focuses its search input only for clicks that land inside the field
itself, so portaled recipient popovers keep their focus while open (a
popover click previously dismissed it mid-copy).
- Unit suites cover the helper, widget, and parser (including the
degenerate-encode and uppercase regressions); the e2e specs that render
these rows assert the npub display.

### Related issue

- Fixes: N/A. Searched existing issues/PRs for npub identity display —
no existing match.
- Stack: #7489 is based on this branch and builds on these primitives;
it does not stand alone on main.

### Testing

At head `b3310c248` (base: main `44316ff72`; 12 files, +440/−39):

- Focused unit suites (pubkey, PubKey, parsePubkeyInput): 20/20 green;
mutation-checked — removing the decoded-length predicate fails the
short/empty checksum-valid-npub assertions in `canonicalNpub` and the
widget, and a wrong-identity clipboard value fails the new copy
assertions.
- `pnpm typecheck` and `pnpm check`: pass; full desktop unit suite
6459/6459 at this exact head.
- Targeted e2e at this exact head: 8/8 across the two specs that own the
clipboard flows — `agent-access-warning.spec.ts` (compact variant,
agent-access owner hint) and `pubkey-display-screenshots.spec.ts` (full
variant, new-DM recipient verification: pointer copy, popover surviving
the copy, inner/outer Escape, Space-then-Enter).
- No Rust-side or build files change in this PR, so those results are
unaffected.

### Task provenance

Buzz channel: `1f0e4a3d-7e01-4efe-bb16-843b357f85c9`

Task:
buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
2026-09-09 10:32:26 -04:00
Taylor Ho
218633b8fd fix(link-preview): keep composer fetches user-paced (#7211)
**Category:** fix
**User Impact:** Link previews can keep loading while a message is being
composed, while sending still has a finite escape hatch and stalled
network transports cannot occupy preview slots forever.

**Problem:** Native metadata and image deadlines could collapse slow
previews into fallback cards while the user was still composing, and a
shared image-host cooldown made pasted batches fail inconsistently after
one rate limit. **Solution:** Keep preview resolution user-paced with no
aggregate request deadline, bound transport inactivity (15s DNS/connect,
30s idle read), serialize image requests by host, and allow at most one
server-directed cooldown wait of up to 30s across an image fetch and its
redirects. The existing bounded post-Send preparation and immediate Skip
paths remain unchanged.

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

**desktop/src-tauri/src/commands/link_preview.rs**
Removes aggregate native deadlines so composer metadata work can
complete at the user's pace, while retaining DNS/connect/idle-read
liveness bounds. Adds bounded host-paced image request coordination that
releases its gate during cooldown, waits inline at most once for at most
30 seconds, and cannot renew that wait through redirects or the outer
transient retry. Same-host image and favicon requests remain
deliberately serialized to align with host rate limits.

**desktop/src-tauri/src/commands/link_preview_rate_limit.rs**
Adds a fixed-size striped host gate so concurrent image requests are
serialized without retaining an unbounded attacker-controlled hostname
map.

**desktop/src-tauri/src/commands/link_preview_tests.rs**
Moves native link-preview tests into a dedicated module and covers the
user-paced metadata contract, bounded one-shot cooldown behavior, and
gate release while a rate-limited request sleeps—including a different
host sharing the same bounded gate stripe.

**desktop/src-tauri/src/commands/link_preview_youtube.rs**
Removes the thumbnail fetch deadline so YouTube previews follow the same
composer lifecycle contract while using the shared bounded transport.

**desktop/src/shared/lib/useResolvedLinkPreviews.ts**
Adds development-only metadata outcome diagnostics with elapsed time and
image/fallback state, without logging encoded image payloads.

</details>

### Reproduction steps

1. Open the desktop composer and paste several GitHub pull request links
whose OpenGraph images share a host.
2. Observe that image requests are paced by host instead of racing, and
slow-but-progressing preview work remains pending rather than
immediately becoming a completed favicon fallback.
3. Send while preview work is still pending and confirm **Preparing link
preview** remains bounded by the existing post-Send budget.
4. Use **Skip** during preparation and confirm the message proceeds
immediately.
5. In a development build, inspect the console for `[link-preview]
metadata fetch completed` diagnostics containing elapsed time and image
state without base64 payloads.

### Related issue

N/A — scoped from the linked Buzz implementation room.

### Testing

At current head `dfb394aafbee537e9ffb04ad3732d08f65f30b8e`:

- Production-bound paused-time metadata regression passed through
`fetch_link_preview_metadata`; restoring the former 10-second aggregate
wrapper makes it fail at the pending assertion.
- Native link-preview module: 19/19 passed.
- `cargo check --manifest-path desktop/src-tauri/Cargo.toml` passed.
- Rust formatting and `git diff --check` passed.
- Pre-push `push-head-scope`, org safety, differential file-size,
branch-skew, and `desktop-tauri-checks` hooks passed.

At prior head `59e2dcf167b15c7a3e637ad2608008b7f9cef5f3`:

- Full Tauri Rust suite: 3,056 passed, 19 ignored; integration crates 7
+ 3 passed.
- Focused native link-preview suite: 26/26 passed.
- The pasted multi-preview workflow was exercised in the desktop app and
confirmed improved before draft publication.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@users.noreply.github.com>
2026-09-08 12:59:03 -07:00
Mic Neale
44316ff72f feat(mesh): upgrade to mesh-llm 0.76.0-rc8 and recommend Qwen3.8 27B (#6189)
Upgrades Buzz's mesh-llm dependencies through the released `v0.76.0-rc8`
tag (`2040765d`), including the Qwen3.8 curated recommendation and rc8's
scheduler/runtime improvements.

**Scope note:** the earlier open-relay/unenforced-admission mode has
been removed from this PR at Mic's direction — it is not a product mode
we want. Mesh admission remains roster/allowlist driven, exactly as on
`main`: on a relay with no NIP-43 membership snapshot the mesh runs
self-only. No NIP-11 mode probing, no mode-transition restarts. A future
perimeter/admission strategy for open relays will be designed
separately.

This PR also:
- seeds `BUZZ_AGENT_LLM_TIMEOUT_SECS=660` for mesh agents, above
MeshLLM's 600-second backend timeout;
- makes `desktop-tauri-clippy` lint both default and `mesh-llm` cfg
graphs;
- runs the feature-enabled desktop test suite in CI;
- recommends Qwen3.8 27B Q4_K_M for 64 GB-and-larger machines, then
ladders down through Gemma 4 E4B and Qwen 9B for smaller machines;
- keeps stored shared-compute `auto` translated to MeshLLM's supported
wire model `mesh`.

RC8 verification:
- `just ci` passed locally at
`92ecc7ec933bdd4df804cc9f28a2b51efa5313c5`.
- Pre-push differential gates passed, including both desktop Tauri cfg
graphs and package tests.
- A prior isolated runtime smoke used the RC8 binary's OpenAI endpoint
for a Buzz-shaped system/user/tool/tool-result/final-response loop; all
assertions passed and the isolated process was shut down.

Perf previously measured on M5 Metal, Qwen3.8-27B-Q4_K_M: TTFT 0.22–0.32
s, ~25 tok/s streaming; agent-shaped turns ~1 s to first token after the
first (prefix cache).

---------

Signed-off-by: Michael Neale <michael.neale@gmail.com>
Signed-off-by: Jimmy <1fe240cd1a8cf775f6f3060f115e5a303181f3abf28ad4cb0c2515f4a02b36a8@meshllm.communities.buzz.xyz>
Signed-off-by: Alessandro Joabar <sandro@squareup.com>
Co-authored-by: Michael Neale <michael.neale@gmail.com>
Co-authored-by: Jimmy <1fe240cd1a8cf775f6f3060f115e5a303181f3abf28ad4cb0c2515f4a02b36a8@meshllm.communities.buzz.xyz>
Co-authored-by: Mic Neale <305999590+micspiral@users.noreply.github.com>
Co-authored-by: Alessandro Joabar <sandro@squareup.com>
2026-09-08 07:30:45 -07:00
Bradley Axen
cd54e26823 fix(agent): route GPT-5+ model-service FQNs to Responses (#7358)
## Summary
Route Databricks Unity Catalog model services to OpenAI Responses when
their service name matches GPT-5 or newer. These models can reject tools
plus reasoning on Chat Completions.

Match only the service component, using the existing family-token
boundaries and a numeric major version. Catalog and schema names cannot
select the protocol. Keep neutral effort capabilities and the full model
ID unchanged; other services still use MLflow Chat Completions.

Keep the Rust and desktop resolvers in sync, add shared boundary cases
and a captured-HTTP regression for completion and summarization, and
update the documented FQN rule.

### Related issue
No duplicate found in searches for “FQN responses” PRs or “astra”
issues. Related: #6918 introduced Unity Catalog discovery.

Originating conversation:
buzz://message?channel=0b881928-a3a6-4c01-b981-8e64268f01ce&id=770949343bc96a9ed88acd90a1b37d358a0efc52c79237d0fdb491ce02b8d4ed

### Testing
No live Databricks inference test. The gateway must accept the full
model-service ID on its OpenAI Responses route; this remains the
integration risk.

The local `just ci` attempt exceeded its five-minute deadline during
`mobile-check`, so the full repository gate was not completed. All
push-hook checks passed.

Generated with Codex

Signed-off-by: Fizz <400e8babadcee6a7f420103f10a2849d84c4a9c71d5bd04f3948c814216648a3@buzz.block.builderlab.xyz>
Co-authored-by: Fizz <400e8babadcee6a7f420103f10a2849d84c4a9c71d5bd04f3948c814216648a3@buzz.block.builderlab.xyz>
2026-09-08 08:23:48 -06:00
Wes
3c7f288c60 chore(release): release Buzz Desktop version 0.5.23 (#7381)
## Buzz Desktop release v0.5.23

- **Frozen main:** `dad5a33865fc81a2e55b3b60746632f615ec1e3a`
- **Reviewed candidate:** `b9392d9d78744df365f9276e1ffe8c1baa5ea903`
- **Previous desktop release:** `desktop-v0.5.22`
- **Proposed immutable tag:** `desktop-v0.5.23`

This PR may be **squash merged** after the Desktop Release Candidate
check and all protected-branch checks pass. Merging authorizes
publication of the exact reviewed candidate; later or unrelated changes
on `main` cannot alter it.

The checked-in changelog accounts for every non-merge commit in the
release range. The Desktop tag points to the reviewed candidate commit,
not the later squash commit. Publication remains bound to that immutable
candidate tag.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
2026-09-05 18:06:14 +00:00
Jeff Hedlund
dad5a33865 fix(desktop): keep packaged frontendDist relative so Windows embeds assets (#7177)
## Root cause

`tauri-command.mjs` points `frontendDist` at a `mkdtemp` directory so
concurrent OSS/internal packages cannot overwrite each other's assets.
On Windows that is an absolute path with a drive letter.

`FrontendDist` is an untagged serde enum whose **first** variant is
`Url(Url)`, and `C:\Users\...` is a valid WHATWG URL with scheme `c:`,
so serde selects `Url`. `tauri-codegen` then does:

    FrontendDist::Url(_url) => Default::default(),   // embed nothing

A missing *directory* panics with a clear message; a URL is silent. The
build exits 0 and produces an installable app with no frontend assets,
which boots to `ERR_FILE_NOT_FOUND` in the WebView.

Linux and macOS are unaffected — `/tmp/...` has no scheme, so it falls
through to `Directory`.

This affects every Windows build that goes through `pnpm tauri build`,
including `release.yml`'s NSIS job and `windows-canary.yml`.

## Fix

Pass the path relative to the config's own directory. `tauri-codegen`
resolves `frontendDist` with `config_parent.join(path)`, so a relative
path reaches the same directory and cannot parse as a URL. When the temp
directory is on another drive there is no relative form, so the scratch
root is created beside the config instead.

`BUZZ_PROTECTED_BUILD_OUTPUT` still receives the absolute path, and
cleanup is unchanged.

## Testing

`tauriCommand.test.mjs` asserted against the value it had just been
handed, so it could not observe this. Its fake CLI also resolved
`frontendDist` against the process cwd, which is not what Tauri does.

- Fake CLI now resolves against the config directory, matching
`config_parent.join(path)`.
- New case asserts the packaged `frontendDist` is not absolute and does
not parse as a URL. The absolute check is what fails on Linux/macOS, so
the regression stays covered on every platform.
- Verified the new case fails on the unpatched wrapper and passes with
the fix; the two existing cases pass either way.
- Desktop suite: 5844 passed. `useDocumentVisible` has a pre-existing
load-dependent flake that also reproduces on an unmodified checkout.
- Biome check clean on both files.

Verified end to end by rebuilding the Windows NSIS installer: embedded
asset keys in `buzz-desktop.exe` went from 0 to 490, and the app
launches.

---------

Signed-off-by: Jeff Hedlund <jhedlund@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 09:04:06 -06:00
Taylor Ho
f038cbbb0d fix(sidebar): simplify unread indicators and emphasize priority activity (#7134)
**Category:** fix
**User Impact:** The sidebar now presents unread activity consistently:
unread rooms are bold, offscreen activity is counted by destination, and
DMs or directed activity receive stronger emphasis.

**Problem:** Sidebar unread state was split across competing signals:
agent work could trigger overflow, message totals inflated its count,
non-DM rows showed redundant numerals, and routine room activity looked
as urgent as DMs or directed messages. This made the sidebar noisy and
made the overflow value harder to interpret.

**Solution:** Use one stable offscreen unread control that counts rooms
and DMs—not messages—and remove agent work from that signal. Keep
ordinary room activity quiet; promote DMs, mentions, broadcasts, and
relevant thread replies; preserve DM avatars and targeting; bold every
unread room; and retain thread dots while removing non-DM numerals.

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

**desktop/src-tauri/src/unread_catch_up.rs**
Classifies relevant thread replies as priority activity during native
unread catch-up so startup state matches live rendering.

**desktop/src/app/AppShell.tsx**
Passes the priority unread destination set into the sidebar.

**desktop/src/features/channels/useUnreadChannels.ts**
Projects unread destinations, priority state, and thread replies
consistently while keeping Dock badge behavior separate from sidebar
emphasis.


**desktop/src/features/sidebar/lib/useOffscreenActivityChannelIds.test.mjs**
Removes tests for the superseded agent/activity overflow projection.

**desktop/src/features/sidebar/lib/useOffscreenActivityChannelIds.ts**
Removes the old agent-plus-message overflow projection so agent work
alone no longer creates the unread indicator.

**desktop/src/features/sidebar/lib/useSidebarActivityOverflow.ts**
Replaces activity-volume overflow state with unread-destination overflow
state.

**desktop/src/features/sidebar/lib/useSidebarUnreadOverflow.test.mjs**
Covers priority detection and destination-count labels for the new
overflow projection.

**desktop/src/features/sidebar/lib/useSidebarUnreadOverflow.ts**
Counts distinct offscreen unread destinations and determines quiet
versus primary treatment per direction.

**desktop/src/features/sidebar/ui/AppSidebar.tsx**
Renders one stable overflow control, preserves protected-DM visibility
filtering, and prioritizes visible unread DMs for previews and
navigation.

**desktop/src/features/sidebar/ui/AppSidebar.types.ts**
Adds the priority unread destination set to the sidebar contract.

**desktop/src/features/sidebar/ui/CustomChannelSection.tsx**
Stops forwarding non-DM unread counts into custom channel rows.

**desktop/src/features/sidebar/ui/MoreUnreadButton.test.mjs**
Updates control coverage for explicit emphasis, destination labels, and
DM targeting.

**desktop/src/features/sidebar/ui/MoreUnreadButton.tsx**
Applies quiet or primary treatment without changing geometry,
accessibility text, DM avatars, or click behavior.

**desktop/src/features/sidebar/ui/SidebarSection.tsx**
Removes non-DM row numerals while retaining unread weight and thread
preview affordances.

**desktop/src/shared/ui/UnreadPill.tsx**
Shares one composition between quiet and primary states so only color
treatment changes.

**desktop/tests/e2e/badge.spec.ts**
Covers destination counting, promotion without count inflation,
DM/thread priority, row bolding, and removed numerals; also captures the
reviewed UI states.

**desktop/tests/e2e/channel-activity-popover.spec.ts**
Updates channel activity assertions for the numeral-free row treatment.

**desktop/tests/e2e/channels.spec.ts**
Updates channel unread expectations to use bold text rather than a row
count.

**desktop/tests/e2e/thread-unread.spec.ts**
Keeps thread unread-dot and popover coverage while asserting the room
itself is bold.

</details>

## Reproduction steps

1. Open a workspace with enough sidebar destinations to scroll rooms
above or below the viewport.
2. Receive ordinary unread activity in an offscreen room; verify one
quiet `N unread` indicator appears and counts the room once regardless
of message volume.
3. Receive a mention, broadcast, or relevant thread reply in an
offscreen room; verify the same count becomes primary without changing
its geometry or adding a badge.
4. Receive an unread DM, including thread-only activity; verify the
indicator is primary, shows the DM avatar when available, and navigates
to that DM.
5. Scroll the destination onscreen or mark it read; verify the count and
treatment update from the remaining offscreen destinations.
6. Inspect unread non-DM rows; verify their names are bold, numeric
badges are absent, and thread dots still open the unread-thread preview.

## Screenshots

### Ordinary unread room
Routine offscreen room activity uses the quiet treatment.

![Quiet unread overflow
indicator](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/7134/sidebar-unread-overflow-default.png)

### Directed unread activity
The same destination count becomes primary when an offscreen room has
directed activity; geometry and label remain unchanged.

![Primary unread overflow
indicator](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/7134/sidebar-unread-overflow-primary.png)

### Thread-only unread DM
A DM stays primary and retains its avatar even when only its thread has
unread activity.

![Primary DM thread overflow
indicator](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/7134/sidebar-dm-thread-overflow-primary.png)

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
2026-09-04 23:03:41 +00:00
Salman Mohammed
4beffef697 feat(buzz-acp): update base prompt; add buzz context and skills to Pi agents (#7335)
## Summary

- launch Pi through a private Buzz wrapper so managed Pi agents receive
Buzz's base prompt plus the configured thread/channel session model via
`--system-prompt`
- load the Buzz workspace skill directory with Pi's repeatable `--skill`
flag, making `~/.buzz/.agents/skills/buzz-cli` available alongside Pi's
normal global skill discovery
- report Pi and `pi-acp` installation states separately so setup
guidance points to the missing component

`pi-acp` does not currently consume the ACP `session/new` system prompt,
but its normal new-session and restore paths do honor
`PI_ACP_PI_COMMAND` as the executable used to launch Pi. Buzz reserves
that variable and sets it to a private generated launcher. The launcher
invokes `pi` from Buzz's effective `PATH`, adds `--system-prompt <file>`
and `--skill <workspace>/.agents/skills`, and forwards `pi-acp`'s RPC
and session arguments unchanged. This keeps the integration entirely in
`block/buzz`, without changes to Pi or `pi-acp`.

### How PI_ACP_PI_COMMAND works in `pi-acp`

`PI_ACP_PI_COMMAND` selects one executable; it does not accept
arguments. Therefore:

```bash
# Does not work
PI_ACP_PI_COMMAND="pi --skill ~/.buzz/.agents/skills"
```

Buzz does not expose this variable as user configuration. It creates a
private launcher and sets `PI_ACP_PI_COMMAND` only on the `pi-acp`
child. Conceptually, that launcher executes:

```sh
#!/bin/sh
exec pi \
  --system-prompt "$PRIVATE_SYSTEM_PROMPT" \
  --skill "$HOME/.buzz/.agents/skills" \
  "$@"
```

### Related issue

Follow-up to #7208. No duplicate issue or open PR found.

### Testing

- `just ci`
- `cargo test -p buzz-acp pi_launcher`
- `cargo test --manifest-path desktop/src-tauri/Cargo.toml
managed_agents::env_vars::tests`
- `cargo test --manifest-path desktop/src-tauri/Cargo.toml
managed_agents::discovery::presets::tests`
- pre-push branch checks

No screenshots: the UI change is state-dependent setup guidance only.

---
**Update Sep 4, 13:35:** Made `PI_ACP_PI_COMMAND` entirely Buzz-owned.
- Managed agent configuration now rejects the variable as a user
override.
- Buzz always launches `pi` from its effective `PATH` and rejects
inherited values before creating its private launcher.
- Removed the custom Pi executable discovery path and its internal
environment alias.

---
**Update Sep 4, 14:03:** Reject inherited `PI_ACP_PI_COMMAND` values
instead of replacing them.
- Pi startup now fails with an actionable message telling the user to
unset the variable.
- Removed the Pi-specific exception from the generic ACP environment
injection path.

Generated with Codex

---------

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
2026-09-04 21:17:00 +00:00
Logan Johnson
4afef8649a fix(desktop): restore mention chip identity icons (#7338)
## Summary

Restore the missing **@ glyph for people and robot icon for agents**
after #7133, and incorporate the requested compact public-key display.

- Wrapping mention chips render their existing bounded icon-bearing
leading fragment.
- Readonly chips show bound keys using the same `8 leading…4 trailing`
formatter as the channel member list: `Scout (150b20bd…15dc)`.
- Full literal labels and exact keys remain authoritative in metadata,
profile targets, title/accessible-name attributes, editor text, saved
bodies and recipient tags. Display abbreviations are never used for
recipient lookup.
- Copy/paste restores the full literal label for a complete compact
chip; partial selections remain plain text. Two keys sharing the same
abbreviation still round-trip to their separate exact recipients.
- No recipient-resolution, authorization, wire-format, composer, or CSS
changes. Existing labels, icons, cloud markers and ordinary mentions
remain intact.

## Verification

Published candidate `9365ab9bc9d9c5d10802580cd576f8e378ff492f`, based on
main `e09f715c9d0ee2cb7bf8a39061e601f3a502f588`:

- **6,444 desktop unit tests passed** on this candidate's final source
tree.
- **44 mock-Chromium tests passed, zero retries in the final run**
across mention recipients, clipboard and cloud provenance: exact
recipient selection, ambiguity rejection, send/edit/reopen, forwarding,
full/partial copy, mismatched-key rejection, matching-abbreviation
collisions and 100%/150% narrow-window geometry.
- TypeScript, desktop Biome/check guards, protected-feature production
build and E2E build passed.
- The compact-renderer regression fails with the formatting call
removed. Original missing-icon and hidden-text accessibility regressions
have red/green evidence.
- Fresh self-review traced rendering, full-key metadata, copy
classifier, paste normalization and identity trust. The same display
formatter owns the accepted compact form on both clipboard sides.

Iteration exposed an existing team-insertion separator flake (passed
final full run) and two new fixture assumptions: non-member sends
require invitation, and Chromium rich paste may retain an NBSP
separator. Tests now exercise invitation and normalize only that
separator when comparing the captured full body and exact tags; no
product change was needed for either.

Earlier local repository-wide `just ci` completed in two invocations
because its initial call hit the ten-minute tool limit during Tauri
compilation. Unchanged native/mobile/backend evidence is reused; the
desktop delta received the full checks above and new remote CI. Native
VoiceOver, real Tauri selection, dark theme and non-Chromium observation
were not performed. Browser artifacts exercise real frontend with mocked
Tauri/relay boundaries, not an installed release.

## Review and visual evidence

Current-head CI and automated review must complete after this update;
the old `2997bfb5` green results do not establish this new head.
Required human review remains separate from agent approvals. No
merge/install/restart authorization.

Before/after icon evidence:
https://github.com/block/buzz/pull/7338#issuecomment-5544575866

Updated compact-key screenshots are posted below. The editor
intentionally retains the full literal address; only readonly chip
display is abbreviated.

Origin:
buzz://message?channel=3355d33a-b72a-423a-b064-a58275f9a8af&id=38b3a27e689f5a9604e273d45f4e3122fceba76081e7bcd3bbdbf439524a5a18

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
2026-09-04 13:36:17 -07:00
morgmart
5d107836c6 Persist video playback speed preference (#7336)
## Summary

- persist the selected desktop video playback speed as a device-level
preference
- apply the shared preference to inline and review video players,
including after a reload
- cover persistence, validation, cross-window updates, and the
end-to-end playback flow

## Verification

- `node --test
desktop/src/shared/lib/videoPlaybackSpeedPreference.test.mjs`
- `cd desktop && pnpm exec tsc --noEmit`
- `cd desktop && pnpm check:px-text`
- `cd desktop && pnpm test`
- `cd desktop && pnpm build:e2e && pnpm exec playwright test
--project=smoke video-attachment.spec.ts`
- pre-push: desktop check, file-size check, typecheck, and 6,416 desktop
tests

---------

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
2026-09-04 13:10:17 -07:00
Logan Johnson
e7e29937a1 feat(desktop): invite owned agents from standalone forums (#7125)
## Summary

In Desktop's standalone Forums, selecting an owned agent from another
device could leave a post or reply unsendable if the agent had not
joined the forum. This adds **Invite / Cancel** to the send flow so you
can resolve membership without leaving your draft.

- **Invite** checks response policy and your permission to add members,
adds the agent to the forum, waits for refreshed membership, then
rechecks authorization before posting to the original destination.
Membership is forum-wide, not limited to one post.
- **Cancel / Escape** keeps the text, attachments and selected
recipients for retry. Unlike chat's **Do nothing / Send anyway**, this
dialog has no reference-only send choice. Invite is disabled while
pending; Cancel remains available.
- Leaving the source post/reply cancels its pending invitation, even if
you return. Errors remain visible, focus returns to the initiating
editor when appropriate, and late completion cannot resume a cancelled
post or interfere with a newer attempt.
- A rejected send restores text, uploaded media and exact selected
recipients to the source draft only if no newer edit, deletion, upload
intent or send supersedes it. Clipboard verification settles before
recipient capture, with stale edits/navigation fenced out.

### Related issue

Targets `main` after #7124 merged. This PR reuses its publication checks
and draft protection; the five forum commits have been replayed
unchanged onto the merged parent. Owned-agent discovery (#7122) is
already merged. Split from #7114.

Forum creation/templates, channel-less Notes and local-agent management
are unchanged. Duplicate-name binding from #7133 is already merged and
retained by this stack. Inviting does not start a remote agent or
promise that it is online or will reply.

### Testing

Forum composer lifecycle tests, including clipboard-settlement cases,
and TypeScript/changed-file formatting checks passed after the restack.
Earlier invitation, focus and transport-recovery browser coverage is
retained, not claimed as a fresh full browser run on this head. See
[live CI](https://github.com/block/buzz/pull/7125/checks) for
current-head results. Browser evidence uses mock IPC; no full local
`just ci` pass or native/live-relay validation is claimed.

To try it: open a forum post or reply, select an owned nonmember agent
and send. Cancel, then retry without reselecting; Invite should add that
agent before posting. Deny the add to check the visible error and
retained draft. Navigate away/back during a pending invitation or
rejected send; no stale publication or overwrite of a newer draft should
occur.

![Forum invitation with Invite and
Cancel](https://raw.githubusercontent.com/block/buzz/6481bd088d661975672addcc834231df6ac88705/pr-7125--forum-invite.png)

*Earlier mock-browser capture, not current-head runtime proof. [Error
and successful-post
captures](https://github.com/block/buzz/pull/7125#issuecomment-5513883462);
no before-state/native capture available.*

**Limits:** cancellation cannot undo accepted membership changes or
dispatched posts; authorization and publication are not atomic. Recovery
is same-window, subject to browser storage limits, and is not a durable
in-flight send journal: reload/crash can lose a pending snapshot.
Cross-window coordination and in-flight upload custody are unchanged.
The parent's legacy member-agent compatibility does not establish
ownership for nonmember invitations.

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
2026-09-04 17:33:30 +00:00
Logan Johnson
d595806fc3 fix(desktop): authorize remote mentions at publication (#7124)
🤖

## Summary

In Buzz Desktop, you could own an agent running on another device but be
unable to mention it in a channel where it had not yet joined: it was
filtered out before you could invite it. A selected agent could also
disappear from the message's recipients when permissions changed. This
lets you select an eligible agent in the existing **@ menu**, invite it
from the message composer, and send to that agent—or see an error and
keep your draft rather than silently sending without it.

#### Where the experience changes

| Screen / control | Before → after |
| --- | --- |
| A channel's **Message #…** composer, or a message's **Reply in thread
to …** composer | Type `@` (or use the existing @ button), choose your
agent, write the message and press **Send message**. An owned agent not
yet in the channel can now reach the existing **“Mention people outside
this channel?”** dialog when its response settings allow you to address
it. |
| That dialog's **Invite** button | Previously the membership
requirement could block the agent before the invitation. Now Invite
checks permission to add it, adds it as an agent member of the
**channel** (not just the thread), then rechecks membership and response
permission before sending the waiting message. An agent already in the
channel needs no invitation. |
| Existing direct message, or the new-message screen with the **To:**
field | A mention is checked against the conversation the message will
actually enter, including a newly created direct message, rather than
the old or not-yet-created destination. This does not add an Invite
control to direct messages. |
| Editing a message / sending attachments | The selected agent remains
part of the send or edit attempt through attachment upload and the final
permission check. Lost permission produces a visible error instead of
dropping that recipient. |

**Invite is not the only chat choice.** The existing **Do nothing**
button sends the message *without inviting or notifying the nonmembers*;
their names remain references in the text. Where you cannot invite, that
choice is labelled **Send anyway**. To abandon the send instead, dismiss
the dialog with Escape. Invitation actions are disabled while
preparation is pending, preventing duplicate clicks.

**Leaving and returning must not resurrect a cancelled send.** Switching
threads or leaving the composer cancels its pending invitation, even if
you return to the same thread. Cancellation before dispatch sends no
message; an accepted membership change cannot be automatically undone.
An ordinary send without a pending invitation remains bound to its
original destination rather than following you into another
conversation.

**Failed sends must not overwrite your next draft.** If you leave a
thread, return and replace or deliberately clear its draft while an
older send is pending, the older failure cannot restore deleted text,
recipients or files; success cannot erase the newer draft—even if its
text is identical. An untouched draft cleared automatically for sending
remains recoverable on failure. This protection also covers reopening
the composer and starting a newer send.

The channel timeline also keeps its existing **new-messages / Jump to
latest** button available when newer messages are waiting to be
displayed. For example, after sharing a reply to the channel and closing
the thread panel, you can click the catch-up button to reveal buffered
messages. Closing the thread does **not** guarantee the shared row
appears automatically or force you away from reading history.

### Related issue

Built on [#7122](https://github.com/block/buzz/pull/7122), base branch
`split/owned-agent-discovery`, which lets Desktop find and verify owned
agents independently of this device. Current integration head:
`1144465d00273cf74b7c22544ae5a3299bd98560`, built on exact published
root `3a56d17824522580fe04cae463b54f4c7ba66021`. Root #7122 has its own
CI and security gates; this PR must not land ahead of that dependency.
Finding an agent is not channel membership, online status or a promise
of a reply. This PR changes what the existing message controls can do
with those agents; it adds no profile, presence, cloud marker or remote
start/stop UI.

Standalone forum post/reply **Invite / Cancel** is added separately in
[#7125](https://github.com/block/buzz/pull/7125); here those composers
only gain visible authorization errors. Same-name selection/binding
fixes ([#7133](https://github.com/block/buzz/pull/7133)) and mention
spacing ([#7128](https://github.com/block/buzz/pull/7128)) are not
included.

Extracted from [#7114](https://github.com/block/buzz/pull/7114)
(historical source `98fe33ec`). [Behavior and draft-recovery
contract](1144465d00/docs/remote-mention-routing.md)
· [Originating
discussion](buzz://message?channel=f7a9536a-1738-4bad-a888-b3ea25010ef1&id=7aa1f0ab23dce514bd8a0221441cf005bf428914621171472b79747c50820848).

### Testing

![Channel composer after Send: RemoteScout is not a channel member;
Invite adds it before sending, while Do nothing sends without inviting
or notifying
it](https://raw.githubusercontent.com/block/buzz/b20345da0bd648ecbc78988637c89893a33ca2f3/pr-7124--remote-invite.png)

*Earlier candidate, mock desktop browser: the existing channel dialog
now reachable for an eligible owned agent on another device. The two
buttons have different send outcomes; Do nothing is not Cancel.*
[Success and denial
captures](https://github.com/block/buzz/pull/7124#issuecomment-5482292703)
· [Pending-state
capture](https://github.com/block/buzz/pull/7124#issuecomment-5482604329).
These show the relevant UI, not live agent availability, native
authorization or the later draft-storage/catch-up repairs. No
before-state screenshot is available.

Existing coverage exercises exact recipients, invitation
rejection/cancellation, new direct-message destinations, uploads, edits,
thread re-entry and stored-draft deletion. The timeline regression
checks the shared reply becomes visible using the available catch-up
action.

**Integration validation (2026-09-02):** independently reviewed the
routing delta onto root `3a56d178`: seven original patches unchanged;
two reconciliations retain generic publication-error toasts alongside
authorization errors and retain non-authored editability updates. Added
two production-hook regression tests (normal and queued-media
publication) requiring visible generic error, recovered draft and
released pending state.

- Writer validation: **5,995 Desktop tests**, **42 focused tests**, **22
mock-IPC browser journeys** (18 routing, 2 root provenance, 2
destination binding), and **1 voice-note failure journey** passed; lint,
types, size guards and E2E build also passed.
- The broad suite ran before the final formatting-only test amendment,
not as an exact-final-head rerun. Independent AST comparison confirmed
that amendment is semantics-preserving; **4 fresh assertions at final
`1144465d`** passed. Publication rechecked final-head TypeScript,
amended-test formatting and `git diff --check` successfully. No new full
repository `just ci` run is claimed.
- Browser tests use an isolated E2E build and **mock IPC**, not live
relay/native authorization. Historical screenshots above are explicitly
earlier UI evidence, not exact-head runtime certification. Packaged
Tauri/live-relay behavior was not independently witnessed.
- **Published-head gates:** [current CI
run](https://github.com/block/buzz/actions/runs/33657948560) and
[renewed exact-head formal review
request](https://github.com/block/buzz/pull/7124#issuecomment-5513218340)
must clear before landing. [Earlier CI
run](https://github.com/block/buzz/actions/runs/33438436438) and the two
earlier approvals cover `7ffead0f`, not this new head. Root CI/security
clearance remains separate; the independent scoped integration approval
is not merge authorization.

To try it: in a channel or thread, select an owned nonmember agent,
Send, then Invite or Escape and retry. Deny the add or revoke its
response permission before sending: expect a visible error and
recoverable draft, not a message missing the agent. During a pending
send, return to the source thread, edit or clear the draft, then leave
again: late completion must not overwrite that choice.

**Limits:** permission checks and sending are separate operations;
cancellation cannot retract a dispatched message. Draft protection is
same-window, not new cross-window deletion synchronization. Standalone
forum transport failure can still restore text/media without the exact
selected recipients. Native compatibility is inherited: open-source
builds may still recognize a valid legacy, self-declared agent already
in the channel when verified ownership is absent or rejected; that does
not establish ownership or unlock this owned-nonmember invitation path.
Invalid policy from a verified owner is still rejected. No agent
response is guaranteed.

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
2026-09-04 16:35:35 +00:00
Salman Mohammed
ce9decb235 fix(acp): rename system tag to agent-instructions (#7332)
## Summary

Rename the paired persona prompt boundary from `<system>` to
`<agent-instructions>` in both modern and legacy ACP delivery paths.
Update Desktop diagnostics to label the new boundary as Agent
Instructions while keeping archived `<system>` captures readable.

`system` is confusing (esp with system role) and agent instructions is
what shows up in the UI today

<img width="767" height="298" alt="Screenshot 2026-09-04 at 11 37 54 AM"
src="https://github.com/user-attachments/assets/4e34ac06-db50-461e-a555-ce17885b0031"
/>

### Related issue

Follow-up to #6701. No duplicate open issue or PR found.

### Testing

- `cargo test -p buzz-acp`
- `cargo clippy -p buzz-acp --all-targets -- -D warnings`
- `node --import ./desktop/test-loader.mjs --experimental-strip-types
--test
desktop/src/features/agents/ui/agentSessionTranscriptHelpers.test.mjs
desktop/src/features/agents/ui/agentSessionTranscript.test.mjs`
- `pnpm --dir desktop typecheck`
- Pre-push hook: 6,237 Desktop tests, 3,164 Tauri tests, and all 13 Rust
unit-test lanes passed

Generated with Codex

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
2026-09-04 16:35:18 +00:00
Logan Johnson
ee883d73fb fix(desktop): bind duplicate mention selections to exact recipients (#7133)
## Summary

Selecting two people or agents named Scout could replace the first
recipient with the second even though the message still looked right.
This binds each selection to its exact identity: the first keeps
`@Scout`, and a conflicting selection gets `@Scout (<full public key>)`.
Removing one no longer removes or redirects the other.

- Reuse the existing **@ suggestion list**; team selection and automatic
agent addressing reserve and reuse distinct labels too. Typing an
ambiguous name manually shows an instruction to use the picker and
preserves the draft without publishing—in chat, edits and standalone
forums.
- Share literal mention matching across recipient extraction, removal,
display and editing so a shorter name cannot claim another recipient's
longer or qualified label.
- Rebuild authored references when a message is edited, and use the
latest authorized snapshot when reopening or forwarding.
Rendering/editing can recover qualified identities only from the
message's recorded references, not from a key typed into its body.
Unresolvable historical names remain literal rather than guessed
recipients.
- Wrap full-key labels within narrow/zoomed layouts while keeping their
complete accessible label and ordinary mention icons. Edit activation
waits for the action menu's focus cleanup before focusing the editor.

### Related issue

Targets `main`; mention spacing (#7128) is already merged. Split from
#7114. This is independent of the #7124 → #7125 remote-invitation stack
and does not expand agent eligibility or invitation permissions. The
separate Enter-selection suffix issue remains tracked in #7253.

### Testing

The desktop unit suite and focused mock-Chromium checks passed on the
published integration candidate, including pending-paste selection,
edit/forwarding, copy and narrow-layout cases; formatting, types and
frontend builds passed. On `0b3b18c0`, 64 focused trust/paste/selection
unit tests and six mock-Chromium tests passed with zero browser retries,
including actual timeline chip copy → fresh channel paste → send and
mismatched-key rejection; TypeScript, changed-file Biome and an isolated
E2E build passed. The broader browser run had copy failures before the
focused repairs and is not claimed as wholly green. See [live
CI](https://github.com/block/buzz/pull/7133/checks) for current-head
results. No full local `just ci` pass, native/live-relay or
cross-browser validation is claimed.

To try it: select two same-name recipients, remove one, send, then
edit/reopen and forward; only the intended identities should remain.
Type an ambiguous name without choosing a suggestion and check that
sending retains the draft with an error. Inspect full-key labels in a
narrow window at 150% text size, and open Edit and type immediately.

![Ambiguous mention keeps the chat
draft](https://raw.githubusercontent.com/block/buzz/c48053209b36b3b1bf7197cf2d606d27b48a27c0/pr-7133--ambiguous-general.png)

![Full-key label in a narrow, zoomed
composer](https://raw.githubusercontent.com/block/buzz/ac262f00de6fe3860bff2f87dc0e98165ed6b651/pr-7133--layout-1.5-composer.png)

*Earlier mock-browser captures, not current-head runtime proof. No new
before-state capture; screenshots alone do not prove recipient
delivery.*

**Clipboard trust:** generated full-key-qualified mentions now retain
their exact recipient after copy/paste when the full key matches the
clipboard record and community directory/profile state independently
vouches for the base alias, including numeric collision suffixes. A
qualifier alone does not establish trust. Arbitrary historical labels
still cannot always be reconstructed.

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-authored-by: Bad Janet <150b20bdf6130418df9239dd1bd082c71612c8d653b47c277200365b9be215dc@buzz>
2026-09-04 11:51:41 -04:00
Will Pfleger
01bacb8df3 chore(release): release Buzz Desktop version 0.5.22 (#7308)
## Buzz Desktop release v0.5.22

- **Frozen main:** `75f101d8b4f5b4b26f9891b73b87c67fedac25a0`
- **Reviewed candidate:** `9ceb1f79bbc21785a0a075c40aecb3c058b1ea15`
- **Previous desktop release:** `desktop-v0.5.20`
- **Proposed immutable tag:** `desktop-v0.5.22`

This PR may be **squash merged** after the Desktop Release Candidate
check and all protected-branch checks pass. Merging authorizes
publication of the exact reviewed candidate; later or unrelated changes
on `main` cannot alter it.

The checked-in changelog accounts for every non-merge commit in the
release range. The Desktop tag points to the reviewed candidate commit,
not the later squash commit. Publication remains bound to that immutable
candidate tag.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
2026-09-04 09:57:17 -04:00
Matt Toohey
b1f6b7ef77 feat(desktop): preserve mentions across copy and paste (#7228)
Copying a message out of the timeline lost the mention. The rendered
chip drops
the `@` for display, so the clipboard carried "John Smith" — two
ordinary words
no composer could bind back to a pubkey. Pasting into another channel
produced
dead text, and sending it tagged nobody.

## What changed

Every Buzz copy now writes two clipboard flavors in one transaction:

- **`text/plain`** — readable anywhere, sigils restored, no pubkeys.
This is what
  TextEdit, Slack, and every other external app receive.
- **`text/html`** — the same content with each mention wrapped in a span
carrying
  `data-mention-pubkey` / `-label` / `-kind`.

On paste, the composer harvests those records, registers each `name →
pubkey`
with the existing mention machinery, and inserts the content: the chip
re-lights
and the send path recovers the identity the author tagged. A marker
attribute
records what the plain flavor holds, so a Markdown copy pastes through
the text
pipeline and a rendered copy through the HTML one.

**Covered surfaces:** timeline selection copy, thread-panel selection
copy,
forum post/reply selection copy, "Copy message", and composer copy/cut —
plus
paste in both the channel and forum composers.

## Trust boundary

Clipboard HTML is untrusted, and the branch treats it that way:

- Records are capped (50), labels bounded (200 chars), and a pubkey must
be
  64 hex before it can become a `p` tag.
- **Only mentions the paste actually shows are registered.** An empty
`<span data-mention-pubkey=… -label="Jane Doe">` would otherwise rebind
that
display name for the rest of the composer session, so a later
hand-written
@Jane Doe would chip-light convincingly against the attacker's key. Each
  branch registers only the records whose label appears in the text *it*
inserts, matched with `getMentionOffsets` — the same matcher the
send-time
  extractor uses.
- **The visibility gate reads only what ProseMirror will insert.**
`DOMParser` hard-drops `script`, `style`, `title`, `noscript`, `object`,
and
`head` content, so `visible<style>@Jane Doe</style>` beside an empty
chip span
used to smuggle a binding past the gate. Those elements are stripped
before
  either output is derived.
- **A partial chip never gains a sigil.** A selection crossing a chip
boundary
falls through to the browser's default copy, which serializes the full
identity
attributes around a slice of the text — pasting that invented "@Smith"
out of
"John Smith". Paste now leaves a fragment as plain text, tolerating only
what a
whole chip picks up in transit (restored sigil, author casing, U+00A0
swaps,
  and the label cap's own ellipsis).

Both clipboard sides share one `matchChipTextToLabel` helper, in the
module that
owns the label attributes, so copy and paste cannot drift on what counts
as a
whole chip.

## Notes

- Mention matching reuses `getMentionOffsets`, so code spans and fences
are
  excluded and the longest display name wins.
- The plain flavor inlines chip boxes before reading `innerText`; a chip
is a
flex container, so the browser's own copy split "@John Smith" onto its
own line.
- `MarkdownMention` and `MacEmacsTextShortcuts` are extracted verbatim
from
`markdown.tsx` and `useRichTextEditor.ts` to keep both files under the
size gate.

## Testing

- ~40 unit tests over the flavor builder/parser, the visibility filter,
the
  ignored-tag sweep, and the chip-match verdicts.
- A Playwright spec (`desktop/tests/e2e/mention-clipboard.spec.ts`)
driving real
  copy/cut/paste DOM events: timeline and "Copy message" of a multi-word
non-member mention pasted into another channel and sent with the
original
pubkey in its `p` tag; the forum copy → forum reply round trip; composer
  copy/cut; plain flavor asserted to contain no 64-hex string; the
boundary-crossing drag; and the hidden-record and `<style>` smuggling
vectors.
- Every regression test is bound to a production seam and fails with its
guard
  removed.
- `just ci` / pre-push lanes green (5927 desktop unit tests, typecheck,
lint,
  file-size gate).

---------

Signed-off-by: Matt Toohey <contact@matttoohey.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-09-04 16:45:13 +10:00
Logan Johnson
4b0744d7f3 test(desktop): await Bestie drag and profile hover endpoints (#7294) 2026-09-03 23:52:55 -04:00
Luke Tornquist
2ac0aa1dd1 Collapse contiguous join messages (#7262) 2026-09-03 19:26:21 -04:00
Will Pfleger
75f101d8b4 chore(release): release Buzz Desktop version 0.5.21 (#7301)
## Buzz Desktop release v0.5.21

- **Frozen main:** `88687876f7808a2fd742b7eb2e4b9f87d999ad8d`
- **Reviewed candidate:** `47e4e0347fb4142df55b03e0e3dc537fe33a5d62`
- **Previous desktop release:** `desktop-v0.5.20`
- **Proposed immutable tag:** `desktop-v0.5.21`

This PR may be **squash merged** after the Desktop Release Candidate
check and all protected-branch checks pass. Merging authorizes
publication of the exact reviewed candidate; later or unrelated changes
on `main` cannot alter it.

The checked-in changelog accounts for every non-merge commit in the
release range. The Desktop tag points to the reviewed candidate commit,
not the later squash commit. Publication remains bound to that immutable
candidate tag.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
2026-09-03 17:41:22 -04:00
Logan Johnson
6cf514ed90 fix(desktop): wrap message tables within the available pane (#7279)
## Summary
- Let Markdown tables use the available message width instead of their
maximum-content width, inheriting the renderer's existing
`wrap-anywhere` handling for long tokens and links.
- Top-align header cells as well as body cells. Give cells a `min-w-24`
readability floor so short labels do not collapse into one-letter
columns; retain the existing table-local scrollbar when many columns
genuinely cannot fit.
- Preserve semantic table markup, links, inline code, and surrounding
message layout. Add three browser regressions through the real message
renderer (channel, narrow thread, and many-column overflow).

### Related issue
Fixes #5313. No matching open table-readability PR found.

Owner-authorized task channel: `819b36d5-7371-4ed0-bf9b-d461d723779e`

Source:
buzz://message?channel=819b36d5-7371-4ed0-bf9b-d461d723779e&id=3bf2d1bd5df28fcb481e71159dc1be2b3888918b05f67e270354fbfea65a8c90

### Testing
Original production candidate:
`4427f2e6f9ef85a01a267fe9eabd1cf13cacfb78`; base:
`7a9a5233d9d755e715be0c585cf7850e935d28cf`.

- `pnpm -C desktop check` — passed.
- `pnpm -C desktop test` — 6,110 passed, none skipped.
- `pnpm -C desktop typecheck` — passed.
- `BUZZ_PROTECTED_BUILD_OUTPUT=<isolated-production-directory> pnpm -C
desktop build` — passed.
- `pnpm -C desktop build:e2e --outDir <isolated-candidate-directory>` —
passed.
- `just file-size-check` and `git diff --check` — passed.
- Chromium / mock Tauri bridge: all 3 new Playwright tests passed
against the fixed candidate build. Both wrapping tests fail on the
unchanged base build, while the overflow-fallback test passes. Each
browser run used a unique output directory and a non-reused local server
pinned to its build directory.
- At 883px channel message width: table scroll width **1,395 → 883px**.
At 292px thread message width: **1,395 → 292px**. All cells top-aligned;
long tokens/URLs wrap, link destinations and code text remain intact,
short labels stay on one line, and document width remains 1,280px.
- Focused fresh-frame review checked actual before/after screenshots and
the complete diff. It caught one-letter label wrapping in an early
width-only candidate; the final cell-width floor and regression
assertion address that finding.

Limitations: native Tauri/WebKit and relay-backed integration were not
exercised for this CSS-only delta. Repository-wide `just ci` was
attempted but exceeded the local command budget during unrelated Rust
compilation; it is **not** reported green. The full affected TypeScript
package gates above passed on the exact candidate. Production/E2E builds
retain existing chunk-size and mixed static/dynamic import warnings.

Before/after screenshots are posted below using the repository's
screenshot publication script. No merge, production installation, or
runtime restart is requested.


### CI-driven test-only follow-up
Current head: `925d964b6cf31ba704d74baf73769110774b2789`.

Smoke shard 3 exposed an older test in `messaging.spec.ts:457` that
still required three columns of ordinary prose to overflow horizontally
([failure
log](https://github.com/block/buzz/actions/runs/33771325404/job/100702242706)).
This expectation contradicts the intended wrapping change. Updated its
name and assertion to require containment; the separate many-column
local-scroll test is unchanged. No production code or harness settings
changed in this follow-up.

- Affected browser validation: **4/4 passed** (updated existing
prose/narrow table case plus the three new channel/thread/overflow
cases).
- Reused the immutable `4427f2e6` E2E build on a fresh non-reused
server; production source is unchanged at the current head. The
package/build checks above remain evidence for that unchanged production
tree, not a claim of rerunning the full suite at the new SHA.
- Targeted Biome check, file-size gate, and `git diff --check` passed.
- Current-head CI:
https://github.com/block/buzz/actions/runs/33773490446
- Prior unrelated Bestie baseline failure and reproduction:
https://github.com/block/buzz/pull/7279#issuecomment-5528077181
- Security authorization and review must target the **current** head,
not the old screenshot/build SHA. A Block organization member must
comment exactly `@buzz-security-review
925d964b6cf31ba704d74baf73769110774b2789`.

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
2026-09-03 16:07:14 +00:00
Will Pfleger
7a9a5233d9 🤖 fix(desktop): harden smoke E2E tests against Bestie overlay and toast timing (#7270)
## What

Two remaining E2E hardening fixes from the Desktop Smoke flake pattern
introduced by `ac5a18697` (Bestie — added `VITE_BUZZ_BESTIE=1` to
`.env.e2e` and mounted `BestieGlobalOverlay` globally).

The toast/DM-retry fix landed independently in main via #7127
(`input.hover()` + bounded `toHaveCount` wait); that hunk is dropped
from this PR.

## Fixes

### `agent-control-regressions.spec.ts:240` — Stop does not accept an
unconfirmed or foreign-channel result

**Cause:** Playwright 1.60.0's `page.clock.install()` fakes all timers
including `requestAnimationFrame`. The test called it before opening the
settings menu. With RAF frozen, the `DropdownMenuContent`'s `zoom-in-95
duration-150` CSS enter-animation never advances — Playwright's
stability check observes a continuously-changing bounding box until the
30s test timeout.

**Fix:** Re-sequence so the menu is opened on real time first. After
`openAgentActivity`, open the trigger, assert visibility/enabled, call
`waitForAnimations(page)` to settle the enter-animation (real
`setTimeout`, no fake clock installed yet), then install the clock. The
`fastForward(8_001)` correlation timeout still works because it's
scheduled after the clock is active. Pointer actionability preserved —
normal `stop.click()` (no `force`) fails with pointer-interception under
a covering surface.

### `message-feedback-snapshots.spec.ts:97` — profile hover uses the
channel hover surface

**Cause:** `channel.hover()` triggers a CSS `transition-colors`
animation. With the Bestie `LayoutGroup` mounted, `evaluate()` captures
a mid-transition background value that never matches the profile card's
settled token.

**Fix:** `waitForAnimations(page)` after `channel.hover()` and before
reading `channelHoverColor`.

## Evidence

- Target specs pass: stop-turn 8/8, profile-hover passes.
- Full `agent-control-regressions.spec.ts` (7 tests) green.
- `just desktop-typecheck` clean at pushed head `0fbfe2a9f`.
- No production code changed.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
2026-09-03 08:59:49 -04:00
klopez4212
c6ca9d94d9 Show status and huddle indicators beside names (#7112)
## Summary

- show status and huddle emoji beside names in DMs and message rows
- provide accessible tooltips, fallback status emoji, and profile-menu
icon replacement
- add the desktop status editor with preset durations, a ShadCN
calendar, and a capped half-hour time menu

## Validation

- desktop checks, typecheck, and file-size guard
- 5,802 desktop tests
- focused Playwright coverage (3 passed)
- E2E build and native Builderlab staging verification

Updated visual snapshots are attached in the PR comments.

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
2026-09-03 09:39:37 +00:00