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>
The frame writer initialized lastFrame to "" and also reset it to ""
on clear(). Since commit() calls clear() then write(), any component
that painted to an empty string (e.g. Transform with no children, or
a component returning null after unmounting children) would be
deduplicated against the cleared state and never emitted.
Use null as the sentinel for "no previous frame" so the first write
after a clear always goes through, even when the frame is empty.
Fixes: Transform with null/undefined children tests
Fixes: Focus unmount crash tests (stale frame persisted)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously Output.get() only used the last clip rect (clips.at(-1)),
which meant an inner overflow:hidden box wider than its outer ancestor
could effectively loosen the outer clip. Now all active clips are
intersected via reduce, and writes are skipped entirely when the
intersection is empty.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port Ink's clip/unclip approach to vue-tui's paint output. When a box
has overflow="hidden", content painted by its children that falls
outside the box's content area (inside borders) is clipped.
Implementation:
- Add ClipRect, clip(), and unclip() to the Output class
- Maintain a clip stack; active clip is applied to each write op
- Horizontal clipping uses slice-ansi for correct ANSI handling
- Vertical clipping slices the lines array
- Store "overflow" in STYLE_PROPS so it's available in node.props for
the paint pass
Also adds explicit flexDirection="row" to two overflow tests where
multiple child boxes should lay out side-by-side (Ink defaults to row,
vue-tui defaults to yoga's column).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ink's <Text> sets flexShrink: 1 by default so text nodes shrink when
they overflow their container in no-wrap flex rows. Without this,
vue-tui's text nodes had flexShrink=0 (yoga default) and would never
shrink, causing overflow tests to fail.
Also ports Ink's measureTextNode guard: when yoga asks if text can fit
in a sub-1px space during shrink calculations, return the natural
dimensions to prevent text from wrapping to infinite height.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ink's Box defaults to flexDirection="row", so multiple <Text> children
lay out side-by-side. Vue-tui uses yoga's default (column). Rather than
changing the global default and breaking 20+ existing tests, add
explicit flexDirection="row" to the inner boxes in these two tests so
they match the intended layout behavior being verified.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The space-around alignment distributes space as 1-row gap / AB / 2-row
gap / CD / 1-row gap for a 6-row container with 2 content rows.
Expected value was "\nAB\n\nCD\n\n" but Ink produces "\nAB\n\n\nCD\n".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
height=6 containers produce 6 lines. Ink's renderToString returns
"A\n\n\nB\n\n" (preserving trailing blank lines). The tests incorrectly
expected "A\n\n\nB" with a comment claiming lastFrame({ trimLines: true })
trims trailing newlines — it does not; it only trims trailing spaces per
line.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ink's Output.get() calls .trimEnd() on each line of the rendered grid.
Without this, vue-tui's paint grid always fills the full root width with
spaces, causing raw frame output to contain 100-char-wide padding. This
broke tests using { raw: true } and made meaningful trailing-space
assertions impossible with { trimLines: true }.
Also corrects test assertions for 'set width in percent' and 'relative
position' that expected trailing spaces which Ink never produces.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers basic character input, arrow keys, return, escape,
backspace, tab, and isActive option.
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>
Covers inheritance, override, hex/rgb/ansi256 colors, wide chars,
emojis, fill area, fill with border/padding, border backgrounds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds 17 text tests from Ink: color (standard/hex/rgb/ansi256),
background color, dim, bold, inversion, reactive remeasure, and
edge cases. 4 tests skipped due to null-children yoga crash and
constructor text lookup bug.
Also fixes lastFrame() treating empty-string frames as undefined
(Codex review feedback) and adds strip-ansi dev dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Translates flex, direction, wrap, align, justify, gap, padding,
margin, width-height, position, display, and overflow tests from
Ink. Adds trimLines mode and trailing-newline strip to test helper.
Fixes Box type for alignContent (space-between/around/evenly) and
alignSelf (stretch/baseline).
21 tests fail due to behavioral differences (overflow clipping,
percentage dimensions, relative position offsets, align-content
space-* variants). These represent real gaps to investigate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds borderTopDimColor/borderBottomDimColor/borderLeftDimColor/
borderRightDimColor and borderBackgroundColor with per-side variants.
Refactors drawBorder to use per-edge colorizers that respect
edge-specific color, dim, and background overrides.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hard wrap breaks text at exact width boundaries without respecting
word boundaries. Uses wrap-ansi with { hard: true, trim: false }.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds 9 new Box props backed by yoga-layout: maxWidth, maxHeight,
aspectRatio, alignContent, position, top, right, bottom, left.
Also adds baseline alignment support. Includes reset semantics
for props that Ink tests exercise clearing on rerender.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ink uses 'ansi256(N)' while vue-tui only parsed 'ansi(N)'.
Both now work. Needed for Ink test migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ink tests use chalk level 3 (24-bit) for hex/rgb/ansi256 colors.
Level 1 (basic 16-color) caused different ANSI codes than expected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stores raw (untrimmed) frames internally. lastFrame() still trims
by default for backward compat. lastFrame({ raw: true }) returns
the untrimmed output for layout tests asserting exact widths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds chalk to pnpm-workspace catalog (runtime-tests referenced
catalog:chalk but it was missing). Also updates vite-plus deps
to fix vitest JSX transform regression.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes trimEnd() from the paint grid output. The test helper's
trimFrame() still handles cosmetic trimming for lastFrame(), so
existing tests are unaffected. This enables layout tests to assert
exact widths via lastFrame({ raw: true }).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tests must exercise the same ANSI code paths users see. Without
FORCE_COLOR, chalk disables all color in non-TTY test environments,
making style bugs invisible to the test suite.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TUI apps should suppress terminal echo to prevent keystroke pollution
in the rendered UI. Raw mode is now on by default (matching Bubbletea,
Textual, Ratatui), with exitOnCtrlC also defaulting to true.
Pass rawMode: false to mount() for pure-output CLI tools that don't
need input suppression.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Wrap each text line with applyChalk(bgProps) after wrapText so
background color is reapplied on every wrapped line (wrap-ansi
doesn't carry ANSI state across line breaks)
- Add wrapped-text backgroundColor regression test
- Fix typo: color=" cyan" → color="cyan" in example
- Add FORCE_COLOR=3 debugging tip to AGENTS.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Fill background inside border area only (not under border chars)
so border placeLine doesn't overwrite background ANSI codes
- Pad text lines to full layout width when inheritedBg is set
so background extends past text content to fill the row
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root causes:
- drawBorder wrote plain characters, overwriting fillBackground's ANSI codes
- renderTextWithInlineStyles spread node.props over inherited bg, but
undefined props from Vue clobbered the inherited backgroundColor
- Child text nodes did not inherit backgroundColor from parent Box
Fixes:
- drawBorder applies backgroundColor (and borderColor) to border chars
- Filter out undefined props before merging in renderTextWithInlineStyles
- Pass inheritedBg through the paint tree to text nodes
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Static content is rendered via paintIsolated (separate channel), but its
yoga node was still participating in the dynamic frame layout calculation,
adding blank lines equal to the Static content height between the last
Static item and the dynamic content below.
Setting display:none on the Static yoga node removes it from the dynamic
layout. paintIsolated is unaffected — it reparents children into a
temporary yoga tree.
flushStatic() writes directly to stdout, but log-update tracks cursor
position for in-place re-rendering. Without clearing log-update first,
the static content gets overwritten when the dynamic frame re-renders.
Adds a regression test that verifies the clear sequence appears before
static content in non-debug mode.
- Bump all packages to 0.0.1
- Add publishConfig.access: public to @vue-tui/cli
- Disable tsgo for dts generation (missing native-preview dep)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Silence Vite server logs (logLevel: 'silent')
- Intercept console.log/info/warn/error/debug in child process to
suppress [vite] and [Vue warn] noise from HMR client
- Add 3s startup grace period to ignore initial reload requests
(prevents double-spawn on first WS connection)
- Clear screen before spawning child process
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prevents Vite startup logs and connection messages from mixing
with the TUI app's output.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- basic-jsx: Counter + Clock with defineComponent() + JSX
- basic-template: Counter + Clock with .vue SFC + <template>
- Both demonstrate HMR via vue-tui dev
- Fix vite-plugin: strip build.lib and external config for bundledDev,
add node builtins resolveId to prevent browser-external shimming
- Remove old hmr-demo example (superseded by basic-template)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Create dev.ts orchestrator wiring Vite dev server, bundle extractor, and
process manager together with HMR reload and crash-recovery logic.
Replace index.ts placeholder with full CLI entry supporting `vue-tui dev [entry]`.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>