feat(threads): thread following / subscribe (LC-310) #332

Merged
longjacksonle merged 1 commit from feat/lc-310-thread-following into main 2026-06-15 03:48:01 +02:00

Summary

Follow a thread to be notified of every new reply, even without an @mention. A Follow/Following toggle in the thread panel header; replying auto-follows the replier, and the thread root's author is auto-followed.

Approach

Follower notifications reuse the mention fan-out (fanout_mention_events): on a new reply, each follower (minus the author, minus anyone who lost room access) gets a kind="thread" WS toast + push + email, all honoring prefs + per-room mute. Follows are NOT mentions rows - a follow is not a per-message mention, and routing through reconcile_mentions would fight the edit path (which deletes follower rows). A standalone chat.db thread_followers table keyed by the thread root, FK ON DELETE CASCADE.

Changes

  • migrations/chat/0060_thread_followers.sql + db/thread_followers.rs (follow/unfollow/is_following/followers).
  • routes/room.rs: post_thread_reply auto-follows replier + root author and calls notify_thread_followers; new post_thread_follow / delete_thread_follow handlers; get_thread_panel loads is_following.
  • views/room.rs: ThreadPanelFragment.is_following + ThreadFollowFragment. partials/thread_follow_button.html + thread-panel header inclusion.
  • routes/mod.rs: POST/DELETE /room/{id}/thread/{parent_id}/follow.
  • layout.html: notify-bus thread title branch ("{author} replied in {room}").
  • locales/{en,es}/room.ftl: follow/following strings.

Testing

  • just check, just fmt, just test, just test-saas all pass.
  • db_thread_followers.rs (follow/unfollow/idempotent/followers + FK cascade on root delete) and routes_thread_follow.rs (reply auto-follows replier + root author; follow/unfollow endpoints flip state; non-participant not followed).
  • No LC-77 fixture change (message.html untouched).

Not operator-visible (no env/config/contract change).

## Summary Follow a thread to be notified of every new reply, even without an @mention. A Follow/Following toggle in the thread panel header; replying auto-follows the replier, and the thread root's author is auto-followed. ## Approach Follower notifications reuse the mention fan-out (`fanout_mention_events`): on a new reply, each follower (minus the author, minus anyone who lost room access) gets a `kind="thread"` WS toast + push + email, all honoring prefs + per-room mute. Follows are NOT `mentions` rows - a follow is not a per-message mention, and routing through `reconcile_mentions` would fight the edit path (which deletes follower rows). A standalone `chat.db` `thread_followers` table keyed by the thread root, FK `ON DELETE CASCADE`. ## Changes - `migrations/chat/0060_thread_followers.sql` + `db/thread_followers.rs` (follow/unfollow/is_following/followers). - `routes/room.rs`: `post_thread_reply` auto-follows replier + root author and calls `notify_thread_followers`; new `post_thread_follow` / `delete_thread_follow` handlers; `get_thread_panel` loads `is_following`. - `views/room.rs`: `ThreadPanelFragment.is_following` + `ThreadFollowFragment`. `partials/thread_follow_button.html` + thread-panel header inclusion. - `routes/mod.rs`: `POST`/`DELETE /room/{id}/thread/{parent_id}/follow`. - `layout.html`: notify-bus `thread` title branch ("{author} replied in {room}"). - `locales/{en,es}/room.ftl`: follow/following strings. ## Testing - `just check`, `just fmt`, `just test`, `just test-saas` all pass. - `db_thread_followers.rs` (follow/unfollow/idempotent/followers + FK cascade on root delete) and `routes_thread_follow.rs` (reply auto-follows replier + root author; follow/unfollow endpoints flip state; non-participant not followed). - No LC-77 fixture change (message.html untouched). Not operator-visible (no env/config/contract change).
feat(threads): thread following / subscribe (LC-310)
All checks were successful
check-secrets / TruffleHog (push) Successful in 4s
check-secrets / Nosey parker (push) Successful in 5s
check-secrets / Kingfisher (push) Successful in 6s
check-secrets / TruffleHog (pull_request) Successful in 4s
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / Kingfisher (pull_request) Successful in 7s
Check / clippy + fmt + tests (pull_request) Successful in 2m15s
Create release / Create release from merged PR (pull_request) Has been skipped
a1746ee2d6
Follow a thread to be notified of every new reply, even without an @mention. A Follow/Following toggle in the thread panel header subscribes/unsubscribes; replying auto-follows the replier, and the thread root's author is auto-followed so they hear about replies to their message.

Follower notifications reuse the mention fan-out (fanout_mention_events): on a new reply, each follower (minus the author, and minus anyone who lost room access) gets a kind="thread" WS toast + push + email, all honoring the recipient's prefs and per-room mute. Follows are deliberately NOT mentions rows - a follow is not a per-message mention, and routing it through reconcile_mentions would fight the edit path (which would delete follower rows). Instead a standalone chat.db thread_followers table keyed by the thread root (FK ON DELETE CASCADE, so deleting the root cleans up).

The notify-bus gains a "thread" branch so the browser notification reads "{author} replied in {room}".

#LC-310
#LC-311

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/lc-310-thread-following 2026-06-15 03:48:01 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!332
No description provided.