diff --git a/src/renderer/src/components/AppDetail/AppDetail.tsx b/src/renderer/src/components/AppDetail/AppDetail.tsx index cf61199..8ef03c2 100644 --- a/src/renderer/src/components/AppDetail/AppDetail.tsx +++ b/src/renderer/src/components/AppDetail/AppDetail.tsx @@ -116,26 +116,33 @@ const AppDetail: React.FC = () => { async (onSuccessCallback?: () => Promise | void) => { if (!currentDomain || !selectedAppId) return undefined; + // Capture at request time to detect staleness after awaits + const capturedDomainId = currentDomain.id; + const capturedAppId = selectedAppId; + setLoading(true); try { const result = await window.api.getAppDetail({ - domainId: currentDomain.id, - appId: selectedAppId, + domainId: capturedDomainId, + appId: capturedAppId, }); - // Check if we're still on the same app and component is mounted before updating if (result.success) { - setCurrentApp(result.data); - // Store revision after callback to avoid being cleared by clearChanges - const revision = result.data.customization?.revision; - // Call the callback if provided + // Guard: discard stale responses from previous domain/app switches + const nowDomainId = useDomainStore.getState().currentDomain?.id; + const nowAppId = useAppStore.getState().selectedAppId; + if (nowDomainId !== capturedDomainId || nowAppId !== capturedAppId) return undefined; + if (onSuccessCallback) { await onSuccessCallback(); } - // Store the revision from Kintone API for remote change detection + // Store revision after callback to avoid being cleared by clearChanges + const revision = result.data.customization?.revision; + setCurrentApp(result.data); // Must be AFTER callback since clearChanges() resets serverRevision to null - if (revision && currentDomain) { - fileChangeStore.setServerRevision(currentDomain.id, selectedAppId, revision); + // Only update knownRevision when user explicitly refreshes or after deployment + if (revision && shouldUpdateKnownRevision) { + fileChangeStore.setServerRevision(capturedDomainId, capturedAppId, revision); } return result.data; } @@ -144,7 +151,13 @@ const AppDetail: React.FC = () => { console.error("Failed to load app detail:", error); return undefined; } finally { - setLoading(false); + // Only reset loading if still on the same context; otherwise the new + // request's loading state should not be cleared by this stale response. + const nowDomainId = useDomainStore.getState().currentDomain?.id; + const nowAppId = useAppStore.getState().selectedAppId; + if (nowDomainId === capturedDomainId && nowAppId === capturedAppId) { + setLoading(false); + } } }, [currentDomain, selectedAppId, setCurrentApp, setLoading] @@ -160,6 +173,9 @@ const AppDetail: React.FC = () => { // Initialize file change store from Kintone data useEffect(() => { if (!currentApp || !currentDomain || !selectedAppId) return; + // Guard against race condition: currentApp may be a stale response from a + // previous app's request that resolved after selectedAppId already changed. + if (String(currentApp.appId) !== String(selectedAppId)) return; const customize = currentApp.customization; if (!customize) return; diff --git a/src/renderer/src/components/AppList/AppList.tsx b/src/renderer/src/components/AppList/AppList.tsx index a1cb5c6..8e6ffb9 100644 --- a/src/renderer/src/components/AppList/AppList.tsx +++ b/src/renderer/src/components/AppList/AppList.tsx @@ -88,23 +88,31 @@ const AppList: React.FC = () => { const handleLoadApps = async () => { if (!currentDomain) return; + const domainIdAtStart = currentDomain.id; setLoading(true); setError(null); try { const result = await window.api.getApps({ - domainId: currentDomain.id, + domainId: domainIdAtStart, }); + // Discard result if domain switched during the request + if (useDomainStore.getState().currentDomain?.id !== domainIdAtStart) return; + if (result.success) { setApps(result.data); } else { setError(result.error || t("loadAppsFailed")); } } catch (err) { - setError(err instanceof Error ? err.message : t("loadAppsFailed")); + if (useDomainStore.getState().currentDomain?.id === domainIdAtStart) { + setError(err instanceof Error ? err.message : t("loadAppsFailed")); + } } finally { - setLoading(false); + if (useDomainStore.getState().currentDomain?.id === domainIdAtStart) { + setLoading(false); + } } };