Skip to content

feat(dm): build the inbox from GET /api/dm/conversations (#85) - #90

Merged
Adron merged 1 commit into
parity/queuefrom
issue/85-dm-conversations
Sep 16, 2026
Merged

Adron merged 1 commit into
parity/queuefrom
issue/85-dm-conversations

Conversation

@Adron

@Adron Adron commented Sep 16, 2026 •

Copy link
Copy Markdown
Member

Closes #85. Part of epic #84.

What changed

The DM inbox now renders GET /api/dm/conversations — one row per conversation, grouped by
pairKey, newest activity first — instead of paging the flat GET /api/dm message list and
grouping client-side. Cursor paging and the offline-first Room-backed pattern are unchanged; this
is a data-source swap, not a rewrite. Send / read / trash / restore / recipients / image upload
and /updates polling are untouched.

  • New ConversationDto + ConversationEntity + Conversation domain type — the row is modelled
    in its own right rather than reusing the message entity.
  • ConversationEntity carries pairKey and a real unreadCount: Int (replacing the
    client-derived hasUnread); DB version = 2, destructive migration was already enabled.
  • DirectMessagesApi.getInbox removed, getConversations added.
  • The unread badge still works: hasUnread is now unreadCount > 0, and the cached per-conversation
    counts are asserted to agree with GET /api/dm/unread-count.

Drive-by fix

POST /api/dm returns { "message": { … } }, which SendMessageResponse never unwrapped — a sent
message kept its optimistic local-… id, so a later read/trash on it would 404. Fixed, with a
regression test.

Verification

./gradlew :app:assembleDebug testDebugUnitTest → BUILD SUCCESSFUL. A --rerun-tasks pass reports
695 unit tests, 0 failures. :feature:directmessages:compileDebugAndroidTestKotlin also
compiles. Instrumented tests not run (no emulator available to the agent).

New/extended tests cover: DTO parsing (canonical nested row, flattened preview/lastMessageAt
row under the conversations key, a defensive row with explicit nulls + missing fields + unknown
keys, an empty row, an empty body); one row per conversation, newest-first; cursor paging appends
page 2; unread counts summing to the unread-count endpoint; rows without a participant username
skipped; opening a thread zeroing unread; the send envelope.

Reviewer note — the one soft spot

/api/dm/conversations is declared as a bare object in the OpenAPI spec and is not documented at
/help/api/direct-messages at all, so the row shape is inferred, not confirmed. The DTO accepts
the participant under otherUser/user/author and the newest message either nested
(lastMessage/message) or flattened (preview/lastMessageBody/lastMessageAt). One
authenticated curl against the live endpoint would settle it and let the DTO be trimmed down.

Also note GET /api/dm is now unimplemented — if a Sent/Deleted folder view is wanted later it
needs re-adding.

The inbox paged `GET /api/dm` — a flat message list — and grouped it
client-side, so a row was really "the newest message that happened to be
cached" rather than a conversation. `GET /api/dm/conversations` returns one
row per conversation (grouped by `pairKey`, newest activity first), which is
what the inbox should render.

- Add `ConversationDto` + `ConversationsResponse` as the conversation row's
  own wire model. The endpoint is unmodelled in the OpenAPI spec and absent
  from the help centre, so the DTO is tolerant: optional everywhere, the
  participant accepted as `otherUser`/`user`/`author`, the newest message
  either nested (`lastMessage`/`message`) or flattened
  (`preview`/`lastMessageAt`/...), the list under `items` or `conversations`.
- `ConversationEntity` now stores the server's `pairKey` and a per-conversation
  `unreadCount` instead of a client-derived `hasUnread` flag; the `Conversation`
  domain type derives `hasUnread` from it, so the inbox unread badge and unread
  dot keep working unchanged. DB version 2 (destructive migration is enabled).
- `refreshInbox` keeps its cursor-paging contract and offline-first Room-backed
  shape; only the data source changed. Pages merge, so paging appends.
- Remove `getInbox`/`InboxResponse` and the client-side grouping helper: nothing
  needed the flat list any more (the thread view uses the by-username endpoint).
- Unwrap `POST /api/dm`'s documented `{ "message": ... }` envelope, found while
  reading the DM help centre for the conversation shape; without it a sent
  message kept its optimistic local id, so read/trash on it would 404.

Tests: conversation DTOs parse (canonical, flattened and a defensive case with
explicit nulls, missing fields and unknown keys); the inbox emits one row per
conversation newest-first; cursor paging forwards the cursor and appends;
cached per-conversation unread counts sum to `GET /api/dm/unread-count`;
opening a thread zeroes that conversation's unread count.

Closes #85
@Adron
Adron merged commit 57e257f into parity/queue Sep 16, 2026
1 check passed
@Adron Adron mentioned this pull request Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant