Whoever can change the ticket flags a comment as a decision (new event
CommentDecisionMarkedEvent on the comment). The ticket page lists its
decisions above the description, with a link to the comment in the
conversation (`?comment=<id>` scrolls to it and points at it), and the
search palette finds them by their text across my projects.
API: markDecision / PUT-DELETE /api/comments/{id}/decision; Issue.decisions
and GET /api/issues/{issue}/decisions; decisions(projectId?, search) and
GET /api/projects/{project}/decisions, GET /api/me/decisions. MCP:
mark_decision, search_decisions, and get_issue includes them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
107 lines
3.5 KiB
TypeScript
107 lines
3.5 KiB
TypeScript
import { type } from '@ngrx/signals';
|
|
import { eventGroup } from '@ngrx/signals/events';
|
|
import {
|
|
Activity,
|
|
ChangeStatusInput,
|
|
Comment,
|
|
CommentChanged,
|
|
Issue,
|
|
IssueSummary,
|
|
LinkIssuesInput,
|
|
UpdateIssueInput,
|
|
} from '@task-stream/web/tracker/domain';
|
|
|
|
export const issuePageEvents = eventGroup({
|
|
source: 'Issue Page',
|
|
events: {
|
|
opened: type<{ idOrKey: string }>(),
|
|
statusChangeRequested: type<ChangeStatusInput>(),
|
|
assignRequested: type<{ issueId: string; assigneeId: string | null }>(),
|
|
updateRequested: type<UpdateIssueInput>(),
|
|
linkRequested: type<LinkIssuesInput>(),
|
|
/** Relate by the other ticket's key (e.g. BE-3), resolved to its id first. */
|
|
linkByKeyRequested: type<{
|
|
issueId: string;
|
|
type: LinkIssuesInput['type'];
|
|
targetKey: string;
|
|
}>(),
|
|
unlinkRequested: type<LinkIssuesInput>(),
|
|
sprintChangeRequested: type<{
|
|
issueId: string;
|
|
sprintId: string | null;
|
|
}>(),
|
|
releaseChangeRequested: type<{
|
|
issueId: string;
|
|
releaseId: string | null;
|
|
}>(),
|
|
commentAddRequested: type<{
|
|
issueId: string;
|
|
body: string;
|
|
/** Uploaded under the new comment once it exists. */
|
|
files?: File[];
|
|
/** Comment it answers (one level of replies). */
|
|
replyTo?: string | null;
|
|
}>(),
|
|
watchRequested: type<{ issueId: string; watching: boolean }>(),
|
|
pinRequested: type<{ issueId: string; pinned: boolean }>(),
|
|
trashRequested: type<{ issueId: string; trashed: boolean }>(),
|
|
/** Place the ticket under another one by its key (e.g. WEB-3); null: out. */
|
|
parentRequested: type<{ issueId: string; parentKey: string | null }>(),
|
|
subtaskCreateRequested: type<{
|
|
projectId: string;
|
|
parentId: string;
|
|
type: string;
|
|
title: string;
|
|
}>(),
|
|
checklistAddRequested: type<{ issueId: string; text: string }>(),
|
|
checklistUpdateRequested: type<{
|
|
issueId: string;
|
|
itemId: string;
|
|
changes: { text?: string; done?: boolean };
|
|
}>(),
|
|
checklistRemoveRequested: type<{ issueId: string; itemId: string }>(),
|
|
attachRequested: type<{
|
|
issueId: string;
|
|
files: File[];
|
|
commentId?: string | null;
|
|
}>(),
|
|
attachmentRemoveRequested: type<{
|
|
issueId: string;
|
|
attachmentId: string;
|
|
}>(),
|
|
commentEditRequested: type<{ commentId: string; body: string }>(),
|
|
reactRequested: type<{ commentId: string; emoji: string; on: boolean }>(),
|
|
decisionRequested: type<{ commentId: string; on: boolean }>(),
|
|
voteRequested: type<{ issueId: string; on: boolean }>(),
|
|
/** An image pasted into a text was uploaded (it joins the ticket's files). */
|
|
imagePasted: type<{ issueId: string }>(),
|
|
commentDeleteRequested: type<{ commentId: string }>(),
|
|
},
|
|
});
|
|
|
|
export const issueApiEvents = eventGroup({
|
|
source: 'Issue API',
|
|
events: {
|
|
loaded: type<Issue>(),
|
|
loadFailed: type<string>(),
|
|
commentsLoaded: type<Comment[]>(),
|
|
historyLoaded: type<Activity[]>(),
|
|
childrenLoaded: type<IssueSummary[]>(),
|
|
commandSucceeded: type<string>(),
|
|
/** A checklist step was saved (no toast for something so small). */
|
|
checklistSaved: type<void>(),
|
|
commandFailed: type<string>(),
|
|
},
|
|
});
|
|
|
|
export const issueRealtimeEvents = eventGroup({
|
|
source: 'Issue Realtime',
|
|
events: {
|
|
/** Something about this ticket changed: refetch it. */
|
|
issueTouched: type<{ issueId: string; version: number }>(),
|
|
/** A ticket under this one changed, arrived or left: reload them. */
|
|
childrenTouched: type<{ issueId: string }>(),
|
|
commentChanged: type<CommentChanged>(),
|
|
},
|
|
});
|