docs: write/optimize READMEs for all published packages

- @vue-tui/runtime: rewrite with pitch, badges, SFC+shallowRef example,
  accurate component/composable tables, app lifecycle examples
- @vue-tui/cli: new README documenting vue-tui dev, HMR, crash recovery
- @vue-tui/testing: rewrite with @testing-library positioning, correct
  async render API, cleanup globals:true requirement
- Add early-stage warning to all sub-packages (matching root README)
- Fix CLI package.json: add license field, correct description

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yunfei He
2026-05-27 16:05:05 +08:00
parent 098a54080e
commit 8209f8440b
5 changed files with 221 additions and 111 deletions
+85 -86
View File
@@ -1,99 +1,95 @@
# @vue-tui/runtime
Vue-idiomatic terminal renderer in the spirit of [React Ink](https://github.com/vadimdemedes/ink). Platform-specific runtime parallel to `@vue/runtime-dom`.
> **Early stage** — under active development. Bug reports welcome, but not recommended for production use yet.
Vue 3 terminal renderer with Yoga flexbox layout — build rich TUI apps with the same component model you use on the web.
[![npm version](https://img.shields.io/npm/v/@vue-tui/runtime?color=%2342b883)](https://www.npmjs.com/package/@vue-tui/runtime)
[![npm downloads](https://img.shields.io/npm/dm/@vue-tui/runtime)](https://www.npmjs.com/package/@vue-tui/runtime)
## Why
- **Vue SFC & JSX** — `<template>`, TSX, or render functions — your choice
- **Yoga flexbox** — the same layout engine behind React Native, not a CSS-subset hack
- **Built-in input system** — keyboard handling, focus management, Tab navigation
- **Terminal-native** — renders directly to stdout, purpose-built for CLI tools and AI agent interfaces
`@vue-tui/runtime` is a terminal platform renderer parallel to `@vue/runtime-dom`, comparable to [React Ink](https://github.com/vadimdemedes/ink) but adapted for Vue's reactivity model.
## Install
```bash
pnpm add @vue-tui/runtime vue
npm install @vue-tui/runtime vue
```
## Quickstart
## Quick Start
```ts
import { defineComponent, h, ref } from "vue";
import { createApp, Box, Text, useInput } from "@vue-tui/runtime";
// src/main.ts
import { createApp } from "@vue-tui/runtime";
import App from "./App.vue";
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();
createApp(App).mount();
```
## API
```vue
<!-- src/App.vue -->
<script setup lang="ts">
import { shallowRef } from "vue";
import { Box, Text, useInput } from "@vue-tui/runtime";
Single entry point — `createApp(root, rootProps?)`. Returns a `TuiApp` (`Omit<VueApp<TuiNode>, "mount">` plus four TUI methods).
const count = shallowRef(0);
useInput((input) => {
if (input === "+") count.value++;
if (input === "-") count.value--;
});
</script>
<template>
<Box>
<Text>Count: </Text>
<Text bold color="green">{{ count }}</Text>
<Text dimColor> (+/- to change)</Text>
</Box>
</template>
```
## Components
| Component | Description |
| ------------- | ---------------------------------------------------------------------------------------------- |
| `<Box>` | Flexbox container — direction, wrap, align, justify, gap, padding, margin, borders, background |
| `<Text>` | Styled text — color, bold, italic, underline, strikethrough, dimColor, wrap/truncate modes |
| `<Spacer>` | Expands to fill available space (`flex-grow: 1`) |
| `<Newline>` | Inserts line breaks (configurable `count`) |
| `<Static>` | Renders a list of items once, above the redrawn region |
| `<Transform>` | Applies a string transform function to each rendered line |
## Composables
| Composable | Description |
| -------------------------- | --------------------------------------------------------------------- |
| `useInput(handler, opts?)` | Keyboard input — `(input, key)` with modifier and arrow key detection |
| `useFocus(opts?)` | Component-level focus — returns `{ isFocused, focus }` |
| `useFocusManager()` | App-level focus — `focusNext()`, `focusPrevious()`, `focus(id)` |
| `useExit()` | Programmatic exit — returns `exit(error?)` |
| `useTerminalSize()` | Reactive terminal dimensions — `{ columns, rows }` |
| `useAnimation(opts?)` | Frame-based animation loop — returns `{ frame, time, delta, reset }` |
| `useBoxMetrics(ref)` | Reactive layout metrics — `{ width, height, left, top, hasMeasured }` |
| `measureElement(node)` | Imperative read of computed `{ width, height }` from a yoga node |
| `useCursor()` | Control terminal cursor visibility |
| `usePaste(handler, opts?)` | Handle clipboard paste events |
| `useStdin()` | Access stdin stream and raw mode control |
| `useStdout()` | Write directly to stdout |
| `useStderr()` | Write directly to stderr |
## App Lifecycle
```ts
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
```ts
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
rawMode?: boolean; // default: true when interactive
interactive?: boolean; // default: true (false if in CI or !stdout.isTTY)
patchConsole?: boolean; // default: true (disabled in debug mode)
maxFps?: number; // default: ~30fps (32ms)
onRender?: (info: { renderTime: number }) => void;
isScreenReaderEnabled?: boolean; // default: false (true when INK_SCREEN_READER=true)
}
```
All fields optional with per-field fallback.
### Components
`Box`, `Text`, `Newline`, `Spacer`, `Static`, `Transform`.
### Composables
`useExit`, `useInput`, `useFocus`, `useFocusManager`, `useStdin`, `useStdout`, `useStderr`, `useTerminalSize` / `useWindowSize`, `useCursor`, `useAnimation`, `useBoxMetrics` / `measureElement`, `usePaste`, `useIsScreenReaderEnabled`.
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
```ts
// Fire-and-forget (most common):
// Fire and forget (most common):
createApp(App).mount();
// Wait for the app to exit:
@@ -101,13 +97,16 @@ 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;
});
// Custom streams (for testing):
createApp(App).mount({ stdout, stdin, stderr });
```
See `docs/superpowers/specs/2026-05-18-vue-tui-core-design.md` for the full design.
## Links
- [vue-tui](https://github.com/vuejs-ai/vue-tui) — monorepo root
- [`@vue-tui/cli`](https://www.npmjs.com/package/@vue-tui/cli) — dev server with HMR
- [`@vue-tui/testing`](https://www.npmjs.com/package/@vue-tui/testing) — test harness for terminal components
## License
MIT