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.
This commit is contained in:
parent
7502cc8e53
commit
65a9928cb5
2 changed files with 60 additions and 6 deletions
|
|
@ -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<TeamSummary[]>(() => {
|
||||
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 => {
|
|||
</p>
|
||||
) : null}
|
||||
|
||||
{teams.length > 0 ? (
|
||||
{teamsWithProvisioning.length > 0 ? (
|
||||
<div className="mt-3 flex items-center gap-2">
|
||||
<div className="relative flex-1">
|
||||
<Search
|
||||
|
|
@ -594,7 +605,7 @@ export const TeamListView = (): React.JSX.Element => {
|
|||
</div>
|
||||
<TeamListFilterPopover
|
||||
filter={filter}
|
||||
teams={teams}
|
||||
teams={teamsWithProvisioning}
|
||||
aliveTeams={aliveTeams}
|
||||
onFilterChange={setFilter}
|
||||
/>
|
||||
|
|
@ -633,7 +644,7 @@ export const TeamListView = (): React.JSX.Element => {
|
|||
);
|
||||
}
|
||||
|
||||
if (teams.length === 0) {
|
||||
if (teamsWithProvisioning.length === 0) {
|
||||
return <TeamEmptyState />;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -353,6 +353,8 @@ export interface TeamSlice {
|
|||
lastSendMessageResult: SendMessageResult | null;
|
||||
reviewActionError: string | null;
|
||||
provisioningRuns: Record<string, TeamProvisioningProgress>;
|
||||
/** Synthetic TeamSummary snapshots for teams currently being provisioned (before config.json exists). */
|
||||
provisioningSnapshotByTeam: Record<string, TeamSummary>;
|
||||
currentProvisioningRunIdByTeam: Record<string, string | null>;
|
||||
currentRuntimeRunIdByTeam: Record<string, string | null>;
|
||||
/** Runs explicitly cleared after Unknown runId polling; late events/progress for them are ignored. */
|
||||
|
|
@ -610,6 +612,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|||
crossTeamTargetsLoading: false,
|
||||
reviewActionError: null,
|
||||
provisioningRuns: {},
|
||||
provisioningSnapshotByTeam: {},
|
||||
currentProvisioningRunIdByTeam: {},
|
||||
currentRuntimeRunIdByTeam: {},
|
||||
ignoredProvisioningRunIds: {},
|
||||
|
|
@ -743,7 +746,22 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (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<AppState, [], [], TeamSlice> = (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<AppState, [], [], TeamSlice> = (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<AppState, [], [], TeamSlice> = (set,
|
|||
[progress.teamName]: progress.runId,
|
||||
},
|
||||
provisioningErrorByTeam: nextErrors,
|
||||
provisioningSnapshotByTeam: nextSnapshots,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue