feat(runtime)!: replace useExit() with Ink-aligned useAppContext() (#69)

Ink's `useApp()` returns `{ exit, waitUntilRenderFlush }`. vue-tui previously
exposed only `exit()` via `useExit()` and kept `waitUntilRenderFlush` on the
`TuiApp` handle alone. Align the public surface with Ink: add `useAppContext()`
returning the same pair, and remove `useExit()`.

- thread `waitUntilRenderFlush` into the injected `AppContext` via a hoisted
  impl shared by the `TuiApp` handle and the composable, so both resolve
  identically
- add `useAppContext()`; delete `useExit()`; migrate all call sites, PTY
  fixtures, examples, READMEs and the public-API surface test
- port Ink's two "useApp waitUntilRenderFlush" tests; Ink's third relies on
  React concurrent mode (N/A in Vue)
- rewrite the ink-divergences entry: this is now a *naming* divergence
  (`useAppContext` vs `useApp`, mirroring `createApp` vs `render`), not a
  surface one — and fix the prior wrong claim that Ink's `useApp` returns
  stdin/stdout/stderr

The name is qualified (`useAppContext`, not `useApp`) so it doesn't read as the
Vue application instance (`createApp`/`app.mount`) — the same Vue-native naming
choice vue-tui already makes with `createApp()` vs Ink's `render()`.

BREAKING CHANGE: `useExit()` is removed. Replace `const exit = useExit()` with
`const { exit } = useAppContext()`.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Yunfei He
2026-05-30 19:57:52 +08:00
committed by GitHub
parent 6edc51b925
commit 9d1e4d7805
38 changed files with 226 additions and 122 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { shallowRef } from "vue";
import { Box, Text, Static, useInput, useExit } from "@vue-tui/runtime";
import { Box, Text, Static, useInput, useAppContext } from "@vue-tui/runtime";
import { runAgentLoop, type Message, type ToolCall } from "./agent";
import MessageList from "./components/MessageList.vue";
@@ -14,7 +14,7 @@ const pendingCommand = shallowRef("");
const messages: Message[] = [];
let approvalResolve: ((approved: boolean) => void) | null = null;
const exit = useExit();
const { exit } = useAppContext();
const autoApprove = process.argv.includes("--yolo");
+2 -2
View File
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, onScopeDispose, reactive } from "vue";
import chalk from "chalk";
import { Box, Text, useExit, useInput } from "@vue-tui/runtime";
import { Box, Text, useAppContext, useInput } from "@vue-tui/runtime";
// --- world dimensions ----------------------------------------------------
@@ -130,7 +130,7 @@ function renderFrame(w: World): string[] {
// --- component -----------------------------------------------------------
const exit = useExit();
const { exit } = useAppContext();
const world = reactive<World>(makeWorld(0));
function flap(): void {