feat: move unfinished tickets between releases

- Bulk editing gets «Release…» (open releases or none), and the list
  view a release filter, also reachable from each release («Ver en la
  lista», ?release=).
- Archiving a release asks where its unfinished tickets go, as closing
  a sprint does: ReleaseArchivedEvent carries `moveTo` (an open release
  of the same project, or none) and the saga moves them there; finished
  ones stay. In GraphQL, REST and the MCP's archive_release.
- Chapter 10 lists the next features and the ideas to weigh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 21:55:45 +02:00
parent 0a73eda9b2
commit 993f80b4b9
34 changed files with 408 additions and 48 deletions

View File

@@ -26,7 +26,10 @@ export const projectPageEvents = eventGroup({
memberRemoveRequested: type<{ projectId: string; userId: string }>(),
releaseCreateRequested: type<CreateReleaseInput>(),
releasePublishRequested: type<{ releaseId: string; force: boolean }>(),
releaseArchiveRequested: type<{ releaseId: string }>(),
releaseArchiveRequested: type<{
releaseId: string;
moveTo: string | null;
}>(),
releaseNotesWriteRequested: type<{
releaseId: string;
notes: string | null;

View File

@@ -144,8 +144,9 @@ export class ProjectFacade {
return this.releasesGateway.notesDraft(releaseId);
}
archiveRelease(releaseId: string): void {
this.dispatch.releaseArchiveRequested({ releaseId });
/** Its unfinished tickets go to `moveTo` (an open release) or leave it. */
archiveRelease(releaseId: string, moveTo: string | null = null): void {
this.dispatch.releaseArchiveRequested({ releaseId, moveTo });
}
/** Replaces statuses, transitions, types and areas (owners and admins). */

View File

@@ -550,7 +550,7 @@ export const ProjectStore = signalStore(
.pipe(
mergeMap(({ payload }) =>
runCommand(
releases.archive(payload.releaseId),
releases.archive(payload.releaseId, payload.moveTo),
'Release archivada',
projectApiEvents.commandSucceeded,
projectApiEvents.commandFailed,