feat: estimate tickets and see the team's velocity

Story points can be set on the ticket page and when creating a ticket,
and show on cards, column headers, subtasks and the backlog. The Sprints
tab charts the points finished per closed sprint and warns when a planned
sprint holds more than the recent average.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 15:04:11 +02:00
parent 50d3ffd1dd
commit 6aec774c92
16 changed files with 304 additions and 20 deletions

View File

@@ -44,6 +44,7 @@ import {
matches,
mentionQuery,
mentionSegments,
POINT_SCALE,
ReleaseStatus,
} from '@task-stream/web/tracker/domain';
import {
@@ -51,6 +52,7 @@ import {
initials,
LINK,
options,
pointOptions,
PRIORITY,
statusLook,
timeAgo,
@@ -248,7 +250,17 @@ const MAX_FILE_BYTES = 25 * 1024 * 1024;
</h2>
@if (facade.children().length) {
<pi-progress-bar
[label]="facade.childProgress().percent + '% terminado'"
[label]="
facade.childProgress().percent +
'% terminado' +
(facade.childProgress().storyPoints
? ' · ' +
facade.childProgress().finishedStoryPoints +
' de ' +
facade.childProgress().storyPoints +
' puntos'
: '')
"
[value]="facade.childProgress().percent"
/>
<ul class="ts-children">
@@ -618,6 +630,14 @@ const MAX_FILE_BYTES = 25 * 1024 * 1024;
>
<pi-label>Prioridad</pi-label>
</pi-select-field>
<pi-select-field
[options]="pointsOptions()"
[disabled]="!project.canContribute()"
[value]="issue.storyPoints === null ? '' : '' + issue.storyPoints"
(valueChange)="update({ storyPoints: $event ? +$event : null })"
>
<pi-label>Estimación</pi-label>
</pi-select-field>
<pi-select-field
[options]="areaOptions()"
[disabled]="!project.canContribute()"
@@ -801,6 +821,9 @@ export class IssuePage {
);
});
protected readonly priorityOptions = options(PRIORITY);
protected readonly pointsOptions = computed(() =>
pointOptions(POINT_SCALE, this.facade.issue()?.storyPoints ?? null),
);
/** Active values, plus the ticket's own even if archived since. */
protected readonly typeOptions = computed<PiSelectFieldOption<string>[]>(
() => {
@@ -964,6 +987,7 @@ export class IssuePage {
type?: string;
priority?: IssuePriority;
area?: string | null;
storyPoints?: number | null;
}): void {
this.facade.update(changes);
}