feat(threads): Slack-style thread panel for rooms and DMs #36

Merged
nrupard merged 1 commit from feat/threads into main 2026-05-06 16:15:21 +02:00
Owner
  • Add messages.parent_id (migration 0011); top-level messages keep parent_id IS NULL, replies hang off a single parent (one level deep, enforced at the route layer).
  • Scope main feed loaders, prior/next message lookups, and unread counts to top-level messages so thread replies do not bleed into the room timeline or sidebar badges.
  • New routes: GET /room/:rid/thread/:mid opens a side panel with the parent + replies + composer; POST /room/:rid/thread/:mid/messages posts a reply; DELETE /thread-panel closes the drawer. The same handlers cover DMs since access is gated by is_room_accessible.
  • Reply pill (N replies) renders under each top-level message; click reopens the panel. Pill updates live via OOB swap when a reply lands.
  • Realtime: ChatEvent::ThreadReply { parent_id, message } fans out to room recipients; per-recipient renderer emits the reply fragment (only takes effect if the panel is open for that parent) plus an OOB reply-count update for the main feed.
  • Typing indicators are scoped per-thread via a separate (room_id, parent_id, user_id) map in the hub, plumbed through new ThreadTyping/ThreadStoppedTyping events and a thread_typing client control frame, so room-composer typing and thread-composer typing never bleed into each other's UI.
  • Stop typing on send mirrors the room composer behavior added in #35.

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

- Add `messages.parent_id` (migration 0011); top-level messages keep `parent_id IS NULL`, replies hang off a single parent (one level deep, enforced at the route layer). - Scope main feed loaders, prior/next message lookups, and unread counts to top-level messages so thread replies do not bleed into the room timeline or sidebar badges. - New routes: `GET /room/:rid/thread/:mid` opens a side panel with the parent + replies + composer; `POST /room/:rid/thread/:mid/messages` posts a reply; `DELETE /thread-panel` closes the drawer. The same handlers cover DMs since access is gated by `is_room_accessible`. - Reply pill (`N replies`) renders under each top-level message; click reopens the panel. Pill updates live via OOB swap when a reply lands. - Realtime: `ChatEvent::ThreadReply { parent_id, message }` fans out to room recipients; per-recipient renderer emits the reply fragment (only takes effect if the panel is open for that parent) plus an OOB reply-count update for the main feed. - Typing indicators are scoped per-thread via a separate `(room_id, parent_id, user_id)` map in the hub, plumbed through new `ThreadTyping`/`ThreadStoppedTyping` events and a `thread_typing` client control frame, so room-composer typing and thread-composer typing never bleed into each other's UI. - Stop typing on send mirrors the room composer behavior added in #35. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add `messages.parent_id` (migration 0011); top-level messages keep `parent_id IS NULL`, replies hang off a single parent (one level deep, enforced at the route layer).
- Scope main feed loaders, prior/next message lookups, and unread counts to top-level messages so thread replies do not bleed into the room timeline or sidebar badges.
- New routes: `GET /room/:rid/thread/:mid` opens a side panel with the parent + replies + composer; `POST /room/:rid/thread/:mid/messages` posts a reply; `DELETE /thread-panel` closes the drawer. The same handlers cover DMs since access is gated by `is_room_accessible`.
- Reply pill (`N replies`) renders under each top-level message; click reopens the panel. Pill updates live via OOB swap when a reply lands.
- Realtime: `ChatEvent::ThreadReply { parent_id, message }` fans out to room recipients; per-recipient renderer emits the reply fragment (only takes effect if the panel is open for that parent) plus an OOB reply-count update for the main feed.
- Typing indicators are scoped per-thread via a separate `(room_id, parent_id, user_id)` map in the hub, plumbed through new `ThreadTyping`/`ThreadStoppedTyping` events and a `thread_typing` client control frame, so room-composer typing and thread-composer typing never bleed into each other's UI.
- Stop typing on send mirrors the room composer behavior added in #35.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nrupard deleted branch feat/threads 2026-05-06 16:15:21 +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!36
No description provided.