Codex review found waitUntilRenderFlush gated the scheduler flush on
hasPending() alone, missing the window after schedule() queues a commit
but before the post-flush callback sets hasPendingFlag — letting the
promise resolve before the pending render flushed. Now delegates to
flush() unconditionally; flush() already short-circuits when nothing is
scheduled or pending, so this only adds the missing wait.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Correctness fixes found by max-effort review of the branch diff:
- frame-writer.sync() now updates lastFrame alongside log-update's
previousOutput. Previously the two dedup layers desynced after a sync()
(the clearTerminal path), silently dropping a legitimately-changed frame
and emitting an empty BSU/ESU pair. Adds a regression test.
- scheduler: the queuePostFlushCb callback now bails if scheduled was reset
by cancel(), so a stale callback can't commit on a torn-down tree or
re-arm an uncancellable trailing timer.
- scheduler.flush() now collects multiple concurrent waiters instead of
overwriting a single resolver — fixes a hang when two waitUntilRenderFlush()
calls await the same pending commit.
- render teardown nulls mountedClear so a post-unmount app.clear() can't
write to a torn-down stream.
- test-streams getContentWrites imports bsu/esu instead of hardcoding the
escape literals.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- scheduler.cancel() now resolves a pending flush() waiter, preventing
waitUntilRenderFlush() from hanging when the app unmounts mid-flush
- BSU/ESU now wrap the actual stream writes instead of being embedded in
the frame string, so synchronization survives log-update's incremental
line diffing (matches Ink ink.tsx:1059-1097). Normal branch guarded by
willRender() to avoid empty synchronized-update pairs on unchanged frames
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Horizontal clipping now runs per-line AFTER transforms instead of before,
preventing Transform-widened text from escaping clip boundaries. Left-edge
clipping also uses the actual removed width to position subsequent text
correctly when a wide char straddles the boundary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Absolute-positioned wide characters (CJK, emoji) could paint past the
right edge of a clipped box or the terminal grid, producing output wider
than the column limit. Three fixes:
- Safe-slice after sliceAnsi in clip logic to handle wide char overshoot
- Bounds check in grid write loop to skip chars exceeding grid width
- Width-aware border fill to account for measured corner char widths
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>
* test: add text ANSI sanitization parity tests from Ink (+15)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: add use-animation parity tests from Ink (+43)
Port 43 new tests from Ink's use-animation test suite covering:
- Multiple animations in sync, different rates, sibling unmount
- Timer cleanup/recreation on unmount and remount
- Inactive animations, timer leak prevention
- Edge intervals (NaN, Infinity, -Infinity, oversized, zero, negative)
- isActive toggle resets, pause/resume cycles
- Frame catch-up, time/delta tracking, reset() behavior
- Newly mounted/activated animations don't inherit elapsed time
- Wall clock monotonicity, getter function isActive support
Uses selective fake timers (setInterval + performance only) so that
render()'s internal setImmediate still works on real clocks. Fake timer
tests read refs directly to avoid Vue scheduler flush timing issues.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: add use-box-metrics/measure parity tests from Ink (+16)
Port 16 missing tests from Ink's use-box-metrics, measure-element, and
measure-text test suites. Fix useBoxMetrics to reset metrics to zeros
when the tracked ref detaches (element unmounts or ref switches to null).
3 tests are skipped because vue-tui's useBoxMetrics uses watchPostEffect
(re-runs only when ref.value changes) rather than Ink's layout-commit
listener pattern, so sibling-content and resize-driven re-measurement
is not yet supported.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: add screen-reader parity tests from Ink (+11)
Add 11 screen-reader integration tests covering aria-label substitution on
Text/Box, ANSI styling omission, multiple/nested components, null component,
aria-state variants (busy, disabled, expanded), multi-line roles, and
multiselectable listbox.
Also fix component prop bug: Vue normalizes kebab-case prop names to camelCase
at runtime, so props["aria-label"] was always undefined. Switch Box/Text prop
declarations and access to camelCase (ariaLabel, ariaHidden, ariaRole, ariaState).
Add isScreenReaderEnabled option to renderToString() so tests can exercise
screen-reader output through the component pipeline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: add render-to-string parity tests from Ink (+18)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: add cursor composable parity tests from Ink (+7)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: add 6 missing screen-reader Ink parity tests
Add tests for aria-hidden, select input (list with roles/states/labels),
aria-state.multiline, aria-state.readonly, aria-state.required, and
nested multi-line text rendering in screen-reader mode.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: fix render-to-string missing Ink parity tests (+10)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: fix cursor composable missing Ink parity tests (+6)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: fix use-box-metrics missing Ink parity tests (+4)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: add layout listener so useBoxMetrics updates on resize and sibling changes
Adds a layout listener mechanism to TuiRoot matching Ink's architecture:
- TuiRoot.layoutListeners Set with addLayoutListener/emitLayoutListeners
- emitLayoutListeners called after every yoga.calculateLayout in commit()
- useBoxMetrics subscribes to layout listeners, diffs values before updating
Enables 4 previously-skipped tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- sanitize-ansi.test.ts: convert raw ESC/C1 control chars to \u escapes
- frame-writer.test.ts: import cursor escapes from cursor-helpers instead
of duplicating, add 7 createFrameWriter integration tests
slice-ansi can't split within a ZWJ grapheme cluster, so the retry
loop may never find a result that fits. Return empty string instead
of the oversized result.
Port 9 text-width tests from Ink's test suite covering wide characters
in fixed-width Boxes, CJK width calculation, mixed ASCII+wide chars,
ANSI styled text layout, empty Text siblings, and CJK truncation
(end/middle/start/box-width). Also fix CJK truncation bug where
slice-ansi would overshoot on wide character boundaries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port all 35 test declarations from Ink's log-update test suite,
producing 41 new runtime tests (6 cursor tests run in both standard
and incremental modes via describe.each). Covers standard rendering,
incremental rendering (surgical updates, shrink, grow), clear/done
reset, sync+update, cursor positioning, no-trailing-newline fullscreen
mode, and render-to-empty.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port all 57 tests from Ink's input-parser test suite (43 named + 14
backspace/delete splitting via test.each). Replaces the previous 12
skeleton tests with full Ink parity covering: CSI parameters, SS3
sequences, meta+CSI/SS3 double escape, escaped printable/supplementary
codepoints, legacy ESC[[ sequences, incomplete holding states, flush
pending, empty chunks, text + incomplete escape, 14 backspace/delete
splitting cases, and 10 bracketed paste tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port all 26 tests from Ink's ansi-tokenizer test suite, covering CSI,
OSC, DCS, SOS, ESC, C1 control tokens and invalid/incomplete sequences.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port 24 missing tests from Ink's sanitize-ansi test suite covering:
- C1 OSC hyperlinks with various terminators (C1 ST, ESC ST, BEL)
- C1 CSI sequences (both SGR and non-SGR)
- DCS passthrough (tmux wrappers, incomplete, BEL in payload)
- SOS control strings (ESC and C1 variants, various terminators)
- Standalone C1 control characters and ST bytes
- ESC ST sequences, malformed ESC sequences, incomplete CSI
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Release events now produce empty input to prevent character duplication
when reportEventTypes flag is enabled. key.eventType is still passed
through so handlers can detect release events.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- useInput now calls app.exit() for kitty Ctrl+C when exitOnCtrlC=true
- Add PTY test and fixture branch for kittyCtrlCExit scenario
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Fix lifecycle test imports to use @vue-tui/runtime/internal
- Add internal.ts exports for kitty-keyboard functions (Task 3.6b)
- Fix capslock-empty fixture to not check key.capsLock modifier flag
- Add render-level mount/unmount integration tests (createApp path)
- Add query response suppression PTY test with queryThenKey fixture
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Note uppercase test uses Ink's codepoint-65 form (terminal variant)
- Specify ignore?: boolean on Keypress type
- Update late-response description to use ignore flag path
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Filter \x1b[?<digits>u in parseKeypress to swallow terminal capability
responses that leak to useInput via late-response or dual-listener race.
Add 2 unit tests for the filter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>