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:
@@ -84,7 +84,7 @@ useInput((input) => {
|
|||||||
## Packages
|
## Packages
|
||||||
|
|
||||||
| Package | Description |
|
| Package | Description |
|
||||||
| ------- | ----------- |
|
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| [`@vue-tui/runtime`](https://www.npmjs.com/package/@vue-tui/runtime) | The core framework — Vue 3 renderer for the terminal with components (`Box`, `Text`, `Static`, etc.), composables (`useInput`, `useFocus`, `useExit`, etc.), and yoga-based flexbox layout |
|
| [`@vue-tui/runtime`](https://www.npmjs.com/package/@vue-tui/runtime) | The core framework — Vue 3 renderer for the terminal with components (`Box`, `Text`, `Static`, etc.), composables (`useInput`, `useFocus`, `useExit`, etc.), and yoga-based flexbox layout |
|
||||||
| [`@vue-tui/cli`](https://www.npmjs.com/package/@vue-tui/cli) | Development tool — `vue-tui dev` starts your app with Vite-powered HMR |
|
| [`@vue-tui/cli`](https://www.npmjs.com/package/@vue-tui/cli) | Development tool — `vue-tui dev` starts your app with Vite-powered HMR |
|
||||||
| [`@vue-tui/testing`](https://www.npmjs.com/package/@vue-tui/testing) | Test harness — render in an isolated fake terminal, simulate input, assert output frame by frame |
|
| [`@vue-tui/testing`](https://www.npmjs.com/package/@vue-tui/testing) | Test harness — render in an isolated fake terminal, simulate input, assert output frame by frame |
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# @vue-tui/cli
|
||||||
|
|
||||||
|
> **Early stage** — under active development. Bug reports welcome, but not recommended for production use yet.
|
||||||
|
|
||||||
|
Development server for vue-tui — Vite-powered HMR for Vue 3 terminal apps.
|
||||||
|
|
||||||
|
[](https://www.npmjs.com/package/@vue-tui/cli)
|
||||||
|
[](https://www.npmjs.com/package/@vue-tui/cli)
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
- **Terminal HMR** — edit a `.vue` file, see the terminal update instantly
|
||||||
|
- **Works with your Vite config** — just run `vue-tui dev` in a project with `index.html` and your existing Vite plugins
|
||||||
|
- **Crash recovery** — auto-restarts the process after a crash
|
||||||
|
|
||||||
|
Built on Vite's `bundledDev` mode. Bundles your vue-tui app into a single Node.js process with hot module replacement — the same edit-save-see loop you get with Vite on the web, but for TUI development.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -D @vue-tui/cli
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vue-tui dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Starts a Vite dev server in `bundledDev` mode, builds your app, and runs it in a managed child process. Most file changes are applied via HMR; changes that require a full reload restart the process automatically.
|
||||||
|
|
||||||
|
### package.json script
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vue-tui dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- [vue-tui](https://github.com/vuejs-ai/vue-tui) — monorepo root
|
||||||
|
- [`@vue-tui/runtime`](https://www.npmjs.com/package/@vue-tui/runtime) — the core framework
|
||||||
|
- [`@vue-tui/testing`](https://www.npmjs.com/package/@vue-tui/testing) — test harness for terminal components
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@vue-tui/cli",
|
"name": "@vue-tui/cli",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "Scaffold and develop Vue 3 terminal apps with Yoga flexbox layout.",
|
"description": "Development server for Vue 3 terminal apps with Yoga flexbox layout.",
|
||||||
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"vue-tui": "./dist/index.mjs"
|
"vue-tui": "./dist/index.mjs"
|
||||||
},
|
},
|
||||||
|
|||||||
+85
-86
@@ -1,99 +1,95 @@
|
|||||||
# @vue-tui/runtime
|
# @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.
|
||||||
|
|
||||||
|
[](https://www.npmjs.com/package/@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
|
## Install
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm add @vue-tui/runtime vue
|
npm install @vue-tui/runtime vue
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quickstart
|
## Quick Start
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { defineComponent, h, ref } from "vue";
|
// src/main.ts
|
||||||
import { createApp, Box, Text, useInput } from "@vue-tui/runtime";
|
import { createApp } from "@vue-tui/runtime";
|
||||||
|
import App from "./App.vue";
|
||||||
|
|
||||||
const Counter = defineComponent({
|
createApp(App).mount();
|
||||||
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
|
```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
|
```ts
|
||||||
import { createApp } from "@vue-tui/runtime";
|
import { createApp } from "@vue-tui/runtime";
|
||||||
|
|
||||||
const app = createApp(App, { initialState }).use(myPlugin).provide(themeKey, dark);
|
// Fire and forget (most common):
|
||||||
|
|
||||||
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):
|
|
||||||
createApp(App).mount();
|
createApp(App).mount();
|
||||||
|
|
||||||
// Wait for the app to exit:
|
// Wait for the app to exit:
|
||||||
@@ -101,13 +97,16 @@ const app = createApp(App);
|
|||||||
app.mount();
|
app.mount();
|
||||||
await app.waitUntilExit();
|
await app.waitUntilExit();
|
||||||
|
|
||||||
// Catch errors thrown from setup / render / useExit(err):
|
// Custom streams (for testing):
|
||||||
const app = createApp(App);
|
createApp(App).mount({ stdout, stdin, stderr });
|
||||||
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.
|
## 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
|
||||||
|
|||||||
+80
-20
@@ -1,35 +1,95 @@
|
|||||||
# @vue-tui/testing
|
# @vue-tui/testing
|
||||||
|
|
||||||
Test harness for [`@vue-tui/runtime`](../runtime).
|
> **Early stage** — under active development. Bug reports welcome, but not recommended for production use yet.
|
||||||
|
|
||||||
|
Test harness for vue-tui — render Vue 3 terminal components, simulate input, assert frames. Like `@testing-library`, but for the terminal.
|
||||||
|
|
||||||
|
[](https://www.npmjs.com/package/@vue-tui/testing)
|
||||||
|
[](https://www.npmjs.com/package/@vue-tui/testing)
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
- **Isolated terminal** — renders into a fake TTY, no real terminal needed
|
||||||
|
- **Input simulation** — inject keystrokes that reach `useInput` handlers
|
||||||
|
- **Frame snapshots** — assert exact visual output with `lastFrame()` and `frames[]`
|
||||||
|
- **Auto-cleanup** — unmounts all rendered apps after each test (requires Vitest `globals: true`)
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
|
Assumes `@vue-tui/runtime` and `vue` are already installed in your project.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm add -D @vue-tui/testing vue
|
npm install -D @vue-tui/testing
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quickstart
|
## Quick Start
|
||||||
|
|
||||||
```ts
|
```tsx
|
||||||
import { defineComponent, h } from "vue";
|
import { defineComponent, shallowRef } from "vue";
|
||||||
import { test, expect } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
import { render, flush } from "@vue-tui/testing";
|
import { render } from "@vue-tui/testing";
|
||||||
import { Text } from "@vue-tui/runtime";
|
import { Box, Text, useInput } from "@vue-tui/runtime";
|
||||||
|
|
||||||
test("renders hello", async () => {
|
test("counter responds to + and - keys", async () => {
|
||||||
const App = defineComponent({ render: () => h(Text, null, "hello") });
|
const Counter = defineComponent(() => {
|
||||||
const r = render(App);
|
const count = shallowRef(0);
|
||||||
await flush();
|
useInput((input) => {
|
||||||
expect(r.lastFrame()).toContain("hello");
|
if (input === "+") count.value++;
|
||||||
r.unmount();
|
if (input === "-") count.value--;
|
||||||
|
});
|
||||||
|
return () => (
|
||||||
|
<Box>
|
||||||
|
<Text>Count: {count.value}</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const { lastFrame, stdin } = await render(Counter);
|
||||||
|
expect(lastFrame()).toContain("Count: 0");
|
||||||
|
|
||||||
|
await stdin.write("+");
|
||||||
|
expect(lastFrame()).toContain("Count: 1");
|
||||||
|
|
||||||
|
await stdin.write("-");
|
||||||
|
expect(lastFrame()).toContain("Count: 0");
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
- **`render(app, { columns?, rows? })`** — mount in a fake-TTY environment, collect frames.
|
### `render(component, options?)`
|
||||||
- **`flush()`** — await Vue's post-flush queue + Node's immediate queue.
|
|
||||||
- **`result.lastFrame()` / `result.frames`** — frame snapshots.
|
Mounts a component in a fake terminal environment. Returns a `RenderResult`.
|
||||||
- **`result.stdin.write(data)`** — inject input that reaches `useInput` handlers.
|
|
||||||
- **`result.app`** — the underlying [`TuiApp`](../runtime/README.md#tuiapp-interface) (use `.waitUntilExit()` if needed).
|
| Option | Type | Default | Description |
|
||||||
- **`result.unmount()` / `result.waitUntilExit()`** — convenience pass-throughs.
|
| ------------- | --------- | ------- | ---------------------------------- |
|
||||||
|
| `columns` | `number` | `100` | Terminal width in columns |
|
||||||
|
| `rows` | `number` | `100` | Terminal height in rows |
|
||||||
|
| `props` | `object` | — | Props passed to the root component |
|
||||||
|
| `exitOnCtrlC` | `boolean` | `false` | Enable Ctrl+C exit handling |
|
||||||
|
|
||||||
|
### `RenderResult`
|
||||||
|
|
||||||
|
| Property / Method | Description |
|
||||||
|
| ------------------------ | -------------------------------------------------------- |
|
||||||
|
| `lastFrame(opts?)` | Latest rendered frame as a string |
|
||||||
|
| `frames` | Array of all captured frame snapshots |
|
||||||
|
| `stdin.write(data)` | Inject input (reaches `useInput` handlers) |
|
||||||
|
| `terminal` | Fake terminal — `columns`, `rows`, `resize()`, `rawMode` |
|
||||||
|
| `unmount()` | Tear down the app |
|
||||||
|
| `waitUntilExit()` | Settles when the app exits (rejects if `exit(error)`) |
|
||||||
|
| `waitUntilRenderFlush()` | Resolves after the next render cycle completes |
|
||||||
|
|
||||||
|
### `cleanup()`
|
||||||
|
|
||||||
|
Unmounts all rendered apps. Auto-registered as a Vitest `afterEach` hook when `globals: true` is set. Call manually if your test runner doesn't expose a global `afterEach`.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- [vue-tui](https://github.com/vuejs-ai/vue-tui) — monorepo root
|
||||||
|
- [`@vue-tui/runtime`](https://www.npmjs.com/package/@vue-tui/runtime) — the core framework
|
||||||
|
- [`@vue-tui/cli`](https://www.npmjs.com/package/@vue-tui/cli) — dev server with HMR
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|||||||
Reference in New Issue
Block a user