Skip to content

[5/10] Threaded replies in project and task discussions - #357

Merged
hturnbull93 merged 5 commits into
mainfrom
threaded-replies
Oct 24, 2026
Merged

hturnbull93 merged 5 commits into
mainfrom
threaded-replies

Conversation

@hturnbull93

@hturnbull93 hturnbull93 commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #356.

Screenshots: https://fedora.taila91c66.ts.net:8444/docs/20261021-134117-pr-357-threaded-replies.html

Discussions on projects, tasks and quick tasks could already hold one level of replies. This PR lets people reply to any comment, quotes the specific reply answered, folds long threads, and tells the person being replied to.

API (workItemComments.add)

  • A reply to a reply still lands in the same top-level thread, and the person told is the author of the comment that was replied to.
  • A new replied_to_id column, distinct from parent_id, records the specific comment a reply answers. It's set only when that's a reply to a reply — a reply straight to the thread's own top-level comment already sits under it, so quoting it back would say nothing new. workItemComments.list resolves it to the target's author and a plain-text snippet (mentions stripped, cut to 140 characters), or marks it removed if the target has since been deleted.
  • That person gets a new comment_reply notification: "X replied to your comment on …". It links to #comment-{id} and is sent by email unless they have muted the Updates category. The preference label already says "comments and replies".
  • Nobody is notified twice. A mention outranks a reply, and a reply outranks the plain "New comment" notice that creators, owners and assignees get. The thread starter still gets "New comment" when someone replies further down their thread.

UI (CommentThreadView)

  • Every live comment, replies included, has a Reply button. Its accessible name says who you are replying to ("Reply to Hal"). The reply box opens at the foot of the thread, labelled "Write a reply to Hal", and takes focus. Choosing a different comment moves the box and focus there. Submitting an empty reply still shows "Write something first."
  • A reply to a reply shows "Replying to Hal: "quoted snippet"" above its own text, so it's clear which earlier reply it answers in a thread with crosstalk. A reply straight to the thread's top-level comment shows no quote — its position already says that. If the quoted comment has since been removed, it shows "Replying to a removed comment" instead.
  • Each thread's replies are a nested <ul> named "Replies to ".
  • A thread with more than four replies shows the first reply and the last two, with a "Show N more replies" button between them. Pressing it shows the hidden replies and moves focus to the first of them. A permalink to a hidden reply, such as the one in the notification, opens its thread.

Tests

  • Router tests cover:
    • nesting depth
    • who is told, with the title, link and email
    • muted email
    • no double notification alongside a mention or the plain comment notice
    • a self-reply
    • an author whose account is gone
    • replying under a removed comment
    • an outsider refused
    • the quote on a reply to a reply, absent on a reply to the thread starter, truncated past 140 characters, and shown as removed once its target is deleted
  • Component tests cover:
    • Reply labels and focus
    • answering a reply
    • the empty-reply hint
    • list markup
    • folding and expanding, with focus
    • a permalink opening a folded thread
    • the quote shown on a reply to a reply, absent on a reply to the thread starter, and worded "a removed comment" for one since deleted
  • New e2e test in 07-project-management-owner.spec.ts: the owner replies, the reply nests under the comment, and the admin sees the reply notification and opens it. The test then seeds a long thread through the API, checks that it folds, and expands it. Snapshots: reply box open, long thread folded.

Decisions

  • Quick tasks get threading too. They share CommentThread and the same router. Bug report threads stay flat (BugReportCommentThread doesn't pass canReply): they are a back-and-forth between the reporter and admins, on a separate model.
  • Deleting and moderation: this is unchanged and already soft-deletes. A deleted comment, whether removed by its author or by a chapter admin, shows as "Comment removed" with its replies kept below it. A removed comment has no Reply button, and the API refuses a reply to it. People can still answer the remaining replies in that thread.
  • Notification category: a reply counts as an update, not needs action. It shows in the Inbox's Updates and doesn't go on the dashboard's needs-action list. This matches the existing email-preference wording.
  • Folding: a thread folds above 4 replies and keeps the first reply and the last two in view, so both the opening answer and the latest exchange stay visible.
  • A reply to a reply doesn't prefill an @mention of the person answered. They are told through the reply notification instead.
  • The quote is plain text, mentions stripped, the same treatment notification previews already get, so a long mention token can't be cut mid-token and the quote can't carry live @mention styling it has no composer to back up.
  • Why a separate column instead of reusing parent_id: parent_id groups a reply under its thread's root, which the existing fold/notification logic depends on staying flat. replied_to_id is purely who-answered-whom display information layered on top, so it doesn't disturb that.

Data impact: a new nullable replied_to_id column, backfilled as empty (no way to recover which reply an existing reply answered). No change to parent_id or deleted_at.

Closes #339

🤖 Generated with Claude Code

@hturnbull93 hturnbull93 changed the title Threaded replies in project and task discussions [6/12] Threaded replies in project and task discussions Oct 21, 2026
@hturnbull93 hturnbull93 changed the title [6/12] Threaded replies in project and task discussions [5/10] Threaded replies in project and task discussions Oct 22, 2026
… person replied to

A reply to a reply joins the same top-level thread but is still answered to
its own author, who gets a comment_reply notification (in the Inbox, and by
email unless they muted updates) in place of the plain new-comment notice.
A mention of them still wins. Threads of more than four replies show the
first and last two until "Show N more replies"; a permalink to a hidden
reply opens its thread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hturnbull93
hturnbull93 changed the base branch from join-by-claiming to main October 23, 2026 15:12
hturnbull93 and others added 4 commits October 23, 2026 17:54
…mment

A reply only ever named who it answered in the Reply button's accessible
name; the posted reply itself showed nothing, so a thread with crosstalk
left other readers unable to tell which earlier reply a given one was
actually responding to.

Each reply now carries the comment it replied to, via a new
replied_to_id column distinct from parent_id (which keeps grouping every
reply under its thread's root). It's set only when that's a reply to a
reply — a reply straight to the thread's top-level comment already sits
under it, so quoting it back would say nothing new. The list response
resolves it to the target's author and a plain-text snippet, cut to 140
characters, or marks it removed if the target has since been deleted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Each folded thread already had its own "Show N more replies" button, but
with several long threads on one page there was no way to open them all
at once. The new button only appears while at least one thread is still
folded, and expands every thread with more than COLLAPSE_ABOVE replies in
one click.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The byline previously showed a plain name. It now shows the author's
initials in a small circle before their name, with the name linking to
/volunteers/{id} (PersonInitials gains an 'xs' size for this). The byline
also moves above the comment body, matching the layout of chat apps like
Discord and Slack rather than sitting underneath it.

The quote shown above a reply to a reply gets the same avatar, an "up and
right" arrow marking it as a quote, and now renders above the replier's
own byline rather than below it, so the thing being answered reads before
who's answering it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… icon

The quote above a reply loses its left border, and its arrow is an SVG
drawn like the copy-link icon rather than the ↱ character. Copy link,
Reply, Edit and Delete move from the author line to a row under the
message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hturnbull93
hturnbull93 merged commit a6d9e18 into main Oct 24, 2026
3 checks passed
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.

Threaded replies in project and task discussions

1 participant