From dd2b472483cef462dbc11e173a297b53f9638f61 Mon Sep 17 00:00:00 2001 From: chris Date: Fri, 15 May 2026 15:01:59 +0800 Subject: [PATCH] Fix team switching not refreshing data across all views BoardPage and CyclesPage maintained their own team key state initialized from localStorage on mount, but never listened for the `involute:active-team-key` event dispatched when clicking a different team in the sidebar. Because the route stays the same (e.g. `/`), components were not remounted and stale team data persisted. - BoardPage: listen for the active-team-key event and feed changes through the existing pendingTeamKey mechanism so the GraphQL query re-fetches with the new team filter - CyclesPage: convert the one-shot readStoredTeamKey() call into reactive state with the same event listener so cycles reload for the selected team Co-authored-by: Cursor --- packages/web/src/routes/BoardPage.tsx | 21 +++++++++++++++++++++ packages/web/src/routes/CyclesPage.tsx | 20 ++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/web/src/routes/BoardPage.tsx b/packages/web/src/routes/BoardPage.tsx index 64640a8..81e6cf7 100644 --- a/packages/web/src/routes/BoardPage.tsx +++ b/packages/web/src/routes/BoardPage.tsx @@ -134,6 +134,27 @@ export function BoardPage() { const navigate = useNavigate(); const [activeTeamKey, setActiveTeamKey] = useState(() => readStoredTeamKey()); const [pendingTeamKey, setPendingTeamKey] = useState(null); + const activeTeamKeyRef = useRef(activeTeamKey); + activeTeamKeyRef.current = activeTeamKey; + + useEffect(() => { + function handleActiveTeamKeyChange(event: Event) { + const nextTeamKey = + event instanceof CustomEvent && (typeof event.detail === 'string' || event.detail === null) + ? (event.detail as string | null) + : readStoredTeamKey(); + + if (nextTeamKey !== activeTeamKeyRef.current) { + setPendingTeamKey(nextTeamKey); + } + } + + window.addEventListener('involute:active-team-key', handleActiveTeamKeyChange as EventListener); + + return () => { + window.removeEventListener('involute:active-team-key', handleActiveTeamKeyChange as EventListener); + }; + }, []); const [isLoadingMoreIssues, setIsLoadingMoreIssues] = useState(false); const [loadMoreIssuesError, setLoadMoreIssuesError] = useState(null); const queryTeamKey = pendingTeamKey ?? activeTeamKey; diff --git a/packages/web/src/routes/CyclesPage.tsx b/packages/web/src/routes/CyclesPage.tsx index 5a32347..a928c1a 100644 --- a/packages/web/src/routes/CyclesPage.tsx +++ b/packages/web/src/routes/CyclesPage.tsx @@ -1,5 +1,5 @@ import { useMutation, useQuery } from '@apollo/client/react'; -import { useRef, useState, useMemo } from 'react'; +import { useEffect, useRef, useState, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { BOARD_PAGE_QUERY, CYCLES_QUERY, CYCLE_CREATE_MUTATION, CYCLE_UPDATE_MUTATION, CYCLE_DELETE_MUTATION } from '../board/queries'; @@ -56,8 +56,24 @@ function isCycleCompleted(cycle: CycleSummary): boolean { export function CyclesPage() { const navigate = useNavigate(); - const teamKey = readStoredTeamKey(); + const [teamKey, setTeamKey] = useState(() => readStoredTeamKey()); const dialogRef = useRef(null); + + useEffect(() => { + function handleActiveTeamKeyChange(event: Event) { + const nextTeamKey = + event instanceof CustomEvent && (typeof event.detail === 'string' || event.detail === null) + ? (event.detail as string | null) + : readStoredTeamKey(); + setTeamKey(nextTeamKey); + } + + window.addEventListener('involute:active-team-key', handleActiveTeamKeyChange as EventListener); + + return () => { + window.removeEventListener('involute:active-team-key', handleActiveTeamKeyChange as EventListener); + }; + }, []); const [dialogMode, setDialogMode] = useState<'create' | 'edit'>('create'); const [editingCycleId, setEditingCycleId] = useState(null); const [formName, setFormName] = useState('');