mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
morgan/thread-readability
1474
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
610bc64d5f |
fix(desktop): scope relaxed spacing to threads
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
e6ad01d29c |
fix(desktop): restore expanded thread gutters
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
e5ad3fd37f |
feat(desktop): improve thread readability
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
cc9a2f7833 |
fix(desktop): make terminal output selectable (#4980)
## Summary - mirror the retained canvas terminal grid into a transparent, selectable text layer - preserve the canvas renderer and terminal focus behavior for ordinary clicks - reconstruct wide and combining glyphs correctly for clipboard text ## Why Buzz Term renders output entirely on a canvas and deliberately called `preventDefault()` on viewport mouse-down, so native selection and copy could not work. A canvas has no selectable text even if that cancellation is removed. The transparent text layer stays aligned with the visible cell grid, lets WebView native selection drive drag highlighting and copy, and follows active-session switches without changing the renderer or PTY protocol. ## Validation - `pnpm --dir desktop typecheck` - `pnpm --dir desktop test` — 4,373 passed - pre-push `desktop-check`, `desktop-test`, and `branch-skew` hooks passed on `1f2a3f8db63f6fe36b4a28bc911aea3c5186b2b0` --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
60ae74b656 |
fix(desktop): use WEBKIT_DMABUF_RENDERER_FORCE_SHM for NVIDIA/AppImage (#3654) (#4505)
## Summary - Heuristic and `--safe-rendering` now set `WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` instead of `WEBKIT_DISABLE_DMABUF_RENDERER=1` - Legacy `DISABLE_DMABUF` stays owned so operators can still set `=0`/`=1` and take over the decision - Linux troubleshooting docs updated to match (#3654) ## Test plan - [ ] unit tests in `webkit_rendering::tests` - [ ] On NVIDIA + WebKitGTK 2.52: workspace switch no longer SIGSEGVs where the distro NVIDIA guard does not fire (Debian/Ubuntu proprietary-NVIDIA may still crash — #3654 stays open for that path) - [ ] `WEBKIT_DISABLE_DMABUF_RENDERER=0` still stands the heuristic down --------- Signed-off-by: Taksh <takshkothari09@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Alia <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz> Co-authored-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
daa8877581 |
Make public starter channels best effort (#5192)
## Summary - treat public starter-channel provisioning as best-effort after preserving the required private Welcome path - let community onboarding complete and focus Welcome when the reported metadata lookup error occurs - remove the now-obsolete retry-toast expectations for optional starter provisioning ## Scope This intentionally does not change relay tombstone semantics or auto-join existing public channels. ## Test plan - `pnpm exec playwright test tests/e2e/deep-link-invite.spec.ts` (8 passed) - `pnpm exec playwright test tests/e2e/onboarding.spec.ts --grep "failed public starter channel setup"` (1 passed) - `pnpm typecheck` - `pnpm check` - `pnpm test` (4483 passed) - pre-push hook: branch-skew, desktop-check, desktop-typecheck, desktop-test passed on `4658a07beb1e1d54443da5cd2e4a28fae0232f24` --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
c8743b2f20 |
Remove agent creation success modal (#5063)
## Summary - remove the post-creation private-key modal - return directly to the underlying page with one “Agent created” toast - preserve failed channel-attachment retry through an actionable toast ## Validation - desktop checks and E2E build - 4,392 desktop unit tests - focused Playwright coverage for standard, customized, and attachment-retry creation flows Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
cd2125c34b |
Improve video review readiness and controls (#5161)
   --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
c71f658539 |
Polish advanced agent setup and Welcome composer (#4926)
## Summary - move **Run on** into Advanced, directly after **Who can send instructions** - reuse the modal’s shared dropdown styling - give the Welcome guidance and composer matching glass treatment while preserving the corrected exit layering ## Validation - `pnpm -C desktop typecheck` - focused Playwright: Run on configuration (3 passed) - focused Playwright: Welcome onboarding flow (1 passed) - desktop unit suite (4,290 passed) --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Watcher <bb7abfd757d0af7b66569d02ab9c0316b616f9d0c151ecf5b964344c462e7f8f@buzz.block.builderlab.xyz> Co-authored-by: Watcher <bb7abfd757d0af7b66569d02ab9c0316b616f9d0c151ecf5b964344c462e7f8f@buzz.block.builderlab.xyz> |
||
|
|
769ac70b74 |
fix(media): require authenticated reads (#4610)
This change requires a valid signed Blossom authorization request and current relay membership for every media GET and HEAD request. It removes the unauthenticated compatibility path and updates desktop reads to send the required authorization. This blocks anonymous retrieval and access after relay-membership revocation. It does not yet bind a blob to its originating channel, so someone removed from a private channel can still read a known blob while remaining a relay member. That channel-ACL follow-up remains required before closing the full finding. ## Testing - `git diff --check origin/main...codex/security-media-read-auth` - Rebased onto `origin/main` at `5c98932` - Full CI pending Originating Buzz thread: `buzz://message?channel=3928fe05-df61-4b5d-b9c7-d623b9b10ea1&id=3c6c02312f763fbe0d2bfc33a6c1a362f91d0354f3d18b039cf7a0558c1439d1` --------- Signed-off-by: Jordan Mecom <jm@squareup.com> Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com> Signed-off-by: Eli Foster <efoster@squareup.com> Co-authored-by: Eli Foster <efoster@squareup.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f03de210cd |
fix(desktop): preserve authoritative agent avatars (#4984)
**Category:** fix **User Impact:** Agent cards and catalog listings now show the avatar belonging to the identity they represent. **Problem:** Running agent cards could show a stale definition avatar instead of the concrete agent profile, while adding another publisher's catalog entry could let local edits repaint that publisher's listing. This made agent identity look inconsistent across My Agents and the Agent Catalog. **Solution:** Treat the concrete agent pubkey profile as authoritative for running-card avatars, with the linked definition as fallback. Keep relay publications authoritative for foreign catalog presentation while using local copies only for linkage and selection state. | before | after | |--|--| | <img width="874" height="592" alt="Screenshot 2026-08-06 at 3 48 43 PM" src="https://github.com/user-attachments/assets/2cc6c9f7-ea50-413c-9c7b-4d34bd8b4ec7" /> | <img width="884" height="597" alt="Screenshot 2026-08-06 at 3 48 40 PM" src="https://github.com/user-attachments/assets/b14a865c-65c4-458f-9c30-d1a557c877d7" /> | | agent-set avatar not showing | agent-set avatar is showing | ## Changes <details> <summary>File changes</summary> **desktop/src/features/agents/lib/agentCardAvatar.ts** Adds the explicit avatar precedence rule for running agent cards and blocks avatar-dependent actions until the authoritative profile query settles. **desktop/src/features/agents/lib/agentCardAvatar.test.mjs** Covers profile precedence, definition fallback, blank avatar handling, and the profile-loading transition for linked-agent actions. **desktop/src/features/agents/lib/personaCatalogRelay.ts** Keeps publisher-provided catalog identity and behavior fields authoritative after a local copy is added. **desktop/src/features/agents/lib/personaCatalogRelay.test.mjs** Verifies local copies contribute linkage and selection without overriding publisher presentation. **desktop/src/features/agents/ui/UnifiedAgentsSection.tsx** Uses the concrete agent profile avatar before the linked definition avatar on running-agent cards. </details> ## Reproduction Steps ### Running agent card uses the agent profile avatar Use two visibly different, publicly reachable image URLs: **A** for the saved definition and **B** for the running agent profile. 1. In **Settings → Experiments**, enable **Agent-managed profiles**. This prevents Desktop from restoring the definition avatar over an agent's own relay-profile changes. 2. In **Agents**, create an agent with image **A** as its avatar and start it. 3. In a channel containing that agent, ask it to update its own Buzz profile avatar to image **B**. The exact CLI operation under the agent identity is `buzz users set-profile --avatar <image-B-url>`. 4. After the agent confirms the update, reopen **Agents → My Agents** (or reload the page so its kind:0 profile is fetched again). 5. Verify the running agent card shows image **B**, not definition image **A**. Open **⋯ → Share** and verify the share flow also uses image **B**. Before this fix, the My Agents card and share flow preferred image **A** whenever the linked definition had an avatar. ### Catalog listing remains publisher-authoritative This scenario requires a second Buzz identity so the entry is foreign to the account under test. 1. As the publisher identity, create an agent definition with a distinctive name, avatar, and instructions, then use **Share → Share to catalog**. 2. As the test identity, open **Agents → Discover agents**, find that publication, and add it. 3. In **My Agents**, open the added copy's **⋯ → Edit**, change its name, avatar, and instructions, and save. 4. Return to **Discover agents** and find the same publisher entry. 5. Verify it remains selected/added but still shows the publisher's original name, avatar, and instructions—not the test identity's local edits. ## Validation - `pnpm test` — 4,376 passed - `pnpm typecheck` — passed - `pnpm check` — passed with existing non-error notices --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
67b77344d6 |
fix(desktop): next/back navigation during key creation onboarding (#4978)
**Category:** fix **User Impact:** Users can navigate back while an identity key is being created, while Next remains visible and unavailable until creation finishes. **Problem:** The key-creation hold hid both navigation actions, leaving users without an escape route or a clear indication of what would happen next. **Solution:** Keep the onboarding footer mounted throughout creation, leave Back enabled, and gate Next on the completed identity state. <details> <summary>File changes</summary> **desktop/src/features/onboarding/ui/BackupStep.tsx** Keeps the onboarding navigation footer visible during key creation, with Back available and Next disabled until the identity is ready. **desktop/tests/e2e/onboarding-backup.spec.ts** Covers the loading and completed navigation states so the intended behavior cannot quietly crawl back out of the pit. </details> ## Reproduction steps 1. Start desktop onboarding and choose to create a new identity. 2. Submit the profile step and observe the key-creation screen. 3. Confirm Back is enabled while Next is visible but disabled. 4. Wait for key creation to finish and confirm Next becomes enabled. ## Screenshots | Before | After | | --- | --- | | Navigation actions are hidden during key creation. | Back remains enabled while Next stays visible and disabled. | |  |  | Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
1399ec1d13 |
Alert community owners and admins when a new key joins (#4900)
Owners and admins of a Buzz community get a desktop notification the
first time a new key joins their community. Requested by Tyler in
buzz-development ("we already have this [roster] — can we alert owners
and admins when a new key joins for the first time?"); design and
verification thread: channel `community-members-visibility`.
## Why the shape is what it is
- **kind:13534 membership snapshot is the alerting signal, not the
kind:8000 delta.** 8000 is leaky on two independent axes: its fan-out is
pod-local (no Redis hop — being fixed separately in #4887), and
`buzz-admin add-member` publishes no 8000 at all by documented design.
The 13534 snapshot is the only signal covering every production join
path with cross-pod delivery (completeness audit: every
membership-insertion path enumerated at base `8342dfcc5`, all emit
13534).
- **This adds Desktop's first live 13534 subscription** — deliberate
line item. The existing read (`relayMembers.ts`) is a one-shot fetch;
without a live subscription no snapshot ever arrives passively and
nothing could fire.
- **8000 is subscribed only as a latency accelerator** and it *refetches
the authoritative snapshot* rather than alerting from its own payload,
so one ledger governs both signals and they cannot double-alert.
- **Persisted per-community/per-viewer ledger, written before the
notification fires.** Snapshot publication is eventual (60s reconciler
repairs failed best-effort publishes) and a reconciler-republished
snapshot is indistinguishable from a fresh one — only a durable record
answers "is this new". Also what makes reconnect replay (`since - 5s`
skew; `since === undefined` full-backlog edge) safe.
- **First snapshot per community seeds silently** (no notification storm
for existing members), and `seeded` is an explicit persisted bit — not
inferred from ledger non-emptiness, which would swallow the first
genuine join in a community whose only member is the viewer.
- Mounted in `useAppShellDesktopNotifications` (owns the
notifications-enabled precondition; `AppShell.tsx` is at the file-size
ratchet ceiling — net growth zero).
5 files, +3103 (production +752, tests +2,351), desktop-only. No relay
changes.
## Verification
**Current reviewed tip: `1854c4a5` — review-blessed code at
`d992ed295ead8c8423f81a752f4ad614718d85c6`** (clean tree, HEAD checked
in the same shell as each gate; history is `0e791f2d3` → merge of main
`2034e693a` → `fdeda44f0` → `5d0d2b4c3` → `a20a7d8cb` → merge of main
`0cfe4832` → `d992ed29` → `1854c4a5`, all fast-forward, no rebase or
force). Independently gated by Eva, Wren, and Sami: typecheck rc=0,
`pnpm check` rc=0 (pre-existing 1 warning / 2 infos), full Desktop unit
package **4431/4431**; push hooks pass. Wren's adversarial verdict at
`d992ed29`: APPROVE — minimalness 9, elegance 9, correctness 9, all four
cancellation seams plus 1b re-derived independently. `1854c4a5` is
assertions and comments only — no production behaviour change, so the
test count is unchanged.
**Remediation commits (review thread `community-members-visibility`):**
- `fdeda44f0` — authorization read from the signed snapshot being
reconciled (a demoting/removing snapshot fails closed before it can
disclose the joins it carries); >3 joins collapse to one summary;
8000-triggered refetches coalesce on a 500ms trailing window.
- `5d0d2b4c3` — join alerts coalesce **across** snapshots, not just
within one: a live burst arrives as several growing rosters, so delivery
defers onto a 1.5s trailing quiet window while ledger persistence and
dedupe stay synchronous per snapshot. Max measured 10 banners from 50
real joins before this; the same shape now produces one.
- `a20a7d8cb` — cancellation covers flushes already in flight, not just
queued timers: a generation token (bumped only by `clearPending`) is
rechecked after the profile lookup and before every send, so
demotion/removal/unmount/community-switch landing mid-flush suppresses
delivery; the notification title is captured with the batch rather than
read at send time. Concurrent-flush semantics pinned: a newer authorized
batch neither cancels nor is cancelled by an in-flight flush.
- `d992ed29` — the stale-authorized-frame disclosure, independently
reproduced at `0cfe4832` (held-open refetch released after a newer
demoting frame: `notifications=1`, body naming the joiner, where 0 is
required). Three fixes in one shape: every callback acts on a
per-effect-run session object (community id, viewer, ledger, ordering
state) instead of ambient current values, closing the community-switch
window; a `created_at` fence plus a fail-closed revocation latch, as one
mechanism, because the relay can publish two snapshots in the same
second so neither `<` nor `<=` alone is safe — the invariant is
“revocation wins”, not “newest wins”; and a 5s clamp on the 1.5s
trailing window so a sustained drip cannot defer delivery without bound.
Red-first: the four new arms fail at `0cfe4832` (25/29) and pass after
(29/29).
- `1854c4a5` — the privacy arm now asserts the persisted ledger is
unchanged across the delayed frame's release, not only the notification
count. Mutation-checked: moving the revoked check after the ledger
advance keeps notifications at 0 and passes the old assertion, and is
killed by the new one. Assertions and comments only.
**Mutation testing:** 9/9 mounted-hook mutants killed at `a20a7d8cb`,
each with a control row before and after — role/enabled gates,
reconnect, 8000 authority, failed-write handling and ref ordering,
community re-key/read, and query invalidation. The reducer/storage fix
separately killed 6/6 mutants with 15/0 controls; the foundational
ledger suite killed 9/9. At `d992ed29`: spelling the fence `<=` kills 5
arms; moving the empty-roster guard after the fence advance kills
exactly the fence-advance arm and nothing else (28/29). One
qualification stated rather than buried — moving the fence advance
itself up to the comparison SURVIVES the whole suite. That is an
equivalent mutant, not a coverage gap: the empty-roster guard returns
before the comparison, and authorization rejection latches `revoked` so
a later frame having moved the fence is unobservable. The scope is
written into the test's docstring. At `1854c4a5`: the
revoked-check-after-ledger-advance mutant is killed by the new ledger
assertion (and by the 1b arm).
**Scale/storage correction in `f6e5a3c57`:** the original 5,000-key cap
could evict members still present in a 5,001+ roster, causing them to
re-alert on every snapshot; read-time truncation reopened the same loop
after reload; and a raw quota exception could reject before notification
dispatch. The fix retains every on-roster key, caps only departed keys,
removes read-time truncation, and uses the app's quota-aware writer.
**Final ordering correction in `0e791f2d3`:** a failed post-recovery
write now skips notification and leaves the in-memory ledger unchanged,
so the next snapshot retries and delivers only after persistence
succeeds.
**Live-local matrix vs a real relay, executed at exact unchanged
`d75cc6cd9` and transferred to the current tip:** a 4,800-sequence
differential found zero old/new reducer divergences below the cap while
exercising the positive alert path; its negative control diverged as
required at 5,100 members (old re-alerts 100; new re-alerts 0). The
final hook change affects only the newly tested failed-write branch;
successful writes follow the same alert path exercised live. The live
communities were sub-cap and persisted successfully, so the matrix
remains applicable without a redundant rerun.
- Invite claim: owner and admin each exactly one notification; plain
member zero; 1.5s quiet window held (8000+13534 deduped); both open
clients live-refreshed the roster. Screenshot receipts SHA-256-pinned
and independently replicated.
- **CLI `buzz-admin add-member` (13534-only path):** DB counts moved
8000 `9→9`, 13534 `15→16` — zero accelerator events, exactly one alert
per manager. Proves snapshot-diff alone alerts.
- Plain member: zero notifications **and** zero
`buzz-community-join-seen.v1:*` localStorage keys before/after the join
(gate sits before the ledger).
- Staggered reload + replay dedupe: no alerts from startup
refetch/replay; republished already-seen snapshot produced zero through
a 2s quiet window.
- Community switch: independent per-community seed state; effect
re-keys; one alert per community, quiet window held at exactly two.
**Live re-verification at `d992ed29` is in progress** (Max; the
after-fix matrix leads with the delayed-refetch demotion arm, A→B switch
ledger isolation, the 5s sustained-drip timing, and packaged-app click
routing behind the positive/NIP-43 controls); earlier receipts at
`a20a7d8cb` cover the instrumented storm and cap-boundary re-drive;
earlier live receipts at `fdeda44f0` — privacy matrix
(demote/remove/promote), summary click-through — transfer where the diff
left those paths untouched.
## Known and accepted
- **8000 cross-pod fan-out is broken relay-side** — fixed in #4887
(separate lane, not a blocker here): on a multi-pod relay the
accelerator only fires on the claim-handling pod; 13534 still covers
everyone, just not instantly.
- **Late-not-lost semantics.** A live frame missed during a
reload/socket gap is recovered by the next snapshot, reconnect refetch,
or remount backfill (`limit: 1`) diffed against the persisted ledger.
One live-run observation of an admin missing an immediate post-reload
fresh join is attributed to harness rate limiting; the recovery paths
above bound the damage to lateness, never duplicates.
- **Remote promotion activates on reload, not on the next snapshot**
(measured by Sami at `fdeda44f0`): the subscriptions are mounted from
the cached membership lookup, so a viewer promoted to admin by someone
else starts receiving join alerts only after a reload, community switch,
or local membership mutation refreshes that cache. Fails safe
(under-notify). Ruled accepted for v1 by Eva; the fix direction
(subscribing before authorization) is a deliberate design change
deferred to a follow-up if product wants instant activation.
- **Cross-user live-delivery staleness reproduced at the PR's own base**
(`2034e693a`, clean relay): a persisted send can fail to appear in an
already-open recipient timeline. Detached from this PR by a pinned-base
discriminator (identical failure with zero PR code) and tracked
separately in issue `6e2bda3092fa`; current main passes 4/4.
- **A stale demoting frame latches a genuine admin until reload or
community switch** (reverse ordering of the stale-frame privacy race,
`d992ed29`): if a snapshot that does not list the viewer as a manager
arrives out of order, the fail-closed revocation latch trips even though
the viewer is still an admin. The invalidation the latch fires refetches
the membership lookup, which correctly returns admin, so `active` stays
true, the effect deps do not change, and the session stays latched.
Fails safe (under-notify, never over-disclose) and consistent with the
promotion-on-reload semantics above. Ruled accepted for v1 by Eva;
self-clearing the latch would cost a third piece of timing state. Pinned
as documented behaviour in `useCommunityJoinAlerts.test.mjs` — and the
suppressed join is re-announced rather than lost, because a latched
session never records it in the ledger.
- **Lifetime-first-only semantics:** ever-seen ledger means
remove→re-add does not re-alert. Flagged for product ruling; one-line
change if re-adds should ping.
---------
Signed-off-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
|
||
|
|
b08c8b126c |
fix(desktop): prevent sidebar prefs from reverting on stale-localStorage boot (#5086)
Fixes the bug where running a dev build with stale localStorage would
publish outdated channel sections, sort preferences, starred channels,
and muted channels to the relay, clobbering the DMG installation's live
state.
## Root cause
All four sidebar-preference sync managers (`channelSectionsSync`,
`channelSortSync`, `channelStarsSync`, `channelMutesSync`) collapsed
five distinct fetch outcomes — no event, timeout, error, auth-race empty
result, decrypt/parse failure — into a single `null`. Each hook's boot
effect treated `null` as "no remote exists" and seed-published whatever
was in localStorage, stamped at `max(now, lastRemoteCreatedAt+1)` with
`lastRemoteCreatedAt` reset to 0 on every boot. A dev build with stale
localStorage therefore re-signed old state as newer, and the DMG's live
subscription applied it.
## Two guards
**1. Tri-state fetch result** (`found | absent | failed`) — decrypt
failure on an existing event reports `failed` and records
`event.created_at`, so seed-publish is blocked even when the payload is
unreadable.
**2. Persisted head watermark** (`sidebarSyncWatermark.ts`) — keyed
`{blobType, pubkey, normalizedRelayUrl}`, written to localStorage on
every observed remote event (before decrypt on all paths: initial fetch,
live subscription, `fetchOwnBlobBeforePublish`), hydrated at
construction. Any session that has ever seen a remote blob skips
seed-publish on the next boot even when the fetch returns empty. Relay
URLs are normalised via `shared/lib/normalizeRelayUrl` (also used by
profile storage) so the same relay written two ways never produces two
keys.
**Bootstrap owns the seed.** Each manager exposes
`bootstrap(localStore)` that fetches, records the raw head, and
delegates the decision to the single `runBootstrap` policy: hold on
`failed` or `absent + prior watermark`, seed on genuine first-sync
(`absent + zero watermark + non-empty local`), `apply-remote` when a
blob was found. Hooks only act on `apply-remote`; they cannot publish
during bootstrap. First-time sync is unchanged: successful EOSE with no
event, zero watermark, and non-empty local state still seeds.
## LWW baseline preservation
`fetchOwnBlobBeforePublish` for sections/sort snapshots the watermark
before `recordRemoteHead` advances it, then compares the fetched event
against the snapshot — advancing first would make `remote.createdAt >
lastRemoteCreatedAt` always false and silently kill the whole-blob LWW
merge. Stars/mutes merge per-entry via `mergeStores`, so no snapshot is
needed there.
## Relay lifecycle
All four hooks require a defined `relayUrl` (plumbed from
`communitiesHook.activeCommunity?.relayUrl` in `AppShell.tsx`); while it
is undefined no manager is constructed and no boot/live/reconnect effect
binds. All effects depend on `[pubkey, relayUrl]`, so community switches
tear down and rebind. `destroy()` cancels pending publishes without
flushing — flushing would race community switching and could publish
relay A's state to relay B via the shared `relayClient` singleton.
Pending debounce-window edits are intentionally dropped: stars/mutes
entries survive via per-entry merge on the next publish; a dropped
sections/sort edit is lost because bootstrap whole-blob-replaces from
remote on return.
Known trade-off: a first boot with the relay unreachable holds (never
seeds) until the user's next explicit edit — preferred over risking a
stale seed-publish.
## Files
- `sidebarSyncWatermark.ts` — watermark persistence + `runBootstrap`
policy (tri-state `FetchResult`, `readWatermark`, `advanceWatermark`)
- `shared/lib/normalizeRelayUrl.ts` — relay-URL normalisation shared by
watermark keys and profile storage
- `channelSectionsSync.ts`, `channelSortSync.ts`, `channelStarsSync.ts`,
`channelMutesSync.ts` — tri-state fetch, pre-decrypt `recordRemoteHead`
on all paths, sections/sort watermark snapshot for LWW, `bootstrap()`,
cancel-without-flush `destroy()`
- `useChannelSections.ts`, `useChannelSortPreference.ts`,
`useChannelStars.ts`, `useChannelMutes.ts` — act on `bootstrap()`
results, gate on `relayUrl`, `[pubkey, relayUrl]` deps on all effects
- `AppShell.tsx` — passes `activeCommunity?.relayUrl` to
`useChannelMutes` and `useChannelStars`
- `sidebarSyncTestHelpers.mjs` — shared fake-window/localStorage/Tauri
mocks for the four manager suites
- Test suites — mutation-sensitive coverage: `failed→hold`,
`absent+watermark→hold`, first-sync seeds, undecryptable head recorded
on all paths, relay-A/B watermark isolation, watermark restart
round-trip, sections/sort LWW baseline
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
6eb65919f1 |
feat(identity): recover desktop identity from a signed-in phone (#4845)
**Category:** new-feature **User Impact:** People who lose a desktop identity can securely restore it from a signed-in Buzz phone without creating a replacement identity. **Problem:** A fresh or identity-lost desktop could not recover its existing full Buzz identity from an already-authorized phone. **Solution:** Add a SAS-confirmed reverse NIP-AB transfer, durable desktop import, a dedicated mobile recovery entry point, and clearer desktop recovery dialogs with tested loading, drag-and-drop, and failure states. https://github.com/user-attachments/assets/e9215c9c-80d0-462f-9161-0fa184ca2f74 <details> <summary>File changes</summary> **crates/buzz-core/src/pairing/session.rs** Adds the reverse encrypted payload and source-completion state transitions used for phone-to-desktop recovery. **desktop/src-tauri/src/commands/identity.rs** Exposes the existing guarded identity commit path for recovery imports. **desktop/src-tauri/src/commands/pairing.rs** Adds recovery-mode pairing, durable nsec import, start serialization, stale-task protection, and explicit rejection of unsupported recovery payloads. **desktop/src-tauri/src/lib.rs** Registers the recovery pairing command. **desktop/src/app/App.tsx** Refreshes the recovered identity before continuing onboarding. **desktop/src/features/onboarding/machineOnboarding.ts** Adds recovery transitions to the onboarding state machine. **desktop/src/features/onboarding/ui/BackupPasswordTimeline.tsx** Adds the visual backup-to-password-to-unlock progression. **desktop/src/features/onboarding/ui/IdentityRecoveryPairing.tsx** Implements QR generation, copy fallback, SAS confirmation, cancellation, expiry, and completion UI. **desktop/src/features/onboarding/ui/MachineOnboardingFlow.tsx** Connects private-key, phone, and backup recovery paths to the onboarding flow. **desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx** Polishes recovery dialogs, backup drag-and-drop, loading stability, and security copy. **desktop/src/shared/api/tauri.ts** Keeps the existing pairing API surface focused on standard desktop-to-mobile pairing. **desktop/src/shared/api/tauriPairing.ts** Adds the recovery pairing invoke without growing the ratcheted shared API file. **desktop/src/testing/e2eBridge.ts** Mocks recovery pairing commands and lifecycle events for browser tests. **desktop/tests/e2e/identity-lost.spec.ts** Covers lost-identity entry, QR/copy recovery, SAS, cancellation, expiry, success, errors, backup import, drag-and-drop, and screenshots. **desktop/tests/e2e/onboarding.spec.ts** Verifies recovered identities continue through harness setup without replacement-key side effects. **mobile/lib/features/pairing/pairing_page.dart** Adds recovery-only scanning and explicit identity-handoff warnings. **mobile/lib/features/pairing/pairing_provider.dart** Recognizes recovery codes, returns the signed-in nsec after mutual SAS approval, and waits for desktop completion. **mobile/lib/features/settings/settings_page.dart** Accepts the recovery route builder at the app composition boundary to preserve feature isolation. **mobile/lib/features/settings/settings_page/connection_section.dart** Adds the signed-in “Send identity to desktop” settings action. **mobile/test/features/pairing/pairing_page_test.dart** Covers recovery-only validation and handoff messaging. **mobile/test/features/pairing/pairing_provider_test.dart** Covers reverse payload encryption, confirmation ordering, success, failure, timeout, and cleanup. </details> ## Reproduction steps 1. Launch Buzz Desktop with identity-lost state and choose **Recover from your phone**. 2. Confirm the QR and persistent **Copy pairing code** fallback appear without layout shift. 3. On a signed-in phone, open **Settings → Send identity to desktop**, scan or paste the recovery code, and compare the six-digit SAS on both devices. 4. Confirm on both sides and verify Desktop restores the identity and continues to harness setup. 5. Repeat from identity-lost state with **Recover from a backup file**; verify picker and drag-and-drop both advance to password entry and restore the encrypted backup. 6. Exercise cancellation, mismatched/unsupported codes, expired sessions, and an invalid backup; verify each returns actionable, non-stuck UI. ## Screenshots ### Desktop phone recovery — complete flow | Recovery entry | Pairing QR | Code match | Receiving identity | |---|---|---|---| |  |  |  |  | ### iOS Simulator — complete handoff flow | Settings entry | Recovery scanner | Manual recovery code | Code confirmation | |---|---|---|---| |  |  |  |  | ### Encrypted backup recovery — adjusted file flow | File picker | Drag-and-drop target | Password step | |---|---|---| |  |  |  | ## Verification - `cargo test -p buzz-core pairing` — 71 passed - `just mobile-test` — 1,169 passed - `pnpm build:e2e && pnpm exec playwright test identity-lost.spec.ts --project=smoke` — 15 passed - Full pre-push gates — desktop checks, desktop unit tests, Rust tests, Tauri checks, and mobile tests passed --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
6ca9641a95 |
Refine agent runtime controls (#5026)
## Summary - replace ambiguous avatar play controls with centered Start and Restart pills - preserve avatar clipping while smoothly morphing actions into the running status dot - use accessible warning contrast and real restart behavior without a duplicate status badge ## Validation - `just ci` - focused Playwright coverage for morphing, shared geometry, and light/dark contrast Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
9213090f60 |
test(desktop): await thread scroll anchor (#3174)
## Why The focus/split E2E test could capture the thread root before its programmatic middle-thread scroll had settled, then incorrectly report a scroll-restoration failure. ## What - Poll until the requested middle-thread scroll position is applied - Require the captured anchor to intersect the thread viewport and differ from the root - Preserve the existing focus-to-split-to-focus viewport assertions ## Risk Assessment Low — test-only synchronization change with no production behavior changes. ## References - Original failure: https://github.com/block/buzz/actions/runs/30231271427/job/89870533541 - Buzz thread: buzz://message?channel=12dd513d-45fd-48ff-80ac-8596d2fcc9d3&id=87ce6024b4bf74bfac2fa75d9f7bbbcc8f8fe2df460afe534152c495929f51ba - Reproduced confidence: 20 consecutive targeted passes, full spec pass, `just desktop-ci`, and `just ci` Generated with Codex Signed-off-by: npub1x4hk035p3p9q39a3fcrd2fe30lpkrhr5dwe0cqzzjphxyyh8m0gsq4vqap <356f67c681884a0897b14e06d527317fc361dc746bb2fc0042906e6212e7dbd1@buzz.block.builderlab.xyz> Co-authored-by: npub1x4hk035p3p9q39a3fcrd2fe30lpkrhr5dwe0cqzzjphxyyh8m0gsq4vqap <356f67c681884a0897b14e06d527317fc361dc746bb2fc0042906e6212e7dbd1@buzz.block.builderlab.xyz> Co-authored-by: Wes <wesbillman@users.noreply.github.com> |
||
|
|
480c41ebf1 |
Improve desktop mobile pairing flow (#5024)
## Summary - add stable three-step guidance to desktop mobile pairing - move code confirmation inline and show animated completion states - preserve pairing reset behavior and reduced-motion support ## Test plan - `pnpm --dir desktop check` - `pnpm --dir desktop exec tsc --noEmit` - `pnpm --dir desktop exec playwright test tests/e2e/mobile-pairing-qr.spec.ts --project=smoke` - pre-push desktop suite: 4,387 tests passed --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Fizz <50a12680c76f1a52c0b7af8dbb17e02c583227c290fb93b9a3defb456114223f@buzz.block.builderlab.xyz> |
||
|
|
5babb97ca3 |
feat(desktop): show selected community in rail (#5000)
## Summary - add a persistent vertical pill beside the active community - keep the selected state visually distinct from unread dots and mention badges - preserve the existing `aria-current` selection semantics ## Screenshot  ## Test plan - `pnpm exec biome check src/features/sidebar/ui/CommunityRail.tsx tests/e2e/community-rail.spec.ts` - `pnpm test` (4,387 passed) - `pnpm build:e2e && pnpm exec playwright test tests/e2e/community-rail.spec.ts --project=smoke` (20 passed) - pre-push hooks: desktop check and 4,387 desktop tests passed on `c1e80c66d12f73c4eb5c03a19e932439b14caf2d` Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
19b41e9c8e |
fix(desktop): stop rate-limited reconnect backfill from tearing down the authenticated socket (#4990)
## Problem Users on v0.5.5 report "Can't reach the relay" toggling with brief "connected" flashes (field reports; also the macOS confirmation in #4908). #4737 closed the stuck-reconnect gaps; this is the opposite failure: the client redials fine, but then kills its own healthy socket. Mechanism (all on `main`): 1. AUTH succeeds → session emits `connected` (`relayClientSession.ts:583`), then awaits `replayLiveSubscriptions()`. 2. Paged channel backfill issues history REQs (`relayReconnectReplay.ts`, page limit 500). 3. A `CLOSED rate-limited:` on a **history** REQ arms the rate-limit gate but still rejects the history promise (`relayClosedRecovery.ts:38-51`). 4. The rejection escapes `replayLiveSubscriptions()` → `resetConnection()` tears down the authenticated socket. 5. Reconnect → AUTH OK → replay rate-limited again → loop. Each iteration re-spends the rate-limit budget, so the loop is self-sustaining. ## Fix Contain backfill failures inside the replay. Each subscription's paged backfill now retries behind the rate-limit gate up to `PAGE_REPLAY_MAX_ATTEMPTS` (3), then degrades to live-only **for this connection**. Socket health no longer depends on backfill success. Nothing is lost: the replay cursor (`lastSeenCreatedAt`) only advances on delivered events, so the next reconnect replays the same missed window. ## Red/green proof - Commit 1 (Pinky): e2e injecting `CLOSED rate-limited:` into the mid-replay history REQ — **red on main** (expected 1 reconnect dial, observed 2; connected-flash then teardown). - Commit 2 (this fix): same test **green unchanged** — one dial, state stays `connected` through the rate-limit hint plus the next backoff window. Why existing coverage missed it: the prior rate-limit e2e pre-armed the gate *before* replay (replay politely waits), and the CLOSED-injection test targeted a *live* subscription (which has its own retry path). Nobody injected back-pressure from the history REQ itself. ## Verification - `pnpm test`: 4374/4374 pass. - `playwright test tests/e2e/relay-reconnect.spec.ts`: 14/14 pass, including the new spec. - `tsc --noEmit` clean; Biome clean on touched files (pre-existing warnings on main in `personaCatalogRelay.test.mjs` / `terminal.css` untouched). ## Not addressed here (follow-ups from the same field reports) - AUTH terminal latch is too aggressive for relay-internal `error:` rejections (3 strikes during a relay bad window → stuck until click/relaunch; #4908). - Server-side: `relay.drainJitterMs` (#4542) defaults to 0 — enabling it on the hosted relay removes the deploy thundering herd that triggers these rate-limit storms. --------- Signed-off-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@buzz.block.builderlab.xyz> Signed-off-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz> Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@buzz.block.builderlab.xyz> Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz> |
||
|
|
96ae141763 |
fix(desktop): skip native notifications outside app bundles (#5004)
## Summary - require the macOS process to be running from an actual `.app` bundle before initializing `UNUserNotificationCenter` - keep the existing bundle-identifier requirement - cover packaged, case-insensitive `.app`, raw `target/debug`, and extensionless paths ## Why PR #4799 guarded native notification initialization with `NSBundle.mainBundle.bundleIdentifier != nil`. Tauri embeds a bundle identifier in raw development executables, so `tauri dev` passed that guard and `UNUserNotificationCenter.current()` raised an uncaught `NSInternalInconsistencyException` because LaunchServices had no bundle proxy. ## Validation - focused macOS notification tests: 6 passed - direct raw debug executable no longer raises the notification-center exception - pre-commit formatting hook passed - pre-push package checks passed on pushed commit `f29a6664d2a863e7b8aa527f6149fd00b183e4de` The first push attempt hit an unrelated timing-test failure in `relay_admission::tests::concurrent_429_extends_the_window_for_parked_waiters`; its focused rerun passed, and the complete pre-push package suite passed on the next push. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
e2796d4a89 |
fix(desktop): virtualize channel member lists (#4991)
## Summary - virtualize the unfiltered channel member roster instead of eagerly mounting every member card - retain the existing member search/add flow and archived-member behavior - cover a 500-member roster, bounded mounted rows, and scrolling to the final member in E2E ## Cause The members sidebar rendered every active member card at once. On large channels this mounted hundreds or thousands of avatars, profile/presence consumers, menus, and DOM rows, blocking the renderer even though fetching the roster itself is fast. ## Testing - `pnpm typecheck` - `pnpm exec biome check src/features/channels/ui/MembersSidebar.tsx tests/e2e/channels.spec.ts` - `pnpm build:e2e` - `pnpm exec playwright test tests/e2e/channels.spec.ts --grep 'members sidebar (virtualizes large channel rosters|can invite relay-authorized agents|can invite and remove managed agents|collapses same-persona managed agents)'` (4 passed) - pre-push: `desktop-check`, full `desktop-test` (4,371 passed), branch-skew Implemented by Carl on Wes's behalf. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
16cc3de6d6 |
fix(desktop): enforce owner-only access in internal builds (#4053)
## Problem Managed agents in internal Buzz builds should answer only their owner. Previously, an agent could keep a broader access setting and respond to other people, which did not match the access policy for internal builds. This PR makes owner-only access effective for every managed agent in internal builds and makes that restriction clear in the Desktop UI. Open source builds remain configurable. ## Changes - Enforce owner-only access when any managed agent starts or is deployed from an internal build. - Show the agent access control as locked to **Only me** in Desktop, with an explanation of why it cannot be changed. - Keep Welcome teammates working under the same rule without triggering unnecessary restarts. - Leave open source build behavior unchanged. This changes effective runtime access without rewriting stored or relay-advertised settings. The companion [#4064](https://github.com/block/buzz/pull/4064) explains the restriction in-thread when someone without access mentions an agent. The enforcement will remain inactive in shipped builds until [squareup/buzz-releases#74](https://github.com/squareup/buzz-releases/pull/74) marks internal releases during the build. ## Screenshots | Before | After | | --- | --- | |  |  | ## Tests Added coverage for: - Runtime enforcement for [locally run agents](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/managed_agents/runtime/tests.rs#L196) and [deployed agents](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/commands/agents_tests.rs#L510). - The [current-build deployment path](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/commands/agents_tests.rs#L455), [invalid stored access](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/managed_agents/access_policy.rs#L98), and the [local startup guard](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/managed_agents/env_vars/tests.rs#L149). - Consistent enforcement across [both agent backends](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/managed_agents/access_policy.rs#L112). - Welcome teammates created as [locally run](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src/features/onboarding/welcomeGuide.test.mjs#L384) or [deployed](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src/features/onboarding/welcomeGuide.test.mjs#L393) agents, including [access-only](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src/features/onboarding/welcomeKickoff.test.mjs#L202) and [runtime-related](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src/features/onboarding/welcomeKickoff.test.mjs#L225) restart behavior. The full Desktop Rust and JavaScript suites, type checks, formatting, clippy, and file-size checks passed. Playwright E2E was not run. --- Originated from Buzz channel [buzz-agent-control](buzz://channel?id=cf5dada7-e26a-4887-ae41-b3bd5f42d3b2). Supersedes #2537. --------- Signed-off-by: Tom Brow <tomb@block.xyz> Signed-off-by: npub1tquskdu6yc4h8l7xxtceculxw600grekeq0xg2ukqfrwl7vrzg3quz3gmp <58390b379a262b73ffc632f19c73e6769ef40f36c81e642b960246eff9831222@buzz.block.builderlab.xyz> Co-authored-by: npub1tquskdu6yc4h8l7xxtceculxw600grekeq0xg2ukqfrwl7vrzg3quz3gmp <58390b379a262b73ffc632f19c73e6769ef40f36c81e642b960246eff9831222@buzz.block.builderlab.xyz> Co-authored-by: Amp <amp@ampcode.com> |
||
|
|
5677e4ca05 |
test(desktop): match attachment button label (#4993)
**Category:** fix **User Impact:** Pull requests can once again pass the Desktop smoke test suite. **Problem:** The inbox attachment-edit smoke test still looked for the composer's former “Attach image” label after the shared action was renamed to “Attach file,” causing shard 3 and the aggregate Desktop CI job to fail on every PR. **Solution:** Update the stale accessible-name selector to match the current composer control while preserving the test's media-tag coverage. <details> <summary>File changes</summary> **desktop/tests/e2e/inbox-edit.spec.ts** Updates the attachment button selector to use the current accessible label so the existing attachment-edit regression test reaches the behavior it is meant to verify. </details> ## Reproduction steps 1. Build the Desktop E2E application with `pnpm -C desktop build:e2e`. 2. Run `cd desktop && pnpm exec playwright test --project=smoke tests/e2e/inbox-edit.spec.ts -g "editing an immediate attachment reply preserves its media tags"`. 3. Confirm the test locates the “Attach file” control and passes. Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
4da7264d90 |
fix(acp): pace observer telemetry at 1/s with per-channel batch envelopes (#4917)
## Problem Observer telemetry is the noisiest client of the relay: the old pacer (167ms spacing + 90/min rolling cap) let a busy session bill up to 6 events/second against the owner's message quota, and the rolling cap silently *dropped* frames once exceeded. Ruling from the rate-limiting investigation thread (channel `826fc99b-1472-40e7-a529-6b9db8943b8c`): pace at 1/s, always emit, minimal PR. **Review round 1 (Max, Sami)** found the first cut wrong in three ways — tick burst (all pending frames per tick), startup burst (`interval` fires at t=0), and per-channel quota arithmetic. All fixed and mutation-verified in round 1. **Review round 2 (Sami, Max)** found two more against the round-1 head: 1. **Drain-rate collapse (Sami, blocker):** front-run-only packing meant a frame held ONE event whenever channels interleaved — measured 275 B/s vs 63.5 KB/s, so an ordinary 2-channel session fell minutes behind with zero drops and no warning. Silent unbounded latency. 2. **Coalescer byte-cap bypass (Max):** chunks pending in `ObserverChunkCoalescer` were unbounded and outside the 4 MiB cap — 500 distinct-`messageId` 50KB chunks retained ~48MB with `pending_bytes == 0` and zero drops. **Review round 3 (Max)** found the drop accounting undercounted merged chunks: a coalescer entry that merged N same-`messageId` chunks counted as **1** in `dropped_events` when evicted (50 merged 1KB chunks evicted → counter read 1, 49 generated events unaccounted). Fixed: accounting is now denominated in **source (generated) observer events** end to end — each pending entry tracks how many chunks it absorbed, eviction charges that count, and the count survives flush into the publish FIFO. **Review round 4 (Sami, Max)** found three more against the round-3 head: 1. **Coalescer byte undercount (both, independently):** a pending merged entry retains its first chunk's text **twice** until flush — once inside the serialized event skeleton and once in the extracted text accumulator — but was charged only `serialized_len`, so true retention overshot the 4 MiB cap ~2× (measured 8.3 MB). Fixed: `push_pending` charges `serialized_len(&event) + text.len()`. 2. **Cap regressions asserted the accumulator against itself,** which is how the undercount hid. All three cap tests now assert on independently **walked** retained bytes (`serialized_len` per FIFO entry + `serialized_len + text.len()` per coalescer entry), with a secondary `accumulator >= walked` sanity check. Reverting the fix makes them fail at exactly 8,328,386 / 8,328,272 bytes. 3. **FIFO-arm source accounting was implemented but untested (Sami M13; Max reproduced at `cc9333b7c` with 102/151):** the round-3 regression only evicted a merged entry while still in the coalescer. New test forces a merged entry (50×1KB, `source_events=50`) through flush into the publish FIFO, then evicts it from there — mutating the FIFO eviction to `dropped += 1` fails with the reviewers' exact numbers (102 vs 151). **Review round 5 (Sami 9/9/9, Max 9/9/9)** — production judged merge-safe by both; remaining items are tests only, all landed at `63d821620`: 1. **The walker instrument was itself unverified (Sami M17–M20; Max independently confirmed the `return 0` mutant survives):** every cap test asks `walked_retained_bytes()` only for `<= CAP`, so a blinded walker passes everything — and paired with a reverted `push_pending` fix the two mutations cancel, hiding exactly the 8.3 MB overshoot it exists to detect. New two-sided pin: the walker must SEE the first chunk's text twice, and must agree with the accumulator EXACTLY while both stores are non-empty. Kills M17, M18, M19, M20. 2. **Two pre-existing snapshot-clone siblings (Sami D5b/D5c; byte-identical at merge-base `7334ad1e1` — not this PR's regression, but the PR made the class visible):** aliasing the inner turns map leaks a post-save turn into the snapshot; aliasing the inner tombstones map leaks a post-save terminal that blocks a legitimate post-restore resurrection. Two isolation tests with in-test controls — all three inner-map clones in `saveActiveAgentTurnsForCommunity` are now pinned. ## Change **Harness (`crates/buzz-acp`)** - **Global pacer: AT MOST ONE relay frame per second**, regardless of channel count or backlog size. `interval_at(now + 1s)` restores the no-startup-burst property; `MissedTickBehavior::Skip` is now pinned by a paused-time test (a stalled tick arm fires one catch-up frame, not one per missed deadline). At 1 frame/s telemetry spends ≤60/min of the shared 120/min quota; `OBSERVER_PUBLISH_TICK` documents the tradeoff as the knob. - **`ObserverPublishQueue` with gather-packing:** events wait as byte-accounted events (FIFO). `next_frame()` packs the front event's channel **gathered queue-wide in FIFO order** — frames never mix channels, and each channel's events keep their FIFO order, but cross-channel frame order MAY differ from arrival order. That is what keeps the drain rate in **bytes per slot** (one ~64KB frame/s) instead of front-run-length events per slot. **Null-channel events (`agent_panic`-class) are packing barriers** nothing gathers across, so causally-global events keep exact order against every channel. - **One byte cap over BOTH stores:** the event FIFO and the coalescer's pending chunk buffer count against the 4 MiB budget together; eviction is oldest-first across both (queue front, then coalescer front — structural age order) with accounting (warn + counter). A high-cardinality chunk flood is bounded exactly like a plain event flood. Coalescer entries are charged their **true** retention (`serialized_len + text.len()` — the first chunk's text lives in both the serialized skeleton and the extracted accumulator until flush). - **Shutdown is not a burst bypass:** paced one-frame-per-tick drain until empty. **Desktop** - `unwrapObserverBatch` expands envelopes on the live relay path and archive-ingest seam (round 1, unchanged). - **`activeAgentTurnsStore` watermark re-keyed per (agent, channel)** with a dedicated null-channel bucket: the per-agent `(timestamp, seq)` gate would silently skip a delayed channel's frames as stale under gather-packing's intentional cross-channel reorder. Safe because every turn-mutating path is channel-scoped by the event's own `channelId` (endTurn's null-turnId fallback matches `turn.channelId`; resurrectTurn keys on `event.channelId`), so per-channel serialization preserves each guard the per-agent gate provided. The tombstone-cap justification is rewritten for the new keying (worst case for an evicted tombstone is a ghost badge the prune reaps — bounded cosmetic staleness, not corruption). Community-switch save/restore deep-clones the nested map. Other per-agent maps stay agent-keyed: the clock offset is a running minimum (order-insensitive); turns/tombstones mutate only through channel-scoped paths. ## Version skew — old desktop + new harness Gather-packing *intentionally* emits cross-channel-reordered frames. An **old desktop** (per-agent watermark) against a **new harness** will silently skip a delayed channel's turn-state events as stale — working badges on that channel can go stale/missing until its next fresh event. Transcript and archive are unaffected (the transcript store sorts + rebuilds on out-of-order arrival; the archive is per-channel by construction). Ship desktop and harness together; skew degrades badges only, not data at rest. ## Throughput ceiling — "lossless" is qualified Sustained lossless rate is what fits in one ~64KB frame per second, now genuinely in bytes under interleaving: | event payload | events per frame | sustained ceiling | |---|---|---| | 100 B | 250 | 250 ev/s | | 500 B | 99 | 99 ev/s | | 2 KB | 30 | 30 ev/s | | 10 KB | 6 | 6 ev/s | With C channels producing concurrently, publish slots round-robin between them: per-channel drain is ~64KB/C per second and the 4 MiB burst budget (~64s single-channel) shortens accordingly. Beyond budget, oldest-first drops **with accounting** — visible, designed loss. **Accounting semantics:** `dropped_events` counts SOURCE (generated) observer events, not retained entries — evicting a coalesced entry that merged N chunks charges N. On the published side, a merged entry ships all N sources' text in ONE event, so the reconciliation invariant is `ingested == dropped_events + Σ source_events over published events` (for unmerged events, source_events = 1). ## Verification At `63d821620d3513505e8766ac691a8002f9d4a96f` (this head; `git rev-parse HEAD` matched in the same shell as every run), rustc 1.95.0: - `cargo test -p buzz-acp`: **689 lib + 9 integration, 0 failed** — regressions: interleaved 2-channel drain packs into ≤4 frames not 200 slots; null-channel barrier; queue-wide gather with within-channel FIFO; distinct-key 50KB chunk flood bounded by the cap with event-level accounting (published + dropped == ingested, survivors newest); paused-time `MissedTickBehavior::Skip` pin (verified to fail under `Burst`: 3 frames vs 1); merged-key eviction accounts every absorbed source chunk in BOTH arms — coalescer-side (Max's round-3 probe) and post-flush FIFO-side (Sami M13 / Max's round-4 probe: fails 102 vs 151 under `+= 1`). All three cap tests assert on independently walked retained bytes, not the accumulator (verified to fail without the `+text.len()` fix: 8,328,386 / 8,328,272 vs 4 MiB); the walker itself is pinned two-sided against the accumulator (all four blinding mutants M17–M20 verified to fail it, including the walker+fix cancellation pair). - `cargo clippy -p buzz-acp --all-targets -- -D warnings` clean, `cargo fmt --check` clean - Desktop: `tsc --noEmit` clean; node tests **4366 passed, 0 failed** — snapshot-clone family fully pinned: watermark aliasing (round 4), turns aliasing and tombstone aliasing (round 5, pre-existing gaps; each mutant verified to fail exactly its target test with an in-test control). Prior rounds: cross-channel reorder processed, cross-channel-delayed null-turnId `turn_error` evicts only its own channel's turn, null-bucket replay idempotency, same-channel stale/duplicate still skipped, watermark survives community-switch save/restore - All pre-push hooks green at the pushed commit (branch-skew, desktop-check, desktop-test, rust-tests, desktop-tauri-checks) Part of the rate-limiting fix stack; independent of `eva/rate-limit-fixes` by design (separate minimal PR per Tyler's ruling). --------- Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Signed-off-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz> Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz> |
||
|
|
a7ea86cdcf |
fix(desktop): enable the content security policy (#4614)
This change enables a Tauri content security policy that limits executable content to the packaged application and does not allow inline scripts. Relay, media, asset, and Tauri IPC schemes remain available for desktop compatibility. The policy contains the impact of a future renderer injection; it does not itself remove an injection bug. ## Testing - `git diff --check origin/main...codex/security-desktop-csp` - Rebased onto `origin/main` at `5c98932` - Full CI pending Originating Buzz thread: `buzz://message?channel=3928fe05-df61-4b5d-b9c7-d623b9b10ea1&id=3c6c02312f763fbe0d2bfc33a6c1a362f91d0354f3d18b039cf7a0558c1439d1` --------- Signed-off-by: Jordan Mecom <jm@squareup.com> Signed-off-by: Eli Foster <efoster@squareup.com> Co-authored-by: Eli Foster <efoster@squareup.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
eb6a37569d |
fix(desktop): enable message editing in Inbox (#2198)
### What changed? Inbox detail now gives the current user's messages the same ownership-gated Edit action as channel view. Editing reuses the existing composer and mutation flow, preserves attachment metadata, and refreshes structural overlays so the edited content appears immediately. Foreign authors' messages remain non-editable, including grouped Inbox conversations whose selected event is not the representative item. | Own Inbox message exposes **Edit message**. | Saving the edit updates the Inbox detail immediately. | | --- | --- | |  |  | ### Why? Inbox rows did not pass an edit handler into the shared message action bar, so a user's own messages could be edited from channel view but not from Inbox detail. ### How is it tested? Desktop checks, unit tests, and the full local CI gate passed. The focused Inbox Playwright regression passed 3 consecutive runs and covers current-user edit/save, foreign and archived-channel denial, and attachment preservation when a just-sent reply is edited before its relay echo arrives. Added tests: - [`inbox-edit.spec.ts`](https://github.com/block/buzz/blob/inbox-message-edit-action/desktop/tests/e2e/inbox-edit.spec.ts) - [`inboxViewHelpers.test.mjs`](https://github.com/block/buzz/blob/inbox-message-edit-action/desktop/src/features/home/lib/inboxViewHelpers.test.mjs) *🤖 This PR was authored [with an agent](buzz://message?channel=7f2d7e02-f4d5-4fb0-a426-0ca60ed3a1c3&id=c09ee04d18399b90296c3f932d22ab0377fa05f7e690ec7b08c36483ee633fbb).* --------- Signed-off-by: Tom Brow <tomb@block.xyz> Signed-off-by: npub1ft62tztwwm2x9xamk25smmuaj4sfckdkldksruf2x2jwqalffkrq0g7arr <4af4a5896e76d4629bbbb2a90def9d95609c59b6fb6d01f12a32a4e077e94d86@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1ft62tztwwm2x9xamk25smmuaj4sfckdkldksruf2x2jwqalffkrq0g7arr <4af4a5896e76d4629bbbb2a90def9d95609c59b6fb6d01f12a32a4e077e94d86@sprout-oss.stage.blox.sqprod.co> |
||
|
|
005fe54d02 |
fix(desktop): outline the selected community (#4969)
**Category:** improvement **User Impact:** Selected communities now use a clear offset outline without tinting or covering their icon. **Problem:** The selected community state replaced the icon surface with an accent fill, obscuring image icons and changing the tile's content treatment. Hover also changed the fill, text color, shape, and opacity, making navigation states visually jumpy. **Solution:** Preserve each community tile's neutral surface and content while using a primary CSS outline for selection and a lighter outline for hover. The transparent outline offset leaves the space around image edges unpainted, and adjusted spacing prevents neighboring outlines from colliding. <img width="200" height="152" alt="Screen Recording 2026-08-05 at 3 23 32 PM" src="https://github.com/user-attachments/assets/5c25b1c0-4be8-41c4-8f1d-ad0010310c92" /> <details> <summary>File changes</summary> **desktop/src/features/sidebar/ui/CommunityRail.tsx** Replaces selected and hover fills with offset outlines, keeps icon presentation stable across states, and adjusts rail and tooltip spacing for the new outline geometry. **desktop/tests/e2e/community-rail.spec.ts** Covers the shared active/inactive surface, radius, text color, opacity, and outline behavior, including hover invariants. </details> ## Reproduction steps 1. Run the desktop app with two or more communities. 2. Give the active community an image icon. 3. Confirm the active icon keeps its original image and receives a 2px primary outline with a transparent 2px gap. 4. Hover another community and confirm only a lighter outline appears; its fill, text color, opacity, and corner radius remain unchanged. 5. Switch communities and confirm the outline follows the active community. ## Screenshots **Full desktop context**  --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
24c7995740 |
fix(desktop): clamp thread panel to channel surface (#4965)
**Category:** fix **User Impact:** Expanded thread panels now stay fully visible within the desktop channel area instead of being cut off. **Problem:** The resize handler clamped the thread panel against the full window width, even though the panel renders inside a narrower channel surface. On a 1720px window, this allowed a 1160px requested width where only 1111px could render, leaving persisted and visible geometry out of sync. **Solution:** Clamp resizing against the measured channel-surface width so the stored width matches what the layout can render while preserving the minimum 300px main pane. <details> <summary>File changes</summary> **desktop/src/features/channels/ui/ChannelScreen.tsx** Passes the measured channel-surface width into the thread-panel sizing hook. **desktop/src/shared/hooks/useThreadPanelWidth.ts** Clamps drag-resize updates against the available channel width instead of the full viewport. **desktop/tests/e2e/threadpane-ultrawide.spec.ts** Adds a 1720px regression proving the requested and rendered panel widths match, while retaining the ultrawide expansion case. </details> ### Reproduction steps 1. Open a channel thread in the desktop app at a 1720×900 window size. 2. Drag the thread panel's left resize handle toward the left edge to expand it as far as possible. 3. Confirm the panel remains fully bounded inside the channel surface and the main channel pane remains at least 300px wide. 4. Reload the channel and confirm the persisted expanded width renders without clipping. ### Testing - `pnpm --dir desktop build:e2e` - `pnpm --dir desktop exec playwright test tests/e2e/threadpane-ultrawide.spec.ts` — 2 passed - Push hooks: `desktop-check` and `desktop-test` passed - `git diff --check origin/main..HEAD` ### Screenshot  ### Related issue None found. Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
cda33978b1 |
style(messages): increase username contrast (#4948)
**Category:** improvement **User Impact:** Message usernames are now bolder, making it easier to distinguish who said what at a glance. **Problem:** Usernames and surrounding message metadata had too little visual separation, which made message headers slower to scan. **Solution:** Increase the shared message-author label from semibold to bold while preserving its existing size, spacing, and interaction behavior. <details> <summary>File changes</summary> **desktop/src/features/messages/ui/MessageHeader.tsx** Raises the shared message-author font weight so standard and system message usernames gain consistent visual contrast. </details> ## Reproduction steps 1. Open a channel containing messages from multiple people or agents. 2. Compare each message username with its timestamp and message body. 3. Confirm the username renders in bold while the surrounding typography and layout remain unchanged. ## Screenshots | Before | After | | --- | --- | |  |  | Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
d42d60d64e |
fix(desktop): rename generic attachment action from 'Attach image' to 'Attach file' (#2381) (#4304)
Fixes #2381. ## What was broken The message composer's paperclip accepts generic attachments — images, videos, PDFs, archives, and any other supported file — but its tooltip and accessible name still read **"Attach image"**. Sighted users might reasonably believe the control is image-only, and screen-reader users get an incomplete description of what the button does. ## The fix Rename the accessible name and tooltip text on the generic composer paperclip in `MessageComposerToolbar.tsx`: - `aria-label` — `"Attach image"` → `"Attach file"` - `<TooltipContent>` — `"Attach image"` → `"Attach file"` Plus update the 12 affected Desktop e2e selectors across five spec files to reference the new accessible name: - `desktop/tests/e2e/file-attachment.spec.ts` (2 selectors) - `desktop/tests/e2e/spoiler.spec.ts` (2) - `desktop/tests/e2e/composer-image-draw.spec.ts` (2) - `desktop/tests/e2e/image-attachment-gallery.spec.ts` (4) - `desktop/tests/e2e/video-attachment.spec.ts` (2) ## Scope (per the issue) The feedback screenshot dialog (`desktop/src/features/settings/ui/SendFeedbackDialog.tsx`) is **unchanged** — that dialog itself is image-only, so its "Attach image" wording is accurate. This PR only touches the generic composer control. ## Test plan - All **105** unit tests in `desktop/src/features/messages/ui/*.test.mjs` pass locally. - Verified no remaining `"Attach image"` string outside the intentionally preserved feedback dialog: ```sh grep -rn '"Attach image"' desktop/ # → only hits in SendFeedbackDialog.tsx ``` - The six e2e specs are only exercised in CI; the selector updates are mechanical and verified by grep to reference the new a11y name. ## Blast radius - **Files touched**: `MessageComposerToolbar.tsx` (two strings); five e2e spec files (12 selector updates). - **User-facing behaviour**: one tooltip + one screen-reader name change; no functional or visual changes otherwise. - **No API or state change.** ## Out of scope - The feedback dialog's "Attach image" wording — kept per the issue's own "Scope" guidance. - Any i18n plumbing — Buzz Desktop doesn't currently localize these strings. Signed-off-by: Sarthak Singh <sarthak.singh@juspay.in> Signed-off-by: Ravneet Arora <rarora@squareup.com> Co-authored-by: Ravneet Arora <rarora@squareup.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
2ea9385015 |
fix(reactions): support max-length custom emoji (#3833)
**Category:** fix **User Impact:** Custom emoji with valid 64-character names can now be used as reactions without errors. **Problem:** Buzz accepted 64-character custom emoji names during registration, but rejected them as reactions after the required surrounding colons made the payload 66 characters. Validation also differed between desktop, SDK, relay, and storage boundaries. <img width="554" height="47" alt="image" src="https://github.com/user-attachments/assets/4013452f-210e-4dd3-9003-f45ff3b28dc8" /> **Solution:** Keep the product limit at 64 ASCII characters for custom emoji names, enforce it consistently when emoji sets are registered, and allow only valid matching custom reaction payloads up to 66 characters. Widen the reaction projection to preserve the wrapped payload while retaining the existing 64-character limit for ordinary reactions. <details> <summary>File changes</summary> **crates/buzz-sdk/src/builders.rs** Defines the shared custom emoji boundaries and covers accepted 64-character and rejected 65-character shortcodes. **crates/buzz-relay/src/handlers/ingest.rs** Validates emoji-set shortcodes and permits 66-character reactions only when they are valid colon-wrapped custom emoji with a matching tag. **crates/buzz-db/src/event.rs** Adds storage regression coverage for maximum-length custom emoji reactions. **crates/buzz-db/src/migration.rs** Verifies the reaction column migration is applied correctly. **desktop/src/shared/api/customEmoji.ts** Enforces the existing 64-character shortcode maximum during desktop normalization and registration/import. **desktop/src/shared/api/customEmoji.test.mjs** Covers the desktop shortcode boundary. **migrations/0027_long_reaction_payloads.sql** Widens stored reaction payloads to 66 characters for the two required surrounding colons. **schema/schema.sql** Keeps the desired schema aligned with the migration. </details> ## Reproduction Steps 1. Register or import a custom emoji whose ASCII shortcode is exactly 64 characters. 2. Select that emoji as a reaction to a message. 3. Confirm the reaction publishes, persists, and renders without an error. 4. Attempt to register a 65-character shortcode and confirm it is rejected. 5. Publish an ordinary or malformed reaction over 64 characters and confirm the relay rejects it. ## Verification - `cargo test -p buzz-sdk`: 243 passed - `cargo test -p buzz-db`: 94 passed, 152 Postgres-required tests ignored - `pnpm test` in `desktop`: 3,859 passed - `cargo test -p buzz-relay`: 795 passed, 9 existing Postgres-unavailable failures, 35 ignored; new reaction boundary tests pass directly - `cargo fmt --all -- --check` - `git diff --check` Originating Buzz channel: `f2ec9671-d78e-4cde-894c-9f4c458c7f1f` --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> |
||
|
|
719f9730d4 |
feat(desktop): allow leaving your final community (#3621)
**Category:** improvement **User Impact:** People can leave their final Buzz community and return to **Join or create a community** without losing their signed-in identity. **Problem:** Buzz Desktop blocked people from leaving when only one community remained. Its existing remove action also changed local configuration without ending relay membership. **Solution:** Allow the final community to be left. Buzz now asks the relay to end membership, removes the community locally only after acceptance, and returns the person to the community selector while keeping their identity signed in. If other communities remain, Buzz switches to one of them. Relay rejection or timeout keeps the community in place and shows an actionable retry error. <details> <summary>File changes</summary> **desktop/src/features/communities/leaveCommunity.ts** Adds signed kind 28936 publishing for active and inactive community relays with actionable timeout handling. **desktop/src/features/communities/leaveCommunity.test.mjs** Covers event shape, relay selection, acceptance gating, rejection, timeout messaging, and cleanup. **desktop/src/features/communities/useCommunities.tsx** Allows final-community removal and clears community-specific storage without touching identity. **desktop/src/features/communities/resolveCommunityRemoval.test.mjs** Covers final, active, and inactive community removal state transitions. **desktop/src/app/useCommunityNavigationTransitions.ts** Gates local removal on relay acceptance and routes to a fallback community or setup selector. **desktop/src/app/AppShell.tsx** Passes the asynchronous leave operation through shell entry points. **desktop/src/features/communities/ui/EditCommunityDialog.tsx** Replaces the local-only remove action with a pending-aware Leave Community action that retains actionable errors. **desktop/src/features/communities/ui/CommunitySwitcher.tsx** Enables leaving the final community and carries the asynchronous callback. **desktop/src/features/sidebar/ui/AppSidebar.tsx** Carries the asynchronous leave callback through sidebar props. **desktop/src/features/sidebar/ui/CommunityRail.tsx** Enables leaving the final community from rail settings. **desktop/src/features/sidebar/ui/SidebarProfileCard.tsx** Carries the asynchronous leave callback through profile community settings. **desktop/src/testing/e2eBridge.ts** Teaches the mock relay to accept NIP-43 leave events. **desktop/tests/e2e/community-rail.spec.ts** Updates leave interactions and verifies final-community setup navigation, storage cleanup, and identity preservation. </details> ### Reproduction steps 1. Run Buzz Desktop with a signed-in identity and one joined community. 2. Open Community settings and choose **Leave Community**. 3. Confirm the app shows **Join or create a community** and the existing identity remains signed in. 4. Repeat with two communities and confirm leaving the active one switches cleanly to the remaining community. 5. Reject or withhold the relay `OK` response and confirm the community remains configured with an actionable error in the dialog. ### Test plan - `pnpm check` - `pnpm build` - `pnpm test` (3,913 passing) - `pnpm build:e2e && pnpm exec playwright test tests/e2e/community-rail.spec.ts --grep "final community"` <img width="557" height="316" alt="image" src="https://github.com/user-attachments/assets/b628182f-cba5-451d-ae4b-bee8d8dd19aa" /> --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: npub14ndfusear8wdpe4kss8h7juc7wjk78atnqzf63zvppcpneknv4sq6x9370 <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> Co-authored-by: npub14ndfusear8wdpe4kss8h7juc7wjk78atnqzf63zvppcpneknv4sq6x9370 <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
7334ad1e16 |
fix(desktop): route macos notification clicks (#4799)
## Summary - deliver macOS notifications through `UNUserNotificationCenter` - route notification clicks to the referenced channel or thread through the existing frontend activation path - preserve click targets across cold startup and frontend remounts with a small process-wide activation queue - keep Linux notification activation behavior unchanged ## Architecture A single `UNUserNotificationCenterDelegate` is installed during Tauri setup. Each notification stores its navigation target in `userInfo`. On click, Rust queues the target before emitting a wake-up event; the frontend atomically drains the queue and dispatches the existing notification action. The queue is the source of truth, which prevents cold-start loss and duplicate delivery. ## Validation Verified at `a81241611d617becf7640bee6fe56b5cdb4d0fab`: - Biome format/check and lint - TypeScript typecheck - repository and desktop-Tauri `cargo fmt --check` - repository and desktop-Tauri Clippy with `-D warnings` - full pre-push desktop tests and Tauri workspace checks/tests - desktop production build Manual macOS validation passed: after explicitly ad-hoc signing the local bundle with `xyz.block.buzz.app`, the operator confirmed real Notification Center delivery and click navigation. <details> <summary>Local macOS test procedure</summary> Tauri's generated ad-hoc signing identifier is not accepted by `UNUserNotificationCenter`. Re-sign the local bundle with its bundle identifier and keep other Buzz copies closed: ```bash just desktop-release-build APP="$HOME/.cache/cargo-target/aarch64-apple-darwin/release/bundle/macos/Buzz.app" codesign --force --deep --sign - \ --identifier xyz.block.buzz.app \ --entitlements desktop/src-tauri/Entitlements.plist \ "$APP" codesign --verify --deep --strict --verbose=2 "$APP" pkill -x buzz-desktop || true open -n "$APP" ``` </details> Buzz channel: `55e2bfca-1b38-48fb-9dc2-584d400501f3` --------- Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Signed-off-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
43cced308d |
feat(desktop): sync themes per community (#3653)
**Category:** new-feature **User Impact:** Users can keep a distinct Appearance scheme for each community and restore it on another desktop signed in with the same identity. **Problem:** A single global theme makes it harder to distinguish among communities, and local-only preferences do not follow a user to another device. **Solution:** Save each community's stable theme, accent, and system-following selection as private encrypted relay state, backed by a responsive local cache and guarded against switch races, invalid future records, and relay failures. <details> <summary>File changes</summary> **desktop/src/app/App.tsx** Mounts the community-scoped theme controller inside the active community lifecycle. **desktop/src/features/settings/lib/appearanceScopeCopy.test.mjs** Covers active-community and fallback labels used to explain Appearance scope. **desktop/src/features/settings/lib/appearanceScopeCopy.ts** Builds a safe, trimmed label for the currently active community. **desktop/src/features/settings/ui/SettingsPanels.tsx** Clarifies which Appearance controls are per-community and which apply globally when multiple communities exist. **desktop/src/shared/constants/kinds.ts** Defines the NIP-78 application-data event kind used for theme preferences. **desktop/src/shared/theme/CommunityThemeController.tsx** Coordinates cached appearance, encrypted relay retrieval, live updates, reconnect behavior, and safe community switching. **desktop/src/shared/theme/ThemeProvider.tsx** Exposes a single appearance application path so synchronized preferences use the existing renderer and persistence behavior. **desktop/src/shared/theme/communityThemePreference.test.mjs** Covers contract validation, user/relay isolation, malformed records, cache failures, and switch-race decisions. **desktop/src/shared/theme/communityThemePreference.ts** Defines the versioned stable preference contract, safe defaults, local cache keys, and persistence guards. **desktop/src/shared/theme/communityThemeSync.test.mjs** Covers relay absence, unreadable records, unavailability, seeding safety, and teardown of pending writes. **desktop/src/shared/theme/communityThemeSync.ts** Encrypts theme preferences to the user, publishes and retrieves NIP-78 state, and handles ordering and lifecycle safety. </details> ### Reproduction steps 1. Join at least two communities and open **Settings → Appearance**. 2. Choose a different theme, accent, or system-following mode in each community. 3. Switch between the communities and verify each one restores its own scheme without overwriting the other. 4. Sign in on another desktop with the same Nostr identity, join the same community, and verify its saved scheme is restored from that community's relay. 5. Disconnect the relay, change Appearance, and verify the UI remains responsive and the local fallback is retained. ### Screenshots / demos <img width="1733" height="948" alt="image" src="https://github.com/user-attachments/assets/5afeabaa-0def-482c-9b87-8a880ee0a467" /> <img width="700" height="412" alt="Screen Recording 2026-07-29 at 4 39 52 PM" src="https://github.com/user-attachments/assets/d58da329-aec5-4324-a4b2-cbcb2702a81a" /> --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> |
||
|
|
6c40ce394f |
feat(desktop): cap OpenClaw agent parallelism at 5 (#4019)
OpenClaw connects to a single shared Gateway daemon. Spawning the
default 10 ACP workers per agent is both resource-expensive and
architecturally wrong — each worker opens a separate gateway connection.
Tyler's ruling: cap at 5, lower if needed.
## Contract
Store the requested value (1–32) verbatim at every persistence and wire
boundary. Apply `effective = min(requested, harness_cap)` only at the
four enforcement points:
| Boundary | Implementation |
|---|---|
| Local spawn | `BUZZ_ACP_AGENTS` env var in child `Command` |
| Remote deploy | `launch.policy_env["BUZZ_ACP_AGENTS"]` + legacy
`parallelism` field |
| Restart badge | `SpawnConfigSnapshot.parallelism` stores effective
value; the diff surface displays what actually runs |
| UI copy | Amber hint when requested > cap; no `max` attribute, no
save-path clamp |
`BUZZ_ACP_AGENTS` is added to `RESERVED_ENV_KEYS` — the Desktop resolves
the effective value into `policy_env`; a user-supplied override in `env`
would bypass the cap and is silently stripped.
## Changes
**`managed_agents/parallelism.rs`** (new) — policy core:
- `OPENCLAW_MAX_PARALLELISM = 5`
- `harness_max_parallelism(command)` — keyed on
`normalize_command_identity` so path prefixes, `.exe` suffixes, and
other cosmetic differences are ignored
- `effective_parallelism(command, value)` — identity for uncapped
harnesses
- `acp_agents_value(command, parallelism)` — `env("BUZZ_ACP_AGENTS", …)`
helper
**`runtime.rs`** — spawn clamp: `BUZZ_ACP_AGENTS =
acp_agents_value(effective_command, record.parallelism)`
**`agents_deploy.rs`** — deploy egress clamp: `build_deploy_payload`
resolves `effective_parallelism` once from `descriptor.command`; both
`launch.policy_env["BUZZ_ACP_AGENTS"]` and the legacy top-level
`parallelism` field use that value — the two are always consistent
regardless of stale `record.agent_command` pins
**`spawn_snapshot.rs`** — `from_inputs` stores
`effective_parallelism(&descriptor.command, record.parallelism)` in the
`parallelism` field. Over-cap edits that don't change the pool (e.g. 10
→ 8, both clamp to 5 on OpenClaw) produce equal snapshots; cap crossings
(8 → 3) produce different snapshots.
**`AcpRuntimeCatalogEntry.max_parallelism: Option<u32>`** — derived from
the static definition command, not the probed `entry.command` (which may
be `null` for unavailable entries), so unavailable OpenClaw entries
still carry the cap. Propagated through all four catalog constructors
(builtin discovery, preset catalog construction, custom discovery,
custom-save response), IPC types
(`RawAcpRuntimeCatalogEntry.max_parallelism`), and the frontend catalog
type.
**UI** — `EditAgentAdvancedFields` and `PersonaAdvancedFields` show an
amber hint when `selectedRuntime.maxParallelism` is set and the current
value exceeds it. Cap and label come from the catalog entry — no
hardcoded 5 in TS. No `max` attribute on inputs; the input stays
`type="text"` with 1–32 copy.
**Docs** — `docs/remote-agents.md`: `BUZZ_ACP_AGENTS` moved from the
deliberately-non-reserved section to reserved; new contract documented.
`desktop/src/features/agents/AGENTS.md`: command-keyed execution policy
documented as the sanctioned second metadata source feeding the catalog
projection.
## Tests
**Rust** (`parallelism.rs`):
- `policy_table` — `harness_max_parallelism` and `effective_parallelism`
across all openclaw variants and uncapped harnesses
- `acp_agents_value_openclaw_above_cap_is_capped` — spawn-env seam
- `override_direction_*` — both override directions (openclaw runtime +
goose override; goose runtime + openclaw override)
- `summary_persona_inherited_*` — live persona wins over stale
`agent_command`
- `snapshot_export_carries_requested_definition_parallelism` — requested
value travels wire/sync unchanged
**Rust** (`spawn_snapshot/tests.rs`):
- `openclaw_above_cap_parallelism_snapshots_equal` — stored 10 vs 8,
both clamp to 5 → snapshots equal
- `openclaw_cap_crossing_parallelism_snapshots_differ` — 8 (clamps to 5)
vs 3 → snapshots differ
**Rust** (`discovery/presets.rs`):
- `openclaw_preset_unavailable_carries_max_parallelism` /
`openclaw_preset_available_carries_max_parallelism` — catalog metadata
present with `command: null` and with a resolved path
**Rust** (`agents_deploy.rs`):
- `launch_block_openclaw_over_cap_policy_env_is_capped` — direct
`launch.policy_env` seam
-
`deploy_payload_json_stale_goose_record_live_openclaw_descriptor_both_capped`
— stale `record.agent_command=goose`, live descriptor=openclaw: both
fields cap to 5
-
`deploy_payload_json_stale_openclaw_record_live_goose_descriptor_both_uncapped`
— stale `record.agent_command=openclaw`, live descriptor=goose: both
fields pass through requested
- `deploy_payload_json_explicit_openclaw_override_both_capped` —
explicit `agent_command_override=openclaw`: both fields cap to 5
**Rust** (`persona_events/stale_pin_tests.rs`):
- `apply_persona_snapshot_goose_to_custom_harness_drops_stale_goose_pin`
— custom-direction stale-pin drop (builtin pin → loaded custom harness
via `update_loaded_harness_registry`)
**TypeScript** (`agentParallelism.test.mjs`):
- `parallelismCapHint` — at/below cap (null), above cap (hint includes
label and cap value), singular form for cap=1, uncapped harness (null)
**TypeScript** (`tauri.test.mjs`):
- `fromRawAcpRuntimeCatalogEntry` round-trips `max_parallelism` →
`maxParallelism`; absent when `undefined`
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
f2ce575b62 |
Fix media attachment actions (#4849)
## Summary - Upload photos immediately while keeping videos queued for background upload. - Move image annotation and video spoiler actions to thumbnail hover overlays. - Preserve the image editor's existing Draw and Spoiler controls. ### Snapshots #### Image annotation overlay  #### Image editor controls  ## Testing - `pnpm typecheck` - `pnpm check` - Focused attachment, drawing, and spoiler smoke tests - Pre-push desktop tests (4,286 passing) --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Honey <47c18026466e670a1618fd7de0ef32b9ff75d6e0b5ccf255d13c8c3d674ed115@buzz.block.builderlab.xyz> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
2034e693a8 |
fix(desktop): remove join API token control (#4897)
## Summary Remove the nonfunctional API-token option from the existing-community join flow. ## Validation - Focused Playwright join-flow coverage - Add-community screenshot coverage Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
014562c063 |
fix(desktop): allow shared agent mentions (#4913)
## Summary - admit relay-discovered agents to autocomplete when their response policy authorizes the viewer - require authorization in the exact active stream/forum channel for mentions, while keeping community-wide discovery for member invitation - fail closed for relay-only agents in DMs and unresolved composer contexts - re-authorize cached autocomplete rows after policy/channel changes so stale agent suggestions cannot leak back in - preserve managed-agent behavior and explicitly reject stale agent-marked channel members absent from both live directories ## Validation - `pnpm --dir desktop test` — 4,288 passed - `pnpm --dir desktop typecheck` - `pnpm --dir desktop check` - `pnpm --dir desktop build:e2e` - focused Playwright mention matrix — 12 passed - focused Playwright member-invitation matrix — 2 passed - pre-push hooks after rebase to current `origin/main` — desktop check and 4,288 tests passed - independent correctness/privacy re-review cleared with no remaining blocker ## Related competing PRs This supersedes or overlaps #2333, #3056, #4242, #4137, #2314, #4058, and #2605. This version adds exact-channel authorization, fail-closed DM/context handling, cached-row reauthorization, forum coverage, outbound mention-tag coverage, explicit stale-member coverage, and add-member discovery coverage. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
efe1893dd3 |
fix(channels): restrict private-channel invitations (#4612)
This change requires an active owner or administrator for third-party additions to private channels. The relay validator and transactional database authority enforce the same rule, including removed-member reactivation and role-change paths. Idempotent self-target behavior remains available, while ordinary members can no longer extend private-channel access to another identity. ## Testing - `git diff --check origin/main...codex/security-private-channel-invite-authority` - Rebased onto `origin/main` at `5c98932` - Full CI pending Originating Buzz thread: `buzz://message?channel=3928fe05-df61-4b5d-b9c7-d623b9b10ea1&id=3c6c02312f763fbe0d2bfc33a6c1a362f91d0354f3d18b039cf7a0558c1439d1` --------- Signed-off-by: Jordan Mecom <jm@squareup.com> Signed-off-by: Eli Foster <efoster@squareup.com> Co-authored-by: Eli Foster <efoster@squareup.com> |
||
|
|
6dbc946512 |
fix(desktop): make missing-command error actionable for released builds (#4802)
User-facing error for missing ACP harness commands has been pointing released-build users to run `cargo build --release --workspace` and read TESTING.md — both dead ends for anyone not building from source. Updated message acknowledges that antivirus software can quarantine bundled binaries and provides practical remediation steps. Preserves pointer to TESTING.md for source builds. Fixes issue context from #4491. Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub16v54tttfqacx9ycvc3k0ut0npj564ahcuajzy6qjvh57ntmsf4uq4806j2 <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz> |
||
|
|
8342dfcc58 |
chore(release): release Buzz Desktop version 0.5.5 (#4809)
## Buzz Desktop release v0.5.5 - **Frozen main:** `25a9cf1be6d245fbd7373cb1160dbc790baf5bd5` - **Reviewed candidate:** `8380c1f8ead8816bcf1f4ea9f66aa08e2441b15a` - **Previous desktop release:** `desktop-v0.5.4` - **Proposed immutable tag:** `desktop-v0.5.5` This PR must be **squash merged** only after the Desktop Release Candidate check passes. The branch must remain based directly on current `main`; stale base, payload drift, incomplete notes, or an unauthorized merge produce no tag. The checked-in changelog accounts for every non-merge commit in the release range. Publication remains bound to the immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |
||
|
|
25a9cf1be6 |
feat: paste composer text without formatting (#4801)
## Summary - handle Cmd+Shift+V on macOS and Ctrl+Shift+V on Windows/Linux in the message composer - read plain text through the native Tauri/arboard clipboard path in packaged builds, with a browser-only Clipboard API fallback - re-enter ProseMirror's paste pipeline with populated `text/plain` clipboard data so selection, undo, multiline behavior, and paste observers remain intact - cover both platform mappings with rendered composer E2E tests that assert the native command path ## Testing - `pnpm test` — 4,286 passed - `pnpm check` - `pnpm typecheck` - `pnpm exec playwright test composer-selection-formatting.spec.ts --project=smoke` — 26 passed - `cargo check --manifest-path desktop/src-tauri/Cargo.toml --workspace --all-targets --target aarch64-apple-darwin` - `just desktop-tauri-test` — 2,206 core tests plus integration and doc-test groups passed - full pre-push hooks passed ## Manual verification Physical packaged-app clipboard verification remains recommended on macOS, Windows, and Linux. The automated E2E uses mocked Tauri IPC but asserts the native `read_clipboard_text` command is invoked. Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
79c52166cf |
Revert "chore(release): release Buzz Desktop version 0.5.5" (#4808)
Reverts block/buzz#4800 |
||
|
|
a0ed13de14 |
chore(release): release Buzz Desktop version 0.5.5 (#4800)
## Buzz Desktop release v0.5.5 - **Frozen main:** `4a2305170eef565bf1836e2859247e67c030f8af` - **Reviewed candidate:** `2d03d37b05b68186b2caad9da79080032be3ac72` - **Previous desktop release:** `desktop-v0.5.4` - **Proposed immutable tag:** `desktop-v0.5.5` This PR must be **squash merged** only after the Desktop Release Candidate check passes. The branch must remain based directly on current `main`; stale base, payload drift, incomplete notes, or an unauthorized merge produce no tag. The checked-in changelog accounts for every non-merge commit in the release range. Publication remains bound to the immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |
||
|
|
4a2305170e |
fix: reauthenticate databricks model discovery (#4008)
## Summary - preserve Databricks catalog 401 responses as authentication failures and retry discovery exactly once after silently refreshing the rejected bearer - preserve runtime OAuth recovery: when discovery has no usable OAuth credential, `session/new` succeeds with only the trimmed configured model so the first `session/prompt` can run the existing browser PKCE flow - reject a rejected configured `DATABRICKS_TOKEN` with actionable, non-interactive guidance; static credentials cannot recover through PKCE - use the configured-model fallback for non-auth discovery failures without caching failed or fallback catalogs, so later sessions retry discovery - keep known Databricks v2 models only for authenticated empty-catalog responses and mark their provenance - resolve discovery before MCP spawn or session registration, preventing failed discovery from leaking resources or consuming session capacity - permit serialized interactive PKCE only from the explicit saved-agent model picker; passive draft discovery never opens a browser ## Runtime flow 1. OAuth discovery attempts cached credentials and silent refresh without opening a browser. 2. If no usable OAuth bearer exists, `session/new` advertises only the configured model and succeeds. 3. The first `session/prompt` uses `TokenSource::bearer()`, which may launch browser PKCE. 4. A later session retries discovery and caches only the authenticated catalog. ## Regression coverage - rejected-but-locally-fresh OAuth bearer performs one refresh and one catalog retry - OAuth mode with no cached token allows `session/new` and returns exactly the trimmed configured model - the OAuth fallback is not cached; a later authenticated session retries discovery and caches the returned catalog - rejected static tokens still reject `session/new` - failed discovery does not consume the sole session slot or spawn the supplied MCP process - Desktop interactive/passive auth intent, static-token redaction, and authenticated empty-catalog provenance ## Verification - `cargo test -p buzz-agent` - `cargo test --manifest-path desktop/src-tauri/Cargo.toml --lib commands::agent_models` - `cargo clippy --manifest-path desktop/src-tauri/Cargo.toml --all-targets -- -D warnings` - `cargo fmt --all -- --check` - `git diff --check` - full pre-push hooks ## Review Adversarial review found and drove fixes for session/MCP resource leakage, duplicate concurrent PKCE flows, sensitive error propagation, incorrect 403 reauthentication, missing discovery-level coverage, passive browser launch, and the Desktop file-size ratchet. The final follow-up preserves the existing prompt-time OAuth flow while retaining static-token rejection and pre-allocation discovery ordering. --------- Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
8faf09f9ae |
Revert "chore(release): release Buzz Desktop version 0.5.5" (#4797)
Reverts block/buzz#4788 |
||
|
|
a1d78f2959 |
feat: Buzz entity links — rich preview cards + in-app navigation for repos, PRs, and issues (#4695)
## Summary
Gives Buzz-hosted git entities the same "GitHub-style" chat experience
GitHub links already get: rich preview cards, real titles, and
click-through — except clicks navigate **in-app** to the Projects view
instead of a browser.
- **Spec**: `docs/buzz-entity-links.md` — link scheme, slices, and
deferred work (`buzz://project`, OS deep links, web routes).
- **Canonical `buzz://` deep links**: new
`desktop/src/shared/lib/entityLink.ts` with builders + strict parser for
`buzz://pr?id=…&owner=…&d=…`, `buzz://issue?…`, and
`buzz://repo?owner=…&d=…`, mirrored by a Rust module
(`crates/buzz-cli/src/links.rs`) with a shared golden-format test so the
two implementations can't drift.
- **Preview cards**: `linkPreview.ts` recognizes `buzz://` entity links
*and* HTTPS relay clone URLs (`{origin}/git/<pubkey>/<repo>`, the shape
agents paste today). Both normalize onto the canonical `buzz://` href,
so the two spellings of a repo dedupe to one `Buzz`-provider card
(`BuzzMark` logo) rendered by `link-preview-attachment.tsx`.
- **Title enrichment**: PR/issue cards fetch the real subject from the
relay event (`subject` tag or first content line) via
`useResolvedLinkPreviews.ts`; the cache is community-scoped and reset in
`resetCommunityState()`.
- **In-app navigation**: clicking a card or inline anchor (including
HTTPS relay clone URLs whose origin matches the active relay) routes to
the canonical `30617:<owner>:<d>` coordinate via `goProject()`
(`markdown/entityLinks.tsx`). **Merge dependency: #4671 must merge
first** — route resolution for `30617:` coordinates is implemented on
that branch (`feat/multi-repository-projects`). Entity-link and
external-anchor logic were extracted out of `markdown.tsx` to stay under
the file-size ratchet.
- **Agent side**: `buzz pr open`, `buzz issues create`, and `buzz repos
create` now return a ready-made `link` field (omitted when the relay
returns `accepted: false`), and `base_prompt.md` instructs agents to
paste it verbatim when announcing work.
## Test plan
- [x] Desktop unit tests: pass, including new `entityLink.test.mjs` and
`linkPreview.test.mjs` coverage (golden formats, malformed-link
rejection, clone-URL/`buzz://` dedupe, origin-gated anchor behavior,
label-must-win invariant, cache epoch)
- [x] Rust: `cargo test -p buzz-cli` golden-format test +
accepted/rejected link guard assertions, clippy + fmt clean
- [x] Biome + `tsc --noEmit` clean; pre-push hooks
(desktop-tauri-checks, rust-tests, desktop-test) pass
- [ ] Manual: paste a relay clone URL and a `buzz://pr` link in a
channel — verify one card each, real PR title, and in-app navigation to
the Projects view
Related: [#4671](https://github.com/block/buzz/pull/4671)
---------
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1g8493u0xfsjrvflg4n08ezd7vec99mnwzlv0qgwpr9d7gvjwhuzqx59rhw <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
|
||
|
|
4c665aeac3 |
fix(desktop): serialize tray channel actions for frontend (#4762)
## Summary - serialize native `openChannel` tray actions with the camelCase field names consumed by the TypeScript frontend - prevent a valid tray channel ID from becoming `/channels/undefined` - add a Rust serialization contract test covering the complete frontend payload shape ### Root cause `TrayAction` renamed the enum variant to `openChannel`, but its struct fields still serialized as `channel_id` and `community_generation`. The frontend reads `action.channelId`, so tray navigation called `goChannel(undefined)`. ### Testing - manually verified the corrected runtime payload and tray navigation before removing temporary logging - `just desktop-ci` - pre-push hooks (desktop checks/tests, Tauri checks, and Rust tests) --------- Signed-off-by: Kalvin Chau <kalvin@block.xyz> Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: npub122y0pqkertljmedu303rl0aqrj3w8pvu43t6jxm6875lzg6f2pwqegc3xc <5288f082d91aff2de5bc8be23fbfa01ca2e3859cac57a91b7a3fa9f12349505c@buzz.block.builderlab.xyz> Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
b948c54792 |
chore(release): release Buzz Desktop version 0.5.5 (#4788)
## Buzz Desktop release v0.5.5 - **Frozen main:** `383d9e1eafd569b44b9c835200dba69ef7cec9dc` - **Reviewed candidate:** `ac589061ef1009f55384536e483cfe9b1260697b` - **Previous desktop release:** `desktop-v0.5.4` - **Proposed immutable tag:** `desktop-v0.5.5` This PR must be **squash merged** only after the Desktop Release Candidate check passes. The branch must remain based directly on current `main`; stale base, payload drift, incomplete notes, or an unauthorized merge produce no tag. The checked-in changelog accounts for every non-merge commit in the release range. Publication remains bound to the immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |