Files
buzz/desktop
Duncan 35873afea3 fix(read-state): close round-6 findings — abort/retry, gen hygiene, production handler
Round-6 fixes for NIP-RS unread state (#3966):

1. Transaction abort/retry on failed cleanup writes

   abortTransaction() on both step-1 (register+receipt persist) and step-3
   (cleanup persist) failure paths: gen1 is restored as the live intent,
   any promoted gen2 is re-buffered into deferredEnqueues, and scheduleDrain
   fires a retry pass.  Promotion only happens inside a successful commit —
   durable gen1 and in-memory gen2 can never silently diverge.

   Retry mechanism: scheduleDrain() after abort, which fires
   pendingFreshDrain handling in drainPendingIntents (immediate re-drain
   in the same session; restart recovery handled by Amendment C receipts).

   Failure semantics on step-3: gen1 register is already durably committed
   (step-1 succeeded).  The receipt is restored in-memory so the retry
   drain's alreadyApplied check fires (cleanup-only, no re-bump).
   The v2 blob retains gen1 pi+receipt until the cleanup write succeeds.

2. Generation hygiene

   Number.isSafeInteger() checks everywhere (pi.gen, receipt intentGen,
   ng).  nextGen normalized to max(intent gens, receipt gens) + 1 on
   hydration, preventing a corrupt ng from reusing an existing generation
   (which would cause an old receipt to silently discard a fresh action
   via the Amendment C already-applied check).  If the increment would
   overflow MAX_SAFE_INTEGER, falls back to a fresh safe value.

3. Production drain outcome handler extracted from hook

   createDrainOutcomeHandler() extracted as a pure typed function in
   useUnreadChannelsHelpers.ts.  useDrainOutcomeCallback delegates to it.
   Tests import createDrainOutcomeHandler directly — no hand-copied mirror,
   no decorative drift guard.

Tests (4762/4762 pass):
- Tests 51/52: timing fixed — scheduleDrain intercepted to prevent the
  abort-path auto-retry from running before intermediate blob assertions;
  blockV2Writes persistent flag used for step-1 failure so both the
  initial throw and any recovery retry fail; manual retry fired after
  assertions.  Both branches covered with restart witnesses.
- Test 53: collision case (pi.gen=7, receipt gen=7, ng=7) — nextGen
  normalizes to 8+, fresh action gets gen > 7, old receipt does not match.
- Test 54: scalar-number priorForcedEntry accepted as valid legacy entry.
- Tests 45/46/47/50: now use createDrainOutcomeHandler from production.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-08-08 14:57:59 -04:00
..
2026-08-03 21:51:17 -04:00
2026-03-09 13:02:11 -07:00
2026-06-11 15:06:24 +00:00
2026-03-09 13:02:11 -07: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