feat: replies to comments
A comment can answer another one (replyTo), one level deep: answering a reply joins the same thread, and it must be on the same ticket. Whoever wrote the comment gets a new REPLIED notification, and the activity says "respondió a un comentario". A comment deleted while it has replies stays, without its text (deleted: true), so the thread still reads; it goes with its last reply and does not count in commentCount. The ticket page shows threads with their replies indented and a "Responder" box; REST, GraphQL and the MCP (add_comment, get_issue) take and show replyTo. Existing comments are unchanged. Docs, Word and PDF updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,12 @@ export interface CommentProps {
|
||||
projectId: string;
|
||||
authorId: string;
|
||||
body: string;
|
||||
/**
|
||||
* The comment this one answers; always a top-level one (one level of
|
||||
* replies). Null for a top-level comment, and on comments made before
|
||||
* there were replies.
|
||||
*/
|
||||
replyTo: string | null;
|
||||
deleted: boolean;
|
||||
createdAt: string;
|
||||
editedAt: string | null;
|
||||
@@ -22,12 +28,13 @@ export interface CommentProps {
|
||||
|
||||
const MAX_BODY = 20_000;
|
||||
|
||||
/** Decisions and explanations inside a ticket. */
|
||||
/** Decisions and explanations inside a ticket, and the replies to them. */
|
||||
export class Comment extends VersionedAggregateRoot implements CommentProps {
|
||||
public issueId!: string;
|
||||
public projectId!: string;
|
||||
public authorId!: string;
|
||||
public body!: string;
|
||||
public replyTo: string | null = null;
|
||||
public deleted = false;
|
||||
public createdAt!: string;
|
||||
public editedAt: string | null = null;
|
||||
|
||||
@@ -10,6 +10,8 @@ export class CommentFactory {
|
||||
projectId: string;
|
||||
authorId: string;
|
||||
body: string;
|
||||
/** Top-level comment it answers. */
|
||||
replyTo?: string | null;
|
||||
}): Comment {
|
||||
const data: CommentProps = {
|
||||
id: newId(),
|
||||
@@ -17,6 +19,7 @@ export class CommentFactory {
|
||||
projectId: props.projectId,
|
||||
authorId: props.authorId,
|
||||
body: validCommentBody(props.body),
|
||||
replyTo: props.replyTo ?? null,
|
||||
deleted: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
editedAt: null,
|
||||
|
||||
@@ -6,6 +6,13 @@ export class CommentReadModel {
|
||||
projectId!: string;
|
||||
author!: UserRefReadModel;
|
||||
body!: string;
|
||||
/** Top-level comment it answers; null for a top-level one. */
|
||||
replyTo!: string | null;
|
||||
/**
|
||||
* Deleted while it had replies: kept, without its text, so the
|
||||
* conversation under it still makes sense. Gone with its last reply.
|
||||
*/
|
||||
deleted!: boolean;
|
||||
createdAt!: string;
|
||||
editedAt!: string | null;
|
||||
version!: number;
|
||||
|
||||
@@ -12,6 +12,8 @@ export enum NotificationKind {
|
||||
COMMENTED = 'COMMENTED',
|
||||
/** Someone wrote @you. */
|
||||
MENTIONED = 'MENTIONED',
|
||||
/** Someone answered a comment of yours. */
|
||||
REPLIED = 'REPLIED',
|
||||
/** A ticket you follow changed. */
|
||||
WATCHED = 'WATCHED',
|
||||
/** You were added to a project. */
|
||||
|
||||
Reference in New Issue
Block a user