mirror of
https://github.com/block/buzz.git
synced 2026-10-30 14:01:37 +00:00
bf18b8cd90db16c963ca4e5266d753d76aabfa1a
1682 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bf18b8cd90 |
fix(desktop): pad focus message bubble
Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
Signed-off-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
(cherry picked from commit
|
||
|
|
a71c38cbf0 |
fix(desktop): anchor activity message hover cues
Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
Signed-off-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
(cherry picked from commit
|
||
|
|
67ebd54748 |
Refine focus-mode message bubbles
Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
Signed-off-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
(cherry picked from commit
|
||
|
|
e297547c93 |
Align focus-mode chat bubble surfaces
Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
Signed-off-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
(cherry picked from commit
|
||
|
|
45712c1262 |
Render agent message sends as chat bubbles
Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
Signed-off-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
(cherry picked from commit
|
||
|
|
8cea5e15ef |
refactor(desktop): extract the channel idle auxiliary surface
Landing the cover drawer on current main pushed `ChannelPane.tsx` to 1003 lines, three over the 1000-line cap. The file was already over the cap on the cover drawer's original base (1010 lines, so shrinking was permitted), but main has since brought it down to 982, so it may not grow at all. Move the caller-owned idle auxiliary surface into its own component, the same split `ChannelAgentSessionSurface` already uses, which brings the pane back to 996. Behaviour-preserving: the panel and both of its presentations are byte-equivalent to the inline version, and the per-presentation React keys stay on `AnimatePresence`'s direct child rather than moving inside the new component, so the cover drawer and the split pane keep the distinct enter/exit identities main gave them. Co-authored-by: Bradley Axen <baxen@squareup.com> Signed-off-by: Bradley Axen <baxen@squareup.com> Signed-off-by: ss-dev-00 <a02c4e0850e5e612b4ddf95dbe2f5c56467cf27c6552203bc833ff438fb31971@buzz.block.builderlab.xyz> |
||
|
|
403de26214 |
feat(desktop): pin the conversation transcript variant to the cover drawer
The last wire of the feature, and what makes the four slices above one thing: the cover drawer now renders the conversation transcript, so opening agent activity in a channel lands in the reading view rather than the dense activity feed. Resolves the `TODO(#6538)` slice A landed with. The variant is chosen by presentation, not inferred from panel width. Width was only ever a proxy, and a breakable one — the split pane can be dragged wide and a narrow overlay can be tall. The presentation that already decided to cover is the thing that knows this is a reading surface, so it decides the variant too. Non-cover hosts return `undefined` rather than `"default"`: the panel already defaults, and naming it here would imply this function decides the non-cover variant when the profile panel picks `compactPreview` at its own call site. Asserted end-to-end on the transcript's `data-transcript-variant` marker rather than on the prop, so the test proves the value survives the whole path from the resolver through `ChannelAgentSessionSurface` and the panel to the transcript. It lives in the screenshot spec because the marker only renders once the transcript has content, and that is the spec that seeds a turn. Verified discriminating: reverting the pinned value alone fails the assertion. Co-authored-by: Bradley Axen <baxen@squareup.com> Signed-off-by: Bradley Axen <baxen@squareup.com> Signed-off-by: ss-dev-00 <a02c4e0850e5e612b4ddf95dbe2f5c56467cf27c6552203bc833ff438fb31971@buzz.block.builderlab.xyz> |
||
|
|
90f526be62 |
feat(desktop): fold focus-mode agent work into one transcript block
Slice C. Focus mode gave every thought and tool step its own row and its own
disclosure, so a turn that did real work scrolled its answer off screen behind
a stack of chrome. This adopts berd's shipping transcript model: within a turn,
everything between the prompt and the answer that is thinking, a tool step, or
an interim agent note becomes one "work block" — a thin rail of small glyph
bullets that folds to a single "N steps" line when the work is done.
Grouping is variant-aware **at the list boundary** rather than inside the
grouping module: `TranscriptDisplayBlockView` runs the additive work-block
transform only for `conversation`, so `default` and `compactPreview` walk the
same segments on the identical code path they always did. That makes slice B's
byte-for-byte baseline fixture hold by construction rather than by assertion —
no future edit to the block can regress the other two variants without first
moving that branch.
Behaviour, mirroring berd:
- Live: the block is open and the rail IS the status, so there is no header
line restating it. The last three steps show in arrival order; older ones go
behind an "N previous steps" disclosure, so a long run cannot push the answer
out of view.
- Finished: folds to "N steps" with a chevron that rotates on open.
- The fold animates. `<details>` cannot — its content is either laid out or
not, with no intermediate height — so a block that was live when it mounted
renders open for a paint and then settles closed, giving the height tween a
start state that was actually painted. A block already finished on mount
(scrollback) never had a rail on screen and closes immediately.
- Reader choice wins: once the reader toggles a block, policy stops opening and
closing it.
One deliberate departure from berd: a finished block holding a failure folds to
"N steps · 1 failed". A bare count is the one thing a reader cannot distinguish
a clean run from a broken one by, and a fold that hides a failure behind a
neutral number invites them not to open it. The rail bullet itself stays muted —
failure is a glyph shape, not a colour, so one bad step does not read as an
alarm across the run.
Items are projected once into rail entries, and the glyph, the body, and the
folded line's counts all read that projection; two independent classifications
of the same item is how a headline and chain eligibility drifted apart on an
earlier attempt. The projection is closed in the type system rather than by
convention, so a future `TranscriptItem` variant cannot silently wear a wrench
and impossible pairs are not representable.
Four defects fixed on top, each mutation-checked in isolation:
- **A reader's open block was folded shut by the agent posting again.** Work
blocks are derived and ided after their first step, and only the LAST
assistant message is exempt from the block — so a second answer demotes the
first, the runs on either side merge, the old block unmounts, and the
`useState` holding the reader's expansion goes with it. The choice now lives
above the block, keyed by the STEP ids it was taken on, since the steps
survive regrouping. An open choice beats a folded one (hiding steps a reader
asked for loses what they were reading; showing extra costs a scroll), and
the choice is recorded against every step in the block — recording only the
first leaves the absorbed block's stale `open` entry behind and, since open
wins, the merged block could never be folded again.
- **A finished block went live again between turns.** `lastTurnId` walked the
display blocks, but a turn that has only emitted setup lifecycle rows
classifies to zero segments and produces no block — so for the whole gap
between `turn_started` and the next turn's first renderable item, "newest
turn with a block" was the turn that had already ended, and its trailing item
became the streaming item: a settled 6-step block re-opened, dropped to its
last three steps, then folded back. Every turn, not an edge case. Liveness
now comes from the newest turn id in the item stream, and a trailing item is
only reported as streaming when the live turn owns it.
- **The running step's pulse ignored reduced motion.** `WorkBlockRailGlyph`
used bare `animate-pulse`, which no `prefers-reduced-motion` block in the app
matches — all 20 are scoped to `buzz-*`/`motion-*`/`t-skel-*`. Now
`motion-safe:animate-pulse`. That swap also repaired three assertions it
would otherwise have disarmed: they matched `.animate-pulse` as a substring
and, worse, all three ran on a folded rail with no rows, so "nothing pulses"
held against a build where abandoned steps pulse forever.
- **A work block whose running step had no live session never settled.**
Two test-hygiene fixes worth keeping: a gap test was pinning a sequence whose
`session/new` card moved `streamingItemId` on its own, so those frames passed
against the pre-fix code and proved nothing (the plain no-restart path now
leads); and the orphaned suite inherited React Query's default 300000ms
`gcTime`, arming a five-minute timer at teardown that node:test waited out —
`gcTime: 0` takes the full desktop suite from 307s to 102s, verified against a
negative control rather than on the passing tree alone.
Squashed from ss-dev-02/tool-chain-cards (#6536) at
|
||
|
|
29412d6ad6 |
style(desktop): bring the conversation variant closer to berd's recipes
A design-delta note measured the conversation variant against berd's shipping
chat surface and named three divergences that made the focus view read as "not
berd" regardless of how the rest was arranged. This takes all three literally,
on Buzz tokens rather than berd's.
**Agent identity row.** The largest gap: agent prose carried no attribution at
all, so a reply read as unowned body text in a full-cover view. berd labels
every agent turn with a 20px round avatar plus the name at `text-xs`. Added for
the `conversation` variant only — a test asserts the row does not leak into the
other variants, so a failure names the cause instead of printing a markup diff.
**Prompt bubble.** berd's user turn is a soft fill with no border, `px-4 py-2`,
12px radius. berd's `rounded-sm` is 12px on its own scale, not Tailwind's stock
2px, so Buzz's `rounded-xl` is the exact equivalent. The cap moves from
`max-w-[85%]` to a fixed 640px measure mirroring `--chat-user-message-max-width`:
a percentage cap re-wraps the prompt on every resize of the cover, a fixed
measure holds one stable line length.
**Fenced code.** The language moves into a real header row above the frame with
the copy action opposite it, framed at Buzz's `rounded-lg` (`--radius: 0.625rem`,
exactly berd's value).
The markdown renderer is shared with channel messages, so the code recipe is
opt-in through `CodeBlockVariantContext` read at render time. A prop would have
to thread through `createMarkdownComponents`, whose component map must stay
module-stable and whose parsed-node cache keys on a variant string — the same
reason `VideoReviewMarkdownContext` already exists. A context provider renders
no DOM, so `default`/`compactPreview` markup stays byte-identical.
Three defects fixed on top of the restyle, each found by review or a bug pass:
- The code-recipe provider was mounted inside `MessageActivity`, which only
handles assistant items — `UserMessageBubble` returns before it, so a fence
inside a human prompt kept the legacy 16px muted frame nested inside the new
12px bubble. The recipe is a property of the *surface*, not of a role, so the
provider moves up to `AgentSessionTranscriptList`.
- The identity row read the raw `agentAvatarUrl`/`agentName` props, and the
primary channel-opened flow passes `agentAvatarUrl: null` because
`ChannelAgentSessionAgent` carries no avatar field — so every channel-opened
focus session showed initials in the identity row while the header directly
above it showed the real profile avatar. Now resolved profile-first out of the
`profiles` lookup the panel already hands down, with the props kept as
fallback: a locally managed agent can hold an avatar its relay profile never
published.
- `UserAvatar` names itself (`<img alt="${displayName} avatar">` or its
fallback initials) and the identity row puts the same name in visible text
right after it, so assistive tech read the identity twice for every agent
turn. The avatar is wrapped `aria-hidden`, verified in Chromium's real
accessibility tree over CDP (`ignoredReasons: ["ariaHiddenSubtree"]`), not
from DOM attributes. Marked at this call site rather than teaching the shared
`UserAvatar` a decorative mode, which would affect all 45 of its call sites.
The conversation test file had reached 1253 lines, past the 1000-line ceiling.
It slipped the `file-size-check` gate because that script only scans
`.ts`/`.tsx` under `src/features` — the rule in AGENTS.md plainly covers it.
Split into a shared non-test harness module (following the existing
`observedUnreadTestHarness.mjs` precedent, because duplicating the
order-sensitive TZ/locale pins into two files is exactly how they drift) plus
two test files, bodies moved verbatim.
AGENTS.md gains the `assert.ok(x === null)` rule earned here: the
`assert.equal(el, null)` form serializes the matched element and its entire
subtree to build a diff when it fails, which on a real transcript exhausts
memory and kills the runner with SIGKILL after ~100s — a genuine regression
becomes unreadable and looks like an OOM in unrelated code.
Squashed from ss-dev-01/berd-restyle (#6720) at
|
||
|
|
cd2037e051 |
feat(desktop): add conversation transcript variant for focus-mode agent sessions
Slice B. A third, purely additive `conversation` transcript variant for the
full-cover reading surface: the human's prompt as a right-aligned bubble, the
agent's reply as unboxed prose at a comfortable measure, reasoning behind a
"Thinking… / Thought for Ns" disclosure, the plan as a live checklist, and
session/status noise receded to quiet dividers.
The transcript previously had two presentations — the dense `default` activity
feed and `compactPreview` — both tuned for a 380px panel. Read at full width
they are a scan surface, not something you read a turn in.
Threaded through `ManagedAgentSessionPanel` and `AgentSessionThreadPanel` as
an optional prop. Tool items deliberately route through the same
`ToolActivity` presenter as `default`, which is what lets the tool-chain card
work in slice C land on top without conflict.
The byte-for-byte guarantee for the existing two variants is enforced by a
captured-markup fixture rather than by inspection: the baseline was produced
by mounting the same transcript on unmodified code, so the test fails on any
drift in `default` or `compactPreview` output.
Three defects found and fixed while building it, each with a
non-vacuous test:
- The author row resolved its name through `resolveUserLabel`'s
`fallbackName: item.title`, whose value describes the *trigger* that started
the turn ("@Mention", "Prompt", a title-cased Buzz event kind) and is never
an identity. Harmless where it only seeded avatar initials; the conversation
author row promotes it to displayed text, so an unresolved sender rendered
as if "@Mention" were their name. Identity now stops at the profile —
display name, NIP-05 handle, truncated pubkey — and the title stays as
trigger chrome in the footer. The existing test was asserting the bug.
- The captured-markup fixture embeds formatted timestamps, so it was
zone-sensitive; pinning `TZ` then exposed that it is locale-sensitive too.
Assigning `process.env.LANG` at runtime does not fix it — node resolves its
default locale once at startup, unlike `TZ`, which `Date` reads lazily. Both
formatting surfaces the render path reaches are overridden, including
`Date.prototype.toLocale*`, which does not route through
`Intl.DateTimeFormat`. `Intl.DateTimeFormat` must be replaced with a plain
function, not an arrow: the render path calls it with `new`.
Squashed from ss-dev-01/conversation-variant (#6538) at
|
||
|
|
6c9b0e2fe7 |
feat(desktop): cover the channel with the agent activity drawer
Slice A of the focus-mode agent conversation. Agent activity gets the same
full-cover presentation threads already had in focus mode, because its
transcript is tool calls, diffs, and command output — content a 380px side
pane cannot show usefully. It deliberately offers no split/focus toggle: at
any viewport wide enough for two panes it always covers.
The drawer surface is extracted from `FocusThreadDrawer` into a
presentation-only `CoverDrawer` that knows nothing about what it covers, so
the thread drawer and the activity drawer are one surface with two contents.
Motion, scrim, Escape ownership, and focus capture/restore all live there.
"Which panel" and "which presentation" now resolve together in
`channelAuxiliarySurface`, which is what makes the two cover drawers mutually
exclusive: there is one covered slot and the resolved surface owns it.
Two ordering hazards the surface has to handle, both covered by tests:
- Escape during a replacement. `AnimatePresence` keeps the outgoing drawer
mounted through its exit, capture-phase listeners fire in registration
order, and the outgoing one registered first — so it would swallow the press
via `stopImmediatePropagation` and the user would have to press Escape
twice. The drawer stands down for its whole exit, gated on presence rather
than the focus slot: a successor whose content takes focus never claims the
slot, which would leave the outgoing claim current for exactly the drawer
that should stand down.
- `StrictMode` replaying the focus-capture effect. By the replayed setup the
drawer has already focused itself, so an unconditional
`document.activeElement` read records the drawer as its own opener and a
real close focuses a detached node, stranding the keyboard on `<body>`. A
one-shot ref captures the opener exactly once.
Reconciled against main, where the idle auxiliary panel gained its own
full-cover presentation (#6590):
- `FocusThreadDrawer` keeps main's `label` prop and passes it through as
`CoverDrawer`'s `ariaLabel`; the idle panel reuses the presentation with its
own name, which is why the label is a prop and not the thread's constant.
- `outline-hidden` carries onto the extracted surface with the reason it
exists recorded — the drawer is `tabIndex={-1}` and focuses itself, so the
ring is noise.
- `useFocusIdleDrawer` sits outside `auxiliarySurface` on purpose: the idle
panel is caller-owned, is not in the last-opened-wins set, and
`idleAuxiliaryOverridesThread` lets it render ahead of a thread. It still
cannot contend for the covered slot, because `shouldUseFocusIdleDrawer`
requires every other candidate to be absent.
- main's `mode="wait"` on the presence boundary is kept. It closes the window
where two drawers are mounted together, but the drawer's presence and
focus-slot guards stay: they protect the surface, not this host's choice of
mode.
Squashed from ss-dev-00/agent-activity-cover (#6542) at
|
||
|
|
ceb8ba605f |
fix(desktop): accent-colored mention badges that count thread mentions (#6900)
**Category:** fix **User Impact:** Sidebar mention numerals now match the user's theme/accent color and appear for mentions inside threads, not just top-level channel messages. Hover-to-preview on channel rows is unchanged. **Problem:** Follow-ups to #6696: 1. The mention numeral used a fixed red `--notification` token that ignored the user's theme and accent selection. 2. The numeral read `appBadgeCount`, which deliberately excludes threaded replies (Home's badge subtotal counts those toward the Dock badge). Mentions inside threads therefore never surfaced a numeral on the channel row — only the generic thread-activity dot. **Solution:** 1. The badge now reuses the primary accent pair (`bg-primary` / `text-primary-foreground`), the same treatment as the DM count badge, so it follows whatever accent the user applies — including the pinned neutral accent of the Buzz themes. The now-unused `--notification` tokens and Tailwind utilities are removed. 2. Non-DM rows now count every unread mention/broadcast — threaded or top-level — via a new `highPriorityCount` channel projection, replacing the boolean `highPriorityUnread` across the TS contract, the Tauri SQLite store, the e2e mock bridge, and the native test rig. The Dock/app badge projection (`appBadgeCount`) is unchanged, so Dock totals do not double-count thread activity that Home already counts. The hover-to-preview channel activity popover is untouched; e2e coverage now pins the combination (thread mention → numeral, no dot, popover still opens and lists the mentioning reply). <details> <summary>File changes</summary> **desktop/src/features/channels/useUnreadChannels.ts** Non-DM sidebar counts switch from `appBadgeCount` to the mention/broadcast-inclusive `highPriorityCount`; the high-priority channel set derives from the same count. **desktop/src/features/sidebar/ui/SidebarSection.tsx** Badge drops the fixed notification classes and inherits the accent-following default. **desktop/src/shared/styles/globals/theme.css / desktop/tailwind.config.js** Remove the unused fixed `--notification` tokens and semantic utilities. **desktop/src/shared/api/tauriObservedUnread.ts / desktop/src-tauri/src/observed_unread.rs** Projection contract: `highPriorityUnread: bool` → `highPriorityCount: u64`, aggregated per unread high-priority event. **desktop/src/testing/e2eBridge.ts / desktop/src/features/channels/observedUnreadNativeRig.mjs** Mock/native-rig projections mirror the new count field. **desktop/src/features/channels/observedUnreadNative.test.mjs** Native badge-lane test seeds a high-priority event to match the numeral's new semantics. **desktop/tests/e2e/badge.spec.ts** Top-level mention test now asserts the badge equals the applied accent (seeded `#22c55e` under an adversarial theme). New test: in-thread mention shows the numeral, no dot, and the hover preview popover still lists the mentioning reply. **desktop/tests/e2e/channel-activity-popover.spec.ts / desktop/tests/e2e/thread-unread.spec.ts** Seeded fixtures that mention the user now expect the numeral (which subsumes the dot). </details> ## Reproduction steps 1. Pick any theme + accent color in Settings → Appearance. 2. Receive a top-level message mentioning you in an inactive channel → the row shows a numeral pill in your accent color. 3. Receive a reply **inside a thread** that mentions you in an inactive channel → the row shows the numeral (previously only a dot). 4. Hover the row → the channel activity preview still opens and lists the mentioning reply. 5. Switch accents/themes → the pill re-colors accordingly; the Dock badge count is unchanged from before. ## Verification - `tsc --noEmit`, Biome, `check:px-text` — clean - Desktop unit tests: 179/179 channel-suite; full suite has one pre-existing failure from #6590 (`useRetainedProjectGitViews.test.mjs` loader error, fails on clean `main`) - Tauri Rust: `observed_unread` tests + clippy + fmt — clean - Playwright smoke: badge (19), channel-activity-popover (10), thread-unread (13), plus channels/inbox/stream/messaging/mentions suites — all green ## Screenshots Screenshots are posted in the PR discussion using immutable repository-hosted image URLs. Signed-off-by: tulsi <tulsi@block.xyz> |
||
|
|
eb8e97cfe7 |
fix(desktop): show edited head content in thread panel (#6887)
## Why Fixes #6886. In the channel timeline an edited message shows its newest body, but opening its thread panel showed the stale pre-edit body until the async thread-aux backfill landed (and permanently if it raced or missed). ## Root cause `desktop/src/features/messages/lib/independentThreadPanel.ts` built `events = [head, ...replyEvents]`, discarding the head's aux overlay events (edits `kind:40003`, deletions, reactions) already present in the channel window. `formatTimelineMessages` only applies an edit when the edit event sits in the same array as its target, so the thread head relied solely on `withThreadAux` to re-supply it. ## What - Carry the head's non-content aux events (via `!isTimelineContentEvent`) from the channel window into the thread head's event array, deduped against `replyEvents` by id. - Thread head is now byte-identical to the main timeline the instant the thread opens, independent of the async aux fetch. - Restricted to non-content kinds so reply content rows never leak in. ## Tests - Unit regression `independentThreadPanel.test.mjs` (6 cases): red pre-fix (`"two PRs"`), green post-fix (`"these PRs"`). - e2e `thread-head-stale-edit.spec.ts`: delays thread-replies aux to force the channel-window-vs-thread-aux divergence, then opens the thread via the reply button and asserts the panel head shows the edited body. Verified red pre-fix (stale `message-thread-panel` head), green post-fix. - Full desktop unit suite: **5562 pass / 0 fail**. `biome` + `tsc --noEmit` clean. Origin: Buzz channel `fix-reply-thread-old-view-bug` (#464b982f-fc05-42c8-918f-409e1f05a1df). --------- Signed-off-by: Leo <5faf251baee50ee6bcde338aef6acdd70bb3e60115664c2cd490d94a55dfc488@buzz.block.builderlab.xyz> Co-authored-by: Leo <5faf251baee50ee6bcde338aef6acdd70bb3e60115664c2cd490d94a55dfc488@buzz.block.builderlab.xyz> |
||
|
|
03bfc837cb |
fix(desktop-tooltip): increase surface contrast (#6897)
**Category:** fix **User Impact:** Tooltips now use a higher-contrast surface, making their labels easier to read across desktop themes. **Problem:** Tooltip surfaces could blend into hovered content, making labels difficult to distinguish. **Solution:** Use the existing popover surface and foreground tokens for clearer visual separation and theme-aware contrast. <details> <summary>File changes</summary> **desktop/src/shared/ui/tooltip.tsx** Updates shared tooltip colors to use higher-contrast semantic surface and foreground tokens. </details> ## Reproduction steps 1. Run the desktop app and hover a control that displays a tooltip. 2. Compare the tooltip against the content beneath it in both light and dark themes. 3. Confirm the tooltip surface remains clearly distinguishable and its label is readable. ## Screenshots | Before | After | | --- | --- | |  |  | ## Validation - `pnpm build:e2e` - `pnpm exec playwright test tests/e2e/tooltip-semantics.spec.ts --project=smoke` — 4 passed - `pnpm exec biome check src/shared/ui/tooltip.tsx tests/e2e/tooltip-semantics.spec.ts` - Pre-push desktop checks, typecheck, and unit tests - `git diff --check` - Full smoke project attempted; 345 of 1,222 tests passed without failure before the 10-minute local execution limit. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
01c87a350b |
Apply access policy when reusing channel agents (#6838)
Reused managed identities now apply their intended inbound-author policy before attachment or startup. An explicit policy wins, then the linked persona policy, then `owner-only`. This covers both channel-agent provisioning and the direct composer autocomplete path. If the policy update fails, attachment, startup, and message send stop. The production Playwright regression starts with a persona-linked agent set to `anyone` with a stale allowlist, then verifies the update happens before membership and startup and that the stored policy is `owner-only`. A small resolver test covers explicit, persona, and default precedence. This closes the stale-policy entry described by [Project Loupe issue #497](https://github.com/project-loupe/audit-buzz/issues/497). The separate Welcome Team reuse path is outside this PR. Checked with the 5,502-test desktop suite, `pnpm check`, `pnpm typecheck`, `pnpm check:file-sizes`, and the focused Playwright composer regression. --------- Signed-off-by: Jordan Mecom <jm@squareup.com> |
||
|
|
4f554b6385 |
feat(sidebar): prioritize unread DMs in overflow navigation (#6842)
**Category:** improvement **User Impact:** Users can see which unread direct messages are waiting offscreen and jump to the most relevant one first. **Problem:** The sidebar overflow control treated every offscreen unread conversation alike, so an unread DM could be hidden behind channel activity with no visible clue about who was waiting. **Solution:** Prioritize the first offscreen unread DM, preview up to three senders with avatars, and keep the arrow, avatars, separator, and message count intact while only the trailing phrase truncates in narrow sidebars. <details> <summary>File changes</summary> **desktop/src/features/sidebar/lib/useUnreadOverflow.ts** Adds ordered DM preview data to unread-overflow calculations while preserving the sidebar's existing visibility and direction rules. **desktop/src/features/sidebar/lib/useUnreadOverflow.test.mjs** Covers DM prioritization, ordering, offscreen filtering, and channel-only fallback behavior. **desktop/src/features/sidebar/ui/AppSidebar.tsx** Passes unread DM previews into the lower overflow control and navigates to the preferred DM before the next unread channel. **desktop/src/features/sidebar/ui/MoreUnreadButton.tsx** Renders the frontmost-first avatar stack, accessible DM target label, middot separator, and message-count treatment. **desktop/src/features/sidebar/ui/MoreUnreadButton.test.mjs** Locks the three-avatar cap, stacking order, preferred target, fallback, accessible copy, and truncation structure. **desktop/src/shared/ui/UnreadPill.tsx** Contains the primary pill and allows only its trailing label to ellipsize under width pressure. **desktop/src/shared/ui/UserAvatar.tsx** Allows the sidebar preview stack to identify each rendered avatar in regression coverage. </details> ## Reproduction steps 1. Open enough sidebar conversations that unread items fall below the visible area. 2. Leave one or more offscreen DMs unread, along with unread channels. 3. Confirm the bottom overflow pill shows up to three overlapping DM avatars followed by a middot and the total new-message count. 4. Select the pill and confirm it navigates to the first offscreen unread DM rather than the nearest unread channel. 5. Resize the sidebar from 420px through 300px to 220px and confirm the arrow, avatars, middot, and full numeric count remain visible; only `new messages` may ellipsize. 6. Repeat with channel-only unread overflow and confirm the control keeps its channel fallback behavior without avatars. ## Screenshots Built-UI matrix with 100px of visible space beyond the sidebar edge. All images are hosted through `pr-screenshots`. | State | 220px min | 300px default | 420px max | Result | | --- | --- | --- | --- | --- | | Channel-only · 13 |  |  |  | Full label throughout. | | Channel-only · 999 |  |  |  | Full label throughout. | | One DM only · 1 |  |  |  | Full label throughout. | | One DM + channels · 99 |  |  |  | Full label throughout. | | Two DMs only · 2 |  |  |  | Full label throughout. | | Two DMs + channels · 47 |  |  |  | Full label throughout. | | Three DMs + channels · 128 |  |  |  | 220px: `128 new messa…`; full at 300/420. | | Three DMs + channels · 999 |  |  |  | 220px: `999 new mess…`; full at 300/420. | | Three DMs + channels · 9,999 |  |  |  | 220px: `9,999 new mes…`; full at 300/420. | **Matrix result:** 27/27 states remain contained. Exactly 3/27 states ellipsize—all are three-avatar cases at 220px. The arrow, all avatars, middot, and complete numeric value remain visible; only the trailing `new messages` phrase yields. ## Validation - Desktop unit suite: 5,517 passed - TypeScript: passed - Biome on all seven changed files: passed - Pre-push desktop checks, typecheck, tests, file-size, and branch-skew gates: passed - 27-state built-UI matrix capture: 27/27 contained; only the three 220px three-avatar states truncate, and only the trailing phrase - `git diff --check`: passed --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@users.noreply.github.com> Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> |
||
|
|
de188ebf06 |
feat(projects): add agent and CLI project-home support (#6590)
## Summary - inject bounded project-home identity and repository context into managed agent sessions - add project-aware CLI flows for creating projects, repositories, issues, and related channels - match project homes through the existing relay query surface, then filter channel metadata client-side This is Part 1 of the channel-first Projects stack. Part 2 contains project creation and model foundations. ## Testing - `cargo fmt --all -- --check` - `cargo clippy -p buzz-cli -p buzz-acp --all-targets -- -D warnings` - `cargo test -p buzz-cli -p buzz-acp` — 1,184 tests passed, 1 doc test ignored - full pre-push gate passed ## Post-Deploy Monitoring & Validation - validate project-home agent context and project-aware CLI commands against a staging relay - healthy signals: project context matches the active channel, explicit repo coordinates remain stable, and normal channels receive no project block - failure signals: cross-channel project context, duplicate project creation, or commands targeting an unrelated repository; mitigate by reverting this PR --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> |
||
|
|
22cdda441d |
feat(desktop): restore message quick reactions (#6892)
## Summary - restore three learned quick-reaction shortcuts to the desktop message action rail - keep Add reaction (`SmilePlus`, `h-4 w-4`) immediately after shortcuts and left of the restored divider - preserve Reply, Copy link, More, custom emoji rendering, accessibility labels, and responsive behavior - hide shortcuts and the divider below `sm`, while keeping Add reaction available ## Tests At `24c4732c2341b846dccf4ae8fdc2455f700fc44d`: - `pnpm exec biome check src/features/messages/ui/MessageActionBar.tsx tests/e2e/message-copy-link.spec.ts tests/e2e/custom-emoji.spec.ts` - `pnpm exec tsc --noEmit` - `pnpm test` — 5,567 passed - `pnpm build:e2e` - `pnpm exec playwright test tests/e2e/message-copy-link.spec.ts tests/e2e/custom-emoji.spec.ts --project=smoke` — 19 passed - `pnpm exec playwright test tests/e2e/reaction-order.spec.ts --project=smoke` — 2 passed - pre-push: file-size-check, desktop-check, desktop-typecheck, desktop-test Vogue design review: **SHIP**. ## Screenshot Restored desktop message action rail: three learned quick reactions, Add reaction (`SmilePlus`), divider, Reply, Copy link, and More.  Signed-off-by: Rivet <a08d9a8418c7ff03afe19964724c8fd87bf1776ab9e9b9cafb8cc920edd02a6e@buzz.block.builderlab.xyz> Co-authored-by: Rivet <a08d9a8418c7ff03afe19964724c8fd87bf1776ab9e9b9cafb8cc920edd02a6e@buzz.block.builderlab.xyz> |
||
|
|
f177f4909d |
Use paired tags for standing & per-turn context (#6701)
## Why PR #6503 bundled a broader context compiler with several prompt-shape and telemetry changes, making it difficult to isolate whether paired XML-like top-level boundaries improve agent behavior. This keeps the follow-up experiment focused on framing only. ## What - Replace top-level standing and per-turn bracket headers with paired tags while preserving inner section content and event/message framing - Carry only metadata already present in dynamic headers as attributes, such as thread counts, truncation, event type, and batch count - Preserve every section body verbatim so the model executes the exact bytes reviewed in Desktop - Keep routing metadata, event content, batching, and delivery behavior unchanged - Teach Desktop diagnostics to parse the new framing while retaining compatibility with archived bracket-framed sessions; ambiguous paired boundaries render as the complete captured prompt ## Risk Assessment Medium — the change is limited to agent prompt framing and diagnostic parsing, but it affects both standing context and every dynamic agent turn. Bodies and delivery behavior remain unchanged, and compatibility parsing remains for prior sessions. ## References - Follow-up to #6503, scoped only to the paired-tag framing hypothesis ## Update — 2026-08-24 - Addressed review feedback by removing entity encoding/decoding from the execution and diagnostic paths. - Added regressions for literal closing tags, placeholders, entity-looking text, ampersands, and authored boundary whitespace. - Verified `cargo test -p buzz-acp` (805 unit + 9 integration), Desktop tests (5,412 before the main sync; 5,439 in the pre-push gate), Desktop typechecking, and Tauri clippy/tests. - Live local relay startup and message ingestion passed. The live ACP reply step is blocked locally because installed Goose 1.0.23 no longer provides the documented `goose acp` command and no alternate ACP adapter is installed. --- **Update Aug 25, 12:13 EDT:** Extended paired tags to per-turn top-level sections. - Converted context, conversation/thread context, event batches, and steer/interrupt boundaries without changing their inner bodies. - Preserved existing header metadata as attributes only; added no new prompt metadata. - Verified 806 Buzz ACP unit tests plus 9 integration tests, 5,441 Desktop tests, Desktop typechecking and formatting, and all differential pre-push lanes. --- **Update Aug 25, 13:58 EDT:** Addressed framing review feedback. - Updated operational base instructions to reference the emitted `<context>` section and added a regression guard against stale `[Context]` instructions. - Restored batched steer/interrupt cardinality in Desktop diagnostic titles with focused coverage for both paths. - Verified 807 Buzz ACP unit tests plus 9 integration tests, 5,442 Desktop tests, Desktop typechecking and formatting, and every differential pre-push lane including Tauri clippy/tests. --- **Update Aug 25, 15:37 EDT:** Hardened legacy transcript compatibility. - Semantic standing-context parsing now requires paired framing at the start and complete input consumption. - Historical bracket-framed personas keep literal balanced tag examples in their original section; malformed or mixed current framing displays as the complete captured prompt. - Added regressions for the reported legacy persona case and trailing unframed text; verified 60 focused parser tests, 5,444 full Desktop tests, formatting, and typechecking. --- **Update Aug 25, 16:31 EDT:** Preserved slash-command prompt block boundaries. - Desktop now sets aside a valid connector-facing slash-command block only when the following block begins recognized paired framing, parses the semantic blocks normally, and retains the command as visible Prompt metadata. - Added an end-to-end transcript regression using the production three-block ACP shape, including the user bubble, author pubkey, event ID, and grouped prompt context. - Verified 113 combined transcript tests, 5,445 full Desktop tests, formatting, typechecking, and all applicable pre-push lanes. Generated with Codex --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com> |
||
|
|
583af02299 |
fix(cli): preserve signatures in event reads (#6884)
## Summary
- preserve string Nostr `sig` values in normal `messages get`, `messages
thread`, `messages search`, and `feed get` output
- omit absent or non-string signatures instead of fabricating empty
values
- keep compact output at exactly `{id, content, created_at}` and update
CLI/managed-agent contract docs
## Validation
- `cargo test -p buzz-cli --lib` (367 passed at final head
`a2498faa0e07f90efc0ab0165606ba3b048327c7`)
- release candidate live-relay matrix: 49/49 normal events independently
recomputed to their NIP-01 IDs and passed BIP-340 verification; all four
compact commands preserved exact three-key output and ordering
- `git diff origin/main...HEAD --check`
- independent adversarial review: clear, no findings
## Compatibility
The normal JSON change is additive for valid relay events. In-repo
consumers use tolerant field lookup. Compact output is unchanged and
locked by regressions for both message and feed formatting.
Generated by Carl, an automated contributor working with Wes.
---------
Signed-off-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@buzz.block.builderlab.xyz>
Co-authored-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@buzz.block.builderlab.xyz>
|
||
|
|
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> |
||
|
|
e8172b5bac |
feat(desktop): hyperlink selected composer text on link paste (#6684)
## Summary Pasting a link while composer text is selected now turns that text into a hyperlink instead of replacing it — the behavior people expect from every other editor. - `resolveExactLinkPaste` recognizes a clipboard payload that is exactly one supported link (bare or `<…>`-wrapped `http(s)`, or a Buzz deep link, canonicalized through the existing composer-link attribute resolver) and returns the href to apply. - The paste handler applies that href as a `link` mark over the current selection, collapses the caret to the end of the newly linked range, clears stored marks so subsequent typing isn't linked, and refocuses the editor. Edge cases handled: - **No link mark available / selection can't carry marks** — falls through to the existing replace-with-node path rather than silently dropping the paste. - **Selection contains a composer message-link chip** — keeps the old replacement behavior instead of nesting a link over a chip. - **Whitespace in the clipboard payload** — `unwrapExactHttpLink` now rejects any text containing whitespace, so multi-line or "text plus URL" pastes are not treated as an exact link. - **Empty selection (caret paste)** — unchanged. - **Idempotent re-paste** of an already-wrapped link is consumed rather than doubling up. ## Testing `desktop/src/features/messages/lib/composerMessageLinkNode.test.mjs` gains coverage for exact-link resolution (per-shape accept/reject cases, Buzz canonicalization) and for the paste handler: linking selected text, canonicalizing Buzz links over a selection, idempotent wrapped-link paste, fall-through when the selection can't carry marks, all-selection collapse, chip-containing selections, and preserved caret-paste behavior. Full pre-push gate ran green on this branch: `file-size-check`, `desktop-check`, `desktop-typecheck`, and `desktop-test` (5425 tests, 0 failures). ## Demo Pasting an `https://` URL over selected composer text turns the selected words into a hyperlink (instead of replacing them); text typed immediately after is not linked: https://github.com/user-attachments/assets/bbc93574-dd9d-4b1b-bad0-53307b6e887c --------- Signed-off-by: Matt Toohey <contact@matttoohey.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> |
||
|
|
52621c09be |
chore(release): release Buzz Desktop version 0.5.20 (#6839)
## Buzz Desktop release v0.5.20 - **Frozen main:** `8471049c430073474939336dfc6aa98272bc8762` - **Reviewed candidate:** `95154bee4034ca7a40b33095c2ddbde8c9aa1614` - **Previous desktop release:** `desktop-v0.5.19` - **Proposed immutable tag:** `desktop-v0.5.20` This PR may be **squash merged** after the Desktop Release Candidate check and all protected-branch checks pass. Merging authorizes publication of the exact reviewed candidate; later or unrelated changes on `main` cannot alter it. The checked-in changelog accounts for every non-merge commit in the release range. The Desktop tag points to the reviewed candidate commit, not the later squash commit. Publication remains bound to that immutable candidate tag. Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |
||
|
|
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> |
||
|
|
7a1b7d8e09 |
chore(release): release Buzz Desktop version 0.5.19 (#6828)
## Buzz Desktop release v0.5.19 - **Frozen main:** `ee6ca5fa28bce04dfecb6717de65b08a57f2ac47` - **Reviewed candidate:** `ab691bcdaeccaa6698a2199beaba3f6e93daae81` - **Previous desktop release:** `desktop-v0.5.18` - **Proposed immutable tag:** `desktop-v0.5.19` This PR may be **squash merged** after the Desktop Release Candidate check and all protected-branch checks pass. Merging authorizes publication of the exact reviewed candidate; later or unrelated changes on `main` cannot alter it. The checked-in changelog accounts for every non-merge commit in the release range. The Desktop tag points to the reviewed candidate commit, not the later squash commit. Publication remains bound to that immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |
||
|
|
22f32c99e2 |
docs(nest): make commit attribution policy-neutral (#6707)
## Summary - replace the generated Nest's unconditional human author/sign-off rules with portable guidance that separates authorship, material co-authorship, DCO certification, and cryptographic signing - defer attribution to repository-local policy, forbid inferred or guessed identities, and require inspection of every outgoing commit - bump the Nest template version so existing installations refresh, with regression coverage for fresh generation and upgrade preservation ### Related issue None found. Related runtime identity work exists in #6177, but this PR is intentionally limited to the generated Nest guidance and its refresh behavior. ### Testing - `bin/just desktop-tauri-clippy` - `bin/just desktop-tauri-test` - `bin/just file-size-check` - `git diff --check` - pre-push hooks: `push-head-scope`, `branch-skew`, `file-size-check`, and `desktop-tauri-checks` Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@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> |
||
|
|
d12dea4e67 | Support community deletion in versioned media buckets (#6738) | ||
|
|
bb5b9357a7 |
Fix TipTap editor mount race (#6779)
## Summary - wait for TipTap's mount lifecycle before reading the selection formatting tray's editor DOM - detach and reattach DOM listeners across editor view unmounts and remounts - cover mounted and pre-mount editor view access ## Testing - `just ci` - `pnpm build:e2e` - `pnpm exec playwright test tests/e2e/composer-selection-formatting.spec.ts --project=smoke --grep "right-clicking selected composer text"` - Native Builderlab staging: reproduced the pre-mount crash on main, relaunched with the fix, and verified the composer mounts without the TipTap error Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
8b812017a7 |
Fix mobile Huddle agent voice turn states (#6611)
## Summary - ignore mobile room tone when deciding whether human speech should interrupt agent audio - show a subtle waiting animation while an agent prepares a response, then restore its avatar when speech begins - render agents from Huddle membership before their audio peer starts transmitting ## Testing - `just ci` - signed Release build installed and launched on a physical iPhone - live Huddle voice and agent-membership behavior verified on device --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@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> |
||
|
|
db5617dd15 |
fix(desktop): emit singular mention feed category so alerts route correctly (#6665)
## Problem Mentions — and thread replies that @-mention you — played the **Needs action** sound instead of the **@Mentions** sound. Reported by @morgmart: "I set Needs action to a different sound and it's the only one I ever hear." ## Root cause Two vocabularies got conflated in #475: - **Section / filter vocabulary** (plural): `mentions`, `needs_action`, `activity`, `agent_activity` — the shape of `FeedSections`, the `--types` filter, and the agent-facing CLI docs. - **Per-item category vocabulary** (singular for mention): `mention`, `needs_action`, `activity`, `agent_activity` — the `FeedItemCategory` contract in `desktop/src/shared/api/types.ts`, unchanged since #12. The Tauri feed builder reused the filter string `"mentions"` as each mention item's `category`. Only one word differs between the vocabularies, so only mentions broke. Every frontend consumer compares against the singular, so real mentions never matched and fell through to the resolver's `needs_action` fallback. The E2E mock bridge emits the singular form, so tests never saw the drift. ### Symptoms this fixes (all from the one mislabel) - Mentions and mentioning thread replies played the Needs-action sound - Mention notifications used the Needs-action title format - Mentions in muted channels were suppressed (the mute-bypass never fired) - Inbox / Home feed labelled mentions "Channel update" - Channel activity popover's mentions list was always empty ## Fix **Fix the owner, not the symptoms.** `FeedItemInfo.category` becomes a `FeedItemCategory` enum whose serde form is exactly the TS union, so a misspelled category can't compile at the producer. A serialization test pins each variant to its wire string. **Frontend:** `slotForFeedKind` maps every known category explicitly. The `needs_action` fallback for unknown categories is **kept on purpose** — a contract drift should cost the user the wrong sound, not a missed alert — but it now `console.warn`s so the drift is visible to developers instead of masquerading as intended behavior. `e2eBridge.ts` and `tauri.ts` now derive the category type from `types.ts` instead of retyping it. Not touched: the plural `--types` filter and `FeedSections` keys. Those are the section vocabulary and are correct as-is. ## Verification - `just ci` green (file-size ratchet, Rust/Tauri/desktop/mobile tests, desktop + web builds) - New tests: 2 Rust (`feed_item_category_serializes_to_frontend_contract`, `feed_item_from_event_carries_singular_mention_category`), 3 TS in `sound.test.mjs` incl. one that feeds the old `"mentions"` string and asserts fallback + warning - **Runtime, dev build against the production relay:** controlled test from an agent identity into a test channel — - mention in channel → @Mentions sound, inbox shows "Mentioned in" ✅ (was Needs-action) - thread reply with mention → @Mentions sound, once ✅ (was Needs-action) - plain thread reply in the channel being viewed → silent, as designed ✅ ## Reviewers - @tlongwell-block — #475 introduced the plural category; please confirm it wasn't intentional - @wesbillman — owner of the original `FeedItemCategory` contract (#12) and most of the feed builder - @taylorkmho — owner of the sound-slot model and resolver (#968); the fallback-with-warning shape is the part to weigh in on - cc @klopez4212 --------- Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
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> |