fork sync: merge block/buzz main (d07457687) - #151
Merged
Conversation
**Category:** fix **User Impact:** Link previews can keep loading while a message is being composed, while sending still has a finite escape hatch and stalled network transports cannot occupy preview slots forever. **Problem:** Native metadata and image deadlines could collapse slow previews into fallback cards while the user was still composing, and a shared image-host cooldown made pasted batches fail inconsistently after one rate limit. **Solution:** Keep preview resolution user-paced with no aggregate request deadline, bound transport inactivity (15s DNS/connect, 30s idle read), serialize image requests by host, and allow at most one server-directed cooldown wait of up to 30s across an image fetch and its redirects. The existing bounded post-Send preparation and immediate Skip paths remain unchanged. <details> <summary>File changes</summary> **desktop/src-tauri/src/commands/link_preview.rs** Removes aggregate native deadlines so composer metadata work can complete at the user's pace, while retaining DNS/connect/idle-read liveness bounds. Adds bounded host-paced image request coordination that releases its gate during cooldown, waits inline at most once for at most 30 seconds, and cannot renew that wait through redirects or the outer transient retry. Same-host image and favicon requests remain deliberately serialized to align with host rate limits. **desktop/src-tauri/src/commands/link_preview_rate_limit.rs** Adds a fixed-size striped host gate so concurrent image requests are serialized without retaining an unbounded attacker-controlled hostname map. **desktop/src-tauri/src/commands/link_preview_tests.rs** Moves native link-preview tests into a dedicated module and covers the user-paced metadata contract, bounded one-shot cooldown behavior, and gate release while a rate-limited request sleeps—including a different host sharing the same bounded gate stripe. **desktop/src-tauri/src/commands/link_preview_youtube.rs** Removes the thumbnail fetch deadline so YouTube previews follow the same composer lifecycle contract while using the shared bounded transport. **desktop/src/shared/lib/useResolvedLinkPreviews.ts** Adds development-only metadata outcome diagnostics with elapsed time and image/fallback state, without logging encoded image payloads. </details> ### Reproduction steps 1. Open the desktop composer and paste several GitHub pull request links whose OpenGraph images share a host. 2. Observe that image requests are paced by host instead of racing, and slow-but-progressing preview work remains pending rather than immediately becoming a completed favicon fallback. 3. Send while preview work is still pending and confirm **Preparing link preview** remains bounded by the existing post-Send budget. 4. Use **Skip** during preparation and confirm the message proceeds immediately. 5. In a development build, inspect the console for `[link-preview] metadata fetch completed` diagnostics containing elapsed time and image state without base64 payloads. ### Related issue N/A — scoped from the linked Buzz implementation room. ### Testing At current head `dfb394aafbee537e9ffb04ad3732d08f65f30b8e`: - Production-bound paused-time metadata regression passed through `fetch_link_preview_metadata`; restoring the former 10-second aggregate wrapper makes it fail at the pending assertion. - Native link-preview module: 19/19 passed. - `cargo check --manifest-path desktop/src-tauri/Cargo.toml` passed. - Rust formatting and `git diff --check` passed. - Pre-push `push-head-scope`, org safety, differential file-size, branch-skew, and `desktop-tauri-checks` hooks passed. At prior head `59e2dcf167b15c7a3e637ad2608008b7f9cef5f3`: - Full Tauri Rust suite: 3,056 passed, 19 ignored; integration crates 7 + 3 passed. - Focused native link-preview suite: 26/26 passed. - The pasted multi-preview workflow was exercised in the desktop app and confirmed improved before draft publication. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@users.noreply.github.com>
…#7325) 🤖 ## Summary When a Buzz agent falls behind on incoming messages, its connection can make the backlog worse while trying to recover. The connection buffers messages from the relay server until the agent is ready to process them; if that buffer overflows, recovery previously requested history for **every subscribed channel** and paused socket reads while sending those requests. That adds traffic to an already overloaded connection. This change requests history only for affected subscriptions, once the code consuming those messages has room, with at least five seconds between attempts. The recovery path now: - Combines repeated losses into one pending recovery per affected subscription, keeping the oldest dropped timestamp so replay starts early enough. - Waits until at least half the consumer queue is free and the relay's existing rate-limit delay has expired. The queue wakes recovery when space becomes available; recovery does not periodically sample capacity or hold queue space away from live messages. - Attempts one subscription at a time, choosing the least recently attempted so a busy channel cannot crowd out other channels or membership notifications. The five-second delay starts when an attempt finishes, including a failed write; failed writes leave recovery pending. Recovery is paced by available capacity, not by how often messages are lost. This is not a larger buffer or a cutoff that abandons recovery. Subscription identifiers, message filters, replay timestamp overlap and duplicate filtering are unchanged; no downstream agent changes are required. This targets a reproducible overload **amplifier**, not every cause of overload or every catch-up limitation. The initial live overload's cause has not been established. Recovery remains best effort: a successful request write is not proof of delivery, and existing history/retention limits, bounded duplicate tracking and replay limitations still apply. There is no exactly-once or complete catch-up guarantee. A stalled write can still pause socket reads for the existing ten-second timeout; the pacing bound does not cover initial subscriptions, reconnects or other retry paths. ### Related issue Closest related: block#5014 (channel re-subscription); also block#6661 (membership reconciliation) and block#6090 (relay backpressure gap signaling). This addresses local overflow recovery scheduling, not those separate mechanisms. ### Testing Recorded offline comparisons against the previous behavior, with the final implementation at `8000636f3073167c5a5107bb179c7d91160f1729`: | Same fixture: 18 subscriptions, three overload rounds | Before | After | | --- | --- | --- | | Recovery history requests | 108 | 3 | | Ping-response delay | About 4.6 seconds | Below the measurement's 1 ms resolution | A separate bounded-history fixture delivered all 320 events plus subsequent live traffic in **both** versions. Regression coverage exercises the real socket-handling task, including intermittent consumer capacity, fairness, failed writes and cancellation of capacity waits before live delivery. These are synthetic results, not production throughput measurements or evidence of a deployed cure. The full local `RUST_TEST_THREADS=4 just ci` run passed on September 4, 2026. Earlier unsuccessful local runs remain part of the validation history. The [recorded validation evidence and separate desktop follow-up](block#7325 (comment)) preserve the original desktop mock-history scroll failure, its passing rerun and the remaining investigation. That desktop path does not run the agent connection code; neither this repair nor the passing rerun fixes the observed scroll problem. --------- Signed-off-by: Logan Johnson <loganj@squareup.com>
…7494) 🤖 ## Summary When an iOS push notification comes from someone the app has no cached name for, the notification title showed the first characters of the sender's raw public key — for example `aa4fc866…`. That fragment is unreadable and doesn't match how the same person appears anywhere else in Buzz. This PR changes that title to the compact form of the sender's npub (npub is the human-readable encoding of a Nostr public key): first 8 and last 4 characters — for example `npub14f8…9nsy`, the same identity shape used across the desktop and mobile apps. - Unnamed senders: raw hex fragment → compact npub. - Named senders: unchanged — a sender the app has a display name for still titles the notification with that name. - Unverifiable sender identities (malformed keys, or lookalike strings that are not literal 64-hex-digit keys) now render a neutral "Someone" instead of partial raw key material. - Everything else about the notification is unchanged: body text, subtitle, thread matching and grouping, deep-link navigation, thread identifiers, and the internal hex public key the resolver matches on. The native iOS notification-service package (`BuzzPushKit`) gains a minimal in-house bech32 codec (bech32 is the checksummed string encoding npubs use) — checksum-validated, 32-byte keys only, and no new external dependency. The hex input branch accepts exactly a 64 ASCII hex digit key before any parsing, so strings that merely parse like hex (for example a run of `+a` pairs) cannot become a displayed identity; this is input validation for presentation. Event signature verification is untouched. ### Related issue Fixes: N/A. Searched existing issues/PRs for push-notification npub identity — closest related: none found. ### Testing At head `3e3f2813b8864b76257ccb50dea3a4b31fa4de0d` (base `44316ff72f5f7de014c66b01cbf534298a70c249`; 4 files, +321/−4): - CI `Mobile Swift` lane, at this exact head — all passed: `swift test` (73 tests, 0 failures), the SwiftPM debug and release builds of `mobile/ios/BuzzPushKit`, and the unsigned iOS release build. - Test coverage: npub encoding cross-checked against independent nostr-rs/NIP-19 vectors; rejection of bad checksums, mixed case, wrong lengths, invalid alphabet, padding, and non-32-byte payloads; resolver boundary matrix — hex/npub/invalid sender keys render compact npub or "Someone" while body, subtitle, sender key, and thread identifier pass through; named senders keep cached display names. ### Task provenance Buzz channel: `1f0e4a3d-7e01-4efe-bb16-843b357f85c9` Task: buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6 --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
…gate, strict parser) (block#7488) 🤖 ## Summary Identity keys in the desktop app are displayed as raw 64-character hex. A person's key shows up as something like `953d3363…` — unreadable, impossible to recognize as the same identity on another screen, and a hazard when copied by hand. Nostr (the protocol Buzz runs on) has a human-readable spelling for identity keys — the `npub1…` form — but the desktop app did not use it consistently. This is the foundation of the desktop npub changes: it adds the shared pieces every identity surface builds on, and two follow-up slices stack directly on this branch — block#7489 converts the identity controls (profile, settings, allowlist, workflow key fields) and block#7495 converts the everyday display surfaces (mentions, member lists, sidebar, and other name fallbacks). After this change: - The shared identity widget shows the compact npub form — `npub1j57...fjmv` — instead of a hex prefix, everywhere it renders (for example the owned-agent public-key row on a profile). Copying it puts the full npub on the clipboard. - Copy is a real interaction, verified end-to-end: both popover variants put the exact canonical npub on the actual clipboard — never the raw hex the popover also lists, never a truncation — and a portaled popover's clicks no longer steal focus from the new-DM To-field mid-copy. Pointer copy, a natural Space-then-Enter path, and inner/outer Escape are covered. - Anything that isn't a valid identity key fails neutrally: short or corrupt values — including degenerate values that technically encode to a checksum-valid npub but aren't real identity keys — show "Unavailable" with no copy button, instead of a misleading value. - Both valid npub spellings display: all-lowercase `npub1…` and all-uppercase `NPUB1…` (Bech32, npub's encoding, permits either casing) both render the same canonical lowercase npub. Mixed case is rejected by the display path as written — `canonicalNpub` and the widget don't case-normalize input — while input parsing (`parsePubkeyInput`) keeps its trim-and-lowercase normalization and accepts mixed-case npubs; both paths require the decoded payload to be exactly a 64-character identity key. - Identity-key input is strict on payload: an npub whose decoded payload isn't exactly a 64-character identity key is rejected, matching the validation the app's Rust side already applies to agent allowlists. Intentional scope boundary: only surfaces that render through the shared widget change here. Outer profile copy, settings identity cards, the respond-to allowlist, and workflow key fields still show hex — they move to npub in the controls follow-up (block#7489). Nothing else changes identity representation: display names, private keys, event IDs, and the hex the app stores, sends, and matches internally are untouched; only the user-facing spelling of an identity key changes. ## Details - `desktop/src/shared/lib/pubkey.ts` — `canonicalNpub()`: strict canonical full-npub helper (64-char hex in any case, or a checksum-validated npub, returns the canonical npub; anything else returns `null`); `truncateNpub()`: the compact display form; existing exports unchanged. - `desktop/src/shared/ui/PubKey.tsx` — the shared widget's identity gate validates through `canonicalNpub`; the popover copies the npub only. - `desktop/src/shared/lib/nostrUtils.ts` — `parsePubkeyInput` rejects npubs whose payload is not exactly a 64-character identity key. - `desktop/src/features/messages/ui/NewMessageScreen.tsx` — the To-field focuses its search input only for clicks that land inside the field itself, so portaled recipient popovers keep their focus while open (a popover click previously dismissed it mid-copy). - Unit suites cover the helper, widget, and parser (including the degenerate-encode and uppercase regressions); the e2e specs that render these rows assert the npub display. ### Related issue - Fixes: N/A. Searched existing issues/PRs for npub identity display — no existing match. - Stack: block#7489 is based on this branch and builds on these primitives; it does not stand alone on main. ### Testing At head `b3310c248` (base: main `44316ff72`; 12 files, +440/−39): - Focused unit suites (pubkey, PubKey, parsePubkeyInput): 20/20 green; mutation-checked — removing the decoded-length predicate fails the short/empty checksum-valid-npub assertions in `canonicalNpub` and the widget, and a wrong-identity clipboard value fails the new copy assertions. - `pnpm typecheck` and `pnpm check`: pass; full desktop unit suite 6459/6459 at this exact head. - Targeted e2e at this exact head: 8/8 across the two specs that own the clipboard flows — `agent-access-warning.spec.ts` (compact variant, agent-access owner hint) and `pubkey-display-screenshots.spec.ts` (full variant, new-DM recipient verification: pointer copy, popover surviving the copy, inner/outer Escape, Space-then-Enter). - No Rust-side or build files change in this PR, so those results are unaffected. ### Task provenance Buzz channel: `1f0e4a3d-7e01-4efe-bb16-843b357f85c9` Task: buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6 --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
…w surfaces (block#7495) 🤖 ## Summary Every Buzz account is identified by a long public key. Before this change, when someone had no display name, surfaces fell back to inconsistent labels — mostly raw hex fragments like `abcd1234…wxyz`, sometimes a generic role label with no key — so the same person looked different from surface to surface, and nothing looked like an npub address. This PR applies the npub identity foundation from block#7488 to the everyday surfaces: a person without a display name now falls back to the same compact npub everywhere — `npub1xxxx…yyyy`, the human-readable spelling of their public key (first 8 + last 4 characters of the full npub) — across messages and mentions, reactions, huddles, member and participant lists, the sidebar and channel activity, search, projects, tray, notifications, and workflow surfaces. - **Mentions and messages**: key-only mention chips render the compact npub. Pasting a copied mention back still re-binds it byte-exactly to the identity it declares, for both the new npub chips and legacy hex-truncated chips copied by older clients — wrong, missing, or tampered key qualification is rejected instead of silently degrading to plain text. - **Reactions and huddles**: huddle reaction events and the huddle roster/participants render the compact npub for unnamed participants; workflow reaction triggers describe authors with the same form. - **Members and sidebar**: channel and community member lists, add-member results and invites, the members sidebar, the channel-activity popover, search, projects (assignees/reviewers/PR panels), the tray menu, and desktop notifications all fall back to the compact npub; titles and aria labels keep the machine-readable full labels. - **Profile labels**: panel/popover display names and owner handles fall back to the compact npub (never raw hex) when there is no name; linked-event (nevent) message metadata shows the npub-shaped author fallback while the event lookup and event IDs are unchanged. - **Workflows**: author-picker secondary labels, step destination keys, and trigger-author references render compact npubs; event and blob IDs keep their existing hex compacts (they are not identities). - **Avatars stay distinct**: fallback avatars for key-only identities derive initials from the key's tail, so prefixed role labels like "Participant npub1…" no longer collapse every unnamed participant onto the same initials; people with names keep their name initials. Preserved exactly: display names and distinct avatars, internal hex keys (storage/API forms unchanged), clipboard identity roundtrips, event/blob ID compaction, private keys (no nsec path is touched), and nevent link handling. Scope: this PR changes what identity labels **display**, not identity controls — profile/settings copy controls, the respond-to allowlist, workflow key fields, and agent dialogs are the sibling slice block#7489, and the shared primitives (`canonicalNpub`, `truncateNpub`, the `<PubKey>` gate, strict input parsing) come from the foundation block#7488. ### Related issue - Fixes: N/A. Searched existing issues/PRs for duplicates — none found; the related work is the npub identity stack this slice belongs to. - Base/dependency: stacks on block#7488 (foundation) — this PR does not stand alone on main. - block#7489 is a sibling slice on the same block#7488 base (profile/agent/workflow controls), not a dependency: this PR does not require block#7489, and block#7489 does not require this PR — both only require block#7488. ### Testing At exact head `4763cbeae1dd521309755e6d61f657324cb98667` (base: `fix/desktop-npub-identity-d1a` @ `5f3a4a8111998c8aa41ad77cf66992bd1c85343c`; 71 files, +656/−189 — production +277/−136, test support +379/−53): - At this head: targeted `mentions.spec.ts` (1/1), the e2e build, typecheck, and biome — green. - 9 changed/related unit files: 100/100 green; typecheck, e2e build, biome, and px text/truncation checks clean; huddle-roster focused run green; channel-activity e2e 11/11; mutation checks confirm the fallback wiring (removing it collapses shared initials and drops fallback rows). - Known pre-existing local e2e failures, unchanged by this PR and reproduced identically at the upstream merge-base: huddle-transcription voice-menu attribution (25 pass / 1 fail) and the `workflow-local-controls` 438px caret drift. Not claimed green locally. - Update at head `236af9e6137386737e84d3a474d6bc808a704c50` (test-only follow-ups `1143af345` + `236af9e6`): the `workflow-local-controls` races were fixed in the test drivers, and the 438px diff was shown to be a stale Darwin snapshot baseline (name-row enable switch already absent and `message_posted` already MessageSquare at recording commit `9390e11c9`) and refreshed — the focused screenshot test, including keyboard/caret assertions, now passes locally (twice). The full spec was not rerun after the snapshot refresh; the huddle-transcription item above is unchanged. Label/copy text changes are asserted by the e2e specs (`mentions`, `mention-recipients`, `pubkey-display-screenshots`, `huddle-transcription`, `channel-activity-popover`, `workflow-local-controls`) rather than new screenshots; the screenshot spec pins the compact npub text forms. ### Task provenance Buzz channel: `1f0e4a3d-7e01-4efe-bb16-843b357f85c9` Task: buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6 --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
…flows (block#7489) 🤖 ## Summary Building on block#7488's npub foundation, this PR finishes the identity display change for the controls where you actually manage people and keys: profile, settings, agent access, and workflows. Everywhere in these surfaces, an identity key shows — and copies — as its canonical npub (npub is the human-readable encoding of a Nostr public key: the compact `npub1j57...fjmv` form where space is tight, the full npub where the whole key matters), and accepts npub as input. After this change: - Profile panel: the public-key row and the managed-by / declared-owner copies show the full npub. If a key can't be encoded, you see "Unavailable" with no copy button — never a raw or partial key. - Settings: the identity card shows and copies the npub. The hosted-communities account identity derives from the bound key (`pubkey_hex`) — the same authority as the mismatch gate and hosted operations — so the display can never disagree with what the app acts on; an unusable hex falls back to a neutral label instead of rendering the unverified server npub. The connected claim and a community's Connect action require that same usable bound key to match the local one — with no usable binding the card cannot claim connected or start Connect, while the community list, linking, and delete/rebind recovery stay available. - Hosted create/onboarding: the account and device identity rows in the create flow and owner onboarding derive from the same authoritative fields (bound key / local key), with the same neutral fallback; readiness requires a usable bound key that matches the local one. - Respond-to allowlist (controls who may respond to an agent): entries can be typed or pasted as hex or npub; both spellings of the same key are recognized as one entry and dedupe. Search results, chips, and remove buttons use the compact npub. - Workflow key fields: to/from keys display as npubs in the form and save back as canonical hex. Templates like `{{trigger.author}}`, roles, and free text pass through untouched; placeholders accept both spellings. - Recipient and agent dialogs: the verify popover is npub-only (the raw-hex line is gone); denied-membership screens never show a raw key. - The Rust-side truncated display name (used for native surfaces) shows the same compact npub, so those surfaces match the web UI. Internal representation is unchanged: keys are still stored, sent, and matched as canonical 64-character hex — npub is a display and input spelling, normalized to hex at the boundary, so existing data and integrations keep working. Bound-key usability and comparison use one normalized form (trimmed, lowercased, 64 hex characters; npub rejected), so padded or mixed-case spellings of the same key match. Display names, private keys, and event IDs are untouched. ## Details - `respondToAllowlist` / `RespondToField`: npub entries normalize to canonical hex; cross-form dedupe; compact npub in rows and chips; direct-add accepts npub and stores canonical hex. - `workflowFormTypes` / `WorkflowStepCard`: hex → npub for display, npub → canonical hex on save; templates, roles, and free text pass through in both directions (roundtrip-tested). - `UserProfilePanelFields`, `ProfileSettingsCard`, `HostedCommunitiesSettingsCard`, `MembershipDenied`, `SelectedRecipientChip`, `AddAgentToChannelDialog`: npub display and copy; invalid keys → "Unavailable" with no copy; hosted identity rows derive from the bound `pubkey_hex` (create/onboarding rows from the bound and local keys), never the unverified server npub; connected/readiness/Connect gates use the same usable-bound-key predicate, and the settings Connect invocation callback re-checks it before starting. - `src-tauri/src/commands/identity.rs`: `truncated_display_name` compacts to the first 8 + last 4 characters of the npub (above a 12-char threshold), mirroring `truncateNpub`. - e2e: profile key rows and clipboard polls assert npub forms and raw-hex suppression; the display-screenshots spec pins the npub-only popover; hosted specs drive the real settings card, create flow, and onboarding rows through their real providers, and the unlinked/npub-only identity cases assert no connected claim and no Connect action. ### Related issue - Fixes: N/A. No separate issue; the related work is the stack below. - Stack: builds on block#7488 (shared npub foundation), now merged; this PR is rebased onto main and stands on its own. ### Testing At head `303c90ffa` (base: main `bfc38485`; 24 files, +1125/−146): - Focused unit suites (respondToAllowlist, workflowFormTypes, hostedCommunityApi bound-key helpers) green; mutation-checked — dropping allowlist canonicalization fails the dedupe case, and dropping bound-key normalization fails the npub-in-hex and padded same-key cases. - Full desktop unit suite 6,477/6,477, `desktop-typecheck`, `desktop-check` (formatting fixed narrowly with `biome check --write` on the touched files only), and a fresh E2E build at the current head; the add-community + hosted-communities-settings specs 18/18 and onboarding integration 69/69 on a fresh dedicated port, with focused new-case runs 4+4 covering padded same-key (ready, Connect kept — no false rebind) and npub-in-hex (neutral label, recovery, no Connect) across the settings card, create flow, and first-community onboarding, plus the unlinked-account settings regression asserting Connect cannot occur. - `cargo fmt`/clippy (both feature sets) and `cargo test identity` (71 pass) passed at the earlier full-change head; since then, the only production changes in this PR's delta are the hosted identity display authority and its fail-closed bound-key gating/normalization above (base-side fixes carry block#7488's receipts) — every other change is test-only. ### Task provenance Buzz channel: `1f0e4a3d-7e01-4efe-bb16-843b357f85c9` Task: buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6 --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
) 🤖 ## Summary In the mobile app, anyone who hasn't set a display name shows up as a raw 64-character hex key (e.g. `3a5d4f9c…`) — unreadable, and unrecognizable as the same identity across screens. Profile and Settings also let you copy that raw hex. Nostr public keys have a standard readable form — `npub1…`, the same encoding other Nostr apps and our desktop app already display. This PR makes every mobile identity surface render npub instead: - **Unnamed people everywhere** — message and thread authors, reactions, typing indicators, member lists, channel details, DM headers and tiles, inbox, search, forum cards, Pulse notes and reply context, mention suggestions, and invite rows — now show a compact npub label: first 8 + last 4 characters of the full npub joined by an ellipsis (`npub1abcd…wxyz`), the same truncation desktop uses. Previously these showed truncated raw hex. - **DM fallback avatars and blank names** — 1:1 DM tiles and headers key their fallback avatar to the same non-self counterpart the label names, including self-first participant order; a self-DM keeps its hex-key-derived initial. Blank or whitespace-only display names fall back to the compact npub instead of rendering empty, while nonblank authored names render verbatim (padding included). - **Profile sheet → "Copy public key"** now copies the full canonical npub — never raw hex. When the identity string isn't a valid public key, the copy tile is disabled, so a malformed key never reaches the clipboard. - **Settings → Identity (pubkey)** displays and copies the full npub; an invalid identity reads "Identity unavailable" with copy disabled. - **Invalid identities never leak truncated raw hex** into the UI anywhere — they render a neutral "Unknown identity" label. - **Unchanged on purpose:** display names and verified handles (NIP-05 — the `name@domain` badge) still render as before. Unnamed avatars keep distinct per-key initials, derived from the underlying hex key rather than the npub — otherwise every unnamed key would render the same "N" initial. Event IDs are not public keys, so they keep their hex truncation (in Pulse's "Replying to", the parent author shows npub while an event-id fallback still shows hex). The nevent share link, private keys, and internal hex storage are untouched. Inputs that accept a key (invite/member entry) accept both hex and npub and keep working in hex internally. ### Related issue N/A. Searched open issues/PRs for npub identity display on mobile — closest related: none found. Desktop's parallel npub standardization lives in the stacked desktop PRs (block#7488 foundation, block#7489 controls, block#7495 display surfaces); this is the independent mobile slice (based directly on `main`, not on those branches). ### Testing At exact head `5a620e420a1fd57d9d8011ac26434eed32fcf765` (base: `main` `44316ff72`; 40 files, +1,345/−154): - Full mobile suite: 2,098 tests passing (`cd mobile && flutter test`); `flutter analyze` clean; `dart format --set-exit-if-changed .` clean — the same checks CI runs. - Widget/unit coverage at production seams: compact labels and hex-keyed avatar initials for DM headers/tiles, member rows, mention suggestions, and Pulse reply context; DM fallback avatars keyed to the labeled counterpart (self-first order and self-DMs); blank/whitespace display-name npub fallback with nonblank authored labels verbatim, including the Activity inbox sender and profile-sheet heading (each with its own empty/whitespace production-seam regression); full-npub copy and disabled-copy semantics in profile and settings; invalid-key suppression; and hex↔npub input round-trips. Verified via unit and widget tests — no device/simulator validation is claimed. ### Task provenance Buzz channel: `1f0e4a3d-7e01-4efe-bb16-843b357f85c9` Task: buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6 --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
…ock#7503) 🤖 ## Summary - Channel members appear in the Members sidebar. A member who has never set a display name is listed under an abbreviated form of their public key (npub), and the sidebar previously sorted those unnamed members by that short label. Short labels are not unique — different keys can share one — so the order of unnamed members could look arbitrary or unstable. Unnamed members now sort by their full public key, so the order is deterministic. - When two members display the same name, the previous tiebreak was membership order (who joined first), which is not visible to a reader and can shift as roster data loads in. The tiebreak is now the full public key, so identical display names always land in the same order. - Nothing gets noisier on screen: the full key is used only for sorting, and the sidebar still shows the compact abbreviated form. Priorities are unchanged — authored (custom) names still outrank fallback labels, and role/current-user grouping still applies. - Scope is the desktop app's Members sidebar and member management: the two existing sort comparators. Mobile and other lists in the app are untouched. ### Related issue Based on block#7495 (introduced the abbreviated npub labels this follows up on). The original five presentation PRs remain independently reviewable. No closer duplicate found. ### Testing - 6469 desktop unit tests, typecheck, and check pass. - The 3 existing consumer-seam E2E tests still pass; a new E2E test asserts the sidebar lists unnamed members in full-key order, with fixture members deliberately inserted in the opposite order so incoming membership order cannot mask the sort. - Negative check: reverting only this change makes the new ordering assertion fail, so it genuinely binds the new sort. - CI has not run on this PR yet. Buzz provenance: channel 1f0e4a3d-7e01-4efe-bb16-843b357f85c9 / task 340c3de9b27dbedb8453c0c7652220f9080d30fcc70a7c4f6e27fdd4fa378056 --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
## Summary Buzz considers codex-acp 1.6.2 current because the supported adapter floor is still 1.1.7. That adapter bundles Codex 0.148.0, so updating a separate Codex CLI to 0.153.4 leaves managed agents on the older runtime and unable to use GPT-6 Astra. Raise the supported adapter floor to the published 1.10.0 release, which depends on `@openai/codex ^0.153.3`. Existing discovery and installation code then classifies older adapters as outdated and offers the managed reinstall path. Update the availability and install-plan regressions to cover the observed 1.6.2 installation and the new minimum. This follows the existing version-floor policy. It does not automatically update a running installation: the user must complete Buzz’s offered adapter upgrade. Future upstream compatibility changes may require another floor update. ### Related issue No exact duplicate found in searches for Astra, CODEX_PATH, bundled Codex, outdated runtime, and codex-acp 1.10. Related: block#3097 raised the older floor to 1.1.7 (already present on main); block#2422 covers lost error details for runtime mismatches. Neither resolves this version gap. Originating conversation: buzz://message?channel=3286cd76-f83e-4c7d-8317-10a16580744d&id=8b79a73078217222b870fff144c27e7d27bcd5a67c966869c18fe726db716898 ### Testing - Isolated npm install of codex-acp 1.10.0 resolved bundled Codex 0.153.4, with no CODEX_PATH override. - Live macOS ACP probe: initialize protocol v1 → session/new → select gpt-6-astra[medium] → prompt. Received `OK` and `stopReason: end_turn`; usage metadata confirms gpt-6-astra. - Existing adapter 1.6.2 initialized but advertised no Astra model in the same probe. - Desktop Rust formatting and `git diff --check` pass. - `just desktop-tauri-test`: 3,266 passed, 20 ignored, zero failures across the Desktop workspace and integration tests. - Workspace and Desktop Clippy, frontend static checks, and `just file-size-check` pass. - Repository `just ci`: still running the remaining mobile/build/workspace-test stages. The installed Buzz app and managed adapter were not replaced or restarted. The live check validates the new adapter/runtime path; a complete packaged Desktop upgrade workflow remains untested. Signed-off-by: Stephen DeLorme <stephen@d.elor.me>
## Summary When an agent reports model-not-found, Buzz retries the unavailable model and delays the failure reply until retries are exhausted. Stop retrying this error and immediately post a threaded recovery notice. The notice tells users to select a different model in agent settings, save, restart the agent to apply the configuration, and re-send their request. This adds one error-handling branch and regression coverage in `buzz-acp`. It matches `-32002` errors containing `model not found`. Other resource-not-found errors, such as stale sessions, retain the existing retry behavior. Detailed error events remain available for diagnosis. The existing restart policy is unchanged. ### Related issue None found in existing issue/PR searches for model-not-found recovery. ### Testing Playwright captured and visually checked the thread UI with seeded conversation data and the exact recovery text. The check opens the request's thread, confirms no reply before the failure, injects the notice, and verifies the full text is visible. [Before/after screenshots](block#7538 (comment)) show the corrected save-and-restart instructions. These are local test captures, not a deployed provider recovery flow. Generated with Codex --------- Signed-off-by: Diem Nguyen <diem@squareup.com>
… narrow panes (block#7550) ## Summary Fixes two instances of the same dead-truncate pattern in the desktop app, where a flex item's implicit `min-width: auto` prevented `truncate` from engaging, so long text painted over adjacent controls instead of ellipsizing: - **Inbox detail title** (`InboxDetailPane.tsx`): the clickable context-title button sized to its text instead of shrinking with the pane, overlapping the header controls (open-in-channel, members, huddle, more menu). Fixed by adding `max-w-full`. - **Message author names** (`MessageHeader.tsx` / `UserProfilePopover.tsx`): the `UserProfilePopover` inline-flex trigger wrapper refused to shrink below the name's nowrap width, running long author names under the hover action bar and off the pane edge. Fixed by adding a `triggerClassName` prop to `UserProfilePopover` and passing `min-w-0 max-w-full` at the author call site. Two other suspected instances (project file breadcrumb, drafts pane title) were stress-tested and already truncate correctly — no change. ### Related issue N/A — none found. ### Testing - New Playwright regression tests for both fixes (`inbox-title-overlap.spec.ts`, `message-author-overlap.spec.ts`, registered in the smoke project), each proven to discriminate: they fail with the fix reverted (real measured overlap) and assert the ellipsis actually engages with non-zero title width, so they can't pass vacuously. - Typecheck, lint, and full desktop unit suite green (pre-push hooks); full desktop e2e smoke suite run earlier: 1402 passed, 3 pre-existing unrelated failures (each fails identically with the fix reverted). **Inbox title — before** (long title paints under the header controls):  **Inbox title — after** (truncates with ellipsis, controls stay clear):  **Author name — before** (long name runs past the header row edge):  **Author name — after** (clean cutoff):  🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: cynfria <yescynthia@gmail.com> Signed-off-by: Tree Trunks <6ba22921d9dc2ad0aa6ecdf63787ddd24726e266d866da31af69f2e4e146ace5@buzz.block.builderlab.xyz> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Tree Trunks <6ba22921d9dc2ad0aa6ecdf63787ddd24726e266d866da31af69f2e4e146ace5@buzz.block.builderlab.xyz>
) ## Summary - Reject kind:20001 presence events with `OK false` / `error: presence storage unavailable` when Redis SET or DEL fails, before publishing, local fan-out, or local-event marking. - Preserve the producer contract needed by snapshot-confirming consumers: delivered live presence must follow successful mutation of the Redis state read by snapshots. - Classify those backend rejections with the existing `IngestError` taxonomy so a presence storage outage counts as `buzz_events_rejected_total{transport="ws",reason="error"}`, not client `reason="invalid"`; genuine client-input refusals (verification failure, membership gates) stay `invalid`, and every wire message is an unchanged fixed sanitized string (review follow-up, no protocol wording change). - Add actual `handle_event` integration coverage for rejected online/offline transitions, healthy online→offline accepted/stored/fanned-out behavior, and the rejection-counter routing on storage failure with an invalid-signature control. This is standalone on main; it does not depend on the mobile implementation. Deploy this relay prerequisite before relying on block#7526's snapshot-confirmation policy. Existing pubsub-failure-after-successful-storage behavior and disconnect TTL cleanup are deliberately unchanged. A storage error may be an ambiguous write outcome, not a rollback guarantee; the rejected event is not published by this handler. Clients may retry the generic `error:` rejection. Desktop's 60s heartbeat retries non-offline presence, not every explicit offline transition. ### Related issue Addresses the relay prerequisite identified in [block#7526 review 5157607827](block#7526 (review)). Searched open presence/storage PRs; no duplicate relay storage-error rejection fix found. block#7382/block#7383/block#7526 heads and bases are unchanged. ### Testing Exact head: `389174df29cc02d0f885c03209eff661d8bb2ec0` (+380/-13; 393 total), one commit `389174df2` on top of the reviewed `c031d6eb1` (DCO-signed; base `bfc384855889432df4a333a0edf3080f332ee169` unchanged). - PASS: `cargo fmt --all -- --check`, `cargo clippy -p buzz-relay --all-targets -- -D warnings`, `git diff --check`, `just file-size-check`, PostgreSQL discovery validation — all run at the exact final head with a clean tree before and after. - PASS: documented native `scripts/postgres-test-run.sh -p buzz-relay --lib --tests`: **89/89** actual integration tests, including the four presence cases (online/offline storage rejection, healthy online→offline, and the new rejection-classification case). Owned PostgreSQL 17/Redis on isolated loopback ports, schema plus reconciliation applied; no shared development database. - PASS: explicit `cargo test -p buzz-relay presence_storage -- --ignored --nocapture`: **4/4**, not skipped. - Full isolated relay crate suite at the final head (`cargo nextest run -p buzz-relay --lib --tests`): **1062 run: 1062 passed, 94 skipped**. The previously failing `api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo` passed in this run (1.5s); it is a known timing-sensitive main baseline failure tracked open in block#7140 and untouched by this PR, so this single passing run is reported as-is and does not claim environmental clearance or close block#7140. No full-suite-green claim is made beyond this run. - Mobile is untouched; block#7526's existing 2090-test/format/analyze evidence remains scoped to its unchanged head. Its separate Desktop Smoke E2E (2) failure remains red; no CI retries requested. [Production-seam regression coverage](https://github.com/block/buzz/blob/389174df29cc02d0f885c03209eff661d8bb2ec0/crates/buzz-relay/src/handlers/event.rs#L1491-L1803): the metric case drives real `handle_event` traffic against a genuinely dead Redis endpoint with a seeded active PostgreSQL community and a registered presence watcher, asserts the storage rejection counts `reason="error"` while a tampered-signature control through the same dispatcher arm stays `reason="invalid"`, and re-asserts the rejected ACK, no fan-out, and no local-event marker. Counter assertions use a thread-local recorder guard held across `.await` points (the buzz-db counter-test convention) inside the per-process nextest postgres-ci lane, so no parallel test can race the counter snapshot. No UI change or screenshot. Local logs and reproducible service/gate scripts are retained under `WORK_LOGS/MOBILE_FEEDBACK_PRESENCE_20260909/relay_prerequisite/metric_correction/` in the engineering workspace. This PR is a review candidate, not merge clearance. Causal checks: restoring only the pre-fix production mutation block makes both original rejection tests fail (`OK true` instead of `false`); healthy success still passes. Reverting only the typed classification (mapping the ephemeral `Internal` arm back to `invalid`) makes the new metric regression fail with the outage counted as `[("ws","invalid",2)]` instead of `[("ws","error",1),("ws","invalid",1)]`. The unchanged mesh echo case also failed 504/200 with the main-production block restored in the prior run, supporting its separation from this change without claiming environmental clearance. Candidate source restored byte-for-byte after each mutation. Repository-wide `just ci` was not rerun; the scoped relay gates above are the new evidence. --------- Signed-off-by: Logan Johnson <loganj@squareup.com>
**Category:** fix **User Impact:** Human and agent mentions now break across lines with the same cloned chip treatment as repository and permalink chips while preserving the conversation text rhythm. **Problem:** Profile-backed rendered mentions sat inside an `inline-flex` popover trigger, unlike entity chips, so the wrapper interfered with true inline fragmentation. The browser-layout test measured text-range rows rather than the painted chip rectangles, allowing touching decorations to pass as “separate” fragments. **Solution:** Keep the profile trigger interactive but override its layout to true `inline`, then give mention fragments 18px computed leading inside the message’s 20px prose rhythm. Chromium paints each fragment at 17px and advances it by 20px, leaving a visible gap between cloned rounded rectangles. The browser test now measures the chip’s own `getClientRects()` and asserts fragment count, height, gap, and step; entity links retain their existing 22px leading. <details> <summary>File changes</summary> **desktop/src/features/profile/ui/UserProfilePopover.tsx** Allows inline consumers to override the trigger wrapper’s layout without changing other profile-popover call sites. **desktop/src/shared/styles/globals/markdown.css** Keeps one shared wrapping-chip mechanic and gives mention decorations enough room to separate visibly within 20px prose. **desktop/src/shared/ui/markdown.test.mjs** Pins both rendered mentions and entity links to the shared wrapping-chip contract. **desktop/src/shared/ui/markdown/MarkdownMention.tsx** Makes the profile-popover trigger truly inline so the nested mention chip can fragment with surrounding prose. **desktop/src/shared/ui/mentionChip.ts** Keeps `wrapping-inline-chip` as the single contract for fragmenting decorated chips. **desktop/tests/e2e/mentions.spec.ts** Measures the painted chip rectangles, requires a positive fragment gap, and verifies the inline trigger remains mouse- and keyboard-operable. **desktop/tests/e2e/navigation.spec.ts** Keeps a wrapped repository chip as the control, asserting its existing 22px line height and fragment advance. </details> ## Reproduction steps 1. Open a channel in Buzz Desktop using dark theme. 2. Send a message containing a human mention and another containing an agent mention; both chips should remain aligned with adjacent text on a 20px line. 3. Render a collision-qualified mention in a narrow message width; it should break into separately decorated fragments exactly like another wrapping chip, while each fragment follows the 20px prose rhythm. 4. Render a long repository or permalink chip in the same constrained width; it should retain its roomier 22px fragment spacing. ## Screenshot The dark-theme production renderer shows the real qualified label (`bob (npub1hv3…tpuc)`) at an 8rem width. The two lines now paint as visibly separate rounded fragments rather than one continuous rectangle.  ## Validation At commit `2b063e1b4ade30e11f1616269ad4ba4190366885`: - Pre-push desktop gates — file-size check, Biome/checks, typecheck, and 6,483 unit tests passed - `pnpm --dir desktop build` — passed - Focused Playwright coverage for single-line agent mention, single-line human mention, wrapped qualified mention including keyboard profile activation, and timeline mention click — 4 passed - `git diff --check` — passed --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Rizz <rizz@agents.buzz> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
## Summary PR block#7335 worked around missing Pi adapter support by generating a private Pi launcher and injecting Buzz's standing prompt and skills at process launch. The Buzz Pi fork now carries the required adapter extensions, so this removes that launcher and returns prompt construction to the normal ACP session path while retaining the base-prompt composition introduced by block#7335. The Pi preset now installs `salman1993/pi-acp` and launches its renamed `buzz-pi-acp` binary. Buzz adds `-- --skill <harness-cwd>/.agents/skills` only when launching that binary, sends the complete composed prompt as the `_meta.systemPrompt` replacement string on `session/new` only when `initialize.agentInfo.name` is `buzz-pi-acp`, and sends the scoped title alongside it as `_meta.sessionTitle`. The fork identity is treated as system-prompt capable regardless of its reported ACP protocol version, which prevents duplicate legacy user-message framing. Upstream `pi-acp` does not receive either fork-specific behavior. Observer transcript projection accepts the string, `{ replace }`, and `{ append }` metadata forms. The fork now stores restore metadata in one atomic file per session under `~/.pi/buzz-pi-acp/sessions/`. This prevents concurrent Buzz workers from overwriting another session's prompt or title. The fix landed in [salman1993/buzz-pi-acp#9](salman1993/buzz-pi-acp#9). This supersedes the closed block#7508. No agent-configuration rules changed; this changes the Buzz Pi adapter contract and launch arguments. ### Related issue block#7329 ### Testing Installed fork commit `09cf07e436b8f18e52401558f988f31a15702313` through the documented Git URL. The installed bundle matched the committed `dist/index.js` byte for byte and contained the `~/.pi/buzz-pi-acp` metadata path. The fork's 106 non-skipped tests, typecheck, and lint pass. Ran the ignored real-Pi integration test through Buzz's production session composer. The test exercised the renamed package, `agentInfo.name`, and the new per-session metadata store. Base, persona, team, core-memory, huddle, canvas, and skill markers each appeared once after switching sessions and again after restarting the adapter, while the other session and Pi's native default prompt were absent. Added regression coverage proving `buzz-pi-acp` receives fork-specific system-prompt metadata and managed skills while upstream `pi-acp` does not. `just ci` passes. Generated with Codex --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com>
Authored by Brain and opened on behalf of Wes (`wesbillman`).
## Summary
Add `buzz repos default-branch get/set` and the relay operation it
needs. Git push changes branch tips but cannot select the server's
symbolic HEAD. This selects an existing branch without deleting
branches, moving refs, or changing packs.
- Reuse immutable Git manifests and captured-ETag CAS; concurrent
pushes/default changes conflict, including stale no-ops.
- Require request-specific NIP-98, body binding, fail-closed replay
protection, host tenancy, current channel membership and
repository-management authority. Push/project roles alone are
insufficient.
- Send the CLI mutation once, without redirects; ambiguous delivery
retains the observed digest and is non-retryable.
- Document the narrow HTTP exception to Nostr-first, delegation scope
and admission-time ACL revocation. Kind:30618 remains derived; the
manifest CAS is authoritative.
- Register the Postgres/Redis/MinIO route and fresh-clone tests in the
existing Backend Integration lane.
## Review fixes — `7457bda50afa6b52e36315572fa96928b2e21db6`
- Reject signed valueless `payload` tags when verifying a body; preserve
the shared verifier's intentional absent-payload compatibility. The real
settings route now proves 401, unchanged manifest pointer, and unchanged
kind:30618 event IDs for malformed and other invalid credentials.
- Require a nonempty returned branch and `head == refs/heads/{branch}`;
SET additionally requires the returned branch to equal the attempted
branch. Malformed/mismatched mutation responses remain non-retryable
`DeliveryUnknown`, retaining the attempted branch and original digest.
Invalid reads fail before any POST.
- Strengthen the later-push/fresh-clone regression to select
`release/v1` while `main` exists.
## Verification
At `7457bda50afa6b52e36315572fa96928b2e21db6`:
- Full pre-push passed (all 14 Rust test groups, serialized Tauri
tests/clippy, file-size and branch-skew gates; 522 seconds). No hooks
disabled.
- All four isolated Postgres/Redis/MinIO settings tests pass, including
real Smart HTTP `ls-remote --symref` and fresh clone on `release/v1`.
- All 15 focused NIP-98 verifier tests and three default-branch CLI
tests pass; malformed credentials and missing-branch responses were
reproduced failing before the fixes.
- Five targeted mutants are killed: restoring the valueless-payload
bypass, weakening canonical HEAD validation, accepting an empty branch,
dropping requested-branch confirmation, and omitting hydrated HEAD for
the selected non-main branch. Mutations ran in a separate worktree and
target directory; original patch bytes were verified restored.
- `cargo clippy -p buzz-auth -p buzz-cli -p buzz-relay --all-targets --
-D warnings`, formatting, diff and file-size checks pass on the fixed
tree.
Earlier validation at `bb195b140`: seven Git authorization database
regressions and 28 existing transport tests passed (three infrastructure
cases in that module not selected); four mutations killed
management/replay/CAS/redirect regressions. Full pre-push passed with
`RUST_TEST_THREADS=1` (all 14 Rust groups, Tauri clippy and 3,173 tests,
size/branch-skew gates). Serial execution avoids an unrelated
process-global Desktop discovery counter flake; no hooks disabled or
Desktop changes.
Carl and Jude's review findings are addressed in the new head; **fresh
reviewer and exact-head security confirmation remain required**. The
earlier source review is not approval of this revision. No claim of
green new-head CI.
## Rollout
Merge and **deploy the relay support first**, then build/use the updated
CLI and explicitly set/verify the desired default. No schema migration.
This PR does not deploy anything or change a live repository.
Originating conversation (channel
`8e2fda80-9142-4df8-8e14-da288bea6471`):
buzz://message?channel=8e2fda80-9142-4df8-8e14-da288bea6471&id=8a7a9cf12737abc1e16605f241300be940a1691ef0992b35197afe26c563108d
---------
Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Pinky, acting on Wes’s behalf. ## Summary Fix a pre-existing false-failure mechanism in the login-shell discovery tests, independently of the thread-window work. - Run the two process-global-counter assertions in separate, bounded libtest processes. The global counter still sees all of each test’s auth/worker threads; no production discovery or cache behavior changes. - Require exact test selection, successful child exit, and a completion receipt after the assertion body. Reject counter resets outside isolation. - Keep the cold/repeated cheap-discovery zero assertions and forced-discovery positive control unchanged. Add parent-probe exclusion and own-worker controls. Three test-only files, +103/-4. Base `00209076c7a10d9e4a475466c313e8ebecf041f5`; head `07f8e86c58a0aa5378537a3daa4610361fdbad55`. ## Reproduction and causal checks On the main-based source, cheap discovery alone counted 0. Running the existing missing-binary version test on another thread inside the measurement interval made the repeated-cheap assertion fail with 1. This reproduces interference, not the exact scheduling/caller behind the original full-suite failure. The counter measures `run_in_login_shell` invocations, not exact OS process counts. Mutation checks: - Removing isolation fails the measurement guard. - Removing the real production `record()` call fails the original forced positive control (0). - A reviewer-identified early-return mutation initially survived; the final helper owns the assertion closure instead. Deleting that invocation now fails the external worker assertion (0 vs 1). Independent read-only review by Brain found no remaining blocking finding at the final head. ## Validation at the final head - **Full `just ci`: passed.** Desktop Tauri: 3,175 passed, 19 ignored; desktop JS: 6,488 passed; mobile: 2,098 passed. Remaining Rust unit, formatting/lint/static, and desktop/web build lanes passed. An initial five-minute tool timeout interrupted compilation; the subsequent complete run used a longer deadline, not altered gates. - **Normal pre-commit and pre-push hooks: passed.** Push includes default + mesh Tauri Clippy, complete Tauri workspace tests, size and branch-scope checks. No bypass. - **Real local relay, following `TESTING.md`: passed.** Built release `buzz-relay`, `buzz`, and `buzz-admin` from this head. Fresh isolated Postgres/Redis, additive migrations, dedicated loopback ports, disposable NIP-98 identity; health, create channel, signed root/reply send, exact-ID get, and thread retrieval passed. Relay and both owned containers stopped afterward. Dev auth-token mode follows the guide; not a production-auth or thread-window/native UI acceptance claim. ## Scope and limits This is process/cache isolation, not a filesystem sandbox. The existing bounded-command helper bounds child runtime/output; its Unix process-group behavior does not guarantee ownership of nested probe groups. Existing individual probe deadlines remain unchanged. No UI change, screenshots, thread-window commits, feature enablement, merge, or deployment included. Originating conversation: buzz://message?channel=0b73400d-6922-4fe3-9b97-454da04d011b&id=984528c883faa8829971d19394870f0f2d543d5c1950565fd55ffddc1b8d8022 Signed-off-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz> Co-authored-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz>
…rs (block#6998) Pinky, an AI agent, is updating this PR on Wes's behalf. ## Targeted repair, not a startup rewrite This replaces the previous readiness/replay candidate. Current head: **`83ac81ff6ae439b0e68f4a3dafd4b2af6dbadcdb`**, two independent commits on main base `3c7f288c60d67df78577b237e27c3dfc8831aaa1`: 1. **`0b6681de2` — keep quota backoff on its transport.** HTTP 429s retain native HTTP backoff without arming the renderer WebSocket gate. WebSocket quota/concurrency CLOSED retains its retry deadline without damping HTTP's separate quota. The explicit `rate-limited: shared admission unavailable` signal still damps HTTP too. Reuse existing all-channel live streams for Home mention invalidation instead of a second per-channel mention subscription; retain kind, recipient, self, membership, replay-deduplication, notification and unread boundaries. 2. **`83ac81ff6` — reuse complete membership rosters within each channel fetch.** Discovery already returns full paginated rosters. Retain those results and request only uncovered directory/pending-owner channel IDs. Skip empty fallback filters and preserve covered counts/pubkeys if that tolerant fallback fails. The next fetch reads current membership again; there is no persistent cache or new invalidation state. No send-route, AUTH-readiness, replay-ordering, scheduler, pacing, relay policy, dependency, or authorization changes. Refresh triggers and activity batching stay unchanged. Independent tracing did not establish a safe-to-delete duplicate normal-startup refresh; recovery invalidations at different replay stages are not interchangeable. **Size:** 18 files, +1,066/-350 overall; nine production paths +76/-205 (**129 fewer production lines**). The roster follow-up itself touches only `fetch.rs` (+31/-14) and its new 378-line regression file. Test bulk is not new startup machinery. ## Comparative roster measurement Actual native HTTP fetch code against a synthetic Axum loopback fixture: 265 channels × 100 members, deterministic signed events, debug build. Seven iterations per condition; medians below exclude the first warm-up. Baseline is `0b6681de2` production fetch, candidate is `83ac81ff6` production fetch, with the same temporary benchmark harness. | Measure | Baseline | Candidate | | --- | ---: | ---: | | HTTP reads per fetch | 7 | 6 | | JSON response-body bytes per fetch | 4,803,136 | 2,453,380 | | Median, no artificial request delay | 262.9 ms | 149.3 ms | | Median, 25 ms artificial delay per request | 376.7 ms | 282.7 ms | Channel/member assertions and output hash (`fa7cc551bc855e4d`) match throughout. This removes **one batched roster request**, not 265 requests. The measured ~43%/~25% reductions describe this large-roster synthetic fixture, **not production startup latency**. Activity requests run concurrently with roster enrichment, so real savings vary. Counts now use the discovery snapshot rather than a later reread within that same non-atomic fetch. ## Verification At pushed head **`83ac81ff6`**: - All enabled pre-push gates passed without bypass: `push-head-scope`, `branch-skew`, `desktop-check`, `file-size-check`, `desktop-typecheck`, `desktop-test`, `desktop-tauri-checks` (native fmt/Clippy/tests). - Full Desktop frontend suite: **6,457 passed**, zero failures; separate tooling suite: **10 passed**. - Full native Desktop workspace: **3,276 passed**, zero failures, 20 ignored, across unit/integration/doc bins. The main native lib accounts for 3,175 passed / 19 ignored; this is not 3,276 lib tests. - `pnpm build` passed the protected-feature artifact matrix; `pnpm build:e2e` also passed. Existing chunk-size/dynamic-import warnings remain. No full repository `just ci` run locally. - **130 selected headless mock-bridge smoke cases passed across two runs**: messaging, reconnect, reconnect affordances, inbox live updates, sidebar snapshots, send-channel binding, boot splash. The first invocation hit the shell's 300-second limit after 117 passes and no failures; all remaining 13 passed in a separate invocation. This is not a claim the interrupted invocation completed. - Six new tests invoke production `fetch_channels` through the real native HTTP client and cover 265/501-channel pagination (including same-timestamp continuation), exact requests, complete/deduplicated rosters, directory/pending fallback, hidden DMs, repeated membership/identity/relay changes, empty lists and errors. - Falsification in an isolated source copy: restoring the redundant baseline fetch fails four roster regressions; removing missing-roster fallback fails two. Restored candidate passes all six. Earlier transport mutations fail HTTP→WS coupling, WS→HTTP coupling, omitted shared-outage damping and duplicate-mention regressions. No mutation was applied to the committed worktree. - Brain's independent static reviews found no blockers in the transport repair or immutable roster patch. Source tracing confirms relay discovery serializes complete rosters, not projected matching p-tags. This was source review, not independent test execution or approval. ### Hosted CI [CI run 34185670504](https://github.com/block/buzz/actions/runs/34185670504) **completed successfully** for this head. GitHub checked out merge ref `25bcf09627e129887481d4441a1af3c6cb0af1ff`; its Git tree is identical to `83ac81ff6` (`49a01f3c2ebf4e547bbe3cd31406f984ac6cefe8`), verified through the GitHub Git API. The final PR rollup has 33 successful checks and 28 skipped, with none failing or pending. - Desktop Core passed frontend/native suites and compiled-feature verification. Windows Rust tests, Windows Desktop build, macOS `.app`/`.dmg` build, all four smoke shards and both relay-backed E2E job shards passed, as did Rust lint, DCO, Semgrep and zizmor. - Browser job totals: **1,620 passed, 13 flaky cases passing automatic retry #1, five skipped, zero terminal failures**. Smoke accounts for 1,389 passed / 12 flaky / three skipped; integration jobs report 231 passed / one flaky / two skipped. No manual CI reruns or source changes to obtain green. - The retries cover channel-browser, empty-edit, huddle menu, runtime-install, overscroll, onboarding avatar, video attachment and workflow UI assertions. They are disclosed, not collectively declared unrelated. The video case reaches the changed real TS all-channel hook despite mocking native I/O: independent tracing found no regression mechanism or blocker, but the missing second-video player remains **unexplained**, and an indirect timing effect is not excluded. The self-authored, unmentioned event cannot use either the new mention branch or the removed old mention handler as a delivery path. A passing retry does not diagnose its cause. - The separate “Desktop Release Candidate” check skipped its validation step; it is not additional release validation. Codex security review was skipped, not passed. ## Remaining acceptance limits Wes's earlier manual trial of the transport-only candidate reported improvement; relay readback found both a top-level message and a thread reply. That was not a fingerprinted or timed trial of the new roster commit. The remaining few-second top-level send wait is **unattributed**, and this PR does not claim to resolve every startup delay. No visible app launch, relaunch, laptop input automation, or production test messages during this follow-up. Local native tests use fixtures; browser suites do not run the Tauri backend. CI building an app does not validate bundled macOS alerts or inactive-channel notification delivery. Those bundled real-workflow checks remain outstanding. No visual UI changes or screenshots; no approval or merge performed. Originating conversation: buzz://message?channel=5b130804-d759-40ad-a564-d64cc907fa8e&id=cb432417e290e06a1842639cfe0268f9d43d186defbcb59260930d129a26af83 --------- Signed-off-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz> Co-authored-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz>
…ock#7385) <!-- Draft PR body for block#7385 · fix(mobile): bind same-name mentions to exact selected identities · block#7385 --> 🤖 ## Summary If two people in a channel share a display name, mobile couldn't tell them apart in mentions: picking the second could overwrite the first's selection, a rendered mention linked whichever same-name person matched first, and a later rename or a shorter name could re-bind the text to the wrong recipient. This PR binds every mention to the exact selected identity: - Each same-name selection keeps its own recipient instead of overwriting by name; conflicting picks get a qualified label like `Name (key…)`. - The longest matching label wins, so a shorter or interior name can never claim part of a longer one and steal its identity. - Rendering resolves recipients by the signed identity key from the event's tags — never from message text alone — so qualified labels stay correct regardless of tag order and survive later renames; an untagged ambiguous label blocks shorter mentions instead of silently re-binding. Ports the landed Desktop exact-recipient behavior (see `docs/mention-editor.md`). ### Related issue - Fixes: N/A. No mobile issue; Desktop's landed exact-recipient fixes are the reference this ports. - Independent base (`main`). block#7387 (child) persists these exact selections in saved drafts. - Landing note: branches in this series overlap in the composer — when rebasing, keep exact/durable mention bindings, the explicit invite/reference-only choice, the account/visit/revision fences, and authorization before membership preparation and publication; don't resolve conflicts by taking either side wholesale. - Draft — not requesting merge yet; the security advisory run for this range timed out without results (no verdict). ### Testing - Regressions cover same-name collisions, prefix/overlap, removal, tag order, and renames. - At `acd841354a692243f1cb4c04059baad42f1d2abb`: `just mobile-check` and full `just mobile-test` pass (2,082 tests). The earlier full `just ci` receipt linked below is reused only for unchanged non-mobile code/tooling, not claimed as a rerun at this head. - Previous-head evidence: `just mobile-check`, the full mobile test suite, and full local `just ci` all pass — receipts in the [exact-head evidence comment](block#7385 (comment)). - Verification is widget-test level; no native device or simulator run is claimed. To see it: mention two teammates with the same display name — both stay distinct, the second shows a qualified label, and the rendered message keeps both correct even after either renames. ### Screenshots Flutter production-widget test renders — not native-device screenshots or acceptance captures. | Scenario | Before | After | |---|---|---| | Qualified mention chip for same-name recipients, at 200% text scale (deliberate stress fixture) |  |  | <details> <summary>Capture provenance</summary> Rendered by the Flutter widget engine in a `flutter test` run (production widgets, production theme; no device or simulator). Before: this PR's declared base `3c7f288c60d67df78577b237e27c3dfc8831aaa1`. After: its head `39afd73b0adfde14164f4b10dbd089cb498312b6`. </details> Signed-off-by: Logan Johnson <loganj@squareup.com>
Signed-off-by: Junchao Yan <yjc801@gmail.com> # Conflicts: # .github/workflows/_ci-desktop.yml # crates/buzz-relay/src/handlers/event.rs # desktop/src/features/channels/ui/MembersSidebar.tsx # desktop/src/features/channels/ui/MembersSidebarAddMemberRows.tsx
This fork ships the desktop app as Waggle (docs/fork-branding.md), but the Pi preset's setup hint -- new from upstream in this sync -- tells the user to "Restart Buzz". On the AdapterMissing and NotInstalled paths a user who also has an upstream Buzz installed can follow that literally, restart the wrong process, and leave the running Waggle holding its stale PATH with buzz-pi-acp still undiscoverable. Upstream authors these hints, so every future sync can reintroduce one in any preset. Alongside the two pinned Pi assertions, assert the whole class: no preset's user-visible setup guidance may contain the upstream app name. Lowercase command names (buzz-pi-acp, buzz-acp) are binaries, not the brand, and stay allowed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Junchao Yan <yjc801@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated
buzz-fork-syncrun for 2026-09-11, landed by hand (forkmainis behind the PR gate — the script's direct push fails with GH013 every cycle).Upstream commits merged (
block/buzzmain @ d074576):Conflicts resolved
.github/workflows/_ci-desktop.yml— upstream raised its singledesktop-corejob to 60m because that job runs the compiled-flag verification. The fork already splits that intodesktop-core(20m) /desktop-tauri(45m) /desktop-tauri-compiled-flags(disabled per PR ci: split and shard the desktop lanes, and fix a dead path filter #118), so no fork job runs the step the extra budget is for. Kept the fork's jobs and timeouts unchanged.crates/buzz-relay/src/handlers/event.rs— both sides appended tests at the same spot: the fork'sa_read_only_connection_may_not_publishand upstream'spresence_storage_postgres_testsmodule. Kept both. Upstream's tests buildAuthState::Authenticated(ctx)(tuple form); the fork's variant isAuthenticated { ctx, class }, so the two construction sites were converted withclass: ConnectionClass::Interactive.desktop/src/features/channels/ui/MembersSidebar.tsx— import block: kept the fork'suseActiveCommunityRelayUrl(still used) and took upstream'scompareMemberNames+formatMemberNameimport.desktop/src/features/channels/ui/MembersSidebarAddMemberRows.tsx— the fork had renamed upstream'sAddMemberSearchResultRow.tsxto this file; upstream's fix(desktop): npub identity controls across profile, agents, and workflows block/buzz#7489/fix(desktop): npub identity displays for mention, member, and workflow surfaces block/buzz#7495 swappedtruncatePubkey→truncateNpubin the old path. Kept the fork's file and import order and applied the swap (body usages had already auto-merged).Validation (all green)
cargo fmt --all -- --check,cargo check --workspace --tests,cargo check --manifest-path desktop/src-tauri/Cargo.toml --tests,cargo test -p buzz-relay --lib a_read_only_connection_may_not_publish, desktoptsc --noEmit+pnpm test(6636 passed) + biome on both resolved tsx files,just file-size-check, and axyz.block.buzzbranding grep over every file the merge touched (clean). Not run locally: clippy (the sync clone has no git hooks installed, so the push ran no pre-push lanes), upstream's new#[ignore]Postgres/Redis presence tests, and the mobile suites — left to CI.