From 667f4748ff98757288afd3076e6ffc0df084d33a Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Sun, 26 Jul 2026 00:55:29 -0700 Subject: [PATCH] fix(terminal): remove scrollback demotion (C1 slice C) The scrollback demotion feature for eviction-exempt hidden worktrees is no longer needed. Retention budget limits are now sufficient without this additional bound. Remove the terminal-hidden-scrollback-demotion module, the selectScrollbackDemotedTerminalWorktrees function, and related per-pane demotion logic. --- src/renderer/src/components/Terminal.tsx | 37 +---- ...erminal-hidden-scrollback-demotion.test.ts | 71 -------- .../terminal-hidden-scrollback-demotion.ts | 72 -------- ...terminal-hidden-worktree-retention.test.ts | 155 ------------------ .../terminal-hidden-worktree-retention.ts | 71 +------- .../use-terminal-pane-lifecycle.ts | 24 +-- src/shared/constants.ts | 1 - src/shared/types.ts | 2 - 8 files changed, 12 insertions(+), 421 deletions(-) delete mode 100644 src/renderer/src/components/terminal-pane/terminal-hidden-scrollback-demotion.test.ts delete mode 100644 src/renderer/src/components/terminal-pane/terminal-hidden-scrollback-demotion.ts diff --git a/src/renderer/src/components/Terminal.tsx b/src/renderer/src/components/Terminal.tsx index 21769cb91db..89d11ff5073 100644 --- a/src/renderer/src/components/Terminal.tsx +++ b/src/renderer/src/components/Terminal.tsx @@ -94,13 +94,8 @@ import { import { TERMINAL_HIDDEN_WORKTREE_RETENTION_TTL_MS, selectRetentionForceParkedTerminalWorktrees, - selectScrollbackDemotedTerminalWorktrees, type TerminalWorktreeRetentionCandidate } from './terminal-pane/terminal-hidden-worktree-retention' -import { - resetTerminalScrollbackDemotion, - setScrollbackDemotedTerminalWorktrees -} from './terminal-pane/terminal-hidden-scrollback-demotion' import { captureTerminalShutdownBuffersBestEffort } from './terminal-pane/shutdown-buffer-captures' import { getTerminalParkingPolicyOverrides } from './terminal-pane/terminal-parking-e2e-overrides' import { useColdParkedTerminalPresentation } from './terminal-pane/use-cold-parked-terminal-presentation' @@ -302,9 +297,6 @@ function Terminal(): React.JSX.Element | null { const terminalRetentionBudgetEnabled = useAppStore( (s) => s.settings?.terminalHiddenWorktreeRetentionBudget !== false ) - const terminalScrollbackDemotionEnabled = useAppStore( - (s) => s.settings?.terminalHiddenScrollbackDemotion !== false - ) const terminalTitleSnapshotAuthorityEnabled = useAppStore((s) => isMainTerminalSideEffectAuthorityForPty({ settings: s.settings, @@ -1072,18 +1064,6 @@ function Terminal(): React.JSX.Element | null { setForceParkedTerminalWorktreeIds((current) => haveSameWorktreeIds(current, forceParkedWorktreeIds) ? current : forceParkedWorktreeIds ) - // C1 slice C: eviction-exempt worktrees stay mounted, so past the TTL their - // hidden panes demote to the minimum scrollback tier instead of unmounting. - setScrollbackDemotedTerminalWorktrees( - selectScrollbackDemotedTerminalWorktrees({ - worktrees: retentionBudgetCandidates, - parkingEnabled: terminalParkingEnabled, - retentionBudgetEnabled: terminalRetentionBudgetEnabled, - demotionEnabled: terminalScrollbackDemotionEnabled, - nowMs, - ...overrides - }) - ) const retentionTtlEligibleIds = new Set( retentionBudgetCandidates .filter((candidate) => !candidate.ordinaryParkingCovers && !candidate.hasPendingSpawnWork) @@ -1105,10 +1085,8 @@ function Terminal(): React.JSX.Element | null { parkCooldownUntilMs: candidate.parkCooldownUntilMs, nowMs, ...overrides, - // 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) + // Why: only retention candidates wake at the eviction TTL; everyone else keeps the ordinary deadlines. + ...(terminalRetentionBudgetEnabled && retentionTtlEligibleIds.has(candidate.worktreeId) ? { retentionTtlMs: overrides.retentionTtlMs ?? TERMINAL_HIDDEN_WORKTREE_RETENTION_TTL_MS } @@ -1133,7 +1111,6 @@ function Terminal(): React.JSX.Element | null { terminalParkingEnabled, terminalParkingRevision, terminalRetentionBudgetEnabled, - terminalScrollbackDemotionEnabled, terminalSshParkingEnabled, workspaceSurfaces ]) @@ -1373,14 +1350,8 @@ function Terminal(): React.JSX.Element | null { workspaceSessionReady, workspaceSurfaces ]) - // Why: on host unmount no reconciliation effect runs again, so dispose every remaining parked watcher and drop the demotion verdicts nothing would recompute. - useEffect( - () => () => { - disposeAllParkedTerminalWatchers() - resetTerminalScrollbackDemotion() - }, - [] - ) + // Why: on host unmount no reconciliation effect runs again, so dispose every remaining parked watcher. + useEffect(() => () => disposeAllParkedTerminalWatchers(), []) // Auto-create first tab when worktree activates useEffect(() => { if (!workspaceSessionReady) { diff --git a/src/renderer/src/components/terminal-pane/terminal-hidden-scrollback-demotion.test.ts b/src/renderer/src/components/terminal-pane/terminal-hidden-scrollback-demotion.test.ts deleted file mode 100644 index 79811f36bda..00000000000 --- a/src/renderer/src/components/terminal-pane/terminal-hidden-scrollback-demotion.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' -import { - TERMINAL_DEMOTED_SCROLLBACK_ROWS, - demotedTerminalScrollbackRows, - isTerminalWorktreeScrollbackDemoted, - resetTerminalScrollbackDemotion, - resolveTerminalMountScrollbackRows, - setScrollbackDemotedTerminalWorktrees, - subscribeTerminalScrollbackDemotion -} from './terminal-hidden-scrollback-demotion' - -afterEach(() => { - setScrollbackDemotedTerminalWorktrees(new Set()) -}) - -describe('setScrollbackDemotedTerminalWorktrees', () => { - it('replaces the demoted set', () => { - setScrollbackDemotedTerminalWorktrees(new Set(['wt-1', 'wt-2'])) - expect(isTerminalWorktreeScrollbackDemoted('wt-1')).toBe(true) - setScrollbackDemotedTerminalWorktrees(new Set(['wt-2'])) - expect(isTerminalWorktreeScrollbackDemoted('wt-1')).toBe(false) - expect(isTerminalWorktreeScrollbackDemoted('wt-2')).toBe(true) - }) - - it('does not notify when the set content is unchanged (flip-loop damping)', () => { - setScrollbackDemotedTerminalWorktrees(new Set(['wt-1'])) - const listener = vi.fn() - const unsubscribe = subscribeTerminalScrollbackDemotion(listener) - setScrollbackDemotedTerminalWorktrees(new Set(['wt-1'])) - expect(listener).not.toHaveBeenCalled() - setScrollbackDemotedTerminalWorktrees(new Set(['wt-1', 'wt-2'])) - expect(listener).toHaveBeenCalledTimes(1) - unsubscribe() - }) -}) - -describe('resetTerminalScrollbackDemotion', () => { - // Why: module state outlives the host, so a stale verdict would demote a pane - // that remounts before any effect recomputes it. - it('clears every verdict and notifies subscribers', () => { - setScrollbackDemotedTerminalWorktrees(new Set(['wt-1', 'wt-2'])) - const listener = vi.fn() - const unsubscribe = subscribeTerminalScrollbackDemotion(listener) - resetTerminalScrollbackDemotion() - expect(isTerminalWorktreeScrollbackDemoted('wt-1')).toBe(false) - expect(isTerminalWorktreeScrollbackDemoted('wt-2')).toBe(false) - expect(listener).toHaveBeenCalledTimes(1) - unsubscribe() - }) -}) - -describe('demotedTerminalScrollbackRows', () => { - it('never exceeds the configured rows', () => { - expect(demotedTerminalScrollbackRows(50_000)).toBe(TERMINAL_DEMOTED_SCROLLBACK_ROWS) - expect(demotedTerminalScrollbackRows(500)).toBe(500) - }) -}) - -describe('resolveTerminalMountScrollbackRows', () => { - // Why: pane births (splits) under an already-demoted worktree bypass the - // post-mount demotion effect entirely — the mount options must demote. - it('demotes new xterms while the worktree verdict is set, restores after clear', () => { - setScrollbackDemotedTerminalWorktrees(new Set(['wt-demoted'])) - expect(resolveTerminalMountScrollbackRows('wt-demoted', 50_000)).toBe( - TERMINAL_DEMOTED_SCROLLBACK_ROWS - ) - expect(resolveTerminalMountScrollbackRows('wt-other', 50_000)).toBe(50_000) - resetTerminalScrollbackDemotion() - expect(resolveTerminalMountScrollbackRows('wt-demoted', 50_000)).toBe(50_000) - }) -}) diff --git a/src/renderer/src/components/terminal-pane/terminal-hidden-scrollback-demotion.ts b/src/renderer/src/components/terminal-pane/terminal-hidden-scrollback-demotion.ts deleted file mode 100644 index 12557d10f47..00000000000 --- a/src/renderer/src/components/terminal-pane/terminal-hidden-scrollback-demotion.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { useSyncExternalStore } from 'react' -import { DESKTOP_TERMINAL_SCROLLBACK_ROWS_MIN } from '../../../../shared/terminal-scrollback-policy' - -/** Demoted hidden panes keep the minimum retention tier (~1.3MB V8 heap vs - * ~19MB at 50k rows, measured); trimmed history is gone by design — reveal - * restores the configured cap for future output only. */ -export const TERMINAL_DEMOTED_SCROLLBACK_ROWS = DESKTOP_TERMINAL_SCROLLBACK_ROWS_MIN - -// Why module state (same pattern as parked watchers): the verdict is computed -// in Terminal.tsx but applied by each mounted pane's lifecycle hook — prop -// threading would cross three component layers for a per-worktree boolean. -const demotedWorktreeIds = new Set() -const listeners = new Set<() => void>() - -export function setScrollbackDemotedTerminalWorktrees(next: ReadonlySet): void { - if ( - next.size === demotedWorktreeIds.size && - [...next].every((id) => demotedWorktreeIds.has(id)) - ) { - return - } - demotedWorktreeIds.clear() - for (const id of next) { - demotedWorktreeIds.add(id) - } - for (const listener of listeners) { - listener() - } -} - -/** - * Clear every verdict — the demotion host is gone, so nothing recomputes them. - * Without this a pane remounting later reads a stale demotion (React runs the - * pane's scrollback effect BEFORE the host effect that would clear it) and - * xterm trims its restore replay to 1 000 rows, unrecoverably. - */ -export function resetTerminalScrollbackDemotion(): void { - setScrollbackDemotedTerminalWorktrees(new Set()) -} - -export function isTerminalWorktreeScrollbackDemoted(worktreeId: string): boolean { - return demotedWorktreeIds.has(worktreeId) -} - -export function subscribeTerminalScrollbackDemotion(listener: () => void): () => void { - listeners.add(listener) - return () => listeners.delete(listener) -} - -/** Live per-worktree demotion verdict for a mounted pane host. */ -export function useTerminalWorktreeScrollbackDemoted(worktreeId: string): boolean { - return useSyncExternalStore(subscribeTerminalScrollbackDemotion, () => - demotedWorktreeIds.has(worktreeId) - ) -} - -export function demotedTerminalScrollbackRows(configuredRows: number): number { - return Math.min(TERMINAL_DEMOTED_SCROLLBACK_ROWS, configuredRows) -} - -/** Scrollback for a NEW xterm in this worktree. Pane births under an - * already-demoted worktree (splits, layout replays) must take the demoted - * tier at create — the post-mount demotion effect only re-runs when the - * verdict or setting flips, so it never covers them. */ -export function resolveTerminalMountScrollbackRows( - worktreeId: string, - configuredRows: number -): number { - return demotedWorktreeIds.has(worktreeId) - ? demotedTerminalScrollbackRows(configuredRows) - : configuredRows -} 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 1cd8fa6a878..867dd33dd67 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 @@ -4,7 +4,6 @@ import { TERMINAL_HIDDEN_WORKTREE_RETENTION_TTL_MS, isEvictionExemptTerminalPty, selectRetentionForceParkedTerminalWorktrees, - selectScrollbackDemotedTerminalWorktrees, type TerminalWorktreeRetentionCandidate } from './terminal-hidden-worktree-retention' @@ -175,157 +174,3 @@ describe('selectRetentionForceParkedTerminalWorktrees', () => { } }) }) - -describe('selectScrollbackDemotedTerminalWorktrees', () => { - const nowMs = 5_000_000 - const aged = nowMs - TERMINAL_HIDDEN_WORKTREE_RETENTION_TTL_MS - const base = { - parkingEnabled: true, - retentionBudgetEnabled: true, - demotionEnabled: true, - nowMs - } - - function exemptCandidate( - worktreeId: string, - hiddenSinceMs: number | null, - partial: Partial = {} - ): TerminalWorktreeRetentionCandidate { - return { - worktreeId, - hiddenSinceMs, - isVisible: false, - shouldMeasureHiddenWorktree: false, - hasActivityTerminalPortal: false, - ordinaryParkingCovers: false, - hasEvictionExemptTab: true, - hasPendingSpawnWork: false, - ...partial - } - } - - it('demotes only eviction-exempt worktrees hidden past the TTL', () => { - const selected = selectScrollbackDemotedTerminalWorktrees({ - ...base, - worktrees: [ - exemptCandidate('wt-aged', aged), - exemptCandidate('wt-recent', nowMs - 60_000), - exemptCandidate('wt-not-exempt', aged, { hasEvictionExemptTab: false }), - exemptCandidate('wt-visible', aged, { isVisible: true }), - exemptCandidate('wt-portal', aged, { hasActivityTerminalPortal: true }), - exemptCandidate('wt-pending', aged, { hasPendingSpawnWork: true }), - exemptCandidate('wt-unhidden', null) - ] - }) - expect(selected).toEqual(new Set(['wt-aged'])) - }) - - 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(['wt-aged'])) - }) - - it('demotes an exempt worktree as soon as it force-parks under the count budget, before the TTL', () => { - // Why: after a force-park the exempt tabs' panes are the only ones left - // mounted in that worktree, so they take the demoted scrollback tier. - const underTtl = nowMs - TERMINAL_WORKTREE_PARK_DELAY_MS - const worktrees = [ - exemptCandidate('wt-a', underTtl - 3), - exemptCandidate('wt-b', underTtl - 2), - exemptCandidate('wt-c', underTtl - 1), - exemptCandidate('wt-d', underTtl) - ] - expect( - selectScrollbackDemotedTerminalWorktrees({ ...base, worktrees, retentionLimit: 2 }) - ).toEqual(new Set(['wt-a', 'wt-b'])) - }) - - it('demotes un-parkable non-exempt worktrees the force-park lever spared, past the TTL', () => { - // wt-solo is the only candidate → last-active exempt from force-park; its - // panes stay fully mounted forever unless demotion bounds them. - const solo = [exemptCandidate('wt-solo', aged, { hasEvictionExemptTab: false })] - expect(selectScrollbackDemotedTerminalWorktrees({ ...base, worktrees: solo })).toEqual( - new Set(['wt-solo']) - ) - // With slice B off nothing force-parks; every un-parkable worktree past - // the TTL demotes (C bounds independently). - const pair = [ - exemptCandidate('wt-a', aged - 1, { hasEvictionExemptTab: false }), - exemptCandidate('wt-b', aged, { hasEvictionExemptTab: false }) - ] - expect( - selectScrollbackDemotedTerminalWorktrees({ - ...base, - worktrees: pair, - retentionBudgetEnabled: false - }) - ).toEqual(new Set(['wt-a', 'wt-b'])) - // With slice B on, wt-a force-parks (unmounted — no demotion needed) and - // only the spared last-active wt-b demotes. - expect(selectScrollbackDemotedTerminalWorktrees({ ...base, worktrees: pair })).toEqual( - new Set(['wt-b']) - ) - // Ordinary-parking-covered worktrees are bounded by the warm cap already. - const covered = [ - exemptCandidate('wt-covered', aged, { - hasEvictionExemptTab: false, - ordinaryParkingCovers: true - }) - ] - expect(selectScrollbackDemotedTerminalWorktrees({ ...base, worktrees: covered })).toEqual( - new Set() - ) - }) - - it('is idempotent and only grows as time advances (flip-loop dwell)', () => { - const worktrees = [ - exemptCandidate('wt-exempt', aged), - exemptCandidate('wt-force-parked', aged, { hasEvictionExemptTab: false }), - exemptCandidate('wt-last-active', nowMs - 60_000, { hasEvictionExemptTab: false }) - ] - const first = selectScrollbackDemotedTerminalWorktrees({ ...base, worktrees }) - expect(selectScrollbackDemotedTerminalWorktrees({ ...base, worktrees })).toEqual(first) - // Why: with unchanged inputs, a later evaluation may only ADD members — - // a verdict that oscillates with time is the React-#185 ingredient. - for (const laterMs of [nowMs + 1_000, nowMs + TERMINAL_HIDDEN_WORKTREE_RETENTION_TTL_MS]) { - const later = selectScrollbackDemotedTerminalWorktrees({ ...base, worktrees, nowMs: laterMs }) - for (const id of first) { - expect(later.has(id)).toBe(true) - } - } - }) - - it('honors the retentionTtlMs override and stays time-monotone', () => { - const worktrees = [exemptCandidate('wt-1', nowMs - 500)] - expect( - selectScrollbackDemotedTerminalWorktrees({ ...base, worktrees, retentionTtlMs: 400 }) - ).toEqual(new Set(['wt-1'])) - expect( - selectScrollbackDemotedTerminalWorktrees({ ...base, worktrees, retentionTtlMs: 600 }) - ).toEqual(new Set()) - expect( - selectScrollbackDemotedTerminalWorktrees({ - ...base, - worktrees, - retentionTtlMs: 600, - nowMs: nowMs + 200 - }) - ).toEqual(new Set(['wt-1'])) - }) -}) 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 2f6b7668b7c..840c79791b2 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 @@ -20,10 +20,10 @@ export const TERMINAL_HIDDEN_WORKTREE_RETENTION_TTL_MS = 45 * 60_000 // Why: an eviction-exempt pty is a live local one a remount could not reattach // (daemon-fail-open separator-less ids, ptys minted under another worktree) — a // fresh spawn would orphan the live shell. Its TAB keeps its mounted pane when -// the worktree force-parks (per-tab exclusion, mirroring Activity portals) and -// relies on scrollback demotion instead. Per-PTY, not per-tab: the coverage -// veto that makes a worktree a retention candidate walks every split pane, so -// the exemption must too (see isEvictionExemptTerminalTab). +// the worktree force-parks (per-tab exclusion, mirroring Activity portals). +// Per-PTY, not per-tab: the coverage veto that makes a worktree a retention +// candidate walks every split pane, so the exemption must too (see +// isEvictionExemptTerminalTab). export function isEvictionExemptTerminalPty( ptyId: string | null | undefined, worktreeId: string @@ -98,66 +98,3 @@ export function selectRetentionForceParkedTerminalWorktrees( hotRetainLimit: args.retentionLimit ?? TERMINAL_HIDDEN_WORKTREE_RETENTION_LIMIT }) } - -/** - * Scrollback demotion targets the hidden panes that stay mounted past the - * retention deadlines: (a) eviction-exempt tabs' worktrees (their panes - * survive force-park via the per-tab exclusion) demote past the TTL — or - * immediately when their worktree force-parks under the count budget, since - * the exempt panes are then the only ones left mounted; (b) un-parkable - * worktrees the force-park lever spared (the last-active exemption, or slice - * B switched off) demote past the TTL instead of holding full scrollback - * forever. Time-monotone for fixed inputs: the force-parked and past-TTL sets - * only ever grow with nowMs and the last-active pick is time-independent, so - * membership only grows until a reveal resets hiddenSince — no oscillation - * surface. - * - * Demotion is WORKTREE-scoped by design: one eviction-exempt leaf demotes - * every still-mounted pane in the worktree, including parkable siblings a - * mixed split tab keeps mounted (exemption is tab-granular). Coarser than - * per-pane, deliberately — do not "fix" to per-pane demotion without - * revisiting the retention-budget contract in the C1 PR body. - */ -export function selectScrollbackDemotedTerminalWorktrees( - args: { - worktrees: readonly TerminalWorktreeRetentionCandidate[] - parkingEnabled: boolean - retentionBudgetEnabled: boolean - demotionEnabled: boolean - nowMs: number - } & TerminalColdParkPolicyOverrides -): Set { - // 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 - const forceParked = selectRetentionForceParkedTerminalWorktrees(args) - const demoted = new Set() - for (const worktree of args.worktrees) { - if ( - worktree.hiddenSinceMs === null || - worktree.isVisible || - worktree.shouldMeasureHiddenWorktree || - worktree.hasActivityTerminalPortal || - worktree.hasPendingSpawnWork - ) { - continue - } - const pastTtl = args.nowMs - worktree.hiddenSinceMs >= ttlMs - if (worktree.hasEvictionExemptTab) { - if (pastTtl || forceParked.has(worktree.worktreeId)) { - demoted.add(worktree.worktreeId) - } - continue - } - // Force-park candidates the lever spared stay fully mounted, so demotion - // is their only bound; force-parked ones already unmounted. - if (!worktree.ordinaryParkingCovers && pastTtl && !forceParked.has(worktree.worktreeId)) { - demoted.add(worktree.worktreeId) - } - } - return demoted -} diff --git a/src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.ts b/src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.ts index b9833e5e943..66e91ee7fdb 100644 --- a/src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.ts +++ b/src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.ts @@ -15,11 +15,6 @@ import { resolveTerminalCursorInactiveStyle } from '@/lib/pane-manager/pane-terminal-options' import { normalizeDesktopTerminalScrollbackRows } from '../../../../shared/terminal-scrollback-policy' -import { - demotedTerminalScrollbackRows, - resolveTerminalMountScrollbackRows, - useTerminalWorktreeScrollbackDemoted -} from './terminal-hidden-scrollback-demotion' import { configureTerminalOutputBacklogCap, writeTerminalOutput @@ -574,8 +569,6 @@ export function useTerminalPaneLifecycle({ const terminalScrollbackRows = normalizeDesktopTerminalScrollbackRows( settings?.terminalScrollbackRows ) - // C1 slice C: hidden eviction-exempt worktrees drop to the minimum scrollback tier past the retention TTL. - const scrollbackDemoted = useTerminalWorktreeScrollbackDemoted(worktreeId) // Why here: backlog cap scales with the scrollback setting; set it where the setting is read to stay in lockstep. configureTerminalOutputBacklogCap(settings?.terminalScrollbackRows) const systemPrefersDarkRef = useRef(systemPrefersDark) @@ -1334,12 +1327,8 @@ export function useTerminalPaneLifecycle({ fontFamily: buildFontFamily(currentSettings?.terminalFontFamily ?? ''), fontWeight: terminalFontWeights.fontWeight, fontWeightBold: terminalFontWeights.fontWeightBold, - // Why resolveTerminalMountScrollbackRows: pane births while the - // worktree is already demoted (C1 slice C) must not resurrect the - // full tier — the post-mount demotion effect doesn't re-run for them. - scrollback: resolveTerminalMountScrollbackRows( - worktreeId, - normalizeDesktopTerminalScrollbackRows(currentSettings?.terminalScrollbackRows) + scrollback: normalizeDesktopTerminalScrollbackRows( + currentSettings?.terminalScrollbackRows ), cursorStyle, cursorInactiveStyle: resolveTerminalCursorInactiveStyle(cursorStyle), @@ -1768,13 +1757,8 @@ export function useTerminalPaneLifecycle({ return } // Why: live row-retention changes are xterm option updates only — must not recreate/replay/refit/resize/signal the PTY. - applyTerminalScrollbackRowsToMountedPanes( - manager, - scrollbackDemoted - ? demotedTerminalScrollbackRows(terminalScrollbackRows) - : terminalScrollbackRows - ) - }, [managerRef, scrollbackDemoted, terminalScrollbackRows]) + applyTerminalScrollbackRowsToMountedPanes(manager, terminalScrollbackRows) + }, [managerRef, terminalScrollbackRows]) useEffect(() => { const manager = managerRef.current diff --git a/src/shared/constants.ts b/src/shared/constants.ts index c52cc86a208..e19f1bff2c4 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -304,7 +304,6 @@ export function getDefaultSettings(homedir: string): GlobalSettings { // settings objects (which omit them) keep the default-on behavior. terminalSshViewParking: true, terminalHiddenWorktreeRetentionBudget: true, - terminalHiddenScrollbackDemotion: true, terminalMainSideEffectAuthority: true, terminalHiddenDeliveryGate: true, terminalModelQueryAuthority: true, diff --git a/src/shared/types.ts b/src/shared/types.ts index 2daee83e358..8965a085142 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -2852,8 +2852,6 @@ export type GlobalSettings = { terminalSshViewParking?: boolean /** Kill switch for the hidden-worktree retention budget (C1): force-parks the least-recently-hidden un-parkable worktrees beyond a count budget or TTL. */ terminalHiddenWorktreeRetentionBudget?: boolean - /** Kill switch for scrollback demotion (C1): eviction-exempt hidden worktrees drop to the minimum scrollback tier past the retention TTL. */ - terminalHiddenScrollbackDemotion?: boolean /** Kill switch for main-process PTY side-effect authority; on (default) = title/bell/agent facts via pty:sideEffect channel, not renderer byte parsing. */ terminalMainSideEffectAuthority?: boolean /** Kill switch for main's hidden-delivery gate (Phase 4): drops PTY bytes to hidden views after model ingestion; requires terminalMainSideEffectAuthority. */