From 69ea26a3aa98ff571c28b1b9fc5819a7a899ecbc Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Mon, 7 Sep 2026 23:07:58 -0400 Subject: [PATCH] refactor(renderer): keep the fence-reply generation in an import-free module A dynamic store import inside the refresh was the workaround for the store -> hydration -> refresh -> store cycle. The counter itself has no dependencies, so it lives alone and both writers import it statically. --- .../lib/legacy-worker-resume-fence-generation.ts | 12 ++++++++++++ .../src/lib/legacy-worker-resume-fence-refresh.ts | 15 ++++++++------- .../terminals/workspace-terminal-hydration.ts | 4 ++-- 3 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 src/renderer/src/lib/legacy-worker-resume-fence-generation.ts diff --git a/src/renderer/src/lib/legacy-worker-resume-fence-generation.ts b/src/renderer/src/lib/legacy-worker-resume-fence-generation.ts new file mode 100644 index 00000000000..172888721d7 --- /dev/null +++ b/src/renderer/src/lib/legacy-worker-resume-fence-generation.ts @@ -0,0 +1,12 @@ +// Why a separate module: hydration (inside the store) and the refresh (which writes the store) +// both bump it; keeping it import-free breaks the store → hydration → refresh → store cycle. +let generation = 0 + +/** A newer hydration or request makes every older in-flight fence reply stale. */ +export function advanceLegacyWorkerResumeFenceGeneration(): number { + return ++generation +} + +export function currentLegacyWorkerResumeFenceGeneration(): number { + return generation +} diff --git a/src/renderer/src/lib/legacy-worker-resume-fence-refresh.ts b/src/renderer/src/lib/legacy-worker-resume-fence-refresh.ts index e7ccee2983f..3325b98474d 100644 --- a/src/renderer/src/lib/legacy-worker-resume-fence-refresh.ts +++ b/src/renderer/src/lib/legacy-worker-resume-fence-refresh.ts @@ -1,14 +1,15 @@ -let generation = 0 -export function markLegacyWorkerResumeFencesHydrated(): void { - generation++ -} +import { useAppStore } from '@/store' +import { + advanceLegacyWorkerResumeFenceGeneration, + currentLegacyWorkerResumeFenceGeneration +} from './legacy-worker-resume-fence-generation' export async function refreshLegacyWorkerResumeFences(): Promise { - const requestGeneration = ++generation + const requestGeneration = advanceLegacyWorkerResumeFenceGeneration() try { const fences = await window.api.app.getLegacyWorkerResumeFences() - const { useAppStore } = await import('@/store') - if (requestGeneration !== generation) { + // A reply older than a later request or hydration describes state that has since been replaced. + if (requestGeneration !== currentLegacyWorkerResumeFenceGeneration()) { return } useAppStore.setState({ legacyWorkerResumeFencesByPaneKey: fences }) diff --git a/src/renderer/src/store/terminals/workspace-terminal-hydration.ts b/src/renderer/src/store/terminals/workspace-terminal-hydration.ts index d3c368d0d87..b558a65d798 100644 --- a/src/renderer/src/store/terminals/workspace-terminal-hydration.ts +++ b/src/renderer/src/store/terminals/workspace-terminal-hydration.ts @@ -1,4 +1,4 @@ -import { markLegacyWorkerResumeFencesHydrated } from '@/lib/legacy-worker-resume-fence-refresh' +import { advanceLegacyWorkerResumeFenceGeneration } from '@/lib/legacy-worker-resume-fence-generation' import { readWorkspaceSessionResumeFences } from '../../../../shared/workspace-session-resume-fences' import type { WorkspaceKey } from '../../../../shared/folder-workspace-types' import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../shared/constants' @@ -42,7 +42,7 @@ export function createWorkspaceTerminalHydrationActions( : null const ownershipTransfersByTabId = new Map() set((s) => { - markLegacyWorkerResumeFencesHydrated() + advanceLegacyWorkerResumeFenceGeneration() const runtimeSessionPlaceholders = buildRuntimeSessionPlaceholders({ repos: s.repos, runtimeHostIdByWorkspaceSessionKey: options?.runtimeHostIdByWorkspaceSessionKey ?? {},