@@ -368,6 +388,21 @@ const RepositoryCard = ({
);
})()
)}
+
+ {/* Active teams running in this project */}
+ {activeTeams && activeTeams.length > 0 && (
+
+
+ {activeTeams.map((t) => (
+
+ {t.displayName}
+
+ ))}
+
+ )}
);
};
@@ -518,6 +553,7 @@ const ProjectsGrid = ({
globalTasksLoading,
fetchAllTasks,
openTeamsTab,
+ teams,
} = useStore(
useShallow((s) => ({
repositoryGroups: s.repositoryGroups,
@@ -529,11 +565,13 @@ const ProjectsGrid = ({
globalTasksLoading: s.globalTasksLoading,
fetchAllTasks: s.fetchAllTasks,
openTeamsTab: s.openTeamsTab,
+ teams: s.teams,
}))
);
const hasFetchedTasksRef = React.useRef(false);
const [visibleProjects, setVisibleProjects] = useState(maxProjects);
+ const [aliveTeams, setAliveTeams] = useState
([]);
useEffect(() => {
if (repositoryGroups.length === 0 && !repositoryGroupsLoading && !repositoryGroupsError) {
@@ -553,6 +591,37 @@ const ProjectsGrid = ({
}
}, [repositoryGroups.length, repositoryGroupsLoading, fetchAllTasks]);
+ // Fetch alive teams for online indicators
+ useEffect(() => {
+ let cancelled = false;
+ void api.teams
+ .aliveList()
+ .then((list) => {
+ if (!cancelled) setAliveTeams(list);
+ })
+ .catch(() => undefined);
+ return () => {
+ cancelled = true;
+ };
+ }, [teams]);
+
+ // Map: normalizedProjectPath → alive TeamSummary[]
+ const activeTeamsByProject = useMemo(() => {
+ const aliveSet = new Set(aliveTeams);
+ const map = new Map();
+ for (const team of teams) {
+ if (!aliveSet.has(team.teamName) || !team.projectPath) continue;
+ const key = normalizePath(team.projectPath);
+ const arr = map.get(key);
+ if (arr) {
+ arr.push(team);
+ } else {
+ map.set(key, [team]);
+ }
+ }
+ return map;
+ }, [teams, aliveTeams]);
+
useEffect(() => {
if (!searchQuery.trim()) {
setVisibleProjects(maxProjects);
@@ -699,6 +768,20 @@ const ProjectsGrid = ({
},
{ pending: 0, inProgress: 0, completed: 0 }
);
+ // Collect active teams for this project (deduplicated by teamName)
+ const seen = new Set();
+ const repoActiveTeams: TeamSummary[] = [];
+ for (const wt of repo.worktrees) {
+ const matched = activeTeamsByProject.get(normalizePath(wt.path));
+ if (matched) {
+ for (const t of matched) {
+ if (!seen.has(t.teamName)) {
+ seen.add(t.teamName);
+ repoActiveTeams.push(t);
+ }
+ }
+ }
+ }
return (
0 ? repoActiveTeams : undefined}
/>
);
})}
diff --git a/src/renderer/components/layout/TabBarRow.tsx b/src/renderer/components/layout/TabBarRow.tsx
index e10f0238..77f4d90c 100644
--- a/src/renderer/components/layout/TabBarRow.tsx
+++ b/src/renderer/components/layout/TabBarRow.tsx
@@ -35,7 +35,7 @@ export const TabBarRow = (): React.JSX.Element => {
style={
{
height: `${HEADER_ROW1_HEIGHT}px`,
- backgroundColor: 'var(--color-surface)',
+ backgroundColor: 'var(--color-surface-sidebar)',
borderBottom: '1px solid var(--color-border)',
WebkitAppRegion: isMacElectron ? 'drag' : undefined,
} as React.CSSProperties
diff --git a/src/renderer/components/team/ProvisioningProgressBlock.tsx b/src/renderer/components/team/ProvisioningProgressBlock.tsx
index 5595ae29..732598f4 100644
--- a/src/renderer/components/team/ProvisioningProgressBlock.tsx
+++ b/src/renderer/components/team/ProvisioningProgressBlock.tsx
@@ -23,6 +23,8 @@ export interface ProvisioningProgressBlockProps {
title: string;
/** Optional status message */
message?: string | null;
+ /** Visual severity for the message subtitle */
+ messageSeverity?: 'error' | 'warning';
/** Visual tone (e.g. highlight errors) */
tone?: 'default' | 'error';
/** Whether Live output is expanded by default */
@@ -118,6 +120,7 @@ function sanitizeAssistantOutput(raw?: string, isError = false): string | null {
export const ProvisioningProgressBlock = ({
title,
message,
+ messageSeverity,
tone = 'default',
defaultLiveOutputOpen = true,
currentStepIndex,
@@ -218,7 +221,11 @@ export const ProvisioningProgressBlock = ({
{message}
diff --git a/src/renderer/components/team/TeamListView.tsx b/src/renderer/components/team/TeamListView.tsx
index e096b325..fae8169f 100644
--- a/src/renderer/components/team/TeamListView.tsx
+++ b/src/renderer/components/team/TeamListView.tsx
@@ -111,24 +111,47 @@ function renderMemberChips(members: TeamSummaryMember[], isLight: boolean): Reac
);
}
-function renderTeamRecentPaths(team: TeamSummary, status: TeamStatus): React.JSX.Element | null {
+function renderTeamRecentPaths(
+ team: TeamSummary,
+ status: TeamStatus,
+ matchesCurrentProject: boolean,
+ isLight: boolean
+): React.JSX.Element | null {
const recentPaths = getRecentProjects(team);
if (recentPaths.length === 0) return null;
return (
-
-
- {recentPaths.map((p, i) => (
-
- {i === 0 && (status === 'active' || status === 'idle') ? (
- {folderName(p)}
- ) : (
- folderName(p)
- )}
- {i < recentPaths.length - 1 ? ', ' : ''}
+ {matchesCurrentProject ? (
+
+
+ {recentPaths.map((p, i) => (
+
+ {folderName(p)}
+ {i < recentPaths.length - 1 ? ', ' : ''}
+
+ ))}
+
+ ) : (
+ <>
+
+
+ {recentPaths.map((p, i) => (
+
+ {i === 0 && (status === 'active' || status === 'idle') ? (
+ {folderName(p)}
+ ) : (
+ folderName(p)
+ )}
+ {i < recentPaths.length - 1 ? ', ' : ''}
+
+ ))}
- ))}
-
+ >
+ )}
);
}
@@ -771,11 +794,7 @@ export const TeamListView = (): React.JSX.Element => {
key={team.teamName}
role="button"
tabIndex={0}
- className={`group relative flex cursor-pointer flex-col overflow-hidden rounded-lg border bg-[var(--color-surface)] p-4 hover:bg-[var(--color-surface-raised)] ${
- matchesCurrentProject
- ? 'border-emerald-500/70 ring-1 ring-emerald-500/30'
- : 'border-[var(--color-border)]'
- }`}
+ className="group relative flex cursor-pointer flex-col overflow-hidden rounded-lg border border-[var(--color-border)] bg-[var(--color-surface)] p-4 hover:bg-[var(--color-surface-raised)]"
style={
teamColorSet
? { borderLeftWidth: '3px', borderLeftColor: teamColorSet.border }
@@ -959,7 +978,7 @@ export const TeamListView = (): React.JSX.Element => {
);
})()}
- {renderTeamRecentPaths(team, status)}
+ {renderTeamRecentPaths(team, status, matchesCurrentProject, isLight)}