Skip to content

feat(read-state): web and desktop read unread state from the relay (buzz_v1) - #1789

Draft
baxen wants to merge 2 commits into
mainfrom
art/read-state-v1
Draft

baxen wants to merge 2 commits into
mainfrom
art/read-state-v1

Conversation

@baxen

@baxen baxen commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

When the relay advertises buzz_v1 with the ping capability, web and desktop take unread state from the relay instead of NIP-RS. That keeps every device in agreement. Relays without it keep the existing NIP-RS path unchanged.

Depends on the relay PR block/buzz#8639. Replaces #427.

Flow

  1. On connect, read-api.ts loads the sidebar from GET /buzz/v1/me/sidebar and applies the relay's unread, mention, latest and followed_threads values. server-unread.ts turns those rows into the unread decisions the sidebar, badge and Inbox already use. unread.ts and read-state.ts hand those surfaces over when v1 is active and stop publishing NIP-RS.
  2. Reading a channel or thread queues a mark-through intent. The queue is durable, so it survives reloads and offline periods, and it posts to POST /me/read-state.
  3. The relay pings the account's other devices with the channels and threads that changed. Those devices refetch only those.
  4. The Inbox asks POST /me/coverage about the specific messages it shows. The relay's follow list decides which thread replies count.

Ordering and recovery

  • Every answer carries read_revision. An older answer than one already applied is dropped.
  • A revision gap waits 1.5 s for late pings, then does one full load.
  • The socket's once-a-minute check probes a single channel and loads fully only if the revision moved. Reconnects always do a full load.
  • A message read once stays read as its scope's anchor moves on.

Transport

  • Web goes through the browser host's relay broker, which adds the read-v1-* routes.
  • Desktop uses one Tauri command, relay_read_v1. It signs NIP-98 in Rust, bounds channel IDs to 20 UUIDs, and caps bodies at 64 KiB and responses at 1 MiB.

Testing

  • At 5618e1ee5: pnpm check passes, and so do all 12,449 vitest tests, Tauri clippy and the pre-push hooks. In the Tauri cargo test run, 568 of 571 passed. The three host_command timing tests failed while the machine was loaded and passed when rerun alone; that file isn't touched here.
  • Live acceptance on Oct 27 against a local relay running the relay PR's code: three browser profiles (Chromium and WebKit), this native app and the mobile app, all on one identity. It covered cross-device read clearing in 0.6–1.0 s, a 10-minute fault soak (63/63 checkpoints), thread follow, unfollow and refollow in the sidebar, menu and Inbox, offline replay, a dropped ping with reconnect, and an ordinary-use window with zero full reloads after startup.
  • Not proven: the native window's "mark read while viewing" (the tests can't focus a window), and fan-out across several relay pods.

Draft until baxen tests the whole stack after the relay PR merges.

🤖 Generated with Claude Code

baxen and others added 2 commits October 27, 2026 21:24
When the relay advertises buzz_v1 with the ping capability, the relay's
per-person read state replaces NIP-RS as the unread authority. The client
loads the sidebar on connect, marks reads through POST /me/read-state with
a durable pending queue, and refetches only the channels and threads a
relay-signed read-state ping names. Inbox per-message state comes from
POST /me/coverage, and the relay's followed_threads decides follows.

- Responses carry read_revision: older answers are dropped, and a gap
  waits 1.5 s for late pings before one full load.
- The live socket's minute check probes one channel; a full load happens
  only when the relay's revision moved past ours, or on reconnect.
- Coverage answers stay cached while only the latest message moves, and a
  message read once stays read as its scope's anchor advances.
- Relays without buzz_v1 keep the existing NIP-RS path.

Co-authored-by: ART <ea39c1c8acf57180161ef4ba4ed68abf8589f5291956b909a5aa991fc66664ca@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
…z_v1 NIP-11 gate

relay_read_v1 exposes the relay's fixed /buzz/v1/me routes (sidebar,
read-state, coverage) as one Tauri command: NIP-98 kind 27235 signed in
Rust, channel IDs bounded to 20 lowercase UUIDs, bodies capped at 64 KiB
and responses at 1 MiB, mirroring the web broker's read-v1 routes.

connectNativeTransport parses NIP-11 buzz_v1 with the broker's
readStateV1() rules and attaches ReadApiHost only when ping is
advertised; otherwise NIP-RS behaves as before. coverage is exposed only
when advertised.

Co-authored-by: worker00 <d76f0aac2b2adc8fd52bb0158d631fff86307909e094308919f47547cf065b5c@buzz.block.builderlab.xyz>
Signed-off-by: Bradley Axen <baxen@squareup.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant