Files
DawnandDawn 0172d41c28 fix(desktop): bound relay bridge reads with a dedicated client
`get_thread_replies` and every other relay read go out on the app-wide
`http_client`, which carries no response deadline. A pooled connection
that is open but dead — the far end gone without a FIN, a proxy holding
the socket, a NAT rebind — leaves the request awaiting headers that never
arrive. The thread panel then sits pending with no data, no error, and no
spinner resolution, which is exactly the reported symptom shape.

Give the bridge (`POST /query`, `POST /events`, `GET /info`) its own
client with a 30s `read_timeout`, and give every client a 15s
`connect_timeout`.

The read timeout cannot simply go on the shared client. Probed against
reqwest 0.13.4 with real sockets rather than assumed:

- `read_timeout` bounds time-to-response-headers, and afterwards acts as
  an idle timer between body frames — both correct for small interactive
  JSON.
- It is NOT reset by request-*body* progress, so any budget tight enough
  to catch a dead read also aborts a healthy slow upload. `do_upload`
  mints a 1-hour auth window for video; the STT model download is ~100 MB.
- A per-request override is not available: reqwest 0.13's
  `RequestBuilder` exposes only the total `.timeout()`, never read
  semantics.

One number cannot serve both surfaces, so this follows the existing
`media_fetch_client` precedent and splits the client. `connect_timeout`
is shared by all three because it stops at connection establishment and
never measures a transfer. A new `http_client_base()` holds the common
`resolve`/pool/connect config so the three cannot drift apart.

Tests cover the stall abort, the upload that must survive, the absence of
a deadline on the streaming clients, and — structurally, via reqwest's
Debug output — that only the bridge client carries one. That last test
exists because moving the read timeout onto the shared client is the
tempting simplification and the worst regression, yet it is invisible to
any behavioural test that finishes in under 30s.

`paused_time_would_void_these_tests` pins the trap that cost the most
here: under `start_paused` tokio auto-advances the clock whenever it is
idle, and a pending socket read counts as idle, so the budgets elapse in
zero real time and every assertion passes while measuring nothing.

Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
2026-08-13 15:25:42 -04:00
..
2026-08-03 21:51:17 -04:00

Buzz

Desktop chat shell with:

  • Tauri + React + TypeScript + Vite
  • Tailwind CSS
  • shadcn/ui-ready shared components
  • Biome (lint/format/check)
  • Feature-driven frontend structure

Scripts

  • pnpm dev - run the web frontend
  • pnpm tauri dev - run the desktop app
  • pnpm build - typecheck and build frontend
  • pnpm typecheck - TypeScript checks
  • pnpm lint - Biome lint
  • pnpm format - Biome format (write)
  • pnpm check - Biome check

Structure

  • src/shared - reusable app-wide code (ui, lib, styles)
  • src/features - feature modules (vertical slices)
  • src/app - top-level app composition