Skip to content

feat(read-state): followed threads, follow intents and a threads view - #8264

Draft
baxen wants to merge 2 commits into
mainfrom
claude00/read-state-follows
Draft

baxen wants to merge 2 commits into
mainfrom
claude00/read-state-follows

Conversation

@baxen

@baxen baxen commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR replaces #8238 and #8154 with a single read-state change. It keeps the /buzz/v1 sidebar wire merged in #8220 and adds only what the follow model needs.

  • Thread replies count only in threads you follow (from feat(read-state): count thread replies only in followed threads #8238, unchanged). Ingest records a follow for:
    • the author;
    • channel members it p-tags;
    • the root's author;
    • every member, in a DM.
  • follow / unfollow intents on a thread target. An unfollow sticks until you post in the thread, which follows you through your post, or are mentioned there, which follows you from just before the mention. An explicit follow starts at the database's now().
  • GET /buzz/v1/me/threads: the threads view.
    • Lists followed threads in joined, nondeleted channels, read or not.
    • Ordered newest reply arrival first, then root_id.
    • Cursor-paged, at most 50 per page.
    • Each row is the sidebar thread row plus channel_id.
  • Thread mentions = direct mentions only: replies that p-tag you, or every unread reply in a DM.
  • Broadcast replies count on the channel timeline and are valid channel anchors. An author's own broadcast reply also reads their timeline.
  • Discovery: NIP-11 buzz_v1 gains capabilities: ["follow","threads"] and max_threads: 50. Clients gate on these.

Wire

Additive only. No field is removed or renamed, and existing clients keep working. See docs/buzz-v1-read-state.md (Following, Threads view, Intents, Deployment).

{"type":"follow","target":{"channel_id":"<uuid>","root_id":"<64-hex-root>"}}
{"type":"unfollow","target":{"channel_id":"<uuid>","root_id":"<64-hex-root>"}}

Engine

The sidebar and the threads view are now index probes by arrival, not bounded scans. MAX_TIMELINE_SCAN is gone.

  • events.read_timeline marks eligible top-level and broadcast messages. A partial index idx_events_read_timeline gives each channel's latest timeline arrival in one probe.
  • thread_metadata.received_at is set for eligible replies. idx_thread_metadata_read_arrival gives each followed thread's latest reply in one probe.
  • personal_read_frontiers.unfollowed stores the sticky unfollow, on thread rows only.
  • idx_event_mentions_pubkey_channel_created serves the mention walk.

Ingest writes one CTE per eligible message, in the same transaction, after the event insert. It adds no work for ineligible kinds.

Migration / deploy

Open decisions

  1. Replies in threads you don't follow count nowhere. A fallback, such as counting them on the channel row, is left out pending a call.
  2. ART's "fix 2" is left out: the author's own-position write on ingest is kept.
  3. A cross-device ping after an intent, so other devices refresh, is a follow-up.
  4. The thread_window prebuild test now runs its held-writer phase through 0060, because 0061's ADD COLUMN correctly waits on that writer's lock. The test then runs every migration after the rollback.

Tests

  • buzz-db Postgres tests (personal_read|migration|thread): 97/97.
    • New tests: follow/unfollow semantics, broadcast timeline and anchor, threads view order and paging.
    • Existing tests now insert through ingest.
  • buzz-relay Postgres tests (accessory|buzz_v1|nip11|thread|ingest): 42/42, including new router-level follow intent and /me/threads coverage.
  • Plain cargo test -p buzz-db / -p buzz-relay: the only failures are 6 media tests, which are environmental: they need a Postgres role buzz on :5432. cargo fmt --check and clippy -D warnings are clean.
  • Live relay built from this branch, with 22/22 checks through the buzz CLI and signed HTTP. Covered:
    • discovery;
    • an unfollowed reply not counting;
    • an explicit follow starting read;
    • a reply plus a direct mention;
    • a follow without a root returning invalid;
    • unfollow sticking, then a mention re-following;
    • a broadcast reply as the timeline latest and a valid anchor;
    • a top-level mention following its thread;
    • /me/threads order, limit=1 paging, bad cursor and limit returning 400, unsigned returning 401;
    • a read thread staying listed;
    • leaving a channel hiding its threads.

🤖 Generated with Claude Code

Honey and others added 2 commits October 11, 2026 15:22
Ingest records follows in the message's transaction: the author reads
through their own message, and mentioned channel members, the root's
author and DM members follow the thread just before it. The sidebar then
counts forward from each read position instead of resolving
participation and walking 4096 events back per channel, and counts stop
at 100. A thread mark_through moves an existing follow and never
creates one. participation.rs is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Honey <8e307ae0076a4dab6b94b036ea3edc7e08f823a625269c1e6919e881a048b4d2@buzz.block.builderlab.xyz>
…s paths

Extend the merged /buzz/v1 read-state wire with the minimum the follow
model needs:

- `follow` / `unfollow` intents on a thread target. An unfollow sticks
  until the actor posts in the thread or is mentioned there again.
- `GET /me/threads`: followed threads in joined channels, newest reply
  arrival first, cursor-paged, at most 50 per page.
- Thread rows count `mentions` as direct mentions only.
- Broadcast replies count on the channel timeline and are valid channel
  anchors.
- NIP-11 advertises `capabilities: ["follow","threads"]` and `max_threads`.

Migration 0061 adds `events.read_timeline`, `thread_metadata.received_at`
and `personal_read_frontiers.unfollowed`, plus arrival-ordered partial
indexes so the sidebar and threads view probe an index per channel and
per follow instead of scanning a bounded window. No backfill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
@jhedlund

Copy link
Copy Markdown
Contributor

@baxen This threads view is the list I've been wanting labels for. #6739 (thread titles) is the other half, and it might be worth designing them together.

Since NIP-AR landed (#7791, #7919), I've been running thread titles as a client-only convention on top of it, against my own relay:

  • Event: a kind 45010 artifact with type = buzz.thread-title. d = UUIDv5(fixed namespace, "buzz.thread-title:" + root id), so there is exactly one title per thread. h = channel, root = thread root. The text goes in a title tag and in content, so search finds it.
  • Edits: rename/clear use the normal artifact revisions (expected_revision from desktop, op=delete to clear). The relay needed no changes.
  • Desktop: the thread header has an editable title, and the sidebar has a Threads view ordered by latest activity, with pins (synced as a self-encrypted kind 30078) and a channel filter.
  • Agents: when a human starts a thread by mentioning an agent, that agent names it (buzz threads title set --if-unset), the way ChatGPT/Claude name a new chat. Later mentions never rename it.

Screenshots are on #6739. After a few weeks of daily use, it's the single biggest help in finding my way around a busy community.

Each /me/threads row already carries (channel_id, root_id), which is exactly the key a title is addressed by. A client can therefore fetch titles for a page of rows in one artifact query, and the view would need no relay changes for titles. If you'd rather the row carry the title itself, that would be a small addition on top.

Separately, Giszmo asked for the same thing on #6067 ("thread name … agents being able to adjust it").

Would you be open to a PR that adds the buzz.thread-title convention to the NIP-AR docs, along with the CLI command and the desktop header? I can keep my Threads screen out of it so it doesn't collide with whatever you build on this endpoint.

This branch has not been deployed

No deployments
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.

2 participants