fix(worktree): address PR #4174 round 5 findings

Finding #3256241831 (Nit, but awareness UX): the built-in `⎇`
indicator used to disappear whenever `statusLineLines.length > 0`,
on the assumption that the user's custom statusline rendered worktree
itself. That assumption is unsafe — scripts written before Phase C
don't know about `payload.worktree`, scripts can deliberately ignore
the field, and partial scripts may render some fields but not
worktree. In any of those cases the user sees no worktree UI while
having an active worktree, risking destructive operations in the
wrong cwd. New behavior: indicator shows by default regardless of
statusline. Added an opt-out setting `ui.hideBuiltinWorktreeIndicator`
(default false) for users whose custom statusline already renders
worktree and want to avoid duplication.

Finding #3256239608 (Nit): `fs.watch` in useWorktreeSession holds
an inode handle to `chatsDir` at mount time. If the directory is
deleted out-of-band (manual cleanup, antivirus quarantine, reset
scripts) and recreated, the watcher does NOT re-attach to the new
inode and the Footer indicator stops reacting to sidecar changes.
Reviewer explicitly accepted this as a documented limitation rather
than adding polling-fallback or error-event-handler complexity for
an edge case that doesn't arise in normal use. Added a JSDoc block
on the hook explaining the limitation and pointing to the future
fix shapes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
LaZzyMan 2026-05-18 12:08:24 +08:00
parent d4e921ae29
commit 80f9cb4958
3 changed files with 34 additions and 5 deletions

View file

@ -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-<branch> (<slug>)` 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',

View file

@ -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}
</Text>
))}
{/* 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 && (
<Text dimColor wrap="truncate">

View file

@ -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<WorktreeSession | null>(null);