mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Persist observed unread events, read markers, membership, and channel projections in a scoped WAL database. Keep renderer mutations ordered with a sequence/revision protocol, migrate localStorage transactionally, and let native catch-up load membership without serializing five capped arrays. Measured at the same populated 5x1000 membership fixture: - before: 335,280 bytes (327.4 KiB) - after: 178 bytes (0.2 KiB) Ack/restart failure matrix: | Failure boundary | Contract | |---|---| | Rust commits sequence N, renderer dies before observing ack | **Handled by replay:** DB ack is durable; reopen returns `lastAckedSequence=N`. Renderer may resend N from its pending local queue; Rust recognizes `N <= ack`, performs no mutation, and returns current revision/snapshot. Event-id idempotence is the second fence. | | Renderer observes ack N, dies before deleting its pending batch | **Handled identically:** replay N is a no-op; no duplicate row or revision bump. | | Renderer deletes pending N without durable ack | **Unrepresentable by construction:** deletion happens only in the resolved-success branch after validating matching scope, sequence, and revision. Reject/throw leaves N queued. | | Scope switch lands while A ingest is running | **Handled:** A request carries immutable scope; transaction commits only to A. Switch drains A first when available, opens B independently, and response merge requires exact current scope; a late A ack cannot advance B's sequence or projection. If drain fails, A's retained unacked batch stays replayable when A reopens. | | Scope switch after A commit but before A ack observation | **Handled by durable ack + scope fence:** A reopen learns ack N; B never sees it. | | Batch N+1 arrives before N / IPC retry reorders | **Explicitly rejected:** only `sequence == ack+1` mutates. `> ack+1` returns `snapshotRequired`/expected sequence; `<= ack` is replay/no-op. Single JS queue makes normal reordering unrepresentable, backend check covers abnormal callers/restarts. | | DB commit succeeds but response serialization/delivery fails | **Handled as lost ack:** resend; durable sequence and idempotent event ids collapse it. | | DB transaction fails halfway (events/markers/prune/revision/ack) | **Unrepresentable:** one SQLite transaction; rollback leaves ack+revision unchanged, so retry is the same next sequence. | | Migration imports rows but renderer dies before seeing marker | **Handled:** rows + migration marker commit atomically. Reopen reports complete and current snapshot; legacy key remains until renderer observes that, then is deleted. Re-sending payload after complete is ignored. | | localStorage delete succeeds, native DB later becomes unavailable | **Handled by one-release fallback limitation explicitly:** fallback can preserve new session events but cannot reconstruct migrated history after confirmed native ownership. Native open failure surfaces and does not mutate/delete legacy data pre-confirmation; DB corruption after confirmed migration is logged/recoverable as degraded state, not silently represented as “zero unread.” I will test this distinction rather than claim impossible loss recovery. | | Revision response gap/out-of-order | **Handled:** apply requires exact `baseRevision`; otherwise discard payload and request full snapshot. Snapshot replacement requires matching scope and `revision >= current`. | | App shutdown during coalesce | **Handled twice:** `pagehide` drains renderer queue; native shutdown flushes SQLite/checkpoint. Already-committed batches need no renderer ack to survive restart. Unsent events inside a renderer killed without pagehide are the irreducible initial-source limit; live relay catch-up can rediscover them, and the existing TS fallback gate remains for native write failures—not arbitrary process kill. | Build extension: each scope carries a generated UUID epoch. A different epoch means the store was rebuilt, so the renderer accepts the replacement snapshot regardless of its old revision and resets sequence/revision. This makes DB recreation distinguishable from a stale snapshot and prevents a revision wedge. Native projection deltas now contain changed channels only. Top-level unread continues to mean an unread observed event whose `rootId` is null, matching the retired renderer helper exactly. Read-state version changes, including relay sync advances, feed marker updates into the same ordered mutation chain. Co-authored-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz> Signed-off-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz>
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 frontendpnpm tauri dev- run the desktop apppnpm build- typecheck and build frontendpnpm typecheck- TypeScript checkspnpm lint- Biome lintpnpm 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