diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index f206200c1..fa771421f 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -652,6 +652,16 @@ const SETTINGS_SCHEMA = { description: 'Custom theme definitions.', showInDialog: false, }, + hideBuiltinWorktreeIndicator: { + type: 'boolean', + label: 'Hide Built-in Worktree Indicator', + category: 'UI', + requiresRestart: false, + default: false, + description: + 'When true, the built-in `⎇ worktree- ()` line in the Footer is hidden. The worktree state is still surfaced to custom statusline scripts via the stdin payload (`worktree.{name, path, branch, original_cwd, original_branch}`). Keep at the default `false` unless your custom statusline renders the worktree itself — otherwise an active worktree silently has no UI affordance.', + showInDialog: false, + }, hideWindowTitle: { type: 'boolean', label: 'Hide Window Title', diff --git a/packages/cli/src/ui/components/Footer.tsx b/packages/cli/src/ui/components/Footer.tsx index 1d7c54504..66f14728c 100644 --- a/packages/cli/src/ui/components/Footer.tsx +++ b/packages/cli/src/ui/components/Footer.tsx @@ -19,6 +19,7 @@ import { useStatusLine } from '../hooks/useStatusLine.js'; import { useConfigInitMessage } from '../hooks/useConfigInitMessage.js'; import { useUIState } from '../contexts/UIStateContext.js'; import { useConfig } from '../contexts/ConfigContext.js'; +import { useSettings } from '../contexts/SettingsContext.js'; import { useVimMode } from '../contexts/VimModeContext.js'; import { ApprovalMode } from '@qwen-code/qwen-code-core'; import { GeminiSpinner } from './GeminiRespondingSpinner.js'; @@ -28,6 +29,7 @@ import { t } from '../../i18n/index.js'; export const Footer: React.FC = () => { const uiState = useUIState(); const config = useConfig(); + const settings = useSettings(); const { vimEnabled, vimMode } = useVimMode(); const { lines: statusLineLines, useThemeColors } = useStatusLine(); const configInitMessage = useConfigInitMessage(uiState.isConfigInitialized); @@ -158,12 +160,19 @@ export const Footer: React.FC = () => { {line} ))} - {/* Built-in worktree indicator. Hidden when the user has a custom - statusline (their script already gets `worktree` in stdin payload - and likely renders it itself). Also hidden during ctrl-quit - warnings so they take precedence. */} + {/* Built-in worktree indicator. Shown by default whenever a + worktree is active so the user always has a UI affordance, + even when a custom statusline is configured — their script + may not render `payload.worktree` (written before Phase C, + ignored by choice, or only rendering some fields), and + silently hiding the indicator could let the user operate + in the wrong cwd. Users who want the suppression behaviour + (e.g. their statusline already renders worktree) can opt + in via the `ui.hideBuiltinWorktreeIndicator` setting. + Hidden during ctrl-quit warnings so they take precedence. + (PR #4174 review #3256241831.) */} {uiState.activeWorktree && - statusLineLines.length === 0 && + !settings.merged.ui?.hideBuiltinWorktreeIndicator && !uiState.ctrlCPressedOnce && !uiState.ctrlDPressedOnce && ( diff --git a/packages/cli/src/ui/hooks/useWorktreeSession.ts b/packages/cli/src/ui/hooks/useWorktreeSession.ts index cc84d20cd..53e95203c 100644 --- a/packages/cli/src/ui/hooks/useWorktreeSession.ts +++ b/packages/cli/src/ui/hooks/useWorktreeSession.ts @@ -19,6 +19,16 @@ import { readWorktreeSession } from '@qwen-code/qwen-code-core'; * directory rather than the file directly because the file may not exist * yet when `enter_worktree` hasn't run. Directory watchers also catch * rename/delete events that file watchers miss. + * + * Known limitation: `fs.watch` holds an inode handle to `chatsDir` at + * mount time. If the directory is deleted out-of-band (manual cleanup, + * antivirus quarantine, reset scripts) and then recreated, the watcher + * does NOT re-attach to the new inode — the Footer indicator stops + * responding to sidecar changes until the session restarts. In normal + * use `chatsDir` is stable for the session's lifetime; if rotation + * becomes a real failure mode, add a polling fallback or listen for + * `watcher.on('error')` and re-run `setupWatcher`. (PR #4174 review + * #3256239608.) */ export function useWorktreeSession(config: Config): WorktreeSession | null { const [session, setSession] = useState(null);