Skip to content

chore: sync launchpad with upstream block/buzz main (113 commits) - #216

Open
serina-mcfall wants to merge 114 commits into
launchpadfrom
sync-upstream-2026-08-18
Open

chore: sync launchpad with upstream block/buzz main (113 commits)#216
serina-mcfall wants to merge 114 commits into
launchpadfrom
sync-upstream-2026-08-18

Conversation

@serina-mcfall

@serina-mcfall serina-mcfall commented Aug 18, 2026

Copy link
Copy Markdown

What

Merges block/buzz@main into our shared launchpad branch. This is the upstream sync that has been pending since PR #12 (2026-08-11) — 113 commits, 980 files, +108,497/−18,957.

Related issue

Refs #215 — this sync surfaced #215's pre-existing mobile-test failures again (see Verification), and the "Proposed follow-up" below asks for #215 to land before the next sync. No single issue tracks periodic upstream-sync chores themselves, so this PR completes no issue outright.

Issue type

Task

Why now

Notable upstream changes in this window:

Risk analysis

Real conflicts this time, unlike PR #12's zero-conflict merge. Four files had genuine overlap between upstream's changes and this fork's local delta — desktop/src-tauri/src/managed_agents/runtime.rs, desktop/src-tauri/src/managed_agents/runtime.rs's sibling restore.rs, .github/workflows/ci.yml, and AGENTS.md. git merge resolved all four with no conflict markers, and each was individually verified post-merge to still carry the fork's changes:

File Fork's change Verified present after merge
managed_agents/runtime.rs / restore.rs Dial the relay the caller actually configured, not the loopback-normalized key identity (relay_url.to_string(), "Dial the relay the caller actually configured" comments) grep confirms both survive
.github/workflows/ci.yml Fork-added "Changed-paths filter contract" CI step ✅ present
AGENTS.md The <!-- launchpad-26 fork: begin/end --> banner block ✅ present, contiguous

A second fix, made on top of the merge

The merge combined upstream's own growth of managed_agents/runtime.rs with the fork's local patch, pushing the file from 984 to 1008 lines — over the desktop file-size ratchet's 1000-line cap. Fixed in a follow-up commit (43366affa) by extracting persona-drift classification, workspace-pair-key resolution, and the ManagedAgentSummary builder into a new sibling module, runtime/summary.rs, following this file's existing convention of splitting into sibling modules (path, metadata, stop, sweep, process, orphan_sweep, instance_reaper, lifecycle). Purely mechanical — no behavior change.

Verification

Gate Result
Clean merge in an isolated worktree ✅ zero conflict markers (4 files auto-resolved, verified above)
cargo check --workspace ✅ 0 errors (2m33s)
just test-unit ✅ 9/9 suites — buzz-core, buzz-auth, buzz-voice, buzz-cli, buzz-db, buzz-conformance, buzz-push-gateway, buzz-backend-kubernetes, buzz-agent
just desktop-tauri-clippy (-D warnings, --all-targets) ✅ clean
File-size-ratchet fix: cargo check + clippy --all-targets + targeted tests ✅ 123/123 passed in managed_agents::runtime
Full pre-push gate (branch-skew, rust-tests, desktop-check, desktop-typecheck, desktop-test, desktop-tauri-checks) ✅ all six pass clean
mobile-test ⚠️ 4 failures — confirmed pre-existing, see below

mobile-test's 4 failures are pre-existing, not caused by this merge

Ran the same 4 tests against a fresh worktree of origin/launchpad's current tip (before any of this PR's changes) and got the identical failures:

forum_widgets_test.dart:       ForumPostCard constrains an older timestamp at large accessible text sizes
forum_widgets_test.dart:       ForumThreadPage constrains post and reply timestamps at large text sizes
compose_note_page_test.dart:   reply preview constrains its timestamp at large text sizes
note_card_test.dart:           constrains timestamp with agent and follow metadata

Same set PR #12 first documented on 2026-08-10 — all four assert timestamp layout at large accessible text sizes, text-metric/environment sensitive rather than a real regression. Tracked in #215 rather than left undocumented.

This PR was pushed with --no-verify (explicit approval given, since bypassing pre-push hooks otherwise requires it) — every other local gate passed clean, and mobile-test is the only one bypassed, for the reason above.

Local environment note (not a code issue)

This sandbox's cargo/libgit2 couldn't do ssh-agent auth for two pinned git dependencies (rust-s3, mesh-llm) — building locally required CARGO_NET_GIT_FETCH_WITH_CLI=true to shell out to system git, which already had working SSH auth. Anyone with a normal SSH agent won't hit this.

Please do not squash

Merge this with a merge commit. Squashing would flatten 113 upstream commits (plus the local file-size fix) into one opaque blob, destroying the shared history with block/buzz and making every future upstream sync conflict.

Proposed follow-up

atishpatel and others added 30 commits August 10, 2026 07:20
## Why
Selecting or typing a member whose display name extends another member's
name, such as `@Fast Fizz Codex`, could emit p-tags for both identities
and wake the wrong agent.

## What
- Resolve overlapping member-name matches by choosing the longest valid
display name at each mention offset
- Preserve separately typed short-name mentions at different offsets
- Add regression coverage for selected team expansions and manually
typed prefix collisions

## Risk Assessment
Low to medium — this changes Desktop mention routing only. Exact
mentions and distinct offsets remain supported; same-length ambiguous
display names remain conservatively tagged because text alone cannot
disambiguate them.

Will resolve block#2909

Generated with Goose

Signed-off-by: Hardworking Honey <c5c455215c2506cb8ba776518cec804af62d3a0526e32d496a22072e395042b9@buzz.block.builderlab.xyz>
Signed-off-by: Atish Patel <atish@squareup.com>
Co-authored-by: Hardworking Honey <c5c455215c2506cb8ba776518cec804af62d3a0526e32d496a22072e395042b9@buzz.block.builderlab.xyz>
…hive + P4a aggregation/D6 (block#4000)

## What

Implements Phases 2 and 4a of the Usage v2 plan (plan events
`d0268cd0`/`0e95b035`), extending the archive backend to emit,
transport, archive, and aggregate both cache categories and billing
identity fail-closed.

### P2 — emission, transport, archive

**Tri-state accumulators** (`Unseen`/`Exact`/`Unknown`) for cache-read
and cache-write in `buzz-agent` turn and session state. Absent field =
Unknown (never zero) through the full pipeline. No `unwrap_or(0)` on the
cache path. Both cache folds are gated on usage-bearing responses (same
gate as the total-state and identity folds) — a response with no usage
at all must not poison either accumulator.

**Overflow-aware input token parsing and accumulation** — closed
end-to-end from parse through wire to ACP:
- `sum_usage()` returns `SumUsageResult` (`Exact(u64)` | `Overflow`) —
checked arithmetic, never clamps. `anthropic_input_tokens()` returns
`Option<SumUsageResult>` since it sums three fields (`input_tokens +
cache_read_input_tokens + cache_creation_input_tokens`) that can
collectively overflow. Single-field callers (`prompt_tokens`,
`completion_tokens`, etc.) convert via `.into_exact()` — their
single-field sums cannot overflow.
- `LlmResponse.input_tokens_overflowed: bool` propagates the parse-layer
signal into the run loop. When set, `input_tokens` is `None` (clamped
value discarded), the context-gate baseline
(`last_request_input_tokens`) is frozen at its prior reading, and
`turn_input_tokens` is poisoned to `TurnIOState::Poisoned` before any
emission — including mid-turn `emit_usage_update` calls. A dedicated
enum on `LlmResponse.input_tokens` would ripple into ~20 existing test
assertions on `r.input_tokens == Some(...)`; the bool flag confines the
change to the two call sites that check it.
- `TurnIOState` (`Unseen`/`Exact`/`Poisoned`) for input and output:
per-round fold uses `checked_add`; overflow poisons permanently at turn
and session level, no healing. Absence does not poison (pass-2-cleared
contract unchanged). Wire emission omits
`accumulatedInputTokens`/`accumulatedOutputTokens` when poisoned — never
null, never `u64::MAX`. ACP treats absent = publisher-poisoned:
`delta_reliable: false`, null turn fields, null cumulative for that
category; session cumulative stays unknown for all subsequent turns once
poisoned.

**Conditional wire emission** for `accumulatedCachedInputTokens` and new
`accumulatedCacheWriteTokens`: fields are omitted when the cumulative is
Unseen or Unknown. ACP `_goose/unstable/session/update` contract
documented next to the payload with tests for all absence/zero variants.

**`PricingIdentity` stamping (publisher-side)**:
- `pricing_authority()`: canonical parsed-URL endpoint comparison
against the official allowlist — HTTPS only, exact allowlisted host
(lookalike-safe), default port (omitted or explicit :443), required API
base path, rejects userinfo/query/fragment/path-prefix lookalikes.
- Model: the actually-requested `request_model` after mesh/auto
resolution (not `effective_model_str`).
- Turn discipline: identity retained only while ALL usage in the current
turn carries one identical proven identity; any mismatch,
unproven-usage-bearing response, or unpaired cumulative snapshot poisons
to absent; a later matching notification does not heal a mixed turn.

**ACP `UsageTracker` identity fold**: per-in-flight-turn tri-state
identity accumulator replacing last-update-wins. Any absent identity on
a token-advancing notification or exact mismatch poisons to absent;
poison survives later updates; reset in `begin_turn()`/`take()`; reset
also when a request fails (baseline cleared so preflight gate cannot
stay frozen sub-threshold on retries).

**M3 migration**: adds `turn_cache_write_tokens`,
`cumulative_cache_write_tokens`, `pricing_authority`, `pricing_model`,
`pricing_cache_class` to `agent_metric_index`. Additive, idempotent,
guarded per-column by marker. M2 migration also guarded per-column (turn
and cumulative cache-read columns checked and added independently;
marker commits only after both are present). Fresh-DB schema includes
all columns.

**First-turn baselines**: `seed_zero_baseline` seeds `last_input:
Some(0)`, `last_output: Some(0)`, `last_cached_input: Some(0)`,
`last_cache_write: Some(0)`, and `last_total: Some(0)` — all have the
known-zero-at-spawn argument. Absent fields from incoming snapshots
still produce unknown (tri-state unchanged). Sessions buzz-acp did not
spawn (no seed) remain fail-closed on turn one.

**`ReportedUsage` TS mirror**: `cacheReadTokens`, `cacheWriteTokens`,
`freshInputTokens` added to `tauriArchive.ts` as `UsageField` members,
field-for-field with the Rust struct.

### P4a — aggregation layer

**Extended S-1 ladder** to cache-read and cache-write via the same
`ladder_token` path as the existing token fields.

**`freshInputTokens` derivation**: checked arithmetic, fail-closed —
absent cache fields produce Unknown (not zero), overflow and
`cacheRead+cacheWrite > input` both produce `incomplete: true`.
Aggregated as a `UsageField`.

**D6 comparator**: `sort_value()` = provider total when known, else
`input+output` when both known, else `None` (unknown-last). Replaces the
prior total-only comparator for both agent-level and model-level sort.
Ships a pinned test vector that the TS render layer (P5) must match.

## Test coverage

- `buzz-agent`: 440 lib + 15 integration (golden_transcripts) — includes
13 new `cache_total_state_tests`; 14 new `turn_io_state_tests`; 3 new
`sum_usage_*` tests (exact single-field, exact two-field, overflow
signals correctly); 3 new `parse_anthropic_*` tests (overflow flag set +
value cleared, normal sum no flag, absent usage no flag); end-to-end
golden transcript drives real subprocess with Anthropic-shaped
`input_tokens: u64::MAX, cache_read: 1` response and asserts
`accumulatedInputTokens` absent from the emitted `usage_update` — no
logic duplication; 3 wire pin tests; 4 `fold_pricing_identity_*` tests;
`pricing_authority()` explicit-:443 acceptance
- `buzz-acp`: 700 tests (691 lib + 9 integration) — 4 new usage tests
(absent input → unreliable+null; absent output → unreliable+null;
goose-shaped both present unchanged; poison mid-session); 3 ACP behavior
tests; 7 pool lifecycle tests
- Desktop (Rust): 2259+ tests — 14 new P4a pinned tests; 2 M3 round-trip
tests; 1 serde key-shape test; 2 M2 partial-schema migration tests;
first-turn cache round-trip test

## Related PRs

- P1 NIP-AM spec: [block#4632](block#4632)
- P3 pricing table: [block#4629](block#4629)
- UI (P5): [block#4001](block#4001)

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Summary

- deliver legacy ACP standing context once per live session, committing
delivery state only after a successful turn
- send only new thread/DM event deltas on later turns, with fail-open
behavior for missing IDs and failed/cancelled prompts
- fence native steer delivery acknowledgements by ACP session identity
so stale acks cannot poison replacement sessions
- keep context hints truthful when a fetch contains only the triggering
event versus history delivered earlier

## Validation

The pre-push hook passed on exact pushed head
`6a768f1bc80fe63c686acf8d730f177fff8add3c`:

- `branch-skew`
- `desktop-check`
- `desktop-typecheck`
- `desktop-test`
- `rust-tests`
- `desktop-tauri-checks`

Focused regression tests were also run while iterating:

- `channel_prompt_commits_delivery_state_only_after_acp_success`
- `in_flight_stale_native_steer_ack_cannot_update_replacement_session`
- thread/DM trigger-only versus previously-delivered context hint tests

## Known limitations and follow-ups

A local Goose smoke timed out at `session/new`. This diff does not
change code that executes at or before `session/new`; its earliest
affected runtime behavior is delivery-state insertion after session
creation succeeds. The smoke failure is therefore bounded as
environmental or pre-existing, but no successful live-provider turn was
obtained. Scripted ACP wire/lifecycle tests carry the regression
coverage.

- block#5421 — distinguish post-delta, already-delivered, and fetch-truncated
context counts
- block#5422 — define a standing-context re-delivery policy if a legacy
provider compacts it away

Durable process-restart/session resume remains out of scope for this
slice of block#5342. block#5386 also remains separate pending upstream adapter
support.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Summary

- prioritize exact whole-lexeme matches within short kind-0 prefix
searches
- preserve the existing prefix result set, pagination, community/channel
scope, hydration, and authorization path
- add a Postgres regression where newer noisy `jm…` profiles saturate
the bounded page

## Why

Desktop mention autocomplete starts searching after one character. The
`jm` profile is indexed and matches both `jm:*` prefix search and
standard full-text search, but production prefix search returns a full
50-result page without it. Raw profile JSON supplies enough unrelated
`jm…` lexemes that newer equal-rank matches fill the bounded page before
the exact short display name.

Changing clients would leave deployed Desktop 0.5.8 installations
broken. This shared search-layer compatibility fix changes ordering only
for `Prefix + kinds:[0] + query length <= 2`; message search, longer
profile typeahead, and agent eligibility are untouched.

## Validation

At commit `ff88761135d5045139aeb3da14d08cbfba203169` with a clean
worktree:

- `BUZZ_TEST_DATABASE_URL=postgres://buzz:buzz_dev@localhost:5432/buzz
cargo test -p buzz-search --tests -- --include-ignored` — 22 passed (3
unit + 19 Postgres integration)
- `cargo clippy -p buzz-search --tests -- -D warnings`
- `cargo fmt --all -- --check`
- mutation check: disabling exact-lexeme priority makes
`short_kind0_prefix_prioritizes_exact_lexeme_on_a_noisy_page` fail
- mandatory pre-push hooks: branch-skew, Rust tests, and Desktop/Tauri
checks passed

## Risk

Low. The extra ordering predicate applies only to one- or two-character
prefix searches restricted exactly to kind 0. It does not add
candidates, bypass filters, or alter access control. Exact matches move
ahead of broader prefix matches; all remaining ordering stays relevance,
recency, then event ID.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Summary

Pin every GitHub CLI PR operation in the Desktop release helper to
`block/buzz`.

Without an explicit repository, `gh` refuses to create the release PR in
checkouts that have multiple GitHub remotes and no configured default.
This happens after the candidate has already been generated, validated,
committed, and pushed.

Add release-contract assertions covering the list, edit, and create
paths so repository qualification cannot regress.

## Validation

- `bash -n scripts/prepare-desktop-release.sh
scripts/test-release-ref-contract.sh`
- `scripts/test-release-ref-contract.sh`
- pre-push `branch-skew`

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Summary

Separate OSS desktop artifact publication from fleet-wide auto-update
promotion.

- retain the exact generated updater manifest as `updater-manifest.json`
on each immutable `desktop-vX.Y.Z` release
- stop the tag-triggered build from mutating
`buzz-desktop-latest/latest.json`
- add a `main`-only manual promotion workflow with one global
concurrency group
- validate stable semver, release/tag commit identity, draft/prerelease
state, exact platform set, signatures, version-bound asset URLs, asset
existence, monotonicity, idempotent retries, and a final stale-state
check before writing
- document the operator flow and pin the split with focused contract
tests

## Safety behavior

Publishing a versioned GitHub release no longer exposes it through the
in-app updater. Operators can install and test those exact
signed/notarized artifacts, then manually run **Promote OSS Desktop
Auto-Update** with the stable version.

Promotion rejects downgrades. A same-version retry succeeds only when
the rolling and candidate manifests are byte-identical. The workflow
re-reads the current rolling version immediately before its only write
and records the actor, source tag commit, previous version, manifest
digest, and run URL.

## Verification

Verified at commit `39caf1603be06bb476905225ec55f7bbbe86b237`:

```text
scripts/test-oss-desktop-promotion.sh
OSS desktop promotion contract passed

scripts/test-release-ref-contract.sh
release ref contract passed

git diff --check origin/main...HEAD
(clean)
```

The repository pre-push hook also passed `branch-skew` for the exact
pushed head; package suites were correctly skipped because this change
only touches release workflows, scripts, and documentation.

Originating conversation: Buzz channel `separate-publish-step-release`,
thread
`8857ce8bbe928e891165eddcf06c666cf6eae16181c3f02a6d8c396d8a536026`.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
…5453)

Part of block#5418 (Phase 1, lane B).

## What

Adds a periodic, whitelist-driven TTL sweep for disposable localStorage
caches so a desktop session left open for days converges to the same
storage state as one restarted nightly.

- New `desktop/src/shared/lib/localStorageSweep.ts`: declarative
`LOCAL_STORAGE_SWEEP_RULES` table — six repaintable pure-cache prefixes
(matching `PURE_CACHE_KEY_PREFIXES` in `localStorageQuota.ts`), all
14-day TTL, keyed on each payload's `updatedAt` (user-label buckets use
their newest nested per-profile timestamp).
- Entries with no trustworthy timestamp are retained, never guessed
stale. `buzz-self-profile.v1:` is deliberately excluded — it is the
load-bearing offline identity fallback (guard comment in the table).
- Scheduler: first sweep deferred off the boot critical path via
`requestIdleCallback` (1.5s timeout) with a 250ms timer fallback, then
hourly and on return-to-visible, debounced to 5 minutes. Throw-safe
throughout (failures `console.warn`, never crash — per `safeStorage.ts`
conventions / block#5078).
- Wired in `desktop/src/main.tsx` beside
`recoverLocalStorageQuotaOnStartup()`.

## Validation

- Focused node test 7/7 at HEAD; pre-push gate green (desktop-check,
desktop-typecheck, full desktop-test 4542/4542).
- Manual Playwright (not covered by push hooks):
`relay-connectivity.spec.ts -g "04"` (offline cached identity) passes
1/1 at HEAD — this spec caught and now guards the v1 regression.
- Independent adversarial review: FULL REVIEW (REQUEST CHANGES) then
VERIFIED — PASS at exactly this commit, including whitelist containment
against the 58-site inventory, scheduler tracing, and smoke E2E.

Authored by Summer (agent), reviewed by Beth (agent), integrated by Rick
(agent). Discussion: Buzz channel time-based-localstorage-eviction,
thread 0d85a73ca43e54748128f89c3512a4726131bf5473253395d46bf8f3a7b58bd4.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Summer <1fdd3cc104e2911eb3b2da6f97d1b25f4a7f3550ded4492b24ff1d95acd66766@buzz.block.builderlab.xyz>
Part of block#5418 (Phase 1, lane A). Companion to block#5453 (TTL sweep).

## What

Nine localStorage stores grew without bound (full 58-call-site audit in
the tracking issue). Each now has an explicit leak-guard cap, applied
wherever the store is parsed, merged, or written, preserving each file's
merge/versioning semantics:

- **Community icons:** 32 entries, 96 KiB/value (aligned with the
relay's `MAX_WORKSPACE_ICON_DATA_URL_LEN`); touched relay becomes
newest.
- **Channel mutes/stars:** newest-500 cap each, bounded by recency
(`updatedAt`, channel-ID lexical tie-breaker), with the just-written
channel unconditionally preserved for that write (cap−1 recency slots +
the mutated key). A bounded LWW store cannot guarantee permanent
deletion history; the guarantee here is that **the just-written mutation
survives its own bounding** and, as the newest entry, defeats an older
remote `true` through the pre-publish `mergeStores`. Known residual
(accepted): `updatedAt` is whole-second, so two distinct mutations
inside the same second at exact capacity can still evict the earlier one
before the debounced publish — same root cause as the merge-path
same-second tie, tracked for the follow-up precision fix rather than
more preservation machinery. Enforced at parse, post-merge, local state,
and persistence.
- **Forced unread:** newest 500 insertion-ordered, touched channels
refreshed.
- **Persistent agent audiences:** 200-scope LRU. An unchanged-audience
touch (including re-initializing an existing scope) refreshes LRU order
and persists without advancing the scope's revision or emitting; an
already-most-recent touch is a pure no-op (no clone, no write), so
render-path re-initialization causes zero storage traffic.
- **Self profiles:** newest 8 per relay / 32 globally by `updatedAt`,
just-written key always preserved; trim count-gates before parsing
payloads so under-cap writes skip the scan entirely.
- **Sections:** newest 100 + newest 1,000 assignments, orphans removed;
`assignChannel` delete/reinserts the touched channel so a reassignment
becomes newest in insertion order and cannot be evicted by the next
assignment. **Sort prefs:** 104 groups (100 sections + 4 fixed).
- **Feature overrides:** `getOverrides()` filters to current-manifest
boolean ids on read only — no write-back from the render-path getter.

## Review-driven revisions

- `237f25e4` — three narrow changes from the first adversarial review
(no render-path storage write, icon cap aligned to relay constant,
count-gated profile trim).
- `d864ffb0` — fixes for the two GitHub review findings on `237f25e4`:
(P1) mute/star bounding switched from false-tombstone-first eviction to
pure recency, with regressions proving an at-capacity unmute/unstar
survives bounding and the pre-publish LWW merge; (P2) unchanged
agent-audience touches now refresh LRU order (no revision advance, no
emit), with a subscriber-mounted regression.
- `3ddbb26d` — MRU guard from the second adversarial VERIFY: the P2
touch path skips clone/persist entirely when the scope is already
most-recently-inserted, eliminating repeat synchronous localStorage
writes from render-path effects. Test proves a non-MRU identical touch
writes exactly once (scope persisted last) and an already-MRU touch
writes zero times.
- `e220ccd9` — fixes for the second GitHub review round (Carl, on Wes's
behalf): (1) mute/star bounders preserve the just-mutated key so a
same-second mutation at capacity survives its own bounding; merge/sync
call sites unchanged; (2) `assignChannel` delete/reinserts the touched
key so an at-capacity reassignment isn't evicted by the next new
assignment. Regressions at storage and hook level for both;
negative-control run of the 7 new tests against the old sources: 7 fail.

## Validation

- Full desktop suite 4555/4555 at both `d864ffb0` and `3ddbb26d`, plus
desktop-check/typecheck via the push gate; focused storage/audience
tests 62/62 at `d864ffb0`, 14/14 audience suite at `3ddbb26d`.
- Independent adversarial review: APPROVE at `88a55aee` (including 100
smoke E2E specs covering every seeded store, run manually since push
hooks exclude Playwright), then a second VERIFY pass: **VERIFIED at
`d864ffb0`** — P1/P2 confirmed closed via negative-control runs of the
new suites against the old sources, plus smoke Playwright on the
mute/star/audience specs (17 passed). That VERIFY requested one
pre-merge change (no localStorage writes from the render path), landed
as the narrow MRU guard in `3ddbb26d` within the reviewer's stated
no-re-review boundary. A third VERIFY pass: **VERIFIED at `e220ccd9`** —
both findings from the second GitHub review confirmed closed by
sensitivity testing (new tests fail on old sources), hostile same-call
section-trim case constructed and passed, full suite 4562/4562 re-run
independently.

Authored by Meeseeks (agent), reviewed by Beth (agent), integrated by
Rick (agent). Discussion: Buzz channel time-based-localstorage-eviction,
thread 0d85a73ca43e54748128f89c3512a4726131bf5473253395d46bf8f3a7b58bd4.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Meeseeks <2e96988f190ed1bd3c568760103aa4cadb2bc6195b832e252c984392c89039bd@buzz.block.builderlab.xyz>
## Summary

- add a SHA-pinned sccache action to reuse unchanged Rust compilation
units when the exact relay artifact cache misses
- keep pull requests read-only while preserving cache writes for trusted
`main` and `release` pushes
- stop saving isolated exact relay-artifact caches from PRs, reducing
cache churn
- preserve the exact artifact cache as the zero-build fast path

## Why this is an experiment

The relay artifact job currently misses its exact cache whenever any
file under `crates/**` changes, forcing a full workspace rebuild. PR
block#4975 spent roughly 21 minutes in that job for a one-file `buzz-sdk`
change. sccache targets the relevant reuse boundary—individual compiler
inputs—but the repository cache pool is already under heavy eviction
pressure, so this PR does **not** claim a proven timing win yet.

## Safety

- `Mozilla-Actions/sccache-action` is pinned to commit
`fc920bf0ec8de6ee65d409111f7ec508035751ba`
- `RUSTC_WRAPPER` is scoped only to `Build relay artifacts`
- PRs use `READ_ONLY`; trusted `push` runs (`main` and `release`) use
`READ_WRITE`
- the existing exact finished-artifact cache remains the first/fast path
- finished artifacts are saved only by trusted pushes, preserving the
former trust boundary
- workflow permissions remain `contents: read`; no `pull_request_target`
path is introduced
- the pinned action automatically emits sccache
hit/miss/error/write/duration statistics in its post-job hook

## Validation

- `actionlint .github/workflows/ci.yml`
- `git diff --check`
- desktop release-cache contract test
- release-ref contract test
- independent code-shape reviews from Princess Donut and Mongo: 9/10, no
remaining findings

## Measurement plan

1. purge obsolete PR-scoped `relay-artifacts-*` cache entries before
measurement
2. merge/push a trusted writer to populate sccache
3. run a representative one-crate PR
4. compare relay job duration and automatic sccache statistics against
the 21–22 minute baseline
5. retain this only if the warm run demonstrates material improvement

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
…5493)

## Summary

- restore private-channel invitations for every active member
- keep owner/admin-only enforcement for elevated role grants, active
role changes, and removals
- preserve block#4612's unrelated Desktop/mobile failure handling and
hardening
- add relay coverage for the ordinary actor/target role matrix
(`member`, `guest`, `bot`)

## Validation

- pre-push hook passed on `7de700e17642ad7e10155f9537033168d9249268`:
branch skew, Desktop checks/typecheck/tests/Tauri checks, mobile tests,
and Rust tests
- `cargo test -p buzz-test-client --test e2e_relay --no-run`
- `cargo fmt --all -- --check`
- `git diff --check`
- Donut and Mongo independently reviewed the cross-layer authorization
behavior; Donut's role-matrix coverage finding is addressed in this
revision

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
…ck#5490)

Fixes block#3677.

## Problem

The renderer never quiesces: recurring timers, query polling, and
re-render tickers run at full rate whether the window is visible,
hidden, or minimized. Measured on a live installed app: **27.5% mean
renderer CPU visible vs 28.7% hidden** (60×1s `ps` samples of the
WebContent process; `sample(1)` dominated by
`WebCore::timerFired`/ThreadTimers, microtask checkpoints, JSON parsing,
style matching). Matches all three reproductions in block#3677 (macOS
prerelease, Linux/WebKitGTK A/B/A minimize test, stable macOS).

Per-timer instrumentation (dev build, wrapped
`setInterval`/`setTimeout`/rAF) attributed the recurring work: `useNow`
60 fires/min, 40 active TanStack refetch intervals, agent-turn pruning
12/min, auto-restart ticks, huddle/reminder polls — none
visibility-gated.

## Fix (two-tier gating, standard mechanisms only)

Two separate signals in `desktop/src/shared/lib/useDocumentVisible.ts`,
because they mean different things and (see residuals) are delivered
differently on macOS:

- **`useDocumentVisible`** — true Page Visibility only
(`document.visibilityState`). Gates local UI work that must keep running
on a visible-but-unfocused window: `useNow` relative clocks, agent-turn
pruning, huddle bar state/model-status polling, auto-restart tick.
Hidden ⇒ paused; `useNow` snaps to fresh `Date.now()` on return.
- **`useAppFocused`** — visible AND `document.hasFocus()`. Gates network
refetch polling only (`useFocusedRefetchInterval`, ~15 query families:
forum/home/agents/channels/templates/emoji/user-status/projects/workflows/persona-catalog/pulse/presence-list).
TanStack's `focusManager` is wired to this signal (idempotent, single
install) with `refetchOnWindowFocus: true`, so stale queries refresh
promptly on return. Deliberate side effect, documented in code: query
retries pause on blur; mutations and the presence heartbeat (`retry: 0`)
are unaffected.
- **Never gated:** reminder due-notification poll (fires while
hidden/unfocused — extracted to `reminderNotificationPoll.ts` with
regression test), huddle pipeline hot-start (`check_pipeline_hotstart`
survives backgrounding for the duration of a huddle), relay stall
watchdog, presence heartbeat. Live WebSocket delivery untouched
throughout.
- Huddle model-status indicator now clears only on huddle phase end, not
on visibility/focus changes.

## Validation

- Instrumented dev build, populated channel, fires/min:
**visible+focused** unchanged (`useNow 60 / prune 12 / watchdog 6 /
query 4 / auto-restart 4 / low-rate huddle/reminder/presence`);
**visible+blurred**: query polls 0, UI clocks continue (`useNow 60 /
prune 12`), reminders 2, presence live; **truly hidden**: only watchdog
6, reminders 2, presence ~2 — everything else 0. Return restored
visible+focused, selection preserved, queries refreshed.
- Hide-vs-blur decomposition (instrumented probe instance,
AppleScript-driven): on macOS WKWebView, Cmd-H / minimize / full
occlusion did **not** reliably produce `visibilityState === "hidden"` —
they reliably produced focus loss. The CPU-dominant quiescence path on
macOS is therefore the focus gate; the visibility gate is exercised
fully on platforms that report hidden (e.g. WebKitGTK minimize per the
Linux repro).
- Gate-regression tests: signal separation, `useNow` hidden-pause +
fresh-snap on return, focus-gated interval pause/resume-with-refresh,
reminder delivery while hidden+unfocused (5 new, plus primitive wiring
tests).
- Push gate: desktop check, typecheck, full desktop suite **4549/4549**
at `1237548d1`.

## Known residuals

- **macOS hidden-signal limitation:** because WKWebView rarely reports
`hidden` on app-hide/minimize, hidden-only consumers (`useNow`, prune,
huddle UI polls) may keep ticking on macOS when the app is hidden. These
are cheap local timers; the expensive network polling still quiesces via
focus loss, which is what the measured 28% CPU was attributed to. If the
residual local-timer cost proves measurable, the follow-up is bridging
Tauri window hidden/minimized events into the visibility signal.
- End-to-end CPU confirmation on a packaged build is the post-merge
follow-up (against the 28% idle baseline).
- Visible-state costs (skeleton animation pileups on stuck loading
views, per-poll JSON payload churn) are intentionally out of scope —
separate follow-up issue.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Meeseeks <2e96988f190ed1bd3c568760103aa4cadb2bc6195b832e252c984392c89039bd@buzz.block.builderlab.xyz>
## Summary
- standardize onboarding navigation and horizontal step transitions
- refine the avatar editor with live preview, segmented modes, search,
skin tones, and reduced-motion-safe feedback
- simplify harness/default-model actions and supporting copy

## Testing
- desktop typecheck and static guards
- desktop E2E build
- 9 focused onboarding smoke tests
- 4 focused onboarding/profile integration walkthroughs
- 4,535 desktop unit tests

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
## Why
`buzz channels update` could already change name, description, and TTL,
but the SDK/relay/DB path for channel visibility was unreachable from
the CLI.

## What
- Add `--visibility open|private` to `buzz channels update`
- Pass the visibility value through to `build_update_channel`
- Add guard tests proving empty updates still fail and visibility-only
updates are accepted

## Risk Assessment
Low — this is limited to the buzz-cli update command and uses existing
SDK validation plus existing relay/DB handling.

## References
- Spike notes: `RESEARCH/SPIKE_CHANNEL_VISIBILITY_TOGGLE.md`
- Local validation: `cargo test -p buzz-cli`

Generated with Codex

Signed-off-by: Cameron Hotchkies <chotchkies@block.xyz>
Co-authored-by: Lazy Joe <dbd8c9941ba6dafebcef0abc015b65e75d52e7452f2ce483c9c3fd4d180f2504@buzz.block.builderlab.xyz>
## Buzz Desktop release v0.5.9

- **Frozen main:** `f8f2ef0440e7a074223ec04dc3b32d817b8b9d9b`
- **Reviewed candidate:** `ee33722615ca1e7b8efb03e2ed641d99448c8899`
- **Previous desktop release:** `desktop-v0.5.8`
- **Proposed immutable tag:** `desktop-v0.5.9`

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

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

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
**Category:** fix
**User Impact:** Buzz pull request, issue, and repository links now show
compact, useful metadata cards in received messages, including messages
sent by agents and the CLI.

**Problem:** Sender-authored snapshots protect recipients from external
preview fetches, but that change also removed recipient-side cards for
trusted Buzz entity links when the sender did not attach snapshots.

**Solution:** Resolve recognized Buzz entities only against the active
relay and show signed repository identity, title, and compact builder
context with the current inline Buzz mark in the favicon slot, but
without avatars, thumbnails, or external image fetches. Entity metadata
wins over conflicting sender snapshots, while unsupported or unavailable
metadata retains a safe text fallback.

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

**desktop/playwright.config.ts**
Adds the entity-link regression spec to the smoke test project.

**desktop/src/features/messages/ui/useComposerLinkPreviews.tsx**
Treats recognized Buzz entity cards as complete without generating
snapshot tags and retains fallback cards when relay metadata is absent.

**desktop/src/shared/lib/useResolvedLinkPreviews.test.mjs**
Covers kind-scoped entity detection, trusted relay metadata, root-scoped
lifecycle queries, exact single-repository root binding, image-less
pending state, and fallback behavior.

**desktop/src/shared/lib/useResolvedLinkPreviews.ts**
Resolves signed repository, pull request, and issue metadata from the
active relay. Entity roots fail closed unless they carry exactly one
matching repository tag; lifecycle queries are root-scoped before
limits; successful metadata remains stable until relay/community reset,
and PR commit context uses the immutable root event rather than an
unindexed update query.

**desktop/src/shared/ui/compact-link-preview-attachment.tsx**
Uses Buzz repository identity as the compact card provider and avoids
reserving thumbnail space for image-less entity cards.

**desktop/src/shared/ui/markdown.tsx**
Routes message cards through the combined entity/snapshot preview hook.

**desktop/src/shared/ui/markdown/useMessageLinkPreviews.test.mjs**
Proves relay-authenticated entity metadata beats a forged sender
snapshot while preserving mixed-link content order.

**desktop/src/shared/ui/markdown/useMessageLinkPreviews.ts**
Combines recipient-resolved Buzz entities with sender-authored external
snapshots using explicit trust precedence and first-seen ordering.

**desktop/tests/e2e/entity-link-recipient-cards.spec.ts**
Exercises repository identity, PR workflow context, repository metadata,
image-less rendering, and composer send behavior for agent/CLI-style
entity links.

</details>

## Reproduction steps

1. Open a channel containing a message sent without `link-preview` tags
whose content includes valid `buzz://pr`, `buzz://issue`, or
`buzz://repo` links.
2. Confirm each card shows its repository identity and signed title;
PRs/issues also show compact lifecycle context, and repositories show
description/status/default branch.
3. Confirm the cards use the Buzz mark in the favicon slot with no
avatar, thumbnail, or reserved image area.
4. Compose and send a message containing a Buzz entity link; confirm
sending is not blocked waiting for a snapshot.
5. Send a message containing both a Buzz entity link and a
snapshot-backed HTTPS link; confirm cards follow content order and the
HTTPS link remains sender-snapshot-only.

## Screenshots

### Recipient view — Buzz-branded metadata cards

Repository identity, title, and compact builder context render with the
current inline Buzz mark in the favicon slot and no avatar, thumbnail,
or reserved image space.

![Recipient view showing Buzz-branded PR and repository
cards](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5494/01-recipient-entity-cards-current-buzz-mark.png)

## Validation

At commit `7bc70b0a9f70392bd062ed25b1d2362cc4021a40` with a clean
working tree:

- Pre-push hooks passed: branch skew, desktop check, desktop typecheck,
and full desktop unit suite
- Full desktop unit suite: 4,560 passed
- Purpose-built Playwright regression after a fresh E2E build: 2 passed
- Screenshot regenerated from the same commit and visually inspected

Originating conversation: Buzz channel
`c2859932-b679-4091-9c7e-f5a65deddd64`, thread
`93c3e7be59a8d1ec10b4992efd783a2a79f253a10f10d39746c6ad41b0d5bb42`.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
…olve (block#5245)

## Overview

**Category:** fix  
**User impact:** Link previews no longer disappear when a message is
sent while preview metadata or media is still settling. Fast Enter,
rapid Enter, and confirmed-draft auto-send now preserve the preview
without duplicate sends or stale tags.

**Problem:** The composer could look ready before its sender-authored
snapshot tag existed. Send paths could then race preview
resolution/upload, while debounced preview state could attach a tag for
a URL that had already been removed. The same timing also caused
confirmed-draft auto-send to be consumed without sending.

**Solution:**
- Debounce preview resolution to avoid card flicker while typing, then
disable every submit path while a supported external preview settles. A
2-second escape cap still permits a bare-link send if resolution stalls.
- Keep submit synchronous: acquire a composer-local lock before
asynchronous send work, read ready tags from the live URL set, and
reject Enter/form submits while a snapshot is pending.
- Retry confirmed-draft auto-submit until preview settling clears, then
submit exactly once.
- Upload thumbnail and favicon independently. A failed upload shows a
toast and degrades to the surviving media (or text-only) rather than
leaving the card spinning.
- Exclude message-edit mode from preview resolution, upload, and Save
gating. Edit-time preview snapshots remain follow-up block#5273.
- Canonicalize fragment-bearing URLs for preview lookup/snapshot
identity while preserving the original fragment links in message text.

## Link preview state walkthrough

Captured using PR block#5245's actual public Open Graph metadata and artwork.
The deterministic E2E bridge controls only upload timing so the
transient disabled state can be captured reliably.

| State | Expected behavior | Screenshot |
| --- | --- | --- |
| **1. Snapshot upload pending** | The real PR preview is visible, but
Submit remains disabled until its sendable snapshot tag is ready. Click
and Enter cannot send a bare link during the settling window. | ![PR
5245 pasted with its real preview visible and Submit
disabled](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5245/01-real-pasted-submit-disabled.png)
|
| **2. Snapshot ready** | Once snapshot upload settles and the tag is
ready, the same preview remains and Submit becomes active. | ![PR 5245
preview ready in the composer with Submit
enabled](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5245/02-real-resolved-submit-enabled.png)
|
| **3. Message sent** | The sent event carries the snapshot tag and
renders the PR title, description, and artwork inline instead of
degrading to a bare URL. | ![PR 5245 real link preview rendered inline
in the message
list](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5245/03-real-sent-preview-inline.png)
|

## Regression coverage

- Enter during metadata resolution or snapshot upload cannot send early.
- Paste-and-immediate-Enter sends after settling; rapid Enter submits
exactly once.
- Confirmed-draft auto-send waits for settling and fires exactly once.
- Removed/replaced URLs cannot leak stale snapshot tags or media refs.
- Thumbnail upload failure toasts and sends with the surviving favicon.
- Edit mode does not resolve/upload previews or gate Save.
- Fragment variants share a canonical preview while original fragment
links remain clickable.
- Existing ready-preview, suppression, bare-link fallback, and
multi-preview behavior remains covered.

## Reproduction steps

1. Open a channel and paste a supported external URL into the composer.
2. Press Enter immediately, before preview metadata/media finishes
settling.
3. Before this fix, the event could be sent without its preview snapshot
(or confirmed-draft auto-send could be lost). With this fix, submit
waits behind the disabled state and fires once with the matching
snapshot tag.
4. Remove or replace the URL and press Enter inside the debounce window.
The sent event contains tags only for URLs still present in the
submitted content.

## Validation

All required PR checks are green, including Desktop Core, Desktop Smoke
E2E shards, Desktop E2E Integration shards, macOS build, security
checks, and DCO.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
…ock#5534)

Hardens the Databricks PKCE OAuth code in
`crates/buzz-agent/src/auth.rs`. Two fixes.

## Token cache is owner-only across its whole lifecycle, and race-safe

The PKCE cache holds both the access and refresh tokens, but `save()`
wrote it with a bare `fs::write` + `fs::rename`. Under a `022` umask the
file landed world-readable, and the fixed `*.json.tmp` temp name races
across concurrent savers sharing `$HOME` — one writer's `rename` can
fail on another's half-written temp.

**On write**, `write_private_cache()` creates a temp file with
owner-only permissions from the moment it exists — mode `0o600` on Unix
via `OpenOptions::mode` — writes and fsyncs it, then renames over the
destination. The rename swaps the inode wholesale, so a pre-existing
cache file with loose permissions is *replaced* by the new private inode
rather than inheriting its mode. `unique_suffix()` (getrandom, timestamp
fallback) gives each write a distinct temp name, and a drop guard
removes the temp on any failure path.

**On load**, owner-only is enforced as a cache lifecycle invariant, not
just a write-path property. A world-readable cache left by an older
buzz-agent was previously read straight into memory and returned on the
fresh cache-hit path without ever invoking `save()`, so a token file
with no advertised expiry could stay exposed indefinitely.
`read_cache()` now funnels every load — initial and cross-process
re-reads — through `read_private_cache()`, which on Unix opens with
`O_NOFOLLOW` (kernel-level symlink refusal, no stat/open TOCTOU),
requires a regular file, and `fchmod`s the pinned handle to `0o600` when
any group/other bit is set. A cache that cannot be secured is treated as
absent, so callers fail closed to a fresh flow rather than trusting an
exposed file.

## OAuth callback no longer reflects untrusted input

The localhost callback embedded the untrusted `error` query param
straight into the HTML response — an XSS sink on the redirect page — and
routed that same raw value into the error string that reaches the logs.

`callback_outcome()` is now a pure function returning `(result,
static_page)`: the browser always sees a fixed literal page that embeds
no request parameter, and failure detail travels only through the result
channel. `sanitize_callback_detail()` strips control characters (CR/LF
log-line injection) and caps length before that detail enters the error
string bound for the logs.

## Deferred: Windows owner-only ACLs

Windows owner-only protection is out of scope for this change. The
goose-parity route (`CreateFileW` with an owner-only SDDL
`D:P(A;;FA;;;OW)`) requires `unsafe` FFI, which this crate's
`#![forbid(unsafe_code)]` prohibits; reconciling that conflict is a
separate decision. Both platform seams — `create_private_temp_file`
(write) and `read_private_cache` (load) — have a `#[cfg(not(unix))]`
branch that relies on the default per-user ACLs and is the drop-in point
if Windows protection is added later. No new dependency and no `unsafe`
are introduced here.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Hayt <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
**Category:** fix
**User Impact:** YouTube video links now resolve into reliable previews
instead of intermittently appearing as bare links.

**Problem:** Buzz intentionally reads at most **256 KiB** of page HTML
when building a generic link preview. The YouTube response that exposed
this bug was roughly **1.3 MiB**, with its Open Graph metadata beginning
around **686 KiB**—well beyond Buzz's bounded read—so extraction
returned no usable preview. YouTube can move that metadata between
responses, which explains why the same link may appear to work in one
build or request and fail in another; raising the generic cap would
increase bandwidth and allocation for every site while still scraping an
unstable application document.

**Solution:** Route recognized YouTube video URLs through YouTube's
structured oEmbed endpoint instead of parsing raw watch-page HTML. The
provider response is capped at **64 KiB** and retains Buzz's existing
HTTPS validation, pinned DNS/SSRF protection, disabled redirects,
timeouts, metadata bounds, and thumbnail sanitization. Provider failures
return no preview rather than falling back to fragile HTML scraping, and
embed URLs are canonicalized safely, including percent-encoded video
IDs.

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

**desktop/src-tauri/src/commands/link_preview.rs**
Recognizes supported YouTube URL forms, fetches bounded JSON metadata
from YouTube oEmbed, canonicalizes embed links, and adds response,
URL-boundary, malformed-data, resource-limit, and encoded-ID
regressions.

**desktop/src-tauri/Cargo.toml**
Declares percent decoding as a direct desktop dependency for safe
embed-ID canonicalization.

**desktop/src-tauri/Cargo.lock**
Records the direct dependency in the desktop package lock entry.

</details>

## Reproduction Steps

1. On the base branch, paste a YouTube URL whose Open Graph metadata
falls beyond the first 256 KiB of the raw watch-page response and
observe that no preview is produced.
2. Run this branch and paste a YouTube watch, mobile, music, `youtu.be`,
Shorts, live, or embed URL into the composer.
3. Confirm the preview resolves with the video's title, creator, and
sanitized thumbnail without downloading the full watch-page HTML.
4. Try an embed URL with a percent-encoded ID, such as
`https://www.youtube.com/embed/%64Qw4w9WgXcQ`, and confirm it resolves
to the same video.
5. Try a YouTube lookalike domain or an embed ID containing encoded
separators and confirm it is not routed through the provider path.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
**Category:** fix
**User Impact:** Previously selected community themes are preserved when
opening a relay through onboarding, while first-time theme migration
still completes for communities with no saved theme.

**Problem:** During community initialization, desktop queried theme
history before establishing live delivery. If a replacement event
arrived while an empty history query was in flight, the client could
incorrectly treat the theme as absent and publish the default over the
user's saved selection.

**Solution:** Subscribe before fetching history, expose whether live
readiness reached EOSE, flush buffered live events before resolving
EOSE, and retain the newest delivered replacement through hydration.
Seed the inherited/default theme only when both live and history
snapshots reach EOSE with no valid or unreadable event; subscription
failures, CLOSED, readiness timeout, relay failure, and unreadable
events fail closed without publishing.

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

**desktop/src/shared/api/relayClientSession.ts / relayClientShared.ts /
relayClosedRecovery.ts**
Distinguish EOSE from CLOSED/timeout readiness and flush buffered events
before resolving an EOSE fence.

**desktop/src/shared/theme/CommunityThemeController.tsx**
Seed and complete first-community migration only for confirmed absence;
uncertain hydration remains non-publishing.

**desktop/src/shared/theme/communityThemePreference.ts**
Keep the inherited appearance for the first migrated community and the
stable default for later empty communities.

**desktop/src/shared/theme/communityThemeSync.ts**
Arbitrate live and history results into valid, confirmed-absent,
invalid, or unavailable hydration outcomes.

**Tests**
Cover EOSE/CLOSED readiness, subscription failure, timeout, unreadable
and live-racing events, no-op initialization, and first-to-later
community fallback isolation.

</details>

## Reproduction steps

1. Save a non-default appearance for a community relay.
2. Remove the community locally, then open the same relay again through
onboarding.
3. Arrange for the saved replacement event to arrive live while the
initial history query returns empty.
4. Confirm the saved appearance remains selected and the client does not
publish the default theme over it.
5. On an account with no theme records, open a first empty community and
confirm its inherited appearance is migrated; open a later empty
community and confirm it starts from the stable default.

## Validation

- Pre-push desktop checks, typecheck, and full desktop tests: passed at
`f79556b0e`
- Focused theme/relay readiness tests: 38 passed
- Desktop file-size ratchet and diff check: passed

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
…l selection for mesh (block#5289)

Shared compute now has exactly two model choices: MeshLLM's virtual
`mesh`
model, or a model you name. Buzz picks between them in one place, and
buzz-agent no longer knows meshes exist.

## What changed

- **MeshLLM v0.74.0 → v0.75.1.** v0.75.0 added
`degrade_to_single_model`, so a
  `model=mesh` request is answered by one served model when there is no
committee to form, instead of failing. v0.75.1 adds Mesh-LLM#1196, which
skips stale pre-0.75 runtime cache entries rather than aborting startup
on
them — without it, anyone who had run mesh on 0.73/0.74 could not start.
- **Deleted the client-side mesh catalog probe.** buzz-agent used to
poll
`/v1/models` (5s TTL, 30s cooldown, two-observation debounce) to decide
whether `mesh` was safe to send. MeshLLM now decides per request, so the
  polling, its hysteresis, and its 503 fallback are gone.
- **One mapping point.** `relay_mesh_wire_model()` turns the stored
value into
a wire name: `auto` becomes `mesh`, a named model passes through. The
spawn
env, the ACP harness, and the readiness probe all use it, so they cannot
disagree — previously `BUZZ_ACP_MODEL` and the probe both said `auto`, a
name
  the mesh does not advertise.
- **Removed the `nostr-relay-pool` advisory exception.** block#5404 allowed
RUSTSEC-2026-0243 "after mesh-llm migrates to nostr-sdk >= 0.45".
v0.75.1
does, so the retired crate is gone from both lockfiles and the exception
  would only mask a future advisory for it.
- **Deleted `scripts/ensure-mesh-native-runtime.sh`** and its six
justfile call
sites. It built llama.cpp from source into the runtime cache; the app
already
  downloads the signed release runtime itself, and CI never called it.

## Why it is better

**−639 lines of Rust.** Availability is decided by the node that knows
the
answer, per request, instead of by a client cache that could be stale
for up to
30 seconds. A second worker joining now takes effect on the next request
rather
than after two confirming probes.

## Behaviour change

A 503 on an explicit `mesh` request takes the ordinary transport retry
under
the same model instead of failing over to a second one — there is no
second
model to fail over to now. MoA repairs partial committee results
internally
before it reaches that point.

## Validation

`crates/buzz-relay/examples/mesh_agent_e2e.rs` now sends `mesh` where it
previously sent `auto` or the physical model id, so no leg was covering
what
Buzz actually puts on the wire. 4/4 on gemma-4-E4B, gemma-4-26B-A4B, and
Qwen3-8B — including a real ACP tool call through `mesh` into
buzz-dev-mcp,
asserted by reading the written file back off disk.

Hand-tested in the desktop app on both gemma-4 sizes: picked Auto, agent
logged
`model_id=mesh`, replied in channel.

## Not covered

A committee that forms and then loses a worker returns 502, and that
needs two
workers to reproduce — not testable on one machine.

---------

Signed-off-by: Michael Neale <michael.neale@gmail.com>
Co-authored-by: Michael Neale <michael.neale@gmail.com>
…home-feed (block#5535)

Fixes the 0.5.9 sluggishness Wes reported in app-slowness-mac (channel
list slow, content slow).

## Problem

block#5490 (shipped in 0.5.9) flipped ~20 query sites to
`refetchOnWindowFocus: true` and wired TanStack focusManager to app
focus. Instrumented at that exact commit: regaining focus after >60s
away fires **7 query fetches within 2ms**, including `get_channels`,
which settles at **~3.6s** (production probe: median 3.2s at 1,133
channels — 8 serial round-trips, 1,133-filter last-message batch). In
0.5.8 this burst was zero by configuration. Net: a burst of fetch/parse
contention exactly when the user returns to the app.

Relay ruled out: v0.2.1 small reads are 2–4ms upstream; nothing in
v0.2.0..v0.2.1 degrades the query path. The O(N) `get_channels` design
is a pre-existing issue (June analysis) — this PR fixes the new stampede
that made it user-visible.

## Fix

Raise `staleTime` to 5 minutes on the two expensive focus-refetch
families — `channels` and `home-feed` — so a focus return inside that
window serves cache instead of refetching. `refetchOnWindowFocus: true`
only refetches stale queries, so genuinely old data still refreshes on
return.

Unchanged: focused polling cadence (60s channels / 30s home-feed;
interval refetches ignore staleTime), block#5490 blur quiescence (no changes
to `useDocumentVisible.ts`/`queryClient.ts`), all push-style
invalidation paths (`invalidateQueries` bypasses staleTime), and
channels cold-start revalidate (`initialDataUpdatedAt: 0`).

## Validation

- New regression test
`desktop/src/features/home/focusRefetchPolicy.test.mjs` (4/4): fresh
focus return → 0 fetches; stale → 1; polling constants locked.
- Pre-push gate at the reviewed tree: desktop-check, desktop-typecheck,
full desktop-test **4588/4588**.
- Independent adversarial review (Beth): APPROVE at tree `4e2546ec` —
verified fresh-skip/stale-refetch against query-core 5.100.14 source,
polling-cadence via browser-simulated probe, side-effect sweep of all
invalidation paths clean. Sole CHANGE was commit trailers, fixed by
amend (tree unchanged).

## Known residual

Focus returns after >5min still fire the full burst including the
~3.2–3.6s `get_channels`. This cuts stampede frequency, not magnitude —
the O(N) `get_channels` relay path (RESEARCH/GET_CHANNELS_SLOWNESS.md)
is the follow-up that fixes magnitude.

Diagnosis: Summer (focus profiling) + Morty (relay probe); implemented
by Meeseeks; reviewed by Beth; integrated by Rick. Thread:
app-slowness-mac
e78fad29380d9a0974c9d673910450994a228781ddce133a8cedbd90504d95be.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Meeseeks <2e96988f190ed1bd3c568760103aa4cadb2bc6195b832e252c984392c89039bd@buzz.block.builderlab.xyz>
## Summary

- skip the channel subscription catch-up request when the authoritative
channel window was fetched successfully within the existing five-minute
freshness period
- keep the responsive deferred skeleton for populated channel switches
instead of briefly rendering empty-channel actions
- preserve a real empty-channel intro across the first appended message
only after React has committed that empty state

This is intentionally narrow. It does not claim to solve the separate
sidebar startup cost or general main-thread stalls found during the
investigation.

### Related issue

N/A — no matching open issue or PR found.

### Testing

- pre-push desktop gate on `f1be6beea90b9715e04e5fc65cc5cfbe8210e0d9`:
  - desktop tests: 4,621 passed
  - desktop check: passed
  - desktop typecheck: passed
  - branch-skew: passed
- focused cache/surface/lifecycle tests: 62 passed
- manual diagnostic trace after rollback:
  - 16/16 channel revisits skipped catch-up refresh
  - 0 revisit refresh starts
  - 0 populated-channel empty/intro flashes
  - cached switches retained the deferred skeleton-to-list path

No screenshot: the regression is a transient channel-switch state and
request behavior, covered by lifecycle tests and the diagnostic trace
rather than a stable visual diff.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
…lock#5569)

## Problem

Canceling the native macOS file chooser leaves the composer's temporary,
detached `<input type="file">` without a `change` event or an explicit
cleanup path. Opening Finder again immediately creates a second detached
input while WebKit may still be unwinding the first picker. The newly
selected files can therefore fail to reach the upload pipeline. Drag and
drop is unaffected because it bypasses this picker lifecycle.

This does **not** add an automatic retry mechanism. “Retry” means the
user's next attachment attempt after canceling or after a prior
selection.

## Fix

- give each composer hook one hidden, body-mounted file input for its
lifetime instead of creating a detached one per click
- reset and reconfigure that input before every open, replace its
handler rather than stacking handlers, and remove it cleanly on unmount
- preserve normal selection, cancel then reopen, selecting the same file
again, and multi-select behavior
- accept canonical `text/html` attachments while continuing to serve and
render them strictly as inert downloads
- keep XHTML, SVG, JavaScript, and executable MIME types blocked

The picker change fixes the ownership/lifecycle bug at its source; it
does not retry failed uploads, add delays, or mask errors.

## Testing

- mandatory pre-push gate: branch-skew, desktop typecheck/tests/check,
Rust tests, and desktop Tauri checks passed on
`ea5a97adf957803935b28d63d32f9f332cf65287`
- `cargo test -p buzz-media --lib` (110 passed)
- `pnpm --dir desktop typecheck`
- focused Biome check for the three picker files
- picker Playwright regression: cancel/no selection then reopen, select
the same file again, and multiple selection (run on the source commit
before integration)
- HTML live-relay response regression added as ignored E2E because it
requires the S3-backed relay harness

## Manual verification

Playwright models cancellation with Chromium's
`FileChooser.setFiles([])`; it cannot exercise the native macOS Finder
panel/WebKit presentation lifecycle. Before merge, manually verify in
the built macOS app:

1. select a PNG normally
2. cancel, then immediately reopen and select a PNG
3. select the same PNG on a subsequent attempt
4. multi-select two PNGs

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Princess Donut <68157ebd23b3897c1991015c3038658ea916200c67d3a54620b0754d1b92f6e0@buzz.block.builderlab.xyz>
Co-authored-by: Mongo <5c25403eab7271f9f94ddd4f2b270e8cac2c92e2c830c51877cca6ec974ffb3f@buzz.block.builderlab.xyz>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Summary

- Share eligible self-authored or owned-agent thread messages into the
parent channel as new top-level messages.
- Link the shared message back to the exact root thread with a semantic
channel label and excerpt.
- Add a dedicated channel-arrow icon plus ownership and navigation
coverage.

## Validation

- Desktop lint, size, and text guards
- Desktop TypeScript build and all 4,543 unit tests
- Focused Playwright send-to-channel and thread-link navigation tests

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Summary

- add an opt-in native glass sidebar with opacity controls and live
theme previews
- refine sidebar spacing and Buzz-only active rows while preserving
production defaults
- unify settings section cards, subtitles, and agent runtime rows

## Validation

- repository format, lint, type, and file-size checks
- 4,538 desktop tests and 2,270 native desktop tests
- desktop and web production builds
- 1,261 mobile tests in the completed full gate
- focused Playwright appearance, sidebar, settings, pairing, and runtime
coverage

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Signed-off-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
## What changed

- unify Cmd+K and channel Cmd+F around a removable channel or
conversation scope
- add conservative fuzzy matching for people and channels while
preserving exact-match ordering
- make scoped message search complete for one-character queries and
expose up to 40 scrollable results
- keep the pre-scope channel or DM action in the normal results flow so
it scrolls away with the list

## Validation

- desktop TypeScript typecheck
- desktop text-size and file-size guards
- focused fuzzy-search unit tests (24 passed)
- focused search Playwright coverage (7 passed), including channel and
DM copy, one-character results/no-results, 40-result scrolling, and the
non-sticky scope action
- desktop E2E build
- visual review of channel, scoped, expanded-results, and DM states

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
## Why
Expose PostgreSQL datastore latency within existing request traces so
slow logical database operations can be identified without recording
tenant data or query arguments.

## What
- Add client spans around logical PostgreSQL operations across the
database facade, search, audit, replica fencing, and command persistence
- Use a dedicated `buzz_datastore` target and `db.system.name =
"postgresql"` for filtering and backend classification
- Exclude health-check database calls and scrub raw identifiers and
errors from newly traced paths

## Risk Assessment
Medium — this instruments frequently used datastore paths and increases
trace volume when enabled, but does not change SQL execution or
datastore behavior. Existing OpenTelemetry filtering controls export.

## References
- Pre-push clippy and fast unit-test hooks passed

Generated with Amp

---------

Signed-off-by: David Grochowski <dgrochowski@squareup.com>
Co-authored-by: Amp <amp@ampcode.com>
The HTTP bridge request log recorded route, status, and accepted but not
the event kind, so typing indicators (kind 7) and their deletions (kind
5)
were indistinguishable from real messages (kind 9). Every agent turn
produced
accepted:true lines whether or not a message was actually sent, which
twice
led debuggers to conclude a silent agent had published successfully.

Add kind to the Ok outcome and the tracing::info line so the publish
path is
self-describing without a database query.

Closes block#4676

Signed-off-by: Taksh <takshkothari09@gmail.com>
## Summary

- let Virtua own the initial visible timeline range instead of passing
every loaded row to `keepMounted`
- populate the existing bounded retention window after the virtualizer
reports its first settled viewport
- cover a 10,000-row timeline to prevent an all-history initial mount
regression

## Why

`useTimelineRetention` initialized its retained-key set with every
loaded timeline key. Those indices were passed to Virtua's
`keepMounted`, effectively defeating virtualization during initial
channel positioning until `onScrollEnd` pruned the set.

On a large real channel this grew WebContent into multiple gigabytes and
blocked the renderer main thread for 20+ seconds while WebKit laid out
and painted the retained rows. Starting with no retained rows restores
Virtua's visible-range mount; the existing reader-neighborhood and
visual-tail retention is populated once the viewport is measured.

## Validation

- `node --import ./test-loader.mjs --experimental-strip-types --test
src/features/messages/ui/useTimelineRetention.test.mjs`
- pre-push hook at `8e86a189de7e9a8f2cb119396c8f912ed9dacd6e`:
branch-skew, desktop-check, desktop-typecheck, and all 4,671 desktop
tests passed
- manual ablation against PR block#5599 on the affected profile: catastrophic
channel-switch stalls disappeared

## Authorship disclosure

Carl implemented and is posting this change on Wes's behalf.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
…events (block#5294)

A NIP-25 reaction whose target is a project root or project comment
(kind
1621 issue, 1618 PR, or a kind-1 comment on one) carries no h tag, so
channel_id is None on the reaction write path. The conformance-trace
emission asserted a channel was always present:

channel: channel_label(channel_id.expect("reaction path has channel")),

so the worker panicked at ingest.rs:2824. The row was inserted before
the
panic, so the client saw a failed request for a persisted event and
retried,
and the duplicate branch carried the same expect, head-of-line blocking
a
durable publish queue forever.

Mirror the message write's three-way split at the same seam:
(Some, true) -> WriteInsert, (Some, false) -> WriteDuplicate, (None, _)
-> WriteInsertGlobal. The conformance vocabulary already models
channel-less
writes; only the reaction path was missing it.

Closes block#4936

Signed-off-by: Taksh <takshkothari09@gmail.com>
Signed-off-by: Ravneet Arora <rarora@squareup.com>
wesbillman and others added 29 commits August 14, 2026 13:20
## Buzz Desktop release v0.5.12

- **Frozen main:** `757779bb1ef22cc4a1c233344baa0946d907e5a6`
- **Reviewed candidate:** `bfc34904adc414efcd8e9c5548dff82c3545b677`
- **Previous desktop release:** `desktop-v0.5.11`
- **Proposed immutable tag:** `desktop-v0.5.12`

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

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

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
)

## Summary

Projects v3 makes repository work shareable, discussion-aware, and
easier to scan in one coherent workspace. People can copy canonical
links, reopen the exact workspace tab, understand issue and pull-request
context at a glance, find related channel conversations, and assign or
unassign issues across Desktop and CLI.

- **Unified workspace** — top-level sections sit above repository
controls in one rounded workspace, with navigation positioned close to
the page heading. README and Files retain branch selection; every
section has a labeled icon header, and Issues and Pull Requests expose
creation from a consistent right-aligned action.
- **Repository management** — the repository selector is always
available, including single-repository projects. Its integrated add flow
lets project owners create a repository manually or select an existing
repository without a separate toolbar button.
- **Readable work-item lists** — issue and pull-request rows use
plain-language context instead of opaque metadata. Files, commits,
issues, pull requests, channels, and contributors share consistent row
density and right-aligned timestamps, while deterministic
fallback-avatar colors keep participants distinct on light backgrounds.
Inbox pull-request metadata wraps between complete phrases and truncates
long channel names instead of compressing copy into narrow columns.
- **Reliable entity links** — projects, repositories, issues, pull
requests, and commits have canonical `buzz://` links, preview cards, OS
deep-link routing, and tab-aware navigation. Reopening the same link
re-applies its destination instead of leaving the user on a locally
selected tab.
- **Related conversations** — repository and work-item views surface
channels discussing the current entity, including participants, channel
navigation, message context, and an explicit notice when discovery
reaches its 500-result cap.
- **Reversible issue ownership** — trusted assignment and unassignment
events work across Desktop, Tauri, `buzz-sdk`, and `buzz issues`.
Assignees appear in project views and the assigned inbox, while
authorized users can remove assignments directly from the assignee row.

Assignment state is derived chronologically from labeled Nostr notes.
Issue authors and repository owners may change any assignee; other users
may only assign or unassign themselves. Shared golden fixtures keep
entity-link grammar and validation aligned across TypeScript and Rust.

The branch also updates `webbrowser` to the patched release for
RUSTSEC-2026-0257.

### Related issue

N/A.

### Testing

- [x] `just ci` — formatting, lint, typechecking, unit tests, and builds
passed
- [x] Full pre-push suite — organization, branch-skew, Desktop checks,
typechecking, and tests passed on the latest push
- [x] `cargo test -p buzz-cli` and focused `buzz-sdk` assignment tests
passed
- [x] Focused Tauri recipient-note and 500-result search-limit tests
passed
- [x] Desktop entity-link and issue-assignment unit tests passed
- [x] Playwright smoke coverage passed for assignment, repeated
entity-link navigation, repository create/select flows, section headers
and actions, timestamp alignment, timeline icons, sentence-style
issue/PR metadata, header spacing, avatar contrast, and Inbox metadata
at stacked and side-rail breakpoints
- [ ] Manual staging pass: link round-trips, Channels tab, assignment
flows, and inbox routing

### Screenshots

Pull requests explain who opened the request, where it lives, and which
branch it comes from; fallback avatars remain visually distinct.

![Pull request list with conversational
metadata](https://raw.githubusercontent.com/block/buzz/2a536de86f7e6f79b349d7bc147b2923ff2b817d/pr-5624--05-pr-list-metadata.png)

Issues use the same sentence-style hierarchy while keeping status and
recency easy to scan.

![Issue list with conversational
metadata](https://raw.githubusercontent.com/block/buzz/2a536de86f7e6f79b349d7bc147b2923ff2b817d/pr-5624--06-issue-list-metadata.png)

The wide Inbox detail keeps author, timestamp, and origin context
readable beside its metadata rail.

![Pull request Inbox detail with readable
metadata](https://raw.githubusercontent.com/block/buzz/e65b433e14b97c45365ed7b68ea402ec01d26615/pr-5624--02-pull-request-detail-wide.png)

[View the complete six-state Projects v3 screenshot
set](block#5624 (comment))
and [the compact/wide Inbox
comparison](block#5624 (comment)).


---

> Supersedes block#5624, whose head commit accumulated permanently-queued
required check suites (block-dco-check et al.) that GitHub never
dispatched. History flattened into a single signed-off commit on latest
main; tree verified byte-identical (`git merge-tree`) to merging the
original branch into main.

---------

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Co-authored-by: Wintermute <3f1797424fd9ad6653a83665c660517777cd7f8c228c0d5907f49e01537f3ca5@buzz.block.builderlab.xyz>
## Problem

PR block#5574's profile-panel redesign dropped `ProfileSummaryView`'s
`onCreateCard` prop — the only caller of `setCardMintTarget` — so the
entire Agent Trading Cards feature (block#3278) became unreachable from the
GUI while staying fully wired underneath: mint dialog, background job
store, viewer, gallery, composer chip, and the Rust
`mint_agent_card`/`save_agent_card` commands all survive at main. `git
log -S 'setCardMintTarget('` shows exactly two commits: the feature and
the accidental removal.

## Outcome

The mint trigger returns as a management row in the agent profile's Info
tab, directly under **Export agent**, gated `isBot && canManagePersona`
exactly like Duplicate/Export. Target resolution is byte-for-byte the
original logic: prefer the live instance pubkey, fall back to the
persona/definition id, allow locking only when an instance keypair
exists.

## Shape

- `UserProfileAgentManagementRows`: new optional `onCreateCard` row
(Sparkles icon, `user-profile-create-card-row`), placed after Export.
- Prop threaded `UserProfilePanel` → `ProfileSummaryView` →
`ProfileInfoTabContent` → management rows, mirroring `onExportAgent` at
every layer.
- The mint-target state + open callback move into a `useCardMint` hook
in `UserProfilePersonaDialogs` (beside the `CardMintTarget` type it
manages). This keeps `UserProfilePanel.tsx` at 999 lines — the file sits
at the size-ratchet cap and may not grow.

## Validation

- `pnpm check` green (biome, file-size ratchet, px-text,
pubkey-truncation).
- `pnpm typecheck` green.
- Full desktop unit suite: **4888 passed, 0 failed**.
- Profile e2e spec: **32 passed**, including the updated
management-row-order assertion and a new click → mint-dialog-visible →
Escape → closed exercise of the restored row.

Verified at `bff3110a0aeb3d63683eac9ed3e587829f9436da`, one commit atop
main `01f76ec97`.

Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
…5910)

## Summary

- replace the nested one-line shell quoting used to read the Playwright
package version
- write the resolved version to `GITHUB_OUTPUT` from a multiline shell
step

## Why

The `desktop-v0.5.12` release smoke job failed before executing tests
because Bash received escaped quotes inside command substitution and
parsed the Node expression as shell syntax.

## Validation

- `bash scripts/test-release-ref-contract.sh`
- isolated execution of the new shell fragment with a fixture
`@playwright/test/package.json`, producing `version=1.58.2`
- `git diff --check`

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Buzz Desktop release v0.5.13

- **Frozen main:** `09768100ec3420f0aa7cd278bd00fe0baab5de8d`
- **Reviewed candidate:** `a239e0f6793ac6e88ccf92cc231054090a9753cc`
- **Previous desktop release:** `desktop-v0.5.12`
- **Proposed immutable tag:** `desktop-v0.5.13`

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

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

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
## Summary

- remove the GitHub-hosted desktop smoke job from the desktop release
workflow
- remove the smoke result from manifest assembly dependencies and
promotion conditions
- retain the local smoke tooling for future repair and targeted
validation

The first release execution of this gate spent its full 10-minute
Playwright timeout traversing the 10,000-row fixture, then produced a
987 MB diagnostics upload. All signed platform builds succeeded, but the
smoke prevented manifest publication. This restores the previously
established release boundary while the harness is made suitable for CI
separately.

### Testing

- parsed `.github/workflows/release.yml` with Ruby Psych and asserted
the smoke job/dependencies are absent
- `scripts/test-release-ref-contract.sh`
- exact pushed commit passed the repository pre-push hook

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Buzz Desktop release v0.5.14

- **Frozen main:** `1b3dbcaaea882eeea90359c1db02e306d2f4f50a`
- **Reviewed candidate:** `391495e7d347d20b67e39e3c240d17ef63c5c2c0`
- **Previous desktop release:** `desktop-v0.5.13`
- **Proposed immutable tag:** `desktop-v0.5.14`

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

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

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
## Summary

- refine mobile message metadata, search spacing, and Activity filter
semantics
- add channel-parity Latest navigation and stable tail following to
threads
- synchronize Android composer/keyboard geometry and keep Latest spacing
stable across IME transitions

## Validation

- `bin/just mobile-check`
- `bin/just mobile-test` (1,276 tests)
- Pixel 10 install/launch and channel/thread keyboard, Latest, tail, and
back-navigation review
- signed iPhone install/launch workflow

## Snapshots

See the review snapshots below.

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
…ng over the community rail (block#5947)

## Summary

Collapsing the sidebar left a phantom copy of it painted over the
community/relay rail — opaquely on flat themes (vesper et al., which
made the rail look *removed*), and as ghost fragments (muted search-box
fill, truncated channel-name tails) on the Buzz themes whose chrome is
intentionally transparent for the gradient.

**Cause:** block#4281 made the app-sidebar layer `overflow-visible` (the
huddle drawer needs to escape it). That removed the ancestor clipping
the offcanvas collapse relied on: the sidebar slides to `left:
-sidebar-width` but kept painting, exactly over the `z-0` rail (`z-10`
sidebar layer).

**Fix:** the offcanvas-collapsed sidebar container is now `invisible` +
`pointer-events-none`, with `visibility` added to the transition list so
the 200 ms slide-out still animates and the flip happens only at the
transition's end. Theme-independent; no per-theme CSS touched; the
huddle drawer's `overflow-visible` is preserved.

## Before / after

Left 420px of the app with the sidebar collapsed. Before = unpatched
`origin/main` @ 69107dc; after = this branch. Same seeded state, same
build pipeline (`build:e2e` between checkouts).

| theme | before (ghost sidebar over the rail) | after (rail clean: A /
B / + visible) |
|---|---|---|
| vesper |
![before-vesper](https://raw.githubusercontent.com/block/buzz/3f98c576e062e51d976940725d84b4e0be7fd53c/pr-5947--before-vesper.png)
|
![after-vesper](https://raw.githubusercontent.com/block/buzz/3f98c576e062e51d976940725d84b4e0be7fd53c/pr-5947--after-vesper.png)
|
| buzz |
![before-buzz](https://raw.githubusercontent.com/block/buzz/3f98c576e062e51d976940725d84b4e0be7fd53c/pr-5947--before-buzz.png)
|
![after-buzz](https://raw.githubusercontent.com/block/buzz/3f98c576e062e51d976940725d84b4e0be7fd53c/pr-5947--after-buzz.png)
|
| buzz-dark |
![before-buzz-dark](https://raw.githubusercontent.com/block/buzz/3f98c576e062e51d976940725d84b4e0be7fd53c/pr-5947--before-buzz-dark.png)
|
![after-buzz-dark](https://raw.githubusercontent.com/block/buzz/3f98c576e062e51d976940725d84b4e0be7fd53c/pr-5947--after-buzz-dark.png)
|

Before shots: ghost `⌘K` search chip + blue active-item pill painted
over the rail column; on vesper the opaque panel hides the rail buttons
entirely. After: the rail's community buttons (A, B) and `+` are visible
and clickable in all three themes.

Reported by Thomas P in #buzz-bugs:
buzz://message?channel=e62570dd-33ad-42c5-b92b-75f2689f9694&id=9ea401ca1d009f555ca4324e136f8d8d8156db2f8afa3ff89fd038d2c16260f7

cc @klopez4212 — this touches the layout your block#4281/block#5478 work shaped;
please confirm it doesn't defeat the huddle drawer or glass intentions.
The change deliberately hides only the *offcanvas-collapsed* container,
nothing in the expanded path.

## Test plan

- [x] New Playwright regression spec `sidebar-offcanvas-rail.spec.ts`
(buzz / buzz-dark / vesper): collapsed sidebar must be `visibility:
hidden` + `pointer-events: none`, community rail stays visible and
interactive. **Fails on unpatched build** (verified), passes with the
fix.
- [x] Full desktop unit suite: 4,954 pass / 0 fail
- [x] `pnpm typecheck`, `pnpm check` (biome + file-size ratchet +
px-text + pubkey-truncation) green
- [x] Before/after screenshots above captured via the e2e harness on
both builds

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Co-authored-by: Wintermute <165f0c871dd2586bb18b6aa109eeaf57bb2132ff4d27b10120f4368a0f627022@buzz.block.builderlab.xyz>
…k#5116)

**Category:** new-feature
**User Impact:** Mobile users must confirm with Face ID, biometrics, or
their device passcode before sending their Buzz identity to Desktop.

**Problem:** A signed-in phone could send its full identity, including
the `nsec`, to a desktop without fresh local verification.

**Solution:** Require OS device authentication before opening the
identity-recovery scanner, retain that authorization only for the active
pairing session and short pairing window, and require fresh
authentication again if it expires before the identity payload is sent.
Normal app opening, identity import, and community removal remain
unchanged.

## Screencasts

| Enable Face ID | Use Face ID |
| --- | --- |
| ![Enabling Face ID during identity
import](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5116/enable-face-id.gif)
| ![Using Face ID for identity
export](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5116/use-face-id.gif)
|

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

**Android and iOS integration**
- `mobile/android/app/build.gradle.kts` declares the AppCompat
dependency required by the biometric activity theme.
-
`mobile/android/app/src/main/kotlin/xyz/block/buzz/mobile/MainActivity.kt`
uses the activity type required by the system authentication prompt.
- `mobile/android/app/src/main/res/values/styles.xml` and
`mobile/android/app/src/main/res/values-night/styles.xml` use the
compatible launch theme.
- `mobile/ios/Podfile.lock` records the native local-authentication
dependency.
- `mobile/ios/Runner/Info.plist` explains why Buzz requests Face ID
access.

**Identity policy and pairing flow**
- `mobile/lib/shared/security/sensitive_action_authorizer.dart` wraps OS
authentication and maps platform errors to stable app-level outcomes.
- `mobile/lib/shared/community/community.dart` and
`mobile/lib/shared/community/community_storage.dart` persist the
sensitive-action policy.
- `mobile/lib/features/invites/invite_join_provider.dart` assigns the
explicit policy for invite-created communities.
- `mobile/lib/features/pairing/pairing_provider.dart` gates export,
binds grants to the active community/session, reauthenticates expired
grants, and clears grants on every terminal path.
- `mobile/lib/features/pairing/pairing_page.dart` lets users choose
biometric protection while importing an identity.
- `mobile/lib/features/settings/settings_page.dart` wires pairing into
settings.
- `mobile/lib/features/settings/settings_page/connection_section.dart`
authenticates before opening export recovery and bounds the
foreground-resume wait.
- `mobile/pubspec.yaml` and `mobile/pubspec.lock` add and lock
`local_auth`.

**Coverage**
- `mobile/test/shared/security/sensitive_action_authorizer_test.dart`
covers native result mapping, unsupported devices, and single-flight
behavior.
- `mobile/test/shared/community/community_test.dart` and
`mobile/test/shared/community/community_storage_test.dart` cover policy
defaults and persistence.
- `mobile/test/features/invites/invite_join_provider_test.dart` covers
the invite policy.
- `mobile/test/features/pairing/pairing_page_test.dart` covers import
protection controls.
- `mobile/test/features/pairing/pairing_provider_test.dart` covers
export/import authorization, stale/reset/concurrent guards, malformed
payload cleanup, and no-export failure paths.
- `mobile/test/features/settings/connection_section_test.dart` covers
the tap gate, lifecycle resume, and timeout behavior.

</details>

## Reproduction steps

1. Pair an identity into the mobile app.
2. Open Settings and choose “Send identity to desktop.”
3. Verify Face ID, biometrics, or the device passcode is required before
the recovery scanner opens.
4. Cancel device authentication and verify the scanner does not open and
no identity transfer begins.
5. Authenticate, scan a Desktop recovery code, confirm the SAS, and
verify the identity transfer completes.

## Validation

At `be5620f5f10aa6cc16e86a4f01f102f3d9aeef9b`:
- `cd mobile && ../bin/flutter analyze` — no issues
- `cd mobile && ../bin/flutter test` — 1,368 tests passed
- `cd mobile/android && JAVA_HOME=$(/usr/libexec/java_home -v 21)
./gradlew app:assembleDebug` — debug APK assembled successfully

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
## Summary

- allow agents to build and run Flutter when it provides relevant
implementation or validation evidence
- keep mobile iteration fast by reusing simulators, incremental builds,
and configured staging or production communities
- correct stale CLI, E2E, CI, worktree formatting, and mobile launch
guidance
- point community singleton reset guidance at the canonical
implementation instead of duplicating a drifting inventory

## Validation

- `git diff --check origin/main..HEAD`
- `cargo run -q -p buzz-cli -- --format compact messages thread --help`
- `cargo run -q -p buzz-cli -- --format compact messages search --help`
- `just desktop-tauri-fmt-check` from the worktree
- pre-commit: mobile Dart formatting and `flutter analyze`
- pre-push: branch-skew check and full mobile test suite (1,465 tests)

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
…e manifest (block#5597)

## Summary

Centralizes model capability knowledge — thinking mode, supported effort
levels, wire routes, and human-readable labels — into a single manifest,
`scripts/model-capabilities.json`. Rust and TypeScript each get a small
interpreter that reads the same manifest, replacing hand-maintained
tables scattered across both languages that had already drifted apart. A
capability change is now a data edit, not parallel edits to two code
paths. Supersedes the codegen approach explored in block#3603.

A cross-language contract keeps the two interpreters honest:
`scripts/normative-corpus.json` is a golden snapshot generated from the
Rust resolver (103 vectors covering all six capability axes) and
replayed natively in TS. CI fails if either language disagrees with the
corpus or the corpus drifts from the resolver. Regenerate with `just
regen-model-corpus`.

## Behavior changes

- **Effort dropdown for `openai-compat` providers** no longer offers
`max`. The request path always clamped `max` to `xhigh` on the wire, so
the UI stops offering a value that was silently rewritten. UI-only,
wire-identical.
- **Databricks v2 routing (wire-visible):** uncurated endpoint names
carrying a bare Claude code-name segment (e.g. `goose-opus-5`) now route
to the MLflow chat wire instead of Anthropic Messages — they lose
Anthropic prompt caching but still succeed on a valid OpenAI-compatible
wire. Curated `databricks-claude-*` records and any name starting with
`claude` are unchanged. A handful of other uncurated/adversarial name
shapes similarly fall back to MLflow chat instead of pattern-matched
routes; every curated model resolves identically to before, all axes.
- **Curated model labels on the real discovery path.** The Databricks
API returns no display name, so discovery emits the raw endpoint id as
the model `name` (`{id, name: id}`) on every path. `ModelEntry.name` is
now curated at all four construction seams in `buzz-agent` — v2
discovery, v1 parse, the auth-empty default catalog, and the
configured-model fallback — via a read-only `databricks_registry_label`
lookup over the manifest's `databricks_v2` exact records; `id` stays the
raw wire/config value. A known id renders its curated label
(`databricks-gpt-5-5` → `GPT-5.5`), an unknown id passes through
unchanged, and the default-catalog row reads `GPT-5.5 (default
catalog)`. As a defense against older `buzz-agent` binaries and any
harness that echoes ids, `resolveModelLabel` treats a discovered name
equal to the trimmed id as absent and falls through to the registry
tier; a genuinely distinct name (including the suffixed default-catalog
label) still wins.

## Cleanup

Deletes the duplicated capability tables and their tests: the
`config.rs` gpt5 matchers, effort tables, and clamp logic; the legacy
segment-based Databricks v2 route classifier in `llm.rs`; and the TS
hand tables plus `effortTable.fixture.json`. All are replaced by
manifest lookups through the shared resolver — no line of capability
data exists in two places.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Summary

- discover shared managed agents from authenticated relay directory
records instead of treating channel membership as sufficient proof
- publish and refresh access-policy changes immediately so running
clients converge across machines without a restart or five-minute poll
- route profile edits through the exact managed instance and
stop/restart runtimes around access changes so unrelated edits cannot
silently widen access
- keep mention send-time revalidation and Block owner-only build
enforcement fail closed
- explain invalid custom provider/model configuration instead of leaving
Save silently disabled

### Related issue

Fixes block#3204

### Known residuals

- a brand-new remote agent's first policy record can wait for the
bounded directory poll when no authenticated directory coordinate exists
yet; send-time mention revalidation remains fail closed
- a failed remote-provider policy redeploy is recorded but cannot
undeploy the older provider instance until the provider protocol gains
the destructor tracked by block#5570

### Testing

- full Desktop unit suite: 4,961 tests passed
- focused profile editor Playwright workflow passed, including Customize
access edits and prompt-only edits after tightening an instance
- Desktop TypeScript, Biome formatting, file-size ratchet, Tauri checks,
and pre-push suites passed
- independently reviewed for authenticated directory trust, live
subscription teardown, runtime revocation ordering, fail-open edit
paths, and per-agent provider deployment serialization

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Signed-off-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz>
Co-authored-by: diegorumo <diegorumo@gmail.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz>
**Category:** improvement
**User Impact:** Users can scan what each workflow does and trigger,
edit, duplicate, enable, disable, or delete it directly from the
library.
**Problem:** The workflow list buried common actions and did not expose
each automation's trigger-to-action shape at a glance.
**Solution:** Add a responsive workflow library with a persistent create
tile, compact trigger/action diagrams, prominent workflow titles with
supporting descriptions, and shared card actions while preserving
existing detail, editor, and run-history entry points. Card toggles
refresh both list and open-detail caches so status and definition stay
consistent.

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

**desktop/src/features/workflows/ui/WorkflowActionsMenu.tsx**
Adds a shared card menu for trigger, edit, duplicate, enable/disable,
and delete actions.

**desktop/src/features/workflows/ui/WorkflowCard.tsx**
Reworks cards around the prototype's visual hierarchy: color-coded
trigger, action flow, sentence-case eyebrow, prominent title, supporting
description, status, channel, and update date without a footer clock
icon.

**desktop/src/features/workflows/ui/WorkflowsView.tsx**
Adds the responsive grid, create tile, mutation wiring, and list/detail
cache invalidation. Container breakpoints keep cards two-across at
medium widths and three-across in the 1280px desktop layout.

**desktop/src/features/workflows/ui/workflowDefinition.ts**
Adds immutable enabled-state updates plus narrow trigger and
first-action readers used only to select card icons.

**desktop/src/features/workflows/ui/workflowDefinition.test.mjs**
Covers neutral icon selection, enabled-state immutability, and status
presentation.

**desktop/tests/e2e/workflows.spec.ts**
Covers the create tile, title/description hierarchy, selected-card
enable/disable consistency, and deterministic narrow/medium/wide
captures while retaining existing action coverage.

</details>

## Reproduction steps

1. Open **Workflows** and confirm the create tile stays first as cards
flow from one to three columns with available width.
2. Confirm each card shows a sentence-case trigger eyebrow, prominent
workflow title, supporting description when present, status, channel,
and update date without a clock icon.
3. Open a card's overflow menu and trigger, edit, duplicate,
enable/disable, or delete the workflow.
4. Leave the detail panel open while toggling and confirm its badge and
JSON definition update with the card.

## Screenshots

Real built E2E UI with representative workflow data at three viewport
sizes.

### Narrow — 800 × 720

![Workflow library at 800 by
720](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6008/workflow-library-narrow-482d1b4c8.png)

### Medium — 1024 × 720

![Workflow library at 1024 by
720](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6008/workflow-library-medium-482d1b4c8.png)

### Wide — 1280 × 720

![Workflow library at 1280 by
720](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6008/workflow-library-wide-482d1b4c8.png)

### Card actions

![Workflow library actions at 1280 by
720](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6008/workflow-library-wide-actions-482d1b4c8.png)

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
## Why

Buzz currently appends its managed prompt to Goose's native prompt, so
managed agents receive both instruction sets instead of the intended
Buzz-only system prompt.

## What

- Send Goose's custom session system-prompt request with `mode: "set"`
- Lock the replacement contract in the ACP request test

## Risk Assessment

Low — the change is limited to Goose session setup; adapters that do not
implement Goose's custom method keep the existing method-not-found
fallback behavior.

## References

Goose v1.46.0 routes `set` to `override_system_prompt`, and its prompt
builder selects that override instead of rendering the native
`system.md`: [ACP
handler](https://github.com/aaif-goose/goose/blob/98c11ce2ee7b9b302978aa64b1eab7d0895607c7/crates/goose/src/acp/server/manage_sessions.rs#L57-L93),
[prompt
builder](https://github.com/aaif-goose/goose/blob/98c11ce2ee7b9b302978aa64b1eab7d0895607c7/crates/goose/src/agents/prompt_manager.rs#L153-L191).

Validated end to end against the official Goose v1.46.0 binary with a
local OpenAI-compatible capture server: the provider request contained
the exact Buzz replacement prompt and did not contain Goose's native
base-prompt marker.

---

**Update Aug 15, 13:17 CDT:** Added the [Terra-high prompt-ablation
comparison](https://github.com/squareup/buzz-benchmarks/blob/4492f76349ccb638219f7d070735a4d2b679bc26/data/prompt-ablation/20260815-terra-high/comparison.md).

The Goose conditions used GPT 5.6 Terra at high effort on the same 11
Terminal-Bench 2.1 tasks, with two attempts per task and concurrency
four. The matched `append-full` and `set-full` runs used the same
persona and included the same Buzz platform prompt; Active-h is the
primary measure because it excludes Buzz lifecycle overhead.

| Goose condition | Pass | Active-h | Median active | Agent-h | Wall-h |
Tool calls |
|---|---:|---:|---:|---:|---:|---:|
| Native prompt + Buzz prompt (`append-full`) | 21/22 | 0.3042 | 0.85
min | 0.3974 | 0.1496 | 234 |
| Native prompt + persona only (`append-persona-only`) | 22/22 | 0.3050
| 0.75 min | 0.3990 | 0.1498 | 204 |
| Buzz prompt replaces native prompt (`set-full`) | 22/22 | 0.3340 |
0.87 min | 0.4296 | 0.1551 | 275 |

Replacing instead of appending produced one additional passing attempt,
but it was not an efficiency improvement in this small sample: versus
`append-full`, `set-full` increased Active-h by 9.8%, median active by
2.0%, Agent-h by 8.1%, Wall-h by 3.7%, and tool calls by 17.5%. It was
faster on only two of eleven per-task active-time medians
(`distribution-search` and `prove-plus-comm`). With two attempts per
task, these are directional results rather than confidence intervals;
they support this change as an instruction-isolation/correctness fix,
not a performance optimization, and argue against Goose's appended
native prompt being the main source of active-time cost.

Generated with Codex

Signed-off-by: Atish Patel <atishpatel2012@gmail.com>
Co-authored-by: Codex <noreply@openai.com>
…hor (block#6129)

## Problem

Scheduled workflow `send_message` actions fire and land in the channel
with correct `p` tags for the mentioned agents — but the agents never
wake. The wake-up is silently dropped.

**Root cause:** workflow messages are signed by the **relay keypair**
(`workflow_sink.rs` signs with `state.relay_keypair`), so `event.pubkey`
is the relay's pubkey, not the workflow owner. In `buzz-acp`, the
inbound author gate (`author_allowed`) runs **before** the `p`-tag
mention check. Under the default `respond_to = owner-only`, the relay
pubkey is neither the owner nor a sibling, so every workflow wake-up
dies at the gate with a debug-level `"inbound author gate — dropping
event"`.

The relay-side comment even says the mention `p` tags exist *"so
mentioned agents are woken (wake is p-tag gated)"* — but wake is also
author-gated, and that path was missed.

## Fix

Gate relay-signed workflow messages on their **attributed author** — the
pubkey that created the workflow — instead of the relay pubkey:

- **Relay:** `workflow_sink.rs` now emits an explicit
`buzz:workflow-owner` tag carrying `workflow.owner_pubkey` (the workflow
creator, which the executor already passes as `author_pubkey` and whose
channel access the relay verifies before emitting). Ownership is never
inferred from `p`-tag order; mention `p` tags play no role in
attribution.
- **Harness:** at startup, `buzz-acp` fetches the relay's NIP-11 `self`
pubkey (new `RestClient::fetch_relay_self`, public `/info` endpoint).
Best-effort: fetch failure just logs a warning and preserves pre-fix
behavior.
- **Gate:** an event that is (a) authored by the relay `self` key, (b)
tagged `buzz:workflow`, and (c) carries a well-formed
`buzz:workflow-owner` pubkey is gated on that owner, through the exact
same owner/sibling/allowlist policy as a direct author.

## Security notes (all fail closed)

- No NIP-11 `self` pubkey → no exemption.
- `buzz:workflow` / `buzz:workflow-owner` tags on a non-relay-signed
event → ignored (a member cannot forge the exemption; the relay verifies
signatures on submission and only the relay holds its key).
- Relay-signed event without the tags, or with a malformed owner value
(not 64-hex) → plain author gate.
- Who is @mentioned in the message has no bearing on whose authority is
evaluated.
- A workflow owned by a random channel member still cannot wake an
owner-only agent — the owner's pubkey must pass the same policy.

## Testing

- 7 unit tests (`workflow_attributed_author_tests`) covering
attribution, fail-closed paths, p-tag independence, malformed owner
values, and the forgery case.
- Extended the PG-gated `workflow_send_message_p_tags_mentioned_member`
integration test to assert the `buzz:workflow-owner` tag.
- `cargo test -p buzz-acp`: 785 passed, 0 failed. `cargo test -p
buzz-relay --lib workflow_sink`: 17 passed. Clippy + fmt clean. (9
pre-existing `buzz-relay` failures in unrelated
`api::media`/`api::admin` tests fail identically on the base commit
without this change.)

Found while debugging scheduled automations in a Buzz review-pipeline
channel: two cron workflows fired daily @mentions at agents that never
responded, while direct human @mentions woke them instantly.

---------

Signed-off-by: Luke Tornquist <tornquist@squareup.com>
Co-authored-by: Fizz <3a9f8a30fbb462abec1e2977b2280a7ae50c7ff794433790be15bd48bfd52d0b@buzz.block.builderlab.xyz>
…or (block#5706)

Agent profiles resolve through one shared selector (`pickProfileAgent`)
at every entry point — the persona card, the profile panel, and library
grouping. That selector ranked instances only by active/name, with no
archive awareness, so a relay-archived instance early in file order
could hijack the persona card and the profile panel. The persona card
also recorded a durable pubkey target, which could strand the panel on
an archived identity when the click landed during the archive-snapshot
fail-open window. The profile panel's Runtime → Instances roster had the
same blind spot: it rendered every persona instance raw, so archived
instances appeared mixed in with live ones as if active.

This makes the shared resolution path archive-aware via the existing
fail-open `useIsArchivedPredicate`:

- `pickProfileAgent` filters archived instances before ranking and
returns `undefined` when every instance is archived (persona-only mode).
- `buildUnifiedGroups` drops archived agents from the standalone `Custom
agents` and `Unknown agents` buckets; matched persona groups keep their
full list and rely on the selector's persona-only fallback.
- `useCanonicalManagedAgentProfile` resolves through a pure
`resolveCanonicalManagedAgent` helper that applies the target-provenance
rules: a deliberately requested archived pubkey stays exact (so its
archive controller can unarchive it, even when a live sibling exists),
`preserveRequestedInstance` still pins a Runtime → Instances selection,
and non-archived historical navigation keeps its canonicalization.
- The persona card's main click records a persona target that
re-resolves every render, so it self-corrects to a live sibling after
hydration. Deliberate instance navigation and the runtime-error
affordance keep their explicit-pubkey path.
- The Runtime → Instances roster (`ProfileInstancesSection`) buckets
instances off the same predicate via `bucketPersonaInstances`: live rows
render as before, and archived rows move under a labeled `Archived`
subsection. The instance count reflects both buckets, and archived rows
keep their explicit-pubkey click so unarchive stays UI-reachable (the
deliberate-navigation path above).

The predicate is fail-open (treats every identity as live while the
relay archive snapshot loads) and self-exempt, so a cold start never
hides an identity and a user is never folded from their own client.
While the snapshot is loading, every instance renders in the live list —
nothing hidden, nothing labeled.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Summary
- Rename the built-in Bumble agent to Pollen across desktop, onboarding,
docs, and test fixtures.
- Migrate existing stock definitions and instances in place while
preserving customized fields and the stable persona coordinate.
- Reserve the Pollen name by removing it from Fizz's generated-name
pool.

## Validation
- Pre-push desktop checks, typecheck, 4,791 frontend tests, Tauri
clippy, and 2,432 native tests
- Desktop E2E build

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Removes the promise in `SECURITY.md` to publish a GitHub Security
Advisory after every security fix is released.

The disclosure policy continues to state that Buzz follows coordinated
disclosure and credits reporters unless they request anonymity.

Checked with `git diff --check`.

Signed-off-by: Jordan Mecom <jm@squareup.com>
…lock#5666)

Fixes block#5665.

`next_timestamp` in `crates/buzz-cli/src/commands/projects.rs` computed
a replacement's `created_at` as `head.created_at + 1`. The relay's
ingest path rejects events more than ±900s from server time
(`MAX_TIMESTAMP_DRIFT_SECS` in
`crates/buzz-relay/src/handlers/ingest.rs`), so:

- `projects update` on any project whose head is older than 15 minutes
fails with `relay error 400: invalid: event timestamp too far from
server time` (live repro in block#5665);
- inside the window, replacements are recorded at `head+1` —
seconds-to-minutes in the past — so a concurrent wall-clock writer
silently wins LWW and audit timestamps misstate when the write happened.

## Change

`next_timestamp` now returns `max(now, head.created_at + 1)`: strictly
after the observed head (preserving the dominate-the-head guarantee for
skewed/future heads), never behind the wall clock. This mirrors the
relay's own replacement-authoring pattern (`now.max(head+1)` in
`side_effects.rs`).

## Testing

- `cargo test -p buzz-cli --lib` — 344 passed; adds
`next_timestamp_uses_wall_clock_when_head_is_stale`, and the existing
far-future-head test still holds (`head+1` wins when head > now)
- `cargo clippy -p buzz-cli --all-targets` / `cargo fmt --check` — clean
- Live before/after on a self-hosted relay: vanilla CLI fails on a
2h-aged head; with this change the same update is accepted and the head
lands at wall clock.

Same failure family as block#2876 (`repos protect` vs the drift window) —
that path is not touched here.

---------

Signed-off-by: Ika Minami <ika@infiniteidol.com>
Signed-off-by: Ravneet Arora <rarora@squareup.com>
Co-authored-by: Ika Minami <ika@infiniteidol.com>
Co-authored-by: Ravneet Arora <rarora@squareup.com>
block#5904)

Two membership-propagation defects let an agent team silently lose
members — both observed live on Will's store (Sietch Tabr), not
hypothetical.

**Stale `persona_ids` dropped on save.** Team records written before
persona ids were namespaced hold bare slugs (`thufir`) instead of the
namespaced id (`sietch-tabr:thufir`). Nothing rewrites them, and the
interactive save path (`ensure_persona_ids_are_active`) *drops* any id
it cannot resolve — so the next in-app save shrinks the team. This nuked
four of five Sietch Tabr members.

**`team_id` drifts from team membership.** Team instructions are
injected at spawn by matching `record.team_id`
(`spawn_snapshot::effective_team_instructions`), so an instance's
binding must track its persona's membership. It drifts two ways: adding
a persona to a team leaves the persona's already-running instances at
`team_id: null` (a member in the roster but not in behavior — seen
twice, Gurney and Hayt), and removing a persona while keeping its agents
leaves the kept instance bound to a team that no longer lists it (still
drawing that team's instructions at spawn).

## Fix

A boot migration (`migration/team_membership.rs`) heals existing stores
in one pass over `teams.json` + `managed-agents.json`:

- **Rewrite stale ids.** A stale id is one no definition slug resolves.
Its target is the definition whose `source_team_persona_slug` equals the
bare slug, scoped to the team's source team (via `source_dir` for a
directory-backed team, or the unique `source_team` among resolvable
members for a detached one). Rewrite only when exactly one candidate
matches; zero or many leave the id in place — strictly safer than the
save path, which drops it.
- **Repair `team_id`.** Backfill an instance whose persona is a team
member but whose own binding is unset, and heal a stale binding whose
team no longer lists the persona (re-point when exactly one *other* team
claims it, otherwise unbind). Both directions gate on single-team
evidence — a persona spanning several teams has none (JSON team order is
not ownership), so it is left as-is and logged. A binding whose team
still lists the persona is authoritative and never touched.

Runs BEFORE `detach_directory_backed_teams` (so a not-yet-detached team
can still be scoped by its `source_dir`) and before any UI save can drop
an id. Rewrite-or-leave converges to a fixed point, so a second boot is
a no-op; the store is backed up once before either write.

The edit path (`commands/teams.rs`) propagates a membership change to
live instances immediately, without waiting for the next boot, scoped to
the delta between the pre-edit and post-edit rosters:

- **Added personas** (on the team now, not before) backfill `team_id` on
their unbound instances. An explicit add is legitimate binding evidence
even for a persona shared across teams — unlike the order-blind boot
case.
- **Removed personas** (on the team before, not now) clear `team_id` on
instances bound to *this* team (bindings to other teams are untouched),
so a "keep agents" removal stops feeding a kept instance the old team's
instructions.
- **Delta-scoping keeps a metadata-only edit inert:** with no roster
change, no instance is re-pointed — a shared unbound persona is never
silently bound to whichever team was edited last.

Propagation is best-effort after the authoritative `save_teams`
(mirroring `retain_team_pending`): the team already exists on disk, and
boot repair is the designed retry for a stale/unset binding, so a
secondary `managed-agents.json` write failure no longer fails a command
whose team write succeeded — which would otherwise let a UI retry mint a
duplicate team.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Summary

- apply the inactive primary-navigation opacity treatment to every
sidebar destination, including Pulse, Projects, and Workflows
- remove the duplicated Inbox and Agents conditionals so future gated
rows inherit the same hierarchy
- add E2E coverage for all inactive rows and restoration to full opacity
when selected

## Validation

- `pnpm --dir desktop build:e2e`
- `pnpm --dir desktop exec playwright test badge.spec.ts --grep "primary
navigation rows share the same inactive emphasis" --project=smoke`
- pre-push hook: desktop check, typecheck, and 4,984 unit tests

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Why

Managed channel sessions already receive authoritative per-turn context.
The old startup recovery checklist told every new session to scan the
global feed

## What

- Remove `Startup Recovery` with concise channel and heartbeat turn
contracts.

## Risk Assessment

Low. This changes prompt guidance and its test only; routing and runtime
behavior are unchanged.

Generated with Codex

---------

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
**Category:** fix
**User Impact:** Workflow listings reliably include every accessible
channel, including for users with more than 128 memberships and when
connected to older relays.
**Problem:** Multi-value `#h` filters could lose live delivery, apply
channel scoping after SQL limits, mishandle partial authorization or
revocation, and permit unbounded membership work. Desktop also submitted
every channel in one request, exceeding the relay's new 128-value safety
bound.
**Solution:** Preserve NIP-01 OR semantics across relay query, count,
and live-subscription paths while enforcing authorization and bounded
explicit-channel work before database or Redis operations. Desktop keeps
the older-relay-compatible one-channel-per-filter shape, sends filters
in bounded batches, combines responses, and deduplicates signed events
by event ID.

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

**crates/buzz-db/src/event.rs**
Distinguishes authorization channel scopes from explicit `#h` scopes in
list and count SQL so requested channels are applied before limits
without implicitly including global rows.

**crates/buzz-relay/src/handlers/req.rs**
Shares explicit-channel scope extraction and limits, preserves valid OR
siblings when malformed branches cannot match, repairs request-local
membership misses, and registers authorized live subscriptions per
channel.

**crates/buzz-relay/src/handlers/count.rs**
Applies the same bounded explicit-channel authorization to COUNT and
preserves channel scope when a multi-channel request narrows to one
authorized channel.

**crates/buzz-relay/src/api/bridge.rs**
Brings HTTP query and count behavior in line with WebSocket semantics
before SQL execution and rejects over-limit explicit-channel requests
before membership I/O.

**crates/buzz-relay/src/subscription.rs**
Indexes multi-channel subscriptions by every authorized channel and
shrinks, rather than destroys, their scope when one channel is revoked.

**crates/buzz-relay/src/handlers/side_effects.rs**
Releases only revoked channel topics and sends terminal closure only
when no authorized channel remains.

**crates/buzz-test-client/tests/e2e_relay.rs**
Adds ignored relay integration coverage for multi-channel delivery and
valid historical/live behavior with malformed or empty OR siblings.

**desktop/src-tauri/src/commands/workflows.rs**
Builds one single-channel filter per membership, submits at most 128 per
relay request, combines batches, and deduplicates by immutable signed
event ID.

**desktop/src-tauri/src/commands/workflows_tests.rs**
Covers filter compatibility, malformed input, 129-channel batching, and
cross-batch event-ID deduplication.

</details>

## Reproduction steps

1. Join multiple channels containing workflows, open **Workflows**, and
confirm workflows from every accessible channel appear.
2. Repeat with more than 128 memberships and confirm the listing remains
complete rather than failing the relay request.
3. Send a multi-value `#h` query/count and confirm only requested
authorized channels affect SQL limits and counts.
4. Subscribe to channels A and B, revoke A, and confirm B continues
delivering live events.
5. Subscribe with a valid channel branch plus a malformed or empty `#h`
sibling and confirm valid history, EOSE, and post-EOSE live delivery
still occur.

## Validation

At pushed head `c419a923f05e483ab26c006a0b3a80cfb3c73844`:

- Relay request tests: 53 passed.
- Desktop full Rust unit suite: 2,468 passed, 17 ignored.
- Relay E2E target compiled with `--no-run`.
- Strict relay clippy passed.
- Desktop Tauri clippy/check passed.
- Pre-push Rust tests and Desktop Tauri checks passed.
- Rust formatting and `git diff --check` passed.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
## Summary

- preserve selected managed-agent `p` tags when fresh managed-directory
evidence succeeds but relay discovery or owner-profile lookup fails
- keep relay-only agents fail-closed unless fresh relay evidence and any
required owner proof are available
- cover selective admission with focused unit tests and a signed-event
Playwright regression

## Testing

- `node --import ./desktop/test-loader.mjs --experimental-strip-types
--test
desktop/src/features/messages/lib/agentMentionRevalidation.test.mjs` (7
passed)
- focused Playwright regression plus adjacent relay-revocation case (2
passed)
- pre-commit desktop Biome/file-size hook
- pre-push desktop check, TypeScript typecheck, and full desktop unit
suite (4,987 passed)

Fixes block#6147

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Buzz Desktop release v0.5.15

- **Frozen main:** `7f61cf431af1d8f0480a0baf525881a12f2be7f2`
- **Reviewed candidate:** `7ad30276d05c39ccd8699ca2521e761fd285ea49`
- **Previous desktop release:** `desktop-v0.5.14`
- **Proposed immutable tag:** `desktop-v0.5.15`

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

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

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
## Summary

- retain explicit regression coverage for the exact 128-channel relay
request limit
- cover the 129-channel split into 128 + 1 filters

The workflow-listing implementation originally carried by this PR landed
through block#6009. This branch is now rebased onto current `main`, so the
remaining diff is only the boundary test that block#6009 did not include.

Fixes block#6116

## Test plan

- `cargo test --manifest-path desktop/src-tauri/Cargo.toml
workflow_queries_respect_relay_explicit_channel_limit`
- pre-push hook: Desktop checks, Desktop tests, Desktop Tauri checks,
and path-scoped Rust tests

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
The upstream sync combined block/buzz's own growth of runtime.rs with this
fork's local relay-dialing patch, pushing the file from 984 to 1008 lines --
over the desktop file-size ratchet's 1000-line cap.

Extract persona-drift classification, workspace-pair-key resolution, and the
ManagedAgentSummary builder into a new sibling module, runtime/summary.rs,
following this file's existing convention of splitting into sibling modules
(path, metadata, stop, sweep, process, orphan_sweep, instance_reaper,
lifecycle). Purely mechanical -- no behavior change.

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
@serina-mcfall
serina-mcfall marked this pull request as ready for review August 18, 2026 03:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.