docs(runtime): finish the useWindowSize rename in docs; tidy contract guards (#164)
Follow-up cleanup for the 7 confirmed findings from a review of #163. The dominant theme: #163 hard-renamed the public composable useTerminalSize -> useWindowSize (no alias) but left stale references to the dead name in user-facing docs. - README.md + packages/runtime/README.md: the composable tables named the removed `useTerminalSize()` (root README even framed the sole real export `useWindowSize` as an "Ink-compat alias" — now inverted). Point both at `useWindowSize()`. - .agents/docs/ink-divergences.md: two vue-tui-side references to `useTerminalSize` (the shallowRef "object of refs" example and the "composables throw outside a render tree" list) -> `useWindowSize`. The Ink-side `useWindowSize -> WindowSize` naming example is left unchanged. - .agents/docs/accessibility-api.md: the intro cited three "blessed entries" but only aria-camelCase is one; `renderToString` layout-only and the `useWindowSize` name are now Ink parity, not divergences. Reword. - .agents/docs/api-contract.md: tighten the `/internal` wording — the test does assert one tripwire on `/internal`, so "not covered by public-api.test.ts" was imprecise. - public-api.test.ts / render-to-string.test.tsx: the public renderToString dropped the `isScreenReaderEnabled` option but (unlike the sibling `ScreenReaderOptions` type) had no compile-time guard. Replace an obscure, fmt-fragile type-indexing guard with a readable call-site `@ts-expect-error` in render-to-string.test.tsx; re-adding the option to the public RenderToStringOptions makes the directive unused and fails `tsc --noEmit`. - Rename terminal-size.test.tsx / .sequential.test.tsx -> window-size.test.tsx / .sequential.test.tsx to match the migrated symbol. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
# Accessibility (ARIA + screen-reader) API
|
# Accessibility (ARIA + screen-reader) API
|
||||||
|
|
||||||
How vue-tui exposes ARIA and renders screen-reader (SR) output, and why. Companion to the terse
|
How vue-tui exposes ARIA and renders screen-reader (SR) output, and why. Companion to the
|
||||||
blessed entries in [[ink-divergences]] (aria props are camelCase; `renderToString` is layout-only;
|
aria-camelCase blessed entry in [[ink-divergences]]; `renderToString` being layout-only and the
|
||||||
`useWindowSize`); this file keeps the _why_ and the researched / run-verified findings the ledger
|
`useWindowSize` name are now Ink parity (not divergences), and the reactive-refs return shape is
|
||||||
entries deliberately omit, so they are not re-derived expensively. The exported aria types are
|
covered by the shallowRef entry there. This file keeps the _why_ and the researched / run-verified
|
||||||
part of the public contract — see [[api-contract]].
|
findings those entries deliberately omit, so they are not re-derived expensively. The exported aria
|
||||||
|
types are part of the public contract — see [[api-contract]].
|
||||||
|
|
||||||
## The constraint that shapes everything
|
## The constraint that shapes everything
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,9 @@ Because it is contract, it is **tested, not merely shipped**:
|
|||||||
|
|
||||||
`@vue-tui/runtime/internal` is an explicitly internal / advanced surface — host-node types
|
`@vue-tui/runtime/internal` is an explicitly internal / advanced surface — host-node types
|
||||||
(`TuiNode`, …), test-only helpers (`renderToStringWithScreenReader`), dev/HMR types (`DevState`,
|
(`TuiNode`, …), test-only helpers (`renderToStringWithScreenReader`), dev/HMR types (`DevState`,
|
||||||
`DevErrorInfo`), kitty-controller internals, etc. It carries **no stability guarantee**, is not
|
`DevErrorInfo`), kitty-controller internals, etc. It carries **no stability guarantee**: its
|
||||||
covered by `public-api.test.ts`, and may change freely between releases.
|
surface is not snapshotted by `public-api.test.ts` (a member may be tripwired there to prove it is
|
||||||
|
internal, but the surface itself carries no contract), and may change freely between releases.
|
||||||
|
|
||||||
Placement rule for any export:
|
Placement rule for any export:
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ current-props model, or API conventions.
|
|||||||
snapshot: it would freeze at setup time. vue-tui returns a **`shallowRef`** whose `.value`
|
snapshot: it would freeze at setup time. vue-tui returns a **`shallowRef`** whose `.value`
|
||||||
updates and re-renders the template; read these as `.value`. Every stateful composable
|
updates and re-renders the template; read these as `.value`. Every stateful composable
|
||||||
follows this — `useFocusManager().activeId` is a single ref read as `.value`, while a
|
follows this — `useFocusManager().activeId` is a single ref read as `.value`, while a
|
||||||
composable may instead return an **object of refs** (`useTerminalSize()` returns
|
composable may instead return an **object of refs** (`useWindowSize()` returns
|
||||||
`{ columns, rows }`, read as `.columns.value` / `.rows.value`). An empty single-ref state
|
`{ columns, rows }`, read as `.columns.value` / `.rows.value`). An empty single-ref state
|
||||||
holds `null` (Vue's convention for an empty ref: a template ref is `ref<T | null>(null)`),
|
holds `null` (Vue's convention for an empty ref: a template ref is `ref<T | null>(null)`),
|
||||||
where Ink's plain value is `undefined`.
|
where Ink's plain value is `undefined`.
|
||||||
@@ -611,7 +611,7 @@ different runtime behavior, ownership rule, or out-of-contract handling.
|
|||||||
|
|
||||||
- **Ink:** the hooks read a React context whose **default** value is a no-op object, so
|
- **Ink:** the hooks read a React context whose **default** value is a no-op object, so
|
||||||
calling e.g. `useStdin()` outside an Ink tree returns inert defaults without an error.
|
calling e.g. `useStdin()` outside an Ink tree returns inert defaults without an error.
|
||||||
- **vue-tui:** `useApp`, `useStdout`, `useStderr`, `useStdin`, `useTerminalSize`,
|
- **vue-tui:** `useApp`, `useStdout`, `useStderr`, `useStdin`, `useWindowSize`,
|
||||||
`useFocus`, `useFocusManager`, `useInput`, `usePaste`, `useCursor`, and
|
`useFocus`, `useFocusManager`, `useInput`, `usePaste`, `useCursor`, and
|
||||||
`useIsScreenReaderEnabled` **throw** when their context is absent ("... must be called
|
`useIsScreenReaderEnabled` **throw** when their context is absent ("... must be called
|
||||||
inside a vue-tui render tree"). `useBoxMetrics` and `useAnimation` do **not** throw:
|
inside a vue-tui render tree"). `useBoxMetrics` and `useAnimation` do **not** throw:
|
||||||
|
|||||||
@@ -112,13 +112,13 @@ useInput((input) => {
|
|||||||
## Composables (Hooks)
|
## Composables (Hooks)
|
||||||
|
|
||||||
| Composable | Description |
|
| Composable | Description |
|
||||||
| -------------------------- | -------------------------------------------------------------------------------------- |
|
| -------------------------- | ------------------------------------------------------------------------------------- |
|
||||||
| `useInput(handler, opts?)` | Handle keyboard input — receives `(input, key)` with modifier and arrow key detection |
|
| `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 |
|
| `usePaste(handler, opts?)` | Handle bracketed paste — receives the pasted `text` as a single event |
|
||||||
| `useFocus(opts?)` | Component-level focus — returns `{ isFocused, focus }` |
|
| `useFocus(opts?)` | Component-level focus — returns `{ isFocused, focus }` |
|
||||||
| `useFocusManager()` | App-level focus control — `focusNext()`, `focusPrevious()`, `focus(id)` |
|
| `useFocusManager()` | App-level focus control — `focusNext()`, `focusPrevious()`, `focus(id)` |
|
||||||
| `useApp()` | App lifecycle — `{ exit(error?), waitUntilRenderFlush() }` |
|
| `useApp()` | App lifecycle — `{ exit(error?), waitUntilRenderFlush() }` |
|
||||||
| `useTerminalSize()` | Reactive terminal dimensions — `{ columns, rows }` (Ink-compat alias: `useWindowSize`) |
|
| `useWindowSize()` | Reactive terminal dimensions — `{ columns, rows }` |
|
||||||
| `useStdin()` | Access stdin stream and raw mode control |
|
| `useStdin()` | Access stdin stream and raw mode control |
|
||||||
| `useStdout()` | Write directly to stdout |
|
| `useStdout()` | Write directly to stdout |
|
||||||
| `useStderr()` | Write directly to stderr |
|
| `useStderr()` | Write directly to stderr |
|
||||||
|
|||||||
@@ -71,3 +71,6 @@ test("does not expose the screen-reader linearizer publicly (Ink keeps it intern
|
|||||||
// It DOES type-check from `/internal`, which the runtime guard above already proves is the home.
|
// It DOES type-check from `/internal`, which the runtime guard above already proves is the home.
|
||||||
// @ts-expect-error - ScreenReaderOptions is exported only from @vue-tui/runtime/internal
|
// @ts-expect-error - ScreenReaderOptions is exported only from @vue-tui/runtime/internal
|
||||||
export type _ScreenReaderOptionsIsInternalOnly = import("@vue-tui/runtime").ScreenReaderOptions;
|
export type _ScreenReaderOptionsIsInternalOnly = import("@vue-tui/runtime").ScreenReaderOptions;
|
||||||
|
|
||||||
|
// The parallel guard for the public `renderToString`'s dropped `isScreenReaderEnabled` OPTION lives
|
||||||
|
// in render-to-string.test.tsx (a call-site `@ts-expect-error`), next to the renderToString tests.
|
||||||
|
|||||||
@@ -42,6 +42,18 @@ describe("renderToString", () => {
|
|||||||
expect(output).toBe("test");
|
expect(output).toBe("test");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Contract guard: `isScreenReaderEnabled` is INTERNAL-only — the public `renderToString` must
|
||||||
|
// reject it at the type level (SR rendering goes through `renderToStringWithScreenReader` in
|
||||||
|
// `@vue-tui/runtime/internal`). If the option is ever re-added to the public `RenderToStringOptions`,
|
||||||
|
// the `@ts-expect-error` below goes unused and `tsc --noEmit` fails. (At runtime the unknown option
|
||||||
|
// is harmlessly ignored — only `columns` is read — so the frame still renders.)
|
||||||
|
test("public renderToString rejects the internal isScreenReaderEnabled option (type-level)", () => {
|
||||||
|
const App = defineComponent(() => () => <Text>x</Text>);
|
||||||
|
// @ts-expect-error - isScreenReaderEnabled is internal-only (use renderToStringWithScreenReader from /internal)
|
||||||
|
const output = renderToString(App, { isScreenReaderEnabled: true });
|
||||||
|
expect(output).toBe("x");
|
||||||
|
});
|
||||||
|
|
||||||
test("rethrows component errors after cleanup", () => {
|
test("rethrows component errors after cleanup", () => {
|
||||||
const App = defineComponent(() => {
|
const App = defineComponent(() => {
|
||||||
throw new Error("boom");
|
throw new Error("boom");
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ useInput((input) => {
|
|||||||
| `useFocus(opts?)` | Component-level focus — returns `{ isFocused, focus }` |
|
| `useFocus(opts?)` | Component-level focus — returns `{ isFocused, focus }` |
|
||||||
| `useFocusManager()` | App-level focus — `focusNext()`, `focusPrevious()`, `focus(id)` |
|
| `useFocusManager()` | App-level focus — `focusNext()`, `focusPrevious()`, `focus(id)` |
|
||||||
| `useApp()` | App lifecycle — `{ exit(error?), waitUntilRenderFlush() }` |
|
| `useApp()` | App lifecycle — `{ exit(error?), waitUntilRenderFlush() }` |
|
||||||
| `useTerminalSize()` | Reactive terminal dimensions — `{ columns, rows }` |
|
| `useWindowSize()` | Reactive terminal dimensions — `{ columns, rows }` |
|
||||||
| `useAnimation(opts?)` | Frame-based animation loop — returns `{ frame, time, delta, reset }` |
|
| `useAnimation(opts?)` | Frame-based animation loop — returns `{ frame, time, delta, reset }` |
|
||||||
| `useBoxMetrics(ref)` | Reactive layout metrics — `{ width, height, left, top, hasMeasured }` |
|
| `useBoxMetrics(ref)` | Reactive layout metrics — `{ width, height, left, top, hasMeasured }` |
|
||||||
| `measureElement(node)` | Imperative read of computed `{ width, height }` from a yoga node |
|
| `measureElement(node)` | Imperative read of computed `{ width, height }` from a yoga node |
|
||||||
|
|||||||
Reference in New Issue
Block a user