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>
This commit is contained in:
@@ -81,6 +81,11 @@ export const issuePageEvents = eventGroup({
|
||||
commentEditRequested: type<{ commentId: string; body: string }>(),
|
||||
reactRequested: type<{ commentId: string; emoji: string; on: boolean }>(),
|
||||
decisionRequested: type<{ commentId: string; on: boolean }>(),
|
||||
threadResolveRequested: type<{
|
||||
commentId: string;
|
||||
resolved: boolean;
|
||||
conclusion: string | null;
|
||||
}>(),
|
||||
voteRequested: type<{ issueId: string; on: boolean }>(),
|
||||
/** An image pasted into a text was uploaded (it joins the ticket's files). */
|
||||
imagePasted: type<{ issueId: string }>(),
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { type } from '@ngrx/signals';
|
||||
import { eventGroup } from '@ngrx/signals/events';
|
||||
import { Presence } from '@task-stream/web/tracker/domain';
|
||||
import { Presence, Typing } from '@task-stream/web/tracker/domain';
|
||||
|
||||
export const presencePageEvents = eventGroup({
|
||||
source: 'Issue Presence',
|
||||
events: {
|
||||
opened: type<string>(),
|
||||
editingChanged: type<boolean>(),
|
||||
/** I typed in an answer to this conversation (null: a new comment). */
|
||||
typed: type<string | null>(),
|
||||
left: type<void>(),
|
||||
},
|
||||
});
|
||||
@@ -15,5 +17,9 @@ export const presenceApiEvents = eventGroup({
|
||||
source: 'Presence API',
|
||||
events: {
|
||||
seen: type<Presence[]>(),
|
||||
/** Someone else is writing. */
|
||||
typing: type<Typing>(),
|
||||
/** …and stopped a while ago (the `at` it was last seen). */
|
||||
typingStopped: type<{ userId: string; at: string }>(),
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user