/** * Pre-wrap text to a known column width, returning an array of * lines no longer than `width` characters. Used by the Detail pane * to render multi-line content (capability_abi guidance, terraform * stderr, etc.) without falling into Ink's "Text sibling wraps and * its remainder overlaps the next sibling" trap. * * Rules: * - Existing newlines (`\n`) are preserved as line breaks. * - Empty input lines render as a single-space output line — a true * empty Text would measure as zero height and let siblings * collapse together; one space keeps the row visible. * - Long lines word-wrap on whitespace when possible. A word longer * than `width` (rare — long URLs, base64) is hard-broken. */ export function wrapText(text: string, width: number): string[] { if (width <= 0) return [text]; const out: string[] = []; for (const line of text.split('\n')) { if (line === '') { out.push(' '); // visible blank row continue; } if (line.length <= width) { out.push(line); continue; } let remaining = line; while (remaining.length > width) { // Prefer breaking at the last whitespace within the window. let breakAt = remaining.lastIndexOf(' ', width); if (breakAt <= 0) { // No whitespace fits — hard-break at the width boundary so a // long URL or base64 string doesn't blow out the layout. breakAt = width; } out.push(remaining.slice(0, breakAt)); // Drop the leading whitespace introduced by the wrap point so // the next line doesn't begin with a stray space. remaining = remaining.slice(breakAt).replace(/^\s+/, ''); } if (remaining) out.push(remaining); } return out; }