feat: epics, subtasks and checklists
Tickets can hang from another one of the same project, three levels deep at most (epic → ticket → subtask). The ticket page lists its subtasks with their progress, creates new ones and moves the ticket under an epic; a new Épicas tab shows every epic and how far it has got. Each ticket also gets a checklist for the small steps that don't deserve a ticket. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,11 @@ export class IssueFacade {
|
||||
readonly history = this.store.history;
|
||||
readonly outwardLinks = this.store.outwardLinks;
|
||||
readonly inwardLinks = this.store.inwardLinks;
|
||||
/** Tickets hanging from the open one, and how far they have got. */
|
||||
readonly children = this.store.children;
|
||||
readonly childProgress = this.store.childProgress;
|
||||
/** Ticked steps of the open ticket's checklist. */
|
||||
readonly checklistProgress = this.store.checklistProgress;
|
||||
readonly loading = this.store.loading;
|
||||
readonly error = this.store.error;
|
||||
/** Signed-in user's id (authors can edit their comments). */
|
||||
@@ -94,6 +99,47 @@ export class IssueFacade {
|
||||
);
|
||||
}
|
||||
|
||||
/** Places the open ticket under another one by its key; null: back to the top. */
|
||||
setParent(parentKey: string | null): void {
|
||||
this.withIssue((issueId) =>
|
||||
this.dispatch.parentRequested({ issueId, parentKey }),
|
||||
);
|
||||
}
|
||||
|
||||
/** Creates a ticket under the open one. */
|
||||
addSubtask(title: string, type: string): void {
|
||||
const issue = this.store.issue();
|
||||
if (!issue || !title.trim()) return;
|
||||
this.dispatch.subtaskCreateRequested({
|
||||
projectId: issue.projectId,
|
||||
parentId: issue.id,
|
||||
type,
|
||||
title: title.trim(),
|
||||
});
|
||||
}
|
||||
|
||||
addChecklistItem(text: string): void {
|
||||
if (!text.trim()) return;
|
||||
this.withIssue((issueId) =>
|
||||
this.dispatch.checklistAddRequested({ issueId, text: text.trim() }),
|
||||
);
|
||||
}
|
||||
|
||||
updateChecklistItem(
|
||||
itemId: string,
|
||||
changes: { text?: string; done?: boolean },
|
||||
): void {
|
||||
this.withIssue((issueId) =>
|
||||
this.dispatch.checklistUpdateRequested({ issueId, itemId, changes }),
|
||||
);
|
||||
}
|
||||
|
||||
removeChecklistItem(itemId: string): void {
|
||||
this.withIssue((issueId) =>
|
||||
this.dispatch.checklistRemoveRequested({ issueId, itemId }),
|
||||
);
|
||||
}
|
||||
|
||||
/** Attaches files to the ticket (or under one of its comments). */
|
||||
attach(files: File[], commentId: string | null = null): void {
|
||||
if (!files.length) return;
|
||||
|
||||
Reference in New Issue
Block a user