Files
vue-tui/packages/runtime
Yunfei He 9d0c59c641 feat: expand key parser with home, end, ctrl+arrow, meta+arrow
Add parsing for Home (ESC[H, ESC[1~, ESC[7~), End (ESC[F, ESC[4~,
ESC[8~), Ctrl+arrow (CSI 1;5 A/B/C/D), and Meta+arrow (double-ESC
prefix) key sequences. Add home/end fields to the Key interface.
Implement 12 new useInput tests for these key combinations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 00:12:05 +08:00
..
2026-05-24 22:58:29 +08:00
2026-05-24 22:58:29 +08:00

@vue-tui/runtime

Vue-idiomatic terminal renderer in the spirit of React Ink. Platform-specific runtime parallel to @vue/runtime-dom.

Install

pnpm add @vue-tui/runtime vue

Quickstart

import { defineComponent, h, ref } from "vue";
import { createApp, Box, Text, useInput } from "@vue-tui/runtime";

const Counter = defineComponent({
  setup() {
    const count = ref(0);
    useInput((input) => {
      if (input === "+") count.value++;
      if (input === "-") count.value--;
    });
    return () => h(Box, null, h(Text, null, `Count: ${count.value}`));
  },
});

createApp(Counter).mount();

API

Single entry point — createApp(root, rootProps?). Returns a TuiApp (Omit<VueApp<TuiNode>, "mount"> plus four TUI methods).

import { createApp } from "@vue-tui/runtime";

const app = createApp(App, { initialState }).use(myPlugin).provide(themeKey, dark);

app.mount(); // all defaults (process.*)
app.mount({ debug: true }); // just flags
app.mount({ stdout: customWritable }); // partial stream override
app.mount({ stdout, stdin, stderr }); // explicit streams (testing)

await app.waitUntilExit();
app.unmount(); // optional — see "Cleanup" below

Cleanup

mount() registers a process.on("exit") listener that runs the same teardown as unmount(). So you only need app.unmount() if you want to tear down the app before the process is ready to exit (mid-program, in tests, or when one process hosts multiple UIs sequentially). For a normal CLI that exits when the user quits or the script ends, cleanup happens automatically.

TuiApp interface

interface TuiApp extends VueApp<TuiNode> {
  mount(options?: MountOptions): ComponentPublicInstance;
  unmount(): void;
  waitUntilExit(): Promise<void>;
}

interface MountOptions {
  stdout?: NodeJS.WriteStream; // default: process.stdout
  stdin?: NodeJS.ReadStream; // default: process.stdin
  stderr?: NodeJS.WriteStream; // default: process.stderr
  debug?: boolean; // default: false
  exitOnCtrlC?: boolean; // default: true
}

All fields optional with per-field fallback. Matches Ink's RenderOptions shape.

Components

Box, Text, Newline, Spacer, Static, Transform.

Composables

useExit, useInput, useFocus, useFocusManager, useStdin, useStdout, useStderr, useTerminalSize.

Tab / Shift+Tab / Escape are handled automatically when any component uses useFocus. useFocus returns { isFocused, focus } and manages raw mode. useFocusManager exposes activeId in addition to focusNext / focusPrevious / focus / enableFocus / disableFocus.

Waiting on exit / handling errors

// Fire-and-forget (most common):
createApp(App).mount();

// Wait for the app to exit:
const app = createApp(App);
app.mount();
await app.waitUntilExit();

// Catch errors thrown from setup / render / useExit(err):
const app = createApp(App);
app.mount();
app.waitUntilExit().catch((err) => {
  console.error(err);
  process.exitCode = 1;
});

See docs/superpowers/specs/2026-05-18-vue-tui-core-design.md for the full design.