mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
## Summary Changes the private read-state API (`/buzz/v1`) to the sidebar wire format agreed in #buzzin-in-secret. **The counting code is unchanged.** Requests and responses take the new shape, so the client integration can build against it now and later behavior fixes (counting rules, followed threads, another-device pings) ship without another wire change. Nothing ships against v1 yet (no buzz-app use on `main`), so this replaces version 1 directly. Supersedes #8188 (same branch content, rebased onto current `main`, plus the thread-cap and doc changes below). ``` GET /buzz/v1/me/sidebar?limit=&cursor= | ?channel_ids=a,b,… → { channels: [channel], next_cursor } channel: { channel_id, unread: bool, mentions, read_through_id|null, latest_id|null, threads: [thread] } thread: { root_id, unread: bool, mentions, read_through_id|null, latest_id } POST /buzz/v1/me/read-state { intents: [{ type: "mark_through", target: { channel_id [, root_id] }, message_id }] } → { outcomes: [applied|blocked|invalid|unknown], channels: [channel] } ``` What changed and why: - **Channel and thread positions are fully separate.** Channel `unread`/`mentions` count only top-level messages. Thread replies count only on their thread row. `mark_channel_read` and its whole-channel cut are gone. "Mark channel read" sends a channel `mark_through` at `latest_id`, plus one per thread row to clear threads too. - **Channel `latest_id` is always a top-level message.** It used to be able to point at a reply, and a channel `mark_through` rejects a reply as its anchor. It also skips a reply-marked message whose ancestry never resolved, because marking through one would fail on every retry. - **`read_through_id`:** frontiers stored only the anchor's arrival time. Migration 0058 adds `personal_read_frontiers.through_message_id`, which follows the greatest arrival, and drops the now-unused `threads_through_timestamp`. This is the only storage change. - **Counts are plain numbers.** The exact/at_least/unknown wrappers are gone. Evidence the relay can't use (unusable tags, unresolved ancestry, undecided conversation membership, past the scan cap) leaves the message out, so counts can undercount. - **POST returns the updated row** of each distinct channel it applied to, so the client doesn't refetch after every read. If those rows can't be read after the writes commit, `channels` is omitted and the outcomes still stand. The row refresh shares the 8 s intent deadline. - **Up to 25 thread rows per channel** (was 5). Five hid a mention in a sixth unread thread. 25 is the most that keeps a POST's rows for `MAX_INTENTS` (100) channels inside the 1 MiB response limit; 50 would be ~1.4 MB, a 503 that hides already-committed outcomes. A unit test builds that worst-case response and fails if the cap outgrows the limit. - **The doc separates shape from behavior.** `/buzz/v1` promises a stable shape (fields, types, status variants, what `mark_through` does). What the counts include (which messages are unread or directed, which threads are listed, the bounds) may change while v1 is pre-release, so clients replace their rows with each response instead of reconciling against local counts. - **Removed from the wire:** the explicit-contexts read (`GET /me/read-state?targets=`), `account`, channel name/type/archived/hidden (these come from Nostr metadata), `latest_message_at`/`_complete`, and the NIP-11 `max_contexts`/`max_context_messages`/`max_thread_summaries`. Deliberately deferred (behavior or additive, no shape change): - Thread counting rule (relay counts a reply if you wrote or replied to its direct parent; the client uses "posted anywhere in the thread or followed"), the workflow-owner attribution p-tag, and thread-row `mentions` counting every counted reply rather than directed ones. - `follow`/`unfollow` intents, listing followed threads that are already read, and a truncation flag for the thread list. Thread: buzz://channel/6f773b94-34d0-4004-bec9-a100b6ad17d5 (#buzz-bff, root 31020fff) ### Related issue Follows #8157. Supersedes #8188; replaces the wire parts of the closed #8155. ### Testing - `cargo test --no-fail-fast -p buzz-db -p buzz-relay` at1f041e46: all pass except 7 relay unit tests (`api::media::tests` ×6 needing a seeded local DB, `api::mesh_demo` round trip) that fail identically on `main` (394d4a43) in the same environment. - `scripts/postgres-test-run.sh -p buzz-db -p buzz-relay` against local PG14 + Redis at88d2f6fc: 836 passed, 26 failed. 25 also fail on `main` in the same environment (partition maintenance tests and `huddle_started_links`, which need PG16). The 26th, `boot_lifecycle::hanging_redis_peer_times_out…`, is a timing assertion that failed under full-suite load and passed 3/3 when rerun alone on this branch. - All `personal_read`, `migration`, `buzz_v1`/`accessory` and NIP-FI wire-contract tests pass, including the new response-size bound test and the 25-thread `summarize` cap test. - `cargo fmt --check` and `cargo clippy -p buzz-db -p buzz-relay --tests -D warnings` are clean. - Not done: running a standalone relay binary (the dev object store rejected writes with HTTP 403). The relay tests drive the real HTTP router with NIP-98-signed requests. A human test against a local relay is still needed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Honey <8e307ae0076a4dab6b94b036ea3edc7e08f823a625269c1e6919e881a048b4d2@buzz.block.builderlab.xyz> Co-authored-by: Honey <8e307ae0076a4dab6b94b036ea3edc7e08f823a625269c1e6919e881a048b4d2@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
22 lines
1.2 KiB
Markdown
22 lines
1.2 KiB
Markdown
# Read-state accessory extension constraints
|
|
|
|
Design guidance for follow-up work, not implemented wire capabilities.
|
|
The current [API contract](buzz-v1-read-state.md) remains authoritative.
|
|
|
|
Thread rows are implemented (see `threads` in the API contract). Richer
|
|
previews that carry signed-event content should be opt-in and preserve the base sidebar's work/byte budgets;
|
|
fetching detail separately remains valid. Reuse canonical classification and
|
|
read frontiers, not a second definition of unread. Historical-mention eligibility
|
|
or mute-aware mentions requires an explicit policy contract, not an unnoticed
|
|
change to today's `mentions` field.
|
|
|
|
Channel-content revisions, personal-state revisions and synchronized preferences
|
|
are separate from read frontiers. Future revisions must describe the same
|
|
snapshot as their payload, and are invalidation tokens, not history cursors or
|
|
access grants. Count reuse also needs time/configuration validity because the
|
|
unread horizon moves without writes. Mutes and manual-unread overrides must not
|
|
be encoded by advancing or rewinding a read frontier.
|
|
|
|
These are extension constraints, not implemented capabilities: this version does
|
|
not advertise revision-based reuse or synchronized overrides.
|