fix(server): keyset message pagination + attachments in List (F36, F37)#23
Conversation
F36 -- pagination lost messages at timestamp ties. The before-cursor compared created_at alone (strictly older) with no ORDER BY tie-breaker: messages sharing the cursor's exact created_at were neither strictly older (skipped forever) nor stably ordered across pages. The cursor is now a keyset on (created_at, id) -- `(m.created_at, m.id) < ($cursorTime, $cursorId)` with `ORDER BY created_at DESC, id DESC` in both branches. Postgres row comparison gives the exact semantics: older, OR same timestamp with a smaller id. The client cursor stays the oldest message id; no client change. F37 -- List omitted attachments entirely. It bulk-fetched reactions but never attachments, so they only ever arrived via realtime MESSAGE_CREATE: any reload or history scroll showed attachment messages without their attachments. List now bulk-fetches attachments the same way (message_id = ANY, mapped by message, ordered by created_at), additive to the response shape the client already renders. Tests (both mutation-verified): 55 messages forced onto one timestamp -- under the old cursor page 2 returned zero (5 messages silently lost); with the keyset both pages cover all 55 exactly once. Attachments: a two-attachment message lists them in order with URLs while a plain message has none; disabling the fetch fails it. Full Go suite green; gofmt/vet clean; golangci-lint 0 new issues vs main.
|
Reviewed PR #23 at
Validation performed at the exact head:
Changes requested. GitHub would not accept a formal changes-requested review because the authenticated account owns the PR, so this verdict is posted as a PR comment instead. |
|
Pushed the three requested blocker fixes directly to Commit: Changes:
Validation at this exact head:
|
Two confirmed server findings from the campaign audit, both in
MessageHandler.List:F36 — pagination loses messages at timestamp ties. The
beforecursor comparedcreated_atalone with noORDER BYtie-breaker: messages sharing the cursor's exactcreated_atwere neither strictly older (skipped forever) nor stably ordered across pages. Fixed with a keyset cursor on(created_at, id)andORDER BY created_at DESC, id DESCin both branches. The client cursor stays the oldest message id — no client change.F37 — List omits attachments. Reactions were bulk-fetched; attachments never were, so they only arrived via realtime
MESSAGE_CREATE— any reload or history scroll showed attachment messages without them. List now bulk-fetches attachments the same way (additive to the response shape the client already renders).Both fixes are query-only: no migration, no config, no client change.
Tests (both mutation-verified):
Full Go suite green; gofmt/vet clean; golangci-lint 0 new issues vs main.