From 65a9928cb572d07a21eae3d524288d2404cc721d Mon Sep 17 00:00:00 2001
From: iliya
Date: Sun, 15 Mar 2026 11:03:26 +0200
Subject: [PATCH] feat: integrate provisioning snapshots into TeamListView for
enhanced team display
- Added provisioningSnapshotByTeam to manage synthetic team summaries during active provisioning.
- Implemented teamsWithProvisioning to merge real teams with synthetic cards, improving the team list display.
- Updated filtering and rendering logic in TeamListView to utilize the new teamsWithProvisioning array, ensuring accurate representation of teams during provisioning states.
---
src/renderer/components/team/TeamListView.tsx | 21 ++++++---
src/renderer/store/slices/teamSlice.ts | 45 ++++++++++++++++++-
2 files changed, 60 insertions(+), 6 deletions(-)
diff --git a/src/renderer/components/team/TeamListView.tsx b/src/renderer/components/team/TeamListView.tsx
index 673d3345..da404d6f 100644
--- a/src/renderer/components/team/TeamListView.tsx
+++ b/src/renderer/components/team/TeamListView.tsx
@@ -225,6 +225,7 @@ export const TeamListView = (): React.JSX.Element => {
provisioningErrorByTeam,
clearProvisioningError,
provisioningRuns,
+ provisioningSnapshotByTeam,
currentProvisioningRunIdByTeam,
leadActivityByTeam,
} = useStore(
@@ -234,6 +235,7 @@ export const TeamListView = (): React.JSX.Element => {
provisioningErrorByTeam: s.provisioningErrorByTeam,
clearProvisioningError: s.clearProvisioningError,
provisioningRuns: s.provisioningRuns,
+ provisioningSnapshotByTeam: s.provisioningSnapshotByTeam,
currentProvisioningRunIdByTeam: s.currentProvisioningRunIdByTeam,
leadActivityByTeam: s.leadActivityByTeam,
}))
@@ -251,6 +253,15 @@ export const TeamListView = (): React.JSX.Element => {
);
}, [currentProvisioningRunIdByTeam, provisioningState]);
+ /** Merge real teams with synthetic launching cards for active provisioning. */
+ const teamsWithProvisioning = useMemo(() => {
+ const existingNames = new Set(teams.map((t) => t.teamName));
+ const synthetic = provisioningTeamNames
+ .filter((name) => !existingNames.has(name) && provisioningSnapshotByTeam[name])
+ .map((name) => provisioningSnapshotByTeam[name]);
+ return synthetic.length > 0 ? [...teams, ...synthetic] : teams;
+ }, [teams, provisioningTeamNames, provisioningSnapshotByTeam]);
+
// Fetch alive teams on mount and when teams list changes
useEffect(() => {
if (!electronMode) return;
@@ -303,7 +314,7 @@ export const TeamListView = (): React.JSX.Element => {
]);
const filteredTeams = useMemo(() => {
- let result = teams;
+ let result = teamsWithProvisioning;
const q = searchQuery.trim().toLowerCase();
if (q) {
@@ -364,7 +375,7 @@ export const TeamListView = (): React.JSX.Element => {
return result;
}, [
- teams,
+ teamsWithProvisioning,
searchQuery,
currentProjectPath,
aliveTeams,
@@ -577,7 +588,7 @@ export const TeamListView = (): React.JSX.Element => {
) : null}
- {teams.length > 0 ? (
+ {teamsWithProvisioning.length > 0 ? (
{
@@ -633,7 +644,7 @@ export const TeamListView = (): React.JSX.Element => {
);
}
- if (teams.length === 0) {
+ if (teamsWithProvisioning.length === 0) {
return
;
}
diff --git a/src/renderer/store/slices/teamSlice.ts b/src/renderer/store/slices/teamSlice.ts
index 39e6f410..dad82e01 100644
--- a/src/renderer/store/slices/teamSlice.ts
+++ b/src/renderer/store/slices/teamSlice.ts
@@ -353,6 +353,8 @@ export interface TeamSlice {
lastSendMessageResult: SendMessageResult | null;
reviewActionError: string | null;
provisioningRuns: Record
;
+ /** Synthetic TeamSummary snapshots for teams currently being provisioned (before config.json exists). */
+ provisioningSnapshotByTeam: Record;
currentProvisioningRunIdByTeam: Record;
currentRuntimeRunIdByTeam: Record;
/** Runs explicitly cleared after Unknown runId polling; late events/progress for them are ignored. */
@@ -610,6 +612,7 @@ export const createTeamSlice: StateCreator = (set,
crossTeamTargetsLoading: false,
reviewActionError: null,
provisioningRuns: {},
+ provisioningSnapshotByTeam: {},
currentProvisioningRunIdByTeam: {},
currentRuntimeRunIdByTeam: {},
ignoredProvisioningRunIds: {},
@@ -743,7 +746,22 @@ export const createTeamSlice: StateCreator = (set,
}
}
}
- set({ teams, teamByName, teamBySessionId, teamsLoading: false, teamsError: null });
+ // Atomic update: set teams AND clean up provisioning snapshots in one call
+ // to prevent any render cycle with duplicate cards.
+ set((state) => {
+ const nextSnapshots = { ...state.provisioningSnapshotByTeam };
+ for (const team of teams) {
+ delete nextSnapshots[team.teamName];
+ }
+ return {
+ teams,
+ teamByName,
+ teamBySessionId,
+ teamsLoading: false,
+ teamsError: null,
+ provisioningSnapshotByTeam: nextSnapshots,
+ };
+ });
} catch (error) {
// On refresh failure, keep existing teams visible
set({
@@ -1413,6 +1431,21 @@ export const createTeamSlice: StateCreator = (set,
...state.currentProvisioningRunIdByTeam,
[request.teamName]: pendingRunId,
},
+ // Synthetic card for the team list — visible until fetchTeams() picks up the real team.
+ provisioningSnapshotByTeam: {
+ ...state.provisioningSnapshotByTeam,
+ [request.teamName]: {
+ teamName: request.teamName,
+ displayName: request.displayName || request.teamName,
+ description: request.description || '',
+ color: request.color,
+ memberCount: request.members.length,
+ members: request.members.map((m) => ({ name: m.name, role: m.role })),
+ taskCount: 0,
+ lastActivity: null,
+ projectPath: request.cwd || undefined,
+ },
+ },
}));
try {
if (typeof api.teams.createTeam !== 'function') {
@@ -1751,6 +1784,15 @@ export const createTeamSlice: StateCreator = (set,
} else {
delete nextErrors[progress.teamName];
}
+ // Clean up provisioning snapshot on terminal failure states
+ const nextSnapshots =
+ progress.state === 'failed' || progress.state === 'cancelled'
+ ? (() => {
+ const s = { ...state.provisioningSnapshotByTeam };
+ delete s[progress.teamName];
+ return s;
+ })()
+ : state.provisioningSnapshotByTeam;
return {
provisioningRuns: nextRuns,
currentProvisioningRunIdByTeam: nextCurrentRunIdByTeam,
@@ -1759,6 +1801,7 @@ export const createTeamSlice: StateCreator = (set,
[progress.teamName]: progress.runId,
},
provisioningErrorByTeam: nextErrors,
+ provisioningSnapshotByTeam: nextSnapshots,
};
});