Skip to content

Commit 3af7798

Browse files
committed
fix(terminal): always hide xterm cursor across all tools
The bar caret survived on terminal / remote tabs as a fallback for raw shells, but in practice the shell prompt + character echo gives users enough positional cue, and the blinking bar pulled the UI down a notch. Drop shouldShowXtermCursor entirely, paint cursor in bg color regardless of tool, and let the existing CSS .xterm-cursor display:none cover the DOM renderer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3b8eb79 commit 3af7798

1 file changed

Lines changed: 14 additions & 17 deletions

File tree

src-ui/src/components/center/TierTerminal.tsx

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -100,16 +100,6 @@ function normalizePasteNewlines(text: string): string {
100100
return text.replace(/\r\n?/g, '\n');
101101
}
102102

103-
// xterm's bar cursor is a fallback for raw shells (`terminal`, `remote`) where
104-
// nothing else paints one. Every AI agent (claude / codex / gemini / opencode /
105-
// qwen / hermes / openclaw) renders its own TUI cursor inside the input box —
106-
// keeping xterm's bar on top would either double up visibly, or strand a lone
107-
// cursor at row 0 col 0 during the startup window where the splash has faded
108-
// but the tool hasn't entered alt-screen yet.
109-
function shouldShowXtermCursor(tool: ToolType): boolean {
110-
return tool === 'terminal' || tool === 'remote';
111-
}
112-
113103
// Derive a slightly-darker, alpha-blended selection background from the
114104
// scheme's fg (or the warm coffee fallback). Multiplying RGB by 0.8 first
115105
// gives the "比主题色深点" feel before xterm composites it over the bg.
@@ -123,7 +113,13 @@ function deriveSelectionBg(hex: string, isDark: boolean): string {
123113
return `rgba(${r},${g},${b},${isDark ? 0.4 : 0.3})`;
124114
}
125115

126-
function buildXtermTheme(themeName: string, hasBg: boolean | undefined, hideCursor: boolean, schemeId?: string) {
116+
// xterm's caret was a leftover from raw-shell mode (terminal / remote) — every
117+
// other surface the user touches (each AI agent's input box, the Compose
118+
// textarea) paints its own caret, so xterm's was either redundant or a
119+
// stranded artifact. Always paint the cursor in the background color so the
120+
// WebGL renderer effectively erases it; the DOM renderer is also covered by
121+
// `.xterm-cursor { display: none }` in TierTerminal.css.
122+
function buildXtermTheme(themeName: string, hasBg: boolean | undefined, schemeId?: string) {
127123
const isDark = themeName !== 'light';
128124
const scheme = schemeId ? TERM_COLOR_SCHEMES.find(s => s.id === schemeId) : undefined;
129125
const bgOpaque = THEME_TERMINAL_BG[themeName] || (isDark ? '#0c0c0c' : '#eeebe2');
@@ -155,7 +151,7 @@ function buildXtermTheme(themeName: string, hasBg: boolean | undefined, hideCurs
155151
...base,
156152
background: bg,
157153
foreground: fg,
158-
cursor: hideCursor ? bgOpaque : fg,
154+
cursor: bgOpaque,
159155
cursorAccent: bgOpaque,
160156
};
161157
}
@@ -356,13 +352,14 @@ function TierTerminalImpl({
356352
allowTransparency: hasBg,
357353
customGlyphs: true, // Pixel-perfect box-drawing on all platforms (canvas-drawn, font-independent)
358354
rescaleOverlappingGlyphs: true, // Force ambiguous-width chars (block chars ▀▄█) to single cell width
359-
cursorStyle: 'bar' as const,
360355
// Cursor blink fires a GPU repaint every ~530ms for the entire app
361356
// lifetime. On laptops (especially Apple Silicon Air without a fan)
362-
// that's a constant power draw users feel as warmth. Off by default.
357+
// that's a constant power draw users feel as warmth. Off by default —
358+
// also redundant since the cursor itself is invisible (theme.cursor =
359+
// bg color), but kept for renderer paths that ignore the color trick.
363360
cursorBlink: false,
364361
scrollback: 5000,
365-
theme: buildXtermTheme(theme, hasBg, !shouldShowXtermCursor(tool), termColorScheme),
362+
theme: buildXtermTheme(theme, hasBg, termColorScheme),
366363
});
367364

368365
const fit = new FitAddon();
@@ -836,8 +833,8 @@ function TierTerminalImpl({
836833
useEffect(() => {
837834
const term = xtermRef.current;
838835
if (!term) return;
839-
term.options.theme = buildXtermTheme(theme, hasBg, !shouldShowXtermCursor(tool), termColorScheme);
840-
}, [theme, tool, termColorScheme, hasBg]);
836+
term.options.theme = buildXtermTheme(theme, hasBg, termColorScheme);
837+
}, [theme, termColorScheme, hasBg]);
841838

842839
// ── IME focus-scroll guard ───────────────────────────────────────────────
843840
// Defense-in-depth for the `overflow: clip` fix in TierTerminal.css.

0 commit comments

Comments
 (0)