perf(team): gate hidden team screen effects
This commit is contained in:
parent
49901b2553
commit
364c5c6471
2 changed files with 36 additions and 19 deletions
|
|
@ -52,7 +52,11 @@ export const PaneContent = ({ pane, isPaneFocused }: PaneContentProps): React.JS
|
||||||
{tab.type === 'teams' && <TeamListView />}
|
{tab.type === 'teams' && <TeamListView />}
|
||||||
{tab.type === 'team' && (
|
{tab.type === 'team' && (
|
||||||
<TabUIProvider tabId={tab.id}>
|
<TabUIProvider tabId={tab.id}>
|
||||||
<TeamDetailView teamName={tab.teamName ?? ''} isPaneFocused={isPaneFocused} />
|
<TeamDetailView
|
||||||
|
teamName={tab.teamName ?? ''}
|
||||||
|
isActive={isActive}
|
||||||
|
isPaneFocused={isPaneFocused}
|
||||||
|
/>
|
||||||
</TabUIProvider>
|
</TabUIProvider>
|
||||||
)}
|
)}
|
||||||
{tab.type === 'session' && (
|
{tab.type === 'session' && (
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,7 @@ import type { ContextUsageLike } from '@shared/utils/contextMetrics';
|
||||||
|
|
||||||
interface TeamDetailViewProps {
|
interface TeamDetailViewProps {
|
||||||
teamName: string;
|
teamName: string;
|
||||||
|
isActive?: boolean;
|
||||||
isPaneFocused?: boolean;
|
isPaneFocused?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -377,10 +378,12 @@ const TeamSpawnStatusWatcher = memo(function TeamSpawnStatusWatcher({
|
||||||
teamName,
|
teamName,
|
||||||
isTeamProvisioning,
|
isTeamProvisioning,
|
||||||
isTeamAlive,
|
isTeamAlive,
|
||||||
|
isThisTabActive,
|
||||||
}: {
|
}: {
|
||||||
teamName: string;
|
teamName: string;
|
||||||
isTeamProvisioning: boolean;
|
isTeamProvisioning: boolean;
|
||||||
isTeamAlive?: boolean;
|
isTeamAlive?: boolean;
|
||||||
|
isThisTabActive: boolean;
|
||||||
}): null {
|
}): null {
|
||||||
const { leadActivity, memberSpawnStatuses, memberSpawnSnapshot, fetchMemberSpawnStatuses } =
|
const { leadActivity, memberSpawnStatuses, memberSpawnSnapshot, fetchMemberSpawnStatuses } =
|
||||||
useStore(
|
useStore(
|
||||||
|
|
@ -393,6 +396,8 @@ const TeamSpawnStatusWatcher = memo(function TeamSpawnStatusWatcher({
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isThisTabActive) return;
|
||||||
|
|
||||||
const hasUnresolvedSpawn = hasUnresolvedMemberSpawnStatus(
|
const hasUnresolvedSpawn = hasUnresolvedMemberSpawnStatus(
|
||||||
memberSpawnStatuses,
|
memberSpawnStatuses,
|
||||||
memberSpawnSnapshot
|
memberSpawnSnapshot
|
||||||
|
|
@ -420,6 +425,7 @@ const TeamSpawnStatusWatcher = memo(function TeamSpawnStatusWatcher({
|
||||||
fetchMemberSpawnStatuses,
|
fetchMemberSpawnStatuses,
|
||||||
isTeamAlive,
|
isTeamAlive,
|
||||||
isTeamProvisioning,
|
isTeamProvisioning,
|
||||||
|
isThisTabActive,
|
||||||
leadActivity,
|
leadActivity,
|
||||||
memberSpawnSnapshot,
|
memberSpawnSnapshot,
|
||||||
memberSpawnStatuses,
|
memberSpawnStatuses,
|
||||||
|
|
@ -888,6 +894,7 @@ const TeamMemberDetailDialogBridge = memo(function TeamMemberDetailDialogBridge(
|
||||||
|
|
||||||
export const TeamDetailView = memo(function TeamDetailView({
|
export const TeamDetailView = memo(function TeamDetailView({
|
||||||
teamName,
|
teamName,
|
||||||
|
isActive = true,
|
||||||
isPaneFocused = false,
|
isPaneFocused = false,
|
||||||
}: TeamDetailViewProps): React.JSX.Element {
|
}: TeamDetailViewProps): React.JSX.Element {
|
||||||
const { isLight } = useTheme();
|
const { isLight } = useTheme();
|
||||||
|
|
@ -1279,8 +1286,7 @@ export const TeamDetailView = memo(function TeamDetailView({
|
||||||
);
|
);
|
||||||
|
|
||||||
const tabId = useTabIdOptional();
|
const tabId = useTabIdOptional();
|
||||||
const activeTabId = useStore((s) => s.activeTabId);
|
const isThisTabActive = isActive;
|
||||||
const isThisTabActive = tabId ? activeTabId === tabId : false;
|
|
||||||
const wasInteractiveRef = useRef(false);
|
const wasInteractiveRef = useRef(false);
|
||||||
|
|
||||||
// Messages panel resize
|
// Messages panel resize
|
||||||
|
|
@ -1324,33 +1330,32 @@ export const TeamDetailView = memo(function TeamDetailView({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const wasProvisioning = wasProvisioningRef.current;
|
const wasProvisioning = wasProvisioningRef.current;
|
||||||
wasProvisioningRef.current = isTeamProvisioning;
|
wasProvisioningRef.current = isTeamProvisioning;
|
||||||
|
if (!isThisTabActive) return;
|
||||||
if (!wasProvisioning && isTeamProvisioning) {
|
if (!wasProvisioning && isTeamProvisioning) {
|
||||||
provisioningBannerRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
provisioningBannerRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
}
|
}
|
||||||
}, [isTeamProvisioning]);
|
}, [isTeamProvisioning, isThisTabActive]);
|
||||||
|
|
||||||
const [kanbanSearch, setKanbanSearch] = useState('');
|
const [kanbanSearch, setKanbanSearch] = useState('');
|
||||||
|
|
||||||
// Open editor overlay when a file reveal is requested (e.g. from chip click)
|
// Open editor overlay when a file reveal is requested (e.g. from chip click)
|
||||||
const pendingRevealFile = useStore((s) => s.editorPendingRevealFile);
|
const pendingRevealFile = useStore((s) => s.editorPendingRevealFile);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isThisTabActive) return;
|
||||||
if (pendingRevealFile && data?.config.projectPath) {
|
if (pendingRevealFile && data?.config.projectPath) {
|
||||||
setEditorOpen(true);
|
setEditorOpen(true);
|
||||||
}
|
}
|
||||||
}, [pendingRevealFile, data?.config.projectPath]);
|
}, [isThisTabActive, pendingRevealFile, data?.config.projectPath]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!teamName) {
|
if (!isThisTabActive || !teamName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void selectTeam(teamName);
|
void selectTeam(teamName);
|
||||||
void fetchDeletedTasks(teamName);
|
void fetchDeletedTasks(teamName);
|
||||||
}, [teamName, selectTeam, fetchDeletedTasks]);
|
}, [isThisTabActive, teamName, selectTeam, fetchDeletedTasks]);
|
||||||
|
|
||||||
// Recovery: after HMR, all mounted TeamDetailView effects re-run simultaneously.
|
// Re-trigger selectTeam when this visible tab becomes active and store data is stale.
|
||||||
// With CSS display-toggle (all tabs stay mounted), the last selectTeam() call wins
|
|
||||||
// and other tabs get stuck with mismatched data (permanent skeleton).
|
|
||||||
// Re-trigger selectTeam when this tab becomes active and store data is stale.
|
|
||||||
const storedTeamName = data?.teamName;
|
const storedTeamName = data?.teamName;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isThisTabActive || !teamName || loading) return;
|
if (!isThisTabActive || !teamName || loading) return;
|
||||||
|
|
@ -1387,7 +1392,7 @@ export const TeamDetailView = memo(function TeamDetailView({
|
||||||
|
|
||||||
// Fetch active teams when launch dialog opens (for conflict warning)
|
// Fetch active teams when launch dialog opens (for conflict warning)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!launchDialogOpen) return;
|
if (!isThisTabActive || !launchDialogOpen) return;
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
const teamsSnapshot = useStore.getState().teams;
|
const teamsSnapshot = useStore.getState().teams;
|
||||||
void (async () => {
|
void (async () => {
|
||||||
|
|
@ -1410,7 +1415,7 @@ export const TeamDetailView = memo(function TeamDetailView({
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, [launchDialogOpen]);
|
}, [isThisTabActive, launchDialogOpen]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (kanbanFilterQuery) {
|
if (kanbanFilterQuery) {
|
||||||
|
|
@ -1456,7 +1461,7 @@ export const TeamDetailView = memo(function TeamDetailView({
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!projectId) return;
|
if (!isThisTabActive || !projectId) return;
|
||||||
|
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
setSessionsLoading(true);
|
setSessionsLoading(true);
|
||||||
|
|
@ -1485,7 +1490,7 @@ export const TeamDetailView = memo(function TeamDetailView({
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, [data?.config.leadSessionId, projectId, sessionHistoryKey]);
|
}, [data?.config.leadSessionId, isThisTabActive, projectId, sessionHistoryKey]);
|
||||||
|
|
||||||
// Live git branch tracking for the lead project and member worktrees
|
// Live git branch tracking for the lead project and member worktrees
|
||||||
const teamProjectPath = data?.config.projectPath?.trim() ?? null;
|
const teamProjectPath = data?.config.projectPath?.trim() ?? null;
|
||||||
|
|
@ -1510,7 +1515,11 @@ export const TeamDetailView = memo(function TeamDetailView({
|
||||||
|
|
||||||
return Array.from(uniquePaths.values());
|
return Array.from(uniquePaths.values());
|
||||||
}, [members, leadProjectPath]);
|
}, [members, leadProjectPath]);
|
||||||
useBranchSync(branchSyncPaths, { live: true });
|
const activeBranchSyncPaths = useMemo(
|
||||||
|
() => (isThisTabActive ? branchSyncPaths : []),
|
||||||
|
[branchSyncPaths, isThisTabActive]
|
||||||
|
);
|
||||||
|
useBranchSync(activeBranchSyncPaths, { live: isThisTabActive });
|
||||||
const trackedBranches = useStore(
|
const trackedBranches = useStore(
|
||||||
useShallow((s) =>
|
useShallow((s) =>
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
|
|
@ -1830,6 +1839,7 @@ export const TeamDetailView = memo(function TeamDetailView({
|
||||||
|
|
||||||
// Pick up pending review request from GlobalTaskDetailDialog
|
// Pick up pending review request from GlobalTaskDetailDialog
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isThisTabActive) return;
|
||||||
if (!pendingReviewRequest) return;
|
if (!pendingReviewRequest) return;
|
||||||
setReviewDialogState({
|
setReviewDialogState({
|
||||||
open: true,
|
open: true,
|
||||||
|
|
@ -1842,11 +1852,12 @@ export const TeamDetailView = memo(function TeamDetailView({
|
||||||
selectReviewFile(pendingReviewRequest.filePath);
|
selectReviewFile(pendingReviewRequest.filePath);
|
||||||
}
|
}
|
||||||
setPendingReviewRequest(null);
|
setPendingReviewRequest(null);
|
||||||
}, [pendingReviewRequest, selectReviewFile, setPendingReviewRequest]);
|
}, [isThisTabActive, pendingReviewRequest, selectReviewFile, setPendingReviewRequest]);
|
||||||
|
|
||||||
const pendingTeamSectionFocus = useStore((s) => s.pendingTeamSectionFocus);
|
const pendingTeamSectionFocus = useStore((s) => s.pendingTeamSectionFocus);
|
||||||
const clearTeamSectionFocus = useStore((s) => s.clearTeamSectionFocus);
|
const clearTeamSectionFocus = useStore((s) => s.clearTeamSectionFocus);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isThisTabActive) return;
|
||||||
if (pendingTeamSectionFocus?.teamName !== teamName) return;
|
if (pendingTeamSectionFocus?.teamName !== teamName) return;
|
||||||
|
|
||||||
const sectionId =
|
const sectionId =
|
||||||
|
|
@ -1868,11 +1879,12 @@ export const TeamDetailView = memo(function TeamDetailView({
|
||||||
if (!section) return;
|
if (!section) return;
|
||||||
section.dispatchEvent(new CustomEvent('team-section-navigate'));
|
section.dispatchEvent(new CustomEvent('team-section-navigate'));
|
||||||
clearTeamSectionFocus();
|
clearTeamSectionFocus();
|
||||||
}, [pendingTeamSectionFocus, clearTeamSectionFocus, teamName, data]);
|
}, [pendingTeamSectionFocus, clearTeamSectionFocus, isThisTabActive, teamName, data]);
|
||||||
|
|
||||||
// Pick up pending member profile request from MemberHoverCard
|
// Pick up pending member profile request from MemberHoverCard
|
||||||
const pendingMemberProfile = useStore((s) => s.pendingMemberProfile);
|
const pendingMemberProfile = useStore((s) => s.pendingMemberProfile);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isThisTabActive) return;
|
||||||
if (!pendingMemberProfile || !data) return;
|
if (!pendingMemberProfile || !data) return;
|
||||||
if (pendingMemberProfile.teamName && pendingMemberProfile.teamName !== teamName) return;
|
if (pendingMemberProfile.teamName && pendingMemberProfile.teamName !== teamName) return;
|
||||||
|
|
||||||
|
|
@ -1889,7 +1901,7 @@ export const TeamDetailView = memo(function TeamDetailView({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
useStore.getState().closeMemberProfile();
|
useStore.getState().closeMemberProfile();
|
||||||
}, [pendingMemberProfile, membersWithLiveBranches, teamName, data]);
|
}, [isThisTabActive, pendingMemberProfile, membersWithLiveBranches, teamName, data]);
|
||||||
|
|
||||||
const handleDeleteTask = useCallback(
|
const handleDeleteTask = useCallback(
|
||||||
(taskId: string) => {
|
(taskId: string) => {
|
||||||
|
|
@ -2058,6 +2070,7 @@ export const TeamDetailView = memo(function TeamDetailView({
|
||||||
teamName={teamName}
|
teamName={teamName}
|
||||||
isTeamProvisioning={isTeamProvisioning}
|
isTeamProvisioning={isTeamProvisioning}
|
||||||
isTeamAlive={data?.isAlive}
|
isTeamAlive={data?.isAlive}
|
||||||
|
isThisTabActive={isThisTabActive}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const teamAgentRuntimeWatcher = (
|
const teamAgentRuntimeWatcher = (
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue