Files
multica/server/migrations/585_issue_read_cursor.up.sql
Claude 3ad79c8548 feat(issues): per-user read cursors with unread dots on the feed
Before: the feed could not tell which conversations had moved since the
reader last looked, so every card read the same.

After: opening an issue (page or peek) records a per-user read cursor after
1.5 s, posting a comment records it at once, and feed cards whose newest
counted comment is later than the cursor carry a small dot on the left: a
plain dot when the reader spoke last, a ringed one when someone else did
and the reply is theirs to make.

How: table issue_read_cursor (no primary key in the create, a CONCURRENTLY
built unique index on (user_id, issue_id) attached as the primary key in a
later migration, no foreign keys), queries UpsertIssueReadCursor (monotonic
GREATEST on the database clock), ListIssueReadCursors and the three
cleanup deletes wired into issue deletion, member removal and workspace
teardown. PUT /api/issues/{id}/read returns 204; thread-summaries gains
last_read_at for the caller. Core adds api.markIssueRead, useMarkIssueRead
and patchThreadSummariesLastRead; the comment-created patch keeps the
cursor. Views add getFeedReadState and the dot on FeedMessageCard, the
delayed mark in IssueDetail and the immediate mark on send in
useIssueTimeline.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_017deksZDSzZBA4QSqZZoBip
2026-10-21 05:07:17 +00:00

20 lines
888 B
SQL

-- Per-user issue read cursors for the project feed's unread dots.
--
-- One row per (user, issue): last_read_at is the server time at which the user
-- last opened the issue. A card is unread when the issue's newest counted
-- comment is later than the cursor; no row means the user never opened it.
--
-- workspace_id is stored so member removal and workspace teardown can sweep a
-- user's or a workspace's cursors without joining issue. No foreign keys by
-- project rule: issue deletion, member removal and workspace deletion clean
-- the rows up in application code.
--
-- (user_id, issue_id) is attached as the primary key in 587 from the index
-- built CONCURRENTLY in 586 (repo convention, see 573-575).
CREATE TABLE IF NOT EXISTS issue_read_cursor (
user_id UUID NOT NULL,
issue_id UUID NOT NULL,
workspace_id UUID NOT NULL,
last_read_at TIMESTAMPTZ NOT NULL
);