Commit Graph

2514 Commits

Author SHA1 Message Date
renovate[bot]
30596d3add chore(deps): update dependency @tanstack/react-virtual to v3.14.10 (#6666)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@tanstack/react-virtual](https://tanstack.com/virtual)
([source](https://redirect.github.com/TanStack/virtual/tree/HEAD/packages/react-virtual))
| [`3.14.9` →
`3.14.10`](https://renovatebot.com/diffs/npm/@tanstack%2freact-virtual/3.14.9/3.14.10)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@tanstack%2freact-virtual/3.14.10?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@tanstack%2freact-virtual/3.14.9/3.14.10?slim=true)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/1) for more information.

---

### Release Notes

<details>
<summary>TanStack/virtual (@&#8203;tanstack/react-virtual)</summary>

###
[`v3.14.10`](https://redirect.github.com/TanStack/virtual/blob/HEAD/packages/react-virtual/CHANGELOG.md#31410)

[Compare
Source](https://redirect.github.com/TanStack/virtual/compare/@tanstack/react-virtual@3.14.9...@tanstack/react-virtual@3.14.10)

##### Patch Changes

- Updated dependencies
\[[`a0a411e`](a0a411e06f),
[`d2cf98b`](d2cf98beea)]:
-
[@&#8203;tanstack/virtual-core](https://redirect.github.com/tanstack/virtual-core)@&#8203;3.17.8

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/block/buzz).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zOS4wIiwidXBkYXRlZEluVmVyIjoiNDQuMzkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-27 09:53:29 -06:00
renovate[bot]
82e748085e chore(deps): update ubuntu:24.04 docker digest to 33ceb71 (#6664)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [ubuntu](https://hub.docker.com/_/ubuntu)
([source](https://git.launchpad.net/cloud-images/+oci/ubuntu-base)) |
container | digest | `561618e` → `33ceb71` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/1) for more information.

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/block/buzz).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zOS4wIiwidXBkYXRlZEluVmVyIjoiNDQuMzkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-27 09:52:37 -06:00
Wes
9176e34405 fix(projects): allow owners to delete agent projects (#6533)
## Summary

- expose project deletion when the current user directly owns the
project or owns/manages its author agent
- publish a viewer-signed, project-only tombstone after fetching the
exact live head, then detect concurrent replacement
- honor relay-authorized owner-signed tombstones in desktop project read
models

## Testing

- pre-push hook: desktop Biome, TypeScript, 5,358 desktop tests, Rust
tests, Tauri checks, and file-size ratchet passed at `a36f7a4cf`
- `cargo check -p buzz-test-client --tests`
- relay E2E coverage added as ignored; not run against a live relay

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
Signed-off-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@buzz.block.builderlab.xyz>
Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
Co-authored-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@buzz.block.builderlab.xyz>
2026-08-27 09:33:04 -06:00
klopez4212
27c1a7c91b Fade expanded video controls on hover (#6926)
## Summary
- fade expanded video controls out when the pointer leaves the video
- restore controls on video hover or keyboard focus while keeping touch
controls visible
- respect reduced-motion preferences

## Testing
- focused expanded-video hover Playwright test
- full video attachment E2E suite (12 tests)
- desktop check and typecheck
- desktop unit tests (5,674)

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
2026-08-27 09:27:54 -06:00
Tyler
b3baa56ba8 fix(db): exclude kind:30179 ciphertext from brownfield FTS (#6822)
## Problem

Kind `30179` (NIP-PMA private managed agent — NIP-44 ciphertext carrying
the agent nsec, env vars, prompt) joined `AUTHOR_ONLY_KINDS` in #4593,
but only **fresh** installs stopped indexing it:

- `migrations/0008` installs the positive FTS allowlist only when
`events` is empty;
- `migrations/0014` wraps the retained brownfield expression for `30350`
alone;
- `schema/schema.sql` still carries the legacy negative skip-set without
`30179`.

So a relay upgraded in place keeps tokenizing `30179` ciphertext into
`events.search_tsv`. Found by Wren while reviewing #4999 (which
activates publication of 30179 from Desktop). No readable leak: `/query`
applies `search_hit_accepted` + `event_visible_to_reader` before
serialization and live foreign searches against builderlab returned `[]`
— this is the storage-layer privacy invariant (`docs/nips/NIP-PMA.md`
deployment step 2) plus wasted FTS work.

## Fix

- `migrations/0033_private_managed_agent_fts.sql` — same shape as 0014:
capture the current generated expression via `pg_get_expr`, drop/re-add
`search_tsv` wrapped with `kind = 30179 → NULL`. Every other kind keeps
whatever policy the database already had (fresh allowlist or brownfield
skip-set). Rebuilds the GIN index; no heap-wide policy rewrite.
- `schema/schema.sql` — add `30179` to the desired-state skip-set.

## Tests

- `populated_upgrade_preserves_search_policy_except_for_private_kinds`
(renamed from `…_push_leases`): provisions the **legacy negative
expression** (migrations 1–7), inserts kind 1 / 30179 / 30350 rows,
checkpoints after 0014 (30179 still searchable, 30350 not), then runs to
head and asserts 30179 is NULL. **Proven red** with the 0033 file
removed: `left: [(1, Some(true)), (30179, Some(true)), (30350, None)]`.
- `embedded_migrator_contains_consolidated_initial_schema`: count 32→33,
asserts 0033 shape and `schema.sql` parity.
- `buzz-search/tests/fts_integration.rs` setup applies 0033 so the FTS
tripwires run against the real chain.

## Verification (HEAD 6154cc33d, same shell)

- `cargo test -p buzz-db -- --include-ignored --test-threads=1`: 305
passed / 3 failed — the 3
(`create_community_with_owner_enforces_per_owner_limit`,
`insert_mentions_indexes_rosters_past_bind_parameter_cap`,
`transfer_ownership_returns_limit_reached_for_maxed_transferee`) fail
identically on pristine `main` f24971033 with a fresh DB; unrelated to
this diff.
- `cargo test -p buzz-search -- --include-ignored`: 19 passed (incl.
`author_only_kinds_are_storage_level_unsearchable`,
`p_gated_persistent_kinds_have_storage_null_tsvector`).
- `desired_state_schema_bootstrap_progresses_beyond_fencing` +
`run_migrations_applies_consolidated_initial_schema_on_fresh_database`:
pass.
- `cargo fmt --all --check` clean; `cargo clippy -p buzz-db -p
buzz-search --all-targets -D warnings` clean.

Independent of #4999 — both main and #4999 are brownfield-exposed today;
this lands either order.

---------

Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-27 10:47:35 -04:00
klopez4212
4d77175c78 fix(client): resurface hidden DMs from live activity (#6885)
## Summary

Hiding a DM row and then receiving a new message from that person never
brought the row back — the reader was left with only an Inbox
notification. This restores the row from live activity, client-side,
with no relay or database changes.

- Subscribe to hidden-DM activity **channel-scoped** over the hidden-DM
id set (`#h`), re-registering whenever the set changes. The relay's
`fan_out_scoped` only routes channel-scoped events (every kind:9 DM
carries a `channel_id`) to channel-scoped subscriptions, so a
community-global `#p` filter receives nothing. Hiding a DM never drops
membership, so `#h` authorization holds. Because every delivered event
is already for a hidden DM the reader belongs to, eligibility no longer
needs a self `p` tag — untagged CLI/agent DMs resurface too — and no
per-event visibility fetch is required.
- On qualifying activity, invoke the existing open-DM action (`41010`)
to reopen the hidden one-to-one or group DM, validating that the relay
reopened the same channel.
- Preserve the existing relay-synced hide snapshot (`41012` → `30622`);
hide state still syncs across devices.
- Let Inbox reopen a hidden DM before navigating to its message, with a
busy state on the row and context-menu "Open in channel" action while a
reopen is pending.
- Coalesce resurface attempts per channel: a concurrent message that
arrives while a reopen is in flight triggers a retry on failure instead
of being marked handled and dropped.
- Write exact dm-visibility query keys on open/hide mutations.

This is the client-only replacement agreed in the Buzz design thread and
supersedes #6713. The accepted limitation is that resurface only fires
while a client is running — a message that arrives while all clients are
closed does not restore the row on next launch (the `30622` snapshot has
no watermark); Inbox click-through is the safety net for that case.

Addresses #6316.

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
2026-08-27 10:29:26 -04:00
Matt Toohey
c856be0fb9 fix(desktop): keep the draft space when typing right after a mention pick (#6875)
## Problem

Desktop Smoke E2E flaked on `mentions.spec.ts` ("typing a mention before
existing text does not interleave spaces"): picking a suggestion
mid-draft and typing immediately could swallow the draft's own space,
producing `hello @bob abcworld` instead of `hello @bob a world`.

With `hello world` drafted and ` @bo` typed after `hello`, picking the
suggestion leaves `hello @bob world` — the mention's trailing space
followed by the draft's space. Chromium reconciles that whitespace run
by re-emitting it as a replacement (`replace(" " -> " a")`), anchored at
*either* edge of the run. The settling-time text-input redirect only
recognized a caret or a rewrite anchored at the chip edge, so the other
shapes fell through to ProseMirror's destructive default and ate the
separator.

The race pre-dates this branch and reproduces on `main`.

## Fix

`mentionHighlightExtension.ts` now redirects on the invariant instead of
on a specific rewrite shape. While autocomplete is settling the user
cannot have a selection (a selection cancels settlement), so a
whitespace-only replacement adjacent to a mention's trailing space is
always the browser normalizing whitespace — never an intentional delete.
The handler keeps every space already in the document and inserts only
the typed characters after the token, whichever edge the rewrite is
anchored at. Non-whitespace replacements still fall through as real
edits.

`insertPosForMentionTextInput` / `mentionTextInputInsertPos` become
`insertionForMentionTextInput` / `mentionTextInputInsertion`, returning
`{insertAt, text}` so the handler can rewrite the payload, not just the
position.

## Tests

- Helper-level cases for all four anchors (caret at chip edge, caret
past the trailing space, run rewritten from the chip edge, draft space
rewritten alone), plus space/NBSP variants and a non-whitespace
replacement that must be left alone.
- A plugin-level test drives the real `handleTextInput` prop and falls
back to ProseMirror's default when it declines — exactly as the browser
does with the return value — so it fails against the previous production
code rather than depending on winning a timing race in a headless
browser.

Desktop unit suite passes on pre-push (5558/5558).

---------

Signed-off-by: Matt Toohey <contact@matttoohey.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 17:37:42 +10:00
Bradley Axen
b622003f74 broker: define the agent-to-broker action contract (#6742)
Addresses the interface half of #6467.

This is the contract for how a keyless agent asks a host to act on its
behalf — and only that. No host, no transport, no signing, no Desktop
code; the only implementation is a test double. An agent that holds no
secret key still has to read its channels, answer a mention, react, keep
its profile, address its memory, and create the agents it needs. This PR
defines the vocabulary for those requests and the rules both sides must
follow.

**#6467's open question, answered: per-operation, not "sign this".** A
raw signing primitive tells the host *who* is asking but not *what for*,
so the only policies it can express are all-or-nothing. A closed set of
named operations lets a host serve channel reads while refusing agent
creation, and gives any later policy layer something to attach to. The
cost is that adding an operation means changing the contract — which is
the point. Signing, publishing, and the auth/encryption NIPs are
deliberately *not* operations; they are mechanisms inside whoever holds
the key, and a test pins that none of those names resolve.

**Nine operations in v1:** read a channel (whole channel, one thread, or
mentions-only — these differ by filter, not permission, so they share
one operation), post, reply, react, set profile, derive a storage
address, and create / update / delete agents. Presence and typing are
deferred as housekeeping a host may decline regardless; streaming is
deferred, so waking on a mention is a polled mentions-only read. Each
operation has typed, validated arguments and a typed outcome.

**What the contract enforces structurally rather than by instruction:**

- *Retry means identical bytes.* A request is validated and serialized
exactly once and the transport only ever sees that frozen form. There is
no way to re-render it, so a retry can't drift and be mistaken for a
conflicting request.
- *Read cursors are opaque.* Host-issued strings, validated for shape
only, never parsed or compared. The host owns ordering and paging
stability instead of being locked into timestamps.
- *Reads return signed Nostr events.* A keyless agent verifies
authorship and content locally and trusts the host only for completeness
and authorization. Reply ancestry and mentions are derived from the
signed tags, so nothing can disagree with the signature.
- *Validation is the only door to a response.* The transport primitive
hands back an unjudged envelope; the only response a caller can actually
obtain is one already checked against the request it answers — request
id correlates, the outcome's operation matches, identifiers and cursors
are well-formed. A mismatch counts as "no answer", not a failure
verdict. The raw primitive can't be invoked from outside the crate
(pinned by compile-failure checks), so skipping that step isn't an
option.
- *Authority is never asserted by a body.* The envelope has no
requester, owner, scope, or credential field; all of that comes from the
session the host authenticated. Ownership of a created agent is
implicitly the requester, never a field — which is what lets agents own
agents while the chain still ends at a human.
- *No secret crosses the wire.* Agent creation returns a pubkey and
handle, never the minted key. Every payload type rejects unknown fields,
and a test pins the exact JSON key set of all eighteen argument and
outcome types, so no field can be added without a reviewer touching that
table. Two honest limits: a string can physically hold secret text, so
keeping secrets out of message content is host policy; and nothing stops
a host from *holding* keys — it just may not hand them over.
- *Null is never legal; omission is the only spelling of absence.* Every
optional member rejects an explicit null. Otherwise null and omitted
would be indistinguishable, and a contradiction check on the response
envelope could be skipped by sending one (a real hole in an earlier
revision). Coverage is derived by walking every path in the real
fixtures, so new optional members are covered automatically. Serializers
that emit null by default (Go without `omitempty`, Python's default
dump) need configuring — called out in the module docs.

**Wire mapping.** One POST endpoint, JSON body is the frozen request,
opaque bearer credential in the auth header. Every verdict the host
reached — including failure and including a rejected credential — comes
back as a well-formed envelope with 200, because a second copy of the
verdict in the status line could only disagree with it. Clients still
try to parse an envelope regardless of status (intermediaries may remap)
and fall back to the status only when none is present. A rejected
credential is a host verdict carrying the promise "the action did not
run"; transport errors are reserved for "nothing can be concluded about
side effects." Binding the credential to a specific agent and
conversation is the host's job and deliberately unenforceable here.

**Relationship to #6543.** Envelope, request-id rules, protocol version,
the three-way succeeded / failed / indeterminate result, error codes,
and the agent-management argument shapes carry over. Changed
deliberately: pubkeys are a validated type rather than bare strings,
"capability" became "action" throughout, and a new "unsupported" error
lets a host decline a best-effort operation without it reading as a
fault. Both PRs land in the same module path — whichever merges second
needs a hand reconcile, which I own as the author of #6543.

**Validation at `7c853e0fb`.** 299 tests in the sdk crate (full package
run), workspace tests green apart from the pre-existing relay timeout
flake, which I reproduced on unmodified `main`. Clippy with warnings
denied, fmt, warning-free docs, and the pre-push gate all pass. Review
findings were closed with mutation tests — each fix verified by
reverting it and watching a named test fail. One of those exposed a gap
in my own coverage (response-side fields weren't pinned); that's fixed
and floored separately from the request side so neither can mask the
other.

**Size.** About 5,400 lines across 8 files, roughly half tests. Every
source file is under the repo's 1,000-line cap except the test file;
#6759 splits it mechanically, is stacked on this branch, retargets to
`main` after merge, and lands before PR 2 of the stack.

---------

Signed-off-by: ss-dev-00 <a02c4e0850e5e612b4ddf95dbe2f5c56467cf27c6552203bc833ff438fb31971@buzz.block.builderlab.xyz>
Signed-off-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: ss-core-02 <cca2c695c61d409321106cc399009c2cc674d72cc8900b2999a68fe54577a411@buzz.block.builderlab.xyz>
Co-authored-by: ss-dev-00 <a02c4e0850e5e612b4ddf95dbe2f5c56467cf27c6552203bc833ff438fb31971@buzz.block.builderlab.xyz>
Co-authored-by: ss-core-02 <cca2c695c61d409321106cc399009c2cc674d72cc8900b2999a68fe54577a411@buzz.block.builderlab.xyz>
2026-08-26 19:00:45 -07:00
thomaspblock
cada3028ce fix(desktop): keep project sheets independent from threads (#6901)
## Summary

Project workspace sheets now slide in from the right as an independent
layer over an open conversation thread instead of replacing the thread
or rendering inside its panel. Closing the sheet restores the unchanged
thread, Escape applies only to the topmost surface, and the covered
thread's resize divider no longer leaks through the overlay.

### Related issue

Related: #6602. No matching issue found.

### Testing

- `pnpm --dir desktop exec biome check
src/features/channels/ui/RightAuxiliaryPane.tsx
tests/e2e/project-commit-detail.spec.ts`
- `pnpm exec playwright test tests/e2e/project-commit-detail.spec.ts
--project=smoke --grep "project workspace sheet stays independent from
an open thread"`
- Pre-push desktop formatting, file-size, typecheck, and unit-test
checks

---------

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
2026-08-26 20:59:47 -04:00
Jordan Mecom
cdab765748 Add gated security reviews (#6816)
Add a gated Codex security review for Buzz pull requests.

Reviews run automatically for Block organization members. External PRs
require a `@buzz-security-review` comment from a Block member, and every
new external push invalidates the previous result.

Codex reviews the PR SHA in a read-only, network-restricted job with no
write GitHub token. A separate trusted job validates and sanitizes
structured findings before posting them. The workflow uses the dedicated
`CODEX_REVIEW_API_KEY` secret.

---------

Signed-off-by: Jordan Mecom <jm@squareup.com>
2026-08-26 15:52:06 -07:00
tulsi
ceb8ba605f fix(desktop): accent-colored mention badges that count thread mentions (#6900)
**Category:** fix
**User Impact:** Sidebar mention numerals now match the user's
theme/accent color and appear for mentions inside threads, not just
top-level channel messages. Hover-to-preview on channel rows is
unchanged.

**Problem:** Follow-ups to #6696:

1. The mention numeral used a fixed red `--notification` token that
ignored the user's theme and accent selection.
2. The numeral read `appBadgeCount`, which deliberately excludes
threaded replies (Home's badge subtotal counts those toward the Dock
badge). Mentions inside threads therefore never surfaced a numeral on
the channel row — only the generic thread-activity dot.

**Solution:**

1. The badge now reuses the primary accent pair (`bg-primary` /
`text-primary-foreground`), the same treatment as the DM count badge, so
it follows whatever accent the user applies — including the pinned
neutral accent of the Buzz themes. The now-unused `--notification`
tokens and Tailwind utilities are removed.
2. Non-DM rows now count every unread mention/broadcast — threaded or
top-level — via a new `highPriorityCount` channel projection, replacing
the boolean `highPriorityUnread` across the TS contract, the Tauri
SQLite store, the e2e mock bridge, and the native test rig. The Dock/app
badge projection (`appBadgeCount`) is unchanged, so Dock totals do not
double-count thread activity that Home already counts.

The hover-to-preview channel activity popover is untouched; e2e coverage
now pins the combination (thread mention → numeral, no dot, popover
still opens and lists the mentioning reply).

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

**desktop/src/features/channels/useUnreadChannels.ts**
Non-DM sidebar counts switch from `appBadgeCount` to the
mention/broadcast-inclusive `highPriorityCount`; the high-priority
channel set derives from the same count.

**desktop/src/features/sidebar/ui/SidebarSection.tsx**
Badge drops the fixed notification classes and inherits the
accent-following default.

**desktop/src/shared/styles/globals/theme.css /
desktop/tailwind.config.js**
Remove the unused fixed `--notification` tokens and semantic utilities.

**desktop/src/shared/api/tauriObservedUnread.ts /
desktop/src-tauri/src/observed_unread.rs**
Projection contract: `highPriorityUnread: bool` → `highPriorityCount:
u64`, aggregated per unread high-priority event.

**desktop/src/testing/e2eBridge.ts /
desktop/src/features/channels/observedUnreadNativeRig.mjs**
Mock/native-rig projections mirror the new count field.

**desktop/src/features/channels/observedUnreadNative.test.mjs**
Native badge-lane test seeds a high-priority event to match the
numeral's new semantics.

**desktop/tests/e2e/badge.spec.ts**
Top-level mention test now asserts the badge equals the applied accent
(seeded `#22c55e` under an adversarial theme). New test: in-thread
mention shows the numeral, no dot, and the hover preview popover still
lists the mentioning reply.

**desktop/tests/e2e/channel-activity-popover.spec.ts /
desktop/tests/e2e/thread-unread.spec.ts**
Seeded fixtures that mention the user now expect the numeral (which
subsumes the dot).

</details>

## Reproduction steps

1. Pick any theme + accent color in Settings → Appearance.
2. Receive a top-level message mentioning you in an inactive channel →
the row shows a numeral pill in your accent color.
3. Receive a reply **inside a thread** that mentions you in an inactive
channel → the row shows the numeral (previously only a dot).
4. Hover the row → the channel activity preview still opens and lists
the mentioning reply.
5. Switch accents/themes → the pill re-colors accordingly; the Dock
badge count is unchanged from before.

## Verification

- `tsc --noEmit`, Biome, `check:px-text` — clean
- Desktop unit tests: 179/179 channel-suite; full suite has one
pre-existing failure from #6590 (`useRetainedProjectGitViews.test.mjs`
loader error, fails on clean `main`)
- Tauri Rust: `observed_unread` tests + clippy + fmt — clean
- Playwright smoke: badge (19), channel-activity-popover (10),
thread-unread (13), plus channels/inbox/stream/messaging/mentions suites
— all green

## Screenshots

Screenshots are posted in the PR discussion using immutable
repository-hosted image URLs.

Signed-off-by: tulsi <tulsi@block.xyz>
2026-08-26 17:44:05 -04:00
Salman Mohammed
79df468255 Add Buzz benchmark evaluation layers (#6823)
## Summary

- classify every Buzz-native benchmark task as `regression` or
`workflow`
- add metadata-backed `--layer` selection with k=1 regression and k=3
workflow defaults, while preserving explicit `--attempts`/`-k`
- split an unqualified mixed Buzz dataset run into two Harbor jobs so
each layer gets the correct default
- document the layer contract and cover metadata, selection, defaults,
overrides, invalid input, and command construction

---------

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
2026-08-26 17:41:56 -04:00
Salman Mohammed
eb8e97cfe7 fix(desktop): show edited head content in thread panel (#6887)
## Why

Fixes #6886. In the channel timeline an edited message shows its newest
body, but opening its thread panel showed the stale pre-edit body until
the async thread-aux backfill landed (and permanently if it raced or
missed).

## Root cause

`desktop/src/features/messages/lib/independentThreadPanel.ts` built
`events = [head, ...replyEvents]`, discarding the head's aux overlay
events (edits `kind:40003`, deletions, reactions) already present in the
channel window. `formatTimelineMessages` only applies an edit when the
edit event sits in the same array as its target, so the thread head
relied solely on `withThreadAux` to re-supply it.

## What

- Carry the head's non-content aux events (via
`!isTimelineContentEvent`) from the channel window into the thread
head's event array, deduped against `replyEvents` by id.
- Thread head is now byte-identical to the main timeline the instant the
thread opens, independent of the async aux fetch.
- Restricted to non-content kinds so reply content rows never leak in.

## Tests

- Unit regression `independentThreadPanel.test.mjs` (6 cases): red
pre-fix (`"two PRs"`), green post-fix (`"these PRs"`).
- e2e `thread-head-stale-edit.spec.ts`: delays thread-replies aux to
force the channel-window-vs-thread-aux divergence, then opens the thread
via the reply button and asserts the panel head shows the edited body.
Verified red pre-fix (stale `message-thread-panel` head), green
post-fix.
- Full desktop unit suite: **5562 pass / 0 fail**. `biome` + `tsc
--noEmit` clean.

Origin: Buzz channel `fix-reply-thread-old-view-bug`
(#464b982f-fc05-42c8-918f-409e1f05a1df).

---------

Signed-off-by: Leo <5faf251baee50ee6bcde338aef6acdd70bb3e60115664c2cd490d94a55dfc488@buzz.block.builderlab.xyz>
Co-authored-by: Leo <5faf251baee50ee6bcde338aef6acdd70bb3e60115664c2cd490d94a55dfc488@buzz.block.builderlab.xyz>
2026-08-26 20:57:19 +00:00
Taylor Ho
03bfc837cb fix(desktop-tooltip): increase surface contrast (#6897)
**Category:** fix
**User Impact:** Tooltips now use a higher-contrast surface, making
their labels easier to read across desktop themes.

**Problem:** Tooltip surfaces could blend into hovered content, making
labels difficult to distinguish.

**Solution:** Use the existing popover surface and foreground tokens for
clearer visual separation and theme-aware contrast.

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

**desktop/src/shared/ui/tooltip.tsx**
Updates shared tooltip colors to use higher-contrast semantic surface
and foreground tokens.

</details>

## Reproduction steps

1. Run the desktop app and hover a control that displays a tooltip.
2. Compare the tooltip against the content beneath it in both light and
dark themes.
3. Confirm the tooltip surface remains clearly distinguishable and its
label is readable.

## Screenshots

| Before | After |
| --- | --- |
| ![Tooltip using the lower-contrast secondary
surface](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6897/tooltip-before.png)
| ![Tooltip using the higher-contrast popover
surface](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6897/tooltip-after.png)
|

## Validation

- `pnpm build:e2e`
- `pnpm exec playwright test tests/e2e/tooltip-semantics.spec.ts
--project=smoke` — 4 passed
- `pnpm exec biome check src/shared/ui/tooltip.tsx
tests/e2e/tooltip-semantics.spec.ts`
- Pre-push desktop checks, typecheck, and unit tests
- `git diff --check`
- Full smoke project attempted; 345 of 1,222 tests passed without
failure before the 10-minute local execution limit.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
2026-08-26 20:56:21 +00:00
Salman Mohammed
8f3566ad9e Deduplicate ACP thread prompt context (#6706)
## Why
This is a small isolated follow-up to
[#6503](https://github.com/block/buzz/pull/6503): remove two proven
duplicate prompt fragments without adopting its broader framing
experiment or changing Buzz-native routing and identity behavior.

## What
- Remove parsed `parent=<id>` only when it exactly duplicates parsed
`root=<id>`; retain `root=<id>`, nested parents where parent differs
from root, raw event tags, event IDs, author identity, mention fields
and rules, and the explicit `--reply-to` instruction.
- Remove the full-history retrieval hint only when complete, untruncated
conversation or thread context is included. Thread context must include
its root and cover every event rendered in the batch; mixed-thread
batches and root-missing windows retain retrieval guidance. Context that
is absent, previously delivered, or truncated also retains retrieval
guidance, and the context header, count, and messages remain unchanged.

## Risk Assessment
Low — the change is confined to Buzz ACP prompt rendering and preserves
all routing, identity, event, and retrieval information outside the two
exact duplicate cases.

## Testing
- `cargo test -p buzz-acp` — 804 unit tests and 9 lifecycle integration
tests passed on the clean rerun
- Focused mixed-thread, same-thread, and missing-root prompt regressions
passed
- The timing-sensitive `keepalive_resets_idle_past_deadline` test passed
in isolation after one unrelated full-suite flake, then passed in the
clean full rerun
- `cargo clippy -p buzz-acp --all-targets -- -D warnings`
- `cargo fmt --all -- --check`
- `just file-size-check`
- Repository pre-push gate at `1b88793574e74e22f00a1fd19bfe9aa8e86150fa`
— file-size, branch, Rust unit-test, and Desktop Tauri lanes passed
- `git diff --check`

Generated with Codex

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
2026-08-26 16:48:40 -04:00
Jordan Mecom
01c87a350b Apply access policy when reusing channel agents (#6838)
Reused managed identities now apply their intended inbound-author policy
before attachment or startup. An explicit policy wins, then the linked
persona policy, then `owner-only`.

This covers both channel-agent provisioning and the direct composer
autocomplete path. If the policy update fails, attachment, startup, and
message send stop.

The production Playwright regression starts with a persona-linked agent
set to `anyone` with a stale allowlist, then verifies the update happens
before membership and startup and that the stored policy is
`owner-only`. A small resolver test covers explicit, persona, and
default precedence.

This closes the stale-policy entry described by [Project Loupe issue
#497](https://github.com/project-loupe/audit-buzz/issues/497). The
separate Welcome Team reuse path is outside this PR.

Checked with the 5,502-test desktop suite, `pnpm check`, `pnpm
typecheck`, `pnpm check:file-sizes`, and the focused Playwright composer
regression.

---------

Signed-off-by: Jordan Mecom <jm@squareup.com>
2026-08-26 13:22:09 -07:00
Taylor Ho
4f554b6385 feat(sidebar): prioritize unread DMs in overflow navigation (#6842)
**Category:** improvement
**User Impact:** Users can see which unread direct messages are waiting
offscreen and jump to the most relevant one first.

**Problem:** The sidebar overflow control treated every offscreen unread
conversation alike, so an unread DM could be hidden behind channel
activity with no visible clue about who was waiting.

**Solution:** Prioritize the first offscreen unread DM, preview up to
three senders with avatars, and keep the arrow, avatars, separator, and
message count intact while only the trailing phrase truncates in narrow
sidebars.

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

**desktop/src/features/sidebar/lib/useUnreadOverflow.ts**
Adds ordered DM preview data to unread-overflow calculations while
preserving the sidebar's existing visibility and direction rules.

**desktop/src/features/sidebar/lib/useUnreadOverflow.test.mjs**
Covers DM prioritization, ordering, offscreen filtering, and
channel-only fallback behavior.

**desktop/src/features/sidebar/ui/AppSidebar.tsx**
Passes unread DM previews into the lower overflow control and navigates
to the preferred DM before the next unread channel.

**desktop/src/features/sidebar/ui/MoreUnreadButton.tsx**
Renders the frontmost-first avatar stack, accessible DM target label,
middot separator, and message-count treatment.

**desktop/src/features/sidebar/ui/MoreUnreadButton.test.mjs**
Locks the three-avatar cap, stacking order, preferred target, fallback,
accessible copy, and truncation structure.

**desktop/src/shared/ui/UnreadPill.tsx**
Contains the primary pill and allows only its trailing label to
ellipsize under width pressure.

**desktop/src/shared/ui/UserAvatar.tsx**
Allows the sidebar preview stack to identify each rendered avatar in
regression coverage.

</details>

## Reproduction steps

1. Open enough sidebar conversations that unread items fall below the
visible area.
2. Leave one or more offscreen DMs unread, along with unread channels.
3. Confirm the bottom overflow pill shows up to three overlapping DM
avatars followed by a middot and the total new-message count.
4. Select the pill and confirm it navigates to the first offscreen
unread DM rather than the nearest unread channel.
5. Resize the sidebar from 420px through 300px to 220px and confirm the
arrow, avatars, middot, and full numeric count remain visible; only `new
messages` may ellipsize.
6. Repeat with channel-only unread overflow and confirm the control
keeps its channel fallback behavior without avatars.

## Screenshots

Built-UI matrix with 100px of visible space beyond the sidebar edge. All
images are hosted through `pr-screenshots`.

| State | 220px min | 300px default | 420px max | Result |
| --- | --- | --- | --- | --- |
| Channel-only · 13 | ![Channel-only · 13 at
220px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/01.png)
| ![Channel-only · 13 at
300px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/02.png)
| ![Channel-only · 13 at
420px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/03.png)
| Full label throughout. |
| Channel-only · 999 | ![Channel-only · 999 at
220px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/04.png)
| ![Channel-only · 999 at
300px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/05.png)
| ![Channel-only · 999 at
420px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/06.png)
| Full label throughout. |
| One DM only · 1 | ![One DM only · 1 at
220px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/07.png)
| ![One DM only · 1 at
300px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/08.png)
| ![One DM only · 1 at
420px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/09.png)
| Full label throughout. |
| One DM + channels · 99 | ![One DM + channels · 99 at
220px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/10.png)
| ![One DM + channels · 99 at
300px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/11.png)
| ![One DM + channels · 99 at
420px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/12.png)
| Full label throughout. |
| Two DMs only · 2 | ![Two DMs only · 2 at
220px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/13.png)
| ![Two DMs only · 2 at
300px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/14.png)
| ![Two DMs only · 2 at
420px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/15.png)
| Full label throughout. |
| Two DMs + channels · 47 | ![Two DMs + channels · 47 at
220px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/16.png)
| ![Two DMs + channels · 47 at
300px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/17.png)
| ![Two DMs + channels · 47 at
420px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/18.png)
| Full label throughout. |
| Three DMs + channels · 128 | ![Three DMs + channels · 128 at
220px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/19.png)
| ![Three DMs + channels · 128 at
300px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/20.png)
| ![Three DMs + channels · 128 at
420px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/21.png)
| 220px: `128 new messa…`; full at 300/420. |
| Three DMs + channels · 999 | ![Three DMs + channels · 999 at
220px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/22.png)
| ![Three DMs + channels · 999 at
300px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/23.png)
| ![Three DMs + channels · 999 at
420px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/24.png)
| 220px: `999 new mess…`; full at 300/420. |
| Three DMs + channels · 9,999 | ![Three DMs + channels · 9,999 at
220px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/25.png)
| ![Three DMs + channels · 9,999 at
300px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/26.png)
| ![Three DMs + channels · 9,999 at
420px](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6842/27.png)
| 220px: `9,999 new mes…`; full at 300/420. |

**Matrix result:** 27/27 states remain contained. Exactly 3/27 states
ellipsize—all are three-avatar cases at 220px. The arrow, all avatars,
middot, and complete numeric value remain visible; only the trailing
`new messages` phrase yields.

## Validation

- Desktop unit suite: 5,517 passed
- TypeScript: passed
- Biome on all seven changed files: passed
- Pre-push desktop checks, typecheck, tests, file-size, and branch-skew
gates: passed
- 27-state built-UI matrix capture: 27/27 contained; only the three
220px three-avatar states truncate, and only the trailing phrase
- `git diff --check`: passed

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@users.noreply.github.com>
Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
2026-08-26 12:59:14 -07:00
thomaspblock
de188ebf06 feat(projects): add agent and CLI project-home support (#6590)
## Summary
- inject bounded project-home identity and repository context into
managed agent sessions
- add project-aware CLI flows for creating projects, repositories,
issues, and related channels
- match project homes through the existing relay query surface, then
filter channel metadata client-side

This is Part 1 of the channel-first Projects stack. Part 2 contains
project creation and model foundations.

## Testing
- `cargo fmt --all -- --check`
- `cargo clippy -p buzz-cli -p buzz-acp --all-targets -- -D warnings`
- `cargo test -p buzz-cli -p buzz-acp` — 1,184 tests passed, 1 doc test
ignored
- full pre-push gate passed

## Post-Deploy Monitoring & Validation
- validate project-home agent context and project-aware CLI commands
against a staging relay
- healthy signals: project context matches the active channel, explicit
repo coordinates remain stable, and normal channels receive no project
block
- failure signals: cross-channel project context, duplicate project
creation, or commands targeting an unrelated repository; mitigate by
reverting this PR

---------

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
2026-08-26 12:09:06 -07:00
morgmart
22cdda441d feat(desktop): restore message quick reactions (#6892)
## Summary

- restore three learned quick-reaction shortcuts to the desktop message
action rail
- keep Add reaction (`SmilePlus`, `h-4 w-4`) immediately after shortcuts
and left of the restored divider
- preserve Reply, Copy link, More, custom emoji rendering, accessibility
labels, and responsive behavior
- hide shortcuts and the divider below `sm`, while keeping Add reaction
available

## Tests

At `24c4732c2341b846dccf4ae8fdc2455f700fc44d`:

- `pnpm exec biome check src/features/messages/ui/MessageActionBar.tsx
tests/e2e/message-copy-link.spec.ts tests/e2e/custom-emoji.spec.ts`
- `pnpm exec tsc --noEmit`
- `pnpm test` — 5,567 passed
- `pnpm build:e2e`
- `pnpm exec playwright test tests/e2e/message-copy-link.spec.ts
tests/e2e/custom-emoji.spec.ts --project=smoke` — 19 passed
- `pnpm exec playwright test tests/e2e/reaction-order.spec.ts
--project=smoke` — 2 passed
- pre-push: file-size-check, desktop-check, desktop-typecheck,
desktop-test

Vogue design review: **SHIP**.

## Screenshot

Restored desktop message action rail: three learned quick reactions, Add
reaction (`SmilePlus`), divider, Reply, Copy link, and More.

![Restored desktop message action
rail](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6892/restored-message-action-rail.png)

Signed-off-by: Rivet <a08d9a8418c7ff03afe19964724c8fd87bf1776ab9e9b9cafb8cc920edd02a6e@buzz.block.builderlab.xyz>
Co-authored-by: Rivet <a08d9a8418c7ff03afe19964724c8fd87bf1776ab9e9b9cafb8cc920edd02a6e@buzz.block.builderlab.xyz>
2026-08-26 10:32:46 -07:00
Salman Mohammed
f177f4909d Use paired tags for standing & per-turn context (#6701)
## Why
PR #6503 bundled a broader context compiler with several prompt-shape
and telemetry changes, making it difficult to isolate whether paired
XML-like top-level boundaries improve agent behavior. This keeps the
follow-up experiment focused on framing only.

## What
- Replace top-level standing and per-turn bracket headers with paired
tags while preserving inner section content and event/message framing
- Carry only metadata already present in dynamic headers as attributes,
such as thread counts, truncation, event type, and batch count
- Preserve every section body verbatim so the model executes the exact
bytes reviewed in Desktop
- Keep routing metadata, event content, batching, and delivery behavior
unchanged
- Teach Desktop diagnostics to parse the new framing while retaining
compatibility with archived bracket-framed sessions; ambiguous paired
boundaries render as the complete captured prompt

## Risk Assessment
Medium — the change is limited to agent prompt framing and diagnostic
parsing, but it affects both standing context and every dynamic agent
turn. Bodies and delivery behavior remain unchanged, and compatibility
parsing remains for prior sessions.

## References
- Follow-up to #6503, scoped only to the paired-tag framing hypothesis

## Update — 2026-08-24
- Addressed review feedback by removing entity encoding/decoding from
the execution and diagnostic paths.
- Added regressions for literal closing tags, placeholders,
entity-looking text, ampersands, and authored boundary whitespace.
- Verified `cargo test -p buzz-acp` (805 unit + 9 integration), Desktop
tests (5,412 before the main sync; 5,439 in the pre-push gate), Desktop
typechecking, and Tauri clippy/tests.
- Live local relay startup and message ingestion passed. The live ACP
reply step is blocked locally because installed Goose 1.0.23 no longer
provides the documented `goose acp` command and no alternate ACP adapter
is installed.

---

**Update Aug 25, 12:13 EDT:** Extended paired tags to per-turn top-level
sections.

- Converted context, conversation/thread context, event batches, and
steer/interrupt boundaries without changing their inner bodies.
- Preserved existing header metadata as attributes only; added no new
prompt metadata.
- Verified 806 Buzz ACP unit tests plus 9 integration tests, 5,441
Desktop tests, Desktop typechecking and formatting, and all differential
pre-push lanes.

---

**Update Aug 25, 13:58 EDT:** Addressed framing review feedback.

- Updated operational base instructions to reference the emitted
`<context>` section and added a regression guard against stale
`[Context]` instructions.
- Restored batched steer/interrupt cardinality in Desktop diagnostic
titles with focused coverage for both paths.
- Verified 807 Buzz ACP unit tests plus 9 integration tests, 5,442
Desktop tests, Desktop typechecking and formatting, and every
differential pre-push lane including Tauri clippy/tests.

---

**Update Aug 25, 15:37 EDT:** Hardened legacy transcript compatibility.

- Semantic standing-context parsing now requires paired framing at the
start and complete input consumption.
- Historical bracket-framed personas keep literal balanced tag examples
in their original section; malformed or mixed current framing displays
as the complete captured prompt.
- Added regressions for the reported legacy persona case and trailing
unframed text; verified 60 focused parser tests, 5,444 full Desktop
tests, formatting, and typechecking.

---

**Update Aug 25, 16:31 EDT:** Preserved slash-command prompt block
boundaries.

- Desktop now sets aside a valid connector-facing slash-command block
only when the following block begins recognized paired framing, parses
the semantic blocks normally, and retains the command as visible Prompt
metadata.
- Added an end-to-end transcript regression using the production
three-block ACP shape, including the user bubble, author pubkey, event
ID, and grouped prompt context.
- Verified 113 combined transcript tests, 5,445 full Desktop tests,
formatting, typechecking, and all applicable pre-push lanes.

Generated with Codex

---------

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
2026-08-26 12:05:41 -04:00
Wes
583af02299 fix(cli): preserve signatures in event reads (#6884)
## Summary
- preserve string Nostr `sig` values in normal `messages get`, `messages
thread`, `messages search`, and `feed get` output
- omit absent or non-string signatures instead of fabricating empty
values
- keep compact output at exactly `{id, content, created_at}` and update
CLI/managed-agent contract docs

## Validation
- `cargo test -p buzz-cli --lib` (367 passed at final head
`a2498faa0e07f90efc0ab0165606ba3b048327c7`)
- release candidate live-relay matrix: 49/49 normal events independently
recomputed to their NIP-01 IDs and passed BIP-340 verification; all four
compact commands preserved exact three-key output and ordering
- `git diff origin/main...HEAD --check`
- independent adversarial review: clear, no findings

## Compatibility
The normal JSON change is additive for valid relay events. In-repo
consumers use tolerant field lookup. Compact output is unchanged and
locked by regressions for both message and feed formatting.

Generated by Carl, an automated contributor working with Wes.

---------

Signed-off-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@buzz.block.builderlab.xyz>
Co-authored-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@buzz.block.builderlab.xyz>
2026-08-26 09:13:11 -06:00
Luke Tornquist
ef0d202568 refactor(db): finish replaceable event store extraction (#6777)
## Why
Finish the replaceable-event slice of [tracker
#2](https://github.com/TheSentinel454/buzz/issues/2) and [domain issue
#6](https://github.com/TheSentinel454/buzz/issues/6) without disturbing
the runtime/store boundary established by #6660 and #6668. PR #6700 has
merged; this PR now targets current main containing its replacement lock
and transaction observability.

## What
- Move `Db::replace_addressable_event`, its SQL, lock/transaction
instrumentation, and focused addressable/parameterized replacement tests
from `lib.rs` to `replaceable.rs`
- Preserve the transaction-required parameterized API, replacement
ordering, rollback semantics, mention indexing, and exactly one
datastore span per public operation
- Keep implementation and focused PostgreSQL tests co-located; follow-up
review removed the dedicated replaceable ownership source guard as low
value

## Stack
- Exact base: main at f249710331
- Exact head: codex/issue-6-finish-replaceable-store at
da018405cc
- Tracker: https://github.com/TheSentinel454/buzz/issues/2
- Domain: https://github.com/TheSentinel454/buzz/issues/6
- Test/span acceptance: https://github.com/TheSentinel454/buzz/issues/17
and https://github.com/TheSentinel454/buzz/issues/19

## Non-goals
- No SQL, schema, retry, timeout, lock ordering, transaction boundary,
or client-visible behavior changes
- No store traits, domain-handle redesign, `PgExecutor` migration, raw
pool accessor, new crate, or directory reorganization
- No changes to, retargeting of, or merge action on PR #6700

## Risk Assessment
Low. This is a mechanical ownership move with unchanged signatures and
SQL. The primary review risk is losing or nesting instrumentation,
covered by exact moved PostgreSQL tests and the cumulative
production-diff audit.

## Blox Verification
Author workstation: `buzz-tornquist-issue-2-store-stack` (`2046520`),
exact head `2de5444e14606ce6912d1bb932bc88bffa379a77`.

- `cargo fmt --all --check` — passed
- `cargo clippy -p buzz-db -p buzz-relay --all-targets -- -D warnings` —
passed
- `cargo test -p buzz-db --quiet` — 108 passed, 200 ignored; both
integration source guards passed
- Native PostgreSQL: `cargo test -p buzz-db replaceable::tests:: --
--ignored --test-threads=1` — 11 passed
- `cargo test -p buzz-relay --lib
api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo --
--exact --test-threads=1` — passed
- Full serial relay suite was also exercised: 908 tests passed but the
existing load-sensitive mesh demo test returned 504 under suite load,
matching PR #6700's documented baseline; it passed in isolation and this
diff does not touch that subsystem

Independent exact-head Blox review: `buzz-tornquist-pr-6777-review`
(`2047422`) at `2de5444e14606ce6912d1bb932bc88bffa379a77`.

- Review findings — none (critical, important, or minor)
- `cargo fmt --all --check` and the ownership guard — passed
- `cargo clippy -p buzz-db -p buzz-relay --all-targets -- -D warnings` —
passed
- `cargo test -p buzz-db --quiet` — 108 passed, 200 ignored; source
guards passed
- Native PostgreSQL moved suite — 11 passed
- `cargo test -p buzz-relay --lib` — 909 passed, 48 ignored

Generated with Codex

## Superseded pre-comment restack verification

PR #6700 merged before publication completed. This layer was restacked
onto current main through the exact parent named above; the final
cumulative tip is 2ddcc8a29f95c8c8c9cb87bb6cf59335f2190fae. Cumulative
author gates passed: formatting and diff checks; buzz-db and buzz-relay
all-target clippy with -D warnings; DB lib 111 passed / 200 ignored;
ownership 22/22; observability 1/1; the full isolated PostgreSQL domain
matrix; and relay lib 910 passed / 49 ignored.

- Workstation: `buzz-tornquist-pr-6777-final-review` (`2057617`), fresh
shallow checkout
- Base: `f24971033178926153b49d320bd876d15d9cb2bf`
- Head: `ffbeaaf00810aa359ab85818ff4820f92263e45f`
- Findings: none

Reviewed `base..head` for the replaceable-store extraction. The
addressable replacement SQL, stale/duplicate outcomes, advisory-lock
ordering, transaction rollback/commit boundaries, mention-index
atomicity, and public `Db` signature are retained in `replaceable.rs`.
The PR #6700 transaction timer remains around the same logical
transaction through `TransactionTimer::observe`, and the public
operation has exactly one datastore span. Focused addressable and
parameterized tests moved with the implementation; the ownership guard
excludes duplicates from `lib.rs`.

Verification: format and diff checks passed; `buzz-db --all-targets`
clippy passed with `-D warnings`; DB lib tests passed (111 passed, 200
PostgreSQL tests ignored); ownership (1/1) and observability (1/1)
guards passed; all 11 replaceable PostgreSQL tests passed on native
PostgreSQL 17 with migrations 1-32 successful; relay lib test target
compiled successfully. Final worktree was detached at the exact head and
clean.

Complete evidence archive SHA-256:
`e37f92bbde4e2343196085095b3093b1bc78196be58bc86826325e0e1639d7ea`.

## Comment-addressed restack

Review follow-up removed the dedicated
`replaceable_store_has_single_ownership` source test as requested. No
production code changed.

- Exact base: `f24971033178926153b49d320bd876d15d9cb2bf`
- Exact head: `da018405cc83605362125c2d5e5a3f91492431ac`
- Final cumulative tip: `6fa2f104d42c6ba85bdf62e7ccb74ceaf4a84f67`
- Focused PR verification: formatting and diff checks passed; strict
`buzz-db`/`buzz-relay` Clippy passed; DB lib 111 passed / 200 ignored;
all 11 replaceable PostgreSQL tests passed.
- Cumulative Blox gate: formatting and diff checks; strict
`buzz-db`/`buzz-relay` Clippy; DB lib 111 passed / 200 ignored;
ownership 21/21; observability 1/1; every moved PostgreSQL test; relay
lib 910 passed / 49 ignored.
- Independent re-review at this exact head: no findings; fresh
exact-parent/head Blox review passed fmt/diff, strict `buzz-db` Clippy,
DB lib 111 passed / 200 ignored, manual one-owner/one-span replacement
checks, observability, 11 replaceable PostgreSQL tests, and relay
compilation.

---------

Signed-off-by: tornquist <tornquist@squareup.com>
2026-08-26 10:33:09 -04:00
Luke Tornquist
dcee330b5c Fix Admin feedback filter overflow (#6825)
## Why
Long community hostnames can make the native Community select exceed the
Admin feedback filter grid, pushing the Received and Status controls
outside the filter bar and creating horizontal overflow.

## What
- Allow filter labels and selects to shrink within their grid tracks
- Add a browser regression test covering an unusually long community
hostname

## Risk Assessment
Low — this is limited to Admin feedback filter sizing and preserves the
existing desktop and mobile grid layouts.

## Before
The Community select expands past the filter bar and pushes the Received
and Status controls outside it.

![Admin feedback filters before the
fix](https://raw.githubusercontent.com/block/buzz/f6c2f6d2cb1386feecf4dd6ac2849ab263b1b1a4/pr-6825--01-before.png)

## After
Long community names remain contained, keeping all four controls inside
the filter bar.

![Admin feedback filters after the
fix](https://raw.githubusercontent.com/block/buzz/f6c2f6d2cb1386feecf4dd6ac2849ab263b1b1a4/pr-6825--02-after.png)

## References
- Verified on Blox at the local checkout revision: Admin checks passed
and all 9 Playwright tests passed

Generated with Codex
2026-08-26 10:05:01 -04:00
Matt Toohey
db514b153a fix(desktop): stop pulsing addressed agents on send (#6873)
## Summary

- Stop replaying the selected-agent pulse when a remembered agent
remains addressed across a send.
- Preserve the existing pulse for genuine selections and shake feedback
for failed sends.

### Related issue

Reported in Buzz:
buzz://message?channel=2745947d-f905-4f31-8a3f-fc4485d3c36c&id=0cffab376186be9cc4e7f29027352cc1c7941aead48b3fed948f36181f4a59af

### Testing

- `cd desktop && pnpm test` (5,556 passed)
- `cd desktop && pnpm typecheck`
- `cd desktop && pnpm check`
- `cd desktop && pnpm build:e2e`
- `cd desktop && pnpm exec playwright test
tests/e2e/persistent-agent-audience.spec.ts --grep 'always-mentioned
agents remain selected|failed always-mentioned send'` (2 passed)

This changes animation timing only; the regression is covered by
Playwright assertions on the pulse and shake versions, while a static
screenshot is unchanged.

Signed-off-by: Matt Toohey <contact@matttoohey.com>
2026-08-26 21:46:19 +10:00
Matt Toohey
4f529a45f2 fix(desktop): prioritize sidebar channel status (#6861)
## Summary

- Treat the sidebar temp-channel icon as a fallback behind working,
mute, numeric unread, and thread-unread indicators.
- Keep the header affordance and the sidebar temp icon when it is the
only row status.
- Extend the ephemeral-channel browser scenario across working, idle,
collapsed-sidebar, and unread states.

## Before / After

| Before: five temp icons compete with duration and unread states |
After: all five duration and unread states remain clear |
| --- | --- |
| ![Before: five long temporary channel rows each show a temp icon, long
duration, and unread
state](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6861/temp-icon-before-five.png)
| ![After: the same five long temporary channel rows retain long
durations and unread states without temp
icons](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6861/temp-icon-after-five.png)
|

## Testing

- `node --import ./test-loader.mjs --experimental-strip-types --test
"src/**/*.test.mjs"` (5,556 passed)
- `tsc --noEmit -p desktop/tsconfig.json`
- `biome check desktop/src/features/sidebar/ui/SidebarSection.tsx
desktop/tests/e2e/channels.spec.ts`
- `vite build --mode e2e`
- Isolated Playwright run of `create ephemeral stream shows sidebar and
header affordances` (1 passed)

## Duplicate check

No matching open PR or issue found for `ephemeral sidebar icon`.

Signed-off-by: Jitter <d14dfe033ef0f809866f9f984de04821b0d900d7652fd85a54776ee40ca3a68f@buzz.block.builderlab.xyz>
Co-authored-by: Jitter <d14dfe033ef0f809866f9f984de04821b0d900d7652fd85a54776ee40ca3a68f@buzz.block.builderlab.xyz>
2026-08-26 20:19:02 +10:00
Matt Toohey
e8172b5bac feat(desktop): hyperlink selected composer text on link paste (#6684)
## Summary

Pasting a link while composer text is selected now turns that text into
a hyperlink instead of replacing it — the behavior people expect from
every other editor.

- `resolveExactLinkPaste` recognizes a clipboard payload that is exactly
one supported link (bare or `<…>`-wrapped `http(s)`, or a Buzz deep
link, canonicalized through the existing composer-link attribute
resolver) and returns the href to apply.
- The paste handler applies that href as a `link` mark over the current
selection, collapses the caret to the end of the newly linked range,
clears stored marks so subsequent typing isn't linked, and refocuses the
editor.

Edge cases handled:

- **No link mark available / selection can't carry marks** — falls
through to the existing replace-with-node path rather than silently
dropping the paste.
- **Selection contains a composer message-link chip** — keeps the old
replacement behavior instead of nesting a link over a chip.
- **Whitespace in the clipboard payload** — `unwrapExactHttpLink` now
rejects any text containing whitespace, so multi-line or "text plus URL"
pastes are not treated as an exact link.
- **Empty selection (caret paste)** — unchanged.
- **Idempotent re-paste** of an already-wrapped link is consumed rather
than doubling up.

## Testing

`desktop/src/features/messages/lib/composerMessageLinkNode.test.mjs`
gains coverage for exact-link resolution (per-shape accept/reject cases,
Buzz canonicalization) and for the paste handler: linking selected text,
canonicalizing Buzz links over a selection, idempotent wrapped-link
paste, fall-through when the selection can't carry marks, all-selection
collapse, chip-containing selections, and preserved caret-paste
behavior.

Full pre-push gate ran green on this branch: `file-size-check`,
`desktop-check`, `desktop-typecheck`, and `desktop-test` (5425 tests, 0
failures).


## Demo

Pasting an `https://` URL over selected composer text turns the selected
words into a hyperlink (instead of replacing them); text typed
immediately after is not linked:


https://github.com/user-attachments/assets/bbc93574-dd9d-4b1b-bad0-53307b6e887c

---------

Signed-off-by: Matt Toohey <contact@matttoohey.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
2026-08-26 15:19:44 +10:00
Will Pfleger
52621c09be chore(release): release Buzz Desktop version 0.5.20 (#6839)
## Buzz Desktop release v0.5.20

- **Frozen main:** `8471049c430073474939336dfc6aa98272bc8762`
- **Reviewed candidate:** `95154bee4034ca7a40b33095c2ddbde8c9aa1614`
- **Previous desktop release:** `desktop-v0.5.19`
- **Proposed immutable tag:** `desktop-v0.5.20`

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-08-25 23:46:04 +00:00
klopez4212
8471049c43 feat(desktop): add KLIPY GIF search to composers (#5554)
## Summary

- Supersedes #1913 with a KLIPY-hosted URL implementation.
- Adds KLIPY GIF search and trending results to desktop message and
forum composers.
- Keeps selected GIFs hosted by KLIPY; Buzz stores only the external URL
and media metadata (no imeta tag, since relays only accept hash-backed
local `/media/` entries).
- Aligns the Emoji/GIF picker with Buzz's standard segmented control,
theme surfaces, and motion behavior.

## Relay-to-provider boundary

- The relay proxies KLIPY search/share so the `BUZZ_KLIPY_API_KEY` never
reaches the desktop; the key stays server-side behind a redacted `Debug`
impl.
- The dedicated GIF `reqwest` client sets `redirect::Policy::none()`.
Because the API key rides in the request path, following a provider
`3xx` could replay a key-bearing URL to an attacker-chosen host (an
SSRF/key-disclosure primitive). With redirects disabled, a `3xx` returns
as a non-success status that the handlers map to a generic `502`; the
`Location` target is never read or forwarded.
- Admission reuses the established NIP-98, membership, replay, and
per-pubkey rate-limit gates, with an upstream response-size cap and
allowlisting so KLIPY error bodies never cross the relay boundary.

## Accessibility

- Under `prefers-reduced-motion: reduce`, the picker grid renders a
static provider poster (a normalized `jpg` asset) instead of the
animated preview, or a named static placeholder when no poster is
available. It reacts to preference changes while mounted.
`no-preference` keeps the animated preview.
- Selected GIFs carry their title through `ImetaMedia`'s `displayLabel`,
so the composer thumbnail, preview dialog, editor, lightbox, and remove
control all derive one non-empty accessible name instead of an empty
`Attachment ` label. Ordinary hashed uploads keep their existing
hash-derived names.

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Signed-off-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
2026-08-25 19:09:55 -04:00
Taylor Ho
e8cd7516e6 fix(desktop): respect automatic mention preference after send (#6837)
**Category:** fix
**User Impact:** Disabling automatic agent mentions now keeps one-time
agent mentions out of the next message draft.

**Problem:** A successfully sent inline agent mention was treated as
eligible for post-send restoration even when **Automatically mention
agents** was disabled, so the agent immediately reappeared in the
composer. **Solution:** Gate the send-success restoration path on the
live preference while leaving explicitly pinned agents and enabled
automatic mentions unchanged.

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

**desktop/src/features/messages/ui/MessageComposer.tsx**
Skips the automatic post-send mention restoration path when the
preference is disabled.

**desktop/tests/e2e/persistent-agent-audience.spec.ts**
Reproduces disabling the toggle before sending an inline agent mention
and verifies the next composer is empty while the outgoing mention still
reaches the agent.

</details>

## Reproduction steps

1. Enable **Automatically mention agents** in the composer mention
options.
2. Disable it again.
3. Compose and send `@Agent test` using the inline mention picker.
4. Confirm the message still mentions the agent, but the cleared
composer does not repopulate `@Agent`.
5. Re-enable the preference and repeat; confirm the mention is restored
for the next message.

## Testing

At `a179a2d00a6ac5cf0bc4a1c7c1ef3d5dfa9d0875`:

- Full desktop unit suite: 5,501 passed.
- Desktop check and typecheck passed in the pre-push hooks.
- Desktop file-size ratchet passed in the pre-push hooks.
- Full `persistent-agent-audience.spec.ts` Playwright file: 17 passed,
including preference off (empty composer), preference on (restored
mention), and the existing address-undo journey.

## Visual validation

| Automatic mentions off | Automatic mentions on |
| --- | --- |
| After sending a one-time mention, the outgoing message is preserved
and the next composer stays empty. | After sending, the agent remains
addressed and is restored in the next composer. |
| ![Automatic mentions disabled: empty composer after
send](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6837/disabled-after-send.png)
| ![Automatic mentions enabled: agent restored after
send](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6837/enabled-after-send.png)
|

<details>
<summary>Original regression</summary>

With automatic mentions disabled, sending `@Alia test` still repopulated
`@Alia` in the cleared composer.

![Original regression: the sent agent reappears in the
composer](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6837/auto-mention-regression-before.png)

</details>

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
2026-08-25 15:59:38 -07:00
Will Pfleger
820a858997 fix(release): attribute desktop candidates to the operator (#6831)
The desktop release tooling hardcoded one contributor's personal
identity into every release candidate commit.
`scripts/prepare-desktop-release.sh` committed the candidate with a `git
-c user.name='Wes' -c user.email='wesbillman@users.noreply.github.com'`
override, and `scripts/desktop_release.py` `validate` required the
candidate author to be exactly `Wes
<wesbillman@users.noreply.github.com>` plus a matching `Signed-off-by`
trailer. That leaked from Wes's working setup into the validation
contract in #3568, so a release cut by any other operator was falsely
attributed to and signed off by Wes (as happened on #6828).

## Change

- `prepare-desktop-release.sh`: drop the `-c` identity overrides so `git
commit -s` uses the operator's own configured identity to author and
sign off the candidate. The automation `Co-authored-by` trailer is
unchanged.
- `desktop_release.py` `validate`: replace the exact-Wes checks with
structural ones — the commit author must be non-empty, the body must
contain a `Signed-off-by` trailer whose name and email match the commit
author (honest DCO), and the existing automation `Co-authored-by` regex
check stays. Failure messages remain specific.
- `test-desktop-release-candidate.sh`: the fixture candidate now commits
under the harness's own identity, and a new negative case rewrites the
author to a mismatched identity and asserts the validator rejects it.

Release authorization is bound to the merged PR via the GitHub API in
`scripts/verify-desktop-release-merge.sh`, never the commit author
field, so this does not weaken the trust model. `RELEASING.md` and
`.github/workflows/desktop-release-candidate.yml` reference no author
identity and need no change.

Verified locally: `scripts/test-desktop-release-candidate.sh` passes,
including the new sign-off/author-mismatch rejection.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-25 18:50:58 -04:00
Will Pfleger
cae7f826b3 fix(ci): check out source in docker.yml merge job (#6833)
## Problem

Every `docker.yml` run on `main` has failed since
[#6781](https://github.com/block/buzz/pull/6781) merged. That PR added a
"Create deployment eligibility predicate" step to the `merge` job, which
runs `jq` against
`$GITHUB_WORKSPACE/scripts/create-deployment-eligibility-predicate.jq`.
But the `merge` job has no `actions/checkout` step — the workspace is
empty, so `jq` can't open the file and exits `2` (`jq: Could not open
... No such file or directory`). The `build` and `qualify` jobs each
check out the source; `merge` never needed one until this step was
added.

## Fix

- `.github/workflows/docker.yml`: add `actions/checkout` (same pinned
SHA as the other jobs, `df4cb1c` / v6.0.3) as the first step of the
`merge` job.
- `scripts/test-relay-image-eligibility-workflow.sh`: guard the
regression by asserting the `merge` job checks out the source before
building the predicate.

## Verification

CI cannot exercise the `merge` job on a pull request — the job is gated
`if: github.event_name != 'pull_request'`, so it only runs on push to
`main`. The proof is the root cause (missing checkout for a step that
reads a repo file) plus the eligibility workflow test:
`scripts/test-relay-image-eligibility-workflow.sh` passes with the fix
and fails with the new guard's specific message (`merge job must check
out the source before building the eligibility predicate`) when the
checkout is removed.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-08-25 18:50:47 -04:00
Will Pfleger
7a1b7d8e09 chore(release): release Buzz Desktop version 0.5.19 (#6828)
## Buzz Desktop release v0.5.19

- **Frozen main:** `ee6ca5fa28bce04dfecb6717de65b08a57f2ac47`
- **Reviewed candidate:** `ab691bcdaeccaa6698a2199beaba3f6e93daae81`
- **Previous desktop release:** `desktop-v0.5.18`
- **Proposed immutable tag:** `desktop-v0.5.19`

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: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
2026-08-25 17:36:57 -04:00
Jordan Mecom
ee6ca5fa28 Remove public relay signing key fallback (#6729)
Require `BUZZ_RELAY_PRIVATE_KEY` on every relay startup and remove the
shared public fallback key completely.

For local development, `just bootstrap` now generates a random key once
in the gitignored `.env` file. Re-running bootstrap preserves that key,
so relay-authored events retain the same identity across restarts. The
local relay recipes reload `.env`, while isolated CI and E2E launchers
pass explicit per-run keys.

Startup fails immediately when the key is missing or invalid, before
connecting to Postgres or Redis. Deployed relays continue to receive
their stable key from the existing chart-managed secret.

Validated with:

- `./scripts/test-ensure-local-relay-key.sh`
- `cargo test -p buzz-relay --bin buzz-relay`
- `cargo clippy -p buzz-relay --bin buzz-relay -- -D warnings`
- `cargo fmt --all -- --check`
- `shellcheck scripts/ensure-local-relay-key.sh
scripts/test-ensure-local-relay-key.sh`

Signed-off-by: Jordan Mecom <jm@squareup.com>
2026-08-25 13:57:23 -07:00
Wes
22f32c99e2 docs(nest): make commit attribution policy-neutral (#6707)
## Summary

- replace the generated Nest's unconditional human author/sign-off rules
with portable guidance that separates authorship, material
co-authorship, DCO certification, and cryptographic signing
- defer attribution to repository-local policy, forbid inferred or
guessed identities, and require inspection of every outgoing commit
- bump the Nest template version so existing installations refresh, with
regression coverage for fresh generation and upgrade preservation

### Related issue

None found. Related runtime identity work exists in #6177, but this PR
is intentionally limited to the generated Nest guidance and its refresh
behavior.

### Testing

- `bin/just desktop-tauri-clippy`
- `bin/just desktop-tauri-test`
- `bin/just file-size-check`
- `git diff --check`
- pre-push hooks: `push-head-scope`, `branch-skew`, `file-size-check`,
and `desktop-tauri-checks`

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
2026-08-25 14:35:53 -06:00
Taylor Ho
b58de7cfa7 fix(desktop-messages): preserve inline agent mentions with persistent addressing (#6793)
**Category:** fix
**User Impact:** Users can place `@Agent` mentions inline anywhere in a
message—even when that agent is also persistently addressed—so separate
agents can receive separate instructions in the same message.

**Problem:** Selecting an agent from the composer mention picker could
turn the selection into persistent addressing instead of leaving an
inline `@Agent` at the cursor. That made persistent addressing and
inline composition mutually exclusive: once the persistent behavior took
over, users lost the clear `@Agent A do X, @Agent B do Y` message
structure they previously had.

**Solution:** Make inline mentioning and persistent addressing
independent behaviors:

- Ordinary mention selections always insert `@Agent` inline at the
current cursor position.
- This remains true when the agent is already persistently addressed;
the persistent audience never blocks or replaces an inline mention.
- If **Automatically mention agents** is enabled, a successfully sent
inline mention may additionally make that agent persistent for later
messages. Existing saved preferences remain respected.
- The dedicated automatic-mention control and primary+Shift+Enter
shortcut continue to add or remove persistent addressing directly.
- For users without a saved preference, **Automatically mention agents**
now defaults off.

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


**desktop/src/features/messages/lib/autoPinMentionedAgentsPreference.test.mjs**
Updates preference coverage for the default-off behavior while
preserving explicit saved choices.


**desktop/src/features/messages/lib/autoPinMentionedAgentsPreference.ts**
Defaults automatic post-send persistence off when no valid preference
exists.

**desktop/src/features/messages/ui/useAgentAddressLockPicker.test.mjs**
Verifies ordinary picker selections insert inline mentions without
changing or pulsing the persistent audience, including when the selected
agent is already persistently addressed.

**desktop/src/features/messages/ui/useAgentAddressLockPicker.ts**
Keeps ordinary mention selection on the existing inline insertion path
while preserving the separate persistent-address controls and shortcut.

**desktop/tests/e2e/persistent-agent-audience.spec.ts**
Covers separately targeted inline instructions to two agents, signed
outgoing recipients, default-off persistence, and explicit opt-in
persistence scenarios.

</details>

## Reproduction steps

### Inline mentions without persistence

1. Open a channel with at least two available agents and leave
**Automatically mention agents** disabled.
2. Use the composer mention picker to insert the first agent, type an
instruction, then insert a second agent and type a different
instruction.
3. Confirm the draft reads like `@Agent A review this, @Agent B test
that` and neither agent appears in the persistent addressed-agent
controls.
4. Send the message and confirm both agents are recipients while neither
remains persistently addressed for the next draft.

### Inline mentions with persistence

1. Enable **Automatically mention agents**, then mention an agent inline
and send successfully.
2. Confirm that agent becomes persistently addressed for later messages.
3. In a new draft, select the same agent from the mention picker again.
4. Confirm a new inline `@Agent` is inserted at the cursor while the
agent remains persistently addressed.
5. Confirm the dedicated automatic-mention control and
primary+Shift+Enter shortcut can still add or remove persistent
addressing directly.

## Demo

- **Before:** Persistent addressing could consume an ordinary picker
selection, preventing users from placing that agent inline in the
message.
- **After:** Ordinary selection always produces an inline `@Agent`;
persistence is a separate optional behavior that can coexist with inline
mentions.

### Related issue

N/A — reported through the Buzz feature room.

### Testing

At `a4579c0a666f3644abe44271614186d7b9356bf5`:

- Post-push hooks passed desktop check, desktop typecheck, and the full
desktop unit suite.
- The two persistence-focused Playwright smoke scenarios passed after
explicitly opting into **Automatically mention agents**.
- The one-time multi-agent targeting and persistent-address shortcut
Playwright scenarios passed on the feature changes before the final
rebase; CI is validating the rebased branch.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
2026-08-25 13:32:09 -07:00
Luke Tornquist
f249710331 Qualify canonical relay images for staged delivery (#6781)
## Why

Phase 1 of progressive delivery needs an explicit eligibility boundary
before deployment automation can safely consume relay artifacts. Today
the canonical Docker workflow can publish independently of the same-SHA
CI result, the relay does not expose its source/build identity, and the
Helm chart only renders mutable tags.

The existing `ghcr.io/block/buzz-staging-dev` workflow in #6709 remains
a manual pre-merge preview lane. It is deliberately not a canonical
promotion input.

## What changed

- Compile the full source SHA and GitHub Actions build identity into
canonical relay binaries and expose it at `/_status`.
- Gate creation of tagged multi-architecture manifests on the latest
exact-SHA `CI` push run succeeding on `main` or `release`.
- Preserve the signed SLSA provenance and add a signed, release-only
deployment-eligibility predicate with source, build run, qualifying CI
run, and exact compatible Buzz chart version.
- Keep failed or pending builds untagged and without an eligibility
predicate; preview-package artifacts are excluded by package and signer
contract.
- Add optional `image.digest` support to Buzz chart `0.1.8`, retaining
the existing tag fallback.
- Document verification and the intentional boundary between image/chart
compatibility and backwards-compatible database migrations.

## Risk and rollout

Canonical tag publication now waits for same-SHA CI and fails closed
when the newest matching run fails. Per-architecture blobs may remain
untagged after a failed qualification; they are not promotion inputs.
Debug manifests are also CI-gated but do not receive the release
eligibility predicate.

There are no migration or schema-orchestration changes. The companion
BuilderBot infrastructure PR will pin a separately verified canonical
main digest and expose a read-only running-deployment inspection
command. Signed eligibility is available only for artifacts published
after this workflow lands.

## Verification

- Blox: workflow contract fixtures, including stale-green rejection and
predicate structure
- Blox: `actionlint` for `ci.yml` and `docker.yml`
- Blox: 46 Helm unit tests, chart lint, and production/quickstart render
fixtures
- Blox: relay status unit test, workspace Rust formatting, and workspace
all-target clippy
- Live evidence: exact-SHA CI selector chose successful run
`32857345153` for source `9d1e4b257657f382d3111ce748f3da8d063b7671`
- Existing canonical digest provenance verified with `gh attestation
verify`

## References

- #6709 — developer-preview precursor; intentionally separate from
canonical eligibility
- `docs/deployment-identity.md`

*Generated with Codex*

---------

Signed-off-by: Luke Tornquist <tornquist@squareup.com>
chart-v0.1.8
2026-08-25 14:44:49 -04:00
Taylor Ho
7ba1197aa6 feat(desktop): persist agent addressing across composer messages (#6714)
**Category:** improvement
**User Impact:** Agent addressing now stays visible, editable, and
consistent across messages, so users can keep talking to the same agents
without rebuilding the audience for every send.

**Problem:** Agent addressing was transient and easy to lose track of.
Inline mention text, persistent audience state, restored drafts, and
sent-message rendering could drift apart, making it unclear who would
receive the next message.

**Solution:** The composer now treats addressed agents as a persistent
audience represented by ordinary editable mention chips and a
synchronized compact avatar tray. Eligible agent mentions can become
persistent immediately, addressing survives sends without flashing
empty, and removing or explicitly unpinning an agent updates that state
without disturbing unrelated draft text. `Cmd+Shift+M` / `Ctrl+Shift+M`
provides a fast way to toggle the current recipient or select the best
eligible agent.

- Promotes eligible inline agent mentions into persistent addressing
when the preference is enabled; human mentions remain unchanged.
- Keeps inline chips, restored drafts, persistent recipient state, and
the avatar tray synchronized.
- Clears an agent's persistent address when its final chip is removed,
while explicit unpinning preserves unrelated draft text.
- Restores persistent mentions optimistically during send and restores
the full draft on failure, avoiding an empty-composer flash.
- Deduplicates inline mentions and tag-backed address prefixes in sent
messages.
- Toggles the current addressed agent with `Cmd+Shift+M` /
`Ctrl+Shift+M`, or selects the best eligible agent when none is
addressed.
- Ranks shortcut selection by recent explicit stream mentions, then
active/member/runnable/stable fallbacks; DM participant fan-out does not
affect recency.
- Adds focused avatar feedback for entry/exit, send bounce, and failure
shake, with reduced-motion and narrow-layout support.
- Applies the same behavior to main and thread composers while
preserving draft restoration, channel switching, and send-race
correctness.

### Screenshots

#### Light theme
The compact tray keeps persistent recipients visible alongside editable
mention chips.

![Light theme persistent agent
addressing](https://raw.githubusercontent.com/block/buzz/f34e87a8c083d36abf20422ac9d8ca2c43a49af6/pr-6714--01-light-theme.png)

#### Dark theme
Persistent addressing remains clear and legible in the dark composer
treatment.

![Dark theme persistent agent
addressing](https://raw.githubusercontent.com/block/buzz/f34e87a8c083d36abf20422ac9d8ca2c43a49af6/pr-6714--02-dark-theme.png)

### Reproduction steps

1. Open a channel with an eligible agent and enable persistent agent
addressing.
2. Insert an agent mention and confirm it appears as a normal editable
chip while the compact avatar tray reflects the same persistent
recipient.
3. Send several messages and confirm the agent remains addressed, the
mention restores without an empty-composer flash, and sent messages do
not duplicate the addressed agent.
4. Remove the agent's final mention chip and confirm its persistent
address clears. Add unrelated draft text, explicitly unpin the agent,
and confirm that text remains intact.
5. Press `Cmd+Shift+M` on macOS or `Ctrl+Shift+M` on Windows/Linux.
Confirm it toggles the current addressed agent or, when none is
selected, chooses the most relevant eligible agent.
6. Verify recent explicit channel/thread mentions influence shortcut
selection, while DM participant fan-out does not.
7. Force a failed send and confirm the addressed avatars shake and the
full draft is restored; confirm successful sends use the subtler bounce
feedback.
8. Repeat in the thread composer, after switching channels, in dark
theme, with reduced motion enabled, and at a narrow composer width.

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

**desktop/src/features/channels/ui/ChannelPane.tsx**  
Feeds recent explicit agent mentions into the composer for contextual
shortcut selection.

**desktop/src/features/messages/lib/getVisibleAgentAddressPubkeys.ts**
and tests
Show tag-backed addressed agents only when they are not already
represented by inline mention text.

**desktop/src/features/messages/lib/mentionCandidates.ts**,
**mentionRanking.ts**, **mentionSuggestionMapping.ts**,
**recentMentionPubkeys.ts**, and tests
Model agent eligibility and activity, derive explicit mention recency,
and rank the best shortcut recipient.

**desktop/src/features/messages/lib/useActiveAgentPubkeys.ts**,
**useDefaultAgentSuggestion.ts**, and **useMentions.ts**
Expose active-agent and recent-recipient context to mention consumers.

**desktop/src/features/messages/ui/ComposerAddressControls.tsx** and
tests
Render the compact persistent-recipient tray with enter, exit, send,
failure, layout, and reduced-motion states.

**desktop/src/features/messages/ui/MessageComposer.tsx** and
**MessageComposer.types.ts**
Connect editable mention text, persistent addressing, shortcut
selection, draft restoration, and send feedback.

**desktop/src/features/messages/ui/MessageRow.tsx**  
Avoids duplicating addressed agents already rendered as inline mentions.

**desktop/src/features/messages/ui/MessageThreadPanel.tsx**  
Brings the same recency and persistent-addressing behavior to thread
composers.

**desktop/src/features/messages/ui/useAgentAddressLockPicker.ts**,
**useAutoPinMentionedAgents.ts**, **useAlwaysAddressShortcut.ts**,
**useMentionSendFlow.ts**, and tests
Synchronize mention chips with persistent audience state, promote
eligible mentions, handle shortcut toggling/default selection, and
preserve recipients through send success or failure.

**desktop/src/features/messages/ui/useComposerPasteHandler.ts** and
**useMentionSendFlow.types.ts**
Keep the composer implementation focused by extracting paste and
send-flow boundaries.

**desktop/src/shared/lib/keyboard-shortcuts.ts**  
Documents `Cmd+Shift+M` / `Ctrl+Shift+M` as the agent-address shortcut.

**desktop/tests/e2e/persistent-agent-audience.spec.ts**  
Exercises persistence, selection, removal, sends, failures, restored
drafts, themes, reduced motion, and narrow layouts.

</details>

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
2026-08-25 18:44:25 +00:00
Kalvin C
a526dca9bc feat: navigate images across message threads (#6705)
## Summary
- navigate across image occurrences that are currently rendered inside
the nearest explicit open-thread DOM scope
- preserve rendered DOM order, duplicate occurrence identity,
hidden-spoiler exclusion, preview-image entry parity, and the exact
thumbnail return target
- keep adjacent timeline messages and unrelated surfaces as separate
galleries
- make Copy and Download follow the current gallery item, including when
a link-preview image opens the gallery first and navigation reaches
Markdown media

## Scope contract

This is intentionally a **rendered-media gallery only**. Gallery
membership comes from mounted image triggers under the nearest explicit
thread scope. Collapsed or unloaded descendants are excluded until the
thread UI renders them. There is no canonical thread fetch, descendant
projection, depth override, or synchronous message reparse in this
change.

`ChannelPane.tsx` remains unchanged from `main`;
`useSearchHighlightProps` and both timeline/thread prop spreads are
preserved.

## Verification
- rebased onto `origin/main` at
`113a33b7e49b7173ee1767c49ef2f49c63803034`
- `git diff --check`
- differential file-size check
- full Desktop check (Biome plus text/pubkey policy checks; only
pre-existing informational diagnostics)
- Desktop TypeScript typecheck
- Desktop production build and rebuilt E2E build
- full Desktop JS units: `5,476/5,476` passed
- full Desktop Tauri workspace tests passed (one documented native
performance test ignored)
- rebuilt image gallery Playwright spec: `14/14` passed
- search-highlight smoke cases: `5/5` passed
- reduced diff: 6 files, `+417/-41`; production `+78/-41`, E2E `+339/-0`

The four added E2E cases cover cross-message thread navigation and
return targeting, rendered-only exclusion of collapsed descendants,
preview-first current-item actions, and adjacent-message isolation.

---------

Signed-off-by: Kalvin Chau <kalvin@block.xyz>
Signed-off-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
Co-authored-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
2026-08-25 11:34:16 -07:00
Luke Tornquist
113a33b7e4 Add database pressure observability (#6700)
## Why

Database pressure currently collapses several distinct delays into one
symptom. This adds the evidence layer needed to distinguish pool
acquisition wait, logical database operation time, advisory-lock wait,
and selected transaction duration before changing timeout or retry
policy.

This is the phase 2 Lane A observability bundle for
[#26](https://github.com/TheSentinel454/buzz/issues/26),
[#28](https://github.com/TheSentinel454/buzz/issues/28), and
[#33](https://github.com/TheSentinel454/buzz/issues/33). It is stacked
on #6668.

## What

- Record explicit reader/writer checkout wait and acquisition outcomes
with `buzz_db_pool_acquire_wait_seconds` and
`buzz_db_pool_acquisitions_total`.
- Extend the compile-time `#[datastore_span(name = "...")]` seam with
`buzz_db_operation_duration_seconds`, so operation labels remain static
source literals instead of request data.
- Route correctness-critical replacement, membership, push-gate,
deletion, and migration/schema-safety advisory locks through one
observer without changing their SQL, order, scope, or blocking behavior.
- Measure six internally owned transaction lifetimes with
`buzz_db_transaction_duration_seconds`, starting after `BEGIN` succeeds
and ending after explicit commit/rollback or scope exit.
- Emit root slow-operation warnings at 500 ms, logging the first slow
completion and then 1/100 per call site with only `operation`,
`outcome`, and `elapsed_ms`.
- Document names, units, fixed label vocabularies, measurement
boundaries, and blind spots in this PR description.

Fixed labels are deliberately small:

- `pool_role`: `writer`, `reader`
- `lock_type`: `replacement`, `membership`, `push_gate`, `deletion`,
`migration_schema_safety`
- `outcome`: `success`, `error`, `timeout` where SQLx/PostgreSQL can
distinguish it accurately
- `operation`: compile-time datastore names plus the six closed
transaction operation names documented in the runbook

No metric or slow warning contains community IDs, event IDs, event
kinds, coordinates, d-tags, SQL/query text, query IDs, returned errors,
or event content.

## Coverage boundaries

- Operation duration is the complete annotated logical function body,
not pure SQL execution; it may include implicit checkout, lock wait,
nested operations, and application work. Cancelled futures do not reach
its completion hook.
- Pool timing covers explicit helper checkouts, including proved-reader
routing and selected writer-owned transactions. Implicit SQLx checkout
through `&PgPool` remains folded into operation duration.
- Lock timing covers application-side blocking locks in the five named
families. Trigger/stored-procedure locks, channel-TTL locking, the usage
try-lock, and the audit service session lock remain outside this slice.
- Transaction timing covers only the six wholly owned boundaries
documented in the runbook. It excludes pool wait, `BEGIN`, asynchronous
rollback cleanup after an early return, and caller-owned
`Db::begin_transaction` lifetime.

## Relationship to #6229

#6229 is the incident-driven timeout precursor. This PR does not add or
change `statement_timeout`, `lock_timeout`,
`idle_in_transaction_session_timeout`, retries, audit durability, or
client-visible conflicts. It provides the missing distributions needed
to evaluate those policies later and intentionally leaves #6229's open
audit retry/durability finding untouched.

The branches overlap in `crates/buzz-db/src/lib.rs` and
`crates/buzz-db/src/migration.rs`, so a later rebase may need textual
conflict resolution, but the behavior is complementary rather than
duplicated.

## Risk assessment

Moderate-low. The primary risk is instrumentation overhead and added
static series. Cardinality is source-bounded, slow logs are
sampled/redacted root events, and the lock/transaction changes wrap
existing awaits without changing policy or ordering.

## Verification

Author workstation: `buzz-tornquist-db-pressure-observability`
(`2010927`), exact head `d7cf833e26c528adfcde3917ded80daf6f4ddac9`,
parent `6f50e6b2b2a996349149af61d35bdd6a355f77fd`.

- `cargo fmt --all --check` — passed
- `cargo clippy -p buzz-datastore-tracing -p buzz-db -p buzz-audit -p
buzz-search -p buzz-relay --all-targets -- -D warnings` — passed
- `cargo test -p buzz-datastore-tracing --quiet` — 4 passed
- `cargo test -p buzz-db --quiet` — 109 passed, 200 ignored
- `cargo test -p buzz-audit -p buzz-search --quiet` — 16 passed, 25
ignored
- `cargo test -p buzz-relay --lib --quiet -- --test-threads=1` — 906
passed, 48 ignored
- Native PostgreSQL focused tests for pool success/timeout/error, lock
success/contention/timeout/error, replacement, membership serialization,
push ordering, deletion fencing, migration/schema exclusion, and reader
fallback — 8 passed

The default-parallel relay run passed once; subsequent runs exposed the
existing load-sensitive
`api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo` 504 at
the end of the suite. That test passes in isolation and the full relay
suite passes serially.

Independent exact-head review workstation:
`buzz-tornquist-db-pressure-observability-review` (`2013067`).
Formatting, the same all-target clippy command, datastore
instrumentation tests, DB unit tests, source privacy guards, and
diff/non-goal audits passed; no review findings.

Generated with Codex

---------

Signed-off-by: tornquist <tornquist@squareup.com>
2026-08-25 13:00:22 -04:00
tulsi
12f3fea26e revert fixed mention highlight (#6716)
**Category:** fix
**User Impact:** Mention chips now follow the active theme without a
fixed yellow fill and remain readable across every supported theme.

**Problem:** PR #6696 added an opaque yellow treatment to every human
and agent mention. Removing that fixed color exposed another issue: some
theme accent colors do not provide WCAG AA contrast when also used as
normal-size mention text.

**Solution:** Remove the fixed yellow treatment and keep the active
theme accent as a subtle translucent chip background. Mention labels use
a light/dark semantic foreground selected for readable contrast,
including hover states.

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

**desktop/src/shared/styles/globals/markdown.css**
Removes the fixed-yellow treatment, retains theme-derived accent
backgrounds, and applies an AA-safe semantic foreground to human and
agent mention labels in resting and hover states.

**desktop/src/shared/styles/globals/theme.css**
Removes the obsolete fixed-yellow tokens and defines light/dark mention
foreground values.

**desktop/tests/e2e/mentions.spec.ts**
Adds rendered contrast coverage for resting and hover states across
every supported syntax theme, compositing translucent chip and ancestor
surfaces before checking the 4.5:1 threshold.

</details>

## Reproduction steps

1. Open a message containing a human or agent mention.
2. Confirm its subtle background follows the active theme accent rather
than using yellow.
3. Hover the mention and confirm the accent background strengthens while
the label remains readable.
4. Switch between default, light, dark, and custom syntax themes and
confirm mention labels remain legible.

This is a focused visual rollback of the mention treatment introduced by
#6696. The channel notification badge changes from that PR remain
intact.

---------

Signed-off-by: tulsi <tulsi@block.xyz>
2026-08-25 09:49:43 -07:00
tulsi
29f2054c69 highlight search terms in results and messages (#6702)
**Category:** fix
**User Impact:** Search terms are now highlighted in yellow in Cmd+F
results and in the message opened from a result.

**Problem:** Search returned relevant messages, but users still had to
reread each preview and destination message to discover where the query
appeared. Search navigation could also lose or misapply highlighting
during rapid typing, repeated navigation, thread opening, and forum
navigation.

**Solution:** Render match-focused previews with a shared yellow
treatment, carry the result query through navigation, and apply it only
to the clicked destination. The implementation binds highlights to the
debounced result set, supports token and prefix matching, and handles
channels, threads, forums, diffs, and wave messages consistently.

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

**desktop/src/app/AppShell.tsx**
Carries the active result query into search-hit navigation so the
destination can preserve the user's context.

**desktop/src/app/navigation/searchHitEventCache.ts**
Adds a bounded, one-shot cache for result queries and navigation IDs
alongside cached search-hit events.

**desktop/src/app/navigation/searchHitNavigation.test.mjs**
Covers query retention, one-shot consumption, repeated same-route
activations, forum posts, forced routing, and cancellation.

**desktop/src/app/navigation/searchHitNavigation.ts**
Associates each search-result activation with a unique navigation ID and
forwards it to channel or forum destinations.

**desktop/src/app/navigation/useAppNavigation.ts**
Extends channel and forum navigation to carry search navigation state
without changing normal navigation behavior.

**desktop/src/app/routes/ChannelRouteScreen.tsx**
Consumes search highlight state after navigation and retains it for the
selected message while clearing it on community context changes.

**desktop/src/app/routes/channels.$channelId.posts.$postId.tsx**
Validates and forwards forum search-navigation IDs.

**desktop/src/app/routes/channels.$channelId.tsx**
Validates and forwards channel search-navigation IDs.

**desktop/src/features/channels/ui/ChannelPane.tsx**
Passes the selected result ID and query into the main timeline and open
thread panel.

**desktop/src/features/channels/ui/ChannelPane.types.ts**
Defines the destination-highlight contract for channel panes.

**desktop/src/features/channels/ui/ChannelScreen.tsx**
Routes destination highlighting to either forum content or the channel
timeline.

**desktop/src/features/channels/ui/ChannelScreen.types.ts**
Defines route-level search-highlight inputs.

**desktop/src/features/channels/ui/ForumChannelContent.tsx**
Forwards search context into expanded forum threads.

**desktop/src/features/forum/ui/ForumThreadPanel.tsx**
Highlights the matching text in the selected forum post or reply.

**desktop/src/features/forum/ui/ForumView.tsx**
Carries selected-result context from forum routing into the thread
panel.

**desktop/src/features/messages/ui/DiffMessage.tsx**
Forwards the query into diff descriptions and rendered diff content.

**desktop/src/features/messages/ui/DiffViewer.tsx**
Provides a highlighted match excerpt for structured diffs and highlights
fallback raw diff text.

**desktop/src/features/messages/ui/MessageRow.tsx**
Passes destination queries into normal Markdown, diff, and wave message
renderers.

**desktop/src/features/messages/ui/MessageThreadPanel.tsx**
Applies highlighting to the selected thread head or reply without
tinting unrelated messages.

**desktop/src/features/messages/ui/WaveMessageAttachment.tsx**
Highlights matches in wave-message fallback text.

**desktop/src/features/search/lib/searchMatch.test.mjs**
Covers case-insensitive, literal, multi-term, prefix, one-character, and
late-preview matches.

**desktop/src/features/search/lib/searchMatch.ts**
Centralizes token extraction, case-insensitive match splitting, and
match-focused preview generation.

**desktop/src/features/search/ui/HighlightedSearchText.tsx**
Adds the reusable accessible mark renderer used by result and
specialized message surfaces.

**desktop/src/features/search/ui/TopbarSearch.tsx**
Highlights result previews, centers excerpts around matches, strips
search operators, and hides stale results during debounce transitions.

**desktop/src/features/sidebar/ui/AppSidebar.types.ts**
Updates the sidebar search callback contract to include the result
query.

**desktop/src/features/sidebar/ui/AppSidebarPinnedHeader.tsx**
Passes query-aware result selection through the pinned search entry
point.

**desktop/src/shared/lib/rehypeSearchHighlight.ts**
Uses the shared matcher to mark every matching Markdown text segment
while leaving code blocks untouched.

**desktop/src/shared/lib/searchHighlightStyle.ts**
Defines one yellow highlight treatment for light and dark themes.

**desktop/src/shared/ui/markdown/nodeCache.test.mjs**
Verifies all Markdown matches, one-character scoped searches, code
exclusion, and transient cache behavior.

**desktop/src/shared/ui/markdown/nodeCache.ts**
Enables transient highlighting for one-character scoped searches without
polluting the Markdown cache.

**desktop/tests/e2e/smoke.spec.ts**
Exercises result and destination highlighting, same-route forum
activation, and stale-query suppression end to end.

</details>

## Reproduction steps

1. Open a channel containing a longer message and press Cmd+F.
2. Search for a word that appears later in the message, such as
`mentions`.
3. Confirm each matching result shows the word in yellow and keeps the
match visible in its preview.
4. Open a result and confirm the same term is highlighted in yellow in
the destination message.
5. Repeat with a thread reply or forum post, and quickly change the
query to confirm stale results are not selectable.

## Demo

- **Before:** Search results returned the correct message but offered no
visual indication of where the query matched; opening the message also
showed no match treatment.
- **After:** Every matching term is marked in yellow in the result
preview and in the exact channel, thread, or forum message opened from
that result.

---------

Signed-off-by: tulsi <tulsi@block.xyz>
2026-08-25 16:22:12 +00:00
Kalvin C
9b6a637d01 fix(desktop): make lightbox zoom controls interactive (#6710)
## Summary

- Replace the decorative lightbox zoom icons with accessible Zoom out
and Zoom in buttons.
- Route button clicks through the existing 1x–3x, 5% stepped zoom state
without dismissing the viewer, including clicks on SVG icon descendants.
- Extract the zoom toolbar so `markdown.tsx` remains below the
repository file-size threshold.

### Testing

- `pnpm build:e2e`
- `pnpm exec playwright test image-attachment-gallery.spec.ts
--project=smoke` — 10 passed
- `pnpm typecheck`
- Full desktop unit suite — 5,397 passed
- Pre-push checks — file-size gate, desktop checks, typecheck, and
desktop tests passed
- `git diff --check`

### UI evidence

The rendered lightbox flow is covered by the gallery smoke test: Zoom
out is disabled at 100%, Zoom in changes the value to 105%, Zoom out
returns it to 100%, and the dialog remains open throughout. Existing
gallery, keyboard, spoiler, context-menu, and close-path assertions also
pass.

---------

Signed-off-by: sanic <c57bf9b4275088b2b33db7f746975407210f159fbd8bd733c0e532375f69aa80@buzz.block.builderlab.xyz>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
Co-authored-by: sanic <c57bf9b4275088b2b33db7f746975407210f159fbd8bd733c0e532375f69aa80@buzz.block.builderlab.xyz>
Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz>
2026-08-25 09:09:13 -07:00
Brad Seiler
d12dea4e67 Support community deletion in versioned media buckets (#6738) 2026-08-25 09:02:18 -07:00
klopez4212
bb5b9357a7 Fix TipTap editor mount race (#6779)
## Summary

- wait for TipTap's mount lifecycle before reading the selection
formatting tray's editor DOM
- detach and reattach DOM listeners across editor view unmounts and
remounts
- cover mounted and pre-mount editor view access

## Testing

- `just ci`
- `pnpm build:e2e`
- `pnpm exec playwright test
tests/e2e/composer-selection-formatting.spec.ts --project=smoke --grep
"right-clicking selected composer text"`
- Native Builderlab staging: reproduced the pre-mount crash on main,
relaunched with the fix, and verified the composer mounts without the
TipTap error

Signed-off-by: kenny lopez <klopez4212@gmail.com>
2026-08-25 16:50:21 +01:00
Will Pfleger
a1219070fa feat(buzz-agent): gate LLM tool calls on session/request_permission (#5712)
## Summary

buzz-agent now authorizes every LLM-issued MCP tool call through
`session/request_permission` before it executes, instead of running
tools unconditionally. The agent **always asks**; the client applies
`BUZZ_ACP_PERMISSION_POLICY` and answers. buzz-agent never reads the
policy — this keeps the policy decision on the client side, matching the
layering of the other ACP harnesses, and avoids duplicating policy logic
that would drift.

### Broker

A crate-local `PermissionBroker`
(`crates/buzz-agent/src/permission.rs`), owned by `App` for the
connection lifetime, owns the full request-correlation lifecycle:

- **Process-wide admission.** A global `Semaphore`
(`BUZZ_AGENT_MAX_PENDING_PERMISSIONS`, default 32, validated `>= 1`) is
acquired *before* any correlation entry is inserted. This is the
security bound: the per-turn tool semaphore is constructed fresh per
turn and `max_sessions` is unbounded by default, so neither bounds
simultaneously-outstanding asks process-wide.
- **Abort-safe cleanup.** A successful admission returns a
`PendingPermission` lease that owns the admission permit and the
correlation id. Its `Drop` synchronously removes the still-pending entry
and releases the slot, covering task abort/panic that bypasses the
normal `run_prompt` tail.
- **At-most-once resolution.** `deliver` claims (removes) the entry
*before* waking the waiter, so each id resolves once and a later lease
`Drop` is a no-op. Unknown/late ids are logged and dropped; only ids the
broker minted (`perm-<n>`) are recognized.
- **Single absolute deadline.** Admission, request enqueue, and response
wait all share one deadline (`BUZZ_AGENT_PERMISSION_TIMEOUT_SECS`,
default 330s, validated `>= 1`), so a saturated call cannot outlive one
timeout window even when a stalled writer blocks the enqueue.
Cancellation races inside every wait — resolution never depends on the
outer abort drain. A writer that dies mid-connection (stdout closed, or
a blocking write that only surfaces its error at flush) is
connection-fatal: it cancels all sessions, which resolves any ask
waiting on a reply that can never be written.

### Wire

`request_permission_params` (`crates/buzz-agent/src/wire.rs`) is
version-aware, keyed on the protocol version negotiated at `initialize`
and stored on `App` for the connection lifetime (never derived from a
later mutable session field). v2 nests the tool call under `subject:
{type: "tool_call", toolCall}` with top-level `title`/`options`; v1 uses
the legacy top-level `toolCall`. No hybrid shape. Both offered options
(`allow_once`, `reject_once`) carry `optionId == kind`, so the client's
`kind`-based selection and this side's `optionId`-based predicate agree
without a lookup table.

### Gate

In each spawned tool task (`crates/buzz-agent/src/agent.rs`) the
sequence is: acquire per-turn permit → argument-shape validation →
broker admission + request + wait → cancellation recheck →
`emit_in_progress` → `mcp.call`. Argument-shape validation is hoisted
out of `mcp.rs::do_call` into `validate_arg_shape` so a malformed
non-object argument is rejected locally without prompting for a call
that could never execute.

Authorization is fail-closed, stated once in `evaluate`: execute IFF
`outcome.outcome == "selected"` **and** the selected `optionId` equals
the offered allow option. Every other shape (reject, cancelled, JSON-RPC
error, malformed, unknown outcome, wrong/unknown option, timeout,
wire-channel closure) denies with a synthetic tool error, and the turn
continues.

### Scope

Only LLM-issued MCP calls are gated. The built-in `load_skill` tool and
`call_hooks` lifecycle calls (`_Stop`, `_PostCompact`) are exempt — they
are not model-issued. `readOnlyHint` is never treated as a security
boundary.

First cut ships `allow_once`/`reject_once` only; session-scoped grants
are deliberately out of scope.

### Related issue

Part of #4938. This PR and
[#5106](https://github.com/block/buzz/pull/5106) jointly implement the
feature: #5106 is the client-side policy engine and permission cards;
this PR is buzz-agent's asking side (`session/request_permission`).
Neither closes #4938 alone.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Hayt <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-25 11:49:07 -04:00
Brad Seiler
931747c9c4 Add staging dev relay image workflow (#6709)
## Summary

Adds a manual, collaborator-triggered workflow for publishing pre-merge
Buzz relay runtime images for bb-block staging.

- Resolves a canonical `block/buzz` branch or tag to an immutable commit
SHA before checkout.
- Builds the relay runtime image for `linux/amd64` and `linux/arm64` and
publishes a single OCI index.
- Tags each publication uniquely as `dev-sha-<full
SHA>-run-<run_id>-<run_attempt>` so no tag is ever reused against the
immutable-tag utility ECR pull-through cache (rebuilds of the same SHA
aren't byte-identical, given mutable base tags and `apt-get update`).
- Uses the staging-only `ghcr.io/block/buzz-staging-dev` namespace,
which maps to a distinct utility ECR pull-through path.
- Restricts dispatch to `block/buzz` on `refs/heads/main`; job
permissions are narrowly scoped to `contents: read` plus `packages:
write`.
- Emits a deployment summary with the exact BPCI `repository` and unique
`tag` values, plus the merged manifest digest.
- Keeps the existing production/release image workflow unchanged.

The first real workflow run must confirm GHCR package creation/access
and utility ECR pull-through import for the new package.

---------

Signed-off-by: Brad Seiler <seiler@squareup.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: tornquist <tornquist@squareup.com>
Co-authored-by: coder 0 <d97ebdbb198c7237c94f84ea8bb8a73583ea067407eebd0062abbb3962527fb1@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: tornquist <tornquist@squareup.com>
2026-08-25 11:23:17 -04:00
Luke Tornquist
9d1e4b2576 Extract community persistence (#6668)
## Why

Community persistence is the next incremental `buzz-db` store
extraction, keeping tenant lifecycle SQL, records, tests, and
instrumentation out of the database runtime module without changing
behavior.

## What

- Move community records and the existing `impl Db` operations into
`community.rs` while preserving crate-root re-exports.
- Move focused PostgreSQL tests with the implementation and enforce
single ownership for each method and datastore span.

## Risk Assessment

Low — this is a structural move of the existing records, SQL, method
bodies, and focused tests; database runtime concerns, schema, and
behavior remain unchanged.

## References

- Architecture guardrail:
https://github.com/TheSentinel454/buzz/issues/34
- Incremental tracker: https://github.com/TheSentinel454/buzz/issues/2
- Primary task: https://github.com/TheSentinel454/buzz/issues/5
- Stacked on: https://github.com/block/buzz/pull/6660

Generated with Codex

Signed-off-by: tornquist <tornquist@squareup.com>
mobile-v0.14.0-rc.5 mobile-v0.15.0-rc.1
2026-08-25 10:05:32 -04:00
klopez4212
8b812017a7 Fix mobile Huddle agent voice turn states (#6611)
## Summary

- ignore mobile room tone when deciding whether human speech should
interrupt agent audio
- show a subtle waiting animation while an agent prepares a response,
then restore its avatar when speech begins
- render agents from Huddle membership before their audio peer starts
transmitting

## Testing

- `just ci`
- signed Release build installed and launched on a physical iPhone
- live Huddle voice and agent-membership behavior verified on device

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@buzz.block.builderlab.xyz>
2026-08-25 14:59:19 +01:00
klopez4212
9aa332af03 Add inline profile camera capture (#6680)
## Summary

- add inline profile photo capture with smooth avatar-to-viewfinder
transitions
- add close, flip, shutter, retry, and use-photo states with haptics and
front-camera mirroring
- polish iOS liquid-glass controls and avatar editor motion

### Related issue

Follow-up to #6583.

### Testing

- just mobile-check
- just mobile-test (1,743 tests)
- built, installed, and launched on Pixel 10 and iPhone Air

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Code Reviewer <037593536284cf40e221c96c931e9877d4166d54f6bb84e5341a86d7fd5d05a4@buzz.block.builderlab.xyz>
Signed-off-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz>
Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Co-authored-by: Code Reviewer <037593536284cf40e221c96c931e9877d4166d54f6bb84e5341a86d7fd5d05a4@buzz.block.builderlab.xyz>
Co-authored-by: Tyler <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz>
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Co-authored-by: morgmart <98432065+morgmart@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-25 14:58:55 +01:00
klopez4212
822c5ab231 Hide Huddles in mobile agent DMs (#6676)
## Summary
- hide the mobile Huddle action in one-to-one agent DMs
- preserve Huddles for human DMs and group DMs

## Testing
- just mobile-check
- flutter test (1,662 tests)

---------

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-08-25 08:18:22 +01:00