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