Skip to content

[upstream-sync] Merge block/buzz 769ac70b7..02f640bc4 (33 commits) - #23

Merged
adrienlacombe merged 35 commits into
mainfrom
upstream-sync-20260808
Aug 9, 2026
Merged

[upstream-sync] Merge block/buzz 769ac70b7..02f640bc4 (33 commits)#23
adrienlacombe merged 35 commits into
mainfrom
upstream-sync-20260808

Conversation

@adrienlacombe

Copy link
Copy Markdown
Owner

Merges block/buzz 769ac70b7..02f640bc433 commits, preserving both merge parents.

What changed upstream

Desktop (162 files) — unified add-agent flows (block#5015), rich link previews on messages (block#3818), Projects follow-ups: access restrictions, fast loading, activity feed polish (block#5073), externalized boot <style> to survive Tauri CSP nonce override (block#5242), imported/recovered identities now finish onboarding (block#5228), selectable terminal output (block#4980), WEBKIT_DMABUF_RENDERER_FORCE_SHM for NVIDIA/AppImage (block#4505), release 0.5.6 → 0.5.7.

Relay / core — accept kind:30179 private managed-agent events at ingest (block#4133), best-effort public starter channels (block#5192).

Agent / CLI — budget summarizer reasoning separately so it cannot starve the handoff summary (block#5248), recover from max-token response truncation (block#5223), escalate LLM timeouts per retry (block#5130), single typed extractor replacing the probe/decider/detail split (block#5191), structured JSON warning on archive/unarchive owner-auth extraction failure (block#4824).

Mobile (20 files) — anchored reaction popover (block#5025), bee pull-to-refresh (block#5059), keep latest messages above composer (block#4981).

Infra — bind development services to loopback (block#4871), @tauri-apps/cli → ~2.11.4 for the Linux app-icon fix (block#4858).

Conflicts

Two, both the "each side added an entry to the same list" shape. Kept both sides in each.

crates/buzz-relay/src/handlers/ingest.rs — two hunks, from upstream block#4133 wiring KIND_PRIVATE_MANAGED_AGENT into ingest while the fork carries the three KIND_SPONSOR_* kinds in the same import list and the same required_scope_for_kind arm.

  1. Import list — took upstream's list and re-inserted KIND_SPONSOR_DEPLOY_REQUEST, KIND_SPONSOR_REQUEST, KIND_SPONSOR_RESULT in sort order. cargo fmt --check passes, so the hand-merged list matches what rustfmt would emit.
  2. required_scope_for_kind — added KIND_PRIVATE_MANAGED_AGENT to the Scope::UsersWrite arm, keeping the fork's three sponsor kinds and their FORK-LOCAL PATCH comment intact. Upstream had collapsed the arm to a one-line brace form; kept the fork's multi-line form since it carries the comment.

Not a kind collision. KIND_PRIVATE_MANAGED_AGENT = 30179 already exists in this fork's kind.rs (arrived in an earlier sync) and sits well outside the fork's reserved 30900–30999 block. crates/buzz-core/src/kind.rs is byte-unchanged by this merge. No wire-format change.

desktop/src-tauri/tauri.conf.json — the standing brand-vs-version conflict. Kept productName: BitcoinMarkets and identifier: app.bitcoinmarkets.desktop, took upstream's version: 0.5.7 (was 0.5.5). Deep-link scheme ["bitcoinmarkets"] verified intact.

Clean-merge review (a clean merge is not a correct merge)

Upstream touched three fork-patched files. All three re-read; every fork marker survived and still means what it did:

  • desktop/src-tauri/src/relay.rs — upstream only touched a test (reset_rate_limit_gate + TEST_SERIAL serialization). Fork's release-build allowlist default and pub mod allowlist; intact.
  • desktop/src-tauri/src/lib.rs — upstream added mod link_preview_tags;, dropped a blank line, and renamed fetch_link_preview_titlefetch_link_preview_metadata. Fork's single patch (deep_link::is_supported_deep_link in the single-instance argv filter) intact. File-size ratchet checked: lib.rs is 992/1000 and relay.rs 998/1000 — pnpm check:file-sizes passes, but headroom is now 2 lines on relay.rs.
  • desktop/src-tauri/tauri.conf.json — resolved above; externalBin unchanged, so macos-canary.yml's sidecar list still tracks upstream's non-Windows lanes.

Also checked and clear: no new files under migrations/ (no duplicate version integers; migrations.len() assertion still 30 and passing), release.yml and macos-canary.yml untouched by upstream, no new sidecar added to a lane this fork runs.

AGENTS.md update

One correction, in its own commit. Upstream block#3818 renamed resetLinkPreviewTitleCacheresetLinkPreviewMetadataCache, so AGENTS.md's list of community-scoped singletons named a function that no longer exists. That list is consulted before adding any module-level cache, so a stale entry is a cross-community data-leak footgun rather than a typo. Also added resetBackgroundMediaUploads, already wired into resetCommunityState but never listed.

Verification

All run locally on this branch. Real results:

Gate Result
cargo fmt --all --check pass
cargo fmt --manifest-path desktop/src-tauri/Cargo.toml --all --check pass
cargo clippy --workspace --all-targets -- -D warnings pass
cargo clippy --manifest-path desktop/src-tauri/Cargo.toml --all-targets -- -D warnings pass
cargo metadata --locked pass — no Cargo.lock re-resolution needed
scripts/test-release-ref-contract.sh pass — release ref contract passed
scripts/test-mobile-worktree-overrides.sh pass — brand contract intact
just test-unit pass — 976 tests, 0 failed
dart format --set-exit-if-changed . pass — 373 files, 0 changed
flutter analyze pass — No issues found
flutter test pass — 1270 tests
pnpm check:file-sizes (desktop) pass

Mobile gates ran against Hermit Dart 3.11.5 (confirmed via dart --version, not the system 3.8.0).

scripts/test-release-ref-contract.sh had to be run from a clean clone — it copies the repo including target/ (43 GB here), which fills the disk from the working checkout. Judged by exit code 0 and the final release ref contract passed; the ::error:: lines it prints are its own negative tests.

Needs a human look

This PR trips tripwire 4 — a conflict resolved in crates/buzz-relay/src/handlers/ingest.rs. Left unmerged and labelled needs-human per the runbook, even though every gate is green.

What to actually check, in priority order:

  1. The required_scope_for_kind resolution. KIND_PRIVATE_MANAGED_AGENT now returns Scope::UsersWrite alongside the fork's sponsor kinds. That is upstream's own intent for the kind, and the arm is shared, so the resolution is mechanical — but it is a relay authorization path, which is why it is a tripwire.
  2. relay.rs at 998/1000 lines. Two lines of headroom. The next upstream change touching it likely trips the ratchet, and the fork's pub mod allowlist; lives there because lib.rs had no headroom. Worth deciding pre-emptively where the allowlist declaration moves next.
  3. Desktop 0.5.5 → 0.5.7 is a two-version jump and this fork's macOS lane is ad-hoc signed and never notarized. Per AGENTS.md the ad-hoc cdhash changes every build, and the identity.key app-data fallback carrying identity across an update is still listed as unverified. This release crossing two versions is a reasonable moment to actually test that.

Nothing else. No wire-format change, no migration, no fork patch deleted, no kind renumber.


Merge with a merge commit, not squash. A squash drops the second parent, leaves the merge base stale, and forces every later sync to re-resolve these same conflicts from the same stale base — this fork has already been repaired by hand once for exactly that (3ce7c8adc).

tlongwell-block and others added 30 commits August 6, 2026 22:26
…block#5133)

## What

Relay-only carve-out of the ingest half of block#4999: generic EVENT ingest
now accepts kind:30179 (NIP-PMA private managed-agent config). One file,
`crates/buzz-relay/src/handlers/ingest.rs`, 16 insertions / 15
deletions; **two semantic lines**, byte-identical to the ingest hunk of
block#4999 at `6f486e88`:

1. `required_scope_for_kind`: 30179 requires `Scope::UsersWrite` — same
arm as its public sibling 30177 and the other owner-authored NIP-AP
kinds.
2. `is_global_only_kind`: 30179 is owner-global, keyed `(pubkey, kind,
d-tag)`; a stray `h` tag must not channel-scope it.

The rest is import reflow plus replacing the guard test with a positive
one (`private_managed_agent_kind_is_owner_scoped_global_user_data`:
asserts UsersWrite scope, global-only, no h-channel scope).

## Why the guard test can be retired

The removed test
(`private_managed_agent_kind_remains_rejected_until_atomic_ingest_exists`)
pinned a stated precondition: *"must not enter generic EVENT ingest
before privacy and aggregate CAS deploy."* Both halves are resolved:

- **Privacy** — the author-only read gates for 30179 shipped to main
with block#4593: `AUTHOR_ONLY_KINDS` membership, `req.rs` pre-filter + result
gates, `count.rs`, `event.rs` fanout, and the bridge pre-filter
(`bridge.rs:999-1000` returns `restricted: author-only kinds require
authors=[self]` / 403). Only the author can read the event back.
- **Aggregate CAS** — block#4999 settled generation as **advisory**: the `g`
tag is shape-validated, never relay-enforced. Last-write-wins per
coordinate is the contract of record (see the kind:30179 contract blurb
in block#4999), so no CAS mechanism is pending on the relay side.

## Why this is inert to existing relays and clients

- No production desktop code on main authors kind:30179 — the codec
(`private_managed_agent.rs`) has zero non-test callers. This PR accepts
a kind nobody can produce yet.
- Content is opaque NIP-44 ciphertext to the relay; the relay never
decrypts it.
- Reads remain author-only via the already-shipped gates above.
- Storage is the standard parameterized-replaceable path already
exercised by kinds 30175–30178. No schema, config, or migration changes.

## Testing

- Full `buzz-relay` package suite at this commit: 859 passed, 1 failed —
`api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo` (504
vs 200), which **reproduces identically on clean main `769ac70b`** with
this change stashed; pre-existing/environmental, not introduced here.
- New positive ingest test passes.
- Pre-push hooks green (branch-skew, rust-tests, desktop-tauri-checks).

## Relationship to block#4999

block#4999 (relay-primary agent config, desktop half) stays DO-NOT-MERGE
pending live relay receipts + real CI; once this lands and deploys, its
live test simplifies to plain `desktop-standalone` against the real
relay, and block#4999 rebases to drop its now-duplicate ingest hunk
(identical bytes → trivial rebase).

Originating thread:
buzz://message?channel=06f13ed3-0557-4ac2-922c-1545dd00bf97&id=2a43b3b4933a2ea78b77088619251c061355f9b7b6dc29ea0d702193f2344149


## Brownfield FTS note (review findings, operator-ruled non-blocking for
this PR)

Max and Sami independently identified that the FTS privacy skip-set is
regime-dependent: migration 0008 installs the positive allowlist (`kind
IN (0, 9, 40002, 45001, 45003)`) **only on an empty events table**; an
already-populated database keeps the 0001/0005 negative skip-list
(wrapped by 0014 to add 30350), which omits 30179 — so on such an
installation this PR admits 30179 rows whose NIP-44 ciphertext gets
indexed by `to_tsvector`. Sami measured both regimes against real
Postgres (brownfield: 30179 INDEXED; fresh: NULL) and demonstrated the
existing drift test only exercises the fresh regime.
`schema/schema.sql:222`'s canonical literal is also the negative list
and omits 30179. Migration dates put any relay deployed with data before
0008 landed (2026-07-13) in the brownfield class.

**Scope of exposure (Sami's trace):** not a content leak —
`event_visible_to_reader` / `is_author_only_event` gates hold on both
search surfaces (`req.rs:725`, `bridge.rs:1770`), so foreign readers
receive nothing. Lost is the storage-level NULL-tsv backstop plus FTS
page budget burned on post-filtered hits.

**Operator ruling (Tyler, events `1472e5b6`, `cbd368ed`):** ship this PR
without an exclusion migration. Safety argument that makes this sound
rather than merely accepted: main has **zero non-test 30179 writers**
until block#4999's desktop half deploys — no 30179 rows can exist, so nothing
can be indexed in any regime while this PR is the only half live.



**Additional review characterizations (Sami, non-blocking, on the
record):**
- *Behavioral delta enumerated:* routing triple
(`required_scope_for_kind` / `is_global_only_kind` /
`requires_h_channel_scope`) compared for all 65,536 kinds at base
`769ac70b` vs head `77eeba6e` — exactly one row differs (30179). No
other kind or client changes behavior.
- *"SQL visibility before LIMIT" (NIP-PMA step 2):* no
`AUTHOR_ONLY_KINDS` pushdown clause exists in `buzz-db` (only
`SHARED_GATED_KINDS` has one). Author-only kinds are protected by the
pre-filter (`author_only_filters_authorized`) plus post-filter omission;
mixed-kind filters can burn candidate-page budget on discarded rows.
Pre-existing and identical for 30300/30350 — not introduced here; noted
so the NIP's step-2 checkbox is not read as fully ticked.
- *Envelope validation gap:* 30179 is the only parameterized-replaceable
kind at ingest with no per-kind envelope validator (codec grammar checks
run in the desktop writer, not the relay). Generic limits only (256 KiB,
±15 min, pubkey==identity, d-tag bound). Self-inflicted footgun bounded
to the author's own coordinate — candidate companion to the exclusion
migration in the block#4999 rebase, deliberately not added here.

**Bound follow-up (required before/with the block#4999 desktop half):** a
0014-shape additive migration (`pg_get_expr` capture + `CASE WHEN kind =
30179 THEN NULL ELSE (<existing>) END` wrap), add 30179 to the
`schema/schema.sql:221` literal, and a brownfield-regime variant of the
FTS drift test, per Sami's finding. Deploy-time spot check if ever
wanted: `SELECT pg_get_expr(d.adbin, d.adrelid) FROM pg_attrdef d JOIN
pg_attribute a ON a.attrelid = d.adrelid AND a.attnum = d.adnum WHERE
d.adrelid = 'events'::regclass AND a.attname = 'search_tsv';`

Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
…lock#5136)

## Problem

The harness posts each trial's task via `buzz messages send`, relying on
`@<orchestrator-id>` name resolution. Task text is untrusted payload:
when it contains @-tokens of its own, the CLI's mention resolver tries
to resolve them as channel members, fails, and refuses to send — killing
the trial with `RuntimeLaunchError` before the agent ever saw the task.

Live occurrence: TB 2.1's `large-scale-text-editing` task embeds Vim
macros (`:%normal! @a`). In the tb21-solo-1 run the trial died at
launch:

```
RuntimeLaunchError: buzz messages send ... exited 1:
{"error":"user_error","message":"mention '@A' does not match a current channel member; retry with --mention <pubkey>"}
```

Any TB task whose statement contains @-syntax is silently zeroed this
way.

## Fix

Pass the orchestrator's pubkey as an explicit `--mention` when posting
the task. The CLI demotes unresolved @-tokens in the text to
presentation-only when any explicit identity is supplied, so delivery
still targets exactly the orchestrator and every @-token in the task
statement becomes inert. The harness already holds the orchestrator's
`AgentCredential` (it writes that pubkey into the worker roster tables),
so no persistence is needed — fresh key per trial, fresh `--mention` per
trial.

Verified both halves against a live relay: a fenced `@a` without
`--mention` still hard-fails (the resolver is not markdown-aware); the
same content with an explicit `--mention` sends clean with
`mention_pubkeys` containing only the target.

## Testing

- `benchmarks/harbor-buzz-orchestra`: full pytest suite — 35 passed (34
baseline + new `test_send_mentions_by_pubkey_so_task_text_stays_inert`),
ruff clean. Run against `origin/main` 769ac70 with exactly this patch
applied.
- `testbed`: full pytest suite — 23 passed, 1 skipped; ruff clean.

## Acceptance

A task statement containing arbitrary @-tokens (Vim registers, emails,
decorators) launches and delivers to the orchestrator instead of dying
in `_send`.

Originating Buzz thread:

`buzz://message?channel=c3252dd2-0142-4e01-88c7-a2183c3960a5&id=74a65a0990fd2197882b66b5ea2707169d4a3dbd2020d1610c45150fb99f140b`

Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
…uth extraction fails (block#4824)

emit structured JSON diagnostics when NIP-OA owner-auth extraction fails
during `buzz agents archive`/`unarchive`

## Problem

When owner-auth extraction returned `None`, the CLI silently sent a bare
request. The relay replied with `400: missing auth tag` and the caller
had no way to know why extraction failed.

## Solution

Extract `resolve_auth_from_profile` — a sync function that owns all
three warning branches and the success path. `resolve_auth` reduces to:
self-check → fetch kind:0 → delegate.

- **Four distinct diagnostics**: no kind:0 profile / no tags array /
`classify_owner_auth_tag` failure (typed `AuthFailure` enum:
`NoAuthTag`, `AmbiguousAuthTag`, `WrongArity`, `NonStringElement`,
`InvalidOwnerHex`, `InvalidSigHex`, `OwnerMismatch`)
- **JSON format**: each fallback emits exactly one `{"warning":"..."}`
line to stderr, matching the CLI's documented structured-stderr contract
and the precedent in `channels.rs:597`
- **Relay-supplied values** (target pubkey, actual owner pubkey) pass
through `serde_json` serialization — no unescaped text
- **Admin bare path preserved**: request is always sent after the
warning; bare non-self requests are legitimate for relay admins
- **Self path unchanged**: silent, no relay query

## Boundary tests

Tests call `resolve_auth_from_profile` directly with `&mut Vec<u8>`.
Each of the three production `writeln!` calls is covered: deleting any
one fails at least one test. Success path asserts zero bytes written.

## Changes

`crates/buzz-cli/src/commands/agents.rs` only:
- `AuthFailure` enum with `message()` formatter
- `classify_owner_auth_tag` returning `Result<[String;4], AuthFailure>`
- `extract_owner_auth_tag` reduced to `#[cfg(test)]` `.ok()` wrapper
- `resolve_auth_from_profile` sync helper (testable without
`BuzzClient`)
- `resolve_auth` reduced to self-check + fetch + delegate
- 9 new boundary tests replacing the prior test-local helper

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Summary
- move **Run on** into Advanced, directly after **Who can send
instructions**
- reuse the modal’s shared dropdown styling
- give the Welcome guidance and composer matching glass treatment while
preserving the corrected exit layering

## Validation
- `pnpm -C desktop typecheck`
- focused Playwright: Run on configuration (3 passed)
- focused Playwright: Welcome onboarding flow (1 passed)
- desktop unit suite (4,290 passed)

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Watcher <bb7abfd757d0af7b66569d02ab9c0316b616f9d0c151ecf5b964344c462e7f8f@buzz.block.builderlab.xyz>
Co-authored-by: Watcher <bb7abfd757d0af7b66569d02ab9c0316b616f9d0c151ecf5b964344c462e7f8f@buzz.block.builderlab.xyz>
## Summary

- resolve the OAuth cache home directory with the repository's
platform-aware `dirs` convention
- preserve the existing `.config/buzz-agent/oauth` cache layout on macOS
and Linux
- make the cache-path regression assertion portable across path
separators

## Problem

`buzz-agent` read only `$HOME` when constructing the OAuth token cache
path. Packaged Windows processes do not guarantee that Unix variable, so
OAuth source construction failed with `oauth cache: $HOME not set` even
though Windows had a valid user profile.

## Validation

Independent reviewers validated exact commit
`836d820483b141b7291170cb33535ac7cb49b2eb` on Windows/MSVC with `HOME`
unset and `USERPROFILE` present:

- `cargo +1.94.1 clippy -p buzz-agent --all-targets --locked -- -D
warnings`
- `cargo +1.94.1 fmt --all -- --check`
- `git diff --check f53bbd1..836d820`
- `auth::` tests: 11/11 passed with `HOME` unset
- full package lib target: 396 passed / 2 failed; identical-base
controls classified both as pre-existing Windows failures

The changed regression fails on the base with `$HOME not set` and passes
on this branch. `dirs 6.0.0` was already locked by other workspace
crates; the lockfile change adds only the `buzz-agent` dependency edge.

Signed-off-by: Kalvin C <kalvinnchau@users.noreply.github.com>
…ency (block#5130)

Non-streaming LLM calls (`"stream": false`) through slow model/provider
combinations routinely take longer than the fixed
`BUZZ_AGENT_LLM_TIMEOUT_SECS` window (default 240 s) to return their
first response byte. The retry loop then re-ran the identical 240 s bet
three times, failed the turn, and the ACP harness requeued the whole
turn from scratch: agents spent 30+ minutes producing nothing while
every attempt died at the same wall. And because the LLM path only
logged WARN lines on failure, a healthy-but-slow call was
indistinguishable from a wedged one.

### Timeout handling

- **Per-attempt escalation**: the per-request budget doubles after each
timeout failure (`base × 2^n`, capped at `max(1200 s, base)` —
`escalated_timeout()` in `llm.rs`), shared by the main `post()` loop and
`openrouter_post()`. Non-timeout retryables (429/5xx/connect) do not
escalate. A call that needs six minutes now succeeds on a later attempt
instead of never.
- **Per-request total timeouts**: enforcement moved from the
client-level `read_timeout` to `RequestBuilder::timeout()` on each LLM
request, so escalated budgets aren't silently floored by the shared
client and each attempt's bound covers connect through body completion.
Timeout error messages were updated to match the new semantics and still
point at `BUZZ_AGENT_LLM_TIMEOUT_SECS`.

### Observability

- One INFO line per completed LLM call: model, provider, `duration_ms`,
`input_tokens`, `cached_input_tokens`, `output_tokens`. Slowness and
prompt-cache effectiveness are now visible in harness logs without
waiting for a failure, and `None` vs `0` token reports stay
distinguishable. Handoff summarization calls log the same line with
duration only.
- The agent main loop wraps the call in a `session_id` tracing span so
each line is attributable to a session.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
## Summary
- remove the post-creation private-key modal
- return directly to the underlying page with one “Agent created” toast
- preserve failed channel-attachment retry through an actionable toast

## Validation
- desktop checks and E2E build
- 4,392 desktop unit tests
- focused Playwright coverage for standard, customized, and
attachment-retry creation flows

Signed-off-by: kenny lopez <klopez4212@gmail.com>
## Summary

Replace Flutter's standard mobile pull-to-refresh indicator with our
animated Buzz bee.

## Testing

- `bin/just mobile-check`
- `bin/just mobile-test` (1,248 tests)
- Connected iPhone and Pixel 10

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Fizz <50a12680c76f1a52c0b7af8dbb17e02c583227c290fb93b9a3defb456114223f@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Fizz <50a12680c76f1a52c0b7af8dbb17e02c583227c290fb93b9a3defb456114223f@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Summary

- replace reaction-only mobile sheets with an anchored long-press
popover
- add haptics, a dimmed frosted spotlight, and a spring-staggered
reaction tray
- preserve the full action sheet whenever other message actions are
available
- keep existing reaction pills outside the spotlight and make long press
reliable across nested content

## Testing

- `flutter analyze`
- `flutter test` (1,248 tests)
- signed iPhone release build installed and launched

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
## Summary
- treat public starter-channel provisioning as best-effort after
preserving the required private Welcome path
- let community onboarding complete and focus Welcome when the reported
metadata lookup error occurs
- remove the now-obsolete retry-toast expectations for optional starter
provisioning

## Scope
This intentionally does not change relay tombstone semantics or
auto-join existing public channels.

## Test plan
- `pnpm exec playwright test tests/e2e/deep-link-invite.spec.ts` (8
passed)
- `pnpm exec playwright test tests/e2e/onboarding.spec.ts --grep "failed
public starter channel setup"` (1 passed)
- `pnpm typecheck`
- `pnpm check`
- `pnpm test` (4483 passed)
- pre-push hook: branch-skew, desktop-check, desktop-typecheck,
desktop-test passed on `4658a07beb1e1d54443da5cd2e4a28fae0232f24`

---------

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

## Summary
- Heuristic and `--safe-rendering` now set
`WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` instead of
`WEBKIT_DISABLE_DMABUF_RENDERER=1`
- Legacy `DISABLE_DMABUF` stays owned so operators can still set
`=0`/`=1` and take over the decision
- Linux troubleshooting docs updated to match (block#3654)

## Test plan
- [ ] unit tests in `webkit_rendering::tests`
- [ ] On NVIDIA + WebKitGTK 2.52: workspace switch no longer SIGSEGVs
where the distro NVIDIA guard does not fire (Debian/Ubuntu
proprietary-NVIDIA may still crash — block#3654 stays open for that path)
- [ ] `WEBKIT_DISABLE_DMABUF_RENDERER=0` still stands the heuristic down

---------

Signed-off-by: Taksh <takshkothari09@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Alia <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
## Summary

- mirror the retained canvas terminal grid into a transparent,
selectable text layer
- preserve the canvas renderer and terminal focus behavior for ordinary
clicks
- reconstruct wide and combining glyphs correctly for clipboard text

## Why

Buzz Term renders output entirely on a canvas and deliberately called
`preventDefault()` on viewport mouse-down, so native selection and copy
could not work. A canvas has no selectable text even if that
cancellation is removed.

The transparent text layer stays aligned with the visible cell grid,
lets WebView native selection drive drag highlighting and copy, and
follows active-session switches without changing the renderer or PTY
protocol.

## Validation

- `pnpm --dir desktop typecheck`
- `pnpm --dir desktop test` — 4,373 passed
- pre-push `desktop-check`, `desktop-test`, and `branch-skew` hooks
passed on `1f2a3f8db63f6fe36b4a28bc911aea3c5186b2b0`

---------

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

## Summary

WebKit throws `SecurityError` from `localStorage.getItem` (not just
`setItem`) when storage access is denied for the origin. With no
`ErrorBoundary` in `desktop/src`, any such throw inside a provider
render (`ThemeProvider`, `CommunitiesProvider`, `App` boot) propagated
to the reconciler, unmounted the root, and left a blank window. Measured
repro in block#5078: a single throwing `getItem` on `buzz-communities` or
`buzz-active-community-id` kills the container.

Closes block#5078.

## What changed

**New helper — `desktop/src/shared/lib/safeStorage.ts`**
- `getStorageItem(key, fallback?)` — wraps
`window.localStorage.getItem`; on a thrown error (SecurityError under
denied-storage origin) it warns once per key and returns the fallback.
- `setStorageItem(key, value)` and `removeStorageItem(key)` — same
fail-closed contract (return `false` on throw).
- Unit tests in `safeStorage.test.mjs` cover the happy path and the
`SecurityError` path.

**Rewired the init-path readers that ran before any UI existed**
- `desktop/src/features/communities/communityStorage.ts` —
`migrateLegacyCommunityStorage`, `loadCommunities`,
`loadActiveCommunityId`, `loadCommunityDiscoveryAfterLeave`,
`initFirstCommunity`
- `desktop/src/features/communities/legacyCommunityStorage.ts` —
`migrateLegacyCommunityStorageBeforeRender`
- `desktop/src/shared/theme/ThemeProvider.tsx` — `readStoredTheme`,
`applyCachedVars`, the `useState` initialisers for `accentColor` and
`followSystem`, and the accent re-read inside `applyTheme`

**Root-level fence — `desktop/src/app/RootErrorBoundary.tsx`**
- New top-level `ErrorBoundary` wrapping the whole provider tree in
`main.tsx`. Any remaining uncaught render error (a future storage read
that bypasses the helper, or any other render-time crash) renders a
degraded splash with a Reload button instead of a blank window.

## Test plan

- `desktop/src/shared/lib/safeStorage.test.mjs` — node `--test` runner,
11 assertions across healthy, absent, and SecurityError-throwing
storage.
- Full `just ci` runs on the blocker.
- Existing `communityStorage.test.mjs` and
`legacyCommunityStorage.test.mjs` continue to pass (they exercise the
same functions via in-memory Storage doubles; the new code path in
`migrateLegacyCommunityStorage` only adds a `try/catch` around the same
body).

## Why not an ErrorBoundary-only fix

A boundary alone can't help on a *clean* mount — the first throw already
unmounted the whole subtree before any state or fallback data was
loaded, so retrying would hit the same throw on the very next render.
The storage accessor has to fail closed *and* the boundary has to exist
for whatever bypasses it. Both are needed; neither is sufficient alone.

---------

Signed-off-by: iroiro147 <sarthak.singh@mastersunion.org>
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>
…ion (block#5143)

## Summary

WebKit throws `NotificationError` from the `Notification` constructor
when the notification backend becomes temporarily unavailable (measured
repro attached to block#5081). Every existing call site used `void
sendDesktopNotification(...).then(...)` — discarding the returned
promise with no rejection handler — so a throwing constructor became an
unhandled promise rejection. The notification was silently dropped and
the only trace was console noise.

Closes block#5081.

## What changed

Fenced the throw at the source inside `sendDesktopNotification`
(`desktop/src/features/notifications/lib/desktop.ts`):

- A new `try { ... } catch { ... }` wraps `new window.Notification(...)`
and the `onclick` attach.
- On catch, we `console.warn` once and `return false`, so the promise
the call sites discard is always fulfilled with the same boolean result.
No caller needs to change.

## Why at the source and not at each call site

The issue body lists four rejecting edges:
`useAppShellDesktopNotifications` (2×), `useReminderNotifications`,
`use-feed-desktop-notifications`. Patching them one-by-one leaves the
door open for the next consumer to make the same mistake — and the
function itself advertises `Promise<boolean>`, so callers are entitled
to assume the promise resolves with the delivery bit rather than
rejects. Fixing the inside satisfies both properties for every present
and future caller.

## Test plan

- Behavior change is a guarded return value around a single constructor;
unit coverage is best expressed inside the mounted-hook harness already
used in the repro. Existing notification helpers
(`shouldNotify*.test.mjs`) continue to pass.
- Full `just ci` runs on the blocker.
- The next notification after a backend blip delivers normally (the
throw is per-call, not sticky).

## Note on scope

This addresses the titled bug: unhandled rejection from a throwing
constructor. A separate, intended follow-up is to wire a user-visible
delivery-miss event if the platform exposes one — that's
notification-observability work, not a } catch.

---------

Signed-off-by: iroiro147 <sarthak.singh@mastersunion.org>
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>
…xtractor (block#5191)

Replaces the four-helper auth resolution path with two focused functions
and adds production async tests that count relay round-trips.

**Before:** `resolve_auth` called `resolve_auth_from_profile`
(warn-emitting probe into a throwaway sink) → `resolve_auth_deciding`
(re-classified the same profile) → `handle_auth_failure` →
`auth_failure_detail` (third classification). `Option<Option<&Value>>`
encoded a sentinel for unreachable state; tests exercised only the pure
sync helper, not the actual fetch count.

**After:**

- `extract_auth(profile, target, signer) -> Result<[String;4],
AuthFailure>` — pure typed extractor; `AuthFailure` now covers
`NoProfile` and `NoTagsArray` inline, no separate helper needed
- `resolve_auth()` is now the linear state machine: self-check → fetch +
extract → on failure: fetch again → route final `Err` to
`CliError::Usage` (default) or one admin warning (`--admin`). No
throwaway sinks, no duplicate classification, no sentinel type.
- Five async tests drive the production resolver through a counted Axum
test server on `POST /query` and assert on both return value and exact
fetch count: first success (1), retry success (2), double failure / no
`--admin` (2 + `Err`), double failure / `--admin` (2 + `Ok(None)` + one
warning), self path (0). Two parser tests pin `--admin` on both
`archive` and `unarchive`.
- `--admin` short help text corrected to describe when the flag takes
effect (after extraction fails, not unconditionally).

341 tests passing, clippy clean, fmt clean.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
…g, activity feed polish (block#5073)

## Summary

Follow-up batch on the Projects overview (continues merged block#1677):

- **Repository access restrictions** — repositories the viewer can't
reach are surfaced with a reason instead of failing silently.
Channel-ACL denials (which arrive as the same 404 as a missing repo, for
anti-enumeration) are re-classified using the repository's channel
binding and the viewer's memberships (`useRepositoryAccess.ts`,
`projectRepoAvailability.ts`).
- **Projects loads in seconds instead of minutes** — enumeration no
longer crawls every kind:5 deletion event on the relay. It fetches
project/repo announcements first, then queries deletions scoped to those
coordinates via chunked `#a` filters (3 queries instead of hundreds on
staging).
- **Activity feed layout polish** — bare event-type glyph beside the
headline (no badge circle), timeline spine runs through the avatars
connecting consecutive cards, linkable actor/project names are bold in
theme foreground, rounded hover state, alignment fixes.
- **Create button pinned** — the "+" create menu is pinned to the pane's
top-right corner (equal 16px insets) and no longer scrolls away with the
page header.
- **List controls as a table header** — the scope selector (left) and
sort + layout toggle (right) render as the first row of the list
container on the Projects/Repositories/PRs/Issues tabs; in card view the
identical bar stands alone with the cards below
(`ProjectsListHeaderBar.tsx`).
- **Repository rows show the git location** — subtitle is
`github.com/org/repo` for external repos or `owner/repo` (resolved
profile name) for Buzz-hosted ones, instead of repeating the project
name (`repositoryDisplayPath`).
- **Uniform work-item row heights** — issue rows previously ran the
author chip in inline flow, letting the 20px avatar grow the line box
~3px taller than PR rows; both lists now share the same flex subtitle.

📸 Screenshots: [feed layout / pinned
button](block#5073 (comment))
· [list header / repo subtitles / row
heights](block#5073 (comment)).

Note: two empty `chore: retrigger CI` commits exist on the branch from
working around the Aug 6 GitHub Actions incident; happy to drop them
with a signoff rebase before undrafting if preferred. Latest `main` is
merged in (`a0cc35220`).

## Test plan

- [x] Desktop unit tests (4,493 pass after merging main), Biome, tsc
- [x] New unit tests for scoped deletion enumeration and repo
availability re-classification
- [x] New unit tests for `repositoryDisplayPath` (external, Buzz-hosted,
unresolvable)
- [x] Screenshot verification of feed layout, connector spine, and
pinned button (top + scrolled states) — posted to the PR
- [x] Screenshot verification of the list header row (list + card), repo
subtitles, and matching PR/issue row heights — posted to the PR
- [ ] Manual pass against staging (projects list load time,
restricted-repo states)

---------

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
## Problem

In the **Edit channel** dialog, flipping visibility (Public <> Private)
persisted **immediately on selection**, bypassing the **Save changes**
button — while every other field (name, description, temporary, TTL)
waited for an explicit save. This surprised users and gave no chance to
cancel a flip, e.g. a private->public change that instantly exposes
channel history.

Reported in the Buzz "Welcome" channel by Kevin Chung.

## Root cause

The visibility dropdown was wired to `handleConvertVisibility()`, which
called the update mutation on selection. This was intentional at the
time (there was even an e2e test named `02 — visibility updates
immediately` and an "Updating…" spinner), but it is inconsistent with
the rest of the dialog and is the surprising behavior reported.

## Change (defer to Save)

- Visibility becomes a **deferred draft** like the other fields:
selecting a value updates local `isPrivateDraft` and marks the draft
dirty. The change commits via `handleSaveChannelEdits` (which already
handled visibility) on **Save**, and is discarded on **Cancel**.
- The dialog title now reflects the **pending draft**
(`nextVisibility`), so the pending choice is visible before saving.
- The edit-dialog reset restores `isPrivateDraft` from server state.
- Removed the now-dead `handleConvertVisibility` handler,
`isConvertingVisibility` state, the `channelIdRef` race guard it needed,
and the unused `isPending`/"Updating…" spinner path in
`ChannelPermissionsSettings` (no caller passes `isPending` anymore).

## Tests

- Rewrote e2e `02` -> **`visibility defers to Save`**: select -> Save
enabled -> title reflects draft -> Save -> persists; toggling back to
the original value clears the draft and disables Save.
- Extended `09` (cancel discards drafts) to also cover a visibility
change.
- Repurposed `10`: the stale-update race it guarded is architecturally
gone, so it now asserts an **unsaved visibility draft does not leak
across a channel switch**.

## Validation

- `pnpm typecheck` — clean
- `biome check` (changed files) — clean
- `pnpm test` — **4497 passed / 0 failed**
- `playwright test --project=smoke channel-controls` — **10 passed**

Signed-off-by: Kevin Chung <chung@squareup.com>
Co-authored-by: Fizz <e3f95089179cc1bcc68d70c334b9bdf670d0470496db90bcdbb20386963432da@buzz.block.builderlab.xyz>
…5202)

## Summary
- preserve each distinct agent pubkey in autocomplete even when agents
share a persona or owner/name
- continue to collapse duplicate source rows for the same normalized
pubkey
- show a truncated pubkey in the channel member-add picker so same-named
instances are selectable

## Validation
- `pnpm --filter buzz test` — 4,489 passed
- `pnpm --filter buzz exec tsc --noEmit --pretty false`
- `pnpm --filter buzz exec biome check
src/features/agents/lib/agentAutocompleteEligibility.ts
src/features/agents/lib/agentAutocompleteEligibility.test.mjs
src/features/channels/ui/MembersSidebar.tsx`
- independent validation by Fast Fizz on
`509cb8d97b82f9708e24d4d59ad17c7b39516643`: typecheck, focused Biome,
22/22 focused tests, and `git diff --check`

Generated by Hardworking Honey.

---------

Signed-off-by: Hardworking Honey <c5c455215c2506cb8ba776518cec804af62d3a0526e32d496a22072e395042b9@buzz.block.builderlab.xyz>
Co-authored-by: Hardworking Honey <c5c455215c2506cb8ba776518cec804af62d3a0526e32d496a22072e395042b9@buzz.block.builderlab.xyz>
…rized, ACP v2 messageId (block#5195)

Three pre-existing gaps in the buzz-agent observer feed fixed together
per Will's ruling ("all 3 in the current PR"):

1. **OpenAI/DBv2-GPT route** — `responses_body` never requested
`reasoning.summary`; GPT-family models billed thinking tokens but
returned `summary: []`.
2. **Anthropic/DBv2-Claude route** — `anthropic_thinking_config()` never
sent `thinking.display`; newest Claude models (Opus 5, Sonnet 5, Fable
5, Mythos 5, Opus 4.7/4.8, Mythos Preview) default to
`display:"omitted"`, returning thinking blocks with an empty `thinking`
field — observer rendered nothing.
3. **ACP v2 compliance** — buzz-agent negotiates ACP v2 but emitted
`agent_thought_chunk` and `agent_message_chunk` without `messageId`,
which ACP v2's `ContentChunk` requires (`messageId` + `content` both
required at schema head `d13d1baa`).

## Changes

**`crates/buzz-agent/src/config.rs`**
- New `ThinkingSummary` enum (`Auto`/`Concise`/`Detailed`) with
`BUZZ_AGENT_THINKING_SUMMARY` env var (default `Auto`); mirrors
`BUZZ_AGENT_THINKING_EFFORT` pattern
- `anthropic_thinking_config()` now emits `"display": "summarized"` in
both the adaptive shape and the manual-budget shape whenever thinking is
enabled
- Rewrote `is_adaptive_thinking_model` and `anthropic_thinking_config`
doc comments to match Anthropic's exact three-way per-model terminology
(doc:
https://platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting#supported-models):
- Opus 4.6/4.7/4.8, Sonnet 4.6: **Off** — thinking OFF by default;
`type:"adaptive"` required to enable
- Opus 5, Sonnet 5: **On** — thinking on by default, can be disabled; we
still send `type:"adaptive"` to activate `output_config.effort`
- Fable 5, Mythos 5, Mythos Preview: **Always on** — thinking cannot be
disabled; we still send `type:"adaptive"` to activate
`output_config.effort`

**`crates/buzz-agent/src/llm.rs`**
- `responses_body` emits `reasoning.summary` alongside
`reasoning.effort` when effort is set (gated — no bare
`reasoning:{summary}` without effort)
- Covers both the pure-OpenAI Responses path and the DBv2 GPT-family
Responses path

**`crates/buzz-agent/src/agent.rs`**
- `agent_thought_chunk` carries `"messageId":
format!("{run_id}-thought-{round}")`
- `agent_message_chunk` carries `"messageId":
format!("{run_id}-message-{round}")`
- The two IDs are distinct (thought and assistant are two logical
messages per the ACP v2 Message ID RFD)
- `run_id` is a fresh random token per `session/prompt` invocation so
IDs are session-unique across multiple prompts

**`crates/buzz-agent/src/lib.rs`**
- `run_id` plumbed into `RunCtx` (was already generated in `run_prompt`,
just not threaded through)

**`crates/buzz-agent/tests/golden_transcripts.rs`**
- `test_acp_v2_chunks_carry_message_id` — negotiates v2, drives two
consecutive `session/prompt` calls, asserts: both chunk types carry
non-empty `messageId`; thought and message IDs are **distinct**; IDs do
**not** recur across the two prompts in the same ACP session

**`desktop/src-tauri/src/managed_agents/env_vars.rs`**
- `BUZZ_AGENT_THINKING_SUMMARY` added to `is_safe_to_reveal` allowlist

**`desktop/src-tauri/src/commands/agent_config_tests.rs`**
- Tests for `BUZZ_AGENT_THINKING_SUMMARY` allowlist entry
(case-insensitive)

## Tests added

- `parse_thinking_summary_round_trips_all_values`
- `parse_thinking_summary_unset_and_empty_yield_auto`
- `parse_thinking_summary_is_case_insensitive`
- `parse_thinking_summary_rejects_unknown_value`
- `thinking_summary_as_str_mapping`
- `responses_body_summary_present_iff_effort_set`
- `responses_body_emits_configured_summary_mode`
- `responses_body_concise_summary_mode`
- `anthropic_thinking_config_adaptive_emits_display_summarized`
- `anthropic_thinking_config_manual_budget_emits_display_summarized`
- `test_acp_v2_chunks_carry_message_id` (integration test — two-prompt
cross-session case)

## Notes

- **DBv2 gateway parity for `display`**: unverified — the DBv2 Claude
route proxies Anthropic Messages shape, but whether the gateway passes
`thinking.display` through is not confirmed. Flagged here rather than
blocking on it.
- buzz-acp and Desktop TS are unchanged — they already parse `messageId`
as optional and will pick it up from the wire automatically.
- Chat Completions and OpenRouter paths: untouched.

---------

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

**Category:** improvement  
**User impact:** Link previews appear in the composer and travel as
privacy-safe sender-authored snapshots, so recipients never contact the
linked site merely by opening a conversation.
**Problem:** Cold-cache link paste could freeze the composer before the
URL painted; recipient-side unfurling leaked visits; invalid or
unresolved preview work could interfere with sending or leave dead cards
behind.
**Solution:** Paint pasted links before starting cold resolver work,
resolve only in the sender's composer, attach only complete validated
snapshots at Send, and render authored snapshots without recipient
fallback fetching.

## Behavior

- **Cold paste stays responsive:** bare and angle-bracket URL paste
paths commit the visible link before resolver work begins.
- **Sender-only fetching:** metadata is resolved while composing;
recipients render only the sender-authored snapshot.
- **Send never waits:** pending, failed, invalid, and unsendable
previews are omitted. They do not block or cancel the message.
- **Terminal misses disappear:** failed, timed-out, or 404 resolver
results remove the composer card while preserving visible link text.
- **Display-text links work:** Markdown links such as `[review the pull
request](…)` produce and send the same snapshots as bare URLs.
- **Compact and Rich presentation:** Compact remains the default; Rich
preserves source description line breaks and paragraphs.
- **Immediate draft-wide dismissal:** clicking × immediately hides all
previews for the draft, suppresses links pasted later, and emits only
`["link-preview", "none"]`. No confirmation detour. Suppression resets
after send or clearing the draft.
- **Zero recipient fallback:** missing, stale, malformed, off-relay,
unsupported, or suppressed snapshots remain ordinary visible links;
recipients never regenerate them.

## Implementation

- Resolve previews from deferred composer URL state so paste can paint
first.
- Upload finished preview media to the active community relay and
snapshot only valid, sendable media references.
- Atomically capture ready snapshots at submit time; never append a late
preview after send.
- Validate snapshot and suppression tags in desktop/native and relay
ingestion, rejecting duplicate or mixed forms.
- Render composer previews as stable 55px attachment cards at desktop
and narrow widths.
- Add deterministic E2E coverage for cold paste,
ready/pending/failed/invalid previews, display-text links, multiline
Rich descriptions, immediate dismissal, later-pasted links, and
suppression reset.

## Validation

Validated head: `9807ba8952f190e76153834abf8ab61dd40be5e2`

- Push hooks passed: `check-push-org`, branch skew, desktop check,
mobile tests, desktop tests, Rust tests, and desktop Tauri checks.
- Focused screenshot E2E at the validated head: 5/5 passed across
Compact/Rich composer and recipient states, 800px/420px geometry,
display-text links, multiline descriptions, and immediate dismissal.
- PR CI was triggered for this exact head and is currently running;
completed checks are green at the time of this update.
- Worktree is clean and both PR head and validated branch resolve to
`9807ba895…`.

## Screenshots

### Compact composer

| Loading | Ready |
|---|---|
| ![Compact composer
loading](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/compact-composer-loading.png)
| ![Compact composer
ready](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/compact-composer-ready.png)
|

### Rich composer

| Loading | Ready |
|---|---|
| ![Rich composer
loading](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/rich-composer-loading.png)
| ![Rich composer
ready](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/rich-composer-ready.png)
|

### Responsive composer

| 800px loading | 800px ready |
|---|---|
| ![800px composer
loading](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/composer-800-loading.png)
| ![800px composer
ready](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/composer-800-ready.png)
|

| 420px loading | 420px ready |
|---|---|
| ![420px composer
loading](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/composer-420-loading.png)
| ![420px composer
ready](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/composer-420-ready.png)
|

### Recipient presentation

| Compact | Rich |
|---|---|
| ![Recipient
compact](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/recipient-compact.png)
| ![Recipient
rich](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/recipient-rich.png)
|

### Display-text Markdown link

| Composer | Recipient |
|---|---|
| ![Display-text link in
composer](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/display-text-composer.png)
| ![Display-text link with recipient
preview](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/display-text-recipient.png)
|

### Rich multiline description

![Rich preview preserving description
paragraphs](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/rich-multiline-recipient.png)

### Immediate dismissal

| Before × | Immediately after × |
|---|---|
| ![Preview before immediate
dismissal](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/dismissal-before.png)
| ![Composer immediately after preview
dismissal](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/dismissal-after.png)
|

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
## Summary
<!-- What does this change and why? -->

block#3419 is a tauri bug (tauri-apps/tauri#15110),
which is already fixed in
tauri-apps/tauri#15596. All we need is bump the
@tauri-apps/cli version to include the bug fix.

```sh
pnpm update --filter ./desktop @tauri-apps/cli@2.11.4
```

This pr simply includes the changes after running the update command.

### Related issue
<!-- Fixes block#1234, or N/A. Before opening: search existing issues/PRs for
duplicates — link the closest one, or say "none found". -->

fix block#3419

close block#3436. this pr supersedes it.


### Testing
<!-- How was this verified? UI change? Include before/after screenshots
(or a short recording). -->

build the appimage and check the symlink in the appimage using
`unsquashfs`.
```sh
$ unsquashfs -o 944632 -ll /tmp/buzz/desktop/src-tauri/target/release/bundle/appimage/Buzz_0.5.4_amd64.AppImage | grep -i dirIcon
lrwxrwxrwx root/root                 8 2026-08-04 21:54 squashfs-root/.DirIcon -> Buzz.png
```

Signed-off-by: Tsung-Han Yu <14802181+johan456789@users.noreply.github.com>
…lders (block#4975)

## What users saw

`buzz messages send` silently removed an explicitly supplied
self-mention. The caller passed `--mention <sender-pubkey>` and received
`accepted:true`, but the signed event had no matching `p` tag and
`mention_pubkeys` was empty.

## Why it happened

`nostr` 0.44 strips `p` tags matching the signer's pubkey by default.
The codebase already opts out with `.allow_self_tagging()` for identity
archive and unarchive requests, but the message and forum builders that
accept mentions did not. The library therefore removed the tag during
signing after the CLI had validated the explicit mention.

## What changed

Added `.allow_self_tagging()` to all three event builders that accept
mention tags:

- `build_message` (kind 9)
- `build_forum_post` (kind 45001)
- `build_forum_comment` (kind 45003)

An explicit mention now survives signing even when it matches the
sender.

## How this was tested

Added one regression test per builder. Each test signs with the same key
included in the mention list and asserts that the resulting event
preserves the self-referential `p` tag.

Validation at `cd0f30bca`:

```text
./bin/cargo fmt --all -- --check
cargo test -p buzz-sdk --lib
cargo test -p buzz-cli --lib
cargo clippy -p buzz-sdk -p buzz-cli --all-targets -- -D warnings
```

All 257 `buzz-sdk` tests and all 321 `buzz-cli` tests passed, and
formatting and strict Clippy checks completed successfully.

## Scope and non-goals

- Does not change mention validation, deduplication, or channel-member
checks.
- Does not change `normalize_mention_pubkeys`, which is not used by the
messages-send path.
- Does not add a dropped-mentions output field because the explicit tags
are now preserved.

Closes block#4906.

---------

Signed-off-by: Brad Groux <bradgroux@hotmail.com>
Signed-off-by: npub17q2gdupkvswvk5kprwc7plergm4gn295uw6fe4mjyjv53ahuhtnq02jd3f <f01486f036641ccb52c11bb1e0ff2346ea89a8b4e3b49cd772249948f6fcbae6@digitalmeld.communities.buzz.xyz>
Signed-off-by: Brad Groux <3053586+BradGroux@users.noreply.github.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: npub17q2gdupkvswvk5kprwc7plergm4gn295uw6fe4mjyjv53ahuhtnq02jd3f <f01486f036641ccb52c11bb1e0ff2346ea89a8b4e3b49cd772249948f6fcbae6@digitalmeld.communities.buzz.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
**Category:** fix
**User Impact:** Mobile users who jump to Latest now see the newest
message fully above the composer instead of partially hidden behind it.

**Problem:** The channel message list treated the raw viewport bottom as
the latest boundary even though the composer occupies part of that
viewport. Latest jumps and follow-mode corrections could therefore place
the newest message underneath the composer.

**Solution:** Derive the latest alignment from the measured composer
inset and use that same boundary for scrolling, follow detection, and
layout correction.

<img width="498" height="1008" alt="Screen Recording 2026-08-05 at 5 18
19 PM"
src="https://github.com/user-attachments/assets/a7fc1a94-3ffb-4c34-908d-9bf4f3f082b4"
/>


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

**mobile/lib/features/channels/channel_detail_page/message_list.dart**
Aligns Latest navigation and follow-mode correction with the visible
bottom edge above the composer, and evaluates boundary state against the
same geometry.

**mobile/test/features/channels/channel_detail_page_test.dart**
Adds a regression assertion that the newest live message clears the
composer and that the Latest control disappears after navigation.

</details>

## Reproduction steps

1. Open a mobile channel with enough messages to scroll away from the
newest message.
2. Tap **Latest**.
3. Confirm the newest message is fully visible immediately above the
composer and the **Latest** control disappears.
4. Resize the composer or keyboard while following latest and confirm
the newest message remains above the composer.

## Tested fix

The newest message remains fully visible above the composer after
jumping to **Latest**.

![Tested fix: latest message remains above the
composer](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/4981/latest-above-composer-tested.gif)

## Validation

- `flutter analyze` — no issues
- `flutter test` — 1,243 passed

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
## Buzz Desktop release v0.5.6

- **Frozen main:** `78c87ae20e182fffdd99744d6c9ff99df82b159c`
- **Reviewed candidate:** `277d98a5cfb6d3b9af8b75122988f7a7df33ed5d`
- **Previous desktop release:** `desktop-v0.5.5`
- **Proposed immutable tag:** `desktop-v0.5.6`

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

- treat provider `max_tokens` as an interrupted assistant response and
continue the same turn with actionable feedback
- discard tool calls from truncated responses, including malformed
partial arguments, so they are neither executed nor replayed with
invalid tool-result pairing
- bound recovery to two retries while preserving normal finite
`max_rounds` accounting

## Verification

- `cargo fmt --all -- --check`
- `cargo test -p buzz-agent` (422 unit tests plus all package
integration/doc suites passed)
- `cargo clippy -p buzz-agent --all-targets -- -D warnings`

## Notes

The pre-push repository-wide hook also ran. Its Rust tests passed (2,270
passed, 14 ignored), but its `buzz-db` unit-test build was blocked
because local rustc 1.89 is below sqlx 0.9's rustc 1.94 requirement. The
affected package suite above is green on the exact pushed commit.

Originating Buzz channel: `c3252dd2-0142-4e01-88c7-a2183c3960a5`

Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
…block#5228)

**Category:** fix
**User Impact:** People who onboard by importing an existing key or
recovering from a phone can now use "Skip for now" (and Next) on the
harness setup and model config steps, instead of getting stuck.

**Problem:** On the "Set up your agent harnesses" and "Configure your
default model settings" onboarding steps, clicking **Skip for now** — or
**Next** — did nothing for anyone who reached those steps by importing
an existing key or recovering an identity from a phone. The app stayed
frozen on the step.

**Solution:** The onboarding state machine sets `continuingPubkeyRef` to
the current pubkey on import/recovery to keep the flow on `onboarding`
until setup finishes (added in block#4845). But `complete()` never cleared
that ref, so once it matched the current pubkey the stage stayed pinned
to `onboarding` forever — completion could never win. `complete()` now
clears the ref so finishing/skipping actually settles the flow.
Fresh-generated keys never set the ref, which is why first-run fresh-key
skip already worked and the gap went unnoticed.

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

**desktop/src/features/onboarding/machineOnboarding.ts**
Clear `continuingPubkeyRef` inside `complete()` so an imported/recovered
identity's "continuing" marker no longer outlives completion and pin the
stage to `onboarding`.

**desktop/tests/e2e/onboarding.spec.ts**
Add a regression test that imports an existing key, reaches harness
setup, clicks **Skip for now**, and asserts onboarding exits (reaches
community onboarding). This fails without the fix. The existing skip
tests only exercised the fresh-key path, which never set the ref — hence
the gap.

</details>

## Reproduction steps

1. Start onboarding and choose **Use an existing key** (or recover from
a phone); import a key and continue to **Set up your agent harnesses**.
2. Click **Skip for now** (or **Next**). Before this change, nothing
happens — the step is stuck. The same trap hits **Configure your default
model settings**.
3. With this change, Skip/Next advances out of onboarding as intended.
4. Automated: `pnpm build:e2e && pnpm exec playwright test
onboarding.spec.ts --project=integration -g "imported-key users can skip
out of harness setup"` — passes with the fix, fails without it.

## Root cause

Introduced by block#4845 (`feat(identity): recover desktop identity from a
signed-in phone`), which added `continuingPubkeyRef.current ===
currentPubkey` as an independent condition selecting the `onboarding`
stage. That guard has no off switch: `complete()` set the completion
flag but never cleared the ref, so the OR'd condition kept the stage
pinned. Not a revert candidate — the guard's intent (keep a
just-published identity in onboarding until setup finishes) is correct;
it just needed to release on completion.

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

## Problem

Two v0.5.6-only regressions were introduced by block#4614 (the first enforced
Tauri CSP):

1. **Tab-complete caret regression** — after tab-completing an @mention,
#channel, or :emoji: shortcode, the cursor landed inside the inserted
text instead of after the trailing space. TipTap inserts the correct
text including the trailing space, but without its base stylesheet
(`.ProseMirror { white-space: break-spaces }`) the trailing space
collapses visually and the caret appears mid-name.

2. **Emoji picker unstyled** — the emoji-mart picker rendered as a giant
unstyled layout (oversized search SVG, collapsed grid) because
emoji-mart's shadow-root stylesheet injection was also blocked.

Both symptoms have the same root cause.

## Root Cause

Tauri's build-time asset processor scans `index.html` for inline
`<style>` elements, injects a nonce token, and adds the corresponding
`'nonce-…'` source to `style-src` at runtime. Per the CSP spec, **once a
nonce is present in a directive, the browser ignores `'unsafe-inline'`
for that directive**.

`index.html` contained an inline `<style>` with the boot background
color. When Tauri nonced it and injected `'nonce-…'` into `style-src`,
the intended `style-src 'self' 'unsafe-inline'` became effectively
`style-src 'self' 'nonce-…'` — blocking any runtime stylesheet injection
not covered by a matching nonce:

- TipTap's `injectCSS()` → `createStyleTag()` injecting `.ProseMirror {
white-space: break-spaces; … }`
- emoji-mart's shadow-root `document.createElement('style')` injection

(Inline scripts follow a separate path — they are SHA-256 hashed, not
nonced.)

This only reproduces in packaged builds (where Tauri's custom protocol
serves the HTML and enforces the policy). `tauri dev` loads from the
Vite dev server and is not affected.

## Fix

Move `html { background-color: #000; }` from an inline `<style>` in
`index.html` to `desktop/public/boot.css`, linked via `<link
rel="stylesheet">`. A linked stylesheet is not subject to Tauri's nonce
injection, so `'unsafe-inline'` in `style-src` applies as declared.

The `<link>` is render-blocking (same as the inline style was), so
boot-flash behaviour is identical.

**The production CSP string is unchanged.** This fix makes the policy
apply as intended — no security properties are altered. Will's follow-up
with the security team (Jordan Mecom / Eli Foster, authors of block#4614) is
noted for post-ship.

A Tauri-faithful CSP harness for the Vite dev path (so this class of
regression is visible before a packaged build) is tracked as a separate
follow-up.

## Files Changed

- `desktop/index.html` — replace inline `<style>` with `<link
rel="stylesheet" href="/boot.css" />`
- `desktop/public/boot.css` — new file, the extracted `html {
background-color: #000; }` plus rationale comment
- `desktop/src-tauri/tests/csp.rs` — update comment: nonce for styles,
SHA-256 for the boot script

## Testing

- `just desktop-typecheck` ✅
- `just desktop-test` ✅ (4535/4535)
- `just desktop-tauri-test` ✅ (all Rust tests including `csp.rs`)
- Packaged validation: `pnpm tauri build --debug` completed; compiled
binary bakes `style-src 'self' 'unsafe-inline'` with no nonce source
injected ✅

---------

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

- serialize the relay error-message test with all other tests mutating
the process-wide admission gate
- clear its 300-second rate-limit expiry after the assertion
- prevent the paused-time waiter test from observing another test's
state

## Root cause

`relay::tests::oversized_hint_is_capped_in_relay_error_message_string`
arms the process-wide gate for 300 seconds without taking `TEST_SERIAL`
or resetting it. In a parallel test run,
`relay_admission::tests::concurrent_429_extends_the_window_for_parked_waiters`
can observe that expiry, producing the reported `300.001s` instead of
`5s`.

## Validation

- focused admission suite + relay error test repeated 10 times
- pre-push `desktop-tauri-checks` passed, including the full Rust
workspace suite
- `branch-skew` passed

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

- **Frozen main:** `74b913cff8512c015dc6f1a7473b253fa803f954`
- **Reviewed candidate:** `f167818d25dd9f03115ab907a16f07daee2ece5c`
- **Previous desktop release:** `desktop-v0.5.6`
- **Proposed immutable tag:** `desktop-v0.5.7`

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>
Karniej and others added 5 commits August 7, 2026 16:09
## What changed

Bind the development Compose stack's published PostgreSQL, Redis,
Adminer, Keycloak, MinIO, and Prometheus ports to `127.0.0.1`.

## Why

Docker publishes a host port on every interface when no host address is
specified. Running the development stack on a remote workstation or VPS
therefore exposes its infrastructure services to that machine's public
networks. Loopback bindings retain host-local development access and
Docker's internal `buzz-net` connectivity without making those services
Internet-reachable.

## Impact

Local workflows continue using the same ports. Deliberate remote
administration now requires an SSH tunnel or another trusted
private-network path.

## Validation

- `docker compose -f docker-compose.yml config --quiet`
- Recreated the six affected services with their existing named volumes
and Docker network
- PostgreSQL remained healthy and retained all 54 application tables
- Redis, MinIO, and Prometheus health checks passed
- All affected ports were closed on the host's public IPv4 and IPv6
addresses while remaining available on loopback

Origin:
`buzz://message?channel=199eb7bc-3feb-484f-ae0e-4995123721ea&id=1c5bc387e86e21bb31677f56e1c862d4d9a17943bce91f8d93e825d029ce7f72`

Signed-off-by: Paweł Karniej <karniej.p@gmail.com>
…starve the handoff summary (block#5248)

## Problem

The handoff summarizer sends `max_tokens: 8192`
(`HANDOFF_MAX_OUTPUT_TOKENS`) with no reasoning budget separation. On
reasoning models, thinking tokens count against that cap: the model can
spend the entire budget reasoning, length-stop with empty `content`, and
`summarize()` — which only reads `content` — reports an empty summary.
The handoff then degrades to lossy history truncation.

Observed on deepseek-v4-flash during a terminal-bench 2.1 run
(tb21-solo-3, 89 tasks): **13 consecutive handoff attempts across 5
trials failed exactly this way** (`handoff returned empty summary;
truncating`), each burning ~3 minutes of full-cap reasoning, before a
stochastically-short reasoning run finally fit. circuit-fibsqrt alone: 5
failures, 5 truncations, then success on attempt 6. video-processing
failed its task by one frame after 3 context truncations.

## Fix

`openrouter_summary_body` now grants reasoning its own equal-sized
budget and excludes it from the response:

- `reasoning.max_tokens = max_output_tokens` — thinking gets a dedicated
budget instead of competing with the summary text
- `reasoning.exclude = true` — reasoning is never in the response body;
`summarize()` only reads `content`
- `max_tokens = max_output_tokens * 2` — the total cap covers both
budgets, so the text budget the caller asked for is actually available
for text

Non-reasoning endpoints ignore the `reasoning` object. Deliberately not
paired with `provider.require_parameters`, for the reasons documented at
`apply_openrouter_mutations` (it hard-404s valid model ids).

The prior test
`openrouter_summary_carries_neither_reasoning_nor_provider` asserted
`reasoning` absent from the summary body — that assertion guarded
against *effort-based* reasoning leaking in from config (the body is
built independently of `cfg`, which is still true and still tested:
`reasoning.effort` stays unset). Replaced with
`openrouter_summary_budgets_reasoning_separately_and_carries_no_provider`.

## Verification

- `cargo test -p buzz-agent`: 422 unit + 110 integration tests pass at
bb2fedd
- `cargo fmt` / `cargo clippy -p buzz-agent --all-targets`: clean
- Not yet validated against a live OpenRouter reasoning endpoint — the
failing scenario needs a long-context session to trigger organically.
Evidence for the mechanism is from run artifacts (13/13 empty-summary
length-stops on deepseek-v4-flash) and OpenRouter's documented
`reasoning.max_tokens`/`reasoning.exclude` semantics.

---------

Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
**Category:** improvement
**User Impact:** Users can create, discover, and import agents from one
consistent Add agent dialog.

**Problem:** Agent creation, discovery, and import were split across a
dropdown and separate dialogs, making the Add agent flow fragmented. The
existing E2E suite also continued targeting the deleted dropdown after
the flows were unified.

**Solution:** Route the new-agent card directly into a unified dialog
with dedicated Create, catalog, and Import navigation, then update the
affected E2E coverage to exercise that interface and its current empty
state.

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

**desktop/src/features/agents/ui/AgentDefinitionDialog.tsx**
Supports rendering the agent definition form inside the unified Add
agent experience while retaining the standalone dialog behavior.

**desktop/src/features/agents/ui/AgentDefinitionDialogShell.tsx**
Adds the shared shell used to present agent-definition content
consistently in embedded and standalone contexts.

**desktop/src/features/agents/ui/AgentDialog.tsx**
Passes the revised dialog state and close behavior through the existing
agent dialog entry point.

**desktop/src/features/agents/ui/AgentsView.tsx**
Connects the Agents page to the unified Add agent dialog and opens newly
added catalog agents in their profile panel.

**desktop/src/features/agents/ui/PersonaCatalogDialog.tsx**
Combines catalog browsing, agent creation, and snapshot import behind
persistent navigation, including dirty-navigation confirmation.

**desktop/src/features/agents/ui/UnifiedAgentsSection.tsx**
Replaces the new-agent dropdown with a direct Add agent entry point and
adjusts the responsive card grid.

**desktop/src/features/agents/ui/personaLibraryCopy.ts**
Updates catalog-facing copy for the unified experience.

**desktop/src/features/agents/ui/usePersonaActions.ts**
Returns the resolved local persona after catalog activation so the
caller can open the added agent.

**desktop/tests/e2e/agent-readiness-screenshots.spec.ts**
Opens the embedded create pane directly for readiness screenshots.

**desktop/tests/e2e/agents.spec.ts**
Covers unified Create, catalog, and Import navigation and asserts the
current shared-agent empty state.

**desktop/tests/e2e/global-agent-config-screenshots.spec.ts**
Updates global configuration screenshot setup for direct create-pane
entry.

**desktop/tests/e2e/inline-custom-harness.spec.ts**
Updates custom harness setup for the embedded create form.

**desktop/tests/e2e/persona-env-vars.spec.ts**
Updates environment-variable and model-provider scenarios for direct
create-pane entry.

**desktop/tests/e2e/persona-model-combobox-screenshots.spec.ts**
Updates model combobox screenshot setup for direct create-pane entry.

**desktop/tests/e2e/smoke.spec.ts**
Updates agent-creation smoke coverage for the unified Add agent dialog.

**desktop/tests/e2e/where-to-run-config.spec.ts**
Updates provider-selection coverage for the embedded create form.

</details>

## Reproduction steps

1. Open the Agents page and select the new-agent card.
2. Confirm the Add agent dialog opens directly on Create without an
intermediate dropdown.
3. Use the left navigation to browse shared agents and open Import.
4. Select a catalog agent and confirm the dialog closes and the added
agent's profile panel opens.
5. Run the affected desktop Playwright smoke and integration specs and
confirm all scenarios pass.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Signed-off-by: adrienlacombe <6303520+adrienlacombe@users.noreply.github.com>

# Conflicts:
#	crates/buzz-relay/src/handlers/ingest.rs
#	desktop/src-tauri/tauri.conf.json
Upstream block#3818 (rich link previews) renamed resetLinkPreviewTitleCache to
resetLinkPreviewMetadataCache, so AGENTS.md's list of community-scoped
singletons named a function that no longer exists. That list is what a
future change consults before adding a module-level cache, so a stale
entry there is a data-leak footgun across community switches, not a
cosmetic typo.

Also adds resetBackgroundMediaUploads, which was already wired into
resetCommunityState but had never been listed.

Signed-off-by: adrienlacombe <6303520+adrienlacombe@users.noreply.github.com>
@adrienlacombe adrienlacombe added upstream-sync needs-human Sync stopped on a tripwire; a human must review and merge labels Aug 8, 2026
@adrienlacombe
adrienlacombe merged commit dede245 into main Aug 9, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-human Sync stopped on a tripwire; a human must review and merge upstream-sync

Projects

None yet

Development

Successfully merging this pull request may close these issues.