Yunfei He 871f348f2f fix(runtime): flush the deferred trailing commit on non-interactive teardown (Ink parity) (#192)
In non-interactive non-debug mode commits are throttled (renderThrottleMs =
ceil(1000/30) = 34ms). teardown() cancel()s the scheduler, which DISCARDS any
pending trailing-edge commit, and the final-commit gate excluded non-interactive
non-debug — so the non-interactive trailing write emitted frameState.lastOutput
(the last commit that actually ran), a STALE frame. A reactive change deferred to
the trailing edge whose app unmounts within the throttle window was lost on
piped/CI output.

Mirror Ink's settleThrottle: broaden the final-commit gate to run mountedCommit()
in every mode before the trailing write. The non-interactive commit() branch only
refreshes frameState.lastOutput/lastOutputToRender to the current tree and writes
write-once <Static> (it DEFERS the dynamic frame), so the refresh feeds the latest
frame into the trailing write without double-writing it. Verified against real Ink
v7.0.4: the same deferred-then-unmount scenario emits "C\n" (latest); vue-tui now
matches (was "A\n").

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 02:18:07 +08:00

vue-tui

Status — the runtime API is stabilizing; the CLI and dev toolkit are still experimental and may change. Bug reports welcome.

The Vue framework for terminal UIs. Build with components, develop with HMR, test with confidence.

@vue-tui/runtime · @vue-tui/cli · @vue-tui/testing

  • Vue SFC & JSX — write terminal interfaces with <template>, TSX, or both
  • Flexbox layout — powered by Yoga, the same engine behind React Native
  • Dev toolkit (experimental)HMR in the terminal via vue-tui dev
  • Input & focus — keyboard handling, focus management, Tab navigation, Kitty keyboard protocol
  • Testing harness — out-of-the-box component-level terminal testing — render, simulate input, assert frames

Flappy Bird — one of the examples included in the repo

Flappy Bird built with vue-tui

Quick Start

npx tiged vuejs-ai/vue-tui-starter my-app
cd my-app
npm install
npm run dev

Edit app.vue and watch the terminal update instantly.

Add to an existing project

npm install @vue-tui/runtime

Example

// src/main.ts
import { createApp } from "@vue-tui/runtime";
import App from "./app.vue";

createApp(App).mount();
<!-- src/app.vue -->
<script setup lang="ts">
import { shallowRef } from "vue";
import { Box, Text, useInput } from "@vue-tui/runtime";

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>

For non-interactive output — snapshots, CI logs, piped commands — renderToString(App) renders a single frame to a string instead of mounting.

Table of Contents

Packages

Package Description
@vue-tui/runtime The core framework — Vue 3 renderer for the terminal with components (Box, Text, Static, etc.), composables (useInput, useFocus, useApp, etc.), and yoga-based flexbox layout. API stabilizing.
@vue-tui/cli Development tool — vue-tui dev starts your app with Vite-powered HMR. Experimental; may change.
@vue-tui/testing Test harness — render in an isolated fake terminal, simulate input, assert output frame by frame

Examples

Example Description
basic-template Vue SFC with <template> syntax
basic-jsx Same app in TSX
coding-agent AI coding agent with LLM streaming and interactive UI
flappy-bird Physics-based terminal game with reactive state and borders

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 (Hooks)

Composable Description
useInput(handler, opts?) Handle keyboard input — receives (input, key) with modifier and arrow key detection
usePaste(handler, opts?) Handle bracketed paste — receives the pasted text as a single event
useFocus(opts?) Component-level focus — returns { isFocused, focus }
useFocusManager() App-level focus control — focusNext(), focusPrevious(), focus(id)
useApp() App lifecycle — { exit(error?), waitUntilRenderFlush() }
useWindowSize() Reactive terminal dimensions — { columns, rows }
useStdin() Access stdin stream and raw mode control
useStdout() Write directly to stdout
useStderr() Write directly to stderr
useBoxMetrics(ref) Measure a <Box> via a template ref — reactive { width, height, left, top, hasMeasured } (or measureElement(el) for a one-off { width, height } read)
useCursor() Control the terminal cursor — setCursorPosition(pos) in output coordinates
useIsScreenReaderEnabled() Whether a screen reader is active — returns a boolean for adapting accessible output
useAnimation(opts?) Frame-based animation driver — reactive { frame, time, delta } + reset()

Testing

The @vue-tui/testing package renders components in an isolated environment and lets you simulate input and assert visual output:

npm install -D @vue-tui/testing
import { defineComponent, shallowRef } from "vue";
import { expect, test } from "vitest";
import { render } from "@vue-tui/testing";
import { Box, Text, useInput } from "@vue-tui/runtime";

test("counter responds to + and - keys", async () => {
  const Counter = defineComponent(() => {
    const count = shallowRef(0);
    useInput((input) => {
      if (input === "+") count.value++;
      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");
});

Development

Requires pnpm and Node.js 22+.

pnpm install          # install dependencies
vp run ready          # lint, typecheck, test, and build (the full check)
vp run -r test        # run tests across all packages
vp run -r build       # build all packages
vue-tui dev           # start an example with HMR

Contributing

Contributions welcome! vue-tui is evolving fast — please open an issue before starting large changes. If you use AI tools, disclose it in your PR and make sure you've reviewed and tested everything before submitting.

Credits

vue-tui is built on the ideas pioneered by Ink — component model, yoga-based layout, focus system, and rendering pipeline — adapted to Vue's philosophy. Thanks to Vadim Demedes, Sindre Sorhus, and the Ink contributors.

License

MIT

S
Description
Vue 3 terminal UI framework — 备份
Readme 3 MiB
Languages
TypeScript 99.2%
Vue 0.7%