feat: teams within a project

Projects keep teams of members in their settings (settings.teams; people
outside the project are dropped, teams are archived, not removed; absent
when saving means unchanged). Tickets go to a team (`team`, also filtered
in the list and in MCP search_issues), and a comment mentioning
@[Frontend](team-FRONT) tells each of its members. Settings has the teams
with their members, the ticket a «Equipo» select, the list a team filter,
and «@» offers the teams after the people.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 16:20:35 +02:00
parent 4acf629106
commit 869acd3f1f
41 changed files with 895 additions and 13 deletions

View File

@@ -46,6 +46,7 @@ export interface CreateIssueInput {
title: string;
description?: string | null;
area?: string | null;
team?: string | null;
priority?: IssuePriority;
labels?: string[];
figmaUrl?: string | null;
@@ -98,6 +99,7 @@ export interface IssueListFilter {
search?: string;
categories?: StatusCategory[];
types?: string[];
teams?: string[];
priorities?: IssuePriority[];
assigneeId?: string;
unassigned?: boolean;

View File

@@ -131,6 +131,8 @@ export interface IssueSummary {
/** Ids of the project's settings. */
type: string;
area: string | null;
/** A team id of the project's settings. */
team: string | null;
title: string;
status: string;
statusCategory: StatusCategory;

View File

@@ -84,6 +84,17 @@ export interface CustomFieldDefinition {
archived: boolean;
}
/** A group of the project's members; `@[Name](team-ID)` tells them all. */
export interface TeamDefinition {
id: string;
name: string;
memberIds: string[];
archived: boolean;
}
/** Prefix of a team's id in a mention. */
export const TEAM_MENTION_PREFIX = 'team-';
/** A ticket's value of a custom field. */
export interface CustomFieldValue {
fieldId: string;
@@ -99,6 +110,7 @@ export interface ProjectSettings {
customFields: CustomFieldDefinition[];
/** Definition of done: a DONE status only with the checklist ticked. */
checklistBeforeDone?: boolean;
teams: TeamDefinition[];
}
export const FINISHED_CATEGORIES: readonly StatusCategory[] = [
@@ -134,6 +146,7 @@ const EMPTY: ProjectSettings = {
issueTypes: [],
areas: [],
customFields: [],
teams: [],
};
/**
@@ -187,6 +200,15 @@ export class Workflow {
return this.settings.areas.filter((a) => !a.archived);
}
/** Teams to pick (archived ones only show where a ticket still has them). */
activeTeams(): TeamDefinition[] {
return (this.settings.teams ?? []).filter((t) => !t.archived);
}
team(id: string): TeamDefinition | undefined {
return this.settings.teams?.find((t) => t.id === id);
}
/** Custom fields to fill in (archived ones only show where they have a value). */
activeCustomFields(): CustomFieldDefinition[] {
return (this.settings.customFields ?? []).filter((f) => !f.archived);