Files
task-stream/libs/api/tracker/domain/src/lib/read-models/comment.read-model.ts
Josep Piguillem 199b60e4ce feat: conversations on a ticket are settled, and you see who is typing
A top-level comment and its replies can be resolved by whoever can
change the ticket (CommentThreadResolvedEvent), with an optional
conclusion added as a reply marked as a decision, so it shows above the
ticket. A settled conversation folds to one line (topic, who settled it
and the conclusion) and can be unfolded or reopened; the header counts
the open ones, and finishing the ticket with one open asks first.

«Ana está escribiendo…» shows in the conversation being answered, at
once: a `typing` mutation publishes to a Redis channel per ticket
(`typing` subscription), never stored. In replies, Enter sends and
Shift+Enter breaks the line, except inside lists and code.

API: resolveThread / PUT-DELETE /api/comments/{id}/resolution, Comment
.resolved/resolvedBy/resolvedAt, typing mutation and subscription. MCP:
resolve_thread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:35:46 +02:00

44 lines
1.4 KiB
TypeScript

import { UserRefReadModel } from './user-ref.read-model';
/** One emoji on a comment and who chose it. */
export class CommentReactionReadModel {
emoji!: string;
users!: UserRefReadModel[];
}
export class CommentReadModel {
id!: string;
issueId!: string;
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;
/** In the order they are offered; only the ones somebody chose. */
reactions!: CommentReactionReadModel[];
/** Marked as a decision of the ticket. */
decision!: boolean;
/** Who marked it and when (null when it is not a decision). */
decidedBy!: UserRefReadModel | null;
decidedAt!: string | null;
/** A top-level comment whose conversation was settled. */
resolved!: boolean;
/** Who settled it and when (null while open). */
resolvedBy!: UserRefReadModel | null;
resolvedAt!: string | null;
createdAt!: string;
editedAt!: string | null;
version!: number;
}
/** A decision with the ticket it belongs to, for searching across tickets. */
export class DecisionReadModel {
comment!: CommentReadModel;
issue!: { id: string; key: string; title: string };
}