Files
vue-tui/packages/runtime-tests/integration/pty/fixtures/cursor-nonempty-app.tsx
T
Yunfei He f6a552d855 fix(runtime): don't hide the cursor for an empty interactive app (Ink parity) (#140)
vue-tui hid the terminal cursor EAGERLY at mount regardless of content, so an
interactive app whose root renders nothing emitted `\x1b[?25l` and hid the
user's cursor. Ink hides LAZILY (log-update, on the first render that writes)
and skips log-update entirely for an empty frame, so an empty app emits zero
cursor escapes.

Remove the eager mount-time hide and rely on log-update's lazy hide. That alone
was insufficient: an empty frame becomes "\n", and the old commit gate
`willRender(outputToRender) || isCursorDirty()` was true for "\n", so log-update
(and its lazy hide) was still reached. Align the outer commit gate to Ink's
exact condition (ink.tsx:1094) `output !== frameState.lastOutput || isCursorDirty()`,
comparing the RAW frame; on an empty first commit both are "" so log-update is
never reached. `willRender` is retained only for the inner BSU/ESU wrap gate.

Verified via PTY: empty app = 0 hides; non-empty = 1 lazy hide; useCursor =
hide-then-show within one render (SHOW last, cursor positioned). alt-screen,
screen-reader, and non-TTY cursor behavior unchanged.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 12:21:10 +08:00

22 lines
785 B
TypeScript

import process from "node:process";
import { createApp, Text, useApp } from "@vue-tui/runtime";
import { defineComponent, h, onMounted } from "vue";
// A non-empty interactive app. The first frame has content, so log-update's
// render() runs and its lazy hide fires — the cursor MUST be hidden on the first
// render, matching Ink. This proves the lazy hide fully covers the non-empty
// case once the eager mount-time hide is removed.
const App = defineComponent(() => {
const { exit } = useApp();
onMounted(() => {
process.stdout.write("__READY__");
setTimeout(() => exit(), 100);
});
return () => h(Text, null, () => "hello");
});
const app = createApp(App);
app.mount({ rawMode: "auto", exitOnCtrlC: false });
await app.waitUntilExit();
console.log("exited");