Commit Graph
2298 Commits
Author SHA1 Message Date
Cea Stapleton Cordasco fcec9ede79 fix(desktop): remove stale Arc import
Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-12 11:20:43 -05:00
Cea Stapleton Cordasco 47f52cbeb1 refactor(desktop): extract app run events from lib.rs to satisfy the file-size gate
Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-12 11:20:43 -05:00
Cea Stapleton Cordasco 539e5a2677 feat(desktop): consume current binding status
Cumulative PR layer #4987. Installs the reviewed native and desktop current-binding status consumer, reconciles public dependency locks, preserves provider application, retires profile-derived trust presentation, and co-commits exact WebSocket operations with stable same-actor status sessions.

Neighbor disclosure: the core status contract remains owned by #4790 and relay production remains owned by #4847; this layer intentionally retires the legacy profile presentation replaced by connection-scoped status.

Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-12 11:20:42 -05:00
Cea Stapleton Cordasco 738838422c ci(db): run authorization migration suites
Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-12 11:20:14 -05:00
Cea Stapleton Cordasco 92fca313fa fix(config): default delegation to disabled
Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-12 11:20:14 -05:00
Cea Stapleton Cordasco b804231f27 fix(db): align desired schema bootstrap
Install pgcrypto before repository-owned desired-schema apply paths and retain the exact extension declaration in the desired schema. Distinguish the intermediate Invitation allocation from the final active object-kind catalog.

Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-12 11:20:14 -05:00
Cea Stapleton Cordasco 341a08a428 feat(relay): install provider-neutral authorization runtime
Cumulative PR layer #4847. Installs the reviewed provider-neutral relay authorization runtime, crash-recoverable current-status delivery, protected-ingress production composition, canonical mutation ordering, and exact runtime-contract regressions. A configured required identity gate remains fail-closed when its retired compatibility verifier is unavailable.

Neighbor disclosure: canonical database/operator authority remains owned by the lower layers, while the core status contract is owned by #4790 and native desktop consumption remains in #4987.

Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-12 11:20:13 -05:00
Cea Stapleton Cordasco 5c02ebd4d3 feat(status): integrate current binding contract
Cumulative PR layer #4790. Integrates the reviewed connection-local current-binding core contract.

Neighbor disclosure: this layer extends #4789 restore and event seams and provides the core status contract consumed by #4847; native desktop consumption remains in #4987.

Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-12 11:20:13 -05:00
Cea Stapleton Cordasco 94e85a6743 refactor(db): depend on http directly
Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-12 11:20:13 -05:00
Cea Stapleton Cordasco 78ec243709 feat(auth): integrate invalidation status runtime
Cumulative PR layer #4789. Integrates the reviewed invalidation, resolver, restore, version, protected-publication, and migration status delta.

Neighbor disclosure: protected publication adapters overlap #4783 and preserve its canonical admission effects; status consumption remains in #4790, and relay production/operator activation remains in #4847.

Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-12 11:20:13 -05:00
Cea Stapleton Cordasco 81385d4f1a fix(auth): use composed proxy assertion fixtures
Restore the generated assertion fixture at the four stale test references without changing trusted-proxy provenance semantics.

Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-12 11:20:12 -05:00
Cea Stapleton Cordasco 0ca9eed2de fix(auth): integrate protected admission effects
Cumulative PR layer #4783. Integrates the reviewed protected admission effects, matching deployment schema, and single-flight publication regressions.

Neighbor disclosure: canonical lifecycle admission remains owned by #4772; invalidation and restore remain in #4789; live relay production composition remains in #4847.

Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-12 11:20:12 -05:00
Cea Stapleton Cordasco f6363842c5 test(db): seed lifecycle audit fixtures
Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-12 11:20:12 -05:00
Cea Stapleton Cordasco 1c34c22603 feat(auth): integrate canonical lifecycle admission
Cumulative PR layer #4772. Integrates canonical lifecycle admission, replay and projection schema repairs, runtime-generated authorization fixtures, desired-schema regeneration, canonical empty-upload support, and exact lifecycle audit regressions.

Neighbor disclosure: protected route effects remain in #4783; invalidation and restore remain in #4789; relay production wiring remains in #4847.

Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-12 11:20:12 -05:00
Cea Stapleton Cordasco 200768dafe fix(auth): raise bounded audit capacity
Cumulative PR layer #3564. Raises the reviewed hard audit ceilings and retains the exact successor-boundary regression.

Neighbor disclosure: the provider-neutral foundation and public dependency sources remain owned by #1476; lifecycle and canonical admission remain in #4772.

Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-12 11:20:11 -05:00
Cea Stapleton Cordasco 1f5cc1071f fix(desktop): remove stale profile import
Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-12 11:19:53 -05:00
Cea Stapleton Cordasco ec30bcb55e chore: merge current main into authorization stack
Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-12 10:31:24 -05:00
63f961c7e4 Refine channel settings and profile panels (#5574)
## Summary

- simplify channel settings into concise detail, member, canvas, and
action sections
- align human and agent profiles around shared rows, segmented tabs, and
top-level actions
- add agent runtime presentation, sticky glass behavior, and
scroll-linked action transitions

## Snapshots

### Channel settings

![Channel
settings](https://raw.githubusercontent.com/block/buzz/72d3958374a6f31c0f15912a8307101415eed084/pr-5574--01-channel-settings.png)

### Agent info

![Agent
info](https://raw.githubusercontent.com/block/buzz/72d3958374a6f31c0f15912a8307101415eed084/pr-5574--02-agent-info.png)

### Agent runtime

![Agent
runtime](https://raw.githubusercontent.com/block/buzz/72d3958374a6f31c0f15912a8307101415eed084/pr-5574--03-agent-runtime.png)

## Validation

- `pnpm -C desktop check`
- `pnpm -C desktop test` (4,604 passed)
- `pnpm -C desktop build:e2e`
- focused channel settings and agent profile Playwright tests

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@buzz.block.builderlab.xyz>
2026-08-12 08:25:04 -07:00
c966b862fe fix(deps): bump webbrowser to 1.2.4 for RUSTSEC-2026-0257 (#5659)
## What

Bumps `webbrowser` from `1.2.1` to `1.2.4` in both lockfiles
(`Cargo.lock` and `desktop/src-tauri/Cargo.lock`) to clear
[RUSTSEC-2026-0257](https://rustsec.org/advisories/RUSTSEC-2026-0257).

## Why

The advisory landed in the RustSec DB and flipped the `Security` job
(`cargo-deny check`) red on `main` — the same job passed on identical
lockfile state before the advisory was published. `webbrowser` 1.2.1
substitutes the URL into the Unix `BROWSER` env template *before*
tokenizing, allowing browser argument injection (e.g.
`--remote-debugging-port`). `crates/buzz-agent` calls
`webbrowser::open()` for the OAuth flow
(`crates/buzz-agent/src/auth.rs`) with an internally-constructed HTTPS
URL, so practical exploitability is low, but the gate is correctly
blocking. Fixed in `1.2.2`+.

## Scope

Lockfile-only. The `crates/buzz-agent/Cargo.toml` constraint is already
`webbrowser = "1"`, so no manifest change is needed. `webbrowser` 1.2.4
pulls in `objc2-app-kit` as a new transitive dependency; the
`windows-sys` edge churn re-unifies to versions already present in the
lockfile (no new `windows-sys` version is introduced).

## Verification

- `cargo-deny check` passes locally on the pinned toolchain (`advisories
ok, bans ok, licenses ok, sources ok`); RUSTSEC-2026-0257 no longer
reported in either lockfile.
- `cargo check -p buzz-agent` compiles clean against `webbrowser 1.2.4`.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-12 08:07:16 -07:00
6e0631f6b5 feat(acp): deliver channel description in prompt [Context] (#4552)
Channels carry a kind-39000 `about` description that the harness never
surfaced to agents. This delivers it in the per-turn `[Context]` block
so an agent knows what a channel is for without having to ask.

## What changes

- `relay::ChannelInfo` and `queue::PromptChannelInfo` gain a
`description: Option<String>` field.
- The `about` tag is parsed in both metadata paths: the startup
discovery map (`merge_discovered_channels`) and the lazy
`fetch_channel_info` lookup. Blank or whitespace-only values become
`None`.
- `format_context_hints` renders a `Description:` line under `Channel:`
for channel- and thread-scope turns. DM turns never render it.

## Safety

- The description is newline-collapsed to a single line before
rendering, so a multi-line `about` value can never spoof another
`[Context]` field.
- It is capped at 500 characters on a UTF-8 char boundary, with a `…`
truncation marker.
- Unresolved channel metadata renders no `Description:` line.

Session creation is untouched — the description rides the existing
per-turn `[Context]` block that already carries `Channel:`.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-12 09:42:59 -04:00
1ff98fa685 fix(desktop): launch Databricks OAuth from passive model discovery (#5607)
When a user's agent runtime is `buzz-agent` with no cached Databricks
OAuth token, the desktop app's passive model-discovery surfaces were
forbidden from launching interactive auth. Discovery failed silently, so
the model dropdown showed only built-in fallback models behind a vague
"Could not load live models for `databricks_v2`" note (reported
internally by Nick and Jose).

## What changed

Both discovery surfaces — the passive draft-form discovery and the
explicit saved-model picker — now launch the browser OAuth flow,
matching goose's behavior. The only behavioral difference between them
is cooldown handling:

- **Passive draft discovery** fires on every form-state change, so a
failed, cancelled, or timed-out sign-in records a per-host cooldown (5
min) that suppresses re-popping the browser on the next keystroke. While
the cooldown is active it returns the "sign-in required" guidance
instead of relaunching.
- **The explicit model picker** is a deliberate user action, so it
always launches and clears any stale cooldown first.

Safety rails:

- A 150s hard timeout (`AUTH_FLOW_TIMEOUT`) bounds the whole interactive
flow so an abandoned SSO tab fails discovery cleanly rather than wedging
the dropdown. Success clears the cooldown; failure and timeout both
record it.
- `AuthCooldown` recovers from a poisoned lock rather than wedging every
future sign-in on one panic.

The frontend maps the terminal Databricks sign-in states to typed,
actionable copy in `formatModelDiscoveryErrorStatus`: "sign-in required"
is a muted note pointing at the picker and `buzz-agent auth databricks`;
a failed or timed-out sign-in is a warning pointing at the explicit
retry. Other Databricks failures fall through to the existing generic
notice.

## Scope

Changes are confined to Databricks discovery and its frontend status
formatter — no `agent_models.rs` call sites are touched. The
interactive-auth helper takes an injected timeout so the
timeout/cooldown policy is unit-testable without a live browser.

## Deferred

Cooldown keys use the raw trimmed `DATABRICKS_HOST`, while the catalog
and OAuth cache normalize trailing slashes
(`crates/buzz-agent/src/catalog.rs:96`,
`crates/buzz-agent/src/llm.rs:2046`). So `https://workspace/` and
`https://workspace` share credentials but get separate cooldown entries
— an equivalent-spelling change to the host field mid-cooldown can
re-pop passive OAuth once within the 5-minute window. Self-limiting (one
extra browser launch, never auth corruption). Follow-up: a
`trim_end_matches('/')` on the cooldown key plus an equivalent-host
test, picked up with the coordinator migration if
[#5545](https://github.com/block/buzz/pull/5545) ever merges.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-12 09:35:48 -04:00
4b3570671e chore(release): release Buzz Desktop version 0.5.10 (#5613)
## Buzz Desktop release v0.5.10

- **Frozen main:** `f35930104bcbdb1332ff13735214ecb9fce1fc7b`
- **Reviewed candidate:** `1fb49103002e898607a7f6fd554cb51e94d92e08`
- **Previous desktop release:** `desktop-v0.5.9`
- **Proposed immutable tag:** `desktop-v0.5.10`

This PR may be **squash merged** after the Desktop Release Candidate
check and all protected-branch checks pass. Merging authorizes
publication of the exact reviewed candidate; later or unrelated changes
on `main` cannot alter it.

The checked-in changelog accounts for every non-merge commit in the
release range. The Desktop tag points to the reviewed candidate commit,
not the later squash commit. Publication remains bound to that immutable
candidate tag.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
2026-08-11 17:36:46 -07:00
Will PflegerandGitHub f35930104b fix(desktop): remove 0.5.9+ perf regressions, speed up get_channels (#5599)
Desktop input latency regressed sharply for users on v0.5.9 and worsened
on latest main: multi-second stalls when clicking back into the app,
slow fresh boots, intermittent lockups, and scroll/mouse degradation.
Reverting to `119a84897` (pre-0.5.9) was confirmed to resolve it,
isolating the regression to that range. Profiling a live production
renderer plus a commit-level audit of the range found three independent,
additive causes — fixed here — plus a long-standing `get_channels` cost
that made every remaining refetch expensive, also addressed here.

## 1. Focus-return refetch storm (`refetchOnWindowFocus`)

#5490 wired TanStack's `focusManager` to app focus and flipped ~20 query
sites to `refetchOnWindowFocus: true`. A focus return after >60s away
fires them all within milliseconds — and a click into an unfocused
window *is* a focus return, so the burst runs before the click is
processed. That is the "click into the composer, wait 5 seconds"
symptom, and it also explains why mouse input feels worse than keyboard
(clicks arrive with focus transitions; typing happens while already
focused). A 5-second `sample` of a live production renderer caught a
single window activity-state transition consuming ~1.25s of main-thread
time, dominated by `JSON.parse` in the focus listener's microtask drain.

#5535 already established the fix pattern but applied it to only two
families (channels, home-feed). This PR extends the same 5-minute
`staleTime` discipline to the remaining families: pulse (×5), workflows
(×4), agents (×4), forum (×2), presence, user-status, custom-emoji,
channel-templates, and the persona catalog. Polling cadences and
push-invalidation paths are untouched — interval refetches and
`invalidateQueries` both bypass `staleTime`, so live-update behavior is
unchanged. Each gated family exports its focus-refetch policy as an
options object that the production hook spreads into `useQuery`, and a
`focusRefetchPolicy.test.mjs` drives a `QueryObserver` with that same
production object — locking the policy behaviorally (fresh focus return
→ 0 fetches; stale → refetch) and failing if a hook's
`staleTime`/`refetchOnWindowFocus` wiring drifts.

Four families deliberately keep tighter freshness, all surfaces where
the 5-minute gate would suppress the only refresh path and none of which
feed the app-wide storm: `repo-sync-status` keeps its fresh focus
refetch (its inline comment documents the "committed in a terminal,
switched back to the app" flow as intended); the workflow-runs list
stale-gates at 10s because a remotely-started run has no push
invalidation and its conditional 1s poll is off while the cache shows no
active runs; the workflow list queries (`useChannelWorkflowsQuery` and
the all-channels aggregate) stale-gate at 10s because they have no poll
and no relay subscription, and mutation-driven invalidation only covers
this renderer — remote workflow creates/edits/deletes surface only via
focus refetch; and the managed-agent log stale-gates at one poll tick
(30s) so returning to a live agent log refreshes immediately. Run
approvals keep the 5-minute gate under
`RUN_APPROVALS_FOCUS_STALE_TIME_MS` — their focused 10s poll already
covers freshness.

## 2. Synchronous localStorage sweep on the boot/focus path

#5453's stale-cache sweep synchronously `getItem` + `JSON.parse`s every
whitelisted localStorage entry on the main thread (multi-MB on seasoned
profiles), scheduled with a `requestIdleCallback` timeout of 1.5s that
guaranteed it landed mid-boot, and re-armed on every hidden→visible
transition — stacking it onto the exact moment the focus storm fires.
#5454's `trimSelfProfileCaches()` additionally scanned every
localStorage key on every `writeSelfProfileCache()` call (which fires
per relay self-profile delivery at boot).

Now: the first sweep waits `BOOT_SWEEP_FLOOR_MS` (30s) after startup,
the scan is time-sliced across idle callbacks, and the visibility
trigger is removed — boot-delayed plus hourly still covers the 14-day
TTL contract. The sliced sweep re-checks staleness immediately before
each removal (a key rewritten fresh mid-sweep survives), isolates
per-key storage errors so one bad entry can't strand the rest of the
snapshot, defers oversized values once rather than parsing them on a
zero-budget slice, guarantees forward progress on timeout-fired
callbacks, and cancels its scheduled slice when stopped. The profile
trim keeps a lazily-initialized memoized key count so the common
under-cap write is O(1); the full parse scan runs only when the count
exceeds a cap, resyncs if external deletions made it stale, and a failed
scan skips the trim instead of aborting the write. Sweep semantics
(rules, TTLs, eviction) are unchanged, and tests cover the scheduling,
slice-progress, error-isolation, defer-once, and trim short-circuit
behaviors.

## 3. The macOS window was never opaque

#5478's glass appearance is correctly opt-in at the CSS layer, but the
compositor cost was baked in deeper than its native `on_webview_ready`
transparency call: the main window is declared `"transparent": true` in
`tauri.conf.json` (added for the original glass work in #1671), which
makes tao call `NSWindow.setOpaque(false)` at creation and resolve every
later `set_background_color(None)` to `clearColor` — and no runtime
`setOpaque(true)` path exists through tauri, while wry's runtime
background setter can only force the WKWebView's `drawsBackground` off,
never back on. So "restore the platform default" was unreachable: every
launch, glass or not, ran with a non-opaque NSWindow, defeating
WindowServer's opaque-window compositing fast path and forcing full
window compositing every frame — compounded by the existing
`backdrop-blur` chrome overlapping the scrolling timeline. This matches
the compositor-shaped symptoms (scroll and pointer input degrading
first).

The window is now created opaque (`"transparent": false`) and the
NSWindow layer is never made transparent at runtime. Glass never needed
a transparent window: behind-window `NSVisualEffectView` vibrancy
renders inside opaque windows (this is how Finder and Notes draw vibrant
sidebars); it only requires a transparent WKWebView canvas, which the
`set_window_vibrancy` enable path already establishes at runtime
(`macos-private-api` compiles that in independent of the window flag).
Enabling glass installs the vibrancy layer and then makes only the
webview canvas see-through; disabling clears the vibrancy layer — the
canvas may stay non-drawing afterwards (wry's flag is one-way at
runtime), which is harmless because glass-off CSS paints fully opaque
above an always-opaque NSWindow. The boot-path first-frame backing
writes touch only the NSWindow backing color and are therefore inert to
glass state regardless of how they order against the `ThemeProvider`'s
vibrancy call on a persisted-glass-on cold boot. Glass-off users (the
default) get an end-to-end opaque window from boot for the first time.

## 4. `get_channels`: serial round-trips and a multi-MB payload on every
refetch

The stale gates in (1) cut refetch frequency; this cuts the cost of the
refetches that legitimately remain (boot, and focus returns after more
than 5 minutes away — previously still a multi-second stall).
`get_channels` made ~8 fully serial relay round-trips (~3.2–3.6s at
1,100+ channels), then shipped the full `ChannelInfo` list — including
every channel's member pubkeys — across IPC, where the renderer's
`JSON.parse` of the multi-MB payload froze the main thread (the ~1.25s
stall captured in the live sample).

- **Concurrent stages**: the membership chain, the open-channel
directory scan, and the hidden-DM snapshot run concurrently, as do the
member-count and last-message queries that follow. The critical path
drops from ~8 sequential round-trips to 2 phases. Filters, limits,
pagination, and merge semantics are unchanged.
- **Not-modified short-circuit**: the command now takes a
client-supplied content hash (FNV-1a 64 over the channel list,
canonicalized by id and excluding `last_message_at`) and omits the
channel list from the response when nothing else changed. Last-message
timestamps — which change on nearly every message anywhere — ship as a
small separate map that the client overlays onto its cached list with
reference preservation, so React Query's structural sharing also skips
downstream re-renders. On a typical refocus the renderer parses
kilobytes instead of megabytes. The hash is stored in the query cache
itself, tying its lifecycle to the data it describes so a community
switch can never leak a stale hash.

The E2E mock bridge speaks the new payload shape — including the
complete `last_messages` map the client treats as authoritative — and
hash canonicalization plus overlay reference-preservation are
unit-tested on both sides.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-08-11 19:47:19 -04:00
9203bf60ee perf(desktop): coalesce read state localStorage persistence (#5591)
Follow-on to #5453/#5454's localStorage work — found while investigating
app-slowness reports on a real profile.

## Problem

`ReadStateManager.persistLocalState()` serialized and rewrote **all
three** read-state localStorage blobs (`buzz.channel-read-state.v2`,
`.publishable.v1`, `.source-created-at.v1`) synchronously on every
context advance. On a real profile (1,643 contexts, ~450K chars across
the three blobs) this produced ~880KB of localStorage sqlite WAL growth
per 30 seconds at idle, with writes every ~5s — steady main-thread
serialization + sync IPC for no user-visible benefit. Observed WAL size
on the affected profile: 94–114MB.

## Fix

- Local persistence coalesced behind a **1s trailing-edge timer**: a
burst of N advances produces one `writeStoredReadState` (one write per
blob).
- Pending dirty state **flushes synchronously** on `pagehide`, hidden
`visibilitychange`, `destroy()`, and before each relay publish — disk is
current before any relay event goes out.
- Hydration still persists immediately. Publish debounce (5s), merge
logic, and blob formats unchanged (`DEBOUNCE_MS` renamed to
`PUBLISH_DEBOUNCE_MS` only).

## Accepted residual

A hard kill (SIGKILL/power loss — not webview teardown) inside the 1s
window loses ≤1s of local read-state advances; relay max-merge bounds
the effect to a message flickering back unread. On the record per
review.

## Validation

- `readStateManager.test.mjs`: fake-timer/mock-storage coverage —
exactly one 3-blob write per burst (zero before the timer fires),
hidden-flush cancels the timer and persists, hydrate persists
immediately, pre-publish flush. Suite 26/26.
- Push gate at the pushed commit: desktop check, typecheck, full desktop
unit suite 4,670/4,670.
- Independent adversarial FULL REVIEW: **APPROVE** at tree `371a02cf`
(commit metadata rewritten afterward for attribution; tree identical) —
all six `persistLocalState` call sites traced, lifecycle/leak checks
(StrictMode remount, pubkey change), no external readers of the blob
keys.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Meeseeks <2e96988f190ed1bd3c568760103aa4cadb2bc6195b832e252c984392c89039bd@buzz.block.builderlab.xyz>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-11 16:41:35 -07:00
Taksh KothariandGitHub 16b7ae7ce6 fix(relay): stop panicking the ingest worker on reactions to project events (#5294)
A NIP-25 reaction whose target is a project root or project comment
(kind
1621 issue, 1618 PR, or a kind-1 comment on one) carries no h tag, so
channel_id is None on the reaction write path. The conformance-trace
emission asserted a channel was always present:

channel: channel_label(channel_id.expect("reaction path has channel")),

so the worker panicked at ingest.rs:2824. The row was inserted before
the
panic, so the client saw a failed request for a persisted event and
retried,
and the duplicate branch carried the same expect, head-of-line blocking
a
durable publish queue forever.

Mirror the message write's three-way split at the same seam:
(Some, true) -> WriteInsert, (Some, false) -> WriteDuplicate, (None, _)
-> WriteInsertGlobal. The conformance vocabulary already models
channel-less
writes; only the reaction path was missing it.

Closes #4936

Signed-off-by: Taksh <takshkothari09@gmail.com>
Signed-off-by: Ravneet Arora <rarora@squareup.com>
2026-08-11 23:03:18 +00:00
d9dc76c0aa fix(desktop): bound initial timeline retention (#5603)
## Summary

- let Virtua own the initial visible timeline range instead of passing
every loaded row to `keepMounted`
- populate the existing bounded retention window after the virtualizer
reports its first settled viewport
- cover a 10,000-row timeline to prevent an all-history initial mount
regression

## Why

`useTimelineRetention` initialized its retained-key set with every
loaded timeline key. Those indices were passed to Virtua's
`keepMounted`, effectively defeating virtualization during initial
channel positioning until `onScrollEnd` pruned the set.

On a large real channel this grew WebContent into multiple gigabytes and
blocked the renderer main thread for 20+ seconds while WebKit laid out
and painted the retained rows. Starting with no retained rows restores
Virtua's visible-range mount; the existing reader-neighborhood and
visual-tail retention is populated once the viewport is measured.

## Validation

- `node --import ./test-loader.mjs --experimental-strip-types --test
src/features/messages/ui/useTimelineRetention.test.mjs`
- pre-push hook at `8e86a189de7e9a8f2cb119396c8f912ed9dacd6e`:
branch-skew, desktop-check, desktop-typecheck, and all 4,671 desktop
tests passed
- manual ablation against PR #5599 on the affected profile: catastrophic
channel-switch stalls disappeared

## Authorship disclosure

Carl implemented and is posting this change on Wes's behalf.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-11 15:59:36 -07:00
Cea Stapleton Cordasco 9872ab14be chore(build): integrate PostgreSQL tracing foundation
Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-11 17:49:29 -05:00
Taksh KothariandGitHub e8153f8f27 fix(relay): log event kind on the HTTP bridge /events line (#5291)
The HTTP bridge request log recorded route, status, and accepted but not
the event kind, so typing indicators (kind 7) and their deletions (kind
5)
were indistinguishable from real messages (kind 9). Every agent turn
produced
accepted:true lines whether or not a message was actually sent, which
twice
led debuggers to conclude a silent agent had published successfully.

Add kind to the Ok outcome and the tracing::info line so the publish
path is
self-describing without a database query.

Closes #4676

Signed-off-by: Taksh <takshkothari09@gmail.com>
2026-08-11 15:44:28 -07:00
397796c5f3 feat(tracing): add PostgreSQL tracing spans (#3678)
## Why
Expose PostgreSQL datastore latency within existing request traces so
slow logical database operations can be identified without recording
tenant data or query arguments.

## What
- Add client spans around logical PostgreSQL operations across the
database facade, search, audit, replica fencing, and command persistence
- Use a dedicated `buzz_datastore` target and `db.system.name =
"postgresql"` for filtering and backend classification
- Exclude health-check database calls and scrub raw identifiers and
errors from newly traced paths

## Risk Assessment
Medium — this instruments frequently used datastore paths and increases
trace volume when enabled, but does not change SQL execution or
datastore behavior. Existing OpenTelemetry filtering controls export.

## References
- Pre-push clippy and fast unit-test hooks passed

Generated with Amp

---------

Signed-off-by: David Grochowski <dgrochowski@squareup.com>
Co-authored-by: Amp <amp@ampcode.com>
2026-08-12 08:04:54 +10:00
Cea Stapleton Cordasco 806e62f823 refactor(desktop): extract message depth guides
Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-11 16:58:59 -05:00
Cea Stapleton Cordasco 9d67b027c3 chore: merge current main into authorization stack
* commit 'cf03bd7c37cb3918afd4fe2a561360d01e11b68e':
  Improve desktop search scoping (#5306)
  Add glass appearance and cohesive settings (#5478)
  Add Send to channel for thread messages (#5305)
  Fix macOS attachment picker lifecycle and allow inert HTML downloads (#5569)
  fix(desktop): preserve fresh channel timelines (#5577)
  fix(desktop): suppress fresh focus-return refetches for channels and home-feed (#5535)

Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-11 16:45:35 -05:00
cf03bd7c37 Improve desktop search scoping (#5306)
## What changed

- unify Cmd+K and channel Cmd+F around a removable channel or
conversation scope
- add conservative fuzzy matching for people and channels while
preserving exact-match ordering
- make scoped message search complete for one-character queries and
expose up to 40 scrollable results
- keep the pre-scope channel or DM action in the normal results flow so
it scrolls away with the list

## Validation

- desktop TypeScript typecheck
- desktop text-size and file-size guards
- focused fuzzy-search unit tests (24 passed)
- focused search Playwright coverage (7 passed), including channel and
DM copy, one-character results/no-results, 40-result scrolling, and the
non-sticky scope action
- desktop E2E build
- visual review of channel, scoped, expanded-results, and DM states

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
2026-08-11 11:11:57 -07:00
Cea Stapleton Cordasco 16ea258b86 refactor(desktop): extract message status metadata
Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-11 13:01:48 -05:00
cd2aa5c12d Add glass appearance and cohesive settings (#5478)
## Summary

- add an opt-in native glass sidebar with opacity controls and live
theme previews
- refine sidebar spacing and Buzz-only active rows while preserving
production defaults
- unify settings section cards, subtitles, and agent runtime rows

## Validation

- repository format, lint, type, and file-size checks
- 4,538 desktop tests and 2,270 native desktop tests
- desktop and web production builds
- 1,261 mobile tests in the completed full gate
- focused Playwright appearance, sidebar, settings, pairing, and runtime
coverage

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Signed-off-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
2026-08-11 10:25:23 -07:00
b0795a10ea Add Send to channel for thread messages (#5305)
## Summary

- Share eligible self-authored or owned-agent thread messages into the
parent channel as new top-level messages.
- Link the shared message back to the exact root thread with a semantic
channel label and excerpt.
- Add a dedicated channel-arrow icon plus ownership and navigation
coverage.

## Validation

- Desktop lint, size, and text guards
- Desktop TypeScript build and all 4,543 unit tests
- Focused Playwright send-to-channel and thread-link navigation tests

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-11 10:18:54 -07:00
Cea Stapleton Cordasco b216c6f22e fix(deps): use registry aws credentials
Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-11 12:13:23 -05:00
bba3e06386 Fix macOS attachment picker lifecycle and allow inert HTML downloads (#5569)
## Problem

Canceling the native macOS file chooser leaves the composer's temporary,
detached `<input type="file">` without a `change` event or an explicit
cleanup path. Opening Finder again immediately creates a second detached
input while WebKit may still be unwinding the first picker. The newly
selected files can therefore fail to reach the upload pipeline. Drag and
drop is unaffected because it bypasses this picker lifecycle.

This does **not** add an automatic retry mechanism. “Retry” means the
user's next attachment attempt after canceling or after a prior
selection.

## Fix

- give each composer hook one hidden, body-mounted file input for its
lifetime instead of creating a detached one per click
- reset and reconfigure that input before every open, replace its
handler rather than stacking handlers, and remove it cleanly on unmount
- preserve normal selection, cancel then reopen, selecting the same file
again, and multi-select behavior
- accept canonical `text/html` attachments while continuing to serve and
render them strictly as inert downloads
- keep XHTML, SVG, JavaScript, and executable MIME types blocked

The picker change fixes the ownership/lifecycle bug at its source; it
does not retry failed uploads, add delays, or mask errors.

## Testing

- mandatory pre-push gate: branch-skew, desktop typecheck/tests/check,
Rust tests, and desktop Tauri checks passed on
`ea5a97adf957803935b28d63d32f9f332cf65287`
- `cargo test -p buzz-media --lib` (110 passed)
- `pnpm --dir desktop typecheck`
- focused Biome check for the three picker files
- picker Playwright regression: cancel/no selection then reopen, select
the same file again, and multiple selection (run on the source commit
before integration)
- HTML live-relay response regression added as ignored E2E because it
requires the S3-backed relay harness

## Manual verification

Playwright models cancellation with Chromium's
`FileChooser.setFiles([])`; it cannot exercise the native macOS Finder
panel/WebKit presentation lifecycle. Before merge, manually verify in
the built macOS app:

1. select a PNG normally
2. cancel, then immediately reopen and select a PNG
3. select the same PNG on a subsequent attempt
4. multi-select two PNGs

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Princess Donut <68157ebd23b3897c1991015c3038658ea916200c67d3a54620b0754d1b92f6e0@buzz.block.builderlab.xyz>
Co-authored-by: Mongo <5c25403eab7271f9f94ddd4f2b270e8cac2c92e2c830c51877cca6ec974ffb3f@buzz.block.builderlab.xyz>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-11 09:51:42 -07:00
d3ec831e0c fix(desktop): preserve fresh channel timelines (#5577)
## Summary

- skip the channel subscription catch-up request when the authoritative
channel window was fetched successfully within the existing five-minute
freshness period
- keep the responsive deferred skeleton for populated channel switches
instead of briefly rendering empty-channel actions
- preserve a real empty-channel intro across the first appended message
only after React has committed that empty state

This is intentionally narrow. It does not claim to solve the separate
sidebar startup cost or general main-thread stalls found during the
investigation.

### Related issue

N/A — no matching open issue or PR found.

### Testing

- pre-push desktop gate on `f1be6beea90b9715e04e5fc65cc5cfbe8210e0d9`:
  - desktop tests: 4,621 passed
  - desktop check: passed
  - desktop typecheck: passed
  - branch-skew: passed
- focused cache/surface/lifecycle tests: 62 passed
- manual diagnostic trace after rollback:
  - 16/16 channel revisits skipped catch-up refresh
  - 0 revisit refresh starts
  - 0 populated-channel empty/intro flashes
  - cached switches retained the deferred skeleton-to-list path

No screenshot: the regression is a transient channel-switch state and
request behavior, covered by lifecycle tests and the diagnostic trace
rather than a stable visual diff.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-11 09:37:12 -07:00
Cea Stapleton Cordasco 8a4fca7f4b chore(deps): merge pinned main release state
Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-11 11:25:18 -05:00
Cea Stapleton Cordasco 18fc3d3e5a fix(deps): adopt main Mesh release state
Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-11 11:12:20 -05:00
4935724494 fix(desktop): suppress fresh focus-return refetches for channels and home-feed (#5535)
Fixes the 0.5.9 sluggishness Wes reported in app-slowness-mac (channel
list slow, content slow).

## Problem

#5490 (shipped in 0.5.9) flipped ~20 query sites to
`refetchOnWindowFocus: true` and wired TanStack focusManager to app
focus. Instrumented at that exact commit: regaining focus after >60s
away fires **7 query fetches within 2ms**, including `get_channels`,
which settles at **~3.6s** (production probe: median 3.2s at 1,133
channels — 8 serial round-trips, 1,133-filter last-message batch). In
0.5.8 this burst was zero by configuration. Net: a burst of fetch/parse
contention exactly when the user returns to the app.

Relay ruled out: v0.2.1 small reads are 2–4ms upstream; nothing in
v0.2.0..v0.2.1 degrades the query path. The O(N) `get_channels` design
is a pre-existing issue (June analysis) — this PR fixes the new stampede
that made it user-visible.

## Fix

Raise `staleTime` to 5 minutes on the two expensive focus-refetch
families — `channels` and `home-feed` — so a focus return inside that
window serves cache instead of refetching. `refetchOnWindowFocus: true`
only refetches stale queries, so genuinely old data still refreshes on
return.

Unchanged: focused polling cadence (60s channels / 30s home-feed;
interval refetches ignore staleTime), #5490 blur quiescence (no changes
to `useDocumentVisible.ts`/`queryClient.ts`), all push-style
invalidation paths (`invalidateQueries` bypasses staleTime), and
channels cold-start revalidate (`initialDataUpdatedAt: 0`).

## Validation

- New regression test
`desktop/src/features/home/focusRefetchPolicy.test.mjs` (4/4): fresh
focus return → 0 fetches; stale → 1; polling constants locked.
- Pre-push gate at the reviewed tree: desktop-check, desktop-typecheck,
full desktop-test **4588/4588**.
- Independent adversarial review (Beth): APPROVE at tree `4e2546ec` —
verified fresh-skip/stale-refetch against query-core 5.100.14 source,
polling-cadence via browser-simulated probe, side-effect sweep of all
invalidation paths clean. Sole CHANGE was commit trailers, fixed by
amend (tree unchanged).

## Known residual

Focus returns after >5min still fire the full burst including the
~3.2–3.6s `get_channels`. This cuts stampede frequency, not magnitude —
the O(N) `get_channels` relay path (RESEARCH/GET_CHANNELS_SLOWNESS.md)
is the follow-up that fixes magnitude.

Diagnosis: Summer (focus profiling) + Morty (relay probe); implemented
by Meeseeks; reviewed by Beth; integrated by Rick. Thread:
app-slowness-mac
e78fad29380d9a0974c9d673910450994a228781ddce133a8cedbd90504d95be.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Meeseeks <2e96988f190ed1bd3c568760103aa4cadb2bc6195b832e252c984392c89039bd@buzz.block.builderlab.xyz>
2026-08-11 09:07:39 -07:00
Cea Stapleton Cordasco 10a31e8000 fix(deps): use public aws-creds source
Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-11 10:53:59 -05:00
240cdd3ea1 chore: mesh upgrade, clean up legacy special case code, simplify model selection for mesh (#5289)
Shared compute now has exactly two model choices: MeshLLM's virtual
`mesh`
model, or a model you name. Buzz picks between them in one place, and
buzz-agent no longer knows meshes exist.

## What changed

- **MeshLLM v0.74.0 → v0.75.1.** v0.75.0 added
`degrade_to_single_model`, so a
  `model=mesh` request is answered by one served model when there is no
committee to form, instead of failing. v0.75.1 adds Mesh-LLM#1196, which
skips stale pre-0.75 runtime cache entries rather than aborting startup
on
them — without it, anyone who had run mesh on 0.73/0.74 could not start.
- **Deleted the client-side mesh catalog probe.** buzz-agent used to
poll
`/v1/models` (5s TTL, 30s cooldown, two-observation debounce) to decide
whether `mesh` was safe to send. MeshLLM now decides per request, so the
  polling, its hysteresis, and its 503 fallback are gone.
- **One mapping point.** `relay_mesh_wire_model()` turns the stored
value into
a wire name: `auto` becomes `mesh`, a named model passes through. The
spawn
env, the ACP harness, and the readiness probe all use it, so they cannot
disagree — previously `BUZZ_ACP_MODEL` and the probe both said `auto`, a
name
  the mesh does not advertise.
- **Removed the `nostr-relay-pool` advisory exception.** #5404 allowed
RUSTSEC-2026-0243 "after mesh-llm migrates to nostr-sdk >= 0.45".
v0.75.1
does, so the retired crate is gone from both lockfiles and the exception
  would only mask a future advisory for it.
- **Deleted `scripts/ensure-mesh-native-runtime.sh`** and its six
justfile call
sites. It built llama.cpp from source into the runtime cache; the app
already
  downloads the signed release runtime itself, and CI never called it.

## Why it is better

**−639 lines of Rust.** Availability is decided by the node that knows
the
answer, per request, instead of by a client cache that could be stale
for up to
30 seconds. A second worker joining now takes effect on the next request
rather
than after two confirming probes.

## Behaviour change

A 503 on an explicit `mesh` request takes the ordinary transport retry
under
the same model instead of failing over to a second one — there is no
second
model to fail over to now. MoA repairs partial committee results
internally
before it reaches that point.

## Validation

`crates/buzz-relay/examples/mesh_agent_e2e.rs` now sends `mesh` where it
previously sent `auto` or the physical model id, so no leg was covering
what
Buzz actually puts on the wire. 4/4 on gemma-4-E4B, gemma-4-26B-A4B, and
Qwen3-8B — including a real ACP tool call through `mesh` into
buzz-dev-mcp,
asserted by reading the written file back off disk.

Hand-tested in the desktop app on both gemma-4 sizes: picked Auto, agent
logged
`model_id=mesh`, replied in channel.

## Not covered

A committee that forms and then loses a worker returns 502, and that
needs two
workers to reproduce — not testable on one machine.

---------

Signed-off-by: Michael Neale <michael.neale@gmail.com>
Co-authored-by: Michael Neale <michael.neale@gmail.com>
2026-08-11 11:40:07 -04:00
83ca595ada fix(desktop): preserve theme when opening communities (#5266)
**Category:** fix
**User Impact:** Previously selected community themes are preserved when
opening a relay through onboarding, while first-time theme migration
still completes for communities with no saved theme.

**Problem:** During community initialization, desktop queried theme
history before establishing live delivery. If a replacement event
arrived while an empty history query was in flight, the client could
incorrectly treat the theme as absent and publish the default over the
user's saved selection.

**Solution:** Subscribe before fetching history, expose whether live
readiness reached EOSE, flush buffered live events before resolving
EOSE, and retain the newest delivered replacement through hydration.
Seed the inherited/default theme only when both live and history
snapshots reach EOSE with no valid or unreadable event; subscription
failures, CLOSED, readiness timeout, relay failure, and unreadable
events fail closed without publishing.

<details>
<summary>File changes</summary>

**desktop/src/shared/api/relayClientSession.ts / relayClientShared.ts /
relayClosedRecovery.ts**
Distinguish EOSE from CLOSED/timeout readiness and flush buffered events
before resolving an EOSE fence.

**desktop/src/shared/theme/CommunityThemeController.tsx**
Seed and complete first-community migration only for confirmed absence;
uncertain hydration remains non-publishing.

**desktop/src/shared/theme/communityThemePreference.ts**
Keep the inherited appearance for the first migrated community and the
stable default for later empty communities.

**desktop/src/shared/theme/communityThemeSync.ts**
Arbitrate live and history results into valid, confirmed-absent,
invalid, or unavailable hydration outcomes.

**Tests**
Cover EOSE/CLOSED readiness, subscription failure, timeout, unreadable
and live-racing events, no-op initialization, and first-to-later
community fallback isolation.

</details>

## Reproduction steps

1. Save a non-default appearance for a community relay.
2. Remove the community locally, then open the same relay again through
onboarding.
3. Arrange for the saved replacement event to arrive live while the
initial history query returns empty.
4. Confirm the saved appearance remains selected and the client does not
publish the default theme over it.
5. On an account with no theme records, open a first empty community and
confirm its inherited appearance is migrated; open a later empty
community and confirm it starts from the stable default.

## Validation

- Pre-push desktop checks, typecheck, and full desktop tests: passed at
`f79556b0e`
- Focused theme/relay readiness tests: 38 passed
- Desktop file-size ratchet and diff check: passed

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
mobile-v0.10.0-rc.1
2026-08-11 08:21:34 -07:00
Taylor HoandGitHub 7eb8cc5a5f fix(link-preview): resolve YouTube videos through oEmbed (#5520)
**Category:** fix
**User Impact:** YouTube video links now resolve into reliable previews
instead of intermittently appearing as bare links.

**Problem:** Buzz intentionally reads at most **256 KiB** of page HTML
when building a generic link preview. The YouTube response that exposed
this bug was roughly **1.3 MiB**, with its Open Graph metadata beginning
around **686 KiB**—well beyond Buzz's bounded read—so extraction
returned no usable preview. YouTube can move that metadata between
responses, which explains why the same link may appear to work in one
build or request and fail in another; raising the generic cap would
increase bandwidth and allocation for every site while still scraping an
unstable application document.

**Solution:** Route recognized YouTube video URLs through YouTube's
structured oEmbed endpoint instead of parsing raw watch-page HTML. The
provider response is capped at **64 KiB** and retains Buzz's existing
HTTPS validation, pinned DNS/SSRF protection, disabled redirects,
timeouts, metadata bounds, and thumbnail sanitization. Provider failures
return no preview rather than falling back to fragile HTML scraping, and
embed URLs are canonicalized safely, including percent-encoded video
IDs.

<details>
<summary>File changes</summary>

**desktop/src-tauri/src/commands/link_preview.rs**
Recognizes supported YouTube URL forms, fetches bounded JSON metadata
from YouTube oEmbed, canonicalizes embed links, and adds response,
URL-boundary, malformed-data, resource-limit, and encoded-ID
regressions.

**desktop/src-tauri/Cargo.toml**
Declares percent decoding as a direct desktop dependency for safe
embed-ID canonicalization.

**desktop/src-tauri/Cargo.lock**
Records the direct dependency in the desktop package lock entry.

</details>

## Reproduction Steps

1. On the base branch, paste a YouTube URL whose Open Graph metadata
falls beyond the first 256 KiB of the raw watch-page response and
observe that no preview is produced.
2. Run this branch and paste a YouTube watch, mobile, music, `youtu.be`,
Shorts, live, or embed URL into the composer.
3. Confirm the preview resolves with the video's title, creator, and
sanitized thumbnail without downloading the full watch-page HTML.
4. Try an embed URL with a percent-encoded ID, such as
`https://www.youtube.com/embed/%64Qw4w9WgXcQ`, and confirm it resolves
to the same video.
5. Try a YouTube lookalike domain or an embed ID containing encoded
separators and confirm it is not routed through the provider path.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
2026-08-11 08:03:01 -07:00
Cea Stapleton Cordasco c11d457b73 chore(deps): reconcile main lock state
Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-11 09:46:11 -05:00
5e4d0fe925 fix(buzz-agent): harden Databricks OAuth token cache and callback (#5534)
Hardens the Databricks PKCE OAuth code in
`crates/buzz-agent/src/auth.rs`. Two fixes.

## Token cache is owner-only across its whole lifecycle, and race-safe

The PKCE cache holds both the access and refresh tokens, but `save()`
wrote it with a bare `fs::write` + `fs::rename`. Under a `022` umask the
file landed world-readable, and the fixed `*.json.tmp` temp name races
across concurrent savers sharing `$HOME` — one writer's `rename` can
fail on another's half-written temp.

**On write**, `write_private_cache()` creates a temp file with
owner-only permissions from the moment it exists — mode `0o600` on Unix
via `OpenOptions::mode` — writes and fsyncs it, then renames over the
destination. The rename swaps the inode wholesale, so a pre-existing
cache file with loose permissions is *replaced* by the new private inode
rather than inheriting its mode. `unique_suffix()` (getrandom, timestamp
fallback) gives each write a distinct temp name, and a drop guard
removes the temp on any failure path.

**On load**, owner-only is enforced as a cache lifecycle invariant, not
just a write-path property. A world-readable cache left by an older
buzz-agent was previously read straight into memory and returned on the
fresh cache-hit path without ever invoking `save()`, so a token file
with no advertised expiry could stay exposed indefinitely.
`read_cache()` now funnels every load — initial and cross-process
re-reads — through `read_private_cache()`, which on Unix opens with
`O_NOFOLLOW` (kernel-level symlink refusal, no stat/open TOCTOU),
requires a regular file, and `fchmod`s the pinned handle to `0o600` when
any group/other bit is set. A cache that cannot be secured is treated as
absent, so callers fail closed to a fresh flow rather than trusting an
exposed file.

## OAuth callback no longer reflects untrusted input

The localhost callback embedded the untrusted `error` query param
straight into the HTML response — an XSS sink on the redirect page — and
routed that same raw value into the error string that reaches the logs.

`callback_outcome()` is now a pure function returning `(result,
static_page)`: the browser always sees a fixed literal page that embeds
no request parameter, and failure detail travels only through the result
channel. `sanitize_callback_detail()` strips control characters (CR/LF
log-line injection) and caps length before that detail enters the error
string bound for the logs.

## Deferred: Windows owner-only ACLs

Windows owner-only protection is out of scope for this change. The
goose-parity route (`CreateFileW` with an owner-only SDDL
`D:P(A;;FA;;;OW)`) requires `unsafe` FFI, which this crate's
`#![forbid(unsafe_code)]` prohibits; reconciling that conflict is a
separate decision. Both platform seams — `create_private_temp_file`
(write) and `read_private_cache` (load) — have a `#[cfg(not(unix))]`
branch that relies on the default per-user ACLs and is the drop-in point
if Windows protection is added later. No new dependency and no `unsafe`
are introduced here.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Hayt <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
2026-08-11 09:58:15 -04:00
Cea Stapleton Cordasco 04b3521696 docs(schema): clarify restored constraints
Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-11 08:56:33 -05:00
Cea Stapleton Cordasco ca1323bd17 feat(auth): integrate reviewed authorization foundation
Cumulative PR layer #1476. Integrates the reviewed authorization foundation, provider-neutral verification backend, loopback fixture repair, public dependency-source normalization, and the advisory-free Mesh revision.

Neighbor disclosure: bounded audit capacity remains in #3564; lifecycle and canonical admission remain in #4772; protected and status consumers remain in the later cumulative layers.

Signed-off-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
2026-08-11 08:22:36 -05:00
be48ce98bd fix(link-preview): reliably render previews sent right after they resolve (#5245)
## Overview

**Category:** fix  
**User impact:** Link previews no longer disappear when a message is
sent while preview metadata or media is still settling. Fast Enter,
rapid Enter, and confirmed-draft auto-send now preserve the preview
without duplicate sends or stale tags.

**Problem:** The composer could look ready before its sender-authored
snapshot tag existed. Send paths could then race preview
resolution/upload, while debounced preview state could attach a tag for
a URL that had already been removed. The same timing also caused
confirmed-draft auto-send to be consumed without sending.

**Solution:**
- Debounce preview resolution to avoid card flicker while typing, then
disable every submit path while a supported external preview settles. A
2-second escape cap still permits a bare-link send if resolution stalls.
- Keep submit synchronous: acquire a composer-local lock before
asynchronous send work, read ready tags from the live URL set, and
reject Enter/form submits while a snapshot is pending.
- Retry confirmed-draft auto-submit until preview settling clears, then
submit exactly once.
- Upload thumbnail and favicon independently. A failed upload shows a
toast and degrades to the surviving media (or text-only) rather than
leaving the card spinning.
- Exclude message-edit mode from preview resolution, upload, and Save
gating. Edit-time preview snapshots remain follow-up #5273.
- Canonicalize fragment-bearing URLs for preview lookup/snapshot
identity while preserving the original fragment links in message text.

## Link preview state walkthrough

Captured using PR #5245's actual public Open Graph metadata and artwork.
The deterministic E2E bridge controls only upload timing so the
transient disabled state can be captured reliably.

| State | Expected behavior | Screenshot |
| --- | --- | --- |
| **1. Snapshot upload pending** | The real PR preview is visible, but
Submit remains disabled until its sendable snapshot tag is ready. Click
and Enter cannot send a bare link during the settling window. | ![PR
5245 pasted with its real preview visible and Submit
disabled](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5245/01-real-pasted-submit-disabled.png)
|
| **2. Snapshot ready** | Once snapshot upload settles and the tag is
ready, the same preview remains and Submit becomes active. | ![PR 5245
preview ready in the composer with Submit
enabled](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5245/02-real-resolved-submit-enabled.png)
|
| **3. Message sent** | The sent event carries the snapshot tag and
renders the PR title, description, and artwork inline instead of
degrading to a bare URL. | ![PR 5245 real link preview rendered inline
in the message
list](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5245/03-real-sent-preview-inline.png)
|

## Regression coverage

- Enter during metadata resolution or snapshot upload cannot send early.
- Paste-and-immediate-Enter sends after settling; rapid Enter submits
exactly once.
- Confirmed-draft auto-send waits for settling and fires exactly once.
- Removed/replaced URLs cannot leak stale snapshot tags or media refs.
- Thumbnail upload failure toasts and sends with the surviving favicon.
- Edit mode does not resolve/upload previews or gate Save.
- Fragment variants share a canonical preview while original fragment
links remain clickable.
- Existing ready-preview, suppression, bare-link fallback, and
multi-preview behavior remains covered.

## Reproduction steps

1. Open a channel and paste a supported external URL into the composer.
2. Press Enter immediately, before preview metadata/media finishes
settling.
3. Before this fix, the event could be sent without its preview snapshot
(or confirmed-draft auto-send could be lost). With this fix, submit
waits behind the disabled state and fires once with the matching
snapshot tag.
4. Remove or replace the URL and press Enter inside the debounce window.
The sent event contains tags only for URLs still present in the
submitted content.

## Validation

All required PR checks are green, including Desktop Core, Desktop Smoke
E2E shards, Desktop E2E Integration shards, macOS build, security
checks, and DCO.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-10 20:27:30 -07:00