Port missing render lifecycle tests from Ink's test/render.tsx:
- onRender fires on input-triggered state update
- throttle renders to maxFps (leading+trailing pattern)
- immediate scheduler in debug mode commits every mutation
- screen reader mode bypasses throttle (immediate commits)
- exit(error) followed by exit(value) still rejects
- exit(value) resolves even when called rapidly twice
- unmount does not write to ended stdout stream
- non-interactive mode writes only last frame at unmount
- non-interactive mode does not emit erase or cursor sequences
- non-interactive unmount does not crash on ended stdout
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- stdin.ref() on raw mode enable, stdin.unref() on disable
- setEncoding("utf8") on raw mode enable
- Raw mode default changed from true to false (hooks acquire it)
- useStdin().setRawMode routed through ref-counted controller
- Stdin listeners attached only when raw mode is active
- EventEmitter maxListeners set to Infinity
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add four behavioral surfaces to the runtime:
- patchConsole (default: true): intercepts console.* methods to route
output through writeToStdout/writeToStderr, preventing frame corruption.
Disabled in debug mode. Restored before Vue cleanup on teardown.
- waitUntilRenderFlush(): flushes pending throttled renders and waits
for the stdout write barrier, exposed on TuiApp and testing RenderResult.
- onRender callback: fires after each commit with { renderTime } in ms.
- maxFps option: overrides the scheduler's default 32ms throttle interval
with 1000/maxFps. The scheduler now accepts a throttleMs option and
exposes hasPending() for flush coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace data event with readable + input-parser state machine
- Intercept \x03 in input pipeline (not process SIGINT)
- useInput now uses parse-keypress for semantic key mapping
- 20ms pending escape flush timer matching Ink
- Esc focus reset in input pipeline
- Delete old key-parser.ts (replaced by input-parser + parse-keypress)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrap user root component in an InternalErrorBoundary that catches all
descendant errors (setup, render, lifecycle) via onErrorCaptured and
routes them through exit(error). This prevents yoga WASM state corruption
that occurred when errors propagated uncaught during Vue's render phase.
- Add ErrorOverview component for rendering error frames
- Replace try-catch around mount with onErrorCaptured boundary
- Update testing render() to detect early exit errors
- Convert 3 todo tests to active tests (nested setup, unhandledRejection, post-mount)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Change Box-in-Text validation from dev-only warn+skip to unconditional
throw, matching Ink's reconciler behavior. Add text-outside-Text
validation that throws when non-empty text-leaf nodes are inserted into
box/root/static containers outside a Text context.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Detect invalid <Box> inside <Text> nesting at the DOM insert level and
emit a dev warning instead of crashing the WASM yoga engine. The box
insertion is skipped to prevent layout corruption.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vue's errorHandler (installed after mount) already catches render-time
errors from nested components and routes them through exit(err),
providing error boundary behavior matching Ink's design.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Write ESC[?25l to stdout after mount and ESC[?25h during teardown,
matching Ink's cursor management. Only active in production mode
(debug: false) to avoid interfering with test streams.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Change exit(error?: Error) to exit(errorOrResult?: unknown). When an
Error is passed, waitUntilExit rejects; otherwise it resolves with the
value. This matches Ink's behavior where exit("hello") resolves the
promise with "hello".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AGENTS.md requires shallowRef by default. All test state uses
reassignment (not mutation), so shallowRef is correct.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Distributes across text, static, transform, newline-spacer,
error-handling, and prop-reset test files.
Tests requiring features not yet in vue-tui are marked test.todo:
- <Transform> inside <Text> (paint pass limitation)
- multi-line transform (transform nodes not yoga carriers)
- text/box nesting validation (no runtime validation yet)
- complex Static rerender patterns (Ink-specific API)
Also fixes wrap="hard" to use wordWrap:false so text breaks at exact
character boundaries rather than word boundaries, matching Ink behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
21 test files covering components, composables, focus, lifecycle,
and scheduler through the public render() API.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>