From 07e55b91f7af9969b7baa63a6262d3d0b341d021 Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Sat, 7 Nov 2026 02:59:48 -0800 Subject: [PATCH] refactor(renderer): spell out logical assignments React Compiler can't lower yet (#39339) React Compiler skips any function containing `??=`, `||=` or `&&=`. Rewrite the statement-position ones whose target re-reads without side effects (identifiers and plain member chains) as the equivalent `if`: a ??= b -> if (a == null) { a = b } a ||= b -> if (!a) { a = b } a &&= b -> if (a) { a = b } 21 files; 25 fewer bailouts and 12 more compiled functions in the compiler-compat baseline. Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> --- config/react-compiler-bailout-baseline.txt | 30 ++++++------------- .../dashboard-popout/useDashboardSnapshot.ts | 4 ++- .../dashboard/useAgentBucketCounts.ts | 4 ++- .../components/dashboard/useRetainedAgents.ts | 4 ++- .../use-rich-markdown-table-control-target.ts | 14 +++++---- .../use-feature-wall-session-depth.ts | 14 +++++---- .../use-feature-wall-tour-telemetry.ts | 4 ++- .../native-chat-resume-on-restart-store.ts | 4 ++- .../NativeChatAccountUsageCard.tsx | 8 +++-- .../use-native-chat-find-matches.ts | 12 ++++++-- .../use-native-chat-history-retention.ts | 4 ++- .../use-native-chat-live-session.ts | 4 ++- .../use-native-chat-message-rail.ts | 4 ++- .../use-native-chat-retained-session.ts | 4 ++- .../use-native-chat-selected-history.ts | 4 ++- .../OnboardingInlineCommandTerminal.tsx | 4 ++- .../frames/use-plugin-panel-theme-revision.ts | 4 ++- .../components/ports/WorkspacePortScanner.tsx | 8 +++-- .../use-plugin-declarative-tree.ts | 4 ++- .../sync/git-status-push-signal-refresh.ts | 4 ++- .../status-bar/status-bar-density.ts | 4 ++- .../SpaceWindowStatePublisher.tsx | 12 ++++---- 22 files changed, 98 insertions(+), 60 deletions(-) diff --git a/config/react-compiler-bailout-baseline.txt b/config/react-compiler-bailout-baseline.txt index 75ee7dc7085..bdf65c43768 100644 --- a/config/react-compiler-bailout-baseline.txt +++ b/config/react-compiler-bailout-baseline.txt @@ -77,10 +77,8 @@ 1 src/renderer/src/components/crash-report/CrashReportDialog.tsx 1 src/renderer/src/components/crash-report/CrashReportDialogSurface.tsx 3 src/renderer/src/components/crash-report/use-crash-report-copy.ts -1 src/renderer/src/components/dashboard-popout/useDashboardSnapshot.ts -2 src/renderer/src/components/dashboard/useAgentBucketCounts.ts +1 src/renderer/src/components/dashboard/useAgentBucketCounts.ts 1 src/renderer/src/components/dashboard/useLiveDashboardSnapshot.ts -1 src/renderer/src/components/dashboard/useRetainedAgents.ts 2 src/renderer/src/components/diff-comments/DiffCommentCard.tsx 5 src/renderer/src/components/diff-comments/DiffCommentPopover.tsx 2 src/renderer/src/components/diff-comments/diff-comment-draft-zone.ts @@ -137,7 +135,6 @@ 1 src/renderer/src/components/editor/rich-markdown/review/useRichMarkdownReviewData.ts 1 src/renderer/src/components/editor/rich-markdown/review/useRichMarkdownReviewRailController.ts 12 src/renderer/src/components/editor/rich-markdown/tables/RichMarkdownTableControls.tsx -2 src/renderer/src/components/editor/rich-markdown/tables/use-rich-markdown-table-control-target.ts 2 src/renderer/src/components/editor/use-editor-group-file-drop-owner.ts 1 src/renderer/src/components/editor/use-monaco-content-sync-bridge.ts 1 src/renderer/src/components/editor/use-monaco-editor-mount.ts @@ -165,8 +162,7 @@ 1 src/renderer/src/components/feature-wall/WorkbenchTerminalSplitMenu.tsx 1 src/renderer/src/components/feature-wall/agents-orchestration/OrchestrationPage.tsx 2 src/renderer/src/components/feature-wall/agents-orchestration/UsageAccountsCard.tsx -1 src/renderer/src/components/feature-wall/use-feature-wall-session-depth.ts -3 src/renderer/src/components/feature-wall/use-feature-wall-tour-telemetry.ts +2 src/renderer/src/components/feature-wall/use-feature-wall-tour-telemetry.ts 3 src/renderer/src/components/feature-wall/useAiCommitPrSettings.ts 1 src/renderer/src/components/floating-terminal/FloatingTerminalIconContextMenu.tsx 3 src/renderer/src/components/floating-terminal/FloatingTerminalToggleButton.tsx @@ -211,9 +207,8 @@ 2 src/renderer/src/components/mobile/use-mobile-page-paired-devices.ts 1 src/renderer/src/components/mobile/use-mobile-pairing-generation.ts 1 src/renderer/src/components/native-chat-restart-runtimes.ts -1 src/renderer/src/components/native-chat-resume-on-restart-store.ts 1 src/renderer/src/components/native-chat/AddToChatMenu.tsx -2 src/renderer/src/components/native-chat/NativeChatAccountUsageCard.tsx +1 src/renderer/src/components/native-chat/NativeChatAccountUsageCard.tsx 6 src/renderer/src/components/native-chat/NativeChatAutocompleteMenus.tsx 1 src/renderer/src/components/native-chat/NativeChatComposerField.tsx 2 src/renderer/src/components/native-chat/NativeChatComposerToolbar.tsx @@ -237,21 +232,19 @@ 8 src/renderer/src/components/native-chat/use-native-chat-composer-attachments.ts 1 src/renderer/src/components/native-chat/use-native-chat-composer-keydown.ts 1 src/renderer/src/components/native-chat/use-native-chat-context-clear-floor.ts -3 src/renderer/src/components/native-chat/use-native-chat-find-matches.ts 1 src/renderer/src/components/native-chat/use-native-chat-find.ts 6 src/renderer/src/components/native-chat/use-native-chat-fork.ts 1 src/renderer/src/components/native-chat/use-native-chat-hand-off.tsx -1 src/renderer/src/components/native-chat/use-native-chat-history-retention.ts 1 src/renderer/src/components/native-chat/use-native-chat-launch-prompt-delivery-notice.ts -3 src/renderer/src/components/native-chat/use-native-chat-live-session.ts -4 src/renderer/src/components/native-chat/use-native-chat-message-rail.ts +2 src/renderer/src/components/native-chat/use-native-chat-live-session.ts +3 src/renderer/src/components/native-chat/use-native-chat-message-rail.ts 1 src/renderer/src/components/native-chat/use-native-chat-mobile-hold-notice.ts 2 src/renderer/src/components/native-chat/use-native-chat-paste-lifetime.ts 1 src/renderer/src/components/native-chat/use-native-chat-pending-delivery.ts 2 src/renderer/src/components/native-chat/use-native-chat-provisional-launch.ts 2 src/renderer/src/components/native-chat/use-native-chat-resolved-path-attachments.ts -4 src/renderer/src/components/native-chat/use-native-chat-retained-session.ts -2 src/renderer/src/components/native-chat/use-native-chat-selected-history.ts +3 src/renderer/src/components/native-chat/use-native-chat-retained-session.ts +1 src/renderer/src/components/native-chat/use-native-chat-selected-history.ts 1 src/renderer/src/components/native-chat/use-native-chat-session-context-actions.tsx 1 src/renderer/src/components/native-chat/use-native-chat-session-option-command.ts 2 src/renderer/src/components/native-chat/use-native-chat-skills.ts @@ -278,7 +271,6 @@ 2 src/renderer/src/components/onboarding/AgentStep.tsx 2 src/renderer/src/components/onboarding/NotificationStep.tsx 8 src/renderer/src/components/onboarding/OnboardingFlow.tsx -1 src/renderer/src/components/onboarding/OnboardingInlineCommandTerminal.tsx 3 src/renderer/src/components/onboarding/OnboardingLanguageSwitcher.tsx 1 src/renderer/src/components/onboarding/ThemeLookCarousel.tsx 1 src/renderer/src/components/onboarding/ThemeLookGallery.tsx @@ -295,7 +287,6 @@ 3 src/renderer/src/components/plugins/dialogs/PluginMarketplaceSourceDialog.tsx 1 src/renderer/src/components/plugins/frames/use-plugin-frame-entry.ts 1 src/renderer/src/components/plugins/frames/use-plugin-frame-host.ts -1 src/renderer/src/components/plugins/frames/use-plugin-panel-theme-revision.ts 4 src/renderer/src/components/plugins/list/PluginsSettingsSection.tsx 1 src/renderer/src/components/plugins/page/PluginContributionActionCommand.tsx 2 src/renderer/src/components/plugins/state/use-plugin-catalog.ts @@ -303,7 +294,7 @@ 1 src/renderer/src/components/plugins/state/use-plugin-install-action.ts 2 src/renderer/src/components/plugins/state/use-plugin-marketplace-lifecycle.ts 1 src/renderer/src/components/plugins/state/use-plugin-settings-writes.ts -3 src/renderer/src/components/ports/WorkspacePortScanner.tsx +1 src/renderer/src/components/ports/WorkspacePortScanner.tsx 2 src/renderer/src/components/provider-accounts/AntigravityChatAuthForm.tsx 1 src/renderer/src/components/pull-request-page/checks/tab.tsx 2 src/renderer/src/components/pull-request-page/comments/composer.tsx @@ -360,7 +351,6 @@ 1 src/renderer/src/components/right-sidebar/hosted-review/provider-actions/HostedReviewActions.tsx 6 src/renderer/src/components/right-sidebar/hosted-review/provider-actions/use-hosted-review-actions.ts 2 src/renderer/src/components/right-sidebar/hosted-review/provider-actions/use-ready-hosted-review-action.ts -1 src/renderer/src/components/right-sidebar/plugin-panels/declarative/use-plugin-declarative-tree.ts 1 src/renderer/src/components/right-sidebar/source-control/ai/text-generation-dialog-form.tsx 1 src/renderer/src/components/right-sidebar/source-control/ai/text-generation-dialog.tsx 1 src/renderer/src/components/right-sidebar/source-control/ai/use-conflict-recovery-composer.ts @@ -389,7 +379,6 @@ 1 src/renderer/src/components/right-sidebar/source-control/review/use-hosted-review-provider-hint.ts 1 src/renderer/src/components/right-sidebar/source-control/sync/git-history-panel.tsx 1 src/renderer/src/components/right-sidebar/source-control/sync/git-status-file-watch-refresh.ts -1 src/renderer/src/components/right-sidebar/source-control/sync/git-status-push-signal-refresh.ts 1 src/renderer/src/components/right-sidebar/source-control/sync/use-branch-compare.ts 1 src/renderer/src/components/right-sidebar/source-control/sync/use-conflict-abort.ts 1 src/renderer/src/components/right-sidebar/source-control/sync/use-remote-action-runner.ts @@ -572,7 +561,7 @@ 1 src/renderer/src/components/status-bar/RuntimeHostStatusRow.tsx 2 src/renderer/src/components/status-bar/SshTargetStatusRow.tsx 1 src/renderer/src/components/status-bar/icons.tsx -4 src/renderer/src/components/status-bar/status-bar-density.ts +3 src/renderer/src/components/status-bar/status-bar-density.ts 2 src/renderer/src/components/status-bar/use-codex-switcher-controller.ts 1 src/renderer/src/components/status-bar/use-owner-usage-rate-limits.ts 1 src/renderer/src/components/status-bar/use-provider-account-list.ts @@ -687,7 +676,6 @@ 2 src/renderer/src/components/workspace-cleanup/use-workspace-cleanup-git-evidence.ts 1 src/renderer/src/components/workspace-cleanup/workspace-cleanup-candidate-list.tsx 2 src/renderer/src/components/workspace-emoji/useWorkspaceEmojiShortcodeInput.ts -1 src/renderer/src/components/workspace-space/SpaceWindowStatePublisher.tsx 1 src/renderer/src/components/worktree-jump/use-worktree-jump-palette-list-entries.ts 1 src/renderer/src/components/worktree-jump/use-worktree-jump-palette-local-state.ts 1 src/renderer/src/components/worktree-jump/use-worktree-jump-palette-open-tabs.ts diff --git a/src/renderer/src/components/dashboard-popout/useDashboardSnapshot.ts b/src/renderer/src/components/dashboard-popout/useDashboardSnapshot.ts index 2ea854aa576..d5332c4d99b 100644 --- a/src/renderer/src/components/dashboard-popout/useDashboardSnapshot.ts +++ b/src/renderer/src/components/dashboard-popout/useDashboardSnapshot.ts @@ -50,7 +50,9 @@ export function useDashboardSnapshot(): DashboardSnapshot { clearTimeout(topologyRefreshTimer) } const now = Date.now() - topologyRefreshStartedAt ??= now + if (topologyRefreshStartedAt == null) { + topologyRefreshStartedAt = now + } const remainingMaxWait = Math.max( 0, TOPOLOGY_REFRESH_MAX_WAIT_MS - (now - topologyRefreshStartedAt) diff --git a/src/renderer/src/components/dashboard/useAgentBucketCounts.ts b/src/renderer/src/components/dashboard/useAgentBucketCounts.ts index c92e9850cc5..4bfbfc19b52 100644 --- a/src/renderer/src/components/dashboard/useAgentBucketCounts.ts +++ b/src/renderer/src/components/dashboard/useAgentBucketCounts.ts @@ -117,7 +117,9 @@ export function useAgentBucketCounts(): AgentBucketCounts { const state = useAppStore(selectAgentBucketCountState) // Cache per worktree so status/title churn in one workspace does not rebuild every row pipeline. const cacheRef = useRef>(undefined!) - cacheRef.current ??= createDashboardBucketCountsCache() + if (cacheRef.current == null) { + cacheRef.current = createDashboardBucketCountsCache() + } return useMemo(() => { // Freshness follows agentStatusEpoch in state; read the clock when that generation invalidates the memo, not as a dependency. // oxlint-disable-next-line react/purity -- wall time is sampled per epoch tick, as explained above. diff --git a/src/renderer/src/components/dashboard/useRetainedAgents.ts b/src/renderer/src/components/dashboard/useRetainedAgents.ts index 29023768959..3dd9efaa966 100644 --- a/src/renderer/src/components/dashboard/useRetainedAgents.ts +++ b/src/renderer/src/components/dashboard/useRetainedAgents.ts @@ -138,7 +138,9 @@ export function useRetainedAgentsSync(): void { TerminalTab, TerminalTab > | null>(null) - tabTopologyProjectionRef.current ??= createRetainedAgentsTabTopologyProjection() + if (tabTopologyProjectionRef.current == null) { + tabTopologyProjectionRef.current = createRetainedAgentsTabTopologyProjection() + } const retainAgents = useAppStore((s) => s.retainAgents) const pruneRetainedAgents = useAppStore((s) => s.pruneRetainedAgents) const clearRetentionSuppressedPaneKeys = useAppStore((s) => s.clearRetentionSuppressedPaneKeys) diff --git a/src/renderer/src/components/editor/rich-markdown/tables/use-rich-markdown-table-control-target.ts b/src/renderer/src/components/editor/rich-markdown/tables/use-rich-markdown-table-control-target.ts index d8060fedce9..46ca080c6d1 100644 --- a/src/renderer/src/components/editor/rich-markdown/tables/use-rich-markdown-table-control-target.ts +++ b/src/renderer/src/components/editor/rich-markdown/tables/use-rich-markdown-table-control-target.ts @@ -97,7 +97,9 @@ export function useRichMarkdownTableControlTarget( if (cell && editorDom.contains(cell)) { activate(cell) pendingPointerRef.current = { cell, x: event.clientX, y: event.clientY } - pointerFrameRef.current ??= window.requestAnimationFrame(flushPointer) + if (pointerFrameRef.current == null) { + pointerFrameRef.current = window.requestAnimationFrame(flushPointer) + } return } if ( @@ -133,10 +135,12 @@ export function useRichMarkdownTableControlTarget( } let layoutFrame: number | null = null const update = (): void => { - layoutFrame ??= window.requestAnimationFrame(() => { - layoutFrame = null - setLayoutVersion((version) => version + 1) - }) + if (layoutFrame == null) { + layoutFrame = window.requestAnimationFrame(() => { + layoutFrame = null + setLayoutVersion((version) => version + 1) + }) + } } const observer = new ResizeObserver(update) observer.observe(table) diff --git a/src/renderer/src/components/feature-wall/use-feature-wall-session-depth.ts b/src/renderer/src/components/feature-wall/use-feature-wall-session-depth.ts index af2c1efb33b..fdab4862b8f 100644 --- a/src/renderer/src/components/feature-wall/use-feature-wall-session-depth.ts +++ b/src/renderer/src/components/feature-wall/use-feature-wall-session-depth.ts @@ -48,12 +48,14 @@ export function useFeatureWallSessionDepth( visitedReviewSteps: Set lastGroupId: FeatureWallWorkflowId | null }>(undefined!) - sessionDepthRef.current ??= { - visitedWorkflows: new Set(), - visitedAgentSteps: new Set(), - visitedWorkbenchSteps: new Set(), - visitedReviewSteps: new Set(), - lastGroupId: null + if (sessionDepthRef.current == null) { + sessionDepthRef.current = { + visitedWorkflows: new Set(), + visitedAgentSteps: new Set(), + visitedWorkbenchSteps: new Set(), + visitedReviewSteps: new Set(), + lastGroupId: null + } } const getTourDepthSummary = useCallback((): FeatureWallTourDepthSummary => { diff --git a/src/renderer/src/components/feature-wall/use-feature-wall-tour-telemetry.ts b/src/renderer/src/components/feature-wall/use-feature-wall-tour-telemetry.ts index 836a1f34b53..51cee2e0250 100644 --- a/src/renderer/src/components/feature-wall/use-feature-wall-tour-telemetry.ts +++ b/src/renderer/src/components/feature-wall/use-feature-wall-tour-telemetry.ts @@ -78,7 +78,9 @@ export function useFeatureWallTourTelemetry(args: { }): { markExitAction: (exitAction: FeatureWallExitAction) => void } { const { isOpen, source, getDepthSummary } = args const telemetryRef = useRef(undefined!) - telemetryRef.current ??= createFeatureWallTourTelemetryState() + if (telemetryRef.current == null) { + telemetryRef.current = createFeatureWallTourTelemetryState() + } const sourceRef = useRef(source) const getDepthSummaryRef = useRef(getDepthSummary) // Refresh payload refs before open/close effects and unmount cleanup can emit. diff --git a/src/renderer/src/components/native-chat-resume-on-restart-store.ts b/src/renderer/src/components/native-chat-resume-on-restart-store.ts index f4c098be5fa..e7b8081c747 100644 --- a/src/renderer/src/components/native-chat-resume-on-restart-store.ts +++ b/src/renderer/src/components/native-chat-resume-on-restart-store.ts @@ -26,7 +26,9 @@ export function useNativeChatRestartOffer(enabled: boolean, store = localNativeC const settingsLoaded = useAppStore((state) => state.settings !== null) useEffect(() => { if (enabled && settingsLoaded && store.target.kind === 'local') { - launch ??= loadLaunchOffer(store).finally(markNativeChatLaunchResumeDecided) + if (launch == null) { + launch = loadLaunchOffer(store).finally(markNativeChatLaunchResumeDecided) + } } }, [enabled, settingsLoaded, store]) return useSyncExternalStore( diff --git a/src/renderer/src/components/native-chat/NativeChatAccountUsageCard.tsx b/src/renderer/src/components/native-chat/NativeChatAccountUsageCard.tsx index b9a58808fe8..993637466d8 100644 --- a/src/renderer/src/components/native-chat/NativeChatAccountUsageCard.tsx +++ b/src/renderer/src/components/native-chat/NativeChatAccountUsageCard.tsx @@ -186,9 +186,11 @@ function ChatUsageReset({ if (busy || !result.reset) { return } - attempt.current ??= { - idempotencyKey: createBrowserUuid(), - expectedScope: result.reset.expectedScope + if (attempt.current == null) { + attempt.current = { + idempotencyKey: createBrowserUuid(), + expectedScope: result.reset.expectedScope + } } setBusy(true) setFailed(false) diff --git a/src/renderer/src/components/native-chat/use-native-chat-find-matches.ts b/src/renderer/src/components/native-chat/use-native-chat-find-matches.ts index c534ae183c1..b0dcadc3168 100644 --- a/src/renderer/src/components/native-chat/use-native-chat-find-matches.ts +++ b/src/renderer/src/components/native-chat/use-native-chat-find-matches.ts @@ -220,15 +220,21 @@ export function useNativeChatFindMatches({ Array.from(record.removedNodes).some((node) => holdsMatch(node, matchNodesRef.current)) ) if (now) { - frame ??= requestAnimationFrame(runSearch) + if (frame == null) { + frame = requestAnimationFrame(runSearch) + } } else { - timer ??= setTimeout(runSearch, TEXT_GROWTH_SEARCH_DELAY_MS) + if (timer == null) { + timer = setTimeout(runSearch, TEXT_GROWTH_SEARCH_DELAY_MS) + } } }) // A disclosure opens with a height animation (not a mutation): what it cut off at first shows at its end. const onAnimationEnd = (event: Event): void => { if (event.target instanceof Node && column?.contains(event.target)) { - frame ??= requestAnimationFrame(runSearch) + if (frame == null) { + frame = requestAnimationFrame(runSearch) + } } } root.addEventListener('animationend', onAnimationEnd) diff --git a/src/renderer/src/components/native-chat/use-native-chat-history-retention.ts b/src/renderer/src/components/native-chat/use-native-chat-history-retention.ts index 22f1a774855..5bdb60155bf 100644 --- a/src/renderer/src/components/native-chat/use-native-chat-history-retention.ts +++ b/src/renderer/src/components/native-chat/use-native-chat-history-retention.ts @@ -53,7 +53,9 @@ export function useNativeChatHistoryRetention({ const leaveEnd = useCallback(() => { cancelTrim() transition('browsing') - release.current ??= retainHistory?.() ?? null + if (release.current == null) { + release.current = retainHistory?.() ?? null + } }, [retainHistory, cancelTrim, transition]) const returnToEnd = useCallback( (reason?: 'send') => { diff --git a/src/renderer/src/components/native-chat/use-native-chat-live-session.ts b/src/renderer/src/components/native-chat/use-native-chat-live-session.ts index 2e2af26df02..b6929b2658c 100644 --- a/src/renderer/src/components/native-chat/use-native-chat-live-session.ts +++ b/src/renderer/src/components/native-chat/use-native-chat-live-session.ts @@ -71,7 +71,9 @@ export function useNativeChatLiveSession( const [appended, setAppended] = useState([]) // Cache the id index so each frame costs O(incoming), not O(existing). const appendMergerRef = useRef>(undefined!) - appendMergerRef.current ??= createNativeChatMerger(NATIVE_CHAT_SOURCE_PRIORITY) + if (appendMergerRef.current == null) { + appendMergerRef.current = createNativeChatMerger(NATIVE_CHAT_SOURCE_PRIORITY) + } const [hookState, hookStateStartedAt, hookHasWorkingSubagents] = useNativeChatHookStatus(paneKey) diff --git a/src/renderer/src/components/native-chat/use-native-chat-message-rail.ts b/src/renderer/src/components/native-chat/use-native-chat-message-rail.ts index 595b9e83196..c55c7a2612e 100644 --- a/src/renderer/src/components/native-chat/use-native-chat-message-rail.ts +++ b/src/renderer/src/components/native-chat/use-native-chat-message-rail.ts @@ -92,7 +92,9 @@ export function useNativeChatMessageRail({ const slotIndex = previousItemsRef.current.find((item) => item.id === clicked.id)?.slotIndex const row = virtualItemsRef.current.find((item) => item.index === slotIndex) if (row && row.end > top && row.start < top + element.clientHeight) { - clicked.offset ??= row.start - top + if (clicked.offset == null) { + clicked.offset = row.start - top + } if (Math.abs(row.start - top - clicked.offset) <= ALIGN_ARRIVED_PX) { return } diff --git a/src/renderer/src/components/native-chat/use-native-chat-retained-session.ts b/src/renderer/src/components/native-chat/use-native-chat-retained-session.ts index 2638be8fee0..47d155f68bd 100644 --- a/src/renderer/src/components/native-chat/use-native-chat-retained-session.ts +++ b/src/renderer/src/components/native-chat/use-native-chat-retained-session.ts @@ -25,7 +25,9 @@ export function useNativeChatRetainedSession( ]) const activeIdentityRef = useRef(identity) const retentionRef = useRef>(undefined!) - retentionRef.current ??= createNativeChatTranscriptRetention() + if (retentionRef.current == null) { + retentionRef.current = createNativeChatTranscriptRetention() + } const sessionMatchesIdentity = activeIdentityRef.current === identity const readPhase = sessionMatchesIdentity ? session.readPhase : 'loading' diff --git a/src/renderer/src/components/native-chat/use-native-chat-selected-history.ts b/src/renderer/src/components/native-chat/use-native-chat-selected-history.ts index e53d8f0b74f..9b1e9e3b306 100644 --- a/src/renderer/src/components/native-chat/use-native-chat-selected-history.ts +++ b/src/renderer/src/components/native-chat/use-native-chat-selected-history.ts @@ -34,7 +34,9 @@ export function useNativeChatSelectedHistory({ return false } setSelectedSource(sourceKey) - release.current ??= retainHistory?.() ?? null + if (release.current == null) { + release.current = retainHistory?.() ?? null + } return true }, [root, retainHistory, sourceKey]) const deferTrim = useCallback(() => { diff --git a/src/renderer/src/components/onboarding/OnboardingInlineCommandTerminal.tsx b/src/renderer/src/components/onboarding/OnboardingInlineCommandTerminal.tsx index 669c9b9dd5f..05d69271fde 100644 --- a/src/renderer/src/components/onboarding/OnboardingInlineCommandTerminal.tsx +++ b/src/renderer/src/components/onboarding/OnboardingInlineCommandTerminal.tsx @@ -256,7 +256,9 @@ export function OnboardingInlineCommandTerminal({ return } if (hasPty) { - ptyFirstSeenAt ??= Date.now() + if (ptyFirstSeenAt == null) { + ptyFirstSeenAt = Date.now() + } // GPU/canvas renderers may expose no DOM prompt text; settled PTY presence provides a bounded fallback. if (Date.now() - ptyFirstSeenAt >= PTY_TEXT_FALLBACK_MS) { scheduleInsert() diff --git a/src/renderer/src/components/plugins/frames/use-plugin-panel-theme-revision.ts b/src/renderer/src/components/plugins/frames/use-plugin-panel-theme-revision.ts index 3204b584eb2..a65a329f99e 100644 --- a/src/renderer/src/components/plugins/frames/use-plugin-panel-theme-revision.ts +++ b/src/renderer/src/components/plugins/frames/use-plugin-panel-theme-revision.ts @@ -15,7 +15,9 @@ export function usePluginPanelThemeRevision(): number { // Compare baked shell values: unrelated root-style writes must not remount iframes and destroy panel state. const snapshotRef = useRef(null) useEffect(() => { - snapshotRef.current ??= readPanelThemeSnapshot() + if (snapshotRef.current == null) { + snapshotRef.current = readPanelThemeSnapshot() + } const observer = new MutationObserver(() => { const snapshot = readPanelThemeSnapshot() if (snapshot === snapshotRef.current) { diff --git a/src/renderer/src/components/ports/WorkspacePortScanner.tsx b/src/renderer/src/components/ports/WorkspacePortScanner.tsx index 4e09b827595..a82cb3bb725 100644 --- a/src/renderer/src/components/ports/WorkspacePortScanner.tsx +++ b/src/renderer/src/components/ports/WorkspacePortScanner.tsx @@ -148,7 +148,9 @@ export function WorkspacePortScanner({ enabled = true }: { enabled?: boolean }): let sourceChanged = Object.keys(scansByKey).length !== Object.keys(publishedScans).length for (const { key, result } of reconciled) { - sourceChanged ||= scansByKey[key] !== result + if (!sourceChanged) { + sourceChanged = scansByKey[key] !== result + } scansByKey[key] = result } const merged = mergeWorkspacePortScans(scansByKey) @@ -290,7 +292,9 @@ export function WorkspacePortScanner({ enabled = true }: { enabled?: boolean }): return } // Keep the leading scan through the quiet window so sequential events share it too. - burstRefresh ??= refresh({ force: true, targets: [LOCAL_TARGET] }) + if (burstRefresh == null) { + burstRefresh = refresh({ force: true, targets: [LOCAL_TARGET] }) + } void burstRefresh.finally(() => { if (disposed || sequence !== eventSequence) { return diff --git a/src/renderer/src/components/right-sidebar/plugin-panels/declarative/use-plugin-declarative-tree.ts b/src/renderer/src/components/right-sidebar/plugin-panels/declarative/use-plugin-declarative-tree.ts index 9a6eadf1c69..1940d396116 100644 --- a/src/renderer/src/components/right-sidebar/plugin-panels/declarative/use-plugin-declarative-tree.ts +++ b/src/renderer/src/components/right-sidebar/plugin-panels/declarative/use-plugin-declarative-tree.ts @@ -144,7 +144,9 @@ export function usePluginDeclarativeTree( return { onRender: (payload) => { pendingRef.current = { payload } - frameRef.current ??= requestAnimationFrame(flush) + if (frameRef.current == null) { + frameRef.current = requestAnimationFrame(flush) + } }, onRefused: (message, retryAfterMs) => { const payload: unknown = diff --git a/src/renderer/src/components/right-sidebar/source-control/sync/git-status-push-signal-refresh.ts b/src/renderer/src/components/right-sidebar/source-control/sync/git-status-push-signal-refresh.ts index 0ffaf14fd41..f72b3d2e701 100644 --- a/src/renderer/src/components/right-sidebar/source-control/sync/git-status-push-signal-refresh.ts +++ b/src/renderer/src/components/right-sidebar/source-control/sync/git-status-push-signal-refresh.ts @@ -101,7 +101,9 @@ export function useGitStatusPushSignalRefresh({ fetchStatus } observers.add(observer) - disposeSignals ??= subscribeSignals() + if (disposeSignals == null) { + disposeSignals = subscribeSignals() + } return () => { observers.delete(observer) if (observers.size === 0) { diff --git a/src/renderer/src/components/status-bar/status-bar-density.ts b/src/renderer/src/components/status-bar/status-bar-density.ts index a1d633c3a2c..20695b7d900 100644 --- a/src/renderer/src/components/status-bar/status-bar-density.ts +++ b/src/renderer/src/components/status-bar/status-bar-density.ts @@ -152,7 +152,9 @@ export function useStatusBarDensity(): { const bind = (target: ElementRef) => (node: HTMLElement | null): void => { - observerRef.current ??= new ResizeObserver(() => evaluate()) + if (observerRef.current == null) { + observerRef.current = new ResizeObserver(() => evaluate()) + } if (target.current) { observerRef.current.unobserve(target.current) } diff --git a/src/renderer/src/components/workspace-space/SpaceWindowStatePublisher.tsx b/src/renderer/src/components/workspace-space/SpaceWindowStatePublisher.tsx index 5002bb6b61e..9d9939c5540 100644 --- a/src/renderer/src/components/workspace-space/SpaceWindowStatePublisher.tsx +++ b/src/renderer/src/components/workspace-space/SpaceWindowStatePublisher.tsx @@ -41,11 +41,13 @@ export function SpaceWindowStatePublisher(): null { } const offRequest = api.onWindowStateRequested(() => { windowOpen = true - offStore ??= useAppStore.subscribe((next, previous) => { - if (spaceWindowStateChanged(previous, next)) { - queuePublish() - } - }) + if (offStore == null) { + offStore = useAppStore.subscribe((next, previous) => { + if (spaceWindowStateChanged(previous, next)) { + queuePublish() + } + }) + } publish(true) }) const offClose = api.onWindowClosed?.(() => {