From d35a6a0ef366f86aeba28c06f40e44cd29cf1f07 Mon Sep 17 00:00:00 2001 From: haoyuren <13851610112@163.com> Date: Tue, 28 Jul 2026 06:29:04 +0800 Subject: Codex/agent-neutral setup, terminal IME fix, hide own overlay cursor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Write AGENTS.md (the cross-tool agent-instructions standard) at the project root alongside .claude/CLAUDE.md, generated from one guide; includes an Agent Setup section with the project-specific Codex MCP registration command. AGENTS.md is excluded from Overleaf sync. - MCP server resolves the project directory from its own script location when cwd doesn't contain the state file, so clients that launch MCP servers from a global config (Codex) work without special setup - Terminal: swallow the duplicate compositionend commit when switching input sources mid-composition (xterm finalizes on the keydown and then again on compositionend, sending pinyin text twice) - Don't render our own colored overlay cursor — the native caret already marks our position; overlay cursors are collaborators-only (web parity) Co-Authored-By: Claude Fable 5 --- src/renderer/src/components/Terminal.tsx | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'src/renderer') diff --git a/src/renderer/src/components/Terminal.tsx b/src/renderer/src/components/Terminal.tsx index ea54343..26b0270 100644 --- a/src/renderer/src/components/Terminal.tsx +++ b/src/renderer/src/components/Terminal.tsx @@ -66,6 +66,37 @@ function TerminalInstance({ id, cwd, cmd, args, visible }: { xterm.loadAddon(fitAddon) xterm.open(termRef.current) + // ── IME double-input workaround ───────────────────────────── + // + // Switching the input source mid-composition (e.g. CapsLock with a + // Chinese IME, committing the raw pinyin as ASCII) makes xterm send the + // text twice: its CompositionHelper.keydown() finalizes and sends + // synchronously, then the browser's compositionend fires and the helper + // sends the same textarea content again. Intercept in the capture phase + // on the container (runs before xterm's textarea listeners): when a + // non-IME keydown just force-finalized a composition, swallow the + // compositionend that follows so the text is only sent once. + const container = termRef.current + let imeComposing = false + let keydownFinalizedAt = 0 + const onCompStart = () => { imeComposing = true } + const onKeyDown = (e: KeyboardEvent) => { + if (imeComposing && e.keyCode !== 229 && e.keyCode !== 16 && e.keyCode !== 17 && e.keyCode !== 18) { + // xterm's CompositionHelper will finalize + send synchronously now + keydownFinalizedAt = Date.now() + imeComposing = false + } + } + const onCompEnd = (e: Event) => { + imeComposing = false + if (Date.now() - keydownFinalizedAt < 100) { + e.stopPropagation() // duplicate commit of already-sent text + } + } + container.addEventListener('compositionstart', onCompStart, true) + container.addEventListener('keydown', onKeyDown, true) + container.addEventListener('compositionend', onCompEnd, true) + setTimeout(() => fitAddon.fit(), 100) xtermRef.current = xterm @@ -101,6 +132,9 @@ function TerminalInstance({ id, cwd, cmd, args, visible }: { return () => { initializedRef.current = false resizeObserver.disconnect() + container.removeEventListener('compositionstart', onCompStart, true) + container.removeEventListener('keydown', onKeyDown, true) + container.removeEventListener('compositionend', onCompEnd, true) unsubData() unsubExit() window.api.ptyKill(id) -- cgit v1.2.3