From ee04aee4fb86c14334ce9c5833b61d81012ab715 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Thu, 18 Jun 2026 18:00:47 -0400 Subject: [PATCH] fix(20): CR-02 derive editorMember from live query data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Store only the member id in editorMemberId state rather than the full AdminMember object. Derive editorMember from membersQuery.data after each render, so query invalidations following a per-section save always give MemberEditorSheet fresh server state — eliminating the stale-reference silent-overwrite race. Co-Authored-By: Claude Sonnet 4.6 --- apps/pwa/src/routes/AdminPage.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/pwa/src/routes/AdminPage.tsx b/apps/pwa/src/routes/AdminPage.tsx index 952eb24..39851b3 100644 --- a/apps/pwa/src/routes/AdminPage.tsx +++ b/apps/pwa/src/routes/AdminPage.tsx @@ -80,7 +80,11 @@ export function AdminPage() { // create ("Add member" trigger) modes (D-07 unification). const [editorOpen, setEditorOpen] = useState(false); const [editorMode, setEditorMode] = useState<'edit' | 'create'>('edit'); - const [editorMember, setEditorMember] = useState(null); + // CR-02: store only the id, not the full member object. The full member is + // derived from live query data each render (see below, after membersQuery), + // so per-section saves + invalidations keep the editor's member prop fresh + // without a stale captured reference. + const [editorMemberId, setEditorMemberId] = useState(null); // Ref to the element that opened the sheet — focus returns here on close (a11y) const editorTriggerRef = useRef(null); // Ref for the "Add member" ghost trigger button @@ -115,6 +119,13 @@ export function AdminPage() { staleTime: 60 * 1000, }); + // CR-02: derive editorMember from live query data so saves + invalidations always + // reflect the latest server state rather than a snapshot from when the row was tapped. + const editorMember = + editorMemberId !== null + ? (membersQuery.data?.members.find((m) => m.id === editorMemberId) ?? null) + : null; + // Calendars query const calendarsQuery = useQuery({ queryKey: ['admin', 'calendars'], @@ -240,7 +251,7 @@ export function AdminPage() { // Open the editor for a specific member (row tap → edit mode) function openEditorForMember(member: AdminMember, rowEl: HTMLElement) { editorTriggerRef.current = rowEl; - setEditorMember(member); + setEditorMemberId(member.id); // CR-02: store id only; editorMember is derived from live data setEditorMode('edit'); setEditorOpen(true); } @@ -248,7 +259,7 @@ export function AdminPage() { // Open the editor in create mode ("Add member" trigger) function openEditorForCreate() { editorTriggerRef.current = addMemberTriggerRef.current; - setEditorMember(null); + setEditorMemberId(null); setEditorMode('create'); setEditorOpen(true); }