Skip to content

[upstream-sync] Merge block/buzz f8692fa9b..93114c9c6 (26 commits) - #48

Merged
adrienlacombe merged 28 commits into
mainfrom
upstream-sync-20260819
Aug 19, 2026
Merged

[upstream-sync] Merge block/buzz f8692fa9b..93114c9c6 (26 commits)#48
adrienlacombe merged 28 commits into
mainfrom
upstream-sync-20260819

Conversation

@adrienlacombe

Copy link
Copy Markdown
Owner

Upstream sync — 26 commits

f8692fa9b..93114c9c6 from block/buzz, merged with --no-ff so the second
parent is preserved.

git cat-file -p <merge>  -> 2 parents
git rev-list --count upstream/main ^HEAD -> 0

What changed upstream, by area

Desktop

Mobile

Agents / relay

CI / deps

Conflicts

File Resolution
desktop/src-tauri/tauri.conf.json Kept the fork's productName: BitcoinMarkets and identifier: app.bitcoinmarkets.desktop; took upstream's version: 0.5.17. The standard resolution for this row.
mobile/android/app/build.gradle.kts Upstream extended the debug label into if (debugAppName != null) … else if (worktreeLabel != null). Took upstream's new branch and kept the fork's "BitcoinMarkets ($worktreeLabel)" in the fallback, with a FORK-LOCAL marker. Both fork hunks (defaultConfig + worktree branch) intact.
Cargo.lock Version-disambiguation noise (windows-sys 0.61.2 vs 0.52.0/0.60.2). Took upstream's file wholesale, then let cargo re-resolve the fork's Starknet/paymaster deps on top. Verified with --locked. Not hand-edited.

Fork-patch sites upstream touched (read even where clean)

ci.yml, CHANGELOG.md, desktop/src-tauri/src/lib.rs,
desktop/src-tauri/src/relay.rs, scripts/test-mobile-worktree-overrides.sh
all auto-merged, all markers verified present and still meaningful:

  • ci.yml — the mesh-llm desktop-lock patch survived. Re-derived both locks:
    root and desktop both pin tag=v0.75.1 (3295c902), so the patch remains a
    temporary no-op, as the AGENTS.md row already records. Not deleted.
  • relay.rs — 995 lines, still under the 1000 ratchet. Upstream's change was
    1/1, no growth. just file-size-check passes.
  • lib.rs — the single deep_link::is_supported_deep_link argv filter intact.
  • CHANGELOG.md — upstream prepended v0.5.17/v0.5.16 at the top; fork-test
    entries still anchored at the bottom.
  • release.yml — untouched by upstream. Checked anyway for the failure mode
    that bit the 2026-08-16 sync: every needs.<job>.result still resolves to a
    defined job (8 jobs, 6 refs, 0 dangling).
  • SidecarsexternalBin unchanged upstream; macos-canary.yml's build list
    still covers all six. No drift.

Verification

Gate Result
cargo fmt --all --check pass
cargo fmt (desktop/src-tauri) pass
cargo clippy --workspace --all-targets -- -D warnings pass, 0 warnings
cargo clippy (desktop/src-tauri) pass, 0 warnings
cargo metadata --locked (root) pass
cargo metadata --locked (desktop) pass
scripts/test-release-ref-contract.sh pass (clean clone)
scripts/test-mobile-worktree-overrides.sh pass
just file-size-check pass
just test-unit pass — 1483 Rust tests
just desktop-test pass — 5047 tests
just desktop-tauri-test pass — 2683 tests
dart format --set-exit-if-changed pass — 421 files, 0 changed
flutter analyze pass — No issues found
flutter test pass — 1532 tests

The release-ref contract prints two ::error:: lines from its own negative
tests; it exits 0 with release ref contract passed.

AGENTS.md updated

Two rows whose rationale shifted under this sync:

  • relay.rs — the row said an overflow surfaces as a red Desktop Core.
    ci: make file-size policy a first-class gate block/buzz#6187 moved the check to just file-size-check, run repository-wide as the
    File size policy step of the scripts job and no longer path-filtered, so
    that pointer was now wrong.
  • build.gradle.kts — records that upstream's debugAppName branch now runs
    ahead of the fork's brand literal, and that the resolution is take upstream's
    branch, keep the fork's fallback
    .

Needs a human look

Nothing blocking. Two notes:

  • No new migrations, no KIND_* changes, no wire-format changes in this range.
  • Upstream's typography rework (Add font size and conversation density preferences block/buzz#5644) changes the chat baseline from a fixed
    16px to a text-message token with a 13/14/15px Smaller/Default/Larger
    contract. No fork patch lives in that area and the desktop suite is green, but
    it is a visible change to how conversation text renders.

Merge with a merge commit, not squash. A squash drops the second parent and
strands the merge base, which is the whole point of this job.

wesbillman and others added 28 commits August 17, 2026 20:24
## Summary

- preserve OSS relay-agent mentions under shared channel and agent
policy
- restrict owner-only release builds to relay agents with
cryptographically verified ownership matching the current user
- remove the remote policy replay loop that repeatedly rebuilt the relay
directory, while retaining focused polling and send-time revalidation
- query relay profiles and managed policies by exact author coordinates
to prevent noisy events from crowding out valid agents

## Diagnosis

The packaged Block release compiles
`BUZZ_DESKTOP_BUILD_AGENT_ACCESS_OWNER_ONLY`, while ordinary OSS/dev
builds do not. Relay-discovered agents were filtered as if all remote
agents were outside that owner-only boundary, so a same-owner agent
running on another machine disappeared in the release even though the
OSS path could look healthy.

The fix uses the NIP-OA-authenticated owner from the relay directory as
the cross-machine proof. Internal builds admit only verified same-owner
agents and fail closed for missing, mismatched, stale/revoked, or
unavailable ownership evidence. OSS builds retain shared channel/policy
behavior.

## Validation

- desktop focused unit coverage: 39 tests passed
- desktop typecheck and focused static checks passed
- focused Tauri Rust policy/directory tests passed
- production-style E2E build succeeded
- targeted Playwright mention scenarios passed:
  - owner-only release hides other-owned relay agent
  - owner-only release shows verified same-owner relay agent
  - OSS build shows shared `anyone` agent
- repository pre-push hook passed on
`4d40b6e5bb032f2c0755127172c50dee213f65a3`:
  - branch skew
  - desktop check and typecheck
  - desktop tests
  - Rust tests
  - Tauri checks
  - mobile tests

---------

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

- **Frozen main:** `ee992ff0822f44d1c308822f116cb9d26f9a3386`
- **Reviewed candidate:** `a6211b0e285600a6f08d6592261e44ecc4a6917b`
- **Previous desktop release:** `desktop-v0.5.15`
- **Proposed immutable tag:** `desktop-v0.5.16`

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>
…er, observer append fast path, poll-tick disk reads (block#6198)

## Summary

Live 0.5.16 review (Royal Court thread) traced the sustained 20–25%
WebContent CPU burn in any large mounted channel with a working agent to
three defects, in descending impact:

1. **Shimmer animation forces per-frame style + compositing walks.**
`.buzz-shimmer` animated `background-position` under
`-webkit-background-clip: text`, which WebKit cannot run on the
compositor: every frame did a full document style resolve plus a
recursive compositing-hierarchy update over the timeline's layer tree.
The highlight now lives on an `aria-hidden` overlay child duplicating
the label text and animates **opacity only** (compositor-accelerated); a
real element is used instead of `::after` generated content so screen
readers never see the duplicate text. Visual: the moving sweep becomes a
gentle pulse. `prefers-reduced-motion` removes the overlay entirely —
static muted label, exactly as before.
2. **Observer journal whole-journal dedup + re-sort per append.** Every
one-per-second observer frame rebuilt a dedup Set over up to 3000
retained events and re-sorted the whole journal with a
`Date.parse`-per-comparison comparator. In-order batches (the ordinary
live path, same condition as the existing incremental transcript fold)
now dedup within the batch and concat; out-of-order/replayed arrivals
keep the full path.
3. **Redundant disk reads in the 5s agent-list poll.**
`build_managed_agent_summary` re-read the global config from disk per
call despite receiving it as a parameter, and re-read the teams store
per tracked pair — 2N redundant reads per poll tick for N agents. Both
are now caller-supplied; one-shot command paths use a
`summarize_from_disk` helper. Same stores, same `unwrap_or_default`
failure posture, read once.

## Evidence (mechanism attribution, live 0.5.16-block, mounted
~1500-event channel)

- True-idle mounted view (working-state UI live): **25.08% mean / 24.15%
median** WebContent CPU; post working-state decay: **6.33% / 3.50%**.
- Reduce Motion A/B during a live agent turn (isolates the shimmer,
working UI still mounted): **19.50% mean → 5.39% mean** (72% collapse).
Native 10s samples: `Document::resolveStyle` 379 samples → 1;
`updateCompositingLayersAfterStyleChange` 378 → 0; recursive
`updateBackingAndHierarchy` 363 → 6.
- The shimmer mechanism predates 0.5.16 (CSS unchanged since block#3151);
current multi-agent workloads exposed and amplified it. The mention
regression itself was fixed separately in block#6182.

## Testing

- `desktop` observer store suites: 55/55 pass, including 5 new tests
pinning the fast-path invariants (equal-timestamp seq ordering,
duplicate-batch redelivery, intra-batch duplicates, overlapping late
arrival takes the slow path, transcript-equals-replay on both paths).
- `cargo test --lib managed_agents` (1016 passed) and `--lib commands`
(727 passed); `cargo clippy` clean; `pnpm typecheck` + biome clean.
- Release gate for the patched build (per Mongo): active-turn mounted
CPU must collapse from the ~21–25% baseline with no per-frame
style/compositing walk in a native sample — measured on Wes's workspace
once a build with this branch is running.

Findings and raw samples:
`RESEARCH/LIVE_0_5_16_WEBKIT_ATTRIBUTION_2026_08_18.md`,
`RESEARCH/RENDERER_STEADY_STATE_LOOP_AUDIT_2026_08_18.md`,
`.scratch/live-app-review/` (Carl/Donut/Mongo/Brain, Royal Court thread
01a7fe75).

---
*Opened by Brain (agent) via @wesbillman's account on his behalf —
coordinated in Buzz channel agent-mention-policy-royal-court, thread
01a7fe75.*

---------

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

- make the differential file-size ratchet a first-class repository gate
- run the same unfiltered gate from pre-push, `just check`, and CI
- remove hidden file-size coupling from Desktop, Web, and Mobile lint
commands
- isolate ratchet Git subprocesses from hook-exported repository state

## Why

The Desktop ratchet grew to govern `desktop/src-tauri/crates/**`, but
the pre-push `desktop-check` command remained path-filtered to non-Tauri
files. That contract drift allowed a Tauri Rust file-size regression
through local validation.

The ratchet already computes its own merge-base diff, so duplicating
governed paths in Lefthook and CI adds drift risk without meaningful
runtime savings. One root gate owns the policy now.

## Testing

- `just file-size-check`
- oversized untracked probe under `desktop/src-tauri/crates/**` fails
with the 1,000-line ceiling
- file-size core tests with hook-style `GIT_DIR` / `GIT_WORK_TREE`
environment
- `lefthook dump` confirms the unfiltered pre-push command
- mandatory pre-push suite passed on
`3217003db10c84d8a0c5f636ec4c92936777a002`
- `git diff --check`

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Summary
- preserve the WebKit-required `Window` receiver when scheduling and
clearing presence subscription retries
- add a receiver-sensitive regression test covering both retry creation
and disposal

## Impact
When subscription opening failed, WebKit rejected the detached timer
call before `retryTimer` could be set. The reconciler's `finally` block
then immediately started another reconciliation because demand was still
unsatisfied and no retry appeared pending. This bypassed the intended
exponential backoff and could repeatedly reopen subscription work during
startup, so the impact was more than console noise.

With the timer receiver fixed, a failed open schedules one bounded retry
at a time (1s exponential backoff, capped at 30s), and disposal cancels
it correctly.

## Validation
- `cd desktop && node --import ./test-loader.mjs
--experimental-strip-types --test
src/features/presence/lib/presenceSubscriptionReconciler.test.mjs` (11
passed)
- `cd desktop && pnpm test` (4,993 passed)
- `cd desktop && pnpm typecheck`
- `cd desktop && pnpm check` (passes with existing unrelated warnings)
- pre-push hook on `578b9a0b5c851b21de0b8746238b04dc69032e78` (desktop
check/typecheck/tests, Rust tests, Tauri checks, mobile tests all
passed)

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
🤖
## Summary
- Bump the locked `h2` version from `0.4.14` to `0.4.16` to clear
RUSTSEC-2026-0258, which affects h2 versions through `0.4.15`.
- Keep the change lockfile-only; no manifest or product-code changes are
included.

## Details
- `h2` is now `0.4.16` with checksum
`a9f37a958b41b3b19ee2707c06439c0e9e547e847223eb791ecb0cb821c65e27`.
- The lockfile was regenerated by `cargo update -p h2 --precise 0.4.16`
using Cargo 1.95.0 / rustc 1.95.0, matching `rust-toolchain.toml` and
the CI pin.
- Cargo's canonical resolver output also rewrites 16 dependency
references to existing `windows-sys` package entries:
- `anstyle-query 1.1.5`, `anstyle-wincon 3.0.11`, `socket2 0.6.3`,
`termina 0.3.3`, and `uds_windows 1.2.1`: `0.61.2` → `0.60.2`.
  - `dirs-sys 0.5.0` and `nu-ansi-term 0.50.3`: `0.59.0` → `0.60.2`.
- `colored 3.1.1`, `errno 0.3.14`, `quinn-udp 0.5.14`, `rustix 0.38.44`,
`rustix 1.1.4`, `rustls-platform-verifier 0.7.0`, `seize 0.5.1`,
`tempfile 3.27.0`, and `winapi-util 0.1.11`: `0.59.0` → `0.52.0`.
- All four referenced `windows-sys` versions (`0.52.0`, `0.59.0`,
`0.60.2`, and `0.61.2`) were already present in the baseline lockfile.
There are zero new package entries or checksums, and the only package
record replacement is `h2 0.4.14` → `0.4.16`. There is no `wasmtime`
edge change.

## Validation
- `cargo-deny check` passed.
- `cargo build --workspace` passed.
- `just test-unit` passed: 455 passed, 0 failed, 1 ignored; all nine
package suites passed.

Signed-off-by: loganj <loganj@squareup.com>
## Summary

- add a targeted native `revalidate_relay_agents(pubkeys, channel_id)`
command for send-time authorization
- scope membership discovery to the destination channel and selected
pubkeys before runtime/profile/policy queries
- replace full relay-directory rebuilds with targeted checks before
agent side effects and again at publication
- preserve managed-agent evidence independently and retain internal
owner-only filtering

## Security and trust boundary

The targeted command reuses the existing authoritative chain:

1. relay-signed kind:39002 membership scoped by viewer and destination
`d` tag
2. agent runtime directory event
3. agent-signed owner profile verification
4. owner-signed managed policy
5. internal-build `owner_only` filtering before policy lookup and on
final results

Relay-only agents are dropped on any targeted directory failure. Fresh
managed-agent evidence remains valid when the unrelated relay directory
fails.

## Validation

- pre-push gate passed: branch skew, Desktop check/typecheck/tests,
Tauri checks, Rust tests, and mobile tests
- Desktop unit suite: 4,992 passed
- targeted Rust relay-directory tests passed
- focused Playwright mention-send acceptance passed
  - relay-only send emits the expected `p` tag
- exactly two targeted `revalidate_relay_agents` calls on the
already-member send path: pre-side-effect and pre-publication
  - no `list_relay_agents` call on that send path
  - relay failure and revocation remain fail-closed
  - internal owner-only mode hides other-owned agents
- manual local Desktop testing by Wes: mention sends felt materially
improved

## Notes

This intentionally does not clear the composer early or add an
optimistic timeline row. Publication still waits for fresh
authorization.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Princess Donut <68157ebd23b3897c1991015c3038658ea916200c67d3a54620b0754d1b92f6e0@buzz.block.builderlab.xyz>
## Why

The `todo` tool description does not say when the tool is unnecessary,
so agents use it for single-turn bookkeeping. Scoping it to cross-turn
persistence reduces avoidable control calls while preserving the
checklist for compaction and genuinely multi-turn work.

## What

- Scope `todo` to work that must continue across turns or survive
context compaction
- Tell agents not to use it for work they can finish in the current turn
- Preserve read/replace semantics and the `_Stop` hook behavior

## Risk Assessment

Low to medium — this changes agent tool-selection guidance, not the tool
name, schema, or implementation. The benchmark covers single-turn
completion but not restart, compaction, or long-lived multi-turn
recovery.

## References

- Companion base-prompt change: block#6186
- Combined benchmark (PR 6186 prompt plus this description): 22/22 pass;
active time 0.3438h → 0.2680h (-22.0%); tool calls 216 → 173 (-19.9%);
todo calls 45 → 0
- Against PR 6186's prompt-only condition: active time 0.2926h → 0.2680h
(-8.4%); tool calls 198 → 173 (-12.6%)
- Results are directional because model and tool behavior is stochastic.

Generated with Codex

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
## Why

The base prompt prescribed a todo lifecycle for every task needing
follow-up tools, which added control calls even for work completed in
one turn. This keeps the important behavioral contract—continue after
publishing pickup—without prescribing the mechanism.

## What

- Replace the 40-word todo lifecycle with a concise pickup
follow-through rule
- Preserve the requirement to publish the outcome or blocker before
stopping

## Risk Assessment

Low to medium — this changes managed-agent instructions, not runtime
code. The terminal benchmark covers single-turn task completion but does
not cover restart, compaction, or long-lived multi-turn recovery.

## References

- Builds on the prompt simplification in
block#6161
- Benchmark setup: GPT 5.6 Terra at high effort; the same 11-task
Terminal-Bench 2.1 slate; four concurrent trials; 4 CPU and 8 GiB per
trial; 3× timeout

| Prompt | Pass | Active-h | Median active | Tool calls |
|---|---:|---:|---:|---:|
| PR 6161 baseline | 22/22 | 0.3438 | 0.91 min | 216 |
| Benchmarked 14-word rule | 22/22 | 0.2926 | 0.75 min | 198 |

The benchmarked rule used 14.9% less active time, 16.9% lower median
active time, and 8.3% fewer tool calls. Across the screen and
confirmation runs it passed 33/33 trials with every completion report
present; results are directional because model and tool behavior is
stochastic.

---
**Update Aug 18, 10:47 EDT:** Expanded the completion outcomes following
review feedback.
- The follow-through rule now covers a verified result, blocker, or key
decision or information that needs to be surfaced.
- The benchmark was not rerun; the table reflects the prior 14-word
formulation. This is a completion-taxonomy clarification, not a return
to a prescribed todo mechanism.

Generated with Codex

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
## Buzz Desktop release v0.5.17

- **Frozen main:** `3fdf289b78c40f80abce86575c25b5ed6361d82c`
- **Reviewed candidate:** `c3bfd66947978fae93f4cfb46bea98ba20e32ccf`
- **Previous desktop release:** `desktop-v0.5.16`
- **Proposed immutable tag:** `desktop-v0.5.17`

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
- add sticky date headers that crossfade with in-timeline dates
- replace the Latest pill with a centered down-arrow control
- animate the control out from its bottom-center anchor
<img width="630" height="1368"
alt="2AF1BB3F-B295-4084-8A65-90AED60B58B5"
src="https://github.com/user-attachments/assets/505de450-66f5-4628-8c59-34b5511ffb5c"
/>

## Validation
- `bin/just mobile-check`
- `bin/just mobile-test` (1,359 tests)
- Android debug and signed iOS release builds

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
…lock#6000)

**Category:** fix
**User Impact:** The desktop content surface now keeps balanced chrome
spacing when the sidebar is collapsed or multiple communities are
visible, with a cleaner sidebar transition.

**Problem:** Collapsing the left sidebar removed the content surface's
left inset, while multi-community layouts also exposed uneven visible
gutters and sidebar content during the exit transition. **Solution:**
Preserve an 8px visible gutter around the content surface, clip and fade
only the sidebar's inner content while it trails off canvas, and leave
the opaque shell in place until the existing visibility transition
completes.

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

**desktop/src/app/BuzzThemeSurfaces.tsx**
Preserves a balanced visible left gutter after accounting for the
content-edge shadow.

**desktop/src/shared/ui/sidebar.tsx**
Clips the collapsing sidebar, disables interaction off canvas, and
fades/translates its inner content without exposing a different
background.

**desktop/tests/e2e/community-rail.spec.ts**
Adds gutter assertions for single-community collapsed-sidebar and
multi-community layouts.

**desktop/tests/e2e/sidebar.spec.ts**
Covers the collapse opacity, translation, clipping, pointer-event
behavior, and restoration on reopen.

</details>

## Reproduction steps

1. Launch the desktop app with the Buzz theme and at least two
communities.
2. Collapse the channel sidebar.
3. Confirm the main content surface keeps equal visible left and right
gutters and does not overlap the community rail.
4. Reopen and collapse the sidebar again; confirm its contents fade and
trail right while the chrome background stays opaque, then restore fully
when reopened.

## Screenshots

| Single community | Multiple communities |
| --- | --- |
| ![Sidebar collapse with a single
community](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6000/multiple-communities-collapse.gif)
| ![Sidebar collapse with multiple
communities](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6000/single-community-collapse.gif)
|

## Validation

- `pnpm build:e2e`
- focused sidebar integration tests: 2 passed
- collapsed multi-community smoke regression: 1 passed
- Biome check on all four changed files
- pre-push desktop check, typecheck, and test hooks
- `git diff --check`

Related issue/PR: none found.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
**Category:** fix
**User Impact:** Hover tooltips and informational popovers now wait for
deliberate pointer dwell instead of appearing while users move around
the app.

**Problem:** Tooltips and hover-controlled popovers appeared after
inconsistent, often very short delays, so moving across composer and
navigation controls could obstruct the next interaction.

**Solution:** Establish a 500 ms shared dwell default with no tooltip
skip-delay cascade, apply it to informational hover-controlled Popovers,
and preserve immediate click and keyboard behavior. The responsive
Community actions navigation submenu retains its documented 80 ms open /
160 ms close timing.

## Before / after

| Before | After |
|---|---|
| ![Before: hover disclosures appear during pointer
transit](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5821/before-hover-disclosure.gif)
| ![After: hover disclosures wait for deliberate pointer
dwell](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5821/after-hover-disclosure.gif)
|

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

**desktop/src/shared/ui/tooltip.tsx**
Wraps the Radix provider with documented 500 ms and zero skip-delay
defaults. The provider API still permits a future proven exception, but
no current `desktop/src` caller overrides either timing.

**desktop/src/shared/ui/popover.tsx**
Exports the documented shared hover-open timing for controlled popovers;
ordinary click/focus Popovers remain immediate.

**desktop/src/main.tsx**
Uses the shared Tooltip provider defaults at the application root.

**desktop/src/shared/ui/sidebar.tsx**
Removes the sidebar's instant Tooltip timing override.

**desktop/src/features/channels/ui/AddChannelBotTeamsSection.tsx**
Removes the local short Tooltip timing override.

**desktop/src/features/home/ui/InboxDetailPane.tsx**
Removes the local short Tooltip timing override.

**desktop/src/features/messages/ui/MessageTimeline.tsx**
Removes the timeline's local short Tooltip timing override.

**desktop/src/features/messages/ui/MessageTimestamp.tsx**
Drops the timestamp-only provider now that its 500 ms, zero-skip
behavior is shared globally.

**desktop/src/features/channels/ui/BotActivityBar.tsx**
Raises composer agent-activity hover dwell from 150 ms to the shared
default while preserving immediate click/focus opening.

**desktop/src/features/sidebar/ui/ChannelActivityPopover.tsx**
Raises channel activity hover dwell from 250 ms to the shared default
while preserving immediate focus opening.

**desktop/src/features/profile/ui/UserProfilePopover.tsx**
Reuses the shared hover timing in place of its equivalent local
constant.

**desktop/src/shared/ui/PubKey.tsx**
Reuses the shared hover timing in place of its equivalent local
constant.

**desktop/src/shared/ui/markdown/InlineEmojiPopover.tsx**
Raises emoji inspection hover dwell from 200 ms to the shared default
while preserving immediate focus opening.

**desktop/src/features/messages/ui/MessageReactions.tsx**
Raises reaction inspection hover dwell from 200 ms to the shared default
while preserving immediate focus and reaction clicks.

**desktop/src/features/communities/ui/CommunitySwitcher.tsx**
Documents the Community actions navigation submenu as an intentional
timing exception: 80 ms to open responsively and 160 ms to preserve the
pointer bridge into its portalled panel.

</details>

## Reproduction steps

Move the pointer rapidly across each surface first, then hold it still
over a labeled control. Hover-only disclosures should stay closed during
transit and open after about **500 ms** of deliberate dwell. Moving
directly between adjacent Tooltip triggers should start a fresh 500 ms
dwell rather than cascading the next Tooltip open immediately.

| Surface | Where to test | What to expect |
|---|---|---|
| Composer controls | Attachment, emoji, image editor, formatting, and
composer toolbar buttons | No Tooltip while sweeping across controls;
the hovered control's Tooltip opens after ~500 ms. Clicking remains
immediate. |
| Message actions | Hover a message, then test reply, react,
more-actions, edit, and related action-bar controls | Each Tooltip waits
~500 ms, including when moving between adjacent actions. The action
itself still runs immediately on click or keyboard activation. |
| Message metadata and content tools | Message timestamps, code-block
copy controls, diff controls, system-message controls, and video-player
controls | Tooltip appears after ~500 ms. Timestamp behavior should look
unchanged; it was already 500 ms with no skip cascade. |
| Reaction pills | Hover a reaction with one or more reactors; also
click the pill | Reactor Popover waits ~500 ms instead of 200 ms.
Clicking still toggles the reaction immediately. |
| Inline custom emoji | Hover a rendered custom emoji in message
Markdown, then focus it with the keyboard | Emoji inspector waits ~500
ms on hover instead of 200 ms. Keyboard focus opens it immediately. |
| Masked links | Hover a masked Markdown link, including one revealed
inside a spoiler | Destination Tooltip waits ~500 ms instead of the
former app-level 300 ms. Hidden spoilers still reveal no destination;
keyboard focus remains immediate. |
| Main and collapsed sidebar controls | Collapse the sidebar and hover
icon-only navigation/menu buttons; also test community-rail controls |
Sidebar Tooltips wait ~500 ms instead of opening instantly. Rapid
movement across icons should not produce a tooltip cascade. |
| Channel header and management controls | Channel members, huddle,
settings, thread-view mode, management rows, and quick-agent controls |
Each Tooltip waits ~500 ms; click/keyboard behavior remains immediate. |
| Add-channel team chips | Open Add channel where saved teams are
available and hover a team chip | Team details Tooltip waits ~500 ms
instead of 150 ms; clicking the chip still toggles the team immediately.
|
| Channel activity preview | Hover a sidebar channel that has activity,
then keyboard-focus its trigger | Activity Popover waits ~500 ms instead
of 250 ms. Focus opens it immediately, and quickly crossing channel rows
should not leave previews in the way. |
| Composer agent activity | Run an agent so the composer activity
control is present; hover, click, and focus it | Hover Popover waits
~500 ms instead of 150 ms. Click and keyboard focus still open it
immediately. |
| Inbox and draft controls | Home inbox open-context/more-actions
controls, inbox-list controls, draft detail, and drafts panel | Tooltips
wait ~500 ms instead of the inbox detail's former 200 ms/local defaults.
Clicks remain immediate. |
| Profile and public-key previews | Hover avatars, names/mentions,
project author identities, and displayed public keys | Profile/pubkey
Popovers open after ~500 ms, matching their prior behavior; the change
centralizes that timing. Click actions and focus behavior remain
immediate. |
| Agent, team, memory, and update controls | Managed-agent rows, team
identity cards, restart-diff badges, memory actions, setup steps, and
update indicator | Tooltips wait ~500 ms and do not cascade when
traversing adjacent controls. Actions remain immediate. |
| Huddle controls | Huddle bar/indicator, mic controls, and
participant-list actions | Tooltips wait ~500 ms; mute, join,
participant, and keyboard actions remain immediate. |
| Projects and activity surfaces | Project/repository cards, overview
rail, contribution graph, activity feed, reviewers, and Pulse note
controls | Tooltips wait ~500 ms with a fresh dwell between adjacent
targets. Clicking/focusing interactive controls remains immediate. |
| Intentional interaction-mode exceptions | Focus a Tooltip trigger;
click/focus an ordinary Popover; move the pointer from an open hover
Popover into its panel | Tooltip focus and Popover click/focus open
immediately because they are explicit user intent, not incidental hover.
Hover Popovers retain their 180–200 ms close grace so the pointer can
cross into the panel. The Community actions navigation submenu is the
intentional exception: it opens after 80 ms and keeps its 160 ms pointer
bridge; informational hover Popovers use the shared 500 ms delay. |

## Verification

- `pnpm typecheck`
- `pnpm check` (passes with three existing diagnostics outside this
diff)
- `pnpm test` — 4,775 passed
- `pnpm build`

---------

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

- run only Dart formatting for mobile changes during pre-commit
- move Flutter static analysis to the path-scoped pre-push graph
- fix case-sensitive `Justfile` triggers for the Rust and Tauri pre-push
gates
- skip whole-tree desktop/web formatting for lockfile-only commits while
retaining every lockfile-triggered pre-push check

No test suite was removed or narrowed. Cargo formatting remains
workspace-scoped; frontend and mobile source changes still select their
existing formatters.

## Benchmark

Warm isolated timings on an M2 Max:

- `dart format .`: 1.92–2.23s
- `flutter analyze`: 5.58–7.51s
- old forced full pre-commit: 10.16–18.40s
- current forced full pre-commit at `f18d9b5`: 5.42–7.09s, median 6.29s
(45% lower)
- current forced full pre-push at `f18d9b5`: 2m45s, effectively
unchanged from the prior warm 2m41s run
- lockfile-only pre-commit after the follow-up: 0.18–0.19s across three
runs

The new mobile analysis lane finishes before the existing mobile test
lane, so it added no observed full-push wall time.

## Validation

At pushed head `79626be60d89ba34e0fe136f254cc38cf1f1c2b8`:

- `lefthook validate`
- isolated temporary-repository selection test:
  - `Justfile` selects both Rust and Tauri pre-push gates
  - `pnpm-lock.yaml` selects neither mutating frontend formatter
  - desktop/web source files still select their formatter
- lockfile-only pre-commit: 0.19s, 0.19s, 0.18s
- pre-push hook passed on the exact pushed head

Earlier full-cycle validation at
`f18d9b5802f11543e8afbef3cd54c5928817e32b`:

- forced full pre-commit: 5.42s, 6.29s, 7.09s
- forced full pre-push: 2m45s; all lanes passed
- mobile tests: 1,465 passed

---------

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

Closes five Claude Code agent-config gaps in Buzz Desktop, split into
three commits that share the spawn-time and live-switch surfaces.

## Config isolation, model authority, and Auto mode (block#2692, block#2884,
block#3493)

- **`CLAUDE_CONFIG_DIR` isolation (block#3493).** `config_bridge` resolves
both `settings.json` and `.claude.json` panel paths from the agent's
effective env (`resolve_effective_agent_env` — baked floor → definition
→ global → persona → record), so the panel reads the same directory the
agent runs against. `mcp_config_file_path_for_runtime` honors a custom
dir; empty/blank is treated as unset, matching Claude's
`CLAUDE_CONFIG_DIR || homedir()` semantics. `AgentConfigPanel` shows a
Keychain caveat when a custom dir is active (a custom dir maps to a
fresh Keychain namespace unless `CLAUDE_SECURESTORAGE_CONFIG_DIR` is
also set).
- **Single startup model authority (block#2692).** `ANTHROPIC_MODEL` is the
sole startup model authority for Claude. Local spawns write
`ANTHROPIC_MODEL` and strip `BUZZ_ACP_MODEL` so the harness never sees
two authorities; remote deploys send `ANTHROPIC_MODEL` in `policy_env`
instead of `BUZZ_ACP_MODEL`. Non-Claude runtimes are unchanged.
- **`PermissionMode::Auto` (block#2884).** Wire string `"auto"`, model-gated,
degrades to the agent default when the active model doesn't advertise
it.

## Thinking effort end-to-end for local Claude agents

Effort flows from the running session's discovered `thought_level`
config option through the config surface to a local-only write control
and a read-only two-facts display.

- **Reader.** Discovers the `thought_level` config option from the
session cache (never hardcoded) and populates `effort_config_id` /
`effort_options` on `RuntimeConfigSurface`. The canonical effort tier
orders record env > `record.effort_level` (`BuzzExplicit`) > ACP >
persona > global > definition > file, so the panel shows the effort the
next spawn will launch with while `resolve_with_override` surfaces the
running ACP value as the struck-through override — neither masks the
other silently.
- **Write control.** `persist_agent_effort_level` is a direct-write
Tauri setter (writes `record.effort_level` + `updated_at`,
`save_managed_agents`) that rejects non-local backends — remote effort
is set at deploy time via `policy_env`. `EffortPickerField` mounts in
`AgentInstanceEditDialog` beside the Model block, gated on a local
backend **AND** a discovered `effortConfigId`. It persists directly and
invalidates the config surface, mirroring the
`setManagedAgentAutoRestart` standalone-setter precedent, so the frozen
`UpdateManagedAgentInput` shape stays frozen.
- **Display.** The read-only configured-vs-current two-facts display is
the `thinkingEffort` normalized field in `AgentConfigPanel`, fed by the
reader's canonical tier ordering.
- `buzz-acp` applies the startup effort env at session start.

## Distinguish a rejected model switch from silent success

A live model switch collapsed adapter rejection into success:
`apply_model_switch` returned `Ok` on both an accepted switch and an
application-level refusal, so the caller cached pre-switch capabilities
as if they described the target model and Desktop reported the pick as
landed.

- `ModelSwitchOutcome::{Applied(Value),Rejected}`. Transport-class
errors still propagate as `Err` (respawn the poisoned stdio); an
application-level refusal is now `Rejected`.
- The caller drives everything off `post_switch_snapshot`: `Applied`
refreshes `model_capabilities` from the target model's echoed
`configOptions` (or drops to `None` when none are echoed, so a
pre-switch snapshot is never mistaken for the target's); `Rejected`
preserves pre-switch caps and emits a `failure` `control_result`. Effort
resolution and the session-config capture read the post-switch snapshot
so they converge on the model the session actually runs;
`modelOverridden` is gated on `switch_succeeded`.
- `liveSwitchOutcome.ts` gains a distinct `"failed"` outcome for the
adapter `failure` frame and treats the busy-path `"sent"` ack as
provisional — it never counts toward success. Success is confirmed only
by a real positive terminal frame (the busy-path deferred apply emits a
correlated `switched` `control_result` when it lands), and the fallback
timeout resolves an honest `"pending"` (accepted, apply deferred), never
a false `"ok"`. `ModelPicker` surfaces a distinct toast per outcome —
failed, unsupported, and pending.

## Scope explicitly excluded

Per-agent config dir provisioning, `CLAUDE_SECURESTORAGE_CONFIG_DIR`
sentinel injection, `settings.json` projection, protected-key stripping,
MCP inheritance, spawn serialization, and the `last_spawn_warnings`
surface are absent from this diff. Silent-fallback machinery for
non-Claude runtimes (block#2265/block#4004) is a tracked follow-up.

## Sanctioned follow-ups

- **Live mid-conversation effort switching.** Effort is spawn-scoped
only in this PR: the worker reads `BUZZ_ACP_EFFORT_LEVEL` once and
applies it at session creation. The live effort-switch machinery
(mid-conversation effort RPC + ack frame) was deliberately removed and
is archived on `archive/claude-config-gaps-live-effort` for a future
plan-gated revival.
- **Idle-path late model-switch rejection is unobservable.** An idle
switch acks `switched` immediately after catalog validation, but the
real `set_config_option` runs at the next session creation — potentially
much later — so a rejection there is not surfaced back to the picker
(holding a subscription that long is not sensible). Pre-existing,
catalog-gated behavior; a durable fix is a tracked follow-up.

Closes block#2692, block#2884, block#3493

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Adds `scripts/buzz-adopt-prod-agents.sh`, which copies the installed-DMG
(production) agent records and owner identity into the dev app-data
store so a dev build boots as the **same agents and same owner npub** as
the installed app — surviving `just reset`.

## What it restores

`just reset` wipes the two things Buzz cannot re-derive:

- The agent **records** — `managed-agents.json`, `personas.json`,
`teams.json`, and `agents/teams/`.
- The **owner identity** nsec, written as `identity.key` (0600, atomic).

Agent private keys and identity-key adoption then happen automatically
on the next dev boot via the shipped in-app migrations.

## When to run it

1. Quit any running **dev** build. A running installed DMG is fine — the
script treats it as a read-only source and only warns against
creating/archiving agents or editing teams while it runs. It never kills
processes.
2. Run `just reset`.
3. Before the first dev boot, run `bash
scripts/buzz-adopt-prod-agents.sh`.
   - `--dry-run` prints every action with zero writes.
   - `--force` overwrites an already-populated dev store.
   - One keychain prompt is expected (reading the prod owner identity).
4. Start the dev build. First boot copies the agent keys prod→dev and
adopts `identity.key`, then deletes it. For worktree launches, export
`BUZZ_SHARE_IDENTITY=1` (and `BUZZ_PRIVATE_KEY`) or the worktree mints
its own duplicate agents.

## Running-process check

Only a running **dev** build blocks the run: the script atomically swaps
the entire dev `agents/` directory and stages `identity.key` for the
next dev boot to consume, so doing that under a live dev process is the
corruption the check must prevent. A running installed DMG is allowed —
the app writes `managed-agents.json` via tmp+atomic-rename, so it can
never hand the script a torn file. Each matching `buzz-desktop` PID is
classified by its true post-exec executable path (via `lsof -d txt`,
which reports the running vnode with symlinks already resolved); the
installed `/Applications/Buzz.app/Contents/MacOS/buzz-desktop` path is
the only allow. Anything else still alive blocks fail-closed; a PID that
exits before its path can be resolved is ignored.

## Requirements

Requires `python3` when a dev `agents/` already exists — it drives the
`renamex_np(RENAME_SWAP)` syscall bash cannot call. The script fails in
preflight with a clear message if `python3` is absent, never mid-commit.

## Safety

Prod is strictly read-only; symlinked or aliased prod/dev roots are
refused before any write. The records/identity restore commits in one
atomic operation (`renamex_np(RENAME_SWAP)` when a dev store exists, a
single `mv` when absent) that fails closed — a crash or I/O error leaves
`agents/` either fully old or fully new, and unrelated live dev state
(`logs/`, `agent-pids/`, `global-agent-config.json`, retention stores)
is preserved verbatim across the swap.

This is a temporary measure while we trial it and decide whether to port
the logic into the dev-build startup path.

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

- align the mobile security-code confirmation screen with the Add
Community visual system while keeping the SAS verification flow
unchanged
- use “Confirm desktop code” and a 15% primary-color outline on each
digit box
- support a gitignored, debug-only Android app name and application ID
override for side-by-side device testing; release and profile identities
remain unchanged

## Validation

- `flutter test test/features/pairing/pairing_page_test.dart` (21 tests)
- full mobile pre-push suite (1,469 tests)
- focused Flutter analysis and formatting
- mobile worktree identity contract checks

---------

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

- Add a lifted-message long-press popover with the reaction tray and
grouped actions.
- Use a native action surface
- Preserve taps and scrolling, exclude attached reactions from the
lifted preview, and align the composition to the bottom safe area.
<img width="1080" height="2424" alt="Screenshot_20260814-160732"
src="https://github.com/user-attachments/assets/a9d5fc91-4ee3-427c-8199-1c3be2b4e98a"
/>

## Testing

- `bin/just mobile-check`
- `flutter test test/features/channels/message_actions_test.dart`
- `flutter test` (1,364 tests)
- Manual interaction review on iPhone and Pixel 10

---------

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>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Summary

- buffer native WebSocket frames until `plugin:websocket|connect`
returns the socket ID and the NIP-42 auth waiter is installed
- drain those frames in order before normal inbound handling begins
- add a deterministic E2E seam and regression for an AUTH challenge
delivered before connect resolves

## Root cause

The native WebSocket task starts forwarding relay frames before the
connect command returns its socket ID. Buzz relay sends the NIP-42 AUTH
challenge immediately on connection, so Desktop could process that
challenge while `wsId` and `authRequest` were still unset. The challenge
was discarded, the cold connection failed authentication, and the first
plain-text send failed. Channel history still appeared because it loads
through the Tauri channel-window command rather than this frontend
WebSocket.

## Verification

- deterministic regression is red before the fix (`connecting` after 5s)
and green after it
- `pnpm test`: 3,721 passed
- `pnpm build:e2e`: passed
- focused early-AUTH E2E: passed
- existing failed-initial-dial E2E: passed
- `pnpm typecheck`: passed
- Biome + file-size pre-commit checks: passed

The complete 7-test relay-reconnect file passed once before the final
line-count-only compaction. After that compaction, two full-file reruns
each had the same unrelated startup-seam flake in the existing
initial-dial test; both the new test and that existing test pass
independently at the committed HEAD.

---------

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

Sharing an agent encodes a `.agent.png` snapshot with the avatar as the
PNG image body. Non-PNG avatars were transcoded at full resolution and
already-PNG avatars were carried through unchanged, so a large source
avatar could produce a PNG that exceeds the 10 MiB
`MAX_SNAPSHOT_PNG_BYTES` cap and fail the send. A 2764×4096 webp avatar
encoded to ~26 MB — over 2.5× the ceiling — which is why sharing that
agent hung for a few seconds (fetching and transcoding the image) and
then failed.

The share dialog compounded the problem: on failure it discarded the
pipeline's real error (`Snapshot exceeds the 10 MiB size limit…`) and
toasted a generic `Couldn't send … Try again.`, hiding the actual cause.

## Changes

- **Downscale the avatar for the snapshot PNG body.** The body is only a
card thumbnail, so raster avatars are now downscaled to a 512px longest
edge (`MAX_PNG_BODY_EDGE`) before PNG re-encoding, mirroring the
frontend SVG rasterizer's 512×512 cap in `snapshotAvatarPng.ts`.
Already-PNG avatars over the dimension cap route through the same
downscaling transcode path instead of a straight tEXt-chunk injection.
The manifest's `avatar_url`/`avatar_data_url` source reference is
untouched — only the PNG image body is downscaled.
- **Surface the real error in the share dialog.** `PersonaShareDialog`
(used by `SnapshotShareDialog`/`TeamShareDialog`) and
`AgentCardViewerDialog` now toast the send controller's actual error
message, falling back to the generic string only when it is empty. A
`getCurrentError()` accessor reads the error through a ref because the
render-captured `state.error` is stale in the closure immediately after
`beginSend` resolves.

A unit test pins the invariant: an oversize (2764×4096) avatar produces
a snapshot that stays far under the cap with body dimensions clamped to
512px.

---------

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>
Fixes block#6015.

`ConfigWriteMechanism` is internally tagged and carried only `rename_all
= "camelCase"`. On an internally tagged enum that renames the
**variants**, never the variants' fields, so the payload went out
snake_case:

```
{"type":"respawnWithEnvVar","env_key":"K"}
{"type":"acpSetConfigOption","config_id":"c"}
{"type":"acpSetSessionModel"}
{"type":"gooseNativeConfigWrite","config_key":"g"}
{"type":"readOnly"}
```

against `envKey` / `configId` / `configKey` in
`desktop/src/shared/api/types.ts:615-620`. That output is a probe run of
the real module before the fix, not a reading of the code.

What makes it read as correct is the asymmetry: the variant *names*
rename fine, so the `type` discriminant and every `switch
(writeVia.type)` behave; and the enclosing `NormalizedField`'s own
fields (`writeVia`, `overriddenValue`, `isRequired`) rename fine too,
because `rename_all` does apply to struct fields. Only the variant's
field is wrong.

Adding `rename_all_fields = "camelCase"` fixes it. `rename_all_fields`
appeared zero times in `desktop/src-tauri` before this.

**Severity, stated plainly: latent, not currently user-visible.**
Nothing in `desktop/src/**` reads `.envKey`/`.configId`/`.configKey` off
a `writeVia` — `AgentConfigPanel.tsx` is the only `RuntimeConfigSurface`
consumer and never touches the field, and no Rust code deserializes the
type either. The write-back path these fields exist for is not wired
yet. The hazard is for whoever wires it: `invokeTauri<T>` is an
unchecked cast, so they get `undefined` with a green `tsc`.

I also carried the attribute onto `ConfigFieldType`. Its only payload
field is `options`, single-word, so that half is not a fix — it is the
attribute the next multi-word field would silently need.

One divergence from the issue's suggested step 3: the 20 `e2eBridge.ts`
sites already emit camelCase, and camelCase is the contract, so they are
correct as written — changing them would have been wrong. What they
lacked was provenance, since agreeing with `api/types.ts` while the
backend emitted something else is exactly what let this sit. They now
name the Rust test that pins the bytes.

**Tests** (`wire_format_tests`, 4 cases, whole-value not key-set — a
key-set assertion still passes when a variant name regresses):
- every variant against the TypeScript spelling;
- the nested `NormalizedField`, which is the shape the renderer actually
receives;
- a camelCase round-trip **plus** an assertion that the old `env_key`
spelling is now rejected, so a revert cannot quietly keep deserializing;
- `ConfigFieldType::Enum`.

Removing `rename_all_fields` again turns three of the four red.

Verified locally: full Tauri library suite 2444 passed / 15 ignored / 0
failed; `cargo clippy --manifest-path desktop/src-tauri/Cargo.toml
--workspace --all-targets -- -D warnings` clean; `cargo fmt
--manifest-path desktop/src-tauri/Cargo.toml --all -- --check`; in
`desktop/`: `pnpm typecheck`, `pnpm check`, `pnpm test` 4954 passed;
`git diff --check`. Not run: the app itself — there is no UI path to
this field yet, which is the same reason the bug is latent.

---------

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

- Add separate **Font size** and **Conversation density** controls in
Appearance.
- Use a 13 / 14 / 15px conversation text ramp for Smaller / Default /
Larger while scaling interface typography through a shared virtual-rem
system.
- Keep layout geometry fixed while typography changes, and compose Cmd
+/- text zoom on top of the selected preference.
- Let Compact, Comfy, and Spacious control conversation row spacing plus
paragraph and list rhythm in Markdown content across Buzz.
- Preview font size and density together with click-and-drag comparison;
interrupted scrubbing restores the saved preference.
- Synchronize preferences across open Buzz windows, including full
local-storage clears during sign-out or dev reset.
- Promote the segmented control to shared UI and make Settings rows
adapt to their card width.

## Validation

- `just ci`
- 4,965+ desktop unit tests across the final merged branch
- 1,465 mobile tests through the full repository gate
- Focused Playwright coverage for font-size/density independence, drag
preview cancellation, Inbox geometry, thread rendering, keyboard zoom,
and cross-surface typography
- Fresh E2E build against the final merged branch

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: morgmart <98432065+morgmart@users.noreply.github.com>
…lock#5905)

The managed "Active Agents" table in `~/.buzz/AGENTS.md` was rendered
from every managed-agent record with no filtering, so archived duplicate
instances kept appearing under the active relay's header. This scopes
the roster to identity-active agents and makes regeneration safe under
concurrency.

## Roster filter: identity-archive only

`render_dynamic_section` now drops only records whose pubkey is present
in the relay's `kind:13535` archive snapshot. Local records can't tell
they're archived — they all carry `is_active: true` (that flag is a
*definition*-archive, not an identity-archive), so archive truth lives
only relay-side. The read **fails open**: an unreachable relay yields an
empty set and hides no one.

There is deliberately **no** relay-scope filter. `relay_url` is a legacy
creation-era field that `effective_agent_relay_url()` ignores — every
agent is eligible on every community, and snapshot-imported records
store `relay_url: ""` by design. Filtering on it would hide valid,
runnable agents after a workspace switch or import. Foreign-relay relic
records leave the table via record deletion, not code.

## Regeneration on archive / unarchive

`archive_identity` and `unarchive_identity` submitted the relay event
and returned without refreshing `AGENTS.md`, unlike the ~20 other
mutation sites that call `try_regenerate_nest`. A just-archived agent
therefore lingered on the roster until an unrelated edit or the next
launch. Both commands now trigger a regeneration after a successful
submit.

Regeneration is bound through a `NestRegenTrigger` trait rather than
constructing the `try_regenerate_nest` callback at the Tauri-command
delegation site. The command cores take `regen: &impl NestRegenTrigger`
and own the `|| regen.trigger()` binding; the thin wrappers only pass
`&app` (whose `impl` calls `try_regenerate_nest`). This puts the regen
wiring inside the unit-tested core — a `CountingRegen` double proves
each core fires exactly one regeneration — instead of an untestable seam
where a wrapper could silently lose the refresh while the suite stayed
green. The regen races the relay's `kind:13535` snapshot update, so it's
best-effort and fail-open — a stale render self-heals on the next cycle.

## Ordered regeneration

`try_regenerate_nest` previously spawned unconstrained tasks that each
snapshotted state, awaited two relay requests, then wrote — so a slow
pre-edit generation could overwrite a newer one. Boot made this
deterministic: the boot regen races the `apply_workspace` regen, and the
fallback-relay render could finish last.

`NestRegenGate` fixes this with a single `highest_requested` watermark.
A monotonic generation is claimed *synchronously* at request time
(encoding call order) and advances the watermark under one lock; the
spawned task carries its generation and, at commit, reads the watermark
under that same lock — the compare-and-write is atomic with no await
held across it. A generation whose number is below the current watermark
drops its result instead of rolling the file back.

Gating on highest-*requested* rather than highest-*written* is the
load-bearing choice: if a newer generation is requested but then fails
its relay reads, an older in-flight generation must **not** publish its
now-obsolete roster. Behavior delta: once a newer regeneration has been
requested, no older generation will ever write; if that newer generation
fails, the file is left as-is and self-heals on the next trigger rather
than regressing to a stale snapshot. This is an ordered,
latest-request-wins gate — not a work coalescer: superseded generations
still perform their relay reads and drop the result at commit time.

The gate's commit lock is acquired with a poison-to-`io::Error` mapping
rather than `expect()`, so a poisoned lock degrades to the same
warn-and-continue path as any other commit failure instead of panicking
the desktop process (a best-effort housekeeping write must never take
down the app).

## One relay target per regeneration

A regeneration read the workspace relay override three times — the
NIP-11 signer in `fetch_relay_self`, the snapshot query in
`query_relay`, and the rendered footer — so a workspace switch
mid-flight could pair one relay's advertised signer with another relay's
snapshot, fail open, and render archived agents as active.
`capture_relay_target` now resolves the effective relay (WebSocket +
HTTP API base) once, before any network work, and
`fetch_archived_pubkeys_at` threads that single target through both the
NIP-11 fetch and the `/query`; the footer renders the same target.
Signer, snapshot, and footer always belong to one relay.

## Monotonic archival snapshot publishing

`publish_nipia_archival_list` stamped its `kind:13535` snapshot with a
whole-second `created_at`. A rapid archive→unarchive within the same
second produced two events whose NIP-16 replaceable-event tie-break
(higher event id wins) could strand the older, stale archive state as
canonical. Publishing now uses a bounded retry that re-reads current
archive state and rebuilds the snapshot each attempt, so the published
list reflects the latest intended state rather than a fixed same-second
race loser. After the attempt budget (8) is exhausted the publisher
`bail!`s; the sole caller treats that as a `warn!` side effect and
continues, matching the surrounding best-effort submit path.

## Test-file split

The renderer, `upsert_managed_section`, and the regeneration-gate tests
moved to `nest/render_tests.rs` so each test file stays under the
repository's 1000-line ratchet.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Mongo <5c25403eab7271f9f94ddd4f2b270e8cac2c92e2c830c51877cca6ec974ffb3f@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Summary

- use a native detented emoji sheet on iOS that opens at two-thirds and
keeps scrolling active at every height
- align the search, close control, background, and full-width category
row while retaining the Flutter tray on Android
- add a persisted desktop-style skin-tone selector and show one selected
variant per emoji

## Testing

- `just ci` gates completed, with the disk-heavy stages resumed
individually after generated artifacts filled the worktree volume
- full mobile suite: 1,360 tests passed
- focused picker/composer/reaction suite: 145 tests passed
- signed iOS Release build succeeded
- Android debug build installed and launched on Pixel 10

Snapshots are attached in a PR comment.

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Watcher <bb7abfd757d0af7b66569d02ab9c0316b616f9d0c151ecf5b964344c462e7f8f@buzz.block.builderlab.xyz>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Watcher <bb7abfd757d0af7b66569d02ab9c0316b616f9d0c151ecf5b964344c462e7f8f@buzz.block.builderlab.xyz>
Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
The shared-agent directory rebuild resolves runtime directories, owner
profiles, and managed policies for every candidate agent via dozens of
exact-author query batches. Issuing those batches serially made a
~100-agent rebuild take 6.5–8.4s, which dominated @mention autocomplete
latency.

block#6224 already scoped the *send-path* revalidation
(`revalidate_relay_agents`) to just the mentioned pubkeys, so the send
stall is fixed. But the autocomplete directory (`list_relay_agents`)
still rebuilds the full membership set serially — this PR removes that
remaining cost.

## Change

Run the query batches with bounded concurrency via a shared
`query_filter_batches` helper. Each directory rebuild constructs one
`tokio::sync::Semaphore` (8 permits) and shares it across all of that
rebuild's phases, so the runtime-directory and owner-profile phases that
run concurrently under one `try_join!` never exceed 8 `/query` requests
in flight together — the bound is per-rebuild. The policy phase reuses
the same budget. Same events, keyed by pubkey downstream so ordering is
irrelevant.

Both `list_relay_agents` (autocomplete) and `revalidate_relay_agents`
(scoped send-path check from block#6224) funnel through
`list_relay_agents_for_selection`, so the helper is a no-op for the tiny
1–3-mention revalidation set and only the full autocomplete rebuild sees
the win — the scoped send path is untouched.

## Measurement

Live on the production relay, warm connection, full `list_relay_agents`:

| | Full-directory rebuild |
|---|---|
| Serial (before) | 6.5–8.4s |
| Bounded-concurrency (after) | 2.0–3.4s |

Returned pubkey set is byte-identical pre/post. The 8-permit ceiling
holds under saturation (24 batched requests → peak exactly 8, zero
failures, zero requests left in flight).

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

- Open Activity items directly in their target thread while preserving
Activity as the Back destination.
- Fade in a muted target-message highlight after navigation settles,
hold it for three seconds, then fade it away.

## Why

Activity deep links hydrated the channel before opening the thread,
which left the temporary channel route in the navigation stack. The
target highlight also appeared before the route settled and remained
indefinitely.

## Validation

- `just mobile-check`
- Full Flutter test suite (1,357 tests)
- Signed Profile build installed and launched on a physical iPhone

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@buzz.block.builderlab.xyz>
Signed-off-by: adrienlacombe <6303520+adrienlacombe@users.noreply.github.com>

# Conflicts:
#	Cargo.lock
#	desktop/src-tauri/tauri.conf.json
#	mobile/android/app/build.gradle.kts
…cedence

Two fork-patch rationales shifted under this sync and would have misled the
next merge:

- Upstream block#6187 made the file-size policy a first-class gate. It is now
  `just file-size-check`, run repository-wide as the `File size policy` step
  of the `scripts` job, and no longer hangs off the desktop/web/mobile path
  filters. The relay.rs row told a future reader to expect an overflow as a
  red `Desktop Core`, which is now the wrong place to look.

- Upstream extended the Android debug label into an
  `if (debugAppName != null) ... else if (worktreeLabel != null)` chain, so
  its branch now runs ahead of the fork's brand literal. That is a recurring
  conflict site, so the row records the resolution: take upstream's new
  branch, keep the fork's brand in the fallback.

Signed-off-by: adrienlacombe <6303520+adrienlacombe@users.noreply.github.com>
@adrienlacombe

Copy link
Copy Markdown
Owner Author

Stopped on a tripwire — not merging

All 34 checks pass. Holding anyway on step-8 tripwire 3: "a row changed in
the AGENTS.md patch table."

Two rows changed in 537b26f37. Both edits touch only the third (rationale)
column
— the File and Change columns are byte-identical, and no fork patch
was added, removed, or altered:

  • desktop/src-tauri/src/relay.rs — the row told a future reader an overflow
    shows up as a red Desktop Core. Upstream ci: make file-size policy a first-class gate block/buzz#6187 moved the check to
    just file-size-check, run repository-wide as the File size policy step of
    the scripts job and no longer path-filtered, so that pointer was wrong.
  • mobile/android/app/build.gradle.kts — records that upstream's
    debugAppName branch now runs ahead of the fork's brand literal, and that the
    resolution is take upstream's branch, keep the fork's fallback.

These are exactly the updates step 6 mandates, which is why the tripwire is
arguably over-broad here — but the gate is written as an imperative and merging
fires deploy-aws.yml at the live relay, so I did not merge. If you agree the
rationale-only distinction is safe, this is a straight merge-commit away.

No other tripwire fired: no new migrations, no KIND_* changes, no fork patch
deleted, no conflict in release.yml / ingest.rs / kind.rs.

The red shard was flaky, and here is the evidence

Desktop Smoke E2E (3) failed on the first run at
message-feedback-snapshots.spec.ts >> profile hover uses the channel hover surface,
through all three attempts. It passed on rerun — everything is green now.

It is a pre-existing upstream race, not a regression from this sync:

  • The spec predates this range (added in the 2026-07-30 sync) and is
    unmodified by these 26 commits. The fork carries no patch in it or in either
    component.
  • Upstream CI is green at 93114c9c6, the exact merge head.
  • Reproduced green locally 5/5 with --repeat-each=5.

Mechanism. The test reads the channel row's hover colour immediately after
channel.hover(), with no settle wait, then asserts the profile card matches it:

await channel.hover();
const channelHoverColor = await channel.evaluate(
  (element) => getComputedStyle(element).backgroundColor,
);
await profile.hover();
await expect(profile).toHaveCSS("background-color", channelHoverColor);

Both elements resolve to the same --buzz-hover-surface token and both carry
transition-colors, so they do agree at steady state. On a loaded runner the
evaluate lands mid-transition and captures a transient — here
rgba(0, 0, 0, 0.03) on the ramp to 0.04. The call log shows toHaveCSS then
polling the profile card through 00.020.04 ×11 and never observing
0.03, so it times out against a value that only ever existed in flight.

The file already imports waitForAnimations, but calls it only before the
screenshot, not before the colour read — which is the fix, and it belongs
upstream rather than as a fork patch. Not changed here: a sync PR should carry
upstream's changes plus conflict resolutions, nothing else.

@adrienlacombe adrienlacombe added the needs-human Sync stopped on a tripwire; a human must review and merge label Aug 19, 2026
@adrienlacombe
adrienlacombe merged commit fcbc872 into main Aug 19, 2026
57 of 59 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.

9 participants