Skip to content

feat: who replied last on a thread, and which reply solved a topic - #348

Merged
grytchat merged 1 commit into
mainfrom
claude/thread-repliers-answer
Oct 28, 2026
Merged

grytchat merged 1 commit into
mainfrom
claude/thread-repliers-answer

Conversation

@grytchat

Copy link
Copy Markdown
Contributor

The server half of C18 and C21/C22 on the design canvas. Part of GRYT-1963. Touches src/db, so it waits for your review.

Recent repliers (C18)

  • listRecentRepliers(threadIds) in threads.ts returns up to three distinct people who replied most recently in each thread, newest first. It's one grouped query over messages.thread_id, and it doesn't count the root's author.
  • chat:fetch includes it on each page thread as recent_repliers. So does the thread:updated a reply sends. Status and tag updates leave it out, and the phone keeps what it had.

The answer (C21/C22)

  • threads.answer_message_id TEXT, added with ALTER TABLE like threads.tags. Null for every existing thread.
  • thread:status:set takes an optional answerMessageId. It only counts with solved, and has to be a reply in that topic (message.thread_id === thread.thread_id). Otherwise it's thread:error { error: "bad_answer" } and nothing changes. Any other status clears it, so reopening forgets the answer.
  • The permission check is the existing one: the author or manage_messages.
  • toThreadSummary carries answer_message_id, so every summary has it.

What to look at

  • The GROUP BY thread_id, sender_server_id ORDER BY MAX(created_at) query. It's bounded by the page's thread ids, at most a page of threads, but it does scan their replies.
  • Running listRecentRepliers once per reply in chat:send. It's one indexed query (idx_messages_thread) on the send path.
  • threadSummaryShape.test.ts now expects answer_message_id everywhere, and recent_repliers on chat:fetch only.

Tests: listRecentRepliers (three, newest first, once each, none for none), the answer being kept, broadcast and dropped on reopen, and a refused answer that isn't a reply. All 2139 server tests pass, and the build, lint and comment check are clean.

The phone half is a separate mobile PR. An older server sends neither field, and the phone handles that.

🤖 Generated with Claude Code

A page's thread summaries (chat:fetch) and the update a reply sends now carry
recent_repliers: up to three people who replied most recently, newest first,
for the faces on a replies line. A thread also keeps answer_message_id: it's
set by thread:status:set when a topic is marked solved with answerMessageId,
which has to be a reply in that topic, and any other status drops it.

The column is added with ALTER TABLE like threads.tags. An older client ignores
both fields, and an older server sends neither, so the phone treats them as
absent.

Part of GRYT-1963.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@grytchat
grytchat requested a review from sivert-io as a code owner October 28, 2026 11:53
@grytchat
grytchat merged commit 2533dc5 into main Oct 28, 2026
8 checks passed
@grytchat
grytchat deleted the claude/thread-repliers-answer branch October 28, 2026 11:58
grytchat added a commit that referenced this pull request Oct 28, 2026
#348 added the answer to every summary built by toThreadSummary, but
thread:fetch builds its thread object by hand, so opening a solved topic
didn't show which reply answered it. Found checking the phone against it.

Part of GRYT-1963.

Co-authored-by: Sivert <316918682+grytchat@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants