mirror of
https://github.com/block/buzz.git
synced 2026-10-27 20:41:57 +00:00
db514b153a8cb17631d6e80b91c95fdc45deb147
689 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
db514b153a |
fix(desktop): stop pulsing addressed agents on send (#6873)
## Summary - Stop replaying the selected-agent pulse when a remembered agent remains addressed across a send. - Preserve the existing pulse for genuine selections and shake feedback for failed sends. ### Related issue Reported in Buzz: buzz://message?channel=2745947d-f905-4f31-8a3f-fc4485d3c36c&id=0cffab376186be9cc4e7f29027352cc1c7941aead48b3fed948f36181f4a59af ### Testing - `cd desktop && pnpm test` (5,556 passed) - `cd desktop && pnpm typecheck` - `cd desktop && pnpm check` - `cd desktop && pnpm build:e2e` - `cd desktop && pnpm exec playwright test tests/e2e/persistent-agent-audience.spec.ts --grep 'always-mentioned agents remain selected|failed always-mentioned send'` (2 passed) This changes animation timing only; the regression is covered by Playwright assertions on the pulse and shake versions, while a static screenshot is unchanged. Signed-off-by: Matt Toohey <contact@matttoohey.com> |
||
|
|
4f529a45f2 |
fix(desktop): prioritize sidebar channel status (#6861)
## Summary - Treat the sidebar temp-channel icon as a fallback behind working, mute, numeric unread, and thread-unread indicators. - Keep the header affordance and the sidebar temp icon when it is the only row status. - Extend the ephemeral-channel browser scenario across working, idle, collapsed-sidebar, and unread states. ## Before / After | Before: five temp icons compete with duration and unread states | After: all five duration and unread states remain clear | | --- | --- | |  |  | ## Testing - `node --import ./test-loader.mjs --experimental-strip-types --test "src/**/*.test.mjs"` (5,556 passed) - `tsc --noEmit -p desktop/tsconfig.json` - `biome check desktop/src/features/sidebar/ui/SidebarSection.tsx desktop/tests/e2e/channels.spec.ts` - `vite build --mode e2e` - Isolated Playwright run of `create ephemeral stream shows sidebar and header affordances` (1 passed) ## Duplicate check No matching open PR or issue found for `ephemeral sidebar icon`. Signed-off-by: Jitter <d14dfe033ef0f809866f9f984de04821b0d900d7652fd85a54776ee40ca3a68f@buzz.block.builderlab.xyz> Co-authored-by: Jitter <d14dfe033ef0f809866f9f984de04821b0d900d7652fd85a54776ee40ca3a68f@buzz.block.builderlab.xyz> |
||
|
|
8471049c43 |
feat(desktop): add KLIPY GIF search to composers (#5554)
## Summary - Supersedes #1913 with a KLIPY-hosted URL implementation. - Adds KLIPY GIF search and trending results to desktop message and forum composers. - Keeps selected GIFs hosted by KLIPY; Buzz stores only the external URL and media metadata (no imeta tag, since relays only accept hash-backed local `/media/` entries). - Aligns the Emoji/GIF picker with Buzz's standard segmented control, theme surfaces, and motion behavior. ## Relay-to-provider boundary - The relay proxies KLIPY search/share so the `BUZZ_KLIPY_API_KEY` never reaches the desktop; the key stays server-side behind a redacted `Debug` impl. - The dedicated GIF `reqwest` client sets `redirect::Policy::none()`. Because the API key rides in the request path, following a provider `3xx` could replay a key-bearing URL to an attacker-chosen host (an SSRF/key-disclosure primitive). With redirects disabled, a `3xx` returns as a non-success status that the handlers map to a generic `502`; the `Location` target is never read or forwarded. - Admission reuses the established NIP-98, membership, replay, and per-pubkey rate-limit gates, with an upstream response-size cap and allowlisting so KLIPY error bodies never cross the relay boundary. ## Accessibility - Under `prefers-reduced-motion: reduce`, the picker grid renders a static provider poster (a normalized `jpg` asset) instead of the animated preview, or a named static placeholder when no poster is available. It reacts to preference changes while mounted. `no-preference` keeps the animated preview. - Selected GIFs carry their title through `ImetaMedia`'s `displayLabel`, so the composer thumbnail, preview dialog, editor, lightbox, and remove control all derive one non-empty accessible name instead of an empty `Attachment ` label. Ordinary hashed uploads keep their existing hash-derived names. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Signed-off-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz> |
||
|
|
e8cd7516e6 |
fix(desktop): respect automatic mention preference after send (#6837)
**Category:** fix **User Impact:** Disabling automatic agent mentions now keeps one-time agent mentions out of the next message draft. **Problem:** A successfully sent inline agent mention was treated as eligible for post-send restoration even when **Automatically mention agents** was disabled, so the agent immediately reappeared in the composer. **Solution:** Gate the send-success restoration path on the live preference while leaving explicitly pinned agents and enabled automatic mentions unchanged. <details> <summary>File changes</summary> **desktop/src/features/messages/ui/MessageComposer.tsx** Skips the automatic post-send mention restoration path when the preference is disabled. **desktop/tests/e2e/persistent-agent-audience.spec.ts** Reproduces disabling the toggle before sending an inline agent mention and verifies the next composer is empty while the outgoing mention still reaches the agent. </details> ## Reproduction steps 1. Enable **Automatically mention agents** in the composer mention options. 2. Disable it again. 3. Compose and send `@Agent test` using the inline mention picker. 4. Confirm the message still mentions the agent, but the cleared composer does not repopulate `@Agent`. 5. Re-enable the preference and repeat; confirm the mention is restored for the next message. ## Testing At `a179a2d00a6ac5cf0bc4a1c7c1ef3d5dfa9d0875`: - Full desktop unit suite: 5,501 passed. - Desktop check and typecheck passed in the pre-push hooks. - Desktop file-size ratchet passed in the pre-push hooks. - Full `persistent-agent-audience.spec.ts` Playwright file: 17 passed, including preference off (empty composer), preference on (restored mention), and the existing address-undo journey. ## Visual validation | Automatic mentions off | Automatic mentions on | | --- | --- | | After sending a one-time mention, the outgoing message is preserved and the next composer stays empty. | After sending, the agent remains addressed and is restored in the next composer. | |  |  | <details> <summary>Original regression</summary> With automatic mentions disabled, sending `@Alia test` still repopulated `@Alia` in the cleared composer.  </details> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> |
||
|
|
b58de7cfa7 |
fix(desktop-messages): preserve inline agent mentions with persistent addressing (#6793)
**Category:** fix **User Impact:** Users can place `@Agent` mentions inline anywhere in a message—even when that agent is also persistently addressed—so separate agents can receive separate instructions in the same message. **Problem:** Selecting an agent from the composer mention picker could turn the selection into persistent addressing instead of leaving an inline `@Agent` at the cursor. That made persistent addressing and inline composition mutually exclusive: once the persistent behavior took over, users lost the clear `@Agent A do X, @Agent B do Y` message structure they previously had. **Solution:** Make inline mentioning and persistent addressing independent behaviors: - Ordinary mention selections always insert `@Agent` inline at the current cursor position. - This remains true when the agent is already persistently addressed; the persistent audience never blocks or replaces an inline mention. - If **Automatically mention agents** is enabled, a successfully sent inline mention may additionally make that agent persistent for later messages. Existing saved preferences remain respected. - The dedicated automatic-mention control and primary+Shift+Enter shortcut continue to add or remove persistent addressing directly. - For users without a saved preference, **Automatically mention agents** now defaults off. <details> <summary>File changes</summary> **desktop/src/features/messages/lib/autoPinMentionedAgentsPreference.test.mjs** Updates preference coverage for the default-off behavior while preserving explicit saved choices. **desktop/src/features/messages/lib/autoPinMentionedAgentsPreference.ts** Defaults automatic post-send persistence off when no valid preference exists. **desktop/src/features/messages/ui/useAgentAddressLockPicker.test.mjs** Verifies ordinary picker selections insert inline mentions without changing or pulsing the persistent audience, including when the selected agent is already persistently addressed. **desktop/src/features/messages/ui/useAgentAddressLockPicker.ts** Keeps ordinary mention selection on the existing inline insertion path while preserving the separate persistent-address controls and shortcut. **desktop/tests/e2e/persistent-agent-audience.spec.ts** Covers separately targeted inline instructions to two agents, signed outgoing recipients, default-off persistence, and explicit opt-in persistence scenarios. </details> ## Reproduction steps ### Inline mentions without persistence 1. Open a channel with at least two available agents and leave **Automatically mention agents** disabled. 2. Use the composer mention picker to insert the first agent, type an instruction, then insert a second agent and type a different instruction. 3. Confirm the draft reads like `@Agent A review this, @Agent B test that` and neither agent appears in the persistent addressed-agent controls. 4. Send the message and confirm both agents are recipients while neither remains persistently addressed for the next draft. ### Inline mentions with persistence 1. Enable **Automatically mention agents**, then mention an agent inline and send successfully. 2. Confirm that agent becomes persistently addressed for later messages. 3. In a new draft, select the same agent from the mention picker again. 4. Confirm a new inline `@Agent` is inserted at the cursor while the agent remains persistently addressed. 5. Confirm the dedicated automatic-mention control and primary+Shift+Enter shortcut can still add or remove persistent addressing directly. ## Demo - **Before:** Persistent addressing could consume an ordinary picker selection, preventing users from placing that agent inline in the message. - **After:** Ordinary selection always produces an inline `@Agent`; persistence is a separate optional behavior that can coexist with inline mentions. ### Related issue N/A — reported through the Buzz feature room. ### Testing At `a4579c0a666f3644abe44271614186d7b9356bf5`: - Post-push hooks passed desktop check, desktop typecheck, and the full desktop unit suite. - The two persistence-focused Playwright smoke scenarios passed after explicitly opting into **Automatically mention agents**. - The one-time multi-agent targeting and persistent-address shortcut Playwright scenarios passed on the feature changes before the final rebase; CI is validating the rebased branch. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
7ba1197aa6 |
feat(desktop): persist agent addressing across composer messages (#6714)
**Category:** improvement **User Impact:** Agent addressing now stays visible, editable, and consistent across messages, so users can keep talking to the same agents without rebuilding the audience for every send. **Problem:** Agent addressing was transient and easy to lose track of. Inline mention text, persistent audience state, restored drafts, and sent-message rendering could drift apart, making it unclear who would receive the next message. **Solution:** The composer now treats addressed agents as a persistent audience represented by ordinary editable mention chips and a synchronized compact avatar tray. Eligible agent mentions can become persistent immediately, addressing survives sends without flashing empty, and removing or explicitly unpinning an agent updates that state without disturbing unrelated draft text. `Cmd+Shift+M` / `Ctrl+Shift+M` provides a fast way to toggle the current recipient or select the best eligible agent. - Promotes eligible inline agent mentions into persistent addressing when the preference is enabled; human mentions remain unchanged. - Keeps inline chips, restored drafts, persistent recipient state, and the avatar tray synchronized. - Clears an agent's persistent address when its final chip is removed, while explicit unpinning preserves unrelated draft text. - Restores persistent mentions optimistically during send and restores the full draft on failure, avoiding an empty-composer flash. - Deduplicates inline mentions and tag-backed address prefixes in sent messages. - Toggles the current addressed agent with `Cmd+Shift+M` / `Ctrl+Shift+M`, or selects the best eligible agent when none is addressed. - Ranks shortcut selection by recent explicit stream mentions, then active/member/runnable/stable fallbacks; DM participant fan-out does not affect recency. - Adds focused avatar feedback for entry/exit, send bounce, and failure shake, with reduced-motion and narrow-layout support. - Applies the same behavior to main and thread composers while preserving draft restoration, channel switching, and send-race correctness. ### Screenshots #### Light theme The compact tray keeps persistent recipients visible alongside editable mention chips.  #### Dark theme Persistent addressing remains clear and legible in the dark composer treatment.  ### Reproduction steps 1. Open a channel with an eligible agent and enable persistent agent addressing. 2. Insert an agent mention and confirm it appears as a normal editable chip while the compact avatar tray reflects the same persistent recipient. 3. Send several messages and confirm the agent remains addressed, the mention restores without an empty-composer flash, and sent messages do not duplicate the addressed agent. 4. Remove the agent's final mention chip and confirm its persistent address clears. Add unrelated draft text, explicitly unpin the agent, and confirm that text remains intact. 5. Press `Cmd+Shift+M` on macOS or `Ctrl+Shift+M` on Windows/Linux. Confirm it toggles the current addressed agent or, when none is selected, chooses the most relevant eligible agent. 6. Verify recent explicit channel/thread mentions influence shortcut selection, while DM participant fan-out does not. 7. Force a failed send and confirm the addressed avatars shake and the full draft is restored; confirm successful sends use the subtler bounce feedback. 8. Repeat in the thread composer, after switching channels, in dark theme, with reduced motion enabled, and at a narrow composer width. <details> <summary>File changes</summary> **desktop/src/features/channels/ui/ChannelPane.tsx** Feeds recent explicit agent mentions into the composer for contextual shortcut selection. **desktop/src/features/messages/lib/getVisibleAgentAddressPubkeys.ts** and tests Show tag-backed addressed agents only when they are not already represented by inline mention text. **desktop/src/features/messages/lib/mentionCandidates.ts**, **mentionRanking.ts**, **mentionSuggestionMapping.ts**, **recentMentionPubkeys.ts**, and tests Model agent eligibility and activity, derive explicit mention recency, and rank the best shortcut recipient. **desktop/src/features/messages/lib/useActiveAgentPubkeys.ts**, **useDefaultAgentSuggestion.ts**, and **useMentions.ts** Expose active-agent and recent-recipient context to mention consumers. **desktop/src/features/messages/ui/ComposerAddressControls.tsx** and tests Render the compact persistent-recipient tray with enter, exit, send, failure, layout, and reduced-motion states. **desktop/src/features/messages/ui/MessageComposer.tsx** and **MessageComposer.types.ts** Connect editable mention text, persistent addressing, shortcut selection, draft restoration, and send feedback. **desktop/src/features/messages/ui/MessageRow.tsx** Avoids duplicating addressed agents already rendered as inline mentions. **desktop/src/features/messages/ui/MessageThreadPanel.tsx** Brings the same recency and persistent-addressing behavior to thread composers. **desktop/src/features/messages/ui/useAgentAddressLockPicker.ts**, **useAutoPinMentionedAgents.ts**, **useAlwaysAddressShortcut.ts**, **useMentionSendFlow.ts**, and tests Synchronize mention chips with persistent audience state, promote eligible mentions, handle shortcut toggling/default selection, and preserve recipients through send success or failure. **desktop/src/features/messages/ui/useComposerPasteHandler.ts** and **useMentionSendFlow.types.ts** Keep the composer implementation focused by extracting paste and send-flow boundaries. **desktop/src/shared/lib/keyboard-shortcuts.ts** Documents `Cmd+Shift+M` / `Ctrl+Shift+M` as the agent-address shortcut. **desktop/tests/e2e/persistent-agent-audience.spec.ts** Exercises persistence, selection, removal, sends, failures, restored drafts, themes, reduced motion, and narrow layouts. </details> --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
a526dca9bc |
feat: navigate images across message threads (#6705)
## Summary - navigate across image occurrences that are currently rendered inside the nearest explicit open-thread DOM scope - preserve rendered DOM order, duplicate occurrence identity, hidden-spoiler exclusion, preview-image entry parity, and the exact thumbnail return target - keep adjacent timeline messages and unrelated surfaces as separate galleries - make Copy and Download follow the current gallery item, including when a link-preview image opens the gallery first and navigation reaches Markdown media ## Scope contract This is intentionally a **rendered-media gallery only**. Gallery membership comes from mounted image triggers under the nearest explicit thread scope. Collapsed or unloaded descendants are excluded until the thread UI renders them. There is no canonical thread fetch, descendant projection, depth override, or synchronous message reparse in this change. `ChannelPane.tsx` remains unchanged from `main`; `useSearchHighlightProps` and both timeline/thread prop spreads are preserved. ## Verification - rebased onto `origin/main` at `113a33b7e49b7173ee1767c49ef2f49c63803034` - `git diff --check` - differential file-size check - full Desktop check (Biome plus text/pubkey policy checks; only pre-existing informational diagnostics) - Desktop TypeScript typecheck - Desktop production build and rebuilt E2E build - full Desktop JS units: `5,476/5,476` passed - full Desktop Tauri workspace tests passed (one documented native performance test ignored) - rebuilt image gallery Playwright spec: `14/14` passed - search-highlight smoke cases: `5/5` passed - reduced diff: 6 files, `+417/-41`; production `+78/-41`, E2E `+339/-0` The four added E2E cases cover cross-message thread navigation and return targeting, rendered-only exclusion of collapsed descendants, preview-first current-item actions, and adjacent-message isolation. --------- Signed-off-by: Kalvin Chau <kalvin@block.xyz> Signed-off-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
12f3fea26e |
revert fixed mention highlight (#6716)
**Category:** fix **User Impact:** Mention chips now follow the active theme without a fixed yellow fill and remain readable across every supported theme. **Problem:** PR #6696 added an opaque yellow treatment to every human and agent mention. Removing that fixed color exposed another issue: some theme accent colors do not provide WCAG AA contrast when also used as normal-size mention text. **Solution:** Remove the fixed yellow treatment and keep the active theme accent as a subtle translucent chip background. Mention labels use a light/dark semantic foreground selected for readable contrast, including hover states. <details> <summary>File changes</summary> **desktop/src/shared/styles/globals/markdown.css** Removes the fixed-yellow treatment, retains theme-derived accent backgrounds, and applies an AA-safe semantic foreground to human and agent mention labels in resting and hover states. **desktop/src/shared/styles/globals/theme.css** Removes the obsolete fixed-yellow tokens and defines light/dark mention foreground values. **desktop/tests/e2e/mentions.spec.ts** Adds rendered contrast coverage for resting and hover states across every supported syntax theme, compositing translucent chip and ancestor surfaces before checking the 4.5:1 threshold. </details> ## Reproduction steps 1. Open a message containing a human or agent mention. 2. Confirm its subtle background follows the active theme accent rather than using yellow. 3. Hover the mention and confirm the accent background strengthens while the label remains readable. 4. Switch between default, light, dark, and custom syntax themes and confirm mention labels remain legible. This is a focused visual rollback of the mention treatment introduced by #6696. The channel notification badge changes from that PR remain intact. --------- Signed-off-by: tulsi <tulsi@block.xyz> |
||
|
|
29f2054c69 |
highlight search terms in results and messages (#6702)
**Category:** fix **User Impact:** Search terms are now highlighted in yellow in Cmd+F results and in the message opened from a result. **Problem:** Search returned relevant messages, but users still had to reread each preview and destination message to discover where the query appeared. Search navigation could also lose or misapply highlighting during rapid typing, repeated navigation, thread opening, and forum navigation. **Solution:** Render match-focused previews with a shared yellow treatment, carry the result query through navigation, and apply it only to the clicked destination. The implementation binds highlights to the debounced result set, supports token and prefix matching, and handles channels, threads, forums, diffs, and wave messages consistently. <details> <summary>File changes</summary> **desktop/src/app/AppShell.tsx** Carries the active result query into search-hit navigation so the destination can preserve the user's context. **desktop/src/app/navigation/searchHitEventCache.ts** Adds a bounded, one-shot cache for result queries and navigation IDs alongside cached search-hit events. **desktop/src/app/navigation/searchHitNavigation.test.mjs** Covers query retention, one-shot consumption, repeated same-route activations, forum posts, forced routing, and cancellation. **desktop/src/app/navigation/searchHitNavigation.ts** Associates each search-result activation with a unique navigation ID and forwards it to channel or forum destinations. **desktop/src/app/navigation/useAppNavigation.ts** Extends channel and forum navigation to carry search navigation state without changing normal navigation behavior. **desktop/src/app/routes/ChannelRouteScreen.tsx** Consumes search highlight state after navigation and retains it for the selected message while clearing it on community context changes. **desktop/src/app/routes/channels.$channelId.posts.$postId.tsx** Validates and forwards forum search-navigation IDs. **desktop/src/app/routes/channels.$channelId.tsx** Validates and forwards channel search-navigation IDs. **desktop/src/features/channels/ui/ChannelPane.tsx** Passes the selected result ID and query into the main timeline and open thread panel. **desktop/src/features/channels/ui/ChannelPane.types.ts** Defines the destination-highlight contract for channel panes. **desktop/src/features/channels/ui/ChannelScreen.tsx** Routes destination highlighting to either forum content or the channel timeline. **desktop/src/features/channels/ui/ChannelScreen.types.ts** Defines route-level search-highlight inputs. **desktop/src/features/channels/ui/ForumChannelContent.tsx** Forwards search context into expanded forum threads. **desktop/src/features/forum/ui/ForumThreadPanel.tsx** Highlights the matching text in the selected forum post or reply. **desktop/src/features/forum/ui/ForumView.tsx** Carries selected-result context from forum routing into the thread panel. **desktop/src/features/messages/ui/DiffMessage.tsx** Forwards the query into diff descriptions and rendered diff content. **desktop/src/features/messages/ui/DiffViewer.tsx** Provides a highlighted match excerpt for structured diffs and highlights fallback raw diff text. **desktop/src/features/messages/ui/MessageRow.tsx** Passes destination queries into normal Markdown, diff, and wave message renderers. **desktop/src/features/messages/ui/MessageThreadPanel.tsx** Applies highlighting to the selected thread head or reply without tinting unrelated messages. **desktop/src/features/messages/ui/WaveMessageAttachment.tsx** Highlights matches in wave-message fallback text. **desktop/src/features/search/lib/searchMatch.test.mjs** Covers case-insensitive, literal, multi-term, prefix, one-character, and late-preview matches. **desktop/src/features/search/lib/searchMatch.ts** Centralizes token extraction, case-insensitive match splitting, and match-focused preview generation. **desktop/src/features/search/ui/HighlightedSearchText.tsx** Adds the reusable accessible mark renderer used by result and specialized message surfaces. **desktop/src/features/search/ui/TopbarSearch.tsx** Highlights result previews, centers excerpts around matches, strips search operators, and hides stale results during debounce transitions. **desktop/src/features/sidebar/ui/AppSidebar.types.ts** Updates the sidebar search callback contract to include the result query. **desktop/src/features/sidebar/ui/AppSidebarPinnedHeader.tsx** Passes query-aware result selection through the pinned search entry point. **desktop/src/shared/lib/rehypeSearchHighlight.ts** Uses the shared matcher to mark every matching Markdown text segment while leaving code blocks untouched. **desktop/src/shared/lib/searchHighlightStyle.ts** Defines one yellow highlight treatment for light and dark themes. **desktop/src/shared/ui/markdown/nodeCache.test.mjs** Verifies all Markdown matches, one-character scoped searches, code exclusion, and transient cache behavior. **desktop/src/shared/ui/markdown/nodeCache.ts** Enables transient highlighting for one-character scoped searches without polluting the Markdown cache. **desktop/tests/e2e/smoke.spec.ts** Exercises result and destination highlighting, same-route forum activation, and stale-query suppression end to end. </details> ## Reproduction steps 1. Open a channel containing a longer message and press Cmd+F. 2. Search for a word that appears later in the message, such as `mentions`. 3. Confirm each matching result shows the word in yellow and keeps the match visible in its preview. 4. Open a result and confirm the same term is highlighted in yellow in the destination message. 5. Repeat with a thread reply or forum post, and quickly change the query to confirm stale results are not selectable. ## Demo - **Before:** Search results returned the correct message but offered no visual indication of where the query matched; opening the message also showed no match treatment. - **After:** Every matching term is marked in yellow in the result preview and in the exact channel, thread, or forum message opened from that result. --------- Signed-off-by: tulsi <tulsi@block.xyz> |
||
|
|
9b6a637d01 |
fix(desktop): make lightbox zoom controls interactive (#6710)
## Summary - Replace the decorative lightbox zoom icons with accessible Zoom out and Zoom in buttons. - Route button clicks through the existing 1x–3x, 5% stepped zoom state without dismissing the viewer, including clicks on SVG icon descendants. - Extract the zoom toolbar so `markdown.tsx` remains below the repository file-size threshold. ### Testing - `pnpm build:e2e` - `pnpm exec playwright test image-attachment-gallery.spec.ts --project=smoke` — 10 passed - `pnpm typecheck` - Full desktop unit suite — 5,397 passed - Pre-push checks — file-size gate, desktop checks, typecheck, and desktop tests passed - `git diff --check` ### UI evidence The rendered lightbox flow is covered by the gallery smoke test: Zoom out is disabled at 100%, Zoom in changes the value to 105%, Zoom out returns it to 100%, and the dialog remains open throughout. Existing gallery, keyboard, spoiler, context-menu, and close-path assertions also pass. --------- Signed-off-by: sanic <c57bf9b4275088b2b33db7f746975407210f159fbd8bd733c0e532375f69aa80@buzz.block.builderlab.xyz> Signed-off-by: Kalvin Chau <kalvin@block.xyz> Co-authored-by: sanic <c57bf9b4275088b2b33db7f746975407210f159fbd8bd733c0e532375f69aa80@buzz.block.builderlab.xyz> Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> |
||
|
|
a8e1c66c4a |
fix(desktop): polish inline chip states (#6718)
**Category:** fix **User Impact:** Inline chips now read more consistently, fit cleanly in the composer, and show deleted message links with the same calm muted treatment as unresolved links. **Problem:** Deleted message links looked like destructive actions even though they are informational, while mention chips had slightly uneven vertical spacing, a high-set human icon, and could clip inside the composer. **Solution:** Unify unavailable-state styling, tighten chip spacing, optically align the human icon, and give composer chips enough line height to paint without changing caret behavior. <details> <summary>File changes</summary> **desktop/src/shared/styles/globals/markdown.css** Makes chip padding vertically symmetric, moves only the human `@` icon down by 1px, and shares muted colors between deleted and unresolved message links while preserving their separate semantic classes. **desktop/src/shared/styles/globals/composer.css** Adds a composer-only line height derived from the text size and chip padding so inline chips no longer clip while retaining inline caret behavior. **desktop/tests/e2e/entity-link-recipient-cards.spec.ts** Verifies deleted and unresolved chips have matching computed colors while deleted links retain their tooltip, semantics, and navigation behavior. **desktop/tests/e2e/mentions.spec.ts** Covers composer chip height, clipping, the human icon's 1px optical offset, and visual capture. </details> ## Reproduction steps 1. Open a channel containing a link to a definitively deleted message and compare it with a transient or unresolved message link; both should use the muted unavailable treatment, while the deleted link still says “Message deleted” and navigates to its fallback destination. 2. Insert a person mention in the composer; the chip should have balanced vertical spacing and paint fully without clipping. 3. Compare a person mention with a channel chip; only the human `@` icon should sit 1px lower. ## Screenshots ### Deleted message link | Before | After | | --- | --- | |  |  | ### Composer chip polish | Before | After | | --- | --- | |  |  | ## Validation - `desktop/tests/e2e/entity-link-recipient-cards.spec.ts` + `desktop/tests/e2e/mentions.spec.ts`: 82/82 passed - Desktop unit suite: 5,397 passed - Formatting and lint checks passed (existing informational warnings only) - Pre-push desktop check, typecheck, and unit tests passed at `0255c3fd49f9244cd727d0dd20c514b3a1812152` --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
e760c51820 |
feat(workflows): discover trigger filter values (#6712)
**Category:** improvement **User Impact:** Workflow authors can discover people and messages while configuring trigger filters, then see readable enriched labels instead of raw identifiers. **Problem:** Author and message filters required users to know and paste raw public keys or event IDs, and configured workflows surfaced those opaque values afterward. **Solution:** Add network-backed pickers and presentation enrichment while keeping deterministic local public-key and event-ID fallbacks authoritative whenever discovery is unavailable or untrusted. Related issue: none found. <details> <summary>File changes</summary> **desktop/src/features/workflows/ui/WorkflowAuthorPicker.tsx** Adds channel-aware author discovery, profile search, keyboard navigation, loading states, and deterministic public-key fallback selection. **desktop/src/features/workflows/ui/WorkflowCard.tsx** Uses enriched trigger presentation when building the workflow card’s readable summary. **desktop/src/features/workflows/ui/WorkflowDialog.tsx** Keeps Escape scoped to an active filter picker before allowing the inspector or dialog to close. **desktop/src/features/workflows/ui/WorkflowFormBuilder.tsx** Threads channel context into trigger filters and renders enriched author/message summaries in the workflow sequence. **desktop/src/features/workflows/ui/WorkflowMessagePicker.tsx** Adds paged channel-history discovery, message search, exact event lookup, profile labels, keyboard navigation, and bounded results. **desktop/src/features/workflows/ui/WorkflowRichTriggerDescription.tsx** Renders compact author identity details and loading presentation inside trigger summaries. **desktop/src/features/workflows/ui/WorkflowTriggerConditions.tsx** Connects author and message filter accordions to their pickers while preserving selected and excluded condition semantics. **desktop/src/features/workflows/ui/useWorkflowAuthorPresentation.ts** Resolves configured author keys to trusted display labels with deterministic fallbacks. **desktop/src/features/workflows/ui/useWorkflowTriggerPresentation.ts** Enriches configured message IDs only after validating the fetched event and channel. **desktop/src/features/workflows/ui/workflowAuthorCandidates.test.mjs** Covers author candidate normalization, ordering, deduplication, and fallback behavior. **desktop/src/features/workflows/ui/workflowAuthorCandidates.ts** Builds stable author candidates from channel members, profiles, and raw public keys. **desktop/src/features/workflows/ui/workflowConditionExpression.ts** Allows message IDs to participate in basic trigger-filter parsing. **desktop/src/features/workflows/ui/workflowDefinition.ts** Accepts enriched trigger text when generating workflow card labels. **desktop/src/features/workflows/ui/workflowMessageCandidates.test.mjs** Covers event validation, source merging, deterministic ordering, and exact-lookup enrichment boundaries. **desktop/src/features/workflows/ui/workflowMessageCandidates.ts** Validates message candidates by event kind, channel, and exact event ID before permitting enrichment. **desktop/src/features/workflows/ui/workflowTriggerDescription.test.mjs** Covers readable selected/excluded author and message descriptions plus loading fallbacks. **desktop/src/features/workflows/ui/workflowTriggerDescription.ts** Builds concise enriched trigger descriptions while retaining stable raw-ID fallbacks. **desktop/tests/e2e/workflow-local-controls.spec.ts** Exercises picker discovery, selection toggles, Escape ownership, bounded scrolling, and enriched workflow summaries. </details> ## Reproduction steps 1. Open Workflows and create a workflow for a channel with members and message history. 2. Choose **Reaction Added** as the trigger and expand **Author**. 3. Confirm channel members and fetched profile results are discoverable, searchable, and keyboard accessible; choose one. 4. Expand **Message**, confirm recent channel messages appear in a bounded list, and choose one. 5. Toggle either selected filter between **is** and **is not**, then collapse the inspector and confirm the sequence summary stays readable. 6. Add a send-message step and create the workflow; confirm its card uses the resolved author and message labels. 7. Repeat while discovery is unavailable and confirm raw public keys/event IDs remain selectable and authoritative. ## Screenshots ### Author discovery  ### Message discovery  ### Selected filter summaries  ### Enriched workflow card  --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <0366ccd5ee09c2779a9d6bd6683daa17c16a508a51f6a7e7314018dab8fdc49b@buzz.block.builderlab.xyz> Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> |
||
|
|
c5166f2164 |
feat(desktop): simplify the message action rail (#6529)
## Problem Quick-reaction shortcuts make the message action rail visually noisy. Reaction access should remain first-class, but through one predictable Add reaction action rather than several learned emoji shortcuts. ## Change The rail is now: **Add reaction · Reply · Copy link · More** - Remove all quick-reaction shortcut buttons and their divider from the message action rail. - Preserve Add reaction as the first action, including its existing picker, tooltip, accessible name, keyboard behavior, reaction behavior, and feedback. - Keep the existing Link2 Copy link action, shared copy handler, More-menu entry, eligibility guards, and success feedback unchanged. - Keep reveal, positioning, responsiveness, styling, and remaining menu paths unchanged. The now-unused quick-reaction rendering component and imports were removed from `MessageActionBar`; shared reaction learning remains intact for other reaction surfaces. ## Tests Focused smoke coverage verifies: - zero `React with …` shortcut buttons; - exact ordered rail: `Open reactions → Reply → Copy link → More actions`; - the rail and More-menu paths emit the same canonical thread-aware `buzz://message` URL; - existing success feedback; - pending and huddle rows omit both copy-link surfaces; - the action bar stays within the open thread panel. The zero-shortcut contract was mutation-checked by restoring the prior production action bar: the focused test failed causally with expected 0 versus received 3 quick-reaction buttons. ## Validation At `4f062e0d60b0f0c16b6862cdea7137c287060947`: - `pnpm exec biome check src/features/messages/ui/MessageActionBar.tsx tests/e2e/message-copy-link.spec.ts` — passed. - `pnpm exec tsc --noEmit` — passed. - `pnpm test` — 5,432 passed, 0 failed. - `pnpm build:e2e` — passed; existing dynamic-import and chunk-size warnings only. - `pnpm exec playwright test tests/e2e/message-copy-link.spec.ts --project=smoke` — 2 passed. - Pre-push `file-size-check`, `desktop-check`, `desktop-typecheck`, and `desktop-test` — passed. Vogue’s design review: **SHIP** — reaction remains discoverable and accessible as the visible first action; the extra click is an intentional efficiency tradeoff for the simpler hierarchy. --------- Signed-off-by: Trace (Engineer) <9d485ff0c62915e08a801162c195c7ef096f6f6143925407255cea2656c4b8a4@buzz.block.builderlab.xyz> Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> Signed-off-by: Rivet <a08d9a8418c7ff03afe19964724c8fd87bf1776ab9e9b9cafb8cc920edd02a6e@buzz.block.builderlab.xyz> Co-authored-by: Trace (Engineer) <9d485ff0c62915e08a801162c195c7ef096f6f6143925407255cea2656c4b8a4@buzz.block.builderlab.xyz> Co-authored-by: Rivet <a08d9a8418c7ff03afe19964724c8fd87bf1776ab9e9b9cafb8cc920edd02a6e@buzz.block.builderlab.xyz> |
||
|
|
30d2fc52f9 |
fix(desktop): restore icon-only remote marker (#6491)
## Summary - restore the agreed icon-only cloud marker for an agent from another setup - retain the accessible `From another Buzz setup` label and native hover title - keep the merged loading, hover/focus persistence, and exact-pubkey routing safeguards unchanged - update the duplicate-agent E2E to require an icon with no visible marker text in both autocomplete and Channel members ## Why PR #6401 accidentally changed the agreed compact icon treatment into a visible `Other setup` badge during review hardening. This is the smallest correction and is intended for the active release. ## Testing - Desktop JS: 5,280/5,280 passed - Desktop TypeScript typecheck passed - E2E build passed - focused duplicate-agent Playwright journey: 1/1 passed - file-size gate passed - changed-file Biome and `git diff --check` passed Carl, an automated reviewer, opened this via Wes's GitHub account. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> |
||
|
|
f79d346a17 |
fix(composer): wrap Buzz chip labels without orphaning icons (#6581)
**Category:** fix **User Impact:** Long Buzz link chips now wrap without overflowing in composers and sent messages; icon-bearing stubs use at most five graphemes when no earlier separator exists, so some sent chips attach the icon to a shorter prefix than before. Labels over 48 graphemes are visibly truncated in the composer while their full identity remains available to assistive technology and in the tooltip. **Problem:** Long repository, issue, pull request, and channel chip labels could orphan their icon or overflow narrow composers and sent messages. **Solution:** Keep the icon with a bounded, grapheme-safe leading fragment while allowing the remaining text to break anywhere; cap visible labels at 48 graphemes without changing the full tooltip or accessible identity. <details> <summary>File changes</summary> **desktop/src/features/messages/lib/composerMessageLinkNode.ts** Splits composer chip content into an icon-bearing leading fragment and a freely wrapping remainder while preserving the semantic label and link metadata. **desktop/src/features/messages/lib/composerMessageLinkNode.test.mjs** Updates renderer assertions for the fragment structure and verifies every supported Buzz link kind retains the intended visible label. **desktop/src/shared/styles/globals/composer.css** Keeps ordinary composer mention decorations inline while relying on the existing shared markdown chip wrapping rules for Buzz links. **desktop/src/shared/ui/mentionChip.ts** Centralizes grapheme-aware leading-fragment boundaries and label truncation so composer and sent chips share the same visible identity. **desktop/src/shared/ui/markdown/BuzzLinkChip.tsx** Uses the shared grapheme-aware boundary when rendering sent-message chip fragments. **desktop/tests/e2e/navigation.spec.ts** Covers increasing wrap depth across constrained widths, icon attachment, the sent-message wrap, accessible labeling, and tooltip positioning over both edge fragments. </details> ## Reproduction steps 1. Open a desktop channel and paste a Buzz link with a long repository or channel name into the composer. 2. Narrow the composer until the chip spans two or more lines. 3. Confirm the label breaks mid-string while the icon remains attached to the first label fragment. 4. Send the message, hover both the first and last rendered fragments, and confirm the tooltip follows the hovered fragment. ## Screenshots **Before — the icon drops onto a separate line from its chip label**  **After — the icon stays attached while the remaining label wraps** The same long repository chip at three composer widths. Its label gains line breaks as space contracts, while the icon remains attached to the leading fragment. **420px — one line**  **210px — two lines**  **150px — three lines**  --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
f6e6617a9d |
fix(desktop): bound thread /query and surface load errors, not false-empty (#6447)
Long threads in the desktop app sometimes never load (stuck on a
skeleton until you close and reopen the panel), and a failed load
silently renders as "No replies in this branch yet" — presenting a
broken fetch as an authoritative empty thread with no way to recover.
This fixes both, the two IMPORTANT findings from the thread-load
investigation.
## Defect 1 — unbounded `/query` request
The shared `reqwest::Client` in `relay.rs` sets no timeout, and neither
`/query` request builder set a per-request `.timeout(...)`. A stalled or
half-open connection (headers or body never arrive) leaves the request
pending forever, so a thread-history load hangs on the skeleton
indefinitely.
Fix: a 30s per-request deadline on both `/query` builders, funnelled
through one `send_query_request` helper so the timeout can never be
applied to one builder and dropped from the other. Scoped per-request
rather than client-level because the same client also serves STT/TTS
model downloads, builderlab auth, and the media proxy — a client-level
timeout would cut those off. The deadline sits above the 25s WS
`HISTORY_TIMEOUT_MS` so a slow-but-live relay isn't cut off before the
WebSocket path would be. A timeout surfaces through
`classify_request_error` as the stable `"relay unreachable: request
timed out"` string.
`send()` resolves as soon as response headers arrive, so a relay that
returns headers and then stalls the body trips the deadline during body
consumption, not at `send()` — and that consumption happens on two
paths: `parse_json_response` for 2xx, and `relay_error_message` for a
non-success status (500/429/…). Both paths route their body-consumption
error through one shared `classify_body_timeout` helper so they can't
drift: a stalled body surfaces the stable `"relay unreachable: request
timed out"` string on either path rather than the malformed-response
bucket (2xx) or a bare `"relay returned 500"` status label (non-2xx). A
genuinely non-stalled error still keeps its status classification.
## Defect 2 — terminal error painted as empty
`ChannelScreen` consumed only `isPending`/`data` from the thread-replies
query. Once React Query exhausted its one retry, `isPending` was false
and the zero-length data fell through `selectDeferredListRenderState` to
the `"empty"` state — indistinguishable from a genuinely empty branch,
with no retry affordance.
Fix: plumb `isError` + `refetch` through `ChannelScreen` → `ChannelPane`
→ `MessageThreadPanel`. A pure `selectThreadRepliesSurface` helper
decides the paint in strict precedence — the load-bearing invariant is
that a terminal error **never** resolves to `"empty"`, and cached
replies stay visible non-destructively under a later error (the error
card only surfaces when there is nothing to show). The panel renders an
explicit "Couldn't load replies" + Retry card (testids
`message-thread-replies-error` / `message-thread-replies-retry`).
`ProjectConversationPanel` is a second producer of the same shared panel
and used to hard-code `threadRepliesPending={false}` with no
error/retry, so a failed load in a Projects conversation still painted
the false-empty. It now propagates the same
`isPending`/`isError`/`refetch` from its `useThreadReplies` query.
The multi-root `useThreadRepliesForRoots` hook (the Huddle transcript
and Projects-agent conversation surfaces) had the same gap in its
`useQueries` `combine`: it returned only `{ events, isPending }`, so a
failed reply subtree contributed zero rows and vanished. The combine is
now a pure, unit-testable `combineThreadRepliesResults` that exposes
aggregate `isError`/`error` plus a `refetch` that re-runs only the
failed subtrees. Both multi-root consumers render the shared "Couldn't
load replies" + Retry card when a subtree fails: the Projects-agent
conversation after its transcript, and the Huddle transcript as a
non-destructive banner above the timeline. `useHuddleChannelMessages`
used to read only `.events` and discard the aggregate state, so one
summarized root failing left the flattened transcript presenting as
complete; it now propagates `threadRepliesError`/`onRetryThreadReplies`
through `ChannelScreen` into `ChannelPane`, where successful rows stay
visible and `onRetry` re-runs only the failed subtrees.
The error card carries `role="alert"` so its asynchronous appearance is
announced to assistive tech — without a live region a screen-reader user
parked in the composer never learns the load failed or that Retry became
available.
## Tests
- `stalled_query_request_times_out_with_classified_error` — a loopback
server that never responds; asserts the stable classified timeout
string.
- `stalled_response_body_times_out_with_classified_error` — a loopback
server that writes valid 2xx JSON headers then stalls the body past the
deadline; asserts the classified timeout string, not the malformed
bucket.
- `stalled_error_response_body_times_out_with_classified_error` — a
loopback that writes `500` headers promising a body it never sends;
asserts the classified timeout string rather than the `500` status
label.
- `non_stalled_error_response_yields_status_message` — a promptly-served
`500` still surfaces `"relay returned 500 Internal Server Error"`,
pinning that timeout preservation is scoped to actual timeouts.
- `selectThreadRepliesSurface` — pending→skeleton, terminal error→error
(never empty), page-2 failure never empty, cached rows stay visible
under error, successful-empty→empty, retry-success→list,
streaming→pending, and huddle-transcript collapse.
- `MessageThreadReplyState` mounted test — terminal error renders the
error card (asserting `role="alert"`), never the empty card.
- `combineThreadRepliesResults` — multi-root aggregation/order, a failed
subtree surfaces the aggregate error and never drops rows, aggregate
pending, refetch re-runs only failed queries, all-success yields no
error.
- `thread-load-failure.spec.ts` (smoke E2E) — binds the real
channel-thread panel wiring: forces a terminal `get_thread_replies`
failure at the IPC boundary, asserts the error card renders (never the
false-empty) and Retry recovers.
- `project-conversation-load-failure.spec.ts` (smoke E2E) — the same
guard for the Projects conversation producer, driven through the
Projects Channels-tab row.
- `huddle-thread-load-failure.spec.ts` (smoke E2E) — the consumer-level
guard the combine unit test can't provide: drives the real Huddle wiring
(`useHuddleChannelMessages` → `ChannelScreen` → `ChannelPane`) with two
summarized roots, fails one subtree's fetch at the IPC boundary, asserts
the surviving root's reply stays visible while the retry alert surfaces,
then Retry recovers the failed subtree and clears the alert.
## Structure
To stay under the desktop file-size ratchet, `relay.rs`'s inline test
module moved to `relay/tests.rs`, and two pure pieces were extracted
from the panel: the empty/error reply cards (`MessageThreadReplyState`)
and the per-row branch-highlight derivation
(`selectThreadRowHighlight`).
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
4bf80978f5 |
fix(messages): route edits to the owning composer (#6575)
**Category:** fix **User Impact:** Editing a channel message now opens in the main composer, while editing a thread reply stays in the thread composer, with focus ready for typing. **Problem:** When a thread was open, Buzz treated its root message as thread-owned and opened edits in the thread composer. Menu-driven edits also lacked regression coverage for immediate focus. **Solution:** Carry the message's semantic root/reply classification into the edit target, route only actual replies to the thread composer, and use the menu primitive's selection event for a reliable handoff. End-to-end tests cover placement and focus for both paths. <details> <summary>File changes</summary> **desktop/src/features/channels/ui/ChannelPane.tsx** Routes edit targets by semantic thread ownership rather than membership in the open thread panel. **desktop/src/features/channels/ui/ChannelPane.types.ts** Uses the shared composer edit-target type so routing metadata stays attached to the target. **desktop/src/features/messages/lib/draftMentionRefs.ts** Classifies each edit target as a root or true thread reply from its event tags. **desktop/src/features/messages/lib/draftMentionRefs.test.mjs** Covers semantic ownership for root and reply edit targets. **desktop/src/features/messages/ui/MessageActionBar.tsx** Handles Edit through the dropdown menu's selection event so focus restoration and edit startup share the intended lifecycle. **desktop/src/features/messages/ui/MessageComposer.types.ts** Adds semantic thread ownership to the edit-target contract. **desktop/tests/e2e/messaging.spec.ts** Verifies root edits use and focus the main composer, while reply edits use and focus the thread composer. </details> ### Reproduction Steps 1. Send a channel message and open its thread. 2. From the thread panel, edit the root message; confirm its content loads in the main composer and the editor is focused. 3. Send a reply in that thread. 4. Edit the reply; confirm its content loads in the thread composer and the editor is focused. ### Screenshots **Editing a channel-root message uses the main composer**  **Editing an actual thread reply uses the thread composer**  --------- 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> |
||
|
|
9f55bf6745 |
fix(desktop): align jump-to-latest pill with composer height (#6606)
**Category:** fix **User Impact:** Jump to Latest now stays above the composer as a draft grows to multiple lines. **Problem:** On WebKit, the pill's transform could retain a stale inherited composer-height value after the composer expanded, leaving the control stranded inside the composer. **Solution:** Position and animate the pill with its absolute bottom offset, which consumes the live composer height through layout rather than a promoted transform layer. A smoke test now verifies that the pill rises by the full composer growth and remains clear of the composer. <details> <summary>File changes</summary> **desktop/src/features/messages/ui/MessageTimeline.tsx** Anchor Jump to Latest with a live bottom offset instead of a translated compositor layer so composer resizing reliably moves it. **desktop/tests/e2e/smoke.spec.ts** Add coverage that expands a detached timeline's composer and checks the pill tracks the full height increase without overlapping it. </details> ## Reproduction steps 1. Open a channel with enough messages to scroll. 2. Scroll away from the newest message until Jump to Latest appears. 3. Add several lines to the composer without sending. 4. Confirm Jump to Latest rises with the composer and remains directly above it. ## Validation - `pnpm --dir desktop test` — 5,397 passed - `pnpm --dir desktop check` — passed with four existing informational warnings outside this diff - `pnpm --dir desktop typecheck` — passed - `pnpm --dir desktop exec playwright test tests/e2e/smoke.spec.ts --project=smoke` — 26 passed - Push hooks — desktop check, typecheck, and tests passed ## Screenshots / Demos Both captures use the same long, mid-history timeline and the same four-line composer. | Before | After | | --- | --- | | The stale pill position overflows into the expanded composer. | The pill tracks the live composer height and stays clear above it. | |  |  | Signed-off-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> |
||
|
|
0e69b3fd7c |
show mention counts in channel notifications (#6696)
**Category:** fix **User Impact:** Stream and forum channels now show an accessible numeric badge for unread mentions while mention chips remain clear in every theme. **Problem:** Mention notifications contributed to the app and Dock badge, but inactive stream and forum rows only became bold, making it difficult to see where multiple mentions were waiting. Mention styling and generic destructive colors could also lose contrast or visual meaning in some themes. **Solution:** Use the same app-badge projection for non-DM channel mention counts, while preserving regular unread bolding, thread activity dots, DM counts, and manual unread behavior. Dedicated notification and opaque mention-highlight tokens keep the new treatments stable and readable across syntax themes. <details> <summary>File changes</summary> **desktop/src/features/channels/useUnreadChannels.ts** Projects app-badge-eligible mention and broadcast counts into stream and forum channel rows while retaining DM-specific counting and manual-unread semantics. **desktop/src/features/sidebar/ui/SidebarSection.tsx** Renders an accessible numeric notification pill on inactive non-DM channels and preserves the thread activity dot fallback. **desktop/src/shared/styles/globals/markdown.css** Applies the shared opaque yellow highlight to human and agent mention chips, including hover treatment. **desktop/src/shared/styles/globals/theme.css** Adds fixed notification and mention-highlight tokens with theme-independent contrast. **desktop/tailwind.config.js** Exposes the notification token pair through semantic Tailwind utilities. **desktop/tests/e2e/badge.spec.ts** Covers aggregated mention counts, broadcasts, unchanged unread tiers, exact accessible text, and badge contrast under an adversarial theme. **desktop/tests/e2e/mentions.spec.ts** Covers human and agent mention styling, hover behavior, dark mode, and WCAG text contrast. </details> ## Reproduction steps 1. Open a stream or forum channel, then navigate to another channel. 2. Receive two messages that mention you in the inactive channel. 3. Confirm the inactive row is bold and shows a red `2` pill matching the two notifications added to the app or Dock badge. 4. Receive a regular channel message and confirm the row only becomes bold, without a numeric pill. 5. Receive a reply in an interested thread and confirm the channel retains its activity dot instead of a mention count. 6. Switch between light and dark themes and confirm human and agent mention chips remain yellow with near-black readable text, including on hover. ## Screenshots Screenshots are posted in the PR discussion using immutable repository-hosted image URLs. Signed-off-by: tulsi <tulsi@block.xyz> |
||
|
|
2f13e30e88 |
fix(desktop): hide selection formatting tray on composer right-click (#6683)
## Summary Right-clicking a text selection in the message composer left the selection formatting tray floating over the native context menu. This suppresses the tray for the duration of the right-click interaction. ## Changes - `SelectionFormattingTray.tsx`: a `contextmenu` listener on the editor DOM sets a suppression ref, cancels any queued rAF reposition, and hides the tray. Suppression clears on the next left-click `pointerdown` or `keydown` in the editor, which reschedules a normal position update. - `scheduleUpdate`/`updatePosition` both honor the suppression ref, so editor `selectionUpdate`/`transaction`/`focus` events fired during the right-click can't bring the tray back. - Extracted `cancelScheduledUpdate` to replace the duplicated rAF-cancel logic, and reset suppression on editor change / cleanup. - E2E coverage in `composer-selection-formatting.spec.ts`: double-click to select, assert the tray shows, right-click and assert the tray hides *and* that `contextmenu` is not `defaultPrevented` (the native menu still opens), then re-select and assert the tray returns. ## Testing `just` pre-push gate ran green: `desktop-check`, `desktop-typecheck`, `desktop-test` (5397 passing), `file-size-check`. ## Demo https://github.com/user-attachments/assets/2fdfced9-6cd6-4eb2-a6df-c03164ab1c42 Signed-off-by: Matt Toohey <contact@matttoohey.com> |
||
|
|
72ba987c36 |
fix(desktop): stabilize members dialog scrolling (#6670)
## Summary - Keep virtualized member rows measurable by removing `content-visibility: auto` from the measured row subtree. - Use the member card's 60px baseline as the virtualizer estimate while retaining deferred rendering for eager search and archived-member lists. - Cover large rosters with a regression test that checks stable scroll extent across the list and verifies the final member remains reachable. ### Related issue None found. ### Testing - `just ci` - `pnpm -C desktop build:e2e` - `pnpm -C desktop exec playwright test tests/e2e/channels.spec.ts --grep 'members sidebar virtualizes large channel rosters' --repeat-each=5` #### Before https://github.com/user-attachments/assets/a5fcc040-6872-4200-bcc3-7b4197a4dd23 #### After https://github.com/user-attachments/assets/f2c97f2f-3719-4c2a-b17a-2450c6c70a55 Signed-off-by: Matt Toohey <contact@matttoohey.com> |
||
|
|
17af15effa |
fix(desktop): keep member runtime status off the UI thread (#6445)
## Summary - skip managed-agent runtime discovery when the members sidebar has no local managed bots - run runtime listing disk, process, and mutex work on Tauri’s blocking pool - preserve local managed-bot status and Start/Stop behavior with positive and negative E2E coverage Opening Add people in a human-only channel could invoke synchronous native runtime discovery before the sidebar painted, leaving the macOS app beachballed. Human invites do not depend on that data. ## Why I have seen slowness opening this dialog in the UI https://github.com/user-attachments/assets/1955eb5e-ee47-4edf-8e5c-606d11ffbc25 ### Related issue Related overlap: #4851 is a broader managed-agent lifecycle change that includes a similar native offload. This draft is intentionally limited to the sidebar critical path and adds the human-only query gate. ### Testing I have verified the pause in the video goes away after this change. - `just ci` - `just desktop-check` - `just desktop-test` (5,241 passed) - `just desktop-tauri-fmt-check` - `just desktop-tauri-clippy` - `just desktop-tauri-test` (2,702 passed; 18 ignored) - focused Playwright: human-only sidebar skips runtime discovery - focused Playwright: local managed bot retains status and Stop/Start controls No visual styling changed, so screenshots are not applicable. Signed-off-by: Matt Toohey <contact@matttoohey.com> |
||
|
|
2d280376ad |
perf(desktop): persist channel heads, collapse thread reads and reply sends (#6572)
## Summary
Lands the build-now items from the desktop latency plan
(#ui-performance-deep-dive) as one change. Every perceived-latency hot
path a user hits on launch, channel open, thread open, and reply send
drops one or more round trips.
**A1 — persisted channel heads (the big one).** Native WAL SQLite cache
(`desktop/src-tauri/src/channel_head_cache.rs`) keyed by `{pubkey,
relayUrl}` scope, 32 rows/scope LRU, 1 MiB per-row drop cap,
schema-version reset, corrupt-row tolerance, checkpointed on shutdown.
Three blocking-pool commands: `channel_head_cache_load` / `_store` /
`_clear`. On the renderer side, `CommunityQueryProvider` kicks off
hydration of up to 12 heads when it constructs the query client — the
app, splash and relay preconnect mount immediately; only
`useChannelMessagesQuery` awaits the seed (`channelHeadHydration`), then
consumes a one-shot hydrated gate so a hydrated channel pays **zero**
`get_channel_window` calls on mount and exactly **one** on the
post-subscription refresh, whose response replaces page zero wholesale.
That refresh fires whether live-subscription setup succeeds or fails,
and is sequenced behind hydration so it is always a distinct
authoritative fetch (see Review follow-ups). Bounds-only persisted heads
(zero rows) are not hydrated and take the cold loading path. The
timeline loading latch recognizes native-hydrated rows as restart-safe
so they paint immediately instead of holding a skeleton. The cache is a
paint accelerator only — the relay response is always authoritative.
Replaces the legacy localStorage `messageSnapshot.ts` (removed, -401
lines).
Kill switch: `VITE_BUZZ_CHANNEL_HEAD_CACHE=off` at build time or
`localStorage["buzz-channel-head-cache"] = "off"` at runtime. Cache is
cleared on community removal and scoped per identity, so a replaced
signer never sees the previous identity's rows.
**B1 — thread aux in one response.** Relay thread filters accept
`include_aux`; the bridge appends the same authorized two-hop
reactions/edits/deletions closure a channel window gets
(`build_aux_query` shared with the window path). Renderer
`useThreadReplies` drops its two follow-up aux fetches. `next_cursor` is
computed from reply-kind rows only since aux rows are unpaged.
Documented in `docs/bridge-channel-window.md`. Thread queries keep
`staleTime: 0` (`bcfe04e2f`): an earlier revision raised it to 30s,
which CI's `thread-unread.spec.ts` caught — once the user leaves a
channel, the live subscription stops feeding that thread's cache, so a
reopen must always take the (now single) authoritative read.
**B2 — cached root on reply send.** `send_channel_message` gains
`root_event_id`; when the renderer already holds the parent (channel or
thread cache) it passes the NIP-10 root, and native signs without the
relay round trip that `resolve_thread_ref` used to make. Strict hex
parse; `root_event_id` requires `parent_event_id`; absent root falls
back to the existing relay resolution. The renderer never sends a
guessed root.
**B4** general HTTP pool idle 10s→300s, max idle per host 1→2. **B5**
relay preconnect fires as soon as identity is ready instead of waiting
for `requestIdleCallback`. One e2e test (`relay-reconnect.spec.ts`
"service restart close resets accumulated backoff") had been relying on
the idle-callback batching to skip past its own seeded dial failures
before the channel list painted; `8133d70bb` makes it wait for the
connected state instead (test-only, still fails with the 1012 backoff
reset disabled). **B6** profile freshness 60s→10 min (both the in-memory
entry check and the query `staleTime`). Tradeoff: another user's
display-name/avatar edit can take up to 10 min to propagate to a client
that already holds their profile (relay reconnect refetches
`users-batch` but resolves from the still-fresh per-pubkey entry); your
own edits still evict the entry immediately (`evictUsersBatchEntries` in
`useUpdateProfileMutation`).
### Related issue
Follows #6456/#6457/#6459/#6460 (already merged). #6455 is the
measurement instrument and is intentionally not folded in. No duplicate
PR found.
### Review follow-ups
Addressing Carl's reviews
[5001114109](https://github.com/block/buzz/pull/6572#pullrequestreview-5001114109)
and
[5002596542](https://github.com/block/buzz/pull/6572#pullrequestreview-5002596542),
each pushed as new commits (no rebase):
- `4f06b7770` fix(desktop): mount app while channel heads hydrate;
always revalidate — provider no longer gates children on the cache load;
`refreshAfterSubscribe` runs on subscribe failure too; bounds-only heads
skipped at seed; seed merges into an existing window store. +3 tests.
- `35834cb31` fix(relay): drain aux closure hops across the page clamp —
`query_all_pages` walks the `(created_at, id)` keyset via
`until`/`before_id` until a short page (`AUX_PAGE_LIMIT` =
`DEFAULT_MAX_PAGE_LIMIT`, `AUX_MAX_PAGES` = 64 warn+truncate) so
one-shot `limit: 1000` newest-first no longer drops the oldest
edits/deletions. +3 tests; `docs/bridge-channel-window.md` updated.
- `db21b0531` merge of `origin/main` `e23632941` (#6558, #6312 — no
overlap).
- `5a5566c0f` fix(desktop): sequence post-subscribe refresh behind
channel head hydration — `refreshChannelWindowMessages` awaits
`channelHeadHydration()` and, for a hydration-seeded query (`data !==
undefined && dataUpdatedAt === 0`), the in-flight snapshot fetch before
invalidating. Without this, a subscription that settles before the
SQLite load invalidated a data-less in-flight query; TanStack dedupes
that onto the existing fetch (`query-core` `fetch()` only cancels when
`state.data` exists), which returned the seeded snapshot — 0
authoritative fetches. Regression test reproduces Carl's exact ordering
(fails at `35834cb31` with 0 calls), plus a cold-channel guard that the
fix does not double-fetch.
- `b129231c8` fix(desktop): let concurrent post-hydration refreshes
share one window fetch — found independently by Max and Wren reviewing
`5a5566c0f`: subscribe settlement + reconnect both wake on the same
snapshot promise and both invalidate; the second (default
`cancelRefetch: true`) cancelled and replaced the first authoritative
fetch (3 queryFn calls, not 2, and the cancelled Tauri invoke still hits
the relay). The seeded branch now invalidates with `cancelRefetch:
false` so a second waker joins the in-flight fetch; cold/warm keep the
default (`test_canceled_stale_fetch_cannot_overwrite_catch_up_window`
relies on it). Concurrent regression test fails at `5a5566c0f` with 3.
### Testing
At `b129231c8` (PR head; verified in one shell with `git rev-parse HEAD`
= `b129231c8`): `pnpm check`, `tsc --noEmit`, desktop unit 5,393 / 0,
Playwright `boot-splash` + `channel-head-restart` + `relay-reconnect` +
`relay-reconnect-affordance` + `thread-unread` 34 / 34 on a fresh
`build:e2e`, pre-push hooks green.
At `5a5566c0f`: `pnpm check`, `tsc --noEmit`, desktop unit 5,392 / 0,
Playwright `boot-splash` + `channel-head-restart` + `relay-reconnect` +
`relay-reconnect-affordance` + `thread-unread` 34 / 34 on a fresh
`build:e2e`, pre-push hooks green.
At `35834cb31`: desktop unit 5,390 / 0; `cargo test -p buzz-relay --lib`
910 / 0; fmt + clippy `-D warnings` clean; Playwright 32 / 32 (same
specs minus affordance); GitHub CI green on every job except Smoke (3)
(unrelated project-review row-count + messaging timing flake, per Carl)
and Unit Tests (sherpa cache skeleton, below).
Earlier, all at `8133d70bb` (this PR head is `0c492366d` =
|
||
|
|
a2d8be5efa |
feat(desktop-messages): keep agents addressed across messages (#6315)
**Category:** new-feature **User Impact:** Users can keep selected agents addressed across consecutive messages without retyping their handles. **Problem:** Repeated conversations with agents require manually typing the same mentions on every turn, which adds friction and makes recipients easy to omit. **Solution:** The composer can now keep agents automatically addressed per channel, either from the mention controls or after a successful inline mention. Addressed agents remain visible in the toolbar, apply to channel threads, survive send failures safely, and never cross community boundaries. ## Changes <details> <summary>File changes</summary> **desktop/src-tauri/src/events/message_tags.rs** Preserves the automatic-address marker on validated mention reference tags. **desktop/src/features/channels/ui/ChannelPane.tsx** Wires the appropriate channel, thread, inbox, or forum composer context without leaking audiences across surfaces. **desktop/src/features/communities/useCommunityInit.ts** Clears composer audience state when the active community changes. **desktop/src/features/forum/ui/ForumComposer.tsx** Wires the appropriate channel, thread, inbox, or forum composer context without leaking audiences across surfaces. **desktop/src/features/forum/ui/ForumComposerAutocompletes.tsx** Wires the appropriate channel, thread, inbox, or forum composer context without leaking audiences across surfaces. **desktop/src/features/home/ui/InboxDetailPane.tsx** Wires the appropriate channel, thread, inbox, or forum composer context without leaking audiences across surfaces. **desktop/src/features/messages/lib/agentAddressMention.d.mts** Defines helpers and types for marked automatic-address mention tags. **desktop/src/features/messages/lib/agentAddressMention.mjs** Defines helpers and types for marked automatic-address mention tags. **desktop/src/features/messages/lib/agentAddressMention.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/lib/applyEditTagOverlay.mjs** Preserves automatic-address metadata when edited message tags are overlaid. **desktop/src/features/messages/lib/applyEditTagOverlay.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/lib/autoPinMentionedAgentsPreference.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/lib/autoPinMentionedAgentsPreference.ts** Stores the preference that keeps explicitly mentioned agents addressed for later messages. **desktop/src/features/messages/lib/extractMentionPersonas.ts** Separates persona recipients from the composer mention orchestration. **desktop/src/features/messages/lib/persistentAgentAudience.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/lib/persistentAgentAudience.ts** Maintains bounded, in-memory, channel-scoped automatic agent audiences. **desktop/src/features/messages/lib/useMentionSelection.ts** Centralizes mention picker selection state and agent-first selection behavior. **desktop/src/features/messages/lib/useMentions.ts** Exposes explicit picker origins and selection controls while preserving inline mention behavior. **desktop/src/features/messages/ui/ComposerAddressControls.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/ui/ComposerAddressControls.tsx** Renders compact addressed-agent avatars and the automatic-mention management entry point. **desktop/src/features/messages/ui/MentionAutocomplete.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/ui/MentionAutocomplete.tsx** Adds automatic-mention controls and options to the existing mention picker. **desktop/src/features/messages/ui/MessageAgentAddressPrefix.tsx** Shows which agents were automatically addressed on a sent message. **desktop/src/features/messages/ui/MessageComposer.tsx** Integrates automatic audiences, picker controls, accessible feedback, shortcuts, and send behavior. **desktop/src/features/messages/ui/MessageComposer.types.ts** Defines the simplified channel audience context shared by composer hosts. **desktop/src/features/messages/ui/MessageComposerToolbar.tsx** Places automatic-address controls in the composer toolbar without crowding narrow layouts. **desktop/src/features/messages/ui/MessageRow.tsx** Displays automatic-address metadata alongside sent message content. **desktop/src/features/messages/ui/MessageThreadPanel.tsx** Wires the appropriate channel, thread, inbox, or forum composer context without leaking audiences across surfaces. **desktop/src/features/messages/ui/composerAgentKeyboard.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/ui/persistentAgentAudienceHosts.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/ui/useAddressMentionPulse.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/ui/useAddressMentionPulse.ts** Provides success and failure animation signals for addressed-agent controls. **desktop/src/features/messages/ui/useAgentAddressLockPicker.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/ui/useAgentAddressLockPicker.ts** Coordinates adding, removing, and announcing automatically addressed agents. **desktop/src/features/messages/ui/useAlwaysAddressShortcut.ts** Implements the platform-aware shortcut for toggling automatic addressing. **desktop/src/features/messages/ui/useAutoPinMentionedAgents.ts** Promotes successfully sent inline agent mentions and provides a single undoable notification. **desktop/src/features/messages/ui/useComposerMentionPicker.ts** Opens the mention picker without rewriting the current draft. **desktop/src/features/messages/ui/useMentionSendFlow.helpers.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/ui/useMentionSendFlow.helpers.ts** Merges automatic and inline recipients, marks outgoing tags, and restores failed sends safely. **desktop/src/features/messages/ui/useMentionSendFlow.ts** Merges automatic and inline recipients, marks outgoing tags, and restores failed sends safely. **desktop/src/features/messages/ui/usePersistentAgentMentionHydration.ts** Removes the prior draft-text hydration approach now that automatic audiences stay at composer ingress. **desktop/src/features/settings/ui/AgentsSettingsPanel.tsx** Replaces the old global behavior with explicit composer-level automatic-mention controls. **desktop/src/features/settings/ui/PreventSleepSettingsCard.tsx** Replaces the old global behavior with explicit composer-level automatic-mention controls. **desktop/src/shared/lib/keyboard-shortcuts.ts** Defines the user-facing automatic-address keyboard shortcut label. **desktop/src/shared/ui/VideoReviewCommentMarkdown.tsx** Allows automatic-address prefixes to compose with video review timecodes. **desktop/tests/e2e/persistent-agent-audience.spec.ts** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. </details> ## Reproduction Steps 1. Open a channel with one or more agents and open the mention picker from the composer. 2. Select an agent for automatic mentions, then send several messages without retyping the handle; confirm the agent remains in the composer control and receives each message. 3. Mention another agent inline, send successfully, and confirm the agent becomes automatically addressed; use the notification's Undo action to reverse it. 4. Open a thread in the same channel and confirm the same addressed agents are available there. 5. Remove an agent from the composer control and confirm later messages stop addressing it. 6. Switch communities and confirm addressed agents do not carry into the other community. ## Screenshots All states below use the dark Buzz theme with a selected lilac accent. ### Addressed composer Selected agents stay visible at the composer ingress without adding handles to the draft.  ### Open mention menu The @ ingress opens the existing mention menu and shows which agents are already addressed.  ### Mention options The inline options pane controls whether a successful one-time agent mention carries into later messages.  ### Agent settings The same preference is available in **Settings → Agents → Conversations**.  --------- 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> |
||
|
|
723affe5d1 |
fix(desktop): remove Buzz entity link previews (#6512)
**Category:** improvement **User Impact:** Buzz-native project, repository, issue, and pull request links now appear once as compact inline chips, with their details available on hover. **Problem:** Buzz-native entity links rendered both an inline chip and a standalone preview card, repeating the same metadata and adding visual noise to conversations. **Solution:** Exclude Buzz-native links from the shared standalone-preview extractor while leaving entity parsing intact for chip tooltips and preserving external web previews and attachment cards. <details> <summary>File changes</summary> **desktop/src/shared/lib/linkPreview.ts** Stops Buzz-native preview candidates after parsing, including same-relay git clone URLs that normalize to repository entities, while allowing external URLs through the existing snapshot path. **desktop/src/shared/lib/linkPreview.test.mjs** Covers project, repository, issue, pull request, markdown-labeled, same-relay clone, and mixed external-link extraction behavior. **desktop/src/shared/ui/markdown/useMessageLinkPreviews.test.mjs** Confirms sent messages no longer merge a standalone Buzz entity card while external sender snapshots still render. </details> ## Reproduction steps 1. Open a desktop channel containing a `buzz://project`, `buzz://repo`, `buzz://issue`, or `buzz://pr` link. 2. Confirm the link renders as an inline entity chip without a second standalone Buzz card below the message. 3. Hover the chip and confirm its entity metadata remains available. 4. Post an external HTTPS link and confirm its web preview still renders. 5. Paste a same-relay `/git/<owner>/<repo>` clone URL and confirm it uses the repository chip without a duplicate card. ## Screenshots | Before | After | | --- | --- | | Inline chip plus redundant standalone Project card | Inline chip is now the sole presentation | |  |  | **After — rich metadata stays available on hover**  ## Verification At commit `3fa74cdd342ac1f6721b7d56a7f111af31e0e6e9`: - focused link-preview + Markdown unit suites — 119/119 passed - targeted registered smoke E2E — 8/8 passed, including labeled same-relay clone metadata, ordinary-link presentation, and in-app navigation - `cd desktop && pnpm exec tsc --noEmit` — passed - `git diff --check origin/main...HEAD` — passed - pre-push hooks — desktop check, TypeScript, and full desktop unit suite passed --------- Signed-off-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Co-authored-by: Mongo <5398c5fd039b963ce132b3e078e7c4af097dd997517bb5e14c2682fe68c25197@buzz.block.builderlab.xyz> |
||
|
|
074561233e |
fix(composer): preserve caret when inserting mentions mid-message (#6531)
**Category:** fix **User Impact:** Users can insert mentions earlier in a draft and continue typing without the caret corrupting the rest of the message. **Problem:** Caret correction ran after every document change, so typing a mention before existing text repeatedly advanced across the mention separator and interleaved spaces into the draft. **Solution:** Limit correction to the autocomplete settlement it was designed for, with transaction-level and browser-level regression coverage for known and unregistered mentions. <details> <summary>File changes</summary> **desktop/src/features/messages/lib/mentionHighlightExtension.ts** Restricts trailing-space caret advancement to an armed autocomplete settlement instead of every document change. **desktop/src/features/messages/lib/mentionHighlightExtension.test.mjs** Exercises the real ProseMirror plugin state and verifies mid-draft mention typing, unknown tokens, end-of-message typing, and completed-mention separators. **desktop/tests/e2e/mentions.spec.ts** Reproduces the reported composer workflow in Chromium and covers the same corruption path for an unregistered `@token`. </details> ## Reproduction steps 1. Open a channel and enter `hello world` in the composer. 2. Move the caret between `hello` and ` world`. 3. Type ` @bo`, select `bob` from autocomplete, and continue typing `abc`. 4. Confirm the composer reads `hello @bob abc world` with the caret after `abc`. 5. Repeat with an unregistered token such as ` @zzq` and confirm the existing text remains intact. ## Before / After | Before | After | | --- | --- | | Typing after a mid-draft mention walks the caret through the existing message. | Continued typing stays after the inserted mention. | |  |  | --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Mongo <5398c5fd039b963ce132b3e078e7c4af097dd997517bb5e14c2682fe68c25197@buzz.block.builderlab.xyz> |
||
|
|
97b1fee5c3 |
fix(desktop): restore true zoom by scaling the root rem (#6514)
## Summary Follow-up to #5644. Cmd +/- had become a text-only zoom: type scaled while rem-based padding, gaps, widths, avatars, and controls stayed frozen, which produced cramped layouts (see [#buzz-frontend thread](buzz://message?channel=a410ffde-c61f-416a-96e0-c296b5f5ecc9&id=1a758115cf07b00c097f6e988553908c045165325a57637519cfa7ed9c9accec)). Root cause: #5644 introduced a virtual typography rem so the **Font size** preference could change text without moving layout — a good decoupling — but it also routed **Cmd +/- zoom** through that same px-valued token and pinned the real root at 16px. One decision ("freeze layout") was applied to two dials that shouldn't share it. This PR gives each dial one owner and lets CSS compose them: | Control | Changes | How | |---|---|---| | **Cmd +/- zoom** | Everything — true zoom | Scales the real `<html>` font-size again (`useWebviewZoomShortcuts`) | | **Font size preference** | Text only | Sets `data-font-size`; `typography.css` maps it to a unitless `--buzz-type-scale`, mirroring how density already works | `--buzz-type-rem` becomes `calc(1rem * var(--buzz-type-scale))` — rem-relative, so it rides on zoom automatically. Resulting text px = `16 × zoom × scale × token-ratio`. The 13 / 14 / 15px conversation contract is unchanged at default zoom. Density and the type ramp from #5644 are untouched. The preference module no longer does px math or knows about zoom; the zoom hook no longer imports the preference module. Net deletion in production code. ## Validation - `pnpm test` — 5,308 desktop unit tests - `pnpm check:px-text`, `tsc --noEmit`, biome - Playwright: `top-chrome-zoom-clearance.spec.ts` (native-chrome clearance stays fixed under root zoom), `inbox-refactor-screenshots.spec.ts` (zoomed row padding now asserts `4.4px` instead of the frozen `4px`), and both `profile.spec.ts` zoom tests (composed zoom × preference, cross-window storage reset) - Before/after screenshots at 140% zoom in the comment below --------- Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
eb484387d5 |
chore(desktop): drop unused ORIGINAL_CONTENT from empty-edit-delete spec (#6517)
`biome check` fails with `lint/correctness/noUnusedVariables` on `ORIGINAL_CONTENT` in `desktop/tests/e2e/empty-edit-delete.spec.ts`, which fails `pnpm check` (Desktop Core) for **every PR touching desktop paths** — e.g. it currently blocks #6460. It presumably landed while Desktop Core was path-skipped on the introducing PR. One-line removal; the constant has no remaining references (the assertions use `RENDERED_ORIGINAL_CONTENT`). Signed-off-by: Max Lampert <maxwell@squareup.com> |
||
|
|
0e48ff2691 |
feat(workflows): clarify workflow setup and activation (#6470)
**Category:** new-feature **User Impact:** Workflow authors can build filtered, runtime-aware automations, understand them at a glance, and get a clear warning before turning on workflows likely to run often. **Problem:** Workflow setup exposed raw configuration without enough help composing message templates, filtering triggers, or understanding saved behavior; activation could also make a broadly triggered workflow live without explaining its likely frequency. **Solution:** Batch 3 adds local, deterministic template variables, trigger filters, and semantic summaries, then refines cards and activation around configured behavior and a risk-aware warning boundary. Scheduling remains the already-shipped implementation, advanced expressions remain lossless, and network-backed identity/message enrichment stays in Batch 4. | Message inputs | Trigger filters | | --- | --- | | Caret-aware, keyboard-accessible suggestions expose trigger-local values and safe prior-step outputs in `send_message.text`. | Structured conditions and validated manual IDs block invalid submission while preserving advanced expressions. | |  |  | | Workflow cards | Risk-aware activation | | --- | --- | | Semantic labels, channel-first hierarchy, configured reaction/action visuals, real step stacks, and compact status controls make behavior scannable. | Broad message and frequent schedule triggers explain the risk before **Turn on**; narrowly scoped triggers proceed without unnecessary ceremony. | |  |  | ## Changes <details> <summary>File changes</summary> **desktop/src/features/workflows/ui/WorkflowActionsMenu.tsx** Separates direct card status controls from secondary actions while retaining modal status actions. **desktop/src/features/workflows/ui/WorkflowCard.tsx** Adds semantic behavior, channel-first hierarchy, configured reaction/action visuals, real subsequent-step stacks, status controls, and reduced-motion-aware trigger feedback. **desktop/src/features/workflows/ui/WorkflowDialog.tsx** Warns before activating broadly triggered workflows while allowing narrowly scoped workflows to proceed directly. **desktop/src/features/workflows/ui/WorkflowFormBuilder.tsx** Connects structured trigger filters and template-aware step inputs while preserving schedules, trigger transitions, and selected YAML authority. **desktop/src/features/workflows/ui/WorkflowStepCard.tsx** Replaces generic labels with deterministic configured-step descriptions. **desktop/src/features/workflows/ui/WorkflowTemplateTextarea.tsx** Adds caret-aware variable suggestions with keyboard navigation and focus restoration. **desktop/src/features/workflows/ui/WorkflowTriggerConditions.tsx** Adds structured local filters, validated author/message IDs, and a lossless advanced-expression fallback. **desktop/src/features/workflows/ui/workflowActivationWarning.ts** and **workflowActivationWarning.test.mjs** Classify broad message and frequent schedule triggers for contextual activation warnings. **desktop/src/features/workflows/ui/workflowConditionExpression.ts** and **workflowConditionExpression.test.mjs** Model and cover parsing, serialization, validation, and advanced-expression preservation. **desktop/src/features/workflows/ui/workflowDefinition.ts** and **workflowDefinition.test.mjs** Preserve trigger/step configuration and derive deterministic card metadata across YAML round trips. **desktop/src/features/workflows/ui/workflowStepDescription.ts** and **workflowStepDescription.test.mjs** Generate and cover local step summaries. **desktop/src/features/workflows/ui/workflowTemplateVariables.ts** and **workflowTemplateVariables.test.mjs** Define and cover trigger-specific, order-bounded variables and caret insertion. **desktop/src/features/workflows/ui/workflowTriggerDescription.ts** and **workflowTriggerDescription.test.mjs** Generate and cover semantic trigger summaries without network lookups. **desktop/tests/e2e/workflow-local-controls.spec.ts** and snapshot Cover filters, IDs, advanced expressions, autocomplete, activation choices, summaries, and YAML authority. **desktop/tests/e2e/workflow-reaction-picker.spec.ts** Covers configured reaction emoji in workflow nodes and summaries. **desktop/tests/e2e/workflows.spec.ts** Covers risk-aware activation warnings, direct safe creation, duplication, and card status controls. </details> ## Reproduction steps 1. Create a message-posted workflow in **Workflows**, add a Send message step, and type `{{trig`; verify keyboard-selectable variables insert at the caret. 2. Configure message-text and manual ID filters; verify malformed IDs block submission and advanced expressions survive Form/YAML transitions. 3. Create a broad message workflow; verify **Back** persists nothing, **Keep off** saves it disabled, and **Turn on** enables it. Confirm a narrowly triggered webhook skips the warning. 4. Inspect the saved card; verify its channel, semantic behavior, configured actions/reaction, real step stack, and status are understandable without opening YAML. ## Validation Validated at exact clean head `f99503819889b95ee3c61657c5c3850aae35481e` on base `24ec6a468ec9d0d425ee58fbfc4d416412c446ad`. - Focused workflow regressions passed 59/60 locally; the only local miss was a 438-pixel macOS snapshot drift, while the checked-in Linux baseline comes from the failing CI artifact. Repository pre-push gates and E2E build/typecheck passed. - A broader 36-test smoke invocation had 31 passes and five unrelated pre-existing expectation/snapshot failures, so it is not claimed as fully green. Adversarial fixes are recorded in [round one](https://github.com/block/buzz/pull/6470#issuecomment-5366366670) and [round two](https://github.com/block/buzz/pull/6470#issuecomment-5366407535). --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: Mongo <5398c5fd039b963ce132b3e078e7c4af097dd997517bb5e14c2682fe68c25197@buzz.block.builderlab.xyz> |
||
|
|
aea0ef8df9 |
fix(desktop): simplify duplicate agent provenance (#6401)
## Summary - leave the local same-name agent row unlabelled because locality is implied - mark only the remote identity with a compact cloud badge reading `Other setup` in mention autocomplete and Channel members - suppress the redundant `managed by you` text for duplicate owned agents while retaining distinct short npubs and exact-pubkey routing ## Testing - Desktop E2E build/typecheck: `pnpm build:e2e` - focused Playwright smoke test: `duplicate owned agents preserve provenance and exact pubkey selection` (1 passed) - visible provenance in autocomplete and Channel members - local identity remains unmarked - keyboard selection routes each exact pubkey - narrow 760×640 viewport containment - focused Biome check on the changed marker and E2E files - `pnpm check:px-text` - `pnpm check:pubkey-truncation` - `just file-size-check` - `git diff --check` --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> |
||
|
|
4e3c9e619c |
fix(desktop): sender names in notifications + macOS click-through routing (#6427)
> Opened by the agents Brain and Pinky on behalf of @wesbillman. Fixes two desktop notification issues (requested in Buzz channel `desktop-notification-improvements`): ## 1. Notifications now show who sent the message Live DM and thread-reply notifications showed only "Direct message" / "Reply in #channel", while home-feed mention toasts already carried the sender's name. All message-notification copy is now centralized: - **`formatMessageNotification`** (`notifications/lib/notificationFormat.ts`) — canonical title/body for all five sources (mention, approval, needs-action, DM, thread reply). Sender-first titles with neutral fallbacks, never a raw pubkey: - DM: `Taylor` instead of `Direct message` - Thread reply: `Taylor replied in #ship-room` instead of `Reply in #ship-room` - **`useNotificationSenderName`** — synchronous cache-only lookup (react-query users-batch entry cache → persisted label cache); a cold miss ships the fallback title immediately and warms the cache in the background. No toast delay, no new network machinery. - **`buildEventNotificationTarget` / `buildFeedItemNotificationTarget`** (`notifications/lib/target.ts`) — the click-through target payload is built in one place instead of three hand-rolled copies. ## 2. macOS notification clicks route to the target message (#3509) On packaged builds, clicking a notification focused the app but never navigated. Three independent gaps lined up behind one symptom: - **Reveal hang**: notification navigation now starts before the best-effort `unminimize → show → setFocus` chain, so a hung native invoke cannot gate click-through; the existing 1.5s reveal timeout remains as a secondary guard. - **Lost emit**: the Rust delegate queues the activation target *before* emitting `native-notification-activated`; a lost emit stranded the target with nothing re-draining the queue. The macOS listener now also drains on window `focus` / `visibilitychange` — delivered by WebKit independently of the Tauri event channel, and always produced by the click's own foregrounding. - **Silent no-op**: `commitNavigation` skips same-href destinations; `goChannel` / `goForumPost` / `openSearchHit` now accept `force`, and the notification activation handler passes it so a click always routes. Multiple queued activations are serialized FIFO with rejection containment so an older click cannot finish after and overwrite a newer one. Queue teardown now aborts already-running activations as well as pending ones; async forum-comment destination resolution rechecks ownership before community-scoped cache writes or routing. Diagnostic evidence from the macOS unified log (packaged v0.5.17): delegate confirmed live in release builds (`willPresent` honored — `(["list"])` presentations); a real click response at 09:25:06 reached `usernoted`, the app was fronted by LaunchServices, and no navigation followed. ## Verification - Fixed the stale relay-backed DM dedupe expectation: exactly one toast remains required, with sender-first title `alice` instead of channel title `alice-tyler` - Rebased onto `origin/main` and verified 15 focused activation/click tests, desktop typecheck/check, file-size gate, Biome on changed files, and `git diff --check` at `21a7e0cf5` - Notification + navigation + AppShell.helpers unit tests: 89/89 pass (includes new `notificationFormat.test.mjs`, `target.test.mjs`, `desktopActivations.test.mjs`) - Full desktop JS suite run by Pinky at f03979ea: 5133/5133 pass; pre-push hooks (desktop-test, desktop-typecheck, desktop-tauri-checks, rust-tests, file-size ratchet) all green on this branch - `tsc --noEmit` and changed-file Biome checks clean; re-verified after rebasing current `origin/main` - Click-through on a packaged build still needs human verification — @wesbillman, next release build is the real test. --------- Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz> Signed-off-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz> Co-authored-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> |
||
|
|
9b32e055fe |
polish(desktop): finish Projects navigation and context chrome (#6429)
## Summary After #6396, Projects still split chrome across the workspace header, a copy-link control, and a labeled Actions group that mixed people, create, and metadata. This PR finishes that surface: the right-hand context box is unlabeled actions plus a Details group, people stacks and contribution heatmaps are gone from that box, Create review sits with Create task, and the top chrome is terminal / chat / info with no copy-link. Sent project context collapses to a pill, and review file diffs keep the last good git view instead of flashing empty while queries refetch. This also lands the remaining navigation polish that followed Part 3: overview and list presentation, readme and commit layout, and opening the latest matching conversation from the Channels tab without leaving the project. ### Related issue N/A. Related: #6396 ## Testing - Walked Files, Tasks, Reviews, task/review detail, overview tabs, and chrome chat vs info in the running desktop app - Pre-push: desktop typecheck, unit tests, Tauri checks, and file-size gate passed - Updated Projects smoke specs for the new context groups, Create review, chrome order, and removed copy-link control - Merged current `origin/main`; one conflict in discussion-channel rows kept conversation-panel navigation and took main's bounded channel-name lookup ## Post-Deploy Monitoring & Validation - validate Projects workspace chrome, context box, and review file diffs in the first staging Desktop session - healthy signals: context box shows unlabeled actions then Details, chat toggle sits between terminal and info, review diffs stay populated across selection changes - failure signals: missing Create review, restored heatmap/people in the context box, or empty Files Changed while the review is still selected; mitigate by reverting this PR --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> |
||
|
|
9891e64f6b |
fix(desktop): clarify add agents channel action (#6374)
"Create agent" button takes you to a UI to **invite** your existing agents to a channel. Rewording the button to make this clear. Before <img width="481" height="251" alt="Screenshot 2026-08-21 at 1 58 40 pm" src="https://github.com/user-attachments/assets/1b64f722-62b8-46fa-a6c4-dafcf2bdfaa7" /> After (Sorry about different elements being in hover state in the screenshots) <img width="481" height="313" alt="Screenshot 2026-08-21 at 1 58 16 pm" src="https://github.com/user-attachments/assets/f68a9c9b-2276-4ef5-ace8-c4017977b1e8" /> ## Summary - Clarify that the empty-channel intro action adds existing agents to the channel. - Assert the exact action title and description in the existing E2E coverage while preserving the separate Welcome create-agent flow. ### Related issue None found. ### Testing - `../node_modules/.bin/biome check src/features/channels/ui/useChannelIntro.tsx tests/e2e/channels.spec.ts` passed. - `./node_modules/.bin/tsc && ./node_modules/.bin/vite build --mode e2e` passed. - The isolated Playwright smoke case `empty channel shows intro actions` passed (1/1) after installing the repo-pinned Chromium. - `env -u BUZZ_AGENT_PROVIDER just ci` passed. - Screenshots not captured; this is a copy-only UI change. --------- Signed-off-by: Matt Toohey <contact@matttoohey.com> |
||
|
|
de8a2741c7 |
feat(desktop-messages): show compact Buzz link metadata (#6252)
**Category:** improvement **User Impact:** Buzz links in messages now resolve into compact, descriptive chips with predictable navigation, while tooltips use the neutral secondary surface consistently across light and dark themes. **Problem:** Raw Buzz-native links and opaque identifiers make message context difficult to scan, while oversized previews disrupt the conversation. Missing, delayed, or deleted metadata also needs a stable presentation that does not mislead users or break navigation. **Solution:** Keep message and issue chips metadata-independent: message chips render only the channel name, while issue chips render only the repository name—never fetched content/title or an event hash. Fetched context remains available in each hover tooltip. Other native links continue to resolve into concise inline labels, with muted secondary fallbacks, distinct deleted states, and existing navigation paths preserved. This also makes two explicit shared-tooltip behavior changes: 1. **Color:** shared and rich tooltips move from the primary color pair to the neutral secondary semantic pair, including dedicated huddle-token handling and matching rich-descendant foregrounds. 2. **Hover behavior:** tooltip roots set Radix `disableHoverableContent`, and tooltip content is pointer-transparent, so moving off the trigger dismisses the tooltip instead of letting the pointer enter and keep the popover open. Together these preserve readable contrast, authoritative project context, and predictable hover dismissal. <details> <summary>File changes</summary> **desktop/src/features/communities/useCommunityInit.ts** Primes channel metadata needed to resolve native links promptly. **desktop/src/features/messages/lib/messageLinkMetadata.test.mjs** Covers message preview extraction, fallbacks, and deleted-message semantics. **desktop/src/features/messages/lib/messageLinkMetadata.ts** Normalizes fetched message metadata into compact labels and status-aware presentation. **desktop/src/features/messages/ui/SentFromThreadLine.tsx** Provides thread context to linked-message metadata resolution. **desktop/src/shared/lib/useResolvedLinkPreviews.ts** Exposes resolved preview state for native entity chips. **desktop/src/shared/styles/globals/markdown.css** Adds compact chip layout and semantic disabled-state styling. **desktop/src/shared/styles/globals/theme.css** Defines the semantic colors used by unavailable and deleted chips. **desktop/src/shared/ui/markdown.test.mjs** Exercises native-link rendering and provider-backed entity metadata behavior. **desktop/src/shared/ui/markdown.tsx** Routes native markdown links through metadata-aware chip components without changing navigation contracts. **desktop/src/shared/ui/markdown/ChannelDeepLink.test.mjs** Covers resolved, unresolved, private, archived, and forum channel presentations. **desktop/src/shared/ui/markdown/ChannelDeepLink.tsx** Renders channel names and concise channel metadata inline with stable fallbacks. **desktop/src/shared/ui/markdown/MessageLinkPill.tsx** Renders loading, ready, unavailable, DM, forum, and deleted message states compactly. **desktop/src/shared/ui/markdown/entityLinks.tsx** Enriches repository, issue, and pull-request chips and uses containing-project context in tooltips. **desktop/src/shared/ui/markdown/types.ts** Extends markdown runtime metadata required by native chip rendering. **desktop/src/shared/ui/markdown/useMessageLinkMetadata.ts** Fetches and classifies linked-message metadata while preserving navigable fallbacks. **desktop/src/testing/e2eBridge.ts** Adds deterministic metadata controls for runtime visual and navigation tests. **desktop/tailwind.config.js** Makes semantic disabled colors available to chip styles. **desktop/tests/e2e/entity-link-recipient-cards.spec.ts** Verifies entity labels, project-aware tooltips, delayed metadata, and navigation behavior. **desktop/tests/e2e/navigation.spec.ts** Verifies channel and message chip states, metadata resolution, deletion handling, and navigation. **desktop/src/shared/ui/tooltip.tsx** Migrates the shared tooltip surface from the primary color pair to the semantic secondary pair. It also sets Radix `disableHoverableContent` and makes tooltip content pointer-transparent so leaving the trigger dismisses the tooltip predictably. **desktop/src/features/agents/ui/RestartDiffBadge.tsx** Aligns restart-diff tooltip values and supporting copy with the secondary tooltip foreground. **desktop/src/features/channels/ui/AddChannelBotTeamsSection.tsx** Aligns rich team tooltip descriptions, persona chips, avatars, and names with secondary tooltip semantics. **desktop/src/features/projects/ui/ProjectAuthorIdentity.tsx** Uses the matching secondary tooltip foreground for project author metadata. **desktop/src/features/projects/ui/ProjectCards.tsx** Aligns repository-unavailable tooltip descriptions with the secondary tooltip foreground. **desktop/src/shared/styles/globals/utilities.css** Makes huddle tooltips consume their dedicated palette directly without misleading primary or secondary aliases. **desktop/src/shared/styles/globals/tooltipSemantics.test.mjs** Pins direct huddle-token consumption and rejects semantic alias drift. **desktop/tests/e2e/tooltip-semantics.spec.ts** Verifies simple and rich tooltip surfaces and descendant tokens in Buzz light and Catppuccin Mocha dark themes. **desktop/playwright.config.ts** Registers tooltip semantic coverage in the desktop smoke suite. </details> ## Reproduction steps 1. Run the desktop app and open a message containing Buzz links for a channel, message, repository, issue, and pull request. 2. Confirm message links show only the channel name and issue links show only the repository name inline, with no fetched content/title or event hash; confirm other native links remain compact rather than rendering raw identifiers or expanded previews. 3. Hover the chips and confirm the neutral secondary tooltip adds concise context without duplicating the visible label. 4. Open each chip and confirm it follows the same channel, message, repository, issue, or pull-request navigation as before. 5. Inspect links while metadata is loading, unavailable, or definitively deleted and confirm the fallback remains compact, legible, and navigable where appropriate. 6. In Buzz light and Catppuccin Mocha dark themes, compare a simple channel-members tooltip and the rich Add agents team tooltip; both should use secondary semantics with readable nested content. 7. Move the pointer from a trigger toward its tooltip; confirm the tooltip dismisses rather than remaining open as hoverable content. ## Screenshots The matrix below covers both the default light theme and Catppuccin Mocha with an orange accent. Message/issue-chip rows affected by the stable-label pivot are temporarily omitted until they can be recaptured from the real built UI; the prior images showed the obsolete expanding inline label. | State | Default light | Dark · orange accent | |---|---|---| | Public stream channel tooltip |  |  | | Private forum channel tooltip |  |  | | Public forum channel tooltip |  |  | | Channel references, including unresolved |  |  | | Unknown channel permalink |  |  | | Archived channel tooltip |  |  | | Repository entity |  |  | | Pull request entity |  |  | | Entity metadata fallback |  |  | ### Tooltip semantic surface spot check | Buzz light | Catppuccin Mocha dark | |---|---| |  |  | --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
569308c23c |
fix(desktop): restore recent channel sorting (#6402)
## Summary - batch desktop last-message queries into groups of 128 so workspaces with larger channel counts stay within the relay's explicit-channel limit - propagate timestamp query failures instead of replacing the sidebar's cached recency with all-null data - advance channel recency from live message events so Recent ordering updates without waiting for the next refresh ## Root cause The desktop sent one explicit `#h` filter per channel in a single `/query`. The relay rejects a request with more than 128 aggregate explicit channel values. The desktop swallowed that rejection and returned no timestamps, so Recent correctly fell back to A–Z for every channel. ## Testing - `cargo test --manifest-path desktop/src-tauri/Cargo.toml last_message_filters_stay_within_relay_channel_cap --lib` - `cd desktop && pnpm test` (5,132 passed) - `cd desktop && pnpm exec biome check src/features/channels/useLiveChannelUpdates.ts src/features/channels/lib/channelRecency.ts src/features/channels/lib/channelRecency.test.mjs tests/e2e/channel-sort.spec.ts` - `cd desktop && pnpm check:file-sizes` - `cargo fmt --all -- --check` - `cd desktop && pnpm build:e2e` followed by `pnpm exec playwright test tests/e2e/channel-sort.spec.ts --project=smoke --workers=1` against a dedicated static server (4 passed) ## Manual test In a workspace with more than 128 channels, choose **Channels → Sort → Recent**. Channels should order by latest message instead of A–Z. While Recent is selected, a new message in a visible channel should move that channel to the top immediately. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> |
||
|
|
54bbe7305b |
perf(desktop): resolve references without directory scans (#6328)
## Summary - keep the channel poll membership-only and fetch the open-channel directory only from explicit discovery surfaces - resolve unknown ID-bearing references through one bounded `get_channel_details` lookup, while name references remain limited to member channels plus a browse/search-warm directory cache - fail closed for fetched private metadata and cover cold lookup, warm/member hits, private visibility, and cached misses with mounted hook contracts --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz> Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz> |
||
|
|
ee0c7076af |
Fix cross-owner relay agent mentions in owner-only builds (#6338)
## Summary - keep `BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY` scoped to Desktop-managed local start and provider deployment boundaries - stop filtering independently operated relay agents by viewer/owner equality in native discovery, autocomplete, and send-time revalidation - preserve NIP-OA ownership verification, owner-authored `respond_to` policy, shared-channel membership, and fail-closed send-time checks - replace the packaged-build regression expectation with coverage for cross-owner allowlisted and `respond_to=anyone` relay agents, including the emitted agent `p` tag Fixes #6329. ## Why The packaged 0.5.17 build reused its managed-runtime owner-only capability in relay-agent mention admission. That silently hid correctly configured shared agents owned by another operator, even when their verified policy explicitly authorized the viewer. The capability is intended to constrain runtimes Desktop starts or deploys, not external relay agents. ## Validation - `pnpm --dir desktop test` (5093 passed) - `cargo test --manifest-path desktop/src-tauri/Cargo.toml relay_directory --lib` (4 passed, 2 ignored real-relay tests) - focused agent mention unit tests (35 passed) - Desktop TypeScript/E2E build - focused Playwright mention tests (3 passed) - pre-commit formatting hooks - pre-push branch-skew, file-size, Desktop check/typecheck/unit tests, and Tauri checks - `git diff --check` --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
1934e83bf5 |
feat(workflows): add workflow editor (#6248)
**Category:** new-feature **User Impact:** Users can create, edit, duplicate, and deep-link to workflows in a responsive visual editor without losing unsupported YAML or unsaved work. **Problem:** Workflow editing was split across disconnected surfaces and lacked reliable URL state, lifecycle protection, and parity between Form and YAML modes. **Solution:** This adds a route-addressable editor foundation with stable pane identity, guarded dirty exits, lossless Form/YAML transitions, responsive workflow and channel controls, and matching reaction-filter execution support. <details> <summary>File changes</summary> **crates/buzz-workflow/src/lib.rs** Apply reaction trigger filters during workflow execution and cover target-message gating. **crates/buzz-workflow/src/schema.rs** Extend the reaction trigger schema with the editor-owned filter field. **desktop/src/app/navigation/useAppNavigation.ts** Add navigation helpers for explicit workflow create, edit, and duplicate editor modes. **desktop/src/app/routes/WorkflowsRouteScreen.tsx** Coordinate route state with the shared workflow library and editor dialog. **desktop/src/app/routes/lazyWorkflowsRouteScreen.ts** Share one lazy route component across workflow route entry points to avoid loading flashes. **desktop/src/app/routes/workflows.$workflowId.tsx** Parse workflow editor modes and pane deep links for workflow-specific URLs. **desktop/src/app/routes/workflows.tsx** Parse library-level create state and render the shared workflow route screen. **desktop/src/app/AppWorkflowEditorOverlayProvider.tsx** Host the shared workflow editor at the app-shell level so channel-originated workflow dialogs stay above the active channel instead of replacing it. **desktop/src/shared/context/WorkflowEditorOverlayContext.tsx** Expose route-independent open-existing and create-new workflow actions to channel settings. **desktop/src/features/workflows/ui/WorkflowEditorHost.tsx** Share editor loading, unavailable, and dialog lifecycle wiring between canonical workflow routes and the channel overlay. **desktop/src/features/channels/ui/ChannelManagementSheet.tsx** Add an experiment-gated, Canvas-style Workflows ingress below Canvas, including channel-scoped loading, error, empty, and list states plus open/create actions; disabled users issue no workflow query. **desktop/src/features/channels/ui/ChannelWorkflowsSection.tsx** Render the channel workflow list and New workflow action without pushing the existing settings sheet past its file-size ceiling. **desktop/src/features/workflows/ui/ChannelCombobox.tsx** Adopt the final channel presentation, portalled scrolling, and one-shot create-flow opening behavior. **desktop/src/features/workflows/ui/CreateWorkflowDialog.tsx** Remove the superseded create-only dialog in favor of the unified workflow editor. **desktop/src/features/workflows/ui/WorkflowCard.tsx** Open workflow cards in the detail and run-history modal while preserving explicit edit and duplicate actions. **desktop/src/features/workflows/ui/WorkflowDetailDialog.tsx** Present workflow Trigger/Steps in the shared modal chrome, with top-chrome ingress to a responsive right-side run-history inspector and an explicit edit action. **desktop/src/features/workflows/ui/WorkflowDialog.tsx** Unify create, edit, and duplicate lifecycle handling with URL panes, generated-name synchronization, dirty-exit guards, stale-write preservation, and protected webhook-secret handoff. **desktop/src/features/workflows/ui/WorkflowFormBuilder.tsx** Build the responsive Form/YAML editor shell, stable step selection, insertion and removal behavior, and lossless canonical-YAML synchronization. **desktop/src/features/workflows/ui/WorkflowStepCard.tsx** Align workflow step controls and presentation with the final editor interaction model. **desktop/src/features/workflows/ui/WorkflowUnavailableDialog.tsx** Show a non-disclosing loading or unavailable state for missing and inaccessible workflow links, with retry and close actions. **desktop/src/features/workflows/ui/WorkflowWebhookSecretDialog.tsx** Obscure one-time webhook secrets by default and require explicit confirmation before any close or navigation discards them. **desktop/src/features/workflows/ui/WorkflowsScreen.tsx** Connect library state and workflow actions to the route-addressable editor. **desktop/src/features/workflows/ui/WorkflowsView.tsx** Restore the responsive workflow library, create tile, cards, loading states, and shared action menu. **desktop/src/features/workflows/ui/workflowEditorPane.test.mjs** Cover pane parsing, serialization, and stable step-ID reconciliation. **desktop/src/features/workflows/ui/workflowEditorPane.ts** Define explicit trigger and stable step pane URL state. **desktop/src/features/workflows/ui/workflowYamlDocument.ts** Read and update header fields independently of full form validation so incomplete steps cannot clear or disable the workflow title. **desktop/src/features/workflows/ui/workflowYamlDocument.test.mjs** Cover document-level workflow header reads and writes for incomplete definitions. **desktop/tests/e2e/workflow-title-stability.spec.ts** Verify generated, renamed, saved, and duplicated titles remain stable while moving between trigger and step panes. **desktop/src/features/workflows/ui/workflowFormTypes.test.mjs** Cover lossless Form/YAML round trips and actionable fallback for unsupported fields. **desktop/src/features/workflows/ui/workflowFormTypes.ts** Own canonical workflow YAML conversion while preserving supported trigger and step fields. **desktop/src/shared/ui/PortalledScrollArea.tsx** Provide bounded scrolling for popovers rendered outside their dialog container. **desktop/src/shared/ui/popover.tsx** Allow workflow popovers to use the shared portalled scroll container. **desktop/tests/e2e/channels.spec.ts** Cover disabled and enabled Workflows experiment states, including suppressed queries while disabled, placement beneath Canvas, channel workflow listing and opening, channel-preselected workflow creation, and direct return to the channel Workflows panel after close, discard, or cancel. **desktop/tests/e2e/workflows.spec.ts** Exercise library actions, deep links, create/edit/duplicate lifecycle, dirty exits, responsive editor behavior, YAML safety, stale updates, and one-shot channel selection. </details> ## Reproduction steps 1. Open **Workflows** and confirm the responsive card library, create tile, card action menu, and card-to-detail/run-history modal navigation. 2. Open `?view=create`; confirm the channel chooser opens once, the trigger inspector stays hidden until a channel is selected, and closing the chooser does not make it reopen after unrelated edits. 3. Create a workflow, switch between Form and YAML, add and remove steps, refresh a pane deep link, and confirm the selected trigger or stable step remains addressable. 4. Edit or duplicate a workflow, make an unsaved change, and confirm close, Escape, browser navigation, and route target changes require discard confirmation while pane-only navigation does not. 5. Enter unsupported YAML and confirm Form mode gives an actionable fallback without rewriting the definition; verify reaction triggers preserve and execute their filter. 6. Open a channel’s settings, select **Workflows** below **Canvas**, and open or create a workflow; confirm the shared modal stays over the channel, the channel URL does not change, New workflow preselects that channel, and closing or discarding returns directly to the channel’s **Workflows** panel. 7. In create, edit, and duplicate modes, move between the trigger and incomplete step panes and confirm the generated or edited title remains visible and editable. ## Screenshots Fresh captures from product head `c5c3abc91a71fe511d43e6cc9168b1626d0c217c`; the later review-guidance fix does not alter these pictured states. ### Workflow library and actions  ### Workflow editor — wide  ### Workflow editor — narrow inspector overlay  ### Workflow editor — active channel overlay  ## Review feedback addressed - Corrected Diff Posted condition guidance to use the executor-supported `str_contains(trigger_text, "deploy")` syntax and added a visible Playwright regression assertion. - Scoped direct workflow-detail navigation coverage to the stable `Edit workflow` dialog name, asserted the workflow title separately, and retained the trigger-node assertion. - Routed dirty channel-overlay Duplicate/Edit transitions through the existing discard confirmation, preserving the original YAML draft when the user keeps editing. - Made workflow deletion await relay success, remain non-dismissible while pending, and retain the confirmation/editor/draft with an actionable inline error on rejection. - Gated the channel-settings Workflows ingress and channel workflow query behind the `workflows` experiment, with defensive rendering if the flag changes while that view is active. - Matched the Reaction Added trigger to the prototype reaction picker, preserving native/custom/legacy values in canonical YAML and providing an explicit clear action. - Kept a channel’s Workflows panel mounted beneath channel-origin editors so clean close, dirty discard, and create cancel return directly to that panel without changing the channel URL. ## Verification Verified at exact pushed head `76ebba7b7ace1e13445744200da70ca9da231b7e`: - Push hooks passed: destination-org policy, branch-skew, differential file-size, Desktop checks, TypeScript typecheck, and **5,115/5,115** Desktop unit tests - Focused channel lifecycle E2E passed: dirty edit discard returns directly to the channel Workflows panel; create cancel does the same while preserving the channel URL and preselected channel - E2E production build, standalone TypeScript typecheck, and Biome checks on both touched files passed - Reaction-picker regression spec remains recorded at `dc28ffa98ec34b4e0656757fb2dd93e60a84674d`: **5/5 passed** (picker interaction, canonical YAML persistence/clear, legacy-value preservation, save/reopen round trip, narrow viewport containment) - Blox existing workflow E2E regression set at `dc28ffa98ec34b4e0656757fb2dd93e60a84674d`: **36/36 passed** - Earlier blocker and review regressions remain recorded at `3760c3d657a525f5af98e8d0f98bdd03999d8e61`: **5,114/5,114** Desktop unit tests and the dirty-overlay/deletion focused checks - Working tree clean; local branch, remote branch, and PR head all match the exact SHA above ### Related issue None found. Closest prior work: #231. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Codex <noreply@openai.com> |
||
|
|
3c228b1082 |
feat(desktop): refine context-aware Projects collaboration (#6396)
## Summary - give inline project agents bounded visible-page and selection context while reusing the shared message-thread presentation - add contextual collaboration actions for discussing project entities in related channels - align project list metadata, context rails, and work-item communication actions with the active workspace This is Part 3 of the Projects v6 stack, following #6368. Part 4 contains the remaining navigation and detail-page polish. ## Testing - Desktop unit suite: 5,125/5,125 passed - Projects smoke specs: 62/62 passed - TypeScript, Biome, typography, pubkey, and differential file-size checks passed - full pre-push gate passed ## Post-Deploy Monitoring & Validation - validate Projects overview/detail agent chat and discuss-in-channel journeys in the first staging Desktop session - healthy signals: context matches the active project/repository/work item, messages remain in the chosen channel, and restored conversations exclude unrelated DM history - failure signals: stale or cross-project context, duplicate/missing thread rows, or collaboration actions targeting the wrong channel; mitigate by reverting this PR Related: #6335 --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> |
||
|
|
2e7583bf5a |
fix(desktop): distinguish duplicate agent devices (#6337)
## Summary - distinguish same-name owned agents by management provenance: `managed here` for Desktop-managed identities and `managed elsewhere` for same-owner relay identities - show provenance only when same-name suggestions collide, alongside each identity's short npub - preserve exact-pubkey selection and keep unique-agent autocomplete unchanged - add a composed mock-bridge regression covering relay owner propagation, rendered labels, keyboard/pointer selection, and outbound mention pubkeys ## Testing - focused mention suggestion mapping and label tests - composed Desktop E2E passes for both same-name identities and exact-pubkey routing - causal mutation verified: replacing the relay candidate's `ownerPubkey` with `null` makes the composed E2E fail on the `managed elsewhere` assertion - pre-push Desktop checks: Biome, TypeScript, file-size ratchet, and 5,103 Desktop tests ## Manual test With two same-name owned agents visible in a channel, type `@<name>`. Duplicate rows identify the identities as `agent · managed here` and `agent · managed elsewhere`, include distinct short npubs, and selecting either routes the mention to that row's exact pubkey. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
2b7be9340d |
feat(desktop): make Projects workspaces selectable (#6368)
## Summary Projects workspaces now support consistent selection across projects, repositories, reviews, tasks, and channel results. A user can collect related entities, see the active selection without losing their current workspace, and pass a bounded, injection-safe representation to an agent for discussion. Detail views now keep repository source and availability explicit while preserving contextual actions across local and remote workspaces. Missing or inaccessible repositories produce a recoverable state instead of leaving the workspace ambiguous. This is part 2 of the Projects v6 stack. Parts 3 and 4 will add context-aware collaboration and navigation/detail-page polish. ### Related issue Related: #6335 ### Testing - Full pre-push gate passed: file-size checks, Biome, TypeScript, Desktop unit tests, Tauri checks, and Rust tests - Project context safety suite: 9/9 passed, including bounded untrusted overview data and hidden-context stripping - Updated Playwright coverage exercises grouped selection, selection-aware agent context, repository source states, and detail actions --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> |
||
|
|
f88cda9eb8 |
feat(desktop): make the Projects overview follow the selected section (#6335)
## Summary Part 1 of 4 stacked PRs continuing the Projects work from #6003. - The Projects overview now follows the selected section: the right-hand context pod stays visible across the Projects / Repositories / Reviews / Tasks / Channels tabs and shows section-specific people, stats, and contribution graphs, so it reads as live context rather than a detached summary. - Entity list rows are consolidated onto one compact line (title first, icons after, aligned dates/counts), shared across projects, repositories, PRs, issues, and channel lists via `ProjectEntityListRow`. - New `projectRelatedChannels` helper resolves the channels a project is discussed in for the overview. Follow-ups in this stack: part 2 (selectable workspaces), part 3 (context-aware collaboration), part 4 (navigation & detail-page polish). ## Test plan - [x] Desktop unit tests (`pnpm test`) — pass - [x] TypeScript (`tsc --noEmit`), Biome, clippy — clean via pre-push gate - [x] Playwright specs updated for the new overview behavior (`projects-v3-screenshots.spec.ts`, `sidebar.spec.ts`) — pass locally in the smoke project --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> |
||
|
|
9c2f05346f |
fix(desktop): show complete repository trees (#5102)
## Summary Large repositories were silently limited to the first 250 lexicographically sorted paths, which could leave the Files tab showing only dot-directories. Retain all tracked paths while limiting eager content previews, and replace the Files tab hard cutoff with incremental 200-entry pagination. ### Related issue Fixes #4960. No duplicate PR found. ### Testing - Targeted Rust regression: `cargo test --manifest-path desktop/src-tauri/Cargo.toml parse_ls_tree_keeps_paths_after_eager_preview_limit --lib` - Pagination helper regression: `node --import ./test-loader.mjs --experimental-strip-types --test src/features/projects/lib/projectsViewHelpers.test.mjs` - Desktop Biome, type checking, and file-size checks pass - All 4,393 desktop tests pass - Repository pre-push hooks pass - `just ci` currently stops on pre-existing Clippy warnings in `crates/buzz-acp/src/queue.rs` and `crates/buzz-agent/src/hints.rs`, outside this PR diff Generated with Codex --------- Signed-off-by: Evan Chen <evanc@squareup.com> Co-authored-by: Codex <noreply@openai.com> |
||
|
|
9a1e861ab8 |
Add appearance preference previews (#6193)
## Summary - Replace the Link previews and Thread layout dropdowns with the shared segmented control from #5644. - Pair each setting with a real, inert preview: production Compact/Rich link cards and a simplified Focus/Split thread diagram. - Keep scrub previews local to Settings so only a completed selection updates the saved preference and real app behavior. - Keep each setting and preview responsive as one grouped unit, with focused light/dark and narrow/wide coverage. ## Dependency - Stacked on #5644, which provides the typography/density foundation, shared `SegmentedControl`, and responsive `SettingsOptionRow`. - After #5644 merges, this PR should be retargeted/rebased onto `main`. ## Validation - `pnpm typecheck` - `pnpm check:px-text` - `pnpm check:file-sizes` - `pnpm exec playwright test tests/e2e/appearance-previews.spec.ts --project=smoke` (3 passed) - Focused existing Appearance coverage (2 passed) - Pre-push desktop checks, typecheck, tests, and Tauri checks Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
f96f1883c5 |
fix(desktop): restore emoji recents (#6263)
## Summary
- normalize emoji-mart's poisoned literal `{}` recents state before
initialization
- preserve the established idle emoji-index prewarm so first picker open
stays responsive
- keep the mounted quick-reaction tray behavior unchanged from main
- cover full-picker same-session recents, prewarm ordering/callback
behavior, and the production picker-to-prewarm import boundary
## Validation
- focused prewarm tests passed 2/2 at
`98188797e219db20670b46379a240493ff8f4cbe`
- mutation check passed: changing `EmojiPicker` back to direct
`@emoji-mart/data` consumption fails the new production-boundary
assertion
- pre-push branch-skew, file-size, Desktop check, typecheck, and full
Desktop unit suite passed (5,024/5,024) at exact pushed head
`98188797e219db20670b46379a240493ff8f4cbe`
- focused full-picker lifecycle plus existing custom-emoji/tray smoke
passed 2/2 on the preceding runtime-equivalent head
- independent Royal Court scope review and headless field test passed
the full-picker journey on the preceding runtime-equivalent head
---------
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
|
||
|
|
bbd20fae75 |
perf(desktop): move five hot renderer paths from JS into Rust (#6024)
## Summary
Moves five hot renderer paths out of JavaScript and into Rust, targeting
main-thread CPU on the desktop app: wakeups, IPC round trips, and
per-frame JS work during reconnect storms, catch-up, and archive sync.
Five commits, reviewed and ratified sequentially (each round included
independent re-review, mutation-tested coverage, and full-suite gates at
pinned heads):
| Commit | Change |
|---|---|
| `9b5a82ab9` | Batch inbound relay frames in the native websocket
plugin: N run-loop wakeups collapse into 1 per 8ms window, byte-bounded
so no batch crosses the Tauri direct-execute threshold onto the slow
fetch path. NIP-42 AUTH bypasses the window. |
| `8b047a35e` | Move the local-archive subscription into Rust: the
renderer no longer sees archived frames at all. Shared
`native_relay_client` (one authenticated socket per relay+pubkey,
declarative subscription reconciliation, CLOSED-driven retry with
write-time eviction), lease/epoch lifecycle ownership so delayed IPC
cannot resurrect a stopped task. |
| `609962b49` | Move persona catalog fetching into Rust: signature
verification (~473us/event, ~0.24s per 500-event page formerly on the
webview thread) now runs under `spawn_blocking`; one projected DTO
crosses IPC. |
| `38afea0a0` | Batch unread catch-up behind one IPC call: N per-channel
REQs/awaits become one command with global two-pass classification,
making the completion-order race unrepresentable. Both IPC DTO surfaces
pinned by wire tests asserting serde output against the TS contract. |
| `b955f528b` | Move observed unread state into native SQLite: scoped
WAL DB with a sequence/revision protocol, transactional localStorage
migration, epoch-tagged rebuild detection. Membership no longer
serializes five capped arrays per catch-up: measured 327.4 KiB before,
0.2 KiB after at the same populated 5x1000 fixture. |
Full design rationale, failure matrices, and verification detail are in
the individual commit messages.
### Known behavior notes
- **Native migration is a one-way door per profile.** A successful
native open sets `migration_complete` in the first open's transaction
and the renderer then deletes the legacy localStorage key
(`useObservedUnreadPersistence.ts:290-294`,
`observed_unread.rs:420-442`). The one-release JS fallback remains for
native open *failure*, but a profile that has completed migration cannot
return to the JS store. Relevant if a rollback path is ever needed.
- **The membership seed is built unconditionally at startup**
(`useUnreadChannels.ts:245-250`), before any `isNative()` check. It is
ref-cached and small, but it is a startup cost the baseline does not
pay, including on the fallback path.
### Related issue
None found. Work originated and was reviewed in the Buzz
`buzz-gui-performance` channel.
### Testing
**Current head is `9a8128ba4d2d695113505162be388fcec62cda0a`. For
receipts at that head, see [Round-3
receipts](#round-3--final-head-9a8128ba4) at the bottom; the block
immediately below is the historical record for the original five-commit
pack and its counts are superseded.**
The receipts in this subsection are at exact pack head
`b955f528b6dfa34db089b21cc718fba0f9284d6f` (tree oid `dcc711a66`),
verified by three independent instruments (implementer, reviewer,
gatekeeper), with `git rev-parse HEAD` confirmed in the same shell as
each run:
- `cargo test --lib` (full package): **2511 passed / 0 failed** (+7 +3
in the other targets)
- `pnpm test`: **4920 / 4920 across 74 suites**
- `cargo fmt --check`, `clippy --all-targets -D warnings`, `tsc
--noEmit`, `pnpm check` (including the file-size ratchet): all clean
- Mutation controls on the final review round: badge-lane controls A and
B each turn exactly one test red; seed-guard (M4) and
anchor-monotonicity (M5) mutants each killed by exactly their own
witness; restored bytes fully green
- The observed-unread native path is exercised by an in-tree
`__TAURI_INTERNALS__` protocol rig (entry, replay, gap, rebuild,
markers, membership, badge lane), added after review found the original
suite never left the localStorage fallback
**Open gates, stated plainly (status as of head `9a8128ba4`):**
- **Paired CPU benchmark still not run.** The three-arm design (baseline
`78cbffeb6` vs pack vs a null arm with native forced off, fresh profile
per arm) was instrument-reviewed but never executed. The IPC payload
reduction above is measured; end-to-end CPU improvement is predicted,
not measured. This gate is unchanged since `b955f528b`.
- **Live-local gate at the final head: GREEN at `9a8128ba4`.** A real
native Tauri live-local pass per `TESTING.md` was run at this exact
head. Receipts:
`.scratch/pr6024-final-9a8128ba4d2d695113505162be388fcec62cda0a/`,
`MANIFEST.txt` `start_utc=2026-08-17T21:58:07Z`, `FINAL_ATTESTATION.txt`
`end_utc=2026-08-17T22:20:47Z` — both after the commit's `21:32:51Z`,
and the build worktree's HEAD is `9a8128ba4` with a clean tree. **I
opened these files rather than relaying the summary**, and recomputed 6
SHA256SUMS entries against the sealed manifest (6/6 match). From the raw
artifacts: historical catch-up persisted 10 seeded events into the
native `observed-unread.db`; a realtime event advanced it 10→11; the
relay was killed and restarted with Desktop up and an event sent after
recovery advanced it 11→12 within 2 polling ticks; Desktop was then
stopped, an event sent offline, and after relaunch with the appdir
preserved catch-up advanced it 12→13. `relay.log` shows 2 app WebSocket
connections, the restart-close signal, and fresh connections after each
recovery — the app-attributable socket evidence the previous round
lacked. The preserved appdir database is still on disk and `select
count(*) from observed_events` returns **13**. The ignored real-relay
archive wire test passed **1/1** at this head. This exercises the
`lib.rs` / `e2eBridge.ts` merge recombination under a real app and
blocker 2's failure-mode family (relay killed mid-session, app killed
with offline traffic in flight, sync did not go dead). It does **not**
directly force the precise overlapping B/C archive-ownership timing
window through the GUI; that invariant is pinned by the compiled
`drop(owner)` mutation proof below, and is deliberately not relabelled
as live race evidence. **Retraction retained for the record:** an
earlier revision of this description said live-local was GREEN at
`9a8128ba4` while citing a run executed against `f079d0914`, the pre-fix
head, roughly three hours before `9a8128ba4` existed. That claim was
false and was corrected before this run existed. The GREEN above rests
on the new receipts only.
- **Workspace-wide `cargo test --workspace` is red on `main`, not on
this PR.** The exact-head live run's workspace logs contain honest
`rc=101` results. All are in crates this PR does not touch: `git diff
origin/main...9a8128ba4` is **empty** for `crates/git-sign-nostr`,
`crates/buzz-relay`, and `crates/buzz-pair-relay`.
`git-sign-nostr::tests::test_parse_envelope_rejects_invalid_oa_pubkey`
fails **deterministically on clean `main`** — reproduced independently
in fresh worktrees at merge-base `a282e0643` and at current tip
`7f61cf431` (55 passed / 1 failed, same assertion at `lib.rs:2136`).
Root cause: `nostr` 0.36→0.44 changed `PublicKey::from_hex` from a
curve-point parse to a plain hex decode, so an all-zeros (off-curve) key
now parses; measured with a standalone probe, `from_hex("0"*64).is_ok()`
is `false` on 0.36.0 and `true` on 0.44.7. Filed as block/buzz#6175,
with the observation that nothing in CI runs this crate's tests (`just
test-unit` enumerates packages and omits it; `server-cross-compile`
compiles it and runs nothing). The mesh-demo and pair-relay failures in
the same logs are timing flakes in equally untouched crates. **None of
these gate this PR.**
- **Full smoke + integration: GREEN at `9a8128ba4`.** CI run 32072147445
is `success` with every job green — all 4 Desktop Smoke E2E shards, both
Desktop E2E Integration shards, Desktop Core, Desktop Build (macOS),
Desktop E2E Relay, Rust Lint, Windows Rust, DCO, Dead Token Reference
Guard. Attempt 1 was cancelled by an infrastructure hang, not by this
code: both integration shards stalled 19m37s inside `Install Playwright
system dependencies` (an apt install that runs before the e2e bundle is
built or the relay starts), so no test executed and all downstream steps
were skipped. The same step took 16s at `9128b9389` and 12s/18s on
attempt 2 of these same bytes. Attempt 2 was a job rerun, not a push;
the head is unchanged.
---
Opened by Eva (agent) on Tyler's behalf at his request.
---
## Review round 2 — head `9128b9389`
Carl's review
[4953970089](https://github.com/block/buzz/pull/6024#pullrequestreview-4953970089)
raised two blockers against `f079d0914`. Both were confirmed real at the
source by a second reader and both are fixed in `9128b9389`.
**Blocker 1 — recovery dropped the mutation whose ingest failed.** Every
captured mutation now gets one retry after `reopen()` refreshes
sequence/revision, which is safe because ingest is idempotent (events
upsert `DO NOTHING`, channel latest advances by `MAX`, membership is
`INSERT OR IGNORE`, and a replayed sequence returns a snapshot rather
than reapplying). `snapshotRequired` takes the same recovery path
instead of being treated as success. If reopen and retry both fail,
native is explicitly degraded and equivalent state is applied to the JS
fallback so `isNative()` stops reporting healthy. Regressions assert the
*originally rejected* marker, destructive clear, and membership delta
each survive; removing the retry takes 5 tests red including all three.
**Blocker 2 — a superseded archive start could tear down the newer
scope's relay session.** `begin` now returns an `ArchiveOwnership` token
that holds both ownership guards, and `archive_session` requires one, so
session acquisition happens inside the ownership critical section rather
than after it.
### Deviation from the requested regression test for blocker 2
Carl asked for "a production-wiring regression that pauses B after
`begin`, fully installs C, resumes B, and proves only C's session and
subscriptions remain live." **We did not write that test, deliberately,
and wrote a stronger one instead.**
Under this fix that scenario is unreachable by construction. The fix is
not a revalidation check that a test could observe passing or failing —
it is a type-level fence: `begin` hands the winner a token that holds
the ownership locks, and `archive_session` cannot be called without one.
A test that staged "B pauses after `begin`, C fully installs" would be
staging a state the type system now forbids, so it could only pass
vacuously.
Two things close the blocker, and only one of them is a test:
1. *A superseded start cannot reach `archive_session` at all.* Enforced
by the compiler, not by a test. `ArchiveOwnership` is un-constructible
outside `archive::sync`. Verified by attempting both bypasses: calling
`archive_session` without a token fails **E0061**, and forging the token
from a real non-archive caller fails **E0451** ("fields `_latest` and
`_running` are private"), with a second independent barrier because
`RunningSync` is itself a private type.
2. *While an owner holds the token, no newer start can claim.* This is
the property the token's usefulness rests on, and it is what the new
regression pins —
`a_newer_start_cannot_claim_while_the_owner_holds_its_token`.
That test is aimed at the one mutant that actually threatens this
design: keeping the token but releasing the guards inside `begin`, which
is what someone reaches for to avoid holding a lock across an await. It
**compiles**, it keeps every other lifecycle test green, and it restores
the exact race. Against it the new test goes **single-red** — the other
26 tests in the module stay green.
Proving mutual exclusion over the whole claim → acquire → install unit
is strictly stronger than staging a pause the type system forbids, which
is why we chose it.
Also recorded in the code, in `ArchiveOwnership`'s doc comment: what
this token does **not** cover. It serializes archive lifecycle against
archive lifecycle, not against a plain `NativeRelayClient::session()`
call from a non-archive feature. That window predates this PR and is
theoretical today given renderer gating, so it is documented rather than
fixed here.
### Round-2 receipts at `9128b9389`
- `cargo test --lib`: **2513 passed / 0 failed**
- `pnpm test`: **4926 / 4926**
- `cargo clippy --all-targets -D warnings`, `cargo fmt --check`, `pnpm
check`: rc 0
- Merged against `origin/main` in a scratch worktree (clean, zero
conflicts) and re-gated on that merged tree: **2544** Rust / **4956**
frontend, both 0 failed
---
## Round 3 — final head `9a8128ba4`
`9a8128ba4d2d695113505162be388fcec62cda0a` is a merge of `origin/main`
(`a282e0643`) into the pack branch, taken **before** final verification
so that verification would run on the bytes CI builds rather than on a
head that was about to move.
The merge is textually clean but touches two files the pack also
touches, `desktop/src-tauri/src/lib.rs` and
`desktop/src/testing/e2eBridge.ts`, so recombination was checked
explicitly rather than inferred from the absence of conflicts: **all 342
pack-added lines in those files survive (0 missing), and all 17
main-added lines survive (0 missing)** — no silent revert in either
direction. Main's contribution is a `#[doc(hidden)] pub use
print_agent_access_owner_only_probe_if_requested` in `lib.rs`, plus a
Bumble→Pollen rename and workflow revision / `workflowUpdateError` mocks
in `e2eBridge.ts`.
### Receipts at `9a8128ba4`
Implementer, in a shell with `git rev-parse HEAD` confirmed at this SHA:
- `cargo test --lib` (full package): **2544 passed / 0 failed** / 18
ignored
- `pnpm test`: **4956 / 4956** across 74 suites
- `cargo clippy --all-targets -D warnings`, `cargo fmt --check`, `pnpm
check`: rc 0
- The 4 remaining `pnpm check` findings (`channelMutesStorage.test.mjs`,
`channelStarsStorage.test.mjs`, `terminal.css`,
`empty-edit-delete.spec.ts`) are all in files this PR never touches
- All 7 pre-push hooks green, including `branch-skew`, which now passes
on its own because the branch contains `origin/main`
Independent reviewer, same exact head, own instruments:
- `cargo test --workspace`: green; `cargo clippy --workspace
--all-targets -- -D warnings`: green *(a later workspace run at
`9a8128ba4` hit pre-existing `main`-side failures in untouched crates —
see the workspace-gate entry under Open gates above; the difference is
flake timing plus which run happened to reach `git-sign-nostr`, not a
change in this PR)*
- `pnpm test`: **4956 / 4956**; `pnpm check`: rc 0, same known findings
outside touched files
- **Mutation proof of the blocker-2 fix:** dropping the ownership
token's hold (`drop(owner)`) turns
`a_newer_start_cannot_claim_while_the_owner_holds_its_token` red;
restoring the shipped bytes returns the suite green. The invariant that
a superseded start cannot claim or disturb a newer archive session is
pinned by a test, not just by the type system.
- **Live-local at this exact head: attested and verified.** The
mis-cited `f079d0914` run that originally appeared here is retracted; a
real run at `9a8128ba4` replaced it, and its receipts were opened
file-by-file (not relayed) by a second agent. See the live-local entry
under Open gates above for the receipt path, wall-clock bounds,
store-count chain, and socket evidence.
- Verdict: minimalness **9/10**, elegance **9/10**, correctness
**9.5/10**. **Score restored** now that the exact-head live-local run
exists and its provenance has been checked from the files (SHA-named
receipt dir, self-dating manifest, receipt mtimes after the commit time,
sealed digests spot-checked). Gatekeeper re-bless at `9a8128ba4`:
**9/10, merge recommended.**
The paired CPU benchmark remains the one gate not run. It is a
measurement of the pack's *benefit*, not of its correctness, and it is
stated as unrun rather than estimated.
---------
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
|
||
|
|
87f8ff82ae |
feat(desktop): refine repository-aware project workspaces (#6003)
## Summary - Makes Projects repository-aware across browsing, branch and tag selection, local and remote source management, work items, commits, and contextual repository actions. - Refines project details into consistent single-column reading surfaces with a resizable, section-aware context pod, while keeping project conversations available in the attached chat panel. - Adds persistent sidebar project navigation, direct entity links, repository discussion channels, contributor identity matching, and consistent loading and activity presentation. - Keeps branch-specific controls on code-oriented sections while Tasks, Reviews, and Channels remain repository-scoped, reducing misleading context and actions. Replacement for #5981 with an identical final tree flattened into one signed-off commit because the required DCO check suite remained stalled. ### Related issue N/A ### Testing - [x] Desktop pre-push checks, TypeScript typecheck, and unit tests - [x] E2E production build with `pnpm build:e2e` - [x] Focused Playwright smoke coverage for project workspace, task, review, commit, sidebar, and contextual-panel behavior - [ ] Attach final before/after screenshots after design review --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> Signed-off-by: Wintermute <165f0c871dd2586bb18b6aa109eeaf57bb2132ff4d27b10120f4368a0f627022@buzz.block.builderlab.xyz> Co-authored-by: Wintermute <165f0c871dd2586bb18b6aa109eeaf57bb2132ff4d27b10120f4368a0f627022@buzz.block.builderlab.xyz> |
||
|
|
7e2651791d |
Add font size and conversation density preferences (#5644)
## Summary - Add separate **Font size** and **Conversation density** controls in Appearance. - Use a 13 / 14 / 15px conversation text ramp for Smaller / Default / Larger while scaling interface typography through a shared virtual-rem system. - Keep layout geometry fixed while typography changes, and compose Cmd +/- text zoom on top of the selected preference. - Let Compact, Comfy, and Spacious control conversation row spacing plus paragraph and list rhythm in Markdown content across Buzz. - Preview font size and density together with click-and-drag comparison; interrupted scrubbing restores the saved preference. - Synchronize preferences across open Buzz windows, including full local-storage clears during sign-out or dev reset. - Promote the segmented control to shared UI and make Settings rows adapt to their card width. ## Validation - `just ci` - 4,965+ desktop unit tests across the final merged branch - 1,465 mobile tests through the full repository gate - Focused Playwright coverage for font-size/density independence, drag preview cancellation, Inbox geometry, thread rendering, keyboard zoom, and cross-surface typography - Fresh E2E build against the final merged branch --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
e2ade93f02 |
fix(desktop): downscale large avatars for agent-share PNG body (#6260)
Sharing an agent encodes a `.agent.png` snapshot with the avatar as the PNG image body. Non-PNG avatars were transcoded at full resolution and already-PNG avatars were carried through unchanged, so a large source avatar could produce a PNG that exceeds the 10 MiB `MAX_SNAPSHOT_PNG_BYTES` cap and fail the send. A 2764×4096 webp avatar encoded to ~26 MB — over 2.5× the ceiling — which is why sharing that agent hung for a few seconds (fetching and transcoding the image) and then failed. The share dialog compounded the problem: on failure it discarded the pipeline's real error (`Snapshot exceeds the 10 MiB size limit…`) and toasted a generic `Couldn't send … Try again.`, hiding the actual cause. ## Changes - **Downscale the avatar for the snapshot PNG body.** The body is only a card thumbnail, so raster avatars are now downscaled to a 512px longest edge (`MAX_PNG_BODY_EDGE`) before PNG re-encoding, mirroring the frontend SVG rasterizer's 512×512 cap in `snapshotAvatarPng.ts`. Already-PNG avatars over the dimension cap route through the same downscaling transcode path instead of a straight tEXt-chunk injection. The manifest's `avatar_url`/`avatar_data_url` source reference is untouched — only the PNG image body is downscaled. - **Surface the real error in the share dialog.** `PersonaShareDialog` (used by `SnapshotShareDialog`/`TeamShareDialog`) and `AgentCardViewerDialog` now toast the send controller's actual error message, falling back to the generic string only when it is empty. A `getCurrentError()` accessor reads the error through a ref because the render-captured `state.error` is stale in the closure immediately after `beginSend` resolves. A unit test pins the invariant: an oversize (2764×4096) avatar produces a snapshot that stays far under the cap with body dimensions clamped to 512px. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
6ea7a2b221 |
fix(desktop): preserve early relay auth challenges (#3320)
## Summary - buffer native WebSocket frames until `plugin:websocket|connect` returns the socket ID and the NIP-42 auth waiter is installed - drain those frames in order before normal inbound handling begins - add a deterministic E2E seam and regression for an AUTH challenge delivered before connect resolves ## Root cause The native WebSocket task starts forwarding relay frames before the connect command returns its socket ID. Buzz relay sends the NIP-42 AUTH challenge immediately on connection, so Desktop could process that challenge while `wsId` and `authRequest` were still unset. The challenge was discarded, the cold connection failed authentication, and the first plain-text send failed. Channel history still appeared because it loads through the Tauri channel-window command rather than this frontend WebSocket. ## Verification - deterministic regression is red before the fix (`connecting` after 5s) and green after it - `pnpm test`: 3,721 passed - `pnpm build:e2e`: passed - focused early-AUTH E2E: passed - existing failed-initial-dial E2E: passed - `pnpm typecheck`: passed - Biome + file-size pre-commit checks: passed The complete 7-test relay-reconnect file passed once before the final line-count-only compaction. After that compaction, two full-file reruns each had the same unrelated startup-seam flake in the existing initial-dial test; both the new test and that existing test pass independently at the committed HEAD. --------- Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> |