fix(runtime): linearize screen-reader static + drop empty-SR-frame newline (Ink parity, G17) (#45)
* fix(runtime): linearize screen-reader static output + drop empty-SR-frame newline (Ink parity, G17) (a) The live static channel flushed <Static> via the 2D grid painter (paintIsolated) even in screen-reader mode, so bordered static items leaked box glyphs. paintStaticNode now takes an isScreenReaderEnabled flag and linearizes fresh static children via renderScreenReaderOutput (skipStaticElements:false) instead — matching Ink's renderer.ts:24, which renders node.staticNode through renderNodeToScreenReaderOutput. Non-SR static is unchanged. render.ts commit() and render-to-string.ts thread the flag. (b) Interactive SR frames went through renderInteractiveFrame, which appends "\n" even for empty output, leaking a spurious blank line. Ink's SR path writes the wrapped output directly with lastOutputToRender = wrappedOutput (no appended newline), so an empty SR frame emits zero lines. We now suppress the trailing newline for EMPTY SR output only — matching ink.tsx:573-626 — leaving non-SR and non-empty SR frames untouched. Follow-up fixes (two review findings): - renderToString in SR mode no longer DROPS <Static> output: the SR return branch now prepends the captured/linearized static output like the non-SR path (Ink's SR renderer returns staticOutput when node.staticNode exists, renderer.ts:24-33). - The SR static linearization now honors the <Static>'s resolved flexDirection for separator + child order (read from yoga via getFlexDirection), matching screen-reader.ts:73-82 (row/row-reverse → space, *-reverse reverses order); the default column case is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(parity): ledger — G17 pr-open, reconcile G16 merged Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -565,7 +565,12 @@ export function createApp(root: Component, rootProps?: RootProps | null): TuiApp
|
||||
// Fullscreen: output fills or exceeds terminal height — no trailing newline.
|
||||
// Only apply when writing to a real TTY — piped output always gets trailing newlines.
|
||||
const isFullscreen = isTty && outputHeight >= viewportRows;
|
||||
const outputToRender = isFullscreen ? output : output + "\n";
|
||||
// SR parity (G17 edge b): Ink's screen-reader path writes the wrapped
|
||||
// output directly with NO appended newline (ink.tsx:617-621), so an empty
|
||||
// SR frame emits zero lines instead of a spurious blank line. We scope
|
||||
// this to EMPTY SR output to avoid touching non-SR or non-empty SR frames.
|
||||
const isEmptyScreenReaderFrame = isScreenReaderEnabled && output === "";
|
||||
const outputToRender = isFullscreen || isEmptyScreenReaderFrame ? output : output + "\n";
|
||||
|
||||
const shouldClear = shouldClearTerminalForFrame({
|
||||
isTty,
|
||||
@@ -638,7 +643,7 @@ export function createApp(root: Component, rootProps?: RootProps | null): TuiApp
|
||||
const w = resolveSize(stdout).columns;
|
||||
let staticOutput = "";
|
||||
for (const stat of findStatics(tuiRoot)) {
|
||||
const staticFrame = paintStaticNode(stat, w);
|
||||
const staticFrame = paintStaticNode(stat, w, isScreenReaderEnabled);
|
||||
if (staticFrame.length > 0) {
|
||||
staticOutput += staticFrame + "\n";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user