mirror of
https://github.com/block/buzz.git
synced 2026-11-08 02:11:36 +00:00
465f1fb02a71e96e66065ed254a36a3f45d0d6c8
2468 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
465f1fb02a |
fix(composer): reconcile live-activity strip with the dock geometry from #3151
Post-rebase integration fixes after rebasing onto main, which landed the composer dock geometry contract (PR #3151) in the same region this branch reworks. The rebase kept main's dock architecture (reserved bottom rail + ComposerActivityAccessory fade) with the branch's pill strip rendered inside it; this commit cleans up the fallout: - ChannelPane.tsx: re-add the useChannelWorkingAgentPubkeys import — the dock's reserved-rail toggle (composer-dock--with-activity) still needs the channel-level working-set check even though ChannelComposerActivityRow computes its own copy — and consolidate the gating into a single hasComposerBottomActivity expression - ChannelComposerActivityAccessory.tsx: delete — main's channel-level wrapper from #3151 is fully superseded by ChannelComposerActivityRow rendered inside ComposerActivityAccessory - AppShell.tsx / routes/root.tsx: move the temporary DebugHarnessMount from AppShell (999/1000 lines on main, so the mount no longer fits the file-size ratchet) to the root route via a RootComponent wrapper - agents/debug/README.md + DebugHarnessMount.tsx: update the removal instructions and header comment for the new mount location Validated with just desktop-check, 3,817 desktop unit tests, and the channels / composer-overflow / threadpane-ultrawide e2e specs (88 passing). Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
3576093047 |
fix(composer): lone typing group uses the full row width instead of truncating at the strip cap
- BotActivityBar.tsx: apply the typing slot's max-w-64 cap only when the
group shares the strip with working-agent pills. The cap exists so a
long "X, Y, and N more are typing" label can't inflate the strip's
scroll extent alongside pills, but it was unconditional — as the
strip's LONE item a two-name label ("Alpha Debug and Beta Debug are
typing...") got cut off at 256px even with the row mostly empty. The
lone item already shrinks with the container (min-w-0 through the
slot chain), so it now consumes the row's free width and only
ellipsizes when the container is genuinely narrow.
- channels.spec.ts: add "lone typing group uses the full row width"
e2e regression — three typers push the label past the old cap, then
asserts no truncation at full width (scrollWidth <= clientWidth) and
graceful ellipsizing without scroll fades at a 200px row. Verified
the test fails against the old unconditional cap.
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
|
||
|
|
d0815bd60a |
fix(composer): past-tense plan and thought headlines in the activity pill
- Normalize plan items in getActivityHeadline (agentSessionTranscriptPresentation.ts):
the fallthrough previously returned the raw item title, so ACP plan
updates headlined the pill as a bare "Plan" / "Plan updated" next to the
past-tense tool verbs ("Read foo.ts", "Ran …") — now "Created plan" for
the initial plan and "Updated plan" for update markers (keyed off isUpdate)
- Normalize thought items the same way: "Thinking" → "Thought", and the
plan-titled thought that rendered "Planning" → "Planned"
- Assistant-message headlines (first line of the streamed reply) are
deliberately untouched — they are content, not action labels
- Add test coverage for all four normalized cases
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
|
||
|
|
01bb32123c |
feat(composer): morph the avatar between typing group and pill on partition flips
The one strip transition that can't be an in-place relabel — an agent's first promotion from the merged typing group into a status pill — played as exit-here/enter-there: the avatar dropped out of the overlapping group while a new pill faded in beside it. Give both avatar homes a matching motion layoutId so the avatar MORPHS from the group into the new pill (geometry keeps the hop short: a new pill appends rightmost, directly adjacent to the trailing typing slot). - desktop/src/features/channels/ui/composerStripAvatarLayout.ts: new leaf module — ComposerStripAvatarScopeContext (per-strip namespace), useComposerStripAvatarLayoutId, and the shared morph spring. layoutIds are app-global and the channel strip + thread panel strip can be mounted at once, so ids bake in a per-strip scope string. Motion's own LayoutGroup id namespacing is deliberately NOT used: LayoutGroup re-measures every layout-animating member whenever any member updates, which turned each pill label ticker resize into a group-wide onLayoutAnimationStart storm that permanently held the pills' deferred label swaps (caught by the hover-freeze e2e test) - desktop/src/features/channels/ui/BotActivityBar.tsx: strip provides the scope via React.useId(); the pill avatar is wrapped in a motion.div carrying the shared-element id (dropped under reduced motion) - desktop/src/features/messages/ui/TypingIndicatorRow.tsx: avatar extracted into a TypingAvatar sub-component that reads the scope from context — no new props, and typing rows rendered outside a strip (context null) are unaffected - ChannelComposerActivityRow.tsx / ChannelPane.tsx: no call-site wiring needed beyond the strip itself (context flows through the typingIndicator slot) E2E: full pill/typing strip set passes (10/10 smoke), including the hover-freeze membership test that failed under the LayoutGroup approach. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
fb28b8d295 |
feat(composer): capability-based pill partition keeps pills through the turn-end gap
The composer strip's pill-vs-typing-group partition keyed on signal
source: any typing-fallback agent went to the merged typing group. An
agent whose observer turn just completed but who was still typing
therefore flipped partitions — its pill exited and a new typing-group
item entered at the trailing slot — and re-typing after a finished turn
mounted a group item instead of relighting the pill.
Partition on capability instead: an agent renders as a pill when there
is a session worth hovering and opening — an active observer turn, or a
headline-able transcript for the channel left behind by a prior turn.
Only an agent's first-ever activity in a channel can now start in the
typing group; every later working↔typing transition is an in-place pill
relabel (the "is typing…" override from cf6014de9).
- desktop/src/features/channels/ui/composerLiveActivity.ts: add pure
partitionComposerWorkingAgents helper (injected getWorkingSource /
getTranscript readers, mirrors deriveAgentWorkingOrder's style);
pill-worthiness = source !== "typing" OR deriveActivityPillLabel
non-null, so lifecycle-noise-only transcripts ("Turn started" seeds)
still fold into the group
- desktop/src/features/channels/ui/ChannelComposerActivityRow.tsx:
partition snapshot now subscribes to BOTH the working signal and the
observer store (a transcript landing must be able to promote a typing
agent); rename observerWorkingPubkeys → pillBotPubkeys to match the
capability semantics
- desktop/src/features/channels/ui/composerLiveActivity.test.mjs: six
new tests — observer always pills, transcript-less typing groups,
turn-end gap keeps the pill, lifecycle-noise-only transcripts group,
channel scoping, mixed-roster order preservation
- desktop/src/features/channels/ui/BotActivityBar.tsx +
desktop/src/features/agents/debug/README.md: docs updated to the
capability partition
E2E: shows-and-clears, composer-does-not-shift, typing-group-in-strip,
typing-avatars, and the pill hover-freeze suite all pass (8/8 smoke).
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
|
||
|
|
62446338e4 |
fix(composer): scope slow reorder pacing to slot travel, not enter/exit
The activity pill slot's single top-level 0.9s transition (PILL_REORDER_DURATION_S) applied to every animated value, so the enter/exit fade+scale inherited the slow spring meant only for reorder travel — pills faded/scaled in and out over 0.9s. - Split AnimatedPillSlot's transition per value: `layout` keeps the 0.9s reorder spring unchanged - `opacity` keeps the 0.9s linear tween only while the slot is moving (the mid-move dip keyframes must still land with the layout spring); otherwise it uses a fast 0.18s ease-out for enter/exit - `scale` always uses the fast tween — it only animates on enter/exit - Add PILL_ENTER_EXIT_DURATION_S (0.18s) alongside the reorder constant Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
ca70b60f85 |
feat(composer): relabel working pill to "is typing" in place while agent types
An observer-backed agent that started typing its reply mid-turn had the typing signal swallowed entirely: computeAgentWorkingState drops a channel's typing entry under observer precedence, so the pill kept showing the stale last tool action and typing could only ever surface as a separate item in the trailing typing group. Now the existing pill's ticker swaps to "<name> is typing…" in the same slot and swaps back to the last action headline when typing clears. - desktop/src/features/agents/agentWorkingSignal.ts: add getAgentChannelTypingSince(pubkey, channelId) — a raw typing-registry read that deliberately bypasses the observer-precedence fold, returning the first-seen typing timestamp or null - desktop/src/features/channels/ui/BotActivityBar.tsx: BotActivityAgentPill subscribes via useSyncExternalStore; while typing, the ticker keys on a dedicated TYPING_LABEL_ID and the label becomes "<name> is typing…" — same slot, avatar, and hover feed, no new strip item. Partition unchanged: typing-only agents still divert to the combined typing indicator group - desktop/src/features/agents/agentWorkingSignal.test.mjs: three new tests pinning the getter (null cases, first-seen anchor lifecycle, and not-folded-under-observer-precedence) Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
f003351166 |
feat(composer): placement-aware pill resize guard for the activity strip
Replace the all-or-nothing hover freeze in the composer activity bar with a guard that only pins pill widths when a resize would actually move the open hover card. - BotActivityBar.tsx: drop the blanket pinWidth/freezeLayout tied to bar hover; compute activeCardIndex (pill anchoring the open card) and pin width / freeze slot layout only for pills at or left of that index. The card renders side="top" align="start" — anchored to its pill's left edge — so only those pills can displace it by resizing - Pills right of the anchor, and every pill when no card is open (even with the bar hovered), now resize freely with their label swaps and keep slot layout animation so neighbors ease instead of snapping - Keep the membership/order freeze on bar hover unchanged — a pill exiting mid-hover would slide the strip under the cursor, which is independent of card placement - Expose the membership hold via a data-hold attribute on the strip root, since the inline width pin is no longer a proxy for it - channels.spec.ts: bar-hover test now waits on data-hold, asserts pills are NOT width-pinned during bar-only hover, and parks the cursor past the last pill (label swaps can legitimately grow a pill into the inter-pill gap); add "open card pins only its anchor pill and the pills left of it" covering both directions; add waitForSettledPillBoxes helper because pills mount with short generic labels and grow via the slot layout spring when seeded headlines land Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
ac3042334c |
style(composer): rebalance activity pill spacing
- Tighten avatar-to-label gap inside each working-agent pill (gap-2 -> gap-1.5) - Widen the gap between strip items (pills + typing indicator) for clearer separation (gap-1.5 -> gap-3) Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
7db0675f6f |
fix(composer): keep strip edge fades live when pill content grows after a resize
The overflow-fade hook resize-observes the scroller and its content wrapper, but the wrapper was a min-w-0 flex item: under a narrow container it collapsed to the scroller's content width, so the shrink-0 pill slots overflowed it invisibly. When a pill's deferred label ticker swapped in a longer label AFTER the resize settled, scrollWidth grew without resizing any observed element — no ResizeObserver callback, and the edge fades stayed stale (missing) until a manual scroll. Caught by the "narrow strip scrolls horizontally with edge fades" e2e spec, which failed deterministically because label swaps are intentionally deferred until slot layout settles. - BotActivityBar.tsx: give the scroller's content wrapper min-w-max on a multi-item strip so its border box always tracks content size and every late growth fires the ResizeObserver; keep min-w-0 for the lone-item case so a single pill/typing group still shrinks to fit (shrinkToFit) instead of overflowing into scroll - Side effect: pills no longer visually compress below their natural width inside a constrained strip (the collapsed wrapper was squeezing them); they keep readable widths and the strip scrolls as designed - Document the min-w-max dependency at the ResizeObserver in useStripOverflowFades so the wrapper's sizing isn't "simplified" back into a stale-fade regression Verified: all 13 activity/pill/typing specs in channels.spec.ts pass (including the previously failing narrow-strip fade spec and the new typing-inside-strip spec), plus both channel-composer-overflow specs. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
518babc1e5 |
feat(composer): terse tool-call headline for the activity pill
- Rewrite the tool branch of getActivityHeadline (agentSessionTranscriptPresentation.ts)
to use the descriptor's terse action tier — verb + compact object, e.g.
"Read foo.ts" — instead of "label · full preview" ("Read file ·
src/agents/ui/foo.ts"), so the composer pill's 200px cap shows the
informative part of the action instead of ellipsizing a long preview
- Basename path-like objects only (file-read / file-edit / skill-read);
shell commands stay whole since they legitimately contain "/", and file
edits reuse the already-basenamed fileEditSummary.filename
- Fall back to the previous "label · preview" format when a descriptor
carries no action (older descriptors)
- Export the existing private basename() from agentSessionFileEditDiff.ts
rather than adding a third copy
- Document the terse format on deriveActivityPillLabel (composerLiveActivity.ts);
no logic change — the pill picks it up via getActivityHeadline, whose only
production consumer is the pill
- Tests: update presentation expectations ("Send Message · abc" → "Sent abc"),
add cases for file-read/file-edit basenaming, shell commands kept whole,
and the no-action fallback; add a pill-level terse-headline case to
composerLiveActivity.test.mjs
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
|
||
|
|
a24b108838 |
feat(composer): unify typing indicator into the pill strip and align pill styling
Typing group becomes a slot sibling of the working-agent pills: - BotActivityBar.tsx: BotActivityComposerAction gains a `typingIndicator` prop rendered as the strip's trailing AnimatedPillSlot, so the typing group shares the scroller, edge fades, and layout/enter/exit animations with the pills; lone-item shrink logic now counts the typing slot - ChannelComposerActivityRow.tsx: the row renders ONE strip hosting both groups instead of a pill strip beside a standalone TypingIndicatorRow - ChannelPane.tsx / MessageThreadPanel.tsx: thread panel mirrors the same structure — ChannelPane builds the strip (thread typers included) and the panel's activity row just mounts it; `threadTypingPubkeys` prop removed from MessageThreadPanel; row height fixed at h-8.5 to match the channel row so the bottom-anchored composer never bumps - TypingIndicatorRow.tsx: drop the now-unused "activity" variant — the strip's slot owns sizing/spacing, so only the default variant remains - composer.css: corner-mask offset 2.5rem -> 2.625rem to match the fixed h-8.5 activity row below the composer in both surfaces - channels.spec.ts: new e2e covering the typing group rendering inside the strip, trailing the pills, and scrolling under the edge fades Align the "is working" pill with the "is typing" indicator: - Remove the pill's border, background, shadow, and horizontal padding — hover/open states signal through text color only - Match label typography (text-xs font-medium, was font-semibold) and the typing label's translate-y-px optical nudge; avatar-to-label gap-2 - Drop the labelClassName="font-semibold" overrides that compensated for the pill's old heavier weight (channel row + thread strip) Also document in AGENTS.md that e2e reruns need `pnpm run build:e2e` — a plain build omits the mock-bridge module and every spec fails at boot. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
5171056f43 |
feat(composer): stable turn-start pill order and persistent action labels
Calm the working-agent pill strip while multiple agents stream: position now encodes identity (stable for a whole turn), liveness stays on the per-pill label tickers, and positional motion is reserved for membership changes (an agent starting or finishing) — the transitions a builder actually needs to notice. Grounded in the work-visibility and completion-awareness JTBDs: transcript-recency ordering shuffled pills on every agent event (cry-wolf motion) and buried quiet/stuck agents at the scrolled-off end of the strip. - composerLiveActivity.ts: replace deriveAgentActivityOrder (transcript recency, newest first) with deriveAgentWorkingOrder — sorts by channel-scoped turn anchorAt ascending (earliest worker left-most, new agents append right), quantized to whole seconds so retroactive clock-offset refinements cannot reorder the strip, stable roster-order tiebreak, no-anchor agents keep roster order at the end - composerLiveActivity.ts: remove the 6s label decay (ACTIVITY_PILL_STALE_MS and the now/staleAfterMs params) — the last real action now persists through quiet stretches; the generic "<name> is working…" label only shows before the first action lands, since the last action is more informative than a placeholder while a turn is in progress - BotActivityBar.tsx: order snapshot subscribes to agentWorkingSignal (turn anchors) instead of the observer store's transcript scan; drop the per-pill useNow(1000) staleness tick so pills no longer re-render every second; hover-freeze machinery (order/membership/width hold) unchanged and still guards membership changes under the cursor - composerLiveActivity.test.mjs: six anchor-ordering tests (earliest first, append-on-later-start, channel scoping, unscoped earliest-across-channels, roster fallback, sub-second-shift stability); decay tests replaced with a headline-persists-regardless-of-age test - channels.spec.ts: retarget the two hover-freeze e2e tests from recency flips to membership changes — a third agent (lyra) starting a turn mid-hover appends only after the hold releases, and a turn completing mid-bar-hover exits only after release - e2eBridge.ts: __BUZZ_E2E_SEED_ACTIVE_TURNS__ accepts an explicit atMs so tests seed well-separated turn-start anchors deterministically - channels.spec.ts: seedPillActivityMessage now takes the matching turnId — the old hardcoded id resurrected a phantom sibling turn that would have kept a completed agent "working" forever - debugAgentHarness.ts / debug README: update decay-era comments to the hold-last-headline behavior Verified: 3652 unit tests pass; 12 pill/activity/typing e2e tests pass against a fresh build:e2e. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
bc3a0cfd46 |
fix(composer): pin activity strip edge fades to the container edges
- Bleed the pill strip's scroll viewport across the mounting row's px-5
gutter (-mx-5 on the strip root, px-5 restored as scroller padding) so
overflowing pills clip at the container's visual edge instead of the
row's padded content box — previously a pill faded out mid-row, 20px
shy of the edge, with a dead gutter after the gradient
- Keep at-rest alignment unchanged: the scroller's internal px-5 keeps
the first pill on the row gutter in both mounting surfaces
(ChannelComposerActivityRow and the thread panel toolbar row)
- Widen the edge fades from w-8 to w-12 so the gradient keeps a soft
fade zone over the pills after spanning the 20px gutter
- Grow the pill label ticker viewport from h-3.5 to h-4 with a matching
leading-4 line box so Inter's descenders ("g", "y") are no longer
sheared off the label
- Extend the narrow-strip e2e test to assert both fades sit within 1px
of the activity row's border-box edges, guarding the full-bleed
geometry against regression
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
|
||
|
|
7ca5d05e53 |
test(composer): seed activity pills from observer turns and drop animation sleeps
The composer pill strip only renders observer-backed agents — typing-only agents fold into the combined typing indicator group by design — but the activity-indicator and no-shift e2e tests still summoned the pill via mock typing events, so the pill never mounted and both tests failed at the first toBeVisible. - Rewrite 'shows and clears activity indicators' on __BUZZ_E2E_SEED_ACTIVE_TURNS__: assert the typing-only fold-in first, then the observer-turn promotion (typing indicator clears as the pill mounts), the channel-scoped aux panel with the live transcript, turn_completed clearing the pill, and a fresh turn re-mounting it. - Rewrite 'composer does not shift' on observer-turn seeds and extend it: the fixed-row contract now covers pill mount, pill clear, AND the combined typing group mounting — all within ±0.5px of the idle composer top, which would catch the reported 2px drift. - Make the bar-hover freeze test deterministic: the strip sizes to its content, so 'the bar's empty right side' could land on the trailing pill and open a hover card. Park the cursor in the inter-pill gap instead and wait for the hold's width-pin style before seeding the reorder. - Replace both 1200ms animation sleeps (0.9s spring + 180ms grace left only ~120ms margin on CI) with state-based assertions: the pill label committing the seeded message text proves the store update reached the strip before the frozen-order assertions run. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
8047f8b93b |
feat(composer): ship live activity by default and scroll the pill strip when narrow
Remove the composerLiveActivity preview flag so the live-activity hover
preview ships for everyone, and land the pill strip overflow behavior
for narrow containers.
Flag removal (ships by default):
- preview-features.json: drop the composerLiveActivity entry — absence
from the manifest means stable/always-on; E2E preview seeding and the
Settings > Experiments card both derive from the manifest, so they
pick this up automatically
- BotActivityBar.tsx: remove the useFeatureEnabled gate and the legacy
flag-off "View activity" popover item; the pill hover popover now
always renders ComposerLiveActivityFeed
- Remove the now-dead openAgentSessionPubkey plumbing through
BotActivityAgentPill / BotActivityComposerAction /
ChannelComposerActivityRow / ChannelPane — it only backed the legacy
item's active-session highlight
- Update stale flag references in ComposerLiveActivityFeed's doc
comment, the agents debug harness README, and a channels.spec.ts
comment
Pill strip overflow (BotActivityBar.tsx, ChannelComposerActivityRow.tsx):
- With several pills in a narrow container (thread panel toolbars
especially), pills keep their natural label-truncated width and the
strip scrolls horizontally — scrollbar hidden, edge gradient fades
signalling clipped pills — instead of compressing every pill into an
unreadable sliver
- New useStripOverflowFades hook tracks scroll position plus
scroller/content resizes (ResizeObserver) so fades appear and
disappear without a re-render triggering event
- A lone pill shrinks to fit (min-w-0) instead of scrolling — an edge
fade over a single pill reads as a cut-off bug, not an affordance
- layoutScroll on the scroller keeps motion's slot layout animations
measured relative to the scroll offset
- e2e: add "narrow strip scrolls horizontally with edge fades instead
of compressing pills" and "lone pill shrinks to fit a narrow
container without scroll fades"
Verified: tsc, biome check, desktop unit tests (3651 pass), and the
pill/strip/live-activity e2e subset. Two pre-existing channels.spec.ts
failures ("composer does not shift when the activity row mounts and
clears", "hovering the bar itself freezes pill order without opening a
card") reproduce identically on the parent commit with a clean tree —
unrelated to this change.
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
|
||
|
|
3550dd4fa5 |
fix(desktop): align thread composer activity row with the full-width composer
The thread footer's activity/typing row was centered into an mx-auto max-w-4xl column while the thread composer above it spans the full pane width, so in a wide thread pane the typing indicator floated toward the pane's center instead of hugging the composer's left gutter. - MessageThreadPanel.tsx: drop the mx-auto/max-w-4xl wrapper so the row spans the same width as the composer above it - Remove flex-1 from the toolbarExtraActions (agent pill strip) wrapper — it previously ate half the row and pushed the typing group to the midpoint whenever it mounted; the strip now sizes to its content, mirroring ChannelComposerActivityRow - Make the typing indicator's leading padding conditional like the channel row: composer-edge padding only when the typing group leads the row, pl-0 when it follows pills so the flex gap is the whole spacing Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
222e419f16 |
feat(composer): collapse typing agents and humans into one indicator group
- ChannelComposerActivityRow.tsx: partition the channel's working set by signal source — observer-backed agents keep the interactive activity pills, while typing-fallback-only agents (no observer turn, nothing to hover or open) are diverted out of the pill strip and merged with the human typers into ONE combined TypingIndicatorRow group with an overlapping avatar set; channel-agent roster names are overlaid onto the profile lookup so roster-only agents never fall back to truncated pubkeys - Fix row layout: the pill strip wrapper and typing row were both flex-1, splitting the row 50/50 and stranding the typing group at the halfway mark — the strip now sizes to content (still shrinkable) and the typing group takes the remainder; row gap set to gap-3, and the typing row's base px-4/sm:px-6 padding is zeroed next to pills (composer-edge alignment padding only applies when the group leads the row) - TypingIndicatorRow.tsx: 4+ typers label now reads "X, Y, and N more are typing..." per spec (was "N others"); new optional labelClassName prop so the composer row can match the pills' font-semibold weight without changing the thread panel's indicator - BotActivityBar.tsx: revert the passive typing pill variant and "is typing…" label — typing-only agents never reach the pill anymore; pill avatar bumped 18px → 20px with a uniform 3px surround (pl-0.75, h-7 minus 1px borders) and asymmetric pr-2 for label breathing room - debug/README.md: document that the harness Typing toggle now renders through the combined typing indicator group Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
03078de3df |
feat(composer): label typing-fallback pills "is typing…" instead of "is working…"
- BotActivityBar.tsx: the generic decay label on a typing-fallback-only pill (working source "typing", no observer turn) now reads "<name> is typing…", matching the human typing indicator's vocabulary; observer-backed pills keep "<name> is working…" - A fresh action headline still wins over both generic labels - Both generic labels share the same ticker id, so a typing → observer source upgrade updates the text in place instead of playing the push-up swap Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
b379459bce |
feat(composer): freeze pill strip on hover, typing-fallback pills, faster label decay
Composer activity strip (BotActivityBar.tsx):
- Centralize hover popover state at the strip level (useStripHoverPopover):
ONE active pill and ONE timer for the whole strip, replacing per-pill
timers that raced each other (double-open on pill-to-pill travel, stuck
cards when the cursor clipped an open card)
- Freeze pill order, membership, layout animation, and pill widths while
the cursor is over the bar or a hover card is showing; queued reorders
apply (animated) after the hold releases
- Pin the hovered pill's rendered width so label swaps can't shift
neighboring pills under the cursor
- Open on keyboard focus only for :focus-visible, and preventDefault on
the card's close auto-focus, fixing the re-open loop when Radix
returned focus to the trigger
- Typing-fallback-only agents (working source "typing", no observer turn)
now render a passive status pill: borderless, no max-width cap, no hover
card, no click-through, not focusable — there is no transcript or
session behind the typing signal to open
Pill label decay (composerLiveActivity.ts):
- Shorten ACTIVITY_PILL_STALE_MS 15s → 6s so pauses in the event stream
cycle the pill back to the generic label promptly; tick 5s → 1s
- Generic label is now agent-named ("<name> is working…") instead of the
bare "Working…"
Debug harness (features/agents/debug):
- Add per-agent "Typing" toggle simulating the basic "is typing"
situation: a synthetic channel-scoped typing entry with no observer
turn, exercising the typing-fallback path (useChannelActivityTyping →
reportChannelBotTyping → passive pill); independent of Working/Progress
so observer-over-typing precedence is testable; "End all turns" also
clears typing
- useDebugHarnessTypingEntries appends the synthetic entries in
useChannelActivityTyping (dev-only, reference-stable when idle)
Tests:
- channels.spec.ts: e2e coverage for hover-freeze — card freezes pill
order with queued reorder applying on close, and bar-hover freeze
without an open card
- composerLiveActivity.test.mjs: freshness fixtures updated for the 6s
stale window
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
|
||
|
|
8cfbcce370 |
feat(composer): sync pill reorder motion and add per-action label ticker
- BotActivityBar.tsx: extract AnimatedPillSlot around each agent pill —
reorders animate with a 0.9s spring (bounce 0.15, subtler overshoot) and
an opacity dip via [1, 0.35, 1] keyframes running the same duration with
linear easing, so the fade lands together with the layout switch instead
of trailing it
- Label updates render as a clipped push-up ticker (h-3.5 overflow-hidden
container; the new text pushes the old text up and out, 0.28s tween,
instant under reduced motion); swaps are deferred while the pill's slot
is mid layout animation (AnimatedPillSlot passes isMoving via render
prop) and play once the spring settles, otherwise immediately
- Ticker keys on the backing transcript item id, not the label string: a
genuinely new action animates, while streamed chunks that grow the same
item (message first line, thought text) update text in place — fixes
phantom re-animations of visually identical truncated text
- composerLiveActivity.ts: deriveActivityPillLabel now returns
ActivityPillHeadline {id, label} and skips usage_update /
available_commands_update lifecycle meta frames, which previously
headlined the pill as bare "Usage"/"Commands" between real actions
- animations.css: .buzz-shimmer.truncate::before ellipsizes the glow
overlay copy the same way as the base text, so the shimmer sweeps the
ellipsis instead of revealing the clipped rightmost characters
- Pill max width raised from max-w-30 (120px) to max-w-36 (144px)
- channels.spec.ts: activity-indicator test now asserts the flag-ON
reality (E2E seeds every preview feature, so composerLiveActivity always
renders): hovering the pill opens the live-activity feed popover and
clicking opens the agent session in the aux panel — replaces the legacy
"View activity" item assertions, which only ever passed via a popover
toggle race in the pre-pill trigger
- composerLiveActivity.test.mjs: object-shape assertions plus new cases —
meta frames never headline, meta-only transcripts stay generic, and the
headline id stays stable while a message streams (19 tests passing)
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
|
||
|
|
5aaef5cdf9 |
feat(composer): flatten agent activity UI into per-agent status pills
- BotActivityBar.tsx: replace the combined "agents working" trigger with one pill per working agent (avatar + latest action summary in a rounded-full chip, max-w-30 with truncation so pill widths stay uniform); extract BotActivityAgentPill with per-pill hover popover state (useHoverPopover) and remove the tab strip, tab selection state, and the 5-headline rotation interval - Pill label shows the agent's single latest action headline and decays to a generic "Working…" state once activity goes quiet (15s staleness window, 5s useNow tick); deliberately no rotation between recent actions - Hovering a pill shows the live activity feed as the popover surface itself (w-80 p-0, edge-to-edge, no inset bordered box); clicking the pill opens the agent's full runtime in the auxiliary panel (preventDefault stops Radix's composed trigger toggle from racing the hover/focus open state, which made click-to-open flaky) - composerLiveActivity.ts: add deriveActivityPillLabel pure helper (channel-scoped spine-first scan + staleness decay, ACTIVITY_PILL_STALE_MS = 15s) and remove the now-unused resolveSelectedActivityAgent - composerLiveActivity.test.mjs: replace resolveSelectedActivityAgent tests with 7 deriveActivityPillLabel cases (fresh headline wins, staleness decay, custom window, channel scoping, spine-over-metadata, metadata fallback, empty transcript) - ComposerLiveActivityFeed.tsx: overlay button rounding rounded-lg → rounded-[inherit] so the hover tint follows the popover's rounded-xl clip when the feed renders edge-to-edge - ChannelComposerActivityRow.tsx / ChannelPane.tsx: drop the removed variant prop (the toolbar variant was unused; both call sites rendered inline) - channels.spec.ts: activity-indicator test now hovers the pill for the legacy preview popover and clicks the pill for direct aux-panel open - Flag-off (composerLiveActivity disabled) hover popover keeps the legacy "View activity" item; pills stay h-7 inside the fixed h-8.5 activity row so the composer-shift guard keeps passing Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
a9d2f80320 |
fix(composer): pin activity row height so composer doesn't shift on agent turns
- Extract the composer status strip into ChannelComposerActivityRow.tsx (new component hosting the inline bot-activity trigger and the typing indicator, plus the useChannelWorkingAgentPubkeys subscription). Also brings ChannelPane.tsx back under the 1000-line file-size ceiling (1006 -> 969). - Change the row from min-h-8 to a fixed h-8.5 (34px). The inline bot-activity trigger is h-7 (28px), which plus the row's pb-1.5 (6px) made the active row 34px — 2px taller than the 32px idle minimum — so the bottom-anchored composer visibly bumped up whenever an agent turn or bot typing mounted the trigger, and dropped back when it cleared. 34px matches the previous active-state height exactly: the working state renders identically and the idle state now reserves the same space. (Typing indicators alone are 20px of content, under the old minimum, which is why only the agent-activity trigger caused the shift.) - channels.spec.ts: add "composer does not shift when the activity row mounts and clears" e2e test asserting the composer's y-position stays stable across trigger mount and clear. Verified via negative control: with min-h-8 restored the test fails with exactly the 2px delta. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
08bb677645 |
fix(composer): make live-activity preview subtree inert to keyboard and AT
Wrap the ManagedAgentSessionPanel subtree in a native inert container so compact transcript rows (which can render keyboard-focusable message links) are removed from pointer, tab-order, and assistive-tech interaction — CSS pointer-events-none only neutralized the mouse. The overlay open button and Last-live pill stay accessible. Also extract deriveLastLiveAt and make it archive-aware: the Last-live pill now takes the newest across the live transcript window, archived channel events, and the active-turn anchor, so it can no longer disagree with archived rows visible in the preview. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
aa8010c674 |
feat(composer): live activity preview for the selected working agent
Rebuild of the composer live-activity experiment (old draft PR #1575) on current main primitives. Behind the new off-by-default composerLiveActivity preview feature, the agents-working popover shows one selected working agent's channel-scoped feed at a time: - ComposerLiveActivityFeed reuses ManagedAgentSessionPanel with the compactPreview transcript variant (same primitive as the profile activity embed), so live/archive merging, projection, anchored scrolling, and idle handling stay owned by that surface. - Agent switching via pill tabs (role=tablist) under the preview; selection resolves explicit tab > open session pane > first working agent, and falls through when the selected agent stops working. - Whole preview is one click target with a Last-live recency pill; transcript rows underneath are inert, avoiding nested interactive controls inside a clickable shell. - formatLastLiveLabel extracted from UserProfilePanelTabs into profile/lib/lastLiveLabel so both surfaces share the recency label. With the experiment off, the popover renders the existing agent list unchanged. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
0720f5380c |
Downgrade desktop Huddles to audio protocol v2 (#6610)
## Summary - negotiate Huddle audio protocol v2 on desktop - decode the released one-byte peer-index prefix - retain roster-driven playout resets and document the missing v3 epoch fence ## Testing - `just desktop-tauri-fmt-check` - `just desktop-tauri-clippy` - `just desktop-tauri-test` Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
e236329413 |
Polish Huddle participant interactions (#6312)
## Summary - arrange Huddle participants in a responsive, equal-weight cluster with spring enter/exit motion and a `+N` overflow - spotlight tapped participants over a blurred call surface, with a roster for hidden participants and no self-avatar action - add selection haptics across full-screen and drawer controls, including both end-call buttons <img width="1080" height="2424" alt="Screenshot_20260819-151448" src="https://github.com/user-attachments/assets/00b7fdca-2304-4788-9952-e07224798513" /> <img width="1080" height="2424" alt="Screenshot_20260819-151422" src="https://github.com/user-attachments/assets/a0cfc861-0519-44ff-bb56-4c983ed6344c" /> ## Validation - `just mobile-check` - focused participant, drawer-control, and full-screen end-call widget tests - Huddle-focused widget suite (15 tests) - full mobile Flutter suite (1,538 tests) ## Dependency Built on #6056 and contains only the follow-up interaction work. Merge after #6056 lands. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: leader <71e9f2c44a6932b6772caaaccda1911d010463c3e2c6c40410b8329956046801@buzz.block.builderlab.xyz> Co-authored-by: Tom Brow <tomb@block.xyz> Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@buzz.block.builderlab.xyz> |
||
|
|
4baccd5394 |
Downgrade mobile Huddles to audio protocol v2 (#6558)
## Summary - downgrade Mobile Huddle authentication and native media configuration from protocol v3 to the currently deployed relay's v2 contract - restore the released one-byte relay peer prefix while retaining later reconnect, roster, and playout-reset reliability fixes - update Android, iOS, protocol documentation, and focused tests together Protocol v2 does not carry v3's occupancy epoch on audio frames, so it cannot fence the narrow delayed-packet/peer-index-reuse race. This is an intentional compatibility tradeoff until the relay v3 rollout is ready. ### Related issue None found. ### Testing - `just mobile-check` - `just mobile-test` — 1,661 tests passed - Android debug build installed and launched on Pixel 10 as `xyz.block.buzz.mobile.sprout_mobile_profile_settings`; foreground process verified - signed iOS Release build installed and launched on iPhone as `com.buzz.buzzMobile`; running process verified A live two-device Huddle audio call remains a manual verification step. Signed-off-by: kenny lopez <klopez4212@gmail.com>mobile-v0.14.0-rc.2 |
||
|
|
040b203f73 |
perf(desktop): make the Projects surface render-cheap (#6460)
Diagnostic profiling on a large community (101 issues, 258 PRs) showed Projects tab switches taking 2.5–3.6s, dominated by single React commits of 0.5–1.5s and per-render recomputation — fetch work was already off the main thread; the cost was building the UI. ### Measured: tab click → painted, per tab | tab | before | after | |---|---|---:| | projects | 3,608ms | 320–580ms | | repositories | 3,126–3,534ms | 310–410ms | | tasks | 395–1,101ms | ~115ms | | reviews | 322–2,603ms | ~96ms | | activity | 597–741ms | ~148ms | Single-commit ceiling dropped from 1,541ms to ≤200ms (growth steps 25–40ms). Fixes in profiled-cost order: - **Profile popover body mounts only while open.** `UserProfilePopover` carried seven query subscriptions plus interaction hooks per instance even when closed; grids mount hundreds (five per card in people stacks, one per row author) — measured **~40ms per card**, the dominant share of the 1.2s card-tab commits. The always-mounted shell is now just the Radix root + trigger; trigger markup, hover timing, and keyboard handling are unchanged, and hover/tooltip event continuity is preserved because the trigger never remounts. - **Incremental row mounting.** The first 12 cards / 30 rows render in the first commit; the rest stream in 36–60-per-frame low-priority transitions. Grouped lists trim across group boundaries via a pure, tested slicer; the mounted count survives in-place refetches. - **Activity feed**: was rebuilt unmemoized on every render, markdown-flattening every issue/PR/comment body in the community just to sort and keep 30 items (~360+ flattens per render on the measured community). Now memoized, and bodies stay raw until after the sort+slice — 30 flattens, once per data change. - **Contribution graph** (always-visible rail, so every tab paid for it): ~180 day cells each wrapped in a Radix tooltip with per-cell Intl date formatting per render. Now memoized, cells precomputed once per data change, native `title` tooltips. (The activity-bar segments keep their styled Radix tooltips — pinned by an existing spec.) - **Rows/cards memoized with identity-stable props**: per-row selection arrays were rebuilt per row per render (O(n²) — 258 PRs × 258-item arrays each render) and are now hoisted and shared; people arrays derive inside the memoized cards; the rail's stat walk over every issue/PR is memoized. - **`content-visibility: auto`** on cards and rows so offscreen entries skip layout and paint; **tab switches run in a React transition** so the click stays responsive while the new tree mounts. Remaining known cost (out of scope): cold-entry data readiness — the work-item and activity queries ship thousands of events to compute counts (2–4s on a large community; see the fan-lifecycle PR). The structural fix is a relay-side aggregate; tracked as follow-up. --------- Signed-off-by: Max Lampert <maxwell@squareup.com> |
||
|
|
f99532585a |
refactor(acp): clarify agent prompt sections (#6501)
## Why The ACP prompt puts a machine-specific Workspace prefix before static base guidance and labels the user-facing agent instruction layer as the generic System section. Because the cwd varies by launch and worktree, leading with it reduces reusable prompt-prefix stability. `[Workspace]` was added in [PR #1194](https://github.com/block/buzz/pull/1194) as a defensive fix after a broken `~/.sprout` → `~/.buzz` migration caused agents to scan `$HOME` and trigger macOS TCC prompts. This change retains that grounding while shrinking it to the current working directory and moving dynamic environment context after the static Base prompt. ## What - Emit the prompt in Base → Workspace → Agent Instructions order - Reduce Workspace to `Current working directory: <absolute path>` - Resolve cwd as an absolute native-platform path and preserve Windows drive/UNC paths instead of checking for a leading `/` - Emit Agent Instructions for persona and standalone agent instructions across modern and legacy ACP paths - Preserve parsing for archived observer frames that used System or the former Workspace-before-Base order, and align the persona catalog label ## Risk Assessment Medium-low — this changes prompt framing for every newly created agent session. Existing archived observer frames remain parseable, and execution still uses the same ACP working directory. Cwd resolution now fails clearly instead of substituting `/` when the process directory cannot be resolved. ## References - https://github.com/block/buzz/pull/1103 - https://github.com/block/buzz/pull/1194 Generated with Codex --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com> |
||
|
|
8c0f42e187 |
Add mobile Huddles voice MVP (#6056)
## Summary - add foreground mobile Huddles on Android and iOS with native Opus capture/playback, mute, speaker routing, participants, lifecycle, and minimized drawer UI - keep mobile Huddle cards and roster state live, including ended rooms, relay-resolved profiles, and agents - broadcast desktop agent TTS through the existing Huddle audio protocol ## Scope Foreground human-to-human voice MVP only. Agent setup/transcripts, background calling, recording, and advanced device controls remain out of scope. ## Validation - `just mobile-check` - `just mobile-test` — 1,500 passed - `just desktop-check` and `just desktop-test` — 4,957 passed - desktop typecheck, strict Clippy, and Tauri tests — 2,445 passed, 15 ignored - mobile worktree identity contract checks - physical Pixel/iPhone behavior reviewed during development --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: leader <71e9f2c44a6932b6772caaaccda1911d010463c3e2c6c40410b8329956046801@buzz.block.builderlab.xyz> Co-authored-by: Tom Brow <tomb@block.xyz> Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@buzz.block.builderlab.xyz>mobile-v0.14.0-rc.1 |
||
|
|
a2d8be5efa |
feat(desktop-messages): keep agents addressed across messages (#6315)
**Category:** new-feature **User Impact:** Users can keep selected agents addressed across consecutive messages without retyping their handles. **Problem:** Repeated conversations with agents require manually typing the same mentions on every turn, which adds friction and makes recipients easy to omit. **Solution:** The composer can now keep agents automatically addressed per channel, either from the mention controls or after a successful inline mention. Addressed agents remain visible in the toolbar, apply to channel threads, survive send failures safely, and never cross community boundaries. ## Changes <details> <summary>File changes</summary> **desktop/src-tauri/src/events/message_tags.rs** Preserves the automatic-address marker on validated mention reference tags. **desktop/src/features/channels/ui/ChannelPane.tsx** Wires the appropriate channel, thread, inbox, or forum composer context without leaking audiences across surfaces. **desktop/src/features/communities/useCommunityInit.ts** Clears composer audience state when the active community changes. **desktop/src/features/forum/ui/ForumComposer.tsx** Wires the appropriate channel, thread, inbox, or forum composer context without leaking audiences across surfaces. **desktop/src/features/forum/ui/ForumComposerAutocompletes.tsx** Wires the appropriate channel, thread, inbox, or forum composer context without leaking audiences across surfaces. **desktop/src/features/home/ui/InboxDetailPane.tsx** Wires the appropriate channel, thread, inbox, or forum composer context without leaking audiences across surfaces. **desktop/src/features/messages/lib/agentAddressMention.d.mts** Defines helpers and types for marked automatic-address mention tags. **desktop/src/features/messages/lib/agentAddressMention.mjs** Defines helpers and types for marked automatic-address mention tags. **desktop/src/features/messages/lib/agentAddressMention.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/lib/applyEditTagOverlay.mjs** Preserves automatic-address metadata when edited message tags are overlaid. **desktop/src/features/messages/lib/applyEditTagOverlay.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/lib/autoPinMentionedAgentsPreference.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/lib/autoPinMentionedAgentsPreference.ts** Stores the preference that keeps explicitly mentioned agents addressed for later messages. **desktop/src/features/messages/lib/extractMentionPersonas.ts** Separates persona recipients from the composer mention orchestration. **desktop/src/features/messages/lib/persistentAgentAudience.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/lib/persistentAgentAudience.ts** Maintains bounded, in-memory, channel-scoped automatic agent audiences. **desktop/src/features/messages/lib/useMentionSelection.ts** Centralizes mention picker selection state and agent-first selection behavior. **desktop/src/features/messages/lib/useMentions.ts** Exposes explicit picker origins and selection controls while preserving inline mention behavior. **desktop/src/features/messages/ui/ComposerAddressControls.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/ui/ComposerAddressControls.tsx** Renders compact addressed-agent avatars and the automatic-mention management entry point. **desktop/src/features/messages/ui/MentionAutocomplete.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/ui/MentionAutocomplete.tsx** Adds automatic-mention controls and options to the existing mention picker. **desktop/src/features/messages/ui/MessageAgentAddressPrefix.tsx** Shows which agents were automatically addressed on a sent message. **desktop/src/features/messages/ui/MessageComposer.tsx** Integrates automatic audiences, picker controls, accessible feedback, shortcuts, and send behavior. **desktop/src/features/messages/ui/MessageComposer.types.ts** Defines the simplified channel audience context shared by composer hosts. **desktop/src/features/messages/ui/MessageComposerToolbar.tsx** Places automatic-address controls in the composer toolbar without crowding narrow layouts. **desktop/src/features/messages/ui/MessageRow.tsx** Displays automatic-address metadata alongside sent message content. **desktop/src/features/messages/ui/MessageThreadPanel.tsx** Wires the appropriate channel, thread, inbox, or forum composer context without leaking audiences across surfaces. **desktop/src/features/messages/ui/composerAgentKeyboard.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/ui/persistentAgentAudienceHosts.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/ui/useAddressMentionPulse.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/ui/useAddressMentionPulse.ts** Provides success and failure animation signals for addressed-agent controls. **desktop/src/features/messages/ui/useAgentAddressLockPicker.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/ui/useAgentAddressLockPicker.ts** Coordinates adding, removing, and announcing automatically addressed agents. **desktop/src/features/messages/ui/useAlwaysAddressShortcut.ts** Implements the platform-aware shortcut for toggling automatic addressing. **desktop/src/features/messages/ui/useAutoPinMentionedAgents.ts** Promotes successfully sent inline agent mentions and provides a single undoable notification. **desktop/src/features/messages/ui/useComposerMentionPicker.ts** Opens the mention picker without rewriting the current draft. **desktop/src/features/messages/ui/useMentionSendFlow.helpers.test.mjs** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. **desktop/src/features/messages/ui/useMentionSendFlow.helpers.ts** Merges automatic and inline recipients, marks outgoing tags, and restores failed sends safely. **desktop/src/features/messages/ui/useMentionSendFlow.ts** Merges automatic and inline recipients, marks outgoing tags, and restores failed sends safely. **desktop/src/features/messages/ui/usePersistentAgentMentionHydration.ts** Removes the prior draft-text hydration approach now that automatic audiences stay at composer ingress. **desktop/src/features/settings/ui/AgentsSettingsPanel.tsx** Replaces the old global behavior with explicit composer-level automatic-mention controls. **desktop/src/features/settings/ui/PreventSleepSettingsCard.tsx** Replaces the old global behavior with explicit composer-level automatic-mention controls. **desktop/src/shared/lib/keyboard-shortcuts.ts** Defines the user-facing automatic-address keyboard shortcut label. **desktop/src/shared/ui/VideoReviewCommentMarkdown.tsx** Allows automatic-address prefixes to compose with video review timecodes. **desktop/tests/e2e/persistent-agent-audience.spec.ts** Covers the automatic-address behavior and its failure, keyboard, layout, or persistence boundaries. </details> ## Reproduction Steps 1. Open a channel with one or more agents and open the mention picker from the composer. 2. Select an agent for automatic mentions, then send several messages without retyping the handle; confirm the agent remains in the composer control and receives each message. 3. Mention another agent inline, send successfully, and confirm the agent becomes automatically addressed; use the notification's Undo action to reverse it. 4. Open a thread in the same channel and confirm the same addressed agents are available there. 5. Remove an agent from the composer control and confirm later messages stop addressing it. 6. Switch communities and confirm addressed agents do not carry into the other community. ## Screenshots All states below use the dark Buzz theme with a selected lilac accent. ### Addressed composer Selected agents stay visible at the composer ingress without adding handles to the draft.  ### Open mention menu The @ ingress opens the existing mention menu and shows which agents are already addressed.  ### Mention options The inline options pane controls whether a successful one-time agent mention carries into later messages.  ### Agent settings The same preference is available in **Settings → Agents → Conversations**.  --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
723affe5d1 |
fix(desktop): remove Buzz entity link previews (#6512)
**Category:** improvement **User Impact:** Buzz-native project, repository, issue, and pull request links now appear once as compact inline chips, with their details available on hover. **Problem:** Buzz-native entity links rendered both an inline chip and a standalone preview card, repeating the same metadata and adding visual noise to conversations. **Solution:** Exclude Buzz-native links from the shared standalone-preview extractor while leaving entity parsing intact for chip tooltips and preserving external web previews and attachment cards. <details> <summary>File changes</summary> **desktop/src/shared/lib/linkPreview.ts** Stops Buzz-native preview candidates after parsing, including same-relay git clone URLs that normalize to repository entities, while allowing external URLs through the existing snapshot path. **desktop/src/shared/lib/linkPreview.test.mjs** Covers project, repository, issue, pull request, markdown-labeled, same-relay clone, and mixed external-link extraction behavior. **desktop/src/shared/ui/markdown/useMessageLinkPreviews.test.mjs** Confirms sent messages no longer merge a standalone Buzz entity card while external sender snapshots still render. </details> ## Reproduction steps 1. Open a desktop channel containing a `buzz://project`, `buzz://repo`, `buzz://issue`, or `buzz://pr` link. 2. Confirm the link renders as an inline entity chip without a second standalone Buzz card below the message. 3. Hover the chip and confirm its entity metadata remains available. 4. Post an external HTTPS link and confirm its web preview still renders. 5. Paste a same-relay `/git/<owner>/<repo>` clone URL and confirm it uses the repository chip without a duplicate card. ## Screenshots | Before | After | | --- | --- | | Inline chip plus redundant standalone Project card | Inline chip is now the sole presentation | |  |  | **After — rich metadata stays available on hover**  ## Verification At commit `3fa74cdd342ac1f6721b7d56a7f111af31e0e6e9`: - focused link-preview + Markdown unit suites — 119/119 passed - targeted registered smoke E2E — 8/8 passed, including labeled same-relay clone metadata, ordinary-link presentation, and in-app navigation - `cd desktop && pnpm exec tsc --noEmit` — passed - `git diff --check origin/main...HEAD` — passed - pre-push hooks — desktop check, TypeScript, and full desktop unit suite passed --------- Signed-off-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Co-authored-by: Mongo <5398c5fd039b963ce132b3e078e7c4af097dd997517bb5e14c2682fe68c25197@buzz.block.builderlab.xyz> |
||
|
|
074561233e |
fix(composer): preserve caret when inserting mentions mid-message (#6531)
**Category:** fix **User Impact:** Users can insert mentions earlier in a draft and continue typing without the caret corrupting the rest of the message. **Problem:** Caret correction ran after every document change, so typing a mention before existing text repeatedly advanced across the mention separator and interleaved spaces into the draft. **Solution:** Limit correction to the autocomplete settlement it was designed for, with transaction-level and browser-level regression coverage for known and unregistered mentions. <details> <summary>File changes</summary> **desktop/src/features/messages/lib/mentionHighlightExtension.ts** Restricts trailing-space caret advancement to an armed autocomplete settlement instead of every document change. **desktop/src/features/messages/lib/mentionHighlightExtension.test.mjs** Exercises the real ProseMirror plugin state and verifies mid-draft mention typing, unknown tokens, end-of-message typing, and completed-mention separators. **desktop/tests/e2e/mentions.spec.ts** Reproduces the reported composer workflow in Chromium and covers the same corruption path for an unregistered `@token`. </details> ## Reproduction steps 1. Open a channel and enter `hello world` in the composer. 2. Move the caret between `hello` and ` world`. 3. Type ` @bo`, select `bob` from autocomplete, and continue typing `abc`. 4. Confirm the composer reads `hello @bob abc world` with the caret after `abc`. 5. Repeat with an unregistered token such as ` @zzq` and confirm the existing text remains intact. ## Before / After | Before | After | | --- | --- | | Typing after a mid-draft mention walks the caret through the existing message. | Continued typing stays after the inserted mention. | |  |  | --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Mongo <5398c5fd039b963ce132b3e078e7c4af097dd997517bb5e14c2682fe68c25197@buzz.block.builderlab.xyz> |
||
|
|
f794216737 |
chore(deps): update rust crate async-trait to v0.1.92 (#6094)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [async-trait](https://redirect.github.com/dtolnay/async-trait) | dependencies | patch | `0.1.91` → `0.1.92` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Release Notes <details> <summary>dtolnay/async-trait (async-trait)</summary> ### [`v0.1.92`](https://redirect.github.com/dtolnay/async-trait/releases/tag/0.1.92) [Compare Source](https://redirect.github.com/dtolnay/async-trait/compare/0.1.91...0.1.92) - Resolve double\_must\_use clippy lint in generated code ([#​303](https://redirect.github.com/dtolnay/async-trait/issues/303)) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4yOS41IiwidXBkYXRlZEluVmVyIjoiNDQuMzkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> |
||
|
|
2d93ea0955 |
chore(deps): update dependency sonner to v2.0.8 (#6093)
This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [sonner](https://sonner.emilkowal.ski/) ([source](https://redirect.github.com/emilkowalski/sonner)) | [`2.0.7` → `2.0.8`](https://renovatebot.com/diffs/npm/sonner/2.0.7/2.0.8) |  |  | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Release Notes <details> <summary>emilkowalski/sonner (sonner)</summary> ### [`v2.0.8`](https://redirect.github.com/emilkowalski/sonner/compare/v2.0.7...ecce1841c55e4a72dfe139a8992b56498660125e) [Compare Source](https://redirect.github.com/emilkowalski/sonner/compare/v2.0.7...v2.0.8) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4yOS41IiwidXBkYXRlZEluVmVyIjoiNDQuMzkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> |
||
|
|
9390e11c9b |
chore(deps): update rust crate http-body-util to v0.1.4 (#5452)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [http-body-util](https://redirect.github.com/hyperium/http-body) | dependencies | patch | `0.1.3` → `0.1.5` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Release Notes <details> <summary>hyperium/http-body (http-body-util)</summary> ### [`v0.1.5`](https://redirect.github.com/hyperium/http-body/compare/http-body-util-v0.1.4...http-body-util-v0.1.5) [Compare Source](https://redirect.github.com/hyperium/http-body/compare/http-body-util-v0.1.4...http-body-util-v0.1.5) ### [`v0.1.4`](https://redirect.github.com/hyperium/http-body/releases/tag/http-body-util-v0.1.4) [Compare Source](https://redirect.github.com/hyperium/http-body/compare/http-body-util-v0.1.3...http-body-util-v0.1.4) #### What's Changed - Add `Fused` body combinator that always returns `None` once completed. - Add `BodyExt::into_stream()` to convert a body into a `Stream`. - Add `Full::into_inner()` to get the full `Buf`. - Add `InspectFrame` and `InspectErr` combinators. </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMi4wIiwidXBkYXRlZEluVmVyIjoiNDQuMzkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> |
||
|
|
1a0a27d358 |
chore(deps): update rust crate http to v1.4.2 (#5451)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [http](https://redirect.github.com/hyperium/http) | dependencies | patch | `1.4.0` → `1.4.2` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Release Notes <details> <summary>hyperium/http (http)</summary> ### [`v1.4.2`](https://redirect.github.com/hyperium/http/blob/HEAD/CHANGELOG.md#142-June-8-2026) [Compare Source](https://redirect.github.com/hyperium/http/compare/v1.4.1...v1.4.2) - Fix `uri::Builder` to allow `"*"` as the path when scheme and authority are also set, used in HTTP/2 requests. - Fix `Uri` to properly reject `DEL` characters. ### [`v1.4.1`](https://redirect.github.com/hyperium/http/blob/HEAD/CHANGELOG.md#141-May-25-2026) [Compare Source](https://redirect.github.com/hyperium/http/compare/v1.4.0...v1.4.1) - Fix `PathAndQuery::from_static()` and `from_shared()` to reject inputs that do not start with `/`. - Fix `Extend` for `HeaderMap` to clamp max size hint and not overflow. - Fix `header::IntoIter` that could use-after-free if the generic value type could panic on drop. - Fix `header::{IterMut, ValuesIterMut}` to not violate stacked borrows. </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMi4wIiwidXBkYXRlZEluVmVyIjoiNDQuMzkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> |
||
|
|
d874d67c11 |
chore(deps): update rust crate futures-util to v0.3.33 (#5448)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [futures-util](https://rust-lang.github.io/futures-rs) ([source](https://redirect.github.com/rust-lang/futures-rs)) | dependencies | patch | `0.3.32` → `0.3.34` | | [futures-util](https://rust-lang.github.io/futures-rs) ([source](https://redirect.github.com/rust-lang/futures-rs)) | workspace.dependencies | patch | `0.3.32` → `0.3.34` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Release Notes <details> <summary>rust-lang/futures-rs (futures-util)</summary> ### [`v0.3.34`](https://redirect.github.com/rust-lang/futures-rs/blob/HEAD/CHANGELOG.md#0334---2026-08-11) [Compare Source](https://redirect.github.com/rust-lang/futures-rs/compare/0.3.33...0.3.34) - Preserve cloned waker identity. ([#​3032](https://redirect.github.com/rust-lang/futures-rs/issues/3032)) - Updato `syn` to 3. ([#​3028](https://redirect.github.com/rust-lang/futures-rs/issues/3028)) ### [`v0.3.33`](https://redirect.github.com/rust-lang/futures-rs/blob/HEAD/CHANGELOG.md#0333---2026-07-18) [Compare Source](https://redirect.github.com/rust-lang/futures-rs/compare/0.3.32...0.3.33) - Fix `ReadLine`'s soundness issue regarding to exception safety. ([#​3020](https://redirect.github.com/rust-lang/futures-rs/issues/3020)) - Fix unsound `Send` impl for `IterPinRef` and `Iter`. ([#​3003](https://redirect.github.com/rust-lang/futures-rs/issues/3003)) - Fix stacked borrows violation in `compat01as03` implementation. ([#​3012](https://redirect.github.com/rust-lang/futures-rs/issues/3012)) - Fix memory leak in `FuturesUnordered::IntoIter`. ([#​3005](https://redirect.github.com/rust-lang/futures-rs/issues/3005)) - Add `portable-atomic-alloc` feature and use it in `FuturesUnordered`. ([#​3007](https://redirect.github.com/rust-lang/futures-rs/issues/3007)) - Re-export `alloc::task::Wake`. ([#​3010](https://redirect.github.com/rust-lang/futures-rs/issues/3010)) - Update `spin` to 0.12. ([#​3014](https://redirect.github.com/rust-lang/futures-rs/issues/3014)) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMi4wIiwidXBkYXRlZEluVmVyIjoiNDQuMzkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> |
||
|
|
2cef92df67 |
chore(deps): update rust crate futures to v0.3.33 (#5445)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [futures](https://rust-lang.github.io/futures-rs) ([source](https://redirect.github.com/rust-lang/futures-rs)) | dev-dependencies | patch | `0.3.32` → `0.3.34` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Release Notes <details> <summary>rust-lang/futures-rs (futures)</summary> ### [`v0.3.34`](https://redirect.github.com/rust-lang/futures-rs/blob/HEAD/CHANGELOG.md#0334---2026-08-11) [Compare Source](https://redirect.github.com/rust-lang/futures-rs/compare/0.3.33...0.3.34) - Preserve cloned waker identity. ([#​3032](https://redirect.github.com/rust-lang/futures-rs/issues/3032)) - Updato `syn` to 3. ([#​3028](https://redirect.github.com/rust-lang/futures-rs/issues/3028)) ### [`v0.3.33`](https://redirect.github.com/rust-lang/futures-rs/blob/HEAD/CHANGELOG.md#0333---2026-07-18) [Compare Source](https://redirect.github.com/rust-lang/futures-rs/compare/0.3.32...0.3.33) - Fix `ReadLine`'s soundness issue regarding to exception safety. ([#​3020](https://redirect.github.com/rust-lang/futures-rs/issues/3020)) - Fix unsound `Send` impl for `IterPinRef` and `Iter`. ([#​3003](https://redirect.github.com/rust-lang/futures-rs/issues/3003)) - Fix stacked borrows violation in `compat01as03` implementation. ([#​3012](https://redirect.github.com/rust-lang/futures-rs/issues/3012)) - Fix memory leak in `FuturesUnordered::IntoIter`. ([#​3005](https://redirect.github.com/rust-lang/futures-rs/issues/3005)) - Add `portable-atomic-alloc` feature and use it in `FuturesUnordered`. ([#​3007](https://redirect.github.com/rust-lang/futures-rs/issues/3007)) - Re-export `alloc::task::Wake`. ([#​3010](https://redirect.github.com/rust-lang/futures-rs/issues/3010)) - Update `spin` to 0.12. ([#​3014](https://redirect.github.com/rust-lang/futures-rs/issues/3014)) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMi4wIiwidXBkYXRlZEluVmVyIjoiNDQuMjkuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> |
||
|
|
f84511c169 |
chore(deps): update dependency @tauri-apps/api to v2.11.1 (#5444)
This PR contains the following updates:
| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@tauri-apps/api](https://redirect.github.com/tauri-apps/tauri) |
[`2.11.0` →
`2.11.1`](https://renovatebot.com/diffs/npm/@tauri-apps%2fapi/2.11.0/2.11.1)
|

|

|
---
> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/1) for more information.
---
### Release Notes
<details>
<summary>tauri-apps/tauri (@​tauri-apps/api)</summary>
###
[`v2.11.1`](https://redirect.github.com/tauri-apps/tauri/releases/tag/%40tauri-apps/api-v2.11.1):
@​tauri-apps/api v2.11.1
[Compare
Source](https://redirect.github.com/tauri-apps/tauri/compare/@tauri-apps/api-v2.11.0...@tauri-apps/api-v2.11.1)
<details>
<summary><em><h4>PNPM Audit</h4></em></summary>
```
No known vulnerabilities found
```
</details>
#### \[2.11.1]
##### Enhancements
-
[`916782601`](
|
||
|
|
9008a4d172 |
chore(deps): update ubuntu:24.04 docker digest to 561618e (#5442)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [ubuntu](https://hub.docker.com/_/ubuntu) ([source](https://git.launchpad.net/cloud-images/+oci/ubuntu-base)) | container | digest | `4fbb8e6` → `561618e` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMi4wIiwidXBkYXRlZEluVmVyIjoiNDQuMjkuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> |
||
|
|
694d804b24 |
chore(deps): update swatinem/rust-cache digest to 6323deb (#5441)
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [Swatinem/rust-cache](https://redirect.github.com/Swatinem/rust-cache)
([changelog](
|
||
|
|
97b1fee5c3 |
fix(desktop): restore true zoom by scaling the root rem (#6514)
## Summary Follow-up to #5644. Cmd +/- had become a text-only zoom: type scaled while rem-based padding, gaps, widths, avatars, and controls stayed frozen, which produced cramped layouts (see [#buzz-frontend thread](buzz://message?channel=a410ffde-c61f-416a-96e0-c296b5f5ecc9&id=1a758115cf07b00c097f6e988553908c045165325a57637519cfa7ed9c9accec)). Root cause: #5644 introduced a virtual typography rem so the **Font size** preference could change text without moving layout — a good decoupling — but it also routed **Cmd +/- zoom** through that same px-valued token and pinned the real root at 16px. One decision ("freeze layout") was applied to two dials that shouldn't share it. This PR gives each dial one owner and lets CSS compose them: | Control | Changes | How | |---|---|---| | **Cmd +/- zoom** | Everything — true zoom | Scales the real `<html>` font-size again (`useWebviewZoomShortcuts`) | | **Font size preference** | Text only | Sets `data-font-size`; `typography.css` maps it to a unitless `--buzz-type-scale`, mirroring how density already works | `--buzz-type-rem` becomes `calc(1rem * var(--buzz-type-scale))` — rem-relative, so it rides on zoom automatically. Resulting text px = `16 × zoom × scale × token-ratio`. The 13 / 14 / 15px conversation contract is unchanged at default zoom. Density and the type ramp from #5644 are untouched. The preference module no longer does px math or knows about zoom; the zoom hook no longer imports the preference module. Net deletion in production code. ## Validation - `pnpm test` — 5,308 desktop unit tests - `pnpm check:px-text`, `tsc --noEmit`, biome - Playwright: `top-chrome-zoom-clearance.spec.ts` (native-chrome clearance stays fixed under root zoom), `inbox-refactor-screenshots.spec.ts` (zoomed row padding now asserts `4.4px` instead of the frozen `4px`), and both `profile.spec.ts` zoom tests (composed zoom × preference, cross-window storage reset) - Before/after screenshots at 140% zoom in the comment below --------- Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
eb484387d5 |
chore(desktop): drop unused ORIGINAL_CONTENT from empty-edit-delete spec (#6517)
`biome check` fails with `lint/correctness/noUnusedVariables` on `ORIGINAL_CONTENT` in `desktop/tests/e2e/empty-edit-delete.spec.ts`, which fails `pnpm check` (Desktop Core) for **every PR touching desktop paths** — e.g. it currently blocks #6460. It presumably landed while Desktop Core was path-skipped on the introducing PR. One-line removal; the constant has no remaining references (the assertions use `RENDERED_ORIGINAL_CONTENT`). Signed-off-by: Max Lampert <maxwell@squareup.com> |
||
|
|
0e48ff2691 |
feat(workflows): clarify workflow setup and activation (#6470)
**Category:** new-feature **User Impact:** Workflow authors can build filtered, runtime-aware automations, understand them at a glance, and get a clear warning before turning on workflows likely to run often. **Problem:** Workflow setup exposed raw configuration without enough help composing message templates, filtering triggers, or understanding saved behavior; activation could also make a broadly triggered workflow live without explaining its likely frequency. **Solution:** Batch 3 adds local, deterministic template variables, trigger filters, and semantic summaries, then refines cards and activation around configured behavior and a risk-aware warning boundary. Scheduling remains the already-shipped implementation, advanced expressions remain lossless, and network-backed identity/message enrichment stays in Batch 4. | Message inputs | Trigger filters | | --- | --- | | Caret-aware, keyboard-accessible suggestions expose trigger-local values and safe prior-step outputs in `send_message.text`. | Structured conditions and validated manual IDs block invalid submission while preserving advanced expressions. | |  |  | | Workflow cards | Risk-aware activation | | --- | --- | | Semantic labels, channel-first hierarchy, configured reaction/action visuals, real step stacks, and compact status controls make behavior scannable. | Broad message and frequent schedule triggers explain the risk before **Turn on**; narrowly scoped triggers proceed without unnecessary ceremony. | |  |  | ## Changes <details> <summary>File changes</summary> **desktop/src/features/workflows/ui/WorkflowActionsMenu.tsx** Separates direct card status controls from secondary actions while retaining modal status actions. **desktop/src/features/workflows/ui/WorkflowCard.tsx** Adds semantic behavior, channel-first hierarchy, configured reaction/action visuals, real subsequent-step stacks, status controls, and reduced-motion-aware trigger feedback. **desktop/src/features/workflows/ui/WorkflowDialog.tsx** Warns before activating broadly triggered workflows while allowing narrowly scoped workflows to proceed directly. **desktop/src/features/workflows/ui/WorkflowFormBuilder.tsx** Connects structured trigger filters and template-aware step inputs while preserving schedules, trigger transitions, and selected YAML authority. **desktop/src/features/workflows/ui/WorkflowStepCard.tsx** Replaces generic labels with deterministic configured-step descriptions. **desktop/src/features/workflows/ui/WorkflowTemplateTextarea.tsx** Adds caret-aware variable suggestions with keyboard navigation and focus restoration. **desktop/src/features/workflows/ui/WorkflowTriggerConditions.tsx** Adds structured local filters, validated author/message IDs, and a lossless advanced-expression fallback. **desktop/src/features/workflows/ui/workflowActivationWarning.ts** and **workflowActivationWarning.test.mjs** Classify broad message and frequent schedule triggers for contextual activation warnings. **desktop/src/features/workflows/ui/workflowConditionExpression.ts** and **workflowConditionExpression.test.mjs** Model and cover parsing, serialization, validation, and advanced-expression preservation. **desktop/src/features/workflows/ui/workflowDefinition.ts** and **workflowDefinition.test.mjs** Preserve trigger/step configuration and derive deterministic card metadata across YAML round trips. **desktop/src/features/workflows/ui/workflowStepDescription.ts** and **workflowStepDescription.test.mjs** Generate and cover local step summaries. **desktop/src/features/workflows/ui/workflowTemplateVariables.ts** and **workflowTemplateVariables.test.mjs** Define and cover trigger-specific, order-bounded variables and caret insertion. **desktop/src/features/workflows/ui/workflowTriggerDescription.ts** and **workflowTriggerDescription.test.mjs** Generate and cover semantic trigger summaries without network lookups. **desktop/tests/e2e/workflow-local-controls.spec.ts** and snapshot Cover filters, IDs, advanced expressions, autocomplete, activation choices, summaries, and YAML authority. **desktop/tests/e2e/workflow-reaction-picker.spec.ts** Covers configured reaction emoji in workflow nodes and summaries. **desktop/tests/e2e/workflows.spec.ts** Covers risk-aware activation warnings, direct safe creation, duplication, and card status controls. </details> ## Reproduction steps 1. Create a message-posted workflow in **Workflows**, add a Send message step, and type `{{trig`; verify keyboard-selectable variables insert at the caret. 2. Configure message-text and manual ID filters; verify malformed IDs block submission and advanced expressions survive Form/YAML transitions. 3. Create a broad message workflow; verify **Back** persists nothing, **Keep off** saves it disabled, and **Turn on** enables it. Confirm a narrowly triggered webhook skips the warning. 4. Inspect the saved card; verify its channel, semantic behavior, configured actions/reaction, real step stack, and status are understandable without opening YAML. ## Validation Validated at exact clean head `f99503819889b95ee3c61657c5c3850aae35481e` on base `24ec6a468ec9d0d425ee58fbfc4d416412c446ad`. - Focused workflow regressions passed 59/60 locally; the only local miss was a 438-pixel macOS snapshot drift, while the checked-in Linux baseline comes from the failing CI artifact. Repository pre-push gates and E2E build/typecheck passed. - A broader 36-test smoke invocation had 31 passes and five unrelated pre-existing expectation/snapshot failures, so it is not claimed as fully green. Adversarial fixes are recorded in [round one](https://github.com/block/buzz/pull/6470#issuecomment-5366366670) and [round two](https://github.com/block/buzz/pull/6470#issuecomment-5366407535). --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: Mongo <5398c5fd039b963ce132b3e078e7c4af097dd997517bb5e14c2682fe68c25197@buzz.block.builderlab.xyz> |
||
|
|
b85d680fb1 |
perf(desktop): stop the Projects fan refetching on re-entry and running after leave (#6458)
Entering Projects fires a large fan: an exhaustive paginated relay enumeration (projects/repos/tombstones), five 2,000-event work-item queries plus assignment-operation scans, per-repo activity summaries, and a local-repository filesystem scan. Measured on a large community (101 issues / 258 PRs): | query | measured cost | |---|---| | work-items (5 × 2,000-event REQs + assignment scans) | 3.5–3.9s | | activity summaries | 4.1s | | repository activity | 1.0–2.2s | | local repository scan | 1.7s | Two lifecycle bugs made the fan far more expensive than it needs to be: - **Freshness windows guaranteed a full refetch on nearly every re-entry** (60s enumeration, 30s work-items/activity, 10s local scan) — i.e., the costs above were re-paid on almost every visit. Every local write path already invalidates its keys explicitly (issue/PR mutations, project creation, repo sync), so the short windows only served remote-actor freshness. Raised to 5m/2m/2m with a 30m enumeration cache: re-entries now paint from cache, and the fan re-runs at most every 2–5 minutes. - **Leaving Projects left the whole fan running**, competing with the next surface's channel fetches on the same relay connection. AbortSignal is now threaded through the enumeration and assignment pagination loops (optional params — behavior identical without a signal), and leaving the surface cancels the work-items query. Deliberately NOT cancelled: the enumeration (the always-mounted sidebar projects section observes it and its 30m cache is valuable), repo snapshots and local scans (native work that can't abort — cancelling would discard the finished result and force the same clones again), and activity summaries (a single bounded request). Abort behavior is covered by red-first unit tests on both pagination loops. Remaining follow-up (out of scope): the queries themselves want a relay-side aggregate instead of shipping thousands of events to compute counts client-side. --------- Signed-off-by: Max Lampert <maxwell@squareup.com> |
||
|
|
b0466ac465 |
perf(desktop): keep the member roster off the channel-switch path (#6456)
Switching channels triggered a full-roster fetch (kind:39002 plus a kind:0 profile batch with every member pubkey as an author) in the common case, and several render paths walked the full roster per render. None of this scales past a few hundred members; the product target is 10k+. - **Members query staleTime 30s → 5min.** Every membership change the client can observe already invalidates the key explicitly: live join/leave system messages for the active channel, member-added/removed notifications for the current identity, and all membership mutations — including previously-uncovered direct write paths (moderation kick, agent-deletion cleanup), which now invalidate through a shared helper. The 30s window bought correctness we already had and charged a roster fetch per switch. - **ChannelMembersBar no longer mounts the roster query for non-DM channels** — the count renders from the channel summary, and the private-channel huddle gate accepts `channel.isMember` (derived from the same kind:39002 event as the roster's self entry). - **Roster-derived lookups are cached on roster identity** (`rosterDerivations.ts`): role map, agent-member subset, member/bot pubkey sets. These were rebuilt O(members) on every live message / profile re-key. React Query's structural sharing keeps the roster identity stable, so each derivation computes once per distinct roster. - **Backend: the kind:0 profile join in `get_channel_members` is capped at the first 500 members** (roster order). Members past the cap keep `display_name: None` (UI falls back to pubkey labels and profile caches); `role=="bot"` agent flags are roster-derived and unaffected. Full roster pagination is the structural follow-up. - **Composer keystroke path**: `useCanAddChannelMembers` re-scanned channels + roster per keystroke; now memoized on data identities, sharing the cached pubkey set. ### Measured / estimated impact | metric | before | after | |---|---|---| | roster fetches while switching (live trace) | nearly every switch | ≤1 per channel per 5 min | | roster fetch cost on the wire (live, 51-member channel) | 273ms per fetch | amortized away | | kind:0 `authors` filter size at 10k members | ~670KB per request (~67 B/pubkey) | capped at 500 authors (~34KB) | | warm-switch longtask at 10k members (mock harness, 4× throttle) | 364ms | 318ms | | per-render roster walks (role map, agent sets) at 10k members | O(members) per live message | once per distinct roster | Deferred deliberately: protocol-level roster pagination and removing `memberPubkeys` from channel summaries (needs relay support). --------- Signed-off-by: Max Lampert <maxwell@squareup.com> |
||
|
|
47526784d5 |
fix(deletion): allow IRSA S3 credentials (#6495)
## Why `buzz-admin deletions` runs inside bb-public relay pods, where S3 credentials are provided by the pod credential chain rather than static `BUZZ_S3_ACCESS_KEY` / `BUZZ_S3_SECRET_KEY` values. The deletion CLI was requiring those two env vars to be non-empty before constructing the shared media storage client, so it could not reach the existing default AWS credential chain. ## What - Treat missing/blank deletion S3 access key and secret key as an empty credential pair so `MediaStorage::new` can use `Credentials::default()`. - Keep the existing static-credential path unchanged when both values are non-empty. - Keep deletion unit tests deterministic by covering only deletion env normalization for missing/blank pair, trimmed static pair, and partial/whitespace-partial outputs; shared media tests continue to own credential-pair enforcement. ## Risk Assessment Low and scoped to the operator-only community deletion CLI startup path. The shared media storage credential validation still owns static-vs-default credential selection and still rejects mixed partial credentials. ## Testing At committed head `0a86c2914b1f97caf4788a771048aa8d9d9d88ac` with a clean worktree before and after (`git rev-parse HEAD` before/after matched): - `just fmt-check` — passed. - `cargo test -p buzz-deletion` — passed: 12 passed, 9 ignored. - `cargo test -p buzz-media` — passed: 120 passed; `static_creds_round_trip_against_minio` remained ignored because it requires live MinIO. - `cargo test -p buzz-admin` — passed: 1 passed. - `cargo clippy -p buzz-deletion --all-targets -- -D warnings` — passed. - Startup smoke at the same head: built `buzz-admin`, then ran `target/debug/buzz-admin deletions drain` with `BUZZ_S3_ACCESS_KEY=` and `BUZZ_S3_SECRET_KEY=' '` plus `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` fallback credentials; command exited `0`, proving startup transitions past deletion S3 key validation and exercises the shared default credential-chain branch using AWS env fallback credentials. - `git push origin HEAD:seiler/deletion-irsa-credentials` — passed; pre-push hooks passed. Not run: the full `TESTING.md` live-local relay workflow. Docker Desktop currently refuses CLI access on this machine with `Sign in to continue using Docker Desktop. Membership in the [squareup] organization is required.` ## References - Buzz channel: `buzz://message?channel=9e4aabc6-414c-4978-aba7-b9f5228776de&id=177c5b8ad9e78a647f438ec7040d25f0c20a7c2b8678dd0e34768effe053f7f4` Generated with Codex --------- Signed-off-by: coder 0 <d97ebdbb198c7237c94f84ea8bb8a73583ea067407eebd0062abbb3962527fb1@buzz.block.builderlab.xyz> Co-authored-by: coder 0 <d97ebdbb198c7237c94f84ea8bb8a73583ea067407eebd0062abbb3962527fb1@buzz.block.builderlab.xyz> |
||
|
|
d97780b477 |
docs(nips): comprehensive NIP-FI — core + claimable profiles (EDGE/LIFECYCLE/DELEG/CONF) (#5946)
## What Comprehensive NIP-FI against `main`: one normative core plus four separately claimable profiles, replacing the single-document structure of #3726 (which was based on #1485's branch, not `main`). Six documents, 1,975 lines, docs only. - **`NIP-FI.md` (core, 632 lines)** — issuer-qualified identity `(iss, sub)`, independent Nostr proof, client-attached assertion, partial bijection with durable tombstones, atomic final admission, bounded leases, private denials with a closed response vocabulary, retire/revoke/rotate, two contract identities (`assertion_policy_id`, `transport_contract_id`), per-policy `skew` / `maximum_assertion_age` / `maximum_status_age` with missing configuration denying, a closed token-class rule (`at+jwt`, `nip-fi+jwt`, named compatibility; ID tokens always deny), declared freshness class (`offline-jwt` | `current-status`), server-declared body authorization relevance (NIP-98 payload-binding fix, including a `payload` tag on an irrelevant-body operation), BCP 14, "equivalent" defined over identity / bounds / provenance classes, a compact non-normative worked wire example, and a non-normative comparison with DPoP, mTLS-bound tokens, and HTTP Message Signatures. FI-INV-01..16 are normative core text. The behavioral-oracle table lists exactly 30 oracle IDs, one per row, with no shorthand. - **`NIP-FI-EDGE.md`** — trusted-edge surface: the `trusted-proxy-hmac-v2` envelope + canonicalization, or a private authenticated-edge adapter under a reviewed contract; `authorization_domain_id` derivation (exact 16 RFC 9562 UUID bytes); `proof_transport_code` registry (0x01 NIP-42, 0x02 NIP-98; 0x03 Git smart-HTTP and 0x04 Blossom reserved pending their transport contracts; 0x05–0x7f unassigned pending published stable specifications) + extension procedure; body-acquisition bounds; three normative test vectors. An independent Nostr proof (the NIP-98 event in `Authorization`, which reaches the verifier byte-identical, or the NIP-42 event after connect) is the only decision input outside the MAC; absent or incomplete provenance on an edge-required route is `missing_evidence`, present-but-failing provenance is `evidence_rejected`. Header-trust-without-provenance is nonconformant. - **`NIP-FI-LIFECYCLE.md`** — provision / disable / re-enable / administrative expiry (`binding_not_after`) / pending-replacement lineage, one conformance trace per privileged transition; every binding-creating transition declares whether it continues or establishes a grant; a private-condition table for CONF enumeration agreement. - **`NIP-FI-DELEG.md`** — delegated agents; explicit temporal boundaries matching core's inclusive-`nbf`/exclusive-`exp` idiom, with the delegated `skew` configured by this profile; lease deadline anchored to the lease issue instant; strict path separation — a delegated request carries no assertion or provenance field, so it cannot traverse an edge-provenance route and uses ingress on which NIP-FI-EDGE is not required. - **`NIP-FI-CONF.md`** — conformance evidence: an immutable claim tuple including the governing document revision and exit fixture digest; the complete 16-row denial-fixture enumeration with three mechanical enumeration-agreement checks; mutation adequacy with a countable denominator — one retained killed mutant per literal oracle-table row (30 core + 6 EDGE + 11 LIFECYCLE + 7 DELEG + 4 CONF = 58), rows selected structurally by their first cell, never by section title, with the release gate and CONF's own oracle rows stated in the same listed-oracle terms and a mutant defined for CONF's own report- and suite-subject oracles; an interoperability exit test compared over signing inputs (per-transport NIP-01 serialization for the NIP-98 and NIP-42 proofs; decoded protected header and claims as JSON values for the assertion), with a shared exit fixture pinning complete pre-signature header/claim JSON and complete unsigned event fields for both transports, and mandatory negative controls. `FI-CONF-INTEROP-EXIT` is `deferred` with reason `no-independent-implementation` until a second independent implementation exists; the canonical fixture is editor-authored at `docs/nips/fixtures/nip-fi-conf-exit.json` and is **not in this PR** — until it is published a claim records `pending-canonical-fixture`, valid only while the exit test is deferred. Explicit not-applicable dispositions, including `offline-jwt` deployments for the two current-status oracles and absence of a revocation-bounded external capability projection for `FI-TRACE-CAPABILITY-REVOCATION`. - **`NIP-FI-MODEL.md`** — non-normative companion; defines no requirement or conformance claim and is not claimable. ## Why The prior draft rated 9 (soundness) / 6 (minimalness) / 7 (elegance) / 7 (correctness) in adversarial + comparative review. This restructure keeps the two-invariant spine untouched, makes everything else a claimable profile, and collapses five stacked versioning mechanisms into two contract identities. Mutation adequacy counts one mutant per literal oracle-table row — a set two implementers enumerate identically — instead of "each normative requirement," which had four defensible readings. Resolved product calls (owner-approved): 1. Enrollment/denial posture is private — boolean enrollment discovery, TOFU extension claim not self-advertised, `key_mismatch → authorization_denied` joins the denial anonymity set, and replayed evidence is classed `authorization_denied` so resubmission reveals nothing about commit. 2. Revocation honesty — only `current-status` deployments may advertise an unconditional residual-revocation bound; `offline-jwt` advertises unbounded/unknown. Access tokens keep RFC 9068 `at+jwt`; `nip-fi+jwt` is reserved for a separately minted Buzz assertion. ## Acceptance bar - Nothing in core is deletable without losing a stated core guarantee. - From the core document plus the CONF exit fixture, a second implementer can produce a valid request equal over the request compared object (signing inputs), and a byte-exact public denial per class — no reference implementation. - Every oracle-table row ships a retained killed mutant satisfying only the entry it was selected for. - Both deployment profiles (trusted proxy = EDGE, client-held OIDC = core client-attached) pass the same lifecycle conformance suite. ## Status Ready at head |