-- 0010_chat_session_read_at: per-session read cursor backing the session-list -- unread badge (DevDocs/design/16 §2.2/§4.2, Android 协同批次 D4/B3). There is a -- single owner account and no per-device identity at this layer, so the cursor is -- session-scoped: reading on any one end marks the session read for every end of -- the account (one cursor, no per-user/per-device cursor table). -- -- read_at is a Unix MILLISECOND timestamp in the same unit as -- chat_messages.created_at (both are INTEGER ms columns; the store assigns -- time.Now().UnixMilli()), so the unread count is a plain -- `created_at > read_at` comparison. 0 means "nothing read yet". -- -- Backfill on upgrade: every existing session's cursor is set to its newest -- message timestamp, so an upgrade does not suddenly light up the whole session -- list as unread (a session with no messages — and a session whose messages do not -- exist — stays at 0, which is also 0 unread). -- -- The 0003 chat_sessions table already exists at this point, so ALTER ADD COLUMN is -- the correct, non-destructive step. The canonical schema (including this column) is -- also re-issued idempotently by internal/runtime/history.NewStore so a fresh -- in-memory store used by tests stays self-contained; the migration is the -- authoritative path for an existing database. -- -- No new index is needed: the unread count filters -- `session_id = ? AND created_at > ?`, which the existing -- idx_chat_msgs_session_created (session_id, created_at DESC) already covers as an -- equality + range scan. ALTER TABLE chat_sessions ADD COLUMN read_at INTEGER NOT NULL DEFAULT 0; UPDATE chat_sessions SET read_at = COALESCE( (SELECT MAX(m.created_at) FROM chat_messages m WHERE m.session_id = chat_sessions.id), 0 );