Register process.once('beforeExit') in waitUntilExit() so TUI apps
that finish naturally (no explicit exit/unmount) clean up before the
event loop drains. Add stdout write barrier in resolveExit() to ensure
all output is flushed before settling the exit promise.
Update testing render helper to await the additional async tick
introduced by the write barrier so early-error detection still works.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- useWindowSize alias for useTerminalSize (Ink API naming parity)
- terminal-size fallback when stdout.columns/rows are 0/missing
- Public API test covers all new exports
- README documents all MountOptions and composables
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Default: true when INK_SCREEN_READER=true env var set
- Screen reader mode bypasses render throttle
- Composable exposes flag via provide/inject
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drives animations with a frame counter, elapsed time, delta, and reset
function. Supports configurable interval and reactive isActive toggle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- setCursorPosition propagates to app context for frame restoration
- Cursor cleared on component unmount via onScopeDispose
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Standalone renderer path with no terminal bindings. Captures Static
output, provides no-op contexts for hooks, proper WASM cleanup.
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>
- Add style prop to Static (default: position absolute, flexDirection column)
- Apply style props to isolated static paint (padding, flexDirection, etc.)
- Track staticNode/previousStaticNode for identity changes
- Reset fullStaticOutput on Static unmount/remount
- Convert all 6 Static todo tests to active passing tests
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Ref-counted bracketed paste mode (\x1b[?2004h/l)
- usePaste acquires raw mode + paste mode, listens on paste channel
- Paste falls through to useInput when no paste listeners exist
- isActive option for conditional paste listening
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>
Port Ink's parse-keypress.ts to map raw input event strings to semantic
key objects with name, ctrl, shift, meta, and kitty protocol support.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port Ink's stateful input parser that splits raw stdin chunks into
discrete input events. Supports CSI/SS3 sequence parsing, double-ESC
prefix, bracketed paste, backspace byte splitting, and pending state
for incomplete escape sequences.
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>
Port Ink's sanitize-ansi.ts to strip non-SGR/non-OSC ANSI escape
sequences (cursor movement, screen clearing) from text nodes.
Integrated as the final step of renderTextWithInlineStyles() so all
squashed text is sanitized before layout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port Ink's ansi-tokenizer.ts (509 lines) as a standalone ANSI escape
sequence parser that produces typed tokens. Exports tokenizeAnsi() and
hasAnsiControlCharacters(). Supports CSI, ESC, OSC, DCS, PM, APC, SOS,
C1 control characters, string terminator, and invalid/malformed tokens.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Transforms were appended (`[...transformers, node.transform]`) causing outer
transforms to execute before inner ones. Ink prepends so inner transforms run
first and outer transforms wrap the result. Adds a nesting-order regression test.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port Ink's output character model using @alcalzone/ansi-tokenize. Each cell is now a
StyledChar with proper grapheme clustering. Fixes emoji alignment, wide char boundaries,
and changes clipping to top-only (matching Ink). Upgraded ansi-tokenize to ^0.3.0.
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>
Ink uses Yoga.EDGE_START for marginLeft and Yoga.EDGE_END for
marginRight, which is correct for RTL-aware layout. Changed from
EDGE_LEFT/EDGE_RIGHT to match.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match Ink's behavior where Newline renders as ink-text (yoga carrier).
When inside a Text parent, Newline still renders as virtual-text for
inline behavior. When standalone, it renders as a text node so it
participates in yoga layout and occupies vertical space.
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>
Transform nodes now participate in yoga layout with flexShrink=1,
flexDirection='row', matching Ink's Transform which renders as ink-text.
This fixes multi-line text under Transform not getting proper layout
height. Transform nodes inside Text parents remain inline (excluded
from yoga tree) to preserve renderTextWithInlineStyles behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add leading+trailing throttle to the commit scheduler in production mode.
In debug/test mode (immediate: true), commits fire without delay to
preserve test determinism. Adjusted the non-debug static test to account
for the throttle timer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Set flexDirection=row, flexShrink=1, flexGrow=0 on text yoga nodes at
creation time, matching Ink's <ink-text> style defaults.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Yoga's measure function already receives the content-area width (minus
parent border/padding), so text wrapping is correct without additional
adjustment in the paint pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match Ink's Box defaults by setting yoga properties at node creation time.
This ensures proper row-based layout regardless of Vue's prop patching order.
Updated affected test snapshots and added explicit flexDirection="column"
where tests relied on the old column default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add parsing for Home (ESC[H, ESC[1~, ESC[7~), End (ESC[F, ESC[4~,
ESC[8~), Ctrl+arrow (CSI 1;5 A/B/C/D), and Meta+arrow (double-ESC
prefix) key sequences. Add home/end fields to the Key interface.
Implement 12 new useInput tests for these key combinations.
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>
Update renderTextWithInlineStyles and flattenLeaves to handle transform
nodes nested inside text elements, matching Ink's squashTextNodes
behavior. Skip transform application on empty text to avoid wrapping
empty strings. Skip writing empty text nodes to avoid applying line
transformers to empty content.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add independent horizontal/vertical clip support in the paint pass,
matching Ink's per-axis overflow behavior. Add overflowX/overflowY to
STYLE_PROPS so they are stored in el.props for the paint pass.
Implement 26 new overflow tests translated from Ink.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Guard flattenLeaves and renderTextWithInlineStyles to skip comment
nodes (produced by Vue for null/undefined children) and return empty
strings for childless text nodes. Also guard bindTextMeasure to return
zero dimensions for empty text. Expand TuiInlineNode to include
TuiComment since Vue inserts comments into text containers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
"constructor" in {} returns true because it hits Object.prototype.
Using Object.hasOwn ensures only actual yoga prop keys are recognized.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace ugly `as unknown as EventEmitter` casts with a proper
type alias `Process = ChildProcess & NodeJS.EventEmitter`.
@types/node@25 removed .on() from ChildProcess class declaration
even though it extends EventEmitter at runtime.
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>
- Fix unhandled promise in coding-agent example (void operator)
- Fix unhandled promise in cli/dev.ts (void operator)
- Fix ChildProcess.on() type error in cli/process-manager.ts
(@types/node v25 removed .on() from ChildProcess type)
- Change example build scripts from "vite build" to "vp build"
(vite resolves to vite-plus-core which has no CLI bin)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ink's wrap mode uses { trim: false } to preserve leading/trailing
whitespace on wrapped lines. We incorrectly used trim: true which
stripped whitespace. Updates unit test and inline snapshots.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove yoga setOverflow call — Ink only clips visually in paint,
not at the yoga layout level. Calling setOverflow(HIDDEN) prevented
nodes from expanding, diverging from Ink's behavior.
- Fix AlignContent type to exclude baseline (not valid for align-content
in CSS/Yoga, and Ink doesn't allow it).
- Keep border background fallback to backgroundColor — Ink achieves
the same visual result via background fill underneath borders.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>