mirror of
https://github.com/block/buzz.git
synced 2026-10-30 05:51:46 +00:00
desktop-new
2672 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
790d2fb794 |
Add onboarding design system showcase (#7511)
## Summary - add an Onboarding section with the four selected account, return, identity-key, and profile states - compose the flows from the shared Panel, Button, backdrop, typography, and color tokens - add a shared Base UI-backed TextField with `inset` and `soft` visual variants - give the soft variant a neutral fill, two-pixel neutral focus ring, matching error ring, and text-only disabled treatment - use tokenized error and disabled treatments, and keep profile artwork shapes unmasked ## Validation - desktop-new check and typecheck - desktop-new tests (44 passed) - verified the soft variant computed styles in light and dark themes, including exact `0 0 0 2px` focus and error rings - full pre-push suite: desktop, Tauri, and Rust checks passed - visually verified onboarding and text-field specimens in light and dark themes ## Review note The branch includes a merge of current `main`, required by the repository pre-push branch-skew guard. The onboarding foundation is commit `7cd451337`; the soft text-field comparison is commit `ec0fe9ba1`. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
b5070c7fd5 |
Merge main with desktop-new UI work
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
051c3a270b |
fix(buzz-acp): report missing models without retrying (#7538)
## 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](https://github.com/block/buzz/pull/7538#issuecomment-5608196506) 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> |
||
|
|
25091d980e |
feat(desktop-new): expand design system and workspace comparisons
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
12023a3cba |
fix(desktop): require a Codex adapter with Astra support (#7427)
## 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: #3097 raised the older floor to 1.1.7 (already present on main); #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> |
||
|
|
ad9591c43b |
fix(desktop): order unnamed roster members by full canonical npub (#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 #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> |
||
|
|
82656ffea0 |
fix(mobile): standardize public-key identity display on npub (#7493)
🤖 ## 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 (#7488 foundation, #7489 controls, #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> |
||
|
|
2226b6f95b |
fix(desktop): npub identity controls across profile, agents, and workflows (#7489)
🤖 ## Summary Building on #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 #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 #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> |
||
|
|
ad2a84131e |
fix(desktop): npub identity displays for mention, member, and workflow surfaces (#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 #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 #7489, and the shared primitives (`canonicalNpub`, `truncateNpub`, the `<PubKey>` gate, strict input parsing) come from the foundation #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 #7488 (foundation) — this PR does not stand alone on main. - #7489 is a sibling slice on the same #7488 base (profile/agent/workflow controls), not a dependency: this PR does not require #7489, and #7489 does not require this PR — both only require #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> |
||
|
|
bfc3848558 |
fix(desktop): shared npub identity foundation (canonicalNpub, PubKey gate, strict parser) (#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 — #7489 converts the identity controls (profile, settings, allowlist, workflow key fields) and #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 (#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: #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> |
||
|
|
93761e411c |
fix(mobile): render push notification sender identity as npub (#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> |
||
|
|
c045321a7f |
fix(acp): pace targeted overflow recovery on consumer capacity (#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: #5014 (channel re-subscription); also #6661 (membership reconciliation) and #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](https://github.com/block/buzz/pull/7325#issuecomment-5540592398) 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> |
||
|
|
218633b8fd |
fix(link-preview): keep composer fetches user-paced (#7211)
**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> |
||
|
|
3ef9466878 |
docs(desktop-new): preview glass on paired backdrop
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
f4afd71894 |
Merge remote-tracking branch 'origin/main' into desktop-new
* origin/main: feat(mesh): upgrade to mesh-llm 0.76.0-rc8 and recommend Qwen3.8 27B (#6189) fix(agent): route GPT-5+ model-service FQNs to Responses (#7358) fix(buzz-acp): wake held ACP threads and fence forked sessions (#7340) fix(mobile): style inline code with the app mono face (#6631) chore(release): release Buzz Desktop version 0.5.23 (#7381) fix(desktop): keep packaged frontendDist relative so Windows embeds assets (#7177) fix(sidebar): simplify unread indicators and emphasize priority activity (#7134) Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
68ee405064 |
feat(desktop-new): shape product UI and system docs
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
44316ff72f |
feat(mesh): upgrade to mesh-llm 0.76.0-rc8 and recommend Qwen3.8 27B (#6189)
Upgrades Buzz's mesh-llm dependencies through the released `v0.76.0-rc8` tag (`2040765d`), including the Qwen3.8 curated recommendation and rc8's scheduler/runtime improvements. **Scope note:** the earlier open-relay/unenforced-admission mode has been removed from this PR at Mic's direction — it is not a product mode we want. Mesh admission remains roster/allowlist driven, exactly as on `main`: on a relay with no NIP-43 membership snapshot the mesh runs self-only. No NIP-11 mode probing, no mode-transition restarts. A future perimeter/admission strategy for open relays will be designed separately. This PR also: - seeds `BUZZ_AGENT_LLM_TIMEOUT_SECS=660` for mesh agents, above MeshLLM's 600-second backend timeout; - makes `desktop-tauri-clippy` lint both default and `mesh-llm` cfg graphs; - runs the feature-enabled desktop test suite in CI; - recommends Qwen3.8 27B Q4_K_M for 64 GB-and-larger machines, then ladders down through Gemma 4 E4B and Qwen 9B for smaller machines; - keeps stored shared-compute `auto` translated to MeshLLM's supported wire model `mesh`. RC8 verification: - `just ci` passed locally at `92ecc7ec933bdd4df804cc9f28a2b51efa5313c5`. - Pre-push differential gates passed, including both desktop Tauri cfg graphs and package tests. - A prior isolated runtime smoke used the RC8 binary's OpenAI endpoint for a Buzz-shaped system/user/tool/tool-result/final-response loop; all assertions passed and the isolated process was shut down. Perf previously measured on M5 Metal, Qwen3.8-27B-Q4_K_M: TTFT 0.22–0.32 s, ~25 tok/s streaming; agent-shaped turns ~1 s to first token after the first (prefix cache). --------- Signed-off-by: Michael Neale <michael.neale@gmail.com> Signed-off-by: Jimmy <1fe240cd1a8cf775f6f3060f115e5a303181f3abf28ad4cb0c2515f4a02b36a8@meshllm.communities.buzz.xyz> Signed-off-by: Alessandro Joabar <sandro@squareup.com> Co-authored-by: Michael Neale <michael.neale@gmail.com> Co-authored-by: Jimmy <1fe240cd1a8cf775f6f3060f115e5a303181f3abf28ad4cb0c2515f4a02b36a8@meshllm.communities.buzz.xyz> Co-authored-by: Mic Neale <305999590+micspiral@users.noreply.github.com> Co-authored-by: Alessandro Joabar <sandro@squareup.com> |
||
|
|
cd54e26823 |
fix(agent): route GPT-5+ model-service FQNs to Responses (#7358)
## Summary Route Databricks Unity Catalog model services to OpenAI Responses when their service name matches GPT-5 or newer. These models can reject tools plus reasoning on Chat Completions. Match only the service component, using the existing family-token boundaries and a numeric major version. Catalog and schema names cannot select the protocol. Keep neutral effort capabilities and the full model ID unchanged; other services still use MLflow Chat Completions. Keep the Rust and desktop resolvers in sync, add shared boundary cases and a captured-HTTP regression for completion and summarization, and update the documented FQN rule. ### Related issue No duplicate found in searches for “FQN responses” PRs or “astra” issues. Related: #6918 introduced Unity Catalog discovery. Originating conversation: buzz://message?channel=0b881928-a3a6-4c01-b981-8e64268f01ce&id=770949343bc96a9ed88acd90a1b37d358a0efc52c79237d0fdb491ce02b8d4ed ### Testing No live Databricks inference test. The gateway must accept the full model-service ID on its OpenAI Responses route; this remains the integration risk. The local `just ci` attempt exceeded its five-minute deadline during `mobile-check`, so the full repository gate was not completed. All push-hook checks passed. Generated with Codex Signed-off-by: Fizz <400e8babadcee6a7f420103f10a2849d84c4a9c71d5bd04f3948c814216648a3@buzz.block.builderlab.xyz> Co-authored-by: Fizz <400e8babadcee6a7f420103f10a2849d84c4a9c71d5bd04f3948c814216648a3@buzz.block.builderlab.xyz> |
||
|
|
86c189e857 |
fix(buzz-acp): wake held ACP threads and fence forked sessions (#7340)
## Summary Adds an independent deadline wakeup so held thread work dispatches after its 10-second bound even when the relay loop is otherwise quiet. Fences session ownership by generation so a worker returning after a fork cannot make an older provider session claimable again. This follows up on the two post-merge findings from [#7337](https://github.com/block/buzz/pull/7337#pullrequestreview-5116329341). ### Related issue Follow-up to #7337. ### Testing - `cargo test -p buzz-acp` - `cargo clippy -p buzz-acp --all-targets -- -D warnings` - Pre-push file-size, differential Rust test, and desktop Tauri gates No UI changes. --- **Update Sep 4, 15:35:** Addressed both Codex review findings. - Queue-cap eviction now prunes orphaned hold deadlines. - An expired hold stays expired until a worker is successfully claimed. - Hold timers remain disabled while every worker is busy; worker return wakes dispatch directly. - Added regressions for queue eviction and pool exhaustion. Generated with Codex --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com> |
||
|
|
fa1b27bcc8 |
fix(mobile): style inline code with the app mono face (#6631)
## Summary Inline code on mobile renders as **bold body text on a faint background wash** — no monospace face, no chip, and it cannot wrap. #5257 diagnosed this as a missing `highlightBuilder`. That is no longer the right fix. `gpt_markdown` 1.2.0 deprecates `highlightBuilder` (removal in 2.0.0), renders inline code as a real chip, and adds `InlineCodeStyle` for restyling it. The package author confirmed this on the issue. So this PR is an upgrade — 1.1.6 → 1.2.1 — plus one theme declaration, rather than the builder the issue originally asked for. **Where the style is declared.** `GptMarkdownThemeData` goes in `AppTheme._buildTheme`, which both `light()` and `dark()` call. That reaches all four `GptMarkdown` call sites — `message_content`, `transcript_item_widget`, `token_pill`, `custom_emoji_render` — so the style is stated once instead of per widget. A widget-level `inlineCodeStyle` would have covered channel messages only, leaving the other three on the package's defaults. **What is declared.** Face, size, ink, chip fill and outline — not the face alone. A face name on its own leaves the rest on the package's defaults, which put inline code at 14.1sp beside a fenced block's 13, on a neutral `onSurface` tint rather than the app's code surface. In dark that tint is *lighter* than the surface, while every other code surface in the app is recessed, so the chip read as a different kind of object. All of it now comes from one `CodeStyle` declaration that the fenced block reads from too, so the two cannot be edited apart. **Three adaptations the upgrade requires.** Each was found by running the gate, not by reading the changelog: 1. **`imageBuilder` widened** to `(context, url, width, height)`. This is a hard compile error, and it is **not listed in the package's migration guide**, which states "nothing here stops code compiling". Worth reporting upstream. 2. **`autolink` now defaults to `true`.** `normalizeBareLinks()` already rewrites bare URLs into Markdown links before rendering, so both would run. `message_content` opts out with `autolink: false` to keep current behaviour exactly. The migration guide argues for dropping the pre-processor instead — a better fix, but a behavioural change that belongs in its own PR. 3. **`gpt_markdown.dart` now re-exports `markdown_config.dart`**, making two direct imports redundant. `flutter analyze` reports `No issues found!` on 1.1.6 and flags both on 1.2.1, so these warnings are new, not pre-existing. **Deliberately out of scope.** The three non-message call sites now autolink bare URLs, since only `message_content` has a pre-processor to collide with. Custom inline components (`_MentionMd`, `CustomEmojiMd`, `_ChannelLinkMd`) could additionally declare `allScopesExceptLinkLabel` — 1.2.0 offers it as the fix for a `WidgetSpan` chip going blank inside a link label on iOS — but current behaviour is unchanged without it, so that stays a separate change. ### Related issue Fixes #5257 Duplicate scan: searched `gpt_markdown`, `inline code mobile`, `highlightBuilder` and `InlineCodeStyle` across both PRs and issues. No open PR touches inline code styling. #6135 (link labels) and #6166 (text selection) also touch mobile Markdown but address different defects. ### Testing Full gate, `just ci` — exit 0: | Stage | Result | |---|---| | Rust (33 suites) | 4768 passed, 0 failed | | Desktop | 5799 passed, 0 failed | | Mobile | **2011 passed**, 0 failed | | `flutter analyze` | `No issues found!` | | Desktop + web build | ok | Run on the branch with `main` merged in, so these numbers match what CI builds. **New regression test** — `renders inline code in the app code style`. It resolves the `CodeTextSpan` the package tags inline code with, which carries both the resolved `TextStyle` and the colours the chip behind it is painted with, so face, size, ink, fill and outline are all asserted rather than a widget's presence. It is negative-controlled: reverting only the theme declaration fails it with ```text Expected: a numeric value within <0.001> of <13.0> Actual: <14.1> ``` and dropping the declaration entirely falls back to `packages/gpt_markdown/JetBrainsMono` — so the test measures the real thing, and it would catch a future regression that silently drops the theme extension. The test passes `baseStyle: messageBodyTextStyle`, the style the message surfaces actually use; the widget's own fallback is the smaller `bodyMedium`, which would move the expected size. The test finds paragraphs with `find.byWidgetPredicate((widget) => widget is RichText)`, not `find.byType(RichText)`: inline code renders through `BidiRichText`, a `RichText` subclass, and `byType` matches exact runtime types. That is a hazard for any test that reads text back out of a paragraph, and one landed after this branch was cut: `message_content_custom_emoji_test.dart` arrived with #6996 and its `code keeps literal emoji while adjacent known tokens render` case reads a code span through `find.byType(RichText)`. It passes on `main` and fails on the merge result, which is what CI builds, so it went red only once CI was authorized. It now uses the same predicate. The two other `byType(RichText)` call sites — the rest of that file and `message_author_meta_test.dart` — were re-run and pass: their content carries no code span, so the exact type still matches. They were left alone. ### Screenshots Rendered through the real `MessageContent` widget with the app's own fonts loaded, at 390pt wide, 3x DPR. Sample text: ``Set `BUZZ_RELAY_URL` before launch, then run `just mobile-test` to verify.`` | | Before (1.1.6) | After (1.2.1) | |---|---|---| | Light |  |  | | Dark |  |  | Before: bold Inter on a flat wash, no chip edge, and `just mobile-test` breaks across the line with the wash simply ending. After: Geist Mono in a bordered, rounded chip, and the wrapped fragment gets its own chip on each line. --------- Signed-off-by: Tolga Cinisli <tolgacinisli@gmail.com> Co-authored-by: Tolga Cinisli <tolgacinisli@gmail.com> |
||
|
|
d34c320fd2 |
feat(desktop-new): evolve navigation and design system
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
e3ae0cb526 |
desktop-new: component specimens show their real boundary
PanelHeader is a header row, not a panel. Its specimen now renders only its own optional icon, title, and actions — no invented Panel around it — and its action is the existing IconButton supplied through the `actions` slot rather than a raw button recreated in the specimen. Panel owns only a surface: fill, border, shadow, rounded silhouette, and clipping. It owns neither a header nor content, so its standalone specimen is now an empty Panel against the gradient it is designed to sit on. The fake header and copy made the live component appear to own a complete work surface, which it does not. The two-panel pseudo-workspace is deleted. Resizing and split geometry belong to DockWorkspace, which owns live layout, resize seams, and persistence policy; a Panel can sit in ordinary layout, a fixed composition, or a workspace and must not pretend it owns any of them. Real resizing gets documented on DockWorkspace's page when that component has a specimen with a running dock. Signed-off-by: Morgan Martin <morganmartin@squareup.com> Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
39679c25c1 |
desktop-new: make the design guide the single colour decision path
The vault's Colour Token System duplicated the token specification, documented a families layer, three border weights, component-named glass, and nine obsolete neutral values. It is deleted; all remaining Buzz Redesign vault links now point to DESIGN.md as the sole visual-system authority. DESIGN.md gains a practical path from context to token. Use a component first; add a variant when the context changes; choose a surface by spatial job; use the shared boundary role; choose public ramp steps for one-step hue decisions; make a proposed role only when one step cannot express both modes; and stop rather than creating a local exception. The border incident is retained as the worked rule: when a shared role fails, repair the role and return every caller to it. Also corrects two stale statements inside DESIGN.md itself: ramps and roles are both public, not roles alone, and growing the system now starts with "write the mode-aware ramp step directly" rather than the obsolete instruction to search the role list first. Signed-off-by: Morgan Martin <morganmartin@squareup.com> Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
16b8dd3464 |
desktop-new: document the decision path from context to token
A shared role that fails in a real context is a defect in the shared decision, not a reason to name a nearby ramp step locally. The specimen frames exposed this with border-primary: neutral-4 was visible enough in light mode and 1.08:1 against a dark panel. The first response bypassed it with neutral-6 just for the documentation. The correct response was to measure every reader, repair the role to neutral-4 light / neutral-6 dark, and return every caller to the same role. DESIGN.md now gives the complete decision order: use a component first; add a variant if the background changes; choose a surface by its spatial job; use the shared boundary role; choose ramp steps directly for a hue decision; add a proposed role only when one step cannot express both modes; and stop and ask rather than making a local exception. It also records why the border uses two different steps and why neutral-5 could not be the one-step dark adjustment. Signed-off-by: Morgan Martin <morganmartin@squareup.com> Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
8ed0dc9931 |
desktop-new: border-primary takes a lighter step in dark mode
Light mode keeps neutral-4; dark mode moves to neutral-6. A hairline needs more separation on a dark surface than the same step number provides, and the old single value was invisible where it mattered: measured against every surface a border sits on, neutral-4 is 1.08:1 on a dark panel and 1.09:1 on the floating composer. Drawn, and not perceptible. neutral-6 is 1.35 and 1.14. Not neutral-5, which is one step and the obvious answer. `bg-float` and `bg-chrome-selected` are both neutral-5 in dark, so a border on a floating surface would have measured 1.00:1 — the same colour as the thing it bounds. This is the asymmetry the role exists for. Light and dark do not have to reach for the same step, and no `border-neutral-4` class could express it; it is the same test the four structural surfaces pass. The /design specimen frames drop their hardcoded `neutral-6` and read the role again. They only named a step directly because the role was invisible in dark, so documentation furniture and product boundaries had diverged for a reason that no longer holds. Signed-off-by: Morgan Martin <morganmartin@squareup.com> Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
1dee19d8a1 |
desktop-new: the palette's step labels describe Buzz, not the scale it came from
Both label sets came with the transcribed values and described a generic scale's intentions: step 4 as "component hover", step 6 as "subtle border", step 7 as "border". The product then decided otherwise and nobody updated the comments, so the file said step 4 was a hover step while it held `border-primary`, the one border weight, and said steps 6 and 7 were for borders while they had no readers at all. A designer opening `tokens.css` or `/design/color` was told the wrong thing by the only documentation to hand. The neutral ramp's comments now name the roles that actually point at each step, including the two steps that have no reader — an empty step is worth seeing, because it is either a gap or a deletion nobody made yet. `STEP_JOBS` keeps generic wording, because it is shared by all eight hues and a hue's step 3 is a tinted surface whatever the product does with it. But it now matches DESIGN.md's step-to-role table exactly, which it did not, and it says so about itself: general intention here, real use in the neutral ramp's comments. The reason it drifted is that it was never rendered. Every palette step carried this text and no page displayed it, so it could only be checked by reading the source. The colour page now shows each step's job under its swatch, which makes the next divergence visible by looking rather than by audit. Signed-off-by: Morgan Martin <morganmartin@squareup.com> Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
d9a0223804 |
desktop-new: move two call sites onto the surviving colour names
`text-danger` was the role that resolved to the wrong red; error text is `text-red-12`, measured against every surface it lands on. `border-secondary` was one of three indistinguishable border weights and is now the single `border-primary`. Signed-off-by: Morgan Martin <morganmartin@squareup.com> Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
41fe22853a |
desktop-new: split the design reasoning out of AGENTS.md
AGENTS.md held two stale copies of the colour system — one section said two layers, another said three and listed an `--accent-1…12` ramp that only exists in a comment explaining its removal. Documenting one subject twice in one file is how that happened, so the duplicates are deleted rather than reconciled: DESIGN.md holds what to do and why, AGENTS.md holds what this is, how to run it, and what will fail you. DESIGN.md gains the colour structure it was missing, rewritten around the reversal in this branch — build from the ramps, and a role exists only when a step cannot express it. The three cases where a name is earned are stated explicitly, since `check-color` now enforces two of them. The step-to-role table survives as guidance for which step to reach for; it is no longer a generator, which is what it was when it produced twenty status roles nobody had designed. Both files also record what is deliberately unresolved: contrast on glass, where the guard measures opaque surfaces only and a translucent surface's real background is the fill composited over a gradient that changes across the screen. Measured, dark mode's brightest glass region puts `text-secondary` at Lc 58 and `text-tertiary` at Lc 43 against targets of 60 and 45. Three candidate fixes are listed and none is chosen, because it wants real content on screen rather than a token edit. Signed-off-by: Morgan Martin <morganmartin@squareup.com> Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
d591179a6b |
desktop-new: the design pages answer which token draws what
`ComponentAnatomy` is a table per component variant: each part, and the token that draws its fill, text, border, shadow, blur, and radius. It answers the one question a specimen cannot, because a picture of a control does not say whether that pill is `bg-chrome-selected` or a hardcoded white. It reads the live component rather than a hand-written list, for the same reason the token table does — a typed-out list of parts and tokens is a second source of truth that goes stale silently. Two things this needed that were not obvious: Vite injects app CSS by setting `textContent` on a `<style>` tag at dev time and those rules are not reachable through `sheet.cssRules`, so the stylesheet text is parsed instead; and matching declarations are ranked by specificity before source order, because ranking by order alone reported a selected tab as unselected once a variant rule outranked a shared one. Both failures rendered a plausible table that was wrong, which is the failure mode it exists to prevent. Every specimen frame now carries a border. It was `bg-panel` on a page that is also `bg-panel` — white on white in light mode, `#1c1c1c` on `#1c1c1c` in dark, with no border and no shadow — so every plain specimen on every component page had an invisible container and a reader could not tell where the documentation stopped and the running component began. It hid because the gradient frames always read as containers, so the pages using them looked correct. A hairline rather than a tinted fill, because a specimen must show the component against the surface it is designed for. Measured at `neutral-4` first: 1.23:1 against the light page and 1.08:1 against the dark one — technically drawn, effectively invisible. `neutral-6` is 1.41 and 1.35. The overview thumbnails had the same defect and take the same fix. The colour table splits into Roles and Ramps, because the ramps — 96 steps across eight hues, the layer every role resolves to — were not shown anywhere. Blur moves off the glass page onto elevation, where the other depth values live. The glass page drops its text-only cards and shows each material over the real app backdrop, since translucency can only be judged against what shows through it. Frames scroll their own overflow: chrome tabs with icons and a trailing action are 377px of unshrinkable inline-flex, so at 420px the whole page used to scroll sideways. A specimen you swipe beats a documentation page that slides. Signed-off-by: Morgan Martin <morganmartin@squareup.com> Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
db12fdb4f7 |
desktop-new: SegmentedNavigation becomes Tabs, with a surface variant
The old name described one variant's appearance — a segmented control — which is exactly why a second appearance felt like it needed a second component. `Tabs` names the behaviour, so both variants sit under it naturally. Base UI's own export is also `Tabs`, so it is imported as `BaseTabs`; that reads better anyway, since `BaseTabs.Root` says plainly which layer each part comes from. `variant="chrome" | "panel"` is the chrome/surface axis DESIGN.md had deferred until a second component made the case. `IconButton` made it once; this made it twice, on a real screen. The failure: the chrome container is `glass-2`, which over a white panel composites to pure white, and its selected pill is `neutral-1` — also pure white. Container and selection became one colour with only a shadow between them, and no guard could see it because the component had no way to state which background it expected. Retinting `--bg-chrome-selected` would only have moved the collision. One component with a variant rather than two components, because behaviour, keyboard model, accessibility, props, and the Base UI parts underneath are identical — a sibling would duplicate all of it to change how selection is drawn, and the two would drift exactly as the four hand-assembled chrome surfaces did. The panel variant still emits `role="tablist"`, correct `aria-selected`, roving `tabIndex`, and arrow-key navigation, which is the concrete payoff. Each variant states all three of its states in full rather than inheriting a colour and overriding part of it. When a shared rule and a variant rule both set a property, the more specific selector wins regardless of source order, and a variant rule is always more specific — so the shared `[data-selected]` rule lost and the selected tab silently rendered as unselected. Hover carries `:not([data-selected])` because hover and selected genuinely overlap: a cursor can sit on the selected tab, and DESIGN.md § State says a selected item in a toggle group is not interactive. Those two selectors score identical specificity, so without the exclusion only source order would keep it right. This follows Chrome's modern CSS guidance: use `:not()` to say a state is irrelevant, never to defend one rule from another. `WorkspaceSurface` is renamed to `Panel` in the same change, since both touch the same rules in components.css. Signed-off-by: Morgan Martin <morganmartin@squareup.com> Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
698b73d943 |
desktop-new: glass is one material, applied whole
A glass surface is four things at once — a translucent fill, a blur behind it, a directional rim, and sometimes an outer lift — and they were four separate things a consumer assembled by hand. Every consumer did, differently. The four chrome surfaces meant to match disagreed about whether to carry an outer shadow, and four inner panels set a translucent fill with no blur at all. `glass-primary` and `glass-secondary` are Tailwind `@utility` blocks carrying the whole material, plus `-interactive` variants that add the hover. Named by stacking depth rather than by component: an earlier cut named them chrome / panel / float and it was wrong twice over, because `chrome` and `float` both landed on glass-3 — 66% opaque in dark, which is why chrome did not read as glass — and naming a material after a component invites a second material the first time a component wants a different depth. `box-shadow` is composed inside each material because it has to be: a rim and a lift both set it, so as separate utilities the second silently overwrites the first. That is a structural argument for bundling, not a preference. The fills are deliberately not registered as colour utilities, so a bare `bg-glass-primary` class cannot exist. Removing a redundant fill from the four workspace panels fixed something no token held. They each set the same `glass-2` as the container they exactly cover — measured 610x551 inside 612x553, the two pixels being its border — and two identical translucent layers composite: 0.52 over 0.52 is 0.77, between glass-4 and glass-5. Panels meant to be the most translucent surface in the system read as nearly solid. That in turn exposed a bug the density had been masking since the dock landed: dockview's theme sets `--dv-group-view-background-color` to a literal `white` and `DockWorkspace` passes `themeLight` in both modes, so an opaque white sheet sat behind the glass. Reset here, because every other dockview surface the variable feeds was already overridden to transparent. Signed-off-by: Morgan Martin <morganmartin@squareup.com> Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
ec918bb75f |
desktop-new: build screens from the ramps, cut 54 colour roles to 15
Registers all eight palette ramps as Tailwind classes, so a screen writes `bg-purple-9` or `text-red-12` directly. This reverses the rule the colour guard used to enforce, and the reversal turns on one fact: every step in this palette is authored per mode, so `neutral-4` is one value in light and another in dark and a component naming it behaves correctly in both. That is what makes a raw step safe here and unsafe in stock Tailwind, where `neutral-200` is a single literal. Once a step is mode-aware, a role whose light and dark values are the same step is a name in front of a number, and a name in front of a number hides the decision instead of recording it. `bg-accent` was `purple-9`; `text-error` was `red-12`. Nineteen roles were exactly that. The fifteen that remain each earn the name: the four structural surfaces take a different step per mode, so no class can express them; the emphasis roles enforce a rule a ramp cannot state, which is that there are three levels of text and one border weight; the glass materials are a bundled treatment; and `--text-on-accent` follows its fill rather than the mode. Also fixes a namespace collision that had shipped a real defect. `--color-x` is not one utility, it is all of them — one declaration defines `bg-x`, `text-x`, `border-x` and `ring-x` from a single value — so `text-danger` resolved to the saturated fill (red-9) instead of the text step. Three error messages rendered at APCA Lc 34 on a dark panel against a target of 60, and `check-contrast` passed the whole time because it measured `--text-danger`, a token no class could reach. Text roles now register as `--text-color-*`, matching the `--border-color-*` split that fixed the same bug for borders. Guards updated to enforce the new rule rather than the old one: `check-color` now rejects a role whose two modes agree and names the class to write instead, and `check-contrast` measures the ramp steps screens actually use, so deleting a role cannot leave it auditing a name nobody types. `registry.test.ts` binds the documented token list to the stylesheet, and `pnpm census` reports every role's readers without failing a build. Signed-off-by: Morgan Martin <morganmartin@squareup.com> Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
ce94bb1599 |
desktop-new: delete the proving-ground dashboard
It was meant to stress the design system by putting many decisions next to each other. It did surface one real finding - the neutral hover step is tuned for hovering a filled component, not a bare row, so the same token is too weak for a quiet button and too strong for a bare row - but the page was ~85% bespoke CSS routed around the shared components, so most of what it showed was its own scaffolding rather than the system. The app itself is the better proving ground. Removing this rather than carrying a mockup whose defects have to be triaged separately from the product's. Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
4100246b75 |
desktop-new: ignore vite.local.config.ts
A local Vite config can wrap the committed one to append a personal dev-only plugin. Ignoring it makes that structurally uncommittable rather than something to remember not to stage. Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
adc6639c06 |
desktop-new: cards carry no default fill
A card sits on the panel and is grouped by a hairline. Fill is reserved for bg-hover, and only where the card is clickable, so a filled card always means you are pointing at this rather than merely this is a box. The region tiles were using bg-inset, which means pushed in - an input or a code block - the opposite gesture from grouping, and read as a grid of empty text fields inside a filled panel. No new role is needed: a card with no default fill has nothing to name. Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
efb443a2cb |
desktop-new: delete the categorical tints, add a proving-ground dashboard
Two changes, both about finding out whether the colour system actually works. Deleted the six categorical tint roles. Evidence: zero component usage — not one reference outside the token file and the registry. They were hand-picked literals of exactly the kind the palette exists to prevent, and the open question 'how many categorical colours does the product need, and does a person choose one or does the product assign it' is unanswered, so naming them now was guessing. The eight palette hues stay; a categorical role gets named when a feature needs to distinguish things. That deletion turned a guard check vacuous, which is its own finding: the drift check named four specific pairs (--tint-purple vs --accent-tint, etc.) and would have passed silently forever once those tokens were gone. Replaced with the general rule — no two family steps may resolve to the same palette step — which catches the same class for any future family without naming pairs. Falsified by pointing --danger-tint at the purple ramp. Added /proving-ground: a fictional fleet-monitoring dashboard, deliberately nothing to do with Buzz. Specimen pages are honest but sympathetic — one component at a time, on a chosen backdrop, at a chosen size. A dashboard puts forty decisions next to each other, which is where a missing role shows up. It is also the surface a generated theme has to be judged on: 'Neon Tokyo' can look fine as twelve swatches and be unusable as a page. Built only from role tokens, and it held: no new roles were needed, contrast passes both modes, and the layout works at 1440 and 620. Page-local parts live in the feature rather than shared/ui on purpose — this page exists to find out what repeats, so promoting its pieces first would be guessing. Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
cef97be05b |
desktop-new: add a palette layer beneath the colour families
The colour system had three layers, and the middle one held two different kinds of thing under one name. --neutral-1..12 is a real scale; --accent-2 and --danger-9 are five specific jobs (tint, tint-hover, border, fill, text) wearing ramp-style numbering. So --accent-2 read as 'a light purple' when it meant 'the surface an accent tint sits on'. Underneath it was 114 hand-picked hex values with nothing enforcing that two tokens doing the same job agreed. They drifted: --accent-2 and --tint-purple were the same purple in light mode and two different purples in dark. I introduced that yesterday and did not notice. Four layers now: 0 palette --palette-purple-3 every hue, 12 steps, authored per mode 1 families --accent-tint the five jobs a hue does, named not numbered 2 roles --bg-accent-tint what a thing is in the interface 3 components bg-accent-tint Tailwind utilities Dark stops being freehand. A family points at a palette step and .dark redefines the whole palette, so 50 hand-picked dark values are simply gone. This also answers a concrete trap. Tailwind's purple gets MORE saturated as it descends (950 is 87% saturation), so a dark tint drawn from its bottom end reads as oversaturated, and the only way to reach a muted deep purple was to write purple-950/50 in a component. That value is correct — it composites to within a few percent of the palette step now in use — but as an expression it has no name, no light/dark pair, and nothing the contrast guard can measure. The palette's dark ramps are authored for dark surfaces, so it is a step you pick. New scripts/check-color.mjs enforces it: rejects opacity modifiers on colour utilities, color-mix() and alpha rgb()/hsl() in component code, components reaching past the role layer, and the layering itself — palette steps must be literals, family steps must reference the palette, two tokens doing one job must resolve to one step, and dark must not restate families. Falsified by reintroducing the original drift and each other failure mode in turn. Values are Radix Colors (MIT), transcribed rather than depended on; Radix is not on Block's Tech Radar so this is a values-only copy with no package. Its twelve-step contract is the one this system already described in comments, step for step. Two documented divergences, both found by the contrast guard: family text takes step 12 rather than the 11 Radix names 'low-contrast text' (Radix sizes 11 for WCAG 4.5:1; every hue's 11 measured Lc 55-61 against our Lc 60 target), and dark neutral surfaces stay hand-authored because they were sized against the real panel stack. The colour page now renders the palette, so picking a step is visual. Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
7b8c73b95f |
desktop-new: purple accent, rename tile to chip, redesign from Figma
Accent hue is now Tailwind purple. The ramp is a slot, so this is five values in tokens.css and every role follows; steps 2 and 3 are purple-100/200 exactly because the chip design was drawn against them and the tint pair is what a person sees most. Dark mode cannot use those two — near-white reads as a light surface on a dark panel — so it holds the hue at dark violets instead. Renamed InlineTile to InlineChip throughout, plus shared/tiles to shared/chips, tileNode to chipNode, and every token, class, and identifier. A chip is what this is. Redesigned to match Figma 700-2782: - One treatment for every kind: accent tint behind primary text. Kinds are told apart by sigil and, where the label is ambiguous, an icon — not by colour. This replaces the earlier per-kind colour split, which spent palette on a distinction the sigil already makes. - Label is primary text, not accent text; the tint does the colour work. - Two new kinds the design draws: message and link, both icon-led with no sigil. AUTHORABLE_CHIP_KINDS records that v1's picker still offers only person, agent, and channel; the others arrive by another route. - Dropped avatar and status dot. The design has neither, and neither was earning its place at text size. - Unresolved is now a real state: neutral fill, lesser text, and never a control, because there is nothing to open. - Chip inset and gap intentionally sit below the 4px spacing floor (3px inset, 4px gap) — a chip hugs its label. Recorded on the tokens. Eight new rendered-contract tests bound to the real specimen page: sigils and selective icons, assistive naming, unresolved-is-never-a-control, read-only claims no stop, activation reports the address, truncation, never breaking across a line, and hover deepening the tint without moving the text. Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
c05b85c421 |
desktop-new: tile address model and shared InlineTile
Implements the foundation the composer sits on: a reference is an address (kind + identity), never the letters of a name. One shared InlineTile serves both the composer and the conversation, mounted in the editor through a React node view. - shared/tiles/address.ts: kind + id, canonical buzz:// form, parse/format. - shared/tiles/faceResolver.ts: resolves the displayed face from an address at render time, so a rename never edits anyone's draft or enters undo history. Community-scoped cache with an explicit reset. - shared/ui/InlineTile.tsx: the one component, person/agent/channel kinds. - features/composer/tileNode.ts: the document node. Stores only the address. - New semantic tokens: --space-tile-inset, --space-tile-gap, --radius-tile. - Specimen page at /design/components/inline-tile covering kinds, in-sentence use, read-only, unresolved, truncation, and activation. Findings while building, all now covered by tests: - A React node view REPAIRS a caret boundary. With static markup, Home before a leading tile landed after it; with the node view both Home and ArrowLeft land correctly. Only the pointer click on a leading tile's left edge still fails — now the single known caret gap, still marked test.fail(). - useSyncExternalStore needs a reference-stable snapshot. Building a fresh unresolved face per call was an infinite render loop that unmounted the whole editor. Unresolved faces are cached like resolved ones. - An unresolved tile must not announce an abbreviated identity to a screen reader. TileFace now carries , and the accessible name says "Unresolved person" rather than reading the stand-in aloud. Falsifiability checked by breaking each protection in turn: selectable:false, the address text projection, and the community reset each turn tests red. Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
179af349f1 |
desktop-new: composition-input spike for inline composer tiles
Answers the blocking question in the Desktop New plan before the composer is designed: does staged character input (ja/ko/zh) survive beside an inline atom tile? It does. Twelve tests, Chromium composition driven through CDP Input.imeSetComposition plus a WebKit pass. Measured findings: - Composition beside a tile is safe in every position; a committed composition leaves the tile address byte-identical. No fence needed. - selectable:false is load-bearing. With selectable:true an arrow key selects the tile and the next keystroke replaces it. - atom:true is redundant for a contentless node (isAtom = isLeaf || spec.atom) and kept as documentation of intent. - Whole-tile deletion works natively; no custom Backspace handler required. - One real gap: the caret position before a leading tile. ArrowLeft reaches it, Home and a pointer click do not, in both engines. Those two tests carry test.fail() until the caret-boundary layer lands. The harness is temporary and deleted when the real composer lands. Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
3c7f288c60 |
chore(release): release Buzz Desktop version 0.5.23 (#7381)
## Buzz Desktop release v0.5.23 - **Frozen main:** `dad5a33865fc81a2e55b3b60746632f615ec1e3a` - **Reviewed candidate:** `b9392d9d78744df365f9276e1ffe8c1baa5ea903` - **Previous desktop release:** `desktop-v0.5.22` - **Proposed immutable tag:** `desktop-v0.5.23` This PR may be **squash merged** after the Desktop Release Candidate check and all protected-branch checks pass. Merging authorizes publication of the exact reviewed candidate; later or unrelated changes on `main` cannot alter it. The checked-in changelog accounts for every non-merge commit in the release range. The Desktop tag points to the reviewed candidate commit, not the later squash commit. Publication remains bound to that immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |
||
|
|
dad5a33865 |
fix(desktop): keep packaged frontendDist relative so Windows embeds assets (#7177)
## Root cause
`tauri-command.mjs` points `frontendDist` at a `mkdtemp` directory so
concurrent OSS/internal packages cannot overwrite each other's assets.
On Windows that is an absolute path with a drive letter.
`FrontendDist` is an untagged serde enum whose **first** variant is
`Url(Url)`, and `C:\Users\...` is a valid WHATWG URL with scheme `c:`,
so serde selects `Url`. `tauri-codegen` then does:
FrontendDist::Url(_url) => Default::default(), // embed nothing
A missing *directory* panics with a clear message; a URL is silent. The
build exits 0 and produces an installable app with no frontend assets,
which boots to `ERR_FILE_NOT_FOUND` in the WebView.
Linux and macOS are unaffected — `/tmp/...` has no scheme, so it falls
through to `Directory`.
This affects every Windows build that goes through `pnpm tauri build`,
including `release.yml`'s NSIS job and `windows-canary.yml`.
## Fix
Pass the path relative to the config's own directory. `tauri-codegen`
resolves `frontendDist` with `config_parent.join(path)`, so a relative
path reaches the same directory and cannot parse as a URL. When the temp
directory is on another drive there is no relative form, so the scratch
root is created beside the config instead.
`BUZZ_PROTECTED_BUILD_OUTPUT` still receives the absolute path, and
cleanup is unchanged.
## Testing
`tauriCommand.test.mjs` asserted against the value it had just been
handed, so it could not observe this. Its fake CLI also resolved
`frontendDist` against the process cwd, which is not what Tauri does.
- Fake CLI now resolves against the config directory, matching
`config_parent.join(path)`.
- New case asserts the packaged `frontendDist` is not absolute and does
not parse as a URL. The absolute check is what fails on Linux/macOS, so
the regression stays covered on every platform.
- Verified the new case fails on the unpatched wrapper and passes with
the fix; the two existing cases pass either way.
- Desktop suite: 5844 passed. `useDocumentVisible` has a pre-existing
load-dependent flake that also reproduces on an unmodified checkout.
- Biome check clean on both files.
Verified end to end by rebuilding the Windows NSIS installer: embedded
asset keys in `buzz-desktop.exe` went from 0 to 490, and the app
launches.
---------
Signed-off-by: Jeff Hedlund <jhedlund@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
ef1b407e02 |
feat(desktop-new): establish visual component foundation
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
2d4d36e086 |
feat(desktop-next): build channel-backed Sessions workspace
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
f038cbbb0d |
fix(sidebar): simplify unread indicators and emphasize priority activity (#7134)
**Category:** fix **User Impact:** The sidebar now presents unread activity consistently: unread rooms are bold, offscreen activity is counted by destination, and DMs or directed activity receive stronger emphasis. **Problem:** Sidebar unread state was split across competing signals: agent work could trigger overflow, message totals inflated its count, non-DM rows showed redundant numerals, and routine room activity looked as urgent as DMs or directed messages. This made the sidebar noisy and made the overflow value harder to interpret. **Solution:** Use one stable offscreen unread control that counts rooms and DMs—not messages—and remove agent work from that signal. Keep ordinary room activity quiet; promote DMs, mentions, broadcasts, and relevant thread replies; preserve DM avatars and targeting; bold every unread room; and retain thread dots while removing non-DM numerals. <details> <summary>File changes</summary> **desktop/src-tauri/src/unread_catch_up.rs** Classifies relevant thread replies as priority activity during native unread catch-up so startup state matches live rendering. **desktop/src/app/AppShell.tsx** Passes the priority unread destination set into the sidebar. **desktop/src/features/channels/useUnreadChannels.ts** Projects unread destinations, priority state, and thread replies consistently while keeping Dock badge behavior separate from sidebar emphasis. **desktop/src/features/sidebar/lib/useOffscreenActivityChannelIds.test.mjs** Removes tests for the superseded agent/activity overflow projection. **desktop/src/features/sidebar/lib/useOffscreenActivityChannelIds.ts** Removes the old agent-plus-message overflow projection so agent work alone no longer creates the unread indicator. **desktop/src/features/sidebar/lib/useSidebarActivityOverflow.ts** Replaces activity-volume overflow state with unread-destination overflow state. **desktop/src/features/sidebar/lib/useSidebarUnreadOverflow.test.mjs** Covers priority detection and destination-count labels for the new overflow projection. **desktop/src/features/sidebar/lib/useSidebarUnreadOverflow.ts** Counts distinct offscreen unread destinations and determines quiet versus primary treatment per direction. **desktop/src/features/sidebar/ui/AppSidebar.tsx** Renders one stable overflow control, preserves protected-DM visibility filtering, and prioritizes visible unread DMs for previews and navigation. **desktop/src/features/sidebar/ui/AppSidebar.types.ts** Adds the priority unread destination set to the sidebar contract. **desktop/src/features/sidebar/ui/CustomChannelSection.tsx** Stops forwarding non-DM unread counts into custom channel rows. **desktop/src/features/sidebar/ui/MoreUnreadButton.test.mjs** Updates control coverage for explicit emphasis, destination labels, and DM targeting. **desktop/src/features/sidebar/ui/MoreUnreadButton.tsx** Applies quiet or primary treatment without changing geometry, accessibility text, DM avatars, or click behavior. **desktop/src/features/sidebar/ui/SidebarSection.tsx** Removes non-DM row numerals while retaining unread weight and thread preview affordances. **desktop/src/shared/ui/UnreadPill.tsx** Shares one composition between quiet and primary states so only color treatment changes. **desktop/tests/e2e/badge.spec.ts** Covers destination counting, promotion without count inflation, DM/thread priority, row bolding, and removed numerals; also captures the reviewed UI states. **desktop/tests/e2e/channel-activity-popover.spec.ts** Updates channel activity assertions for the numeral-free row treatment. **desktop/tests/e2e/channels.spec.ts** Updates channel unread expectations to use bold text rather than a row count. **desktop/tests/e2e/thread-unread.spec.ts** Keeps thread unread-dot and popover coverage while asserting the room itself is bold. </details> ## Reproduction steps 1. Open a workspace with enough sidebar destinations to scroll rooms above or below the viewport. 2. Receive ordinary unread activity in an offscreen room; verify one quiet `N unread` indicator appears and counts the room once regardless of message volume. 3. Receive a mention, broadcast, or relevant thread reply in an offscreen room; verify the same count becomes primary without changing its geometry or adding a badge. 4. Receive an unread DM, including thread-only activity; verify the indicator is primary, shows the DM avatar when available, and navigates to that DM. 5. Scroll the destination onscreen or mark it read; verify the count and treatment update from the remaining offscreen destinations. 6. Inspect unread non-DM rows; verify their names are bold, numeric badges are absent, and thread dots still open the unread-thread preview. ## Screenshots ### Ordinary unread room Routine offscreen room activity uses the quiet treatment.  ### Directed unread activity The same destination count becomes primary when an offscreen room has directed activity; geometry and label remain unchanged.  ### Thread-only unread DM A DM stays primary and retains its avatar even when only its thread has unread activity.  --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
8dc0610f5d |
Merge remote-tracking branch 'origin/main' into morgan/desktop-next-sessions
* origin/main: Add generic information-flow control core (#7293) feat(buzz-acp): update base prompt; add buzz context and skills to Pi agents (#7335) fix(desktop): restore mention chip identity icons (#7338) Persist video playback speed preference (#7336) Verify ACP relay events before prompt routing (#7010) fix(buzz-acp): bound busy-owner hold to prevent cross-channel starvation (#7337) feat(desktop): invite owned agents from standalone forums (#7125) fix(desktop): authorize remote mentions at publication (#7124) fix(acp): rename system tag to agent-instructions (#7332) fix(desktop): bind duplicate mention selections to exact recipients (#7133) refactor(relay): extract NIP-29 membership authorization (#7285) chore(release): release Buzz Desktop version 0.5.22 (#7308) feat(desktop): preserve mentions across copy and paste (#7228) test(desktop): await Bestie drag and profile hover endpoints (#7294) Collapse contiguous join messages (#7262) Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
4d447b9c20 |
Add generic information-flow control core (#7293)
Adds a dependency-free `ifc-core` crate implementing reader-set confidentiality labels and monotonic per-computation flow state. - Defines flow ordering, join, and meet over caller-supplied principal universes. - Fails closed for unknown or cross-universe input and checks reader widening at egress. - Includes property tests for lattice laws and monotonic taint, plus the design paper that motivates the broker integration. The crate deliberately contains no Buzz, Nostr, channel, membership, or grant policy; those remain in the Buzz adapter. --------- Signed-off-by: Jordan Mecom <jm@squareup.com> |
||
|
|
4beffef697 |
feat(buzz-acp): update base prompt; add buzz context and skills to Pi agents (#7335)
## Summary - launch Pi through a private Buzz wrapper so managed Pi agents receive Buzz's base prompt plus the configured thread/channel session model via `--system-prompt` - load the Buzz workspace skill directory with Pi's repeatable `--skill` flag, making `~/.buzz/.agents/skills/buzz-cli` available alongside Pi's normal global skill discovery - report Pi and `pi-acp` installation states separately so setup guidance points to the missing component `pi-acp` does not currently consume the ACP `session/new` system prompt, but its normal new-session and restore paths do honor `PI_ACP_PI_COMMAND` as the executable used to launch Pi. Buzz reserves that variable and sets it to a private generated launcher. The launcher invokes `pi` from Buzz's effective `PATH`, adds `--system-prompt <file>` and `--skill <workspace>/.agents/skills`, and forwards `pi-acp`'s RPC and session arguments unchanged. This keeps the integration entirely in `block/buzz`, without changes to Pi or `pi-acp`. ### How PI_ACP_PI_COMMAND works in `pi-acp` `PI_ACP_PI_COMMAND` selects one executable; it does not accept arguments. Therefore: ```bash # Does not work PI_ACP_PI_COMMAND="pi --skill ~/.buzz/.agents/skills" ``` Buzz does not expose this variable as user configuration. It creates a private launcher and sets `PI_ACP_PI_COMMAND` only on the `pi-acp` child. Conceptually, that launcher executes: ```sh #!/bin/sh exec pi \ --system-prompt "$PRIVATE_SYSTEM_PROMPT" \ --skill "$HOME/.buzz/.agents/skills" \ "$@" ``` ### Related issue Follow-up to #7208. No duplicate issue or open PR found. ### Testing - `just ci` - `cargo test -p buzz-acp pi_launcher` - `cargo test --manifest-path desktop/src-tauri/Cargo.toml managed_agents::env_vars::tests` - `cargo test --manifest-path desktop/src-tauri/Cargo.toml managed_agents::discovery::presets::tests` - pre-push branch checks No screenshots: the UI change is state-dependent setup guidance only. --- **Update Sep 4, 13:35:** Made `PI_ACP_PI_COMMAND` entirely Buzz-owned. - Managed agent configuration now rejects the variable as a user override. - Buzz always launches `pi` from its effective `PATH` and rejects inherited values before creating its private launcher. - Removed the custom Pi executable discovery path and its internal environment alias. --- **Update Sep 4, 14:03:** Reject inherited `PI_ACP_PI_COMMAND` values instead of replacing them. - Pi startup now fails with an actionable message telling the user to unset the variable. - Removed the Pi-specific exception from the generic ACP environment injection path. Generated with Codex --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com> |
||
|
|
4afef8649a |
fix(desktop): restore mention chip identity icons (#7338)
## Summary Restore the missing **@ glyph for people and robot icon for agents** after #7133, and incorporate the requested compact public-key display. - Wrapping mention chips render their existing bounded icon-bearing leading fragment. - Readonly chips show bound keys using the same `8 leading…4 trailing` formatter as the channel member list: `Scout (150b20bd…15dc)`. - Full literal labels and exact keys remain authoritative in metadata, profile targets, title/accessible-name attributes, editor text, saved bodies and recipient tags. Display abbreviations are never used for recipient lookup. - Copy/paste restores the full literal label for a complete compact chip; partial selections remain plain text. Two keys sharing the same abbreviation still round-trip to their separate exact recipients. - No recipient-resolution, authorization, wire-format, composer, or CSS changes. Existing labels, icons, cloud markers and ordinary mentions remain intact. ## Verification Published candidate `9365ab9bc9d9c5d10802580cd576f8e378ff492f`, based on main `e09f715c9d0ee2cb7bf8a39061e601f3a502f588`: - **6,444 desktop unit tests passed** on this candidate's final source tree. - **44 mock-Chromium tests passed, zero retries in the final run** across mention recipients, clipboard and cloud provenance: exact recipient selection, ambiguity rejection, send/edit/reopen, forwarding, full/partial copy, mismatched-key rejection, matching-abbreviation collisions and 100%/150% narrow-window geometry. - TypeScript, desktop Biome/check guards, protected-feature production build and E2E build passed. - The compact-renderer regression fails with the formatting call removed. Original missing-icon and hidden-text accessibility regressions have red/green evidence. - Fresh self-review traced rendering, full-key metadata, copy classifier, paste normalization and identity trust. The same display formatter owns the accepted compact form on both clipboard sides. Iteration exposed an existing team-insertion separator flake (passed final full run) and two new fixture assumptions: non-member sends require invitation, and Chromium rich paste may retain an NBSP separator. Tests now exercise invitation and normalize only that separator when comparing the captured full body and exact tags; no product change was needed for either. Earlier local repository-wide `just ci` completed in two invocations because its initial call hit the ten-minute tool limit during Tauri compilation. Unchanged native/mobile/backend evidence is reused; the desktop delta received the full checks above and new remote CI. Native VoiceOver, real Tauri selection, dark theme and non-Chromium observation were not performed. Browser artifacts exercise real frontend with mocked Tauri/relay boundaries, not an installed release. ## Review and visual evidence Current-head CI and automated review must complete after this update; the old `2997bfb5` green results do not establish this new head. Required human review remains separate from agent approvals. No merge/install/restart authorization. Before/after icon evidence: https://github.com/block/buzz/pull/7338#issuecomment-5544575866 Updated compact-key screenshots are posted below. The editor intentionally retains the full literal address; only readonly chip display is abbreviated. Origin: buzz://message?channel=3355d33a-b72a-423a-b064-a58275f9a8af&id=38b3a27e689f5a9604e273d45f4e3122fceba76081e7bcd3bbdbf439524a5a18 --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> |
||
|
|
5d107836c6 |
Persist video playback speed preference (#7336)
## Summary - persist the selected desktop video playback speed as a device-level preference - apply the shared preference to inline and review video players, including after a reload - cover persistence, validation, cross-window updates, and the end-to-end playback flow ## Verification - `node --test desktop/src/shared/lib/videoPlaybackSpeedPreference.test.mjs` - `cd desktop && pnpm exec tsc --noEmit` - `cd desktop && pnpm check:px-text` - `cd desktop && pnpm test` - `cd desktop && pnpm build:e2e && pnpm exec playwright test --project=smoke video-attachment.spec.ts` - pre-push: desktop check, file-size check, typecheck, and 6,416 desktop tests --------- Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |