fix(runtime): skip eager visual validation under screen-reader mode (Ink parity) (#197)

assertBoxValid (Box) and text.vue's validate() run eager render-time validation
of paint-time VISUAL props (backgroundColor, border fg/bg colors, borderStyle
shape) and throw into the error boundary on an invalid value (e.g. a chalk
modifier name like "bold" used as a color). They were gated only by the per-node
ariaHidden skip (srHidden), not by GLOBAL screen-reader mode.

Under global SR mode (isScreenReaderEnabled; INK_SCREEN_READER=true) vue-tui,
like Ink, linearizes the whole tree to PLAIN TEXT and never colorizes / draws
borders for any node — Ink's colorize path is bypassed entirely, so it never
throws on an invalid color. vue-tui still ran the eager validation for non-
ariaHidden boxes under SR and threw, crashing a screen-reader user out of
accessible content over a paint-only prop value.

Skip the eager visual validation when global SR is on, in addition to the
existing per-node srHidden skip: box.vue gates `!srHidden && (srEnabled ||
assertBoxValid(props))`, text.vue gates `!srHidden && (srEnabled || validate())
&& hasContent`. The validation is all paint-time visual input (no structural
checks), so skipping it under SR is safe and matches Ink.

Verified against real Ink v7.0.4: with INK_SCREEN_READER=true a
<Box backgroundColor="bold"> renders plain text and does NOT throw; without it
Ink throws in colorize.js. This is an alignment fix (removes a vue-tui
over-throw), not a new divergence — the existing ink-divergences entry gets a
factual, unstamped note about the SR carve-out.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yunfei He
2026-06-15 03:04:54 +08:00
committed by GitHub
parent 90713177bb
commit 6469b08c46
5 changed files with 128 additions and 10 deletions
+13
View File
@@ -748,6 +748,19 @@ different runtime behavior, ownership rule, or out-of-contract handling.
content, and content-gated validation is a latent footgun. Principle: reasonable behavior
over incidental Ink parity. The former `wouldRenderNonEmptyText` gate was removed.
Screen-reader-hidden Text still returns before validation (matches Box). [VOUCHED @hyf0]
- **Global screen-reader mode is carved out (skipped) — ALIGNS to Ink, not a new
divergence:** all of the above validation is paint-time VISUAL input (color / bg /
border), and under GLOBAL screen-reader mode (`isScreenReaderEnabled`;
`INK_SCREEN_READER=true`) vue-tui — like Ink — linearizes the whole tree to PLAIN TEXT
and never colorizes / draws borders for any node. Ink's colorize path is bypassed
entirely under SR, so it never throws on an invalid color (run-verified against Ink
v7.0.4: `<Box backgroundColor="bold">` with `INK_SCREEN_READER=true` renders plain text
and does NOT throw; without it Ink throws in `colorize.js`). vue-tui previously still ran
the eager validation for non-`ariaHidden` boxes under SR and threw — crashing a
screen-reader user out of accessible content over a paint-only prop value. The validation
is now skipped when global SR is on (`box.vue` / `text.vue` v-if gate on `srEnabled`),
matching Ink. This removes a vue-tui over-throw and so is an alignment fix, not a new
divergence. Tests: the "GLOBAL SR" cases in `background-color.test.tsx`.
## Non-Behavioral Notes