FN-5822: wire shared branch-group visibility and merge controls

Add dashboard and API support for viewing shared branch groups and triggering group merge actions.

- add branch-group API routes and register them in integrated routers
- extend dashboard legacy API client with branch-group fetch and merge-control actions
- add BranchGroupCard UI + styles and surface it in task/detail/subtask views
- add dashboard/API test coverage for branch-group routes and UI integration points
- document branch-group behavior and add a changeset for @runfusion/fusion

Files changed:
 .changeset/fn-5822-branch-group-dashboard.md       |   5 +
 docs/dashboard-guide.md                            |  31 ++++++
 packages/dashboard/app/api/legacy.ts               |  54 +++++++++
 .../dashboard/app/components/BranchGroupCard.css   |  83 ++++++++++++++
 .../dashboard/app/components/BranchGroupCard.tsx   | 123 +++++++++++++++++++++
 .../app/components/SubtaskBreakdownModal.tsx       |   3 +
 packages/dashboard/app/components/TaskCard.tsx     |  19 ++++
 .../dashboard/app/components/TaskDetailModal.tsx   |   4 +
 .../components/__tests__/BranchGroupCard.test.tsx  |  96 ++++++++++++++++
 .../__tests__/SubtaskBreakdownModal.test.tsx       |   1 +
 .../app/components/__tests__/TaskCard.test.tsx     |  16 ++++
 .../components/__tests__/TaskDetailModal.test.tsx  |  22 ++++
 .../src/__tests__/routes-branch-groups.test.ts     | 119 ++++++++++++++++++++
 .../src/routes/register-branch-groups-routes.ts    | 118 ++++++++++++++++++++
 .../src/routes/register-integrated-routers.ts      |  13 +++
 packages/dashboard/vitest.config.ts                |   4 +-
 16 files changed, 709 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-5822

Fusion-Task-Lineage: 199c25b8-f6ec-43b4-8fab-509f23fb5ac7
This commit is contained in:
gsxdsm
2026-06-01 04:47:19 -07:00
parent 9c29e2e776
commit e9de195ef5
16 changed files with 709 additions and 2 deletions

View File

@@ -77,6 +77,8 @@ import type {
ProjectNodePathMapping,
ApprovalRequestStatus,
TaskIdIntegrityReport,
BranchGroup,
BranchGroupPrState,
} from "@fusion/core";
import type { PlanningQuestion, PlanningSummary } from "@fusion/core";
import type { GithubIssueAction, ScheduledTask, ScheduledTaskCreateInput, ScheduledTaskUpdateInput, AutomationRunResult, Routine, RoutineCreateInput, RoutineUpdateInput, RoutineExecutionResult } from "@fusion/core";
@@ -564,6 +566,58 @@ export function mergeTask(id: string, projectId?: string): Promise<MergeResult>
return api<MergeResult>(withProjectId(`/tasks/${id}/merge`, projectId), { method: "POST" });
}
export interface BranchGroupMemberSummary {
taskId: string;
title: string;
column: Task["column"];
landed: boolean;
}
export interface BranchGroupSummary extends BranchGroup {
members: BranchGroupMemberSummary[];
completion: {
landed: number;
total: number;
complete: boolean;
};
}
export interface PromoteBranchGroupResult {
groupId: string;
status?: BranchGroup["status"];
prState?: BranchGroupPrState;
prNumber?: number;
prUrl?: string;
}
export function apiListBranchGroups(projectId?: string, status?: BranchGroup["status"]): Promise<{ groups: BranchGroupSummary[] }> {
const search = new URLSearchParams();
if (status) search.set("status", status);
const suffix = search.size > 0 ? `?${search.toString()}` : "";
return api<{ groups: BranchGroupSummary[] }>(withProjectId(`/branch-groups${suffix}`, projectId));
}
export function apiGetBranchGroup(id: string, projectId?: string): Promise<{ group: BranchGroupSummary }> {
return api<{ group: BranchGroupSummary }>(withProjectId(`/branch-groups/${id}`, projectId));
}
export function apiAssignTaskBranchGroup(
payload: { taskId: string; groupId?: string | null; branchName?: string },
projectId?: string,
): Promise<{ taskId: string; groupId: string | null }> {
return api<{ taskId: string; groupId: string | null }>(withProjectId("/branch-groups/assign", projectId), {
method: "POST",
body: JSON.stringify(payload),
});
}
export function apiPromoteBranchGroup(id: string, projectId?: string): Promise<PromoteBranchGroupResult> {
return api<PromoteBranchGroupResult>(withProjectId(`/branch-groups/${id}/promote`, projectId), {
method: "POST",
body: JSON.stringify({}),
});
}
export type RecoverBranchBindingOutcome =
| { taskId: string; result: "applied"; branch: string; aheadCount: number; integrationBase: string; previousBranch: string | null }
| { taskId: string; result: "skipped"; reason: "binding-intact" | "no-live-branch" | "ambiguous-candidates" | "no-unique-work"; candidates?: Array<{ branch: string; aheadCount: number }> };