Commit Graph

2368 Commits

Author SHA1 Message Date
klopez4212
a567dfc2df Unify mobile channel details (#6113)
## Summary

- combine channel identity, actions, member previews, and lifecycle
controls in one navigable mobile details page
- add compact member management and desktop-aligned channel editing
- smooth the profile entrance transition and standardize settings-group
spacing
- align Huddle lifecycle rows with regular message author and body
spacing

## Testing

- `just mobile-check`
- `just mobile-test` — 1,477 tests
- Pixel 10 debug build, install, and interaction review

## Snapshots

### Channel header

![Channel header with channel name and member
count](https://raw.githubusercontent.com/block/buzz/c61c64a8db0f69fc71e0ac344d64384a85ad02b5/pr-6113--01-channel-header.png)

### Combined channel details

![Combined channel details with actions and member
preview](https://raw.githubusercontent.com/block/buzz/c61c64a8db0f69fc71e0ac344d64384a85ad02b5/pr-6113--02-channel-details-members.png)

### Manage channel

![Manage channel editing
controls](https://raw.githubusercontent.com/block/buzz/c61c64a8db0f69fc71e0ac344d64384a85ad02b5/pr-6113--03-manage-channel.png)

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
2026-08-19 17:48:09 +01:00
Luke Tornquist
08eb46ef3c Revert "fix(acp): gate relay-signed workflow messages on their attributed author" (#6311)
Reverts block/buzz#6129
2026-08-19 15:28:04 +00:00
thomaspblock
e5a6e2022f fix(desktop): morph the drawer panel icon instead of sliding it (#6306)
## Summary
- Replaces the drawer panel icon's CSS `translateX` slide with a
`motion/react` width + corner-radius morph, so the icon reads as the
panel opening rather than the glyph drifting sideways.
- Honors `prefers-reduced-motion` via `useReducedMotion` (no animation
for users who opt out).

Follow-up polish to the projects workspaces work that just landed in
#6003 — the slide animation shipped there was the wrong visual.

## Test plan
- [x] Desktop unit tests and typecheck pass with this file at this
content (validated as part of the projects-v6 branch validation)
- [ ] Visual check: open/close the right drawer and confirm the icon
morphs in place

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
2026-08-19 09:06:58 -04:00
thomaspblock
87f8ff82ae feat(desktop): refine repository-aware project workspaces (#6003)
## Summary
- Makes Projects repository-aware across browsing, branch and tag
selection, local and remote source management, work items, commits, and
contextual repository actions.
- Refines project details into consistent single-column reading surfaces
with a resizable, section-aware context pod, while keeping project
conversations available in the attached chat panel.
- Adds persistent sidebar project navigation, direct entity links,
repository discussion channels, contributor identity matching, and
consistent loading and activity presentation.
- Keeps branch-specific controls on code-oriented sections while Tasks,
Reviews, and Channels remain repository-scoped, reducing misleading
context and actions.

Replacement for #5981 with an identical final tree flattened into one
signed-off commit because the required DCO check suite remained stalled.

### Related issue
N/A

### Testing
- [x] Desktop pre-push checks, TypeScript typecheck, and unit tests
- [x] E2E production build with `pnpm build:e2e`
- [x] Focused Playwright smoke coverage for project workspace, task,
review, commit, sidebar, and contextual-panel behavior
- [ ] Attach final before/after screenshots after design review

---------

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Wintermute <165f0c871dd2586bb18b6aa109eeaf57bb2132ff4d27b10120f4368a0f627022@buzz.block.builderlab.xyz>
Co-authored-by: Wintermute <165f0c871dd2586bb18b6aa109eeaf57bb2132ff4d27b10120f4368a0f627022@buzz.block.builderlab.xyz>
2026-08-19 12:50:00 +01:00
klopez4212
93114c9c65 Fix mobile Activity thread navigation (#5850)
## 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>
mobile-v0.12.0-rc.1
2026-08-18 16:01:15 -07:00
Will Pfleger
a362fecc23 perf(desktop): parallelize relay agent directory rebuild (#6258)
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.

#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 #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>
2026-08-18 22:35:50 +00:00
klopez4212
359fe64675 Refine the mobile emoji picker (#5853)
## 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>
2026-08-18 15:32:19 -07:00
Will Pfleger
121e4b3ce7 fix(desktop): exclude archived agents from nest, order regeneration (#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>
2026-08-18 18:26:35 -04:00
klopez4212
7e2651791d Add font size and conversation density preferences (#5644)
## 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>
2026-08-18 15:24:09 -07:00
Taksh Kothari
6e8d078ffe fix(desktop): emit camelCase config-write payload fields (#6062)
Fixes #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>
2026-08-18 15:19:05 -07:00
Will Pfleger
e2ade93f02 fix(desktop): downscale large avatars for agent-share PNG body (#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>
2026-08-18 15:18:48 -07:00
Tyler
6ea7a2b221 fix(desktop): preserve early relay auth challenges (#3320)
## 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>
2026-08-18 15:09:58 -07:00
klopez4212
78267b0c3a Polish mobile message actions (#5873)
## 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>
2026-08-18 15:00:45 -07:00
klopez4212
40f1dac691 Refine mobile pairing confirmation (#6018)
## 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>
2026-08-18 14:06:53 -07:00
Will Pfleger
4f9727a4b3 chore(scripts): add buzz-adopt-prod-agents.sh (#6250)
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>
2026-08-18 16:49:41 -04:00
Will Pfleger
50a71137e6 feat(managed-agents): close five Claude Code agent-config gaps (#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 (#2692, #2884,
#3493)

- **`CLAUDE_CONFIG_DIR` isolation (#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 (#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` (#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 (#2265/#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 #2692, #2884, #3493

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-18 16:29:28 -04:00
Wes
b74700daaf chore(hooks): keep mobile analysis out of pre-commit (#6236)
## 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>
2026-08-18 12:56:06 -07:00
Taylor Ho
d7e8fdb10c fix(shared-ui): delay hover disclosures by default (#5821)
**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>
2026-08-18 12:55:00 -07:00
Taylor Ho
c442a90a17 fix(desktop-chrome): preserve balanced layout when sidebar collapses (#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>
2026-08-18 12:54:40 -07:00
klopez4212
417eea2230 Polish mobile timeline navigation (#5874)
## 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>
2026-08-18 18:56:58 +01:00
Wes
8232299cbe chore(release): release Buzz Desktop version 0.5.17 (#6234)
## 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>
2026-08-18 09:40:31 -07:00
Salman Mohammed
d2cfd377e2 fix(prompt): simplify pickup follow-through (#6186)
## 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
https://github.com/block/buzz/pull/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>
2026-08-18 16:24:12 +00:00
Salman Mohammed
5694e78def fix(mcp): scope todo usage (#6216)
## 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: https://github.com/block/buzz/pull/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>
2026-08-18 16:04:48 +00:00
Wes
3fdf289b78 fix(desktop): bound remote agent mention authorization (#6224)
## 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>
2026-08-18 08:46:26 -07:00
Logan Johnson
cc8a8b0dcb fix: bump h2 for RUSTSEC-2026-0258 (#6222)
🤖
## 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>
2026-08-18 08:00:52 -07:00
Wes
081910424a fix(desktop): bind presence retry timers (#6213)
## 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>
2026-08-18 06:36:50 -07:00
Wes
6d45f98665 ci: make file-size policy a first-class gate (#6187)
## 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>
2026-08-18 06:36:25 -07:00
Wes
f0234f1449 fix(desktop): eliminate mounted-view CPU burn — compositor-safe shimmer, observer append fast path, poll-tick disk reads (#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 #3151);
current multi-agent workloads exposed and amplified it. The mention
regression itself was fixed separately in #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>
2026-08-18 05:47:53 -07:00
Wes
978e585e8d chore(release): release Buzz Desktop version 0.5.16 (#6191)
## 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>
2026-08-17 20:54:34 -07:00
Wes
ee992ff082 fix(desktop): restore release agent mentions (#6182)
## 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>
2026-08-17 20:24:33 -07:00
Wes
f8692fa9b5 test(desktop): cover exact workflow batch limit (#6168)
## Summary

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

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

Fixes #6116

## Test plan

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

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-17 16:47:07 -07:00
Wes
c8c8eb58ad chore(release): release Buzz Desktop version 0.5.15 (#6173)
## Buzz Desktop release v0.5.15

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

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

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

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
2026-08-17 16:21:49 -07:00
Wes
7f61cf431a Preserve managed agent mentions during relay errors (#6167)
## Summary

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

## Testing

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

Fixes #6147

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-17 15:27:37 -07:00
Taylor Ho
f7a01bda7b fix(workflows): preserve multi-channel listing semantics (#6009)
**Category:** fix
**User Impact:** Workflow listings reliably include every accessible
channel, including for users with more than 128 memberships and when
connected to older relays.
**Problem:** Multi-value `#h` filters could lose live delivery, apply
channel scoping after SQL limits, mishandle partial authorization or
revocation, and permit unbounded membership work. Desktop also submitted
every channel in one request, exceeding the relay's new 128-value safety
bound.
**Solution:** Preserve NIP-01 OR semantics across relay query, count,
and live-subscription paths while enforcing authorization and bounded
explicit-channel work before database or Redis operations. Desktop keeps
the older-relay-compatible one-channel-per-filter shape, sends filters
in bounded batches, combines responses, and deduplicates signed events
by event ID.

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

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

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

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

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

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

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

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

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

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

</details>

## Reproduction steps

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

## Validation

At pushed head `c419a923f05e483ab26c006a0b3a80cfb3c73844`:

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

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
2026-08-17 15:16:14 -07:00
Salman Mohammed
f64899e5d1 Remove Startup Recovery section in base prompt (#6161)
## Why

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

## What

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

## Risk Assessment

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

Generated with Codex

---------

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
2026-08-17 18:11:27 -04:00
Wes
439c037491 fix(desktop): align preview sidebar row styling (#6163)
## Summary

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

## Validation

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

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-17 14:49:31 -07:00
Will Pfleger
57feca2f20 fix(desktop): repair dropped team membership links at boot and on edit (#5904)
Two membership-propagation defects let an agent team silently lose
members — both observed live on Will's store (Sietch Tabr), not
hypothetical.

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

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

## Fix

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

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

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

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

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

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

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-17 14:44:43 -07:00
Illumi
a282e0643f fix(cli): keep project replacement timestamps at or after wall clock (#5666)
Fixes #5665.

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

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

## Change

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

## Testing

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

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

---------

Signed-off-by: Ika Minami <ika@infiniteidol.com>
Signed-off-by: Ravneet Arora <rarora@squareup.com>
Co-authored-by: Ika Minami <ika@infiniteidol.com>
Co-authored-by: Ravneet Arora <rarora@squareup.com>
2026-08-17 12:49:52 -07:00
Jordan Mecom
85bacea52b Remove GitHub security advisory commitment (#6144)
Removes the promise in `SECURITY.md` to publish a GitHub Security
Advisory after every security fix is released.

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

Checked with `git diff --check`.

Signed-off-by: Jordan Mecom <jm@squareup.com>
2026-08-17 11:48:38 -07:00
klopez4212
076081bfc6 Rename Bumble agent to Pollen (#5864)
## Summary
- Rename the built-in Bumble agent to Pollen across desktop, onboarding,
docs, and test fixtures.
- Migrate existing stock definitions and instances in place while
preserving customized fields and the stable persona coordinate.
- Reserve the Pollen name by removing it from Fizz's generated-name
pool.

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

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-17 10:49:03 -07:00
Will Pfleger
d12d825778 fix(desktop): resolve agent profiles through one archive-aware selector (#5706)
Agent profiles resolve through one shared selector (`pickProfileAgent`)
at every entry point — the persona card, the profile panel, and library
grouping. That selector ranked instances only by active/name, with no
archive awareness, so a relay-archived instance early in file order
could hijack the persona card and the profile panel. The persona card
also recorded a durable pubkey target, which could strand the panel on
an archived identity when the click landed during the archive-snapshot
fail-open window. The profile panel's Runtime → Instances roster had the
same blind spot: it rendered every persona instance raw, so archived
instances appeared mixed in with live ones as if active.

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

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

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

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-17 13:46:25 -04:00
Luke Tornquist
54f11219ef fix(acp): gate relay-signed workflow messages on their attributed author (#6129)
## Problem

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

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

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

## Fix

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

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

## Security notes (all fail closed)

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

## Testing

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

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

---------

Signed-off-by: Luke Tornquist <tornquist@squareup.com>
Co-authored-by: Fizz <3a9f8a30fbb462abec1e2977b2280a7ae50c7ff794433790be15bd48bfd52d0b@buzz.block.builderlab.xyz>
2026-08-17 13:29:20 -04:00
Atish Patel
5b3f0375a2 fix(acp): replace Goose native system prompt (#5964)
## Why

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

## What

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

## Risk Assessment

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

## References

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

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

---

**Update Aug 15, 13:17 CDT:** Added the [Terra-high prompt-ablation
comparison](4492f76349/data/prompt-ablation/20260815-terra-high/comparison.md).

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

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

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

Generated with Codex

Signed-off-by: Atish Patel <atishpatel2012@gmail.com>
Co-authored-by: Codex <noreply@openai.com>
2026-08-17 13:03:14 -04:00
Taylor Ho
edc4a09aaa feat(workflows): add responsive library card actions (#6008)
**Category:** improvement
**User Impact:** Users can scan what each workflow does and trigger,
edit, duplicate, enable, disable, or delete it directly from the
library.
**Problem:** The workflow list buried common actions and did not expose
each automation's trigger-to-action shape at a glance.
**Solution:** Add a responsive workflow library with a persistent create
tile, compact trigger/action diagrams, prominent workflow titles with
supporting descriptions, and shared card actions while preserving
existing detail, editor, and run-history entry points. Card toggles
refresh both list and open-detail caches so status and definition stay
consistent.

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

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

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

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

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

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

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

</details>

## Reproduction steps

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

## Screenshots

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

### Narrow — 800 × 720

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

### Medium — 1024 × 720

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

### Wide — 1280 × 720

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

### Card actions

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

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
2026-08-17 16:56:34 +00:00
Wes
f716eef437 fix(desktop): enforce shared agent access across devices (#6086)
## Summary

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

### Related issue

Fixes #3204

### Known residuals

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

### Testing

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

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Signed-off-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz>
Co-authored-by: diegorumo <diegorumo@gmail.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz>
2026-08-17 09:51:00 -07:00
Will Pfleger
1b7e5ac1be feat(model-capabilities): drive model capabilities and labels from one manifest (#5597)
## Summary

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

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

## Behavior changes

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

## Cleanup

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

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-17 11:23:28 -04:00
Wes
f956e6fe06 docs: refresh agent development guidance (#6049)
## Summary

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

## Validation

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

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-16 08:25:51 -07:00
Taylor Ho
d8281b9c93 feat(mobile): require device authentication for identity export (#5116)
**Category:** new-feature
**User Impact:** Mobile users must confirm with Face ID, biometrics, or
their device passcode before sending their Buzz identity to Desktop.

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

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

## Screencasts

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

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

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

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

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

</details>

## Reproduction steps

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

## Validation

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

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
2026-08-15 18:34:02 -07:00
thomaspblock
78cbffeb64 fix(desktop): hide the offcanvas-collapsed sidebar so it stops painting over the community rail (#5947)
## Summary

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

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

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

## Before / after

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

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

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

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

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

## Test plan

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

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Co-authored-by: Wintermute <165f0c871dd2586bb18b6aa109eeaf57bb2132ff4d27b10120f4368a0f627022@buzz.block.builderlab.xyz>
2026-08-15 07:49:41 -07:00
klopez4212
69107dc3bf Polish mobile message threads and composer (#5645)
## Summary

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

## Validation

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

## Snapshots

See the review snapshots below.

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
2026-08-15 09:14:14 +01:00