From 0392ed3af5e24dfdaab5371c4db37debedc6e3a8 Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Mon, 12 Oct 2026 00:36:15 +0200 Subject: [PATCH 1/2] test(e2e): a program that asks for the mouse gets it, under Node and Bun (#182) A raw-mode program turns mouse reporting on, as Claude Code's fullscreen view does: xterm takes the mode (the arrow, not the text caret), a key and a click reach it as an SGR press and release. Under Node and under Bun, Claude Code's runtime. It passes on main: the caret in Claude Code (#182) was Claude Code's own fullscreen auto-disable, not the terminal. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA --- tools/e2e/run.mjs | 60 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tools/e2e/run.mjs b/tools/e2e/run.mjs index 91b5d02..6ed6e51 100644 --- a/tools/e2e/run.mjs +++ b/tools/e2e/run.mjs @@ -1163,6 +1163,66 @@ const scenarios = { } }, + // A PROGRAM THAT ASKS FOR THE MOUSE GETS IT (#182; owner, 2026-10-12: "in + // claude code it shows a caret cursor but it should show the normal cursor + // ... i cant use the cursor to interact with the ui like with askusertool"). + // A raw-mode program turns mouse reporting on, as Claude Code does, and prints + // every byte it is sent; run under Node and under Bun (Claude Code's own + // runtime). Measured: xterm takes the mode (its arrow cursor), and a click + // comes back to the program as an SGR press and release. + async mouseReport(ok) { + const w = world() + const probe = join(w.alpha, 'mouse-probe.cjs') + writeFileSync( + probe, + [ + 'process.stdin.setRawMode(true)', + 'process.stdin.resume()', + "process.stdout.write('\\x1b[?1049h\\x1b[?1000h\\x1b[?1002h\\x1b[?1006hPROBE-READY\\r\\n')", + "process.stdin.on('data', (b) => process.stdout.write('GOT' + JSON.stringify(b.toString().split(String.fromCharCode(27)).join('ESC')) + '\\r\\n'))", + "setTimeout(() => { process.stdout.write('\\x1b[?1000l\\x1b[?1002l\\x1b[?1006l\\x1b[?1049l'); process.exit(0) }, 15000)" + ].join('\n') + ) + const { app, page } = await launch(w, { args: [w.alpha] }) + try { + const look = () => + page.evaluate(() => { + const x = document.querySelector('[data-term-host] .xterm') + return { on: !!x?.classList.contains('enable-mouse-events'), cursor: x ? getComputedStyle(x.querySelector('.xterm-screen') ?? x).cursor : '' } + }) + for (const [runtime, exe] of [ + ['node', process.execPath], + ['bun', join(process.env.USERPROFILE ?? '', '.bun', 'bin', 'bun.exe')] + ]) { + if (runtime === 'bun' && !existsSync(exe)) { + ok(true, 'bun is not installed here: skipped') + continue + } + await page.waitForFunction(() => /PS [^>]*>\s*$/.test((document.querySelector('.xterm .xterm-rows')?.textContent ?? '').trimEnd()), null, { timeout: 45000 }) + await page.locator('[data-term-host] .xterm').first().click() + await page.keyboard.type(`cls; & '${exe}' '${probe}'`) + await page.keyboard.press('Enter') + ok(!!(await until(async () => (await termText(page)).includes('PROBE-READY'), 15000)), `${runtime}: the program is up and has asked for the mouse`) + await sleep(300) + const l = await look() + ok(l.on, `${runtime}: xterm took the mouse mode (${JSON.stringify(l)})`) + ok(l.cursor !== 'text', `${runtime}: so the pointer is not the text caret (${l.cursor})`) + await page.keyboard.type('k') + ok(!!(await until(async () => (await termText(page)).includes('GOT"k"'), 8000)), `${runtime}: a key reaches it`) + const box = await page.locator('[data-term-host] .xterm-screen').first().boundingBox() + await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2) + const got = await until(async () => { + const t = await termText(page) + return /ESC\[<0;\d+;\d+M/.test(t) && /ESC\[<0;\d+;\d+m/.test(t) ? t : null + }, 5000) + ok(!!got, `${runtime}: the click reaches it, press and release (${((got || (await termText(page))).match(/GOT"[^"]*"/g) || []).slice(-3).join(' ')})`) + await until(async () => /PS [^>]*>\s*$/.test((await termText(page)).trimEnd()), 20000, 200) + } + } finally { + await closeApp(app) + } + }, + async clickCaret(ok) { const w = world() const { app, page } = await launch(w, { args: [w.alpha] }) From 9566f565dcbc45fd4560c198808fb3fd17a3f2d5 Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Mon, 12 Oct 2026 00:36:44 +0200 Subject: [PATCH 2/2] docs: Claude Code's caret and dead clicks, and how to read its fullscreen auto-disable (#182) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA --- docs/diagnostics.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/diagnostics.md b/docs/diagnostics.md index 9ca2b3c..bf4b234 100644 --- a/docs/diagnostics.md +++ b/docs/diagnostics.md @@ -182,3 +182,13 @@ second passes `{ often: true }`. stack never comes (MEASURED: "Website owner has not opted in"). - Preload: `...createDiagApi(ipcRenderer)`. Page: `startDiag(bridge)` before the first render. - Settings: `` and `coreSettingsIndex({ diagnostics: true })`. + +## Claude Code shows a text caret and ignores clicks (#182, 2026-10-12) + +Not the terminal (the `mouseReport` e2e proves a program that asks for the mouse gets it, under Node +and Bun). Claude Code asks for the mouse only in its fullscreen TUI, and switches that off by itself +after repeated failed starts: `~/.claude.json` then holds `fullscreenAutoDisabled` (`version`, `at`, +`strikes`). A "failed start" is a Claude Code that dies before it is healthy, which is what killing +Prism Terminal does to its restored Claude tabs while they are still booting. Check that key first; +`/tui fullscreen` in Claude Code turns fullscreen back on (the record is also per Claude Code +version). Installs and test launches: let a launch settle before closing it.