From 6bb3a7ea90291e0b3e44c4b5acaee89d98b705ad Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Sat, 25 Jul 2026 14:22:30 -0700 Subject: [PATCH] fix(terminal): decouple scrollback demotion (slice C) from the retention-budget switch (C1 review #2) Per the approved contract each slice reverts behind its own switch: slice C now requires only the master terminalHiddenViewParking plus its own terminalHiddenScrollbackDemotion flag. The TTL wakeup timer fires for demotion candidates even with the budget switch off. No DEFAULT_SETTINGS entries exist for sibling flags (defaults are the '!== false' optional pattern), so no explicit defaults are added. Co-authored-by: Orca --- src/renderer/src/components/Terminal.tsx | 14 +++++--------- .../terminal-hidden-worktree-retention.test.ts | 14 +++++++++----- .../terminal-hidden-worktree-retention.ts | 5 ++++- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/renderer/src/components/Terminal.tsx b/src/renderer/src/components/Terminal.tsx index caebd0f7d4a..e06119c34fc 100644 --- a/src/renderer/src/components/Terminal.tsx +++ b/src/renderer/src/components/Terminal.tsx @@ -993,13 +993,7 @@ function Terminal(): React.JSX.Element | null { ) const retentionTtlEligibleIds = new Set( retentionBudgetCandidates - .filter( - (candidate) => - !candidate.ordinaryParkingCovers && - !candidate.hasPendingSpawnWork && - // Why: exempt worktrees still need the TTL wakeup — it fires their scrollback demotion. - (!candidate.hasEvictionExemptTab || terminalScrollbackDemotionEnabled) - ) + .filter((candidate) => !candidate.ordinaryParkingCovers && !candidate.hasPendingSpawnWork) .map((candidate) => candidate.worktreeId) ) @@ -1017,8 +1011,10 @@ function Terminal(): React.JSX.Element | null { hiddenSinceMs: candidate.hiddenSinceMs, nowMs, ...overrides, - // Why: only retention candidates wake at the eviction TTL; everyone else keeps the ordinary deadlines. - ...(terminalRetentionBudgetEnabled && retentionTtlEligibleIds.has(candidate.worktreeId) + // Why: only retention candidates wake at the eviction TTL; everyone else keeps the ordinary + // deadlines. Demotion (slice C) needs the wakeup too — it no longer requires slice B's switch. + ...((terminalRetentionBudgetEnabled || terminalScrollbackDemotionEnabled) && + retentionTtlEligibleIds.has(candidate.worktreeId) ? { retentionTtlMs: overrides.retentionTtlMs ?? TERMINAL_HIDDEN_WORKTREE_RETENTION_TTL_MS } diff --git a/src/renderer/src/components/terminal-pane/terminal-hidden-worktree-retention.test.ts b/src/renderer/src/components/terminal-pane/terminal-hidden-worktree-retention.test.ts index ed6df790c44..18b3e9874dc 100644 --- a/src/renderer/src/components/terminal-pane/terminal-hidden-worktree-retention.test.ts +++ b/src/renderer/src/components/terminal-pane/terminal-hidden-worktree-retention.test.ts @@ -185,21 +185,25 @@ describe('selectScrollbackDemotedTerminalWorktrees', () => { expect(selected).toEqual(new Set(['wt-aged'])) }) - it('returns empty when any kill switch is off', () => { + it('returns empty when the master or demotion switch is off', () => { const worktrees = [exemptCandidate('wt-aged', aged)] expect( selectScrollbackDemotedTerminalWorktrees({ ...base, worktrees, parkingEnabled: false }) ).toEqual(new Set()) + expect( + selectScrollbackDemotedTerminalWorktrees({ ...base, worktrees, demotionEnabled: false }) + ).toEqual(new Set()) + }) + + it('demotes independently of the slice-B retention-budget switch', () => { + const worktrees = [exemptCandidate('wt-aged', aged)] expect( selectScrollbackDemotedTerminalWorktrees({ ...base, worktrees, retentionBudgetEnabled: false }) - ).toEqual(new Set()) - expect( - selectScrollbackDemotedTerminalWorktrees({ ...base, worktrees, demotionEnabled: false }) - ).toEqual(new Set()) + ).toEqual(new Set(['wt-aged'])) }) it('honors the retentionTtlMs override and stays time-monotone', () => { diff --git a/src/renderer/src/components/terminal-pane/terminal-hidden-worktree-retention.ts b/src/renderer/src/components/terminal-pane/terminal-hidden-worktree-retention.ts index 68e8ac2bcc9..6a2ba57c480 100644 --- a/src/renderer/src/components/terminal-pane/terminal-hidden-worktree-retention.ts +++ b/src/renderer/src/components/terminal-pane/terminal-hidden-worktree-retention.ts @@ -106,7 +106,10 @@ export function selectScrollbackDemotedTerminalWorktrees( nowMs: number } & TerminalColdParkPolicyOverrides ): Set { - if (!args.parkingEnabled || !args.retentionBudgetEnabled || !args.demotionEnabled) { + // Why parkingEnabled still gates: it is the master kill switch — parking + // globally off means nothing parks OR demotes. Slice B's budget switch does + // not gate slice C; each slice reverts independently (approved contract). + if (!args.parkingEnabled || !args.demotionEnabled) { return new Set() } const ttlMs = args.retentionTtlMs ?? TERMINAL_HIDDEN_WORKTREE_RETENTION_TTL_MS