silvery 0.19.0 → 0.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +2 -2
- package/dist/{render-string-Tv-jqM16.mjs → render-string-CDCeYkS3.mjs} +1 -1
- package/dist/{render-string-Cbuf63Ya.mjs → render-string-Darrg7ku.mjs} +61 -47
- package/dist/{render-string-Cbuf63Ya.mjs.map → render-string-Darrg7ku.mjs.map} +1 -1
- package/dist/runtime.mjs +1 -1
- package/dist/{src-C2uvC-r0.mjs → src-CF-6UN01.mjs} +26 -8
- package/dist/src-CF-6UN01.mjs.map +1 -0
- package/package.json +45 -54
- package/dist/src-C2uvC-r0.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-string-Cbuf63Ya.mjs","names":["defaultGraphemeWidth","collectTextContent","getTextWidth","traverseTree","log","log","hexToRgb","hexToRgb","hexToRgb","hexToRgb"],"sources":["../packages/ag-term/src/pipeline/prepared-text.ts","../packages/ag-term/src/pipeline/pretext.ts","../packages/ag-term/src/pipeline/helpers.ts","../packages/ag-term/src/pipeline/measure-phase.ts","../packages/ag-term/src/pipeline/layout-phase.ts","../packages/ag-term/src/pipeline/state.ts","../packages/ag-term/src/pipeline/render-helpers.ts","../packages/ag-term/src/pipeline/render-text.ts","../packages/ag-term/src/pipeline/render-box.ts","../packages/ag-term/src/pipeline/decoration-phase.ts","../packages/ag-term/src/pipeline/cascade-predicates.ts","../packages/ag-term/src/pipeline/reactive-node.ts","../packages/ag-term/src/pipeline/render-phase.ts","../packages/ag-term/src/pipeline/backdrop/color.ts","../packages/ag-term/src/pipeline/backdrop/plan.ts","../packages/ag-term/src/pipeline/backdrop/color-compat.ts","../packages/ag-term/src/pipeline/backdrop/region.ts","../packages/ag-term/src/pipeline/backdrop/realize-buffer.ts","../packages/ag-term/src/pipeline/backdrop/realize-kitty.ts","../packages/ag-term/src/pipeline/backdrop/index.ts","../packages/ag-term/src/ag.ts","../packages/ag-react/src/reconciler/string-reconciler.ts","../packages/ag-react/src/render-string.tsx"],"sourcesContent":["/**\n * PreparedText: Per-node text analysis cache (Design G, Steps 1-3).\n *\n * Caches three levels of text analysis on a WeakMap<AgNode> to avoid\n * redundant work across frames:\n *\n * Level 0 — Plain text (measure phase + maxDisplayWidth computation)\n * Level 1 — Collected styled text with bg segments (render phase)\n * Level 2 — Formatted lines per width (render phase, LRU by width)\n *\n * Invalidation uses the epoch-based dirty flag system:\n * - Plain text: CONTENT_BIT | CHILDREN_BIT\n * - Collected text: CONTENT_BIT | CHILDREN_BIT | STYLE_PROPS_BIT | BG_BIT | SUBTREE_BIT | context theme ref\n * - Format entries: cleared when collected text is invalidated; keyed by (width, wrap, trim)\n *\n * The WeakMap ensures automatic cleanup when nodes are removed from the tree.\n * Format entries use a small LRU (4 entries) to handle Flexily measure probes\n * (min-content, max-content, final width) without unbounded growth.\n */\n\nimport type { AgNode } from \"@silvery/ag/types\"\nimport {\n isDirty,\n CONTENT_BIT,\n CHILDREN_BIT,\n STYLE_PROPS_BIT,\n BG_BIT,\n SUBTREE_BIT,\n} from \"@silvery/ag/epoch\"\nimport type { TextAnalysis } from \"./pretext\"\nimport type { Theme } from \"@silvery/ansi\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Cached formatted lines for a specific width/wrap/trim combination. */\nexport interface FormatEntry {\n width: number\n wrap: string | boolean | undefined\n trim: boolean\n lines: string[]\n lineOffsets: Array<{ start: number; end: number }>\n hasLineOffsets: boolean\n}\n\n/** Minimal shape of collected text result. Structurally matches TextWithBg. */\nexport interface CollectedTextResult {\n text: string\n bgSegments: readonly { start: number; end: number; bg: unknown }[]\n childSpans: readonly { node: AgNode; start: number; end: number }[]\n plainLen: number\n}\n\n/** Per-text-node cache entry. */\ninterface TextNodeCache {\n // Level 0: plain text\n plainText: string | null\n plainTextLineCount: number\n\n // Level 1: collected styled text\n collected: CollectedTextResult | null\n collectedMaxDisplayWidth: number | undefined\n /** The context theme (by reference) when collected was last computed.\n * When the nearest-ancestor ThemeProvider changes its theme, this reference\n * becomes stale and getCachedCollectedText returns null, forcing re-collection\n * with the new theme's token values embedded in the ANSI codes. */\n collectedContextTheme: Theme | null\n\n // Level 2: formatted lines (LRU, max 4 entries)\n formats: FormatEntry[]\n\n // Level 3: Pretext analysis (cumWidths, breakpoints, etc.)\n // Built from collected text, invalidated when collected text changes\n analysis: TextAnalysis | null\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst MAX_FORMAT_ENTRIES = 4\n\n/** Content-affecting flags that invalidate plain text. */\nconst PLAIN_TEXT_DIRTY = CONTENT_BIT | CHILDREN_BIT\n\n/**\n * All flags that affect collected text (ANSI codes, bg segments, child spans).\n * SUBTREE_BIT is included because collectTextWithBg recurses into virtual text\n * children — a child's style change sets SUBTREE_BIT on the parent without\n * setting STYLE_PROPS_BIT (the parent's own props didn't change).\n */\nconst COLLECTED_TEXT_DIRTY = CONTENT_BIT | CHILDREN_BIT | STYLE_PROPS_BIT | BG_BIT | SUBTREE_BIT\n\n// ============================================================================\n// Storage\n// ============================================================================\n\n/** Set to true to disable all caching (for testing/debugging). */\nlet _cacheDisabled = !!process.env.SILVERY_NO_TEXT_CACHE\nexport function setPreparedTextCacheEnabled(enabled: boolean): void {\n _cacheDisabled = !enabled\n}\n\nconst textCaches = new WeakMap<AgNode, TextNodeCache>()\n\nfunction getOrCreate(node: AgNode): TextNodeCache {\n let entry = textCaches.get(node)\n if (!entry) {\n entry = {\n plainText: null,\n plainTextLineCount: 0,\n collected: null,\n collectedMaxDisplayWidth: undefined,\n collectedContextTheme: null,\n formats: [],\n analysis: null,\n }\n textCaches.set(node, entry)\n }\n return entry\n}\n\n// ============================================================================\n// Level 0: Plain text (measure phase + maxDisplayWidth computation)\n// ============================================================================\n\n/**\n * Get cached plain text and line count.\n * Returns null on cache miss (content/children changed or first access).\n */\nexport function getCachedPlainText(node: AgNode): { text: string; lineCount: number } | null {\n if (_cacheDisabled) return null\n const entry = textCaches.get(node)\n if (entry?.plainText == null) return null\n if (isDirty(node.dirtyBits, node.dirtyEpoch, PLAIN_TEXT_DIRTY)) {\n entry.plainText = null\n return null\n }\n return { text: entry.plainText, lineCount: entry.plainTextLineCount }\n}\n\n/** Store plain text in cache. */\nexport function setCachedPlainText(node: AgNode, text: string, lineCount: number): void {\n const entry = getOrCreate(node)\n entry.plainText = text\n entry.plainTextLineCount = lineCount\n}\n\n// ============================================================================\n// Level 1: Collected styled text (render phase)\n// ============================================================================\n\n/**\n * Get cached collected text (from collectTextWithBg).\n * Invalidated by content, children, style, or bg changes, maxDisplayWidth mismatch,\n * or a context theme change (ancestor ThemeProvider changed token values).\n *\n * @param contextTheme - The active theme at the time of rendering (from getActiveTheme()).\n * Used as a cache key so that when the nearest-ancestor ThemeProvider changes its\n * merged theme, text nodes that embed $token ANSI codes in their collected text\n * are re-collected with the new token values. Pass null when no theme context exists.\n */\nexport function getCachedCollectedText(\n node: AgNode,\n maxDisplayWidth: number | undefined,\n contextTheme: Theme | null,\n): CollectedTextResult | null {\n if (_cacheDisabled) return null\n const entry = textCaches.get(node)\n if (!entry?.collected) return null\n\n if (isDirty(node.dirtyBits, node.dirtyEpoch, COLLECTED_TEXT_DIRTY)) {\n entry.collected = null\n entry.formats = [] // collected text changed → format stale\n entry.analysis = null // analysis depends on collected text\n return null\n }\n\n if (entry.collectedMaxDisplayWidth !== maxDisplayWidth) {\n entry.collected = null\n entry.formats = []\n entry.analysis = null\n return null\n }\n\n // When the ancestor theme changes, ANSI-encoded $token colors in the collected\n // text are stale. Invalidate so collectTextWithBg re-runs with new token values.\n if (entry.collectedContextTheme !== contextTheme) {\n entry.collected = null\n entry.formats = []\n entry.analysis = null\n return null\n }\n\n return entry.collected\n}\n\n/** Store collected text in cache. */\nexport function setCachedCollectedText(\n node: AgNode,\n result: CollectedTextResult,\n maxDisplayWidth: number | undefined,\n contextTheme: Theme | null,\n): void {\n const entry = getOrCreate(node)\n entry.collected = result\n entry.collectedMaxDisplayWidth = maxDisplayWidth\n entry.collectedContextTheme = contextTheme\n}\n\n// ============================================================================\n// Level 2: Formatted lines per width (render phase, LRU)\n// ============================================================================\n\n/**\n * Get cached formatted lines for the given width/wrap/trim.\n * Returns null on cache miss.\n */\nexport function getCachedFormat(\n node: AgNode,\n width: number,\n wrap: string | boolean | undefined,\n trim: boolean,\n): FormatEntry | null {\n if (_cacheDisabled) return null\n const entry = textCaches.get(node)\n if (!entry || entry.formats.length === 0) return null\n\n for (let i = 0; i < entry.formats.length; i++) {\n const f = entry.formats[i]!\n if (f.width === width && f.wrap === wrap && f.trim === trim) {\n // LRU: move to end (most recently used)\n if (i < entry.formats.length - 1) {\n entry.formats.splice(i, 1)\n entry.formats.push(f)\n }\n return f\n }\n }\n return null\n}\n\n/** Store formatted lines in cache (LRU, evicts oldest when full). */\nexport function setCachedFormat(\n node: AgNode,\n width: number,\n wrap: string | boolean | undefined,\n trim: boolean,\n lines: string[],\n lineOffsets: Array<{ start: number; end: number }>,\n hasLineOffsets: boolean,\n): void {\n const entry = getOrCreate(node)\n\n // Replace existing entry for same key\n for (let i = 0; i < entry.formats.length; i++) {\n const f = entry.formats[i]!\n if (f.width === width && f.wrap === wrap && f.trim === trim) {\n entry.formats[i] = { width, wrap, trim, lines, lineOffsets, hasLineOffsets }\n return\n }\n }\n\n // Evict oldest if at capacity\n if (entry.formats.length >= MAX_FORMAT_ENTRIES) {\n entry.formats.shift()\n }\n entry.formats.push({ width, wrap, trim, lines, lineOffsets, hasLineOffsets })\n}\n\n// ============================================================================\n// Level 3: Pretext analysis (cumWidths, breakpoints — for snug-content/even wrap)\n// ============================================================================\n\n/**\n * Get cached text analysis. Invalidated when content changes.\n * Uses PLAIN_TEXT_DIRTY (not COLLECTED_TEXT_DIRTY) because analysis\n * is built from plain text in measure phase, not styled text.\n */\nexport function getCachedAnalysis(node: AgNode): TextAnalysis | null {\n if (_cacheDisabled) return null\n const entry = textCaches.get(node)\n if (!entry?.analysis) return null\n if (isDirty(node.dirtyBits, node.dirtyEpoch, PLAIN_TEXT_DIRTY)) {\n entry.analysis = null\n return null\n }\n return entry.analysis\n}\n\n/** Store text analysis in cache. */\nexport function setCachedAnalysis(node: AgNode, analysis: TextAnalysis): void {\n const entry = getOrCreate(node)\n entry.analysis = analysis\n}\n","/**\n * Pretext: Grapheme-indexed text analysis for layout queries.\n *\n * Inspired by https://chenglou.me/pretext/ — prepare text once, measure at\n * any width cheaply. Enables layout algorithms CSS can't express:\n *\n * - **Shrinkwrap**: find the narrowest width that keeps the same line count\n * - **Balanced**: equalize line widths (reduce raggedness)\n * - **Knuth-Plass**: optimal paragraph breaking (minimize total raggedness)\n * - **Height prediction**: exact line count at any width without full wrapping\n */\n\nimport {\n graphemeWidth as defaultGraphemeWidth,\n splitGraphemesAnsiAware,\n isWordBoundary,\n canBreakAnywhere,\n wrapText,\n} from \"../unicode\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Grapheme-level text analysis for fast width queries. */\nexport interface TextAnalysis {\n /** ANSI-aware graphemes (visible chars + zero-width ANSI tokens). */\n graphemes: string[]\n /** Display width per grapheme (0 for ANSI tokens). */\n widths: number[]\n /** Prefix sums: cumWidths[i] = sum(widths[0..i-1]). cumWidths[0] = 0. */\n cumWidths: number[]\n /** Total display width of all graphemes. */\n totalWidth: number\n /** Width of the widest unbreakable word segment. */\n maxWordWidth: number\n /** Width of the widest single grapheme. */\n maxGraphemeWidth: number\n /** Grapheme indices where newlines occur. */\n newlineIndices: number[]\n /** Grapheme indices where word breaks are legal. */\n breakIndices: number[]\n /** Original text (for delegating to wrapText). */\n text: string\n}\n\n// ============================================================================\n// Build\n// ============================================================================\n\n/**\n * Build text analysis from an ANSI-embedded text string.\n * O(N) where N is grapheme count. Call once per text change (cached by PreparedText).\n */\nexport function buildTextAnalysis(\n text: string,\n gWidthFn: (g: string) => number = defaultGraphemeWidth,\n): TextAnalysis {\n const graphemes = splitGraphemesAnsiAware(text)\n const len = graphemes.length\n const widths = new Array<number>(len)\n const cumWidths = new Array<number>(len + 1)\n const newlineIndices: number[] = []\n const breakIndices: number[] = []\n\n cumWidths[0] = 0\n let maxWordWidth = 0\n let maxGraphemeWidth = 0\n let currentWordWidth = 0\n\n for (let i = 0; i < len; i++) {\n const g = graphemes[i]!\n const w = gWidthFn(g)\n widths[i] = w\n cumWidths[i + 1] = cumWidths[i]! + w\n if (w > maxGraphemeWidth) maxGraphemeWidth = w\n\n if (g === \"\\n\") {\n newlineIndices.push(i)\n maxWordWidth = Math.max(maxWordWidth, currentWordWidth)\n currentWordWidth = 0\n } else if (isWordBoundary(g)) {\n breakIndices.push(i + 1)\n maxWordWidth = Math.max(maxWordWidth, currentWordWidth)\n currentWordWidth = 0\n } else if (canBreakAnywhere(g)) {\n breakIndices.push(i)\n maxWordWidth = Math.max(maxWordWidth, currentWordWidth)\n currentWordWidth = w\n } else if (w > 0) {\n currentWordWidth += w\n }\n }\n maxWordWidth = Math.max(maxWordWidth, currentWordWidth)\n\n return {\n graphemes,\n widths,\n cumWidths,\n totalWidth: cumWidths[len]!,\n maxWordWidth,\n maxGraphemeWidth,\n newlineIndices,\n breakIndices,\n text,\n }\n}\n\n// ============================================================================\n// Line counting\n// ============================================================================\n\n/**\n * Count how many lines text would occupy at a given width.\n *\n * Delegates to wrapText for correctness — the greedy wrapping algorithm has\n * subtle boundary-char handling (spaces consumed on overflow, leading space\n * trimming on continuation lines) that's error-prone to reimplement.\n *\n * For terminal text (20-200 chars), wrapText is ~5-12µs per call.\n * Shrinkwrap does ~7-9 calls (log2(width)), so total is ~50-100µs.\n */\nexport function countLinesAtWidth(analysis: TextAnalysis, width: number): number {\n if (width <= 0) return Infinity\n if (analysis.totalWidth <= width && analysis.newlineIndices.length === 0) return 1\n return wrapText(analysis.text, width, true, true).length\n}\n\n// ============================================================================\n// Shrinkwrap\n// ============================================================================\n\n/**\n * Find the narrowest integer width that produces the same line count as maxWidth.\n *\n * CSS fit-content uses the widest wrapped line — leaving dead space when the\n * last line is short. Shrinkwrap binary-searches for the tightest width that\n * keeps the same number of lines, eliminating wasted area in bubbles/cards.\n *\n * O(log(maxWidth) × wrapText) — ~7-9 iterations for terminal widths.\n */\nexport function shrinkwrapWidth(analysis: TextAnalysis, maxWidth: number): number {\n if (maxWidth <= 0) return 0\n const targetLineCount = countLinesAtWidth(analysis, maxWidth)\n if (targetLineCount <= 1) {\n return Math.min(Math.ceil(analysis.totalWidth), maxWidth)\n }\n\n // Lower bound: max grapheme width (character wrap allows widths below maxWordWidth)\n // Upper bound: maxWidth (can't return wider than the container)\n let lo = Math.max(1, analysis.maxGraphemeWidth)\n let hi = maxWidth\n\n // Guard: if lo >= hi, nothing to search\n if (lo >= hi) return Math.min(hi, maxWidth)\n\n while (lo < hi) {\n const mid = (lo + hi) >> 1\n if (countLinesAtWidth(analysis, mid) <= targetLineCount) {\n hi = mid\n } else {\n lo = mid + 1\n }\n }\n\n // Clamp to maxWidth (safety)\n return Math.min(lo, maxWidth)\n}\n\n// ============================================================================\n// Balanced breaking\n// ============================================================================\n\n/**\n * Find a width that produces lines of approximately equal length.\n *\n * Strategy: compute total width / line count as the ideal per-line width,\n * then find the narrowest width at that line count via shrinkwrap.\n */\nexport function balancedWidth(analysis: TextAnalysis, maxWidth: number): number {\n if (maxWidth <= 0) return 0\n const lineCount = countLinesAtWidth(analysis, maxWidth)\n if (lineCount <= 1) return Math.min(Math.ceil(analysis.totalWidth), maxWidth)\n\n // Ideal balanced width: total / lines, rounded up\n const idealWidth = Math.ceil(analysis.totalWidth / lineCount)\n\n // Clamp to [maxGraphemeWidth, maxWidth]\n const candidateWidth = Math.max(analysis.maxGraphemeWidth, Math.min(idealWidth, maxWidth))\n\n // Verify this doesn't increase line count\n if (countLinesAtWidth(analysis, candidateWidth) > lineCount) {\n return shrinkwrapWidth(analysis, maxWidth)\n }\n\n // Further tighten via shrinkwrap at the balanced line count\n return shrinkwrapWidth(analysis, candidateWidth)\n}\n\n// ============================================================================\n// Knuth-Plass optimal paragraph breaking\n// ============================================================================\n\n/**\n * Find optimal line breaks that minimize total raggedness.\n *\n * Runs per-paragraph (split by newlines) to avoid penalty interactions\n * around forced breaks. Falls back to greedy wrapping for paragraphs\n * where the DP finds no feasible solution (overlong words).\n *\n * O(breakpoints²) per paragraph, typically much less with pruning.\n */\nexport function knuthPlassBreaks(analysis: TextAnalysis, width: number): number[] {\n if (width <= 0) return []\n if (analysis.totalWidth <= width && analysis.newlineIndices.length === 0) return []\n\n // Split into paragraphs at newlines and process each independently\n const { newlineIndices, graphemes } = analysis\n const allBreaks: number[] = []\n\n const paragraphStarts = [0]\n for (const nl of newlineIndices) {\n paragraphStarts.push(nl + 1)\n }\n\n for (let p = 0; p < paragraphStarts.length; p++) {\n const pStart = paragraphStarts[p]!\n const pEnd = p + 1 < paragraphStarts.length ? paragraphStarts[p + 1]! - 1 : graphemes.length // -1 to exclude newline\n\n if (pStart >= pEnd) continue // empty paragraph\n\n const breaks = knuthPlassForParagraph(analysis, pStart, pEnd, width)\n allBreaks.push(...breaks)\n\n // Add newline break if not the last paragraph\n if (p < paragraphStarts.length - 1 && pEnd < graphemes.length) {\n allBreaks.push(pEnd + 1) // after the newline\n }\n }\n\n return allBreaks\n}\n\n/** DP for a single paragraph (no newlines). */\nfunction knuthPlassForParagraph(\n analysis: TextAnalysis,\n pStart: number,\n pEnd: number,\n width: number,\n): number[] {\n const { cumWidths, breakIndices, widths, graphemes } = analysis\n\n // Build candidates for this paragraph\n const candidates: number[] = [pStart]\n for (const bp of breakIndices) {\n if (bp > pStart && bp <= pEnd) candidates.push(bp)\n }\n candidates.push(pEnd)\n\n const n = candidates.length\n if (n <= 2) return [] // single segment, no breaks needed\n\n const cost = new Array<number>(n).fill(Infinity)\n const next = new Array<number>(n).fill(-1)\n cost[n - 1] = 0\n\n for (let i = n - 2; i >= 0; i--) {\n const lineStart = candidates[i]!\n const lineStartCum = cumWidths[lineStart]!\n\n for (let j = i + 1; j < n; j++) {\n const lineEnd = candidates[j]!\n\n // Compute line width, trimming trailing whitespace\n let trimEnd = lineEnd\n while (trimEnd > lineStart) {\n const prevG = graphemes[trimEnd - 1]\n const prevW = widths[trimEnd - 1]\n if (prevW === 0) {\n trimEnd--\n continue\n } // skip ANSI\n if (prevG === \" \" || prevG === \"\\t\") {\n trimEnd--\n continue\n }\n break\n }\n const lineWidth = cumWidths[trimEnd]! - lineStartCum\n\n if (lineWidth > width) break // too wide, skip wider candidates\n\n const leftover = width - lineWidth\n const lineCost = j === n - 1 ? 0 : leftover * leftover\n const totalCost = lineCost + cost[j]!\n\n if (totalCost < cost[i]!) {\n cost[i] = totalCost\n next[i] = j\n }\n }\n }\n\n // If DP failed (no feasible path), return empty (caller falls back to greedy)\n if (cost[0] === Infinity) return []\n\n // Trace back\n const breaks: number[] = []\n let idx = 0\n while (idx < n - 1 && next[idx]! >= 0) {\n idx = next[idx]!\n if (idx < n - 1) {\n breaks.push(candidates[idx]!)\n }\n }\n\n return breaks\n}\n\n/**\n * Wrap text using Knuth-Plass optimal breaks.\n * Returns line strings — drop-in replacement for greedy wrap.\n * Falls back to greedy wrapText when DP finds no feasible solution.\n */\nexport function optimalWrap(text: string, analysis: TextAnalysis, width: number): string[] {\n const breaks = knuthPlassBreaks(analysis, width)\n if (breaks.length === 0) {\n // No breaks found — either single line or DP infeasible → fall back to greedy\n if (analysis.totalWidth <= width && analysis.newlineIndices.length === 0) return [text]\n return wrapText(text, width, true, true)\n }\n\n const { graphemes, widths } = analysis\n const lines: string[] = []\n let lineStart = 0\n\n for (const bp of breaks) {\n // Trim trailing whitespace (skip zero-width ANSI tokens)\n let lineEnd = bp\n while (lineEnd > lineStart) {\n const w = widths[lineEnd - 1]!\n if (w === 0) {\n lineEnd--\n continue\n } // ANSI token\n const g = graphemes[lineEnd - 1]!\n if (g === \" \" || g === \"\\t\" || g === \"\\n\") {\n lineEnd--\n continue\n }\n break\n }\n lines.push(graphemes.slice(lineStart, lineEnd).join(\"\"))\n\n // Skip leading whitespace on next line (skip ANSI tokens)\n lineStart = bp\n while (lineStart < graphemes.length) {\n const g = graphemes[lineStart]!\n if (g === \" \" || g === \"\\t\") {\n lineStart++\n continue\n }\n break\n }\n }\n\n // Last line\n if (lineStart < graphemes.length) {\n lines.push(graphemes.slice(lineStart).join(\"\"))\n }\n\n return lines\n}\n","/**\n * Shared helper functions for silvery pipeline phases.\n */\n\nimport type { BoxProps } from \"@silvery/ag/types\"\nimport { getActiveLineHeight } from \"../unicode\"\n\n/**\n * Get padding values from props.\n */\nexport function getPadding(props: BoxProps): {\n top: number\n bottom: number\n left: number\n right: number\n} {\n return {\n top: props.paddingTop ?? props.paddingY ?? props.padding ?? 0,\n bottom: props.paddingBottom ?? props.paddingY ?? props.padding ?? 0,\n left: props.paddingLeft ?? props.paddingX ?? props.padding ?? 0,\n right: props.paddingRight ?? props.paddingX ?? props.padding ?? 0,\n }\n}\n\n/**\n * Get border size (1 or 0 for each side).\n * In pixel/canvas mode (lineHeight > 1), borders are visual-only (fillRoundedRect)\n * and don't affect content positioning — returns 0.\n */\nexport function getBorderSize(props: BoxProps): {\n top: number\n bottom: number\n left: number\n right: number\n} {\n if (!props.borderStyle || getActiveLineHeight() > 1) {\n return { top: 0, bottom: 0, left: 0, right: 0 }\n }\n return {\n top: props.borderTop !== false ? 1 : 0,\n bottom: props.borderBottom !== false ? 1 : 0,\n left: props.borderLeft !== false ? 1 : 0,\n right: props.borderRight !== false ? 1 : 0,\n }\n}\n","/**\n * Phase 1: Measure Phase\n *\n * Handle fit-content nodes by measuring their intrinsic content size.\n */\n\nimport type { BoxProps, AgNode, TextProps } from \"@silvery/ag/types\"\nimport { displayWidthAnsi, graphemeWidth, wrapText, getActiveLineHeight } from \"../unicode\"\nimport { collectPlainText as collectTextContent } from \"./collect-text\"\nimport {\n getCachedPlainText,\n setCachedPlainText,\n getCachedAnalysis,\n setCachedAnalysis,\n} from \"./prepared-text\"\nimport { buildTextAnalysis, shrinkwrapWidth } from \"./pretext\"\nimport { getBorderSize, getPadding } from \"./helpers\"\nimport type { PipelineContext } from \"./types\"\n\n/**\n * Handle fit-content nodes by measuring their intrinsic content size.\n *\n * Traverses the tree and for any node with width=\"fit-content\" or\n * height=\"fit-content\", measures the content and sets the Yoga constraint.\n */\nexport function measurePhase(root: AgNode, ctx?: PipelineContext): void {\n traverseTree(root, (node) => {\n // Skip nodes without Yoga (raw text nodes)\n if (!node.layoutNode) return\n\n const props = node.props as BoxProps\n\n // width=\"fit-content\" is now handled natively by Flexily (UNIT_FIT_CONTENT).\n // The reconciler calls setWidthFitContent() directly.\n // width=\"snug-content\" uses Flexily's UNIT_SNUG_CONTENT for basic sizing,\n // but still needs the binary-search tightening pass here.\n // height=\"fit-content\" still needs the pre-layout polyfill.\n const isSnugContent = props.width === \"snug-content\"\n const isHeightFitContent = props.height === \"fit-content\"\n\n if (isSnugContent || isHeightFitContent) {\n // Pass an available-width constraint to child measurement whenever a\n // definite upper bound exists — either a fixed width (height=\"fit-content\"\n // + width:number case) or a maxWidth cap on the snug-content box itself.\n let availableWidth: number | undefined\n const widthIsFixed = typeof props.width === \"number\"\n let definiteUpperWidth: number | undefined =\n widthIsFixed && isHeightFitContent\n ? (props.width as number)\n : typeof props.maxWidth === \"number\"\n ? (props.maxWidth as number)\n : undefined\n if (definiteUpperWidth === undefined) {\n definiteUpperWidth = findAncestorDefiniteWidth(node)\n }\n if (definiteUpperWidth !== undefined) {\n const padding = getPadding(props)\n availableWidth = definiteUpperWidth - padding.left - padding.right\n if (props.borderStyle) {\n const border = getBorderSize(props)\n availableWidth -= border.left + border.right\n }\n if (availableWidth < 1) availableWidth = 1\n }\n\n if (isSnugContent) {\n const intrinsicSize = measureIntrinsicSize(node, ctx, availableWidth)\n // Fit-snug: find the narrowest width that keeps the same line count.\n // Binary search for tightest width on top of Flexily's native fit-content.\n const shrunkWidth = computeSnugContentWidth(node, intrinsicSize.width, ctx)\n // setMaxWidth caps the snug-content box at the binary-searched width.\n // Flexily's UNIT_SNUG_CONTENT handles the shrink-wrap + available clamping.\n node.layoutNode.setMaxWidth(shrunkWidth)\n }\n if (isHeightFitContent) {\n const intrinsicSize = measureIntrinsicSize(node, ctx, availableWidth)\n node.layoutNode.setHeight(intrinsicSize.height)\n }\n }\n })\n}\n\n/**\n * Measure the intrinsic size of a node's content.\n *\n * For text nodes: measures the text width and line count.\n * For box nodes: recursively measures children based on flex direction.\n *\n * @param availableWidth - When set, text nodes wrap at this width for height calculation.\n * Used when a container has fixed width + fit-content height.\n */\nfunction measureIntrinsicSize(\n node: AgNode,\n ctx?: PipelineContext,\n availableWidth?: number,\n): {\n width: number\n height: number\n} {\n const props = node.props as BoxProps\n\n // display=\"none\" nodes have 0x0 intrinsic size\n if (props.display === \"none\") {\n return { width: 0, height: 0 }\n }\n\n if (node.type === \"silvery-text\") {\n const textProps = props as TextProps\n // PreparedText cache: reuse plain text from previous frames when content unchanged\n const cached = getCachedPlainText(node)\n let text: string\n if (cached) {\n text = cached.text\n } else {\n text = collectTextContent(node)\n const lineCount = (text.match(/\\n/g)?.length ?? 0) + 1\n setCachedPlainText(node, text, lineCount)\n }\n\n // Apply internal_transform if present (used by Transform component).\n // The transform is applied per-line, which can change the width.\n const transform = textProps.internal_transform\n let lines: string[]\n\n if (availableWidth !== undefined && availableWidth > 0 && isWrapEnabled(textProps.wrap)) {\n // Wrap text at available width to compute correct height\n lines = ctx\n ? ctx.measurer.wrapText(text, availableWidth, true, true)\n : wrapText(text, availableWidth, true, true)\n } else {\n lines = text.split(\"\\n\")\n }\n\n if (transform) {\n lines = lines.map((line, index) => transform(line, index))\n }\n\n const width = Math.max(...lines.map((line) => getTextWidth(line, ctx)))\n return {\n width,\n height: lines.length * getActiveLineHeight(),\n }\n }\n\n // For boxes, measure based on flex direction\n const isRow = props.flexDirection === \"row\" || props.flexDirection === \"row-reverse\"\n\n let width = 0\n let height = 0\n\n let childCount = 0\n for (const child of node.children) {\n const childSize = measureIntrinsicSize(child, ctx, availableWidth)\n childCount++\n\n if (isRow) {\n width += childSize.width\n height = Math.max(height, childSize.height)\n } else {\n width = Math.max(width, childSize.width)\n height += childSize.height\n }\n }\n\n // Add gap between children\n const gap = (props.gap as number) ?? 0\n if (gap > 0 && childCount > 1) {\n const totalGap = gap * (childCount - 1)\n if (isRow) {\n width += totalGap\n } else {\n height += totalGap\n }\n }\n\n // Add padding\n const padding = getPadding(props)\n width += padding.left + padding.right\n height += padding.top + padding.bottom\n\n // Add border\n if (props.borderStyle) {\n const border = getBorderSize(props)\n width += border.left + border.right\n height += border.top + border.bottom\n }\n\n return { width, height }\n}\n\n/**\n * Check if text wrapping is enabled for a text node.\n */\nfunction isWrapEnabled(wrap: TextProps[\"wrap\"]): boolean {\n return (\n wrap === \"wrap\" || wrap === \"hard\" || wrap === \"even\" || wrap === true || wrap === undefined\n )\n}\n\n/**\n * Compute snug-content width for a node.\n * Uses Pretext analysis to binary-search for the tightest width\n * that keeps the same line count as the fit-content width.\n */\nfunction computeSnugContentWidth(\n node: AgNode,\n fitContentWidth: number,\n ctx?: PipelineContext,\n): number {\n const props = node.props as BoxProps\n\n // Subtract padding + border from fitContentWidth to get CONTENT width.\n // measureIntrinsicSize includes padding+border in its result, but\n // shrinkwrapWidth operates on text content width only.\n let overhead = 0\n const padding = getPadding(props)\n overhead += padding.left + padding.right\n if (props.borderStyle) {\n const border = getBorderSize(props)\n overhead += border.left + border.right\n }\n const contentWidth = fitContentWidth - overhead\n\n // Get or build text analysis\n let analysis = getCachedAnalysis(node)\n if (!analysis) {\n const cached = getCachedPlainText(node)\n const text = cached ? cached.text : collectTextContent(node)\n const gWidthFn = ctx?.measurer?.graphemeWidth?.bind(ctx.measurer) ?? graphemeWidth\n analysis = buildTextAnalysis(text, gWidthFn)\n setCachedAnalysis(node, analysis)\n if (!cached) {\n const lineCount = (text.match(/\\n/g)?.length ?? 0) + 1\n setCachedPlainText(node, text, lineCount)\n }\n }\n\n // Shrinkwrap the content, then add overhead back\n return shrinkwrapWidth(analysis, contentWidth) + overhead\n}\n\n/**\n * Walk up the tree from a node to find the nearest ancestor with a definite\n * width (a fixed number, not \"fit-content\" or \"snug-content\"). Returns the\n * ancestor's inner content width (after subtracting its own padding and border).\n * Returns undefined if no definite-width ancestor is found.\n */\nfunction findAncestorDefiniteWidth(node: AgNode): number | undefined {\n let current = node.parent\n while (current) {\n const p = current.props as BoxProps\n if (typeof p.width === \"number\") {\n let inner = p.width as number\n const padding = getPadding(p)\n inner -= padding.left + padding.right\n if (p.borderStyle) {\n const border = getBorderSize(p)\n inner -= border.left + border.right\n }\n return inner > 0 ? inner : 1\n }\n if (typeof p.maxWidth === \"number\") {\n let inner = p.maxWidth as number\n const padding = getPadding(p)\n inner -= padding.left + padding.right\n if (p.borderStyle) {\n const border = getBorderSize(p)\n inner -= border.left + border.right\n }\n return inner > 0 ? inner : 1\n }\n current = current.parent\n }\n return undefined\n}\n\n/**\n * Traverse tree in depth-first order.\n */\nfunction traverseTree(node: AgNode, callback: (node: AgNode) => void): void {\n callback(node)\n for (const child of node.children) {\n traverseTree(child, callback)\n }\n}\n\n/**\n * Get text display width (accounting for wide characters and ANSI codes).\n * Uses ANSI-aware width calculation to handle styled text.\n */\nfunction getTextWidth(text: string, ctx?: PipelineContext): number {\n if (ctx) return ctx.measurer.displayWidthAnsi(text)\n return displayWidthAnsi(text)\n}\n\n// collectTextContent is imported from ./collect-text as collectPlainText.\n// Previously duplicated here; now shared across measure-phase, render-text,\n// and the reconciler's measure function.\n","/**\n * Phase 2: Layout Phase\n *\n * Run Yoga layout calculation and propagate dimensions to all nodes.\n */\n\nimport { createLogger } from \"loggily\"\nimport { measureStats } from \"./measure-stats\"\nimport { type BoxProps, type AgNode, type Rect, rectEqual } from \"@silvery/ag/types\"\n// Layout dirty gate: Flexily's root.layoutNode.isDirty() is the sole source\n// of truth. No silvery-side layout dirty tracking needed.\nimport {\n getRenderEpoch,\n INITIAL_EPOCH,\n isCurrentEpoch,\n isDirty,\n SUBTREE_BIT,\n CHILDREN_BIT,\n ABS_CHILD_BIT,\n DESC_OVERFLOW_BIT,\n} from \"@silvery/ag/epoch\"\nimport { getBorderSize, getPadding } from \"./helpers\"\nimport { syncRectSignals } from \"@silvery/ag/layout-signals\"\n\nconst log = createLogger(\"silvery:layout\")\n\n/**\n * Run Yoga layout calculation and propagate dimensions to all nodes.\n *\n * @param root The root SilveryNode\n * @param width Terminal width in columns\n * @param height Terminal height in rows\n */\nexport function layoutPhase(root: AgNode, width: number, height: number): void {\n // Check if dimensions changed from previous layout\n const prevLayout = root.boxRect\n const dimensionsChanged =\n prevLayout && (prevLayout.width !== width || prevLayout.height !== height)\n\n // Only recalculate if something changed (dirty nodes or dimensions).\n // Flexily's root isDirty() propagates from any markDirty() call —\n // no silvery-side tracking needed.\n if (!dimensionsChanged && !root.layoutNode?.isDirty()) {\n // Even when layout is clean, style-only changes (outline add/remove,\n // absolute child structural changes) need cascade input caching.\n // These checks run in propagateLayout normally, but when the layout\n // phase skips, they're never computed. Run a lightweight traversal\n // that follows only subtreeDirty paths to cache these inputs.\n if (isDirty(root.dirtyBits, root.dirtyEpoch, SUBTREE_BIT)) {\n propagateCascadeInputs(root)\n }\n return\n }\n // Run layout calculation (root always has a layoutNode)\n if (root.layoutNode) {\n const nodeCount = countNodes(root)\n measureStats.reset()\n const t0 = Date.now()\n root.layoutNode.calculateLayout(width, height)\n const elapsed = Date.now() - t0\n log.debug?.(\n `calculateLayout: ${elapsed}ms (${nodeCount} nodes) measure: calls=${measureStats.calls} hits=${measureStats.cacheHits} collects=${measureStats.textCollects} displayWidth=${measureStats.displayWidthCalls}`,\n )\n }\n\n // Propagate computed dimensions to all nodes.\n // When dimensions haven't changed, enable incremental skip: subtrees\n // whose Flexily-computed rect matches their existing boxRect are skipped\n // entirely (O(1) rect comparison prunes O(subtree) walk).\n // On dimension change, the root constraint changed so all nodes may get\n // new results — skip nothing, propagate the full tree.\n const incrementalSkip = !dimensionsChanged\n propagateLayout(root, 0, 0, incrementalSkip)\n\n // NOTE: Subscribers are NOT notified here anymore.\n // They are notified by the pipeline AFTER scrollrectPhase completes,\n // so useScrollRect can read the correct screen positions.\n}\n\n/**\n * Count total nodes in tree.\n */\nfunction countNodes(node: AgNode): number {\n let count = 1\n for (const child of node.children) {\n count += countNodes(child)\n }\n return count\n}\n\n/**\n * Propagate computed layout from Yoga nodes to SilveryNodes.\n * Sets boxRect (content-relative position) on each node.\n *\n * When `incrementalSkip` is true, nodes whose Flexily-computed rect matches\n * their existing boxRect can skip the entire subtree — their layout is\n * unchanged. This converts the O(N) tree walk into O(dirty) for frames\n * where only a few nodes changed layout.\n *\n * The skip is safe because:\n * - Flexily's internal fingerprint caching guarantees identical output for\n * subtrees whose inputs didn't change\n * - If the parent's rect matches, all descendants' rects also match\n * (Flexily computes absolute positions from parent dimensions)\n * - prevLayout and layoutChangedThisFrame (stale epoch, won't match\n * current) all retain correct values\n *\n * @param node The node to process\n * @param parentX Absolute X position of parent\n * @param parentY Absolute Y position of parent\n * @param incrementalSkip When true, skip subtrees where Flexily results match existing boxRect\n */\nfunction propagateLayout(\n node: AgNode,\n parentX: number,\n parentY: number,\n incrementalSkip: boolean,\n): void {\n // Virtual/raw text nodes (no layoutNode) inherit parent's position\n if (!node.layoutNode) {\n // Save previous layout for change detection\n node.prevLayout = node.boxRect\n const rect: Rect = {\n x: parentX,\n y: parentY,\n width: 0,\n height: 0,\n }\n node.boxRect = rect\n // Still recurse to children (virtual text nodes can have raw text children)\n for (const child of node.children) {\n propagateLayout(child, parentX, parentY, incrementalSkip)\n }\n return\n }\n\n // Compute absolute position from Yoga (content-relative)\n const rect: Rect = {\n x: parentX + node.layoutNode.getComputedLeft(),\n y: parentY + node.layoutNode.getComputedTop(),\n width: node.layoutNode.getComputedWidth(),\n height: node.layoutNode.getComputedHeight(),\n }\n\n // Container-level layout skip: if incremental mode is enabled and this\n // node's Flexily-computed rect matches the existing boxRect, the entire\n // subtree is unchanged. Skip propagation — all descendants retain correct\n // prevLayout, boxRect, and layoutChangedThisFrame (stale epoch) from the\n // previous frame.\n //\n // This check is O(1) per node (4 number comparisons + 1 epoch check) and\n // prunes entire subtrees, converting propagateLayout from O(N) to O(changed).\n // Note: prevLayout is already synced to boxRect by syncPrevLayout() at\n // the end of the previous render pass, so skipping is safe.\n //\n // subtreeDirtyEpoch guard: even when this node's rect is unchanged, a\n // descendant may need processing (e.g., new child mounted via appendChild).\n // The reconciler's markSubtreeDirty propagates the current epoch upward,\n // so checking subtreeDirtyEpoch ensures we don't skip over dirty descendants.\n if (\n incrementalSkip &&\n node.boxRect &&\n !isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT) &&\n !isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT)\n ) {\n if (\n rect.x === node.boxRect.x &&\n rect.y === node.boxRect.y &&\n rect.width === node.boxRect.width &&\n rect.height === node.boxRect.height\n ) {\n return\n }\n }\n\n // Save previous layout for change detection (must happen AFTER the skip\n // check above — skipped nodes don't need prevLayout updated since\n // syncPrevLayout already set prevLayout = boxRect after the previous frame)\n node.prevLayout = node.boxRect\n node.boxRect = rect\n\n // Set authoritative \"layout changed this frame\" epoch stamp.\n // Unlike !rectEqual(prevLayout, boxRect) which becomes stale when\n // layout phase skips on subsequent frames, this epoch is explicitly set\n // each time propagateLayout runs and expires when the render epoch advances.\n const layoutDidChange = !!(node.prevLayout && !rectEqual(node.prevLayout, node.boxRect))\n node.layoutChangedThisFrame = layoutDidChange ? getRenderEpoch() : INITIAL_EPOCH\n\n // STRICT invariant: if layoutChangedThisFrame is current epoch, prevLayout must differ from boxRect.\n // This validates that the flag is consistent with the actual rect comparison. A violation\n // would mean the flag is set spuriously, causing unnecessary re-renders and cascade propagation.\n if (process?.env?.SILVERY_STRICT && isCurrentEpoch(node.layoutChangedThisFrame)) {\n if (rectEqual(node.prevLayout, node.boxRect)) {\n const props = node.props as BoxProps\n throw new Error(\n `[SILVERY_STRICT] layoutChangedThisFrame=true but prevLayout equals boxRect ` +\n `(node: ${props.id ?? node.type}, rect: ${JSON.stringify(node.boxRect)})`,\n )\n }\n }\n\n // When layout changes, mark ancestors subtreeDirty so renderPhase doesn't\n // fast-path skip them. Without this, a deeply nested node whose dimensions\n // change (e.g., width 3→4) would never be re-rendered because all ancestors\n // appear clean — their own layout didn't change, just a descendant's did.\n if (isCurrentEpoch(node.layoutChangedThisFrame)) {\n const epoch = getRenderEpoch()\n let ancestor = node.parent\n while (ancestor && !isDirty(ancestor.dirtyBits, ancestor.dirtyEpoch, SUBTREE_BIT)) {\n if (ancestor.dirtyEpoch !== epoch) {\n ancestor.dirtyBits = SUBTREE_BIT\n ancestor.dirtyEpoch = epoch\n } else {\n ancestor.dirtyBits |= SUBTREE_BIT\n }\n ancestor = ancestor.parent\n }\n }\n\n // Recurse to children\n for (const child of node.children) {\n propagateLayout(child, rect.x, rect.y, incrementalSkip)\n }\n\n // Cache cascade inputs that render-phase would otherwise compute via tree walks.\n // Both checks require children to have finalized layoutChangedThisFrame, boxRect,\n // prevLayout, childrenDirtyEpoch, and subtreeDirtyEpoch — all set above.\n // Guard: only compute when subtreeDirty (matches buildCascadeInputs guard).\n if (isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT) && node.children.length > 0) {\n const epoch = getRenderEpoch()\n\n // absoluteChildMutated: check direct children for absolute-positioned nodes\n // that had structural changes (children mount/unmount/reorder, layout change,\n // child position shift).\n const absChild = _hasAbsoluteChildMutated(node.children)\n\n // descendantOverflowChanged: recursive check for descendants whose prevLayout\n // extended beyond THIS node's rect and had layoutChangedThisFrame.\n const descOverflow = _hasDescendantOverflowChanged(node, rect)\n\n // Set or clear the layout-phase bits\n let bits = node.dirtyBits\n if (absChild) bits |= ABS_CHILD_BIT\n else bits &= ~ABS_CHILD_BIT\n if (descOverflow) bits |= DESC_OVERFLOW_BIT\n else bits &= ~DESC_OVERFLOW_BIT\n node.dirtyBits = bits\n node.dirtyEpoch = epoch\n } else {\n // Clear layout-phase bits (keep reconciler bits intact)\n if (node.dirtyEpoch === getRenderEpoch()) {\n node.dirtyBits &= ~(ABS_CHILD_BIT | DESC_OVERFLOW_BIT)\n }\n }\n}\n\n/**\n * Lightweight cascade input caching when the layout phase skips.\n *\n * When no layout nodes are dirty and dimensions haven't changed,\n * `layoutPhase` returns early and `propagateLayout` never runs.\n * But structural changes (absolute child mount/unmount, descendant overflow)\n * still need cascade input bits (ABS_CHILD_BIT, DESC_OVERFLOW_BIT) to be\n * computed for the render phase.\n *\n * This traversal follows only subtreeDirty paths (O(changed) not O(N))\n * and computes the same cascade inputs as propagateLayout's caching block.\n * No layout changes, no prevLayout updates, no layoutChangedThisFrame.\n */\nfunction propagateCascadeInputs(node: AgNode): void {\n if (!isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT)) return\n if (!node.children || node.children.length === 0) return\n\n // Recurse into dirty children first (they need their own cascade inputs)\n for (const child of node.children) {\n if (isDirty(child.dirtyBits, child.dirtyEpoch, SUBTREE_BIT)) {\n propagateCascadeInputs(child)\n }\n }\n\n // Compute cascade inputs for this node (same logic as in propagateLayout)\n const epoch = getRenderEpoch()\n const absChild = _hasAbsoluteChildMutated(node.children)\n const descOverflow = node.boxRect ? _hasDescendantOverflowChanged(node, node.boxRect) : false\n\n let bits = node.dirtyBits\n if (absChild) bits |= ABS_CHILD_BIT\n else bits &= ~ABS_CHILD_BIT\n if (descOverflow) bits |= DESC_OVERFLOW_BIT\n else bits &= ~DESC_OVERFLOW_BIT\n node.dirtyBits = bits\n node.dirtyEpoch = epoch\n}\n\n/**\n * Check if any direct child is position=\"absolute\" and had structural changes.\n */\nfunction _hasAbsoluteChildMutated(children: readonly AgNode[]): boolean {\n for (const child of children) {\n const cp = child.props as BoxProps\n if (\n cp.position === \"absolute\" &&\n (isDirty(child.dirtyBits, child.dirtyEpoch, CHILDREN_BIT) ||\n isCurrentEpoch(child.layoutChangedThisFrame) ||\n _hasChildPositionChanged(child))\n ) {\n return true\n }\n }\n return false\n}\n\n/**\n * Check if any child's position changed (boxRect vs prevLayout).\n */\nfunction _hasChildPositionChanged(node: AgNode): boolean {\n for (const child of node.children) {\n if (child.boxRect && child.prevLayout) {\n if (child.boxRect.x !== child.prevLayout.x || child.boxRect.y !== child.prevLayout.y) {\n return true\n }\n }\n }\n return false\n}\n\n/**\n * Check if any descendant was overflowing THIS node's rect and had its layout change.\n * Recursive: follows subtreeDirty paths for efficiency.\n */\nfunction _hasDescendantOverflowChanged(node: AgNode, rect: Rect): boolean {\n return _checkDescendantOverflow(\n node.children,\n rect.x,\n rect.y,\n rect.x + rect.width,\n rect.y + rect.height,\n )\n}\n\nfunction _checkDescendantOverflow(\n children: readonly AgNode[],\n nodeLeft: number,\n nodeTop: number,\n nodeRight: number,\n nodeBottom: number,\n): boolean {\n for (const child of children) {\n if (child.prevLayout && isCurrentEpoch(child.layoutChangedThisFrame)) {\n const prev = child.prevLayout\n if (\n prev.x + prev.width > nodeRight ||\n prev.y + prev.height > nodeBottom ||\n prev.x < nodeLeft ||\n prev.y < nodeTop\n ) {\n return true\n }\n }\n if (isDirty(child.dirtyBits, child.dirtyEpoch, SUBTREE_BIT) && child.children !== undefined) {\n if (_checkDescendantOverflow(child.children, nodeLeft, nodeTop, nodeRight, nodeBottom)) {\n return true\n }\n }\n }\n return false\n}\n\n/**\n * Notify all layout subscribers of dimension changes.\n *\n * Called by the pipeline AFTER scrollrectPhase completes,\n * so useScrollRect can read correct screen positions.\n *\n * Notifies when EITHER boxRect, scrollRect, or screenRect changed.\n * scrollRect can change from scroll offset changes even when\n * boxRect stays the same — subscribers (like useScrollRect)\n * need notification in both cases. screenRect can change from sticky\n * offset changes even when scrollRect stays the same.\n */\nexport function notifyLayoutSubscribers(node: AgNode): void {\n // Notify if content rect, screen rect, or render rect changed\n const contentChanged = !rectEqual(node.prevLayout, node.boxRect)\n const screenChanged = !rectEqual(node.prevScrollRect, node.scrollRect)\n const renderChanged = !rectEqual(node.prevScreenRect, node.screenRect)\n // Sync rect values into alien-signals (for signal-based hooks).\n // Always sync — even when no rect changed — because the signal may\n // have been created after the last sync (lazy initialization).\n syncRectSignals(node)\n\n // Recurse to children\n for (const child of node.children) {\n notifyLayoutSubscribers(child)\n }\n}\n\n// ============================================================================\n// STRICT Layout Overflow Invariant\n// ============================================================================\n\n/**\n * Verify that no child's boxRect.width exceeds its parent's inner content width.\n *\n * This catches fit-content/snug-content bugs at the source — any measure-phase\n * or correction-pass error fires immediately.\n *\n * - SILVERY_STRICT=1: console.warn on violation\n * - SILVERY_STRICT=2: throw on violation\n *\n * Exceptions:\n * - Parent has overflow: \"scroll\" or \"hidden\" (overflow is allowed)\n * - Child has position: \"absolute\" (absolute nodes can overflow)\n */\nexport function strictLayoutOverflowCheck(root: AgNode): void {\n const strict = process?.env?.SILVERY_STRICT\n if (!strict) return\n\n const shouldThrow = strict === \"2\"\n\n function walk(node: AgNode): void {\n for (const child of node.children) {\n if (child.boxRect && node.boxRect) {\n const childProps = child.props as BoxProps\n\n // Skip absolute-positioned children — they're allowed to overflow\n if (childProps.position === \"absolute\") {\n walk(child)\n continue\n }\n\n const parentProps = node.props as BoxProps\n\n // Skip if parent allows overflow (scroll or hidden)\n if (parentProps.overflow === \"scroll\" || parentProps.overflow === \"hidden\") {\n walk(child)\n continue\n }\n\n // Compute parent's inner content width\n const border = parentProps.borderStyle\n ? getBorderSize(parentProps)\n : { top: 0, bottom: 0, left: 0, right: 0 }\n const padding = getPadding(parentProps)\n const parentInnerWidth =\n node.boxRect.width - padding.left - padding.right - border.left - border.right\n\n if (child.boxRect.width > parentInnerWidth) {\n const childId = (childProps as any).id ?? child.type\n const parentId = (parentProps as any).id ?? node.type\n const msg =\n `[SILVERY_STRICT] Layout overflow: child \"${childId}\" width ${child.boxRect.width} ` +\n `exceeds parent \"${parentId}\" inner width ${parentInnerWidth} ` +\n `(parent box: ${node.boxRect.width}, border: ${border.left}+${border.right}, padding: ${padding.left}+${padding.right})`\n\n if (shouldThrow) {\n throw new Error(msg)\n } else {\n console.warn(msg)\n }\n }\n }\n\n walk(child)\n }\n }\n\n walk(root)\n}\n\n// Re-export from types\nexport { rectEqual } from \"@silvery/ag/types\"\n\n// ============================================================================\n// Phase 2.5: Scroll Phase (for overflow='scroll' containers)\n// ============================================================================\n\n/**\n * Options for scrollPhase.\n */\nexport interface ScrollPhaseOptions {\n /**\n * Skip state updates (for fresh render comparisons).\n * When true, calculates scroll positions but doesn't mutate node.scrollState.\n * Default: false\n */\n skipStateUpdates?: boolean\n}\n\n/**\n * Calculate scroll state for all overflow='scroll' containers.\n *\n * This phase runs after layout to determine which children are visible\n * within each scrollable container.\n */\nexport function scrollPhase(root: AgNode, options: ScrollPhaseOptions = {}): void {\n const { skipStateUpdates = false } = options\n traverseTree(root, (node) => {\n const props = node.props as BoxProps\n if (props.overflow !== \"scroll\") return\n\n // Calculate scroll state for this container\n calculateScrollState(node, props, skipStateUpdates)\n })\n}\n\n/**\n * Snap scroll offset so the first visible child (after the top overflow\n * indicator's reserved row) aligns with a child-top boundary.\n *\n * When scrolling \"down to show the target at the bottom,\" the raw offset\n * `target.bottom - effectiveHeight` assumes the entire viewport above the\n * bottom-indicator is usable content. But the TOP overflow indicator also\n * consumes a row when `hiddenAbove > 0`, rendering at viewport row 0 on top\n * of whatever child starts there. If that row is a card's top border, the\n * border is overwritten — users see a \"headless\" card and perceive the\n * column as \"gotten shorter\" (see km-tui `column-top-disappears`).\n *\n * This snap shifts the offset so `offset + 1 === firstFullyVisibleChild.top`:\n * the top-indicator row coincides with the 1-row gap ABOVE the first child,\n * not with that child's content. When children have heterogeneous heights,\n * this means moving the viewport DOWN by a few rows (so an earlier, shorter\n * child scrolls fully off-screen and the next child starts cleanly).\n *\n * Guardrails:\n * - Never snap past the target's own top (keeps the target visible).\n * - If no suitable boundary exists above `rawOffset + 1` and ≤ `target.top`,\n * returns `rawOffset` unchanged (scroll behaves as before).\n * - Returns 0 unchanged — offset=0 means no top indicator, no conflict.\n */\nfunction snapOffsetToChildTop(\n rawOffset: number,\n childPositions: { child: AgNode; top: number; bottom: number; index: number; isSticky: boolean }[],\n target: { top: number; bottom: number; index: number },\n): number {\n if (rawOffset <= 0) return rawOffset\n // Desired: first-visible-child.top === offset + 1 (leaving row 0 for indicator).\n // Find the smallest child-top in the range (rawOffset + 1, target.top] and\n // set offset = that child-top - 1. This places the child-top one row below\n // the viewport top — exactly the row the top indicator occupies.\n let bestChildTop = -1\n for (const cp of childPositions) {\n if (cp.isSticky) continue\n if (cp.top === cp.bottom) continue // skip zero-height\n if (cp.top > rawOffset && cp.top <= target.top) {\n if (bestChildTop === -1 || cp.top < bestChildTop) {\n bestChildTop = cp.top\n }\n }\n }\n if (bestChildTop === -1) return rawOffset\n // Reserve one row for the top indicator: scrollOffset = childTop - 1.\n // This keeps the child at viewport row 1 (just below the indicator row).\n const snapped = bestChildTop - 1\n // Safety: never reduce offset below rawOffset (would hide target.bottom).\n return snapped >= rawOffset ? snapped : rawOffset\n}\n\n/**\n * Calculate scroll state for a single scrollable container.\n */\nfunction calculateScrollState(node: AgNode, props: BoxProps, skipStateUpdates: boolean): void {\n const layout = node.boxRect\n if (!layout || !node.layoutNode) return\n\n // Calculate viewport (container minus borders/padding)\n const border = props.borderStyle ? getBorderSize(props) : { top: 0, bottom: 0, left: 0, right: 0 }\n const padding = getPadding(props)\n\n const rawViewportHeight =\n layout.height - border.top - border.bottom - padding.top - padding.bottom\n\n // Calculate total content height and child positions\n let contentHeight = 0\n const childPositions: {\n child: AgNode\n top: number\n bottom: number\n index: number\n isSticky: boolean\n stickyTop?: number\n stickyBottom?: number\n }[] = []\n\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i]!\n if (!child.layoutNode || !child.boxRect) continue\n\n const childTop = child.boxRect.y - layout.y - border.top - padding.top\n const childBottom = childTop + child.boxRect.height\n const childProps = child.props as BoxProps\n\n childPositions.push({\n child: child!,\n top: childTop,\n bottom: childBottom,\n index: i,\n isSticky: childProps.position === \"sticky\",\n stickyTop: childProps.stickyTop,\n stickyBottom: childProps.stickyBottom,\n })\n\n contentHeight = Math.max(contentHeight, childBottom)\n }\n\n const viewportHeight = rawViewportHeight\n\n // Reserve 1 row at the bottom for the overflow indicator when:\n // 1. Container uses borderless overflow indicators (overflowIndicator prop)\n // 2. Content exceeds viewport (there will be hidden items below or above)\n // This ensures the indicator doesn't overlay the last visible child's content.\n const showBorderlessIndicator = props.overflowIndicator === true && !props.borderStyle\n const hasOverflow = contentHeight > rawViewportHeight\n const indicatorReserve = showBorderlessIndicator && hasOverflow ? 1 : 0\n\n // Calculate scroll offset based on scrollTo prop\n // Use \"ensure visible\" scrolling: only scroll when target would be off-screen\n // Preserve previous offset when target is already visible\n //\n // Priority:\n // 1. If scrollTo is defined: use edge-based scrolling to ensure child is visible\n // 2. If scrollOffset is defined: use explicit offset (for frozen scroll state)\n // 3. Otherwise: use previous offset or default to 0\n const prevOffset = node.scrollState?.offset\n const explicitOffset = props.scrollOffset\n let scrollOffset = explicitOffset ?? prevOffset ?? 0\n const scrollTo = props.scrollTo\n\n if (scrollTo !== undefined && scrollTo >= 0 && scrollTo < childPositions.length) {\n // Find the target child\n const target = childPositions.find((c) => c.index === scrollTo)\n if (target) {\n // Calculate current visible range, accounting for indicator reserve.\n // The effective visible height is reduced by indicatorReserve so the\n // scrollTo target is fully visible ABOVE the overflow indicator row.\n const effectiveHeight = viewportHeight - indicatorReserve\n const visibleTop = scrollOffset\n const visibleBottom = scrollOffset + effectiveHeight\n\n // Only scroll if target is outside visible range\n if (target.top < visibleTop) {\n // Target is above viewport - scroll up to show it at top.\n //\n // Reserve one row for the TOP overflow indicator so it doesn't\n // overwrite the target's top border. When target.top > 0 there\n // will be a top indicator (target isn't the first child, so items\n // above exist). Shifting scrollOffset one row up places the\n // indicator at viewport row 0 (over the preceding card's bottom\n // row — typically its bottom border) and leaves target.top at\n // viewport row 1, rendering its top border cleanly.\n scrollOffset = target.top > 0 ? target.top - 1 : 0\n } else if (target.bottom > visibleBottom) {\n // Target is below viewport - scroll down to show it at bottom.\n //\n // Snap to a child-top boundary when a pixel-exact offset would land\n // inside a child (clipping its top border). Without snapping, mixed-\n // height children produce a \"headless card\" at the viewport top —\n // users perceive it as \"column got shorter\" (see km-tui bug\n // `column-top-disappears`). Snap DOWN (toward a larger offset) so the\n // target remains visible at the bottom of the viewport.\n const rawOffset = target.bottom - effectiveHeight\n scrollOffset = snapOffsetToChildTop(rawOffset, childPositions, target)\n }\n // Otherwise, keep current scroll position (target is visible)\n }\n }\n\n // Clamp to valid range — applies to both scrollTo and explicit scrollOffset.\n // Without this, explicit scrollOffset can scroll past content into blank space.\n scrollOffset = Math.max(0, scrollOffset)\n scrollOffset = Math.min(scrollOffset, Math.max(0, contentHeight - viewportHeight))\n\n // Determine visible children.\n // When the overflow indicator reserves a row (indicatorReserve=1), reduce the\n // visible bottom by 1 so the indicator has its own row after the last visible child.\n const visibleTop = scrollOffset\n const visibleBottom = scrollOffset + viewportHeight - indicatorReserve\n\n let firstVisible = -1\n let lastVisible = -1\n let hiddenAbove = 0\n let hiddenBelow = 0\n\n for (const cp of childPositions) {\n // Sticky children are always considered \"visible\" for rendering purposes\n if (cp.isSticky) {\n if (firstVisible === -1) firstVisible = cp.index\n lastVisible = Math.max(lastVisible, cp.index)\n continue\n }\n\n // Skip zero-height children from hidden counts — they have no visual\n // presence and would produce spurious overflow indicators (e.g., a\n // zero-height child at position 0 has top=0, bottom=0, and 0 <= 0\n // would incorrectly count it as \"hidden above\").\n if (cp.top === cp.bottom) {\n continue\n }\n\n if (cp.bottom <= visibleTop) {\n hiddenAbove++\n } else if (cp.top >= visibleBottom) {\n hiddenBelow++\n } else if (cp.top < visibleTop) {\n // Child is partially visible at top — render it (clipped by scroll\n // container's clip bounds) so partial content is visible instead of blank space\n if (firstVisible === -1) firstVisible = cp.index\n lastVisible = Math.max(lastVisible, cp.index)\n } else if (cp.bottom > visibleBottom) {\n // Child is partially visible at bottom — render it (clipped by scroll\n // container's clip bounds) so partial content is visible instead of blank space.\n // When indicatorReserve is active, this child extends past the reserved row,\n // but we still render it — the overflow indicator renders AFTER children and\n // overlays the appropriate row.\n if (firstVisible === -1) firstVisible = cp.index\n lastVisible = cp.index\n // When indicator reserve is active, count partially visible bottom children\n // in hiddenBelow so the indicator shows the correct count.\n if (indicatorReserve > 0) {\n hiddenBelow++\n }\n } else {\n // This child is fully visible within the viewport\n if (firstVisible === -1) firstVisible = cp.index\n lastVisible = cp.index\n }\n }\n\n // Calculate sticky children render positions\n const stickyChildren: NonNullable<AgNode[\"scrollState\"]>[\"stickyChildren\"] = []\n\n for (const cp of childPositions) {\n if (!cp.isSticky) continue\n\n const childHeight = cp.bottom - cp.top\n const stickyTop = cp.stickyTop ?? 0\n const stickyBottom = cp.stickyBottom\n\n // Natural position: where it would be without sticking (relative to viewport)\n const naturalRenderY = cp.top - scrollOffset\n\n let renderOffset: number\n\n if (stickyBottom !== undefined) {\n // Sticky to bottom: element pins to bottom edge when scrolled past\n const bottomPinPosition = viewportHeight - stickyBottom - childHeight\n // Use natural position if it's below the pin point, otherwise pin\n renderOffset = Math.min(naturalRenderY, bottomPinPosition)\n } else if (naturalRenderY >= stickyTop) {\n // Child hasn't reached stick point: use natural position\n renderOffset = naturalRenderY\n } else if (childHeight > viewportHeight) {\n // Oversized sticky-top child scrolled past stick point: progressively\n // scroll the child so its bottom aligns with viewport bottom when\n // scrolled far enough. Clamp between bottom-align and stick point.\n renderOffset = Math.max(viewportHeight - childHeight, naturalRenderY)\n } else {\n // Normal sticky-top child scrolled past stick point: pin at stickyTop\n renderOffset = stickyTop\n }\n\n // Clamp to viewport bounds — only when element is actually sticking.\n // Elements at their natural position below the viewport must NOT be\n // pulled up into view by clamping (that would overwrite other children's\n // pixels, corrupting incremental rendering's buffer shift).\n const isSticking = renderOffset !== naturalRenderY\n if (isSticking) {\n if (childHeight > viewportHeight) {\n renderOffset = Math.max(viewportHeight - childHeight, renderOffset)\n } else {\n renderOffset = Math.max(0, Math.min(renderOffset, viewportHeight - childHeight))\n }\n }\n\n // Skip off-screen sticky children — they're not visible and shouldn't\n // be rendered (would corrupt other children's pixels in the buffer).\n if (renderOffset + childHeight <= 0 || renderOffset >= viewportHeight) continue\n\n stickyChildren.push({\n index: cp.index,\n renderOffset,\n naturalTop: cp.top,\n height: childHeight,\n })\n }\n\n // Skip state updates for fresh render comparisons (SILVERY_STRICT)\n if (skipStateUpdates) return\n\n // Track previous visible range for incremental rendering\n const prevFirstVisible = node.scrollState?.firstVisibleChild ?? firstVisible\n const prevLastVisible = node.scrollState?.lastVisibleChild ?? lastVisible\n\n // Mark node dirty if scroll offset or visible range changed (for incremental rendering)\n // Without this, renderPhase would skip the container and children would\n // remain at their old pixel positions in the cloned buffer\n if (\n scrollOffset !== prevOffset ||\n firstVisible !== prevFirstVisible ||\n lastVisible !== prevLastVisible\n ) {\n const epoch = getRenderEpoch()\n if (node.dirtyEpoch !== epoch) {\n node.dirtyBits = SUBTREE_BIT\n node.dirtyEpoch = epoch\n } else {\n node.dirtyBits |= SUBTREE_BIT\n }\n }\n\n // Store scroll state (preserve previous offset and visible range for incremental rendering)\n node.scrollState = {\n offset: scrollOffset,\n prevOffset: prevOffset ?? scrollOffset,\n contentHeight,\n viewportHeight,\n firstVisibleChild: firstVisible,\n lastVisibleChild: lastVisible,\n prevFirstVisibleChild: prevFirstVisible,\n prevLastVisibleChild: prevLastVisible,\n hiddenAbove,\n hiddenBelow,\n stickyChildren: stickyChildren.length > 0 ? stickyChildren : undefined,\n }\n}\n\n// ============================================================================\n// Phase 2.55: Sticky Phase (for non-scroll containers with sticky children)\n// ============================================================================\n\n/**\n * Compute sticky offsets for non-scroll containers that have sticky children.\n *\n * Scroll containers handle their own sticky logic in calculateScrollState().\n * This phase handles the remaining case: parents that are NOT overflow=\"scroll\"\n * but still contain position=\"sticky\" children with stickyBottom.\n *\n * For non-scroll containers, sticky means: pin the child to the parent's bottom\n * edge when content is shorter than the parent. When content fills the parent,\n * the child stays at its natural position.\n */\nexport function stickyPhase(root: AgNode): void {\n traverseTree(root, (node) => {\n const props = node.props as BoxProps\n // Skip scroll containers — they handle sticky in scrollPhase\n if (props.overflow === \"scroll\") return\n\n // Check if any children are sticky with stickyBottom\n let hasStickyChildren = false\n for (const child of node.children) {\n const childProps = child.props as BoxProps\n if (childProps.position === \"sticky\" && childProps.stickyBottom !== undefined) {\n hasStickyChildren = true\n break\n }\n }\n\n if (!hasStickyChildren) {\n // Clear stale data if previously had sticky children\n if (node.stickyChildren !== undefined) {\n node.stickyChildren = undefined\n const epoch = getRenderEpoch()\n if (node.dirtyEpoch !== epoch) {\n node.dirtyBits = SUBTREE_BIT\n node.dirtyEpoch = epoch\n } else {\n node.dirtyBits |= SUBTREE_BIT\n }\n }\n return\n }\n\n const layout = node.boxRect\n if (!layout || !node.layoutNode) return\n\n const border = props.borderStyle\n ? getBorderSize(props)\n : { top: 0, bottom: 0, left: 0, right: 0 }\n const padding = getPadding(props)\n const parentContentHeight =\n layout.height - border.top - border.bottom - padding.top - padding.bottom\n\n const newStickyChildren: NonNullable<AgNode[\"stickyChildren\"]> = []\n\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i]!\n const childProps = child.props as BoxProps\n if (childProps.position !== \"sticky\") continue\n if (childProps.stickyBottom === undefined) continue\n\n if (!child.boxRect) continue\n\n // Natural position relative to parent content area\n const naturalY = child.boxRect.y - layout.y - border.top - padding.top\n const childHeight = child.boxRect.height\n const stickyBottom = childProps.stickyBottom\n\n // Pin position: where the child would be if pinned to parent bottom\n const bottomPin = parentContentHeight - stickyBottom - childHeight\n // Child pins to bottom when content is short (naturalY < bottomPin)\n // Stays at natural position when content fills parent (naturalY >= bottomPin)\n const renderOffset = Math.max(naturalY, bottomPin)\n\n newStickyChildren.push({\n index: i,\n renderOffset,\n naturalTop: naturalY,\n height: childHeight,\n })\n }\n\n // Compare with previous value to detect changes\n const prev = node.stickyChildren\n const next = newStickyChildren.length > 0 ? newStickyChildren : undefined\n\n const changed = !stickyChildrenEqual(prev, next)\n node.stickyChildren = next\n\n if (changed) {\n const epoch = getRenderEpoch()\n if (node.dirtyEpoch !== epoch) {\n node.dirtyBits = SUBTREE_BIT\n node.dirtyEpoch = epoch\n } else {\n node.dirtyBits |= SUBTREE_BIT\n }\n }\n })\n}\n\n/**\n * Compare two stickyChildren arrays for equality.\n */\nfunction stickyChildrenEqual(a: AgNode[\"stickyChildren\"], b: AgNode[\"stickyChildren\"]): boolean {\n if (a === b) return true\n if (!a || !b) return false\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n const ai = a[i]!\n const bi = b[i]!\n if (\n ai.index !== bi.index ||\n ai.renderOffset !== bi.renderOffset ||\n ai.naturalTop !== bi.naturalTop ||\n ai.height !== bi.height\n ) {\n return false\n }\n }\n return true\n}\n\n/**\n * Traverse tree in depth-first order.\n */\nfunction traverseTree(node: AgNode, callback: (node: AgNode) => void): void {\n callback(node)\n for (const child of node.children) {\n traverseTree(child, callback)\n }\n}\n\n// ============================================================================\n// Phase 2.6: Screen Rect Phase\n// ============================================================================\n\n/**\n * Calculate screen-relative positions for all nodes.\n *\n * This phase runs after scroll phase to compute where each node actually\n * appears on the terminal screen, accounting for all ancestor scroll offsets.\n *\n * Also computes `screenRect` which accounts for sticky render offsets.\n * For non-sticky nodes, screenRect === scrollRect. For sticky nodes,\n * screenRect reflects the actual pixel position where the node is painted.\n *\n * Screen position = content position - sum of ancestor scroll offsets\n */\nexport function scrollrectPhase(root: AgNode): void {\n propagateScrollRect(root, 0)\n}\n\n/**\n * Fast path for scrollrectPhase when no scroll containers or sticky nodes exist.\n *\n * When there are no scroll containers and no sticky nodes, ancestorScrollOffset\n * is always 0, so scrollRect === boxRect and screenRect === scrollRect. This\n * avoids the overhead of accumulating scroll offsets through the tree.\n */\nexport function scrollrectPhaseSimple(root: AgNode): void {\n propagateScrollRectSimple(root)\n}\n\n/**\n * Propagate screen-relative positions through the tree.\n *\n * @param node The node to process\n * @param ancestorScrollOffset Sum of all ancestor scroll offsets\n */\nfunction propagateScrollRect(node: AgNode, ancestorScrollOffset: number): void {\n // Save previous rects for change detection in notifyLayoutSubscribers\n node.prevScrollRect = node.scrollRect\n node.prevScreenRect = node.screenRect\n\n const content = node.boxRect\n if (!content) {\n node.scrollRect = null\n node.screenRect = null\n for (const child of node.children) {\n propagateScrollRect(child, ancestorScrollOffset)\n }\n return\n }\n\n // Compute screen position by subtracting ancestor scroll offsets\n node.scrollRect = {\n x: content.x,\n y: content.y - ancestorScrollOffset,\n width: content.width,\n height: content.height,\n }\n\n // Default: screenRect equals scrollRect (overridden below for sticky nodes)\n node.screenRect = node.scrollRect\n\n // If this node is a scroll container, add its offset for children\n const scrollOffset = node.scrollState?.offset ?? 0\n const childScrollOffset = ancestorScrollOffset + scrollOffset\n\n // Compute screenRect for sticky children.\n // Sticky nodes render at a computed offset instead of their layout position.\n // The offset data lives on the parent (this node) in either scrollState.stickyChildren\n // (for scroll containers) or node.stickyChildren (for non-scroll parents).\n computeStickyScreenRects(node)\n\n // Recurse to children\n for (const child of node.children) {\n propagateScrollRect(child, childScrollOffset)\n }\n}\n\n/**\n * Compute screenRect for sticky children of a node.\n *\n * For sticky children, the actual render position differs from the layout\n * position (scrollRect). The renderOffset from the scroll/sticky phase\n * determines where pixels are actually painted. This function sets\n * screenRect on those children to reflect the true screen position.\n *\n * @param parent The parent node whose sticky children need screenRect computation\n */\nfunction computeStickyScreenRects(parent: AgNode): void {\n // Determine which sticky children list to use\n const stickyList = parent.scrollState?.stickyChildren ?? parent.stickyChildren\n if (!stickyList || stickyList.length === 0) return\n\n // Calculate the parent's content area origin on screen (inside border/padding)\n const parentScrollRect = parent.scrollRect\n if (!parentScrollRect) return\n\n const props = parent.props as BoxProps\n const border = props.borderStyle ? getBorderSize(props) : { top: 0, bottom: 0, left: 0, right: 0 }\n const padding = getPadding(props)\n const contentOriginY = parentScrollRect.y + border.top + padding.top\n\n for (const sticky of stickyList) {\n const child = parent.children[sticky.index]\n if (!child?.scrollRect) continue\n\n // screenRect has the same x, width, height as scrollRect,\n // but Y is adjusted to the sticky render position\n child.screenRect = {\n x: child.scrollRect.x,\n y: contentOriginY + sticky.renderOffset,\n width: child.scrollRect.width,\n height: child.scrollRect.height,\n }\n }\n}\n\n// ============================================================================\n// Simple scrollRect propagation (no scroll/sticky)\n// ============================================================================\n\n/**\n * Simple scrollRect propagation for trees without scroll containers or sticky nodes.\n * When ancestorScrollOffset is always 0, scrollRect === boxRect and screenRect === scrollRect.\n * Saves the overhead of accumulating scroll offsets and computing sticky screen rects.\n */\nfunction propagateScrollRectSimple(node: AgNode): void {\n node.prevScrollRect = node.scrollRect\n node.prevScreenRect = node.screenRect\n\n const content = node.boxRect\n if (!content) {\n node.scrollRect = null\n node.screenRect = null\n for (const child of node.children) {\n propagateScrollRectSimple(child)\n }\n return\n }\n\n // No scroll offset — scrollRect equals boxRect\n node.scrollRect = {\n x: content.x,\n y: content.y,\n width: content.width,\n height: content.height,\n }\n node.screenRect = node.scrollRect\n\n for (const child of node.children) {\n propagateScrollRectSimple(child)\n }\n}\n\n// ============================================================================\n// Feature Detection\n// ============================================================================\n\n/**\n * Pipeline feature flags — tracks which optional phases the tree needs.\n *\n * Flags are one-way: once set to true, they stay true for the lifetime\n * of the Ag instance. This ensures that if a component dynamically mounts\n * a scroll container or sticky child, the phase starts running immediately\n * and never gets skipped again.\n */\nexport interface PipelineFeatures {\n /** Tree contains at least one `overflow=\"scroll\"` node. */\n hasScroll: boolean\n /** Tree contains at least one `position=\"sticky\"` node. */\n hasSticky: boolean\n}\n\n/**\n * Scan the tree for features that require optional pipeline phases.\n *\n * Returns feature flags. This is called on every layout pass so newly\n * mounted components are detected. The caller should merge flags with\n * one-way semantics (false → true, never true → false).\n */\nexport function detectPipelineFeatures(root: AgNode): PipelineFeatures {\n let hasScroll = false\n let hasSticky = false\n\n function scan(node: AgNode): void {\n const props = node.props as BoxProps\n if (props.overflow === \"scroll\") hasScroll = true\n if (props.position === \"sticky\") hasSticky = true\n // Early exit if both features detected\n if (hasScroll && hasSticky) return\n for (const child of node.children) {\n scan(child)\n if (hasScroll && hasSticky) return\n }\n }\n\n scan(root)\n return { hasScroll, hasSticky }\n}\n","/**\n * Active theme state — module-level fallback for pipeline access.\n *\n * Theme flows through the AgNode tree via `<Box theme={}>` props (set by\n * ThemeProvider in @silvery/ag-react) and the pushContextTheme/popContextTheme\n * cascade in render-phase.ts. `getActiveTheme()` reads the nearest stack entry,\n * falling back to ansi16DarkTheme for code paths that render without a\n * ThemeProvider (bare tests, xterm renderer before wrap).\n *\n * Usage of standalone resolveThemeColor(token, theme) is preferred for callers\n * that have a Theme reference available.\n *\n * `setActiveTheme()` was removed in R2 (km-silvery.theme-v3-r2-agnode-cascade);\n * the no-op stub is gone too. Callers should wrap in ThemeProvider.\n */\n\nimport type { Theme } from \"@silvery/ansi\"\n// The `@silvery/theme` re-export of ansi16DarkTheme is pre-populated with\n// Sterling flat tokens; `@silvery/ansi`'s is not. Bare-test render paths need\n// the flat tokens to resolve `$fg-accent` / `$bg-surface-subtle` etc.\nimport { ansi16DarkTheme } from \"@silvery/theme\"\n\n// ============================================================================\n// Active Theme (fallback only — not set by ThemeProvider)\n// ============================================================================\n\n/**\n * Safe fallback theme. Never mutated — the theme flows via the AgNode tree\n * (Box theme= prop + pushContextTheme/popContextTheme in render-phase.ts).\n * This is only returned by getActiveTheme() when called from a code path that\n * has no pushContextTheme frame on the stack, e.g. a bare test that renders\n * without ThemeProvider.\n *\n * `@silvery/theme`'s `ansi16DarkTheme` ships with Sterling flat tokens baked\n * in, so bare-test render paths resolve `$fg-accent` / `$bg-surface-subtle` /\n * etc. without needing an explicit ThemeProvider.\n */\nconst _activeTheme: Theme = ansi16DarkTheme\n\n/** Get the active theme (fallback to ansi16DarkTheme when no context stack entry exists). */\nexport function getActiveTheme(): Theme {\n return _contextStack.length > 0 ? _contextStack[_contextStack.length - 1]! : _activeTheme\n}\n\n// ============================================================================\n// Active Color Level (tier dispatch)\n// ============================================================================\n\n/**\n * Color tier the render pipeline is targeting.\n *\n * Mirrors `TerminalCaps.colorLevel` but lives in module state for the\n * render-helpers parseColor() / getTextStyle() functions, which don't have\n * access to the OutputContext or React props. Set by the runtime\n * (`createPipeline()` in `@silvery/ag-term/measurer.ts`) before the first\n * render, and updated on cap changes.\n *\n * At `\"none\"` (monochrome), `parseColor(\"$primary\")` returns `null` and\n * `getTextStyle()` injects mono-attrs (bold, dim, italic, underline, inverse,\n * strikethrough) from `DEFAULT_MONO_ATTRS`. See `hub/silvery/design/v10-terminal/theme-system-v2-plan.md#p4`.\n */\nexport type ActiveColorLevel = \"none\" | \"basic\" | \"256\" | \"truecolor\"\n\nlet _activeColorLevel: ActiveColorLevel = \"truecolor\"\n\n/** Set the active color level (called by the runtime based on TerminalCaps). */\nexport function setActiveColorLevel(level: ActiveColorLevel): void {\n _activeColorLevel = level\n}\n\n/** Get the active color level (called by parseColor / getTextStyle in render-helpers). */\nexport function getActiveColorLevel(): ActiveColorLevel {\n return _activeColorLevel\n}\n\n// ============================================================================\n// Context Theme Stack (per-subtree overrides during render phase)\n// ============================================================================\n\n/**\n * Stack of per-subtree theme overrides, pushed/popped during render phase\n * tree walk. When a Box has a `theme` prop, its theme is pushed before\n * rendering children and popped after. getActiveTheme() checks this stack\n * first, falling back to _activeTheme.\n *\n * This enables CSS custom property-like cascading: the nearest ancestor\n * Box with a theme prop determines $token resolution for its subtree.\n * ThemeProvider (in @silvery/ag-react) renders a <Box theme={merged}>\n * wrapper, so its theme is naturally pushed via this mechanism.\n */\nconst _contextStack: Theme[] = []\n\n/** Push a context theme (called by render phase for Box nodes with theme prop). */\nexport function pushContextTheme(theme: Theme): void {\n _contextStack.push(theme)\n}\n\n/** Pop a context theme (called by render phase after processing Box subtree). */\nexport function popContextTheme(): void {\n _contextStack.pop()\n}\n","/**\n * Render Helpers - Pure utility functions for content rendering.\n *\n * Contains:\n * - Color parsing (parseColor)\n * - Border character definitions (getBorderChars)\n * - Style extraction (getTextStyle)\n * - Text width utilities (getTextWidth)\n *\n * Re-exports layout helpers from helpers.ts:\n * - getPadding, getBorderSize\n */\n\nimport { DEFAULT_BG, type Color, type Style, type UnderlineStyle } from \"../buffer\"\nimport { getActiveColorLevel, getActiveTheme } from \"./state\"\nimport { resolveThemeColor } from \"@silvery/ansi\"\nimport { monoAttrsForColorString, type MonoAttr } from \"@silvery/ansi\"\nimport type { BoxProps, TextProps } from \"@silvery/ag/types\"\nimport { displayWidthAnsi } from \"../unicode\"\nimport type { BorderChars, PipelineContext } from \"./types\"\n\n// Re-export shared layout helpers\nexport { getBorderSize, getPadding } from \"./helpers\"\n\n// ============================================================================\n// Color Parsing\n// ============================================================================\n\n// Named colors map to 256-color indices (hoisted to module scope to avoid per-call allocation)\nconst namedColors: Record<string, number> = {\n black: 0,\n red: 1,\n green: 2,\n yellow: 3,\n blue: 4,\n magenta: 5,\n cyan: 6,\n white: 7,\n gray: 8,\n grey: 8,\n blackBright: 8,\n redBright: 9,\n greenBright: 10,\n yellowBright: 11,\n blueBright: 12,\n magentaBright: 13,\n cyanBright: 14,\n whiteBright: 15,\n}\n\n/**\n * Blend two RGB colors in sRGB space.\n * Formula: result = c1 * (1 - t) + c2 * t, where t is 0..1.\n * Returns an RGB object with each channel clamped to 0-255.\n */\nfunction blendColors(\n c1: { r: number; g: number; b: number },\n c2: { r: number; g: number; b: number },\n t: number,\n): { r: number; g: number; b: number } {\n return {\n r: Math.round(c1.r * (1 - t) + c2.r * t),\n g: Math.round(c1.g * (1 - t) + c2.g * t),\n b: Math.round(c1.b * (1 - t) + c2.b * t),\n }\n}\n\n/**\n * Parse color string to Color type.\n * Supports: mix(c1,c2,amount), $token (theme), named colors, hex (#rgb, #rrggbb), rgb(r,g,b)\n */\nexport function parseColor(color: string): Color {\n // Inherit: no color — parent's color flows through (like CSS color: inherit).\n // \"currentColor\" is a CSS synonym — both keywords resolve identically here.\n // For child-cascade purposes, render-phase detects these keywords directly\n // (before parseColor) so the parent's inheritedFg is preserved in children.\n if (color === \"inherit\" || color === \"currentColor\") return null\n\n // Special token: terminal's default background (SGR 49)\n if (color === \"$default\") return DEFAULT_BG\n\n // Mix: blend two colors — mix(color1, color2, amount)\n // Amount can be a percentage (e.g. 50%) or a decimal (e.g. 0.5).\n // Both colors are recursively resolved via parseColor (supports theme tokens, hex, named, etc.).\n // Only blends when both colors resolve to RGB objects; returns null if either is null or an ANSI index.\n if (color.startsWith(\"mix(\") && color.endsWith(\")\")) {\n const inner = color.slice(4, -1)\n // Split on commas, but respect nested parentheses (e.g. rgb(r,g,b) as an argument)\n const args: string[] = []\n let depth = 0\n let start = 0\n for (let i = 0; i < inner.length; i++) {\n if (inner[i] === \"(\") depth++\n else if (inner[i] === \")\") depth--\n else if (inner[i] === \",\" && depth === 0) {\n args.push(inner.slice(start, i).trim())\n start = i + 1\n }\n }\n args.push(inner.slice(start).trim())\n\n if (args.length === 3) {\n const c1 = parseColor(args[0]!)\n const c2 = parseColor(args[1]!)\n const amountStr = args[2]!\n\n // Parse amount: percentage (e.g. \"50%\") or decimal (e.g. \"0.5\")\n let t: number\n if (amountStr.endsWith(\"%\")) {\n t = Number.parseFloat(amountStr.slice(0, -1)) / 100\n } else {\n t = Number.parseFloat(amountStr)\n }\n\n // Only blend RGB objects; ANSI indices (number) and null cannot be blended\n if (\n c1 !== null &&\n c2 !== null &&\n typeof c1 === \"object\" &&\n typeof c2 === \"object\" &&\n !Number.isNaN(t)\n ) {\n return blendColors(c1, c2, Math.max(0, Math.min(1, t)))\n }\n return null\n }\n }\n\n // Future: slash notation for background opacity (e.g. \"$link/10\") is not yet supported.\n // It would require richer return types to carry opacity alongside the base color.\n\n // Resolve $token colors against the active theme\n if (color.startsWith(\"$\")) {\n // At monochrome tier, strip all token-resolved colors. Hierarchy is carried\n // by per-token SGR attrs (see getTextStyle → monoAttrsForColorString). The\n // output phase sees `null` and emits SGR 39/49 (terminal default), never\n // an RGB sequence.\n if (getActiveColorLevel() === \"none\") return null\n const resolved = resolveThemeColor(color, getActiveTheme())\n if (resolved && resolved !== color) return parseColor(resolved)\n return null\n }\n\n if (color in namedColors) {\n return namedColors[color as keyof typeof namedColors]!\n }\n\n // Hex color\n if (color.startsWith(\"#\")) {\n const hex = color.slice(1)\n if (hex.length === 3) {\n const r = Number.parseInt(hex[0]! + hex[0]!, 16)\n const g = Number.parseInt(hex[1]! + hex[1]!, 16)\n const b = Number.parseInt(hex[2]! + hex[2]!, 16)\n return { r, g, b }\n }\n if (hex.length === 6) {\n const r = Number.parseInt(hex.slice(0, 2), 16)\n const g = Number.parseInt(hex.slice(2, 4), 16)\n const b = Number.parseInt(hex.slice(4, 6), 16)\n return { r, g, b }\n }\n }\n\n // rgb(r,g,b)\n const rgbMatch = color.match(/^rgb\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)$/i)\n if (rgbMatch) {\n return {\n r: Number.parseInt(rgbMatch[1]!, 10),\n g: Number.parseInt(rgbMatch[2]!, 10),\n b: Number.parseInt(rgbMatch[3]!, 10),\n }\n }\n\n // ansi256(N) — 256-color palette index (0-255)\n const ansi256Match = color.match(/^ansi256\\s*\\(\\s*(\\d+)\\s*\\)$/i)\n if (ansi256Match) {\n return Number.parseInt(ansi256Match[1]!, 10)\n }\n\n return null\n}\n\n// ============================================================================\n// Border Characters\n// ============================================================================\n\n/**\n * Border character sets by style. Hoisted to module scope to avoid\n * re-allocating 7 objects on every call.\n */\nconst borders: Record<NonNullable<BoxProps[\"borderStyle\"]>, BorderChars> = {\n single: {\n topLeft: \"\\u250c\",\n topRight: \"\\u2510\",\n bottomLeft: \"\\u2514\",\n bottomRight: \"\\u2518\",\n horizontal: \"\\u2500\",\n vertical: \"\\u2502\",\n },\n double: {\n topLeft: \"\\u2554\",\n topRight: \"\\u2557\",\n bottomLeft: \"\\u255a\",\n bottomRight: \"\\u255d\",\n horizontal: \"\\u2550\",\n vertical: \"\\u2551\",\n },\n round: {\n topLeft: \"\\u256d\",\n topRight: \"\\u256e\",\n bottomLeft: \"\\u2570\",\n bottomRight: \"\\u256f\",\n horizontal: \"\\u2500\",\n vertical: \"\\u2502\",\n },\n bold: {\n topLeft: \"\\u250f\",\n topRight: \"\\u2513\",\n bottomLeft: \"\\u2517\",\n bottomRight: \"\\u251b\",\n horizontal: \"\\u2501\",\n vertical: \"\\u2503\",\n },\n singleDouble: {\n topLeft: \"\\u2553\",\n topRight: \"\\u2556\",\n bottomLeft: \"\\u2559\",\n bottomRight: \"\\u255c\",\n horizontal: \"\\u2500\",\n vertical: \"\\u2551\",\n },\n doubleSingle: {\n topLeft: \"\\u2552\",\n topRight: \"\\u2555\",\n bottomLeft: \"\\u2558\",\n bottomRight: \"\\u255b\",\n horizontal: \"\\u2550\",\n vertical: \"\\u2502\",\n },\n classic: {\n topLeft: \"+\",\n topRight: \"+\",\n bottomLeft: \"+\",\n bottomRight: \"+\",\n horizontal: \"-\",\n vertical: \"|\",\n },\n}\n\n/**\n * Get border characters for a style.\n */\nexport function getBorderChars(style: BoxProps[\"borderStyle\"]): BorderChars {\n if (style && typeof style === \"object\") {\n // Custom border object (Ink compat): map Ink's top/bottom/left/right to\n // silvery's horizontal/vertical format. Supports distinct chars per side.\n const obj = style as Record<string, string>\n const topHorizontal = obj.top ?? obj.horizontal ?? \"-\"\n const leftVertical = obj.left ?? obj.vertical ?? \"|\"\n return {\n topLeft: obj.topLeft ?? \"+\",\n topRight: obj.topRight ?? \"+\",\n bottomLeft: obj.bottomLeft ?? \"+\",\n bottomRight: obj.bottomRight ?? \"+\",\n horizontal: topHorizontal,\n vertical: leftVertical,\n bottomHorizontal: obj.bottom && obj.bottom !== topHorizontal ? obj.bottom : undefined,\n rightVertical: obj.right && obj.right !== leftVertical ? obj.right : undefined,\n }\n }\n return borders[style ?? \"single\"]\n}\n\n// ============================================================================\n// Style Extraction\n// ============================================================================\n\n/**\n * Collect monochrome attrs from a color string (`\"$primary\"` → `[\"bold\"]`).\n *\n * At mono tier, `parseColor` strips the color (returns `null`). The hierarchy\n * signal lives in the attrs bag. This helper merges the mapped attrs from\n * `DEFAULT_MONO_ATTRS` into a mutable accumulator. Called per color-carrying\n * prop in `getTextStyle`.\n *\n * No-op when the color is not a `$token` — non-token hex / named colors\n * pass through with no attrs (spec: \"apps that hardcoded #FF0000 get nothing\").\n */\nfunction collectMonoAttrs(color: string | undefined, into: Set<MonoAttr>): void {\n if (!color) return\n const attrs = monoAttrsForColorString(color, getActiveTheme())\n if (!attrs) return\n for (const a of attrs) into.add(a)\n}\n\n/**\n * Get text style from props.\n */\nexport function getTextStyle(props: TextProps): Style {\n // Determine underline style: underlineStyle takes precedence over underline boolean\n let underlineStyle: UnderlineStyle | undefined\n if (props.underlineStyle !== undefined) {\n underlineStyle = props.underlineStyle\n } else if (props.underline) {\n underlineStyle = \"single\"\n }\n\n // Start with the user-specified attrs.\n let bold = props.bold\n let dim = props.dim || props.dimColor // dimColor is Ink compatibility alias\n let italic = props.italic\n let underline = props.underline || !!underlineStyle\n let strikethrough = props.strikethrough\n let inverse = props.inverse\n\n // Monochrome tier: inject per-token SGR attrs from DEFAULT_MONO_ATTRS. Colors\n // are stripped by parseColor (returns null for $tokens at mono tier). The\n // attrs carry the hierarchy: $primary → bold, $muted → dim, $error →\n // bold+inverse, $link → underline, etc. User-supplied attrs always OR-in.\n if (getActiveColorLevel() === \"none\") {\n const monoAttrs = new Set<MonoAttr>()\n collectMonoAttrs(props.color, monoAttrs)\n collectMonoAttrs(props.backgroundColor, monoAttrs)\n if (monoAttrs.has(\"bold\")) bold = true\n if (monoAttrs.has(\"dim\")) dim = true\n if (monoAttrs.has(\"italic\")) italic = true\n if (monoAttrs.has(\"underline\")) {\n underline = true\n if (!underlineStyle) underlineStyle = \"single\"\n }\n if (monoAttrs.has(\"strikethrough\")) strikethrough = true\n if (monoAttrs.has(\"inverse\")) inverse = true\n }\n\n return {\n fg: props.color ? parseColor(props.color) : null,\n bg: props.backgroundColor ? parseColor(props.backgroundColor) : null,\n underlineColor: props.underlineColor ? parseColor(props.underlineColor) : null,\n attrs: {\n bold,\n dim,\n italic,\n underline,\n underlineStyle,\n strikethrough,\n inverse,\n },\n }\n}\n\n// ============================================================================\n// Text Width Utilities\n// ============================================================================\n\n/**\n * Get text display width (accounting for wide characters and ANSI codes).\n * Uses ANSI-aware width calculation to handle styled text.\n *\n * When a PipelineContext is provided, uses the context's measurer for\n * terminal-capability-aware width calculation. Falls back to the module-level\n * displayWidthAnsi (which reads the scoped measurer or default).\n */\nexport function getTextWidth(text: string, ctx?: PipelineContext): number {\n if (ctx) return ctx.measurer.displayWidthAnsi(text)\n return displayWidthAnsi(text)\n}\n","/**\n * Text Rendering - Functions for rendering text content to the buffer.\n *\n * Contains:\n * - ANSI text line rendering (renderAnsiTextLine)\n * - Plain text line rendering (renderTextLine)\n * - Text formatting (formatTextLines)\n * - Text truncation (truncateText)\n * - Text content collection (collectTextContent)\n */\n\nimport {\n type CellAttrs,\n type Color,\n type Style,\n type TerminalBuffer,\n type UnderlineStyle,\n createMutableCell,\n} from \"../buffer\"\nimport type { AgNode, TextProps } from \"@silvery/ag/types\"\nimport {\n type StyledSegment,\n ensureEmojiPresentation,\n graphemeWidth,\n hasAnsi,\n parseAnsiText,\n sliceByWidth,\n sliceByWidthFromEnd,\n splitGraphemes,\n wrapText,\n} from \"../unicode\"\nimport { collectPlainText } from \"./collect-text\"\nimport { getTextStyle, getTextWidth, parseColor } from \"./render-helpers\"\nimport { getActiveTheme } from \"./state\"\nimport {\n getCachedPlainText,\n setCachedPlainText,\n getCachedCollectedText,\n setCachedCollectedText,\n getCachedFormat,\n setCachedFormat,\n getCachedAnalysis,\n setCachedAnalysis,\n} from \"./prepared-text\"\nimport { buildTextAnalysis, balancedWidth as computeBalancedWidth, optimalWrap } from \"./pretext\"\nimport type { BgConflictMode, NodeRenderState, PipelineContext } from \"./types\"\nimport { createLogger } from \"loggily\"\n\nconst log = createLogger(\"silvery:content\")\n\n// ============================================================================\n// Background Conflict Detection\n// ============================================================================\n\n/** Cached bg conflict mode. Read from env once at module load. */\nlet bgConflictMode: BgConflictMode = (() => {\n const env =\n typeof process !== \"undefined\" ? process.env.SILVERY_BG_CONFLICT?.toLowerCase() : undefined\n if (env === \"ignore\" || env === \"warn\" || env === \"throw\") return env\n return \"throw\" // default - fail fast on programming errors\n})()\n\n/**\n * Get the current background conflict detection mode.\n */\nfunction getBgConflictMode(): BgConflictMode {\n return bgConflictMode\n}\n\n/**\n * Set the background conflict detection mode. For tests.\n */\nexport function setBgConflictMode(mode: BgConflictMode): void {\n bgConflictMode = mode\n}\n\n// Track warned conflicts to avoid spam (only used in 'warn' mode)\nconst warnedBgConflicts = new Set<string>()\n\n/** Format a Color value for bg conflict diagnostics */\nfunction formatBgConflictColor(\n c: number | { r: number; g: number; b: number } | null | undefined,\n): string {\n if (c === null || c === undefined) return \"none\"\n if (typeof c === \"number\") {\n // Packed RGB (0x1000000 marker) or ANSI palette index\n if (c & 0x1000000) {\n const r = (c >> 16) & 0xff\n const g = (c >> 8) & 0xff\n const b = c & 0xff\n return `#${r.toString(16).padStart(2, \"0\")}${g.toString(16).padStart(2, \"0\")}${b.toString(16).padStart(2, \"0\")}`\n }\n // Map SGR codes to names for readability\n const names: Record<number, string> = {\n 40: \"black\",\n 41: \"red\",\n 42: \"green\",\n 43: \"yellow\",\n 44: \"blue\",\n 45: \"magenta\",\n 46: \"cyan\",\n 47: \"white\",\n 100: \"brightBlack\",\n 101: \"brightRed\",\n 102: \"brightGreen\",\n 103: \"brightYellow\",\n 104: \"brightBlue\",\n 105: \"brightMagenta\",\n 106: \"brightCyan\",\n 107: \"brightWhite\",\n }\n return names[c] ?? `palette(${c})`\n }\n return `rgb(${c.r},${c.g},${c.b})`\n}\n\n/**\n * Clear the background conflict warning cache.\n * Call this at the start of each render cycle to:\n * - Prevent memory leaks in long-running apps\n * - Allow warnings to repeat after user fixes issues\n */\nexport function clearBgConflictWarnings(): void {\n warnedBgConflicts.clear()\n}\n\n// ============================================================================\n// Text Content Collection\n// ============================================================================\n\n/**\n * Style context for nested Text elements.\n * Tracks cumulative styles through the tree to enable proper push/pop behavior.\n */\ninterface StyleContext {\n color?: string\n backgroundColor?: string\n bold?: boolean\n dim?: boolean\n italic?: boolean\n underline?: boolean\n underlineStyle?: string | false\n underlineColor?: string\n inverse?: boolean\n strikethrough?: boolean\n}\n\n/**\n * Build ANSI escape sequence for a style context.\n *\n * Note: backgroundColor is intentionally NOT embedded as ANSI codes.\n * Background color is handled at the buffer level (via BgSegment tracking)\n * to prevent bg bleed across wrapped text lines. See km-silvery.bg-bleed.\n */\nfunction styleToAnsi(style: StyleContext): string {\n const parts: string[] = []\n\n // Foreground color - use parseColor directly instead of roundtripping through getTextStyle\n if (style.color) {\n const color = parseColor(style.color)\n if (color !== null) {\n if (typeof color === \"number\") {\n parts.push(`38;5;${color}`)\n } else {\n parts.push(`38;2;${color.r};${color.g};${color.b}`)\n }\n }\n }\n\n // backgroundColor is NOT embedded here - it is tracked separately via\n // BgSegment and applied at the buffer level in renderText(). This prevents\n // bg color from bleeding across wrapped lines. See collectTextWithBg().\n\n // Attributes\n if (style.bold) parts.push(\"1\")\n if (style.dim) parts.push(\"2\")\n if (style.italic) parts.push(\"3\")\n // Underline: prefer underlineStyle (SGR 4:x subparam) over boolean (SGR 4)\n if (style.underlineStyle) {\n const styleMap: Record<string, string> = {\n single: \"4:1\",\n double: \"4:2\",\n curly: \"4:3\",\n dotted: \"4:4\",\n dashed: \"4:5\",\n }\n parts.push(styleMap[style.underlineStyle] ?? \"4\")\n } else if (style.underline) {\n parts.push(\"4\")\n }\n // Underline color (SGR 58;5;N or 58;2;r;g;b).\n // \"currentColor\"/\"inherit\" → resolve to the merged fg (style.color), so the\n // underline tracks whatever color the surrounding text ended up as.\n if (style.underlineColor) {\n const underlineSource =\n style.underlineColor === \"currentColor\" || style.underlineColor === \"inherit\"\n ? style.color\n : style.underlineColor\n if (underlineSource) {\n const ulColor = parseColor(underlineSource)\n if (ulColor !== null) {\n if (typeof ulColor === \"number\") {\n parts.push(`58;5;${ulColor}`)\n } else {\n parts.push(`58;2;${ulColor.r};${ulColor.g};${ulColor.b}`)\n }\n }\n }\n }\n if (style.inverse) parts.push(\"7\")\n if (style.strikethrough) parts.push(\"9\")\n\n if (parts.length === 0) {\n return \"\"\n }\n\n return `\\x1b[${parts.join(\";\")}m`\n}\n\n/**\n * Merge child props into parent context.\n * Child values override parent values when specified.\n */\nfunction mergeStyleContext(parent: StyleContext, childProps: TextProps): StyleContext {\n // color=\"inherit\"/\"currentColor\" on a virtual-text child is a pass-through:\n // the child's effective color is whatever the parent already resolved to.\n // Without this, nested <Text color=\"inherit\"> clobbered the merged context\n // with the raw keyword, which styleToAnsi then mapped to null (no fg SGR).\n const isInheritKeyword = childProps.color === \"inherit\" || childProps.color === \"currentColor\"\n const effectiveChildColor = isInheritKeyword ? parent.color : childProps.color\n return {\n color: effectiveChildColor ?? parent.color,\n backgroundColor: childProps.backgroundColor ?? parent.backgroundColor,\n bold: childProps.bold ?? parent.bold,\n dim: childProps.dim ?? childProps.dimColor ?? parent.dim,\n italic: childProps.italic ?? parent.italic,\n underline:\n (childProps.underline ?? (childProps as any).underlineStyle) ? true : parent.underline,\n underlineStyle: (childProps as any).underlineStyle ?? parent.underlineStyle,\n underlineColor: (childProps as any).underlineColor ?? parent.underlineColor,\n inverse: childProps.inverse ?? parent.inverse,\n strikethrough: childProps.strikethrough ?? parent.strikethrough,\n }\n}\n\n/**\n * Apply text styles as ANSI escape codes with proper push/pop behavior.\n * After the child text, restores the parent context's styles.\n *\n * @param text - The text content to wrap\n * @param childStyle - The merged style for this child (child overrides parent)\n * @param parentStyle - The parent's style context to restore after\n */\nfunction applyTextStyleAnsi(\n text: string,\n childStyle: StyleContext,\n parentStyle: StyleContext,\n): string {\n if (!text) {\n return text\n }\n\n const childAnsi = styleToAnsi(childStyle)\n const parentAnsi = styleToAnsi(parentStyle)\n\n // If child has no style changes, just return text\n if (!childAnsi) {\n return text\n }\n\n // Apply child style, then reset and re-apply parent style\n // We use \\x1b[0m to reset, then re-apply parent styles\n return `${childAnsi}${text}\\x1b[0m${parentAnsi}`\n}\n\n/**\n * Recursively collect text content from a node and its children.\n * Handles both raw text nodes (textContent set directly) and\n * Text component wrappers (text in children).\n *\n * For nested Text nodes with style props (color, bold, etc.),\n * applies ANSI codes so the styles are preserved when rendered.\n * Uses a style stack to properly restore parent styles after nested elements.\n *\n * @param node - The node to collect text from\n * @param parentContext - The inherited style context from parent (used for restoration)\n */\nexport function collectTextContent(node: AgNode, parentContext: StyleContext = {}): string {\n // If this node has direct text content, return it\n if (node.textContent !== undefined) {\n return node.textContent\n }\n\n // Otherwise, collect from children\n // Matching Ink's squashTextNodes: apply internal_transform to the full text\n // of each child node (not per-line), using the child index as the index argument.\n let result = \"\"\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i]!\n // If child is a Text node (virtual/nested) with style props, apply ANSI codes\n if (child.type === \"silvery-text\" && child.props && !child.layoutNode) {\n const childProps = child.props as TextProps\n // Merge child props with parent context to get effective child style\n const childContext = mergeStyleContext(parentContext, childProps)\n // Recursively collect with child's context\n let childContent = collectTextContent(child, childContext)\n // Apply internal_transform from virtual text nodes (nested Transform components).\n // Matches Ink's squashTextNodes: transform is applied to the full concatenated\n // text of the child, with index = child position in parent's children array.\n const childTransform = (childProps as any).internal_transform\n if (childTransform && childContent.length > 0) {\n childContent = childTransform(childContent, i)\n }\n // Apply styles with proper push/pop (child style, then restore parent)\n result += applyTextStyleAnsi(childContent, childContext, parentContext)\n } else {\n // Not a styled Text node, just collect recursively\n result += collectTextContent(child, parentContext)\n }\n }\n return result\n}\n\n// ============================================================================\n// Background Segment Tracking\n// ============================================================================\n\n/**\n * A background color segment in collected text.\n * Tracks which character range has which background color,\n * independent of ANSI codes. Used to apply bg at the buffer level\n * after text wrapping, preventing bg bleed across wrapped lines.\n */\ninterface BgSegment {\n /** Start character offset in the collected text (inclusive) */\n start: number\n /** End character offset in the collected text (exclusive) */\n end: number\n /** Background color to apply */\n bg: Color\n}\n\n/**\n * A span mapping a virtual text child node to its character range.\n * Used to compute inlineRects for hit testing on nested Text.\n */\ninterface ChildSpan {\n /** The virtual text node */\n node: AgNode\n /** Start display-width offset in the collected text (inclusive) */\n start: number\n /** End display-width offset in the collected text (exclusive) */\n end: number\n}\n\n/**\n * Result of collecting text with background segments.\n */\ninterface TextWithBg {\n /** The collected text string (with ANSI codes for fg/attrs, but NOT bg) */\n text: string\n /** Background color segments from nested Text elements */\n bgSegments: BgSegment[]\n /** Spans mapping virtual text children to display-width ranges */\n childSpans: ChildSpan[]\n /** Plain text character count (excluding ANSI codes). Used for DOM-level budget tracking. */\n plainLen: number\n}\n\n// collectPlainText is imported from ./collect-text.\n// Previously duplicated here; now shared across measure-phase, render-text,\n// and the reconciler's measure function.\n\n/**\n * Collect text content and background color segments from a node tree.\n *\n * Like collectTextContent, but also tracks backgroundColor from nested Text\n * elements as separate BgSegment entries. Background is NOT embedded as ANSI\n * codes, preventing bg bleed when text wraps across lines.\n *\n * @param node - The node to collect text from\n * @param parentContext - The inherited style context from parent\n * @param offset - Current character offset in the collected text (for bg tracking)\n * @param maxDisplayWidth - Maximum display width (columns) to collect. When set,\n * stops collecting once this many display columns of content have been gathered.\n * This truncates at the DOM level BEFORE ANSI serialization, so escape sequences\n * (OSC 8, etc.) are never generated for content that won't be displayed.\n * Uses getTextWidth (ANSI-aware) so pre-styled leaf text is handled correctly.\n */\nfunction collectTextWithBg(\n node: AgNode,\n parentContext: StyleContext = {},\n offset = 0,\n maxDisplayWidth?: number,\n ctx?: PipelineContext,\n): TextWithBg {\n // If this node has direct text content, return it with no bg segments\n if (node.textContent !== undefined) {\n let text = node.textContent\n // DOM-level truncation: trim leaf text to display width budget\n if (maxDisplayWidth !== undefined) {\n const textW = getTextWidth(text, ctx)\n if (textW > maxDisplayWidth) {\n const sliceFn = ctx ? ctx.measurer.sliceByWidth : sliceByWidth\n text = sliceFn(text, maxDisplayWidth)\n }\n }\n // plainLen tracks display width for budget and BgSegment offset tracking.\n // Both use display-width coordinates consistently: collectTextWithBg uses\n // getTextWidth for offsets, mapLinesToCharOffsets returns display-width,\n // and applyBgSegmentsToLine compares via display-width (col - x).\n const plainLen = getTextWidth(text, ctx)\n return { text, bgSegments: [], childSpans: [], plainLen }\n }\n\n let result = \"\"\n const bgSegments: BgSegment[] = []\n const childSpans: ChildSpan[] = []\n let currentOffset = offset\n let displayWidthCollected = 0\n\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i]!\n // Stop collecting if budget exhausted\n if (maxDisplayWidth !== undefined && displayWidthCollected >= maxDisplayWidth) break\n\n // Compute remaining budget for this child\n const childBudget =\n maxDisplayWidth !== undefined ? maxDisplayWidth - displayWidthCollected : undefined\n\n if (child.type === \"silvery-text\" && child.props && !child.layoutNode) {\n const childProps = child.props as TextProps\n const childContext = mergeStyleContext(parentContext, childProps)\n\n // Recursively collect with child's context and budget\n const childResult = collectTextWithBg(child, childContext, currentOffset, childBudget, ctx)\n\n // Apply internal_transform from virtual text nodes (nested Transform components).\n // Matches Ink's squashTextNodes: transform is applied to the full concatenated\n // text of the child, with index = child position in parent's children array.\n const childTransform = (childProps as any).internal_transform\n if (childTransform && childResult.text.length > 0) {\n childResult.text = childTransform(childResult.text, i)\n }\n\n // Apply ANSI styles for fg/attrs (but NOT bg) with push/pop\n const styledText = applyTextStyleAnsi(childResult.text, childContext, parentContext)\n result += styledText\n\n // Track bg segment if this child (or its ancestors) has backgroundColor.\n // When backgroundColor is \"\" (empty string), create a null-bg segment to\n // explicitly clear inherited background (e.g., from a parent Box).\n if (childContext.backgroundColor) {\n const bg = parseColor(childContext.backgroundColor)\n if (bg !== null) {\n if (childResult.plainLen > 0) {\n bgSegments.push({\n start: currentOffset,\n end: currentOffset + childResult.plainLen,\n bg,\n })\n }\n }\n } else if (childProps.backgroundColor === \"\" && childResult.plainLen > 0) {\n // Explicit backgroundColor=\"\" clears inherited bg (from parent Text\n // or ancestor Box's inheritedBg). Push a null-bg segment so\n // applyBgSegmentsToLine overrides inheritedBg to null for this range.\n bgSegments.push({\n start: currentOffset,\n end: currentOffset + childResult.plainLen,\n bg: null,\n })\n }\n\n // Track child span for inlineRects computation\n if (childResult.plainLen > 0) {\n childSpans.push({\n node: child,\n start: currentOffset,\n end: currentOffset + childResult.plainLen,\n })\n }\n\n // Include child's nested bg segments and child spans\n bgSegments.push(...childResult.bgSegments)\n childSpans.push(...childResult.childSpans)\n\n // Track using plainLen (display width) — not text.length which includes ANSI codes\n currentOffset += childResult.plainLen\n displayWidthCollected += childResult.plainLen\n } else {\n // Not a styled Text node, just collect recursively\n const childResult = collectTextWithBg(child, parentContext, currentOffset, childBudget, ctx)\n result += childResult.text\n bgSegments.push(...childResult.bgSegments)\n childSpans.push(...childResult.childSpans)\n currentOffset += childResult.plainLen\n displayWidthCollected += childResult.plainLen\n }\n }\n\n return { text: result, bgSegments, childSpans, plainLen: displayWidthCollected }\n}\n\n/**\n * Apply background segments to buffer cells for a single rendered line.\n *\n * Maps character offsets from the original collected text to screen positions,\n * accounting for text wrapping. Each bg segment fills only the cells that\n * correspond to actual text characters, not trailing whitespace.\n *\n * @param buffer - The terminal buffer to write to\n * @param x - Screen x position of the line start\n * @param y - Screen y position of the line\n * @param lineText - The rendered line text (may contain ANSI codes)\n * @param lineCharStart - Character offset in original text where this line starts\n * @param lineCharEnd - Character offset in original text where this line ends\n * @param bgSegments - Background color segments to apply\n */\nfunction applyBgSegmentsToLine(\n buffer: TerminalBuffer,\n x: number,\n y: number,\n lineText: string,\n lineCharStart: number,\n lineCharEnd: number,\n bgSegments: BgSegment[],\n ctx?: PipelineContext,\n): void {\n if (bgSegments.length === 0) return\n if (y < 0 || y >= buffer.height) return\n\n // Reusable cell for readCellInto to avoid per-character allocation\n const bgCell = createMutableCell()\n const gWidthFn = ctx ? ctx.measurer.graphemeWidth : graphemeWidth\n\n // For each bg segment that overlaps this line's character range,\n // calculate the screen columns and fill the bg\n for (const seg of bgSegments) {\n // Check overlap between segment [seg.start, seg.end) and line [lineCharStart, lineCharEnd)\n const overlapStart = Math.max(seg.start, lineCharStart)\n const overlapEnd = Math.min(seg.end, lineCharEnd)\n if (overlapStart >= overlapEnd) continue\n\n // Convert display-width offsets to column positions within the line.\n // BgSegment offsets and lineCharStart/lineCharEnd are all in display-width\n // coordinates, so relStart/relEnd are display-width offsets within the line.\n const relStart = overlapStart - lineCharStart\n const relEnd = overlapEnd - lineCharStart\n\n // Walk through the line's visible characters to find screen columns.\n // Use display-width offset (col - x) to match BgSegment coordinate system.\n let col = x\n const graphemes = splitGraphemes(hasAnsi(lineText) ? stripAnsiForBg(lineText) : lineText)\n\n for (const grapheme of graphemes) {\n const gWidth = gWidthFn(grapheme)\n if (gWidth === 0) continue\n\n const displayOffset = col - x\n if (displayOffset >= relStart && displayOffset < relEnd) {\n // This character is within the bg segment -- set bg on its cells.\n // Use readCellInto to avoid allocating a new Cell per iteration.\n buffer.readCellInto(col, y, bgCell)\n bgCell.bg = seg.bg\n buffer.setCell(col, y, bgCell)\n if (gWidth === 2 && col + 1 < buffer.width) {\n buffer.readCellInto(col + 1, y, bgCell)\n bgCell.bg = seg.bg\n buffer.setCell(col + 1, y, bgCell)\n }\n }\n\n col += gWidth\n if (col - x >= relEnd) break\n }\n }\n}\n\n/**\n * Strip ANSI escape codes from text for character counting.\n */\nfunction stripAnsiForBg(text: string): string {\n return text\n .replace(/\\x1b\\[[0-9;:?]*[A-Za-z]/g, \"\")\n .replace(/\\x1b\\][^\\x07\\x1b]*(?:\\x07|\\x1b\\\\)/g, \"\")\n .replace(/\\x1b[DME78]/g, \"\")\n .replace(/\\x1b\\(B/g, \"\")\n}\n\n/**\n * Map formatted lines back to character offsets in the original text.\n *\n * After wrapping/truncation, each output line corresponds to a range\n * of characters in the original text. This function computes those ranges\n * by searching for each line's content in the normalized text.\n *\n * Handles characters consumed by word wrapping (spaces at break points,\n * newlines) and characters added by truncation (ellipsis).\n *\n * Returns display-width offsets (not UTF-16 code units) to match BgSegment\n * coordinate system. BgSegments use display-width via getTextWidth/plainLen.\n *\n * @param originalText - The original collected text (with ANSI, before wrapping)\n * @param formattedLines - The wrapped/truncated output lines\n * @param ctx - Pipeline context for width measurement\n * @returns Array of { start, end } display-width offsets for each formatted line\n */\nfunction mapLinesToCharOffsets(\n originalText: string,\n formattedLines: string[],\n ctx?: PipelineContext,\n): Array<{ start: number; end: number }> {\n // Strip ANSI from the original to get the plain text character sequence\n const plainOriginal = hasAnsi(originalText) ? stripAnsiForBg(originalText) : originalText\n // Normalize tabs to match formatTextLines behavior\n const normalized = plainOriginal.replace(/\\t/g, \" \")\n\n const result: Array<{ start: number; end: number }> = []\n let charOffset = 0 // UTF-16 offset for string matching (findLineStart)\n let displayOffset = 0 // Display-width offset for BgSegment matching\n\n for (const line of formattedLines) {\n const plainLine = hasAnsi(line) ? stripAnsiForBg(line) : line\n\n // Find where this line starts in the normalized text (UTF-16 matching).\n const lineStart = findLineStart(normalized, plainLine, charOffset)\n\n // Convert skipped characters (between previous line end and this line start)\n // to display width. These are whitespace/newlines consumed by wrapping.\n if (lineStart > charOffset) {\n const skipped = normalized.slice(charOffset, lineStart)\n displayOffset += getTextWidth(skipped, ctx)\n }\n\n // Line content display width\n const lineDisplayWidth = getTextWidth(plainLine, ctx)\n result.push({ start: displayOffset, end: displayOffset + lineDisplayWidth })\n\n // Advance both offset trackers\n const lineLen = Math.min(plainLine.length, normalized.length - lineStart)\n charOffset = lineStart + lineLen\n displayOffset += lineDisplayWidth\n }\n\n return result\n}\n\n/**\n * Find where a formatted line starts in the normalized original text.\n *\n * Scans forward from the given offset, matching the line content\n * character by character. Skips newlines and whitespace that were\n * consumed by wrapping between lines.\n */\nfunction findLineStart(normalized: string, plainLine: string, fromOffset: number): number {\n if (plainLine.length === 0) {\n // Empty line -- skip to next newline\n let pos = fromOffset\n while (pos < normalized.length && normalized[pos] === \"\\n\") {\n pos++\n }\n return pos\n }\n\n // Try exact match at current offset first (fast path for first line\n // and for lines that follow explicit newlines without space trimming)\n if (normalized.startsWith(plainLine, fromOffset)) {\n return fromOffset\n }\n\n // For truncated lines, extract prefix before ellipsis for matching.\n // startsWith fails when the line has \"…\" that doesn't exist in the original.\n const ELLIPSIS = \"\\u2026\"\n const ellipsisIdx = plainLine.indexOf(ELLIPSIS)\n const truncatedPrefix = ellipsisIdx > 0 ? plainLine.slice(0, ellipsisIdx) : null\n\n if (truncatedPrefix && normalized.startsWith(truncatedPrefix, fromOffset)) {\n return fromOffset\n }\n\n // Scan forward, skipping newlines and spaces consumed by wrapping\n let pos = fromOffset\n while (pos < normalized.length) {\n const ch = normalized[pos]!\n if (ch === \"\\n\" || ch === \" \") {\n pos++\n continue\n }\n // Found a non-whitespace character -- check if line starts here\n if (normalized.startsWith(plainLine, pos)) {\n return pos\n }\n // Check truncated prefix match (e.g. \"abcde…\" -> match \"abcde\")\n if (truncatedPrefix && normalized.startsWith(truncatedPrefix, pos)) {\n return pos\n }\n pos++\n }\n\n // Fallback: return current position\n return fromOffset\n}\n\n// ============================================================================\n// Text Formatting\n// ============================================================================\n\n/**\n * Format text into lines based on wrap mode.\n *\n * @param trim - When true, trims trailing spaces on broken lines and skips leading\n * spaces on continuation lines. When false (e.g., text has backgroundColor),\n * preserves trailing spaces so background color covers them. Defaults to true.\n */\nexport function formatTextLines(\n text: string,\n width: number,\n wrap: TextProps[\"wrap\"],\n ctx?: PipelineContext,\n trim = true,\n): string[] {\n // Guard against width <= 0 to prevent infinite loops\n // This can happen with display=\"none\" nodes (0x0 dimensions)\n if (width <= 0) {\n return []\n }\n\n // Convert tabs to spaces (tabs have 0 display width in string-width library)\n const normalizedText = text.replace(/\\t/g, \" \")\n const lines = normalizedText.split(\"\\n\")\n\n // Hard clip: truncate without ellipsis (used by Fill component)\n if (wrap === \"clip\") {\n const sliceFn = ctx ? ctx.measurer.sliceByWidth : sliceByWidth\n return lines.map((line) => {\n if (getTextWidth(line, ctx) <= width) return line\n return sliceFn(line, width)\n })\n }\n\n // Hard wrap: character-level wrapping without regard to word boundaries.\n // Matches Ink's wrap=\"hard\" behavior (wrap-ansi with wordWrap=false), so\n // \"Hello World\" at width=7 becomes [\"Hello W\", \"orld\"] — the break lands\n // mid-word rather than at the space. Multi-line input is hard-wrapped\n // line-by-line; each line is repeatedly sliced by display width.\n if (wrap === \"hard\") {\n const sliceFn = ctx ? ctx.measurer.sliceByWidth : sliceByWidth\n const out: string[] = []\n for (const line of lines) {\n if (line === \"\") {\n out.push(\"\")\n continue\n }\n let remaining = line\n // Guard against infinite loops when sliceByWidth cannot advance\n // (e.g., a single grapheme wider than `width`). In that case, push\n // the remaining text as-is and break.\n while (getTextWidth(remaining, ctx) > width) {\n const head = sliceFn(remaining, width)\n if (head.length === 0) break\n out.push(head)\n remaining = remaining.slice(head.length)\n }\n out.push(remaining)\n }\n return out\n }\n\n // No wrapping, just truncate at end\n if (wrap === false || wrap === \"truncate-end\" || wrap === \"truncate\") {\n return lines.map((line) => truncateText(line, width, \"end\", ctx))\n }\n\n if (wrap === \"truncate-start\") {\n return lines.map((line) => truncateText(line, width, \"start\", ctx))\n }\n\n if (wrap === \"truncate-middle\") {\n return lines.map((line) => truncateText(line, width, \"middle\", ctx))\n }\n\n // Optimal wrapping (Knuth-Plass): minimize total raggedness across all lines.\n // Uses dynamic programming over breakpoints for globally optimal line breaks.\n if (wrap === \"even\") {\n const gWidthFn = ctx?.measurer?.graphemeWidth?.bind(ctx.measurer) ?? graphemeWidth\n const analysis = buildTextAnalysis(normalizedText, gWidthFn)\n return optimalWrap(normalizedText, analysis, width)\n }\n\n // Balanced wrapping: disabled — the heuristic (totalWidth / lineCount) doesn't\n // reliably produce better results than optimal. It narrows the width (changing\n // line count) rather than optimizing break placement. Keep the algorithm in\n // pretext.ts for potential future use; just treat \"balanced\" as greedy here.\n\n // wrap === true or wrap === 'wrap' or wrap === 'balanced' - word-aware wrapping\n // Uses wrapText from unicode.ts with trim for rendering\n // (when trim=true, trims trailing spaces on broken lines, skips leading spaces\n // on continuation lines; when trim=false, preserves spaces for bg-colored text)\n if (ctx) return ctx.measurer.wrapText(normalizedText, width, true, trim)\n return wrapText(normalizedText, width, true, trim)\n}\n\n/**\n * Truncate text to fit within width.\n */\nexport function truncateText(\n text: string,\n width: number,\n mode: \"start\" | \"middle\" | \"end\",\n ctx?: PipelineContext,\n): string {\n const textWidth = getTextWidth(text, ctx)\n if (textWidth <= width) return text\n\n const ellipsis = \"\\u2026\" // ...\n const availableWidth = width - 1 // Reserve space for ellipsis\n\n if (availableWidth <= 0) {\n return width > 0 ? ellipsis : \"\"\n }\n\n const sliceFn = ctx ? ctx.measurer.sliceByWidth : sliceByWidth\n const sliceEndFn = ctx ? ctx.measurer.sliceByWidthFromEnd : sliceByWidthFromEnd\n\n if (mode === \"end\") {\n return sliceFn(text, availableWidth) + ellipsis\n }\n\n if (mode === \"start\") {\n return ellipsis + sliceEndFn(text, availableWidth)\n }\n\n // middle\n const halfWidth = Math.floor(availableWidth / 2)\n const startPart = sliceFn(text, halfWidth)\n const endPart = sliceEndFn(text, availableWidth - halfWidth)\n return startPart + ellipsis + endPart\n}\n\n// ============================================================================\n// Text Line Rendering\n// ============================================================================\n\n/**\n * Render a single line of text to the buffer.\n *\n * @param maxCol - Right edge of the text node's layout area. Wide characters\n * whose continuation cell would exceed this boundary are replaced with a\n * space, matching terminal behavior for wide chars at the screen edge.\n * Without this, continuation cells overflow into adjacent containers and\n * become stale during incremental rendering (the owning container's dirty\n * tracking doesn't cover cells outside its layout bounds).\n */\nexport function renderTextLine(\n buffer: TerminalBuffer,\n x: number,\n y: number,\n text: string,\n baseStyle: Style,\n maxCol?: number,\n inheritedBg?: Color,\n ctx?: PipelineContext,\n): void {\n // Check if text contains ANSI escape sequences\n if (hasAnsi(text)) {\n renderAnsiTextLine(buffer, x, y, text, baseStyle, maxCol, inheritedBg, ctx)\n return\n }\n\n renderGraphemes(buffer, splitGraphemes(text), x, y, baseStyle, maxCol, inheritedBg, ctx)\n}\n\n/**\n * Like renderTextLine but returns the column position after the last rendered character.\n * Used by renderText to know where to clear remaining cells.\n */\nfunction renderTextLineReturn(\n buffer: TerminalBuffer,\n x: number,\n y: number,\n text: string,\n baseStyle: Style,\n maxCol?: number,\n inheritedBg?: Color,\n ctx?: PipelineContext,\n minCol?: number,\n): number {\n if (hasAnsi(text)) {\n return renderAnsiTextLineReturn(buffer, x, y, text, baseStyle, maxCol, inheritedBg, ctx, minCol)\n }\n return renderGraphemes(\n buffer,\n splitGraphemes(text),\n x,\n y,\n baseStyle,\n maxCol,\n inheritedBg,\n ctx,\n minCol,\n )\n}\n\n/**\n * Render graphemes to buffer cells with proper Unicode handling.\n * Shared by renderTextLine (plain text) and renderAnsiTextLine (per-segment).\n *\n * @param maxCol - Right edge of the text node's layout area (exclusive).\n * Wide characters whose continuation cell would reach or exceed this\n * boundary are replaced with a space character. This matches terminal\n * behavior for wide chars at the right edge of a container and prevents\n * continuation cells from overflowing into adjacent containers, where\n * they become stale during incremental rendering.\n * @param minCol - Left edge of the visible region (inclusive). Graphemes\n * whose end position is at or before minCol are skipped (col still advances).\n * Used to clip text that overflows the LEFT edge of an overflow:hidden\n * container with a border (so the border isn't overwritten).\n *\n * Returns the column position after the last rendered grapheme.\n */\nfunction renderGraphemes(\n buffer: TerminalBuffer,\n graphemes: string[],\n startCol: number,\n y: number,\n style: Style,\n maxCol?: number,\n inheritedBg?: Color,\n ctx?: PipelineContext,\n minCol?: number,\n): number {\n let col = startCol\n // Effective right boundary: text node's layout edge or buffer edge\n const rightEdge = maxCol !== undefined ? Math.min(maxCol, buffer.width) : buffer.width\n // Effective left boundary: max of clipBounds.left and 0 (no negative columns)\n const leftEdge = minCol !== undefined ? Math.max(minCol, 0) : 0\n const gWidthFn = ctx ? ctx.measurer.graphemeWidth : graphemeWidth\n\n for (const grapheme of graphemes) {\n if (col >= rightEdge) break\n\n const width = gWidthFn(grapheme)\n if (width === 0) continue\n\n // Skip graphemes whose end is still left of leftEdge (still advance col).\n // This clips text that overflows the LEFT edge of an overflow:hidden\n // container — without this, the text would overwrite the parent's left\n // border or padding cells.\n if (col + width <= leftEdge) {\n col += width\n continue\n }\n\n // Partial overlap: a wide grapheme straddling the left edge. Replace with\n // a space at leftEdge so the visible cell is preserved without the\n // grapheme's continuation cell extending outside the clip region.\n if (col < leftEdge) {\n // Skip this grapheme (the visible portion is its right cell which we\n // can't draw without the leading half). Advance to leftEdge.\n col = leftEdge\n // Don't draw a partial wide char — fall through to the next grapheme.\n continue\n }\n\n // Determine background color for this cell.\n // Priority: 1) Text's own bg, 2) inherited bg from ancestor Box, 3) buffer read (legacy fallback).\n // Using inherited bg instead of getCellBg decouples text rendering from buffer state,\n // which is critical for incremental rendering: the cloned buffer may have stale bg\n // at positions outside the parent's bg-filled region (e.g., overflow text).\n const existingBg =\n style.bg !== null\n ? style.bg\n : inheritedBg !== undefined\n ? inheritedBg\n : buffer.getCellBg(col, y)\n\n // Wide character at the boundary: the continuation cell would overflow\n // into an adjacent container. Replace with a space to match terminal\n // behavior (real terminals leave the last column blank for wide chars\n // that don't fit). Without this, the continuation cell extends outside\n // the text node's layout bounds and becomes stale during incremental\n // rendering — the owning container's dirty flag tracking doesn't cover\n // cells outside its layout area.\n if (width === 2 && col + 1 >= rightEdge) {\n buffer.setCell(col, y, {\n char: \" \",\n fg: style.fg,\n bg: existingBg,\n underlineColor: style.underlineColor ?? null,\n attrs: style.attrs,\n wide: false,\n continuation: false,\n hyperlink: style.hyperlink,\n })\n col += 1\n continue\n }\n\n // For text-presentation emoji, add VS16 so terminals render at 2 columns\n const outputChar = width === 2 ? ensureEmojiPresentation(grapheme) : grapheme\n\n buffer.setCell(col, y, {\n char: outputChar,\n fg: style.fg,\n bg: existingBg,\n underlineColor: style.underlineColor ?? null,\n attrs: style.attrs,\n wide: width === 2,\n continuation: false,\n hyperlink: style.hyperlink,\n })\n\n if (width === 2 && col + 1 < buffer.width) {\n const existingBg2 =\n style.bg !== null\n ? style.bg\n : inheritedBg !== undefined\n ? inheritedBg\n : buffer.getCellBg(col + 1, y)\n buffer.setCell(col + 1, y, {\n char: \"\",\n fg: style.fg,\n bg: existingBg2,\n underlineColor: style.underlineColor ?? null,\n attrs: style.attrs,\n wide: false,\n continuation: true,\n hyperlink: style.hyperlink,\n })\n col += 2\n } else {\n col += width\n }\n }\n\n return col\n}\n\n/**\n * Render text line with ANSI escape sequences.\n * Parses ANSI codes and applies styles to individual segments.\n */\nexport function renderAnsiTextLine(\n buffer: TerminalBuffer,\n x: number,\n y: number,\n text: string,\n baseStyle: Style,\n maxCol?: number,\n inheritedBg?: Color,\n ctx?: PipelineContext,\n): void {\n renderAnsiTextLineReturn(buffer, x, y, text, baseStyle, maxCol, inheritedBg, ctx)\n}\n\n/**\n * Like renderAnsiTextLine but returns the column position after the last rendered character.\n */\nfunction renderAnsiTextLineReturn(\n buffer: TerminalBuffer,\n x: number,\n y: number,\n text: string,\n baseStyle: Style,\n maxCol?: number,\n inheritedBg?: Color,\n ctx?: PipelineContext,\n minCol?: number,\n): number {\n const segments = parseAnsiText(text)\n let col = x\n\n for (const segment of segments) {\n // Merge segment style with base style\n const style = mergeAnsiStyle(baseStyle, segment)\n\n // Detect background conflict: chalk.bg* overwrites existing silvery background\n // Check both: 1) Text's own backgroundColor, 2) Parent Box's bg already in buffer\n // Skip if segment has bgOverride flag (explicit opt-out via ansi.bgOverride)\n const effectiveBgConflictMode = ctx?.bgConflictMode ?? getBgConflictMode()\n if (\n effectiveBgConflictMode !== \"ignore\" &&\n !segment.bgOverride &&\n segment.bg !== undefined &&\n segment.bg !== null\n ) {\n // Check if there's an existing background (from Text prop or parent Box fill)\n const existingBufBg = col < buffer.width ? buffer.getCellBg(col, y) : null\n const hasExistingBg = baseStyle.bg !== null || existingBufBg !== null\n\n if (hasExistingBg) {\n const preview = segment.text.slice(0, 30)\n const chalkBg = formatBgConflictColor(segment.bg)\n const silveryBg =\n baseStyle.bg !== null\n ? `Text.bg=${formatBgConflictColor(baseStyle.bg)}`\n : `bufferBg=${formatBgConflictColor(existingBufBg)}`\n // Show a snippet of the raw ANSI text around the conflict for debugging\n const textPreview = text.length > 80 ? text.slice(0, 80) + \"…\" : text\n const msg = `[silvery] Background conflict at (${col},${y}): chalk bg=${chalkBg} on silvery ${silveryBg}. Text: \"${preview}${segment.text.length > 30 ? \"…\" : \"\"}\". Raw ANSI (first 80): ${JSON.stringify(textPreview)}. Chalk bg will override only text characters, causing visual gaps in padding. Use ansi.bgOverride() to suppress if intentional.`\n\n if (effectiveBgConflictMode === \"throw\") {\n throw new Error(msg)\n }\n // 'warn' mode - deduplicate\n const effectiveWarnedBgConflicts = ctx?.warnedBgConflicts ?? warnedBgConflicts\n const key = `${JSON.stringify(existingBufBg)}-${segment.bg}-${preview}`\n if (!effectiveWarnedBgConflicts.has(key)) {\n effectiveWarnedBgConflicts.add(key)\n log.warn?.(msg)\n }\n }\n }\n\n col = renderGraphemes(\n buffer,\n splitGraphemes(segment.text),\n col,\n y,\n style,\n maxCol,\n inheritedBg,\n ctx,\n minCol,\n )\n }\n return col\n}\n\n// ============================================================================\n// Style Merging (Category-Based)\n// ============================================================================\n\n/**\n * Options for category-based style merging.\n */\nexport interface MergeStylesOptions {\n /**\n * Preserve decoration attributes through layers (OR merge).\n * Affects: underline, underlineStyle, underlineColor, strikethrough\n * Default: true\n */\n preserveDecorations?: boolean\n /**\n * Preserve emphasis attributes through layers (OR merge).\n * Affects: bold, dim, italic\n * Default: true\n */\n preserveEmphasis?: boolean\n}\n\n/**\n * Merge two styles using category-based semantics.\n *\n * Categories and their merge behavior:\n * - Container (bg): overlay replaces base\n * - Text (fg): overlay replaces base\n * - Decorations (underline*, strikethrough): OR merge if preserveDecorations=true\n * - Emphasis (bold, dim, italic): OR merge if preserveEmphasis=true\n * - Transform (inverse, hidden, blink): overlay only, not inherited\n *\n * @param base - The base style (from parent/container)\n * @param overlay - The overlay style (from child/content)\n * @param options - Merge behavior options\n */\nexport function mergeStyles(\n base: Style,\n overlay: Partial<Style>,\n options: MergeStylesOptions = {},\n): Style {\n const { preserveDecorations = true, preserveEmphasis = true } = options\n\n const baseAttrs = base.attrs ?? {}\n const overlayAttrs = overlay.attrs ?? {}\n\n // Merge attributes by category\n const attrs: CellAttrs = {}\n\n // Decorations: OR if preserving, otherwise overlay takes precedence\n if (preserveDecorations) {\n // Underline: OR the boolean, but style from overlay wins if specified\n const hasBaseUnderline = baseAttrs.underline || baseAttrs.underlineStyle\n const hasOverlayUnderline = overlayAttrs.underline || overlayAttrs.underlineStyle\n if (hasBaseUnderline || hasOverlayUnderline) {\n attrs.underline = true\n // Style: overlay wins if specified, else base\n attrs.underlineStyle = overlayAttrs.underlineStyle ?? baseAttrs.underlineStyle ?? \"single\"\n }\n attrs.strikethrough = overlayAttrs.strikethrough || baseAttrs.strikethrough\n } else {\n attrs.underline = overlayAttrs.underline ?? baseAttrs.underline\n attrs.underlineStyle = overlayAttrs.underlineStyle ?? baseAttrs.underlineStyle\n attrs.strikethrough = overlayAttrs.strikethrough ?? baseAttrs.strikethrough\n }\n\n // Emphasis: OR if preserving\n if (preserveEmphasis) {\n attrs.bold = overlayAttrs.bold || baseAttrs.bold\n attrs.dim = overlayAttrs.dim || baseAttrs.dim\n attrs.italic = overlayAttrs.italic || baseAttrs.italic\n } else {\n attrs.bold = overlayAttrs.bold ?? baseAttrs.bold\n attrs.dim = overlayAttrs.dim ?? baseAttrs.dim\n attrs.italic = overlayAttrs.italic ?? baseAttrs.italic\n }\n\n // Transform: overlay only, not inherited from base\n attrs.inverse = overlayAttrs.inverse\n attrs.hidden = overlayAttrs.hidden\n attrs.blink = overlayAttrs.blink\n\n return {\n // Container/Text: overlay wins if specified\n fg: overlay.fg ?? base.fg,\n bg: overlay.bg ?? base.bg,\n // Underline color: always use overlay ?? base (part of decoration preservation)\n underlineColor: overlay.underlineColor ?? base.underlineColor,\n attrs,\n }\n}\n\n// ============================================================================\n// ANSI Style Helpers\n// ============================================================================\n\n/**\n * Merge ANSI segment style with base style.\n * Uses category-based merging to preserve decorations and emphasis.\n */\nfunction mergeAnsiStyle(\n base: Style,\n segment: StyledSegment,\n options: MergeStylesOptions = {},\n): Style {\n const { preserveDecorations = true, preserveEmphasis = true } = options\n\n // Convert ANSI SGR codes to overlay style\n let fg: Color = base.fg\n let bg: Color = base.bg\n let underlineColor: Color = base.underlineColor ?? null\n\n if (segment.fg !== undefined && segment.fg !== null) {\n fg = ansiColorToColor(segment.fg)\n }\n if (segment.bg !== undefined && segment.bg !== null) {\n bg = ansiColorToColor(segment.bg)\n }\n if (segment.underlineColor !== undefined && segment.underlineColor !== null) {\n underlineColor = ansiColorToColor(segment.underlineColor)\n }\n\n // Build overlay attrs from segment\n const overlayAttrs: CellAttrs = {}\n if (segment.bold !== undefined) overlayAttrs.bold = segment.bold\n if (segment.dim !== undefined) overlayAttrs.dim = segment.dim\n if (segment.italic !== undefined) overlayAttrs.italic = segment.italic\n if (segment.underline !== undefined) {\n overlayAttrs.underline = segment.underline\n }\n if (segment.underlineStyle !== undefined) {\n overlayAttrs.underlineStyle = segment.underlineStyle as UnderlineStyle\n }\n if (segment.inverse !== undefined) overlayAttrs.inverse = segment.inverse\n\n // Use mergeStyles for consistent category-based merging\n const merged = mergeStyles(\n base,\n { fg, bg, underlineColor, attrs: overlayAttrs },\n { preserveDecorations, preserveEmphasis },\n )\n\n // Pass through OSC 8 hyperlink from segment (not an SGR attribute)\n if (segment.hyperlink) {\n merged.hyperlink = segment.hyperlink\n }\n\n return merged\n}\n\n/**\n * Convert ANSI SGR color code to our Color type.\n * Color is: number (256-color index) | { r, g, b } (true color) | null\n */\nfunction ansiColorToColor(code: number): Color {\n // True color (packed RGB with 0x1000000 marker from parseAnsiText)\n if (code >= 0x1000000) {\n const r = (code >> 16) & 0xff\n const g = (code >> 8) & 0xff\n const b = code & 0xff\n return { r, g, b }\n }\n\n // 256 color palette index (0-255)\n if (code < 30 || (code >= 38 && code < 40) || (code >= 48 && code < 90)) {\n // Direct palette index (0-255) — return as-is\n return code\n }\n\n // Standard foreground colors (30-37) map to palette 0-7\n if (code >= 30 && code <= 37) {\n return code - 30\n }\n\n // Standard background colors (40-47) map to palette 0-7\n if (code >= 40 && code <= 47) {\n return code - 40\n }\n\n // Bright foreground colors (90-97) map to palette 8-15\n if (code >= 90 && code <= 97) {\n return code - 90 + 8\n }\n\n // Bright background colors (100-107) map to palette 8-15\n if (code >= 100 && code <= 107) {\n return code - 100 + 8\n }\n\n return null\n}\n\n// ============================================================================\n// Render Text Node (Main Entry Point)\n// ============================================================================\n\n/**\n * Render a Text node.\n *\n * Background colors from nested Text elements are handled at the buffer level\n * (not via ANSI codes) to prevent bg bleed across wrapped text lines.\n * See km-silvery.bg-bleed for details.\n */\nexport function renderText(\n node: AgNode,\n buffer: TerminalBuffer,\n layout: { x: number; y: number; width: number; height: number },\n props: TextProps,\n nodeState: NodeRenderState,\n inheritedBg?: Color,\n inheritedFg?: Color,\n ctx?: PipelineContext,\n): void {\n const { scrollOffset, clipBounds } = nodeState\n const { x, width, height } = layout\n let { y } = layout\n\n // Apply scroll offset\n y -= scrollOffset\n\n // Explicit backgroundColor=\"\" on a Text node means \"no background\" — force\n // null bg to override both inherited bg from ancestor Boxes and any bg\n // already in the buffer cells (set by Box's renderBox fill). The sentinel\n // value `null` is used instead of `undefined` so renderGraphemes uses it\n // directly instead of falling back to buffer.getCellBg().\n if (props.backgroundColor === \"\") {\n inheritedBg = null\n }\n\n // Clip to bounds if specified\n if (clipBounds) {\n if (y + height <= clipBounds.top || y >= clipBounds.bottom) {\n return // Completely outside vertical clip bounds\n }\n if (clipBounds.left !== undefined && clipBounds.right !== undefined) {\n if (x + width <= clipBounds.left || x >= clipBounds.right) {\n return // Completely outside horizontal clip bounds\n }\n }\n }\n\n // --- PreparedText cache: Level 0 (plain text for maxDisplayWidth) ---\n // Compute DOM-level display width budget for truncate-end modes.\n // This limits how much text collectTextWithBg gathers BEFORE ANSI serialization,\n // making OSC 8 hyperlinks and other escape sequences safe by construction.\n let maxDisplayWidth: number | undefined\n const isTruncateEnd =\n props.wrap === false ||\n props.wrap === \"truncate-end\" ||\n props.wrap === \"truncate\" ||\n props.wrap === \"clip\"\n if (isTruncateEnd && width > 0) {\n const cachedPlain = getCachedPlainText(node)\n let lineCount: number\n if (cachedPlain) {\n lineCount = cachedPlain.lineCount\n } else {\n const plainText = collectPlainText(node)\n lineCount = (plainText.match(/\\n/g)?.length ?? 0) + 1\n setCachedPlainText(node, plainText, lineCount)\n }\n maxDisplayWidth = (width + 1) * lineCount\n }\n\n // --- PreparedText cache: Level 1 (collected styled text) ---\n // Collect text content and background segments from this node and all children.\n // Background color from nested Text elements is tracked as BgSegments\n // (not embedded as ANSI codes) to survive text wrapping correctly.\n let text: string\n let bgSegments: BgSegment[]\n let childSpans: ChildSpan[]\n\n // Seed the collection parent context with this Text's own effective props so\n // nested virtual <Text color=\"inherit\"> children can resolve \"inherit\" /\n // \"currentColor\" back to the owner Text's color. Without this, virtual\n // children only see \"{}\" and keyword lookups produced no fg.\n const rootContext: StyleContext = {\n color: props.color,\n backgroundColor: props.backgroundColor,\n bold: props.bold,\n dim: props.dim || props.dimColor,\n italic: props.italic,\n underline: !!(props.underline || props.underlineStyle),\n underlineStyle: props.underlineStyle,\n underlineColor: props.underlineColor,\n inverse: props.inverse,\n strikethrough: props.strikethrough,\n }\n\n // Pass the active context theme as a cache key so that $token ANSI codes\n // embedded in collected text are invalidated when the nearest-ancestor\n // ThemeProvider changes its theme. Without this, a theme change would leave\n // stale ANSI-encoded token colors in the cache (e.g., $primary → blue from\n // the first render), causing the new theme's green to be overridden.\n const contextTheme = getActiveTheme()\n const cachedCollected = getCachedCollectedText(node, maxDisplayWidth, contextTheme)\n if (cachedCollected) {\n text = cachedCollected.text\n bgSegments = cachedCollected.bgSegments as BgSegment[]\n childSpans = cachedCollected.childSpans as ChildSpan[]\n } else {\n const collected = collectTextWithBg(node, rootContext, 0, maxDisplayWidth, ctx)\n text = collected.text\n bgSegments = collected.bgSegments\n childSpans = collected.childSpans\n setCachedCollectedText(node, collected, maxDisplayWidth, contextTheme)\n }\n\n // Get style for this Text node.\n // Inherit foreground from nearest ancestor Box with color prop (CSS semantics).\n const style = getTextStyle(props)\n if (style.fg === null && inheritedFg !== undefined) {\n style.fg = inheritedFg\n }\n // underlineColor=\"currentColor\"/\"inherit\" tracks the text's resolved fg.\n // getTextStyle already produced style.underlineColor = null for the keyword\n // (parseColor(\"currentColor\") === null). Upgrade it here — AFTER fg\n // inheritance has been applied — so the underline matches the visible text.\n if (props.underlineColor === \"currentColor\" || props.underlineColor === \"inherit\") {\n style.underlineColor = style.fg\n }\n\n // --- PreparedText cache: Level 2 (formatted lines per width) ---\n // When text has background color, preserve trailing spaces so bg covers them.\n const hasBg =\n style.bg !== null ||\n bgSegments.length > 0 ||\n (inheritedBg !== undefined && inheritedBg !== null)\n const trim = !hasBg\n const internalTransform = props.internal_transform\n\n let lines: string[]\n let lineOffsets: Array<{ start: number; end: number }>\n\n // Skip format cache when internal_transform is present (may depend on external state)\n const cachedFmt = !internalTransform ? getCachedFormat(node, width, props.wrap, trim) : null\n if (cachedFmt) {\n lines = cachedFmt.lines\n lineOffsets = cachedFmt.hasLineOffsets ? cachedFmt.lineOffsets : []\n } else {\n lines = formatTextLines(text, width, props.wrap, ctx, trim)\n if (internalTransform) {\n lines = lines.map((line, index) => internalTransform(line, index))\n }\n const needLineOffsets = bgSegments.length > 0 || childSpans.length > 0\n lineOffsets = needLineOffsets ? mapLinesToCharOffsets(text, lines, ctx) : []\n if (!internalTransform) {\n setCachedFormat(node, width, props.wrap, trim, lines, lineOffsets, needLineOffsets)\n }\n }\n\n // Render each line\n for (let lineIdx = 0; lineIdx < lines.length && lineIdx < height; lineIdx++) {\n const lineY = y + lineIdx\n // Skip lines outside clip bounds\n if (clipBounds && (lineY < clipBounds.top || lineY >= clipBounds.bottom)) {\n continue\n }\n const line = lines[lineIdx]!\n\n // Pass maxCol to prevent wide characters from overflowing into adjacent\n // containers. Without this, continuation cells outside the text node's\n // layout bounds become stale during incremental rendering.\n // Clip right edge to horizontal clip bounds (overflow:hidden containers).\n // When internal_transform is active, expand maxCol to buffer width so the\n // transformed text (which may be wider than the original layout) is not clipped.\n const layoutRight = internalTransform ? buffer.width : x + width\n const maxCol =\n clipBounds && \"right\" in clipBounds && clipBounds.right !== undefined\n ? Math.min(layoutRight, clipBounds.right)\n : layoutRight\n // Clip left edge to horizontal clip bounds. Without this, text rendered\n // by a node whose x is BEFORE the parent's clip-left (e.g., negative\n // marginLeft inside an overflow:hidden container with a border) would\n // overwrite the parent's left border or padding cells.\n const minCol =\n clipBounds && \"left\" in clipBounds && clipBounds.left !== undefined\n ? clipBounds.left\n : undefined\n const endCol = renderTextLineReturn(\n buffer,\n x,\n lineY,\n line,\n style,\n maxCol,\n inheritedBg,\n ctx,\n minCol,\n )\n\n // Clear remaining cells after text to end of layout width (clipped).\n // When text content shrinks (e.g., breadcrumb changes from long to short path),\n // the parent Box may skip its bg fill (skipBgFill=true when only subtreeDirty).\n // Without explicit clearing here, stale chars from the previous longer text\n // survive in the cloned buffer. This is safe: we only clear within our own\n // layout area, writing spaces with the correct inherited background.\n // Respect minCol so we don't clear cells inside the parent's left border.\n const clearStart = minCol !== undefined ? Math.max(endCol, minCol) : endCol\n if (clearStart < maxCol) {\n const clearBg = inheritedBg ?? null\n for (let cx = clearStart; cx < maxCol && cx < buffer.width; cx++) {\n buffer.setCell(cx, lineY, {\n char: \" \",\n fg: style.fg,\n bg: clearBg,\n underlineColor: null,\n attrs: {\n bold: false,\n dim: false,\n italic: false,\n underline: false,\n inverse: false,\n strikethrough: false,\n blink: false,\n hidden: false,\n },\n wide: false,\n continuation: false,\n })\n }\n }\n\n // Apply background segments from nested Text elements to the buffer.\n // This happens after renderTextLine so the bg is applied to cells\n // that already have the correct character/fg/attrs written.\n if (bgSegments.length > 0 && lineIdx < lineOffsets.length) {\n const { start, end } = lineOffsets[lineIdx]!\n applyBgSegmentsToLine(buffer, x, lineY, line, start, end, bgSegments, ctx)\n }\n }\n\n // Compute inlineRects for virtual text children.\n // Maps each child's display-width span to screen-space rectangles,\n // accounting for text wrapping (one rect per line fragment).\n if (childSpans.length > 0 && lineOffsets.length > 0) {\n computeInlineRects(childSpans, lineOffsets, x, y, lines.length, height)\n }\n}\n\n/**\n * Compute inlineRects for virtual text children based on their display-width spans\n * and the formatted line offsets. For wrapped text, a child may span multiple lines,\n * producing one rect per line fragment.\n *\n * @param childSpans - Virtual text children with their display-width ranges\n * @param lineOffsets - Display-width offset ranges for each formatted line\n * @param parentX - Screen X of the parent Text node\n * @param parentY - Screen Y of the parent Text node (after scroll offset)\n * @param lineCount - Number of formatted lines\n * @param maxHeight - Maximum height (layout height) of the parent Text node\n */\nfunction computeInlineRects(\n childSpans: ChildSpan[],\n lineOffsets: Array<{ start: number; end: number }>,\n parentX: number,\n parentY: number,\n lineCount: number,\n maxHeight: number,\n): void {\n for (const span of childSpans) {\n const rects: Array<{ x: number; y: number; width: number; height: number }> = []\n\n for (let lineIdx = 0; lineIdx < lineCount && lineIdx < maxHeight; lineIdx++) {\n const lineOffset = lineOffsets[lineIdx]\n if (!lineOffset) continue\n\n // Check overlap between span [span.start, span.end) and line [lineOffset.start, lineOffset.end)\n const overlapStart = Math.max(span.start, lineOffset.start)\n const overlapEnd = Math.min(span.end, lineOffset.end)\n if (overlapStart >= overlapEnd) continue\n\n // Convert to screen coordinates\n const rectX = parentX + (overlapStart - lineOffset.start)\n const rectY = parentY + lineIdx\n const rectWidth = overlapEnd - overlapStart\n\n rects.push({ x: rectX, y: rectY, width: rectWidth, height: 1 })\n }\n\n span.node.inlineRects = rects.length > 0 ? rects : null\n }\n}\n","/**\n * Box Rendering - Functions for rendering box elements to the buffer.\n *\n * Contains:\n * - Box rendering (renderBox)\n * - Border rendering (renderBorder)\n * - Scroll indicators (renderScrollIndicators)\n */\n\nimport type { Color, Style, TerminalBuffer } from \"../buffer\"\nimport type { BoxProps, AgNode, Rect } from \"@silvery/ag/types\"\nimport { getPadding } from \"./helpers\"\nimport { getBorderChars, getBorderSize, parseColor } from \"./render-helpers\"\nimport { renderTextLine } from \"./render-text\"\nimport type { NodeRenderState, PipelineContext } from \"./types\"\n\n/**\n * Get the effective background color string for a Box.\n * Returns explicit `backgroundColor` if set, otherwise the Theme's root\n * surface background — Sterling's `bg-surface-default` if present, falling\n * back to the legacy `bg` root for any pre-Sterling Theme shape.\n * Used by both renderBox (paint fill) and render-phase (cascade logic).\n */\nexport function getEffectiveBg(props: BoxProps): string | undefined {\n if (props.backgroundColor) return props.backgroundColor as string\n if (props.theme) {\n const theme = props.theme as unknown as Record<string, unknown>\n const sterlingBg = theme[\"bg-surface-default\"]\n if (typeof sterlingBg === \"string\") return sterlingBg\n const legacyBg = theme[\"bg\"]\n if (typeof legacyBg === \"string\") return legacyBg\n }\n return undefined\n}\n\n// ============================================================================\n// Box Rendering\n// ============================================================================\n\n/**\n * Render a Box node.\n */\nexport function renderBox(\n _node: AgNode,\n buffer: TerminalBuffer,\n layout: Rect,\n props: BoxProps,\n nodeState: NodeRenderState,\n skipBgFill = false,\n inheritedBg?: Color | null,\n bgOnlyChange = false,\n inheritedFg?: Color | null,\n): void {\n const { scrollOffset, clipBounds } = nodeState\n const { x, width, height } = layout\n // Apply scroll offset to y position\n const y = layout.y - scrollOffset\n\n // Skip if completely outside clip bounds\n if (clipBounds) {\n if (y + height <= clipBounds.top || y >= clipBounds.bottom) return\n if (clipBounds.left !== undefined && clipBounds.right !== undefined) {\n if (x + width <= clipBounds.left || x >= clipBounds.right) return\n }\n }\n\n // Fill background if set (explicit backgroundColor or theme.bg).\n // In incremental mode, skipBgFill=true when the box itself hasn't changed\n // (only subtreeDirty). The cloned buffer already has the correct bg fill,\n // and re-filling would destroy child pixels that won't be repainted.\n //\n // bgOnlyChange: when ONLY backgroundColor changed (no content/layout/children\n // changes), use fillBg() which updates bg without overwriting chars. This\n // preserves child content from the cloned buffer, enabling the cascade\n // optimization where clean children are skipped entirely.\n const effectiveBgStr = getEffectiveBg(props)\n if (effectiveBgStr && !skipBgFill) {\n const bg = parseColor(effectiveBgStr)\n // Clip background fill to bounds\n if (clipBounds) {\n const clippedY = Math.max(y, clipBounds.top)\n const clippedHeight = Math.min(y + height, clipBounds.bottom) - clippedY\n let clippedX = x\n let clippedWidth = width\n if (clipBounds.left !== undefined && clipBounds.right !== undefined) {\n clippedX = Math.max(x, clipBounds.left)\n clippedWidth = Math.min(x + width, clipBounds.right) - clippedX\n }\n if (clippedHeight > 0 && clippedWidth > 0) {\n if (bgOnlyChange) {\n buffer.fillBg(clippedX, clippedY, clippedWidth, clippedHeight, bg)\n } else {\n buffer.fill(clippedX, clippedY, clippedWidth, clippedHeight, { bg })\n }\n }\n } else {\n if (bgOnlyChange) {\n buffer.fillBg(x, y, width, height, bg)\n } else {\n buffer.fill(x, y, width, height, { bg })\n }\n }\n }\n\n // Render border if set\n if (props.borderStyle) {\n renderBorder(buffer, x, y, width, height, props, clipBounds, inheritedBg, inheritedFg)\n }\n}\n\n// ============================================================================\n// Border Rendering\n// ============================================================================\n\n/**\n * Render a border around a box.\n */\nexport function renderBorder(\n buffer: TerminalBuffer,\n x: number,\n y: number,\n width: number,\n height: number,\n props: BoxProps,\n clipBounds?: { top: number; bottom: number; left?: number; right?: number },\n inheritedBg?: Color | null,\n inheritedFg?: Color | null,\n): void {\n const chars = getBorderChars(props.borderStyle ?? \"single\")\n // borderColor=\"currentColor\"/\"inherit\" resolves to the Box's own fg —\n // explicit props.color if set, else the inherited fg from the nearest\n // ancestor with a color. Mirrors CSS `border-color: currentColor`.\n let color: Color | null\n if (props.borderColor === \"currentColor\" || props.borderColor === \"inherit\") {\n color = props.color ? parseColor(props.color) : (inheritedFg ?? null)\n } else {\n color = props.borderColor ? parseColor(props.borderColor) : null\n }\n // Preserve the box's background color on border cells. Falls back to\n // inherited bg from the nearest ancestor with backgroundColor, ensuring\n // border cells don't punch transparent holes through parent backgrounds.\n const baseBg = props.backgroundColor ? parseColor(props.backgroundColor) : (inheritedBg ?? null)\n\n // Per-side border background colors — each side falls back to the shorthand\n // borderBackgroundColor, then to the box's own bg / inherited bg.\n const borderBgStr = (props as BoxProps).borderBackgroundColor\n const borderBgBase = borderBgStr ? parseColor(borderBgStr) : baseBg\n const topBorderBgStr = (props as BoxProps).borderTopBackgroundColor\n const bottomBorderBgStr = (props as BoxProps).borderBottomBackgroundColor\n const leftBorderBgStr = (props as BoxProps).borderLeftBackgroundColor\n const rightBorderBgStr = (props as BoxProps).borderRightBackgroundColor\n const topBg = topBorderBgStr ? parseColor(topBorderBgStr) : borderBgBase\n const bottomBg = bottomBorderBgStr ? parseColor(bottomBorderBgStr) : borderBgBase\n const leftBg = leftBorderBgStr ? parseColor(leftBorderBgStr) : borderBgBase\n const rightBg = rightBorderBgStr ? parseColor(rightBorderBgStr) : borderBgBase\n\n const showTop = props.borderTop !== false\n const showBottom = props.borderBottom !== false\n const showLeft = props.borderLeft !== false\n const showRight = props.borderRight !== false\n\n // Helper to check if a row is visible within clip bounds\n const isRowVisible = (row: number): boolean => {\n if (!clipBounds) return row >= 0 && row < buffer.height\n return row >= clipBounds.top && row < clipBounds.bottom && row < buffer.height\n }\n\n // Helper to check if a column is visible within clip bounds\n const isColVisible = (col: number): boolean => {\n if (clipBounds?.left === undefined || clipBounds.right === undefined)\n return col >= 0 && col < buffer.width\n return col >= clipBounds.left && col < clipBounds.right && col < buffer.width\n }\n\n // Top border — corners use the bg of the horizontal side (top/bottom)\n if (showTop && isRowVisible(y)) {\n if (showLeft && isColVisible(x))\n buffer.setCell(x, y, { char: chars.topLeft, fg: color, bg: topBg })\n const hStart = showLeft ? x + 1 : x\n const hEnd = showRight ? x + width - 1 : x + width\n for (let col = hStart; col < hEnd && col < buffer.width; col++) {\n if (isColVisible(col))\n buffer.setCell(col, y, { char: chars.horizontal, fg: color, bg: topBg })\n }\n if (showRight && x + width - 1 < buffer.width && isColVisible(x + width - 1)) {\n buffer.setCell(x + width - 1, y, { char: chars.topRight, fg: color, bg: topBg })\n }\n }\n\n // Side borders — extend range when top/bottom borders are hidden\n const rightVertical = chars.rightVertical ?? chars.vertical\n const sideStart = showTop ? y + 1 : y\n const sideEnd = showBottom ? y + height - 1 : y + height\n for (let row = sideStart; row < sideEnd; row++) {\n if (!isRowVisible(row)) continue\n if (showLeft && isColVisible(x))\n buffer.setCell(x, row, { char: chars.vertical, fg: color, bg: leftBg })\n if (showRight && x + width - 1 < buffer.width && isColVisible(x + width - 1)) {\n buffer.setCell(x + width - 1, row, { char: rightVertical, fg: color, bg: rightBg })\n }\n }\n\n // Bottom border\n const bottomHorizontal = chars.bottomHorizontal ?? chars.horizontal\n const bottomY = y + height - 1\n if (showBottom && isRowVisible(bottomY)) {\n if (showLeft && isColVisible(x)) {\n buffer.setCell(x, bottomY, { char: chars.bottomLeft, fg: color, bg: bottomBg })\n }\n const bStart = showLeft ? x + 1 : x\n const bEnd = showRight ? x + width - 1 : x + width\n for (let col = bStart; col < bEnd && col < buffer.width; col++) {\n if (isColVisible(col))\n buffer.setCell(col, bottomY, { char: bottomHorizontal, fg: color, bg: bottomBg })\n }\n if (showRight && x + width - 1 < buffer.width && isColVisible(x + width - 1)) {\n buffer.setCell(x + width - 1, bottomY, {\n char: chars.bottomRight,\n fg: color,\n bg: bottomBg,\n })\n }\n }\n}\n\n// ============================================================================\n// Outline Rendering\n// ============================================================================\n\n/**\n * Render an outline around a box.\n *\n * Unlike borders, outlines do NOT affect layout dimensions. They draw border\n * characters OUTSIDE the box — one cell beyond each edge, in the gap/margin\n * space between siblings. This matches CSS `outline` semantics.\n *\n * The outline occupies cells at (x-1, y-1) through (x+width, y+height) —\n * entirely outside the box's own rect. Content is never overlapped.\n */\nexport function renderOutline(\n buffer: TerminalBuffer,\n x: number,\n y: number,\n width: number,\n height: number,\n props: BoxProps,\n clipBounds?: { top: number; bottom: number; left?: number; right?: number },\n inheritedBg?: Color | null,\n): void {\n const chars = getBorderChars(props.outlineStyle ?? \"single\")\n const color = props.outlineColor ? parseColor(props.outlineColor) : null\n const bg = props.backgroundColor ? parseColor(props.backgroundColor) : (inheritedBg ?? null)\n const attrs = props.outlineDimColor ? { dim: true } : {}\n\n // Outline draws OUTSIDE the box: one cell beyond each edge\n const ox = x - 1 // outline left column\n const oy = y - 1 // outline top row\n const ow = width + 2 // outline total width\n const oh = height + 2 // outline total height\n\n // Helper to check if a row is visible within clip bounds\n const isRowVisible = (row: number): boolean => {\n if (!clipBounds) return row >= 0 && row < buffer.height\n return row >= clipBounds.top && row < clipBounds.bottom && row < buffer.height\n }\n\n // Helper to check if a column is visible within clip bounds\n const isColVisible = (col: number): boolean => {\n if (clipBounds?.left === undefined || clipBounds.right === undefined)\n return col >= 0 && col < buffer.width\n return col >= clipBounds.left && col < clipBounds.right && col < buffer.width\n }\n\n const showTop = props.outlineTop !== false\n const showBottom = props.outlineBottom !== false\n const showLeft = props.outlineLeft !== false\n const showRight = props.outlineRight !== false\n\n // Top border (one row above the box)\n if (showTop && isRowVisible(oy)) {\n if (showLeft && isColVisible(ox))\n buffer.setCell(ox, oy, { char: chars.topLeft, fg: color, bg, attrs })\n for (let col = ox + 1; col < ox + ow - 1 && col < buffer.width; col++) {\n if (isColVisible(col))\n buffer.setCell(col, oy, { char: chars.horizontal, fg: color, bg, attrs })\n }\n if (showRight && ox + ow - 1 < buffer.width && isColVisible(ox + ow - 1)) {\n buffer.setCell(ox + ow - 1, oy, { char: chars.topRight, fg: color, bg, attrs })\n }\n }\n\n // Side borders — run along the box's own height (y to y+height-1)\n const outlineRightVertical = chars.rightVertical ?? chars.vertical\n const sideStart = showTop ? oy + 1 : oy\n const sideEnd = showBottom ? oy + oh - 1 : oy + oh\n for (let row = sideStart; row < sideEnd; row++) {\n if (!isRowVisible(row)) continue\n if (showLeft && isColVisible(ox))\n buffer.setCell(ox, row, { char: chars.vertical, fg: color, bg, attrs })\n if (showRight && ox + ow - 1 < buffer.width && isColVisible(ox + ow - 1)) {\n buffer.setCell(ox + ow - 1, row, { char: outlineRightVertical, fg: color, bg, attrs })\n }\n }\n\n // Bottom border (one row below the box)\n const outlineBottomHorizontal = chars.bottomHorizontal ?? chars.horizontal\n const bottomY = oy + oh - 1\n if (showBottom && isRowVisible(bottomY)) {\n if (showLeft && isColVisible(ox)) {\n buffer.setCell(ox, bottomY, { char: chars.bottomLeft, fg: color, bg, attrs })\n }\n for (let col = ox + 1; col < ox + ow - 1 && col < buffer.width; col++) {\n if (isColVisible(col))\n buffer.setCell(col, bottomY, { char: outlineBottomHorizontal, fg: color, bg, attrs })\n }\n if (showRight && ox + ow - 1 < buffer.width && isColVisible(ox + ow - 1)) {\n buffer.setCell(ox + ow - 1, bottomY, {\n char: chars.bottomRight,\n fg: color,\n bg,\n attrs,\n })\n }\n }\n}\n\n// ============================================================================\n// Scroll Indicators\n// ============================================================================\n\n/**\n * Render scroll indicators showing hidden items above/below viewport.\n *\n * Two rendering modes:\n * 1. Bordered containers: Indicators appear on the border (e.g., \"───▲42───\")\n * 2. Borderless containers with overflowIndicator: Indicators appear directly\n * after the last visible child (not at the viewport bottom)\n *\n * Uses ▲N for items hidden above, ▼N for items hidden below.\n */\nexport function renderScrollIndicators(\n _node: AgNode,\n buffer: TerminalBuffer,\n layout: Rect,\n props: BoxProps,\n ss: NonNullable<AgNode[\"scrollState\"]>,\n ctx?: PipelineContext,\n): void {\n const border = props.borderStyle ? getBorderSize(props) : { top: 0, bottom: 0, left: 0, right: 0 }\n\n // Inverse bar style: white text on dark background\n const indicatorStyle: Style = {\n fg: 15, // Bright white\n bg: 8, // Dark gray\n attrs: {},\n }\n\n // Determine if we should show indicators for borderless containers\n const showBorderless = props.overflowIndicator === true\n\n // Top indicator\n if (ss.hiddenAbove > 0) {\n const indicator = `\\u25b2${ss.hiddenAbove}`\n\n if (border.top > 0) {\n // Bordered: render centered inverse bar on top border line\n const contentWidth = layout.width - border.left - border.right\n const bar = padCenter(indicator, contentWidth)\n const x = layout.x + border.left\n const y = layout.y\n const maxCol = x + contentWidth\n renderTextLine(buffer, x, y, bar, indicatorStyle, maxCol, undefined, ctx)\n } else if (showBorderless) {\n // Borderless: render centered inverse bar on first content row\n const padding = getPadding(props)\n const contentWidth = layout.width - padding.left - padding.right\n const bar = padCenter(indicator, contentWidth)\n const x = layout.x + padding.left\n const y = layout.y + padding.top\n const maxCol = x + contentWidth\n renderTextLine(buffer, x, y, bar, indicatorStyle, maxCol, undefined, ctx)\n }\n }\n\n // Bottom indicator\n if (ss.hiddenBelow > 0) {\n const indicator = `\\u25bc${ss.hiddenBelow}`\n\n if (border.bottom > 0) {\n // Bordered: render centered inverse bar on bottom border line\n const contentWidth = layout.width - border.left - border.right\n const bar = padCenter(indicator, contentWidth)\n const x = layout.x + border.left\n const y = layout.y + layout.height - 1\n const maxCol = x + contentWidth\n renderTextLine(buffer, x, y, bar, indicatorStyle, maxCol, undefined, ctx)\n } else if (showBorderless) {\n // Borderless: render indicator flush to viewport bottom\n const padding = getPadding(props)\n const contentWidth = layout.width - padding.left - padding.right\n const bar = padCenter(indicator, contentWidth)\n const x = layout.x + padding.left\n const y = layout.y + layout.height - padding.bottom - 1\n const maxCol = x + contentWidth\n renderTextLine(buffer, x, y, bar, indicatorStyle, maxCol, undefined, ctx)\n }\n }\n}\n\n/** Center text within a fixed width, padding with spaces on both sides.\n * Truncates from the right if text exceeds available width. */\nfunction padCenter(text: string, width: number): string {\n if (width <= 0) return \"\"\n if (text.length > width) return text.slice(0, width)\n if (text.length === width) return text\n const leftPad = Math.floor((width - text.length) / 2)\n const rightPad = width - text.length - leftPad\n return \" \".repeat(leftPad) + text + \" \".repeat(rightPad)\n}\n","/**\n * Phase 3.5: Decoration Phase\n *\n * Draws \"outside\" decorations — content that writes pixels OUTSIDE the owning\n * node's rect, into the parent's pixel space. Currently handles outlines.\n *\n * ## Why a separate phase?\n *\n * Outlines draw 1 cell beyond each edge of a Box. Those cells are NOT part of\n * the box's own region — they live in the parent's or even grandparent's\n * space. This breaks the per-node incremental dirty cascade:\n * - The box can be clean while its outline changed\n * - The outline pixels are not in any single node's \"region\"\n * - Clearing on removal requires knowing where the outline previously drew\n *\n * Instead of folding outlines into the cascade (which produced subtle\n * false-positive bugs at realistic scale), we treat them as a separate\n * decoration pass that runs AFTER content rendering on every frame:\n *\n * 1. Before content: restore cells under previous frame's outlines\n * (using snapshots captured when we drew them last time)\n * 2. Content render runs as normal — no outline-specific tracking\n * 3. After content: walk the tree, draw outlines for every node with\n * `outlineStyle`, snapshotting each written cell so the next frame\n * can restore it\n *\n * The snapshots are stored on the TerminalBuffer and travel with it via\n * clone(), so the cascade is naturally fed by the same prevBuffer that\n * drives incremental rendering.\n *\n * This pattern generalizes to other overlays (focus rings, hover halos,\n * selection borders) — any decoration that draws outside its owning node.\n */\n\nimport type { TerminalBuffer, Cell, Color } from \"../buffer\"\nimport type { BoxProps, AgNode } from \"@silvery/ag/types\"\nimport { getBorderSize, getPadding } from \"./helpers\"\nimport { renderOutline, getEffectiveBg } from \"./render-box\"\nimport { parseColor } from \"./render-helpers\"\nimport type { ClipBounds } from \"./types\"\n\n/**\n * Snapshot of a single cell, used to restore it when the overlaying outline\n * is removed on the next frame. Captures the full cell state so restore is\n * byte-identical to what was there before we drew the outline.\n */\nexport interface OutlineCellSnapshot {\n x: number\n y: number\n cell: Cell\n}\n\n/**\n * Restore cells at previously-drawn outline positions to their pre-outline\n * state. Called at the start of each incremental render, before the content\n * phase, on the cloned buffer. No-op when there are no previous snapshots\n * (fresh render or no outlines last frame).\n */\nexport function clearPreviousOutlines(buffer: TerminalBuffer): void {\n const snapshots = buffer.outlineSnapshots\n if (!snapshots || snapshots.length === 0) return\n for (const snap of snapshots) {\n buffer.setCell(snap.x, snap.y, snap.cell)\n }\n buffer.outlineSnapshots = []\n}\n\n/**\n * Walk the node tree, drawing outlines for every node with `outlineStyle`.\n * Captures per-cell snapshots so the next frame can restore these positions.\n *\n * Called AFTER the content render phase on every frame (both fresh and\n * incremental). Mirrors `renderNodeToBuffer`'s state threading for scroll\n * offsets, clip bounds, and inherited background — but does nothing except\n * visit the tree and draw outlines.\n */\nexport function renderDecorationPass(buffer: TerminalBuffer, root: AgNode): void {\n const snapshots: OutlineCellSnapshot[] = []\n walk(root, buffer, 0, undefined, { color: null }, snapshots)\n buffer.outlineSnapshots = snapshots\n}\n\n/**\n * Recursive tree walk. Each invocation corresponds to the state at a single\n * node — scroll offset, clip bounds, inherited background — matching what\n * `renderNodeToBuffer` would have threaded through its `NodeRenderState`.\n */\nfunction walk(\n node: AgNode,\n buffer: TerminalBuffer,\n scrollOffset: number,\n clipBounds: ClipBounds | undefined,\n inheritedBg: { color: Color },\n snapshots: OutlineCellSnapshot[],\n): void {\n // Virtual text nodes have no layout — they're rendered by their parent's\n // text collection pass. Nothing to draw and no children to descend.\n if (!node.layoutNode) return\n const layout = node.boxRect\n if (!layout) return\n\n // Respect the same visibility gates as the content phase.\n if (node.hidden) return\n const props = node.props as BoxProps\n if (props.display === \"none\") return\n\n // Apply scroll offset to the effective y position.\n const y = layout.y - scrollOffset\n\n // Off-screen viewport clipping — mirrors renderNodeToBuffer. Keeps the\n // walker cheap: large scroll containers with many hidden children return\n // early for any node entirely outside the visible window.\n if (y >= buffer.height || y + layout.height <= 0) return\n\n // Compute the effective background the outline should inherit from this\n // box — matches the `boxInheritedBg` calculation in renderOwnContent.\n const effectiveBg = getEffectiveBg(props)\n const theme = props.theme as Record<string, unknown> | undefined\n const themeBg =\n theme && typeof theme[\"bg-surface-default\"] === \"string\"\n ? (theme[\"bg-surface-default\"] as string)\n : theme && typeof theme[\"bg\"] === \"string\"\n ? (theme[\"bg\"] as string)\n : undefined\n const childInheritedBg: { color: Color } = effectiveBg\n ? { color: parseColor(effectiveBg) }\n : themeBg !== undefined\n ? { color: parseColor(themeBg) }\n : inheritedBg\n\n // Draw the outline AFTER content — this means we paint on top of whatever\n // siblings rendered in the gap around this box. The snapshot captures\n // those pre-outline pixels so the next frame can restore them.\n if (node.type === \"silvery-box\" && props.outlineStyle) {\n const boxInheritedBg = effectiveBg ? undefined : inheritedBg.color\n const positions = collectOutlineCells(\n layout.x,\n y,\n layout.width,\n layout.height,\n props,\n clipBounds,\n buffer,\n )\n for (const pos of positions) {\n // Snapshot the cell BEFORE the outline overwrites it.\n snapshots.push({ x: pos.x, y: pos.y, cell: buffer.getCell(pos.x, pos.y) })\n }\n renderOutline(\n buffer,\n layout.x,\n y,\n layout.width,\n layout.height,\n props,\n clipBounds,\n boxInheritedBg,\n )\n }\n\n // Descend into children with the appropriate state. Scroll containers\n // override scrollOffset for their normal-flow children; sticky children\n // use their computed sticky offset. Clip bounds tighten for overflow\n // containers.\n if (node.children.length === 0) return\n\n const isScrollContainer = props.overflow === \"scroll\" && node.scrollState\n const clipX = (props.overflowX ?? props.overflow) === \"hidden\"\n const clipY = (props.overflowY ?? props.overflow) === \"hidden\"\n\n if (isScrollContainer) {\n const ss = node.scrollState!\n const childClip = computeChildClip(layout, props, clipBounds, 0, false, true)\n // Non-sticky children: rendered with container scroll offset.\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i]\n if (!child) continue\n const cp = child.props as BoxProps\n if (cp.position === \"sticky\") continue\n if (i < ss.firstVisibleChild || i > ss.lastVisibleChild) continue\n walk(child, buffer, ss.offset, childClip, childInheritedBg, snapshots)\n }\n // Sticky children: rendered at their computed sticky offset.\n if (ss.stickyChildren) {\n for (const sticky of ss.stickyChildren) {\n const child = node.children[sticky.index]\n if (!child) continue\n const stickyOffset = sticky.naturalTop - sticky.renderOffset\n walk(child, buffer, stickyOffset, childClip, childInheritedBg, snapshots)\n }\n }\n } else {\n const childClip =\n clipX || clipY\n ? computeChildClip(layout, props, clipBounds, scrollOffset, clipX, clipY)\n : clipBounds\n for (const child of node.children) {\n walk(child, buffer, scrollOffset, childClip, childInheritedBg, snapshots)\n }\n }\n}\n\n/**\n * Compute the cells an outline would write, given the same inputs\n * `renderOutline` uses. Kept in lockstep with render-box.ts — any change to\n * the outline geometry (e.g., new outline styles, per-side toggles) must be\n * mirrored here. If they drift, the snapshots won't cover every cell the\n * next `renderOutline` call writes, and stale pixels will leak through.\n *\n * Uses the SAME visibility checks as `renderOutline` so the snapshot set is\n * an exact match for the cell set the renderer will overwrite.\n */\nfunction collectOutlineCells(\n x: number,\n y: number,\n width: number,\n height: number,\n props: BoxProps,\n clipBounds: ClipBounds | undefined,\n buffer: TerminalBuffer,\n): { x: number; y: number }[] {\n const out: { x: number; y: number }[] = []\n const ox = x - 1\n const oy = y - 1\n const ow = width + 2\n const oh = height + 2\n\n const isRowVisible = (row: number): boolean => {\n if (!clipBounds) return row >= 0 && row < buffer.height\n return row >= clipBounds.top && row < clipBounds.bottom && row < buffer.height\n }\n const isColVisible = (col: number): boolean => {\n if (clipBounds?.left === undefined || clipBounds.right === undefined)\n return col >= 0 && col < buffer.width\n return col >= clipBounds.left && col < clipBounds.right && col < buffer.width\n }\n\n const showTop = props.outlineTop !== false\n const showBottom = props.outlineBottom !== false\n const showLeft = props.outlineLeft !== false\n const showRight = props.outlineRight !== false\n\n // Top row\n if (showTop && isRowVisible(oy)) {\n if (showLeft && isColVisible(ox)) out.push({ x: ox, y: oy })\n for (let col = ox + 1; col < ox + ow - 1 && col < buffer.width; col++) {\n if (isColVisible(col)) out.push({ x: col, y: oy })\n }\n if (showRight && ox + ow - 1 < buffer.width && isColVisible(ox + ow - 1)) {\n out.push({ x: ox + ow - 1, y: oy })\n }\n }\n\n // Sides\n const sideStart = showTop ? oy + 1 : oy\n const sideEnd = showBottom ? oy + oh - 1 : oy + oh\n for (let row = sideStart; row < sideEnd; row++) {\n if (!isRowVisible(row)) continue\n if (showLeft && isColVisible(ox)) out.push({ x: ox, y: row })\n if (showRight && ox + ow - 1 < buffer.width && isColVisible(ox + ow - 1)) {\n out.push({ x: ox + ow - 1, y: row })\n }\n }\n\n // Bottom row\n const bottomY = oy + oh - 1\n if (showBottom && isRowVisible(bottomY)) {\n if (showLeft && isColVisible(ox)) out.push({ x: ox, y: bottomY })\n for (let col = ox + 1; col < ox + ow - 1 && col < buffer.width; col++) {\n if (isColVisible(col)) out.push({ x: col, y: bottomY })\n }\n if (showRight && ox + ow - 1 < buffer.width && isColVisible(ox + ow - 1)) {\n out.push({ x: ox + ow - 1, y: bottomY })\n }\n }\n\n return out\n}\n\n/**\n * Local copy of `computeChildClipBounds` from render-phase.ts. The decoration\n * walker can't import private render-phase helpers without tangling modules,\n * so we reproduce the same computation here. Must stay in sync.\n */\nfunction computeChildClip(\n layout: NonNullable<AgNode[\"boxRect\"]>,\n props: BoxProps,\n parentClip: ClipBounds | undefined,\n scrollOffset: number,\n horizontal: boolean,\n vertical: boolean,\n): ClipBounds {\n const border = props.borderStyle ? getBorderSize(props) : { top: 0, bottom: 0, left: 0, right: 0 }\n const padding = getPadding(props)\n const adjustedY = layout.y - scrollOffset\n const nodeClip: ClipBounds = vertical\n ? {\n top: adjustedY + border.top + padding.top,\n bottom: adjustedY + layout.height - border.bottom - padding.bottom,\n }\n : { top: -Infinity, bottom: Infinity }\n if (horizontal) {\n nodeClip.left = layout.x + border.left + padding.left\n nodeClip.right = layout.x + layout.width - border.right - padding.right\n }\n if (!parentClip) return nodeClip\n const result: ClipBounds = {\n top: vertical ? Math.max(parentClip.top, nodeClip.top) : parentClip.top,\n bottom: vertical ? Math.min(parentClip.bottom, nodeClip.bottom) : parentClip.bottom,\n }\n if (horizontal && nodeClip.left !== undefined && nodeClip.right !== undefined) {\n result.left = Math.max(parentClip.left ?? 0, nodeClip.left)\n result.right = Math.min(parentClip.right ?? Infinity, nodeClip.right)\n } else if (parentClip.left !== undefined && parentClip.right !== undefined) {\n result.left = parentClip.left\n result.right = parentClip.right\n }\n return result\n}\n","/**\n * Cascade Predicates — Pure boolean logic extracted from renderNodeToBuffer.\n *\n * TEST/STRICT-ONLY ORACLE: In production, the reactive system (alien-signals)\n * drives cascade computation. This module is only used as a verification oracle\n * when SILVERY_REACTIVE_VERIFY=1 or SILVERY_REACTIVE=0 (fallback mode). The\n * bundler tree-shakes it when STRICT is off since all call sites are gated\n * behind `_reactiveVerifyEnabled` or `!_reactiveEnabled`.\n *\n * These 6 computed values (plus 1 intermediate: textPaintDirty) control the\n * entire incremental rendering cascade. Extracted here for exhaustive testing.\n *\n * The actual rendering code in render-phase.ts computes some inputs inline\n * (absoluteChildMutated, descendantOverflowChanged require node tree access),\n * but the boolean algebra is identical.\n *\n * TRUTH TABLE (key invariants):\n *\n * ┌─────────────────────────────────────────────────────────────────────────────┐\n * │ canSkipEntireSubtree │\n * │ = hasPrevBuffer && !contentDirty && !stylePropsDirty && !layoutChanged │\n * │ && !subtreeDirty && !childrenDirty && !childPositionChanged │\n * │ && !ancestorLayoutChanged │\n * │ True only when hasPrevBuffer=true AND all 7 dirty flags are false. │\n * │ When true, the node is skipped entirely (clone has correct pixels). │\n * │ NOTE: render-phase.ts also checks !scrollOffsetChanged (node-level │\n * │ defensive check for scroll containers — not modeled here). │\n * ├─────────────────────────────────────────────────────────────────────────────┤\n * │ textPaintDirty (intermediate) │\n * │ = isTextNode && stylePropsDirty │\n * │ For TEXT nodes, stylePropsDirty IS a content area change (no borders). │\n * ├─────────────────────────────────────────────────────────────────────────────┤\n * │ contentAreaAffected │\n * │ = contentDirty || layoutChanged || childPositionChanged │\n * │ || childrenDirty || bgDirty || textPaintDirty │\n * │ || absoluteChildMutated || descendantOverflowChanged │\n * │ True when anything changed that affects the node's content area. │\n * │ Excludes border-only paint changes for BOX nodes. Outlines are NOT │\n * │ tracked here — they're handled by a separate decoration pass. │\n * ├─────────────────────────────────────────────────────────────────────────────┤\n * │ bgRefillNeeded │\n * │ = hasPrevBuffer && !contentAreaAffected && subtreeDirty && hasBgColor │\n * │ Descendant changed inside a bg-bearing Box. Forces bg refill. │\n * │ Mutually exclusive with contentAreaAffected (gated on !contentAreaAffected).│\n * ├─────────────────────────────────────────────────────────────────────────────┤\n * │ contentRegionCleared │\n * │ = (hasPrevBuffer || ancestorCleared) && contentAreaAffected │\n * │ && !hasBgColor │\n * │ Clear region with inherited bg when content changed but no own bg fill. │\n * │ False when hasPrevBuffer=false AND ancestorCleared=false (fresh buffer). │\n * ├─────────────────────────────────────────────────────────────────────────────┤\n * │ skipBgFill │\n * │ = hasPrevBuffer && !ancestorCleared && !contentAreaAffected │\n * │ && !bgRefillNeeded │\n * │ Clone already has correct bg. Skip redundant fill. │\n * ├─────────────────────────────────────────────────────────────────────────────┤\n * │ childrenNeedFreshRender │\n * │ = (hasPrevBuffer || ancestorCleared) && (contentAreaAffected │\n * │ || bgRefillNeeded) │\n * │ Children must re-render (childHasPrev=false). │\n * │ False when hasPrevBuffer=false AND ancestorCleared=false (fresh buffer). │\n * └─────────────────────────────────────────────────────────────────────────────┘\n *\n * KEY INVARIANTS:\n * 1. contentAreaAffected && bgRefillNeeded can never both be true\n * (bgRefillNeeded is gated on !contentAreaAffected)\n * 2. contentRegionCleared && skipBgFill can never both be true\n * (contentRegionCleared requires contentAreaAffected; skipBgFill requires !contentAreaAffected)\n * 3. When !hasPrevBuffer && !ancestorCleared: contentRegionCleared=false, childrenNeedFreshRender=false\n * (both gated on hasPrevBuffer || ancestorCleared)\n * 4. canSkipEntireSubtree requires hasPrevBuffer=true\n */\n\n// ============================================================================\n// COMPLETE INVALIDATION MODEL\n// ============================================================================\n//\n// This section documents EVERY condition that affects skip/render/clear/buffer\n// decisions in the silvery render pipeline. The 14 CascadeInputs below model the\n// core boolean algebra, but the real pipeline has additional conditions checked\n// inline in render-phase.ts that are not captured in computeCascade(). This\n// document is the authoritative inventory.\n//\n// ┌─────────────────────────────────────────────────────────────────────────────┐\n// │ PART 1: ALL INVALIDATION INPUTS │\n// │ │\n// │ Input │ Owner │ Set by │ Cleared by │ Lifetime │ In computeCascade? │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ contentDirtyEpoch │ reconciler │ commitUpdate, │ advanceRenderEpoch (O(1)) │ per-render-pass │ YES (contentDirty) │\n// │ │ │ commitTextUpdate, │ clearNodeDirtyFlags (skip) │ │ │\n// │ │ │ appendChild, etc. │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ stylePropsDirtyEpoch │ reconciler │ commitUpdate │ advanceRenderEpoch │ per-render-pass │ YES (stylePropsDirty)│\n// │ │ │ (always for visual │ clearNodeDirtyFlags (skip) │ │ │\n// │ │ │ changes; survives │ │ │ │\n// │ │ │ measure phase │ │ │ │\n// │ │ │ clearing of │ │ │ │\n// │ │ │ contentDirty) │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ bgDirtyEpoch │ reconciler │ commitUpdate (when │ advanceRenderEpoch │ per-render-pass │ YES (bgDirty) │\n// │ │ │ backgroundColor, │ clearNodeDirtyFlags (skip) │ │ │\n// │ │ │ borderStyle removed,│ │ │ │\n// │ │ │ outlineStyle │ │ │ │\n// │ │ │ removed, or theme │ │ │ │\n// │ │ │ changed) │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ subtreeDirtyEpoch │ reconciler │ markSubtreeDirty │ advanceRenderEpoch │ per-render-pass │ YES (subtreeDirty) │\n// │ │ + layout │ (walks up from any │ clearNodeDirtyFlags (skip) │ │ │\n// │ │ │ dirty descendant); │ │ │ │\n// │ │ │ propagateLayout │ │ │ │\n// │ │ │ (when child rect │ │ │ │\n// │ │ │ changes); scrollPhase│ │ │ │\n// │ │ │ (on offset/range │ │ │ │\n// │ │ │ change); stickyPhase│ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ childrenDirtyEpoch │ reconciler │ appendChild, │ advanceRenderEpoch │ per-render-pass │ YES (childrenDirty)│\n// │ │ │ removeChild, │ clearNodeDirtyFlags (skip) │ │ │\n// │ │ │ insertBefore, │ │ │ │\n// │ │ │ clearContainer │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ layoutChangedThisFrame │ layout │ propagateLayout │ advanceRenderEpoch │ per-render-pass │ YES (layoutChanged)│\n// │ │ │ (when rect differs │ clearNodeDirtyFlags (skip) │ │ via isCurrentEpoch │\n// │ │ │ from prevLayout) │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ Flexily isDirty │ reconciler │ commitUpdate (when │ cleared by Flexily after │ per-layout-pass │ NO — consumed by │\n// │ (via markDirty()) │ │ layout props change)│ calculateLayout() runs │ │ layout phase only │\n// │ │ │ │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ childPositionChanged │ render │ hasChildPositionChanged│ implicit (per-frame │ per-render-frame│ YES │\n// │ (computed inline) │ │ compares child │ computation, not stored) │ │ │\n// │ │ │ boxRect.x/y vs │ │ │ │\n// │ │ │ prevLayout.x/y │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ ancestorLayoutChanged │ render │ propagated top-down │ implicit (per-frame │ per-render-frame│ YES │\n// │ (threaded state) │ │ via NodeRenderState │ propagation via nodeState) │ │ │\n// │ │ │ = isCurrentEpoch( │ │ │ │\n// │ │ │ node.layoutChanged │ │ │ │\n// │ │ │ ThisFrame) || │ │ │ │\n// │ │ │ parent's value │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ ancestorCleared │ render │ propagated top-down │ implicit (per-frame │ per-render-frame│ YES │\n// │ (threaded state) │ │ = contentRegion- │ propagation via nodeState) │ │ │\n// │ │ │ Cleared || (parent │ │ │ │\n// │ │ │ ancestorCleared && │ │ │ │\n// │ │ │ !effectiveBg) │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ hasPrevBuffer │ render │ top-level: prevBuf │ implicit (per-frame │ per-render-frame│ YES │\n// │ (threaded state) │ │ && same dimensions; │ propagation via nodeState) │ │ │\n// │ │ │ per-child: false │ │ │ │\n// │ │ │ when childrenDirty │ │ │ │\n// │ │ │ || childPosition- │ │ │ │\n// │ │ │ Changed || children-│ │ │ │\n// │ │ │ NeedFreshRender │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ isTextNode │ inherent │ node.type at │ never (immutable) │ node lifetime │ YES │\n// │ │ │ creation │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ hasBgColor │ render │ getEffectiveBg( │ implicit (per-frame │ per-render-frame│ YES │\n// │ (computed inline) │ │ props): explicit │ computation from props) │ │ │\n// │ │ │ backgroundColor or │ │ │ │\n// │ │ │ theme.bg │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ absoluteChildMutated │ render │ buildCascadeInputs: │ implicit (per-frame │ per-render-frame│ YES │\n// │ (computed inline) │ │ any absolute child │ computation) │ │ │\n// │ │ │ has childrenDirty, │ │ │ │\n// │ │ │ layoutChanged, or │ │ │ │\n// │ │ │ childPositionChanged│ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ descendantOverflowChanged │ render │ buildCascadeInputs │ implicit (per-frame │ per-render-frame│ YES │\n// │ (computed inline) │ │ → hasDescendant- │ computation) │ │ │\n// │ │ │ OverflowChanged: │ │ │ │\n// │ │ │ recursive check if │ │ │ │\n// │ │ │ descendant prev- │ │ │ │\n// │ │ │ Layout overflows │ │ │ │\n// │ │ │ this node's rect │ │ │ │\n// │ │ │ AND layoutChanged- │ │ │ │\n// │ │ │ ThisFrame is current │ │ │ │\n// ├──────────────────────────┼────────────┼─────────────────────┼────────────────────────────┼─────────────────┼────────────────────┤\n// │ │\n// │ THE FOLLOWING INPUTS ARE NOT IN computeCascade — CHECKED INLINE IN render-phase.ts │\n// │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ scrollOffsetChanged │ render │ inline comparison: │ implicit (per-frame │ per-render-frame│ NO — checked in │\n// │ │ │ node.scrollState. │ computation) │ │ canSkipEntireSub- │\n// │ │ │ offset !== node. │ │ │ tree (prevents skip│\n// │ │ │ scrollState. │ │ │ when scroll offset │\n// │ │ │ prevOffset │ │ │ changed) │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ node.hidden │ reconciler │ hideInstance / │ unhideInstance │ until unhidden │ NO — early return │\n// │ │ │ hideTextInstance │ │ │ before cascade │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ display=\"none\" │ reconciler │ commitUpdate (prop) │ commitUpdate (prop change) │ until prop change│ NO — early return │\n// │ │ │ │ │ │ before cascade │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ !node.layoutNode │ reconciler │ node creation │ never (immutable) │ node lifetime │ NO — early return │\n// │ (virtual text node) │ │ (virtual text has │ │ │ (rendered by parent │\n// │ │ │ no Yoga layout) │ │ │ collectTextContent) │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ off-screen (viewport │ render │ inline: screenY >= │ implicit (per-frame │ per-render-frame│ NO — early return │\n// │ clipping) │ │ buffer.height || │ computation) │ │ (dirty flags │\n// │ │ │ screenY + height │ │ │ preserved for when │\n// │ │ │ <= 0 │ │ │ scrolled into view)│\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ prevBuffer dimension │ render │ renderPhase entry: │ implicit (per-frame) │ per-render-pass │ NO — sets top-level│\n// │ mismatch │ │ prevBuffer.width │ │ │ hasPrevBuffer=false│\n// │ │ │ !== layout.width │ │ │ (full fresh render)│\n// │ │ │ || height mismatch │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ bufferIsCloned │ render │ set once at render- │ implicit (per-frame) │ per-render-pass │ NO — guards │\n// │ (threaded state) │ │ Phase entry from │ │ │ clearExcessArea │\n// │ │ │ hasPrevBuffer │ │ │ (no stale pixels │\n// │ │ │ │ │ │ on fresh buffer) │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ hasDescendantWithBg │ render │ inline tree walk │ implicit (per-frame) │ per-render-frame│ NO — disables │\n// │ (computed inline) │ │ when bgOnlyChange │ │ │ bgOnlyChange fast │\n// │ │ │ is true │ │ │ path │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ isStyleOnlyDirty │ reconciler │ trackStyleOnlyDirty │ clearDirtyTracking (post- │ per-render-pass │ NO — enables text │\n// │ (module set) │ │ in commitUpdate │ render) │ │ restyle fast path │\n// │ │ │ when contentChanged │ │ │ (CURRENTLY DISABLED)│\n// │ │ │ =\"style\" && no │ │ │ │\n// │ │ │ layout/content/bg │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ hasChildWithBg │ render │ inline tree walk │ implicit (per-frame) │ per-render-frame│ NO — disables text │\n// │ (computed inline) │ │ when text style- │ │ │ restyle fast path │\n// │ │ │ only path active │ │ │ (CURRENTLY DISABLED)│\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ scroll container tier │ render │ planScrollRender() │ implicit (per-frame) │ per-render-frame│ NO — separate tier │\n// │ (shift/clear/subtree) │ │ pure function from │ │ │ planner with own │\n// │ │ │ scrollOffsetChanged,│ │ │ inputs (see below) │\n// │ │ │ visibleRangeChanged,│ │ │ │\n// │ │ │ hasStickyChildren, │ │ │ │\n// │ │ │ childrenNeedFresh- │ │ │ │\n// │ │ │ Render, childrenDirty│ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ stickyForceRefresh │ render │ scroll: planScroll- │ implicit (per-frame) │ per-render-frame│ NO — forces all │\n// │ │ │ Render (Tier 3 + │ │ │ first-pass children│\n// │ │ │ hasStickyChildren); │ │ │ to hasPrevBuffer= │\n// │ │ │ normal: hasPrev && │ │ │ false │\n// │ │ │ hasStickyChildren │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ visibleRangeChanged │ render │ inline comparison: │ implicit (per-frame) │ per-render-frame│ NO — input to │\n// │ (scroll containers) │ │ firstVisibleChild │ │ │ planScrollRender │\n// │ │ │ !== prev or last │ │ │ │\n// │ │ │ !== prev │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ nodeTheme (theme prop) │ reconciler │ commitUpdate (prop) │ per-frame (read from props)│ per-render-frame│ NO — pushContext- │\n// │ │ │ │ │ │ Theme/popContext- │\n// │ │ │ │ │ │ Theme during render│\n// │ │ │ │ │ │ (bgDirtyEpoch set │\n// │ │ │ │ │ │ on theme change) │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ clipBounds │ render │ computeChildClip- │ implicit (per-frame) │ per-render-frame│ NO — passed through│\n// │ (threaded state) │ │ Bounds from │ │ │ nodeState; used for│\n// │ │ │ overflow=hidden/ │ │ │ clipping fills and │\n// │ │ │ scroll containers │ │ │ text rendering │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ inheritedBg / inheritedFg │ render │ threaded top-down: │ implicit (per-frame) │ per-render-frame│ NO — used for text │\n// │ (threaded state) │ │ computed from │ │ │ bg inheritance and │\n// │ │ │ effectiveBg/color/ │ │ │ region clearing │\n// │ │ │ theme at each node │ │ │ │\n// └──────────────────────────┴────────────┴─────────────────────┴────────────────────────────┴─────────────────┴────────────────────┘\n//\n// ┌─────────────────────────────────────────────────────────────────────────────┐\n// │ PART 2: NON-LOCAL DEPENDENCIES THAT CAN FORCE REPAINT │\n// └─────────────────────────────────────────────────────────────────────────────┘\n//\n// 1. ANCESTOR BACKGROUND CHANGE\n// When an ancestor's backgroundColor changes, the ancestor's bgDirty triggers\n// contentAreaAffected, which cascades childrenNeedFreshRender=true to all\n// descendants. Descendants get hasPrevBuffer=false, forcing full re-render.\n// If bgOnlyChange were enabled (currently disabled), the fillBg() path would\n// update bg in-place, but only when no descendant has its own bg\n// (hasDescendantWithBg check).\n//\n// 2. ANCESTOR LAYOUT CHANGE\n// When an ancestor moves or resizes, ancestorLayoutChanged propagates down\n// through the entire subtree. Even if a descendant has no dirty flags, its\n// pixels in the cloned buffer are at wrong absolute coordinates. The skip\n// condition checks !ancestorLayoutChanged. Additionally, the parent's\n// childrenNeedFreshRender cascade sets childHasPrev=false.\n// Key: ancestorLayoutChanged does NOT break at backgroundColor boundaries\n// (unlike ancestorCleared), because bg fill doesn't fix wrong coordinates.\n//\n// 3. SIBLING POSITION SHIFT\n// When a sibling resizes, other siblings shift positions (flexbox reflow).\n// hasChildPositionChanged detects this at the parent level by comparing each\n// child's boxRect.x/y to prevLayout.x/y. Triggers childrenNeedRepaint=true,\n// setting childHasPrev=false for all children.\n//\n// 4. ANCESTOR CLEAR CASCADE\n// When an ancestor without backgroundColor clears its region (contentRegion-\n// Cleared=true), ancestorCleared propagates down. This cascade BREAKS at\n// nodes with backgroundColor — their bg fill covers the stale pixels, so\n// their children don't see stale buffer content.\n//\n// 5. ABSOLUTE CHILD MUTATION → PARENT CLEAR\n// When an absolute-positioned child changes structure (children mount/unmount,\n// layout shift), absoluteChildMutated forces the PARENT's contentAreaAffected\n// =true. This clears the parent's entire region and re-renders all normal-flow\n// children, removing stale overlay pixels from gap areas.\n//\n// 6. DESCENDANT OVERFLOW → ANCESTOR CLEAR\n// When a descendant overflows beyond an ancestor's rect and then shrinks,\n// the stale overflow pixels are OUTSIDE the descendant's parent's content\n// area. hasDescendantOverflowChanged recursively detects this at the ancestor\n// level and triggers contentAreaAffected + clearDescendantOverflowRegions.\n//\n// 7. SCROLL OFFSET CHANGE → SUBTREE RE-RENDER\n// The scroll phase sets subtreeDirty on the scroll container when offset or\n// visible range changes. The render phase then plans a scroll tier:\n// - Tier 1 (shift): buffer.scrollRegion shifts pixels; only edges re-render\n// - Tier 2 (clear): full viewport clear; all children get hasPrevBuffer=false\n// - Tier 3 (subtree): only dirty descendants re-render\n// Tier 1 is UNSAFE with sticky children (sticky overwrite + shift = corruption).\n//\n// 8. STICKY CHILDREN → stickyForceRefresh\n// When sticky children exist in Tier 3 (or in normal containers), all\n// first-pass children are forced to re-render (hasPrevBuffer=false). The\n// cloned buffer has stale content from previous frames' sticky positions.\n// A pre-clear to null bg ensures fresh render baseline.\n//\n// 9. THEME CHANGE CASCADE\n// When a node's `theme` prop changes, bgDirtyEpoch is set (because theme\n// contains bg). pushContextTheme/popContextTheme during rendering ensures\n// all $token-based colors resolve to new values. Children re-render because\n// bgDirty → contentAreaAffected → childrenNeedFreshRender.\n//\n// 10. VISIBLE RANGE CHANGE (scroll containers)\n// When firstVisibleChild/lastVisibleChild changes (scroll phase detects\n// this), the scroll container's subtreeDirtyEpoch is set and\n// visibleRangeChanged feeds into planScrollRender to select the tier.\n//\n// ┌─────────────────────────────────────────────────────────────────────────────┐\n// │ PART 3: BUFFER VALIDITY STATES AND TRANSITIONS │\n// └─────────────────────────────────────────────────────────────────────────────┘\n//\n// A node's cached buffer (pixels in the cloned TerminalBuffer) can be in one of\n// these validity states:\n//\n// VALID (canSkipEntireSubtree = true)\n// All of: hasPrevBuffer=true, no dirty flags, no ancestorLayoutChanged,\n// no scrollOffsetChanged. The cloned buffer has correct pixels at correct\n// positions. Node is skipped entirely.\n//\n// STALE_CONTENT (hasPrevBuffer=true, some dirty flag set)\n// The buffer has pixels from the previous frame but they're wrong:\n// - contentDirty: text content or content-affecting props changed\n// - stylePropsDirty: visual style changed (color, border, etc.)\n// - bgDirty: background color changed\n// - childrenDirty: children added/removed/reordered\n// - layoutChanged: node moved or resized\n// - subtreeDirty: some descendant changed (node itself may be skippable)\n// - childPositionChanged: siblings shifted positions\n// - absoluteChildMutated: overlay child changed\n// - descendantOverflowChanged: overflow descendant changed\n// Action: re-render with clearing as appropriate.\n//\n// STALE_POSITION (hasPrevBuffer=true, ancestorLayoutChanged=true)\n// The buffer has correct content but at WRONG coordinates because an\n// ancestor moved/resized. The node itself has no dirty flags.\n// Action: must re-render at new position; can't skip.\n//\n// STALE_SCROLL (hasPrevBuffer=true, scrollOffsetChanged=true)\n// The buffer has correct content but scroll offset changed, so visible\n// children may have shifted. Defensive check in canSkipEntireSubtree.\n// Action: apply scroll tier strategy (shift/clear/subtree).\n//\n// CLEARED (hasPrevBuffer=false or true, ancestorCleared=true)\n// An ancestor erased this node's buffer region. The buffer at this\n// position contains inherited bg (spaces), not previous content.\n// - If hasPrevBuffer=false: fresh render, no clearing needed.\n// - If ancestorCleared=true: parent cleared, descendants may still need\n// their own clearing for sub-regions.\n// Breaks at backgroundColor boundaries (bg fill covers cleared area).\n//\n// FRESH (hasPrevBuffer=false, ancestorCleared=false)\n// First render or dimension change. Buffer is a blank TerminalBuffer\n// (all cells empty). No clearing needed, no skipping possible.\n// contentRegionCleared=false and childrenNeedFreshRender=false because\n// both are gated on (hasPrevBuffer || ancestorCleared).\n//\n// FRESH_OVERLAY (hasPrevBuffer=false, ancestorCleared=false, absolute/sticky)\n// Buffer at this position contains first-pass content from normal-flow\n// siblings (not \"previous frame\" content). Used for absolute and sticky\n// children in the second/third rendering pass.\n// - ancestorCleared=false prevents transparent overlays from clearing\n// the normal-flow content underneath.\n//\n// State transitions per frame:\n// VALID --[dirty flag set]--> STALE_*\n// VALID --[ancestor layout]--> STALE_POSITION\n// VALID --[scroll change]---> STALE_SCROLL\n// STALE_* --[re-render]-----> VALID (dirty flags cleared)\n// FRESH --[first render]----> VALID (buffer populated)\n// any --[dimension change]--> FRESH (new buffer allocated)\n// any --[parent cascade]----> CLEARED (parent cleared region)\n//\n// ┌─────────────────────────────────────────────────────────────────────────────┐\n// │ PART 4: REASONS A CACHED BUFFER CAN BE INVALID │\n// └─────────────────────────────────────────────────────────────────────────────┘\n//\n// The cloned buffer can have stale pixels for these reasons:\n//\n// 1. Text content changed (contentDirtyEpoch) — old chars in buffer\n// 2. Style props changed (stylePropsDirtyEpoch) — old colors/attrs in buffer\n// 3. Background changed (bgDirtyEpoch) — old bg color, or removed bg leaving\n// stale colored pixels that need clearing\n// 4. Children restructured (childrenDirtyEpoch) — old children's pixels remain;\n// gap areas may have orphaned content\n// 5. Layout changed (layoutChangedThisFrame) — pixels at wrong position;\n// node may have grown (new area uninitialized) or shrunk (excess area stale)\n// 6. Child position shifted (childPositionChanged) — siblings moved, gap areas\n// between children have stale pixels from old positions\n// 7. Absolute child mutated (absoluteChildMutated) — overlay pixels in gap areas\n// between current children are stale from old overlay positions\n// 8. Descendant overflow changed (descendantOverflowChanged) — pixels beyond this\n// node's rect are stale from previous overflow that no longer extends there\n// 9. Scroll offset changed (scrollOffsetChanged) — children's visual positions\n// shifted; buffer has content at old scroll positions\n// 10. Ancestor layout changed (ancestorLayoutChanged) — this node's absolute\n// position in the buffer is wrong even though its own layout didn't change\n// 11. Ancestor cleared (ancestorCleared) — an ancestor erased this area; buffer\n// has inherited bg, not this node's content\n// 12. Sticky children stale positioning — cloned buffer has pixels from previous\n// frames' sticky render positions; stickyForceRefresh pre-clears and forces\n// all first-pass children to re-render\n// 13. Node shrunk (clearExcessArea) — old bounds were larger; excess pixels remain\n// in the right/bottom margin of the old rect\n// 14. Node hidden/unhidden — Suspense boundary toggled visibility\n// 15. display=\"none\" toggled — node occupies 0x0 space but old pixels may remain\n// 16. Border/outline removed (bgDirtyEpoch) — stale border/outline characters\n// persist in the clone; bgDirty ensures contentAreaAffected triggers clearing\n// 17. Theme changed (bgDirtyEpoch) — all $token colors resolve differently;\n// the entire subtree's colors are stale\n//\n// ┌─────────────────────────────────────────────────────────────────────────────┐\n// │ PART 5: PIPELINE PHASE OWNERSHIP SUMMARY │\n// └─────────────────────────────────────────────────────────────────────────────┘\n//\n// RECONCILER (host-config.ts):\n// Sets: contentDirtyEpoch, stylePropsDirtyEpoch, bgDirtyEpoch,\n// childrenDirtyEpoch, hidden\n// Calls: layoutNode.markDirty() (Flexily's isDirty propagates to root)\n// Propagates: subtreeDirtyEpoch (upward via markSubtreeDirty)\n// Tracks: contentDirtyNodes, styleOnlyDirtyNodes,\n// scrollDirtyNodes (in dirty-tracking.ts)\n//\n// LAYOUT PHASE (layout-phase.ts):\n// Sets: layoutChangedThisFrame (via propagateLayout)\n// Propagates: subtreeDirtyEpoch (upward when layout changes)\n// Checks: root.layoutNode.isDirty() — sole gate for running layout\n// Computes: boxRect, prevLayout, scrollState (scroll phase),\n// stickyChildren (sticky phase), scrollRect/screenRect\n//\n// RENDER PHASE (render-phase.ts):\n// Reads: ALL epoch flags, layoutChangedThisFrame, scrollState, stickyChildren\n// Computes: childPositionChanged, absoluteChildMutated, descendantOverflow-\n// Changed, scrollOffsetChanged, clipBounds, inheritedBg/Fg\n// Threads: hasPrevBuffer, ancestorCleared, ancestorLayoutChanged,\n// bufferIsCloned, scrollOffset, clipBounds, inheritedBg, inheritedFg\n// Calls: computeCascade() for core boolean algebra\n// Clears: all dirty epoch flags via advanceRenderEpoch() (O(1))\n// Syncs: prevLayout = boxRect via syncPrevLayout() (post-render)\n//\n// ┌─────────────────────────────────────────────────────────────────────────────┐\n// │ PART 6: DISABLED FAST PATHS (currently hardcoded false) │\n// └─────────────────────────────────────────────────────────────────────────────┘\n//\n// bgOnlyChange (line 180 in this file):\n// DISABLED — hardcoded `const bgOnlyChange = false`.\n// When only backgroundColor changed on a Box with bg, fillBg() would preserve\n// child chars. Disabled because it causes incremental rendering mismatches\n// (fg colors lost on child nodes). Additional safety conditions exist even\n// in the enabled path:\n// - hasDescendantWithBg: descendant bg would be overwritten by fillBg\n// - !ancestorLayoutChanged: children positions may have shifted\n// - !ancestorCleared: parent cleared stale pixels, children must re-render\n//\n// useTextStyleFastPath (render-phase.ts):\n// DISABLED — hardcoded `const useTextStyleFastPath = false`.\n// When only visual style props changed on a Text node (isStyleOnlyDirty),\n// buffer.restyleRegion() would update fg/bg/attrs in-place without re-\n// collecting text. Disabled because it causes incremental rendering mismatches\n// (fg colors lost). Additional safety conditions:\n// - !contentDirty, !childrenDirty, !bgDirty\n// - !ancestorCleared, !ancestorLayoutChanged\n// - !hasChildWithBg (nested children with own bg)\n// - hasPrevBuffer=true\n//\n// ============================================================================\n\n/** Inputs to the cascade predicates (all boolean flags from renderNodeToBuffer) */\nexport interface CascadeInputs {\n hasPrevBuffer: boolean\n contentDirty: boolean\n stylePropsDirty: boolean\n layoutChanged: boolean\n subtreeDirty: boolean\n childrenDirty: boolean\n childPositionChanged: boolean\n ancestorLayoutChanged: boolean\n ancestorCleared: boolean\n bgDirty: boolean\n isTextNode: boolean\n hasBgColor: boolean\n absoluteChildMutated: boolean\n descendantOverflowChanged: boolean\n}\n\n/** Outputs of the cascade predicates */\nexport interface CascadeOutputs {\n canSkipEntireSubtree: boolean\n contentAreaAffected: boolean\n bgRefillNeeded: boolean\n contentRegionCleared: boolean\n skipBgFill: boolean\n childrenNeedFreshRender: boolean\n /**\n * True when bgDirty is the ONLY reason contentAreaAffected is true, and the\n * node has a backgroundColor. In this case, renderBox can use fillBg() (which\n * preserves existing chars) instead of fill() (which overwrites with spaces).\n * This avoids the cascade to children — clean children keep their chars from\n * the cloned buffer with the new bg applied.\n *\n * Requirements: hasPrevBuffer, bgDirty, hasBgColor, no other contentAreaAffected triggers.\n */\n bgOnlyChange: boolean\n}\n\n/**\n * Compute all cascade predicate values from boolean inputs.\n *\n * This is a pure function — no side effects, no node dependencies.\n * The formulas exactly match those in render-phase.ts renderNodeToBuffer.\n */\nexport function computeCascade(inputs: CascadeInputs): CascadeOutputs {\n const {\n hasPrevBuffer,\n contentDirty,\n stylePropsDirty,\n layoutChanged,\n subtreeDirty,\n childrenDirty,\n childPositionChanged,\n ancestorLayoutChanged,\n ancestorCleared,\n bgDirty,\n isTextNode,\n hasBgColor,\n absoluteChildMutated,\n descendantOverflowChanged,\n } = inputs\n\n // FAST PATH: Skip unchanged subtrees when we have a valid previous buffer.\n const canSkipEntireSubtree =\n hasPrevBuffer &&\n !contentDirty &&\n !stylePropsDirty &&\n !layoutChanged &&\n !subtreeDirty &&\n !childrenDirty &&\n !childPositionChanged &&\n !ancestorLayoutChanged\n\n // Intermediate: for TEXT nodes, stylePropsDirty IS a content area change (no borders).\n const textPaintDirty = isTextNode && stylePropsDirty\n\n // Did this node's CONTENT AREA change?\n const contentAreaAffected =\n contentDirty ||\n layoutChanged ||\n childPositionChanged ||\n childrenDirty ||\n bgDirty ||\n textPaintDirty ||\n absoluteChildMutated ||\n descendantOverflowChanged\n\n // Is bgDirty the ONLY trigger for contentAreaAffected?\n // When true AND hasBgColor: we can use fillBg() (preserves chars) instead of\n // fill() (overwrites with spaces), eliminating the cascade to children.\n const bgOnlyAffected =\n bgDirty &&\n !contentDirty &&\n !layoutChanged &&\n !childPositionChanged &&\n !childrenDirty &&\n !textPaintDirty &&\n !absoluteChildMutated &&\n !descendantOverflowChanged\n\n // Style-only fast path: when only bg changed on a Box with bg, use fillBg\n // to preserve child chars. Children see hasPrevBuffer=true (skippable).\n //\n // Additional safety checks:\n // - !ancestorLayoutChanged: children's positions may have shifted in the clone\n // - !ancestorCleared: parent cleared stale pixels, children must re-render\n //\n // IMPORTANT: this is only safe when no descendant has its own explicit\n // backgroundColor that would be incorrectly overwritten by fillBg. The\n // render phase checks this condition (hasDescendantWithBg) and falls back\n // to the full path when descendants have their own bg.\n // DISABLED: bgOnlyChange fast path causes incremental rendering mismatches\n // (fg colors lost on child nodes). Needs investigation before re-enabling.\n const bgOnlyChange = false\n\n // Descendant changed inside a bg-bearing Box (forces bg refill).\n const bgRefillNeeded = hasPrevBuffer && !contentAreaAffected && subtreeDirty && hasBgColor\n\n // Clear region with inherited bg when content changed but no own bg fill.\n // bgOnlyChange on nodes WITHOUT bg still needs clearing (bg removed).\n const contentRegionCleared =\n (hasPrevBuffer || ancestorCleared) && contentAreaAffected && !hasBgColor\n\n // Skip bg fill when clone already has correct bg at this position.\n const skipBgFill = hasPrevBuffer && !ancestorCleared && !contentAreaAffected && !bgRefillNeeded\n\n // Children must re-render (content area modified OR bg needs refresh).\n // Exception: bgOnlyChange uses fillBg() which preserves chars, so children\n // don't need fresh render — they keep their correct chars from the clone.\n const childrenNeedFreshRender =\n (hasPrevBuffer || ancestorCleared) && (contentAreaAffected || bgRefillNeeded) && !bgOnlyChange\n\n return {\n canSkipEntireSubtree,\n contentAreaAffected,\n bgRefillNeeded,\n contentRegionCleared,\n skipBgFill,\n childrenNeedFreshRender,\n bgOnlyChange,\n }\n}\n","/**\n * Reactive Node State — alien-signals wrappers for cascade derivations.\n *\n * E+ Phase 2: Replace manual cascade formula computation with reactive\n * computeds. The cascade-predicates.ts `computeCascade()` function is the\n * oracle — this module must produce identical outputs.\n *\n * Incremental approach:\n * 1. Writable signals mirror epoch-stamped dirty flags\n * 2. Computeds derive cascade outputs (isDirty, contentAreaAffected, etc.)\n * 3. `syncToSignals()` bridges epoch flags → signals at render-phase entry\n * 4. Dev-mode assertions verify reactive === oracle\n *\n * alien-signals computeds are lazy (pull-based): reading a computed re-evaluates\n * only if a dependency changed. No batching needed for correctness — the\n * reconciler's synchronous commit writes epoch stamps, and the render phase\n * reads computeds after all commits are done.\n */\n\nimport { signal, computed } from \"@silvery/signals\"\nimport type { AgNode } from \"@silvery/ag/types\"\nimport {\n isDirty,\n CONTENT_BIT,\n STYLE_PROPS_BIT,\n BG_BIT,\n CHILDREN_BIT,\n SUBTREE_BIT,\n} from \"@silvery/ag/epoch\"\nimport { computeCascade, type CascadeInputs, type CascadeOutputs } from \"./cascade-predicates.ts\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Writable signal — call with no args to read, call with value to write.\n * Type alias for clarity (alien-signals returns this shape from `signal()`).\n */\ntype Signal<T> = {\n (): T\n (value: T): void\n}\n\n/** Read-only computed — call with no args to read. */\ntype Computed<T> = () => T\n\n/**\n * Per-node reactive state that lives alongside an AgNode.\n *\n * Writable signals are synced from epoch-stamped flags before each render pass.\n * Computed derivations automatically recompute when inputs change.\n */\nexport interface ReactiveNodeState {\n // --- Writable signals (synced from epoch flags) ---\n readonly contentDirty: Signal<boolean>\n readonly stylePropsDirty: Signal<boolean>\n readonly bgDirty: Signal<boolean>\n readonly childrenDirty: Signal<boolean>\n readonly subtreeDirty: Signal<boolean>\n readonly layoutChanged: Signal<boolean>\n\n // --- Context signals (set per-node during tree traversal) ---\n readonly hasPrevBuffer: Signal<boolean>\n readonly childPositionChanged: Signal<boolean>\n readonly ancestorLayoutChanged: Signal<boolean>\n readonly ancestorCleared: Signal<boolean>\n readonly isTextNode: Signal<boolean>\n readonly hasBgColor: Signal<boolean>\n readonly absoluteChildMutated: Signal<boolean>\n readonly descendantOverflowChanged: Signal<boolean>\n\n // --- Computed derivations ---\n readonly canSkipEntireSubtree: Computed<boolean>\n readonly textPaintDirty: Computed<boolean>\n readonly contentAreaAffected: Computed<boolean>\n readonly bgRefillNeeded: Computed<boolean>\n readonly contentRegionCleared: Computed<boolean>\n readonly skipBgFill: Computed<boolean>\n readonly childrenNeedFreshRender: Computed<boolean>\n readonly bgOnlyChange: Computed<boolean>\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\n/**\n * Create a reactive state wrapper for a node.\n *\n * The computed formulas exactly replicate cascade-predicates.ts `computeCascade()`.\n * They are NOT a replacement — the oracle function remains authoritative.\n * In dev mode, assertions verify equivalence.\n */\nexport function createReactiveNodeState(): ReactiveNodeState {\n // Writable signals — all start false, synced before each render pass\n const contentDirty = signal(false)\n const stylePropsDirty = signal(false)\n const bgDirty = signal(false)\n const childrenDirty = signal(false)\n const subtreeDirty = signal(false)\n const layoutChanged = signal(false)\n\n // Context signals — set per-node during tree traversal\n const hasPrevBuffer = signal(false)\n const childPositionChanged = signal(false)\n const ancestorLayoutChanged = signal(false)\n const ancestorCleared = signal(false)\n const isTextNode = signal(false)\n const hasBgColor = signal(false)\n const absoluteChildMutated = signal(false)\n const descendantOverflowChanged = signal(false)\n\n // --- Computed derivations (formulas from cascade-predicates.ts) ---\n\n const canSkipEntireSubtree = computed(\n () =>\n hasPrevBuffer() &&\n !contentDirty() &&\n !stylePropsDirty() &&\n !layoutChanged() &&\n !subtreeDirty() &&\n !childrenDirty() &&\n !childPositionChanged() &&\n !ancestorLayoutChanged(),\n )\n\n const textPaintDirty = computed(() => isTextNode() && stylePropsDirty())\n\n const contentAreaAffected = computed(\n () =>\n contentDirty() ||\n layoutChanged() ||\n childPositionChanged() ||\n childrenDirty() ||\n bgDirty() ||\n textPaintDirty() ||\n absoluteChildMutated() ||\n descendantOverflowChanged(),\n )\n\n // DISABLED: bgOnlyChange fast path causes incremental rendering mismatches.\n // Matches cascade-predicates.ts which hardcodes `false`.\n const bgOnlyChange = computed(() => false)\n\n const bgRefillNeeded = computed(\n () => hasPrevBuffer() && !contentAreaAffected() && subtreeDirty() && hasBgColor(),\n )\n\n const contentRegionCleared = computed(\n () => (hasPrevBuffer() || ancestorCleared()) && contentAreaAffected() && !hasBgColor(),\n )\n\n const skipBgFill = computed(\n () => hasPrevBuffer() && !ancestorCleared() && !contentAreaAffected() && !bgRefillNeeded(),\n )\n\n const childrenNeedFreshRender = computed(\n () =>\n (hasPrevBuffer() || ancestorCleared()) &&\n (contentAreaAffected() || bgRefillNeeded()) &&\n !bgOnlyChange(),\n )\n\n return {\n contentDirty,\n stylePropsDirty,\n bgDirty,\n childrenDirty,\n subtreeDirty,\n layoutChanged,\n hasPrevBuffer,\n childPositionChanged,\n ancestorLayoutChanged,\n ancestorCleared,\n isTextNode,\n hasBgColor,\n absoluteChildMutated,\n descendantOverflowChanged,\n canSkipEntireSubtree,\n textPaintDirty,\n contentAreaAffected,\n bgRefillNeeded,\n contentRegionCleared,\n skipBgFill,\n childrenNeedFreshRender,\n bgOnlyChange,\n }\n}\n\n// ============================================================================\n// Sync: Epoch flags → Signals\n// ============================================================================\n\n/**\n * Sync a node's epoch-stamped dirty flags into the reactive signals.\n *\n * Called once per node at the start of renderNodeToBuffer, BEFORE reading\n * any computed. Context-dependent inputs (hasPrevBuffer, ancestorCleared, etc.)\n * are also set here since they vary per tree-traversal position.\n */\nexport function syncToSignals(\n state: ReactiveNodeState,\n node: AgNode,\n ctx: {\n hasPrevBuffer: boolean\n layoutChanged: boolean\n childPositionChanged: boolean\n ancestorLayoutChanged: boolean\n ancestorCleared: boolean\n absoluteChildMutated: boolean\n descendantOverflowChanged: boolean\n hasBgColor: boolean\n },\n): void {\n // Sync epoch flags → boolean signals\n state.contentDirty(isDirty(node.dirtyBits, node.dirtyEpoch, CONTENT_BIT))\n state.stylePropsDirty(isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT))\n state.bgDirty(isDirty(node.dirtyBits, node.dirtyEpoch, BG_BIT))\n state.childrenDirty(isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT))\n state.subtreeDirty(isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT))\n state.layoutChanged(ctx.layoutChanged)\n\n // Sync context-dependent inputs\n state.hasPrevBuffer(ctx.hasPrevBuffer)\n state.childPositionChanged(ctx.childPositionChanged)\n state.ancestorLayoutChanged(ctx.ancestorLayoutChanged)\n state.ancestorCleared(ctx.ancestorCleared)\n state.isTextNode(node.type === \"silvery-text\")\n state.hasBgColor(ctx.hasBgColor)\n state.absoluteChildMutated(ctx.absoluteChildMutated)\n state.descendantOverflowChanged(ctx.descendantOverflowChanged)\n}\n\n// ============================================================================\n// Reactive-driven cascade (replaces computeCascade for production)\n// ============================================================================\n\n/**\n * Read all cascade outputs from the reactive computeds.\n * Call AFTER `syncToSignals()`. Returns the same CascadeOutputs shape\n * as `computeCascade()` but derived from the signal graph.\n */\nexport function readReactiveCascade(state: ReactiveNodeState): CascadeOutputs {\n return {\n canSkipEntireSubtree: state.canSkipEntireSubtree(),\n contentAreaAffected: state.contentAreaAffected(),\n bgRefillNeeded: state.bgRefillNeeded(),\n contentRegionCleared: state.contentRegionCleared(),\n skipBgFill: state.skipBgFill(),\n childrenNeedFreshRender: state.childrenNeedFreshRender(),\n bgOnlyChange: state.bgOnlyChange(),\n }\n}\n\n// ============================================================================\n// Oracle Verification\n// ============================================================================\n\n/**\n * Verify that reactive computeds match the cascade oracle.\n *\n * Call in dev mode (SILVERY_STRICT=1) after syncToSignals + computeCascade.\n * Throws on mismatch with a detailed diff.\n */\nexport function assertReactiveMatchesOracle(\n state: ReactiveNodeState,\n oracle: CascadeOutputs,\n nodeId: string,\n): void {\n const fields: (keyof CascadeOutputs)[] = [\n \"canSkipEntireSubtree\",\n \"contentAreaAffected\",\n \"bgRefillNeeded\",\n \"contentRegionCleared\",\n \"skipBgFill\",\n \"childrenNeedFreshRender\",\n \"bgOnlyChange\",\n ]\n\n const mismatches: string[] = []\n for (const field of fields) {\n const reactiveValue = state[field]()\n const oracleValue = oracle[field]\n if (reactiveValue !== oracleValue) {\n mismatches.push(` ${field}: reactive=${reactiveValue}, oracle=${oracleValue}`)\n }\n }\n\n if (mismatches.length > 0) {\n throw new Error(\n `ReactiveNodeState mismatch for ${nodeId || \"(unnamed)\"}:\\n${mismatches.join(\"\\n\")}`,\n )\n }\n}\n\n// ============================================================================\n// Node State Cache\n// ============================================================================\n\n/**\n * WeakMap from AgNode to its reactive state.\n *\n * Lazily created on first access. Automatically garbage-collected when the\n * node is removed from the tree (WeakMap semantics).\n */\nconst nodeStates = new WeakMap<AgNode, ReactiveNodeState>()\n\n/**\n * Get or create the reactive state for a node.\n *\n * Uses a WeakMap so states are automatically cleaned up when nodes are GC'd.\n */\nexport function getReactiveState(node: AgNode): ReactiveNodeState {\n let state = nodeStates.get(node)\n if (!state) {\n state = createReactiveNodeState()\n nodeStates.set(node, state)\n }\n return state\n}\n","/**\n * Phase 3: Render Phase\n *\n * Render all nodes to a terminal buffer.\n *\n * This module orchestrates the rendering process by traversing the node tree\n * and delegating to specialized rendering functions for boxes and text.\n *\n * Layout (top-down):\n * renderPhase → renderNodeToBuffer → buildCascadeInputs + computeCascade (oracle)\n * → traceRenderDecision (diagnostics)\n * → executeRegionClearing\n * → renderOwnContent\n * → renderScrollContainerChildren / renderNormalChildren\n * Helpers: clearDirtyFlags, hasChildPositionChanged, computeChildClipBounds\n * Region clearing: clearNodeRegion, clearExcessArea, clippedFill\n */\n\nimport { createLogger } from \"loggily\"\nimport type { Color } from \"../buffer\"\nimport { TerminalBuffer } from \"../buffer\"\nimport type { BoxProps, AgNode, TextProps } from \"@silvery/ag/types\"\nimport { getBorderSize, getPadding } from \"./helpers\"\nimport { renderBox, renderScrollIndicators, getEffectiveBg } from \"./render-box\"\nimport { clearPreviousOutlines, renderDecorationPass } from \"./decoration-phase\"\nimport { getTextStyle, parseColor } from \"./render-helpers\"\nimport { clearBgConflictWarnings, renderText, setBgConflictMode } from \"./render-text\"\nimport { pushContextTheme, popContextTheme } from \"./state\"\nimport type { Theme } from \"@silvery/ansi\"\n// cascade-predicates is the imperative oracle — used for STRICT verification\n// and as the fallback when SILVERY_REACTIVE=0 (bench only).\nimport { computeCascade } from \"./cascade-predicates\"\nimport { isStyleOnlyDirty } from \"@silvery/ag/dirty-tracking\"\nimport {\n isCurrentEpoch,\n isAnyDirty,\n INITIAL_EPOCH,\n advanceRenderEpoch,\n isDirty,\n CONTENT_BIT,\n STYLE_PROPS_BIT,\n BG_BIT,\n CHILDREN_BIT,\n SUBTREE_BIT,\n ABS_CHILD_BIT,\n DESC_OVERFLOW_BIT,\n} from \"@silvery/ag/epoch\"\nimport type { CascadeOutputs } from \"./cascade-predicates\"\nimport type {\n ClipBounds,\n RenderPhaseStats,\n NodeRenderState,\n NodeTraceEntry,\n PipelineContext,\n} from \"./types\"\nimport {\n getReactiveState,\n syncToSignals,\n readReactiveCascade,\n assertReactiveMatchesOracle,\n} from \"./reactive-node\"\n\nconst contentLog = createLogger(\"silvery:content\")\nconst traceLog = createLogger(\"silvery:content:trace\")\nconst cellLog = createLogger(\"silvery:content:cell\")\n\n/**\n * Render all nodes to a terminal buffer.\n *\n * @param root The root SilveryNode\n * @param prevBuffer Previous buffer for incremental rendering (optional)\n * @returns A TerminalBuffer with the rendered content\n */\nexport function renderPhase(\n root: AgNode,\n prevBuffer?: TerminalBuffer | null,\n ctx?: PipelineContext,\n): TerminalBuffer {\n const layout = root.boxRect\n if (!layout) {\n throw new Error(\"renderPhase called before layout phase\")\n }\n\n const instr = resolveInstrumentation(ctx)\n\n // Clone prevBuffer if same dimensions, else create fresh\n const hasPrevBuffer =\n prevBuffer && prevBuffer.width === layout.width && prevBuffer.height === layout.height\n\n // No-op frame skip: if nothing changed since last render and we have a valid\n // prev buffer, return it unchanged. Saves buffer clone + tree walk + epoch advance.\n //\n // Currently doesn't fire with React (createElement always produces new children\n // array → commitUpdate sets CHILDREN_BIT on root). Will fire with:\n // - Signal-driven updates (v1.5) that bypass React\n // - Timer-driven re-renders where nothing changed\n // - Scheduler polling without React pending work\n if (\n hasPrevBuffer &&\n !isAnyDirty(root.dirtyBits, root.dirtyEpoch) &&\n !isCurrentEpoch(root.layoutChangedThisFrame)\n ) {\n if (instr.enabled) instr.stats._noopSkip = 1\n advanceRenderEpoch()\n return prevBuffer\n }\n\n if (instr.enabled) {\n instr.stats._prevBufferNull = prevBuffer == null ? 1 : 0\n instr.stats._prevBufferDimMismatch = prevBuffer && !hasPrevBuffer ? 1 : 0\n instr.stats._hasPrevBuffer = hasPrevBuffer ? 1 : 0\n instr.stats._layoutW = layout.width\n instr.stats._layoutH = layout.height\n instr.stats._prevW = prevBuffer?.width ?? 0\n instr.stats._prevH = prevBuffer?.height ?? 0\n }\n\n const t0 = instr.enabled ? performance.now() : 0\n const buffer = hasPrevBuffer\n ? prevBuffer.clone()\n : new TerminalBuffer(layout.width, layout.height)\n const tClone = instr.enabled ? performance.now() - t0 : 0\n\n // Default: root is selectable (userSelect defaults to \"text\").\n // renderNodeToBuffer will override per-node as it traverses.\n buffer.setSelectableMode(true)\n\n // Restore cells under previous-frame outlines BEFORE content rendering.\n // Outlines draw OUTSIDE their owning node into the parent's pixel space,\n // so they don't fit the per-node cascade. We treat them as a separate\n // decoration pass: clear prev positions here, redraw new positions after\n // the content phase below. The snapshots were captured when we drew the\n // previous frame and travel with the cloned buffer. No-op for fresh\n // buffers (no snapshots on a newly constructed TerminalBuffer).\n clearPreviousOutlines(buffer)\n\n const t1 = instr.enabled ? performance.now() : 0\n renderNodeToBuffer(\n root,\n buffer,\n {\n scrollOffset: 0,\n clipBounds: undefined,\n hasPrevBuffer: !!hasPrevBuffer,\n ancestorCleared: false,\n bufferIsCloned: !!hasPrevBuffer,\n ancestorLayoutChanged: false,\n inheritedBg: { color: null, ancestorRect: null },\n inheritedFg: null,\n },\n ctx,\n )\n const tRender = instr.enabled ? performance.now() - t1 : 0\n\n // Decoration phase: draw outlines AFTER content rendering. Walks the full\n // tree (O(N)) independent of dirty flags — outlines are idempotent per\n // frame and cheap to redraw (~5000 cells at worst). Populates fresh\n // snapshots on the buffer for the next frame's `clearPreviousOutlines`.\n renderDecorationPass(buffer, root)\n\n if (instr.enabled) {\n emitRenderPhaseStats(instr.stats, instr.nodeTrace, instr.nodeTraceEnabled, tClone, tRender)\n }\n\n // Sync prevLayout after render phase to prevent staleness on subsequent frames.\n // Skip when no layout changed this frame (cursor move, style-only changes).\n // The layout phase sets layoutChangedThisFrame on affected nodes; if root's\n // subtree has any, we need the full sync. If not, prevLayout is already correct.\n const anyLayoutChanged =\n isCurrentEpoch(root.layoutChangedThisFrame) ||\n isDirty(root.dirtyBits, root.dirtyEpoch, SUBTREE_BIT)\n syncPrevLayout(root, anyLayoutChanged || !hasPrevBuffer)\n\n // Advance the render epoch — all dirty flags stamped with the old epoch\n // instantly become \"not dirty\". This replaces the O(N) clearDirtyFlags walk\n // for rendered nodes (skipped nodes still need explicit clearing).\n advanceRenderEpoch()\n\n return buffer\n}\n\n/**\n * Sync prevLayout to boxRect for all nodes in the tree.\n *\n * Called at the end of each renderPhase pass. This prevents:\n * 1. The O(N) staleness bug where prevLayout drifts from boxRect\n * causing !rectEqual to always be true on subsequent frames.\n * 2. Stale old-bounds references in clearExcessArea on doRender iteration 2+.\n * 3. Asymmetry between incremental and fresh renders — doFreshRender's layout\n * phase syncs prevLayout before content, so without this, the real render\n * has null/stale prevLayout while fresh has synced prevLayout, causing\n * different cascade behavior (layoutChanged true vs false).\n */\n/**\n * Sync prevLayout = boxRect for nodes whose layout changed.\n *\n * Previously walked ALL nodes O(N) every frame. Now only visits nodes\n * with layoutChangedThisFrame (set by propagateLayout in layout phase).\n * Falls back to full walk when layout phase ran (dimensions changed or\n * Flexily isDirty) since any node may have moved.\n *\n * For cursor move (no layout change): O(1) — no nodes to sync.\n * For resize: O(N) — all nodes may have moved (same as before).\n */\nfunction syncPrevLayout(root: AgNode, layoutPhaseRan: boolean): void {\n // When layout phase ran, any node could have moved — full walk needed.\n // When layout was skipped (cursor move, style-only), no rects changed.\n if (!layoutPhaseRan) return\n\n const stack: AgNode[] = [root]\n while (stack.length > 0) {\n const node = stack.pop()!\n node.prevLayout = node.boxRect\n const children = node.children\n for (let i = children.length - 1; i >= 0; i--) {\n stack.push(children[i]!)\n }\n }\n}\n\n/** Check if an env var is truthy (treats \"0\" and \"false\" as disabled). */\nfunction envTruthy(val: string | undefined): boolean {\n return !!val && val !== \"0\" && val !== \"false\"\n}\n\n/** Instrumentation enabled when SILVERY_STRICT or SILVERY_INSTRUMENT is set */\nconst _instrumentEnabled =\n typeof process !== \"undefined\" &&\n (envTruthy(process.env?.SILVERY_STRICT) || envTruthy(process.env?.SILVERY_INSTRUMENT))\n\n/** Mutable stats counters — reset after each renderPhase call */\nconst _renderPhaseStats: RenderPhaseStats = {\n nodesVisited: 0,\n nodesRendered: 0,\n nodesSkipped: 0,\n textNodes: 0,\n boxNodes: 0,\n clearOps: 0,\n noPrevBuffer: 0,\n flagContentDirty: 0,\n flagStylePropsDirty: 0,\n flagLayoutChanged: 0,\n flagSubtreeDirty: 0,\n flagChildrenDirty: 0,\n flagChildPositionChanged: 0,\n flagAncestorLayoutChanged: 0,\n scrollContainerCount: 0,\n scrollViewportCleared: 0,\n scrollClearReason: \"\",\n normalChildrenRepaint: 0,\n normalRepaintReason: \"\",\n cascadeMinDepth: 999,\n cascadeNodes: \"\",\n _noopSkip: 0,\n _prevBufferNull: 0,\n _prevBufferDimMismatch: 0,\n _hasPrevBuffer: 0,\n _layoutW: 0,\n _layoutH: 0,\n _prevW: 0,\n _prevH: 0,\n _callCount: 0,\n}\n\nlet _renderPhaseCallCount = 0\n\n/** Module-level node trace (fallback when ctx.nodeTrace is not provided) */\nconst _nodeTrace: NodeTraceEntry[] = []\nconst _nodeTraceEnabled = typeof process !== \"undefined\" && envTruthy(process.env?.SILVERY_STRICT)\n\n/**\n * Reactive cascade: alien-signals computeds drive rendering (production path).\n * SILVERY_REACTIVE=0: fall back to imperative computeCascade() (bench only).\n * SILVERY_STRICT=1: oracle verifies reactive output matches imperative.\n */\nlet _reactiveEnabled = typeof process === \"undefined\" || process.env?.SILVERY_REACTIVE !== \"0\"\nlet _reactiveVerifyEnabled =\n _reactiveEnabled && typeof process !== \"undefined\" && envTruthy(process.env?.SILVERY_STRICT)\n\n/** Toggle reactive cascade mode at runtime (for three-way bench). */\nexport function setReactiveEnabled(enabled: boolean): void {\n _reactiveEnabled = enabled\n _reactiveVerifyEnabled =\n enabled && typeof process !== \"undefined\" && envTruthy(process.env?.SILVERY_STRICT)\n}\n\n// ============================================================================\n// Instrumentation Helpers\n// ============================================================================\n\n/** Resolved instrumentation state — avoids repeating ctx ?? module fallbacks. */\ninterface InstrumentState {\n readonly enabled: boolean\n readonly stats: RenderPhaseStats\n readonly nodeTrace: NodeTraceEntry[]\n readonly nodeTraceEnabled: boolean\n}\n\n/** Resolve instrumentation from PipelineContext or module-level defaults. */\nfunction resolveInstrumentation(ctx?: PipelineContext): InstrumentState {\n return {\n enabled: ctx?.instrumentEnabled ?? _instrumentEnabled,\n stats: ctx?.stats ?? _renderPhaseStats,\n nodeTrace: ctx?.nodeTrace ?? _nodeTrace,\n nodeTraceEnabled: ctx?.nodeTraceEnabled ?? _nodeTraceEnabled,\n }\n}\n\n/** Cell debug state — read once from globalThis per function scope. */\ntype CellDebug = { x: number; y: number; log: string[] }\n\n/** Read the cell debug target (set by SILVERY_CELL_DEBUG env var). */\nfunction getCellDebug(): CellDebug | undefined {\n return (globalThis as any).__silvery_cell_debug as CellDebug | undefined\n}\n\n/** Check if a rect covers the cell debug target point. */\nfunction cellCoversPoint(\n cellDbg: CellDebug,\n x: number,\n y: number,\n width: number,\n height: number,\n): boolean {\n return x <= cellDbg.x && x + width > cellDbg.x && y <= cellDbg.y && y + height > cellDbg.y\n}\n\n/** DIAG: compute node depth in tree */\nfunction _getNodeDepth(node: AgNode): number {\n let depth = 0\n let n: AgNode | null = node.parent\n while (n) {\n depth++\n n = n.parent\n }\n return depth\n}\n\n/**\n * Emit render-phase stats to globalThis + loggily, then reset counters.\n * Called at end of renderPhase when instrumentation is active.\n */\nfunction emitRenderPhaseStats(\n stats: RenderPhaseStats,\n nodeTrace: NodeTraceEntry[],\n nodeTraceEnabled: boolean,\n tClone: number,\n tRender: number,\n): void {\n _renderPhaseCallCount++\n stats._callCount = _renderPhaseCallCount\n const snap = {\n clone: tClone,\n render: tRender,\n ...structuredClone(stats),\n }\n // Retain globalThis for programmatic consumers (STRICT diagnostics, perf profiling)\n ;(globalThis as any).__silvery_content_detail = snap\n const arr = ((globalThis as any).__silvery_content_all ??= [] as (typeof snap)[])\n arr.push(snap)\n // Route human-readable output through loggily\n contentLog.debug?.(\n `frame ${snap._callCount}: ${snap.nodesRendered}/${snap.nodesVisited} rendered, ${snap.nodesSkipped} skipped (${tClone.toFixed(1)}ms clone, ${tRender.toFixed(1)}ms render)`,\n )\n for (const key of Object.keys(stats) as (keyof RenderPhaseStats)[]) {\n ;(stats as any)[key] = 0\n }\n stats.cascadeMinDepth = 999\n stats.cascadeNodes = \"\"\n stats.scrollClearReason = \"\"\n stats.normalRepaintReason = \"\"\n\n // Export node trace for SILVERY_STRICT diagnosis\n if (nodeTraceEnabled && nodeTrace.length > 0) {\n const traceArr = ((globalThis as any).__silvery_node_trace ??= [] as NodeTraceEntry[][])\n traceArr.push([...nodeTrace])\n traceLog.debug?.(`${nodeTrace.length} nodes traced`)\n nodeTrace.length = 0\n }\n}\n\n// Re-export for consumers who need to clear bg conflict warnings\nexport { clearBgConflictWarnings, setBgConflictMode }\n\n// ============================================================================\n// Core Rendering\n// ============================================================================\n\n/**\n * Render a single node to the buffer.\n */\nfunction renderNodeToBuffer(\n node: AgNode,\n buffer: TerminalBuffer,\n nodeState: NodeRenderState,\n ctx?: PipelineContext,\n): void {\n const {\n scrollOffset,\n clipBounds,\n hasPrevBuffer,\n ancestorCleared,\n bufferIsCloned,\n ancestorLayoutChanged = false,\n } = nodeState\n const instr = resolveInstrumentation(ctx)\n if (instr.enabled) instr.stats.nodesVisited++\n const layout = node.boxRect\n if (!layout) return\n\n // Skip nodes without Yoga (raw text and virtual text nodes)\n // Their content is rendered by their parent silvery-text via collectTextContent()\n if (!node.layoutNode) {\n clearVirtualTextFlags(node)\n return\n }\n\n // Skip hidden nodes (Suspense support)\n if (node.hidden) {\n clearDirtyFlags(node)\n return\n }\n\n const props = node.props as BoxProps & TextProps\n\n // Resolve userSelect for SELECTABLE_FLAG stamping.\n const prevSelectableMode = buffer.getSelectableMode()\n const userSelect = props.userSelect\n if (userSelect === \"none\") {\n buffer.setSelectableMode(false)\n } else if (userSelect === \"text\" || userSelect === \"contain\") {\n buffer.setSelectableMode(true)\n }\n\n // Skip display=\"none\" nodes\n if (props.display === \"none\") {\n clearDirtyFlags(node)\n buffer.setSelectableMode(prevSelectableMode)\n return\n }\n\n // Skip nodes entirely off-screen (viewport clipping).\n // IMPORTANT: Don't clear dirty flags on off-screen nodes — they need their\n // flags intact so they render correctly when scrolled into view.\n const screenY = layout.y - scrollOffset\n if (screenY >= buffer.height || screenY + layout.height <= 0) {\n buffer.setSelectableMode(prevSelectableMode)\n return\n }\n\n // FAST PATH: Skip entire subtree if unchanged and we have a previous buffer.\n // layoutChanged uses layoutChangedThisFrame (symmetric between incremental/fresh).\n const layoutChanged = isCurrentEpoch(node.layoutChangedThisFrame)\n const childPositionChanged = !!(hasPrevBuffer && !layoutChanged && hasChildPositionChanged(node))\n const scrollOffsetChanged = !!(\n node.scrollState && node.scrollState.offset !== node.scrollState.prevOffset\n )\n\n const canSkipEntireSubtree =\n hasPrevBuffer &&\n !isDirty(node.dirtyBits, node.dirtyEpoch, CONTENT_BIT) &&\n !isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT) &&\n !layoutChanged &&\n !isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT) &&\n !isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT) &&\n !childPositionChanged &&\n !ancestorLayoutChanged &&\n !scrollOffsetChanged\n\n // Diagnostics: node ID, cell debug, node trace\n const _nodeId = instr.enabled ? ((props.id as string | undefined) ?? \"\") : \"\"\n const _traceThis = instr.enabled && instr.nodeTraceEnabled && _nodeId\n const _cellDbg = getCellDebug()\n const _coversCellNow =\n _cellDbg && cellCoversPoint(_cellDbg, layout.x, screenY, layout.width, layout.height)\n const _coversCellPrev =\n _cellDbg &&\n node.prevLayout &&\n cellCoversPoint(\n _cellDbg,\n node.prevLayout.x,\n node.prevLayout.y - scrollOffset,\n node.prevLayout.width,\n node.prevLayout.height,\n )\n\n if (canSkipEntireSubtree) {\n if (_cellDbg && (_coversCellNow || _coversCellPrev)) {\n const id = (props.id as string) ?? node.type\n const depth = _getNodeDepth(node)\n const prev = node.prevLayout\n const msg =\n `SKIP ${id}@${depth} rect=${layout.x},${screenY} ${layout.width}x${layout.height}` +\n ` prev=${prev ? `${prev.x},${prev.y - scrollOffset} ${prev.width}x${prev.height}` : \"null\"}` +\n ` coversNow=${_coversCellNow} coversPrev=${_coversCellPrev}`\n _cellDbg.log.push(msg)\n cellLog.debug?.(msg)\n }\n if (instr.enabled) {\n instr.stats.nodesSkipped++\n if (_traceThis) {\n instr.nodeTrace.push({\n id: _nodeId,\n type: node.type,\n depth: _getNodeDepth(node),\n rect: `${layout.x},${layout.y} ${layout.width}x${layout.height}`,\n prevLayout: node.prevLayout\n ? `${node.prevLayout.x},${node.prevLayout.y} ${node.prevLayout.width}x${node.prevLayout.height}`\n : \"null\",\n hasPrev: hasPrevBuffer,\n ancestorCleared,\n flags: \"\",\n decision: \"SKIPPED\",\n layoutChanged,\n })\n }\n }\n clearDirtyFlags(node)\n buffer.setSelectableMode(prevSelectableMode)\n return\n }\n if (instr.enabled) {\n instr.stats.nodesRendered++\n if (!hasPrevBuffer) instr.stats.noPrevBuffer++\n if (isDirty(node.dirtyBits, node.dirtyEpoch, CONTENT_BIT)) instr.stats.flagContentDirty++\n if (isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT)) instr.stats.flagStylePropsDirty++\n if (layoutChanged) instr.stats.flagLayoutChanged++\n if (isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT)) instr.stats.flagSubtreeDirty++\n if (isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT)) instr.stats.flagChildrenDirty++\n if (childPositionChanged) instr.stats.flagChildPositionChanged++\n if (ancestorLayoutChanged) instr.stats.flagAncestorLayoutChanged++\n }\n\n // Push per-subtree theme override (if this Box has a theme prop).\n // Placed after all early returns and fast-path skip — only active during\n // actual rendering. Popped at the end of this function after all child passes.\n const nodeTheme = (props as BoxProps).theme as Theme | undefined\n if (nodeTheme) {\n pushContextTheme(nodeTheme)\n }\n try {\n // Check if this is a scrollable container\n const isScrollContainer = props.overflow === \"scroll\" && node.scrollState\n\n // Build tree-dependent cascade inputs (child traversal).\n const { absoluteChildMutated, descendantOverflowChanged } = buildCascadeInputs(\n node,\n hasPrevBuffer,\n )\n\n // Cascade computation: reactive (alien-signals) is the production path.\n // SILVERY_REACTIVE=0 falls back to imperative computeCascade (bench only).\n const cascadeInputs = {\n hasPrevBuffer,\n contentDirty: isDirty(node.dirtyBits, node.dirtyEpoch, CONTENT_BIT),\n stylePropsDirty: isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT),\n layoutChanged,\n subtreeDirty: isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT),\n childrenDirty: isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT),\n childPositionChanged,\n ancestorLayoutChanged,\n ancestorCleared,\n bgDirty: isDirty(node.dirtyBits, node.dirtyEpoch, BG_BIT),\n isTextNode: node.type === \"silvery-text\",\n hasBgColor: !!getEffectiveBg(props),\n absoluteChildMutated,\n descendantOverflowChanged,\n }\n let cascade: CascadeOutputs\n if (_reactiveEnabled) {\n const reactiveState = getReactiveState(node)\n syncToSignals(reactiveState, node, {\n hasPrevBuffer,\n layoutChanged,\n childPositionChanged,\n ancestorLayoutChanged,\n ancestorCleared,\n absoluteChildMutated,\n descendantOverflowChanged,\n hasBgColor: cascadeInputs.hasBgColor,\n })\n cascade = readReactiveCascade(reactiveState)\n\n // STRICT: verify reactive matches imperative oracle.\n if (_reactiveVerifyEnabled) {\n assertReactiveMatchesOracle(\n reactiveState,\n computeCascade(cascadeInputs),\n (props.id as string) ?? node.type,\n )\n }\n } else {\n cascade = computeCascade(cascadeInputs)\n }\n\n // bgOnlyChange safety check: fillBg updates ALL cells in the region, which\n // would incorrectly overwrite children with their own explicit backgroundColor.\n // Fall back to the full path when any descendant has its own bg.\n if (cascade.bgOnlyChange && hasDescendantWithBg(node)) {\n const childrenNeedFreshRender =\n (hasPrevBuffer || ancestorCleared) &&\n (cascade.contentAreaAffected || cascade.bgRefillNeeded)\n cascade = { ...cascade, bgOnlyChange: false, childrenNeedFreshRender }\n }\n const { contentRegionCleared, skipBgFill, childrenNeedFreshRender } = cascade\n\n // DIAG: Per-node trace, cascade tracking, and cell debug\n if (instr.enabled || (_cellDbg && (_coversCellNow || _coversCellPrev))) {\n traceRenderDecision(\n node,\n props,\n layout,\n screenY,\n scrollOffset,\n hasPrevBuffer,\n ancestorCleared,\n layoutChanged,\n childPositionChanged,\n cascade,\n _nodeId,\n _traceThis,\n _cellDbg,\n _coversCellNow,\n _coversCellPrev,\n instr.enabled,\n instr.stats,\n instr.nodeTrace,\n )\n }\n\n // DISABLED: text style-only fast path causes incremental rendering mismatches\n // (fg colors lost). Needs investigation before re-enabling.\n const useTextStyleFastPath = false\n\n // Clear stale regions in the cloned buffer before rendering content.\n // Suppress clearing when using the text style-only fast path — chars are\n // correct in the clone and clearNodeRegion would destroy them with spaces.\n executeRegionClearing(\n node,\n buffer,\n layout,\n scrollOffset,\n clipBounds,\n bufferIsCloned,\n layoutChanged,\n useTextStyleFastPath ? false : contentRegionCleared,\n descendantOverflowChanged,\n instr.enabled,\n instr.stats,\n nodeState.inheritedBg,\n )\n\n // Determine if this node's own content (border, bg, text) needs repainting.\n // When hasPrevBuffer=true and only subtreeDirty is set (no own visual changes),\n // the cloned buffer already has correct own content. Skipping avoids a border\n // redraw that would overwrite child content rendered on top of the border area\n // (e.g., text overflow into border columns).\n const needsOwnRepaint =\n !hasPrevBuffer ||\n ancestorCleared ||\n ancestorLayoutChanged ||\n cascade.contentAreaAffected ||\n isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT) ||\n cascade.bgRefillNeeded\n\n // Render this node's own content (box bg/border or text).\n // Compute boxInheritedBg even when skipping own repaint — it's needed by\n // outline rendering (after children) and may be needed by child rendering.\n const boxInheritedBg =\n node.type === \"silvery-box\" && !getEffectiveBg(props)\n ? nodeState.inheritedBg.color\n : undefined\n if (needsOwnRepaint) {\n renderOwnContent(\n node,\n buffer,\n layout,\n props,\n nodeState,\n skipBgFill,\n instr.enabled,\n instr.stats,\n ctx,\n cascade.bgOnlyChange,\n useTextStyleFastPath,\n )\n }\n\n // Compute inherited bg/fg for children. If this node sets backgroundColor,\n // color, or theme, children inherit from this node. Otherwise, inherit from parent.\n const effectiveBg = getEffectiveBg(props)\n const childInheritedBg = effectiveBg\n ? { color: parseColor(effectiveBg), ancestorRect: node.boxRect }\n : nodeTheme\n ? { color: parseColor(nodeTheme.bg), ancestorRect: node.boxRect }\n : nodeState.inheritedBg\n // color=\"inherit\"/\"currentColor\" is a pass-through — children inherit\n // from this node's OWN inheritedFg (our parent's color), not from null.\n // Without this, an intermediate \"inherit\" node breaks the cascade to\n // grandchildren because parseColor(\"inherit\") returns null, clobbering\n // the ancestor fg. See Bead km-silvery.color-inherit.\n const isInheritKeyword = props.color === \"inherit\" || props.color === \"currentColor\"\n const childInheritedFg = isInheritKeyword\n ? nodeState.inheritedFg\n : props.color\n ? parseColor(props.color)\n : nodeTheme\n ? parseColor(nodeTheme.fg)\n : nodeState.inheritedFg\n\n // Render children — pass inherited bg/fg so children don't walk the parent chain\n const childState: NodeRenderState = {\n ...nodeState,\n inheritedBg: childInheritedBg,\n inheritedFg: childInheritedFg,\n }\n if (isScrollContainer) {\n renderScrollContainerChildren(\n node,\n buffer,\n props,\n childState,\n contentRegionCleared,\n childrenNeedFreshRender,\n ctx,\n )\n\n // Render overflow indicators AFTER children so they survive viewport clear.\n // renderScrollContainerChildren may clear the viewport (Tier 2) which would\n // overwrite indicators drawn before children.\n renderScrollIndicators(node, buffer, layout, props, node.scrollState!, ctx)\n } else {\n renderNormalChildren(\n node,\n buffer,\n props,\n childState,\n childPositionChanged,\n contentRegionCleared,\n childrenNeedFreshRender,\n ctx,\n )\n }\n\n // Outlines are NOT rendered here — the decoration phase (post-content)\n // walks the tree independently and draws outlines for every node with\n // outlineStyle, using per-cell snapshots to clear prev positions next\n // frame. See decoration-phase.ts.\n\n // Clear dirty flags (current node only — children clear their own when rendered)\n clearNodeDirtyFlags(node)\n } finally {\n // Pop per-subtree theme override (after ALL child passes including absolute/sticky)\n if (nodeTheme) popContextTheme()\n // Restore parent's selectable mode\n buffer.setSelectableMode(prevSelectableMode)\n }\n}\n\n// ============================================================================\n// Cascade Input Helpers\n// ============================================================================\n\n/**\n * Build tree-dependent cascade inputs that require child traversal.\n *\n * These feed into computeCascade() alongside the node's own dirty flags.\n * Separated from renderNodeToBuffer to keep the main function focused on\n * the rendering flow rather than child traversal details.\n */\nfunction buildCascadeInputs(\n node: AgNode,\n hasPrevBuffer: boolean,\n): { absoluteChildMutated: boolean; descendantOverflowChanged: boolean } {\n if (\n !hasPrevBuffer ||\n !isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT) ||\n node.children === undefined\n ) {\n return { absoluteChildMutated: false, descendantOverflowChanged: false }\n }\n\n // Phase 3b: Read cached epoch values computed during layout phase\n // (propagateLayout), avoiding per-node tree walks in the render phase.\n return {\n absoluteChildMutated: isDirty(node.dirtyBits, node.dirtyEpoch, ABS_CHILD_BIT),\n descendantOverflowChanged: isDirty(node.dirtyBits, node.dirtyEpoch, DESC_OVERFLOW_BIT),\n }\n}\n\n// ============================================================================\n// Render Decision Tracing\n// ============================================================================\n\n/**\n * Log per-node trace, cascade tracking, and cell debug info.\n *\n * Gated on instrumentation or cell debug being active. Separated from\n * renderNodeToBuffer to keep the main function focused on rendering logic.\n */\nfunction traceRenderDecision(\n node: AgNode,\n props: BoxProps & TextProps,\n layout: NonNullable<AgNode[\"boxRect\"]>,\n screenY: number,\n scrollOffset: number,\n hasPrevBuffer: boolean,\n ancestorCleared: boolean,\n layoutChanged: boolean,\n childPositionChanged: boolean,\n cascade: CascadeOutputs,\n _nodeId: string,\n _traceThis: string | false | 0 | \"\",\n _cellDbg: { x: number; y: number; log: string[] } | undefined,\n _coversCellNow: boolean | undefined,\n _coversCellPrev: boolean | null | undefined,\n instrumentEnabled: boolean,\n stats: RenderPhaseStats,\n nodeTrace: NodeTraceEntry[],\n): void {\n const { contentAreaAffected, contentRegionCleared, skipBgFill, childrenNeedFreshRender } = cascade\n\n // Per-node trace and cascade tracking (gated on instrumentation)\n if (instrumentEnabled) {\n if (_traceThis) {\n const flagStr = [\n isDirty(node.dirtyBits, node.dirtyEpoch, CONTENT_BIT) && \"C\",\n isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT) && \"P\",\n isDirty(node.dirtyBits, node.dirtyEpoch, BG_BIT) && \"B\",\n isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT) && \"S\",\n isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT) && \"Ch\",\n childPositionChanged && \"CP\",\n ]\n .filter(Boolean)\n .join(\",\")\n const childrenNeedRepaint_ =\n isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT) ||\n childPositionChanged ||\n childrenNeedFreshRender\n const childHasPrev_ = childrenNeedRepaint_ ? false : hasPrevBuffer\n const childAncestorCleared_ =\n contentRegionCleared || (ancestorCleared && !getEffectiveBg(props))\n nodeTrace.push({\n id: _nodeId,\n type: node.type,\n depth: _getNodeDepth(node),\n rect: `${layout.x},${layout.y} ${layout.width}x${layout.height}`,\n prevLayout: node.prevLayout\n ? `${node.prevLayout.x},${node.prevLayout.y} ${node.prevLayout.width}x${node.prevLayout.height}`\n : \"null\",\n hasPrev: hasPrevBuffer,\n ancestorCleared,\n flags: flagStr,\n decision: \"RENDER\",\n layoutChanged,\n contentAreaAffected,\n contentRegionCleared,\n childrenNeedFreshRender,\n childHasPrev: childHasPrev_,\n childAncestorCleared: childAncestorCleared_,\n skipBgFill,\n bgColor: props.backgroundColor as string | undefined,\n })\n }\n if (childrenNeedFreshRender && node.children.length > 0) {\n const depth = _getNodeDepth(node)\n if (depth < stats.cascadeMinDepth) {\n stats.cascadeMinDepth = depth\n }\n const id = (node.props as Record<string, unknown>).id ?? node.type\n const flags = [\n isDirty(node.dirtyBits, node.dirtyEpoch, CONTENT_BIT) && \"C\",\n isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT) && \"P\",\n isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT) && \"Ch\",\n layoutChanged && \"L\",\n childPositionChanged && \"CP\",\n ]\n .filter(Boolean)\n .join(\"\")\n const entry = `${id}@${depth}[${flags}:${node.children.length}ch]`\n stats.cascadeNodes += (stats.cascadeNodes ? \" \" : \"\") + entry\n }\n }\n\n // Cell debug: log render decision for nodes covering target cell\n if (_cellDbg && (_coversCellNow || _coversCellPrev)) {\n const id = (props.id as string) ?? node.type\n const depth = _getNodeDepth(node)\n const prev = node.prevLayout\n const flags = [\n isDirty(node.dirtyBits, node.dirtyEpoch, CONTENT_BIT) && \"C\",\n isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT) && \"P\",\n layoutChanged && \"L\",\n isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT) && \"S\",\n isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT) && \"Ch\",\n childPositionChanged && \"CP\",\n isDirty(node.dirtyBits, node.dirtyEpoch, BG_BIT) && \"B\",\n ]\n .filter(Boolean)\n .join(\",\")\n const msg =\n `RENDER ${id}@${depth} rect=${layout.x},${screenY} ${layout.width}x${layout.height}` +\n ` prev=${prev ? `${prev.x},${prev.y - scrollOffset} ${prev.width}x${prev.height}` : \"null\"}` +\n ` flags=[${flags}] hasPrev=${hasPrevBuffer} ancClr=${ancestorCleared}` +\n ` caa=${contentAreaAffected} prc=${contentRegionCleared} prm=${childrenNeedFreshRender}` +\n ` coversNow=${_coversCellNow} coversPrev=${_coversCellPrev}` +\n ` bg=${props.backgroundColor ?? \"none\"}`\n _cellDbg.log.push(msg)\n cellLog.debug?.(msg)\n }\n}\n\n// ============================================================================\n// Region Clearing (executeRegionClearing)\n// ============================================================================\n\n/**\n * Handle all region clearing before rendering own content.\n *\n * Three clearing paths:\n * 1. contentRegionCleared: clear the node's region with inherited bg (no own bg)\n * 2. Excess area: clear stale pixels when a node shrank (even without contentRegionCleared)\n * 3. Descendant overflow: clear areas where descendants previously overflowed this node's rect\n *\n * All clearing runs BEFORE renderBox/renderText so borders drawn later are not overwritten.\n */\nfunction executeRegionClearing(\n node: AgNode,\n buffer: TerminalBuffer,\n layout: NonNullable<AgNode[\"boxRect\"]>,\n scrollOffset: number,\n clipBounds: ClipBounds | undefined,\n bufferIsCloned: boolean,\n layoutChanged: boolean,\n contentRegionCleared: boolean,\n descendantOverflowChanged: boolean,\n instrumentEnabled: boolean,\n stats: RenderPhaseStats,\n threadedInheritedBg: NodeRenderState[\"inheritedBg\"],\n): void {\n if (contentRegionCleared) {\n if (instrumentEnabled) stats.clearOps++\n clearNodeRegion(\n node,\n buffer,\n layout,\n scrollOffset,\n clipBounds,\n layoutChanged,\n threadedInheritedBg,\n )\n } else if (bufferIsCloned && layoutChanged && node.prevLayout) {\n // Even when contentRegionCleared is false, a shrinking node needs its excess\n // area cleared. Key scenario: absolute-positioned overlays (e.g., search dialog)\n // that shrink while normal-flow siblings are dirty -- forceRepaint sets\n // hasPrevBuffer=false + ancestorCleared=false, making contentRegionCleared=false,\n // but the cloned buffer still has stale pixels from the old larger layout.\n // Also applies to nodes WITH backgroundColor: renderBox fills only the NEW\n // (smaller) region, leaving stale pixels in the excess area.\n //\n // Gated on bufferIsCloned: on a fresh buffer (e.g., multi-pass resize where\n // dimensions changed between passes), there are no stale pixels to clear.\n // Without this guard, clearExcessArea writes inherited bg into cells that\n // doFreshRender leaves as default, causing STRICT mismatches.\n clearExcessArea(\n node,\n buffer,\n layout,\n scrollOffset,\n clipBounds,\n layoutChanged,\n threadedInheritedBg,\n )\n }\n\n // Clear descendant overflow regions: areas where descendants' previous layouts\n // extended beyond THIS node's rect. clearNodeRegion covers the node's interior,\n // but overflow content is beyond it. This is separate from contentRegionCleared\n // because overflow is OUTSIDE the rect -- it needs clearing even for nodes with\n // backgroundColor (whose interior is handled by renderBox's bg fill).\n if (descendantOverflowChanged) {\n clearDescendantOverflowRegions(\n node,\n buffer,\n layout,\n scrollOffset,\n clipBounds,\n threadedInheritedBg,\n )\n }\n\n // Outline cleanup is handled entirely by the decoration phase (outlines\n // are treated as a separate pass that snapshots under-cells and restores\n // them next frame). No outline-aware work needed here.\n}\n\n// ============================================================================\n// Own Content Rendering\n// ============================================================================\n\n/**\n * Render this node's own content (box background/border or text).\n *\n * For boxes: computes inherited bg for border rendering and calls renderBox.\n * For text: computes inherited bg/fg for text rendering and calls renderText.\n *\n * @returns The boxInheritedBg color (needed by outline rendering after children).\n */\nfunction renderOwnContent(\n node: AgNode,\n buffer: TerminalBuffer,\n layout: NonNullable<AgNode[\"boxRect\"]>,\n props: BoxProps & TextProps,\n nodeState: NodeRenderState,\n skipBgFill: boolean,\n instrumentEnabled: boolean,\n stats: RenderPhaseStats,\n ctx?: PipelineContext,\n bgOnlyChange = false,\n useTextStyleFastPath = false,\n): Color | undefined {\n // O(1) inherited bg/fg from nodeState — threaded top-down, no parent chain walks.\n const boxInheritedBg =\n node.type === \"silvery-box\" && !getEffectiveBg(props) ? nodeState.inheritedBg.color : undefined\n\n if (node.type === \"silvery-box\") {\n if (instrumentEnabled) stats.boxNodes++\n // inheritedFg is threaded so renderBorder can resolve borderColor=\"currentColor\".\n renderBox(\n node,\n buffer,\n layout,\n props,\n nodeState,\n skipBgFill,\n boxInheritedBg,\n bgOnlyChange,\n nodeState.inheritedFg,\n )\n } else if (node.type === \"silvery-text\") {\n if (instrumentEnabled) stats.textNodes++\n // O(1) inherited bg/fg — threaded top-down through nodeState.\n // inheritedBg decouples text rendering from buffer state, which is critical\n // for incremental rendering: the cloned buffer may have stale bg at positions\n // outside the parent's bg-filled region (e.g., overflow text, moved nodes).\n // Foreground inheritance matches CSS semantics: Box color cascades to Text children.\n const textInheritedBg = nodeState.inheritedBg.color\n const textInheritedFg = nodeState.inheritedFg\n\n // Style-only fast path for text nodes: when only visual style props changed\n // (color, bold, dim, inverse, etc.) but text content is identical, skip the\n // expensive collectTextWithBg → formatTextLines → renderGraphemes pipeline.\n // Instead, restyle existing cells in-place with the new style.\n //\n // Conditions (pre-computed as useTextStyleFastPath):\n // 1. hasPrevBuffer: cloned buffer has correct chars from previous frame\n // 2. isStyleOnlyDirty: only style props changed (no content, bg, or children)\n // 3. No nested children with bg: restyleRegion would overwrite their bg\n // 4. Not ancestorCleared/ancestorLayoutChanged: cells are at correct positions\n //\n // This avoids O(text_length) text processing for the common case of\n // cursor/selection styling (just color/bold/inverse changes on text nodes).\n if (useTextStyleFastPath) {\n const style = getTextStyle(props)\n if (style.fg === null && textInheritedFg !== undefined) {\n style.fg = textInheritedFg\n }\n const effectiveBg = style.bg !== null ? style.bg : (textInheritedBg ?? null)\n const { x, width, height } = layout\n const y = layout.y - nodeState.scrollOffset\n buffer.restyleRegion(x, y, width, height, {\n fg: style.fg,\n bg: effectiveBg,\n underlineColor: style.underlineColor ?? null,\n attrs: style.attrs,\n })\n } else {\n renderText(node, buffer, layout, props, nodeState, textInheritedBg, textInheritedFg, ctx)\n }\n }\n\n return boxInheritedBg\n}\n\n// ============================================================================\n// Scroll Tier Planner\n// ============================================================================\n\n/** Which tier strategy a scroll container uses for this frame. */\nexport type ScrollTier = \"shift\" | \"clear\" | \"subtree-only\"\n\n/** Inputs for the scroll tier decision (all from renderScrollContainerChildren). */\nexport interface ScrollPlanInputs {\n /** Scroll offset changed since last frame. */\n scrollOffsetChanged: boolean\n /** Visible child index range changed. */\n visibleRangeChanged: boolean\n /** Scroll container has sticky children. */\n hasStickyChildren: boolean\n /** Parent cascade: children need fresh render (contentAreaAffected || bgRefillNeeded). */\n childrenNeedFreshRender: boolean\n /** Node has restructured children (added/removed/reordered). */\n childrenDirty: boolean\n /** Buffer from previous frame is available (incremental mode). */\n hasPrevBuffer: boolean\n /** An ancestor cleared its region. */\n ancestorCleared: boolean\n /** This node's content region was cleared (no own bg). */\n contentRegionCleared: boolean\n /** The bg to use for viewport clears (own bg or inherited). */\n scrollBg: Color | null\n}\n\n/** Result of the scroll tier decision. */\nexport interface ScrollPlan {\n /** Which tier strategy to use. */\n tier: ScrollTier\n /** Background color for viewport clear/shift fill (null = no bg). */\n clearBg: Color | null\n /** Default hasPrevBuffer for children. */\n childHasPrev: boolean\n /** Default ancestorCleared for children. */\n childAncestorCleared: boolean\n /** Whether all first-pass items must re-render (Tier 3 with sticky children). */\n stickyForceRefresh: boolean\n /** Human-readable reasons for the tier decision (for instrumentation). */\n reasons: string[]\n}\n\n/**\n * Determine the scroll tier strategy for this frame.\n *\n * Pure function -- no side effects, no node access beyond the inputs.\n *\n * Three-tier strategy:\n * 1. **shift**: Only scroll offset changed, no sticky children. Buffer contents\n * shifted by scroll delta; only newly visible edges re-render.\n * 2. **clear**: Children restructured, visible range changed with scroll, or\n * parent region changed. Entire viewport cleared and all children re-render.\n * 3. **subtree-only**: Only some descendants changed. Children use hasPrevBuffer=true\n * and skip via fast-path if clean. With sticky children, forces all first-pass\n * items to re-render (stickyForceRefresh).\n */\nexport function planScrollRender(inputs: ScrollPlanInputs): ScrollPlan {\n const {\n scrollOffsetChanged,\n visibleRangeChanged,\n hasStickyChildren,\n childrenNeedFreshRender,\n childrenDirty,\n hasPrevBuffer,\n ancestorCleared,\n contentRegionCleared,\n scrollBg,\n } = inputs\n\n // Tier 1: Buffer shift -- scroll offset changed but nothing else.\n // Unsafe with sticky children (sticky second pass overwrites shifted pixels).\n const scrollOnly =\n hasPrevBuffer &&\n scrollOffsetChanged &&\n !childrenDirty &&\n !childrenNeedFreshRender &&\n !hasStickyChildren &&\n !visibleRangeChanged\n\n // Tier 2: Full viewport clear -- children restructured, scroll+sticky, or parent changed.\n const needsViewportClear =\n hasPrevBuffer &&\n !scrollOnly &&\n (scrollOffsetChanged || childrenDirty || childrenNeedFreshRender || visibleRangeChanged)\n\n // Tier 3 with sticky: force all first-pass items to re-render.\n // The cloned buffer has stale content from previous frames' sticky positions.\n const stickyForceRefresh = hasStickyChildren && hasPrevBuffer && !needsViewportClear\n\n // Build reasons for instrumentation\n const reasons: string[] = []\n if (scrollOnly) reasons.push(\"SHIFT\")\n if (needsViewportClear) {\n if (scrollOffsetChanged) reasons.push(\"scrollOffset\")\n if (childrenDirty) reasons.push(\"childrenDirty\")\n if (childrenNeedFreshRender) reasons.push(\"childrenNeedFreshRender\")\n if (visibleRangeChanged) reasons.push(\"visibleRangeChanged\")\n }\n if (stickyForceRefresh) reasons.push(\"stickyForceRefresh\")\n\n const tier: ScrollTier = scrollOnly ? \"shift\" : needsViewportClear ? \"clear\" : \"subtree-only\"\n\n const childHasPrev = needsViewportClear ? false : hasPrevBuffer\n const childAncestorCleared = needsViewportClear ? true : ancestorCleared || contentRegionCleared\n\n return {\n tier,\n clearBg: scrollOnly || needsViewportClear ? scrollBg : null,\n childHasPrev,\n childAncestorCleared,\n stickyForceRefresh,\n reasons,\n }\n}\n\n/**\n * Render children of a scroll container with proper clipping and offset.\n */\nfunction renderScrollContainerChildren(\n node: AgNode,\n buffer: TerminalBuffer,\n props: BoxProps,\n nodeState: NodeRenderState,\n contentRegionCleared = false,\n childrenNeedFreshRender = false,\n ctx?: PipelineContext,\n): void {\n const {\n clipBounds,\n hasPrevBuffer,\n ancestorCleared,\n bufferIsCloned,\n ancestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n } = nodeState\n const instr = resolveInstrumentation(ctx)\n const layout = node.boxRect\n const ss = node.scrollState\n if (!layout || !ss) return\n\n const border = props.borderStyle ? getBorderSize(props) : { top: 0, bottom: 0, left: 0, right: 0 }\n const padding = getPadding(props)\n // Scroll containers clip vertically (for scrolling) but NOT horizontally.\n // Scroll containers clip vertically (viewport) but not horizontally —\n // horizontal containment is handled by text wrapping, not clipping.\n const viewportClipBounds = computeChildClipBounds(\n layout,\n props,\n clipBounds,\n 0,\n /* horizontal */ false,\n /* vertical */ true,\n )\n\n // Borderless overflow indicators (overflowIndicator=true with no borderStyle)\n // render ON TOP of the first/last content row of the viewport. When a child\n // overflows into that row, the indicator overwrites the child's content,\n // producing the user-reported \"▼1 covers the last card's text\" bug\n // (km-tui.column-top-disappears).\n //\n // Build a reduced clip for children that excludes the reserved indicator\n // row(s). Viewport operations (Tier 1 shift indicator pre-clear, Tier 2\n // viewport clear) continue to use the full `viewportClipBounds` so the\n // indicator row itself is repainted correctly.\n const showBorderlessIndicator = props.overflowIndicator === true && !props.borderStyle\n const childClipBounds =\n showBorderlessIndicator && (ss.hiddenAbove > 0 || ss.hiddenBelow > 0)\n ? {\n ...viewportClipBounds,\n top: ss.hiddenAbove > 0 ? viewportClipBounds.top + 1 : viewportClipBounds.top,\n bottom: ss.hiddenBelow > 0 ? viewportClipBounds.bottom - 1 : viewportClipBounds.bottom,\n }\n : viewportClipBounds\n\n // Determine if scroll offset changed since last render.\n const scrollOffsetChanged = ss.offset !== ss.prevOffset\n const hasStickyChildren = !!(ss.stickyChildren && ss.stickyChildren.length > 0)\n const visibleRangeChanged =\n ss.firstVisibleChild !== ss.prevFirstVisibleChild ||\n ss.lastVisibleChild !== ss.prevLastVisibleChild\n\n // Compute viewport geometry (shared by all tiers).\n // `clearY` / `clearHeight` describe the FULL viewport (including indicator\n // rows) — used by Tier 1 shift / Tier 2 clear / Tier 3 sticky-force-refresh,\n // all of which need to repaint the indicator rows. Children still render\n // with the shrunk `childClipBounds` so they don't overwrite indicators.\n const clearY = viewportClipBounds.top\n const clearHeight = viewportClipBounds.bottom - viewportClipBounds.top\n const contentX = layout.x + border.left + padding.left\n const contentWidth = layout.width - border.left - border.right - padding.left - padding.right\n\n // Compute scroll bg eagerly -- planScrollRender needs it and it's cheap\n const scrollBg =\n scrollOffsetChanged ||\n isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT) ||\n childrenNeedFreshRender ||\n visibleRangeChanged\n ? getEffectiveBg(props)\n ? parseColor(getEffectiveBg(props)!)\n : inheritedBg.color\n : null\n\n // Plan the scroll tier strategy (pure decision, no side effects)\n const plan = planScrollRender({\n scrollOffsetChanged,\n visibleRangeChanged,\n hasStickyChildren,\n childrenNeedFreshRender,\n childrenDirty: isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT),\n hasPrevBuffer,\n ancestorCleared,\n contentRegionCleared,\n scrollBg,\n })\n const { tier, stickyForceRefresh } = plan\n const defaultChildHasPrev = plan.childHasPrev\n const defaultChildAncestorCleared = plan.childAncestorCleared\n\n if (instr.enabled) {\n instr.stats.scrollContainerCount++\n if (tier !== \"subtree-only\" || stickyForceRefresh) {\n instr.stats.scrollViewportCleared++\n const reasons = [...plan.reasons]\n if (scrollOffsetChanged) reasons.push(`scrollOffset(${ss.prevOffset}->${ss.offset})`)\n reasons.push(\n `vp=${ss.viewportHeight} content=${ss.contentHeight} vis=${ss.firstVisibleChild}-${ss.lastVisibleChild}`,\n )\n instr.stats.scrollClearReason = reasons.join(\"+\")\n }\n }\n\n // STRICT invariant: Tier 1 (buffer shift) must never be used with sticky children.\n if (process?.env?.SILVERY_STRICT && tier === \"shift\" && hasStickyChildren) {\n throw new Error(\n `[SILVERY_STRICT] Scroll Tier 1 (buffer shift) activated with sticky children ` +\n `(node: ${(props.id as string | undefined) ?? node.type}, ` +\n `stickyCount: ${ss.stickyChildren?.length ?? 0})`,\n )\n }\n\n // Apply the plan: buffer shift, viewport clear, or sticky force refresh\n const scrollDelta = ss.offset - (ss.prevOffset ?? ss.offset)\n if (tier === \"shift\" && clearHeight > 0) {\n // Clear scroll indicator rows before shifting to prevent stale indicator\n // pixels at edges (columns not covered by children).\n const showBorderless = props.overflowIndicator === true\n if (showBorderless && !border.top && !border.bottom) {\n const topIndicatorY = clearY\n const bottomIndicatorY = clearY + clearHeight - 1\n if (ss.prevOffset != null && ss.prevOffset > 0) {\n buffer.fill(contentX, topIndicatorY, contentWidth, 1, { char: \" \", bg: plan.clearBg })\n }\n buffer.fill(contentX, bottomIndicatorY, contentWidth, 1, { char: \" \", bg: plan.clearBg })\n }\n buffer.scrollRegion(contentX, clearY, contentWidth, clearHeight, scrollDelta, {\n char: \" \",\n bg: plan.clearBg,\n })\n }\n\n if (tier === \"clear\" && clearHeight > 0) {\n buffer.fill(contentX, clearY, contentWidth, clearHeight, {\n char: \" \",\n bg: plan.clearBg,\n })\n }\n\n // Tier 3 with sticky: clear viewport to null bg (matches fresh render state)\n // before re-rendering all items, so the sticky second pass works correctly.\n if (stickyForceRefresh && clearHeight > 0) {\n buffer.fill(contentX, clearY, contentWidth, clearHeight, { char: \" \", bg: null })\n }\n\n // Propagate ancestor layout change to scroll container children.\n const childAncestorLayoutChanged =\n isCurrentEpoch(node.layoutChangedThisFrame) || !!ancestorLayoutChanged\n\n // For buffer shift: children that were fully visible in BOTH the previous\n // and current frames have correct pixels after the shift (childHasPrev=true).\n const prevVisTop = ss.prevOffset ?? ss.offset\n const prevVisBottom = prevVisTop + ss.viewportHeight\n\n // First pass: render non-sticky visible children with scroll offset\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i]\n if (!child) continue\n const childProps = child.props as BoxProps\n\n // Skip sticky children - they're rendered in second pass\n if (childProps.position === \"sticky\") {\n continue\n }\n\n // Skip children that are completely outside the visible range\n if (i < ss.firstVisibleChild || i > ss.lastVisibleChild) {\n continue\n }\n\n // Determine per-child hasPrev for buffer shift mode\n let thisChildHasPrev = defaultChildHasPrev\n let thisChildAncestorCleared = defaultChildAncestorCleared\n if (tier === \"shift\") {\n // Check if child was fully visible in the previous frame\n const childRect = child.boxRect\n if (childRect) {\n const childTop = childRect.y - layout.y - border.top - padding.top\n const childBottom = childTop + childRect.height\n const wasFullyVisible = childTop >= prevVisTop && childBottom <= prevVisBottom\n thisChildHasPrev = wasFullyVisible\n // Shifted children: their pixels are intact (not cleared)\n // Newly visible: exposed region was filled by scrollRegion\n thisChildAncestorCleared = wasFullyVisible ? ancestorCleared || contentRegionCleared : true\n }\n }\n\n // Force fresh rendering when sticky children exist (see stickyForceRefresh).\n if (stickyForceRefresh && thisChildHasPrev) {\n thisChildHasPrev = false\n thisChildAncestorCleared = false\n }\n\n // Phase 4: dirty set pre-check — skip clean subtrees without function call overhead.\n if (canSkipChildSubtree(child, thisChildHasPrev, childAncestorLayoutChanged)) {\n continue\n }\n\n // Render visible children with scroll offset applied.\n renderNodeToBuffer(\n child,\n buffer,\n {\n scrollOffset: ss.offset,\n clipBounds: childClipBounds,\n hasPrevBuffer: thisChildHasPrev,\n ancestorCleared: thisChildAncestorCleared,\n bufferIsCloned,\n ancestorLayoutChanged: childAncestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n },\n ctx,\n )\n }\n\n // Second pass: render sticky children at their computed positions\n // Rendered last so they appear on top of other content\n if (ss.stickyChildren) {\n for (const sticky of ss.stickyChildren) {\n const child = node.children[sticky.index]\n if (!child?.boxRect) continue\n\n // Calculate the scroll offset that would place the child at its sticky position\n // stickyOffset = naturalTop - renderOffset\n // This makes the child render at renderOffset instead of its natural position\n const stickyScrollOffset = sticky.naturalTop - sticky.renderOffset\n\n // Sticky children always re-render (hasPrevBuffer=false) since their\n // effective scroll offset may change even when the container's doesn't.\n //\n // ancestorCleared=false matches fresh render semantics: on a fresh render,\n // the buffer at sticky positions has first-pass content (not \"cleared\").\n // Using ancestorCleared=true would cause transparent spacer Boxes to clear\n // their region (via layoutChanged=true from prevLayout=null → cascading\n // contentRegionCleared), wiping overlapping sticky headers rendered earlier\n // in this pass.\n //\n // Stale bg from previous frames is handled by the stickyForceRefresh\n // pre-clear above, which ensures correct bg is in the buffer before sticky\n // children render on top of first-pass content.\n renderNodeToBuffer(\n child,\n buffer,\n {\n scrollOffset: stickyScrollOffset,\n clipBounds: childClipBounds,\n hasPrevBuffer: false,\n ancestorCleared: false,\n bufferIsCloned,\n ancestorLayoutChanged: childAncestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n },\n ctx,\n )\n }\n }\n}\n\n/**\n * Render children of a normal (non-scroll) container.\n */\nfunction renderNormalChildren(\n node: AgNode,\n buffer: TerminalBuffer,\n props: BoxProps,\n nodeState: NodeRenderState,\n childPositionChanged = false,\n contentRegionCleared = false,\n childrenNeedFreshRender = false,\n ctx?: PipelineContext,\n): void {\n const {\n scrollOffset,\n clipBounds,\n hasPrevBuffer,\n ancestorCleared,\n bufferIsCloned,\n ancestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n } = nodeState\n const instr = resolveInstrumentation(ctx)\n const layout = node.boxRect\n if (!layout) return\n\n // For overflow='hidden' containers, clip children to content area.\n // Supports per-axis clipping: overflowX/overflowY override the shorthand overflow prop.\n const clipX = (props.overflowX ?? props.overflow) === \"hidden\"\n const clipY = (props.overflowY ?? props.overflow) === \"hidden\"\n const effectiveClipBounds =\n clipX || clipY\n ? computeChildClipBounds(layout, props, clipBounds, scrollOffset, clipX, clipY)\n : clipBounds\n\n // Non-scroll sticky children support. When the layout phase computes\n // node.stickyChildren, we use the same two-pass pattern as scroll containers:\n // first pass renders non-sticky children, second pass renders sticky children\n // at their computed renderOffset positions.\n const hasStickyChildren = !!(node.stickyChildren && node.stickyChildren.length > 0)\n\n // When sticky children exist and hasPrevBuffer is true, force all first-pass\n // children to re-render. The cloned buffer may have stale pixels from previous\n // frames' sticky positions. This matches the stickyForceRefresh pattern from\n // scroll containers (Tier 3).\n const stickyForceRefresh = hasStickyChildren && hasPrevBuffer\n\n // Pre-clear the content area to bg=null when stickyForceRefresh is true.\n // Fresh renders start with a blank buffer (null bg everywhere). The cloned\n // buffer has stale content from old sticky positions that would leak through\n // on incremental renders. Clearing to null matches fresh render state before\n // any content renders.\n if (stickyForceRefresh) {\n const border = props.borderStyle\n ? getBorderSize(props)\n : { top: 0, bottom: 0, left: 0, right: 0 }\n const padding = getPadding(props)\n let clearX = layout.x + border.left + padding.left\n let clearY = layout.y - scrollOffset + border.top + padding.top\n let clearW = layout.width - border.left - border.right - padding.left - padding.right\n let clearH = layout.height - border.top - border.bottom - padding.top - padding.bottom\n // Clip to clipBounds (same discipline as scroll container clear)\n if (clipBounds) {\n const clipTop = clipBounds.top\n const clipBottom = clipBounds.bottom\n if (clearY < clipTop) {\n clearH -= clipTop - clearY\n clearY = clipTop\n }\n if (clearY + clearH > clipBottom) {\n clearH = clipBottom - clearY\n }\n if (clipBounds.left !== undefined && clearX < clipBounds.left) {\n clearW -= clipBounds.left - clearX\n clearX = clipBounds.left\n }\n if (clipBounds.right !== undefined && clearX + clearW > clipBounds.right) {\n clearW = clipBounds.right - clearX\n }\n }\n if (clearW > 0 && clearH > 0) {\n buffer.fill(clearX, clearY, clearW, clearH, { char: \" \", bg: null })\n }\n }\n\n // Force children to re-render when parent's region was modified on a clone,\n // children were restructured, or sibling positions shifted.\n const childrenNeedRepaint =\n isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT) ||\n childPositionChanged ||\n childrenNeedFreshRender\n if (instr.enabled && childrenNeedRepaint && hasPrevBuffer) {\n instr.stats.normalChildrenRepaint++\n const reasons: string[] = []\n if (isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT)) reasons.push(\"childrenDirty\")\n if (childPositionChanged) reasons.push(\"childPositionChanged\")\n if (childrenNeedFreshRender) reasons.push(\"childrenNeedFreshRender\")\n instr.stats.normalRepaintReason = reasons.join(\"+\")\n }\n let childHasPrev = childrenNeedRepaint ? false : hasPrevBuffer\n // childAncestorCleared: tells descendants that STALE pixels exist in the buffer.\n // Only contentRegionCleared (no bg fill → stale pixels remain) propagates this.\n // childrenNeedFreshRender WITHOUT contentRegionCleared means the parent filled its bg,\n // so children's positions have correct bg — NOT stale. Setting ancestorCleared\n // there would cause children to re-fill, overwriting border cells at boundaries.\n // When this node has backgroundColor, its renderBox fill covers any stale\n // pixels from ancestor clears — so children don't need ancestorCleared.\n let childAncestorCleared = contentRegionCleared || (ancestorCleared && !getEffectiveBg(props))\n\n // Propagate ancestor layout change to children: if this node or any ancestor\n // had layoutChangedThisFrame, children must not be skipped even if their own\n // flags are clean — their pixels in the cloned buffer are at wrong positions.\n const childAncestorLayoutChanged =\n isCurrentEpoch(node.layoutChangedThisFrame) || !!ancestorLayoutChanged\n\n // Override child flags when sticky force refresh is active — all first-pass\n // children must re-render fresh (matching the scroll container pattern).\n if (stickyForceRefresh) {\n childHasPrev = false\n childAncestorCleared = false\n }\n\n // Multi-pass rendering to match CSS paint order:\n // 1. Normal-flow children (skip sticky and absolute)\n // 2. Sticky children at computed positions (on top of normal-flow)\n // 3. Absolute children on top of everything\n //\n // This ensures absolute children's pixels (bg fills, text) are never\n // overwritten by normal-flow siblings' clearNodeRegion/render.\n //\n // Pre-scan: detect if any non-absolute, non-sticky sibling is dirty. When\n // true, absolute children in the third pass must force-repaint because the\n // first pass may have overwritten their pixels in the cloned buffer.\n let hasAbsoluteChildren = false\n\n // First pass: render normal-flow children (skip sticky + absolute), track dirty state\n for (const child of node.children) {\n const childProps = child.props as BoxProps\n if (childProps.position === \"absolute\") {\n hasAbsoluteChildren = true\n continue // Skip — rendered in third pass\n }\n if (hasStickyChildren && childProps.position === \"sticky\") {\n continue // Skip — rendered in second pass\n }\n\n // Phase 4: dirty set pre-check — skip clean subtrees without function call overhead.\n // The existing canSkipEntireSubtree inside renderNodeToBuffer is preserved as\n // the second line of defense for edge cases the pre-check doesn't cover.\n if (canSkipChildSubtree(child, childHasPrev, childAncestorLayoutChanged)) {\n continue\n }\n\n renderNodeToBuffer(\n child,\n buffer,\n {\n scrollOffset,\n clipBounds: effectiveClipBounds,\n hasPrevBuffer: childHasPrev,\n ancestorCleared: childAncestorCleared,\n bufferIsCloned,\n ancestorLayoutChanged: childAncestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n },\n ctx,\n )\n }\n\n // Second pass: render sticky children at their computed positions.\n // Rendered after normal-flow so they appear on top of other content.\n if (node.stickyChildren) {\n for (const sticky of node.stickyChildren) {\n const child = node.children[sticky.index]\n if (!child?.boxRect) continue\n\n // Calculate the scroll offset that would place the child at its sticky position.\n // stickyScrollOffset = naturalTop - renderOffset\n // This makes the child render at renderOffset instead of its natural position.\n const stickyScrollOffset = sticky.naturalTop - sticky.renderOffset\n\n // Sticky children always re-render (hasPrevBuffer=false) since their\n // effective position may change between frames.\n //\n // ancestorCleared=false matches fresh render semantics: on a fresh render,\n // the buffer at sticky positions has first-pass content (not \"cleared\").\n // Using ancestorCleared=true would cause transparent spacer Boxes to clear\n // their region, wiping overlapping sticky headers rendered earlier in this pass.\n //\n // ancestorLayoutChanged propagated so descendants know to re-render.\n renderNodeToBuffer(\n child,\n buffer,\n {\n scrollOffset: stickyScrollOffset,\n clipBounds: effectiveClipBounds,\n hasPrevBuffer: false,\n ancestorCleared: false,\n bufferIsCloned,\n ancestorLayoutChanged: childAncestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n },\n ctx,\n )\n }\n }\n\n // Third pass: render absolute children on top (CSS paint order)\n if (hasAbsoluteChildren) {\n for (const child of node.children) {\n const childProps = child.props as BoxProps\n if (childProps.position !== \"absolute\") continue\n\n // Both hasPrevBuffer and ancestorCleared must be false for absolute children\n // in the second pass. The buffer at the absolute child's position contains\n // first-pass content (normal-flow siblings), not \"previous frame\" content.\n // This is conceptually a fresh render at the absolute child's position:\n //\n // - hasPrevBuffer=false: prevents contentRegionCleared from firing.\n // Without this, a transparent overlay (no backgroundColor) that changes\n // (contentAreaAffected=true) would clear its entire region, wiping the\n // normal-flow content painted in the first pass. On a fresh render,\n // hasPrevBuffer=false prevents clearing, so this matches.\n //\n // - ancestorCleared=false: prevents transparent descendants from clearing\n // their regions, which would also wipe first-pass content.\n renderNodeToBuffer(\n child,\n buffer,\n {\n scrollOffset,\n clipBounds: effectiveClipBounds,\n hasPrevBuffer: false,\n ancestorCleared: false,\n bufferIsCloned,\n ancestorLayoutChanged: childAncestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n },\n ctx,\n )\n }\n }\n}\n\n// ============================================================================\n// Dirty Set Pre-Check (Phase 4)\n// ============================================================================\n\n/**\n * O(1) pre-check: can we skip calling renderNodeToBuffer() on this child entirely?\n *\n * This is the Phase 4 \"dirty set rendering\" optimization. Instead of calling\n * renderNodeToBuffer() on every child (which checks canSkipEntireSubtree and\n * returns early for clean nodes), we skip the function call entirely for\n * subtrees with no dirty descendants.\n *\n * The pre-check is CONSERVATIVE: it only skips when we're certain the subtree\n * is clean. False negatives (calling renderNodeToBuffer unnecessarily) are\n * harmless — the existing canSkipEntireSubtree check inside handles them.\n *\n * Key insight: subtreeDirtyEpoch is propagated from every dirty node to the\n * root by markSubtreeDirty (reconciler) and propagateLayout (layout phase).\n * If subtreeDirtyEpoch !== currentEpoch, no descendant is dirty. Combined\n * with layoutChangedThisFrame (set by layout phase but NOT included in\n * subtreeDirtyEpoch on self — only on parent), this covers all dirty paths.\n */\nfunction canSkipChildSubtree(\n child: AgNode,\n childHasPrev: boolean,\n childAncestorLayoutChanged: boolean,\n): boolean {\n // Can't skip without a previous buffer (first render or dimension change)\n if (!childHasPrev) return false\n // Ancestor layout change forces re-render at new position\n if (childAncestorLayoutChanged) return false\n // Any descendant dirty (includes own flags via markSubtreeDirty)\n if (isDirty(child.dirtyBits, child.dirtyEpoch, SUBTREE_BIT)) return false\n // Own layout changed (layout phase sets this but only propagates\n // subtreeDirtyEpoch to PARENT, not self)\n if (isCurrentEpoch(child.layoutChangedThisFrame)) return false\n // Defensive: scroll offset changed without dirty propagation\n if (child.scrollState && child.scrollState.offset !== child.scrollState.prevOffset) return false\n return true\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Clear dirty flags on the current node only (no recursion).\n * Used after rendering a node to reset its flags.\n *\n * With epoch-stamped flags, this is only needed when a subtree is SKIPPED\n * by the fast path (clearDirtyFlags on skipped subtrees) or for the\n * render-phase-adapter. The normal render path relies on advanceRenderEpoch()\n * to expire all flags at once — O(1) instead of O(N).\n */\nfunction clearNodeDirtyFlags(node: AgNode): void {\n node.dirtyBits = 0\n node.dirtyEpoch = INITIAL_EPOCH\n node.layoutChangedThisFrame = INITIAL_EPOCH\n}\n\n/**\n * Clear dirty flags on a subtree that was skipped during incremental rendering.\n */\nfunction clearDirtyFlags(node: AgNode): void {\n clearNodeDirtyFlags(node)\n for (const child of node.children) {\n if (child.layoutNode) {\n clearDirtyFlags(child)\n } else {\n // Virtual text children also need flags cleared — they're rendered by\n // their parent's collectTextContent(), not by renderNodeToBuffer().\n clearVirtualTextFlags(child)\n }\n }\n}\n\n/**\n * Clear dirty flags on a virtual text node and its descendants.\n * Virtual text nodes (no layoutNode) are rendered by their parent layout\n * ancestor via collectTextContent(). Their dirty flags must be cleared\n * after the parent renders, otherwise stale subtreeDirty blocks\n * markSubtreeDirty() propagation on future updates.\n */\nfunction clearVirtualTextFlags(node: AgNode): void {\n clearNodeDirtyFlags(node)\n for (const child of node.children) {\n clearVirtualTextFlags(child)\n }\n}\n\n/**\n * Check if any child's position changed since last render (sibling shift).\n * Checked even when subtreeDirty=true because subtreeDirty only means\n * descendants are dirty, not that this container's gap regions need clearing.\n */\nfunction hasChildPositionChanged(node: AgNode): boolean {\n for (const child of node.children) {\n if (child.boxRect && child.prevLayout) {\n if (child.boxRect.x !== child.prevLayout.x || child.boxRect.y !== child.prevLayout.y) {\n return true\n }\n }\n }\n return false\n}\n\n// hasDescendantOverflowChanged and _checkDescendantOverflow removed in Phase 3b:\n// Now cached as descendantOverflowChangedEpoch on AgNode, computed during layout phase.\n\n/**\n * Check if any descendant has an explicit backgroundColor.\n *\n * Used by the bgOnlyChange fast path: fillBg() updates ALL cells in the region\n * with the parent's bg. If a descendant has its own bg, those cells would be\n * incorrectly overwritten (the descendant is clean and won't re-render to fix it).\n *\n * Only checks Box nodes with explicit backgroundColor or effective bg from theme.\n * Text nodes with backgroundColor are also checked since they render their own bg.\n * Stops at first match (early exit).\n *\n * Performance: walks the child tree, but only runs when bgOnlyChange is true\n * (bg changed, no other flags). This is the cursor-move hot path where trees\n * are typically small (card contents: ~5-20 nodes).\n */\nfunction hasDescendantWithBg(node: AgNode): boolean {\n for (const child of node.children) {\n if (getEffectiveBg(child.props as BoxProps)) return true\n if (child.children.length > 0 && hasDescendantWithBg(child)) return true\n }\n return false\n}\n\n/**\n * Check if a text node has any virtual text children with explicit backgroundColor.\n *\n * Used by the text style-only fast path: restyleRegion() applies a uniform\n * style to all cells. If nested children have their own bg, the uniform restyle\n * would overwrite it (those children rendered their own bg during the original\n * renderText, and won't re-render to restore it).\n */\nfunction hasChildWithBg(node: AgNode): boolean {\n for (const child of node.children) {\n if ((child.props as BoxProps).backgroundColor) return true\n if (child.children.length > 0 && hasChildWithBg(child)) return true\n }\n return false\n}\n\n/**\n * Compute clip bounds for a container's children by insetting for border+padding,\n * then intersecting with parent clip bounds.\n */\nfunction computeChildClipBounds(\n layout: NonNullable<AgNode[\"boxRect\"]>,\n props: BoxProps,\n parentClip: ClipBounds | undefined,\n scrollOffset = 0,\n /** Compute left/right clip bounds for horizontal overflow clipping. */\n horizontal = true,\n /** Compute top/bottom clip bounds for vertical overflow clipping.\n * Defaults to true — scroll containers pass vertical=true, horizontal=false\n * (horizontal containment is via layout OVERFLOW_HIDDEN, not render clipping). */\n vertical = true,\n): ClipBounds {\n const border = props.borderStyle ? getBorderSize(props) : { top: 0, bottom: 0, left: 0, right: 0 }\n const padding = getPadding(props)\n const adjustedY = layout.y - scrollOffset\n const nodeClip: ClipBounds = vertical\n ? {\n top: adjustedY + border.top + padding.top,\n bottom: adjustedY + layout.height - border.bottom - padding.bottom,\n }\n : { top: -Infinity, bottom: Infinity }\n if (horizontal) {\n nodeClip.left = layout.x + border.left + padding.left\n nodeClip.right = layout.x + layout.width - border.right - padding.right\n }\n if (!parentClip) return nodeClip\n const result: ClipBounds = {\n top: vertical ? Math.max(parentClip.top, nodeClip.top) : parentClip.top,\n bottom: vertical ? Math.min(parentClip.bottom, nodeClip.bottom) : parentClip.bottom,\n }\n if (horizontal && nodeClip.left !== undefined && nodeClip.right !== undefined) {\n result.left = Math.max(parentClip.left ?? 0, nodeClip.left)\n result.right = Math.min(parentClip.right ?? Infinity, nodeClip.right)\n } else if (parentClip.left !== undefined && parentClip.right !== undefined) {\n // Pass through parent's horizontal clip bounds without adding own\n result.left = parentClip.left\n result.right = parentClip.right\n }\n return result\n}\n\n// ============================================================================\n// Region Clearing\n// ============================================================================\n\n/**\n * Clear overflow regions: areas where children's prevLayouts extended beyond\n * this node's rect. Called when childOverflowChanged detected stale overflow.\n *\n * clearNodeRegion handles the node's own rect. This function handles the\n * overflow area — pixels that a child rendered OUTSIDE the parent's rect\n * in a previous frame (via overflow:visible behavior). When the child shrinks,\n * those pixels become stale in the cloned buffer.\n *\n * Clears each child's overflow extent, clipped to buffer bounds.\n */\n/**\n * Clear areas where descendants' previous layouts overflowed beyond THIS node's rect.\n * Only clears OUTSIDE the node's rect — interior clearing is handled by clearNodeRegion\n * and renderBox. Recursive: follows subtreeDirty paths to find all overflowing descendants.\n */\nfunction clearDescendantOverflowRegions(\n node: AgNode,\n buffer: TerminalBuffer,\n layout: NonNullable<AgNode[\"boxRect\"]>,\n scrollOffset: number,\n clipBounds: ClipBounds | undefined,\n threadedInheritedBg: NodeRenderState[\"inheritedBg\"],\n): void {\n const clearBg = threadedInheritedBg.color\n const nodeRight = layout.x + layout.width\n const nodeBottom = layout.y - scrollOffset + layout.height\n const nodeLeft = layout.x\n const nodeTop = layout.y - scrollOffset\n\n _clearDescendantOverflow(\n node.children,\n buffer,\n nodeLeft,\n nodeTop,\n nodeRight,\n nodeBottom,\n scrollOffset,\n clipBounds,\n clearBg,\n )\n}\n\nfunction _clearDescendantOverflow(\n children: readonly AgNode[],\n buffer: TerminalBuffer,\n nodeLeft: number,\n nodeTop: number,\n nodeRight: number,\n nodeBottom: number,\n scrollOffset: number,\n clipBounds: ClipBounds | undefined,\n clearBg: Color,\n): void {\n for (const child of children) {\n if (child.prevLayout && isCurrentEpoch(child.layoutChangedThisFrame)) {\n const prev = child.prevLayout\n const prevRight = prev.x + prev.width\n const prevBottom = prev.y - scrollOffset + prev.height\n const prevTop = prev.y - scrollOffset\n\n // Clear overflow to the right of the ancestor\n if (prevRight > nodeRight) {\n const overflowX = nodeRight\n const overflowWidth = Math.min(prevRight, buffer.width) - overflowX\n const overflowTop = Math.max(prevTop, clipBounds?.top ?? 0)\n const overflowBottom = Math.min(prevBottom, clipBounds?.bottom ?? buffer.height)\n if (overflowWidth > 0 && overflowBottom > overflowTop) {\n buffer.fill(overflowX, overflowTop, overflowWidth, overflowBottom - overflowTop, {\n char: \" \",\n bg: clearBg,\n })\n }\n }\n // Clear overflow below the ancestor\n if (prevBottom > nodeBottom) {\n const overflowTop = Math.max(nodeBottom, clipBounds?.top ?? 0)\n const overflowBottom = Math.min(prevBottom, clipBounds?.bottom ?? buffer.height)\n const overflowX = Math.max(prev.x, clipBounds?.left ?? 0)\n const overflowWidth = Math.min(prevRight, clipBounds?.right ?? buffer.width) - overflowX\n if (overflowWidth > 0 && overflowBottom > overflowTop) {\n buffer.fill(overflowX, overflowTop, overflowWidth, overflowBottom - overflowTop, {\n char: \" \",\n bg: clearBg,\n })\n }\n }\n // Clear overflow to the left of the ancestor\n if (prev.x < nodeLeft) {\n const overflowX = Math.max(prev.x, 0)\n const overflowWidth = Math.min(nodeLeft, buffer.width) - overflowX\n const overflowTop = Math.max(prevTop, clipBounds?.top ?? 0)\n const overflowBottom = Math.min(prevBottom, clipBounds?.bottom ?? buffer.height)\n if (overflowWidth > 0 && overflowBottom > overflowTop) {\n buffer.fill(overflowX, overflowTop, overflowWidth, overflowBottom - overflowTop, {\n char: \" \",\n bg: clearBg,\n })\n }\n }\n // Clear overflow above the ancestor\n if (prevTop < nodeTop) {\n const overflowTop = Math.max(prevTop, clipBounds?.top ?? 0)\n const overflowBottom = Math.min(nodeTop, clipBounds?.bottom ?? buffer.height)\n const overflowX = Math.max(prev.x, clipBounds?.left ?? 0)\n const overflowWidth = Math.min(prevRight, clipBounds?.right ?? buffer.width) - overflowX\n if (overflowWidth > 0 && overflowBottom > overflowTop) {\n buffer.fill(overflowX, overflowTop, overflowWidth, overflowBottom - overflowTop, {\n char: \" \",\n bg: clearBg,\n })\n }\n }\n }\n // Recurse into subtree-dirty children to find deeper overflows\n if (isDirty(child.dirtyBits, child.dirtyEpoch, SUBTREE_BIT) && child.children !== undefined) {\n _clearDescendantOverflow(\n child.children,\n buffer,\n nodeLeft,\n nodeTop,\n nodeRight,\n nodeBottom,\n scrollOffset,\n clipBounds,\n clearBg,\n )\n }\n }\n}\n\n/**\n * Clear a node's region with inherited bg when it has no backgroundColor.\n * Also clears excess area when the node shrank (previous layout was larger).\n *\n * Clipping: clips to parent's boxRect (prevents overflow) and to the\n * colored ancestor's bounds (prevents bg color bleeding into siblings).\n */\nfunction clearNodeRegion(\n node: AgNode,\n buffer: TerminalBuffer,\n layout: NonNullable<AgNode[\"boxRect\"]>,\n scrollOffset: number,\n clipBounds: ClipBounds | undefined,\n layoutChanged: boolean,\n threadedInheritedBg: NodeRenderState[\"inheritedBg\"],\n): void {\n const inherited = threadedInheritedBg\n const clearBg = inherited.color\n const screenY = layout.y - scrollOffset\n\n // Clip to parent's boxRect to prevent oversized children from clearing\n // beyond their parent's bounds and bleeding inherited bg into sibling regions.\n const parentRect = node.parent?.boxRect\n const parentBottom = parentRect ? parentRect.y - scrollOffset + parentRect.height : undefined\n\n const clearY = clipBounds ? Math.max(screenY, clipBounds.top) : screenY\n let clearBottom = clipBounds\n ? Math.min(screenY + layout.height, clipBounds.bottom)\n : screenY + layout.height\n if (parentBottom !== undefined) {\n clearBottom = Math.min(clearBottom, parentBottom)\n }\n\n // Clip horizontally to clipBounds (overflow:hidden containers) and to the\n // colored ancestor's bounds (prevents inherited bg bleeding into siblings).\n let clearX = layout.x\n let clearWidth = layout.width\n if (clipBounds?.left !== undefined && clipBounds.right !== undefined) {\n if (clearX < clipBounds.left) {\n clearWidth -= clipBounds.left - clearX\n clearX = clipBounds.left\n }\n if (clearX + clearWidth > clipBounds.right) {\n clearWidth = Math.max(0, clipBounds.right - clearX)\n }\n }\n if (inherited.ancestorRect) {\n const ancestorRight = inherited.ancestorRect.x + inherited.ancestorRect.width\n const ancestorLeft = inherited.ancestorRect.x\n if (clearX < ancestorLeft) {\n clearWidth -= ancestorLeft - clearX\n clearX = ancestorLeft\n }\n if (clearX + clearWidth > ancestorRight) {\n clearWidth = Math.max(0, ancestorRight - clearX)\n }\n }\n\n const clearHeight = clearBottom - clearY\n if (clearHeight > 0 && clearWidth > 0) {\n const _cellDbg2 = getCellDebug()\n if (_cellDbg2 && cellCoversPoint(_cellDbg2, clearX, clearY, clearWidth, clearHeight)) {\n const id = ((node.props as Record<string, unknown>).id as string) ?? node.type\n const msg = `CLEAR_REGION ${id} fill=${clearX},${clearY} ${clearWidth}x${clearHeight} bg=${String(clearBg)} COVERS TARGET`\n _cellDbg2.log.push(msg)\n cellLog.debug?.(msg)\n }\n buffer.fill(clearX, clearY, clearWidth, clearHeight, {\n char: \" \",\n bg: clearBg,\n })\n }\n\n // Delegate excess area clearing to shared helper\n clearExcessArea(node, buffer, layout, scrollOffset, clipBounds, layoutChanged, inherited)\n}\n\n/**\n * Clear the excess area when a node shrinks (old bounds were larger than new).\n *\n * This is separated from clearNodeRegion because excess area clearing must happen\n * even when contentRegionCleared is false. Key scenario: absolute-positioned overlays\n * (e.g., search dialog) that shrink while normal-flow siblings are dirty. The\n * forceRepaint path sets hasPrevBuffer=false + ancestorCleared=false, making\n * contentRegionCleared=false — but the cloned buffer still has stale pixels from\n * the old larger layout that must be cleared.\n *\n * Clips to the COLORED ANCESTOR's content area (not immediate parent's full rect)\n * to prevent inherited color from bleeding into sibling areas with different bg.\n *\n * IMPORTANT: Uses content area (inside border/padding), not full boxRect.\n * Without this, excess clearing of a child that previously filled the parent's\n * content area will extend into the parent's border row, overwriting border chars.\n */\nfunction clearExcessArea(\n node: AgNode,\n buffer: TerminalBuffer,\n layout: NonNullable<AgNode[\"boxRect\"]>,\n scrollOffset: number,\n clipBounds: ClipBounds | undefined,\n layoutChanged: boolean,\n inherited: NodeRenderState[\"inheritedBg\"],\n): void {\n if (!layoutChanged || !node.prevLayout) return\n const prev = node.prevLayout\n\n const _cellDbg3 = getCellDebug()\n const _prevCoversCell3 =\n _cellDbg3 && cellCoversPoint(_cellDbg3, prev.x, prev.y - scrollOffset, prev.width, prev.height)\n\n // Only clear if the node actually shrank in at least one dimension\n if (prev.width <= layout.width && prev.height <= layout.height) {\n if (_cellDbg3 && _prevCoversCell3) {\n const id = ((node.props as Record<string, unknown>).id as string) ?? node.type\n const msg =\n `EXCESS_SKIP_NO_SHRINK ${id} prev=${prev.x},${prev.y - scrollOffset} ${prev.width}x${prev.height}` +\n ` now=${layout.x},${layout.y - scrollOffset} ${layout.width}x${layout.height}`\n _cellDbg3.log.push(msg)\n cellLog.debug?.(msg)\n }\n return\n }\n\n // Skip excess clearing when the node MOVED (changed x or y position).\n // The right/bottom excess formulas use new-x + old-y coordinates, which\n // creates a phantom rectangle at wrong positions when the node moved.\n // Example: text at old=(30,7,23,1) → new=(22,8,14,2) computes excess at\n // (36,7) which overwrites a sibling's border character.\n //\n // When the node moved, the parent handles old-pixel cleanup:\n // - Parent's clearNodeRegion covers old pixels within parent's current rect\n // - Parent's clearExcessArea covers old pixels outside parent's rect\n if (prev.x !== layout.x || prev.y !== layout.y) {\n if (_cellDbg3 && _prevCoversCell3) {\n const id = ((node.props as Record<string, unknown>).id as string) ?? node.type\n const msg =\n `EXCESS_SKIP_MOVED ${id} prev=${prev.x},${prev.y - scrollOffset} ${prev.width}x${prev.height}` +\n ` now=${layout.x},${layout.y - scrollOffset} ${layout.width}x${layout.height}` +\n ` (dx=${layout.x - prev.x} dy=${layout.y - prev.y})`\n _cellDbg3.log.push(msg)\n cellLog.debug?.(msg)\n }\n return\n }\n\n const clearBg = inherited.color\n const screenY = layout.y - scrollOffset\n const prevScreenY = prev.y - scrollOffset\n\n // Clip to prevent excess clearing from bleeding outside valid bounds.\n // Start with the colored ancestor's rect (prevents bg color bleed),\n // then further restrict to the immediate parent's content area (prevents\n // overwriting parent's border characters).\n const clipRect = inherited.ancestorRect ?? node.parent?.boxRect\n if (!clipRect) return\n\n const clipRectScreenY = clipRect.y - scrollOffset\n let clipRectBottom = clipRectScreenY + clipRect.height\n let clipRectRight = clipRect.x + clipRect.width\n\n // Always inset by the immediate parent's border/padding.\n // Without this, a child's excess clearing extends into the parent's\n // border row, overwriting border characters with spaces.\n // (The old code skipped inset when clip rect came from a colored ancestor,\n // assuming \"its bg fill covers its border area\" — but bg fill only covers\n // the inside, while renderBorder draws characters on the border row.)\n const parent = node.parent\n if (parent?.boxRect) {\n const parentProps = parent.props as BoxProps\n const border = getBorderSize(parentProps)\n const padding = getPadding(parentProps)\n const parentRight = parent.boxRect.x + parent.boxRect.width - border.right - padding.right\n const parentBottom =\n parent.boxRect.y - scrollOffset + parent.boxRect.height - border.bottom - padding.bottom\n clipRectRight = Math.min(clipRectRight, parentRight)\n clipRectBottom = Math.min(clipRectBottom, parentBottom)\n }\n\n // Clear right margin (old was wider than new)\n if (prev.width > layout.width) {\n const excessX = layout.x + layout.width\n let excessWidth = prev.width - layout.width\n // Clip horizontally to parent's content area (inside border/padding).\n // Without this, excess clearing of a child that previously filled a wider\n // layout extends into the parent's right border, overwriting border chars.\n if (excessX + excessWidth > clipRectRight) {\n excessWidth = Math.max(0, clipRectRight - excessX)\n }\n if (excessWidth > 0) {\n clippedFill(\n buffer,\n excessX,\n excessWidth,\n prevScreenY,\n prevScreenY + prev.height,\n clipBounds,\n clipRectBottom,\n clearBg,\n )\n }\n }\n\n // Clear bottom margin (old was taller than new)\n if (prev.height > layout.height) {\n let bottomWidth = prev.width\n // Clip horizontally to parent's content area\n if (layout.x + bottomWidth > clipRectRight) {\n bottomWidth = Math.max(0, clipRectRight - layout.x)\n }\n clippedFill(\n buffer,\n layout.x,\n bottomWidth,\n screenY + layout.height,\n prevScreenY + prev.height,\n clipBounds,\n clipRectBottom,\n clearBg,\n )\n }\n}\n\n/** Fill a rectangular region, clipping to clipBounds and an outer bottom limit. */\nfunction clippedFill(\n buffer: TerminalBuffer,\n x: number,\n width: number,\n top: number,\n bottom: number,\n clipBounds: ClipBounds | undefined,\n outerBottom: number,\n bg: Color,\n): void {\n const clippedTop = clipBounds ? Math.max(top, clipBounds.top) : top\n const clippedBottom = Math.min(\n clipBounds ? Math.min(bottom, clipBounds.bottom) : bottom,\n outerBottom,\n )\n let clippedX = x\n let clippedWidth = width\n if (clipBounds?.left !== undefined && clipBounds.right !== undefined) {\n if (clippedX < clipBounds.left) {\n clippedWidth -= clipBounds.left - clippedX\n clippedX = clipBounds.left\n }\n if (clippedX + clippedWidth > clipBounds.right) {\n clippedWidth = Math.max(0, clipBounds.right - clippedX)\n }\n }\n const height = clippedBottom - clippedTop\n if (height > 0 && clippedWidth > 0) {\n buffer.fill(clippedX, clippedTop, clippedWidth, height, { char: \" \", bg })\n }\n}\n","/**\n * Backdrop fade — core color helpers.\n *\n * This module owns the buffer-cell adapter (`colorToHex`), hex↔rgb\n * conversion (`rgbToHex`, `hexToRgb`), hex normalization (`normalizeHex`),\n * and the structural `HexColor` type alias. The color math ops that can\n * pass through to `@silvery/color` (`mixSrgb`, `deemphasizeOklch*`) live in\n * `./color-compat.ts` — they follow the upstream-with-fallback pattern so\n * silvery stays publishable during `@silvery/color` release cycles.\n *\n * @see ./color-compat.ts for the upstream color math shim.\n * @see ./plan.ts for the full backdrop color model.\n */\n\nimport { ansi256ToRgb, isDefaultBg, type Color } from \"../../buffer\"\n\n/**\n * Template-literal brand for a canonical 6-digit lowercase hex color.\n *\n * Values of this type have passed through `normalizeHex`, so they are\n * guaranteed to match `/^#[0-9a-f]{6}$/`. Downstream math helpers\n * (`mixSrgb`, `deemphasizeOklch*`) accept plain `string` for upstream\n * compatibility; within the backdrop module we prefer `HexColor` on\n * normalized inputs so TypeScript flags accidental un-normalized passthrough.\n */\nexport type HexColor = `#${string}`\n\n/** Convert a buffer Color to a `#rrggbb` hex string, or null if unresolvable. */\nexport function colorToHex(color: Color): HexColor | null {\n if (color === null) return null\n if (typeof color === \"number\") {\n const rgb = ansi256ToRgb(color)\n return rgbToHex(rgb.r, rgb.g, rgb.b)\n }\n if (isDefaultBg(color)) return null\n return rgbToHex(color.r, color.g, color.b)\n}\n\nexport function rgbToHex(r: number, g: number, b: number): HexColor {\n const clamp = (n: number): string => {\n const v = Math.max(0, Math.min(255, Math.round(n)))\n return v.toString(16).padStart(2, \"0\")\n }\n return `#${clamp(r)}${clamp(g)}${clamp(b)}` as HexColor\n}\n\n/**\n * Parse `#rrggbb` or `#rgb` (any case, with or without leading `#`) into\n * `{ r, g, b }`. Returns null when the input is not a hex color.\n *\n * Strict character-class validation — `parseInt(\"0g\", 16)` returns `0`\n * silently, which would accept malformed hex values. Regex guard rejects\n * anything outside `[0-9a-f]` regardless of case.\n */\nexport function hexToRgb(hex: string): { r: number; g: number; b: number } | null {\n if (typeof hex !== \"string\") return null\n let s = hex.trim().toLowerCase()\n if (s.startsWith(\"#\")) s = s.slice(1)\n if (s.length === 3) {\n if (!/^[0-9a-f]{3}$/.test(s)) return null\n s = s[0]! + s[0]! + s[1]! + s[1]! + s[2]! + s[2]!\n } else if (!/^[0-9a-f]{6}$/.test(s)) {\n return null\n }\n return {\n r: parseInt(s.slice(0, 2), 16),\n g: parseInt(s.slice(2, 4), 16),\n b: parseInt(s.slice(4, 6), 16),\n }\n}\n\n/**\n * Normalize any permissible hex input to a canonical `#rrggbb` lowercase\n * string. Handles `#abc` → `#aabbcc` expansion, case folding, optional\n * leading `#`, and surrounding whitespace. Returns null when the input is\n * not a hex color.\n *\n * Applied by `buildPlan` to every user-provided color option\n * (`defaultBg`, `defaultFg`, `scrimColor`) exactly once so downstream\n * comparisons (`scrim === defaultBg`, etc.) work regardless of input\n * casing or shorthand.\n */\nexport function normalizeHex(hex: string | null | undefined): HexColor | null {\n if (hex === null || hex === undefined) return null\n const rgb = hexToRgb(hex)\n if (!rgb) return null\n return rgbToHex(rgb.r, rgb.g, rgb.b)\n}\n","/**\n * Backdrop fade — stage 1: build the immutable `Plan`.\n *\n * `buildPlan(root, options)` is a PURE, capability-independent pass that\n * walks the tree, collects `data-backdrop-fade` / `data-backdrop-fade-excluded`\n * markers, enforces the single-amount invariant, and resolves the scrim +\n * default colors. The realizers (`./realize-buffer.ts`, `./realize-kitty.ts`)\n * trust the plan: they do NOT re-walk the tree, re-resolve the scrim, or\n * re-validate amounts. This module is the single source of truth.\n *\n * ## The model: per-channel alpha scrim with perceptually-aware fg\n *\n * The pass fades every covered cell by blending BOTH fg AND bg toward a\n * neutral scrim color at the caller's `amount`. Default scrim: pure black\n * for dark themes (Apple `colorWithWhite:0.0 alpha:0.4`), pure white for\n * light. Default amount: `DEFAULT_AMOUNT` (0.25) — calibrated against\n * macOS 0.20, Material 3 0.32, iOS 0.40, Flutter 0.54.\n *\n * ### Two operations, one per channel\n *\n * fg' = deemphasizeOklchToward(fg, amount, towardLight)\n * // OKLCH: L toward 0 or 1,\n * // C *= (1-α)²\n * bg' = mixSrgb(bg, scrim, amount) // sRGB source-over alpha\n *\n * Fg uses OKLCH deemphasize with explicit polarity so colored text\n * deemphasizes toward the correct theme neutral — toward black on dark\n * themes (same formula we've always used), toward white on light themes\n * (new — see `./color-compat.ts` for the math). The quadratic chroma\n * falloff compensates for the human-vision nonlinearity that reads chroma\n * relative to luminance. Bg uses sRGB source-over because the Kitty\n * graphics scrim overlay composites in sRGB at alpha at the hardware\n * level.\n *\n * ### Uniform amount per channel, heaviness tuned at call site\n *\n * Both fg and bg use the same `amount`. An earlier revision halved bg\n * amount to prevent \"scene drowning\" — that caused border/panel brightness\n * inversion (fg-dominated border darkens faster than bg-dominated fill).\n * Heaviness is controlled by `amount`, not by asymmetric math.\n *\n * ## Scrim color\n *\n * - Dark themes: pure black (`#000000`) — Apple's modal-sheet dimming color.\n * - Light themes: pure white (`#ffffff`) — the sign-flipped equivalent.\n *\n * Null-bg cells are resolved to `defaultBg` first, then `mixSrgb` toward\n * the scrim — empty cells darken at the same rate as explicitly-colored ones.\n *\n * Tiers (`colorLevel`): a single code path for all supported tiers. For\n * `\"none\"` (monochrome) the pass short-circuits to a no-op. For `basic`,\n * `256`, and `truecolor`, the per-cell operation is identical — the output\n * phase quantizes the mixed truecolor hex to the tier's palette on emit.\n *\n * ## Purity\n *\n * This module is pure: no console I/O, no buffer access, no mutable module\n * state. `buildPlan` returns a `Plan` whose `mixedAmounts` flag signals\n * multi-amount frames; the orchestrator (`./index.ts`) emits the dev-mode\n * warning so stage 1 remains a pure function of its inputs.\n */\n\nimport { relativeLuminance } from \"@silvery/color\"\nimport type { AgNode, Rect } from \"@silvery/ag/types\"\nimport type { ColorLevel as AnsiColorLevel } from \"@silvery/ansi\"\nimport { type HexColor, normalizeHex } from \"./color\"\n\n/**\n * Terminal color tier for the backdrop pass. Extends `@silvery/ansi`'s\n * `ColorLevel` with `\"none\"`, which short-circuits the pass to a no-op on\n * monochrome terminals.\n */\nexport type ColorLevel = AnsiColorLevel | \"none\"\n\nexport interface BackdropOptions {\n /**\n * Terminal color tier. `\"none\"` short-circuits to a no-op (monochrome).\n * All other tiers run the same sRGB scrim mix — output phase quantizes\n * to the tier's palette on emit.\n */\n colorLevel?: ColorLevel\n /**\n * Explicit scrim color, or `\"auto\"` (default) to derive from theme\n * luminance: pure black for dark themes, pure white for light. Apps that\n * want a tinted scrim (e.g., a mid-gray for flat-color TUIs) override\n * here.\n */\n scrimColor?: HexColor | string | \"auto\"\n /**\n * Default background hex — resolves null/default `cell.bg` before mixing\n * toward the scrim AND feeds the auto-scrim luminance derivation when\n * `scrimColor` is `\"auto\"`.\n */\n defaultBg?: HexColor | string\n /**\n * Default foreground hex — resolves null/default `cell.fg` before the\n * deemphasize pass. Without this, text using the terminal's default fg\n * would stay at full brightness against a darkened backdrop (looks like\n * the text is POPPING instead of receding). If omitted, the pass picks\n * the opposite of the scrim (white for dark scrim, black for light).\n */\n defaultFg?: HexColor | string\n /**\n * When true, emit Kitty graphics protocol overlays on emoji cells inside\n * the faded region. The terminal renders a translucent scrim image above\n * the emoji glyph, which SGR 2 \"dim\" alone can't fade on bitmap emoji.\n *\n * CJK wide-char cells are NOT emoji — they respond to fg color like text,\n * so they go through the normal deemphasize path regardless of Kitty\n * availability. Only emoji cells (detected via `isLikelyEmoji`) skip the\n * buffer mix when Kitty is active.\n */\n kittyGraphics?: boolean\n}\n\n/** Marker prop key for include rects (fade cells INSIDE the node's rect). */\nexport const BACKDROP_FADE_ATTR = \"data-backdrop-fade\"\n/** Marker prop key for exclude rects (fade everything OUTSIDE the node's rect). */\nexport const BACKDROP_FADE_EXCLUDE_ATTR = \"data-backdrop-fade-excluded\"\n\n/**\n * Luminance threshold for dark/light theme detection.\n *\n * 0.18 is well below the WCAG midpoint. Standard dark terminal themes\n * (Catppuccin Mocha bg #1e1e2e, luminance ≈ 0.012; Tokyo Night bg #1a1b26,\n * ≈ 0.010) are well below. Light themes (GitHub Light #ffffff = 1.0) above.\n */\nexport const DARK_LUMINANCE_THRESHOLD = 0.18\n\n/** Canonical scrim colors — Apple's `colorWithWhite:0.0` / `:1.0`. */\nexport const DARK_SCRIM: HexColor = \"#000000\"\nexport const LIGHT_SCRIM: HexColor = \"#ffffff\"\n\n/**\n * Default fade amount — the calibrated baseline used when a marker\n * materializes as a presence attribute (`<Backdrop fade />`,\n * `data-backdrop-fade=\"\"`, `data-backdrop-fade={true}`) without an explicit\n * numeric value. Calibrated against macOS 0.20, Material 3 0.32, iOS 0.40,\n * Flutter 0.54. Re-exported from `index.ts` so downstream callers can\n * reference the same constant.\n */\nexport const DEFAULT_AMOUNT = 0.25\n\n/**\n * A single fade region. The per-frame `amount` lives on `Plan`, not on\n * the individual rect — the single-amount invariant (one scrim image per\n * frame at one alpha) makes per-rect amounts meaningless for realization.\n * `buildPlan` inspects the per-marker amount ONLY to detect the mixed-\n * amounts condition, then discards it.\n */\nexport interface PlanRect {\n readonly rect: Rect\n}\n\n/**\n * The immutable output of `buildPlan` — a capability-independent\n * description of what the backdrop pass intends to do this frame.\n *\n * The realizers (`realizeToBuffer`, `realizeToKitty`) trust the plan:\n * they do NOT re-walk the tree, re-resolve the scrim, or re-validate\n * amounts. `buildPlan` is the single source of truth.\n *\n * ### Invariants enforced by `buildPlan`\n *\n * - `active = includes.length > 0 || excludes.length > 0` whenever a\n * non-zero fade marker is present. The stage-1 pass short-circuits to an\n * inactive plan for `colorLevel: \"none\"`.\n * - `amount ∈ [0, 1]`, clamped, and identical across all collected rects\n * (single-amount invariant — mixed amounts break the Kitty overlay's\n * one-image-one-alpha model; `mixedAmounts=true` surfaces the dev warn,\n * prod falls back to first).\n * - `scrim` is either a normalized `#rrggbb` hex (for the truecolor/256\n * tiers with a known theme bg or an explicit `scrimColor`) or `null`\n * (legacy fallback where `fadeCell` mixes fg toward cell.bg without a\n * scrim).\n * - `defaultBg` / `defaultFg` are resolved for the stage-2 passes — the\n * realizers substitute these when `cell.bg` / `cell.fg` is null.\n * - `scrimTowardLight` records whether the scrim is light (white-ish) or\n * dark (black-ish). The fg deemphasize pass uses this to drift toward\n * the correct neutral — toward 0 on dark themes, toward 1 on light.\n * - `kittyEnabled` is the DERIVED capability flag — true only when the\n * caller enabled `kittyGraphics` AND the plan has a resolvable scrim\n * (Kitty overlay needs a tint). Realizers read this instead of\n * re-deriving at each call site.\n */\nexport interface Plan {\n /** True when the tree had at least one fade marker with amount > 0. */\n readonly active: boolean\n /**\n * The enforced single amount for this frame, clamped to [0, 1]. Zero\n * when `active` is false.\n */\n readonly amount: number\n /**\n * Resolved scrim hex, or null when no theme bg is available. The\n * buffer-realizer falls back to a legacy single-channel mix when null.\n */\n readonly scrim: HexColor | null\n /** Default background hex for resolving null/default `cell.bg`. */\n readonly defaultBg: HexColor | null\n /**\n * Default foreground hex for resolving null/default `cell.fg`. Derived\n * from `options.defaultFg`, else the opposite of the scrim (white for\n * dark scrim, black for light).\n */\n readonly defaultFg: HexColor | null\n /** Rects marked `data-backdrop-fade` — fade cells INSIDE each rect. */\n readonly includes: readonly PlanRect[]\n /**\n * Rects marked `data-backdrop-fade-excluded` — fade everything OUTSIDE\n * each rect (the modal \"cuts a hole\").\n */\n readonly excludes: readonly PlanRect[]\n /**\n * True when the collected markers had differing `amount` values. The\n * orchestrator reads this to emit a dev-mode warning; stage 1 stays pure\n * by surfacing the signal instead of calling `console.warn` inline.\n * Mixed amounts break the Kitty overlay's one-image-one-alpha model;\n * prod falls back to the first observed amount.\n */\n readonly mixedAmounts: boolean\n /**\n * True when the resolved `scrim` is on the LIGHT side of the luminance\n * threshold (scrim drifts toward white on light themes). False for dark\n * themes (scrim drifts toward black). Null-scrim plans default to\n * `false` (legacy dark-theme behavior).\n *\n * Determined by luminance comparison, NOT by string equality against\n * `DARK_SCRIM` / `LIGHT_SCRIM` — apps that supply a tinted scrim\n * (e.g., a mid-gray neutral for a flat-color TUI) still get the right\n * polarity.\n */\n readonly scrimTowardLight: boolean\n /**\n * True when the Kitty graphics overlay should run this frame. Derived\n * from `options.kittyGraphics === true && scrim !== null` — the overlay\n * needs a resolvable tint to composite, so a null-scrim plan can't use\n * the Kitty path even when the caller has the capability enabled.\n * Realizers read this directly (no re-derivation at call sites).\n */\n readonly kittyEnabled: boolean\n}\n\n/** Sentinel \"nothing to do\" plan — reused across frames to avoid allocations. */\nexport const INACTIVE_PLAN: Plan = Object.freeze({\n active: false,\n amount: 0,\n scrim: null,\n defaultBg: null,\n defaultFg: null,\n includes: Object.freeze([]) as readonly PlanRect[],\n excludes: Object.freeze([]) as readonly PlanRect[],\n mixedAmounts: false,\n scrimTowardLight: false,\n kittyEnabled: false,\n})\n\n/**\n * Quick check: does the tree contain any backdrop markers? Used as a gate so\n * we don't clone the buffer every frame when no fade is active. Walks the\n * full tree once (O(N)) — the alternative (tracking dirty markers in the\n * reconciler) is more complex and the walk is cheap compared to the pass.\n */\nexport function hasBackdropMarkers(root: AgNode): boolean {\n const props = root.props as Record<string, unknown>\n if (props[BACKDROP_FADE_ATTR] !== undefined || props[BACKDROP_FADE_EXCLUDE_ATTR] !== undefined) {\n return true\n }\n for (const child of root.children) {\n if (hasBackdropMarkers(child)) return true\n }\n return false\n}\n\n/**\n * Stage 1 — build the immutable `Plan`.\n *\n * Pure function of `(tree markers, options)`. No buffer access, no Kitty\n * capability knowledge, no console I/O. The realizers read from the plan\n * exclusively; the orchestrator (`./index.ts`) handles dev-mode diagnostics\n * derived from `plan.mixedAmounts`.\n *\n * Returns `INACTIVE_PLAN` when:\n * - `colorLevel === \"none\"` (monochrome terminal — pass is a no-op).\n * - The tree has no backdrop markers, OR all markers have `amount <= 0`.\n */\nexport function buildPlan(root: AgNode, options?: BackdropOptions): Plan {\n const colorLevel: ColorLevel = options?.colorLevel ?? \"truecolor\"\n if (colorLevel === \"none\") return INACTIVE_PLAN\n\n // Collect rects + per-marker amounts. The amounts are inspected only to\n // verify the single-amount invariant; they're discarded after.\n const includes: PlanRect[] = []\n const excludes: PlanRect[] = []\n const includeAmounts: number[] = []\n const excludeAmounts: number[] = []\n collectBackdropMarkers(root, includes, excludes, includeAmounts, excludeAmounts)\n\n if (includes.length === 0 && excludes.length === 0) return INACTIVE_PLAN\n\n // Resolve the three color inputs. Every user-provided hex is normalized\n // exactly once here — downstream comparisons (e.g., `scrim === defaultBg`)\n // and string-equality tests in the realizers work regardless of input\n // casing or shorthand.\n // - defaultBg: used to resolve null/default cell.bg before sRGB mix\n // AND feeds auto-scrim luminance derivation.\n // - scrimColor: the target of the mix. \"auto\" (default) derives from\n // luminance — black for dark themes, white for light.\n // - defaultFg: used to resolve null/default cell.fg before deemphasize.\n // Critical for default-fg text (common in TUIs that don't set colors\n // on every Text node) — without it, default-fg cells skip the fade\n // and the text pops against a dimmed bg.\n const defaultBg = normalizeHex(options?.defaultBg ?? null)\n const scrimColorOpt = options?.scrimColor\n // Explicit scrimColor wins when it parses to a valid hex. Unparseable\n // strings (e.g. \"#zzz\", \"rgb(...)\") quietly fall back to the luminance-\n // derived default — treating a typo as \"use auto\" is friendlier than\n // nullifying the scrim and dropping to the legacy single-channel path.\n const explicitScrim =\n typeof scrimColorOpt === \"string\" && scrimColorOpt !== \"auto\" ? normalizeHex(scrimColorOpt) : null\n const scrim = explicitScrim ?? deriveAutoScrimColor(defaultBg)\n\n // Polarity by luminance: scrim with luminance >= threshold is \"light\"\n // (drift fg toward white), below is \"dark\" (drift toward black). Uses\n // the same WCAG-derived `relativeLuminance` as the auto-scrim derivation\n // so any custom scrim color lands on the correct branch.\n const scrimTowardLight = isLightScrim(scrim)\n\n // Default fg fallback: opposite of the scrim polarity. For a tinted\n // scrim the user probably wants to override with an explicit `defaultFg`\n // anyway, but this gives a sensible default.\n const defaultFg =\n normalizeHex(options?.defaultFg) ?? (scrim === null ? null : scrimTowardLight ? DARK_SCRIM : LIGHT_SCRIM)\n\n // Single-amount invariant: one scrim image per frame at one alpha. Mixed\n // amounts are surfaced via `mixedAmounts` so the orchestrator can emit a\n // dev-mode warning; stage 1 stays pure.\n const { amount, hasMixedAmounts } = assertSingleAmount(includeAmounts, excludeAmounts)\n\n // Kitty overlay is available only when the caller enabled the capability\n // AND the plan resolved a scrim — the overlay needs a tint to composite.\n const kittyEnabled = options?.kittyGraphics === true && scrim !== null\n\n return {\n active: true,\n amount,\n scrim,\n defaultBg,\n defaultFg,\n includes,\n excludes,\n mixedAmounts: hasMixedAmounts,\n scrimTowardLight,\n kittyEnabled,\n }\n}\n\n/**\n * Detect the single-amount invariant across all markers. Returns the\n * clamped first-observed amount AND a flag indicating whether any later\n * marker differed. The orchestrator uses the flag to emit a dev-mode warn.\n *\n * Mixed amounts currently break the Kitty overlay (one image, one alpha)\n * and have unclear composition semantics (max? source-over compound?).\n * Production behavior is first-wins but will look wrong until the markers\n * are reconciled to a single value.\n */\nfunction assertSingleAmount(\n includeAmounts: readonly number[],\n excludeAmounts: readonly number[],\n): { amount: number; hasMixedAmounts: boolean } {\n const first =\n includeAmounts.length > 0\n ? includeAmounts[0]!\n : excludeAmounts.length > 0\n ? excludeAmounts[0]!\n : 0\n let hasMixedAmounts = false\n for (const a of includeAmounts) {\n if (Math.abs(a - first) > 1e-6) {\n hasMixedAmounts = true\n break\n }\n }\n if (!hasMixedAmounts) {\n for (const a of excludeAmounts) {\n if (Math.abs(a - first) > 1e-6) {\n hasMixedAmounts = true\n break\n }\n }\n }\n return { amount: Math.max(0, Math.min(1, first)), hasMixedAmounts }\n}\n\n/**\n * Derive the auto scrim color from a normalized bg hex. Dark themes scrim\n * toward `DARK_SCRIM`; light themes scrim toward `LIGHT_SCRIM`. Returns\n * `null` when `bg` is absent or unparseable — signals legacy single-\n * channel fallback in `fadeCell`.\n */\nfunction deriveAutoScrimColor(bg: HexColor | null): HexColor | null {\n if (!bg) return null\n const lum = relativeLuminance(bg)\n if (lum === null) return null\n return lum < DARK_LUMINANCE_THRESHOLD ? DARK_SCRIM : LIGHT_SCRIM\n}\n\n/**\n * Polarity detection for an arbitrary scrim color. Returns `true` when the\n * scrim is on the LIGHT side of the luminance threshold (fg should drift\n * toward white), `false` otherwise. Uses luminance, not string equality,\n * so tinted scrims (mid-gray neutrals, etc.) land on the correct branch.\n * Null scrim defaults to false (dark-theme fallback behavior).\n */\nfunction isLightScrim(scrim: HexColor | null): boolean {\n if (scrim === null) return false\n const lum = relativeLuminance(scrim)\n if (lum === null) return false\n return lum >= DARK_LUMINANCE_THRESHOLD\n}\n\nfunction collectBackdropMarkers(\n node: AgNode,\n includes: PlanRect[],\n excludes: PlanRect[],\n includeAmounts: number[],\n excludeAmounts: number[],\n): void {\n const props = node.props as Record<string, unknown>\n const includeRaw = props[BACKDROP_FADE_ATTR]\n const excludeRaw = props[BACKDROP_FADE_EXCLUDE_ATTR]\n\n if (includeRaw !== undefined || excludeRaw !== undefined) {\n const rect = node.screenRect ?? node.scrollRect ?? node.boxRect\n if (rect && rect.width > 0 && rect.height > 0) {\n const inc = parseFade(includeRaw)\n if (inc !== null) {\n includes.push({ rect })\n includeAmounts.push(inc)\n }\n const exc = parseFade(excludeRaw)\n if (exc !== null) {\n excludes.push({ rect })\n excludeAmounts.push(exc)\n }\n }\n }\n\n for (const child of node.children) {\n collectBackdropMarkers(child, includes, excludes, includeAmounts, excludeAmounts)\n }\n}\n\n/**\n * Coerce a marker attribute value into a fade amount in (0, 1], or `null`\n * when the marker is absent / disabled.\n *\n * Accepted inputs:\n *\n * - `undefined`, `null`, `false` → `null` (marker absent)\n * - `true` → `DEFAULT_AMOUNT` (presence attribute, e.g. `<Backdrop fade />`)\n * - `\"\"` → `DEFAULT_AMOUNT` (HTML-attribute presence idiom)\n * - finite numeric or numeric-string (including in scientific notation):\n * - `<= 0` → `null` (explicit opt-out)\n * - `> 1` → `1` (clamped)\n * - otherwise → the numeric value itself\n * - any other non-numeric string (e.g. `\"bad\"`) → `null`\n *\n * The presence-attribute idiom lets components emit `data-backdrop-fade`\n * without threading a numeric value through when the default is fine. The\n * React `Backdrop.tsx` / `ModalDialog.tsx` today always emit a numeric\n * attribute, but the semantic is forward-compatible so nothing breaks if\n * a future component (or a hand-written JSX usage) prefers presence-only.\n */\nfunction parseFade(raw: unknown): number | null {\n if (raw === undefined || raw === null) return null\n if (raw === false) return null\n if (raw === true) return DEFAULT_AMOUNT\n if (raw === \"\") return DEFAULT_AMOUNT\n const n = typeof raw === \"number\" ? raw : Number(raw)\n if (!Number.isFinite(n)) return null\n if (n <= 0) return null\n return n > 1 ? 1 : n\n}\n","/**\n * Backdrop fade — `@silvery/color` compatibility shim.\n *\n * Temporary shim while `@silvery/color` lags behind on publish cycles.\n * `@silvery/color` does export `mixSrgb` and `deemphasize` from source; this\n * module prefers the upstream versions at runtime and falls back to a\n * local implementation when an upstream export is missing — e.g., when a\n * new helper is introduced in the same release cycle as the silvery\n * package that imports it (the published `@silvery/color` dist doesn't\n * ship the new name until its next publish, breaking CI verify).\n *\n * The fallback implementations are byte-identical to the upstream ones.\n * Once all downstream consumers of silvery are on a published version of\n * `@silvery/color` that exports every name we reference, delete the\n * `local*` fallbacks and collapse each export to a direct re-export.\n *\n * Light-theme-aware deemphasize (`deemphasizeOklchToward`) is NOT in\n * upstream yet — it's only shipped by this module. When it lands in\n * `@silvery/color`, replace the local implementation with an upstream\n * re-export behind the same shim.\n *\n * @see ./color.ts for hex↔rgb adapter helpers and `HexColor` type.\n */\n\nimport * as Upstream from \"@silvery/color\"\nimport { hexToRgb, rgbToHex } from \"./color\"\n\n/**\n * sRGB source-over alpha mix. `out = a * (1 - t) + b * t`.\n *\n * Prefers `@silvery/color`'s published export; falls back to the local copy\n * when upstream doesn't ship the name yet. The local implementation matches\n * `@silvery/color/src/color.ts` byte-for-byte and is safe to use while the\n * publish train catches up.\n */\nfunction localMixSrgb(a: string, b: string, t: number): string {\n const ra = hexToRgb(a)\n const rb = hexToRgb(b)\n if (!ra || !rb) return a\n const u = Math.max(0, Math.min(1, t))\n const r = ra.r * (1 - u) + rb.r * u\n const g = ra.g * (1 - u) + rb.g * u\n const bl = ra.b * (1 - u) + rb.b * u\n return rgbToHex(r, g, bl)\n}\n\nconst upstreamMixSrgb = (Upstream as unknown as Record<string, unknown>).mixSrgb as\n | ((a: string, b: string, t: number) => string)\n | undefined\n\n/** sRGB source-over mix. Prefers upstream `@silvery/color`; falls back to the local copy. */\nexport const mixSrgb: (a: string, b: string, t: number) => string = upstreamMixSrgb ?? localMixSrgb\n\n/**\n * OKLCH-native deemphasize that drifts toward EITHER black (dark themes)\n * OR white (light themes). `towardLight` controls the lightness target;\n * the chroma falloff is identical in both directions.\n *\n * towardLight=false (dark themes):\n * L' = L × (1 - amount) // linear toward black\n * towardLight=true (light themes):\n * L' = L + (1 - L) × amount // linear toward white\n * (both branches):\n * C' = C × (1 - amount)² // quadratic chroma falloff\n * H' = H // hue preserved\n *\n * The asymmetric chroma falloff corrects for a perceptual nonlinearity:\n * the human visual system reads chroma RELATIVE to luminance, so a modest\n * OKLCH C at extreme L *appears* distinctly more chromatic than the same C\n * near mid-L. Proportional L+C scaling (`C *= 1-α`, preserving C/L) feels\n * \"more saturated when darkened\" to viewers — the exact complaint that\n * prompted the quadratic version.\n *\n * Using `(1-α)²` for chroma reduces saturation faster than lightness on\n * both polarities:\n *\n * α=0.25 → C *= 0.563 (C/L drops to 75% of original)\n * α=0.40 → C *= 0.360 (C/L drops to 60%)\n * α=0.50 → C *= 0.250 (C/L drops to 50%)\n * α=1.00 → C *= 0 (fully faded to the target luminance).\n *\n * Light-theme case (towardLight=true): a bright colored text on a light\n * backdrop is made paler by raising L toward 1 and dropping C — the\n * symmetric \"fade toward the page color\" behavior macOS ships in light\n * mode. Without the polarity flip, the dark-only formula `L *= (1 - α)`\n * would darken colored text on a light bg, which reads as \"text popping\"\n * against the faded scrim rather than receding.\n */\nfunction localDeemphasizeOklchToward(hex: string, amount: number, towardLight: boolean): string {\n const o = upstreamHexToOklch(hex)\n if (!o) return hex\n const a = Math.max(0, Math.min(1, amount))\n const chromaFactor = (1 - a) * (1 - a)\n const L = towardLight ? o.L + (1 - o.L) * a : o.L * (1 - a)\n return upstreamOklchToHex({\n L: Math.max(0, Math.min(1, L)),\n C: Math.max(0, o.C * chromaFactor),\n H: o.H,\n })\n}\n\n// Lightweight typed indirection into upstream — avoids `as any` at every\n// call site. Using the runtime-export form so bundlers don't require the\n// names at import time (supports the \"same-release-cycle publish\" case).\ntype Oklch = { L: number; C: number; H: number }\nconst upstreamHexToOklch = (Upstream as unknown as Record<string, unknown>).hexToOklch as\n | ((hex: string) => Oklch | null)\n // The upstream `hexToOklch` has always shipped — cast is a safety net,\n // not a feature-gate like the compat helpers above.\n | ((hex: string) => Oklch | null)\nconst upstreamOklchToHex = (Upstream as unknown as Record<string, unknown>).oklchToHex as (\n color: Oklch,\n) => string\n\n/**\n * OKLCH deemphasize with explicit polarity control. See\n * `localDeemphasizeOklchToward` for the math and rationale.\n *\n * Prefers the upstream `deemphasizeOklchToward` export when available;\n * falls back to the local implementation when upstream doesn't ship the\n * name yet (expected during the first release cycle that needs the\n * light-theme polarity).\n */\nconst upstreamDeemphasizeToward = (Upstream as unknown as Record<string, unknown>)\n .deemphasizeOklchToward as\n | ((hex: string, amount: number, towardLight: boolean) => string)\n | undefined\n\nexport const deemphasizeOklchToward: (hex: string, amount: number, towardLight: boolean) => string =\n upstreamDeemphasizeToward ?? localDeemphasizeOklchToward\n\n/**\n * Dark-theme deemphasize. Retained as a convenience alias so existing\n * callers don't have to thread `towardLight=false` everywhere. New code\n * should prefer `deemphasizeOklchToward` directly so the polarity is\n * explicit at the call site.\n */\nexport function deemphasizeOklch(hex: string, amount: number): string {\n return deemphasizeOklchToward(hex, amount, false)\n}\n","/**\n * Backdrop fade — shared region walker.\n *\n * Both stage 2 realizers (`realize-buffer.ts` and `realize-kitty.ts`) need\n * to iterate every cell covered by the plan's include + exclude rects with\n * single-visit semantics. Overlapping rects would otherwise double-fade\n * cells (buffer) or emit duplicate Kitty placements (overlay).\n *\n * `forEachFadeRegionCell` centralizes that walk with a `Uint8Array`\n * \"visited\" bitset. Pass an `includes` list to fade cells INSIDE each rect\n * (data-backdrop-fade), and an `excludes` list to fade everything OUTSIDE\n * each rect (data-backdrop-fade-excluded). Cells inside any include and\n * cells outside any exclude are both visited — but each cell is visited at\n * most once.\n *\n * The walker is pure / allocation-conscious: a single `Uint8Array` sized\n * to the buffer. No per-rect allocation; no closure captures beyond the\n * visitor callback.\n *\n * ### Determinism\n *\n * Visit order is stable and deterministic:\n *\n * 1. Includes are walked in their given order (parent before child,\n * matching `collectBackdropMarkers` walk order).\n * 2. Within each include rect, rows ascend; within each row, cols ascend.\n * 3. Excludes follow, with the same row-ascending/col-ascending scan\n * over the full buffer.\n *\n * The Kitty overlay's STRICT determinism invariant depends on this —\n * identical (plan, buffer) inputs must produce byte-identical overlay\n * strings across fresh and incremental paths.\n *\n * @see ./plan.ts — `Plan`, `PlanRect`\n * @see ./realize-buffer.ts — stage 2a (cell-level transform)\n * @see ./realize-kitty.ts — stage 2b (Kitty overlay emission)\n */\n\nimport type { PlanRect } from \"./plan\"\n\n/**\n * Walk every cell covered by the plan's include and exclude rects and\n * invoke `visit(x, y)` for each unique cell.\n *\n * `includes` cells are those INSIDE any include rect.\n * `excludes` cells are those OUTSIDE any exclude rect (i.e., excluded from\n * the exclude's interior — the modal \"cuts a hole\" pattern).\n *\n * Rects are clipped to the buffer bounds (`[0, bufferWidth)` ×\n * `[0, bufferHeight)`). Zero-size rects are skipped. Cells are deduped\n * across all rects via a `Uint8Array` bitset — a cell belonging to two\n * overlapping includes is visited once, not twice.\n *\n * Returns the count of unique cells visited. Useful for short-circuiting\n * the \"was any cell modified?\" signal in realizers.\n */\nexport function forEachFadeRegionCell(\n bufferWidth: number,\n bufferHeight: number,\n includes: readonly PlanRect[],\n excludes: readonly PlanRect[],\n visit: (x: number, y: number) => void,\n): number {\n if (bufferWidth <= 0 || bufferHeight <= 0) return 0\n if (includes.length === 0 && excludes.length === 0) return 0\n\n const seen = new Uint8Array(bufferWidth * bufferHeight)\n let count = 0\n\n const once = (x: number, y: number): void => {\n const i = y * bufferWidth + x\n if (seen[i] !== 0) return\n seen[i] = 1\n count += 1\n visit(x, y)\n }\n\n for (const { rect } of includes) {\n const x0 = Math.max(0, rect.x)\n const y0 = Math.max(0, rect.y)\n const x1 = Math.min(bufferWidth, rect.x + rect.width)\n const y1 = Math.min(bufferHeight, rect.y + rect.height)\n if (x0 >= x1 || y0 >= y1) continue\n for (let y = y0; y < y1; y++) {\n for (let x = x0; x < x1; x++) once(x, y)\n }\n }\n\n if (excludes.length > 0) {\n for (const { rect } of excludes) {\n const ix0 = Math.max(0, rect.x)\n const iy0 = Math.max(0, rect.y)\n const ix1 = Math.min(bufferWidth, rect.x + rect.width)\n const iy1 = Math.min(bufferHeight, rect.y + rect.height)\n const innerValid = ix0 < ix1 && iy0 < iy1\n for (let y = 0; y < bufferHeight; y++) {\n for (let x = 0; x < bufferWidth; x++) {\n if (innerValid && x >= ix0 && x < ix1 && y >= iy0 && y < iy1) continue\n once(x, y)\n }\n }\n }\n }\n\n return count\n}\n","/**\n * Backdrop fade — stage 2a: apply the plan's cell-level transform to the\n * terminal buffer.\n *\n * Uses the shared `forEachFadeRegionCell` walker (`./region.ts`) to visit\n * every cell covered by the plan's include + exclude rects exactly once.\n * Trusts the plan: no marker re-collection, no scrim/default resolution,\n * no amount validation, no capability re-derivation (`plan.kittyEnabled`\n * is the sole source of truth for the emoji branch).\n *\n * Wide ≠ emoji. CJK / Hangul / Japanese fullwidth text occupies two columns\n * but responds to `fg` color normally — it goes through the standard mix\n * path. Only EMOJI (bitmap glyphs that ignore `fg`) need special handling,\n * detected via `isLikelyEmoji(cell.char)`.\n *\n * For emoji cells, two paths, mutually exclusive:\n *\n * 1. **Kitty graphics available** (`plan.kittyEnabled === true`): emoji cells\n * are SKIPPED entirely here. `./realize-kitty.ts` emits a translucent\n * scrim image at alpha=amount above each emoji cell, and the terminal\n * composites the overlay on top of the unmixed cell, landing at\n * `cell_bg * (1 - amount) + scrim * amount` — the same luminance as\n * surrounding text cells. This avoids the double-fade that would make\n * emoji bg visibly blacker.\n *\n * 2. **Kitty graphics unavailable** (`plan.kittyEnabled === false`): the\n * per-cell mix runs on emoji cells too and stamps `attrs.dim` (SGR 2)\n * on lead + continuation. Terminals honoring SGR 2 on emoji fade the\n * glyph; others see the glyph at full brightness but the cell bg\n * matches surroundings.\n *\n * @see ./plan.ts for the color model and scrim derivation.\n * @see ./color.ts for the hex↔rgb adapter helpers.\n * @see ./color-compat.ts for `mixSrgb` + `deemphasizeOklchToward`.\n * @see ./region.ts for the shared include/exclude region walker.\n */\n\nimport type { TerminalBuffer } from \"../../buffer\"\nimport { isLikelyEmoji } from \"../../unicode\"\nimport { colorToHex, type HexColor, hexToRgb } from \"./color\"\nimport { deemphasizeOklchToward, mixSrgb } from \"./color-compat\"\nimport { DARK_SCRIM, LIGHT_SCRIM, type Plan } from \"./plan\"\nimport { forEachFadeRegionCell } from \"./region\"\n\n/**\n * Stage 2a — apply the plan's cell-level transform to the buffer.\n *\n * Walks every include + exclude cell once via `forEachFadeRegionCell` and\n * applies `fadeCell` with the plan's single `amount`. The buffer is mutated\n * in place.\n *\n * When `plan.kittyEnabled === true`, emoji cells (detected via\n * `isLikelyEmoji(cell.char)`) are SKIPPED — the Kitty overlay realizer\n * composites the scrim on top of the unmixed cell. When\n * `plan.kittyEnabled === false`, emoji cells go through the per-cell mix\n * AND get SGR 2 (`attrs.dim`) stamped on lead + continuation.\n *\n * Returns `true` when at least one buffer cell was mutated.\n */\nexport function realizeToBuffer(plan: Plan, buffer: TerminalBuffer): boolean {\n if (!plan.active) return false\n if (plan.amount <= 0) return false\n\n let modified = false\n forEachFadeRegionCell(buffer.width, buffer.height, plan.includes, plan.excludes, (x, y) => {\n if (fadeCell(buffer, x, y, plan)) modified = true\n })\n return modified\n}\n\n/**\n * Fade a single cell. Returns true if the cell was modified.\n *\n * Two-channel transform (see `./plan.ts` for the full color model):\n *\n * fg' = deemphasizeOklchToward(fg, amount, scrimTowardLight)\n * bg' = mixSrgb(bg, scrim, amount)\n *\n * Fg uses OKLCH deemphasize (not sRGB mixing) so colored text deemphasizes\n * perceptually — pale lavender becomes dull slate on dark themes, pale\n * grey on light themes. The polarity flag `scrimTowardLight` (from the\n * plan) steers L toward 0 or 1; chroma falloff is symmetric. Bg uses sRGB\n * source-over because the Kitty graphics scrim overlay composites in sRGB\n * at alpha at the hardware level.\n *\n * `null`/`DEFAULT_BG` cells are resolved to `plan.defaultBg` first (that\n * IS the color the terminal paints), then mixed toward the scrim — so\n * empty cells darken at the same rate as explicitly-colored cells.\n *\n * Uniform amounts for fg + bg preserve relative brightness ordering across\n * borders vs fills. Heaviness is controlled by `plan.amount` (default\n * 0.25, calibrated against macOS 0.20, Material 3 0.32, iOS 0.40, Flutter\n * 0.54).\n *\n * The `scrim !== null` gate activates the full two-channel path: fg always\n * deemphasizes, and bg mixes toward the scrim when a resolvable bg hex is\n * available (`cell.bg` non-null OR `defaultBg` non-null). When both\n * `scrim` and a resolvable bg are null (no theme context at all): falls\n * back to mixing fg toward `cell.bg` so the cell still reads as \"receded\"\n * without needing external theme info.\n *\n * ### Wide-char / emoji handling\n *\n * Terminals render emoji using the glyph's own bitmap colors — the fg mix\n * has no visible effect on the emoji glyph. Two paths, mutually exclusive:\n *\n * 1. Kitty graphics available: `fadeCell` SKIPS emoji wide cells entirely.\n * The Kitty overlay composites the scrim at alpha=amount on top, landing\n * at `cell * (1 - amount) + scrim * amount` — same as surrounding cells.\n * 2. Kitty unavailable: mix the cell bg + stamp `attrs.dim` on lead +\n * continuation. Terminals honoring SGR 2 on emoji fade the glyph. Wide\n * TEXT (CJK etc.) goes through the normal deemphasize path on both\n * branches — the fg mix works fine and SGR 2 on CJK over-fades.\n */\nfunction fadeCell(buffer: TerminalBuffer, x: number, y: number, plan: Plan): boolean {\n // Skip continuation half of wide chars — the leading cell at x-1 updates\n // this cell in lockstep when it's processed.\n if (buffer.isCellContinuation(x, y)) return false\n\n const cell = buffer.getCell(x, y)\n\n // Glyph classification: only EMOJI cells (bitmap glyphs that ignore fg\n // color) go through the Kitty overlay path. CJK and other wide TEXT cells\n // respond to fg color like narrow text and go through the buffer mix\n // path, which is correct for them. `cell.wide` alone is the wrong\n // discriminator — wide != emoji — pro review flagged this as a bug class.\n const isEmojiGlyph = cell.wide && isLikelyEmoji(cell.char ?? \"\")\n\n // When Kitty is available and this cell is an emoji, skip the buffer mix\n // — the Kitty overlay will composite the scrim at alpha=amount above the\n // unmixed cell, landing at `cell_bg * (1 - amount) + scrim * amount`,\n // same luminance as surrounding mixed cells. Mixing here too would\n // double-fade and produce a visibly blacker emoji bg.\n if (plan.kittyEnabled && isEmojiGlyph) return false\n\n const { amount, scrim, defaultBg, defaultFg, scrimTowardLight } = plan\n const rawFgHex = colorToHex(cell.fg)\n\n if (scrim !== null) {\n // Two-channel path — scrim is available. An explicit scrim is useful\n // even without a `defaultBg`: fg always deemphasizes toward neutrality,\n // and cells with explicit (non-null) `cell.bg` still mix toward the\n // scrim. Only cells whose bg is unresolvable (null) AND have no\n // `defaultBg` to fall back on skip the bg mix.\n //\n // Resolve null/default fg BEFORE deemphasize. Without this, default-fg\n // text (common in TUIs that don't set Text color explicitly) skips the\n // fade entirely — bg darkens but fg stays at full terminal brightness,\n // producing a visible \"text POPS against faded bg\" effect that users\n // perceive as \"colors look more saturated when darkened\".\n const fgHex: HexColor =\n rawFgHex ?? defaultFg ?? (scrimTowardLight ? DARK_SCRIM : LIGHT_SCRIM)\n\n // sRGB source-over mix: uniform bg toward scrim at `amount`. sRGB\n // matches the Kitty graphics overlay compositing so text-cell bg and\n // emoji-cell bg land at the same luminance in shared faded regions.\n // `colorToHex(cell.bg) ?? defaultBg` — when cell.bg is null/default\n // and no defaultBg is available, bgHex stays null and we skip the bg\n // mix while still deemphasizing fg.\n const bgHex: HexColor | null = colorToHex(cell.bg) ?? defaultBg\n const mixedBgHex = bgHex !== null ? mixSrgb(bgHex, scrim, amount) : null\n const mixedBg = mixedBgHex !== null ? hexToRgb(mixedBgHex) : null\n\n // Stamp SGR 2 dim on emoji cells when Kitty is NOT available — it's the\n // only portable way to signal \"faded\" on a glyph the fg mix can't\n // affect. For wide TEXT (CJK etc.), do NOT stamp dim: the fg mix works\n // fine, and SGR 2 on CJK over-fades the glyph.\n const stampEmojiDim = isEmojiGlyph\n const newAttrs = stampEmojiDim && !cell.attrs.dim ? { ...cell.attrs, dim: true } : cell.attrs\n\n // Fg uses OKLCH deemphasize — L toward 0 (dark) or 1 (light) per\n // `scrimTowardLight`, C *= (1-α)², H preserved. See\n // `deemphasizeOklchToward` docblock for the perceptual rationale. Bg\n // stays sRGB to match Kitty overlay compositing.\n const deemphasizedFgHex = deemphasizeOklchToward(fgHex, amount, scrimTowardLight)\n const mixedFg = hexToRgb(deemphasizedFgHex)\n\n if (mixedFg) {\n if (mixedBg) {\n buffer.setCell(x, y, { ...cell, fg: mixedFg, bg: mixedBg, attrs: newAttrs })\n propagateToContinuation(buffer, cell, x, y, { bg: mixedBg, dim: stampEmojiDim })\n return true\n }\n buffer.setCell(x, y, { ...cell, fg: mixedFg, attrs: newAttrs })\n if (stampEmojiDim) propagateToContinuation(buffer, cell, x, y, { dim: true })\n return true\n }\n\n // Fg deemphasize failed (rare — hex parse edge). Fall back to bg-only\n // mix + dim stamp.\n if (mixedBg) {\n buffer.setCell(x, y, { ...cell, bg: mixedBg, attrs: newAttrs })\n propagateToContinuation(buffer, cell, x, y, { bg: mixedBg, dim: stampEmojiDim })\n return true\n }\n if (cell.attrs.dim) return false\n buffer.setCell(x, y, { ...cell, attrs: { ...cell.attrs, dim: true } })\n return true\n }\n\n const fgHex = rawFgHex\n\n // Legacy path (no scrim): mix fg toward cell.bg.\n const bgHex = colorToHex(cell.bg)\n\n if (fgHex && bgHex) {\n const mixedHex = mixSrgb(fgHex, bgHex, amount)\n const mixedRgb = hexToRgb(mixedHex)\n if (!mixedRgb) return false\n buffer.setCell(x, y, { ...cell, fg: mixedRgb })\n return true\n }\n\n // Fallback — bg unresolvable (DEFAULT_BG / null) or fg null. Stamp dim so\n // the cell still reads as \"backdrop\".\n if (cell.attrs.dim) return false\n buffer.setCell(x, y, { ...cell, attrs: { ...cell.attrs, dim: true } })\n if (cell.wide && x + 1 < buffer.width) {\n const cont = buffer.getCell(x + 1, y)\n if (!cont.attrs.dim) {\n buffer.setCell(x + 1, y, { ...cont, attrs: { ...cont.attrs, dim: true } })\n }\n }\n return true\n}\n\n/**\n * Propagate lead-cell updates to the continuation cell of a wide char.\n *\n * When a wide char (emoji, CJK) has its bg or dim attribute changed on the\n * lead cell, the continuation cell at `x+1` must track in lockstep or the\n * two halves of the glyph render inconsistently (different bg → visually\n * split glyph; missing dim → half-faded emoji).\n *\n * `patch.bg` copies the mixed bg onto the continuation. `patch.dim` stamps\n * `attrs.dim`. Either or both may be provided; the function is a no-op\n * when neither is set.\n */\nfunction propagateToContinuation(\n buffer: TerminalBuffer,\n leadCell: { wide: boolean },\n x: number,\n y: number,\n patch: { bg?: { r: number; g: number; b: number }; dim?: boolean },\n): void {\n if (!leadCell.wide) return\n if (x + 1 >= buffer.width) return\n const cont = buffer.getCell(x + 1, y)\n if (!cont.continuation) return\n\n const stampDim = patch.dim === true && !cont.attrs.dim\n const writeBg = patch.bg !== undefined\n\n // Nothing to do: skip the setCell allocation.\n if (!stampDim && !writeBg) return\n\n const attrs = stampDim ? { ...cont.attrs, dim: true } : cont.attrs\n if (writeBg) {\n buffer.setCell(x + 1, y, { ...cont, bg: patch.bg, attrs })\n } else {\n buffer.setCell(x + 1, y, { ...cont, attrs })\n }\n}\n","/**\n * Backdrop fade — stage 2b: emit the Kitty graphics overlay for the plan.\n *\n * The output always begins with `CURSOR_SAVE + kittyDeleteAllScrimPlacements()\n * + CURSOR_RESTORE` when `plan.active` is true — even when zero emoji cells\n * fall inside the faded region this frame. The unconditional clear is what\n * erases stale placements from a previous frame (e.g., a modal that covered\n * an emoji in frame N, then moved in frame N+1). Without it, orphan scrim\n * rectangles persist on screen.\n *\n * ### STRICT determinism invariant\n *\n * For a given `(plan, buffer)` pair, this function produces a byte-identical\n * string on every invocation. STRICT mode compares the overlay across\n * fresh and incremental paths to catch latent non-determinism in marker\n * collection order, emoji walk ordering, or placement ID derivation. The\n * shared `forEachFadeRegionCell` walker provides the stable row-ascending,\n * col-ascending iteration order.\n *\n * @see ./plan.ts for the Plan shape and color model.\n * @see ./realize-buffer.ts for the complementary cell-level transform.\n * @see ./region.ts for the shared include/exclude walker.\n */\n\nimport {\n backdropPlacementId,\n buildScrimPixels,\n cupTo,\n CURSOR_RESTORE,\n CURSOR_SAVE,\n kittyDeleteAllScrimPlacements,\n kittyPlaceAt,\n kittyUploadScrimImage,\n} from \"@silvery/ansi\"\nimport type { TerminalBuffer } from \"../../buffer\"\nimport { isLikelyEmoji } from \"../../unicode\"\nimport { hexToRgb } from \"./color\"\nimport type { Plan } from \"./plan\"\nimport { forEachFadeRegionCell } from \"./region\"\n\n/**\n * Stage 2b — emit the Kitty graphics overlay for the plan.\n *\n * The output always begins with `CURSOR_SAVE + kittyDeleteAllScrimPlacements()\n * + CURSOR_RESTORE` when `plan.active` is true — even when zero emoji cells\n * fall inside the faded region this frame. The unconditional clear is what\n * erases stale placements from a previous frame.\n *\n * Returns `\"\"` when `plan.active` is false. Callers that also need to\n * suppress the overlay because Kitty graphics are not available should NOT\n * call this function at all — the orchestrator (`./index.ts`) guards the\n * call site with `plan.kittyEnabled`.\n */\nexport function realizeToKitty(plan: Plan, buffer: TerminalBuffer): string {\n if (!plan.active) return \"\"\n\n const cells = collectEmojiCellsInFadeRegion(buffer, plan)\n\n // Tint the scrim with the same color used for cell mixing (pure black /\n // white by theme luminance, or an app-supplied custom scrim). Fallback\n // to pure black.\n const tintHex = plan.scrim ?? plan.defaultBg ?? \"#000000\"\n const tint = hexToRgb(tintHex) ?? { r: 0, g: 0, b: 0 }\n const scrimAlpha = Math.max(0, Math.min(255, Math.round(plan.amount * 255)))\n\n const parts: string[] = []\n parts.push(CURSOR_SAVE)\n\n if (cells.length === 0) {\n // No wide cells to cover this frame, but we must still clear any\n // placements left over from a prior frame where there were some.\n parts.push(kittyDeleteAllScrimPlacements())\n parts.push(CURSOR_RESTORE)\n return parts.join(\"\")\n }\n\n const pixels = buildScrimPixels(tint, scrimAlpha)\n parts.push(kittyUploadScrimImage(pixels, 2, 2))\n parts.push(kittyDeleteAllScrimPlacements())\n\n for (const { x, y } of cells) {\n parts.push(cupTo(x, y))\n parts.push(\n kittyPlaceAt({\n placementId: backdropPlacementId(x, y),\n cols: 2,\n rows: 1,\n z: 1,\n }),\n )\n }\n parts.push(CURSOR_RESTORE)\n return parts.join(\"\")\n}\n\n/**\n * Collect the coordinates of every EMOJI lead cell inside the plan's faded\n * region. CJK and other wide TEXT cells are excluded — they respond to fg\n * color mixing like normal text and don't need the Kitty overlay. Only\n * bitmap-glyph cells (detected via `isLikelyEmoji(cell.char)`) need an\n * overlay because their rendering ignores the fg color.\n *\n * The iteration order is deterministic (delegated to\n * `forEachFadeRegionCell`), matching the buffer realizer's order. STRICT\n * mode compares the overlay string across fresh and incremental paths —\n * any drift in this order would fail the comparison.\n */\nfunction collectEmojiCellsInFadeRegion(\n buffer: TerminalBuffer,\n plan: Plan,\n): Array<{ x: number; y: number }> {\n const out: Array<{ x: number; y: number }> = []\n forEachFadeRegionCell(buffer.width, buffer.height, plan.includes, plan.excludes, (x, y) => {\n if (x + 1 >= buffer.width) return // no room for continuation\n if (!buffer.isCellWide(x, y)) return\n if (buffer.isCellContinuation(x, y)) return\n const cell = buffer.getCell(x, y)\n if (!isLikelyEmoji(cell.char ?? \"\")) return\n out.push({ x, y })\n })\n return out\n}\n","/**\n * Backdrop fade pass — mask → realize two-stage model.\n *\n * Runs AFTER the content + decoration phases, BEFORE the output phase. The\n * pipeline orchestrator (`ag.ts`) invokes `applyBackdrop(root, buffer,\n * options)`, which performs two independent stages:\n *\n * 1. `buildPlan(root, options)` — PURE, capability-independent tree\n * walk. Collects `data-backdrop-fade` / `data-backdrop-fade-excluded`\n * markers, enforces the single-amount invariant, resolves the scrim +\n * default colors, and derives `plan.kittyEnabled` from\n * `options.kittyGraphics` + scrim availability. See `./plan.ts`.\n * 2a. `realizeToBuffer(plan, buffer)` — cell-level transform over the\n * plan's include/exclude rects. Mutates the buffer in place. Reads\n * `plan.kittyEnabled` to decide the emoji branch. See\n * `./realize-buffer.ts`.\n * 2b. `realizeToKitty(plan, buffer)` — emits the Kitty graphics escape\n * sequence for emoji cells in the faded region. See\n * `./realize-kitty.ts`.\n *\n * The split exists so each stage is independently testable and so\n * STRICT-mode diagnostics can compare plans + overlays across\n * fresh/incremental paths without re-walking the buffer.\n *\n * ## Incremental correctness\n *\n * The pass mutates the final buffer in place after the decoration phase. The\n * PRE-transform buffer is snapshotted and stored as `_prevBuffer` (see\n * `ag.ts`), so the next frame's incremental render clones pre-fade pixels\n * and re-fades them freshly. Because `buildPlan` is pure and the realizers\n * trust the plan, fresh and incremental paths produce identical\n * post-transform buffers — `SILVERY_STRICT=1` stays green.\n *\n * **STRICT overlay invariant**: `realizeToKitty` is a pure function of\n * `(plan, buffer)`. When the same tree is rendered via the fresh path and\n * the incremental path within a single frame, both produce byte-identical\n * Kitty overlay strings. STRICT mode compares these overlays alongside the\n * buffer (see `scheduler.ts`) — any drift signals a latent determinism bug\n * in marker collection or the emoji walk.\n *\n * ## Emoji vs wide-text cells\n *\n * Wide ≠ emoji. CJK / Hangul / Japanese fullwidth text occupies two columns\n * but responds to `fg` color normally — it goes through the standard mix\n * path. Only EMOJI (bitmap glyphs that ignore `fg`) need special handling,\n * detected via `isLikelyEmoji(cell.char)`. See `./realize-buffer.ts` for\n * the full text-vs-emoji decision table.\n *\n * ## Module layout\n *\n * ./plan.ts — stage 1: buildPlan, Plan / PlanRect shapes,\n * marker collection, single-amount invariant\n * ./realize-buffer.ts — stage 2a: cell-level buffer transform\n * ./realize-kitty.ts — stage 2b: Kitty overlay emission\n * ./region.ts — shared include/exclude region walker (Uint8Array\n * dedup, deterministic iteration order)\n * ./color.ts — hex↔rgb adapter, normalizeHex, HexColor brand type\n * ./color-compat.ts — upstream-with-fallback shim for mixSrgb /\n * deemphasizeOklch[Toward]; prefers @silvery/color\n * exports, falls back to local copies during\n * publish-cycle lag\n * ./index.ts — this file: applyBackdrop orchestrator + barrel\n */\n\nimport {\n CURSOR_RESTORE,\n CURSOR_SAVE,\n kittyDeleteAllScrimPlacements,\n} from \"@silvery/ansi\"\nimport type { AgNode } from \"@silvery/ag/types\"\nimport type { TerminalBuffer } from \"../../buffer\"\nimport { buildPlan, type BackdropOptions } from \"./plan\"\nimport { realizeToBuffer } from \"./realize-buffer\"\nimport { realizeToKitty } from \"./realize-kitty\"\n\n// Public re-exports — callers import from `./pipeline/backdrop` (the barrel\n// below) or from `./pipeline` (which re-re-exports).\nexport {\n buildPlan,\n DEFAULT_AMOUNT,\n hasBackdropMarkers,\n INACTIVE_PLAN,\n type BackdropOptions,\n type ColorLevel,\n type Plan,\n type PlanRect,\n} from \"./plan\"\nexport { type HexColor, normalizeHex } from \"./color\"\nexport { deemphasizeOklch, deemphasizeOklchToward, mixSrgb } from \"./color-compat\"\nexport { forEachFadeRegionCell } from \"./region\"\nexport { realizeToBuffer } from \"./realize-buffer\"\nexport { realizeToKitty } from \"./realize-kitty\"\n\n/**\n * Result of `applyBackdrop`.\n *\n * - `modified` — true when at least one buffer cell was mutated by the\n * pass. STRICT mode compares buffers — this is the narrow \"did we\n * mutate the buffer\" signal.\n * - `overlay` — out-of-band ANSI escapes appended after the normal output\n * phase diff. Non-empty whenever Kitty graphics are enabled AND a\n * backdrop is active (includes a delete-all-placements command so\n * last-frame scrims get cleared even if this frame has no wide cells),\n * or when Kitty is enabled and the backdrop is INACTIVE this frame (the\n * orchestrator emits `CURSOR_SAVE + kittyDeleteAllScrimPlacements() +\n * CURSOR_RESTORE` so stale placements from a prior active frame are\n * cleaned up even when stage 2a and 2b short-circuit).\n *\n * Callers gating on \"did anything change visually\" compute\n * `modified || overlay.length > 0` at the call site — no pre-computed\n * derived field lives on the result.\n */\nexport interface BackdropResult {\n /** True when at least one buffer cell was mutated by the pass. */\n readonly modified: boolean\n /**\n * Out-of-band ANSI escapes appended after the normal output phase diff.\n * See the interface docblock for when this is non-empty.\n */\n readonly overlay: string\n}\n\nconst EMPTY_RESULT: BackdropResult = Object.freeze({\n modified: false,\n overlay: \"\",\n})\n\n/**\n * Cached \"Kitty inactive-frame cleanup\" overlay. Emitted when Kitty\n * graphics are enabled but the backdrop is inactive this frame so any\n * stale scrim placements from the previous frame are cleared. Kept as a\n * module-level constant so repeated inactive frames don't rebuild the\n * same string.\n */\nconst KITTY_CLEANUP_OVERLAY: string =\n CURSOR_SAVE + kittyDeleteAllScrimPlacements() + CURSOR_RESTORE\n\n/**\n * Apply backdrop-fade to the buffer based on tree markers.\n *\n * Thin orchestrator over the mask → realize stages:\n *\n * plan = buildPlan(root, options)\n * modified = realizeToBuffer(plan, buffer)\n * overlay = plan.kittyEnabled ? realizeToKitty(plan, buffer) : \"\"\n *\n * Returns a `BackdropResult`:\n * - `modified` — any buffer cells changed.\n * - `overlay` — out-of-band ANSI escapes. Non-empty when Kitty graphics\n * are enabled (regardless of whether the plan is active):\n * - Plan active: contains at minimum a scrim-clear command so last-frame\n * placements get erased even if this frame has no wide cells.\n * - Plan inactive: contains a cursor-save/delete-all/cursor-restore\n * sequence so stale placements from a prior active frame are cleaned\n * up. Without this, leftover scrim rectangles persist on screen when\n * the backdrop deactivates.\n *\n * Because the overlay self-cleans on deactivation, the higher-level\n * `_kittyActive` tracker in `ag.ts` is redundant for this module. It is\n * intentionally left in place (out of scope for this pass); future cleanup\n * can delete it once the renderer-level tracker is also removed.\n */\nexport function applyBackdrop(\n root: AgNode,\n buffer: TerminalBuffer,\n options?: BackdropOptions,\n): BackdropResult {\n const plan = buildPlan(root, options)\n\n // Stage 1 diagnostics: surface the mixed-amounts warning at the orchestrator\n // so `buildPlan` can remain a pure function of its inputs. The warning\n // fires in dev/test only — production suppresses via NODE_ENV.\n if (plan.mixedAmounts && process.env.NODE_ENV !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(\n `[silvery:backdrop] multiple fade amounts in one frame (using ${plan.amount}); ` +\n `Kitty overlay will use the first observed amount. See plan.ts / assertSingleAmount.`,\n )\n }\n\n if (!plan.active) {\n // Kitty cleanup path: even when the plan is inactive this frame, if the\n // caller has Kitty graphics enabled we must emit the delete-all so any\n // scrim placements from a prior active frame are cleared. Without this,\n // the orphan rectangles persist on screen after the backdrop turns off.\n if (options?.kittyGraphics === true) {\n return {\n modified: false,\n overlay: KITTY_CLEANUP_OVERLAY,\n }\n }\n return EMPTY_RESULT\n }\n\n const modified = realizeToBuffer(plan, buffer)\n\n // Kitty overlay. Always emitted when plan.kittyEnabled (even if no emoji\n // this frame) so last-frame placements get cleared by the delete-all at\n // the head of the overlay string.\n const overlay = plan.kittyEnabled ? realizeToKitty(plan, buffer) : \"\"\n\n return { modified, overlay }\n}\n","/**\n * Ag — tree + layout engine + renderer.\n *\n * The sole pipeline entry point. Two independent phases:\n * - ag.layout(dims) — measure + flexbox → positions/sizes\n * - ag.render() — positioned tree → cell grid → TextFrame\n *\n * The output phase (buffer → ANSI) is NOT part of ag — it lives in term.paint().\n *\n * @example\n * ```ts\n * const ag = createAg(root, { measurer })\n * ag.layout({ cols: 80, rows: 24 })\n * const { frame, buffer } = ag.render()\n * const output = term.paint(buffer, prevBuffer)\n * ```\n */\n\nimport { createLogger } from \"loggily\"\nimport type { AgNode, AgNodeType } from \"@silvery/ag/types\"\nimport {\n getRenderEpoch,\n INITIAL_EPOCH,\n ALL_RECONCILER_BITS,\n CONTENT_BIT,\n STYLE_PROPS_BIT,\n} from \"@silvery/ag/epoch\"\nimport { getLayoutEngine } from \"./layout-engine\"\nimport type { TextFrame } from \"@silvery/ag/text-frame\"\nimport { type TerminalBuffer, createTextFrame } from \"./buffer\"\nimport { runWithMeasurer, type Measurer } from \"./unicode\"\nimport { measurePhase } from \"./pipeline/measure-phase\"\nimport {\n layoutPhase,\n scrollPhase,\n stickyPhase,\n scrollrectPhase,\n scrollrectPhaseSimple,\n notifyLayoutSubscribers,\n detectPipelineFeatures,\n strictLayoutOverflowCheck,\n} from \"./pipeline/layout-phase\"\nimport { renderPhase, clearBgConflictWarnings } from \"./pipeline/render-phase\"\nimport {\n applyBackdrop,\n hasBackdropMarkers,\n type ColorLevel as BackdropColorLevel,\n} from \"./pipeline/backdrop\"\nimport { CURSOR_RESTORE, CURSOR_SAVE, kittyDeleteAllScrimPlacements } from \"@silvery/ansi\"\nimport { clearDirtyTracking, hasScrollDirty } from \"@silvery/ag/dirty-tracking\"\nimport type { PipelineContext } from \"./pipeline/types\"\n\nconst log = createLogger(\"silvery:render\")\nconst baseLog = createLogger(\"@silvery/ag-react\")\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface AgLayoutOptions {\n skipLayoutNotifications?: boolean\n skipScrollStateUpdates?: boolean\n}\n\nexport interface AgRenderOptions {\n /** Force fresh render — no incremental, doesn't update internal prevBuffer. */\n fresh?: boolean\n /** Override prevBuffer for this render (bypasses internal tracking). */\n prevBuffer?: TerminalBuffer | null\n}\n\nexport interface CreateAgOptionsInternal {\n /** Width measurer scoped to terminal capabilities. */\n measurer?: Measurer\n /**\n * Terminal color tier for the backdrop-fade pass (see `pipeline/backdrop/`).\n * Defaults to `\"truecolor\"` (OKLab blend). Set to `\"basic\"` at ANSI 16 tier\n * (SGR 2 dim) or `\"none\"` to disable the pass entirely.\n */\n colorLevel?: BackdropColorLevel\n /**\n * When true, the backdrop-fade pass emits Kitty graphics placements over\n * emoji / wide-char cells in the faded region so those glyphs visually\n * fade alongside surrounding text. Required because SGR 2 \"dim\" is a\n * no-op on bitmap emoji in most terminals (Ghostty confirmed).\n *\n * When undefined, `ag.ts` falls back to an env heuristic (Kitty/Ghostty/\n * WezTerm, not inside tmux, `SILVERY_KITTY_GRAPHICS` env not \"0\"). Pass\n * `false` to force-disable (tests, fallback terminals).\n */\n kittyGraphics?: boolean\n}\n\nexport interface AgRenderResult {\n /** Immutable TextFrame snapshot of the rendered output. */\n readonly frame: TextFrame\n /**\n * Post-transform buffer for painting. Includes backdrop-fade cell transforms\n * (if any). Pass this to `term.paint()` / `outputPhase()` as `next`.\n */\n readonly buffer: TerminalBuffer\n /**\n * Pre-transform buffer. Identical to `buffer` when no backdrop-fade markers\n * are present. Callers managing their own incremental prev-buffer state must\n * carry THIS (not `buffer`) forward, so the next frame's render phase starts\n * from pre-fade cells and the fade pass re-applies deterministically.\n */\n readonly carryForwardBuffer: TerminalBuffer\n /** Previous frame's buffer (null on first render). For output-phase diffing. */\n readonly prevBuffer: TerminalBuffer | null\n /**\n * Out-of-band ANSI escapes that must be appended to the output stream after\n * the normal output phase diff. Currently carries Kitty graphics placements\n * emitted by the backdrop-fade pass to scrim emoji / wide-char cells. Empty\n * string when no overlays are active (backdrop inactive, kittyGraphics cap\n * disabled, or no wide cells in the faded region).\n */\n readonly overlay: string\n}\n\nexport interface Ag {\n /** The root AgNode tree. */\n readonly root: AgNode\n\n // -------------------------------------------------------------------------\n // Pipeline\n // -------------------------------------------------------------------------\n\n /**\n * Run layout phases: measure → flexbox → scroll → sticky → scrollRect → notify.\n * Mutates layout nodes in place.\n */\n layout(dims: { cols: number; rows: number }, options?: AgLayoutOptions): void\n\n /**\n * Run the render phase: positioned tree → cell grid → TextFrame.\n * Uses internal prevBuffer for incremental rendering.\n * Returns frame (public read API) + buffer/prevBuffer (for output phase).\n */\n render(options?: AgRenderOptions): AgRenderResult\n\n /** Reset internal prevBuffer (call on resize — forces fresh render next frame). */\n resetBuffer(): void\n\n // -------------------------------------------------------------------------\n // Tree Mutation API (Phase 4)\n // -------------------------------------------------------------------------\n\n /** Create a new AgNode with a layout node. */\n createNode(type: AgNodeType, props: Record<string, unknown>): AgNode\n\n /** Insert child at index in both ag tree and layout tree. */\n insertChild(parent: AgNode, child: AgNode, index: number): void\n\n /** Remove child from both ag tree and layout tree. */\n removeChild(parent: AgNode, child: AgNode): void\n\n /** Update node props (applies to layout node if layout-affecting). */\n updateProps(\n node: AgNode,\n props: Record<string, unknown>,\n oldProps?: Record<string, unknown>,\n ): void\n\n /** Update text content on a node. */\n setText(node: AgNode, text: string): void\n\n /** Structural text representation (no layout). */\n toString(): string\n}\n\nexport interface CreateAgOptions {\n /** Width measurer scoped to terminal capabilities. */\n measurer?: Measurer\n /**\n * Terminal color tier for the backdrop-fade pass. Defaults to `\"truecolor\"`.\n * See `pipeline/backdrop/` for tier semantics.\n */\n colorLevel?: BackdropColorLevel\n /**\n * Whether the backdrop-fade pass may emit Kitty graphics placements for\n * emoji scrim. Defaults to an env heuristic (see `isKittyGraphicsEnabled`).\n * Pass `false` to force-disable (tests, explicit opt-out).\n */\n kittyGraphics?: boolean\n}\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/**\n * Walk the ag tree top-down to find the root ThemeProvider's background color.\n *\n * ThemeProvider in @silvery/ag-react renders a `<Box theme={merged}>` wrapper.\n * The render phase pushes/pops this theme via pushContextTheme/popContextTheme,\n * so the module-level theme stack is empty after the render phase completes.\n * We walk the tree directly to recover the root bg without requiring the\n * render phase to be running.\n *\n * Returns the first Box node's Sterling `bg-surface-default` (with legacy `bg`\n * fallback for backdrop-only Themes that pre-date Sterling's flat surface\n * tokens) found in a depth-first walk, or `null` if no theme node is present\n * (bare tests without ThemeProvider).\n */\nfunction findRootThemeBg(root: AgNode): string | null {\n const props = root.props as Record<string, unknown>\n if (props.theme) {\n const theme = props.theme as Record<string, unknown>\n const sterlingBg = theme[\"bg-surface-default\"]\n if (typeof sterlingBg === \"string\") return sterlingBg\n const legacyBg = theme[\"bg\"]\n if (typeof legacyBg === \"string\") return legacyBg\n }\n for (const child of root.children) {\n const found = findRootThemeBg(child)\n if (found !== null) return found\n }\n return null\n}\n\n/**\n * Env heuristic: should the backdrop-fade pass emit Kitty graphics overlays?\n *\n * This is the MVP gate — a lightweight capability detector used when the\n * caller doesn't pass `kittyGraphics` explicitly. Matches the Option C design\n * intent: emit only on modern terminals where Kitty graphics are known to\n * work (Kitty, Ghostty, WezTerm), NOT inside tmux (DCS passthrough is\n * unreliable), with an explicit `SILVERY_KITTY_GRAPHICS` override.\n *\n * - `SILVERY_KITTY_GRAPHICS=0` → always off\n * - `SILVERY_KITTY_GRAPHICS=1` → always on (bypasses tmux + term checks)\n * - `TMUX` env var present → off (unless forced on above)\n * - `TERM_PROGRAM` in {Ghostty, WezTerm} → on\n * - `TERM` contains \"kitty\" → on\n * - `KITTY_WINDOW_ID` set → on\n * - otherwise → off\n *\n * The long-term plan is to promote this to a `TerminalCaps.kittyGraphics`\n * consumer. That field exists (see `@silvery/ansi` detectTerminalCaps) but\n * isn't threaded into the render pipeline yet — tracked as a follow-up.\n */\nfunction isKittyGraphicsEnabledFromEnv(): boolean {\n const env =\n typeof process !== \"undefined\" ? process.env : ({} as Record<string, string | undefined>)\n\n const override = env.SILVERY_KITTY_GRAPHICS\n if (override === \"0\" || override === \"false\") return false\n if (override === \"1\" || override === \"true\") return true\n\n // tmux's DCS passthrough for Kitty graphics is flaky — off by default.\n // User can override via SILVERY_KITTY_GRAPHICS=1 if their tmux config\n // (allow-passthrough + extended keys) actually works.\n if (env.TMUX) return false\n\n const program = env.TERM_PROGRAM ?? \"\"\n if (program === \"ghostty\" || program === \"Ghostty\" || program === \"WezTerm\") return true\n\n const term = env.TERM ?? \"\"\n if (term.includes(\"kitty\")) return true\n\n if (env.KITTY_WINDOW_ID) return true\n\n return false\n}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\nexport function createAg(root: AgNode, options?: CreateAgOptions): Ag {\n const measurer = options?.measurer\n const colorLevel: BackdropColorLevel = options?.colorLevel ?? \"truecolor\"\n // Kitty graphics: explicit option wins. Otherwise fall back to env heuristic\n // so the default behavior matches the terminal running the app without\n // callers needing to thread TerminalCaps through every site. Tests that\n // want to pin determinism pass `kittyGraphics: false`.\n const kittyGraphics =\n options?.kittyGraphics !== undefined ? options.kittyGraphics : isKittyGraphicsEnabledFromEnv()\n const ctx: PipelineContext | undefined = measurer ? { measurer } : undefined\n let _prevBuffer: TerminalBuffer | null = null\n // True when the PREVIOUS frame had backdrop markers (and so emitted Kitty\n // placements). Drives the one-shot delete-all on the first frame where the\n // backdrop goes away so leftover scrim rectangles don't linger on screen.\n // Scoped per-Ag; non-persistent-Ag callers (test driver renderer.ts)\n // additionally track at their own level — see that file.\n let _kittyActive = false\n\n // Feature flags — one-way: once true, stays true for the lifetime of this Ag.\n // This ensures dynamically mounted scroll/sticky components enable their phases\n // and never get skipped again.\n let hasScroll = false\n let hasSticky = false\n\n function doLayout(\n cols: number,\n rows: number,\n opts?: AgLayoutOptions,\n ): { tMeasure: number; tLayout: number; tScroll: number; tScrollRect: number; tNotify: number } {\n // Layout-on-demand gate: skip ALL layout phases when Flexily reports\n // no dirty nodes, no scroll offset changed, and dimensions haven't changed.\n // This eliminates ~38% of per-frame pipeline cost for cursor/style-only changes.\n // First render always has isDirty (Flexily nodes start dirty on creation).\n // scrollTo/scrollOffset changes don't affect Flexily (they don't change\n // dimensions) but DO need scroll/sticky/scrollRect/notify phases to run.\n const prevRootLayout = root.boxRect\n const dimensionsChanged =\n prevRootLayout && (prevRootLayout.width !== cols || prevRootLayout.height !== rows)\n if (!dimensionsChanged && !root.layoutNode?.isDirty() && !hasScrollDirty()) {\n log.debug?.(\"layout: skipped (Flexily clean, no scrollDirty, dimensions unchanged)\")\n // Even when the full layout phase is skipped, style-only changes\n // (outline add/remove, absolute child structural changes) need cascade\n // input bits computed for the render phase. Without this, the render\n // phase can't detect outline mutations and stale outline pixels persist.\n layoutPhase(root, cols, rows)\n return { tMeasure: 0, tLayout: 0, tScroll: 0, tScrollRect: 0, tNotify: 0 }\n }\n\n using render = baseLog.span(\"pipeline\", { width: cols, height: rows })\n\n let tMeasure: number\n {\n using _m = render.span(\"measure\")\n const t = performance.now()\n measurePhase(root, ctx)\n tMeasure = performance.now() - t\n log.debug?.(`measure: ${tMeasure.toFixed(2)}ms`)\n }\n\n let tLayout: number\n {\n using _l = render.span(\"layout\")\n const t = performance.now()\n layoutPhase(root, cols, rows)\n tLayout = performance.now() - t\n log.debug?.(`layout: ${tLayout.toFixed(2)}ms`)\n }\n\n // STRICT invariant: verify no child overflows its parent's inner width.\n // Catches fit-content/snug-content/measure-phase bugs at the source.\n strictLayoutOverflowCheck(root)\n\n // Detect features for phase skipping. One-way merge: false → true only.\n // This scan runs every layout pass to catch newly mounted components.\n if (!hasScroll || !hasSticky) {\n const features = detectPipelineFeatures(root)\n if (features.hasScroll) hasScroll = true\n if (features.hasSticky) hasSticky = true\n }\n\n let tScroll: number\n if (hasScroll) {\n using _s = render.span(\"scroll\")\n const t = performance.now()\n scrollPhase(root, { skipStateUpdates: opts?.skipScrollStateUpdates })\n tScroll = performance.now() - t\n } else {\n tScroll = 0\n }\n\n if (hasSticky) {\n stickyPhase(root)\n }\n\n let tScrollRect: number\n {\n using _r = render.span(\"scrollRect\")\n const t = performance.now()\n if (hasScroll || hasSticky) {\n scrollrectPhase(root)\n } else {\n // Fast path: no scroll offsets or sticky positions to account for.\n // scrollRect === boxRect, screenRect === scrollRect.\n scrollrectPhaseSimple(root)\n }\n tScrollRect = performance.now() - t\n }\n\n let tNotify = 0\n if (!opts?.skipLayoutNotifications) {\n using _n = render.span(\"notify\")\n const t = performance.now()\n notifyLayoutSubscribers(root)\n tNotify = performance.now() - t\n }\n\n // Bench instrumentation: accumulate per-phase timings in a global counter\n // that a harness can read + reset between iterations. Cheap: five `+=` ops.\n // See __silvery_bench_accumulate / __silvery_bench_reset helpers below.\n const acc = (globalThis as any).__silvery_bench_phases\n if (acc) {\n acc.measure += tMeasure\n acc.layout += tLayout\n acc.scroll += tScroll\n acc.scrollRect += tScrollRect\n acc.notify += tNotify\n acc.layoutTotal += tMeasure + tLayout + tScroll + tScrollRect + tNotify\n }\n\n return { tMeasure, tLayout, tScroll, tScrollRect, tNotify }\n }\n\n function doRender(opts?: AgRenderOptions): AgRenderResult & { tContent: number } {\n clearBgConflictWarnings()\n const prevBuffer = opts?.fresh\n ? null\n : opts?.prevBuffer !== undefined\n ? opts.prevBuffer\n : _prevBuffer\n\n let tContent: number\n let buffer: TerminalBuffer\n {\n const t = performance.now()\n buffer = renderPhase(root, prevBuffer, ctx)\n tContent = performance.now() - t\n log.debug?.(`content: ${tContent.toFixed(2)}ms`)\n }\n\n // Backdrop-fade pass — runs after content + decoration, before output.\n //\n // Incremental invariant: fast-path cells carry the PREVIOUS frame's\n // pixels into the clone inside renderPhase. If those pixels are\n // POST-fade, the fade pass re-fades already-faded cells and the result\n // compounds across frames (STRICT: incremental post-fade diverges from\n // fresh post-fade after 2+ frames).\n //\n // Solution: snapshot the PRE-transform buffer BEFORE applying fade.\n // Store it as `_prevBuffer` (for internal ag state) AND return it as\n // `carryForwardBuffer` so external callers managing their own prev\n // state (renderer.ts) can track pre-fade. The post-fade `buffer` is\n // what gets painted; pre-fade is what gets cloned for incremental.\n let carryForwardBuffer: TerminalBuffer\n let overlay = \"\"\n const backdropActive = hasBackdropMarkers(root)\n if (backdropActive) {\n carryForwardBuffer = buffer.clone()\n if (!opts?.fresh) {\n _prevBuffer = carryForwardBuffer\n }\n const defaultBg = findRootThemeBg(root) ?? undefined\n const result = applyBackdrop(root, buffer, {\n colorLevel,\n defaultBg,\n kittyGraphics,\n })\n overlay = result.overlay\n } else {\n carryForwardBuffer = buffer\n if (!opts?.fresh) {\n _prevBuffer = buffer\n }\n // Kitty scrim deactivation: when the backdrop was active last frame but\n // is gone now (modal closed), emit a one-shot delete-all so leftover\n // placements don't linger on screen. The flag `_kittyActive` tracks\n // whether we emitted placements in the previous frame.\n if (_kittyActive) {\n overlay = CURSOR_SAVE + kittyDeleteAllScrimPlacements() + CURSOR_RESTORE\n }\n }\n // Track active placements across frames. True when we emitted (or will\n // emit) overlay escapes this frame WITH backdrop still active.\n _kittyActive = backdropActive && overlay.length > 0\n\n // Clear the module-level dirty tracking after each render pass.\n // Content dirty nodes were processed by renderPhase; layout dirty is\n // managed by Flexily internally (isDirty cleared after calculateLayout).\n clearDirtyTracking()\n\n // Bench instrumentation: accumulate content-phase timing.\n const acc = (globalThis as any).__silvery_bench_phases\n if (acc) {\n acc.content += tContent\n acc.renderCalls += 1\n }\n\n const frame = createTextFrame(buffer)\n return { frame, buffer, carryForwardBuffer, prevBuffer, tContent, overlay }\n }\n\n // -------------------------------------------------------------------------\n // Tree Mutation\n // -------------------------------------------------------------------------\n\n function agCreateNode(type: AgNodeType, props: Record<string, unknown>): AgNode {\n const engine = getLayoutEngine()\n const layoutNode = engine.createNode()\n return {\n type,\n props,\n children: [],\n parent: null,\n layoutNode,\n boxRect: null,\n scrollRect: null,\n screenRect: null,\n prevLayout: null,\n prevScrollRect: null,\n prevScreenRect: null,\n layoutChangedThisFrame: INITIAL_EPOCH,\n dirtyBits: ALL_RECONCILER_BITS,\n dirtyEpoch: getRenderEpoch(),\n }\n }\n\n function agInsertChild(parent: AgNode, child: AgNode, index: number): void {\n // Remove from old parent if already in a tree (keyed reorder)\n if (child.parent) {\n agRemoveChild(child.parent, child)\n }\n\n // Insert into children array\n parent.children.splice(index, 0, child)\n child.parent = parent\n\n // Sync layout tree\n if (parent.layoutNode && child.layoutNode) {\n // Layout index = count of children with layoutNode before this position\n const layoutIndex = parent.children\n .slice(0, index)\n .filter((c) => c.layoutNode !== null).length\n parent.layoutNode.insertChild(child.layoutNode, layoutIndex)\n }\n }\n\n function agRemoveChild(parent: AgNode, child: AgNode): void {\n const index = parent.children.indexOf(child)\n if (index === -1) return\n\n parent.children.splice(index, 1)\n\n if (parent.layoutNode && child.layoutNode) {\n parent.layoutNode.removeChild(child.layoutNode)\n child.layoutNode.free()\n }\n\n child.parent = null\n }\n\n return {\n root,\n\n // Pipeline\n layout(dims, options) {\n if (measurer) {\n runWithMeasurer(measurer, () => doLayout(dims.cols, dims.rows, options))\n } else {\n doLayout(dims.cols, dims.rows, options)\n }\n },\n\n render(options) {\n const result = measurer\n ? runWithMeasurer(measurer, () => doRender(options))\n : doRender(options)\n return {\n frame: result.frame,\n buffer: result.buffer,\n carryForwardBuffer: result.carryForwardBuffer,\n prevBuffer: result.prevBuffer,\n overlay: result.overlay,\n }\n },\n\n resetBuffer() {\n _prevBuffer = null\n },\n\n // Tree mutations\n createNode: agCreateNode,\n insertChild: agInsertChild,\n removeChild: agRemoveChild,\n\n updateProps(node, props, oldProps) {\n node.props = props\n if (node.layoutNode) {\n node.layoutNode.markDirty()\n }\n },\n\n setText(node, text) {\n ;(node as any).textContent = text\n const epoch = getRenderEpoch()\n const bits = CONTENT_BIT | STYLE_PROPS_BIT\n node.dirtyBits = node.dirtyEpoch !== epoch ? bits : node.dirtyBits | bits\n node.dirtyEpoch = epoch\n if (node.layoutNode) {\n node.layoutNode.markDirty()\n }\n },\n\n toString() {\n return `[Ag root=${root.type} children=${root.children.length}]`\n },\n }\n}\n","/**\n * Separate React reconciler instance for renderStringSync.\n *\n * renderStringSync may be called from within React effects (e.g., useScrollback\n * freezing items to scrollback). If it uses the same reconciler singleton as the\n * main render tree, this causes re-entrancy: the nested reconciliation interferes\n * with the outer one, producing empty output.\n *\n * By using a dedicated reconciler instance, renderStringSync operates on an\n * independent fiber tree with no shared reconciler state.\n */\n\n// @ts-expect-error - react-reconciler has no type declarations\nimport Reconciler from \"react-reconciler\"\nimport { hostConfig } from \"./host-config\"\n\n/**\n * Dedicated reconciler for string rendering.\n *\n * Uses the same host config functions but overrides isPrimaryRenderer to false,\n * since this is a secondary renderer used only for one-shot string rendering.\n * This avoids conflicts with the main reconciler's hook ownership.\n */\nexport const stringReconciler = Reconciler({\n ...hostConfig,\n isPrimaryRenderer: false,\n})\n","/**\n * renderString - Static one-shot rendering to string\n *\n * Renders a React element to a string without needing a terminal.\n * Use for:\n * - CI output (no cursor control needed)\n * - Piped output\n * - One-shot reports/summaries\n * - Testing component output\n *\n * @example\n * ```tsx\n * import { renderString, Box, Text } from '@silvery/ag-react'\n *\n * // Basic usage\n * const output = renderString(<Summary stats={stats} />)\n * console.log(output)\n *\n * // Custom width\n * const wide = renderString(<Report />, { width: 120 })\n *\n * // Plain text (no ANSI)\n * const plain = renderString(<Report />, { plain: true })\n * ```\n */\n\nimport React, { type ReactElement, act } from \"react\"\n\nimport { createTerm } from \"@silvery/ag-term/ansi\"\n\nimport { bufferToStyledText, bufferToText, type TerminalBuffer } from \"@silvery/ag-term/buffer\"\nimport { StdoutContext, StderrContext, TermContext } from \"./context\"\nimport { isLayoutEngineInitialized } from \"@silvery/ag-term/layout-engine\"\nimport type { PipelineConfig } from \"@silvery/ag-term/pipeline\"\nimport { createAg } from \"@silvery/ag-term/ag\"\nimport { runWithMeasurer } from \"@silvery/ag-term/unicode\"\nimport { createContainer, getContainerRoot } from \"./reconciler\"\nimport { stringReconciler } from \"./reconciler/string-reconciler\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Options for renderString().\n */\nexport interface RenderStringOptions {\n /**\n * Width in columns for layout calculations.\n * Default: 80\n */\n width?: number\n\n /**\n * Height in rows for layout calculations.\n * Default: 24\n */\n height?: number\n\n /**\n * Strip ANSI codes for plain text output.\n * Default: false (includes ANSI styling)\n */\n plain?: boolean\n\n /**\n * Pipeline configuration (scoped width measurer + output phase).\n * When provided, the render pipeline uses these for width measurement\n * and output generation instead of the global defaults.\n */\n pipelineConfig?: PipelineConfig\n\n /**\n * Trim trailing whitespace from each line.\n * Default: true (trims trailing spaces)\n *\n * Set to false when rendering to stdout where trailing spaces are\n * semantically significant (e.g., ink compat static renders).\n */\n trimTrailingWhitespace?: boolean\n\n /**\n * Trim trailing empty lines from the output.\n * Default: true (removes trailing empty lines)\n *\n * Set to false when padding/margin at the bottom should be preserved\n * (e.g., ink compat renders where `\\n\\n` padding is significant).\n */\n trimEmptyLines?: boolean\n\n /**\n * Callback to receive the computed content height (root node layout height).\n * Useful for callers that need to know the actual content bounds\n * (e.g., Ink compat layer needs to trim buffer padding but preserve\n * content-area empty lines).\n */\n onContentHeight?: (height: number) => void\n\n /**\n * Always use styled output (bufferToStyledText) even when plain=true.\n * Default: false\n *\n * When true, the output includes ANSI codes from embedded sequences in text\n * content (SGR, OSC hyperlinks) even though the mock term has no color\n * support (plain=true). This is needed for Ink compatibility: Ink passes\n * embedded ANSI sequences through regardless of chalk's color level, but\n * silvery's plain mode would strip them via bufferToText.\n *\n * The `plain` flag still controls whether the mock term has color support,\n * which determines whether Text component style props (color, bold, etc.)\n * produce style attributes in the buffer.\n */\n alwaysStyled?: boolean\n}\n\n// ============================================================================\n// Module State\n// ============================================================================\n\n// Track if we've initialized to avoid redundant imports\nlet engineInitialized = false\n\nasync function ensureLayoutEngine(): Promise<void> {\n if (engineInitialized || isLayoutEngineInitialized()) {\n return\n }\n // Use centralized default engine initialization\n const { ensureDefaultLayoutEngine } = await import(\"@silvery/ag-term/layout-engine\")\n await ensureDefaultLayoutEngine()\n engineInitialized = true\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Render a React element to a string (async version).\n *\n * Automatically initializes the layout engine if needed.\n * Use this when you're not sure if the layout engine is ready.\n *\n * @param element - React element to render\n * @param options - Render options (width, height, plain)\n * @returns Rendered string (with or without ANSI codes)\n *\n * @example\n * ```tsx\n * const output = await renderString(<Summary stats={stats} />)\n * console.log(output)\n * ```\n */\nexport async function renderString(\n element: ReactElement,\n options: RenderStringOptions = {},\n): Promise<string> {\n await ensureLayoutEngine()\n return renderStringSync(element, options)\n}\n\n/**\n * Render a React element to a string (sync version).\n *\n * Requires the layout engine to be already initialized.\n * Throws if the layout engine is not ready.\n *\n * @param element - React element to render\n * @param options - Render options (width, height, plain)\n * @returns Rendered string (with or without ANSI codes)\n *\n * @example\n * ```tsx\n * // After layout engine is initialized\n * const output = renderStringSync(<Summary stats={stats} />)\n * console.log(output)\n * ```\n */\nexport function renderStringSync(element: ReactElement, options: RenderStringOptions = {}): string {\n if (!isLayoutEngineInitialized()) {\n throw new Error(\n \"Layout engine not initialized. Use renderString() (async) or initialize with setLayoutEngine().\",\n )\n }\n\n const {\n width = 80,\n height = 24,\n plain = false,\n pipelineConfig,\n trimTrailingWhitespace = true,\n trimEmptyLines = true,\n onContentHeight,\n alwaysStyled = false,\n } = options\n\n // Track whether React committed new work (from layout notifications etc.)\n let hadReactCommit = false\n const container = createContainer(() => {\n hadReactCommit = true\n })\n\n // Capture uncaught errors from the reconciler so they propagate to the caller\n let uncaughtError: unknown = null\n const onUncaughtError = (error: unknown) => {\n uncaughtError = error\n }\n\n // Create fiber root using the dedicated string reconciler (not the main one)\n const fiberRoot = stringReconciler.createContainer(\n container,\n 1, // ConcurrentRoot\n null, // hydrationCallbacks\n false, // isStrictMode\n null, // concurrentUpdatesByDefaultOverride\n \"\", // identifierPrefix\n onUncaughtError, // onUncaughtError\n () => {}, // onCaughtError\n () => {}, // onRecoverableError\n null, // onDefaultTransitionIndicator\n )\n\n // Create minimal mock stdout for components that use useStdout\n const mockStdout = {\n columns: width,\n rows: height,\n write: () => true,\n isTTY: false,\n on: () => mockStdout,\n off: () => mockStdout,\n once: () => mockStdout,\n removeListener: () => mockStdout,\n addListener: () => mockStdout,\n } as unknown as NodeJS.WriteStream\n\n // Create mock term for components that use useTerm()\n const mockTerm = createTerm({ color: plain ? null : \"truecolor\" })\n\n // Wrap with minimal contexts (no input handling needed)\n const wrapped = React.createElement(\n TermContext.Provider,\n { value: mockTerm },\n React.createElement(\n StdoutContext.Provider,\n {\n value: {\n stdout: mockStdout,\n write: () => {},\n },\n },\n React.createElement(\n StderrContext.Provider,\n {\n value: {\n stderr: process.stderr,\n write: (data: string) => {\n process.stderr.write(data)\n },\n },\n },\n element,\n ),\n ),\n )\n\n // Mount the React tree inside act() so layout feedback works\n withActEnvironment(() => {\n act(() => {\n stringReconciler.updateContainerSync(wrapped, fiberRoot, null, null)\n stringReconciler.flushSyncWork()\n })\n })\n\n // Propagate any uncaught render errors (e.g., text outside <Text> in strict mode)\n if (uncaughtError) {\n throw uncaughtError instanceof Error ? uncaughtError : new Error(String(uncaughtError))\n }\n\n // Layout stabilization loop: run the pipeline, flush React work from\n // layout notifications (useBoxRect forceUpdate etc.), repeat until stable.\n // This matches the test renderer's multi-pass approach.\n let buffer!: TerminalBuffer\n let rootNode: ReturnType<typeof getContainerRoot> | undefined\n const MAX_ITERATIONS = 5\n for (let i = 0; i < MAX_ITERATIONS; i++) {\n hadReactCommit = false\n withActEnvironment(() => {\n act(() => {\n const root = getContainerRoot(container)\n rootNode = root\n const measurer = pipelineConfig?.measurer\n const doRender = () => {\n const ag = createAg(root, { measurer })\n ag.layout({ cols: width, rows: height })\n return ag.render()\n }\n const result = measurer ? runWithMeasurer(measurer, doRender) : doRender()\n buffer = result.buffer\n })\n if (!hadReactCommit) {\n act(() => {\n stringReconciler.flushSyncWork()\n })\n }\n })\n if (!hadReactCommit) break\n }\n\n // Report content height if callback provided.\n // Compute from children's outer bottom edges (including margins) rather than\n // root.boxRect.height which equals the buffer height (root stretches to fill).\n if (onContentHeight && rootNode) {\n let maxBottom = 0\n let hasChildren = false\n for (const child of rootNode.children) {\n if (child.boxRect) {\n hasChildren = true\n const props = child.props as Record<string, unknown>\n const mb =\n (props.marginBottom as number) ??\n (props.marginY as number) ??\n (props.margin as number) ??\n 0\n const childBottom = child.boxRect.y + child.boxRect.height + mb\n if (childBottom > maxBottom) maxBottom = childBottom\n }\n }\n onContentHeight(hasChildren ? maxBottom : 0)\n }\n\n // Unmount (cleanup)\n withActEnvironment(() => {\n act(() => {\n stringReconciler.updateContainerSync(null, fiberRoot, null, null)\n stringReconciler.flushSyncWork()\n })\n })\n\n return plain && !alwaysStyled\n ? bufferToText(buffer, { trimTrailingWhitespace, trimEmptyLines })\n : bufferToStyledText(buffer, { trimTrailingWhitespace, trimEmptyLines })\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Run a function with IS_REACT_ACT_ENVIRONMENT temporarily set to true.\n * This ensures act() captures forceUpdate/setState from layout notifications.\n */\nfunction withActEnvironment(fn: () => void): void {\n const prev = (globalThis as any).IS_REACT_ACT_ENVIRONMENT\n ;(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true\n try {\n fn()\n } finally {\n ;(globalThis as any).IS_REACT_ACT_ENVIRONMENT = prev\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"render-string-Darrg7ku.mjs","names":["defaultGraphemeWidth","collectTextContent","getTextWidth","traverseTree","log","log","hexToRgb","hexToRgb","hexToRgb","hexToRgb"],"sources":["../packages/ag-term/src/pipeline/prepared-text.ts","../packages/ag-term/src/pipeline/pretext.ts","../packages/ag-term/src/pipeline/helpers.ts","../packages/ag-term/src/pipeline/measure-phase.ts","../packages/ag-term/src/pipeline/layout-phase.ts","../packages/ag-term/src/pipeline/state.ts","../packages/ag-term/src/pipeline/render-helpers.ts","../packages/ag-term/src/pipeline/render-text.ts","../packages/ag-term/src/pipeline/render-box.ts","../packages/ag-term/src/pipeline/decoration-phase.ts","../packages/ag-term/src/pipeline/cascade-predicates.ts","../packages/ag-term/src/pipeline/reactive-node.ts","../packages/ag-term/src/pipeline/render-phase.ts","../packages/ag-term/src/pipeline/backdrop/color.ts","../packages/ag-term/src/pipeline/backdrop/plan.ts","../packages/ag-term/src/pipeline/backdrop/color-compat.ts","../packages/ag-term/src/pipeline/backdrop/region.ts","../packages/ag-term/src/pipeline/backdrop/realize-buffer.ts","../packages/ag-term/src/pipeline/backdrop/realize-kitty.ts","../packages/ag-term/src/pipeline/backdrop/index.ts","../packages/ag-term/src/ag.ts","../packages/ag-react/src/reconciler/string-reconciler.ts","../packages/ag-react/src/render-string.tsx"],"sourcesContent":["/**\n * PreparedText: Per-node text analysis cache (Design G, Steps 1-3).\n *\n * Caches three levels of text analysis on a WeakMap<AgNode> to avoid\n * redundant work across frames:\n *\n * Level 0 — Plain text (measure phase + maxDisplayWidth computation)\n * Level 1 — Collected styled text with bg segments (render phase)\n * Level 2 — Formatted lines per width (render phase, LRU by width)\n *\n * Invalidation uses the epoch-based dirty flag system:\n * - Plain text: CONTENT_BIT | CHILDREN_BIT\n * - Collected text: CONTENT_BIT | CHILDREN_BIT | STYLE_PROPS_BIT | BG_BIT | SUBTREE_BIT | context theme ref\n * - Format entries: cleared when collected text is invalidated; keyed by (width, wrap, trim)\n *\n * The WeakMap ensures automatic cleanup when nodes are removed from the tree.\n * Format entries use a small LRU (4 entries) to handle Flexily measure probes\n * (min-content, max-content, final width) without unbounded growth.\n */\n\nimport type { AgNode } from \"@silvery/ag/types\"\nimport {\n isDirty,\n CONTENT_BIT,\n CHILDREN_BIT,\n STYLE_PROPS_BIT,\n BG_BIT,\n SUBTREE_BIT,\n} from \"@silvery/ag/epoch\"\nimport type { TextAnalysis } from \"./pretext\"\nimport type { Theme } from \"@silvery/ansi\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Cached formatted lines for a specific width/wrap/trim combination. */\nexport interface FormatEntry {\n width: number\n wrap: string | boolean | undefined\n trim: boolean\n lines: string[]\n lineOffsets: Array<{ start: number; end: number }>\n hasLineOffsets: boolean\n}\n\n/** Minimal shape of collected text result. Structurally matches TextWithBg. */\nexport interface CollectedTextResult {\n text: string\n bgSegments: readonly { start: number; end: number; bg: unknown }[]\n childSpans: readonly { node: AgNode; start: number; end: number }[]\n plainLen: number\n}\n\n/** Per-text-node cache entry. */\ninterface TextNodeCache {\n // Level 0: plain text\n plainText: string | null\n plainTextLineCount: number\n\n // Level 1: collected styled text\n collected: CollectedTextResult | null\n collectedMaxDisplayWidth: number | undefined\n /** The context theme (by reference) when collected was last computed.\n * When the nearest-ancestor ThemeProvider changes its theme, this reference\n * becomes stale and getCachedCollectedText returns null, forcing re-collection\n * with the new theme's token values embedded in the ANSI codes. */\n collectedContextTheme: Theme | null\n\n // Level 2: formatted lines (LRU, max 4 entries)\n formats: FormatEntry[]\n\n // Level 3: Pretext analysis (cumWidths, breakpoints, etc.)\n // Built from collected text, invalidated when collected text changes\n analysis: TextAnalysis | null\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst MAX_FORMAT_ENTRIES = 4\n\n/** Content-affecting flags that invalidate plain text. */\nconst PLAIN_TEXT_DIRTY = CONTENT_BIT | CHILDREN_BIT\n\n/**\n * All flags that affect collected text (ANSI codes, bg segments, child spans).\n * SUBTREE_BIT is included because collectTextWithBg recurses into virtual text\n * children — a child's style change sets SUBTREE_BIT on the parent without\n * setting STYLE_PROPS_BIT (the parent's own props didn't change).\n */\nconst COLLECTED_TEXT_DIRTY = CONTENT_BIT | CHILDREN_BIT | STYLE_PROPS_BIT | BG_BIT | SUBTREE_BIT\n\n// ============================================================================\n// Storage\n// ============================================================================\n\n/** Set to true to disable all caching (for testing/debugging). */\nlet _cacheDisabled = !!process.env.SILVERY_NO_TEXT_CACHE\nexport function setPreparedTextCacheEnabled(enabled: boolean): void {\n _cacheDisabled = !enabled\n}\n\nconst textCaches = new WeakMap<AgNode, TextNodeCache>()\n\nfunction getOrCreate(node: AgNode): TextNodeCache {\n let entry = textCaches.get(node)\n if (!entry) {\n entry = {\n plainText: null,\n plainTextLineCount: 0,\n collected: null,\n collectedMaxDisplayWidth: undefined,\n collectedContextTheme: null,\n formats: [],\n analysis: null,\n }\n textCaches.set(node, entry)\n }\n return entry\n}\n\n// ============================================================================\n// Level 0: Plain text (measure phase + maxDisplayWidth computation)\n// ============================================================================\n\n/**\n * Get cached plain text and line count.\n * Returns null on cache miss (content/children changed or first access).\n */\nexport function getCachedPlainText(node: AgNode): { text: string; lineCount: number } | null {\n if (_cacheDisabled) return null\n const entry = textCaches.get(node)\n if (entry?.plainText == null) return null\n if (isDirty(node.dirtyBits, node.dirtyEpoch, PLAIN_TEXT_DIRTY)) {\n entry.plainText = null\n return null\n }\n return { text: entry.plainText, lineCount: entry.plainTextLineCount }\n}\n\n/** Store plain text in cache. */\nexport function setCachedPlainText(node: AgNode, text: string, lineCount: number): void {\n const entry = getOrCreate(node)\n entry.plainText = text\n entry.plainTextLineCount = lineCount\n}\n\n// ============================================================================\n// Level 1: Collected styled text (render phase)\n// ============================================================================\n\n/**\n * Get cached collected text (from collectTextWithBg).\n * Invalidated by content, children, style, or bg changes, maxDisplayWidth mismatch,\n * or a context theme change (ancestor ThemeProvider changed token values).\n *\n * @param contextTheme - The active theme at the time of rendering (from getActiveTheme()).\n * Used as a cache key so that when the nearest-ancestor ThemeProvider changes its\n * merged theme, text nodes that embed $token ANSI codes in their collected text\n * are re-collected with the new token values. Pass null when no theme context exists.\n */\nexport function getCachedCollectedText(\n node: AgNode,\n maxDisplayWidth: number | undefined,\n contextTheme: Theme | null,\n): CollectedTextResult | null {\n if (_cacheDisabled) return null\n const entry = textCaches.get(node)\n if (!entry?.collected) return null\n\n if (isDirty(node.dirtyBits, node.dirtyEpoch, COLLECTED_TEXT_DIRTY)) {\n entry.collected = null\n entry.formats = [] // collected text changed → format stale\n entry.analysis = null // analysis depends on collected text\n return null\n }\n\n if (entry.collectedMaxDisplayWidth !== maxDisplayWidth) {\n entry.collected = null\n entry.formats = []\n entry.analysis = null\n return null\n }\n\n // When the ancestor theme changes, ANSI-encoded $token colors in the collected\n // text are stale. Invalidate so collectTextWithBg re-runs with new token values.\n if (entry.collectedContextTheme !== contextTheme) {\n entry.collected = null\n entry.formats = []\n entry.analysis = null\n return null\n }\n\n return entry.collected\n}\n\n/** Store collected text in cache. */\nexport function setCachedCollectedText(\n node: AgNode,\n result: CollectedTextResult,\n maxDisplayWidth: number | undefined,\n contextTheme: Theme | null,\n): void {\n const entry = getOrCreate(node)\n entry.collected = result\n entry.collectedMaxDisplayWidth = maxDisplayWidth\n entry.collectedContextTheme = contextTheme\n}\n\n// ============================================================================\n// Level 2: Formatted lines per width (render phase, LRU)\n// ============================================================================\n\n/**\n * Get cached formatted lines for the given width/wrap/trim.\n * Returns null on cache miss.\n */\nexport function getCachedFormat(\n node: AgNode,\n width: number,\n wrap: string | boolean | undefined,\n trim: boolean,\n): FormatEntry | null {\n if (_cacheDisabled) return null\n const entry = textCaches.get(node)\n if (!entry || entry.formats.length === 0) return null\n\n for (let i = 0; i < entry.formats.length; i++) {\n const f = entry.formats[i]!\n if (f.width === width && f.wrap === wrap && f.trim === trim) {\n // LRU: move to end (most recently used)\n if (i < entry.formats.length - 1) {\n entry.formats.splice(i, 1)\n entry.formats.push(f)\n }\n return f\n }\n }\n return null\n}\n\n/** Store formatted lines in cache (LRU, evicts oldest when full). */\nexport function setCachedFormat(\n node: AgNode,\n width: number,\n wrap: string | boolean | undefined,\n trim: boolean,\n lines: string[],\n lineOffsets: Array<{ start: number; end: number }>,\n hasLineOffsets: boolean,\n): void {\n const entry = getOrCreate(node)\n\n // Replace existing entry for same key\n for (let i = 0; i < entry.formats.length; i++) {\n const f = entry.formats[i]!\n if (f.width === width && f.wrap === wrap && f.trim === trim) {\n entry.formats[i] = { width, wrap, trim, lines, lineOffsets, hasLineOffsets }\n return\n }\n }\n\n // Evict oldest if at capacity\n if (entry.formats.length >= MAX_FORMAT_ENTRIES) {\n entry.formats.shift()\n }\n entry.formats.push({ width, wrap, trim, lines, lineOffsets, hasLineOffsets })\n}\n\n// ============================================================================\n// Level 3: Pretext analysis (cumWidths, breakpoints — for snug-content/even wrap)\n// ============================================================================\n\n/**\n * Get cached text analysis. Invalidated when content changes.\n * Uses PLAIN_TEXT_DIRTY (not COLLECTED_TEXT_DIRTY) because analysis\n * is built from plain text in measure phase, not styled text.\n */\nexport function getCachedAnalysis(node: AgNode): TextAnalysis | null {\n if (_cacheDisabled) return null\n const entry = textCaches.get(node)\n if (!entry?.analysis) return null\n if (isDirty(node.dirtyBits, node.dirtyEpoch, PLAIN_TEXT_DIRTY)) {\n entry.analysis = null\n return null\n }\n return entry.analysis\n}\n\n/** Store text analysis in cache. */\nexport function setCachedAnalysis(node: AgNode, analysis: TextAnalysis): void {\n const entry = getOrCreate(node)\n entry.analysis = analysis\n}\n","/**\n * Pretext: Grapheme-indexed text analysis for layout queries.\n *\n * Inspired by https://chenglou.me/pretext/ — prepare text once, measure at\n * any width cheaply. Enables layout algorithms CSS can't express:\n *\n * - **Shrinkwrap**: find the narrowest width that keeps the same line count\n * - **Balanced**: equalize line widths (reduce raggedness)\n * - **Knuth-Plass**: optimal paragraph breaking (minimize total raggedness)\n * - **Height prediction**: exact line count at any width without full wrapping\n */\n\nimport {\n graphemeWidth as defaultGraphemeWidth,\n splitGraphemesAnsiAware,\n isWordBoundary,\n canBreakAnywhere,\n wrapText,\n} from \"../unicode\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Grapheme-level text analysis for fast width queries. */\nexport interface TextAnalysis {\n /** ANSI-aware graphemes (visible chars + zero-width ANSI tokens). */\n graphemes: string[]\n /** Display width per grapheme (0 for ANSI tokens). */\n widths: number[]\n /** Prefix sums: cumWidths[i] = sum(widths[0..i-1]). cumWidths[0] = 0. */\n cumWidths: number[]\n /** Total display width of all graphemes. */\n totalWidth: number\n /** Width of the widest unbreakable word segment. */\n maxWordWidth: number\n /** Width of the widest single grapheme. */\n maxGraphemeWidth: number\n /** Grapheme indices where newlines occur. */\n newlineIndices: number[]\n /** Grapheme indices where word breaks are legal. */\n breakIndices: number[]\n /** Original text (for delegating to wrapText). */\n text: string\n}\n\n// ============================================================================\n// Build\n// ============================================================================\n\n/**\n * Build text analysis from an ANSI-embedded text string.\n * O(N) where N is grapheme count. Call once per text change (cached by PreparedText).\n */\nexport function buildTextAnalysis(\n text: string,\n gWidthFn: (g: string) => number = defaultGraphemeWidth,\n): TextAnalysis {\n const graphemes = splitGraphemesAnsiAware(text)\n const len = graphemes.length\n const widths = new Array<number>(len)\n const cumWidths = new Array<number>(len + 1)\n const newlineIndices: number[] = []\n const breakIndices: number[] = []\n\n cumWidths[0] = 0\n let maxWordWidth = 0\n let maxGraphemeWidth = 0\n let currentWordWidth = 0\n\n for (let i = 0; i < len; i++) {\n const g = graphemes[i]!\n const w = gWidthFn(g)\n widths[i] = w\n cumWidths[i + 1] = cumWidths[i]! + w\n if (w > maxGraphemeWidth) maxGraphemeWidth = w\n\n if (g === \"\\n\") {\n newlineIndices.push(i)\n maxWordWidth = Math.max(maxWordWidth, currentWordWidth)\n currentWordWidth = 0\n } else if (isWordBoundary(g)) {\n breakIndices.push(i + 1)\n maxWordWidth = Math.max(maxWordWidth, currentWordWidth)\n currentWordWidth = 0\n } else if (canBreakAnywhere(g)) {\n breakIndices.push(i)\n maxWordWidth = Math.max(maxWordWidth, currentWordWidth)\n currentWordWidth = w\n } else if (w > 0) {\n currentWordWidth += w\n }\n }\n maxWordWidth = Math.max(maxWordWidth, currentWordWidth)\n\n return {\n graphemes,\n widths,\n cumWidths,\n totalWidth: cumWidths[len]!,\n maxWordWidth,\n maxGraphemeWidth,\n newlineIndices,\n breakIndices,\n text,\n }\n}\n\n// ============================================================================\n// Line counting\n// ============================================================================\n\n/**\n * Count how many lines text would occupy at a given width.\n *\n * Delegates to wrapText for correctness — the greedy wrapping algorithm has\n * subtle boundary-char handling (spaces consumed on overflow, leading space\n * trimming on continuation lines) that's error-prone to reimplement.\n *\n * For terminal text (20-200 chars), wrapText is ~5-12µs per call.\n * Shrinkwrap does ~7-9 calls (log2(width)), so total is ~50-100µs.\n */\nexport function countLinesAtWidth(analysis: TextAnalysis, width: number): number {\n if (width <= 0) return Infinity\n if (analysis.totalWidth <= width && analysis.newlineIndices.length === 0) return 1\n return wrapText(analysis.text, width, true, true).length\n}\n\n// ============================================================================\n// Shrinkwrap\n// ============================================================================\n\n/**\n * Find the narrowest integer width that produces the same line count as maxWidth.\n *\n * CSS fit-content uses the widest wrapped line — leaving dead space when the\n * last line is short. Shrinkwrap binary-searches for the tightest width that\n * keeps the same number of lines, eliminating wasted area in bubbles/cards.\n *\n * O(log(maxWidth) × wrapText) — ~7-9 iterations for terminal widths.\n */\nexport function shrinkwrapWidth(analysis: TextAnalysis, maxWidth: number): number {\n if (maxWidth <= 0) return 0\n const targetLineCount = countLinesAtWidth(analysis, maxWidth)\n if (targetLineCount <= 1) {\n return Math.min(Math.ceil(analysis.totalWidth), maxWidth)\n }\n\n // Lower bound: max grapheme width (character wrap allows widths below maxWordWidth)\n // Upper bound: maxWidth (can't return wider than the container)\n let lo = Math.max(1, analysis.maxGraphemeWidth)\n let hi = maxWidth\n\n // Guard: if lo >= hi, nothing to search\n if (lo >= hi) return Math.min(hi, maxWidth)\n\n while (lo < hi) {\n const mid = (lo + hi) >> 1\n if (countLinesAtWidth(analysis, mid) <= targetLineCount) {\n hi = mid\n } else {\n lo = mid + 1\n }\n }\n\n // Clamp to maxWidth (safety)\n return Math.min(lo, maxWidth)\n}\n\n// ============================================================================\n// Balanced breaking\n// ============================================================================\n\n/**\n * Find a width that produces lines of approximately equal length.\n *\n * Strategy: compute total width / line count as the ideal per-line width,\n * then find the narrowest width at that line count via shrinkwrap.\n */\nexport function balancedWidth(analysis: TextAnalysis, maxWidth: number): number {\n if (maxWidth <= 0) return 0\n const lineCount = countLinesAtWidth(analysis, maxWidth)\n if (lineCount <= 1) return Math.min(Math.ceil(analysis.totalWidth), maxWidth)\n\n // Ideal balanced width: total / lines, rounded up\n const idealWidth = Math.ceil(analysis.totalWidth / lineCount)\n\n // Clamp to [maxGraphemeWidth, maxWidth]\n const candidateWidth = Math.max(analysis.maxGraphemeWidth, Math.min(idealWidth, maxWidth))\n\n // Verify this doesn't increase line count\n if (countLinesAtWidth(analysis, candidateWidth) > lineCount) {\n return shrinkwrapWidth(analysis, maxWidth)\n }\n\n // Further tighten via shrinkwrap at the balanced line count\n return shrinkwrapWidth(analysis, candidateWidth)\n}\n\n// ============================================================================\n// Knuth-Plass optimal paragraph breaking\n// ============================================================================\n\n/**\n * Find optimal line breaks that minimize total raggedness.\n *\n * Runs per-paragraph (split by newlines) to avoid penalty interactions\n * around forced breaks. Falls back to greedy wrapping for paragraphs\n * where the DP finds no feasible solution (overlong words).\n *\n * O(breakpoints²) per paragraph, typically much less with pruning.\n */\nexport function knuthPlassBreaks(analysis: TextAnalysis, width: number): number[] {\n if (width <= 0) return []\n if (analysis.totalWidth <= width && analysis.newlineIndices.length === 0) return []\n\n // Split into paragraphs at newlines and process each independently\n const { newlineIndices, graphemes } = analysis\n const allBreaks: number[] = []\n\n const paragraphStarts = [0]\n for (const nl of newlineIndices) {\n paragraphStarts.push(nl + 1)\n }\n\n for (let p = 0; p < paragraphStarts.length; p++) {\n const pStart = paragraphStarts[p]!\n const pEnd = p + 1 < paragraphStarts.length ? paragraphStarts[p + 1]! - 1 : graphemes.length // -1 to exclude newline\n\n if (pStart >= pEnd) continue // empty paragraph\n\n const breaks = knuthPlassForParagraph(analysis, pStart, pEnd, width)\n allBreaks.push(...breaks)\n\n // Add newline break if not the last paragraph\n if (p < paragraphStarts.length - 1 && pEnd < graphemes.length) {\n allBreaks.push(pEnd + 1) // after the newline\n }\n }\n\n return allBreaks\n}\n\n/** DP for a single paragraph (no newlines). */\nfunction knuthPlassForParagraph(\n analysis: TextAnalysis,\n pStart: number,\n pEnd: number,\n width: number,\n): number[] {\n const { cumWidths, breakIndices, widths, graphemes } = analysis\n\n // Build candidates for this paragraph\n const candidates: number[] = [pStart]\n for (const bp of breakIndices) {\n if (bp > pStart && bp <= pEnd) candidates.push(bp)\n }\n candidates.push(pEnd)\n\n const n = candidates.length\n if (n <= 2) return [] // single segment, no breaks needed\n\n const cost = new Array<number>(n).fill(Infinity)\n const next = new Array<number>(n).fill(-1)\n cost[n - 1] = 0\n\n for (let i = n - 2; i >= 0; i--) {\n const lineStart = candidates[i]!\n const lineStartCum = cumWidths[lineStart]!\n\n for (let j = i + 1; j < n; j++) {\n const lineEnd = candidates[j]!\n\n // Compute line width, trimming trailing whitespace\n let trimEnd = lineEnd\n while (trimEnd > lineStart) {\n const prevG = graphemes[trimEnd - 1]\n const prevW = widths[trimEnd - 1]\n if (prevW === 0) {\n trimEnd--\n continue\n } // skip ANSI\n if (prevG === \" \" || prevG === \"\\t\") {\n trimEnd--\n continue\n }\n break\n }\n const lineWidth = cumWidths[trimEnd]! - lineStartCum\n\n if (lineWidth > width) break // too wide, skip wider candidates\n\n const leftover = width - lineWidth\n const lineCost = j === n - 1 ? 0 : leftover * leftover\n const totalCost = lineCost + cost[j]!\n\n if (totalCost < cost[i]!) {\n cost[i] = totalCost\n next[i] = j\n }\n }\n }\n\n // If DP failed (no feasible path), return empty (caller falls back to greedy)\n if (cost[0] === Infinity) return []\n\n // Trace back\n const breaks: number[] = []\n let idx = 0\n while (idx < n - 1 && next[idx]! >= 0) {\n idx = next[idx]!\n if (idx < n - 1) {\n breaks.push(candidates[idx]!)\n }\n }\n\n return breaks\n}\n\n/**\n * Wrap text using Knuth-Plass optimal breaks.\n * Returns line strings — drop-in replacement for greedy wrap.\n * Falls back to greedy wrapText when DP finds no feasible solution.\n */\nexport function optimalWrap(text: string, analysis: TextAnalysis, width: number): string[] {\n const breaks = knuthPlassBreaks(analysis, width)\n if (breaks.length === 0) {\n // No breaks found — either single line or DP infeasible → fall back to greedy\n if (analysis.totalWidth <= width && analysis.newlineIndices.length === 0) return [text]\n return wrapText(text, width, true, true)\n }\n\n const { graphemes, widths } = analysis\n const lines: string[] = []\n let lineStart = 0\n\n for (const bp of breaks) {\n // Trim trailing whitespace (skip zero-width ANSI tokens)\n let lineEnd = bp\n while (lineEnd > lineStart) {\n const w = widths[lineEnd - 1]!\n if (w === 0) {\n lineEnd--\n continue\n } // ANSI token\n const g = graphemes[lineEnd - 1]!\n if (g === \" \" || g === \"\\t\" || g === \"\\n\") {\n lineEnd--\n continue\n }\n break\n }\n lines.push(graphemes.slice(lineStart, lineEnd).join(\"\"))\n\n // Skip leading whitespace on next line (skip ANSI tokens)\n lineStart = bp\n while (lineStart < graphemes.length) {\n const g = graphemes[lineStart]!\n if (g === \" \" || g === \"\\t\") {\n lineStart++\n continue\n }\n break\n }\n }\n\n // Last line\n if (lineStart < graphemes.length) {\n lines.push(graphemes.slice(lineStart).join(\"\"))\n }\n\n return lines\n}\n","/**\n * Shared helper functions for silvery pipeline phases.\n */\n\nimport type { BoxProps } from \"@silvery/ag/types\"\nimport { getActiveLineHeight } from \"../unicode\"\n\n/**\n * Get padding values from props.\n */\nexport function getPadding(props: BoxProps): {\n top: number\n bottom: number\n left: number\n right: number\n} {\n return {\n top: props.paddingTop ?? props.paddingY ?? props.padding ?? 0,\n bottom: props.paddingBottom ?? props.paddingY ?? props.padding ?? 0,\n left: props.paddingLeft ?? props.paddingX ?? props.padding ?? 0,\n right: props.paddingRight ?? props.paddingX ?? props.padding ?? 0,\n }\n}\n\n/**\n * Get border size (1 or 0 for each side).\n * In pixel/canvas mode (lineHeight > 1), borders are visual-only (fillRoundedRect)\n * and don't affect content positioning — returns 0.\n */\nexport function getBorderSize(props: BoxProps): {\n top: number\n bottom: number\n left: number\n right: number\n} {\n if (!props.borderStyle || getActiveLineHeight() > 1) {\n return { top: 0, bottom: 0, left: 0, right: 0 }\n }\n return {\n top: props.borderTop !== false ? 1 : 0,\n bottom: props.borderBottom !== false ? 1 : 0,\n left: props.borderLeft !== false ? 1 : 0,\n right: props.borderRight !== false ? 1 : 0,\n }\n}\n","/**\n * Phase 1: Measure Phase\n *\n * Handle fit-content nodes by measuring their intrinsic content size.\n */\n\nimport type { BoxProps, AgNode, TextProps } from \"@silvery/ag/types\"\nimport { displayWidthAnsi, graphemeWidth, wrapText, getActiveLineHeight } from \"../unicode\"\nimport { collectPlainText as collectTextContent } from \"./collect-text\"\nimport {\n getCachedPlainText,\n setCachedPlainText,\n getCachedAnalysis,\n setCachedAnalysis,\n} from \"./prepared-text\"\nimport { buildTextAnalysis, shrinkwrapWidth } from \"./pretext\"\nimport { getBorderSize, getPadding } from \"./helpers\"\nimport type { PipelineContext } from \"./types\"\n\n/**\n * Handle fit-content nodes by measuring their intrinsic content size.\n *\n * Traverses the tree and for any node with width=\"fit-content\" or\n * height=\"fit-content\", measures the content and sets the Yoga constraint.\n */\nexport function measurePhase(root: AgNode, ctx?: PipelineContext): void {\n traverseTree(root, (node) => {\n // Skip nodes without Yoga (raw text nodes)\n if (!node.layoutNode) return\n\n const props = node.props as BoxProps\n\n // width=\"fit-content\" is now handled natively by Flexily (UNIT_FIT_CONTENT).\n // The reconciler calls setWidthFitContent() directly.\n // width=\"snug-content\" uses Flexily's UNIT_SNUG_CONTENT for basic sizing,\n // but still needs the binary-search tightening pass here.\n // height=\"fit-content\" still needs the pre-layout polyfill.\n const isSnugContent = props.width === \"snug-content\"\n const isHeightFitContent = props.height === \"fit-content\"\n\n if (isSnugContent || isHeightFitContent) {\n // Pass an available-width constraint to child measurement whenever a\n // definite upper bound exists — either a fixed width (height=\"fit-content\"\n // + width:number case) or a maxWidth cap on the snug-content box itself.\n let availableWidth: number | undefined\n const widthIsFixed = typeof props.width === \"number\"\n let definiteUpperWidth: number | undefined =\n widthIsFixed && isHeightFitContent\n ? (props.width as number)\n : typeof props.maxWidth === \"number\"\n ? (props.maxWidth as number)\n : undefined\n if (definiteUpperWidth === undefined) {\n definiteUpperWidth = findAncestorDefiniteWidth(node)\n }\n if (definiteUpperWidth !== undefined) {\n const padding = getPadding(props)\n availableWidth = definiteUpperWidth - padding.left - padding.right\n if (props.borderStyle) {\n const border = getBorderSize(props)\n availableWidth -= border.left + border.right\n }\n if (availableWidth < 1) availableWidth = 1\n }\n\n if (isSnugContent) {\n const intrinsicSize = measureIntrinsicSize(node, ctx, availableWidth)\n // Fit-snug: find the narrowest width that keeps the same line count.\n // Binary search for tightest width on top of Flexily's native fit-content.\n const shrunkWidth = computeSnugContentWidth(node, intrinsicSize.width, ctx)\n // setMaxWidth caps the snug-content box at the binary-searched width.\n // Flexily's UNIT_SNUG_CONTENT handles the shrink-wrap + available clamping.\n node.layoutNode.setMaxWidth(shrunkWidth)\n }\n if (isHeightFitContent) {\n const intrinsicSize = measureIntrinsicSize(node, ctx, availableWidth)\n node.layoutNode.setHeight(intrinsicSize.height)\n }\n }\n })\n}\n\n/**\n * Measure the intrinsic size of a node's content.\n *\n * For text nodes: measures the text width and line count.\n * For box nodes: recursively measures children based on flex direction.\n *\n * @param availableWidth - When set, text nodes wrap at this width for height calculation.\n * Used when a container has fixed width + fit-content height.\n */\nfunction measureIntrinsicSize(\n node: AgNode,\n ctx?: PipelineContext,\n availableWidth?: number,\n): {\n width: number\n height: number\n} {\n const props = node.props as BoxProps\n\n // display=\"none\" nodes have 0x0 intrinsic size\n if (props.display === \"none\") {\n return { width: 0, height: 0 }\n }\n\n if (node.type === \"silvery-text\") {\n const textProps = props as TextProps\n // PreparedText cache: reuse plain text from previous frames when content unchanged\n const cached = getCachedPlainText(node)\n let text: string\n if (cached) {\n text = cached.text\n } else {\n text = collectTextContent(node)\n const lineCount = (text.match(/\\n/g)?.length ?? 0) + 1\n setCachedPlainText(node, text, lineCount)\n }\n\n // Apply internal_transform if present (used by Transform component).\n // The transform is applied per-line, which can change the width.\n const transform = textProps.internal_transform\n let lines: string[]\n\n if (availableWidth !== undefined && availableWidth > 0 && isWrapEnabled(textProps.wrap)) {\n // Wrap text at available width to compute correct height\n lines = ctx\n ? ctx.measurer.wrapText(text, availableWidth, true, true)\n : wrapText(text, availableWidth, true, true)\n } else {\n lines = text.split(\"\\n\")\n }\n\n if (transform) {\n lines = lines.map((line, index) => transform(line, index))\n }\n\n const width = Math.max(...lines.map((line) => getTextWidth(line, ctx)))\n return {\n width,\n height: lines.length * getActiveLineHeight(),\n }\n }\n\n // For boxes, measure based on flex direction\n const isRow = props.flexDirection === \"row\" || props.flexDirection === \"row-reverse\"\n\n let width = 0\n let height = 0\n\n let childCount = 0\n for (const child of node.children) {\n const childSize = measureIntrinsicSize(child, ctx, availableWidth)\n childCount++\n\n if (isRow) {\n width += childSize.width\n height = Math.max(height, childSize.height)\n } else {\n width = Math.max(width, childSize.width)\n height += childSize.height\n }\n }\n\n // Add gap between children\n const gap = (props.gap as number) ?? 0\n if (gap > 0 && childCount > 1) {\n const totalGap = gap * (childCount - 1)\n if (isRow) {\n width += totalGap\n } else {\n height += totalGap\n }\n }\n\n // Add padding\n const padding = getPadding(props)\n width += padding.left + padding.right\n height += padding.top + padding.bottom\n\n // Add border\n if (props.borderStyle) {\n const border = getBorderSize(props)\n width += border.left + border.right\n height += border.top + border.bottom\n }\n\n return { width, height }\n}\n\n/**\n * Check if text wrapping is enabled for a text node.\n */\nfunction isWrapEnabled(wrap: TextProps[\"wrap\"]): boolean {\n return (\n wrap === \"wrap\" || wrap === \"hard\" || wrap === \"even\" || wrap === true || wrap === undefined\n )\n}\n\n/**\n * Compute snug-content width for a node.\n * Uses Pretext analysis to binary-search for the tightest width\n * that keeps the same line count as the fit-content width.\n */\nfunction computeSnugContentWidth(\n node: AgNode,\n fitContentWidth: number,\n ctx?: PipelineContext,\n): number {\n const props = node.props as BoxProps\n\n // Subtract padding + border from fitContentWidth to get CONTENT width.\n // measureIntrinsicSize includes padding+border in its result, but\n // shrinkwrapWidth operates on text content width only.\n let overhead = 0\n const padding = getPadding(props)\n overhead += padding.left + padding.right\n if (props.borderStyle) {\n const border = getBorderSize(props)\n overhead += border.left + border.right\n }\n const contentWidth = fitContentWidth - overhead\n\n // Get or build text analysis\n let analysis = getCachedAnalysis(node)\n if (!analysis) {\n const cached = getCachedPlainText(node)\n const text = cached ? cached.text : collectTextContent(node)\n const gWidthFn = ctx?.measurer?.graphemeWidth?.bind(ctx.measurer) ?? graphemeWidth\n analysis = buildTextAnalysis(text, gWidthFn)\n setCachedAnalysis(node, analysis)\n if (!cached) {\n const lineCount = (text.match(/\\n/g)?.length ?? 0) + 1\n setCachedPlainText(node, text, lineCount)\n }\n }\n\n // Shrinkwrap the content, then add overhead back\n return shrinkwrapWidth(analysis, contentWidth) + overhead\n}\n\n/**\n * Walk up the tree from a node to find the nearest ancestor with a definite\n * width (a fixed number, not \"fit-content\" or \"snug-content\"). Returns the\n * ancestor's inner content width (after subtracting its own padding and border).\n * Returns undefined if no definite-width ancestor is found.\n */\nfunction findAncestorDefiniteWidth(node: AgNode): number | undefined {\n let current = node.parent\n while (current) {\n const p = current.props as BoxProps\n if (typeof p.width === \"number\") {\n let inner = p.width as number\n const padding = getPadding(p)\n inner -= padding.left + padding.right\n if (p.borderStyle) {\n const border = getBorderSize(p)\n inner -= border.left + border.right\n }\n return inner > 0 ? inner : 1\n }\n if (typeof p.maxWidth === \"number\") {\n let inner = p.maxWidth as number\n const padding = getPadding(p)\n inner -= padding.left + padding.right\n if (p.borderStyle) {\n const border = getBorderSize(p)\n inner -= border.left + border.right\n }\n return inner > 0 ? inner : 1\n }\n current = current.parent\n }\n return undefined\n}\n\n/**\n * Traverse tree in depth-first order.\n */\nfunction traverseTree(node: AgNode, callback: (node: AgNode) => void): void {\n callback(node)\n for (const child of node.children) {\n traverseTree(child, callback)\n }\n}\n\n/**\n * Get text display width (accounting for wide characters and ANSI codes).\n * Uses ANSI-aware width calculation to handle styled text.\n */\nfunction getTextWidth(text: string, ctx?: PipelineContext): number {\n if (ctx) return ctx.measurer.displayWidthAnsi(text)\n return displayWidthAnsi(text)\n}\n\n// collectTextContent is imported from ./collect-text as collectPlainText.\n// Previously duplicated here; now shared across measure-phase, render-text,\n// and the reconciler's measure function.\n","/**\n * Phase 2: Layout Phase\n *\n * Run Yoga layout calculation and propagate dimensions to all nodes.\n */\n\nimport { createLogger } from \"loggily\"\nimport { measureStats } from \"./measure-stats\"\nimport { type BoxProps, type AgNode, type Rect, rectEqual } from \"@silvery/ag/types\"\n// Layout dirty gate: Flexily's root.layoutNode.isDirty() is the sole source\n// of truth. No silvery-side layout dirty tracking needed.\nimport {\n getRenderEpoch,\n INITIAL_EPOCH,\n isCurrentEpoch,\n isDirty,\n SUBTREE_BIT,\n CHILDREN_BIT,\n ABS_CHILD_BIT,\n DESC_OVERFLOW_BIT,\n} from \"@silvery/ag/epoch\"\nimport { getBorderSize, getPadding } from \"./helpers\"\nimport { syncRectSignals } from \"@silvery/ag/layout-signals\"\n\nconst log = createLogger(\"silvery:layout\")\n\n/**\n * Run Yoga layout calculation and propagate dimensions to all nodes.\n *\n * @param root The root SilveryNode\n * @param width Terminal width in columns\n * @param height Terminal height in rows\n */\nexport function layoutPhase(root: AgNode, width: number, height: number): void {\n // Check if dimensions changed from previous layout\n const prevLayout = root.boxRect\n const dimensionsChanged =\n prevLayout && (prevLayout.width !== width || prevLayout.height !== height)\n\n // Only recalculate if something changed (dirty nodes or dimensions).\n // Flexily's root isDirty() propagates from any markDirty() call —\n // no silvery-side tracking needed.\n if (!dimensionsChanged && !root.layoutNode?.isDirty()) {\n // Even when layout is clean, style-only changes (outline add/remove,\n // absolute child structural changes) need cascade input caching.\n // These checks run in propagateLayout normally, but when the layout\n // phase skips, they're never computed. Run a lightweight traversal\n // that follows only subtreeDirty paths to cache these inputs.\n if (isDirty(root.dirtyBits, root.dirtyEpoch, SUBTREE_BIT)) {\n propagateCascadeInputs(root)\n }\n return\n }\n // Run layout calculation (root always has a layoutNode)\n if (root.layoutNode) {\n const nodeCount = countNodes(root)\n measureStats.reset()\n const t0 = Date.now()\n root.layoutNode.calculateLayout(width, height)\n const elapsed = Date.now() - t0\n log.debug?.(\n `calculateLayout: ${elapsed}ms (${nodeCount} nodes) measure: calls=${measureStats.calls} hits=${measureStats.cacheHits} collects=${measureStats.textCollects} displayWidth=${measureStats.displayWidthCalls}`,\n )\n }\n\n // Propagate computed dimensions to all nodes.\n // When dimensions haven't changed, enable incremental skip: subtrees\n // whose Flexily-computed rect matches their existing boxRect are skipped\n // entirely (O(1) rect comparison prunes O(subtree) walk).\n // On dimension change, the root constraint changed so all nodes may get\n // new results — skip nothing, propagate the full tree.\n const incrementalSkip = !dimensionsChanged\n propagateLayout(root, 0, 0, incrementalSkip)\n\n // NOTE: Subscribers are NOT notified here anymore.\n // They are notified by the pipeline AFTER scrollrectPhase completes,\n // so useScrollRect can read the correct screen positions.\n}\n\n/**\n * Count total nodes in tree.\n */\nfunction countNodes(node: AgNode): number {\n let count = 1\n for (const child of node.children) {\n count += countNodes(child)\n }\n return count\n}\n\n/**\n * Propagate computed layout from Yoga nodes to SilveryNodes.\n * Sets boxRect (content-relative position) on each node.\n *\n * When `incrementalSkip` is true, nodes whose Flexily-computed rect matches\n * their existing boxRect can skip the entire subtree — their layout is\n * unchanged. This converts the O(N) tree walk into O(dirty) for frames\n * where only a few nodes changed layout.\n *\n * The skip is safe because:\n * - Flexily's internal fingerprint caching guarantees identical output for\n * subtrees whose inputs didn't change\n * - If the parent's rect matches, all descendants' rects also match\n * (Flexily computes absolute positions from parent dimensions)\n * - prevLayout and layoutChangedThisFrame (stale epoch, won't match\n * current) all retain correct values\n *\n * @param node The node to process\n * @param parentX Absolute X position of parent\n * @param parentY Absolute Y position of parent\n * @param incrementalSkip When true, skip subtrees where Flexily results match existing boxRect\n */\nfunction propagateLayout(\n node: AgNode,\n parentX: number,\n parentY: number,\n incrementalSkip: boolean,\n): void {\n // Virtual/raw text nodes (no layoutNode) inherit parent's position\n if (!node.layoutNode) {\n // Save previous layout for change detection\n node.prevLayout = node.boxRect\n const rect: Rect = {\n x: parentX,\n y: parentY,\n width: 0,\n height: 0,\n }\n node.boxRect = rect\n // Still recurse to children (virtual text nodes can have raw text children)\n for (const child of node.children) {\n propagateLayout(child, parentX, parentY, incrementalSkip)\n }\n return\n }\n\n // Compute absolute position from Yoga (content-relative)\n const rect: Rect = {\n x: parentX + node.layoutNode.getComputedLeft(),\n y: parentY + node.layoutNode.getComputedTop(),\n width: node.layoutNode.getComputedWidth(),\n height: node.layoutNode.getComputedHeight(),\n }\n\n // Container-level layout skip: if incremental mode is enabled and this\n // node's Flexily-computed rect matches the existing boxRect, the entire\n // subtree is unchanged. Skip propagation — all descendants retain correct\n // prevLayout, boxRect, and layoutChangedThisFrame (stale epoch) from the\n // previous frame.\n //\n // This check is O(1) per node (4 number comparisons + 1 epoch check) and\n // prunes entire subtrees, converting propagateLayout from O(N) to O(changed).\n // Note: prevLayout is already synced to boxRect by syncPrevLayout() at\n // the end of the previous render pass, so skipping is safe.\n //\n // subtreeDirtyEpoch guard: even when this node's rect is unchanged, a\n // descendant may need processing (e.g., new child mounted via appendChild).\n // The reconciler's markSubtreeDirty propagates the current epoch upward,\n // so checking subtreeDirtyEpoch ensures we don't skip over dirty descendants.\n if (\n incrementalSkip &&\n node.boxRect &&\n !isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT) &&\n !isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT)\n ) {\n if (\n rect.x === node.boxRect.x &&\n rect.y === node.boxRect.y &&\n rect.width === node.boxRect.width &&\n rect.height === node.boxRect.height\n ) {\n return\n }\n }\n\n // Save previous layout for change detection (must happen AFTER the skip\n // check above — skipped nodes don't need prevLayout updated since\n // syncPrevLayout already set prevLayout = boxRect after the previous frame)\n node.prevLayout = node.boxRect\n node.boxRect = rect\n\n // Set authoritative \"layout changed this frame\" epoch stamp.\n // Unlike !rectEqual(prevLayout, boxRect) which becomes stale when\n // layout phase skips on subsequent frames, this epoch is explicitly set\n // each time propagateLayout runs and expires when the render epoch advances.\n const layoutDidChange = !!(node.prevLayout && !rectEqual(node.prevLayout, node.boxRect))\n node.layoutChangedThisFrame = layoutDidChange ? getRenderEpoch() : INITIAL_EPOCH\n\n // STRICT invariant: if layoutChangedThisFrame is current epoch, prevLayout must differ from boxRect.\n // This validates that the flag is consistent with the actual rect comparison. A violation\n // would mean the flag is set spuriously, causing unnecessary re-renders and cascade propagation.\n if (process?.env?.SILVERY_STRICT && isCurrentEpoch(node.layoutChangedThisFrame)) {\n if (rectEqual(node.prevLayout, node.boxRect)) {\n const props = node.props as BoxProps\n throw new Error(\n `[SILVERY_STRICT] layoutChangedThisFrame=true but prevLayout equals boxRect ` +\n `(node: ${props.id ?? node.type}, rect: ${JSON.stringify(node.boxRect)})`,\n )\n }\n }\n\n // When layout changes, mark ancestors subtreeDirty so renderPhase doesn't\n // fast-path skip them. Without this, a deeply nested node whose dimensions\n // change (e.g., width 3→4) would never be re-rendered because all ancestors\n // appear clean — their own layout didn't change, just a descendant's did.\n if (isCurrentEpoch(node.layoutChangedThisFrame)) {\n const epoch = getRenderEpoch()\n let ancestor = node.parent\n while (ancestor && !isDirty(ancestor.dirtyBits, ancestor.dirtyEpoch, SUBTREE_BIT)) {\n if (ancestor.dirtyEpoch !== epoch) {\n ancestor.dirtyBits = SUBTREE_BIT\n ancestor.dirtyEpoch = epoch\n } else {\n ancestor.dirtyBits |= SUBTREE_BIT\n }\n ancestor = ancestor.parent\n }\n }\n\n // Recurse to children\n for (const child of node.children) {\n propagateLayout(child, rect.x, rect.y, incrementalSkip)\n }\n\n // Cache cascade inputs that render-phase would otherwise compute via tree walks.\n // Both checks require children to have finalized layoutChangedThisFrame, boxRect,\n // prevLayout, childrenDirtyEpoch, and subtreeDirtyEpoch — all set above.\n // Guard: only compute when subtreeDirty (matches buildCascadeInputs guard).\n if (isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT) && node.children.length > 0) {\n const epoch = getRenderEpoch()\n\n // absoluteChildMutated: check direct children for absolute-positioned nodes\n // that had structural changes (children mount/unmount/reorder, layout change,\n // child position shift).\n const absChild = _hasAbsoluteChildMutated(node.children)\n\n // descendantOverflowChanged: recursive check for descendants whose prevLayout\n // extended beyond THIS node's rect and had layoutChangedThisFrame.\n const descOverflow = _hasDescendantOverflowChanged(node, rect)\n\n // Set or clear the layout-phase bits\n let bits = node.dirtyBits\n if (absChild) bits |= ABS_CHILD_BIT\n else bits &= ~ABS_CHILD_BIT\n if (descOverflow) bits |= DESC_OVERFLOW_BIT\n else bits &= ~DESC_OVERFLOW_BIT\n node.dirtyBits = bits\n node.dirtyEpoch = epoch\n } else {\n // Clear layout-phase bits (keep reconciler bits intact)\n if (node.dirtyEpoch === getRenderEpoch()) {\n node.dirtyBits &= ~(ABS_CHILD_BIT | DESC_OVERFLOW_BIT)\n }\n }\n}\n\n/**\n * Lightweight cascade input caching when the layout phase skips.\n *\n * When no layout nodes are dirty and dimensions haven't changed,\n * `layoutPhase` returns early and `propagateLayout` never runs.\n * But structural changes (absolute child mount/unmount, descendant overflow)\n * still need cascade input bits (ABS_CHILD_BIT, DESC_OVERFLOW_BIT) to be\n * computed for the render phase.\n *\n * This traversal follows only subtreeDirty paths (O(changed) not O(N))\n * and computes the same cascade inputs as propagateLayout's caching block.\n * No layout changes, no prevLayout updates, no layoutChangedThisFrame.\n */\nfunction propagateCascadeInputs(node: AgNode): void {\n if (!isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT)) return\n if (!node.children || node.children.length === 0) return\n\n // Recurse into dirty children first (they need their own cascade inputs)\n for (const child of node.children) {\n if (isDirty(child.dirtyBits, child.dirtyEpoch, SUBTREE_BIT)) {\n propagateCascadeInputs(child)\n }\n }\n\n // Compute cascade inputs for this node (same logic as in propagateLayout)\n const epoch = getRenderEpoch()\n const absChild = _hasAbsoluteChildMutated(node.children)\n const descOverflow = node.boxRect ? _hasDescendantOverflowChanged(node, node.boxRect) : false\n\n let bits = node.dirtyBits\n if (absChild) bits |= ABS_CHILD_BIT\n else bits &= ~ABS_CHILD_BIT\n if (descOverflow) bits |= DESC_OVERFLOW_BIT\n else bits &= ~DESC_OVERFLOW_BIT\n node.dirtyBits = bits\n node.dirtyEpoch = epoch\n}\n\n/**\n * Check if any direct child is position=\"absolute\" and had structural changes.\n */\nfunction _hasAbsoluteChildMutated(children: readonly AgNode[]): boolean {\n for (const child of children) {\n const cp = child.props as BoxProps\n if (\n cp.position === \"absolute\" &&\n (isDirty(child.dirtyBits, child.dirtyEpoch, CHILDREN_BIT) ||\n isCurrentEpoch(child.layoutChangedThisFrame) ||\n _hasChildPositionChanged(child))\n ) {\n return true\n }\n }\n return false\n}\n\n/**\n * Check if any child's position changed (boxRect vs prevLayout).\n */\nfunction _hasChildPositionChanged(node: AgNode): boolean {\n for (const child of node.children) {\n if (child.boxRect && child.prevLayout) {\n if (child.boxRect.x !== child.prevLayout.x || child.boxRect.y !== child.prevLayout.y) {\n return true\n }\n }\n }\n return false\n}\n\n/**\n * Check if any descendant was overflowing THIS node's rect and had its layout change.\n * Recursive: follows subtreeDirty paths for efficiency.\n */\nfunction _hasDescendantOverflowChanged(node: AgNode, rect: Rect): boolean {\n return _checkDescendantOverflow(\n node.children,\n rect.x,\n rect.y,\n rect.x + rect.width,\n rect.y + rect.height,\n )\n}\n\nfunction _checkDescendantOverflow(\n children: readonly AgNode[],\n nodeLeft: number,\n nodeTop: number,\n nodeRight: number,\n nodeBottom: number,\n): boolean {\n for (const child of children) {\n if (child.prevLayout && isCurrentEpoch(child.layoutChangedThisFrame)) {\n const prev = child.prevLayout\n if (\n prev.x + prev.width > nodeRight ||\n prev.y + prev.height > nodeBottom ||\n prev.x < nodeLeft ||\n prev.y < nodeTop\n ) {\n return true\n }\n }\n if (isDirty(child.dirtyBits, child.dirtyEpoch, SUBTREE_BIT) && child.children !== undefined) {\n if (_checkDescendantOverflow(child.children, nodeLeft, nodeTop, nodeRight, nodeBottom)) {\n return true\n }\n }\n }\n return false\n}\n\n/**\n * Notify all layout subscribers of dimension changes.\n *\n * Called by the pipeline AFTER scrollrectPhase completes,\n * so useScrollRect can read correct screen positions.\n *\n * Notifies when EITHER boxRect, scrollRect, or screenRect changed.\n * scrollRect can change from scroll offset changes even when\n * boxRect stays the same — subscribers (like useScrollRect)\n * need notification in both cases. screenRect can change from sticky\n * offset changes even when scrollRect stays the same.\n */\nexport function notifyLayoutSubscribers(node: AgNode): void {\n // Notify if content rect, screen rect, or render rect changed\n const contentChanged = !rectEqual(node.prevLayout, node.boxRect)\n const screenChanged = !rectEqual(node.prevScrollRect, node.scrollRect)\n const renderChanged = !rectEqual(node.prevScreenRect, node.screenRect)\n // Sync rect values into alien-signals (for signal-based hooks).\n // Always sync — even when no rect changed — because the signal may\n // have been created after the last sync (lazy initialization).\n syncRectSignals(node)\n\n // Recurse to children\n for (const child of node.children) {\n notifyLayoutSubscribers(child)\n }\n}\n\n// ============================================================================\n// STRICT Layout Overflow Invariant\n// ============================================================================\n\n/**\n * Verify that no child's boxRect.width exceeds its parent's inner content width.\n *\n * This catches fit-content/snug-content bugs at the source — any measure-phase\n * or correction-pass error fires immediately.\n *\n * - SILVERY_STRICT=1: console.warn on violation\n * - SILVERY_STRICT=2: throw on violation\n *\n * Exceptions:\n * - Parent has overflow: \"scroll\" or \"hidden\" (overflow is allowed)\n * - Child has position: \"absolute\" (absolute nodes can overflow)\n */\nexport function strictLayoutOverflowCheck(root: AgNode): void {\n const strict = process?.env?.SILVERY_STRICT\n if (!strict) return\n\n const shouldThrow = strict === \"2\"\n\n function walk(node: AgNode): void {\n for (const child of node.children) {\n if (child.boxRect && node.boxRect) {\n const childProps = child.props as BoxProps\n\n // Skip absolute-positioned children — they're allowed to overflow\n if (childProps.position === \"absolute\") {\n walk(child)\n continue\n }\n\n const parentProps = node.props as BoxProps\n\n // Skip if parent allows overflow (scroll or hidden)\n if (parentProps.overflow === \"scroll\" || parentProps.overflow === \"hidden\") {\n walk(child)\n continue\n }\n\n // Compute parent's inner content width\n const border = parentProps.borderStyle\n ? getBorderSize(parentProps)\n : { top: 0, bottom: 0, left: 0, right: 0 }\n const padding = getPadding(parentProps)\n const parentInnerWidth =\n node.boxRect.width - padding.left - padding.right - border.left - border.right\n\n if (child.boxRect.width > parentInnerWidth) {\n const childId = (childProps as any).id ?? child.type\n const parentId = (parentProps as any).id ?? node.type\n const msg =\n `[SILVERY_STRICT] Layout overflow: child \"${childId}\" width ${child.boxRect.width} ` +\n `exceeds parent \"${parentId}\" inner width ${parentInnerWidth} ` +\n `(parent box: ${node.boxRect.width}, border: ${border.left}+${border.right}, padding: ${padding.left}+${padding.right})`\n\n if (shouldThrow) {\n throw new Error(msg)\n } else {\n console.warn(msg)\n }\n }\n }\n\n walk(child)\n }\n }\n\n walk(root)\n}\n\n// Re-export from types\nexport { rectEqual } from \"@silvery/ag/types\"\n\n// ============================================================================\n// Phase 2.5: Scroll Phase (for overflow='scroll' containers)\n// ============================================================================\n\n/**\n * Options for scrollPhase.\n */\nexport interface ScrollPhaseOptions {\n /**\n * Skip state updates (for fresh render comparisons).\n * When true, calculates scroll positions but doesn't mutate node.scrollState.\n * Default: false\n */\n skipStateUpdates?: boolean\n}\n\n/**\n * Calculate scroll state for all overflow='scroll' containers.\n *\n * This phase runs after layout to determine which children are visible\n * within each scrollable container.\n */\nexport function scrollPhase(root: AgNode, options: ScrollPhaseOptions = {}): void {\n const { skipStateUpdates = false } = options\n traverseTree(root, (node) => {\n const props = node.props as BoxProps\n if (props.overflow !== \"scroll\") return\n\n // Calculate scroll state for this container\n calculateScrollState(node, props, skipStateUpdates)\n })\n}\n\n/**\n * Snap scroll offset so the first visible child (after the top overflow\n * indicator's reserved row) aligns with a child-top boundary.\n *\n * When scrolling \"down to show the target at the bottom,\" the raw offset\n * `target.bottom - effectiveHeight` assumes the entire viewport above the\n * bottom-indicator is usable content. But the TOP overflow indicator also\n * consumes a row when `hiddenAbove > 0`, rendering at viewport row 0 on top\n * of whatever child starts there. If that row is a card's top border, the\n * border is overwritten — users see a \"headless\" card and perceive the\n * column as \"gotten shorter\" (see km-tui `column-top-disappears`).\n *\n * This snap shifts the offset so `offset + 1 === firstFullyVisibleChild.top`:\n * the top-indicator row coincides with the 1-row gap ABOVE the first child,\n * not with that child's content. When children have heterogeneous heights,\n * this means moving the viewport DOWN by a few rows (so an earlier, shorter\n * child scrolls fully off-screen and the next child starts cleanly).\n *\n * Guardrails:\n * - Never snap past the target's own top (keeps the target visible).\n * - If no suitable boundary exists above `rawOffset + 1` and ≤ `target.top`,\n * returns `rawOffset` unchanged (scroll behaves as before).\n * - Returns 0 unchanged — offset=0 means no top indicator, no conflict.\n */\nfunction snapOffsetToChildTop(\n rawOffset: number,\n childPositions: { child: AgNode; top: number; bottom: number; index: number; isSticky: boolean }[],\n target: { top: number; bottom: number; index: number },\n): number {\n if (rawOffset <= 0) return rawOffset\n // Desired: first-visible-child.top === offset + 1 (leaving row 0 for indicator).\n // Find the smallest child-top in the range (rawOffset + 1, target.top] and\n // set offset = that child-top - 1. This places the child-top one row below\n // the viewport top — exactly the row the top indicator occupies.\n let bestChildTop = -1\n for (const cp of childPositions) {\n if (cp.isSticky) continue\n if (cp.top === cp.bottom) continue // skip zero-height\n if (cp.top > rawOffset && cp.top <= target.top) {\n if (bestChildTop === -1 || cp.top < bestChildTop) {\n bestChildTop = cp.top\n }\n }\n }\n if (bestChildTop === -1) return rawOffset\n // Reserve one row for the top indicator: scrollOffset = childTop - 1.\n // This keeps the child at viewport row 1 (just below the indicator row).\n const snapped = bestChildTop - 1\n // Safety: never reduce offset below rawOffset (would hide target.bottom).\n return snapped >= rawOffset ? snapped : rawOffset\n}\n\n/**\n * Calculate scroll state for a single scrollable container.\n */\nfunction calculateScrollState(node: AgNode, props: BoxProps, skipStateUpdates: boolean): void {\n const layout = node.boxRect\n if (!layout || !node.layoutNode) return\n\n // Calculate viewport (container minus borders/padding)\n const border = props.borderStyle ? getBorderSize(props) : { top: 0, bottom: 0, left: 0, right: 0 }\n const padding = getPadding(props)\n\n const rawViewportHeight =\n layout.height - border.top - border.bottom - padding.top - padding.bottom\n\n // Calculate total content height and child positions\n let contentHeight = 0\n const childPositions: {\n child: AgNode\n top: number\n bottom: number\n index: number\n isSticky: boolean\n stickyTop?: number\n stickyBottom?: number\n }[] = []\n\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i]!\n if (!child.layoutNode || !child.boxRect) continue\n\n const childTop = child.boxRect.y - layout.y - border.top - padding.top\n const childBottom = childTop + child.boxRect.height\n const childProps = child.props as BoxProps\n\n childPositions.push({\n child: child!,\n top: childTop,\n bottom: childBottom,\n index: i,\n isSticky: childProps.position === \"sticky\",\n stickyTop: childProps.stickyTop,\n stickyBottom: childProps.stickyBottom,\n })\n\n contentHeight = Math.max(contentHeight, childBottom)\n }\n\n const viewportHeight = rawViewportHeight\n\n // Reserve 1 row at the bottom for the overflow indicator when:\n // 1. Container uses borderless overflow indicators (overflowIndicator prop)\n // 2. Content exceeds viewport (there will be hidden items below or above)\n // This ensures the indicator doesn't overlay the last visible child's content.\n const showBorderlessIndicator = props.overflowIndicator === true && !props.borderStyle\n const hasOverflow = contentHeight > rawViewportHeight\n const indicatorReserve = showBorderlessIndicator && hasOverflow ? 1 : 0\n\n // Calculate scroll offset based on scrollTo prop\n // Use \"ensure visible\" scrolling: only scroll when target would be off-screen\n // Preserve previous offset when target is already visible\n //\n // Priority:\n // 1. If scrollTo is defined: use edge-based scrolling to ensure child is visible\n // 2. If scrollOffset is defined: use explicit offset (for frozen scroll state)\n // 3. Otherwise: use previous offset or default to 0\n const prevOffset = node.scrollState?.offset\n const explicitOffset = props.scrollOffset\n let scrollOffset = explicitOffset ?? prevOffset ?? 0\n const scrollTo = props.scrollTo\n\n if (scrollTo !== undefined && scrollTo >= 0 && scrollTo < childPositions.length) {\n // Find the target child\n const target = childPositions.find((c) => c.index === scrollTo)\n if (target) {\n // Calculate current visible range, accounting for indicator reserve.\n // The effective visible height is reduced by indicatorReserve so the\n // scrollTo target is fully visible ABOVE the overflow indicator row.\n const effectiveHeight = viewportHeight - indicatorReserve\n const visibleTop = scrollOffset\n const visibleBottom = scrollOffset + effectiveHeight\n\n // Only scroll if target is outside visible range\n if (target.top < visibleTop) {\n // Target is above viewport - scroll up to show it at top.\n //\n // Reserve one row for the TOP overflow indicator so it doesn't\n // overwrite the target's top border. When target.top > 0 there\n // will be a top indicator (target isn't the first child, so items\n // above exist). Shifting scrollOffset one row up places the\n // indicator at viewport row 0 (over the preceding card's bottom\n // row — typically its bottom border) and leaves target.top at\n // viewport row 1, rendering its top border cleanly.\n scrollOffset = target.top > 0 ? target.top - 1 : 0\n } else if (target.bottom > visibleBottom) {\n // Target is below viewport - scroll down to show it at bottom.\n //\n // Snap to a child-top boundary when a pixel-exact offset would land\n // inside a child (clipping its top border). Without snapping, mixed-\n // height children produce a \"headless card\" at the viewport top —\n // users perceive it as \"column got shorter\" (see km-tui bug\n // `column-top-disappears`). Snap DOWN (toward a larger offset) so the\n // target remains visible at the bottom of the viewport.\n const rawOffset = target.bottom - effectiveHeight\n scrollOffset = snapOffsetToChildTop(rawOffset, childPositions, target)\n }\n // Otherwise, keep current scroll position (target is visible)\n }\n }\n\n // Clamp to valid range — applies to both scrollTo and explicit scrollOffset.\n // Without this, explicit scrollOffset can scroll past content into blank space.\n scrollOffset = Math.max(0, scrollOffset)\n scrollOffset = Math.min(scrollOffset, Math.max(0, contentHeight - viewportHeight))\n\n // Determine visible children.\n // When the overflow indicator reserves a row (indicatorReserve=1), reduce the\n // visible bottom by 1 so the indicator has its own row after the last visible child.\n const visibleTop = scrollOffset\n const visibleBottom = scrollOffset + viewportHeight - indicatorReserve\n\n let firstVisible = -1\n let lastVisible = -1\n let hiddenAbove = 0\n let hiddenBelow = 0\n\n for (const cp of childPositions) {\n // Sticky children are always considered \"visible\" for rendering purposes\n if (cp.isSticky) {\n if (firstVisible === -1) firstVisible = cp.index\n lastVisible = Math.max(lastVisible, cp.index)\n continue\n }\n\n // Skip zero-height children from hidden counts — they have no visual\n // presence and would produce spurious overflow indicators (e.g., a\n // zero-height child at position 0 has top=0, bottom=0, and 0 <= 0\n // would incorrectly count it as \"hidden above\").\n if (cp.top === cp.bottom) {\n continue\n }\n\n if (cp.bottom <= visibleTop) {\n hiddenAbove++\n } else if (cp.top >= visibleBottom) {\n hiddenBelow++\n } else if (cp.top < visibleTop) {\n // Child is partially visible at top — render it (clipped by scroll\n // container's clip bounds) so partial content is visible instead of blank space\n if (firstVisible === -1) firstVisible = cp.index\n lastVisible = Math.max(lastVisible, cp.index)\n } else if (cp.bottom > visibleBottom) {\n // Child is partially visible at bottom — render it (clipped by scroll\n // container's clip bounds) so partial content is visible instead of blank space.\n // When indicatorReserve is active, this child extends past the reserved row,\n // but we still render it — the overflow indicator renders AFTER children and\n // overlays the appropriate row.\n if (firstVisible === -1) firstVisible = cp.index\n lastVisible = cp.index\n // When indicator reserve is active, count partially visible bottom children\n // in hiddenBelow so the indicator shows the correct count.\n if (indicatorReserve > 0) {\n hiddenBelow++\n }\n } else {\n // This child is fully visible within the viewport\n if (firstVisible === -1) firstVisible = cp.index\n lastVisible = cp.index\n }\n }\n\n // Calculate sticky children render positions\n const stickyChildren: NonNullable<AgNode[\"scrollState\"]>[\"stickyChildren\"] = []\n\n for (const cp of childPositions) {\n if (!cp.isSticky) continue\n\n const childHeight = cp.bottom - cp.top\n const stickyTop = cp.stickyTop ?? 0\n const stickyBottom = cp.stickyBottom\n\n // Natural position: where it would be without sticking (relative to viewport)\n const naturalRenderY = cp.top - scrollOffset\n\n let renderOffset: number\n\n if (stickyBottom !== undefined) {\n // Sticky to bottom: element pins to bottom edge when scrolled past\n const bottomPinPosition = viewportHeight - stickyBottom - childHeight\n // Use natural position if it's below the pin point, otherwise pin\n renderOffset = Math.min(naturalRenderY, bottomPinPosition)\n } else if (naturalRenderY >= stickyTop) {\n // Child hasn't reached stick point: use natural position\n renderOffset = naturalRenderY\n } else if (childHeight > viewportHeight) {\n // Oversized sticky-top child scrolled past stick point: progressively\n // scroll the child so its bottom aligns with viewport bottom when\n // scrolled far enough. Clamp between bottom-align and stick point.\n renderOffset = Math.max(viewportHeight - childHeight, naturalRenderY)\n } else {\n // Normal sticky-top child scrolled past stick point: pin at stickyTop\n renderOffset = stickyTop\n }\n\n // Clamp to viewport bounds — only when element is actually sticking.\n // Elements at their natural position below the viewport must NOT be\n // pulled up into view by clamping (that would overwrite other children's\n // pixels, corrupting incremental rendering's buffer shift).\n const isSticking = renderOffset !== naturalRenderY\n if (isSticking) {\n if (childHeight > viewportHeight) {\n renderOffset = Math.max(viewportHeight - childHeight, renderOffset)\n } else {\n renderOffset = Math.max(0, Math.min(renderOffset, viewportHeight - childHeight))\n }\n }\n\n // Skip off-screen sticky children — they're not visible and shouldn't\n // be rendered (would corrupt other children's pixels in the buffer).\n if (renderOffset + childHeight <= 0 || renderOffset >= viewportHeight) continue\n\n stickyChildren.push({\n index: cp.index,\n renderOffset,\n naturalTop: cp.top,\n height: childHeight,\n })\n }\n\n // Skip state updates for fresh render comparisons (SILVERY_STRICT)\n if (skipStateUpdates) return\n\n // Track previous visible range for incremental rendering\n const prevFirstVisible = node.scrollState?.firstVisibleChild ?? firstVisible\n const prevLastVisible = node.scrollState?.lastVisibleChild ?? lastVisible\n\n // Mark node dirty if scroll offset or visible range changed (for incremental rendering)\n // Without this, renderPhase would skip the container and children would\n // remain at their old pixel positions in the cloned buffer\n if (\n scrollOffset !== prevOffset ||\n firstVisible !== prevFirstVisible ||\n lastVisible !== prevLastVisible\n ) {\n const epoch = getRenderEpoch()\n if (node.dirtyEpoch !== epoch) {\n node.dirtyBits = SUBTREE_BIT\n node.dirtyEpoch = epoch\n } else {\n node.dirtyBits |= SUBTREE_BIT\n }\n }\n\n // Store scroll state (preserve previous offset and visible range for incremental rendering)\n node.scrollState = {\n offset: scrollOffset,\n prevOffset: prevOffset ?? scrollOffset,\n contentHeight,\n viewportHeight,\n firstVisibleChild: firstVisible,\n lastVisibleChild: lastVisible,\n prevFirstVisibleChild: prevFirstVisible,\n prevLastVisibleChild: prevLastVisible,\n hiddenAbove,\n hiddenBelow,\n stickyChildren: stickyChildren.length > 0 ? stickyChildren : undefined,\n }\n}\n\n// ============================================================================\n// Phase 2.55: Sticky Phase (for non-scroll containers with sticky children)\n// ============================================================================\n\n/**\n * Compute sticky offsets for non-scroll containers that have sticky children.\n *\n * Scroll containers handle their own sticky logic in calculateScrollState().\n * This phase handles the remaining case: parents that are NOT overflow=\"scroll\"\n * but still contain position=\"sticky\" children with stickyBottom.\n *\n * For non-scroll containers, sticky means: pin the child to the parent's bottom\n * edge when content is shorter than the parent. When content fills the parent,\n * the child stays at its natural position.\n */\nexport function stickyPhase(root: AgNode): void {\n traverseTree(root, (node) => {\n const props = node.props as BoxProps\n // Skip scroll containers — they handle sticky in scrollPhase\n if (props.overflow === \"scroll\") return\n\n // Check if any children are sticky with stickyBottom\n let hasStickyChildren = false\n for (const child of node.children) {\n const childProps = child.props as BoxProps\n if (childProps.position === \"sticky\" && childProps.stickyBottom !== undefined) {\n hasStickyChildren = true\n break\n }\n }\n\n if (!hasStickyChildren) {\n // Clear stale data if previously had sticky children\n if (node.stickyChildren !== undefined) {\n node.stickyChildren = undefined\n const epoch = getRenderEpoch()\n if (node.dirtyEpoch !== epoch) {\n node.dirtyBits = SUBTREE_BIT\n node.dirtyEpoch = epoch\n } else {\n node.dirtyBits |= SUBTREE_BIT\n }\n }\n return\n }\n\n const layout = node.boxRect\n if (!layout || !node.layoutNode) return\n\n const border = props.borderStyle\n ? getBorderSize(props)\n : { top: 0, bottom: 0, left: 0, right: 0 }\n const padding = getPadding(props)\n const parentContentHeight =\n layout.height - border.top - border.bottom - padding.top - padding.bottom\n\n const newStickyChildren: NonNullable<AgNode[\"stickyChildren\"]> = []\n\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i]!\n const childProps = child.props as BoxProps\n if (childProps.position !== \"sticky\") continue\n if (childProps.stickyBottom === undefined) continue\n\n if (!child.boxRect) continue\n\n // Natural position relative to parent content area\n const naturalY = child.boxRect.y - layout.y - border.top - padding.top\n const childHeight = child.boxRect.height\n const stickyBottom = childProps.stickyBottom\n\n // Pin position: where the child would be if pinned to parent bottom\n const bottomPin = parentContentHeight - stickyBottom - childHeight\n // Child pins to bottom when content is short (naturalY < bottomPin)\n // Stays at natural position when content fills parent (naturalY >= bottomPin)\n const renderOffset = Math.max(naturalY, bottomPin)\n\n newStickyChildren.push({\n index: i,\n renderOffset,\n naturalTop: naturalY,\n height: childHeight,\n })\n }\n\n // Compare with previous value to detect changes\n const prev = node.stickyChildren\n const next = newStickyChildren.length > 0 ? newStickyChildren : undefined\n\n const changed = !stickyChildrenEqual(prev, next)\n node.stickyChildren = next\n\n if (changed) {\n const epoch = getRenderEpoch()\n if (node.dirtyEpoch !== epoch) {\n node.dirtyBits = SUBTREE_BIT\n node.dirtyEpoch = epoch\n } else {\n node.dirtyBits |= SUBTREE_BIT\n }\n }\n })\n}\n\n/**\n * Compare two stickyChildren arrays for equality.\n */\nfunction stickyChildrenEqual(a: AgNode[\"stickyChildren\"], b: AgNode[\"stickyChildren\"]): boolean {\n if (a === b) return true\n if (!a || !b) return false\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n const ai = a[i]!\n const bi = b[i]!\n if (\n ai.index !== bi.index ||\n ai.renderOffset !== bi.renderOffset ||\n ai.naturalTop !== bi.naturalTop ||\n ai.height !== bi.height\n ) {\n return false\n }\n }\n return true\n}\n\n/**\n * Traverse tree in depth-first order.\n */\nfunction traverseTree(node: AgNode, callback: (node: AgNode) => void): void {\n callback(node)\n for (const child of node.children) {\n traverseTree(child, callback)\n }\n}\n\n// ============================================================================\n// Phase 2.6: Screen Rect Phase\n// ============================================================================\n\n/**\n * Calculate screen-relative positions for all nodes.\n *\n * This phase runs after scroll phase to compute where each node actually\n * appears on the terminal screen, accounting for all ancestor scroll offsets.\n *\n * Also computes `screenRect` which accounts for sticky render offsets.\n * For non-sticky nodes, screenRect === scrollRect. For sticky nodes,\n * screenRect reflects the actual pixel position where the node is painted.\n *\n * Screen position = content position - sum of ancestor scroll offsets\n */\nexport function scrollrectPhase(root: AgNode): void {\n propagateScrollRect(root, 0)\n}\n\n/**\n * Fast path for scrollrectPhase when no scroll containers or sticky nodes exist.\n *\n * When there are no scroll containers and no sticky nodes, ancestorScrollOffset\n * is always 0, so scrollRect === boxRect and screenRect === scrollRect. This\n * avoids the overhead of accumulating scroll offsets through the tree.\n */\nexport function scrollrectPhaseSimple(root: AgNode): void {\n propagateScrollRectSimple(root)\n}\n\n/**\n * Propagate screen-relative positions through the tree.\n *\n * @param node The node to process\n * @param ancestorScrollOffset Sum of all ancestor scroll offsets\n */\nfunction propagateScrollRect(node: AgNode, ancestorScrollOffset: number): void {\n // Save previous rects for change detection in notifyLayoutSubscribers\n node.prevScrollRect = node.scrollRect\n node.prevScreenRect = node.screenRect\n\n const content = node.boxRect\n if (!content) {\n node.scrollRect = null\n node.screenRect = null\n for (const child of node.children) {\n propagateScrollRect(child, ancestorScrollOffset)\n }\n return\n }\n\n // Compute screen position by subtracting ancestor scroll offsets\n node.scrollRect = {\n x: content.x,\n y: content.y - ancestorScrollOffset,\n width: content.width,\n height: content.height,\n }\n\n // Default: screenRect equals scrollRect (overridden below for sticky nodes)\n node.screenRect = node.scrollRect\n\n // If this node is a scroll container, add its offset for children\n const scrollOffset = node.scrollState?.offset ?? 0\n const childScrollOffset = ancestorScrollOffset + scrollOffset\n\n // Compute screenRect for sticky children.\n // Sticky nodes render at a computed offset instead of their layout position.\n // The offset data lives on the parent (this node) in either scrollState.stickyChildren\n // (for scroll containers) or node.stickyChildren (for non-scroll parents).\n computeStickyScreenRects(node)\n\n // Recurse to children\n for (const child of node.children) {\n propagateScrollRect(child, childScrollOffset)\n }\n}\n\n/**\n * Compute screenRect for sticky children of a node.\n *\n * For sticky children, the actual render position differs from the layout\n * position (scrollRect). The renderOffset from the scroll/sticky phase\n * determines where pixels are actually painted. This function sets\n * screenRect on those children to reflect the true screen position.\n *\n * @param parent The parent node whose sticky children need screenRect computation\n */\nfunction computeStickyScreenRects(parent: AgNode): void {\n // Determine which sticky children list to use\n const stickyList = parent.scrollState?.stickyChildren ?? parent.stickyChildren\n if (!stickyList || stickyList.length === 0) return\n\n // Calculate the parent's content area origin on screen (inside border/padding)\n const parentScrollRect = parent.scrollRect\n if (!parentScrollRect) return\n\n const props = parent.props as BoxProps\n const border = props.borderStyle ? getBorderSize(props) : { top: 0, bottom: 0, left: 0, right: 0 }\n const padding = getPadding(props)\n const contentOriginY = parentScrollRect.y + border.top + padding.top\n\n for (const sticky of stickyList) {\n const child = parent.children[sticky.index]\n if (!child?.scrollRect) continue\n\n // screenRect has the same x, width, height as scrollRect,\n // but Y is adjusted to the sticky render position\n child.screenRect = {\n x: child.scrollRect.x,\n y: contentOriginY + sticky.renderOffset,\n width: child.scrollRect.width,\n height: child.scrollRect.height,\n }\n }\n}\n\n// ============================================================================\n// Simple scrollRect propagation (no scroll/sticky)\n// ============================================================================\n\n/**\n * Simple scrollRect propagation for trees without scroll containers or sticky nodes.\n * When ancestorScrollOffset is always 0, scrollRect === boxRect and screenRect === scrollRect.\n * Saves the overhead of accumulating scroll offsets and computing sticky screen rects.\n */\nfunction propagateScrollRectSimple(node: AgNode): void {\n node.prevScrollRect = node.scrollRect\n node.prevScreenRect = node.screenRect\n\n const content = node.boxRect\n if (!content) {\n node.scrollRect = null\n node.screenRect = null\n for (const child of node.children) {\n propagateScrollRectSimple(child)\n }\n return\n }\n\n // No scroll offset — scrollRect equals boxRect\n node.scrollRect = {\n x: content.x,\n y: content.y,\n width: content.width,\n height: content.height,\n }\n node.screenRect = node.scrollRect\n\n for (const child of node.children) {\n propagateScrollRectSimple(child)\n }\n}\n\n// ============================================================================\n// Feature Detection\n// ============================================================================\n\n/**\n * Pipeline feature flags — tracks which optional phases the tree needs.\n *\n * Flags are one-way: once set to true, they stay true for the lifetime\n * of the Ag instance. This ensures that if a component dynamically mounts\n * a scroll container or sticky child, the phase starts running immediately\n * and never gets skipped again.\n */\nexport interface PipelineFeatures {\n /** Tree contains at least one `overflow=\"scroll\"` node. */\n hasScroll: boolean\n /** Tree contains at least one `position=\"sticky\"` node. */\n hasSticky: boolean\n}\n\n/**\n * Scan the tree for features that require optional pipeline phases.\n *\n * Returns feature flags. This is called on every layout pass so newly\n * mounted components are detected. The caller should merge flags with\n * one-way semantics (false → true, never true → false).\n */\nexport function detectPipelineFeatures(root: AgNode): PipelineFeatures {\n let hasScroll = false\n let hasSticky = false\n\n function scan(node: AgNode): void {\n const props = node.props as BoxProps\n if (props.overflow === \"scroll\") hasScroll = true\n if (props.position === \"sticky\") hasSticky = true\n // Early exit if both features detected\n if (hasScroll && hasSticky) return\n for (const child of node.children) {\n scan(child)\n if (hasScroll && hasSticky) return\n }\n }\n\n scan(root)\n return { hasScroll, hasSticky }\n}\n","/**\n * Active theme state — module-level fallback for pipeline access.\n *\n * Theme flows through the AgNode tree via `<Box theme={}>` props (set by\n * ThemeProvider in @silvery/ag-react) and the pushContextTheme/popContextTheme\n * cascade in render-phase.ts. `getActiveTheme()` reads the nearest stack entry,\n * falling back to ansi16DarkTheme for code paths that render without a\n * ThemeProvider (bare tests, xterm renderer before wrap).\n *\n * Usage of standalone resolveThemeColor(token, theme) is preferred for callers\n * that have a Theme reference available.\n *\n * `setActiveTheme()` was removed in R2 (km-silvery.theme-v3-r2-agnode-cascade);\n * the no-op stub is gone too. Callers should wrap in ThemeProvider.\n */\n\nimport type { Theme } from \"@silvery/ansi\"\n// The `@silvery/theme` re-export of ansi16DarkTheme is pre-populated with\n// Sterling flat tokens; `@silvery/ansi`'s is not. Bare-test render paths need\n// the flat tokens to resolve `$fg-accent` / `$bg-surface-subtle` etc.\nimport { ansi16DarkTheme } from \"@silvery/theme\"\n\n// ============================================================================\n// Active Theme (fallback only — not set by ThemeProvider)\n// ============================================================================\n\n/**\n * Safe fallback theme. Never mutated — the theme flows via the AgNode tree\n * (Box theme= prop + pushContextTheme/popContextTheme in render-phase.ts).\n * This is only returned by getActiveTheme() when called from a code path that\n * has no pushContextTheme frame on the stack, e.g. a bare test that renders\n * without ThemeProvider.\n *\n * `@silvery/theme`'s `ansi16DarkTheme` ships with Sterling flat tokens baked\n * in, so bare-test render paths resolve `$fg-accent` / `$bg-surface-subtle` /\n * etc. without needing an explicit ThemeProvider.\n */\nconst _activeTheme: Theme = ansi16DarkTheme\n\n/** Get the active theme (fallback to ansi16DarkTheme when no context stack entry exists). */\nexport function getActiveTheme(): Theme {\n return _contextStack.length > 0 ? _contextStack[_contextStack.length - 1]! : _activeTheme\n}\n\n// ============================================================================\n// Active Color Level (tier dispatch)\n// ============================================================================\n\n/**\n * Color tier the render pipeline is targeting.\n *\n * Mirrors `TerminalCaps.colorLevel` but lives in module state for the\n * render-helpers parseColor() / getTextStyle() functions, which don't have\n * access to the OutputContext or React props. Set by the runtime\n * (`createPipeline()` in `@silvery/ag-term/measurer.ts`) before the first\n * render, and updated on cap changes.\n *\n * At `\"none\"` (monochrome), `parseColor(\"$primary\")` returns `null` and\n * `getTextStyle()` injects mono-attrs (bold, dim, italic, underline, inverse,\n * strikethrough) from `DEFAULT_MONO_ATTRS`. See `hub/silvery/design/v10-terminal/theme-system-v2-plan.md#p4`.\n */\nexport type ActiveColorLevel = \"none\" | \"basic\" | \"256\" | \"truecolor\"\n\nlet _activeColorLevel: ActiveColorLevel = \"truecolor\"\n\n/** Set the active color level (called by the runtime based on TerminalCaps). */\nexport function setActiveColorLevel(level: ActiveColorLevel): void {\n _activeColorLevel = level\n}\n\n/** Get the active color level (called by parseColor / getTextStyle in render-helpers). */\nexport function getActiveColorLevel(): ActiveColorLevel {\n return _activeColorLevel\n}\n\n// ============================================================================\n// Context Theme Stack (per-subtree overrides during render phase)\n// ============================================================================\n\n/**\n * Stack of per-subtree theme overrides, pushed/popped during render phase\n * tree walk. When a Box has a `theme` prop, its theme is pushed before\n * rendering children and popped after. getActiveTheme() checks this stack\n * first, falling back to _activeTheme.\n *\n * This enables CSS custom property-like cascading: the nearest ancestor\n * Box with a theme prop determines $token resolution for its subtree.\n * ThemeProvider (in @silvery/ag-react) renders a <Box theme={merged}>\n * wrapper, so its theme is naturally pushed via this mechanism.\n */\nconst _contextStack: Theme[] = []\n\n/** Push a context theme (called by render phase for Box nodes with theme prop). */\nexport function pushContextTheme(theme: Theme): void {\n _contextStack.push(theme)\n}\n\n/** Pop a context theme (called by render phase after processing Box subtree). */\nexport function popContextTheme(): void {\n _contextStack.pop()\n}\n","/**\n * Render Helpers - Pure utility functions for content rendering.\n *\n * Contains:\n * - Color parsing (parseColor)\n * - Border character definitions (getBorderChars)\n * - Style extraction (getTextStyle)\n * - Text width utilities (getTextWidth)\n *\n * Re-exports layout helpers from helpers.ts:\n * - getPadding, getBorderSize\n */\n\nimport { DEFAULT_BG, type Color, type Style, type UnderlineStyle } from \"../buffer\"\nimport { getActiveColorLevel, getActiveTheme } from \"./state\"\nimport { resolveThemeColor } from \"@silvery/ansi\"\nimport { monoAttrsForColorString, type MonoAttr } from \"@silvery/ansi\"\nimport type { BoxProps, TextProps } from \"@silvery/ag/types\"\nimport { displayWidthAnsi } from \"../unicode\"\nimport type { BorderChars, PipelineContext } from \"./types\"\n\n// Re-export shared layout helpers\nexport { getBorderSize, getPadding } from \"./helpers\"\n\n// ============================================================================\n// Color Parsing\n// ============================================================================\n\n// Named colors map to 256-color indices (hoisted to module scope to avoid per-call allocation)\nconst namedColors: Record<string, number> = {\n black: 0,\n red: 1,\n green: 2,\n yellow: 3,\n blue: 4,\n magenta: 5,\n cyan: 6,\n white: 7,\n gray: 8,\n grey: 8,\n blackBright: 8,\n redBright: 9,\n greenBright: 10,\n yellowBright: 11,\n blueBright: 12,\n magentaBright: 13,\n cyanBright: 14,\n whiteBright: 15,\n}\n\n/**\n * Blend two RGB colors in sRGB space.\n * Formula: result = c1 * (1 - t) + c2 * t, where t is 0..1.\n * Returns an RGB object with each channel clamped to 0-255.\n */\nfunction blendColors(\n c1: { r: number; g: number; b: number },\n c2: { r: number; g: number; b: number },\n t: number,\n): { r: number; g: number; b: number } {\n return {\n r: Math.round(c1.r * (1 - t) + c2.r * t),\n g: Math.round(c1.g * (1 - t) + c2.g * t),\n b: Math.round(c1.b * (1 - t) + c2.b * t),\n }\n}\n\n/**\n * Parse color string to Color type.\n * Supports: mix(c1,c2,amount), $token (theme), named colors, hex (#rgb, #rrggbb), rgb(r,g,b)\n */\nexport function parseColor(color: string): Color {\n // Inherit: no color — parent's color flows through (like CSS color: inherit).\n // \"currentColor\" is a CSS synonym — both keywords resolve identically here.\n // For child-cascade purposes, render-phase detects these keywords directly\n // (before parseColor) so the parent's inheritedFg is preserved in children.\n if (color === \"inherit\" || color === \"currentColor\") return null\n\n // Special token: terminal's default background (SGR 49)\n if (color === \"$default\") return DEFAULT_BG\n\n // Mix: blend two colors — mix(color1, color2, amount)\n // Amount can be a percentage (e.g. 50%) or a decimal (e.g. 0.5).\n // Both colors are recursively resolved via parseColor (supports theme tokens, hex, named, etc.).\n // Only blends when both colors resolve to RGB objects; returns null if either is null or an ANSI index.\n if (color.startsWith(\"mix(\") && color.endsWith(\")\")) {\n const inner = color.slice(4, -1)\n // Split on commas, but respect nested parentheses (e.g. rgb(r,g,b) as an argument)\n const args: string[] = []\n let depth = 0\n let start = 0\n for (let i = 0; i < inner.length; i++) {\n if (inner[i] === \"(\") depth++\n else if (inner[i] === \")\") depth--\n else if (inner[i] === \",\" && depth === 0) {\n args.push(inner.slice(start, i).trim())\n start = i + 1\n }\n }\n args.push(inner.slice(start).trim())\n\n if (args.length === 3) {\n const c1 = parseColor(args[0]!)\n const c2 = parseColor(args[1]!)\n const amountStr = args[2]!\n\n // Parse amount: percentage (e.g. \"50%\") or decimal (e.g. \"0.5\")\n let t: number\n if (amountStr.endsWith(\"%\")) {\n t = Number.parseFloat(amountStr.slice(0, -1)) / 100\n } else {\n t = Number.parseFloat(amountStr)\n }\n\n // Only blend RGB objects; ANSI indices (number) and null cannot be blended\n if (\n c1 !== null &&\n c2 !== null &&\n typeof c1 === \"object\" &&\n typeof c2 === \"object\" &&\n !Number.isNaN(t)\n ) {\n return blendColors(c1, c2, Math.max(0, Math.min(1, t)))\n }\n return null\n }\n }\n\n // Future: slash notation for background opacity (e.g. \"$link/10\") is not yet supported.\n // It would require richer return types to carry opacity alongside the base color.\n\n // Resolve $token colors against the active theme\n if (color.startsWith(\"$\")) {\n // At monochrome tier, strip all token-resolved colors. Hierarchy is carried\n // by per-token SGR attrs (see getTextStyle → monoAttrsForColorString). The\n // output phase sees `null` and emits SGR 39/49 (terminal default), never\n // an RGB sequence.\n if (getActiveColorLevel() === \"none\") return null\n const resolved = resolveThemeColor(color, getActiveTheme())\n if (resolved && resolved !== color) return parseColor(resolved)\n return null\n }\n\n if (color in namedColors) {\n return namedColors[color as keyof typeof namedColors]!\n }\n\n // Hex color\n if (color.startsWith(\"#\")) {\n const hex = color.slice(1)\n if (hex.length === 3) {\n const r = Number.parseInt(hex[0]! + hex[0]!, 16)\n const g = Number.parseInt(hex[1]! + hex[1]!, 16)\n const b = Number.parseInt(hex[2]! + hex[2]!, 16)\n return { r, g, b }\n }\n if (hex.length === 6) {\n const r = Number.parseInt(hex.slice(0, 2), 16)\n const g = Number.parseInt(hex.slice(2, 4), 16)\n const b = Number.parseInt(hex.slice(4, 6), 16)\n return { r, g, b }\n }\n }\n\n // rgb(r,g,b)\n const rgbMatch = color.match(/^rgb\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)$/i)\n if (rgbMatch) {\n return {\n r: Number.parseInt(rgbMatch[1]!, 10),\n g: Number.parseInt(rgbMatch[2]!, 10),\n b: Number.parseInt(rgbMatch[3]!, 10),\n }\n }\n\n // ansi256(N) — 256-color palette index (0-255)\n const ansi256Match = color.match(/^ansi256\\s*\\(\\s*(\\d+)\\s*\\)$/i)\n if (ansi256Match) {\n return Number.parseInt(ansi256Match[1]!, 10)\n }\n\n return null\n}\n\n// ============================================================================\n// Border Characters\n// ============================================================================\n\n/**\n * Border character sets by style. Hoisted to module scope to avoid\n * re-allocating 7 objects on every call.\n */\nconst borders: Record<NonNullable<BoxProps[\"borderStyle\"]>, BorderChars> = {\n single: {\n topLeft: \"\\u250c\",\n topRight: \"\\u2510\",\n bottomLeft: \"\\u2514\",\n bottomRight: \"\\u2518\",\n horizontal: \"\\u2500\",\n vertical: \"\\u2502\",\n },\n double: {\n topLeft: \"\\u2554\",\n topRight: \"\\u2557\",\n bottomLeft: \"\\u255a\",\n bottomRight: \"\\u255d\",\n horizontal: \"\\u2550\",\n vertical: \"\\u2551\",\n },\n round: {\n topLeft: \"\\u256d\",\n topRight: \"\\u256e\",\n bottomLeft: \"\\u2570\",\n bottomRight: \"\\u256f\",\n horizontal: \"\\u2500\",\n vertical: \"\\u2502\",\n },\n bold: {\n topLeft: \"\\u250f\",\n topRight: \"\\u2513\",\n bottomLeft: \"\\u2517\",\n bottomRight: \"\\u251b\",\n horizontal: \"\\u2501\",\n vertical: \"\\u2503\",\n },\n singleDouble: {\n topLeft: \"\\u2553\",\n topRight: \"\\u2556\",\n bottomLeft: \"\\u2559\",\n bottomRight: \"\\u255c\",\n horizontal: \"\\u2500\",\n vertical: \"\\u2551\",\n },\n doubleSingle: {\n topLeft: \"\\u2552\",\n topRight: \"\\u2555\",\n bottomLeft: \"\\u2558\",\n bottomRight: \"\\u255b\",\n horizontal: \"\\u2550\",\n vertical: \"\\u2502\",\n },\n classic: {\n topLeft: \"+\",\n topRight: \"+\",\n bottomLeft: \"+\",\n bottomRight: \"+\",\n horizontal: \"-\",\n vertical: \"|\",\n },\n}\n\n/**\n * Get border characters for a style.\n */\nexport function getBorderChars(style: BoxProps[\"borderStyle\"]): BorderChars {\n if (style && typeof style === \"object\") {\n // Custom border object (Ink compat): map Ink's top/bottom/left/right to\n // silvery's horizontal/vertical format. Supports distinct chars per side.\n const obj = style as Record<string, string>\n const topHorizontal = obj.top ?? obj.horizontal ?? \"-\"\n const leftVertical = obj.left ?? obj.vertical ?? \"|\"\n return {\n topLeft: obj.topLeft ?? \"+\",\n topRight: obj.topRight ?? \"+\",\n bottomLeft: obj.bottomLeft ?? \"+\",\n bottomRight: obj.bottomRight ?? \"+\",\n horizontal: topHorizontal,\n vertical: leftVertical,\n bottomHorizontal: obj.bottom && obj.bottom !== topHorizontal ? obj.bottom : undefined,\n rightVertical: obj.right && obj.right !== leftVertical ? obj.right : undefined,\n }\n }\n return borders[style ?? \"single\"]\n}\n\n// ============================================================================\n// Style Extraction\n// ============================================================================\n\n/**\n * Collect monochrome attrs from a color string (`\"$primary\"` → `[\"bold\"]`).\n *\n * At mono tier, `parseColor` strips the color (returns `null`). The hierarchy\n * signal lives in the attrs bag. This helper merges the mapped attrs from\n * `DEFAULT_MONO_ATTRS` into a mutable accumulator. Called per color-carrying\n * prop in `getTextStyle`.\n *\n * No-op when the color is not a `$token` — non-token hex / named colors\n * pass through with no attrs (spec: \"apps that hardcoded #FF0000 get nothing\").\n */\nfunction collectMonoAttrs(color: string | undefined, into: Set<MonoAttr>): void {\n if (!color) return\n const attrs = monoAttrsForColorString(color, getActiveTheme())\n if (!attrs) return\n for (const a of attrs) into.add(a)\n}\n\n/**\n * Get text style from props.\n */\nexport function getTextStyle(props: TextProps): Style {\n // Determine underline style: underlineStyle takes precedence over underline boolean\n let underlineStyle: UnderlineStyle | undefined\n if (props.underlineStyle !== undefined) {\n underlineStyle = props.underlineStyle\n } else if (props.underline) {\n underlineStyle = \"single\"\n }\n\n // Start with the user-specified attrs.\n let bold = props.bold\n let dim = props.dim || props.dimColor // dimColor is Ink compatibility alias\n let italic = props.italic\n let underline = props.underline || !!underlineStyle\n let strikethrough = props.strikethrough\n let inverse = props.inverse\n\n // Monochrome tier: inject per-token SGR attrs from DEFAULT_MONO_ATTRS. Colors\n // are stripped by parseColor (returns null for $tokens at mono tier). The\n // attrs carry the hierarchy: $primary → bold, $muted → dim, $error →\n // bold+inverse, $link → underline, etc. User-supplied attrs always OR-in.\n if (getActiveColorLevel() === \"none\") {\n const monoAttrs = new Set<MonoAttr>()\n collectMonoAttrs(props.color, monoAttrs)\n collectMonoAttrs(props.backgroundColor, monoAttrs)\n if (monoAttrs.has(\"bold\")) bold = true\n if (monoAttrs.has(\"dim\")) dim = true\n if (monoAttrs.has(\"italic\")) italic = true\n if (monoAttrs.has(\"underline\")) {\n underline = true\n if (!underlineStyle) underlineStyle = \"single\"\n }\n if (monoAttrs.has(\"strikethrough\")) strikethrough = true\n if (monoAttrs.has(\"inverse\")) inverse = true\n }\n\n return {\n fg: props.color ? parseColor(props.color) : null,\n bg: props.backgroundColor ? parseColor(props.backgroundColor) : null,\n underlineColor: props.underlineColor ? parseColor(props.underlineColor) : null,\n attrs: {\n bold,\n dim,\n italic,\n underline,\n underlineStyle,\n strikethrough,\n inverse,\n },\n }\n}\n\n// ============================================================================\n// Text Width Utilities\n// ============================================================================\n\n/**\n * Get text display width (accounting for wide characters and ANSI codes).\n * Uses ANSI-aware width calculation to handle styled text.\n *\n * When a PipelineContext is provided, uses the context's measurer for\n * terminal-capability-aware width calculation. Falls back to the module-level\n * displayWidthAnsi (which reads the scoped measurer or default).\n */\nexport function getTextWidth(text: string, ctx?: PipelineContext): number {\n if (ctx) return ctx.measurer.displayWidthAnsi(text)\n return displayWidthAnsi(text)\n}\n","/**\n * Text Rendering - Functions for rendering text content to the buffer.\n *\n * Contains:\n * - ANSI text line rendering (renderAnsiTextLine)\n * - Plain text line rendering (renderTextLine)\n * - Text formatting (formatTextLines)\n * - Text truncation (truncateText)\n * - Text content collection (collectTextContent)\n */\n\nimport {\n type CellAttrs,\n type Color,\n type Style,\n type TerminalBuffer,\n type UnderlineStyle,\n createMutableCell,\n} from \"../buffer\"\nimport type { AgNode, TextProps } from \"@silvery/ag/types\"\nimport {\n type StyledSegment,\n ensureEmojiPresentation,\n graphemeWidth,\n hasAnsi,\n parseAnsiText,\n sliceByWidth,\n sliceByWidthFromEnd,\n splitGraphemes,\n wrapText,\n} from \"../unicode\"\nimport { collectPlainText } from \"./collect-text\"\nimport { getTextStyle, getTextWidth, parseColor } from \"./render-helpers\"\nimport { getActiveTheme } from \"./state\"\nimport {\n getCachedPlainText,\n setCachedPlainText,\n getCachedCollectedText,\n setCachedCollectedText,\n getCachedFormat,\n setCachedFormat,\n getCachedAnalysis,\n setCachedAnalysis,\n} from \"./prepared-text\"\nimport { buildTextAnalysis, balancedWidth as computeBalancedWidth, optimalWrap } from \"./pretext\"\nimport type { BgConflictMode, NodeRenderState, PipelineContext } from \"./types\"\nimport { createLogger } from \"loggily\"\n\nconst log = createLogger(\"silvery:content\")\n\n// ============================================================================\n// Background Conflict Detection\n// ============================================================================\n\n/** Cached bg conflict mode. Read from env once at module load. */\nlet bgConflictMode: BgConflictMode = (() => {\n const env =\n typeof process !== \"undefined\" ? process.env.SILVERY_BG_CONFLICT?.toLowerCase() : undefined\n if (env === \"ignore\" || env === \"warn\" || env === \"throw\") return env\n return \"throw\" // default - fail fast on programming errors\n})()\n\n/**\n * Get the current background conflict detection mode.\n */\nfunction getBgConflictMode(): BgConflictMode {\n return bgConflictMode\n}\n\n/**\n * Set the background conflict detection mode. For tests.\n */\nexport function setBgConflictMode(mode: BgConflictMode): void {\n bgConflictMode = mode\n}\n\n// Track warned conflicts to avoid spam (only used in 'warn' mode)\nconst warnedBgConflicts = new Set<string>()\n\n/** Format a Color value for bg conflict diagnostics */\nfunction formatBgConflictColor(\n c: number | { r: number; g: number; b: number } | null | undefined,\n): string {\n if (c === null || c === undefined) return \"none\"\n if (typeof c === \"number\") {\n // Packed RGB (0x1000000 marker) or ANSI palette index\n if (c & 0x1000000) {\n const r = (c >> 16) & 0xff\n const g = (c >> 8) & 0xff\n const b = c & 0xff\n return `#${r.toString(16).padStart(2, \"0\")}${g.toString(16).padStart(2, \"0\")}${b.toString(16).padStart(2, \"0\")}`\n }\n // Map SGR codes to names for readability\n const names: Record<number, string> = {\n 40: \"black\",\n 41: \"red\",\n 42: \"green\",\n 43: \"yellow\",\n 44: \"blue\",\n 45: \"magenta\",\n 46: \"cyan\",\n 47: \"white\",\n 100: \"brightBlack\",\n 101: \"brightRed\",\n 102: \"brightGreen\",\n 103: \"brightYellow\",\n 104: \"brightBlue\",\n 105: \"brightMagenta\",\n 106: \"brightCyan\",\n 107: \"brightWhite\",\n }\n return names[c] ?? `palette(${c})`\n }\n return `rgb(${c.r},${c.g},${c.b})`\n}\n\n/**\n * Clear the background conflict warning cache.\n * Call this at the start of each render cycle to:\n * - Prevent memory leaks in long-running apps\n * - Allow warnings to repeat after user fixes issues\n */\nexport function clearBgConflictWarnings(): void {\n warnedBgConflicts.clear()\n}\n\n// ============================================================================\n// Text Content Collection\n// ============================================================================\n\n/**\n * Style context for nested Text elements.\n * Tracks cumulative styles through the tree to enable proper push/pop behavior.\n */\ninterface StyleContext {\n color?: string\n backgroundColor?: string\n bold?: boolean\n dim?: boolean\n italic?: boolean\n underline?: boolean\n underlineStyle?: string | false\n underlineColor?: string\n inverse?: boolean\n strikethrough?: boolean\n}\n\n/**\n * Build ANSI escape sequence for a style context.\n *\n * Note: backgroundColor is intentionally NOT embedded as ANSI codes.\n * Background color is handled at the buffer level (via BgSegment tracking)\n * to prevent bg bleed across wrapped text lines. See km-silvery.bg-bleed.\n */\nfunction styleToAnsi(style: StyleContext): string {\n const parts: string[] = []\n\n // Foreground color - use parseColor directly instead of roundtripping through getTextStyle\n if (style.color) {\n const color = parseColor(style.color)\n if (color !== null) {\n if (typeof color === \"number\") {\n parts.push(`38;5;${color}`)\n } else {\n parts.push(`38;2;${color.r};${color.g};${color.b}`)\n }\n }\n }\n\n // backgroundColor is NOT embedded here - it is tracked separately via\n // BgSegment and applied at the buffer level in renderText(). This prevents\n // bg color from bleeding across wrapped lines. See collectTextWithBg().\n\n // Attributes\n if (style.bold) parts.push(\"1\")\n if (style.dim) parts.push(\"2\")\n if (style.italic) parts.push(\"3\")\n // Underline: prefer underlineStyle (SGR 4:x subparam) over boolean (SGR 4)\n if (style.underlineStyle) {\n const styleMap: Record<string, string> = {\n single: \"4:1\",\n double: \"4:2\",\n curly: \"4:3\",\n dotted: \"4:4\",\n dashed: \"4:5\",\n }\n parts.push(styleMap[style.underlineStyle] ?? \"4\")\n } else if (style.underline) {\n parts.push(\"4\")\n }\n // Underline color (SGR 58;5;N or 58;2;r;g;b).\n // \"currentColor\"/\"inherit\" → resolve to the merged fg (style.color), so the\n // underline tracks whatever color the surrounding text ended up as.\n if (style.underlineColor) {\n const underlineSource =\n style.underlineColor === \"currentColor\" || style.underlineColor === \"inherit\"\n ? style.color\n : style.underlineColor\n if (underlineSource) {\n const ulColor = parseColor(underlineSource)\n if (ulColor !== null) {\n if (typeof ulColor === \"number\") {\n parts.push(`58;5;${ulColor}`)\n } else {\n parts.push(`58;2;${ulColor.r};${ulColor.g};${ulColor.b}`)\n }\n }\n }\n }\n if (style.inverse) parts.push(\"7\")\n if (style.strikethrough) parts.push(\"9\")\n\n if (parts.length === 0) {\n return \"\"\n }\n\n return `\\x1b[${parts.join(\";\")}m`\n}\n\n/**\n * Merge child props into parent context.\n * Child values override parent values when specified.\n */\nfunction mergeStyleContext(parent: StyleContext, childProps: TextProps): StyleContext {\n // color=\"inherit\"/\"currentColor\" on a virtual-text child is a pass-through:\n // the child's effective color is whatever the parent already resolved to.\n // Without this, nested <Text color=\"inherit\"> clobbered the merged context\n // with the raw keyword, which styleToAnsi then mapped to null (no fg SGR).\n const isInheritKeyword = childProps.color === \"inherit\" || childProps.color === \"currentColor\"\n const effectiveChildColor = isInheritKeyword ? parent.color : childProps.color\n return {\n color: effectiveChildColor ?? parent.color,\n backgroundColor: childProps.backgroundColor ?? parent.backgroundColor,\n bold: childProps.bold ?? parent.bold,\n dim: childProps.dim ?? childProps.dimColor ?? parent.dim,\n italic: childProps.italic ?? parent.italic,\n underline:\n (childProps.underline ?? (childProps as any).underlineStyle) ? true : parent.underline,\n underlineStyle: (childProps as any).underlineStyle ?? parent.underlineStyle,\n underlineColor: (childProps as any).underlineColor ?? parent.underlineColor,\n inverse: childProps.inverse ?? parent.inverse,\n strikethrough: childProps.strikethrough ?? parent.strikethrough,\n }\n}\n\n/**\n * Apply text styles as ANSI escape codes with proper push/pop behavior.\n * After the child text, restores the parent context's styles.\n *\n * @param text - The text content to wrap\n * @param childStyle - The merged style for this child (child overrides parent)\n * @param parentStyle - The parent's style context to restore after\n */\nfunction applyTextStyleAnsi(\n text: string,\n childStyle: StyleContext,\n parentStyle: StyleContext,\n): string {\n if (!text) {\n return text\n }\n\n const childAnsi = styleToAnsi(childStyle)\n const parentAnsi = styleToAnsi(parentStyle)\n\n // If child has no style changes, just return text\n if (!childAnsi) {\n return text\n }\n\n // Apply child style, then reset and re-apply parent style\n // We use \\x1b[0m to reset, then re-apply parent styles\n return `${childAnsi}${text}\\x1b[0m${parentAnsi}`\n}\n\n/**\n * Recursively collect text content from a node and its children.\n * Handles both raw text nodes (textContent set directly) and\n * Text component wrappers (text in children).\n *\n * For nested Text nodes with style props (color, bold, etc.),\n * applies ANSI codes so the styles are preserved when rendered.\n * Uses a style stack to properly restore parent styles after nested elements.\n *\n * @param node - The node to collect text from\n * @param parentContext - The inherited style context from parent (used for restoration)\n */\nexport function collectTextContent(node: AgNode, parentContext: StyleContext = {}): string {\n // If this node has direct text content, return it\n if (node.textContent !== undefined) {\n return node.textContent\n }\n\n // Otherwise, collect from children\n // Matching Ink's squashTextNodes: apply internal_transform to the full text\n // of each child node (not per-line), using the child index as the index argument.\n let result = \"\"\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i]!\n // If child is a Text node (virtual/nested) with style props, apply ANSI codes\n if (child.type === \"silvery-text\" && child.props && !child.layoutNode) {\n const childProps = child.props as TextProps\n // Merge child props with parent context to get effective child style\n const childContext = mergeStyleContext(parentContext, childProps)\n // Recursively collect with child's context\n let childContent = collectTextContent(child, childContext)\n // Apply internal_transform from virtual text nodes (nested Transform components).\n // Matches Ink's squashTextNodes: transform is applied to the full concatenated\n // text of the child, with index = child position in parent's children array.\n const childTransform = (childProps as any).internal_transform\n if (childTransform && childContent.length > 0) {\n childContent = childTransform(childContent, i)\n }\n // Apply styles with proper push/pop (child style, then restore parent)\n result += applyTextStyleAnsi(childContent, childContext, parentContext)\n } else {\n // Not a styled Text node, just collect recursively\n result += collectTextContent(child, parentContext)\n }\n }\n return result\n}\n\n// ============================================================================\n// Background Segment Tracking\n// ============================================================================\n\n/**\n * A background color segment in collected text.\n * Tracks which character range has which background color,\n * independent of ANSI codes. Used to apply bg at the buffer level\n * after text wrapping, preventing bg bleed across wrapped lines.\n */\ninterface BgSegment {\n /** Start character offset in the collected text (inclusive) */\n start: number\n /** End character offset in the collected text (exclusive) */\n end: number\n /** Background color to apply */\n bg: Color\n}\n\n/**\n * A span mapping a virtual text child node to its character range.\n * Used to compute inlineRects for hit testing on nested Text.\n */\ninterface ChildSpan {\n /** The virtual text node */\n node: AgNode\n /** Start display-width offset in the collected text (inclusive) */\n start: number\n /** End display-width offset in the collected text (exclusive) */\n end: number\n}\n\n/**\n * Result of collecting text with background segments.\n */\ninterface TextWithBg {\n /** The collected text string (with ANSI codes for fg/attrs, but NOT bg) */\n text: string\n /** Background color segments from nested Text elements */\n bgSegments: BgSegment[]\n /** Spans mapping virtual text children to display-width ranges */\n childSpans: ChildSpan[]\n /** Plain text character count (excluding ANSI codes). Used for DOM-level budget tracking. */\n plainLen: number\n}\n\n// collectPlainText is imported from ./collect-text.\n// Previously duplicated here; now shared across measure-phase, render-text,\n// and the reconciler's measure function.\n\n/**\n * Collect text content and background color segments from a node tree.\n *\n * Like collectTextContent, but also tracks backgroundColor from nested Text\n * elements as separate BgSegment entries. Background is NOT embedded as ANSI\n * codes, preventing bg bleed when text wraps across lines.\n *\n * @param node - The node to collect text from\n * @param parentContext - The inherited style context from parent\n * @param offset - Current character offset in the collected text (for bg tracking)\n * @param maxDisplayWidth - Maximum display width (columns) to collect. When set,\n * stops collecting once this many display columns of content have been gathered.\n * This truncates at the DOM level BEFORE ANSI serialization, so escape sequences\n * (OSC 8, etc.) are never generated for content that won't be displayed.\n * Uses getTextWidth (ANSI-aware) so pre-styled leaf text is handled correctly.\n */\nfunction collectTextWithBg(\n node: AgNode,\n parentContext: StyleContext = {},\n offset = 0,\n maxDisplayWidth?: number,\n ctx?: PipelineContext,\n): TextWithBg {\n // If this node has direct text content, return it with no bg segments\n if (node.textContent !== undefined) {\n let text = node.textContent\n // DOM-level truncation: trim leaf text to display width budget\n if (maxDisplayWidth !== undefined) {\n const textW = getTextWidth(text, ctx)\n if (textW > maxDisplayWidth) {\n const sliceFn = ctx ? ctx.measurer.sliceByWidth : sliceByWidth\n text = sliceFn(text, maxDisplayWidth)\n }\n }\n // plainLen tracks display width for budget and BgSegment offset tracking.\n // Both use display-width coordinates consistently: collectTextWithBg uses\n // getTextWidth for offsets, mapLinesToCharOffsets returns display-width,\n // and applyBgSegmentsToLine compares via display-width (col - x).\n const plainLen = getTextWidth(text, ctx)\n return { text, bgSegments: [], childSpans: [], plainLen }\n }\n\n let result = \"\"\n const bgSegments: BgSegment[] = []\n const childSpans: ChildSpan[] = []\n let currentOffset = offset\n let displayWidthCollected = 0\n\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i]!\n // Stop collecting if budget exhausted\n if (maxDisplayWidth !== undefined && displayWidthCollected >= maxDisplayWidth) break\n\n // Compute remaining budget for this child\n const childBudget =\n maxDisplayWidth !== undefined ? maxDisplayWidth - displayWidthCollected : undefined\n\n if (child.type === \"silvery-text\" && child.props && !child.layoutNode) {\n const childProps = child.props as TextProps\n const childContext = mergeStyleContext(parentContext, childProps)\n\n // Recursively collect with child's context and budget\n const childResult = collectTextWithBg(child, childContext, currentOffset, childBudget, ctx)\n\n // Apply internal_transform from virtual text nodes (nested Transform components).\n // Matches Ink's squashTextNodes: transform is applied to the full concatenated\n // text of the child, with index = child position in parent's children array.\n const childTransform = (childProps as any).internal_transform\n if (childTransform && childResult.text.length > 0) {\n childResult.text = childTransform(childResult.text, i)\n }\n\n // Apply ANSI styles for fg/attrs (but NOT bg) with push/pop\n const styledText = applyTextStyleAnsi(childResult.text, childContext, parentContext)\n result += styledText\n\n // Track bg segment if this child (or its ancestors) has backgroundColor.\n // When backgroundColor is \"\" (empty string), create a null-bg segment to\n // explicitly clear inherited background (e.g., from a parent Box).\n if (childContext.backgroundColor) {\n const bg = parseColor(childContext.backgroundColor)\n if (bg !== null) {\n if (childResult.plainLen > 0) {\n bgSegments.push({\n start: currentOffset,\n end: currentOffset + childResult.plainLen,\n bg,\n })\n }\n }\n } else if (childProps.backgroundColor === \"\" && childResult.plainLen > 0) {\n // Explicit backgroundColor=\"\" clears inherited bg (from parent Text\n // or ancestor Box's inheritedBg). Push a null-bg segment so\n // applyBgSegmentsToLine overrides inheritedBg to null for this range.\n bgSegments.push({\n start: currentOffset,\n end: currentOffset + childResult.plainLen,\n bg: null,\n })\n }\n\n // Track child span for inlineRects computation\n if (childResult.plainLen > 0) {\n childSpans.push({\n node: child,\n start: currentOffset,\n end: currentOffset + childResult.plainLen,\n })\n }\n\n // Include child's nested bg segments and child spans\n bgSegments.push(...childResult.bgSegments)\n childSpans.push(...childResult.childSpans)\n\n // Track using plainLen (display width) — not text.length which includes ANSI codes\n currentOffset += childResult.plainLen\n displayWidthCollected += childResult.plainLen\n } else {\n // Not a styled Text node, just collect recursively\n const childResult = collectTextWithBg(child, parentContext, currentOffset, childBudget, ctx)\n result += childResult.text\n bgSegments.push(...childResult.bgSegments)\n childSpans.push(...childResult.childSpans)\n currentOffset += childResult.plainLen\n displayWidthCollected += childResult.plainLen\n }\n }\n\n return { text: result, bgSegments, childSpans, plainLen: displayWidthCollected }\n}\n\n/**\n * Apply background segments to buffer cells for a single rendered line.\n *\n * Maps character offsets from the original collected text to screen positions,\n * accounting for text wrapping. Each bg segment fills only the cells that\n * correspond to actual text characters, not trailing whitespace.\n *\n * @param buffer - The terminal buffer to write to\n * @param x - Screen x position of the line start\n * @param y - Screen y position of the line\n * @param lineText - The rendered line text (may contain ANSI codes)\n * @param lineCharStart - Character offset in original text where this line starts\n * @param lineCharEnd - Character offset in original text where this line ends\n * @param bgSegments - Background color segments to apply\n */\nfunction applyBgSegmentsToLine(\n buffer: TerminalBuffer,\n x: number,\n y: number,\n lineText: string,\n lineCharStart: number,\n lineCharEnd: number,\n bgSegments: BgSegment[],\n ctx?: PipelineContext,\n): void {\n if (bgSegments.length === 0) return\n if (y < 0 || y >= buffer.height) return\n\n // Reusable cell for readCellInto to avoid per-character allocation\n const bgCell = createMutableCell()\n const gWidthFn = ctx ? ctx.measurer.graphemeWidth : graphemeWidth\n\n // For each bg segment that overlaps this line's character range,\n // calculate the screen columns and fill the bg\n for (const seg of bgSegments) {\n // Check overlap between segment [seg.start, seg.end) and line [lineCharStart, lineCharEnd)\n const overlapStart = Math.max(seg.start, lineCharStart)\n const overlapEnd = Math.min(seg.end, lineCharEnd)\n if (overlapStart >= overlapEnd) continue\n\n // Convert display-width offsets to column positions within the line.\n // BgSegment offsets and lineCharStart/lineCharEnd are all in display-width\n // coordinates, so relStart/relEnd are display-width offsets within the line.\n const relStart = overlapStart - lineCharStart\n const relEnd = overlapEnd - lineCharStart\n\n // Walk through the line's visible characters to find screen columns.\n // Use display-width offset (col - x) to match BgSegment coordinate system.\n let col = x\n const graphemes = splitGraphemes(hasAnsi(lineText) ? stripAnsiForBg(lineText) : lineText)\n\n for (const grapheme of graphemes) {\n const gWidth = gWidthFn(grapheme)\n if (gWidth === 0) continue\n\n const displayOffset = col - x\n if (displayOffset >= relStart && displayOffset < relEnd) {\n // This character is within the bg segment -- set bg on its cells.\n // Use readCellInto to avoid allocating a new Cell per iteration.\n buffer.readCellInto(col, y, bgCell)\n bgCell.bg = seg.bg\n buffer.setCell(col, y, bgCell)\n if (gWidth === 2 && col + 1 < buffer.width) {\n buffer.readCellInto(col + 1, y, bgCell)\n bgCell.bg = seg.bg\n buffer.setCell(col + 1, y, bgCell)\n }\n }\n\n col += gWidth\n if (col - x >= relEnd) break\n }\n }\n}\n\n/**\n * Strip ANSI escape codes from text for character counting.\n */\nfunction stripAnsiForBg(text: string): string {\n return text\n .replace(/\\x1b\\[[0-9;:?]*[A-Za-z]/g, \"\")\n .replace(/\\x1b\\][^\\x07\\x1b]*(?:\\x07|\\x1b\\\\)/g, \"\")\n .replace(/\\x1b[DME78]/g, \"\")\n .replace(/\\x1b\\(B/g, \"\")\n}\n\n/**\n * Map formatted lines back to character offsets in the original text.\n *\n * After wrapping/truncation, each output line corresponds to a range\n * of characters in the original text. This function computes those ranges\n * by searching for each line's content in the normalized text.\n *\n * Handles characters consumed by word wrapping (spaces at break points,\n * newlines) and characters added by truncation (ellipsis).\n *\n * Returns display-width offsets (not UTF-16 code units) to match BgSegment\n * coordinate system. BgSegments use display-width via getTextWidth/plainLen.\n *\n * @param originalText - The original collected text (with ANSI, before wrapping)\n * @param formattedLines - The wrapped/truncated output lines\n * @param ctx - Pipeline context for width measurement\n * @returns Array of { start, end } display-width offsets for each formatted line\n */\nfunction mapLinesToCharOffsets(\n originalText: string,\n formattedLines: string[],\n ctx?: PipelineContext,\n): Array<{ start: number; end: number }> {\n // Strip ANSI from the original to get the plain text character sequence\n const plainOriginal = hasAnsi(originalText) ? stripAnsiForBg(originalText) : originalText\n // Normalize tabs to match formatTextLines behavior\n const normalized = plainOriginal.replace(/\\t/g, \" \")\n\n const result: Array<{ start: number; end: number }> = []\n let charOffset = 0 // UTF-16 offset for string matching (findLineStart)\n let displayOffset = 0 // Display-width offset for BgSegment matching\n\n for (const line of formattedLines) {\n const plainLine = hasAnsi(line) ? stripAnsiForBg(line) : line\n\n // Find where this line starts in the normalized text (UTF-16 matching).\n const lineStart = findLineStart(normalized, plainLine, charOffset)\n\n // Convert skipped characters (between previous line end and this line start)\n // to display width. These are whitespace/newlines consumed by wrapping.\n if (lineStart > charOffset) {\n const skipped = normalized.slice(charOffset, lineStart)\n displayOffset += getTextWidth(skipped, ctx)\n }\n\n // Line content display width\n const lineDisplayWidth = getTextWidth(plainLine, ctx)\n result.push({ start: displayOffset, end: displayOffset + lineDisplayWidth })\n\n // Advance both offset trackers\n const lineLen = Math.min(plainLine.length, normalized.length - lineStart)\n charOffset = lineStart + lineLen\n displayOffset += lineDisplayWidth\n }\n\n return result\n}\n\n/**\n * Find where a formatted line starts in the normalized original text.\n *\n * Scans forward from the given offset, matching the line content\n * character by character. Skips newlines and whitespace that were\n * consumed by wrapping between lines.\n */\nfunction findLineStart(normalized: string, plainLine: string, fromOffset: number): number {\n if (plainLine.length === 0) {\n // Empty line -- skip to next newline\n let pos = fromOffset\n while (pos < normalized.length && normalized[pos] === \"\\n\") {\n pos++\n }\n return pos\n }\n\n // Try exact match at current offset first (fast path for first line\n // and for lines that follow explicit newlines without space trimming)\n if (normalized.startsWith(plainLine, fromOffset)) {\n return fromOffset\n }\n\n // For truncated lines, extract prefix before ellipsis for matching.\n // startsWith fails when the line has \"…\" that doesn't exist in the original.\n const ELLIPSIS = \"\\u2026\"\n const ellipsisIdx = plainLine.indexOf(ELLIPSIS)\n const truncatedPrefix = ellipsisIdx > 0 ? plainLine.slice(0, ellipsisIdx) : null\n\n if (truncatedPrefix && normalized.startsWith(truncatedPrefix, fromOffset)) {\n return fromOffset\n }\n\n // Scan forward, skipping newlines and spaces consumed by wrapping\n let pos = fromOffset\n while (pos < normalized.length) {\n const ch = normalized[pos]!\n if (ch === \"\\n\" || ch === \" \") {\n pos++\n continue\n }\n // Found a non-whitespace character -- check if line starts here\n if (normalized.startsWith(plainLine, pos)) {\n return pos\n }\n // Check truncated prefix match (e.g. \"abcde…\" -> match \"abcde\")\n if (truncatedPrefix && normalized.startsWith(truncatedPrefix, pos)) {\n return pos\n }\n pos++\n }\n\n // Fallback: return current position\n return fromOffset\n}\n\n// ============================================================================\n// Text Formatting\n// ============================================================================\n\n/**\n * Format text into lines based on wrap mode.\n *\n * @param trim - When true, trims trailing spaces on broken lines and skips leading\n * spaces on continuation lines. When false (e.g., text has backgroundColor),\n * preserves trailing spaces so background color covers them. Defaults to true.\n */\nexport function formatTextLines(\n text: string,\n width: number,\n wrap: TextProps[\"wrap\"],\n ctx?: PipelineContext,\n trim = true,\n): string[] {\n // Guard against width <= 0 to prevent infinite loops\n // This can happen with display=\"none\" nodes (0x0 dimensions)\n if (width <= 0) {\n return []\n }\n\n // Convert tabs to spaces (tabs have 0 display width in string-width library)\n const normalizedText = text.replace(/\\t/g, \" \")\n const lines = normalizedText.split(\"\\n\")\n\n // Hard clip: truncate without ellipsis (used by Fill component)\n if (wrap === \"clip\") {\n const sliceFn = ctx ? ctx.measurer.sliceByWidth : sliceByWidth\n return lines.map((line) => {\n if (getTextWidth(line, ctx) <= width) return line\n return sliceFn(line, width)\n })\n }\n\n // Hard wrap: character-level wrapping without regard to word boundaries.\n // Matches Ink's wrap=\"hard\" behavior (wrap-ansi with wordWrap=false), so\n // \"Hello World\" at width=7 becomes [\"Hello W\", \"orld\"] — the break lands\n // mid-word rather than at the space. Multi-line input is hard-wrapped\n // line-by-line; each line is repeatedly sliced by display width.\n if (wrap === \"hard\") {\n const sliceFn = ctx ? ctx.measurer.sliceByWidth : sliceByWidth\n const out: string[] = []\n for (const line of lines) {\n if (line === \"\") {\n out.push(\"\")\n continue\n }\n let remaining = line\n // Guard against infinite loops when sliceByWidth cannot advance\n // (e.g., a single grapheme wider than `width`). In that case, push\n // the remaining text as-is and break.\n while (getTextWidth(remaining, ctx) > width) {\n const head = sliceFn(remaining, width)\n if (head.length === 0) break\n out.push(head)\n remaining = remaining.slice(head.length)\n }\n out.push(remaining)\n }\n return out\n }\n\n // No wrapping, just truncate at end\n if (wrap === false || wrap === \"truncate-end\" || wrap === \"truncate\") {\n return lines.map((line) => truncateText(line, width, \"end\", ctx))\n }\n\n if (wrap === \"truncate-start\") {\n return lines.map((line) => truncateText(line, width, \"start\", ctx))\n }\n\n if (wrap === \"truncate-middle\") {\n return lines.map((line) => truncateText(line, width, \"middle\", ctx))\n }\n\n // Optimal wrapping (Knuth-Plass): minimize total raggedness across all lines.\n // Uses dynamic programming over breakpoints for globally optimal line breaks.\n if (wrap === \"even\") {\n const gWidthFn = ctx?.measurer?.graphemeWidth?.bind(ctx.measurer) ?? graphemeWidth\n const analysis = buildTextAnalysis(normalizedText, gWidthFn)\n return optimalWrap(normalizedText, analysis, width)\n }\n\n // Balanced wrapping: disabled — the heuristic (totalWidth / lineCount) doesn't\n // reliably produce better results than optimal. It narrows the width (changing\n // line count) rather than optimizing break placement. Keep the algorithm in\n // pretext.ts for potential future use; just treat \"balanced\" as greedy here.\n\n // wrap === true or wrap === 'wrap' or wrap === 'balanced' - word-aware wrapping\n // Uses wrapText from unicode.ts with trim for rendering\n // (when trim=true, trims trailing spaces on broken lines, skips leading spaces\n // on continuation lines; when trim=false, preserves spaces for bg-colored text)\n if (ctx) return ctx.measurer.wrapText(normalizedText, width, true, trim)\n return wrapText(normalizedText, width, true, trim)\n}\n\n/**\n * Truncate text to fit within width.\n */\nexport function truncateText(\n text: string,\n width: number,\n mode: \"start\" | \"middle\" | \"end\",\n ctx?: PipelineContext,\n): string {\n const textWidth = getTextWidth(text, ctx)\n if (textWidth <= width) return text\n\n const ellipsis = \"\\u2026\" // ...\n const availableWidth = width - 1 // Reserve space for ellipsis\n\n if (availableWidth <= 0) {\n return width > 0 ? ellipsis : \"\"\n }\n\n const sliceFn = ctx ? ctx.measurer.sliceByWidth : sliceByWidth\n const sliceEndFn = ctx ? ctx.measurer.sliceByWidthFromEnd : sliceByWidthFromEnd\n\n if (mode === \"end\") {\n return sliceFn(text, availableWidth) + ellipsis\n }\n\n if (mode === \"start\") {\n return ellipsis + sliceEndFn(text, availableWidth)\n }\n\n // middle\n const halfWidth = Math.floor(availableWidth / 2)\n const startPart = sliceFn(text, halfWidth)\n const endPart = sliceEndFn(text, availableWidth - halfWidth)\n return startPart + ellipsis + endPart\n}\n\n// ============================================================================\n// Text Line Rendering\n// ============================================================================\n\n/**\n * Render a single line of text to the buffer.\n *\n * @param maxCol - Right edge of the text node's layout area. Wide characters\n * whose continuation cell would exceed this boundary are replaced with a\n * space, matching terminal behavior for wide chars at the screen edge.\n * Without this, continuation cells overflow into adjacent containers and\n * become stale during incremental rendering (the owning container's dirty\n * tracking doesn't cover cells outside its layout bounds).\n */\nexport function renderTextLine(\n buffer: TerminalBuffer,\n x: number,\n y: number,\n text: string,\n baseStyle: Style,\n maxCol?: number,\n inheritedBg?: Color,\n ctx?: PipelineContext,\n): void {\n // Check if text contains ANSI escape sequences\n if (hasAnsi(text)) {\n renderAnsiTextLine(buffer, x, y, text, baseStyle, maxCol, inheritedBg, ctx)\n return\n }\n\n renderGraphemes(buffer, splitGraphemes(text), x, y, baseStyle, maxCol, inheritedBg, ctx)\n}\n\n/**\n * Like renderTextLine but returns the column position after the last rendered character.\n * Used by renderText to know where to clear remaining cells.\n */\nfunction renderTextLineReturn(\n buffer: TerminalBuffer,\n x: number,\n y: number,\n text: string,\n baseStyle: Style,\n maxCol?: number,\n inheritedBg?: Color,\n ctx?: PipelineContext,\n minCol?: number,\n): number {\n if (hasAnsi(text)) {\n return renderAnsiTextLineReturn(buffer, x, y, text, baseStyle, maxCol, inheritedBg, ctx, minCol)\n }\n return renderGraphemes(\n buffer,\n splitGraphemes(text),\n x,\n y,\n baseStyle,\n maxCol,\n inheritedBg,\n ctx,\n minCol,\n )\n}\n\n/**\n * Render graphemes to buffer cells with proper Unicode handling.\n * Shared by renderTextLine (plain text) and renderAnsiTextLine (per-segment).\n *\n * @param maxCol - Right edge of the text node's layout area (exclusive).\n * Wide characters whose continuation cell would reach or exceed this\n * boundary are replaced with a space character. This matches terminal\n * behavior for wide chars at the right edge of a container and prevents\n * continuation cells from overflowing into adjacent containers, where\n * they become stale during incremental rendering.\n * @param minCol - Left edge of the visible region (inclusive). Graphemes\n * whose end position is at or before minCol are skipped (col still advances).\n * Used to clip text that overflows the LEFT edge of an overflow:hidden\n * container with a border (so the border isn't overwritten).\n *\n * Returns the column position after the last rendered grapheme.\n */\nfunction renderGraphemes(\n buffer: TerminalBuffer,\n graphemes: string[],\n startCol: number,\n y: number,\n style: Style,\n maxCol?: number,\n inheritedBg?: Color,\n ctx?: PipelineContext,\n minCol?: number,\n): number {\n let col = startCol\n // Effective right boundary: text node's layout edge or buffer edge\n const rightEdge = maxCol !== undefined ? Math.min(maxCol, buffer.width) : buffer.width\n // Effective left boundary: max of clipBounds.left and 0 (no negative columns)\n const leftEdge = minCol !== undefined ? Math.max(minCol, 0) : 0\n const gWidthFn = ctx ? ctx.measurer.graphemeWidth : graphemeWidth\n\n for (const grapheme of graphemes) {\n if (col >= rightEdge) break\n\n const width = gWidthFn(grapheme)\n if (width === 0) continue\n\n // Skip graphemes whose end is still left of leftEdge (still advance col).\n // This clips text that overflows the LEFT edge of an overflow:hidden\n // container — without this, the text would overwrite the parent's left\n // border or padding cells.\n if (col + width <= leftEdge) {\n col += width\n continue\n }\n\n // Partial overlap: a wide grapheme straddling the left edge. Replace with\n // a space at leftEdge so the visible cell is preserved without the\n // grapheme's continuation cell extending outside the clip region.\n if (col < leftEdge) {\n // Skip this grapheme (the visible portion is its right cell which we\n // can't draw without the leading half). Advance to leftEdge.\n col = leftEdge\n // Don't draw a partial wide char — fall through to the next grapheme.\n continue\n }\n\n // Determine background color for this cell.\n // Priority: 1) Text's own bg, 2) inherited bg from ancestor Box, 3) buffer read (legacy fallback).\n // Using inherited bg instead of getCellBg decouples text rendering from buffer state,\n // which is critical for incremental rendering: the cloned buffer may have stale bg\n // at positions outside the parent's bg-filled region (e.g., overflow text).\n const existingBg =\n style.bg !== null\n ? style.bg\n : inheritedBg !== undefined\n ? inheritedBg\n : buffer.getCellBg(col, y)\n\n // Wide character at the boundary: the continuation cell would overflow\n // into an adjacent container. Replace with a space to match terminal\n // behavior (real terminals leave the last column blank for wide chars\n // that don't fit). Without this, the continuation cell extends outside\n // the text node's layout bounds and becomes stale during incremental\n // rendering — the owning container's dirty flag tracking doesn't cover\n // cells outside its layout area.\n if (width === 2 && col + 1 >= rightEdge) {\n buffer.setCell(col, y, {\n char: \" \",\n fg: style.fg,\n bg: existingBg,\n underlineColor: style.underlineColor ?? null,\n attrs: style.attrs,\n wide: false,\n continuation: false,\n hyperlink: style.hyperlink,\n })\n col += 1\n continue\n }\n\n // For text-presentation emoji, add VS16 so terminals render at 2 columns\n const outputChar = width === 2 ? ensureEmojiPresentation(grapheme) : grapheme\n\n buffer.setCell(col, y, {\n char: outputChar,\n fg: style.fg,\n bg: existingBg,\n underlineColor: style.underlineColor ?? null,\n attrs: style.attrs,\n wide: width === 2,\n continuation: false,\n hyperlink: style.hyperlink,\n })\n\n if (width === 2 && col + 1 < buffer.width) {\n const existingBg2 =\n style.bg !== null\n ? style.bg\n : inheritedBg !== undefined\n ? inheritedBg\n : buffer.getCellBg(col + 1, y)\n buffer.setCell(col + 1, y, {\n char: \"\",\n fg: style.fg,\n bg: existingBg2,\n underlineColor: style.underlineColor ?? null,\n attrs: style.attrs,\n wide: false,\n continuation: true,\n hyperlink: style.hyperlink,\n })\n col += 2\n } else {\n col += width\n }\n }\n\n return col\n}\n\n/**\n * Render text line with ANSI escape sequences.\n * Parses ANSI codes and applies styles to individual segments.\n */\nexport function renderAnsiTextLine(\n buffer: TerminalBuffer,\n x: number,\n y: number,\n text: string,\n baseStyle: Style,\n maxCol?: number,\n inheritedBg?: Color,\n ctx?: PipelineContext,\n): void {\n renderAnsiTextLineReturn(buffer, x, y, text, baseStyle, maxCol, inheritedBg, ctx)\n}\n\n/**\n * Like renderAnsiTextLine but returns the column position after the last rendered character.\n */\nfunction renderAnsiTextLineReturn(\n buffer: TerminalBuffer,\n x: number,\n y: number,\n text: string,\n baseStyle: Style,\n maxCol?: number,\n inheritedBg?: Color,\n ctx?: PipelineContext,\n minCol?: number,\n): number {\n const segments = parseAnsiText(text)\n let col = x\n\n for (const segment of segments) {\n // Merge segment style with base style\n const style = mergeAnsiStyle(baseStyle, segment)\n\n // Detect background conflict: chalk.bg* overwrites existing silvery background\n // Check both: 1) Text's own backgroundColor, 2) Parent Box's bg already in buffer\n // Skip if segment has bgOverride flag (explicit opt-out via ansi.bgOverride)\n const effectiveBgConflictMode = ctx?.bgConflictMode ?? getBgConflictMode()\n if (\n effectiveBgConflictMode !== \"ignore\" &&\n !segment.bgOverride &&\n segment.bg !== undefined &&\n segment.bg !== null\n ) {\n // Check if there's an existing background (from Text prop or parent Box fill)\n const existingBufBg = col < buffer.width ? buffer.getCellBg(col, y) : null\n const hasExistingBg = baseStyle.bg !== null || existingBufBg !== null\n\n if (hasExistingBg) {\n const preview = segment.text.slice(0, 30)\n const chalkBg = formatBgConflictColor(segment.bg)\n const silveryBg =\n baseStyle.bg !== null\n ? `Text.bg=${formatBgConflictColor(baseStyle.bg)}`\n : `bufferBg=${formatBgConflictColor(existingBufBg)}`\n // Show a snippet of the raw ANSI text around the conflict for debugging\n const textPreview = text.length > 80 ? text.slice(0, 80) + \"…\" : text\n const msg = `[silvery] Background conflict at (${col},${y}): chalk bg=${chalkBg} on silvery ${silveryBg}. Text: \"${preview}${segment.text.length > 30 ? \"…\" : \"\"}\". Raw ANSI (first 80): ${JSON.stringify(textPreview)}. Chalk bg will override only text characters, causing visual gaps in padding. Use ansi.bgOverride() to suppress if intentional.`\n\n if (effectiveBgConflictMode === \"throw\") {\n throw new Error(msg)\n }\n // 'warn' mode - deduplicate\n const effectiveWarnedBgConflicts = ctx?.warnedBgConflicts ?? warnedBgConflicts\n const key = `${JSON.stringify(existingBufBg)}-${segment.bg}-${preview}`\n if (!effectiveWarnedBgConflicts.has(key)) {\n effectiveWarnedBgConflicts.add(key)\n log.warn?.(msg)\n }\n }\n }\n\n col = renderGraphemes(\n buffer,\n splitGraphemes(segment.text),\n col,\n y,\n style,\n maxCol,\n inheritedBg,\n ctx,\n minCol,\n )\n }\n return col\n}\n\n// ============================================================================\n// Style Merging (Category-Based)\n// ============================================================================\n\n/**\n * Options for category-based style merging.\n */\nexport interface MergeStylesOptions {\n /**\n * Preserve decoration attributes through layers (OR merge).\n * Affects: underline, underlineStyle, underlineColor, strikethrough\n * Default: true\n */\n preserveDecorations?: boolean\n /**\n * Preserve emphasis attributes through layers (OR merge).\n * Affects: bold, dim, italic\n * Default: true\n */\n preserveEmphasis?: boolean\n}\n\n/**\n * Merge two styles using category-based semantics.\n *\n * Categories and their merge behavior:\n * - Container (bg): overlay replaces base\n * - Text (fg): overlay replaces base\n * - Decorations (underline*, strikethrough): OR merge if preserveDecorations=true\n * - Emphasis (bold, dim, italic): OR merge if preserveEmphasis=true\n * - Transform (inverse, hidden, blink): overlay only, not inherited\n *\n * @param base - The base style (from parent/container)\n * @param overlay - The overlay style (from child/content)\n * @param options - Merge behavior options\n */\nexport function mergeStyles(\n base: Style,\n overlay: Partial<Style>,\n options: MergeStylesOptions = {},\n): Style {\n const { preserveDecorations = true, preserveEmphasis = true } = options\n\n const baseAttrs = base.attrs ?? {}\n const overlayAttrs = overlay.attrs ?? {}\n\n // Merge attributes by category\n const attrs: CellAttrs = {}\n\n // Decorations: OR if preserving, otherwise overlay takes precedence\n if (preserveDecorations) {\n // Underline: OR the boolean, but style from overlay wins if specified\n const hasBaseUnderline = baseAttrs.underline || baseAttrs.underlineStyle\n const hasOverlayUnderline = overlayAttrs.underline || overlayAttrs.underlineStyle\n if (hasBaseUnderline || hasOverlayUnderline) {\n attrs.underline = true\n // Style: overlay wins if specified, else base\n attrs.underlineStyle = overlayAttrs.underlineStyle ?? baseAttrs.underlineStyle ?? \"single\"\n }\n attrs.strikethrough = overlayAttrs.strikethrough || baseAttrs.strikethrough\n } else {\n attrs.underline = overlayAttrs.underline ?? baseAttrs.underline\n attrs.underlineStyle = overlayAttrs.underlineStyle ?? baseAttrs.underlineStyle\n attrs.strikethrough = overlayAttrs.strikethrough ?? baseAttrs.strikethrough\n }\n\n // Emphasis: OR if preserving\n if (preserveEmphasis) {\n attrs.bold = overlayAttrs.bold || baseAttrs.bold\n attrs.dim = overlayAttrs.dim || baseAttrs.dim\n attrs.italic = overlayAttrs.italic || baseAttrs.italic\n } else {\n attrs.bold = overlayAttrs.bold ?? baseAttrs.bold\n attrs.dim = overlayAttrs.dim ?? baseAttrs.dim\n attrs.italic = overlayAttrs.italic ?? baseAttrs.italic\n }\n\n // Transform: overlay only, not inherited from base\n attrs.inverse = overlayAttrs.inverse\n attrs.hidden = overlayAttrs.hidden\n attrs.blink = overlayAttrs.blink\n\n return {\n // Container/Text: overlay wins if specified\n fg: overlay.fg ?? base.fg,\n bg: overlay.bg ?? base.bg,\n // Underline color: always use overlay ?? base (part of decoration preservation)\n underlineColor: overlay.underlineColor ?? base.underlineColor,\n attrs,\n }\n}\n\n// ============================================================================\n// ANSI Style Helpers\n// ============================================================================\n\n/**\n * Merge ANSI segment style with base style.\n * Uses category-based merging to preserve decorations and emphasis.\n */\nfunction mergeAnsiStyle(\n base: Style,\n segment: StyledSegment,\n options: MergeStylesOptions = {},\n): Style {\n const { preserveDecorations = true, preserveEmphasis = true } = options\n\n // Convert ANSI SGR codes to overlay style\n let fg: Color = base.fg\n let bg: Color = base.bg\n let underlineColor: Color = base.underlineColor ?? null\n\n if (segment.fg !== undefined && segment.fg !== null) {\n fg = ansiColorToColor(segment.fg)\n }\n if (segment.bg !== undefined && segment.bg !== null) {\n bg = ansiColorToColor(segment.bg)\n }\n if (segment.underlineColor !== undefined && segment.underlineColor !== null) {\n underlineColor = ansiColorToColor(segment.underlineColor)\n }\n\n // Build overlay attrs from segment\n const overlayAttrs: CellAttrs = {}\n if (segment.bold !== undefined) overlayAttrs.bold = segment.bold\n if (segment.dim !== undefined) overlayAttrs.dim = segment.dim\n if (segment.italic !== undefined) overlayAttrs.italic = segment.italic\n if (segment.underline !== undefined) {\n overlayAttrs.underline = segment.underline\n }\n if (segment.underlineStyle !== undefined) {\n overlayAttrs.underlineStyle = segment.underlineStyle as UnderlineStyle\n }\n if (segment.inverse !== undefined) overlayAttrs.inverse = segment.inverse\n\n // Use mergeStyles for consistent category-based merging\n const merged = mergeStyles(\n base,\n { fg, bg, underlineColor, attrs: overlayAttrs },\n { preserveDecorations, preserveEmphasis },\n )\n\n // Pass through OSC 8 hyperlink from segment (not an SGR attribute)\n if (segment.hyperlink) {\n merged.hyperlink = segment.hyperlink\n }\n\n return merged\n}\n\n/**\n * Convert ANSI SGR color code to our Color type.\n * Color is: number (256-color index) | { r, g, b } (true color) | null\n */\nfunction ansiColorToColor(code: number): Color {\n // True color (packed RGB with 0x1000000 marker from parseAnsiText)\n if (code >= 0x1000000) {\n const r = (code >> 16) & 0xff\n const g = (code >> 8) & 0xff\n const b = code & 0xff\n return { r, g, b }\n }\n\n // 256 color palette index (0-255)\n if (code < 30 || (code >= 38 && code < 40) || (code >= 48 && code < 90)) {\n // Direct palette index (0-255) — return as-is\n return code\n }\n\n // Standard foreground colors (30-37) map to palette 0-7\n if (code >= 30 && code <= 37) {\n return code - 30\n }\n\n // Standard background colors (40-47) map to palette 0-7\n if (code >= 40 && code <= 47) {\n return code - 40\n }\n\n // Bright foreground colors (90-97) map to palette 8-15\n if (code >= 90 && code <= 97) {\n return code - 90 + 8\n }\n\n // Bright background colors (100-107) map to palette 8-15\n if (code >= 100 && code <= 107) {\n return code - 100 + 8\n }\n\n return null\n}\n\n// ============================================================================\n// Render Text Node (Main Entry Point)\n// ============================================================================\n\n/**\n * Render a Text node.\n *\n * Background colors from nested Text elements are handled at the buffer level\n * (not via ANSI codes) to prevent bg bleed across wrapped text lines.\n * See km-silvery.bg-bleed for details.\n */\nexport function renderText(\n node: AgNode,\n buffer: TerminalBuffer,\n layout: { x: number; y: number; width: number; height: number },\n props: TextProps,\n nodeState: NodeRenderState,\n inheritedBg?: Color,\n inheritedFg?: Color,\n ctx?: PipelineContext,\n): void {\n const { scrollOffset, clipBounds } = nodeState\n const { x, width, height } = layout\n let { y } = layout\n\n // Apply scroll offset\n y -= scrollOffset\n\n // Explicit backgroundColor=\"\" on a Text node means \"no background\" — force\n // null bg to override both inherited bg from ancestor Boxes and any bg\n // already in the buffer cells (set by Box's renderBox fill). The sentinel\n // value `null` is used instead of `undefined` so renderGraphemes uses it\n // directly instead of falling back to buffer.getCellBg().\n if (props.backgroundColor === \"\") {\n inheritedBg = null\n }\n\n // Clip to bounds if specified\n if (clipBounds) {\n if (y + height <= clipBounds.top || y >= clipBounds.bottom) {\n return // Completely outside vertical clip bounds\n }\n if (clipBounds.left !== undefined && clipBounds.right !== undefined) {\n if (x + width <= clipBounds.left || x >= clipBounds.right) {\n return // Completely outside horizontal clip bounds\n }\n }\n }\n\n // --- PreparedText cache: Level 0 (plain text for maxDisplayWidth) ---\n // Compute DOM-level display width budget for truncate-end modes.\n // This limits how much text collectTextWithBg gathers BEFORE ANSI serialization,\n // making OSC 8 hyperlinks and other escape sequences safe by construction.\n let maxDisplayWidth: number | undefined\n const isTruncateEnd =\n props.wrap === false ||\n props.wrap === \"truncate-end\" ||\n props.wrap === \"truncate\" ||\n props.wrap === \"clip\"\n if (isTruncateEnd && width > 0) {\n const cachedPlain = getCachedPlainText(node)\n let lineCount: number\n if (cachedPlain) {\n lineCount = cachedPlain.lineCount\n } else {\n const plainText = collectPlainText(node)\n lineCount = (plainText.match(/\\n/g)?.length ?? 0) + 1\n setCachedPlainText(node, plainText, lineCount)\n }\n maxDisplayWidth = (width + 1) * lineCount\n }\n\n // --- PreparedText cache: Level 1 (collected styled text) ---\n // Collect text content and background segments from this node and all children.\n // Background color from nested Text elements is tracked as BgSegments\n // (not embedded as ANSI codes) to survive text wrapping correctly.\n let text: string\n let bgSegments: BgSegment[]\n let childSpans: ChildSpan[]\n\n // Seed the collection parent context with this Text's own effective props so\n // nested virtual <Text color=\"inherit\"> children can resolve \"inherit\" /\n // \"currentColor\" back to the owner Text's color. Without this, virtual\n // children only see \"{}\" and keyword lookups produced no fg.\n const rootContext: StyleContext = {\n color: props.color,\n backgroundColor: props.backgroundColor,\n bold: props.bold,\n dim: props.dim || props.dimColor,\n italic: props.italic,\n underline: !!(props.underline || props.underlineStyle),\n underlineStyle: props.underlineStyle,\n underlineColor: props.underlineColor,\n inverse: props.inverse,\n strikethrough: props.strikethrough,\n }\n\n // Pass the active context theme as a cache key so that $token ANSI codes\n // embedded in collected text are invalidated when the nearest-ancestor\n // ThemeProvider changes its theme. Without this, a theme change would leave\n // stale ANSI-encoded token colors in the cache (e.g., $primary → blue from\n // the first render), causing the new theme's green to be overridden.\n const contextTheme = getActiveTheme()\n const cachedCollected = getCachedCollectedText(node, maxDisplayWidth, contextTheme)\n if (cachedCollected) {\n text = cachedCollected.text\n bgSegments = cachedCollected.bgSegments as BgSegment[]\n childSpans = cachedCollected.childSpans as ChildSpan[]\n } else {\n const collected = collectTextWithBg(node, rootContext, 0, maxDisplayWidth, ctx)\n text = collected.text\n bgSegments = collected.bgSegments\n childSpans = collected.childSpans\n setCachedCollectedText(node, collected, maxDisplayWidth, contextTheme)\n }\n\n // Get style for this Text node.\n // Inherit foreground from nearest ancestor Box with color prop (CSS semantics).\n const style = getTextStyle(props)\n if (style.fg === null && inheritedFg !== undefined) {\n style.fg = inheritedFg\n }\n // underlineColor=\"currentColor\"/\"inherit\" tracks the text's resolved fg.\n // getTextStyle already produced style.underlineColor = null for the keyword\n // (parseColor(\"currentColor\") === null). Upgrade it here — AFTER fg\n // inheritance has been applied — so the underline matches the visible text.\n if (props.underlineColor === \"currentColor\" || props.underlineColor === \"inherit\") {\n style.underlineColor = style.fg\n }\n\n // --- PreparedText cache: Level 2 (formatted lines per width) ---\n // When text has background color, preserve trailing spaces so bg covers them.\n const hasBg =\n style.bg !== null ||\n bgSegments.length > 0 ||\n (inheritedBg !== undefined && inheritedBg !== null)\n const trim = !hasBg\n const internalTransform = props.internal_transform\n\n let lines: string[]\n let lineOffsets: Array<{ start: number; end: number }>\n\n // Skip format cache when internal_transform is present (may depend on external state)\n const cachedFmt = !internalTransform ? getCachedFormat(node, width, props.wrap, trim) : null\n if (cachedFmt) {\n lines = cachedFmt.lines\n lineOffsets = cachedFmt.hasLineOffsets ? cachedFmt.lineOffsets : []\n } else {\n lines = formatTextLines(text, width, props.wrap, ctx, trim)\n if (internalTransform) {\n lines = lines.map((line, index) => internalTransform(line, index))\n }\n const needLineOffsets = bgSegments.length > 0 || childSpans.length > 0\n lineOffsets = needLineOffsets ? mapLinesToCharOffsets(text, lines, ctx) : []\n if (!internalTransform) {\n setCachedFormat(node, width, props.wrap, trim, lines, lineOffsets, needLineOffsets)\n }\n }\n\n // Render each line\n for (let lineIdx = 0; lineIdx < lines.length && lineIdx < height; lineIdx++) {\n const lineY = y + lineIdx\n // Skip lines outside clip bounds\n if (clipBounds && (lineY < clipBounds.top || lineY >= clipBounds.bottom)) {\n continue\n }\n const line = lines[lineIdx]!\n\n // Pass maxCol to prevent wide characters from overflowing into adjacent\n // containers. Without this, continuation cells outside the text node's\n // layout bounds become stale during incremental rendering.\n // Clip right edge to horizontal clip bounds (overflow:hidden containers).\n // When internal_transform is active, expand maxCol to buffer width so the\n // transformed text (which may be wider than the original layout) is not clipped.\n const layoutRight = internalTransform ? buffer.width : x + width\n const maxCol =\n clipBounds && \"right\" in clipBounds && clipBounds.right !== undefined\n ? Math.min(layoutRight, clipBounds.right)\n : layoutRight\n // Clip left edge to horizontal clip bounds. Without this, text rendered\n // by a node whose x is BEFORE the parent's clip-left (e.g., negative\n // marginLeft inside an overflow:hidden container with a border) would\n // overwrite the parent's left border or padding cells.\n const minCol =\n clipBounds && \"left\" in clipBounds && clipBounds.left !== undefined\n ? clipBounds.left\n : undefined\n const endCol = renderTextLineReturn(\n buffer,\n x,\n lineY,\n line,\n style,\n maxCol,\n inheritedBg,\n ctx,\n minCol,\n )\n\n // Clear remaining cells after text to end of layout width (clipped).\n // When text content shrinks (e.g., breadcrumb changes from long to short path),\n // the parent Box may skip its bg fill (skipBgFill=true when only subtreeDirty).\n // Without explicit clearing here, stale chars from the previous longer text\n // survive in the cloned buffer. This is safe: we only clear within our own\n // layout area, writing spaces with the correct inherited background.\n // Respect minCol so we don't clear cells inside the parent's left border.\n const clearStart = minCol !== undefined ? Math.max(endCol, minCol) : endCol\n if (clearStart < maxCol) {\n const clearBg = inheritedBg ?? null\n for (let cx = clearStart; cx < maxCol && cx < buffer.width; cx++) {\n buffer.setCell(cx, lineY, {\n char: \" \",\n fg: style.fg,\n bg: clearBg,\n underlineColor: null,\n attrs: {\n bold: false,\n dim: false,\n italic: false,\n underline: false,\n inverse: false,\n strikethrough: false,\n blink: false,\n hidden: false,\n },\n wide: false,\n continuation: false,\n })\n }\n }\n\n // Apply background segments from nested Text elements to the buffer.\n // This happens after renderTextLine so the bg is applied to cells\n // that already have the correct character/fg/attrs written.\n if (bgSegments.length > 0 && lineIdx < lineOffsets.length) {\n const { start, end } = lineOffsets[lineIdx]!\n applyBgSegmentsToLine(buffer, x, lineY, line, start, end, bgSegments, ctx)\n }\n }\n\n // Compute inlineRects for virtual text children.\n // Maps each child's display-width span to screen-space rectangles,\n // accounting for text wrapping (one rect per line fragment).\n if (childSpans.length > 0 && lineOffsets.length > 0) {\n computeInlineRects(childSpans, lineOffsets, x, y, lines.length, height)\n }\n}\n\n/**\n * Compute inlineRects for virtual text children based on their display-width spans\n * and the formatted line offsets. For wrapped text, a child may span multiple lines,\n * producing one rect per line fragment.\n *\n * @param childSpans - Virtual text children with their display-width ranges\n * @param lineOffsets - Display-width offset ranges for each formatted line\n * @param parentX - Screen X of the parent Text node\n * @param parentY - Screen Y of the parent Text node (after scroll offset)\n * @param lineCount - Number of formatted lines\n * @param maxHeight - Maximum height (layout height) of the parent Text node\n */\nfunction computeInlineRects(\n childSpans: ChildSpan[],\n lineOffsets: Array<{ start: number; end: number }>,\n parentX: number,\n parentY: number,\n lineCount: number,\n maxHeight: number,\n): void {\n for (const span of childSpans) {\n const rects: Array<{ x: number; y: number; width: number; height: number }> = []\n\n for (let lineIdx = 0; lineIdx < lineCount && lineIdx < maxHeight; lineIdx++) {\n const lineOffset = lineOffsets[lineIdx]\n if (!lineOffset) continue\n\n // Check overlap between span [span.start, span.end) and line [lineOffset.start, lineOffset.end)\n const overlapStart = Math.max(span.start, lineOffset.start)\n const overlapEnd = Math.min(span.end, lineOffset.end)\n if (overlapStart >= overlapEnd) continue\n\n // Convert to screen coordinates\n const rectX = parentX + (overlapStart - lineOffset.start)\n const rectY = parentY + lineIdx\n const rectWidth = overlapEnd - overlapStart\n\n rects.push({ x: rectX, y: rectY, width: rectWidth, height: 1 })\n }\n\n span.node.inlineRects = rects.length > 0 ? rects : null\n }\n}\n","/**\n * Box Rendering - Functions for rendering box elements to the buffer.\n *\n * Contains:\n * - Box rendering (renderBox)\n * - Border rendering (renderBorder)\n * - Scroll indicators (renderScrollIndicators)\n */\n\nimport type { Color, Style, TerminalBuffer } from \"../buffer\"\nimport type { BoxProps, AgNode, Rect } from \"@silvery/ag/types\"\nimport { getPadding } from \"./helpers\"\nimport { getBorderChars, getBorderSize, parseColor } from \"./render-helpers\"\nimport { renderTextLine } from \"./render-text\"\nimport type { NodeRenderState, PipelineContext } from \"./types\"\n\n/**\n * Get the effective background color string for a Box.\n * Returns explicit `backgroundColor` if set, otherwise the Theme's root\n * surface background — Sterling's `bg-surface-default` if present, falling\n * back to the legacy `bg` root for any pre-Sterling Theme shape.\n * Used by both renderBox (paint fill) and render-phase (cascade logic).\n */\nexport function getEffectiveBg(props: BoxProps): string | undefined {\n if (props.backgroundColor) return props.backgroundColor as string\n if (props.theme) {\n const theme = props.theme as unknown as Record<string, unknown>\n const sterlingBg = theme[\"bg-surface-default\"]\n if (typeof sterlingBg === \"string\") return sterlingBg\n const legacyBg = theme[\"bg\"]\n if (typeof legacyBg === \"string\") return legacyBg\n }\n return undefined\n}\n\n// ============================================================================\n// Box Rendering\n// ============================================================================\n\n/**\n * Render a Box node.\n */\nexport function renderBox(\n _node: AgNode,\n buffer: TerminalBuffer,\n layout: Rect,\n props: BoxProps,\n nodeState: NodeRenderState,\n skipBgFill = false,\n inheritedBg?: Color | null,\n bgOnlyChange = false,\n inheritedFg?: Color | null,\n): void {\n const { scrollOffset, clipBounds } = nodeState\n const { x, width, height } = layout\n // Apply scroll offset to y position\n const y = layout.y - scrollOffset\n\n // Skip if completely outside clip bounds\n if (clipBounds) {\n if (y + height <= clipBounds.top || y >= clipBounds.bottom) return\n if (clipBounds.left !== undefined && clipBounds.right !== undefined) {\n if (x + width <= clipBounds.left || x >= clipBounds.right) return\n }\n }\n\n // Fill background if set (explicit backgroundColor or theme.bg).\n // In incremental mode, skipBgFill=true when the box itself hasn't changed\n // (only subtreeDirty). The cloned buffer already has the correct bg fill,\n // and re-filling would destroy child pixels that won't be repainted.\n //\n // bgOnlyChange: when ONLY backgroundColor changed (no content/layout/children\n // changes), use fillBg() which updates bg without overwriting chars. This\n // preserves child content from the cloned buffer, enabling the cascade\n // optimization where clean children are skipped entirely.\n const effectiveBgStr = getEffectiveBg(props)\n if (effectiveBgStr && !skipBgFill) {\n const bg = parseColor(effectiveBgStr)\n // Clip background fill to bounds\n if (clipBounds) {\n const clippedY = Math.max(y, clipBounds.top)\n const clippedHeight = Math.min(y + height, clipBounds.bottom) - clippedY\n let clippedX = x\n let clippedWidth = width\n if (clipBounds.left !== undefined && clipBounds.right !== undefined) {\n clippedX = Math.max(x, clipBounds.left)\n clippedWidth = Math.min(x + width, clipBounds.right) - clippedX\n }\n if (clippedHeight > 0 && clippedWidth > 0) {\n if (bgOnlyChange) {\n buffer.fillBg(clippedX, clippedY, clippedWidth, clippedHeight, bg)\n } else {\n buffer.fill(clippedX, clippedY, clippedWidth, clippedHeight, { bg })\n }\n }\n } else {\n if (bgOnlyChange) {\n buffer.fillBg(x, y, width, height, bg)\n } else {\n buffer.fill(x, y, width, height, { bg })\n }\n }\n }\n\n // Render border if set\n if (props.borderStyle) {\n renderBorder(buffer, x, y, width, height, props, clipBounds, inheritedBg, inheritedFg)\n }\n}\n\n// ============================================================================\n// Border Rendering\n// ============================================================================\n\n/**\n * Render a border around a box.\n */\nexport function renderBorder(\n buffer: TerminalBuffer,\n x: number,\n y: number,\n width: number,\n height: number,\n props: BoxProps,\n clipBounds?: { top: number; bottom: number; left?: number; right?: number },\n inheritedBg?: Color | null,\n inheritedFg?: Color | null,\n): void {\n const chars = getBorderChars(props.borderStyle ?? \"single\")\n // borderColor=\"currentColor\"/\"inherit\" resolves to the Box's own fg —\n // explicit props.color if set, else the inherited fg from the nearest\n // ancestor with a color. Mirrors CSS `border-color: currentColor`.\n let color: Color | null\n if (props.borderColor === \"currentColor\" || props.borderColor === \"inherit\") {\n color = props.color ? parseColor(props.color) : (inheritedFg ?? null)\n } else {\n color = props.borderColor ? parseColor(props.borderColor) : null\n }\n // Preserve the box's background color on border cells. Falls back to\n // inherited bg from the nearest ancestor with backgroundColor, ensuring\n // border cells don't punch transparent holes through parent backgrounds.\n const baseBg = props.backgroundColor ? parseColor(props.backgroundColor) : (inheritedBg ?? null)\n\n // Per-side border background colors — each side falls back to the shorthand\n // borderBackgroundColor, then to the box's own bg / inherited bg.\n const borderBgStr = (props as BoxProps).borderBackgroundColor\n const borderBgBase = borderBgStr ? parseColor(borderBgStr) : baseBg\n const topBorderBgStr = (props as BoxProps).borderTopBackgroundColor\n const bottomBorderBgStr = (props as BoxProps).borderBottomBackgroundColor\n const leftBorderBgStr = (props as BoxProps).borderLeftBackgroundColor\n const rightBorderBgStr = (props as BoxProps).borderRightBackgroundColor\n const topBg = topBorderBgStr ? parseColor(topBorderBgStr) : borderBgBase\n const bottomBg = bottomBorderBgStr ? parseColor(bottomBorderBgStr) : borderBgBase\n const leftBg = leftBorderBgStr ? parseColor(leftBorderBgStr) : borderBgBase\n const rightBg = rightBorderBgStr ? parseColor(rightBorderBgStr) : borderBgBase\n\n const showTop = props.borderTop !== false\n const showBottom = props.borderBottom !== false\n const showLeft = props.borderLeft !== false\n const showRight = props.borderRight !== false\n\n // Helper to check if a row is visible within clip bounds\n const isRowVisible = (row: number): boolean => {\n if (!clipBounds) return row >= 0 && row < buffer.height\n return row >= clipBounds.top && row < clipBounds.bottom && row < buffer.height\n }\n\n // Helper to check if a column is visible within clip bounds\n const isColVisible = (col: number): boolean => {\n if (clipBounds?.left === undefined || clipBounds.right === undefined)\n return col >= 0 && col < buffer.width\n return col >= clipBounds.left && col < clipBounds.right && col < buffer.width\n }\n\n // Top border — corners use the bg of the horizontal side (top/bottom)\n if (showTop && isRowVisible(y)) {\n if (showLeft && isColVisible(x))\n buffer.setCell(x, y, { char: chars.topLeft, fg: color, bg: topBg })\n const hStart = showLeft ? x + 1 : x\n const hEnd = showRight ? x + width - 1 : x + width\n for (let col = hStart; col < hEnd && col < buffer.width; col++) {\n if (isColVisible(col))\n buffer.setCell(col, y, { char: chars.horizontal, fg: color, bg: topBg })\n }\n if (showRight && x + width - 1 < buffer.width && isColVisible(x + width - 1)) {\n buffer.setCell(x + width - 1, y, { char: chars.topRight, fg: color, bg: topBg })\n }\n }\n\n // Side borders — extend range when top/bottom borders are hidden\n const rightVertical = chars.rightVertical ?? chars.vertical\n const sideStart = showTop ? y + 1 : y\n const sideEnd = showBottom ? y + height - 1 : y + height\n for (let row = sideStart; row < sideEnd; row++) {\n if (!isRowVisible(row)) continue\n if (showLeft && isColVisible(x))\n buffer.setCell(x, row, { char: chars.vertical, fg: color, bg: leftBg })\n if (showRight && x + width - 1 < buffer.width && isColVisible(x + width - 1)) {\n buffer.setCell(x + width - 1, row, { char: rightVertical, fg: color, bg: rightBg })\n }\n }\n\n // Bottom border\n const bottomHorizontal = chars.bottomHorizontal ?? chars.horizontal\n const bottomY = y + height - 1\n if (showBottom && isRowVisible(bottomY)) {\n if (showLeft && isColVisible(x)) {\n buffer.setCell(x, bottomY, { char: chars.bottomLeft, fg: color, bg: bottomBg })\n }\n const bStart = showLeft ? x + 1 : x\n const bEnd = showRight ? x + width - 1 : x + width\n for (let col = bStart; col < bEnd && col < buffer.width; col++) {\n if (isColVisible(col))\n buffer.setCell(col, bottomY, { char: bottomHorizontal, fg: color, bg: bottomBg })\n }\n if (showRight && x + width - 1 < buffer.width && isColVisible(x + width - 1)) {\n buffer.setCell(x + width - 1, bottomY, {\n char: chars.bottomRight,\n fg: color,\n bg: bottomBg,\n })\n }\n }\n}\n\n// ============================================================================\n// Outline Rendering\n// ============================================================================\n\n/**\n * Render an outline around a box.\n *\n * Unlike borders, outlines do NOT affect layout dimensions. They draw border\n * characters OUTSIDE the box — one cell beyond each edge, in the gap/margin\n * space between siblings. This matches CSS `outline` semantics.\n *\n * The outline occupies cells at (x-1, y-1) through (x+width, y+height) —\n * entirely outside the box's own rect. Content is never overlapped.\n */\nexport function renderOutline(\n buffer: TerminalBuffer,\n x: number,\n y: number,\n width: number,\n height: number,\n props: BoxProps,\n clipBounds?: { top: number; bottom: number; left?: number; right?: number },\n inheritedBg?: Color | null,\n): void {\n const chars = getBorderChars(props.outlineStyle ?? \"single\")\n const color = props.outlineColor ? parseColor(props.outlineColor) : null\n const bg = props.backgroundColor ? parseColor(props.backgroundColor) : (inheritedBg ?? null)\n const attrs = props.outlineDimColor ? { dim: true } : {}\n\n // Outline draws OUTSIDE the box: one cell beyond each edge\n const ox = x - 1 // outline left column\n const oy = y - 1 // outline top row\n const ow = width + 2 // outline total width\n const oh = height + 2 // outline total height\n\n // Helper to check if a row is visible within clip bounds\n const isRowVisible = (row: number): boolean => {\n if (!clipBounds) return row >= 0 && row < buffer.height\n return row >= clipBounds.top && row < clipBounds.bottom && row < buffer.height\n }\n\n // Helper to check if a column is visible within clip bounds\n const isColVisible = (col: number): boolean => {\n if (clipBounds?.left === undefined || clipBounds.right === undefined)\n return col >= 0 && col < buffer.width\n return col >= clipBounds.left && col < clipBounds.right && col < buffer.width\n }\n\n const showTop = props.outlineTop !== false\n const showBottom = props.outlineBottom !== false\n const showLeft = props.outlineLeft !== false\n const showRight = props.outlineRight !== false\n\n // Top border (one row above the box)\n if (showTop && isRowVisible(oy)) {\n if (showLeft && isColVisible(ox))\n buffer.setCell(ox, oy, { char: chars.topLeft, fg: color, bg, attrs })\n for (let col = ox + 1; col < ox + ow - 1 && col < buffer.width; col++) {\n if (isColVisible(col))\n buffer.setCell(col, oy, { char: chars.horizontal, fg: color, bg, attrs })\n }\n if (showRight && ox + ow - 1 < buffer.width && isColVisible(ox + ow - 1)) {\n buffer.setCell(ox + ow - 1, oy, { char: chars.topRight, fg: color, bg, attrs })\n }\n }\n\n // Side borders — run along the box's own height (y to y+height-1)\n const outlineRightVertical = chars.rightVertical ?? chars.vertical\n const sideStart = showTop ? oy + 1 : oy\n const sideEnd = showBottom ? oy + oh - 1 : oy + oh\n for (let row = sideStart; row < sideEnd; row++) {\n if (!isRowVisible(row)) continue\n if (showLeft && isColVisible(ox))\n buffer.setCell(ox, row, { char: chars.vertical, fg: color, bg, attrs })\n if (showRight && ox + ow - 1 < buffer.width && isColVisible(ox + ow - 1)) {\n buffer.setCell(ox + ow - 1, row, { char: outlineRightVertical, fg: color, bg, attrs })\n }\n }\n\n // Bottom border (one row below the box)\n const outlineBottomHorizontal = chars.bottomHorizontal ?? chars.horizontal\n const bottomY = oy + oh - 1\n if (showBottom && isRowVisible(bottomY)) {\n if (showLeft && isColVisible(ox)) {\n buffer.setCell(ox, bottomY, { char: chars.bottomLeft, fg: color, bg, attrs })\n }\n for (let col = ox + 1; col < ox + ow - 1 && col < buffer.width; col++) {\n if (isColVisible(col))\n buffer.setCell(col, bottomY, { char: outlineBottomHorizontal, fg: color, bg, attrs })\n }\n if (showRight && ox + ow - 1 < buffer.width && isColVisible(ox + ow - 1)) {\n buffer.setCell(ox + ow - 1, bottomY, {\n char: chars.bottomRight,\n fg: color,\n bg,\n attrs,\n })\n }\n }\n}\n\n// ============================================================================\n// Scroll Indicators\n// ============================================================================\n\n/**\n * Render scroll indicators showing hidden items above/below viewport.\n *\n * Two rendering modes:\n * 1. Bordered containers: Indicators appear on the border (e.g., \"───▲42───\")\n * 2. Borderless containers with overflowIndicator: Indicators appear directly\n * after the last visible child (not at the viewport bottom)\n *\n * Uses ▲N for items hidden above, ▼N for items hidden below.\n */\nexport function renderScrollIndicators(\n _node: AgNode,\n buffer: TerminalBuffer,\n layout: Rect,\n props: BoxProps,\n ss: NonNullable<AgNode[\"scrollState\"]>,\n ctx?: PipelineContext,\n): void {\n const border = props.borderStyle ? getBorderSize(props) : { top: 0, bottom: 0, left: 0, right: 0 }\n\n // Inverse bar style: white text on dark background\n const indicatorStyle: Style = {\n fg: 15, // Bright white\n bg: 8, // Dark gray\n attrs: {},\n }\n\n // Determine if we should show indicators for borderless containers\n const showBorderless = props.overflowIndicator === true\n\n // Top indicator\n if (ss.hiddenAbove > 0) {\n const indicator = `\\u25b2${ss.hiddenAbove}`\n\n if (border.top > 0) {\n // Bordered: render centered inverse bar on top border line\n const contentWidth = layout.width - border.left - border.right\n const bar = padCenter(indicator, contentWidth)\n const x = layout.x + border.left\n const y = layout.y\n const maxCol = x + contentWidth\n renderTextLine(buffer, x, y, bar, indicatorStyle, maxCol, undefined, ctx)\n } else if (showBorderless) {\n // Borderless: render centered inverse bar on first content row\n const padding = getPadding(props)\n const contentWidth = layout.width - padding.left - padding.right\n const bar = padCenter(indicator, contentWidth)\n const x = layout.x + padding.left\n const y = layout.y + padding.top\n const maxCol = x + contentWidth\n renderTextLine(buffer, x, y, bar, indicatorStyle, maxCol, undefined, ctx)\n }\n }\n\n // Bottom indicator\n if (ss.hiddenBelow > 0) {\n const indicator = `\\u25bc${ss.hiddenBelow}`\n\n if (border.bottom > 0) {\n // Bordered: render centered inverse bar on bottom border line\n const contentWidth = layout.width - border.left - border.right\n const bar = padCenter(indicator, contentWidth)\n const x = layout.x + border.left\n const y = layout.y + layout.height - 1\n const maxCol = x + contentWidth\n renderTextLine(buffer, x, y, bar, indicatorStyle, maxCol, undefined, ctx)\n } else if (showBorderless) {\n // Borderless: render indicator flush to viewport bottom\n const padding = getPadding(props)\n const contentWidth = layout.width - padding.left - padding.right\n const bar = padCenter(indicator, contentWidth)\n const x = layout.x + padding.left\n const y = layout.y + layout.height - padding.bottom - 1\n const maxCol = x + contentWidth\n renderTextLine(buffer, x, y, bar, indicatorStyle, maxCol, undefined, ctx)\n }\n }\n}\n\n/** Center text within a fixed width, padding with spaces on both sides.\n * Truncates from the right if text exceeds available width. */\nfunction padCenter(text: string, width: number): string {\n if (width <= 0) return \"\"\n if (text.length > width) return text.slice(0, width)\n if (text.length === width) return text\n const leftPad = Math.floor((width - text.length) / 2)\n const rightPad = width - text.length - leftPad\n return \" \".repeat(leftPad) + text + \" \".repeat(rightPad)\n}\n","/**\n * Phase 3.5: Decoration Phase\n *\n * Draws \"outside\" decorations — content that writes pixels OUTSIDE the owning\n * node's rect, into the parent's pixel space. Currently handles outlines.\n *\n * ## Why a separate phase?\n *\n * Outlines draw 1 cell beyond each edge of a Box. Those cells are NOT part of\n * the box's own region — they live in the parent's or even grandparent's\n * space. This breaks the per-node incremental dirty cascade:\n * - The box can be clean while its outline changed\n * - The outline pixels are not in any single node's \"region\"\n * - Clearing on removal requires knowing where the outline previously drew\n *\n * Instead of folding outlines into the cascade (which produced subtle\n * false-positive bugs at realistic scale), we treat them as a separate\n * decoration pass that runs AFTER content rendering on every frame:\n *\n * 1. Before content: restore cells under previous frame's outlines\n * (using snapshots captured when we drew them last time)\n * 2. Content render runs as normal — no outline-specific tracking\n * 3. After content: walk the tree, draw outlines for every node with\n * `outlineStyle`, snapshotting each written cell so the next frame\n * can restore it\n *\n * The snapshots are stored on the TerminalBuffer and travel with it via\n * clone(), so the cascade is naturally fed by the same prevBuffer that\n * drives incremental rendering.\n *\n * This pattern generalizes to other overlays (focus rings, hover halos,\n * selection borders) — any decoration that draws outside its owning node.\n */\n\nimport type { TerminalBuffer, Cell, Color } from \"../buffer\"\nimport type { BoxProps, AgNode } from \"@silvery/ag/types\"\nimport { getBorderSize, getPadding } from \"./helpers\"\nimport { renderOutline, getEffectiveBg } from \"./render-box\"\nimport { parseColor } from \"./render-helpers\"\nimport type { ClipBounds } from \"./types\"\n\n/**\n * Snapshot of a single cell, used to restore it when the overlaying outline\n * is removed on the next frame. Captures the full cell state so restore is\n * byte-identical to what was there before we drew the outline.\n */\nexport interface OutlineCellSnapshot {\n x: number\n y: number\n cell: Cell\n}\n\n/**\n * Restore cells at previously-drawn outline positions to their pre-outline\n * state. Called at the start of each incremental render, before the content\n * phase, on the cloned buffer. No-op when there are no previous snapshots\n * (fresh render or no outlines last frame).\n */\nexport function clearPreviousOutlines(buffer: TerminalBuffer): void {\n const snapshots = buffer.outlineSnapshots\n if (!snapshots || snapshots.length === 0) return\n for (const snap of snapshots) {\n buffer.setCell(snap.x, snap.y, snap.cell)\n }\n buffer.outlineSnapshots = []\n}\n\n/**\n * Walk the node tree, drawing outlines for every node with `outlineStyle`.\n * Captures per-cell snapshots so the next frame can restore these positions.\n *\n * Called AFTER the content render phase on every frame (both fresh and\n * incremental). Mirrors `renderNodeToBuffer`'s state threading for scroll\n * offsets, clip bounds, and inherited background — but does nothing except\n * visit the tree and draw outlines.\n */\nexport function renderDecorationPass(buffer: TerminalBuffer, root: AgNode): void {\n const snapshots: OutlineCellSnapshot[] = []\n walk(root, buffer, 0, undefined, { color: null }, snapshots)\n buffer.outlineSnapshots = snapshots\n}\n\n/**\n * Recursive tree walk. Each invocation corresponds to the state at a single\n * node — scroll offset, clip bounds, inherited background — matching what\n * `renderNodeToBuffer` would have threaded through its `NodeRenderState`.\n */\nfunction walk(\n node: AgNode,\n buffer: TerminalBuffer,\n scrollOffset: number,\n clipBounds: ClipBounds | undefined,\n inheritedBg: { color: Color },\n snapshots: OutlineCellSnapshot[],\n): void {\n // Virtual text nodes have no layout — they're rendered by their parent's\n // text collection pass. Nothing to draw and no children to descend.\n if (!node.layoutNode) return\n const layout = node.boxRect\n if (!layout) return\n\n // Respect the same visibility gates as the content phase.\n if (node.hidden) return\n const props = node.props as BoxProps\n if (props.display === \"none\") return\n\n // Apply scroll offset to the effective y position.\n const y = layout.y - scrollOffset\n\n // Off-screen viewport clipping — mirrors renderNodeToBuffer. Keeps the\n // walker cheap: large scroll containers with many hidden children return\n // early for any node entirely outside the visible window.\n if (y >= buffer.height || y + layout.height <= 0) return\n\n // Compute the effective background the outline should inherit from this\n // box — matches the `boxInheritedBg` calculation in renderOwnContent.\n const effectiveBg = getEffectiveBg(props)\n const theme = props.theme as Record<string, unknown> | undefined\n const themeBg =\n theme && typeof theme[\"bg-surface-default\"] === \"string\"\n ? (theme[\"bg-surface-default\"] as string)\n : theme && typeof theme[\"bg\"] === \"string\"\n ? (theme[\"bg\"] as string)\n : undefined\n const childInheritedBg: { color: Color } = effectiveBg\n ? { color: parseColor(effectiveBg) }\n : themeBg !== undefined\n ? { color: parseColor(themeBg) }\n : inheritedBg\n\n // Draw the outline AFTER content — this means we paint on top of whatever\n // siblings rendered in the gap around this box. The snapshot captures\n // those pre-outline pixels so the next frame can restore them.\n if (node.type === \"silvery-box\" && props.outlineStyle) {\n const boxInheritedBg = effectiveBg ? undefined : inheritedBg.color\n const positions = collectOutlineCells(\n layout.x,\n y,\n layout.width,\n layout.height,\n props,\n clipBounds,\n buffer,\n )\n for (const pos of positions) {\n // Snapshot the cell BEFORE the outline overwrites it.\n snapshots.push({ x: pos.x, y: pos.y, cell: buffer.getCell(pos.x, pos.y) })\n }\n renderOutline(\n buffer,\n layout.x,\n y,\n layout.width,\n layout.height,\n props,\n clipBounds,\n boxInheritedBg,\n )\n }\n\n // Descend into children with the appropriate state. Scroll containers\n // override scrollOffset for their normal-flow children; sticky children\n // use their computed sticky offset. Clip bounds tighten for overflow\n // containers.\n if (node.children.length === 0) return\n\n const isScrollContainer = props.overflow === \"scroll\" && node.scrollState\n const clipX = (props.overflowX ?? props.overflow) === \"hidden\"\n const clipY = (props.overflowY ?? props.overflow) === \"hidden\"\n\n if (isScrollContainer) {\n const ss = node.scrollState!\n const childClip = computeChildClip(layout, props, clipBounds, 0, false, true)\n // Non-sticky children: rendered with container scroll offset.\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i]\n if (!child) continue\n const cp = child.props as BoxProps\n if (cp.position === \"sticky\") continue\n if (i < ss.firstVisibleChild || i > ss.lastVisibleChild) continue\n walk(child, buffer, ss.offset, childClip, childInheritedBg, snapshots)\n }\n // Sticky children: rendered at their computed sticky offset.\n if (ss.stickyChildren) {\n for (const sticky of ss.stickyChildren) {\n const child = node.children[sticky.index]\n if (!child) continue\n const stickyOffset = sticky.naturalTop - sticky.renderOffset\n walk(child, buffer, stickyOffset, childClip, childInheritedBg, snapshots)\n }\n }\n } else {\n const childClip =\n clipX || clipY\n ? computeChildClip(layout, props, clipBounds, scrollOffset, clipX, clipY)\n : clipBounds\n for (const child of node.children) {\n walk(child, buffer, scrollOffset, childClip, childInheritedBg, snapshots)\n }\n }\n}\n\n/**\n * Compute the cells an outline would write, given the same inputs\n * `renderOutline` uses. Kept in lockstep with render-box.ts — any change to\n * the outline geometry (e.g., new outline styles, per-side toggles) must be\n * mirrored here. If they drift, the snapshots won't cover every cell the\n * next `renderOutline` call writes, and stale pixels will leak through.\n *\n * Uses the SAME visibility checks as `renderOutline` so the snapshot set is\n * an exact match for the cell set the renderer will overwrite.\n */\nfunction collectOutlineCells(\n x: number,\n y: number,\n width: number,\n height: number,\n props: BoxProps,\n clipBounds: ClipBounds | undefined,\n buffer: TerminalBuffer,\n): { x: number; y: number }[] {\n const out: { x: number; y: number }[] = []\n const ox = x - 1\n const oy = y - 1\n const ow = width + 2\n const oh = height + 2\n\n const isRowVisible = (row: number): boolean => {\n if (!clipBounds) return row >= 0 && row < buffer.height\n return row >= clipBounds.top && row < clipBounds.bottom && row < buffer.height\n }\n const isColVisible = (col: number): boolean => {\n if (clipBounds?.left === undefined || clipBounds.right === undefined)\n return col >= 0 && col < buffer.width\n return col >= clipBounds.left && col < clipBounds.right && col < buffer.width\n }\n\n const showTop = props.outlineTop !== false\n const showBottom = props.outlineBottom !== false\n const showLeft = props.outlineLeft !== false\n const showRight = props.outlineRight !== false\n\n // Top row\n if (showTop && isRowVisible(oy)) {\n if (showLeft && isColVisible(ox)) out.push({ x: ox, y: oy })\n for (let col = ox + 1; col < ox + ow - 1 && col < buffer.width; col++) {\n if (isColVisible(col)) out.push({ x: col, y: oy })\n }\n if (showRight && ox + ow - 1 < buffer.width && isColVisible(ox + ow - 1)) {\n out.push({ x: ox + ow - 1, y: oy })\n }\n }\n\n // Sides\n const sideStart = showTop ? oy + 1 : oy\n const sideEnd = showBottom ? oy + oh - 1 : oy + oh\n for (let row = sideStart; row < sideEnd; row++) {\n if (!isRowVisible(row)) continue\n if (showLeft && isColVisible(ox)) out.push({ x: ox, y: row })\n if (showRight && ox + ow - 1 < buffer.width && isColVisible(ox + ow - 1)) {\n out.push({ x: ox + ow - 1, y: row })\n }\n }\n\n // Bottom row\n const bottomY = oy + oh - 1\n if (showBottom && isRowVisible(bottomY)) {\n if (showLeft && isColVisible(ox)) out.push({ x: ox, y: bottomY })\n for (let col = ox + 1; col < ox + ow - 1 && col < buffer.width; col++) {\n if (isColVisible(col)) out.push({ x: col, y: bottomY })\n }\n if (showRight && ox + ow - 1 < buffer.width && isColVisible(ox + ow - 1)) {\n out.push({ x: ox + ow - 1, y: bottomY })\n }\n }\n\n return out\n}\n\n/**\n * Local copy of `computeChildClipBounds` from render-phase.ts. The decoration\n * walker can't import private render-phase helpers without tangling modules,\n * so we reproduce the same computation here. Must stay in sync.\n */\nfunction computeChildClip(\n layout: NonNullable<AgNode[\"boxRect\"]>,\n props: BoxProps,\n parentClip: ClipBounds | undefined,\n scrollOffset: number,\n horizontal: boolean,\n vertical: boolean,\n): ClipBounds {\n const border = props.borderStyle ? getBorderSize(props) : { top: 0, bottom: 0, left: 0, right: 0 }\n const padding = getPadding(props)\n const adjustedY = layout.y - scrollOffset\n const nodeClip: ClipBounds = vertical\n ? {\n top: adjustedY + border.top + padding.top,\n bottom: adjustedY + layout.height - border.bottom - padding.bottom,\n }\n : { top: -Infinity, bottom: Infinity }\n if (horizontal) {\n nodeClip.left = layout.x + border.left + padding.left\n nodeClip.right = layout.x + layout.width - border.right - padding.right\n }\n if (!parentClip) return nodeClip\n const result: ClipBounds = {\n top: vertical ? Math.max(parentClip.top, nodeClip.top) : parentClip.top,\n bottom: vertical ? Math.min(parentClip.bottom, nodeClip.bottom) : parentClip.bottom,\n }\n if (horizontal && nodeClip.left !== undefined && nodeClip.right !== undefined) {\n result.left = Math.max(parentClip.left ?? 0, nodeClip.left)\n result.right = Math.min(parentClip.right ?? Infinity, nodeClip.right)\n } else if (parentClip.left !== undefined && parentClip.right !== undefined) {\n result.left = parentClip.left\n result.right = parentClip.right\n }\n return result\n}\n","/**\n * Cascade Predicates — Pure boolean logic extracted from renderNodeToBuffer.\n *\n * TEST/STRICT-ONLY ORACLE: In production, the reactive system (alien-signals)\n * drives cascade computation. This module is only used as a verification oracle\n * when SILVERY_REACTIVE_VERIFY=1 or SILVERY_REACTIVE=0 (fallback mode). The\n * bundler tree-shakes it when STRICT is off since all call sites are gated\n * behind `_reactiveVerifyEnabled` or `!_reactiveEnabled`.\n *\n * These 6 computed values (plus 1 intermediate: textPaintDirty) control the\n * entire incremental rendering cascade. Extracted here for exhaustive testing.\n *\n * The actual rendering code in render-phase.ts computes some inputs inline\n * (absoluteChildMutated, descendantOverflowChanged require node tree access),\n * but the boolean algebra is identical.\n *\n * TRUTH TABLE (key invariants):\n *\n * ┌─────────────────────────────────────────────────────────────────────────────┐\n * │ canSkipEntireSubtree │\n * │ = hasPrevBuffer && !contentDirty && !stylePropsDirty && !layoutChanged │\n * │ && !subtreeDirty && !childrenDirty && !childPositionChanged │\n * │ && !ancestorLayoutChanged │\n * │ True only when hasPrevBuffer=true AND all 7 dirty flags are false. │\n * │ When true, the node is skipped entirely (clone has correct pixels). │\n * │ NOTE: render-phase.ts also checks !scrollOffsetChanged (node-level │\n * │ defensive check for scroll containers — not modeled here). │\n * ├─────────────────────────────────────────────────────────────────────────────┤\n * │ textPaintDirty (intermediate) │\n * │ = isTextNode && stylePropsDirty │\n * │ For TEXT nodes, stylePropsDirty IS a content area change (no borders). │\n * ├─────────────────────────────────────────────────────────────────────────────┤\n * │ contentAreaAffected │\n * │ = contentDirty || layoutChanged || childPositionChanged │\n * │ || childrenDirty || bgDirty || textPaintDirty │\n * │ || absoluteChildMutated || descendantOverflowChanged │\n * │ True when anything changed that affects the node's content area. │\n * │ Excludes border-only paint changes for BOX nodes. Outlines are NOT │\n * │ tracked here — they're handled by a separate decoration pass. │\n * ├─────────────────────────────────────────────────────────────────────────────┤\n * │ bgRefillNeeded │\n * │ = hasPrevBuffer && !contentAreaAffected && subtreeDirty && hasBgColor │\n * │ Descendant changed inside a bg-bearing Box. Forces bg refill. │\n * │ Mutually exclusive with contentAreaAffected (gated on !contentAreaAffected).│\n * ├─────────────────────────────────────────────────────────────────────────────┤\n * │ contentRegionCleared │\n * │ = (hasPrevBuffer || ancestorCleared) && contentAreaAffected │\n * │ && !hasBgColor │\n * │ Clear region with inherited bg when content changed but no own bg fill. │\n * │ False when hasPrevBuffer=false AND ancestorCleared=false (fresh buffer). │\n * ├─────────────────────────────────────────────────────────────────────────────┤\n * │ skipBgFill │\n * │ = hasPrevBuffer && !ancestorCleared && !contentAreaAffected │\n * │ && !bgRefillNeeded │\n * │ Clone already has correct bg. Skip redundant fill. │\n * ├─────────────────────────────────────────────────────────────────────────────┤\n * │ childrenNeedFreshRender │\n * │ = (hasPrevBuffer || ancestorCleared) && (contentAreaAffected │\n * │ || bgRefillNeeded) │\n * │ Children must re-render (childHasPrev=false). │\n * │ False when hasPrevBuffer=false AND ancestorCleared=false (fresh buffer). │\n * └─────────────────────────────────────────────────────────────────────────────┘\n *\n * KEY INVARIANTS:\n * 1. contentAreaAffected && bgRefillNeeded can never both be true\n * (bgRefillNeeded is gated on !contentAreaAffected)\n * 2. contentRegionCleared && skipBgFill can never both be true\n * (contentRegionCleared requires contentAreaAffected; skipBgFill requires !contentAreaAffected)\n * 3. When !hasPrevBuffer && !ancestorCleared: contentRegionCleared=false, childrenNeedFreshRender=false\n * (both gated on hasPrevBuffer || ancestorCleared)\n * 4. canSkipEntireSubtree requires hasPrevBuffer=true\n */\n\n// ============================================================================\n// COMPLETE INVALIDATION MODEL\n// ============================================================================\n//\n// This section documents EVERY condition that affects skip/render/clear/buffer\n// decisions in the silvery render pipeline. The 14 CascadeInputs below model the\n// core boolean algebra, but the real pipeline has additional conditions checked\n// inline in render-phase.ts that are not captured in computeCascade(). This\n// document is the authoritative inventory.\n//\n// ┌─────────────────────────────────────────────────────────────────────────────┐\n// │ PART 1: ALL INVALIDATION INPUTS │\n// │ │\n// │ Input │ Owner │ Set by │ Cleared by │ Lifetime │ In computeCascade? │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ contentDirtyEpoch │ reconciler │ commitUpdate, │ advanceRenderEpoch (O(1)) │ per-render-pass │ YES (contentDirty) │\n// │ │ │ commitTextUpdate, │ clearNodeDirtyFlags (skip) │ │ │\n// │ │ │ appendChild, etc. │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ stylePropsDirtyEpoch │ reconciler │ commitUpdate │ advanceRenderEpoch │ per-render-pass │ YES (stylePropsDirty)│\n// │ │ │ (always for visual │ clearNodeDirtyFlags (skip) │ │ │\n// │ │ │ changes; survives │ │ │ │\n// │ │ │ measure phase │ │ │ │\n// │ │ │ clearing of │ │ │ │\n// │ │ │ contentDirty) │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ bgDirtyEpoch │ reconciler │ commitUpdate (when │ advanceRenderEpoch │ per-render-pass │ YES (bgDirty) │\n// │ │ │ backgroundColor, │ clearNodeDirtyFlags (skip) │ │ │\n// │ │ │ borderStyle removed,│ │ │ │\n// │ │ │ outlineStyle │ │ │ │\n// │ │ │ removed, or theme │ │ │ │\n// │ │ │ changed) │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ subtreeDirtyEpoch │ reconciler │ markSubtreeDirty │ advanceRenderEpoch │ per-render-pass │ YES (subtreeDirty) │\n// │ │ + layout │ (walks up from any │ clearNodeDirtyFlags (skip) │ │ │\n// │ │ │ dirty descendant); │ │ │ │\n// │ │ │ propagateLayout │ │ │ │\n// │ │ │ (when child rect │ │ │ │\n// │ │ │ changes); scrollPhase│ │ │ │\n// │ │ │ (on offset/range │ │ │ │\n// │ │ │ change); stickyPhase│ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ childrenDirtyEpoch │ reconciler │ appendChild, │ advanceRenderEpoch │ per-render-pass │ YES (childrenDirty)│\n// │ │ │ removeChild, │ clearNodeDirtyFlags (skip) │ │ │\n// │ │ │ insertBefore, │ │ │ │\n// │ │ │ clearContainer │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ layoutChangedThisFrame │ layout │ propagateLayout │ advanceRenderEpoch │ per-render-pass │ YES (layoutChanged)│\n// │ │ │ (when rect differs │ clearNodeDirtyFlags (skip) │ │ via isCurrentEpoch │\n// │ │ │ from prevLayout) │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ Flexily isDirty │ reconciler │ commitUpdate (when │ cleared by Flexily after │ per-layout-pass │ NO — consumed by │\n// │ (via markDirty()) │ │ layout props change)│ calculateLayout() runs │ │ layout phase only │\n// │ │ │ │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ childPositionChanged │ render │ hasChildPositionChanged│ implicit (per-frame │ per-render-frame│ YES │\n// │ (computed inline) │ │ compares child │ computation, not stored) │ │ │\n// │ │ │ boxRect.x/y vs │ │ │ │\n// │ │ │ prevLayout.x/y │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ ancestorLayoutChanged │ render │ propagated top-down │ implicit (per-frame │ per-render-frame│ YES │\n// │ (threaded state) │ │ via NodeRenderState │ propagation via nodeState) │ │ │\n// │ │ │ = isCurrentEpoch( │ │ │ │\n// │ │ │ node.layoutChanged │ │ │ │\n// │ │ │ ThisFrame) || │ │ │ │\n// │ │ │ parent's value │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ ancestorCleared │ render │ propagated top-down │ implicit (per-frame │ per-render-frame│ YES │\n// │ (threaded state) │ │ = contentRegion- │ propagation via nodeState) │ │ │\n// │ │ │ Cleared || (parent │ │ │ │\n// │ │ │ ancestorCleared && │ │ │ │\n// │ │ │ !effectiveBg) │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ hasPrevBuffer │ render │ top-level: prevBuf │ implicit (per-frame │ per-render-frame│ YES │\n// │ (threaded state) │ │ && same dimensions; │ propagation via nodeState) │ │ │\n// │ │ │ per-child: false │ │ │ │\n// │ │ │ when childrenDirty │ │ │ │\n// │ │ │ || childPosition- │ │ │ │\n// │ │ │ Changed || children-│ │ │ │\n// │ │ │ NeedFreshRender │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ isTextNode │ inherent │ node.type at │ never (immutable) │ node lifetime │ YES │\n// │ │ │ creation │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ hasBgColor │ render │ getEffectiveBg( │ implicit (per-frame │ per-render-frame│ YES │\n// │ (computed inline) │ │ props): explicit │ computation from props) │ │ │\n// │ │ │ backgroundColor or │ │ │ │\n// │ │ │ theme.bg │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ absoluteChildMutated │ render │ buildCascadeInputs: │ implicit (per-frame │ per-render-frame│ YES │\n// │ (computed inline) │ │ any absolute child │ computation) │ │ │\n// │ │ │ has childrenDirty, │ │ │ │\n// │ │ │ layoutChanged, or │ │ │ │\n// │ │ │ childPositionChanged│ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ descendantOverflowChanged │ render │ buildCascadeInputs │ implicit (per-frame │ per-render-frame│ YES │\n// │ (computed inline) │ │ → hasDescendant- │ computation) │ │ │\n// │ │ │ OverflowChanged: │ │ │ │\n// │ │ │ recursive check if │ │ │ │\n// │ │ │ descendant prev- │ │ │ │\n// │ │ │ Layout overflows │ │ │ │\n// │ │ │ this node's rect │ │ │ │\n// │ │ │ AND layoutChanged- │ │ │ │\n// │ │ │ ThisFrame is current │ │ │ │\n// ├──────────────────────────┼────────────┼─────────────────────┼────────────────────────────┼─────────────────┼────────────────────┤\n// │ │\n// │ THE FOLLOWING INPUTS ARE NOT IN computeCascade — CHECKED INLINE IN render-phase.ts │\n// │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ scrollOffsetChanged │ render │ inline comparison: │ implicit (per-frame │ per-render-frame│ NO — checked in │\n// │ │ │ node.scrollState. │ computation) │ │ canSkipEntireSub- │\n// │ │ │ offset !== node. │ │ │ tree (prevents skip│\n// │ │ │ scrollState. │ │ │ when scroll offset │\n// │ │ │ prevOffset │ │ │ changed) │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ node.hidden │ reconciler │ hideInstance / │ unhideInstance │ until unhidden │ NO — early return │\n// │ │ │ hideTextInstance │ │ │ before cascade │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ display=\"none\" │ reconciler │ commitUpdate (prop) │ commitUpdate (prop change) │ until prop change│ NO — early return │\n// │ │ │ │ │ │ before cascade │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ !node.layoutNode │ reconciler │ node creation │ never (immutable) │ node lifetime │ NO — early return │\n// │ (virtual text node) │ │ (virtual text has │ │ │ (rendered by parent │\n// │ │ │ no Yoga layout) │ │ │ collectTextContent) │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ off-screen (viewport │ render │ inline: screenY >= │ implicit (per-frame │ per-render-frame│ NO — early return │\n// │ clipping) │ │ buffer.height || │ computation) │ │ (dirty flags │\n// │ │ │ screenY + height │ │ │ preserved for when │\n// │ │ │ <= 0 │ │ │ scrolled into view)│\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ prevBuffer dimension │ render │ renderPhase entry: │ implicit (per-frame) │ per-render-pass │ NO — sets top-level│\n// │ mismatch │ │ prevBuffer.width │ │ │ hasPrevBuffer=false│\n// │ │ │ !== layout.width │ │ │ (full fresh render)│\n// │ │ │ || height mismatch │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ bufferIsCloned │ render │ set once at render- │ implicit (per-frame) │ per-render-pass │ NO — guards │\n// │ (threaded state) │ │ Phase entry from │ │ │ clearExcessArea │\n// │ │ │ hasPrevBuffer │ │ │ (no stale pixels │\n// │ │ │ │ │ │ on fresh buffer) │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ hasDescendantWithBg │ render │ inline tree walk │ implicit (per-frame) │ per-render-frame│ NO — disables │\n// │ (computed inline) │ │ when bgOnlyChange │ │ │ bgOnlyChange fast │\n// │ │ │ is true │ │ │ path │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ isStyleOnlyDirty │ reconciler │ trackStyleOnlyDirty │ clearDirtyTracking (post- │ per-render-pass │ NO — enables text │\n// │ (module set) │ │ in commitUpdate │ render) │ │ restyle fast path │\n// │ │ │ when contentChanged │ │ │ (CURRENTLY DISABLED)│\n// │ │ │ =\"style\" && no │ │ │ │\n// │ │ │ layout/content/bg │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ hasChildWithBg │ render │ inline tree walk │ implicit (per-frame) │ per-render-frame│ NO — disables text │\n// │ (computed inline) │ │ when text style- │ │ │ restyle fast path │\n// │ │ │ only path active │ │ │ (CURRENTLY DISABLED)│\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ scroll container tier │ render │ planScrollRender() │ implicit (per-frame) │ per-render-frame│ NO — separate tier │\n// │ (shift/clear/subtree) │ │ pure function from │ │ │ planner with own │\n// │ │ │ scrollOffsetChanged,│ │ │ inputs (see below) │\n// │ │ │ visibleRangeChanged,│ │ │ │\n// │ │ │ hasStickyChildren, │ │ │ │\n// │ │ │ childrenNeedFresh- │ │ │ │\n// │ │ │ Render, childrenDirty│ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ stickyForceRefresh │ render │ scroll: planScroll- │ implicit (per-frame) │ per-render-frame│ NO — forces all │\n// │ │ │ Render (Tier 3 + │ │ │ first-pass children│\n// │ │ │ hasStickyChildren); │ │ │ to hasPrevBuffer= │\n// │ │ │ normal: hasPrev && │ │ │ false │\n// │ │ │ hasStickyChildren │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ visibleRangeChanged │ render │ inline comparison: │ implicit (per-frame) │ per-render-frame│ NO — input to │\n// │ (scroll containers) │ │ firstVisibleChild │ │ │ planScrollRender │\n// │ │ │ !== prev or last │ │ │ │\n// │ │ │ !== prev │ │ │ │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ nodeTheme (theme prop) │ reconciler │ commitUpdate (prop) │ per-frame (read from props)│ per-render-frame│ NO — pushContext- │\n// │ │ │ │ │ │ Theme/popContext- │\n// │ │ │ │ │ │ Theme during render│\n// │ │ │ │ │ │ (bgDirtyEpoch set │\n// │ │ │ │ │ │ on theme change) │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ clipBounds │ render │ computeChildClip- │ implicit (per-frame) │ per-render-frame│ NO — passed through│\n// │ (threaded state) │ │ Bounds from │ │ │ nodeState; used for│\n// │ │ │ overflow=hidden/ │ │ │ clipping fills and │\n// │ │ │ scroll containers │ │ │ text rendering │\n// │──────────────────────────│────────────│─────────────────────│────────────────────────────│─────────────────│────────────────────│\n// │ inheritedBg / inheritedFg │ render │ threaded top-down: │ implicit (per-frame) │ per-render-frame│ NO — used for text │\n// │ (threaded state) │ │ computed from │ │ │ bg inheritance and │\n// │ │ │ effectiveBg/color/ │ │ │ region clearing │\n// │ │ │ theme at each node │ │ │ │\n// └──────────────────────────┴────────────┴─────────────────────┴────────────────────────────┴─────────────────┴────────────────────┘\n//\n// ┌─────────────────────────────────────────────────────────────────────────────┐\n// │ PART 2: NON-LOCAL DEPENDENCIES THAT CAN FORCE REPAINT │\n// └─────────────────────────────────────────────────────────────────────────────┘\n//\n// 1. ANCESTOR BACKGROUND CHANGE\n// When an ancestor's backgroundColor changes, the ancestor's bgDirty triggers\n// contentAreaAffected, which cascades childrenNeedFreshRender=true to all\n// descendants. Descendants get hasPrevBuffer=false, forcing full re-render.\n// If bgOnlyChange were enabled (currently disabled), the fillBg() path would\n// update bg in-place, but only when no descendant has its own bg\n// (hasDescendantWithBg check).\n//\n// 2. ANCESTOR LAYOUT CHANGE\n// When an ancestor moves or resizes, ancestorLayoutChanged propagates down\n// through the entire subtree. Even if a descendant has no dirty flags, its\n// pixels in the cloned buffer are at wrong absolute coordinates. The skip\n// condition checks !ancestorLayoutChanged. Additionally, the parent's\n// childrenNeedFreshRender cascade sets childHasPrev=false.\n// Key: ancestorLayoutChanged does NOT break at backgroundColor boundaries\n// (unlike ancestorCleared), because bg fill doesn't fix wrong coordinates.\n//\n// 3. SIBLING POSITION SHIFT\n// When a sibling resizes, other siblings shift positions (flexbox reflow).\n// hasChildPositionChanged detects this at the parent level by comparing each\n// child's boxRect.x/y to prevLayout.x/y. Triggers childrenNeedRepaint=true,\n// setting childHasPrev=false for all children.\n//\n// 4. ANCESTOR CLEAR CASCADE\n// When an ancestor without backgroundColor clears its region (contentRegion-\n// Cleared=true), ancestorCleared propagates down. This cascade BREAKS at\n// nodes with backgroundColor — their bg fill covers the stale pixels, so\n// their children don't see stale buffer content.\n//\n// 5. ABSOLUTE CHILD MUTATION → PARENT CLEAR\n// When an absolute-positioned child changes structure (children mount/unmount,\n// layout shift), absoluteChildMutated forces the PARENT's contentAreaAffected\n// =true. This clears the parent's entire region and re-renders all normal-flow\n// children, removing stale overlay pixels from gap areas.\n//\n// 6. DESCENDANT OVERFLOW → ANCESTOR CLEAR\n// When a descendant overflows beyond an ancestor's rect and then shrinks,\n// the stale overflow pixels are OUTSIDE the descendant's parent's content\n// area. hasDescendantOverflowChanged recursively detects this at the ancestor\n// level and triggers contentAreaAffected + clearDescendantOverflowRegions.\n//\n// 7. SCROLL OFFSET CHANGE → SUBTREE RE-RENDER\n// The scroll phase sets subtreeDirty on the scroll container when offset or\n// visible range changes. The render phase then plans a scroll tier:\n// - Tier 1 (shift): buffer.scrollRegion shifts pixels; only edges re-render\n// - Tier 2 (clear): full viewport clear; all children get hasPrevBuffer=false\n// - Tier 3 (subtree): only dirty descendants re-render\n// Tier 1 is UNSAFE with sticky children (sticky overwrite + shift = corruption).\n//\n// 8. STICKY CHILDREN → stickyForceRefresh\n// When sticky children exist in Tier 3 (or in normal containers), all\n// first-pass children are forced to re-render (hasPrevBuffer=false). The\n// cloned buffer has stale content from previous frames' sticky positions.\n// A pre-clear to null bg ensures fresh render baseline.\n//\n// 9. THEME CHANGE CASCADE\n// When a node's `theme` prop changes, bgDirtyEpoch is set (because theme\n// contains bg). pushContextTheme/popContextTheme during rendering ensures\n// all $token-based colors resolve to new values. Children re-render because\n// bgDirty → contentAreaAffected → childrenNeedFreshRender.\n//\n// 10. VISIBLE RANGE CHANGE (scroll containers)\n// When firstVisibleChild/lastVisibleChild changes (scroll phase detects\n// this), the scroll container's subtreeDirtyEpoch is set and\n// visibleRangeChanged feeds into planScrollRender to select the tier.\n//\n// ┌─────────────────────────────────────────────────────────────────────────────┐\n// │ PART 3: BUFFER VALIDITY STATES AND TRANSITIONS │\n// └─────────────────────────────────────────────────────────────────────────────┘\n//\n// A node's cached buffer (pixels in the cloned TerminalBuffer) can be in one of\n// these validity states:\n//\n// VALID (canSkipEntireSubtree = true)\n// All of: hasPrevBuffer=true, no dirty flags, no ancestorLayoutChanged,\n// no scrollOffsetChanged. The cloned buffer has correct pixels at correct\n// positions. Node is skipped entirely.\n//\n// STALE_CONTENT (hasPrevBuffer=true, some dirty flag set)\n// The buffer has pixels from the previous frame but they're wrong:\n// - contentDirty: text content or content-affecting props changed\n// - stylePropsDirty: visual style changed (color, border, etc.)\n// - bgDirty: background color changed\n// - childrenDirty: children added/removed/reordered\n// - layoutChanged: node moved or resized\n// - subtreeDirty: some descendant changed (node itself may be skippable)\n// - childPositionChanged: siblings shifted positions\n// - absoluteChildMutated: overlay child changed\n// - descendantOverflowChanged: overflow descendant changed\n// Action: re-render with clearing as appropriate.\n//\n// STALE_POSITION (hasPrevBuffer=true, ancestorLayoutChanged=true)\n// The buffer has correct content but at WRONG coordinates because an\n// ancestor moved/resized. The node itself has no dirty flags.\n// Action: must re-render at new position; can't skip.\n//\n// STALE_SCROLL (hasPrevBuffer=true, scrollOffsetChanged=true)\n// The buffer has correct content but scroll offset changed, so visible\n// children may have shifted. Defensive check in canSkipEntireSubtree.\n// Action: apply scroll tier strategy (shift/clear/subtree).\n//\n// CLEARED (hasPrevBuffer=false or true, ancestorCleared=true)\n// An ancestor erased this node's buffer region. The buffer at this\n// position contains inherited bg (spaces), not previous content.\n// - If hasPrevBuffer=false: fresh render, no clearing needed.\n// - If ancestorCleared=true: parent cleared, descendants may still need\n// their own clearing for sub-regions.\n// Breaks at backgroundColor boundaries (bg fill covers cleared area).\n//\n// FRESH (hasPrevBuffer=false, ancestorCleared=false)\n// First render or dimension change. Buffer is a blank TerminalBuffer\n// (all cells empty). No clearing needed, no skipping possible.\n// contentRegionCleared=false and childrenNeedFreshRender=false because\n// both are gated on (hasPrevBuffer || ancestorCleared).\n//\n// FRESH_OVERLAY (hasPrevBuffer=false, ancestorCleared=false, absolute/sticky)\n// Buffer at this position contains first-pass content from normal-flow\n// siblings (not \"previous frame\" content). Used for absolute and sticky\n// children in the second/third rendering pass.\n// - ancestorCleared=false prevents transparent overlays from clearing\n// the normal-flow content underneath.\n//\n// State transitions per frame:\n// VALID --[dirty flag set]--> STALE_*\n// VALID --[ancestor layout]--> STALE_POSITION\n// VALID --[scroll change]---> STALE_SCROLL\n// STALE_* --[re-render]-----> VALID (dirty flags cleared)\n// FRESH --[first render]----> VALID (buffer populated)\n// any --[dimension change]--> FRESH (new buffer allocated)\n// any --[parent cascade]----> CLEARED (parent cleared region)\n//\n// ┌─────────────────────────────────────────────────────────────────────────────┐\n// │ PART 4: REASONS A CACHED BUFFER CAN BE INVALID │\n// └─────────────────────────────────────────────────────────────────────────────┘\n//\n// The cloned buffer can have stale pixels for these reasons:\n//\n// 1. Text content changed (contentDirtyEpoch) — old chars in buffer\n// 2. Style props changed (stylePropsDirtyEpoch) — old colors/attrs in buffer\n// 3. Background changed (bgDirtyEpoch) — old bg color, or removed bg leaving\n// stale colored pixels that need clearing\n// 4. Children restructured (childrenDirtyEpoch) — old children's pixels remain;\n// gap areas may have orphaned content\n// 5. Layout changed (layoutChangedThisFrame) — pixels at wrong position;\n// node may have grown (new area uninitialized) or shrunk (excess area stale)\n// 6. Child position shifted (childPositionChanged) — siblings moved, gap areas\n// between children have stale pixels from old positions\n// 7. Absolute child mutated (absoluteChildMutated) — overlay pixels in gap areas\n// between current children are stale from old overlay positions\n// 8. Descendant overflow changed (descendantOverflowChanged) — pixels beyond this\n// node's rect are stale from previous overflow that no longer extends there\n// 9. Scroll offset changed (scrollOffsetChanged) — children's visual positions\n// shifted; buffer has content at old scroll positions\n// 10. Ancestor layout changed (ancestorLayoutChanged) — this node's absolute\n// position in the buffer is wrong even though its own layout didn't change\n// 11. Ancestor cleared (ancestorCleared) — an ancestor erased this area; buffer\n// has inherited bg, not this node's content\n// 12. Sticky children stale positioning — cloned buffer has pixels from previous\n// frames' sticky render positions; stickyForceRefresh pre-clears and forces\n// all first-pass children to re-render\n// 13. Node shrunk (clearExcessArea) — old bounds were larger; excess pixels remain\n// in the right/bottom margin of the old rect\n// 14. Node hidden/unhidden — Suspense boundary toggled visibility\n// 15. display=\"none\" toggled — node occupies 0x0 space but old pixels may remain\n// 16. Border/outline removed (bgDirtyEpoch) — stale border/outline characters\n// persist in the clone; bgDirty ensures contentAreaAffected triggers clearing\n// 17. Theme changed (bgDirtyEpoch) — all $token colors resolve differently;\n// the entire subtree's colors are stale\n//\n// ┌─────────────────────────────────────────────────────────────────────────────┐\n// │ PART 5: PIPELINE PHASE OWNERSHIP SUMMARY │\n// └─────────────────────────────────────────────────────────────────────────────┘\n//\n// RECONCILER (host-config.ts):\n// Sets: contentDirtyEpoch, stylePropsDirtyEpoch, bgDirtyEpoch,\n// childrenDirtyEpoch, hidden\n// Calls: layoutNode.markDirty() (Flexily's isDirty propagates to root)\n// Propagates: subtreeDirtyEpoch (upward via markSubtreeDirty)\n// Tracks: contentDirtyNodes, styleOnlyDirtyNodes,\n// scrollDirtyNodes (in dirty-tracking.ts)\n//\n// LAYOUT PHASE (layout-phase.ts):\n// Sets: layoutChangedThisFrame (via propagateLayout)\n// Propagates: subtreeDirtyEpoch (upward when layout changes)\n// Checks: root.layoutNode.isDirty() — sole gate for running layout\n// Computes: boxRect, prevLayout, scrollState (scroll phase),\n// stickyChildren (sticky phase), scrollRect/screenRect\n//\n// RENDER PHASE (render-phase.ts):\n// Reads: ALL epoch flags, layoutChangedThisFrame, scrollState, stickyChildren\n// Computes: childPositionChanged, absoluteChildMutated, descendantOverflow-\n// Changed, scrollOffsetChanged, clipBounds, inheritedBg/Fg\n// Threads: hasPrevBuffer, ancestorCleared, ancestorLayoutChanged,\n// bufferIsCloned, scrollOffset, clipBounds, inheritedBg, inheritedFg\n// Calls: computeCascade() for core boolean algebra\n// Clears: all dirty epoch flags via advanceRenderEpoch() (O(1))\n// Syncs: prevLayout = boxRect via syncPrevLayout() (post-render)\n//\n// ┌─────────────────────────────────────────────────────────────────────────────┐\n// │ PART 6: DISABLED FAST PATHS (currently hardcoded false) │\n// └─────────────────────────────────────────────────────────────────────────────┘\n//\n// bgOnlyChange (line 180 in this file):\n// DISABLED — hardcoded `const bgOnlyChange = false`.\n// When only backgroundColor changed on a Box with bg, fillBg() would preserve\n// child chars. Disabled because it causes incremental rendering mismatches\n// (fg colors lost on child nodes). Additional safety conditions exist even\n// in the enabled path:\n// - hasDescendantWithBg: descendant bg would be overwritten by fillBg\n// - !ancestorLayoutChanged: children positions may have shifted\n// - !ancestorCleared: parent cleared stale pixels, children must re-render\n//\n// useTextStyleFastPath (render-phase.ts):\n// DISABLED — hardcoded `const useTextStyleFastPath = false`.\n// When only visual style props changed on a Text node (isStyleOnlyDirty),\n// buffer.restyleRegion() would update fg/bg/attrs in-place without re-\n// collecting text. Disabled because it causes incremental rendering mismatches\n// (fg colors lost). Additional safety conditions:\n// - !contentDirty, !childrenDirty, !bgDirty\n// - !ancestorCleared, !ancestorLayoutChanged\n// - !hasChildWithBg (nested children with own bg)\n// - hasPrevBuffer=true\n//\n// ============================================================================\n\n/** Inputs to the cascade predicates (all boolean flags from renderNodeToBuffer) */\nexport interface CascadeInputs {\n hasPrevBuffer: boolean\n contentDirty: boolean\n stylePropsDirty: boolean\n layoutChanged: boolean\n subtreeDirty: boolean\n childrenDirty: boolean\n childPositionChanged: boolean\n ancestorLayoutChanged: boolean\n ancestorCleared: boolean\n bgDirty: boolean\n isTextNode: boolean\n hasBgColor: boolean\n absoluteChildMutated: boolean\n descendantOverflowChanged: boolean\n}\n\n/** Outputs of the cascade predicates */\nexport interface CascadeOutputs {\n canSkipEntireSubtree: boolean\n contentAreaAffected: boolean\n bgRefillNeeded: boolean\n contentRegionCleared: boolean\n skipBgFill: boolean\n childrenNeedFreshRender: boolean\n /**\n * True when bgDirty is the ONLY reason contentAreaAffected is true, and the\n * node has a backgroundColor. In this case, renderBox can use fillBg() (which\n * preserves existing chars) instead of fill() (which overwrites with spaces).\n * This avoids the cascade to children — clean children keep their chars from\n * the cloned buffer with the new bg applied.\n *\n * Requirements: hasPrevBuffer, bgDirty, hasBgColor, no other contentAreaAffected triggers.\n */\n bgOnlyChange: boolean\n}\n\n/**\n * Compute all cascade predicate values from boolean inputs.\n *\n * This is a pure function — no side effects, no node dependencies.\n * The formulas exactly match those in render-phase.ts renderNodeToBuffer.\n */\nexport function computeCascade(inputs: CascadeInputs): CascadeOutputs {\n const {\n hasPrevBuffer,\n contentDirty,\n stylePropsDirty,\n layoutChanged,\n subtreeDirty,\n childrenDirty,\n childPositionChanged,\n ancestorLayoutChanged,\n ancestorCleared,\n bgDirty,\n isTextNode,\n hasBgColor,\n absoluteChildMutated,\n descendantOverflowChanged,\n } = inputs\n\n // FAST PATH: Skip unchanged subtrees when we have a valid previous buffer.\n const canSkipEntireSubtree =\n hasPrevBuffer &&\n !contentDirty &&\n !stylePropsDirty &&\n !layoutChanged &&\n !subtreeDirty &&\n !childrenDirty &&\n !childPositionChanged &&\n !ancestorLayoutChanged\n\n // Intermediate: for TEXT nodes, stylePropsDirty IS a content area change (no borders).\n const textPaintDirty = isTextNode && stylePropsDirty\n\n // Did this node's CONTENT AREA change?\n const contentAreaAffected =\n contentDirty ||\n layoutChanged ||\n childPositionChanged ||\n childrenDirty ||\n bgDirty ||\n textPaintDirty ||\n absoluteChildMutated ||\n descendantOverflowChanged\n\n // Is bgDirty the ONLY trigger for contentAreaAffected?\n // When true AND hasBgColor: we can use fillBg() (preserves chars) instead of\n // fill() (overwrites with spaces), eliminating the cascade to children.\n const bgOnlyAffected =\n bgDirty &&\n !contentDirty &&\n !layoutChanged &&\n !childPositionChanged &&\n !childrenDirty &&\n !textPaintDirty &&\n !absoluteChildMutated &&\n !descendantOverflowChanged\n\n // Style-only fast path: when only bg changed on a Box with bg, use fillBg\n // to preserve child chars. Children see hasPrevBuffer=true (skippable).\n //\n // Additional safety checks:\n // - !ancestorLayoutChanged: children's positions may have shifted in the clone\n // - !ancestorCleared: parent cleared stale pixels, children must re-render\n //\n // IMPORTANT: this is only safe when no descendant has its own explicit\n // backgroundColor that would be incorrectly overwritten by fillBg. The\n // render phase checks this condition (hasDescendantWithBg) and falls back\n // to the full path when descendants have their own bg.\n // DISABLED: bgOnlyChange fast path causes incremental rendering mismatches\n // (fg colors lost on child nodes). Needs investigation before re-enabling.\n const bgOnlyChange = false\n\n // Descendant changed inside a bg-bearing Box (forces bg refill).\n const bgRefillNeeded = hasPrevBuffer && !contentAreaAffected && subtreeDirty && hasBgColor\n\n // Clear region with inherited bg when content changed but no own bg fill.\n // bgOnlyChange on nodes WITHOUT bg still needs clearing (bg removed).\n const contentRegionCleared =\n (hasPrevBuffer || ancestorCleared) && contentAreaAffected && !hasBgColor\n\n // Skip bg fill when clone already has correct bg at this position.\n const skipBgFill = hasPrevBuffer && !ancestorCleared && !contentAreaAffected && !bgRefillNeeded\n\n // Children must re-render (content area modified OR bg needs refresh).\n // Exception: bgOnlyChange uses fillBg() which preserves chars, so children\n // don't need fresh render — they keep their correct chars from the clone.\n const childrenNeedFreshRender =\n (hasPrevBuffer || ancestorCleared) && (contentAreaAffected || bgRefillNeeded) && !bgOnlyChange\n\n return {\n canSkipEntireSubtree,\n contentAreaAffected,\n bgRefillNeeded,\n contentRegionCleared,\n skipBgFill,\n childrenNeedFreshRender,\n bgOnlyChange,\n }\n}\n","/**\n * Reactive Node State — alien-signals wrappers for cascade derivations.\n *\n * E+ Phase 2: Replace manual cascade formula computation with reactive\n * computeds. The cascade-predicates.ts `computeCascade()` function is the\n * oracle — this module must produce identical outputs.\n *\n * Incremental approach:\n * 1. Writable signals mirror epoch-stamped dirty flags\n * 2. Computeds derive cascade outputs (isDirty, contentAreaAffected, etc.)\n * 3. `syncToSignals()` bridges epoch flags → signals at render-phase entry\n * 4. Dev-mode assertions verify reactive === oracle\n *\n * alien-signals computeds are lazy (pull-based): reading a computed re-evaluates\n * only if a dependency changed. No batching needed for correctness — the\n * reconciler's synchronous commit writes epoch stamps, and the render phase\n * reads computeds after all commits are done.\n */\n\nimport { signal, computed } from \"@silvery/signals\"\nimport type { AgNode } from \"@silvery/ag/types\"\nimport {\n isDirty,\n CONTENT_BIT,\n STYLE_PROPS_BIT,\n BG_BIT,\n CHILDREN_BIT,\n SUBTREE_BIT,\n} from \"@silvery/ag/epoch\"\nimport { computeCascade, type CascadeInputs, type CascadeOutputs } from \"./cascade-predicates.ts\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Writable signal — call with no args to read, call with value to write.\n * Type alias for clarity (alien-signals returns this shape from `signal()`).\n */\ntype Signal<T> = {\n (): T\n (value: T): void\n}\n\n/** Read-only computed — call with no args to read. */\ntype Computed<T> = () => T\n\n/**\n * Per-node reactive state that lives alongside an AgNode.\n *\n * Writable signals are synced from epoch-stamped flags before each render pass.\n * Computed derivations automatically recompute when inputs change.\n */\nexport interface ReactiveNodeState {\n // --- Writable signals (synced from epoch flags) ---\n readonly contentDirty: Signal<boolean>\n readonly stylePropsDirty: Signal<boolean>\n readonly bgDirty: Signal<boolean>\n readonly childrenDirty: Signal<boolean>\n readonly subtreeDirty: Signal<boolean>\n readonly layoutChanged: Signal<boolean>\n\n // --- Context signals (set per-node during tree traversal) ---\n readonly hasPrevBuffer: Signal<boolean>\n readonly childPositionChanged: Signal<boolean>\n readonly ancestorLayoutChanged: Signal<boolean>\n readonly ancestorCleared: Signal<boolean>\n readonly isTextNode: Signal<boolean>\n readonly hasBgColor: Signal<boolean>\n readonly absoluteChildMutated: Signal<boolean>\n readonly descendantOverflowChanged: Signal<boolean>\n\n // --- Computed derivations ---\n readonly canSkipEntireSubtree: Computed<boolean>\n readonly textPaintDirty: Computed<boolean>\n readonly contentAreaAffected: Computed<boolean>\n readonly bgRefillNeeded: Computed<boolean>\n readonly contentRegionCleared: Computed<boolean>\n readonly skipBgFill: Computed<boolean>\n readonly childrenNeedFreshRender: Computed<boolean>\n readonly bgOnlyChange: Computed<boolean>\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\n/**\n * Create a reactive state wrapper for a node.\n *\n * The computed formulas exactly replicate cascade-predicates.ts `computeCascade()`.\n * They are NOT a replacement — the oracle function remains authoritative.\n * In dev mode, assertions verify equivalence.\n */\nexport function createReactiveNodeState(): ReactiveNodeState {\n // Writable signals — all start false, synced before each render pass\n const contentDirty = signal(false)\n const stylePropsDirty = signal(false)\n const bgDirty = signal(false)\n const childrenDirty = signal(false)\n const subtreeDirty = signal(false)\n const layoutChanged = signal(false)\n\n // Context signals — set per-node during tree traversal\n const hasPrevBuffer = signal(false)\n const childPositionChanged = signal(false)\n const ancestorLayoutChanged = signal(false)\n const ancestorCleared = signal(false)\n const isTextNode = signal(false)\n const hasBgColor = signal(false)\n const absoluteChildMutated = signal(false)\n const descendantOverflowChanged = signal(false)\n\n // --- Computed derivations (formulas from cascade-predicates.ts) ---\n\n const canSkipEntireSubtree = computed(\n () =>\n hasPrevBuffer() &&\n !contentDirty() &&\n !stylePropsDirty() &&\n !layoutChanged() &&\n !subtreeDirty() &&\n !childrenDirty() &&\n !childPositionChanged() &&\n !ancestorLayoutChanged(),\n )\n\n const textPaintDirty = computed(() => isTextNode() && stylePropsDirty())\n\n const contentAreaAffected = computed(\n () =>\n contentDirty() ||\n layoutChanged() ||\n childPositionChanged() ||\n childrenDirty() ||\n bgDirty() ||\n textPaintDirty() ||\n absoluteChildMutated() ||\n descendantOverflowChanged(),\n )\n\n // DISABLED: bgOnlyChange fast path causes incremental rendering mismatches.\n // Matches cascade-predicates.ts which hardcodes `false`.\n const bgOnlyChange = computed(() => false)\n\n const bgRefillNeeded = computed(\n () => hasPrevBuffer() && !contentAreaAffected() && subtreeDirty() && hasBgColor(),\n )\n\n const contentRegionCleared = computed(\n () => (hasPrevBuffer() || ancestorCleared()) && contentAreaAffected() && !hasBgColor(),\n )\n\n const skipBgFill = computed(\n () => hasPrevBuffer() && !ancestorCleared() && !contentAreaAffected() && !bgRefillNeeded(),\n )\n\n const childrenNeedFreshRender = computed(\n () =>\n (hasPrevBuffer() || ancestorCleared()) &&\n (contentAreaAffected() || bgRefillNeeded()) &&\n !bgOnlyChange(),\n )\n\n return {\n contentDirty,\n stylePropsDirty,\n bgDirty,\n childrenDirty,\n subtreeDirty,\n layoutChanged,\n hasPrevBuffer,\n childPositionChanged,\n ancestorLayoutChanged,\n ancestorCleared,\n isTextNode,\n hasBgColor,\n absoluteChildMutated,\n descendantOverflowChanged,\n canSkipEntireSubtree,\n textPaintDirty,\n contentAreaAffected,\n bgRefillNeeded,\n contentRegionCleared,\n skipBgFill,\n childrenNeedFreshRender,\n bgOnlyChange,\n }\n}\n\n// ============================================================================\n// Sync: Epoch flags → Signals\n// ============================================================================\n\n/**\n * Sync a node's epoch-stamped dirty flags into the reactive signals.\n *\n * Called once per node at the start of renderNodeToBuffer, BEFORE reading\n * any computed. Context-dependent inputs (hasPrevBuffer, ancestorCleared, etc.)\n * are also set here since they vary per tree-traversal position.\n */\nexport function syncToSignals(\n state: ReactiveNodeState,\n node: AgNode,\n ctx: {\n hasPrevBuffer: boolean\n layoutChanged: boolean\n childPositionChanged: boolean\n ancestorLayoutChanged: boolean\n ancestorCleared: boolean\n absoluteChildMutated: boolean\n descendantOverflowChanged: boolean\n hasBgColor: boolean\n },\n): void {\n // Sync epoch flags → boolean signals\n state.contentDirty(isDirty(node.dirtyBits, node.dirtyEpoch, CONTENT_BIT))\n state.stylePropsDirty(isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT))\n state.bgDirty(isDirty(node.dirtyBits, node.dirtyEpoch, BG_BIT))\n state.childrenDirty(isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT))\n state.subtreeDirty(isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT))\n state.layoutChanged(ctx.layoutChanged)\n\n // Sync context-dependent inputs\n state.hasPrevBuffer(ctx.hasPrevBuffer)\n state.childPositionChanged(ctx.childPositionChanged)\n state.ancestorLayoutChanged(ctx.ancestorLayoutChanged)\n state.ancestorCleared(ctx.ancestorCleared)\n state.isTextNode(node.type === \"silvery-text\")\n state.hasBgColor(ctx.hasBgColor)\n state.absoluteChildMutated(ctx.absoluteChildMutated)\n state.descendantOverflowChanged(ctx.descendantOverflowChanged)\n}\n\n// ============================================================================\n// Reactive-driven cascade (replaces computeCascade for production)\n// ============================================================================\n\n/**\n * Read all cascade outputs from the reactive computeds.\n * Call AFTER `syncToSignals()`. Returns the same CascadeOutputs shape\n * as `computeCascade()` but derived from the signal graph.\n */\nexport function readReactiveCascade(state: ReactiveNodeState): CascadeOutputs {\n return {\n canSkipEntireSubtree: state.canSkipEntireSubtree(),\n contentAreaAffected: state.contentAreaAffected(),\n bgRefillNeeded: state.bgRefillNeeded(),\n contentRegionCleared: state.contentRegionCleared(),\n skipBgFill: state.skipBgFill(),\n childrenNeedFreshRender: state.childrenNeedFreshRender(),\n bgOnlyChange: state.bgOnlyChange(),\n }\n}\n\n// ============================================================================\n// Oracle Verification\n// ============================================================================\n\n/**\n * Verify that reactive computeds match the cascade oracle.\n *\n * Call in dev mode (SILVERY_STRICT=1) after syncToSignals + computeCascade.\n * Throws on mismatch with a detailed diff.\n */\nexport function assertReactiveMatchesOracle(\n state: ReactiveNodeState,\n oracle: CascadeOutputs,\n nodeId: string,\n): void {\n const fields: (keyof CascadeOutputs)[] = [\n \"canSkipEntireSubtree\",\n \"contentAreaAffected\",\n \"bgRefillNeeded\",\n \"contentRegionCleared\",\n \"skipBgFill\",\n \"childrenNeedFreshRender\",\n \"bgOnlyChange\",\n ]\n\n const mismatches: string[] = []\n for (const field of fields) {\n const reactiveValue = state[field]()\n const oracleValue = oracle[field]\n if (reactiveValue !== oracleValue) {\n mismatches.push(` ${field}: reactive=${reactiveValue}, oracle=${oracleValue}`)\n }\n }\n\n if (mismatches.length > 0) {\n throw new Error(\n `ReactiveNodeState mismatch for ${nodeId || \"(unnamed)\"}:\\n${mismatches.join(\"\\n\")}`,\n )\n }\n}\n\n// ============================================================================\n// Node State Cache\n// ============================================================================\n\n/**\n * WeakMap from AgNode to its reactive state.\n *\n * Lazily created on first access. Automatically garbage-collected when the\n * node is removed from the tree (WeakMap semantics).\n */\nconst nodeStates = new WeakMap<AgNode, ReactiveNodeState>()\n\n/**\n * Get or create the reactive state for a node.\n *\n * Uses a WeakMap so states are automatically cleaned up when nodes are GC'd.\n */\nexport function getReactiveState(node: AgNode): ReactiveNodeState {\n let state = nodeStates.get(node)\n if (!state) {\n state = createReactiveNodeState()\n nodeStates.set(node, state)\n }\n return state\n}\n","/**\n * Phase 3: Render Phase\n *\n * Render all nodes to a terminal buffer.\n *\n * This module orchestrates the rendering process by traversing the node tree\n * and delegating to specialized rendering functions for boxes and text.\n *\n * Layout (top-down):\n * renderPhase → renderNodeToBuffer → buildCascadeInputs + computeCascade (oracle)\n * → traceRenderDecision (diagnostics)\n * → executeRegionClearing\n * → renderOwnContent\n * → renderScrollContainerChildren / renderNormalChildren\n * Helpers: clearDirtyFlags, hasChildPositionChanged, computeChildClipBounds\n * Region clearing: clearNodeRegion, clearExcessArea, clippedFill\n */\n\nimport { createLogger } from \"loggily\"\nimport type { Color } from \"../buffer\"\nimport { TerminalBuffer } from \"../buffer\"\nimport type { BoxProps, AgNode, TextProps } from \"@silvery/ag/types\"\nimport { getBorderSize, getPadding } from \"./helpers\"\nimport { renderBox, renderScrollIndicators, getEffectiveBg } from \"./render-box\"\nimport { clearPreviousOutlines, renderDecorationPass } from \"./decoration-phase\"\nimport { getTextStyle, parseColor } from \"./render-helpers\"\nimport { clearBgConflictWarnings, renderText, setBgConflictMode } from \"./render-text\"\nimport { pushContextTheme, popContextTheme } from \"./state\"\nimport type { Theme } from \"@silvery/ansi\"\n// cascade-predicates is the imperative oracle — used for STRICT verification\n// and as the fallback when SILVERY_REACTIVE=0 (bench only).\nimport { computeCascade } from \"./cascade-predicates\"\nimport { isStyleOnlyDirty } from \"@silvery/ag/dirty-tracking\"\nimport {\n isCurrentEpoch,\n isAnyDirty,\n INITIAL_EPOCH,\n advanceRenderEpoch,\n isDirty,\n CONTENT_BIT,\n STYLE_PROPS_BIT,\n BG_BIT,\n CHILDREN_BIT,\n SUBTREE_BIT,\n ABS_CHILD_BIT,\n DESC_OVERFLOW_BIT,\n} from \"@silvery/ag/epoch\"\nimport type { CascadeOutputs } from \"./cascade-predicates\"\nimport type {\n ClipBounds,\n RenderPhaseStats,\n NodeRenderState,\n NodeTraceEntry,\n PipelineContext,\n} from \"./types\"\nimport {\n getReactiveState,\n syncToSignals,\n readReactiveCascade,\n assertReactiveMatchesOracle,\n} from \"./reactive-node\"\n\nconst contentLog = createLogger(\"silvery:content\")\nconst traceLog = createLogger(\"silvery:content:trace\")\nconst cellLog = createLogger(\"silvery:content:cell\")\n\n/**\n * Render all nodes to a terminal buffer.\n *\n * @param root The root SilveryNode\n * @param prevBuffer Previous buffer for incremental rendering (optional)\n * @returns A TerminalBuffer with the rendered content\n */\nexport function renderPhase(\n root: AgNode,\n prevBuffer?: TerminalBuffer | null,\n ctx?: PipelineContext,\n): TerminalBuffer {\n const layout = root.boxRect\n if (!layout) {\n throw new Error(\"renderPhase called before layout phase\")\n }\n\n const instr = resolveInstrumentation(ctx)\n\n // Clone prevBuffer if same dimensions, else create fresh\n const hasPrevBuffer =\n prevBuffer && prevBuffer.width === layout.width && prevBuffer.height === layout.height\n\n // No-op frame skip: if nothing changed since last render and we have a valid\n // prev buffer, return it unchanged. Saves buffer clone + tree walk + epoch advance.\n //\n // Currently doesn't fire with React (createElement always produces new children\n // array → commitUpdate sets CHILDREN_BIT on root). Will fire with:\n // - Signal-driven updates (v1.5) that bypass React\n // - Timer-driven re-renders where nothing changed\n // - Scheduler polling without React pending work\n if (\n hasPrevBuffer &&\n !isAnyDirty(root.dirtyBits, root.dirtyEpoch) &&\n !isCurrentEpoch(root.layoutChangedThisFrame)\n ) {\n if (instr.enabled) instr.stats._noopSkip = 1\n advanceRenderEpoch()\n return prevBuffer\n }\n\n if (instr.enabled) {\n instr.stats._prevBufferNull = prevBuffer == null ? 1 : 0\n instr.stats._prevBufferDimMismatch = prevBuffer && !hasPrevBuffer ? 1 : 0\n instr.stats._hasPrevBuffer = hasPrevBuffer ? 1 : 0\n instr.stats._layoutW = layout.width\n instr.stats._layoutH = layout.height\n instr.stats._prevW = prevBuffer?.width ?? 0\n instr.stats._prevH = prevBuffer?.height ?? 0\n }\n\n const t0 = instr.enabled ? performance.now() : 0\n const buffer = hasPrevBuffer\n ? prevBuffer.clone()\n : new TerminalBuffer(layout.width, layout.height)\n const tClone = instr.enabled ? performance.now() - t0 : 0\n\n // Default: root is selectable (userSelect defaults to \"text\").\n // renderNodeToBuffer will override per-node as it traverses.\n buffer.setSelectableMode(true)\n\n // Restore cells under previous-frame outlines BEFORE content rendering.\n // Outlines draw OUTSIDE their owning node into the parent's pixel space,\n // so they don't fit the per-node cascade. We treat them as a separate\n // decoration pass: clear prev positions here, redraw new positions after\n // the content phase below. The snapshots were captured when we drew the\n // previous frame and travel with the cloned buffer. No-op for fresh\n // buffers (no snapshots on a newly constructed TerminalBuffer).\n clearPreviousOutlines(buffer)\n\n const t1 = instr.enabled ? performance.now() : 0\n renderNodeToBuffer(\n root,\n buffer,\n {\n scrollOffset: 0,\n clipBounds: undefined,\n hasPrevBuffer: !!hasPrevBuffer,\n ancestorCleared: false,\n bufferIsCloned: !!hasPrevBuffer,\n ancestorLayoutChanged: false,\n inheritedBg: { color: null, ancestorRect: null },\n inheritedFg: null,\n },\n ctx,\n )\n const tRender = instr.enabled ? performance.now() - t1 : 0\n\n // Decoration phase: draw outlines AFTER content rendering. Walks the full\n // tree (O(N)) independent of dirty flags — outlines are idempotent per\n // frame and cheap to redraw (~5000 cells at worst). Populates fresh\n // snapshots on the buffer for the next frame's `clearPreviousOutlines`.\n renderDecorationPass(buffer, root)\n\n if (instr.enabled) {\n emitRenderPhaseStats(instr.stats, instr.nodeTrace, instr.nodeTraceEnabled, tClone, tRender)\n }\n\n // Sync prevLayout after render phase to prevent staleness on subsequent frames.\n // Skip when no layout changed this frame (cursor move, style-only changes).\n // The layout phase sets layoutChangedThisFrame on affected nodes; if root's\n // subtree has any, we need the full sync. If not, prevLayout is already correct.\n const anyLayoutChanged =\n isCurrentEpoch(root.layoutChangedThisFrame) ||\n isDirty(root.dirtyBits, root.dirtyEpoch, SUBTREE_BIT)\n syncPrevLayout(root, anyLayoutChanged || !hasPrevBuffer)\n\n // Advance the render epoch — all dirty flags stamped with the old epoch\n // instantly become \"not dirty\". This replaces the O(N) clearDirtyFlags walk\n // for rendered nodes (skipped nodes still need explicit clearing).\n advanceRenderEpoch()\n\n return buffer\n}\n\n/**\n * Sync prevLayout to boxRect for all nodes in the tree.\n *\n * Called at the end of each renderPhase pass. This prevents:\n * 1. The O(N) staleness bug where prevLayout drifts from boxRect\n * causing !rectEqual to always be true on subsequent frames.\n * 2. Stale old-bounds references in clearExcessArea on doRender iteration 2+.\n * 3. Asymmetry between incremental and fresh renders — doFreshRender's layout\n * phase syncs prevLayout before content, so without this, the real render\n * has null/stale prevLayout while fresh has synced prevLayout, causing\n * different cascade behavior (layoutChanged true vs false).\n */\n/**\n * Sync prevLayout = boxRect for nodes whose layout changed.\n *\n * Previously walked ALL nodes O(N) every frame. Now only visits nodes\n * with layoutChangedThisFrame (set by propagateLayout in layout phase).\n * Falls back to full walk when layout phase ran (dimensions changed or\n * Flexily isDirty) since any node may have moved.\n *\n * For cursor move (no layout change): O(1) — no nodes to sync.\n * For resize: O(N) — all nodes may have moved (same as before).\n */\nfunction syncPrevLayout(root: AgNode, layoutPhaseRan: boolean): void {\n // When layout phase ran, any node could have moved — full walk needed.\n // When layout was skipped (cursor move, style-only), no rects changed.\n if (!layoutPhaseRan) return\n\n const stack: AgNode[] = [root]\n while (stack.length > 0) {\n const node = stack.pop()!\n node.prevLayout = node.boxRect\n const children = node.children\n for (let i = children.length - 1; i >= 0; i--) {\n stack.push(children[i]!)\n }\n }\n}\n\n/** Check if an env var is truthy (treats \"0\" and \"false\" as disabled). */\nfunction envTruthy(val: string | undefined): boolean {\n return !!val && val !== \"0\" && val !== \"false\"\n}\n\n/** Instrumentation enabled when SILVERY_STRICT or SILVERY_INSTRUMENT is set */\nconst _instrumentEnabled =\n typeof process !== \"undefined\" &&\n (envTruthy(process.env?.SILVERY_STRICT) || envTruthy(process.env?.SILVERY_INSTRUMENT))\n\n/** Mutable stats counters — reset after each renderPhase call */\nconst _renderPhaseStats: RenderPhaseStats = {\n nodesVisited: 0,\n nodesRendered: 0,\n nodesSkipped: 0,\n textNodes: 0,\n boxNodes: 0,\n clearOps: 0,\n noPrevBuffer: 0,\n flagContentDirty: 0,\n flagStylePropsDirty: 0,\n flagLayoutChanged: 0,\n flagSubtreeDirty: 0,\n flagChildrenDirty: 0,\n flagChildPositionChanged: 0,\n flagAncestorLayoutChanged: 0,\n scrollContainerCount: 0,\n scrollViewportCleared: 0,\n scrollClearReason: \"\",\n normalChildrenRepaint: 0,\n normalRepaintReason: \"\",\n cascadeMinDepth: 999,\n cascadeNodes: \"\",\n _noopSkip: 0,\n _prevBufferNull: 0,\n _prevBufferDimMismatch: 0,\n _hasPrevBuffer: 0,\n _layoutW: 0,\n _layoutH: 0,\n _prevW: 0,\n _prevH: 0,\n _callCount: 0,\n}\n\nlet _renderPhaseCallCount = 0\n\n/** Module-level node trace (fallback when ctx.nodeTrace is not provided) */\nconst _nodeTrace: NodeTraceEntry[] = []\nconst _nodeTraceEnabled = typeof process !== \"undefined\" && envTruthy(process.env?.SILVERY_STRICT)\n\n/**\n * Reactive cascade: alien-signals computeds drive rendering (production path).\n * SILVERY_REACTIVE=0: fall back to imperative computeCascade() (bench only).\n * SILVERY_STRICT=1: oracle verifies reactive output matches imperative.\n */\nlet _reactiveEnabled = typeof process === \"undefined\" || process.env?.SILVERY_REACTIVE !== \"0\"\nlet _reactiveVerifyEnabled =\n _reactiveEnabled && typeof process !== \"undefined\" && envTruthy(process.env?.SILVERY_STRICT)\n\n/** Toggle reactive cascade mode at runtime (for three-way bench). */\nexport function setReactiveEnabled(enabled: boolean): void {\n _reactiveEnabled = enabled\n _reactiveVerifyEnabled =\n enabled && typeof process !== \"undefined\" && envTruthy(process.env?.SILVERY_STRICT)\n}\n\n// ============================================================================\n// Instrumentation Helpers\n// ============================================================================\n\n/** Resolved instrumentation state — avoids repeating ctx ?? module fallbacks. */\ninterface InstrumentState {\n readonly enabled: boolean\n readonly stats: RenderPhaseStats\n readonly nodeTrace: NodeTraceEntry[]\n readonly nodeTraceEnabled: boolean\n}\n\n/** Resolve instrumentation from PipelineContext or module-level defaults. */\nfunction resolveInstrumentation(ctx?: PipelineContext): InstrumentState {\n return {\n enabled: ctx?.instrumentEnabled ?? _instrumentEnabled,\n stats: ctx?.stats ?? _renderPhaseStats,\n nodeTrace: ctx?.nodeTrace ?? _nodeTrace,\n nodeTraceEnabled: ctx?.nodeTraceEnabled ?? _nodeTraceEnabled,\n }\n}\n\n/** Cell debug state — read once from globalThis per function scope. */\ntype CellDebug = { x: number; y: number; log: string[] }\n\n/** Read the cell debug target (set by SILVERY_CELL_DEBUG env var). */\nfunction getCellDebug(): CellDebug | undefined {\n return (globalThis as any).__silvery_cell_debug as CellDebug | undefined\n}\n\n/** Check if a rect covers the cell debug target point. */\nfunction cellCoversPoint(\n cellDbg: CellDebug,\n x: number,\n y: number,\n width: number,\n height: number,\n): boolean {\n return x <= cellDbg.x && x + width > cellDbg.x && y <= cellDbg.y && y + height > cellDbg.y\n}\n\n/** DIAG: compute node depth in tree */\nfunction _getNodeDepth(node: AgNode): number {\n let depth = 0\n let n: AgNode | null = node.parent\n while (n) {\n depth++\n n = n.parent\n }\n return depth\n}\n\n/**\n * Emit render-phase stats to globalThis + loggily, then reset counters.\n * Called at end of renderPhase when instrumentation is active.\n */\nfunction emitRenderPhaseStats(\n stats: RenderPhaseStats,\n nodeTrace: NodeTraceEntry[],\n nodeTraceEnabled: boolean,\n tClone: number,\n tRender: number,\n): void {\n _renderPhaseCallCount++\n stats._callCount = _renderPhaseCallCount\n const snap = {\n clone: tClone,\n render: tRender,\n ...structuredClone(stats),\n }\n // Retain globalThis for programmatic consumers (STRICT diagnostics, perf profiling)\n ;(globalThis as any).__silvery_content_detail = snap\n const arr = ((globalThis as any).__silvery_content_all ??= [] as (typeof snap)[])\n arr.push(snap)\n // Route human-readable output through loggily\n contentLog.debug?.(\n `frame ${snap._callCount}: ${snap.nodesRendered}/${snap.nodesVisited} rendered, ${snap.nodesSkipped} skipped (${tClone.toFixed(1)}ms clone, ${tRender.toFixed(1)}ms render)`,\n )\n for (const key of Object.keys(stats) as (keyof RenderPhaseStats)[]) {\n ;(stats as any)[key] = 0\n }\n stats.cascadeMinDepth = 999\n stats.cascadeNodes = \"\"\n stats.scrollClearReason = \"\"\n stats.normalRepaintReason = \"\"\n\n // Export node trace for SILVERY_STRICT diagnosis\n if (nodeTraceEnabled && nodeTrace.length > 0) {\n const traceArr = ((globalThis as any).__silvery_node_trace ??= [] as NodeTraceEntry[][])\n traceArr.push([...nodeTrace])\n traceLog.debug?.(`${nodeTrace.length} nodes traced`)\n nodeTrace.length = 0\n }\n}\n\n// Re-export for consumers who need to clear bg conflict warnings\nexport { clearBgConflictWarnings, setBgConflictMode }\n\n// ============================================================================\n// Core Rendering\n// ============================================================================\n\n/**\n * Render a single node to the buffer.\n */\nfunction renderNodeToBuffer(\n node: AgNode,\n buffer: TerminalBuffer,\n nodeState: NodeRenderState,\n ctx?: PipelineContext,\n): void {\n const {\n scrollOffset,\n clipBounds,\n hasPrevBuffer,\n ancestorCleared,\n bufferIsCloned,\n ancestorLayoutChanged = false,\n } = nodeState\n const instr = resolveInstrumentation(ctx)\n if (instr.enabled) instr.stats.nodesVisited++\n const layout = node.boxRect\n if (!layout) return\n\n // Skip nodes without Yoga (raw text and virtual text nodes)\n // Their content is rendered by their parent silvery-text via collectTextContent()\n if (!node.layoutNode) {\n clearVirtualTextFlags(node)\n return\n }\n\n // Skip hidden nodes (Suspense support)\n if (node.hidden) {\n clearDirtyFlags(node)\n return\n }\n\n const props = node.props as BoxProps & TextProps\n\n // Resolve userSelect for SELECTABLE_FLAG stamping.\n const prevSelectableMode = buffer.getSelectableMode()\n const userSelect = props.userSelect\n if (userSelect === \"none\") {\n buffer.setSelectableMode(false)\n } else if (userSelect === \"text\" || userSelect === \"contain\") {\n buffer.setSelectableMode(true)\n }\n\n // Skip display=\"none\" nodes\n if (props.display === \"none\") {\n clearDirtyFlags(node)\n buffer.setSelectableMode(prevSelectableMode)\n return\n }\n\n // Skip nodes entirely off-screen (viewport clipping).\n // IMPORTANT: Don't clear dirty flags on off-screen nodes — they need their\n // flags intact so they render correctly when scrolled into view.\n const screenY = layout.y - scrollOffset\n if (screenY >= buffer.height || screenY + layout.height <= 0) {\n buffer.setSelectableMode(prevSelectableMode)\n return\n }\n\n // FAST PATH: Skip entire subtree if unchanged and we have a previous buffer.\n // layoutChanged uses layoutChangedThisFrame (symmetric between incremental/fresh).\n const layoutChanged = isCurrentEpoch(node.layoutChangedThisFrame)\n const childPositionChanged = !!(hasPrevBuffer && !layoutChanged && hasChildPositionChanged(node))\n const scrollOffsetChanged = !!(\n node.scrollState && node.scrollState.offset !== node.scrollState.prevOffset\n )\n\n const canSkipEntireSubtree =\n hasPrevBuffer &&\n !isDirty(node.dirtyBits, node.dirtyEpoch, CONTENT_BIT) &&\n !isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT) &&\n !layoutChanged &&\n !isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT) &&\n !isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT) &&\n !childPositionChanged &&\n !ancestorLayoutChanged &&\n !scrollOffsetChanged\n\n // Diagnostics: node ID, cell debug, node trace\n const _nodeId = instr.enabled ? ((props.id as string | undefined) ?? \"\") : \"\"\n const _traceThis = instr.enabled && instr.nodeTraceEnabled && _nodeId\n const _cellDbg = getCellDebug()\n const _coversCellNow =\n _cellDbg && cellCoversPoint(_cellDbg, layout.x, screenY, layout.width, layout.height)\n const _coversCellPrev =\n _cellDbg &&\n node.prevLayout &&\n cellCoversPoint(\n _cellDbg,\n node.prevLayout.x,\n node.prevLayout.y - scrollOffset,\n node.prevLayout.width,\n node.prevLayout.height,\n )\n\n if (canSkipEntireSubtree) {\n if (_cellDbg && (_coversCellNow || _coversCellPrev)) {\n const id = (props.id as string) ?? node.type\n const depth = _getNodeDepth(node)\n const prev = node.prevLayout\n const msg =\n `SKIP ${id}@${depth} rect=${layout.x},${screenY} ${layout.width}x${layout.height}` +\n ` prev=${prev ? `${prev.x},${prev.y - scrollOffset} ${prev.width}x${prev.height}` : \"null\"}` +\n ` coversNow=${_coversCellNow} coversPrev=${_coversCellPrev}`\n _cellDbg.log.push(msg)\n cellLog.debug?.(msg)\n }\n if (instr.enabled) {\n instr.stats.nodesSkipped++\n if (_traceThis) {\n instr.nodeTrace.push({\n id: _nodeId,\n type: node.type,\n depth: _getNodeDepth(node),\n rect: `${layout.x},${layout.y} ${layout.width}x${layout.height}`,\n prevLayout: node.prevLayout\n ? `${node.prevLayout.x},${node.prevLayout.y} ${node.prevLayout.width}x${node.prevLayout.height}`\n : \"null\",\n hasPrev: hasPrevBuffer,\n ancestorCleared,\n flags: \"\",\n decision: \"SKIPPED\",\n layoutChanged,\n })\n }\n }\n clearDirtyFlags(node)\n buffer.setSelectableMode(prevSelectableMode)\n return\n }\n if (instr.enabled) {\n instr.stats.nodesRendered++\n if (!hasPrevBuffer) instr.stats.noPrevBuffer++\n if (isDirty(node.dirtyBits, node.dirtyEpoch, CONTENT_BIT)) instr.stats.flagContentDirty++\n if (isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT)) instr.stats.flagStylePropsDirty++\n if (layoutChanged) instr.stats.flagLayoutChanged++\n if (isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT)) instr.stats.flagSubtreeDirty++\n if (isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT)) instr.stats.flagChildrenDirty++\n if (childPositionChanged) instr.stats.flagChildPositionChanged++\n if (ancestorLayoutChanged) instr.stats.flagAncestorLayoutChanged++\n }\n\n // Push per-subtree theme override (if this Box has a theme prop).\n // Placed after all early returns and fast-path skip — only active during\n // actual rendering. Popped at the end of this function after all child passes.\n const nodeTheme = (props as BoxProps).theme as Theme | undefined\n if (nodeTheme) {\n pushContextTheme(nodeTheme)\n }\n try {\n // Check if this is a scrollable container\n const isScrollContainer = props.overflow === \"scroll\" && node.scrollState\n\n // Build tree-dependent cascade inputs (child traversal).\n const { absoluteChildMutated, descendantOverflowChanged } = buildCascadeInputs(\n node,\n hasPrevBuffer,\n )\n\n // Cascade computation: reactive (alien-signals) is the production path.\n // SILVERY_REACTIVE=0 falls back to imperative computeCascade (bench only).\n const cascadeInputs = {\n hasPrevBuffer,\n contentDirty: isDirty(node.dirtyBits, node.dirtyEpoch, CONTENT_BIT),\n stylePropsDirty: isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT),\n layoutChanged,\n subtreeDirty: isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT),\n childrenDirty: isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT),\n childPositionChanged,\n ancestorLayoutChanged,\n ancestorCleared,\n bgDirty: isDirty(node.dirtyBits, node.dirtyEpoch, BG_BIT),\n isTextNode: node.type === \"silvery-text\",\n hasBgColor: !!getEffectiveBg(props),\n absoluteChildMutated,\n descendantOverflowChanged,\n }\n let cascade: CascadeOutputs\n if (_reactiveEnabled) {\n const reactiveState = getReactiveState(node)\n syncToSignals(reactiveState, node, {\n hasPrevBuffer,\n layoutChanged,\n childPositionChanged,\n ancestorLayoutChanged,\n ancestorCleared,\n absoluteChildMutated,\n descendantOverflowChanged,\n hasBgColor: cascadeInputs.hasBgColor,\n })\n cascade = readReactiveCascade(reactiveState)\n\n // STRICT: verify reactive matches imperative oracle.\n if (_reactiveVerifyEnabled) {\n assertReactiveMatchesOracle(\n reactiveState,\n computeCascade(cascadeInputs),\n (props.id as string) ?? node.type,\n )\n }\n } else {\n cascade = computeCascade(cascadeInputs)\n }\n\n // bgOnlyChange safety check: fillBg updates ALL cells in the region, which\n // would incorrectly overwrite children with their own explicit backgroundColor.\n // Fall back to the full path when any descendant has its own bg.\n if (cascade.bgOnlyChange && hasDescendantWithBg(node)) {\n const childrenNeedFreshRender =\n (hasPrevBuffer || ancestorCleared) &&\n (cascade.contentAreaAffected || cascade.bgRefillNeeded)\n cascade = { ...cascade, bgOnlyChange: false, childrenNeedFreshRender }\n }\n const { contentRegionCleared, skipBgFill, childrenNeedFreshRender } = cascade\n\n // DIAG: Per-node trace, cascade tracking, and cell debug\n if (instr.enabled || (_cellDbg && (_coversCellNow || _coversCellPrev))) {\n traceRenderDecision(\n node,\n props,\n layout,\n screenY,\n scrollOffset,\n hasPrevBuffer,\n ancestorCleared,\n layoutChanged,\n childPositionChanged,\n cascade,\n _nodeId,\n _traceThis,\n _cellDbg,\n _coversCellNow,\n _coversCellPrev,\n instr.enabled,\n instr.stats,\n instr.nodeTrace,\n )\n }\n\n // DISABLED: text style-only fast path causes incremental rendering mismatches\n // (fg colors lost). Needs investigation before re-enabling.\n const useTextStyleFastPath = false\n\n // Clear stale regions in the cloned buffer before rendering content.\n // Suppress clearing when using the text style-only fast path — chars are\n // correct in the clone and clearNodeRegion would destroy them with spaces.\n executeRegionClearing(\n node,\n buffer,\n layout,\n scrollOffset,\n clipBounds,\n bufferIsCloned,\n layoutChanged,\n useTextStyleFastPath ? false : contentRegionCleared,\n descendantOverflowChanged,\n instr.enabled,\n instr.stats,\n nodeState.inheritedBg,\n )\n\n // Determine if this node's own content (border, bg, text) needs repainting.\n // When hasPrevBuffer=true and only subtreeDirty is set (no own visual changes),\n // the cloned buffer already has correct own content. Skipping avoids a border\n // redraw that would overwrite child content rendered on top of the border area\n // (e.g., text overflow into border columns).\n const needsOwnRepaint =\n !hasPrevBuffer ||\n ancestorCleared ||\n ancestorLayoutChanged ||\n cascade.contentAreaAffected ||\n isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT) ||\n cascade.bgRefillNeeded\n\n // Render this node's own content (box bg/border or text).\n // Compute boxInheritedBg even when skipping own repaint — it's needed by\n // outline rendering (after children) and may be needed by child rendering.\n const boxInheritedBg =\n node.type === \"silvery-box\" && !getEffectiveBg(props)\n ? nodeState.inheritedBg.color\n : undefined\n if (needsOwnRepaint) {\n renderOwnContent(\n node,\n buffer,\n layout,\n props,\n nodeState,\n skipBgFill,\n instr.enabled,\n instr.stats,\n ctx,\n cascade.bgOnlyChange,\n useTextStyleFastPath,\n )\n }\n\n // Compute inherited bg/fg for children. If this node sets backgroundColor,\n // color, or theme, children inherit from this node. Otherwise, inherit from parent.\n const effectiveBg = getEffectiveBg(props)\n const childInheritedBg = effectiveBg\n ? { color: parseColor(effectiveBg), ancestorRect: node.boxRect }\n : nodeTheme\n ? { color: parseColor(nodeTheme.bg), ancestorRect: node.boxRect }\n : nodeState.inheritedBg\n // color=\"inherit\"/\"currentColor\" is a pass-through — children inherit\n // from this node's OWN inheritedFg (our parent's color), not from null.\n // Without this, an intermediate \"inherit\" node breaks the cascade to\n // grandchildren because parseColor(\"inherit\") returns null, clobbering\n // the ancestor fg. See Bead km-silvery.color-inherit.\n const isInheritKeyword = props.color === \"inherit\" || props.color === \"currentColor\"\n const childInheritedFg = isInheritKeyword\n ? nodeState.inheritedFg\n : props.color\n ? parseColor(props.color)\n : nodeTheme\n ? parseColor(nodeTheme.fg)\n : nodeState.inheritedFg\n\n // Render children — pass inherited bg/fg so children don't walk the parent chain\n const childState: NodeRenderState = {\n ...nodeState,\n inheritedBg: childInheritedBg,\n inheritedFg: childInheritedFg,\n }\n if (isScrollContainer) {\n renderScrollContainerChildren(\n node,\n buffer,\n props,\n childState,\n contentRegionCleared,\n childrenNeedFreshRender,\n ctx,\n )\n\n // Render overflow indicators AFTER children so they survive viewport clear.\n // renderScrollContainerChildren may clear the viewport (Tier 2) which would\n // overwrite indicators drawn before children.\n renderScrollIndicators(node, buffer, layout, props, node.scrollState!, ctx)\n } else {\n renderNormalChildren(\n node,\n buffer,\n props,\n childState,\n childPositionChanged,\n contentRegionCleared,\n childrenNeedFreshRender,\n ctx,\n )\n }\n\n // Outlines are NOT rendered here — the decoration phase (post-content)\n // walks the tree independently and draws outlines for every node with\n // outlineStyle, using per-cell snapshots to clear prev positions next\n // frame. See decoration-phase.ts.\n\n // Clear dirty flags (current node only — children clear their own when rendered)\n clearNodeDirtyFlags(node)\n } finally {\n // Pop per-subtree theme override (after ALL child passes including absolute/sticky)\n if (nodeTheme) popContextTheme()\n // Restore parent's selectable mode\n buffer.setSelectableMode(prevSelectableMode)\n }\n}\n\n// ============================================================================\n// Cascade Input Helpers\n// ============================================================================\n\n/**\n * Build tree-dependent cascade inputs that require child traversal.\n *\n * These feed into computeCascade() alongside the node's own dirty flags.\n * Separated from renderNodeToBuffer to keep the main function focused on\n * the rendering flow rather than child traversal details.\n */\nfunction buildCascadeInputs(\n node: AgNode,\n hasPrevBuffer: boolean,\n): { absoluteChildMutated: boolean; descendantOverflowChanged: boolean } {\n if (\n !hasPrevBuffer ||\n !isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT) ||\n node.children === undefined\n ) {\n return { absoluteChildMutated: false, descendantOverflowChanged: false }\n }\n\n // Phase 3b: Read cached epoch values computed during layout phase\n // (propagateLayout), avoiding per-node tree walks in the render phase.\n return {\n absoluteChildMutated: isDirty(node.dirtyBits, node.dirtyEpoch, ABS_CHILD_BIT),\n descendantOverflowChanged: isDirty(node.dirtyBits, node.dirtyEpoch, DESC_OVERFLOW_BIT),\n }\n}\n\n// ============================================================================\n// Render Decision Tracing\n// ============================================================================\n\n/**\n * Log per-node trace, cascade tracking, and cell debug info.\n *\n * Gated on instrumentation or cell debug being active. Separated from\n * renderNodeToBuffer to keep the main function focused on rendering logic.\n */\nfunction traceRenderDecision(\n node: AgNode,\n props: BoxProps & TextProps,\n layout: NonNullable<AgNode[\"boxRect\"]>,\n screenY: number,\n scrollOffset: number,\n hasPrevBuffer: boolean,\n ancestorCleared: boolean,\n layoutChanged: boolean,\n childPositionChanged: boolean,\n cascade: CascadeOutputs,\n _nodeId: string,\n _traceThis: string | false | 0 | \"\",\n _cellDbg: { x: number; y: number; log: string[] } | undefined,\n _coversCellNow: boolean | undefined,\n _coversCellPrev: boolean | null | undefined,\n instrumentEnabled: boolean,\n stats: RenderPhaseStats,\n nodeTrace: NodeTraceEntry[],\n): void {\n const { contentAreaAffected, contentRegionCleared, skipBgFill, childrenNeedFreshRender } = cascade\n\n // Per-node trace and cascade tracking (gated on instrumentation)\n if (instrumentEnabled) {\n if (_traceThis) {\n const flagStr = [\n isDirty(node.dirtyBits, node.dirtyEpoch, CONTENT_BIT) && \"C\",\n isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT) && \"P\",\n isDirty(node.dirtyBits, node.dirtyEpoch, BG_BIT) && \"B\",\n isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT) && \"S\",\n isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT) && \"Ch\",\n childPositionChanged && \"CP\",\n ]\n .filter(Boolean)\n .join(\",\")\n const childrenNeedRepaint_ =\n isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT) ||\n childPositionChanged ||\n childrenNeedFreshRender\n const childHasPrev_ = childrenNeedRepaint_ ? false : hasPrevBuffer\n const childAncestorCleared_ =\n contentRegionCleared || (ancestorCleared && !getEffectiveBg(props))\n nodeTrace.push({\n id: _nodeId,\n type: node.type,\n depth: _getNodeDepth(node),\n rect: `${layout.x},${layout.y} ${layout.width}x${layout.height}`,\n prevLayout: node.prevLayout\n ? `${node.prevLayout.x},${node.prevLayout.y} ${node.prevLayout.width}x${node.prevLayout.height}`\n : \"null\",\n hasPrev: hasPrevBuffer,\n ancestorCleared,\n flags: flagStr,\n decision: \"RENDER\",\n layoutChanged,\n contentAreaAffected,\n contentRegionCleared,\n childrenNeedFreshRender,\n childHasPrev: childHasPrev_,\n childAncestorCleared: childAncestorCleared_,\n skipBgFill,\n bgColor: props.backgroundColor as string | undefined,\n })\n }\n if (childrenNeedFreshRender && node.children.length > 0) {\n const depth = _getNodeDepth(node)\n if (depth < stats.cascadeMinDepth) {\n stats.cascadeMinDepth = depth\n }\n const id = (node.props as Record<string, unknown>).id ?? node.type\n const flags = [\n isDirty(node.dirtyBits, node.dirtyEpoch, CONTENT_BIT) && \"C\",\n isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT) && \"P\",\n isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT) && \"Ch\",\n layoutChanged && \"L\",\n childPositionChanged && \"CP\",\n ]\n .filter(Boolean)\n .join(\"\")\n const entry = `${id}@${depth}[${flags}:${node.children.length}ch]`\n stats.cascadeNodes += (stats.cascadeNodes ? \" \" : \"\") + entry\n }\n }\n\n // Cell debug: log render decision for nodes covering target cell\n if (_cellDbg && (_coversCellNow || _coversCellPrev)) {\n const id = (props.id as string) ?? node.type\n const depth = _getNodeDepth(node)\n const prev = node.prevLayout\n const flags = [\n isDirty(node.dirtyBits, node.dirtyEpoch, CONTENT_BIT) && \"C\",\n isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT) && \"P\",\n layoutChanged && \"L\",\n isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT) && \"S\",\n isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT) && \"Ch\",\n childPositionChanged && \"CP\",\n isDirty(node.dirtyBits, node.dirtyEpoch, BG_BIT) && \"B\",\n ]\n .filter(Boolean)\n .join(\",\")\n const msg =\n `RENDER ${id}@${depth} rect=${layout.x},${screenY} ${layout.width}x${layout.height}` +\n ` prev=${prev ? `${prev.x},${prev.y - scrollOffset} ${prev.width}x${prev.height}` : \"null\"}` +\n ` flags=[${flags}] hasPrev=${hasPrevBuffer} ancClr=${ancestorCleared}` +\n ` caa=${contentAreaAffected} prc=${contentRegionCleared} prm=${childrenNeedFreshRender}` +\n ` coversNow=${_coversCellNow} coversPrev=${_coversCellPrev}` +\n ` bg=${props.backgroundColor ?? \"none\"}`\n _cellDbg.log.push(msg)\n cellLog.debug?.(msg)\n }\n}\n\n// ============================================================================\n// Region Clearing (executeRegionClearing)\n// ============================================================================\n\n/**\n * Handle all region clearing before rendering own content.\n *\n * Three clearing paths:\n * 1. contentRegionCleared: clear the node's region with inherited bg (no own bg)\n * 2. Excess area: clear stale pixels when a node shrank (even without contentRegionCleared)\n * 3. Descendant overflow: clear areas where descendants previously overflowed this node's rect\n *\n * All clearing runs BEFORE renderBox/renderText so borders drawn later are not overwritten.\n */\nfunction executeRegionClearing(\n node: AgNode,\n buffer: TerminalBuffer,\n layout: NonNullable<AgNode[\"boxRect\"]>,\n scrollOffset: number,\n clipBounds: ClipBounds | undefined,\n bufferIsCloned: boolean,\n layoutChanged: boolean,\n contentRegionCleared: boolean,\n descendantOverflowChanged: boolean,\n instrumentEnabled: boolean,\n stats: RenderPhaseStats,\n threadedInheritedBg: NodeRenderState[\"inheritedBg\"],\n): void {\n if (contentRegionCleared) {\n if (instrumentEnabled) stats.clearOps++\n clearNodeRegion(\n node,\n buffer,\n layout,\n scrollOffset,\n clipBounds,\n layoutChanged,\n threadedInheritedBg,\n )\n } else if (bufferIsCloned && layoutChanged && node.prevLayout) {\n // Even when contentRegionCleared is false, a shrinking node needs its excess\n // area cleared. Key scenario: absolute-positioned overlays (e.g., search dialog)\n // that shrink while normal-flow siblings are dirty -- forceRepaint sets\n // hasPrevBuffer=false + ancestorCleared=false, making contentRegionCleared=false,\n // but the cloned buffer still has stale pixels from the old larger layout.\n // Also applies to nodes WITH backgroundColor: renderBox fills only the NEW\n // (smaller) region, leaving stale pixels in the excess area.\n //\n // Gated on bufferIsCloned: on a fresh buffer (e.g., multi-pass resize where\n // dimensions changed between passes), there are no stale pixels to clear.\n // Without this guard, clearExcessArea writes inherited bg into cells that\n // doFreshRender leaves as default, causing STRICT mismatches.\n clearExcessArea(\n node,\n buffer,\n layout,\n scrollOffset,\n clipBounds,\n layoutChanged,\n threadedInheritedBg,\n )\n }\n\n // Clear descendant overflow regions: areas where descendants' previous layouts\n // extended beyond THIS node's rect. clearNodeRegion covers the node's interior,\n // but overflow content is beyond it. This is separate from contentRegionCleared\n // because overflow is OUTSIDE the rect -- it needs clearing even for nodes with\n // backgroundColor (whose interior is handled by renderBox's bg fill).\n if (descendantOverflowChanged) {\n clearDescendantOverflowRegions(\n node,\n buffer,\n layout,\n scrollOffset,\n clipBounds,\n threadedInheritedBg,\n )\n }\n\n // Outline cleanup is handled entirely by the decoration phase (outlines\n // are treated as a separate pass that snapshots under-cells and restores\n // them next frame). No outline-aware work needed here.\n}\n\n// ============================================================================\n// Own Content Rendering\n// ============================================================================\n\n/**\n * Render this node's own content (box background/border or text).\n *\n * For boxes: computes inherited bg for border rendering and calls renderBox.\n * For text: computes inherited bg/fg for text rendering and calls renderText.\n *\n * @returns The boxInheritedBg color (needed by outline rendering after children).\n */\nfunction renderOwnContent(\n node: AgNode,\n buffer: TerminalBuffer,\n layout: NonNullable<AgNode[\"boxRect\"]>,\n props: BoxProps & TextProps,\n nodeState: NodeRenderState,\n skipBgFill: boolean,\n instrumentEnabled: boolean,\n stats: RenderPhaseStats,\n ctx?: PipelineContext,\n bgOnlyChange = false,\n useTextStyleFastPath = false,\n): Color | undefined {\n // O(1) inherited bg/fg from nodeState — threaded top-down, no parent chain walks.\n const boxInheritedBg =\n node.type === \"silvery-box\" && !getEffectiveBg(props) ? nodeState.inheritedBg.color : undefined\n\n if (node.type === \"silvery-box\") {\n if (instrumentEnabled) stats.boxNodes++\n // inheritedFg is threaded so renderBorder can resolve borderColor=\"currentColor\".\n renderBox(\n node,\n buffer,\n layout,\n props,\n nodeState,\n skipBgFill,\n boxInheritedBg,\n bgOnlyChange,\n nodeState.inheritedFg,\n )\n } else if (node.type === \"silvery-text\") {\n if (instrumentEnabled) stats.textNodes++\n // O(1) inherited bg/fg — threaded top-down through nodeState.\n // inheritedBg decouples text rendering from buffer state, which is critical\n // for incremental rendering: the cloned buffer may have stale bg at positions\n // outside the parent's bg-filled region (e.g., overflow text, moved nodes).\n // Foreground inheritance matches CSS semantics: Box color cascades to Text children.\n const textInheritedBg = nodeState.inheritedBg.color\n const textInheritedFg = nodeState.inheritedFg\n\n // Style-only fast path for text nodes: when only visual style props changed\n // (color, bold, dim, inverse, etc.) but text content is identical, skip the\n // expensive collectTextWithBg → formatTextLines → renderGraphemes pipeline.\n // Instead, restyle existing cells in-place with the new style.\n //\n // Conditions (pre-computed as useTextStyleFastPath):\n // 1. hasPrevBuffer: cloned buffer has correct chars from previous frame\n // 2. isStyleOnlyDirty: only style props changed (no content, bg, or children)\n // 3. No nested children with bg: restyleRegion would overwrite their bg\n // 4. Not ancestorCleared/ancestorLayoutChanged: cells are at correct positions\n //\n // This avoids O(text_length) text processing for the common case of\n // cursor/selection styling (just color/bold/inverse changes on text nodes).\n if (useTextStyleFastPath) {\n const style = getTextStyle(props)\n if (style.fg === null && textInheritedFg !== undefined) {\n style.fg = textInheritedFg\n }\n const effectiveBg = style.bg !== null ? style.bg : (textInheritedBg ?? null)\n const { x, width, height } = layout\n const y = layout.y - nodeState.scrollOffset\n buffer.restyleRegion(x, y, width, height, {\n fg: style.fg,\n bg: effectiveBg,\n underlineColor: style.underlineColor ?? null,\n attrs: style.attrs,\n })\n } else {\n renderText(node, buffer, layout, props, nodeState, textInheritedBg, textInheritedFg, ctx)\n }\n }\n\n return boxInheritedBg\n}\n\n// ============================================================================\n// Scroll Tier Planner\n// ============================================================================\n\n/** Which tier strategy a scroll container uses for this frame. */\nexport type ScrollTier = \"shift\" | \"clear\" | \"subtree-only\"\n\n/** Inputs for the scroll tier decision (all from renderScrollContainerChildren). */\nexport interface ScrollPlanInputs {\n /** Scroll offset changed since last frame. */\n scrollOffsetChanged: boolean\n /** Visible child index range changed. */\n visibleRangeChanged: boolean\n /** Scroll container has sticky children. */\n hasStickyChildren: boolean\n /** Parent cascade: children need fresh render (contentAreaAffected || bgRefillNeeded). */\n childrenNeedFreshRender: boolean\n /** Node has restructured children (added/removed/reordered). */\n childrenDirty: boolean\n /** Buffer from previous frame is available (incremental mode). */\n hasPrevBuffer: boolean\n /** An ancestor cleared its region. */\n ancestorCleared: boolean\n /** This node's content region was cleared (no own bg). */\n contentRegionCleared: boolean\n /** The bg to use for viewport clears (own bg or inherited). */\n scrollBg: Color | null\n}\n\n/** Result of the scroll tier decision. */\nexport interface ScrollPlan {\n /** Which tier strategy to use. */\n tier: ScrollTier\n /** Background color for viewport clear/shift fill (null = no bg). */\n clearBg: Color | null\n /** Default hasPrevBuffer for children. */\n childHasPrev: boolean\n /** Default ancestorCleared for children. */\n childAncestorCleared: boolean\n /** Whether all first-pass items must re-render (Tier 3 with sticky children). */\n stickyForceRefresh: boolean\n /** Human-readable reasons for the tier decision (for instrumentation). */\n reasons: string[]\n}\n\n/**\n * Determine the scroll tier strategy for this frame.\n *\n * Pure function -- no side effects, no node access beyond the inputs.\n *\n * Three-tier strategy:\n * 1. **shift**: Only scroll offset changed, no sticky children. Buffer contents\n * shifted by scroll delta; only newly visible edges re-render.\n * 2. **clear**: Children restructured, visible range changed with scroll, or\n * parent region changed. Entire viewport cleared and all children re-render.\n * 3. **subtree-only**: Only some descendants changed. Children use hasPrevBuffer=true\n * and skip via fast-path if clean. With sticky children, forces all first-pass\n * items to re-render (stickyForceRefresh).\n */\nexport function planScrollRender(inputs: ScrollPlanInputs): ScrollPlan {\n const {\n scrollOffsetChanged,\n visibleRangeChanged,\n hasStickyChildren,\n childrenNeedFreshRender,\n childrenDirty,\n hasPrevBuffer,\n ancestorCleared,\n contentRegionCleared,\n scrollBg,\n } = inputs\n\n // Tier 1: Buffer shift -- scroll offset changed but nothing else.\n // Unsafe with sticky children (sticky second pass overwrites shifted pixels).\n const scrollOnly =\n hasPrevBuffer &&\n scrollOffsetChanged &&\n !childrenDirty &&\n !childrenNeedFreshRender &&\n !hasStickyChildren &&\n !visibleRangeChanged\n\n // Tier 2: Full viewport clear -- children restructured, scroll+sticky, or parent changed.\n const needsViewportClear =\n hasPrevBuffer &&\n !scrollOnly &&\n (scrollOffsetChanged || childrenDirty || childrenNeedFreshRender || visibleRangeChanged)\n\n // Tier 3 with sticky: force all first-pass items to re-render.\n // The cloned buffer has stale content from previous frames' sticky positions.\n const stickyForceRefresh = hasStickyChildren && hasPrevBuffer && !needsViewportClear\n\n // Build reasons for instrumentation\n const reasons: string[] = []\n if (scrollOnly) reasons.push(\"SHIFT\")\n if (needsViewportClear) {\n if (scrollOffsetChanged) reasons.push(\"scrollOffset\")\n if (childrenDirty) reasons.push(\"childrenDirty\")\n if (childrenNeedFreshRender) reasons.push(\"childrenNeedFreshRender\")\n if (visibleRangeChanged) reasons.push(\"visibleRangeChanged\")\n }\n if (stickyForceRefresh) reasons.push(\"stickyForceRefresh\")\n\n const tier: ScrollTier = scrollOnly ? \"shift\" : needsViewportClear ? \"clear\" : \"subtree-only\"\n\n const childHasPrev = needsViewportClear ? false : hasPrevBuffer\n const childAncestorCleared = needsViewportClear ? true : ancestorCleared || contentRegionCleared\n\n return {\n tier,\n clearBg: scrollOnly || needsViewportClear ? scrollBg : null,\n childHasPrev,\n childAncestorCleared,\n stickyForceRefresh,\n reasons,\n }\n}\n\n/**\n * Render children of a scroll container with proper clipping and offset.\n */\nfunction renderScrollContainerChildren(\n node: AgNode,\n buffer: TerminalBuffer,\n props: BoxProps,\n nodeState: NodeRenderState,\n contentRegionCleared = false,\n childrenNeedFreshRender = false,\n ctx?: PipelineContext,\n): void {\n const {\n clipBounds,\n hasPrevBuffer,\n ancestorCleared,\n bufferIsCloned,\n ancestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n } = nodeState\n const instr = resolveInstrumentation(ctx)\n const layout = node.boxRect\n const ss = node.scrollState\n if (!layout || !ss) return\n\n const border = props.borderStyle ? getBorderSize(props) : { top: 0, bottom: 0, left: 0, right: 0 }\n const padding = getPadding(props)\n // Scroll containers clip vertically (for scrolling) but NOT horizontally.\n // Scroll containers clip vertically (viewport) but not horizontally —\n // horizontal containment is handled by text wrapping, not clipping.\n const viewportClipBounds = computeChildClipBounds(\n layout,\n props,\n clipBounds,\n 0,\n /* horizontal */ false,\n /* vertical */ true,\n )\n\n // Borderless overflow indicators (overflowIndicator=true with no borderStyle)\n // render ON TOP of the first/last content row of the viewport. When a child\n // overflows into that row, the indicator overwrites the child's content,\n // producing the user-reported \"▼1 covers the last card's text\" bug\n // (km-tui.column-top-disappears).\n //\n // Build a reduced clip for children that excludes the reserved indicator\n // row(s). Viewport operations (Tier 1 shift indicator pre-clear, Tier 2\n // viewport clear) continue to use the full `viewportClipBounds` so the\n // indicator row itself is repainted correctly.\n const showBorderlessIndicator = props.overflowIndicator === true && !props.borderStyle\n const childClipBounds =\n showBorderlessIndicator && (ss.hiddenAbove > 0 || ss.hiddenBelow > 0)\n ? {\n ...viewportClipBounds,\n top: ss.hiddenAbove > 0 ? viewportClipBounds.top + 1 : viewportClipBounds.top,\n bottom: ss.hiddenBelow > 0 ? viewportClipBounds.bottom - 1 : viewportClipBounds.bottom,\n }\n : viewportClipBounds\n\n // Determine if scroll offset changed since last render.\n const scrollOffsetChanged = ss.offset !== ss.prevOffset\n const hasStickyChildren = !!(ss.stickyChildren && ss.stickyChildren.length > 0)\n const visibleRangeChanged =\n ss.firstVisibleChild !== ss.prevFirstVisibleChild ||\n ss.lastVisibleChild !== ss.prevLastVisibleChild\n\n // Compute viewport geometry (shared by all tiers).\n // `clearY` / `clearHeight` describe the FULL viewport (including indicator\n // rows) — used by Tier 1 shift / Tier 2 clear / Tier 3 sticky-force-refresh,\n // all of which need to repaint the indicator rows. Children still render\n // with the shrunk `childClipBounds` so they don't overwrite indicators.\n const clearY = viewportClipBounds.top\n const clearHeight = viewportClipBounds.bottom - viewportClipBounds.top\n const contentX = layout.x + border.left + padding.left\n const contentWidth = layout.width - border.left - border.right - padding.left - padding.right\n\n // Compute scroll bg eagerly -- planScrollRender needs it and it's cheap\n const scrollBg =\n scrollOffsetChanged ||\n isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT) ||\n childrenNeedFreshRender ||\n visibleRangeChanged\n ? getEffectiveBg(props)\n ? parseColor(getEffectiveBg(props)!)\n : inheritedBg.color\n : null\n\n // Plan the scroll tier strategy (pure decision, no side effects)\n const plan = planScrollRender({\n scrollOffsetChanged,\n visibleRangeChanged,\n hasStickyChildren,\n childrenNeedFreshRender,\n childrenDirty: isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT),\n hasPrevBuffer,\n ancestorCleared,\n contentRegionCleared,\n scrollBg,\n })\n const { tier, stickyForceRefresh } = plan\n const defaultChildHasPrev = plan.childHasPrev\n const defaultChildAncestorCleared = plan.childAncestorCleared\n\n if (instr.enabled) {\n instr.stats.scrollContainerCount++\n if (tier !== \"subtree-only\" || stickyForceRefresh) {\n instr.stats.scrollViewportCleared++\n const reasons = [...plan.reasons]\n if (scrollOffsetChanged) reasons.push(`scrollOffset(${ss.prevOffset}->${ss.offset})`)\n reasons.push(\n `vp=${ss.viewportHeight} content=${ss.contentHeight} vis=${ss.firstVisibleChild}-${ss.lastVisibleChild}`,\n )\n instr.stats.scrollClearReason = reasons.join(\"+\")\n }\n }\n\n // STRICT invariant: Tier 1 (buffer shift) must never be used with sticky children.\n if (process?.env?.SILVERY_STRICT && tier === \"shift\" && hasStickyChildren) {\n throw new Error(\n `[SILVERY_STRICT] Scroll Tier 1 (buffer shift) activated with sticky children ` +\n `(node: ${(props.id as string | undefined) ?? node.type}, ` +\n `stickyCount: ${ss.stickyChildren?.length ?? 0})`,\n )\n }\n\n // Apply the plan: buffer shift, viewport clear, or sticky force refresh\n const scrollDelta = ss.offset - (ss.prevOffset ?? ss.offset)\n if (tier === \"shift\" && clearHeight > 0) {\n // Clear scroll indicator rows before shifting to prevent stale indicator\n // pixels at edges (columns not covered by children).\n const showBorderless = props.overflowIndicator === true\n if (showBorderless && !border.top && !border.bottom) {\n const topIndicatorY = clearY\n const bottomIndicatorY = clearY + clearHeight - 1\n if (ss.prevOffset != null && ss.prevOffset > 0) {\n buffer.fill(contentX, topIndicatorY, contentWidth, 1, { char: \" \", bg: plan.clearBg })\n }\n buffer.fill(contentX, bottomIndicatorY, contentWidth, 1, { char: \" \", bg: plan.clearBg })\n }\n buffer.scrollRegion(contentX, clearY, contentWidth, clearHeight, scrollDelta, {\n char: \" \",\n bg: plan.clearBg,\n })\n }\n\n if (tier === \"clear\" && clearHeight > 0) {\n buffer.fill(contentX, clearY, contentWidth, clearHeight, {\n char: \" \",\n bg: plan.clearBg,\n })\n }\n\n // Tier 3 with sticky: clear viewport to null bg (matches fresh render state)\n // before re-rendering all items, so the sticky second pass works correctly.\n if (stickyForceRefresh && clearHeight > 0) {\n buffer.fill(contentX, clearY, contentWidth, clearHeight, { char: \" \", bg: null })\n }\n\n // Propagate ancestor layout change to scroll container children.\n const childAncestorLayoutChanged =\n isCurrentEpoch(node.layoutChangedThisFrame) || !!ancestorLayoutChanged\n\n // For buffer shift: children that were fully visible in BOTH the previous\n // and current frames have correct pixels after the shift (childHasPrev=true).\n const prevVisTop = ss.prevOffset ?? ss.offset\n const prevVisBottom = prevVisTop + ss.viewportHeight\n\n // First pass: render non-sticky visible children with scroll offset\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i]\n if (!child) continue\n const childProps = child.props as BoxProps\n\n // Skip sticky children - they're rendered in second pass\n if (childProps.position === \"sticky\") {\n continue\n }\n\n // Skip children that are completely outside the visible range\n if (i < ss.firstVisibleChild || i > ss.lastVisibleChild) {\n continue\n }\n\n // Determine per-child hasPrev for buffer shift mode\n let thisChildHasPrev = defaultChildHasPrev\n let thisChildAncestorCleared = defaultChildAncestorCleared\n if (tier === \"shift\") {\n // Check if child was fully visible in the previous frame\n const childRect = child.boxRect\n if (childRect) {\n const childTop = childRect.y - layout.y - border.top - padding.top\n const childBottom = childTop + childRect.height\n const wasFullyVisible = childTop >= prevVisTop && childBottom <= prevVisBottom\n thisChildHasPrev = wasFullyVisible\n // Shifted children: their pixels are intact (not cleared)\n // Newly visible: exposed region was filled by scrollRegion\n thisChildAncestorCleared = wasFullyVisible ? ancestorCleared || contentRegionCleared : true\n }\n }\n\n // Force fresh rendering when sticky children exist (see stickyForceRefresh).\n if (stickyForceRefresh && thisChildHasPrev) {\n thisChildHasPrev = false\n thisChildAncestorCleared = false\n }\n\n // Phase 4: dirty set pre-check — skip clean subtrees without function call overhead.\n if (canSkipChildSubtree(child, thisChildHasPrev, childAncestorLayoutChanged)) {\n continue\n }\n\n // Render visible children with scroll offset applied.\n renderNodeToBuffer(\n child,\n buffer,\n {\n scrollOffset: ss.offset,\n clipBounds: childClipBounds,\n hasPrevBuffer: thisChildHasPrev,\n ancestorCleared: thisChildAncestorCleared,\n bufferIsCloned,\n ancestorLayoutChanged: childAncestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n },\n ctx,\n )\n }\n\n // Second pass: render sticky children at their computed positions\n // Rendered last so they appear on top of other content\n if (ss.stickyChildren) {\n for (const sticky of ss.stickyChildren) {\n const child = node.children[sticky.index]\n if (!child?.boxRect) continue\n\n // Calculate the scroll offset that would place the child at its sticky position\n // stickyOffset = naturalTop - renderOffset\n // This makes the child render at renderOffset instead of its natural position\n const stickyScrollOffset = sticky.naturalTop - sticky.renderOffset\n\n // Sticky children always re-render (hasPrevBuffer=false) since their\n // effective scroll offset may change even when the container's doesn't.\n //\n // ancestorCleared=false matches fresh render semantics: on a fresh render,\n // the buffer at sticky positions has first-pass content (not \"cleared\").\n // Using ancestorCleared=true would cause transparent spacer Boxes to clear\n // their region (via layoutChanged=true from prevLayout=null → cascading\n // contentRegionCleared), wiping overlapping sticky headers rendered earlier\n // in this pass.\n //\n // Stale bg from previous frames is handled by the stickyForceRefresh\n // pre-clear above, which ensures correct bg is in the buffer before sticky\n // children render on top of first-pass content.\n renderNodeToBuffer(\n child,\n buffer,\n {\n scrollOffset: stickyScrollOffset,\n clipBounds: childClipBounds,\n hasPrevBuffer: false,\n ancestorCleared: false,\n bufferIsCloned,\n ancestorLayoutChanged: childAncestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n },\n ctx,\n )\n }\n }\n}\n\n/**\n * Render children of a normal (non-scroll) container.\n */\nfunction renderNormalChildren(\n node: AgNode,\n buffer: TerminalBuffer,\n props: BoxProps,\n nodeState: NodeRenderState,\n childPositionChanged = false,\n contentRegionCleared = false,\n childrenNeedFreshRender = false,\n ctx?: PipelineContext,\n): void {\n const {\n scrollOffset,\n clipBounds,\n hasPrevBuffer,\n ancestorCleared,\n bufferIsCloned,\n ancestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n } = nodeState\n const instr = resolveInstrumentation(ctx)\n const layout = node.boxRect\n if (!layout) return\n\n // For overflow='hidden' containers, clip children to content area.\n // Supports per-axis clipping: overflowX/overflowY override the shorthand overflow prop.\n const clipX = (props.overflowX ?? props.overflow) === \"hidden\"\n const clipY = (props.overflowY ?? props.overflow) === \"hidden\"\n const effectiveClipBounds =\n clipX || clipY\n ? computeChildClipBounds(layout, props, clipBounds, scrollOffset, clipX, clipY)\n : clipBounds\n\n // Non-scroll sticky children support. When the layout phase computes\n // node.stickyChildren, we use the same two-pass pattern as scroll containers:\n // first pass renders non-sticky children, second pass renders sticky children\n // at their computed renderOffset positions.\n const hasStickyChildren = !!(node.stickyChildren && node.stickyChildren.length > 0)\n\n // When sticky children exist and hasPrevBuffer is true, force all first-pass\n // children to re-render. The cloned buffer may have stale pixels from previous\n // frames' sticky positions. This matches the stickyForceRefresh pattern from\n // scroll containers (Tier 3).\n const stickyForceRefresh = hasStickyChildren && hasPrevBuffer\n\n // Pre-clear the content area to bg=null when stickyForceRefresh is true.\n // Fresh renders start with a blank buffer (null bg everywhere). The cloned\n // buffer has stale content from old sticky positions that would leak through\n // on incremental renders. Clearing to null matches fresh render state before\n // any content renders.\n if (stickyForceRefresh) {\n const border = props.borderStyle\n ? getBorderSize(props)\n : { top: 0, bottom: 0, left: 0, right: 0 }\n const padding = getPadding(props)\n let clearX = layout.x + border.left + padding.left\n let clearY = layout.y - scrollOffset + border.top + padding.top\n let clearW = layout.width - border.left - border.right - padding.left - padding.right\n let clearH = layout.height - border.top - border.bottom - padding.top - padding.bottom\n // Clip to clipBounds (same discipline as scroll container clear)\n if (clipBounds) {\n const clipTop = clipBounds.top\n const clipBottom = clipBounds.bottom\n if (clearY < clipTop) {\n clearH -= clipTop - clearY\n clearY = clipTop\n }\n if (clearY + clearH > clipBottom) {\n clearH = clipBottom - clearY\n }\n if (clipBounds.left !== undefined && clearX < clipBounds.left) {\n clearW -= clipBounds.left - clearX\n clearX = clipBounds.left\n }\n if (clipBounds.right !== undefined && clearX + clearW > clipBounds.right) {\n clearW = clipBounds.right - clearX\n }\n }\n if (clearW > 0 && clearH > 0) {\n buffer.fill(clearX, clearY, clearW, clearH, { char: \" \", bg: null })\n }\n }\n\n // Force children to re-render when parent's region was modified on a clone,\n // children were restructured, or sibling positions shifted.\n const childrenNeedRepaint =\n isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT) ||\n childPositionChanged ||\n childrenNeedFreshRender\n if (instr.enabled && childrenNeedRepaint && hasPrevBuffer) {\n instr.stats.normalChildrenRepaint++\n const reasons: string[] = []\n if (isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT)) reasons.push(\"childrenDirty\")\n if (childPositionChanged) reasons.push(\"childPositionChanged\")\n if (childrenNeedFreshRender) reasons.push(\"childrenNeedFreshRender\")\n instr.stats.normalRepaintReason = reasons.join(\"+\")\n }\n let childHasPrev = childrenNeedRepaint ? false : hasPrevBuffer\n // childAncestorCleared: tells descendants that STALE pixels exist in the buffer.\n // Only contentRegionCleared (no bg fill → stale pixels remain) propagates this.\n // childrenNeedFreshRender WITHOUT contentRegionCleared means the parent filled its bg,\n // so children's positions have correct bg — NOT stale. Setting ancestorCleared\n // there would cause children to re-fill, overwriting border cells at boundaries.\n // When this node has backgroundColor, its renderBox fill covers any stale\n // pixels from ancestor clears — so children don't need ancestorCleared.\n let childAncestorCleared = contentRegionCleared || (ancestorCleared && !getEffectiveBg(props))\n\n // Propagate ancestor layout change to children: if this node or any ancestor\n // had layoutChangedThisFrame, children must not be skipped even if their own\n // flags are clean — their pixels in the cloned buffer are at wrong positions.\n const childAncestorLayoutChanged =\n isCurrentEpoch(node.layoutChangedThisFrame) || !!ancestorLayoutChanged\n\n // Override child flags when sticky force refresh is active — all first-pass\n // children must re-render fresh (matching the scroll container pattern).\n if (stickyForceRefresh) {\n childHasPrev = false\n childAncestorCleared = false\n }\n\n // Multi-pass rendering to match CSS paint order:\n // 1. Normal-flow children (skip sticky and absolute)\n // 2. Sticky children at computed positions (on top of normal-flow)\n // 3. Absolute children on top of everything\n //\n // This ensures absolute children's pixels (bg fills, text) are never\n // overwritten by normal-flow siblings' clearNodeRegion/render.\n //\n // Pre-scan: detect if any non-absolute, non-sticky sibling is dirty. When\n // true, absolute children in the third pass must force-repaint because the\n // first pass may have overwritten their pixels in the cloned buffer.\n let hasAbsoluteChildren = false\n\n // First pass: render normal-flow children (skip sticky + absolute), track dirty state\n for (const child of node.children) {\n const childProps = child.props as BoxProps\n if (childProps.position === \"absolute\") {\n hasAbsoluteChildren = true\n continue // Skip — rendered in third pass\n }\n if (hasStickyChildren && childProps.position === \"sticky\") {\n continue // Skip — rendered in second pass\n }\n\n // Phase 4: dirty set pre-check — skip clean subtrees without function call overhead.\n // The existing canSkipEntireSubtree inside renderNodeToBuffer is preserved as\n // the second line of defense for edge cases the pre-check doesn't cover.\n if (canSkipChildSubtree(child, childHasPrev, childAncestorLayoutChanged)) {\n continue\n }\n\n renderNodeToBuffer(\n child,\n buffer,\n {\n scrollOffset,\n clipBounds: effectiveClipBounds,\n hasPrevBuffer: childHasPrev,\n ancestorCleared: childAncestorCleared,\n bufferIsCloned,\n ancestorLayoutChanged: childAncestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n },\n ctx,\n )\n }\n\n // Second pass: render sticky children at their computed positions.\n // Rendered after normal-flow so they appear on top of other content.\n if (node.stickyChildren) {\n for (const sticky of node.stickyChildren) {\n const child = node.children[sticky.index]\n if (!child?.boxRect) continue\n\n // Calculate the scroll offset that would place the child at its sticky position.\n // stickyScrollOffset = naturalTop - renderOffset\n // This makes the child render at renderOffset instead of its natural position.\n const stickyScrollOffset = sticky.naturalTop - sticky.renderOffset\n\n // Sticky children always re-render (hasPrevBuffer=false) since their\n // effective position may change between frames.\n //\n // ancestorCleared=false matches fresh render semantics: on a fresh render,\n // the buffer at sticky positions has first-pass content (not \"cleared\").\n // Using ancestorCleared=true would cause transparent spacer Boxes to clear\n // their region, wiping overlapping sticky headers rendered earlier in this pass.\n //\n // ancestorLayoutChanged propagated so descendants know to re-render.\n renderNodeToBuffer(\n child,\n buffer,\n {\n scrollOffset: stickyScrollOffset,\n clipBounds: effectiveClipBounds,\n hasPrevBuffer: false,\n ancestorCleared: false,\n bufferIsCloned,\n ancestorLayoutChanged: childAncestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n },\n ctx,\n )\n }\n }\n\n // Third pass: render absolute children on top (CSS paint order)\n if (hasAbsoluteChildren) {\n for (const child of node.children) {\n const childProps = child.props as BoxProps\n if (childProps.position !== \"absolute\") continue\n\n // Both hasPrevBuffer and ancestorCleared must be false for absolute children\n // in the second pass. The buffer at the absolute child's position contains\n // first-pass content (normal-flow siblings), not \"previous frame\" content.\n // This is conceptually a fresh render at the absolute child's position:\n //\n // - hasPrevBuffer=false: prevents contentRegionCleared from firing.\n // Without this, a transparent overlay (no backgroundColor) that changes\n // (contentAreaAffected=true) would clear its entire region, wiping the\n // normal-flow content painted in the first pass. On a fresh render,\n // hasPrevBuffer=false prevents clearing, so this matches.\n //\n // - ancestorCleared=false: prevents transparent descendants from clearing\n // their regions, which would also wipe first-pass content.\n renderNodeToBuffer(\n child,\n buffer,\n {\n scrollOffset,\n clipBounds: effectiveClipBounds,\n hasPrevBuffer: false,\n ancestorCleared: false,\n bufferIsCloned,\n ancestorLayoutChanged: childAncestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n },\n ctx,\n )\n }\n }\n}\n\n// ============================================================================\n// Dirty Set Pre-Check (Phase 4)\n// ============================================================================\n\n/**\n * O(1) pre-check: can we skip calling renderNodeToBuffer() on this child entirely?\n *\n * This is the Phase 4 \"dirty set rendering\" optimization. Instead of calling\n * renderNodeToBuffer() on every child (which checks canSkipEntireSubtree and\n * returns early for clean nodes), we skip the function call entirely for\n * subtrees with no dirty descendants.\n *\n * The pre-check is CONSERVATIVE: it only skips when we're certain the subtree\n * is clean. False negatives (calling renderNodeToBuffer unnecessarily) are\n * harmless — the existing canSkipEntireSubtree check inside handles them.\n *\n * Key insight: subtreeDirtyEpoch is propagated from every dirty node to the\n * root by markSubtreeDirty (reconciler) and propagateLayout (layout phase).\n * If subtreeDirtyEpoch !== currentEpoch, no descendant is dirty. Combined\n * with layoutChangedThisFrame (set by layout phase but NOT included in\n * subtreeDirtyEpoch on self — only on parent), this covers all dirty paths.\n */\nfunction canSkipChildSubtree(\n child: AgNode,\n childHasPrev: boolean,\n childAncestorLayoutChanged: boolean,\n): boolean {\n // Can't skip without a previous buffer (first render or dimension change)\n if (!childHasPrev) return false\n // Ancestor layout change forces re-render at new position\n if (childAncestorLayoutChanged) return false\n // Any descendant dirty (includes own flags via markSubtreeDirty)\n if (isDirty(child.dirtyBits, child.dirtyEpoch, SUBTREE_BIT)) return false\n // Own layout changed (layout phase sets this but only propagates\n // subtreeDirtyEpoch to PARENT, not self)\n if (isCurrentEpoch(child.layoutChangedThisFrame)) return false\n // Defensive: scroll offset changed without dirty propagation\n if (child.scrollState && child.scrollState.offset !== child.scrollState.prevOffset) return false\n return true\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Clear dirty flags on the current node only (no recursion).\n * Used after rendering a node to reset its flags.\n *\n * With epoch-stamped flags, this is only needed when a subtree is SKIPPED\n * by the fast path (clearDirtyFlags on skipped subtrees) or for the\n * render-phase-adapter. The normal render path relies on advanceRenderEpoch()\n * to expire all flags at once — O(1) instead of O(N).\n */\nfunction clearNodeDirtyFlags(node: AgNode): void {\n node.dirtyBits = 0\n node.dirtyEpoch = INITIAL_EPOCH\n node.layoutChangedThisFrame = INITIAL_EPOCH\n}\n\n/**\n * Clear dirty flags on a subtree that was skipped during incremental rendering.\n */\nfunction clearDirtyFlags(node: AgNode): void {\n clearNodeDirtyFlags(node)\n for (const child of node.children) {\n if (child.layoutNode) {\n clearDirtyFlags(child)\n } else {\n // Virtual text children also need flags cleared — they're rendered by\n // their parent's collectTextContent(), not by renderNodeToBuffer().\n clearVirtualTextFlags(child)\n }\n }\n}\n\n/**\n * Clear dirty flags on a virtual text node and its descendants.\n * Virtual text nodes (no layoutNode) are rendered by their parent layout\n * ancestor via collectTextContent(). Their dirty flags must be cleared\n * after the parent renders, otherwise stale subtreeDirty blocks\n * markSubtreeDirty() propagation on future updates.\n */\nfunction clearVirtualTextFlags(node: AgNode): void {\n clearNodeDirtyFlags(node)\n for (const child of node.children) {\n clearVirtualTextFlags(child)\n }\n}\n\n/**\n * Check if any child's position changed since last render (sibling shift).\n * Checked even when subtreeDirty=true because subtreeDirty only means\n * descendants are dirty, not that this container's gap regions need clearing.\n */\nfunction hasChildPositionChanged(node: AgNode): boolean {\n for (const child of node.children) {\n if (child.boxRect && child.prevLayout) {\n if (child.boxRect.x !== child.prevLayout.x || child.boxRect.y !== child.prevLayout.y) {\n return true\n }\n }\n }\n return false\n}\n\n// hasDescendantOverflowChanged and _checkDescendantOverflow removed in Phase 3b:\n// Now cached as descendantOverflowChangedEpoch on AgNode, computed during layout phase.\n\n/**\n * Check if any descendant has an explicit backgroundColor.\n *\n * Used by the bgOnlyChange fast path: fillBg() updates ALL cells in the region\n * with the parent's bg. If a descendant has its own bg, those cells would be\n * incorrectly overwritten (the descendant is clean and won't re-render to fix it).\n *\n * Only checks Box nodes with explicit backgroundColor or effective bg from theme.\n * Text nodes with backgroundColor are also checked since they render their own bg.\n * Stops at first match (early exit).\n *\n * Performance: walks the child tree, but only runs when bgOnlyChange is true\n * (bg changed, no other flags). This is the cursor-move hot path where trees\n * are typically small (card contents: ~5-20 nodes).\n */\nfunction hasDescendantWithBg(node: AgNode): boolean {\n for (const child of node.children) {\n if (getEffectiveBg(child.props as BoxProps)) return true\n if (child.children.length > 0 && hasDescendantWithBg(child)) return true\n }\n return false\n}\n\n/**\n * Check if a text node has any virtual text children with explicit backgroundColor.\n *\n * Used by the text style-only fast path: restyleRegion() applies a uniform\n * style to all cells. If nested children have their own bg, the uniform restyle\n * would overwrite it (those children rendered their own bg during the original\n * renderText, and won't re-render to restore it).\n */\nfunction hasChildWithBg(node: AgNode): boolean {\n for (const child of node.children) {\n if ((child.props as BoxProps).backgroundColor) return true\n if (child.children.length > 0 && hasChildWithBg(child)) return true\n }\n return false\n}\n\n/**\n * Compute clip bounds for a container's children by insetting for border+padding,\n * then intersecting with parent clip bounds.\n */\nfunction computeChildClipBounds(\n layout: NonNullable<AgNode[\"boxRect\"]>,\n props: BoxProps,\n parentClip: ClipBounds | undefined,\n scrollOffset = 0,\n /** Compute left/right clip bounds for horizontal overflow clipping. */\n horizontal = true,\n /** Compute top/bottom clip bounds for vertical overflow clipping.\n * Defaults to true — scroll containers pass vertical=true, horizontal=false\n * (horizontal containment is via layout OVERFLOW_HIDDEN, not render clipping). */\n vertical = true,\n): ClipBounds {\n const border = props.borderStyle ? getBorderSize(props) : { top: 0, bottom: 0, left: 0, right: 0 }\n const padding = getPadding(props)\n const adjustedY = layout.y - scrollOffset\n const nodeClip: ClipBounds = vertical\n ? {\n top: adjustedY + border.top + padding.top,\n bottom: adjustedY + layout.height - border.bottom - padding.bottom,\n }\n : { top: -Infinity, bottom: Infinity }\n if (horizontal) {\n nodeClip.left = layout.x + border.left + padding.left\n nodeClip.right = layout.x + layout.width - border.right - padding.right\n }\n if (!parentClip) return nodeClip\n const result: ClipBounds = {\n top: vertical ? Math.max(parentClip.top, nodeClip.top) : parentClip.top,\n bottom: vertical ? Math.min(parentClip.bottom, nodeClip.bottom) : parentClip.bottom,\n }\n if (horizontal && nodeClip.left !== undefined && nodeClip.right !== undefined) {\n result.left = Math.max(parentClip.left ?? 0, nodeClip.left)\n result.right = Math.min(parentClip.right ?? Infinity, nodeClip.right)\n } else if (parentClip.left !== undefined && parentClip.right !== undefined) {\n // Pass through parent's horizontal clip bounds without adding own\n result.left = parentClip.left\n result.right = parentClip.right\n }\n return result\n}\n\n// ============================================================================\n// Region Clearing\n// ============================================================================\n\n/**\n * Clear overflow regions: areas where children's prevLayouts extended beyond\n * this node's rect. Called when childOverflowChanged detected stale overflow.\n *\n * clearNodeRegion handles the node's own rect. This function handles the\n * overflow area — pixels that a child rendered OUTSIDE the parent's rect\n * in a previous frame (via overflow:visible behavior). When the child shrinks,\n * those pixels become stale in the cloned buffer.\n *\n * Clears each child's overflow extent, clipped to buffer bounds.\n */\n/**\n * Clear areas where descendants' previous layouts overflowed beyond THIS node's rect.\n * Only clears OUTSIDE the node's rect — interior clearing is handled by clearNodeRegion\n * and renderBox. Recursive: follows subtreeDirty paths to find all overflowing descendants.\n */\nfunction clearDescendantOverflowRegions(\n node: AgNode,\n buffer: TerminalBuffer,\n layout: NonNullable<AgNode[\"boxRect\"]>,\n scrollOffset: number,\n clipBounds: ClipBounds | undefined,\n threadedInheritedBg: NodeRenderState[\"inheritedBg\"],\n): void {\n const clearBg = threadedInheritedBg.color\n const nodeRight = layout.x + layout.width\n const nodeBottom = layout.y - scrollOffset + layout.height\n const nodeLeft = layout.x\n const nodeTop = layout.y - scrollOffset\n\n _clearDescendantOverflow(\n node.children,\n buffer,\n nodeLeft,\n nodeTop,\n nodeRight,\n nodeBottom,\n scrollOffset,\n clipBounds,\n clearBg,\n )\n}\n\nfunction _clearDescendantOverflow(\n children: readonly AgNode[],\n buffer: TerminalBuffer,\n nodeLeft: number,\n nodeTop: number,\n nodeRight: number,\n nodeBottom: number,\n scrollOffset: number,\n clipBounds: ClipBounds | undefined,\n clearBg: Color,\n): void {\n for (const child of children) {\n if (child.prevLayout && isCurrentEpoch(child.layoutChangedThisFrame)) {\n const prev = child.prevLayout\n const prevRight = prev.x + prev.width\n const prevBottom = prev.y - scrollOffset + prev.height\n const prevTop = prev.y - scrollOffset\n\n // Clear overflow to the right of the ancestor\n if (prevRight > nodeRight) {\n const overflowX = nodeRight\n const overflowWidth = Math.min(prevRight, buffer.width) - overflowX\n const overflowTop = Math.max(prevTop, clipBounds?.top ?? 0)\n const overflowBottom = Math.min(prevBottom, clipBounds?.bottom ?? buffer.height)\n if (overflowWidth > 0 && overflowBottom > overflowTop) {\n buffer.fill(overflowX, overflowTop, overflowWidth, overflowBottom - overflowTop, {\n char: \" \",\n bg: clearBg,\n })\n }\n }\n // Clear overflow below the ancestor\n if (prevBottom > nodeBottom) {\n const overflowTop = Math.max(nodeBottom, clipBounds?.top ?? 0)\n const overflowBottom = Math.min(prevBottom, clipBounds?.bottom ?? buffer.height)\n const overflowX = Math.max(prev.x, clipBounds?.left ?? 0)\n const overflowWidth = Math.min(prevRight, clipBounds?.right ?? buffer.width) - overflowX\n if (overflowWidth > 0 && overflowBottom > overflowTop) {\n buffer.fill(overflowX, overflowTop, overflowWidth, overflowBottom - overflowTop, {\n char: \" \",\n bg: clearBg,\n })\n }\n }\n // Clear overflow to the left of the ancestor\n if (prev.x < nodeLeft) {\n const overflowX = Math.max(prev.x, 0)\n const overflowWidth = Math.min(nodeLeft, buffer.width) - overflowX\n const overflowTop = Math.max(prevTop, clipBounds?.top ?? 0)\n const overflowBottom = Math.min(prevBottom, clipBounds?.bottom ?? buffer.height)\n if (overflowWidth > 0 && overflowBottom > overflowTop) {\n buffer.fill(overflowX, overflowTop, overflowWidth, overflowBottom - overflowTop, {\n char: \" \",\n bg: clearBg,\n })\n }\n }\n // Clear overflow above the ancestor\n if (prevTop < nodeTop) {\n const overflowTop = Math.max(prevTop, clipBounds?.top ?? 0)\n const overflowBottom = Math.min(nodeTop, clipBounds?.bottom ?? buffer.height)\n const overflowX = Math.max(prev.x, clipBounds?.left ?? 0)\n const overflowWidth = Math.min(prevRight, clipBounds?.right ?? buffer.width) - overflowX\n if (overflowWidth > 0 && overflowBottom > overflowTop) {\n buffer.fill(overflowX, overflowTop, overflowWidth, overflowBottom - overflowTop, {\n char: \" \",\n bg: clearBg,\n })\n }\n }\n }\n // Recurse into subtree-dirty children to find deeper overflows\n if (isDirty(child.dirtyBits, child.dirtyEpoch, SUBTREE_BIT) && child.children !== undefined) {\n _clearDescendantOverflow(\n child.children,\n buffer,\n nodeLeft,\n nodeTop,\n nodeRight,\n nodeBottom,\n scrollOffset,\n clipBounds,\n clearBg,\n )\n }\n }\n}\n\n/**\n * Clear a node's region with inherited bg when it has no backgroundColor.\n * Also clears excess area when the node shrank (previous layout was larger).\n *\n * Clipping: clips to parent's boxRect (prevents overflow) and to the\n * colored ancestor's bounds (prevents bg color bleeding into siblings).\n */\nfunction clearNodeRegion(\n node: AgNode,\n buffer: TerminalBuffer,\n layout: NonNullable<AgNode[\"boxRect\"]>,\n scrollOffset: number,\n clipBounds: ClipBounds | undefined,\n layoutChanged: boolean,\n threadedInheritedBg: NodeRenderState[\"inheritedBg\"],\n): void {\n const inherited = threadedInheritedBg\n const clearBg = inherited.color\n const screenY = layout.y - scrollOffset\n\n // Clip to parent's boxRect to prevent oversized children from clearing\n // beyond their parent's bounds and bleeding inherited bg into sibling regions.\n const parentRect = node.parent?.boxRect\n const parentBottom = parentRect ? parentRect.y - scrollOffset + parentRect.height : undefined\n\n const clearY = clipBounds ? Math.max(screenY, clipBounds.top) : screenY\n let clearBottom = clipBounds\n ? Math.min(screenY + layout.height, clipBounds.bottom)\n : screenY + layout.height\n if (parentBottom !== undefined) {\n clearBottom = Math.min(clearBottom, parentBottom)\n }\n\n // Clip horizontally to clipBounds (overflow:hidden containers) and to the\n // colored ancestor's bounds (prevents inherited bg bleeding into siblings).\n let clearX = layout.x\n let clearWidth = layout.width\n if (clipBounds?.left !== undefined && clipBounds.right !== undefined) {\n if (clearX < clipBounds.left) {\n clearWidth -= clipBounds.left - clearX\n clearX = clipBounds.left\n }\n if (clearX + clearWidth > clipBounds.right) {\n clearWidth = Math.max(0, clipBounds.right - clearX)\n }\n }\n if (inherited.ancestorRect) {\n const ancestorRight = inherited.ancestorRect.x + inherited.ancestorRect.width\n const ancestorLeft = inherited.ancestorRect.x\n if (clearX < ancestorLeft) {\n clearWidth -= ancestorLeft - clearX\n clearX = ancestorLeft\n }\n if (clearX + clearWidth > ancestorRight) {\n clearWidth = Math.max(0, ancestorRight - clearX)\n }\n }\n\n const clearHeight = clearBottom - clearY\n if (clearHeight > 0 && clearWidth > 0) {\n const _cellDbg2 = getCellDebug()\n if (_cellDbg2 && cellCoversPoint(_cellDbg2, clearX, clearY, clearWidth, clearHeight)) {\n const id = ((node.props as Record<string, unknown>).id as string) ?? node.type\n const msg = `CLEAR_REGION ${id} fill=${clearX},${clearY} ${clearWidth}x${clearHeight} bg=${String(clearBg)} COVERS TARGET`\n _cellDbg2.log.push(msg)\n cellLog.debug?.(msg)\n }\n buffer.fill(clearX, clearY, clearWidth, clearHeight, {\n char: \" \",\n bg: clearBg,\n })\n }\n\n // Delegate excess area clearing to shared helper\n clearExcessArea(node, buffer, layout, scrollOffset, clipBounds, layoutChanged, inherited)\n}\n\n/**\n * Clear the excess area when a node shrinks (old bounds were larger than new).\n *\n * This is separated from clearNodeRegion because excess area clearing must happen\n * even when contentRegionCleared is false. Key scenario: absolute-positioned overlays\n * (e.g., search dialog) that shrink while normal-flow siblings are dirty. The\n * forceRepaint path sets hasPrevBuffer=false + ancestorCleared=false, making\n * contentRegionCleared=false — but the cloned buffer still has stale pixels from\n * the old larger layout that must be cleared.\n *\n * Clips to the COLORED ANCESTOR's content area (not immediate parent's full rect)\n * to prevent inherited color from bleeding into sibling areas with different bg.\n *\n * IMPORTANT: Uses content area (inside border/padding), not full boxRect.\n * Without this, excess clearing of a child that previously filled the parent's\n * content area will extend into the parent's border row, overwriting border chars.\n */\nfunction clearExcessArea(\n node: AgNode,\n buffer: TerminalBuffer,\n layout: NonNullable<AgNode[\"boxRect\"]>,\n scrollOffset: number,\n clipBounds: ClipBounds | undefined,\n layoutChanged: boolean,\n inherited: NodeRenderState[\"inheritedBg\"],\n): void {\n if (!layoutChanged || !node.prevLayout) return\n const prev = node.prevLayout\n\n const _cellDbg3 = getCellDebug()\n const _prevCoversCell3 =\n _cellDbg3 && cellCoversPoint(_cellDbg3, prev.x, prev.y - scrollOffset, prev.width, prev.height)\n\n // Only clear if the node actually shrank in at least one dimension\n if (prev.width <= layout.width && prev.height <= layout.height) {\n if (_cellDbg3 && _prevCoversCell3) {\n const id = ((node.props as Record<string, unknown>).id as string) ?? node.type\n const msg =\n `EXCESS_SKIP_NO_SHRINK ${id} prev=${prev.x},${prev.y - scrollOffset} ${prev.width}x${prev.height}` +\n ` now=${layout.x},${layout.y - scrollOffset} ${layout.width}x${layout.height}`\n _cellDbg3.log.push(msg)\n cellLog.debug?.(msg)\n }\n return\n }\n\n // Skip excess clearing when the node MOVED (changed x or y position).\n // The right/bottom excess formulas use new-x + old-y coordinates, which\n // creates a phantom rectangle at wrong positions when the node moved.\n // Example: text at old=(30,7,23,1) → new=(22,8,14,2) computes excess at\n // (36,7) which overwrites a sibling's border character.\n //\n // When the node moved, the parent handles old-pixel cleanup:\n // - Parent's clearNodeRegion covers old pixels within parent's current rect\n // - Parent's clearExcessArea covers old pixels outside parent's rect\n if (prev.x !== layout.x || prev.y !== layout.y) {\n if (_cellDbg3 && _prevCoversCell3) {\n const id = ((node.props as Record<string, unknown>).id as string) ?? node.type\n const msg =\n `EXCESS_SKIP_MOVED ${id} prev=${prev.x},${prev.y - scrollOffset} ${prev.width}x${prev.height}` +\n ` now=${layout.x},${layout.y - scrollOffset} ${layout.width}x${layout.height}` +\n ` (dx=${layout.x - prev.x} dy=${layout.y - prev.y})`\n _cellDbg3.log.push(msg)\n cellLog.debug?.(msg)\n }\n return\n }\n\n const clearBg = inherited.color\n const screenY = layout.y - scrollOffset\n const prevScreenY = prev.y - scrollOffset\n\n // Clip to prevent excess clearing from bleeding outside valid bounds.\n // Start with the colored ancestor's rect (prevents bg color bleed),\n // then further restrict to the immediate parent's content area (prevents\n // overwriting parent's border characters).\n const clipRect = inherited.ancestorRect ?? node.parent?.boxRect\n if (!clipRect) return\n\n const clipRectScreenY = clipRect.y - scrollOffset\n let clipRectBottom = clipRectScreenY + clipRect.height\n let clipRectRight = clipRect.x + clipRect.width\n\n // Always inset by the immediate parent's border/padding.\n // Without this, a child's excess clearing extends into the parent's\n // border row, overwriting border characters with spaces.\n // (The old code skipped inset when clip rect came from a colored ancestor,\n // assuming \"its bg fill covers its border area\" — but bg fill only covers\n // the inside, while renderBorder draws characters on the border row.)\n const parent = node.parent\n if (parent?.boxRect) {\n const parentProps = parent.props as BoxProps\n const border = getBorderSize(parentProps)\n const padding = getPadding(parentProps)\n const parentRight = parent.boxRect.x + parent.boxRect.width - border.right - padding.right\n const parentBottom =\n parent.boxRect.y - scrollOffset + parent.boxRect.height - border.bottom - padding.bottom\n clipRectRight = Math.min(clipRectRight, parentRight)\n clipRectBottom = Math.min(clipRectBottom, parentBottom)\n }\n\n // Clear right margin (old was wider than new)\n if (prev.width > layout.width) {\n const excessX = layout.x + layout.width\n let excessWidth = prev.width - layout.width\n // Clip horizontally to parent's content area (inside border/padding).\n // Without this, excess clearing of a child that previously filled a wider\n // layout extends into the parent's right border, overwriting border chars.\n if (excessX + excessWidth > clipRectRight) {\n excessWidth = Math.max(0, clipRectRight - excessX)\n }\n if (excessWidth > 0) {\n clippedFill(\n buffer,\n excessX,\n excessWidth,\n prevScreenY,\n prevScreenY + prev.height,\n clipBounds,\n clipRectBottom,\n clearBg,\n )\n }\n }\n\n // Clear bottom margin (old was taller than new)\n if (prev.height > layout.height) {\n let bottomWidth = prev.width\n // Clip horizontally to parent's content area\n if (layout.x + bottomWidth > clipRectRight) {\n bottomWidth = Math.max(0, clipRectRight - layout.x)\n }\n clippedFill(\n buffer,\n layout.x,\n bottomWidth,\n screenY + layout.height,\n prevScreenY + prev.height,\n clipBounds,\n clipRectBottom,\n clearBg,\n )\n }\n}\n\n/** Fill a rectangular region, clipping to clipBounds and an outer bottom limit. */\nfunction clippedFill(\n buffer: TerminalBuffer,\n x: number,\n width: number,\n top: number,\n bottom: number,\n clipBounds: ClipBounds | undefined,\n outerBottom: number,\n bg: Color,\n): void {\n const clippedTop = clipBounds ? Math.max(top, clipBounds.top) : top\n const clippedBottom = Math.min(\n clipBounds ? Math.min(bottom, clipBounds.bottom) : bottom,\n outerBottom,\n )\n let clippedX = x\n let clippedWidth = width\n if (clipBounds?.left !== undefined && clipBounds.right !== undefined) {\n if (clippedX < clipBounds.left) {\n clippedWidth -= clipBounds.left - clippedX\n clippedX = clipBounds.left\n }\n if (clippedX + clippedWidth > clipBounds.right) {\n clippedWidth = Math.max(0, clipBounds.right - clippedX)\n }\n }\n const height = clippedBottom - clippedTop\n if (height > 0 && clippedWidth > 0) {\n buffer.fill(clippedX, clippedTop, clippedWidth, height, { char: \" \", bg })\n }\n}\n","/**\n * Backdrop fade — core color helpers.\n *\n * This module owns the buffer-cell adapter (`colorToHex`), hex↔rgb\n * conversion (`rgbToHex`, `hexToRgb`), hex normalization (`normalizeHex`),\n * and the structural `HexColor` type alias. The color math ops that can\n * pass through to `@silvery/color` (`mixSrgb`, `deemphasizeOklch*`) live in\n * `./color-compat.ts` — they follow the upstream-with-fallback pattern so\n * silvery stays publishable during `@silvery/color` release cycles.\n *\n * @see ./color-compat.ts for the upstream color math shim.\n * @see ./plan.ts for the full backdrop color model.\n */\n\nimport { ansi256ToRgb, isDefaultBg, type Color } from \"../../buffer\"\n\n/**\n * Template-literal brand for a canonical 6-digit lowercase hex color.\n *\n * Values of this type have passed through `normalizeHex`, so they are\n * guaranteed to match `/^#[0-9a-f]{6}$/`. Downstream math helpers\n * (`mixSrgb`, `deemphasizeOklch*`) accept plain `string` for upstream\n * compatibility; within the backdrop module we prefer `HexColor` on\n * normalized inputs so TypeScript flags accidental un-normalized passthrough.\n */\nexport type HexColor = `#${string}`\n\n/** Convert a buffer Color to a `#rrggbb` hex string, or null if unresolvable. */\nexport function colorToHex(color: Color): HexColor | null {\n if (color === null) return null\n if (typeof color === \"number\") {\n const rgb = ansi256ToRgb(color)\n return rgbToHex(rgb.r, rgb.g, rgb.b)\n }\n if (isDefaultBg(color)) return null\n return rgbToHex(color.r, color.g, color.b)\n}\n\nexport function rgbToHex(r: number, g: number, b: number): HexColor {\n const clamp = (n: number): string => {\n const v = Math.max(0, Math.min(255, Math.round(n)))\n return v.toString(16).padStart(2, \"0\")\n }\n return `#${clamp(r)}${clamp(g)}${clamp(b)}` as HexColor\n}\n\n/**\n * Parse `#rrggbb` or `#rgb` (any case, with or without leading `#`) into\n * `{ r, g, b }`. Returns null when the input is not a hex color.\n *\n * Strict character-class validation — `parseInt(\"0g\", 16)` returns `0`\n * silently, which would accept malformed hex values. Regex guard rejects\n * anything outside `[0-9a-f]` regardless of case.\n */\nexport function hexToRgb(hex: string): { r: number; g: number; b: number } | null {\n if (typeof hex !== \"string\") return null\n let s = hex.trim().toLowerCase()\n if (s.startsWith(\"#\")) s = s.slice(1)\n if (s.length === 3) {\n if (!/^[0-9a-f]{3}$/.test(s)) return null\n s = s[0]! + s[0]! + s[1]! + s[1]! + s[2]! + s[2]!\n } else if (!/^[0-9a-f]{6}$/.test(s)) {\n return null\n }\n return {\n r: parseInt(s.slice(0, 2), 16),\n g: parseInt(s.slice(2, 4), 16),\n b: parseInt(s.slice(4, 6), 16),\n }\n}\n\n/**\n * Normalize any permissible hex input to a canonical `#rrggbb` lowercase\n * string. Handles `#abc` → `#aabbcc` expansion, case folding, optional\n * leading `#`, and surrounding whitespace. Returns null when the input is\n * not a hex color.\n *\n * Applied by `buildPlan` to every user-provided color option\n * (`defaultBg`, `defaultFg`, `scrimColor`) exactly once so downstream\n * comparisons (`scrim === defaultBg`, etc.) work regardless of input\n * casing or shorthand.\n */\nexport function normalizeHex(hex: string | null | undefined): HexColor | null {\n if (hex === null || hex === undefined) return null\n const rgb = hexToRgb(hex)\n if (!rgb) return null\n return rgbToHex(rgb.r, rgb.g, rgb.b)\n}\n","/**\n * Backdrop fade — stage 1: build the immutable `Plan`.\n *\n * `buildPlan(root, options)` is a PURE, capability-independent pass that\n * walks the tree, collects `data-backdrop-fade` / `data-backdrop-fade-excluded`\n * markers, enforces the single-amount invariant, and resolves the scrim +\n * default colors. The realizers (`./realize-buffer.ts`, `./realize-kitty.ts`)\n * trust the plan: they do NOT re-walk the tree, re-resolve the scrim, or\n * re-validate amounts. This module is the single source of truth.\n *\n * ## The model: per-channel alpha scrim with perceptually-aware fg\n *\n * The pass fades every covered cell by blending BOTH fg AND bg toward a\n * neutral scrim color at the caller's `amount`. Default scrim: pure black\n * for dark themes (Apple `colorWithWhite:0.0 alpha:0.4`), pure white for\n * light. Default amount: `DEFAULT_AMOUNT` (0.25) — calibrated against\n * macOS 0.20, Material 3 0.32, iOS 0.40, Flutter 0.54.\n *\n * ### Two operations, one per channel\n *\n * fg' = deemphasizeOklchToward(fg, amount, towardLight)\n * // OKLCH: L toward 0 or 1,\n * // C *= (1-α)²\n * bg' = mixSrgb(bg, scrim, amount) // sRGB source-over alpha\n *\n * Fg uses OKLCH deemphasize with explicit polarity so colored text\n * deemphasizes toward the correct theme neutral — toward black on dark\n * themes (same formula we've always used), toward white on light themes\n * (new — see `./color-compat.ts` for the math). The quadratic chroma\n * falloff compensates for the human-vision nonlinearity that reads chroma\n * relative to luminance. Bg uses sRGB source-over because the Kitty\n * graphics scrim overlay composites in sRGB at alpha at the hardware\n * level.\n *\n * ### Uniform amount per channel, heaviness tuned at call site\n *\n * Both fg and bg use the same `amount`. An earlier revision halved bg\n * amount to prevent \"scene drowning\" — that caused border/panel brightness\n * inversion (fg-dominated border darkens faster than bg-dominated fill).\n * Heaviness is controlled by `amount`, not by asymmetric math.\n *\n * ## Scrim color\n *\n * - Dark themes: pure black (`#000000`) — Apple's modal-sheet dimming color.\n * - Light themes: pure white (`#ffffff`) — the sign-flipped equivalent.\n *\n * Null-bg cells are resolved to `defaultBg` first, then `mixSrgb` toward\n * the scrim — empty cells darken at the same rate as explicitly-colored ones.\n *\n * Tiers (`colorLevel`): a single code path for all supported tiers. For\n * `\"none\"` (monochrome) the pass short-circuits to a no-op. For `basic`,\n * `256`, and `truecolor`, the per-cell operation is identical — the output\n * phase quantizes the mixed truecolor hex to the tier's palette on emit.\n *\n * ## Purity\n *\n * This module is pure: no console I/O, no buffer access, no mutable module\n * state. `buildPlan` returns a `Plan` whose `mixedAmounts` flag signals\n * multi-amount frames; the orchestrator (`./index.ts`) emits the dev-mode\n * warning so stage 1 remains a pure function of its inputs.\n */\n\nimport { relativeLuminance } from \"@silvery/color\"\nimport type { AgNode, Rect } from \"@silvery/ag/types\"\nimport type { ColorLevel as AnsiColorLevel } from \"@silvery/ansi\"\nimport { type HexColor, normalizeHex } from \"./color\"\n\n/**\n * Terminal color tier for the backdrop pass. Extends `@silvery/ansi`'s\n * `ColorLevel` with `\"none\"`, which short-circuits the pass to a no-op on\n * monochrome terminals.\n */\nexport type ColorLevel = AnsiColorLevel | \"none\"\n\nexport interface BackdropOptions {\n /**\n * Terminal color tier. `\"none\"` short-circuits to a no-op (monochrome).\n * All other tiers run the same sRGB scrim mix — output phase quantizes\n * to the tier's palette on emit.\n */\n colorLevel?: ColorLevel\n /**\n * Explicit scrim color, or `\"auto\"` (default) to derive from theme\n * luminance: pure black for dark themes, pure white for light. Apps that\n * want a tinted scrim (e.g., a mid-gray for flat-color TUIs) override\n * here.\n */\n scrimColor?: HexColor | string | \"auto\"\n /**\n * Default background hex — resolves null/default `cell.bg` before mixing\n * toward the scrim AND feeds the auto-scrim luminance derivation when\n * `scrimColor` is `\"auto\"`.\n */\n defaultBg?: HexColor | string\n /**\n * Default foreground hex — resolves null/default `cell.fg` before the\n * deemphasize pass. Without this, text using the terminal's default fg\n * would stay at full brightness against a darkened backdrop (looks like\n * the text is POPPING instead of receding). If omitted, the pass picks\n * the opposite of the scrim (white for dark scrim, black for light).\n */\n defaultFg?: HexColor | string\n /**\n * When true, emit Kitty graphics protocol overlays on emoji cells inside\n * the faded region. The terminal renders a translucent scrim image above\n * the emoji glyph, which SGR 2 \"dim\" alone can't fade on bitmap emoji.\n *\n * CJK wide-char cells are NOT emoji — they respond to fg color like text,\n * so they go through the normal deemphasize path regardless of Kitty\n * availability. Only emoji cells (detected via `isLikelyEmoji`) skip the\n * buffer mix when Kitty is active.\n */\n kittyGraphics?: boolean\n}\n\n/** Marker prop key for include rects (fade cells INSIDE the node's rect). */\nexport const BACKDROP_FADE_ATTR = \"data-backdrop-fade\"\n/** Marker prop key for exclude rects (fade everything OUTSIDE the node's rect). */\nexport const BACKDROP_FADE_EXCLUDE_ATTR = \"data-backdrop-fade-excluded\"\n\n/**\n * Luminance threshold for dark/light theme detection.\n *\n * 0.18 is well below the WCAG midpoint. Standard dark terminal themes\n * (Catppuccin Mocha bg #1e1e2e, luminance ≈ 0.012; Tokyo Night bg #1a1b26,\n * ≈ 0.010) are well below. Light themes (GitHub Light #ffffff = 1.0) above.\n */\nexport const DARK_LUMINANCE_THRESHOLD = 0.18\n\n/** Canonical scrim colors — Apple's `colorWithWhite:0.0` / `:1.0`. */\nexport const DARK_SCRIM: HexColor = \"#000000\"\nexport const LIGHT_SCRIM: HexColor = \"#ffffff\"\n\n/**\n * Default fade amount — the calibrated baseline used when a marker\n * materializes as a presence attribute (`<Backdrop fade />`,\n * `data-backdrop-fade=\"\"`, `data-backdrop-fade={true}`) without an explicit\n * numeric value. Calibrated against macOS 0.20, Material 3 0.32, iOS 0.40,\n * Flutter 0.54. Re-exported from `index.ts` so downstream callers can\n * reference the same constant.\n */\nexport const DEFAULT_AMOUNT = 0.25\n\n/**\n * A single fade region. The per-frame `amount` lives on `Plan`, not on\n * the individual rect — the single-amount invariant (one scrim image per\n * frame at one alpha) makes per-rect amounts meaningless for realization.\n * `buildPlan` inspects the per-marker amount ONLY to detect the mixed-\n * amounts condition, then discards it.\n */\nexport interface PlanRect {\n readonly rect: Rect\n}\n\n/**\n * The immutable output of `buildPlan` — a capability-independent\n * description of what the backdrop pass intends to do this frame.\n *\n * The realizers (`realizeToBuffer`, `realizeToKitty`) trust the plan:\n * they do NOT re-walk the tree, re-resolve the scrim, or re-validate\n * amounts. `buildPlan` is the single source of truth.\n *\n * ### Invariants enforced by `buildPlan`\n *\n * - `active = includes.length > 0 || excludes.length > 0` whenever a\n * non-zero fade marker is present. The stage-1 pass short-circuits to an\n * inactive plan for `colorLevel: \"none\"`.\n * - `amount ∈ [0, 1]`, clamped, and identical across all collected rects\n * (single-amount invariant — mixed amounts break the Kitty overlay's\n * one-image-one-alpha model; `mixedAmounts=true` surfaces the dev warn,\n * prod falls back to first).\n * - `scrim` is either a normalized `#rrggbb` hex (for the truecolor/256\n * tiers with a known theme bg or an explicit `scrimColor`) or `null`\n * (legacy fallback where `fadeCell` mixes fg toward cell.bg without a\n * scrim).\n * - `defaultBg` / `defaultFg` are resolved for the stage-2 passes — the\n * realizers substitute these when `cell.bg` / `cell.fg` is null.\n * - `scrimTowardLight` records whether the scrim is light (white-ish) or\n * dark (black-ish). The fg deemphasize pass uses this to drift toward\n * the correct neutral — toward 0 on dark themes, toward 1 on light.\n * - `kittyEnabled` is the DERIVED capability flag — true only when the\n * caller enabled `kittyGraphics` AND the plan has a resolvable scrim\n * (Kitty overlay needs a tint). Realizers read this instead of\n * re-deriving at each call site.\n */\nexport interface Plan {\n /** True when the tree had at least one fade marker with amount > 0. */\n readonly active: boolean\n /**\n * The enforced single amount for this frame, clamped to [0, 1]. Zero\n * when `active` is false.\n */\n readonly amount: number\n /**\n * Resolved scrim hex, or null when no theme bg is available. The\n * buffer-realizer falls back to a legacy single-channel mix when null.\n */\n readonly scrim: HexColor | null\n /** Default background hex for resolving null/default `cell.bg`. */\n readonly defaultBg: HexColor | null\n /**\n * Default foreground hex for resolving null/default `cell.fg`. Derived\n * from `options.defaultFg`, else the opposite of the scrim (white for\n * dark scrim, black for light).\n */\n readonly defaultFg: HexColor | null\n /** Rects marked `data-backdrop-fade` — fade cells INSIDE each rect. */\n readonly includes: readonly PlanRect[]\n /**\n * Rects marked `data-backdrop-fade-excluded` — fade everything OUTSIDE\n * each rect (the modal \"cuts a hole\").\n */\n readonly excludes: readonly PlanRect[]\n /**\n * True when the collected markers had differing `amount` values. The\n * orchestrator reads this to emit a dev-mode warning; stage 1 stays pure\n * by surfacing the signal instead of calling `console.warn` inline.\n * Mixed amounts break the Kitty overlay's one-image-one-alpha model;\n * prod falls back to the first observed amount.\n */\n readonly mixedAmounts: boolean\n /**\n * True when the resolved `scrim` is on the LIGHT side of the luminance\n * threshold (scrim drifts toward white on light themes). False for dark\n * themes (scrim drifts toward black). Null-scrim plans default to\n * `false` (legacy dark-theme behavior).\n *\n * Determined by luminance comparison, NOT by string equality against\n * `DARK_SCRIM` / `LIGHT_SCRIM` — apps that supply a tinted scrim\n * (e.g., a mid-gray neutral for a flat-color TUI) still get the right\n * polarity.\n */\n readonly scrimTowardLight: boolean\n /**\n * True when the Kitty graphics overlay should run this frame. Derived\n * from `options.kittyGraphics === true && scrim !== null` — the overlay\n * needs a resolvable tint to composite, so a null-scrim plan can't use\n * the Kitty path even when the caller has the capability enabled.\n * Realizers read this directly (no re-derivation at call sites).\n */\n readonly kittyEnabled: boolean\n}\n\n/** Sentinel \"nothing to do\" plan — reused across frames to avoid allocations. */\nexport const INACTIVE_PLAN: Plan = Object.freeze({\n active: false,\n amount: 0,\n scrim: null,\n defaultBg: null,\n defaultFg: null,\n includes: Object.freeze([]) as readonly PlanRect[],\n excludes: Object.freeze([]) as readonly PlanRect[],\n mixedAmounts: false,\n scrimTowardLight: false,\n kittyEnabled: false,\n})\n\n/**\n * Quick check: does the tree contain any backdrop markers? Used as a gate so\n * we don't clone the buffer every frame when no fade is active. Walks the\n * full tree once (O(N)) — the alternative (tracking dirty markers in the\n * reconciler) is more complex and the walk is cheap compared to the pass.\n */\nexport function hasBackdropMarkers(root: AgNode): boolean {\n const props = root.props as Record<string, unknown>\n if (props[BACKDROP_FADE_ATTR] !== undefined || props[BACKDROP_FADE_EXCLUDE_ATTR] !== undefined) {\n return true\n }\n for (const child of root.children) {\n if (hasBackdropMarkers(child)) return true\n }\n return false\n}\n\n/**\n * Stage 1 — build the immutable `Plan`.\n *\n * Pure function of `(tree markers, options)`. No buffer access, no Kitty\n * capability knowledge, no console I/O. The realizers read from the plan\n * exclusively; the orchestrator (`./index.ts`) handles dev-mode diagnostics\n * derived from `plan.mixedAmounts`.\n *\n * Returns `INACTIVE_PLAN` when:\n * - `colorLevel === \"none\"` (monochrome terminal — pass is a no-op).\n * - The tree has no backdrop markers, OR all markers have `amount <= 0`.\n */\nexport function buildPlan(root: AgNode, options?: BackdropOptions): Plan {\n const colorLevel: ColorLevel = options?.colorLevel ?? \"truecolor\"\n if (colorLevel === \"none\") return INACTIVE_PLAN\n\n // Collect rects + per-marker amounts. The amounts are inspected only to\n // verify the single-amount invariant; they're discarded after.\n const includes: PlanRect[] = []\n const excludes: PlanRect[] = []\n const includeAmounts: number[] = []\n const excludeAmounts: number[] = []\n collectBackdropMarkers(root, includes, excludes, includeAmounts, excludeAmounts)\n\n if (includes.length === 0 && excludes.length === 0) return INACTIVE_PLAN\n\n // Resolve the three color inputs. Every user-provided hex is normalized\n // exactly once here — downstream comparisons (e.g., `scrim === defaultBg`)\n // and string-equality tests in the realizers work regardless of input\n // casing or shorthand.\n // - defaultBg: used to resolve null/default cell.bg before sRGB mix\n // AND feeds auto-scrim luminance derivation.\n // - scrimColor: the target of the mix. \"auto\" (default) derives from\n // luminance — black for dark themes, white for light.\n // - defaultFg: used to resolve null/default cell.fg before deemphasize.\n // Critical for default-fg text (common in TUIs that don't set colors\n // on every Text node) — without it, default-fg cells skip the fade\n // and the text pops against a dimmed bg.\n const defaultBg = normalizeHex(options?.defaultBg ?? null)\n const scrimColorOpt = options?.scrimColor\n // Explicit scrimColor wins when it parses to a valid hex. Unparseable\n // strings (e.g. \"#zzz\", \"rgb(...)\") quietly fall back to the luminance-\n // derived default — treating a typo as \"use auto\" is friendlier than\n // nullifying the scrim and dropping to the legacy single-channel path.\n const explicitScrim =\n typeof scrimColorOpt === \"string\" && scrimColorOpt !== \"auto\" ? normalizeHex(scrimColorOpt) : null\n const scrim = explicitScrim ?? deriveAutoScrimColor(defaultBg)\n\n // Polarity by luminance: scrim with luminance >= threshold is \"light\"\n // (drift fg toward white), below is \"dark\" (drift toward black). Uses\n // the same WCAG-derived `relativeLuminance` as the auto-scrim derivation\n // so any custom scrim color lands on the correct branch.\n const scrimTowardLight = isLightScrim(scrim)\n\n // Default fg fallback: opposite of the scrim polarity. For a tinted\n // scrim the user probably wants to override with an explicit `defaultFg`\n // anyway, but this gives a sensible default.\n const defaultFg =\n normalizeHex(options?.defaultFg) ?? (scrim === null ? null : scrimTowardLight ? DARK_SCRIM : LIGHT_SCRIM)\n\n // Single-amount invariant: one scrim image per frame at one alpha. Mixed\n // amounts are surfaced via `mixedAmounts` so the orchestrator can emit a\n // dev-mode warning; stage 1 stays pure.\n const { amount, hasMixedAmounts } = assertSingleAmount(includeAmounts, excludeAmounts)\n\n // Kitty overlay is available only when the caller enabled the capability\n // AND the plan resolved a scrim — the overlay needs a tint to composite.\n const kittyEnabled = options?.kittyGraphics === true && scrim !== null\n\n return {\n active: true,\n amount,\n scrim,\n defaultBg,\n defaultFg,\n includes,\n excludes,\n mixedAmounts: hasMixedAmounts,\n scrimTowardLight,\n kittyEnabled,\n }\n}\n\n/**\n * Detect the single-amount invariant across all markers. Returns the\n * clamped first-observed amount AND a flag indicating whether any later\n * marker differed. The orchestrator uses the flag to emit a dev-mode warn.\n *\n * Mixed amounts currently break the Kitty overlay (one image, one alpha)\n * and have unclear composition semantics (max? source-over compound?).\n * Production behavior is first-wins but will look wrong until the markers\n * are reconciled to a single value.\n */\nfunction assertSingleAmount(\n includeAmounts: readonly number[],\n excludeAmounts: readonly number[],\n): { amount: number; hasMixedAmounts: boolean } {\n const first =\n includeAmounts.length > 0\n ? includeAmounts[0]!\n : excludeAmounts.length > 0\n ? excludeAmounts[0]!\n : 0\n let hasMixedAmounts = false\n for (const a of includeAmounts) {\n if (Math.abs(a - first) > 1e-6) {\n hasMixedAmounts = true\n break\n }\n }\n if (!hasMixedAmounts) {\n for (const a of excludeAmounts) {\n if (Math.abs(a - first) > 1e-6) {\n hasMixedAmounts = true\n break\n }\n }\n }\n return { amount: Math.max(0, Math.min(1, first)), hasMixedAmounts }\n}\n\n/**\n * Derive the auto scrim color from a normalized bg hex. Dark themes scrim\n * toward `DARK_SCRIM`; light themes scrim toward `LIGHT_SCRIM`. Returns\n * `null` when `bg` is absent or unparseable — signals legacy single-\n * channel fallback in `fadeCell`.\n */\nfunction deriveAutoScrimColor(bg: HexColor | null): HexColor | null {\n if (!bg) return null\n const lum = relativeLuminance(bg)\n if (lum === null) return null\n return lum < DARK_LUMINANCE_THRESHOLD ? DARK_SCRIM : LIGHT_SCRIM\n}\n\n/**\n * Polarity detection for an arbitrary scrim color. Returns `true` when the\n * scrim is on the LIGHT side of the luminance threshold (fg should drift\n * toward white), `false` otherwise. Uses luminance, not string equality,\n * so tinted scrims (mid-gray neutrals, etc.) land on the correct branch.\n * Null scrim defaults to false (dark-theme fallback behavior).\n */\nfunction isLightScrim(scrim: HexColor | null): boolean {\n if (scrim === null) return false\n const lum = relativeLuminance(scrim)\n if (lum === null) return false\n return lum >= DARK_LUMINANCE_THRESHOLD\n}\n\nfunction collectBackdropMarkers(\n node: AgNode,\n includes: PlanRect[],\n excludes: PlanRect[],\n includeAmounts: number[],\n excludeAmounts: number[],\n): void {\n const props = node.props as Record<string, unknown>\n const includeRaw = props[BACKDROP_FADE_ATTR]\n const excludeRaw = props[BACKDROP_FADE_EXCLUDE_ATTR]\n\n if (includeRaw !== undefined || excludeRaw !== undefined) {\n const rect = node.screenRect ?? node.scrollRect ?? node.boxRect\n if (rect && rect.width > 0 && rect.height > 0) {\n const inc = parseFade(includeRaw)\n if (inc !== null) {\n includes.push({ rect })\n includeAmounts.push(inc)\n }\n const exc = parseFade(excludeRaw)\n if (exc !== null) {\n excludes.push({ rect })\n excludeAmounts.push(exc)\n }\n }\n }\n\n for (const child of node.children) {\n collectBackdropMarkers(child, includes, excludes, includeAmounts, excludeAmounts)\n }\n}\n\n/**\n * Coerce a marker attribute value into a fade amount in (0, 1], or `null`\n * when the marker is absent / disabled.\n *\n * Accepted inputs:\n *\n * - `undefined`, `null`, `false` → `null` (marker absent)\n * - `true` → `DEFAULT_AMOUNT` (presence attribute, e.g. `<Backdrop fade />`)\n * - `\"\"` → `DEFAULT_AMOUNT` (HTML-attribute presence idiom)\n * - finite numeric or numeric-string (including in scientific notation):\n * - `<= 0` → `null` (explicit opt-out)\n * - `> 1` → `1` (clamped)\n * - otherwise → the numeric value itself\n * - any other non-numeric string (e.g. `\"bad\"`) → `null`\n *\n * The presence-attribute idiom lets components emit `data-backdrop-fade`\n * without threading a numeric value through when the default is fine. The\n * React `Backdrop.tsx` / `ModalDialog.tsx` today always emit a numeric\n * attribute, but the semantic is forward-compatible so nothing breaks if\n * a future component (or a hand-written JSX usage) prefers presence-only.\n */\nfunction parseFade(raw: unknown): number | null {\n if (raw === undefined || raw === null) return null\n if (raw === false) return null\n if (raw === true) return DEFAULT_AMOUNT\n if (raw === \"\") return DEFAULT_AMOUNT\n const n = typeof raw === \"number\" ? raw : Number(raw)\n if (!Number.isFinite(n)) return null\n if (n <= 0) return null\n return n > 1 ? 1 : n\n}\n","/**\n * Backdrop fade — `@silvery/color` compatibility shim.\n *\n * Temporary shim while `@silvery/color` lags behind on publish cycles.\n * `@silvery/color` does export `mixSrgb` and `deemphasize` from source; this\n * module prefers the upstream versions at runtime and falls back to a\n * local implementation when an upstream export is missing — e.g., when a\n * new helper is introduced in the same release cycle as the silvery\n * package that imports it (the published `@silvery/color` dist doesn't\n * ship the new name until its next publish, breaking CI verify).\n *\n * The fallback implementations are byte-identical to the upstream ones.\n * Once all downstream consumers of silvery are on a published version of\n * `@silvery/color` that exports every name we reference, delete the\n * `local*` fallbacks and collapse each export to a direct re-export.\n *\n * Light-theme-aware deemphasize (`deemphasizeOklchToward`) is NOT in\n * upstream yet — it's only shipped by this module. When it lands in\n * `@silvery/color`, replace the local implementation with an upstream\n * re-export behind the same shim.\n *\n * @see ./color.ts for hex↔rgb adapter helpers and `HexColor` type.\n */\n\nimport * as Upstream from \"@silvery/color\"\nimport { hexToRgb, rgbToHex } from \"./color\"\n\n/**\n * sRGB source-over alpha mix. `out = a * (1 - t) + b * t`.\n *\n * Prefers `@silvery/color`'s published export; falls back to the local copy\n * when upstream doesn't ship the name yet. The local implementation matches\n * `@silvery/color/src/color.ts` byte-for-byte and is safe to use while the\n * publish train catches up.\n */\nfunction localMixSrgb(a: string, b: string, t: number): string {\n const ra = hexToRgb(a)\n const rb = hexToRgb(b)\n if (!ra || !rb) return a\n const u = Math.max(0, Math.min(1, t))\n const r = ra.r * (1 - u) + rb.r * u\n const g = ra.g * (1 - u) + rb.g * u\n const bl = ra.b * (1 - u) + rb.b * u\n return rgbToHex(r, g, bl)\n}\n\nconst upstreamMixSrgb = (Upstream as unknown as Record<string, unknown>).mixSrgb as\n | ((a: string, b: string, t: number) => string)\n | undefined\n\n/** sRGB source-over mix. Prefers upstream `@silvery/color`; falls back to the local copy. */\nexport const mixSrgb: (a: string, b: string, t: number) => string = upstreamMixSrgb ?? localMixSrgb\n\n/**\n * OKLCH-native deemphasize that drifts toward EITHER black (dark themes)\n * OR white (light themes). `towardLight` controls the lightness target;\n * the chroma falloff is identical in both directions.\n *\n * towardLight=false (dark themes):\n * L' = L × (1 - amount) // linear toward black\n * towardLight=true (light themes):\n * L' = L + (1 - L) × amount // linear toward white\n * (both branches):\n * C' = C × (1 - amount)² // quadratic chroma falloff\n * H' = H // hue preserved\n *\n * The asymmetric chroma falloff corrects for a perceptual nonlinearity:\n * the human visual system reads chroma RELATIVE to luminance, so a modest\n * OKLCH C at extreme L *appears* distinctly more chromatic than the same C\n * near mid-L. Proportional L+C scaling (`C *= 1-α`, preserving C/L) feels\n * \"more saturated when darkened\" to viewers — the exact complaint that\n * prompted the quadratic version.\n *\n * Using `(1-α)²` for chroma reduces saturation faster than lightness on\n * both polarities:\n *\n * α=0.25 → C *= 0.563 (C/L drops to 75% of original)\n * α=0.40 → C *= 0.360 (C/L drops to 60%)\n * α=0.50 → C *= 0.250 (C/L drops to 50%)\n * α=1.00 → C *= 0 (fully faded to the target luminance).\n *\n * Light-theme case (towardLight=true): a bright colored text on a light\n * backdrop is made paler by raising L toward 1 and dropping C — the\n * symmetric \"fade toward the page color\" behavior macOS ships in light\n * mode. Without the polarity flip, the dark-only formula `L *= (1 - α)`\n * would darken colored text on a light bg, which reads as \"text popping\"\n * against the faded scrim rather than receding.\n */\nfunction localDeemphasizeOklchToward(hex: string, amount: number, towardLight: boolean): string {\n const o = upstreamHexToOklch(hex)\n if (!o) return hex\n const a = Math.max(0, Math.min(1, amount))\n const chromaFactor = (1 - a) * (1 - a)\n const L = towardLight ? o.L + (1 - o.L) * a : o.L * (1 - a)\n return upstreamOklchToHex({\n L: Math.max(0, Math.min(1, L)),\n C: Math.max(0, o.C * chromaFactor),\n H: o.H,\n })\n}\n\n// Lightweight typed indirection into upstream — avoids `as any` at every\n// call site. Using the runtime-export form so bundlers don't require the\n// names at import time (supports the \"same-release-cycle publish\" case).\ntype Oklch = { L: number; C: number; H: number }\nconst upstreamHexToOklch = (Upstream as unknown as Record<string, unknown>).hexToOklch as\n | ((hex: string) => Oklch | null)\n // The upstream `hexToOklch` has always shipped — cast is a safety net,\n // not a feature-gate like the compat helpers above.\n | ((hex: string) => Oklch | null)\nconst upstreamOklchToHex = (Upstream as unknown as Record<string, unknown>).oklchToHex as (\n color: Oklch,\n) => string\n\n/**\n * OKLCH deemphasize with explicit polarity control. See\n * `localDeemphasizeOklchToward` for the math and rationale.\n *\n * Prefers the upstream `deemphasizeOklchToward` export when available;\n * falls back to the local implementation when upstream doesn't ship the\n * name yet (expected during the first release cycle that needs the\n * light-theme polarity).\n */\nconst upstreamDeemphasizeToward = (Upstream as unknown as Record<string, unknown>)\n .deemphasizeOklchToward as\n | ((hex: string, amount: number, towardLight: boolean) => string)\n | undefined\n\nexport const deemphasizeOklchToward: (hex: string, amount: number, towardLight: boolean) => string =\n upstreamDeemphasizeToward ?? localDeemphasizeOklchToward\n\n/**\n * Dark-theme deemphasize. Retained as a convenience alias so existing\n * callers don't have to thread `towardLight=false` everywhere. New code\n * should prefer `deemphasizeOklchToward` directly so the polarity is\n * explicit at the call site.\n */\nexport function deemphasizeOklch(hex: string, amount: number): string {\n return deemphasizeOklchToward(hex, amount, false)\n}\n","/**\n * Backdrop fade — shared region walker.\n *\n * Both stage 2 realizers (`realize-buffer.ts` and `realize-kitty.ts`) need\n * to iterate every cell covered by the plan's include + exclude rects with\n * single-visit semantics. Overlapping rects would otherwise double-fade\n * cells (buffer) or emit duplicate Kitty placements (overlay).\n *\n * `forEachFadeRegionCell` centralizes that walk with a `Uint8Array`\n * \"visited\" bitset. Pass an `includes` list to fade cells INSIDE each rect\n * (data-backdrop-fade), and an `excludes` list to fade everything OUTSIDE\n * each rect (data-backdrop-fade-excluded). Cells inside any include and\n * cells outside any exclude are both visited — but each cell is visited at\n * most once.\n *\n * The walker is pure / allocation-conscious: a single `Uint8Array` sized\n * to the buffer. No per-rect allocation; no closure captures beyond the\n * visitor callback.\n *\n * ### Determinism\n *\n * Visit order is stable and deterministic:\n *\n * 1. Includes are walked in their given order (parent before child,\n * matching `collectBackdropMarkers` walk order).\n * 2. Within each include rect, rows ascend; within each row, cols ascend.\n * 3. Excludes follow as a SINGLE buffer scan (rows ascend, cols ascend\n * within each row). Each cell is tested against the union of all\n * clipped exclude rects via an `insideAnyExclude` guard so the\n * \"outside the union\" semantic holds across multiple holes.\n *\n * The Kitty overlay's STRICT determinism invariant depends on this —\n * identical (plan, buffer) inputs must produce byte-identical overlay\n * strings across fresh and incremental paths.\n *\n * @see ./plan.ts — `Plan`, `PlanRect`\n * @see ./realize-buffer.ts — stage 2a (cell-level transform)\n * @see ./realize-kitty.ts — stage 2b (Kitty overlay emission)\n */\n\nimport type { PlanRect } from \"./plan\"\n\n/**\n * Walk every cell covered by the plan's include and exclude rects and\n * invoke `visit(x, y)` for each unique cell.\n *\n * `includes` cells are those INSIDE any include rect.\n * `excludes` cells are those OUTSIDE any exclude rect (i.e., excluded from\n * the exclude's interior — the modal \"cuts a hole\" pattern).\n *\n * Rects are clipped to the buffer bounds (`[0, bufferWidth)` ×\n * `[0, bufferHeight)`). Zero-size rects are skipped. Cells are deduped\n * across all rects via a `Uint8Array` bitset — a cell belonging to two\n * overlapping includes is visited once, not twice.\n *\n * Returns the count of unique cells visited. Useful for short-circuiting\n * the \"was any cell modified?\" signal in realizers.\n */\nexport function forEachFadeRegionCell(\n bufferWidth: number,\n bufferHeight: number,\n includes: readonly PlanRect[],\n excludes: readonly PlanRect[],\n visit: (x: number, y: number) => void,\n): number {\n if (bufferWidth <= 0 || bufferHeight <= 0) return 0\n if (includes.length === 0 && excludes.length === 0) return 0\n\n const seen = new Uint8Array(bufferWidth * bufferHeight)\n let count = 0\n\n const once = (x: number, y: number): void => {\n const i = y * bufferWidth + x\n if (seen[i] !== 0) return\n seen[i] = 1\n count += 1\n visit(x, y)\n }\n\n for (const { rect } of includes) {\n const x0 = Math.max(0, rect.x)\n const y0 = Math.max(0, rect.y)\n const x1 = Math.min(bufferWidth, rect.x + rect.width)\n const y1 = Math.min(bufferHeight, rect.y + rect.height)\n if (x0 >= x1 || y0 >= y1) continue\n for (let y = y0; y < y1; y++) {\n for (let x = x0; x < x1; x++) once(x, y)\n }\n }\n\n if (excludes.length > 0) {\n // Semantics: visit every cell OUTSIDE the UNION of all exclude rects.\n //\n // Per-rect iteration is incorrect: outside(A) ∪ outside(B) ⊃ outside(A ∪ B).\n // With two disjoint excludes, cells inside exclude-A's hole are \"outside\n // exclude-B\" (and vice versa) and would be visited, fading the hole.\n //\n // Single-pass scan with an `insideAnyExclude` guard preserves the modal\n // \"cuts a hole\" semantic across multiple holes: a cell is visited iff it\n // is outside EVERY exclude rect.\n const clipped: Array<{ x0: number; y0: number; x1: number; y1: number }> = []\n for (const { rect } of excludes) {\n const x0 = Math.max(0, rect.x)\n const y0 = Math.max(0, rect.y)\n const x1 = Math.min(bufferWidth, rect.x + rect.width)\n const y1 = Math.min(bufferHeight, rect.y + rect.height)\n if (x0 < x1 && y0 < y1) clipped.push({ x0, y0, x1, y1 })\n }\n if (clipped.length > 0) {\n for (let y = 0; y < bufferHeight; y++) {\n for (let x = 0; x < bufferWidth; x++) {\n let insideAnyExclude = false\n for (const r of clipped) {\n if (x >= r.x0 && x < r.x1 && y >= r.y0 && y < r.y1) {\n insideAnyExclude = true\n break\n }\n }\n if (!insideAnyExclude) once(x, y)\n }\n }\n } else {\n // All exclude rects clipped to empty — degenerate case where no cell is\n // \"inside\" any exclude, so every cell is \"outside the union\" and gets\n // visited. Matches the historical single-rect behavior when the rect\n // was wholly outside the buffer.\n for (let y = 0; y < bufferHeight; y++) {\n for (let x = 0; x < bufferWidth; x++) once(x, y)\n }\n }\n }\n\n return count\n}\n","/**\n * Backdrop fade — stage 2a: apply the plan's cell-level transform to the\n * terminal buffer.\n *\n * Uses the shared `forEachFadeRegionCell` walker (`./region.ts`) to visit\n * every cell covered by the plan's include + exclude rects exactly once.\n * Trusts the plan: no marker re-collection, no scrim/default resolution,\n * no amount validation, no capability re-derivation (`plan.kittyEnabled`\n * is the sole source of truth for the emoji branch).\n *\n * Wide ≠ emoji. CJK / Hangul / Japanese fullwidth text occupies two columns\n * but responds to `fg` color normally — it goes through the standard mix\n * path. Only EMOJI (bitmap glyphs that ignore `fg`) need special handling,\n * detected via `isLikelyEmoji(cell.char)`.\n *\n * For emoji cells, two paths, mutually exclusive:\n *\n * 1. **Kitty graphics available** (`plan.kittyEnabled === true`): emoji cells\n * are SKIPPED entirely here. `./realize-kitty.ts` emits a translucent\n * scrim image at alpha=amount above each emoji cell, and the terminal\n * composites the overlay on top of the unmixed cell, landing at\n * `cell_bg * (1 - amount) + scrim * amount` — the same luminance as\n * surrounding text cells. This avoids the double-fade that would make\n * emoji bg visibly blacker.\n *\n * 2. **Kitty graphics unavailable** (`plan.kittyEnabled === false`): the\n * per-cell mix runs on emoji cells too and stamps `attrs.dim` (SGR 2)\n * on lead + continuation. Terminals honoring SGR 2 on emoji fade the\n * glyph; others see the glyph at full brightness but the cell bg\n * matches surroundings.\n *\n * @see ./plan.ts for the color model and scrim derivation.\n * @see ./color.ts for the hex↔rgb adapter helpers.\n * @see ./color-compat.ts for `mixSrgb` + `deemphasizeOklchToward`.\n * @see ./region.ts for the shared include/exclude region walker.\n */\n\nimport type { TerminalBuffer } from \"../../buffer\"\nimport { isLikelyEmoji } from \"../../unicode\"\nimport { colorToHex, type HexColor, hexToRgb } from \"./color\"\nimport { deemphasizeOklchToward, mixSrgb } from \"./color-compat\"\nimport { DARK_SCRIM, LIGHT_SCRIM, type Plan } from \"./plan\"\nimport { forEachFadeRegionCell } from \"./region\"\n\n/**\n * Stage 2a — apply the plan's cell-level transform to the buffer.\n *\n * Walks every include + exclude cell once via `forEachFadeRegionCell` and\n * applies `fadeCell` with the plan's single `amount`. The buffer is mutated\n * in place.\n *\n * When `plan.kittyEnabled === true`, emoji cells (detected via\n * `isLikelyEmoji(cell.char)`) are SKIPPED — the Kitty overlay realizer\n * composites the scrim on top of the unmixed cell. When\n * `plan.kittyEnabled === false`, emoji cells go through the per-cell mix\n * AND get SGR 2 (`attrs.dim`) stamped on lead + continuation.\n *\n * Returns `true` when at least one buffer cell was mutated.\n */\nexport function realizeToBuffer(plan: Plan, buffer: TerminalBuffer): boolean {\n if (!plan.active) return false\n if (plan.amount <= 0) return false\n\n let modified = false\n forEachFadeRegionCell(buffer.width, buffer.height, plan.includes, plan.excludes, (x, y) => {\n if (fadeCell(buffer, x, y, plan)) modified = true\n })\n return modified\n}\n\n/**\n * Fade a single cell. Returns true if the cell was modified.\n *\n * Two-channel transform (see `./plan.ts` for the full color model):\n *\n * fg' = deemphasizeOklchToward(fg, amount, scrimTowardLight)\n * bg' = mixSrgb(bg, scrim, amount)\n *\n * Fg uses OKLCH deemphasize (not sRGB mixing) so colored text deemphasizes\n * perceptually — pale lavender becomes dull slate on dark themes, pale\n * grey on light themes. The polarity flag `scrimTowardLight` (from the\n * plan) steers L toward 0 or 1; chroma falloff is symmetric. Bg uses sRGB\n * source-over because the Kitty graphics scrim overlay composites in sRGB\n * at alpha at the hardware level.\n *\n * `null`/`DEFAULT_BG` cells are resolved to `plan.defaultBg` first (that\n * IS the color the terminal paints), then mixed toward the scrim — so\n * empty cells darken at the same rate as explicitly-colored cells.\n *\n * Uniform amounts for fg + bg preserve relative brightness ordering across\n * borders vs fills. Heaviness is controlled by `plan.amount` (default\n * 0.25, calibrated against macOS 0.20, Material 3 0.32, iOS 0.40, Flutter\n * 0.54).\n *\n * The `scrim !== null` gate activates the full two-channel path: fg always\n * deemphasizes, and bg mixes toward the scrim when a resolvable bg hex is\n * available (`cell.bg` non-null OR `defaultBg` non-null). When both\n * `scrim` and a resolvable bg are null (no theme context at all): falls\n * back to mixing fg toward `cell.bg` so the cell still reads as \"receded\"\n * without needing external theme info.\n *\n * ### Wide-char / emoji handling\n *\n * Terminals render emoji using the glyph's own bitmap colors — the fg mix\n * has no visible effect on the emoji glyph. Two paths, mutually exclusive:\n *\n * 1. Kitty graphics available: `fadeCell` SKIPS emoji wide cells entirely.\n * The Kitty overlay composites the scrim at alpha=amount on top, landing\n * at `cell * (1 - amount) + scrim * amount` — same as surrounding cells.\n * 2. Kitty unavailable: mix the cell bg + stamp `attrs.dim` on lead +\n * continuation. Terminals honoring SGR 2 on emoji fade the glyph. Wide\n * TEXT (CJK etc.) goes through the normal deemphasize path on both\n * branches — the fg mix works fine and SGR 2 on CJK over-fades.\n */\nfunction fadeCell(buffer: TerminalBuffer, x: number, y: number, plan: Plan): boolean {\n // Skip continuation half of wide chars — the leading cell at x-1 updates\n // this cell in lockstep when it's processed.\n if (buffer.isCellContinuation(x, y)) return false\n\n const cell = buffer.getCell(x, y)\n\n // Glyph classification: only EMOJI cells (bitmap glyphs that ignore fg\n // color) go through the Kitty overlay path. CJK and other wide TEXT cells\n // respond to fg color like narrow text and go through the buffer mix\n // path, which is correct for them. `cell.wide` alone is the wrong\n // discriminator — wide != emoji — pro review flagged this as a bug class.\n const isEmojiGlyph = cell.wide && isLikelyEmoji(cell.char ?? \"\")\n\n // When Kitty is available and this cell is an emoji, skip the buffer mix\n // — the Kitty overlay will composite the scrim at alpha=amount above the\n // unmixed cell, landing at `cell_bg * (1 - amount) + scrim * amount`,\n // same luminance as surrounding mixed cells. Mixing here too would\n // double-fade and produce a visibly blacker emoji bg.\n if (plan.kittyEnabled && isEmojiGlyph) return false\n\n const { amount, scrim, defaultBg, defaultFg, scrimTowardLight } = plan\n const rawFgHex = colorToHex(cell.fg)\n\n if (scrim !== null) {\n // Two-channel path — scrim is available. An explicit scrim is useful\n // even without a `defaultBg`: fg always deemphasizes toward neutrality,\n // and cells with explicit (non-null) `cell.bg` still mix toward the\n // scrim. Only cells whose bg is unresolvable (null) AND have no\n // `defaultBg` to fall back on skip the bg mix.\n //\n // Resolve null/default fg BEFORE deemphasize. Without this, default-fg\n // text (common in TUIs that don't set Text color explicitly) skips the\n // fade entirely — bg darkens but fg stays at full terminal brightness,\n // producing a visible \"text POPS against faded bg\" effect that users\n // perceive as \"colors look more saturated when darkened\".\n const fgHex: HexColor =\n rawFgHex ?? defaultFg ?? (scrimTowardLight ? DARK_SCRIM : LIGHT_SCRIM)\n\n // sRGB source-over mix: uniform bg toward scrim at `amount`. sRGB\n // matches the Kitty graphics overlay compositing so text-cell bg and\n // emoji-cell bg land at the same luminance in shared faded regions.\n // `colorToHex(cell.bg) ?? defaultBg` — when cell.bg is null/default\n // and no defaultBg is available, bgHex stays null and we skip the bg\n // mix while still deemphasizing fg.\n const bgHex: HexColor | null = colorToHex(cell.bg) ?? defaultBg\n const mixedBgHex = bgHex !== null ? mixSrgb(bgHex, scrim, amount) : null\n const mixedBg = mixedBgHex !== null ? hexToRgb(mixedBgHex) : null\n\n // Stamp SGR 2 dim on emoji cells when Kitty is NOT available — it's the\n // only portable way to signal \"faded\" on a glyph the fg mix can't\n // affect. For wide TEXT (CJK etc.), do NOT stamp dim: the fg mix works\n // fine, and SGR 2 on CJK over-fades the glyph.\n const stampEmojiDim = isEmojiGlyph\n const newAttrs = stampEmojiDim && !cell.attrs.dim ? { ...cell.attrs, dim: true } : cell.attrs\n\n // Fg uses OKLCH deemphasize — L toward 0 (dark) or 1 (light) per\n // `scrimTowardLight`, C *= (1-α)², H preserved. See\n // `deemphasizeOklchToward` docblock for the perceptual rationale. Bg\n // stays sRGB to match Kitty overlay compositing.\n const deemphasizedFgHex = deemphasizeOklchToward(fgHex, amount, scrimTowardLight)\n const mixedFg = hexToRgb(deemphasizedFgHex)\n\n if (mixedFg) {\n if (mixedBg) {\n buffer.setCell(x, y, { ...cell, fg: mixedFg, bg: mixedBg, attrs: newAttrs })\n propagateToContinuation(buffer, cell, x, y, { bg: mixedBg, dim: stampEmojiDim })\n return true\n }\n buffer.setCell(x, y, { ...cell, fg: mixedFg, attrs: newAttrs })\n if (stampEmojiDim) propagateToContinuation(buffer, cell, x, y, { dim: true })\n return true\n }\n\n // Fg deemphasize failed (rare — hex parse edge). Fall back to bg-only\n // mix + dim stamp.\n if (mixedBg) {\n buffer.setCell(x, y, { ...cell, bg: mixedBg, attrs: newAttrs })\n propagateToContinuation(buffer, cell, x, y, { bg: mixedBg, dim: stampEmojiDim })\n return true\n }\n if (cell.attrs.dim) return false\n buffer.setCell(x, y, { ...cell, attrs: { ...cell.attrs, dim: true } })\n return true\n }\n\n const fgHex = rawFgHex\n\n // Legacy path (no scrim): mix fg toward cell.bg.\n const bgHex = colorToHex(cell.bg)\n\n if (fgHex && bgHex) {\n const mixedHex = mixSrgb(fgHex, bgHex, amount)\n const mixedRgb = hexToRgb(mixedHex)\n if (!mixedRgb) return false\n // Emoji glyphs ignore fg color — the mix has no visible effect on the\n // glyph. Stamp attrs.dim on lead + continuation so terminals honoring\n // SGR 2 on emoji still fade the glyph. CJK and other wide TEXT keep\n // getting the fg mix only; SGR 2 on CJK over-fades.\n if (isEmojiGlyph) {\n const newAttrs = cell.attrs.dim ? cell.attrs : { ...cell.attrs, dim: true }\n buffer.setCell(x, y, { ...cell, fg: mixedRgb, attrs: newAttrs })\n propagateToContinuation(buffer, cell, x, y, { dim: true })\n return true\n }\n buffer.setCell(x, y, { ...cell, fg: mixedRgb })\n return true\n }\n\n // Fallback — bg unresolvable (DEFAULT_BG / null) or fg null. Stamp dim so\n // the cell still reads as \"backdrop\".\n if (cell.attrs.dim) return false\n buffer.setCell(x, y, { ...cell, attrs: { ...cell.attrs, dim: true } })\n if (cell.wide && x + 1 < buffer.width) {\n const cont = buffer.getCell(x + 1, y)\n if (!cont.attrs.dim) {\n buffer.setCell(x + 1, y, { ...cont, attrs: { ...cont.attrs, dim: true } })\n }\n }\n return true\n}\n\n/**\n * Propagate lead-cell updates to the continuation cell of a wide char.\n *\n * When a wide char (emoji, CJK) has its bg or dim attribute changed on the\n * lead cell, the continuation cell at `x+1` must track in lockstep or the\n * two halves of the glyph render inconsistently (different bg → visually\n * split glyph; missing dim → half-faded emoji).\n *\n * `patch.bg` copies the mixed bg onto the continuation. `patch.dim` stamps\n * `attrs.dim`. Either or both may be provided; the function is a no-op\n * when neither is set.\n */\nfunction propagateToContinuation(\n buffer: TerminalBuffer,\n leadCell: { wide: boolean },\n x: number,\n y: number,\n patch: { bg?: { r: number; g: number; b: number }; dim?: boolean },\n): void {\n if (!leadCell.wide) return\n if (x + 1 >= buffer.width) return\n const cont = buffer.getCell(x + 1, y)\n if (!cont.continuation) return\n\n const stampDim = patch.dim === true && !cont.attrs.dim\n const writeBg = patch.bg !== undefined\n\n // Nothing to do: skip the setCell allocation.\n if (!stampDim && !writeBg) return\n\n const attrs = stampDim ? { ...cont.attrs, dim: true } : cont.attrs\n if (writeBg) {\n buffer.setCell(x + 1, y, { ...cont, bg: patch.bg, attrs })\n } else {\n buffer.setCell(x + 1, y, { ...cont, attrs })\n }\n}\n","/**\n * Backdrop fade — stage 2b: emit the Kitty graphics overlay for the plan.\n *\n * The output always begins with `CURSOR_SAVE + kittyDeleteAllScrimPlacements()\n * + CURSOR_RESTORE` when `plan.active` is true — even when zero emoji cells\n * fall inside the faded region this frame. The unconditional clear is what\n * erases stale placements from a previous frame (e.g., a modal that covered\n * an emoji in frame N, then moved in frame N+1). Without it, orphan scrim\n * rectangles persist on screen.\n *\n * ### STRICT determinism invariant\n *\n * For a given `(plan, buffer)` pair, this function produces a byte-identical\n * string on every invocation. STRICT mode compares the overlay across\n * fresh and incremental paths to catch latent non-determinism in marker\n * collection order, emoji walk ordering, or placement ID derivation. The\n * shared `forEachFadeRegionCell` walker provides the stable row-ascending,\n * col-ascending iteration order.\n *\n * @see ./plan.ts for the Plan shape and color model.\n * @see ./realize-buffer.ts for the complementary cell-level transform.\n * @see ./region.ts for the shared include/exclude walker.\n */\n\nimport {\n backdropPlacementId,\n buildScrimPixels,\n cupTo,\n CURSOR_RESTORE,\n CURSOR_SAVE,\n kittyDeleteAllScrimPlacements,\n kittyPlaceAt,\n kittyUploadScrimImage,\n} from \"@silvery/ansi\"\nimport type { TerminalBuffer } from \"../../buffer\"\nimport { isLikelyEmoji } from \"../../unicode\"\nimport { hexToRgb } from \"./color\"\nimport type { Plan } from \"./plan\"\nimport { forEachFadeRegionCell } from \"./region\"\n\n/**\n * Stage 2b — emit the Kitty graphics overlay for the plan.\n *\n * The output always begins with `CURSOR_SAVE + kittyDeleteAllScrimPlacements()\n * + CURSOR_RESTORE` when the plan is realized — even when zero emoji cells\n * fall inside the faded region this frame. The unconditional clear is what\n * erases stale placements from a previous frame.\n *\n * Returns `\"\"` when the plan can't be realized as a Kitty overlay. Public\n * API safety — callers (tests, future consumers) should be able to invoke\n * this directly without re-deriving the `kittyEnabled` / `scrim` /\n * `amount > 0` gate. The orchestrator (`./index.ts`) also gates with\n * `plan.kittyEnabled` for clarity, but the early-out below is the\n * authoritative contract.\n */\nexport function realizeToKitty(plan: Plan, buffer: TerminalBuffer): string {\n if (!plan.active || !plan.kittyEnabled || plan.amount <= 0 || plan.scrim === null) return \"\"\n\n const cells = collectEmojiCellsInFadeRegion(buffer, plan)\n\n // Tint the scrim with the same color used for cell mixing (pure black /\n // white by theme luminance, or an app-supplied custom scrim). Fallback\n // to pure black.\n const tintHex = plan.scrim ?? plan.defaultBg ?? \"#000000\"\n const tint = hexToRgb(tintHex) ?? { r: 0, g: 0, b: 0 }\n const scrimAlpha = Math.max(0, Math.min(255, Math.round(plan.amount * 255)))\n\n const parts: string[] = []\n parts.push(CURSOR_SAVE)\n\n if (cells.length === 0) {\n // No wide cells to cover this frame, but we must still clear any\n // placements left over from a prior frame where there were some.\n parts.push(kittyDeleteAllScrimPlacements())\n parts.push(CURSOR_RESTORE)\n return parts.join(\"\")\n }\n\n const pixels = buildScrimPixels(tint, scrimAlpha)\n parts.push(kittyUploadScrimImage(pixels, 2, 2))\n parts.push(kittyDeleteAllScrimPlacements())\n\n for (const { x, y } of cells) {\n parts.push(cupTo(x, y))\n parts.push(\n kittyPlaceAt({\n placementId: backdropPlacementId(x, y),\n cols: 2,\n rows: 1,\n z: 1,\n }),\n )\n }\n parts.push(CURSOR_RESTORE)\n return parts.join(\"\")\n}\n\n/**\n * Collect the coordinates of every EMOJI lead cell inside the plan's faded\n * region. CJK and other wide TEXT cells are excluded — they respond to fg\n * color mixing like normal text and don't need the Kitty overlay. Only\n * bitmap-glyph cells (detected via `isLikelyEmoji(cell.char)`) need an\n * overlay because their rendering ignores the fg color.\n *\n * The iteration order is deterministic (delegated to\n * `forEachFadeRegionCell`), matching the buffer realizer's order. STRICT\n * mode compares the overlay string across fresh and incremental paths —\n * any drift in this order would fail the comparison.\n */\nfunction collectEmojiCellsInFadeRegion(\n buffer: TerminalBuffer,\n plan: Plan,\n): Array<{ x: number; y: number }> {\n const out: Array<{ x: number; y: number }> = []\n forEachFadeRegionCell(buffer.width, buffer.height, plan.includes, plan.excludes, (x, y) => {\n if (x + 1 >= buffer.width) return // no room for continuation\n if (!buffer.isCellWide(x, y)) return\n if (buffer.isCellContinuation(x, y)) return\n const cell = buffer.getCell(x, y)\n if (!isLikelyEmoji(cell.char ?? \"\")) return\n out.push({ x, y })\n })\n return out\n}\n","/**\n * Backdrop fade pass — mask → realize two-stage model.\n *\n * Runs AFTER the content + decoration phases, BEFORE the output phase. The\n * pipeline orchestrator (`ag.ts`) invokes `applyBackdrop(root, buffer,\n * options)`, which performs two independent stages:\n *\n * 1. `buildPlan(root, options)` — PURE, capability-independent tree\n * walk. Collects `data-backdrop-fade` / `data-backdrop-fade-excluded`\n * markers, enforces the single-amount invariant, resolves the scrim +\n * default colors, and derives `plan.kittyEnabled` from\n * `options.kittyGraphics` + scrim availability. See `./plan.ts`.\n * 2a. `realizeToBuffer(plan, buffer)` — cell-level transform over the\n * plan's include/exclude rects. Mutates the buffer in place. Reads\n * `plan.kittyEnabled` to decide the emoji branch. See\n * `./realize-buffer.ts`.\n * 2b. `realizeToKitty(plan, buffer)` — emits the Kitty graphics escape\n * sequence for emoji cells in the faded region. See\n * `./realize-kitty.ts`.\n *\n * The split exists so each stage is independently testable and so\n * STRICT-mode diagnostics can compare plans + overlays across\n * fresh/incremental paths without re-walking the buffer.\n *\n * ## Incremental correctness\n *\n * The pass mutates the final buffer in place after the decoration phase. The\n * PRE-transform buffer is snapshotted and stored as `_prevBuffer` (see\n * `ag.ts`), so the next frame's incremental render clones pre-fade pixels\n * and re-fades them freshly. Because `buildPlan` is pure and the realizers\n * trust the plan, fresh and incremental paths produce identical\n * post-transform buffers — `SILVERY_STRICT=1` stays green.\n *\n * **STRICT overlay invariant**: `realizeToKitty` is a pure function of\n * `(plan, buffer)`. When the same tree is rendered via the fresh path and\n * the incremental path within a single frame, both produce byte-identical\n * Kitty overlay strings. STRICT mode compares these overlays alongside the\n * buffer (see `scheduler.ts`) — any drift signals a latent determinism bug\n * in marker collection or the emoji walk.\n *\n * ## Emoji vs wide-text cells\n *\n * Wide ≠ emoji. CJK / Hangul / Japanese fullwidth text occupies two columns\n * but responds to `fg` color normally — it goes through the standard mix\n * path. Only EMOJI (bitmap glyphs that ignore `fg`) need special handling,\n * detected via `isLikelyEmoji(cell.char)`. See `./realize-buffer.ts` for\n * the full text-vs-emoji decision table.\n *\n * ## Module layout\n *\n * ./plan.ts — stage 1: buildPlan, Plan / PlanRect shapes,\n * marker collection, single-amount invariant\n * ./realize-buffer.ts — stage 2a: cell-level buffer transform\n * ./realize-kitty.ts — stage 2b: Kitty overlay emission\n * ./region.ts — shared include/exclude region walker (Uint8Array\n * dedup, deterministic iteration order)\n * ./color.ts — hex↔rgb adapter, normalizeHex, HexColor brand type\n * ./color-compat.ts — upstream-with-fallback shim for mixSrgb /\n * deemphasizeOklch[Toward]; prefers @silvery/color\n * exports, falls back to local copies during\n * publish-cycle lag\n * ./index.ts — this file: applyBackdrop orchestrator + barrel\n */\n\nimport type { AgNode } from \"@silvery/ag/types\"\nimport type { TerminalBuffer } from \"../../buffer\"\nimport { buildPlan, type BackdropOptions } from \"./plan\"\nimport { realizeToBuffer } from \"./realize-buffer\"\nimport { realizeToKitty } from \"./realize-kitty\"\n\n// Public re-exports — callers import from `./pipeline/backdrop` (the barrel\n// below) or from `./pipeline` (which re-re-exports).\nexport {\n buildPlan,\n DEFAULT_AMOUNT,\n hasBackdropMarkers,\n INACTIVE_PLAN,\n type BackdropOptions,\n type ColorLevel,\n type Plan,\n type PlanRect,\n} from \"./plan\"\nexport { type HexColor, normalizeHex } from \"./color\"\nexport { deemphasizeOklch, deemphasizeOklchToward, mixSrgb } from \"./color-compat\"\nexport { forEachFadeRegionCell } from \"./region\"\nexport { realizeToBuffer } from \"./realize-buffer\"\nexport { realizeToKitty } from \"./realize-kitty\"\n\n/**\n * Result of `applyBackdrop`.\n *\n * - `modified` — true when at least one buffer cell was mutated by the\n * pass. STRICT mode compares buffers — this is the narrow \"did we\n * mutate the buffer\" signal.\n * - `overlay` — out-of-band ANSI escapes appended after the normal output\n * phase diff. Non-empty whenever Kitty graphics are enabled AND a\n * backdrop is active (includes a delete-all-placements command so\n * last-frame scrims get cleared even if this frame has no wide cells),\n * or when Kitty is enabled and the backdrop is INACTIVE this frame (the\n * orchestrator emits `CURSOR_SAVE + kittyDeleteAllScrimPlacements() +\n * CURSOR_RESTORE` so stale placements from a prior active frame are\n * cleaned up even when stage 2a and 2b short-circuit).\n *\n * Callers gating on \"did anything change visually\" compute\n * `modified || overlay.length > 0` at the call site — no pre-computed\n * derived field lives on the result.\n */\nexport interface BackdropResult {\n /** True when at least one buffer cell was mutated by the pass. */\n readonly modified: boolean\n /**\n * Out-of-band ANSI escapes appended after the normal output phase diff.\n * See the interface docblock for when this is non-empty.\n */\n readonly overlay: string\n}\n\nconst EMPTY_RESULT: BackdropResult = Object.freeze({\n modified: false,\n overlay: \"\",\n})\n\n/**\n * Apply backdrop-fade to the buffer based on tree markers.\n *\n * Thin orchestrator over the mask → realize stages:\n *\n * plan = buildPlan(root, options)\n * modified = realizeToBuffer(plan, buffer)\n * overlay = plan.kittyEnabled ? realizeToKitty(plan, buffer) : \"\"\n *\n * Returns a `BackdropResult`:\n * - `modified` — any buffer cells changed.\n * - `overlay` — out-of-band ANSI escapes. Non-empty only when the plan is\n * active AND Kitty graphics are enabled. An active overlay always begins\n * with a delete-all command so last-frame placements get erased even if\n * this frame has no wide cells.\n *\n * **Inactive frames are silent.** When `plan.active` is false this returns\n * `EMPTY_RESULT` regardless of `options.kittyGraphics`. Stale scrim\n * placements from a prior active frame must be cleaned up at the\n * deactivation EDGE by the caller (e.g., `ag.ts` tracks `_kittyActive`\n * across frames and emits a one-shot delete-all when active→inactive).\n * Emitting the delete-all every inactive frame here would spam the\n * terminal — Modal's default `fade={0}` would push a cleanup string every\n * frame indefinitely.\n */\nexport function applyBackdrop(\n root: AgNode,\n buffer: TerminalBuffer,\n options?: BackdropOptions,\n): BackdropResult {\n const plan = buildPlan(root, options)\n\n // Stage 1 diagnostics: surface the mixed-amounts warning at the orchestrator\n // so `buildPlan` can remain a pure function of its inputs. The warning\n // fires in dev/test only — production suppresses via NODE_ENV.\n if (plan.mixedAmounts && process.env.NODE_ENV !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(\n `[silvery:backdrop] multiple fade amounts in one frame (using ${plan.amount}); ` +\n `Kitty overlay will use the first observed amount. See plan.ts / assertSingleAmount.`,\n )\n }\n\n if (!plan.active) return EMPTY_RESULT\n\n const modified = realizeToBuffer(plan, buffer)\n\n // Kitty overlay. Always emitted when plan.kittyEnabled (even if no emoji\n // this frame) so last-frame placements get cleared by the delete-all at\n // the head of the overlay string.\n const overlay = plan.kittyEnabled ? realizeToKitty(plan, buffer) : \"\"\n\n return { modified, overlay }\n}\n","/**\n * Ag — tree + layout engine + renderer.\n *\n * The sole pipeline entry point. Two independent phases:\n * - ag.layout(dims) — measure + flexbox → positions/sizes\n * - ag.render() — positioned tree → cell grid → TextFrame\n *\n * The output phase (buffer → ANSI) is NOT part of ag — it lives in term.paint().\n *\n * @example\n * ```ts\n * const ag = createAg(root, { measurer })\n * ag.layout({ cols: 80, rows: 24 })\n * const { frame, buffer } = ag.render()\n * const output = term.paint(buffer, prevBuffer)\n * ```\n */\n\nimport { createLogger } from \"loggily\"\nimport type { AgNode, AgNodeType } from \"@silvery/ag/types\"\nimport {\n getRenderEpoch,\n INITIAL_EPOCH,\n ALL_RECONCILER_BITS,\n CONTENT_BIT,\n STYLE_PROPS_BIT,\n} from \"@silvery/ag/epoch\"\nimport { getLayoutEngine } from \"./layout-engine\"\nimport type { TextFrame } from \"@silvery/ag/text-frame\"\nimport { type TerminalBuffer, createTextFrame } from \"./buffer\"\nimport { runWithMeasurer, type Measurer } from \"./unicode\"\nimport { measurePhase } from \"./pipeline/measure-phase\"\nimport {\n layoutPhase,\n scrollPhase,\n stickyPhase,\n scrollrectPhase,\n scrollrectPhaseSimple,\n notifyLayoutSubscribers,\n detectPipelineFeatures,\n strictLayoutOverflowCheck,\n} from \"./pipeline/layout-phase\"\nimport { renderPhase, clearBgConflictWarnings } from \"./pipeline/render-phase\"\nimport {\n applyBackdrop,\n hasBackdropMarkers,\n type ColorLevel as BackdropColorLevel,\n} from \"./pipeline/backdrop\"\nimport { CURSOR_RESTORE, CURSOR_SAVE, kittyDeleteAllScrimPlacements } from \"@silvery/ansi\"\nimport { clearDirtyTracking, hasScrollDirty } from \"@silvery/ag/dirty-tracking\"\nimport type { PipelineContext } from \"./pipeline/types\"\n\nconst log = createLogger(\"silvery:render\")\nconst baseLog = createLogger(\"@silvery/ag-react\")\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface AgLayoutOptions {\n skipLayoutNotifications?: boolean\n skipScrollStateUpdates?: boolean\n}\n\nexport interface AgRenderOptions {\n /** Force fresh render — no incremental, doesn't update internal prevBuffer. */\n fresh?: boolean\n /** Override prevBuffer for this render (bypasses internal tracking). */\n prevBuffer?: TerminalBuffer | null\n}\n\nexport interface CreateAgOptionsInternal {\n /** Width measurer scoped to terminal capabilities. */\n measurer?: Measurer\n /**\n * Terminal color tier for the backdrop-fade pass (see `pipeline/backdrop/`).\n * Defaults to `\"truecolor\"` (OKLab blend). Set to `\"basic\"` at ANSI 16 tier\n * (SGR 2 dim) or `\"none\"` to disable the pass entirely.\n */\n colorLevel?: BackdropColorLevel\n /**\n * When true, the backdrop-fade pass emits Kitty graphics placements over\n * emoji / wide-char cells in the faded region so those glyphs visually\n * fade alongside surrounding text. Required because SGR 2 \"dim\" is a\n * no-op on bitmap emoji in most terminals (Ghostty confirmed).\n *\n * When undefined, `ag.ts` falls back to an env heuristic (Kitty/Ghostty/\n * WezTerm, not inside tmux, `SILVERY_KITTY_GRAPHICS` env not \"0\"). Pass\n * `false` to force-disable (tests, fallback terminals).\n */\n kittyGraphics?: boolean\n}\n\nexport interface AgRenderResult {\n /** Immutable TextFrame snapshot of the rendered output. */\n readonly frame: TextFrame\n /**\n * Post-transform buffer for painting. Includes backdrop-fade cell transforms\n * (if any). Pass this to `term.paint()` / `outputPhase()` as `next`.\n */\n readonly buffer: TerminalBuffer\n /**\n * Pre-transform buffer. Identical to `buffer` when no backdrop-fade markers\n * are present. Callers managing their own incremental prev-buffer state must\n * carry THIS (not `buffer`) forward, so the next frame's render phase starts\n * from pre-fade cells and the fade pass re-applies deterministically.\n */\n readonly carryForwardBuffer: TerminalBuffer\n /** Previous frame's buffer (null on first render). For output-phase diffing. */\n readonly prevBuffer: TerminalBuffer | null\n /**\n * Out-of-band ANSI escapes that must be appended to the output stream after\n * the normal output phase diff. Currently carries Kitty graphics placements\n * emitted by the backdrop-fade pass to scrim emoji / wide-char cells. Empty\n * string when no overlays are active (backdrop inactive, kittyGraphics cap\n * disabled, or no wide cells in the faded region).\n */\n readonly overlay: string\n}\n\nexport interface Ag {\n /** The root AgNode tree. */\n readonly root: AgNode\n\n // -------------------------------------------------------------------------\n // Pipeline\n // -------------------------------------------------------------------------\n\n /**\n * Run layout phases: measure → flexbox → scroll → sticky → scrollRect → notify.\n * Mutates layout nodes in place.\n */\n layout(dims: { cols: number; rows: number }, options?: AgLayoutOptions): void\n\n /**\n * Run the render phase: positioned tree → cell grid → TextFrame.\n * Uses internal prevBuffer for incremental rendering.\n * Returns frame (public read API) + buffer/prevBuffer (for output phase).\n */\n render(options?: AgRenderOptions): AgRenderResult\n\n /** Reset internal prevBuffer (call on resize — forces fresh render next frame). */\n resetBuffer(): void\n\n // -------------------------------------------------------------------------\n // Tree Mutation API (Phase 4)\n // -------------------------------------------------------------------------\n\n /** Create a new AgNode with a layout node. */\n createNode(type: AgNodeType, props: Record<string, unknown>): AgNode\n\n /** Insert child at index in both ag tree and layout tree. */\n insertChild(parent: AgNode, child: AgNode, index: number): void\n\n /** Remove child from both ag tree and layout tree. */\n removeChild(parent: AgNode, child: AgNode): void\n\n /** Update node props (applies to layout node if layout-affecting). */\n updateProps(\n node: AgNode,\n props: Record<string, unknown>,\n oldProps?: Record<string, unknown>,\n ): void\n\n /** Update text content on a node. */\n setText(node: AgNode, text: string): void\n\n /** Structural text representation (no layout). */\n toString(): string\n}\n\nexport interface CreateAgOptions {\n /** Width measurer scoped to terminal capabilities. */\n measurer?: Measurer\n /**\n * Terminal color tier for the backdrop-fade pass. Defaults to `\"truecolor\"`.\n * See `pipeline/backdrop/` for tier semantics.\n */\n colorLevel?: BackdropColorLevel\n /**\n * Whether the backdrop-fade pass may emit Kitty graphics placements for\n * emoji scrim. Defaults to an env heuristic (see `isKittyGraphicsEnabled`).\n * Pass `false` to force-disable (tests, explicit opt-out).\n */\n kittyGraphics?: boolean\n}\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/**\n * Walk the ag tree top-down to find the root ThemeProvider's background color.\n *\n * ThemeProvider in @silvery/ag-react renders a `<Box theme={merged}>` wrapper.\n * The render phase pushes/pops this theme via pushContextTheme/popContextTheme,\n * so the module-level theme stack is empty after the render phase completes.\n * We walk the tree directly to recover the root bg without requiring the\n * render phase to be running.\n *\n * Returns the first Box node's Sterling `bg-surface-default` (with legacy `bg`\n * fallback for backdrop-only Themes that pre-date Sterling's flat surface\n * tokens) found in a depth-first walk, or `null` if no theme node is present\n * (bare tests without ThemeProvider).\n */\nfunction findRootThemeBg(root: AgNode): string | null {\n const props = root.props as Record<string, unknown>\n if (props.theme) {\n const theme = props.theme as Record<string, unknown>\n const sterlingBg = theme[\"bg-surface-default\"]\n if (typeof sterlingBg === \"string\") return sterlingBg\n const legacyBg = theme[\"bg\"]\n if (typeof legacyBg === \"string\") return legacyBg\n }\n for (const child of root.children) {\n const found = findRootThemeBg(child)\n if (found !== null) return found\n }\n return null\n}\n\n/**\n * Env heuristic: should the backdrop-fade pass emit Kitty graphics overlays?\n *\n * This is the MVP gate — a lightweight capability detector used when the\n * caller doesn't pass `kittyGraphics` explicitly. Matches the Option C design\n * intent: emit only on modern terminals where Kitty graphics are known to\n * work (Kitty, Ghostty, WezTerm), NOT inside tmux (DCS passthrough is\n * unreliable), with an explicit `SILVERY_KITTY_GRAPHICS` override.\n *\n * - `SILVERY_KITTY_GRAPHICS=0` → always off\n * - `SILVERY_KITTY_GRAPHICS=1` → always on (bypasses tmux + term checks)\n * - `TMUX` env var present → off (unless forced on above)\n * - `TERM_PROGRAM` in {Ghostty, WezTerm} → on\n * - `TERM` contains \"kitty\" → on\n * - `KITTY_WINDOW_ID` set → on\n * - otherwise → off\n *\n * The long-term plan is to promote this to a `TerminalCaps.kittyGraphics`\n * consumer. That field exists (see `@silvery/ansi` detectTerminalCaps) but\n * isn't threaded into the render pipeline yet — tracked as a follow-up.\n */\nfunction isKittyGraphicsEnabledFromEnv(): boolean {\n const env =\n typeof process !== \"undefined\" ? process.env : ({} as Record<string, string | undefined>)\n\n const override = env.SILVERY_KITTY_GRAPHICS\n if (override === \"0\" || override === \"false\") return false\n if (override === \"1\" || override === \"true\") return true\n\n // tmux's DCS passthrough for Kitty graphics is flaky — off by default.\n // User can override via SILVERY_KITTY_GRAPHICS=1 if their tmux config\n // (allow-passthrough + extended keys) actually works.\n if (env.TMUX) return false\n\n const program = env.TERM_PROGRAM ?? \"\"\n if (program === \"ghostty\" || program === \"Ghostty\" || program === \"WezTerm\") return true\n\n const term = env.TERM ?? \"\"\n if (term.includes(\"kitty\")) return true\n\n if (env.KITTY_WINDOW_ID) return true\n\n return false\n}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\nexport function createAg(root: AgNode, options?: CreateAgOptions): Ag {\n const measurer = options?.measurer\n const colorLevel: BackdropColorLevel = options?.colorLevel ?? \"truecolor\"\n // Kitty graphics: explicit option wins. Otherwise fall back to env heuristic\n // so the default behavior matches the terminal running the app without\n // callers needing to thread TerminalCaps through every site. Tests that\n // want to pin determinism pass `kittyGraphics: false`.\n const kittyGraphics =\n options?.kittyGraphics !== undefined ? options.kittyGraphics : isKittyGraphicsEnabledFromEnv()\n const ctx: PipelineContext | undefined = measurer ? { measurer } : undefined\n let _prevBuffer: TerminalBuffer | null = null\n // True when the PREVIOUS frame had backdrop markers (and so emitted Kitty\n // placements). Drives the one-shot delete-all on the first frame where the\n // backdrop goes away so leftover scrim rectangles don't linger on screen.\n // Scoped per-Ag; non-persistent-Ag callers (test driver renderer.ts)\n // additionally track at their own level — see that file.\n let _kittyActive = false\n\n // Feature flags — one-way: once true, stays true for the lifetime of this Ag.\n // This ensures dynamically mounted scroll/sticky components enable their phases\n // and never get skipped again.\n let hasScroll = false\n let hasSticky = false\n\n function doLayout(\n cols: number,\n rows: number,\n opts?: AgLayoutOptions,\n ): { tMeasure: number; tLayout: number; tScroll: number; tScrollRect: number; tNotify: number } {\n // Layout-on-demand gate: skip ALL layout phases when Flexily reports\n // no dirty nodes, no scroll offset changed, and dimensions haven't changed.\n // This eliminates ~38% of per-frame pipeline cost for cursor/style-only changes.\n // First render always has isDirty (Flexily nodes start dirty on creation).\n // scrollTo/scrollOffset changes don't affect Flexily (they don't change\n // dimensions) but DO need scroll/sticky/scrollRect/notify phases to run.\n const prevRootLayout = root.boxRect\n const dimensionsChanged =\n prevRootLayout && (prevRootLayout.width !== cols || prevRootLayout.height !== rows)\n if (!dimensionsChanged && !root.layoutNode?.isDirty() && !hasScrollDirty()) {\n log.debug?.(\"layout: skipped (Flexily clean, no scrollDirty, dimensions unchanged)\")\n // Even when the full layout phase is skipped, style-only changes\n // (outline add/remove, absolute child structural changes) need cascade\n // input bits computed for the render phase. Without this, the render\n // phase can't detect outline mutations and stale outline pixels persist.\n layoutPhase(root, cols, rows)\n return { tMeasure: 0, tLayout: 0, tScroll: 0, tScrollRect: 0, tNotify: 0 }\n }\n\n using render = baseLog.span(\"pipeline\", { width: cols, height: rows })\n\n let tMeasure: number\n {\n using _m = render.span(\"measure\")\n const t = performance.now()\n measurePhase(root, ctx)\n tMeasure = performance.now() - t\n log.debug?.(`measure: ${tMeasure.toFixed(2)}ms`)\n }\n\n let tLayout: number\n {\n using _l = render.span(\"layout\")\n const t = performance.now()\n layoutPhase(root, cols, rows)\n tLayout = performance.now() - t\n log.debug?.(`layout: ${tLayout.toFixed(2)}ms`)\n }\n\n // STRICT invariant: verify no child overflows its parent's inner width.\n // Catches fit-content/snug-content/measure-phase bugs at the source.\n strictLayoutOverflowCheck(root)\n\n // Detect features for phase skipping. One-way merge: false → true only.\n // This scan runs every layout pass to catch newly mounted components.\n if (!hasScroll || !hasSticky) {\n const features = detectPipelineFeatures(root)\n if (features.hasScroll) hasScroll = true\n if (features.hasSticky) hasSticky = true\n }\n\n let tScroll: number\n if (hasScroll) {\n using _s = render.span(\"scroll\")\n const t = performance.now()\n scrollPhase(root, { skipStateUpdates: opts?.skipScrollStateUpdates })\n tScroll = performance.now() - t\n } else {\n tScroll = 0\n }\n\n if (hasSticky) {\n stickyPhase(root)\n }\n\n let tScrollRect: number\n {\n using _r = render.span(\"scrollRect\")\n const t = performance.now()\n if (hasScroll || hasSticky) {\n scrollrectPhase(root)\n } else {\n // Fast path: no scroll offsets or sticky positions to account for.\n // scrollRect === boxRect, screenRect === scrollRect.\n scrollrectPhaseSimple(root)\n }\n tScrollRect = performance.now() - t\n }\n\n let tNotify = 0\n if (!opts?.skipLayoutNotifications) {\n using _n = render.span(\"notify\")\n const t = performance.now()\n notifyLayoutSubscribers(root)\n tNotify = performance.now() - t\n }\n\n // Bench instrumentation: accumulate per-phase timings in a global counter\n // that a harness can read + reset between iterations. Cheap: five `+=` ops.\n // See __silvery_bench_accumulate / __silvery_bench_reset helpers below.\n const acc = (globalThis as any).__silvery_bench_phases\n if (acc) {\n acc.measure += tMeasure\n acc.layout += tLayout\n acc.scroll += tScroll\n acc.scrollRect += tScrollRect\n acc.notify += tNotify\n acc.layoutTotal += tMeasure + tLayout + tScroll + tScrollRect + tNotify\n }\n\n return { tMeasure, tLayout, tScroll, tScrollRect, tNotify }\n }\n\n function doRender(opts?: AgRenderOptions): AgRenderResult & { tContent: number } {\n clearBgConflictWarnings()\n const prevBuffer = opts?.fresh\n ? null\n : opts?.prevBuffer !== undefined\n ? opts.prevBuffer\n : _prevBuffer\n\n let tContent: number\n let buffer: TerminalBuffer\n {\n const t = performance.now()\n buffer = renderPhase(root, prevBuffer, ctx)\n tContent = performance.now() - t\n log.debug?.(`content: ${tContent.toFixed(2)}ms`)\n }\n\n // Backdrop-fade pass — runs after content + decoration, before output.\n //\n // Incremental invariant: fast-path cells carry the PREVIOUS frame's\n // pixels into the clone inside renderPhase. If those pixels are\n // POST-fade, the fade pass re-fades already-faded cells and the result\n // compounds across frames (STRICT: incremental post-fade diverges from\n // fresh post-fade after 2+ frames).\n //\n // Solution: snapshot the PRE-transform buffer BEFORE applying fade.\n // Store it as `_prevBuffer` (for internal ag state) AND return it as\n // `carryForwardBuffer` so external callers managing their own prev\n // state (renderer.ts) can track pre-fade. The post-fade `buffer` is\n // what gets painted; pre-fade is what gets cloned for incremental.\n let carryForwardBuffer: TerminalBuffer\n let overlay = \"\"\n const backdropActive = hasBackdropMarkers(root)\n if (backdropActive) {\n carryForwardBuffer = buffer.clone()\n if (!opts?.fresh) {\n _prevBuffer = carryForwardBuffer\n }\n const defaultBg = findRootThemeBg(root) ?? undefined\n const result = applyBackdrop(root, buffer, {\n colorLevel,\n defaultBg,\n kittyGraphics,\n })\n overlay = result.overlay\n } else {\n carryForwardBuffer = buffer\n if (!opts?.fresh) {\n _prevBuffer = buffer\n }\n }\n // Kitty scrim deactivation — edge-triggered. When the previous frame\n // painted Kitty placements but this frame did not, emit a one-shot\n // delete-all so leftover placements don't linger on screen. Handles\n // BOTH cases:\n // (a) markers removed entirely (backdropActive=false), and\n // (b) markers still present but plan became inactive (e.g., fade={0}),\n // where applyBackdrop intentionally returns an empty overlay.\n // This MUST be edge-triggered: emitting the delete-all every inactive\n // frame would spam the terminal indefinitely once a Modal mounts at\n // fade={0}.\n const kittyActiveThisFrame = backdropActive && overlay.length > 0\n if (_kittyActive && !kittyActiveThisFrame) {\n overlay = CURSOR_SAVE + kittyDeleteAllScrimPlacements() + CURSOR_RESTORE\n }\n _kittyActive = kittyActiveThisFrame\n\n // Clear the module-level dirty tracking after each render pass.\n // Content dirty nodes were processed by renderPhase; layout dirty is\n // managed by Flexily internally (isDirty cleared after calculateLayout).\n clearDirtyTracking()\n\n // Bench instrumentation: accumulate content-phase timing.\n const acc = (globalThis as any).__silvery_bench_phases\n if (acc) {\n acc.content += tContent\n acc.renderCalls += 1\n }\n\n const frame = createTextFrame(buffer)\n return { frame, buffer, carryForwardBuffer, prevBuffer, tContent, overlay }\n }\n\n // -------------------------------------------------------------------------\n // Tree Mutation\n // -------------------------------------------------------------------------\n\n function agCreateNode(type: AgNodeType, props: Record<string, unknown>): AgNode {\n const engine = getLayoutEngine()\n const layoutNode = engine.createNode()\n return {\n type,\n props,\n children: [],\n parent: null,\n layoutNode,\n boxRect: null,\n scrollRect: null,\n screenRect: null,\n prevLayout: null,\n prevScrollRect: null,\n prevScreenRect: null,\n layoutChangedThisFrame: INITIAL_EPOCH,\n dirtyBits: ALL_RECONCILER_BITS,\n dirtyEpoch: getRenderEpoch(),\n }\n }\n\n function agInsertChild(parent: AgNode, child: AgNode, index: number): void {\n // Remove from old parent if already in a tree (keyed reorder)\n if (child.parent) {\n agRemoveChild(child.parent, child)\n }\n\n // Insert into children array\n parent.children.splice(index, 0, child)\n child.parent = parent\n\n // Sync layout tree\n if (parent.layoutNode && child.layoutNode) {\n // Layout index = count of children with layoutNode before this position\n const layoutIndex = parent.children\n .slice(0, index)\n .filter((c) => c.layoutNode !== null).length\n parent.layoutNode.insertChild(child.layoutNode, layoutIndex)\n }\n }\n\n function agRemoveChild(parent: AgNode, child: AgNode): void {\n const index = parent.children.indexOf(child)\n if (index === -1) return\n\n parent.children.splice(index, 1)\n\n if (parent.layoutNode && child.layoutNode) {\n parent.layoutNode.removeChild(child.layoutNode)\n child.layoutNode.free()\n }\n\n child.parent = null\n }\n\n return {\n root,\n\n // Pipeline\n layout(dims, options) {\n if (measurer) {\n runWithMeasurer(measurer, () => doLayout(dims.cols, dims.rows, options))\n } else {\n doLayout(dims.cols, dims.rows, options)\n }\n },\n\n render(options) {\n const result = measurer\n ? runWithMeasurer(measurer, () => doRender(options))\n : doRender(options)\n return {\n frame: result.frame,\n buffer: result.buffer,\n carryForwardBuffer: result.carryForwardBuffer,\n prevBuffer: result.prevBuffer,\n overlay: result.overlay,\n }\n },\n\n resetBuffer() {\n _prevBuffer = null\n },\n\n // Tree mutations\n createNode: agCreateNode,\n insertChild: agInsertChild,\n removeChild: agRemoveChild,\n\n updateProps(node, props, oldProps) {\n node.props = props\n if (node.layoutNode) {\n node.layoutNode.markDirty()\n }\n },\n\n setText(node, text) {\n ;(node as any).textContent = text\n const epoch = getRenderEpoch()\n const bits = CONTENT_BIT | STYLE_PROPS_BIT\n node.dirtyBits = node.dirtyEpoch !== epoch ? bits : node.dirtyBits | bits\n node.dirtyEpoch = epoch\n if (node.layoutNode) {\n node.layoutNode.markDirty()\n }\n },\n\n toString() {\n return `[Ag root=${root.type} children=${root.children.length}]`\n },\n }\n}\n","/**\n * Separate React reconciler instance for renderStringSync.\n *\n * renderStringSync may be called from within React effects (e.g., useScrollback\n * freezing items to scrollback). If it uses the same reconciler singleton as the\n * main render tree, this causes re-entrancy: the nested reconciliation interferes\n * with the outer one, producing empty output.\n *\n * By using a dedicated reconciler instance, renderStringSync operates on an\n * independent fiber tree with no shared reconciler state.\n */\n\n// @ts-expect-error - react-reconciler has no type declarations\nimport Reconciler from \"react-reconciler\"\nimport { hostConfig } from \"./host-config\"\n\n/**\n * Dedicated reconciler for string rendering.\n *\n * Uses the same host config functions but overrides isPrimaryRenderer to false,\n * since this is a secondary renderer used only for one-shot string rendering.\n * This avoids conflicts with the main reconciler's hook ownership.\n */\nexport const stringReconciler = Reconciler({\n ...hostConfig,\n isPrimaryRenderer: false,\n})\n","/**\n * renderString - Static one-shot rendering to string\n *\n * Renders a React element to a string without needing a terminal.\n * Use for:\n * - CI output (no cursor control needed)\n * - Piped output\n * - One-shot reports/summaries\n * - Testing component output\n *\n * @example\n * ```tsx\n * import { renderString, Box, Text } from '@silvery/ag-react'\n *\n * // Basic usage\n * const output = renderString(<Summary stats={stats} />)\n * console.log(output)\n *\n * // Custom width\n * const wide = renderString(<Report />, { width: 120 })\n *\n * // Plain text (no ANSI)\n * const plain = renderString(<Report />, { plain: true })\n * ```\n */\n\nimport React, { type ReactElement, act } from \"react\"\n\nimport { createTerm } from \"@silvery/ag-term/ansi\"\n\nimport { bufferToStyledText, bufferToText, type TerminalBuffer } from \"@silvery/ag-term/buffer\"\nimport { StdoutContext, StderrContext, TermContext } from \"./context\"\nimport { isLayoutEngineInitialized } from \"@silvery/ag-term/layout-engine\"\nimport type { PipelineConfig } from \"@silvery/ag-term/pipeline\"\nimport { createAg } from \"@silvery/ag-term/ag\"\nimport { runWithMeasurer } from \"@silvery/ag-term/unicode\"\nimport { createContainer, getContainerRoot } from \"./reconciler\"\nimport { stringReconciler } from \"./reconciler/string-reconciler\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Options for renderString().\n */\nexport interface RenderStringOptions {\n /**\n * Width in columns for layout calculations.\n * Default: 80\n */\n width?: number\n\n /**\n * Height in rows for layout calculations.\n * Default: 24\n */\n height?: number\n\n /**\n * Strip ANSI codes for plain text output.\n * Default: false (includes ANSI styling)\n */\n plain?: boolean\n\n /**\n * Pipeline configuration (scoped width measurer + output phase).\n * When provided, the render pipeline uses these for width measurement\n * and output generation instead of the global defaults.\n */\n pipelineConfig?: PipelineConfig\n\n /**\n * Trim trailing whitespace from each line.\n * Default: true (trims trailing spaces)\n *\n * Set to false when rendering to stdout where trailing spaces are\n * semantically significant (e.g., ink compat static renders).\n */\n trimTrailingWhitespace?: boolean\n\n /**\n * Trim trailing empty lines from the output.\n * Default: true (removes trailing empty lines)\n *\n * Set to false when padding/margin at the bottom should be preserved\n * (e.g., ink compat renders where `\\n\\n` padding is significant).\n */\n trimEmptyLines?: boolean\n\n /**\n * Callback to receive the computed content height (root node layout height).\n * Useful for callers that need to know the actual content bounds\n * (e.g., Ink compat layer needs to trim buffer padding but preserve\n * content-area empty lines).\n */\n onContentHeight?: (height: number) => void\n\n /**\n * Always use styled output (bufferToStyledText) even when plain=true.\n * Default: false\n *\n * When true, the output includes ANSI codes from embedded sequences in text\n * content (SGR, OSC hyperlinks) even though the mock term has no color\n * support (plain=true). This is needed for Ink compatibility: Ink passes\n * embedded ANSI sequences through regardless of chalk's color level, but\n * silvery's plain mode would strip them via bufferToText.\n *\n * The `plain` flag still controls whether the mock term has color support,\n * which determines whether Text component style props (color, bold, etc.)\n * produce style attributes in the buffer.\n */\n alwaysStyled?: boolean\n}\n\n// ============================================================================\n// Module State\n// ============================================================================\n\n// Track if we've initialized to avoid redundant imports\nlet engineInitialized = false\n\nasync function ensureLayoutEngine(): Promise<void> {\n if (engineInitialized || isLayoutEngineInitialized()) {\n return\n }\n // Use centralized default engine initialization\n const { ensureDefaultLayoutEngine } = await import(\"@silvery/ag-term/layout-engine\")\n await ensureDefaultLayoutEngine()\n engineInitialized = true\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Render a React element to a string (async version).\n *\n * Automatically initializes the layout engine if needed.\n * Use this when you're not sure if the layout engine is ready.\n *\n * @param element - React element to render\n * @param options - Render options (width, height, plain)\n * @returns Rendered string (with or without ANSI codes)\n *\n * @example\n * ```tsx\n * const output = await renderString(<Summary stats={stats} />)\n * console.log(output)\n * ```\n */\nexport async function renderString(\n element: ReactElement,\n options: RenderStringOptions = {},\n): Promise<string> {\n await ensureLayoutEngine()\n return renderStringSync(element, options)\n}\n\n/**\n * Render a React element to a string (sync version).\n *\n * Requires the layout engine to be already initialized.\n * Throws if the layout engine is not ready.\n *\n * @param element - React element to render\n * @param options - Render options (width, height, plain)\n * @returns Rendered string (with or without ANSI codes)\n *\n * @example\n * ```tsx\n * // After layout engine is initialized\n * const output = renderStringSync(<Summary stats={stats} />)\n * console.log(output)\n * ```\n */\nexport function renderStringSync(element: ReactElement, options: RenderStringOptions = {}): string {\n if (!isLayoutEngineInitialized()) {\n throw new Error(\n \"Layout engine not initialized. Use renderString() (async) or initialize with setLayoutEngine().\",\n )\n }\n\n const {\n width = 80,\n height = 24,\n plain = false,\n pipelineConfig,\n trimTrailingWhitespace = true,\n trimEmptyLines = true,\n onContentHeight,\n alwaysStyled = false,\n } = options\n\n // Track whether React committed new work (from layout notifications etc.)\n let hadReactCommit = false\n const container = createContainer(() => {\n hadReactCommit = true\n })\n\n // Capture uncaught errors from the reconciler so they propagate to the caller\n let uncaughtError: unknown = null\n const onUncaughtError = (error: unknown) => {\n uncaughtError = error\n }\n\n // Create fiber root using the dedicated string reconciler (not the main one)\n const fiberRoot = stringReconciler.createContainer(\n container,\n 1, // ConcurrentRoot\n null, // hydrationCallbacks\n false, // isStrictMode\n null, // concurrentUpdatesByDefaultOverride\n \"\", // identifierPrefix\n onUncaughtError, // onUncaughtError\n () => {}, // onCaughtError\n () => {}, // onRecoverableError\n null, // onDefaultTransitionIndicator\n )\n\n // Create minimal mock stdout for components that use useStdout\n const mockStdout = {\n columns: width,\n rows: height,\n write: () => true,\n isTTY: false,\n on: () => mockStdout,\n off: () => mockStdout,\n once: () => mockStdout,\n removeListener: () => mockStdout,\n addListener: () => mockStdout,\n } as unknown as NodeJS.WriteStream\n\n // Create mock term for components that use useTerm()\n const mockTerm = createTerm({ color: plain ? null : \"truecolor\" })\n\n // Wrap with minimal contexts (no input handling needed)\n const wrapped = React.createElement(\n TermContext.Provider,\n { value: mockTerm },\n React.createElement(\n StdoutContext.Provider,\n {\n value: {\n stdout: mockStdout,\n write: () => {},\n },\n },\n React.createElement(\n StderrContext.Provider,\n {\n value: {\n stderr: process.stderr,\n write: (data: string) => {\n process.stderr.write(data)\n },\n },\n },\n element,\n ),\n ),\n )\n\n // Mount the React tree inside act() so layout feedback works\n withActEnvironment(() => {\n act(() => {\n stringReconciler.updateContainerSync(wrapped, fiberRoot, null, null)\n stringReconciler.flushSyncWork()\n })\n })\n\n // Propagate any uncaught render errors (e.g., text outside <Text> in strict mode)\n if (uncaughtError) {\n throw uncaughtError instanceof Error ? uncaughtError : new Error(String(uncaughtError))\n }\n\n // Layout stabilization loop: run the pipeline, flush React work from\n // layout notifications (useBoxRect forceUpdate etc.), repeat until stable.\n // This matches the test renderer's multi-pass approach.\n let buffer!: TerminalBuffer\n let rootNode: ReturnType<typeof getContainerRoot> | undefined\n const MAX_ITERATIONS = 5\n for (let i = 0; i < MAX_ITERATIONS; i++) {\n hadReactCommit = false\n withActEnvironment(() => {\n act(() => {\n const root = getContainerRoot(container)\n rootNode = root\n const measurer = pipelineConfig?.measurer\n const doRender = () => {\n const ag = createAg(root, { measurer })\n ag.layout({ cols: width, rows: height })\n return ag.render()\n }\n const result = measurer ? runWithMeasurer(measurer, doRender) : doRender()\n buffer = result.buffer\n })\n if (!hadReactCommit) {\n act(() => {\n stringReconciler.flushSyncWork()\n })\n }\n })\n if (!hadReactCommit) break\n }\n\n // Report content height if callback provided.\n // Compute from children's outer bottom edges (including margins) rather than\n // root.boxRect.height which equals the buffer height (root stretches to fill).\n if (onContentHeight && rootNode) {\n let maxBottom = 0\n let hasChildren = false\n for (const child of rootNode.children) {\n if (child.boxRect) {\n hasChildren = true\n const props = child.props as Record<string, unknown>\n const mb =\n (props.marginBottom as number) ??\n (props.marginY as number) ??\n (props.margin as number) ??\n 0\n const childBottom = child.boxRect.y + child.boxRect.height + mb\n if (childBottom > maxBottom) maxBottom = childBottom\n }\n }\n onContentHeight(hasChildren ? maxBottom : 0)\n }\n\n // Unmount (cleanup)\n withActEnvironment(() => {\n act(() => {\n stringReconciler.updateContainerSync(null, fiberRoot, null, null)\n stringReconciler.flushSyncWork()\n })\n })\n\n return plain && !alwaysStyled\n ? bufferToText(buffer, { trimTrailingWhitespace, trimEmptyLines })\n : bufferToStyledText(buffer, { trimTrailingWhitespace, trimEmptyLines })\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Run a function with IS_REACT_ACT_ENVIRONMENT temporarily set to true.\n * This ensures act() captures forceUpdate/setState from layout notifications.\n */\nfunction withActEnvironment(fn: () => void): void {\n const prev = (globalThis as any).IS_REACT_ACT_ENVIRONMENT\n ;(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true\n try {\n fn()\n } finally {\n ;(globalThis as any).IS_REACT_ACT_ENVIRONMENT = prev\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAiFA,MAAA,qBAAA;;AAGA,MAAA,mBAAA;;;;;;;AAQA,MAAA,uBAAA;;AAOA,IAAA,iBAAA,CAAA,CAAA,QAAA,IAAA;AAKA,MAAA,6BAAA,IAAA,SAAA;AAEA,SAAA,YAAA,MAAA;;AAEE,KAAA,CAAA,OAAA;AACE,UAAA;;;;;;;;;AASA,aAAA,IAAA,MAAA,MAAA;;AAEF,QAAA;;;;;;AAWF,SAAA,mBAAA,MAAA;AACE,KAAA,eAAA,QAAA;;AAEA,KAAA,OAAA,aAAA,KAAA,QAAA;AACA,KAAA,QAAA,KAAA,WAAA,KAAA,YAAA,iBAAA,EAAA;AACE,QAAA,YAAA;AACA,SAAA;;AAEF,QAAA;;;;;;AAIF,SAAA,mBAAA,MAAA,MAAA,WAAA;;AAEE,OAAA,YAAA;AACA,OAAA,qBAAA;;;;;;;;;;;;AAiBF,SAAA,uBAAA,MAAA,iBAAA,cAAA;AAKE,KAAA,eAAA,QAAA;;AAEA,KAAA,CAAA,OAAA,UAAA,QAAA;AAEA,KAAA,QAAA,KAAA,WAAA,KAAA,YAAA,qBAAA,EAAA;AACE,QAAA,YAAA;AACA,QAAA,UAAA,EAAA;AACA,QAAA,WAAA;AACA,SAAA;;AAGF,KAAA,MAAA,6BAAA,iBAAA;AACE,QAAA,YAAA;AACA,QAAA,UAAA,EAAA;AACA,QAAA,WAAA;AACA,SAAA;;AAKF,KAAA,MAAA,0BAAA,cAAA;AACE,QAAA,YAAA;AACA,QAAA,UAAA,EAAA;AACA,QAAA,WAAA;AACA,SAAA;;AAGF,QAAA,MAAA;;;AAIF,SAAA,uBAAA,MAAA,QAAA,iBAAA,cAAA;;AAOE,OAAA,YAAA;AACA,OAAA,2BAAA;AACA,OAAA,wBAAA;;;;;;AAWF,SAAA,gBAAA,MAAA,OAAA,MAAA,MAAA;AAME,KAAA,eAAA,QAAA;;AAEA,KAAA,CAAA,SAAA,MAAA,QAAA,WAAA,EAAA,QAAA;AAEA,MAAA,IAAA,IAAA,GAAA,IAAA,MAAA,QAAA,QAAA,KAAA;;AAEE,MAAA,EAAA,UAAA,SAAA,EAAA,SAAA,QAAA,EAAA,SAAA,MAAA;AAEE,OAAA,IAAA,MAAA,QAAA,SAAA,GAAA;AACE,UAAA,QAAA,OAAA,GAAA,EAAA;AACA,UAAA,QAAA,KAAA,EAAA;;AAEF,UAAA;;;AAGJ,QAAA;;;AAIF,SAAA,gBAAA,MAAA,OAAA,MAAA,MAAA,OAAA,aAAA,gBAAA;;AAYE,MAAA,IAAA,IAAA,GAAA,IAAA,MAAA,QAAA,QAAA,KAAA;;AAEE,MAAA,EAAA,UAAA,SAAA,EAAA,SAAA,QAAA,EAAA,SAAA,MAAA;AACE,SAAA,QAAA,KAAA;;;;;;;;AACA;;;AAKJ,KAAA,MAAA,QAAA,UAAA,mBAAA,OAAA,QAAA,OAAA;AAGA,OAAA,QAAA,KAAA;;;;;;;;;;;;;;AAYF,SAAA,kBAAA,MAAA;AACE,KAAA,eAAA,QAAA;;AAEA,KAAA,CAAA,OAAA,SAAA,QAAA;AACA,KAAA,QAAA,KAAA,WAAA,KAAA,YAAA,iBAAA,EAAA;AACE,QAAA,WAAA;AACA,SAAA;;AAEF,QAAA,MAAA;;;AAIF,SAAA,kBAAA,MAAA,UAAA;;AAEE,OAAA,WAAA;;;;;;;;;;;;;;;;;;;AChPF,SAAgB,kBACd,MACA,WAAkCA,eACpB;CACd,MAAM,YAAY,wBAAwB,KAAK;CAC/C,MAAM,MAAM,UAAU;CACtB,MAAM,SAAS,IAAI,MAAc,IAAI;CACrC,MAAM,YAAY,IAAI,MAAc,MAAM,EAAE;CAC5C,MAAM,iBAA2B,EAAE;CACnC,MAAM,eAAyB,EAAE;AAEjC,WAAU,KAAK;CACf,IAAI,eAAe;CACnB,IAAI,mBAAmB;CACvB,IAAI,mBAAmB;AAEvB,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,KAAK;EAC5B,MAAM,IAAI,UAAU;EACpB,MAAM,IAAI,SAAS,EAAE;AACrB,SAAO,KAAK;AACZ,YAAU,IAAI,KAAK,UAAU,KAAM;AACnC,MAAI,IAAI,iBAAkB,oBAAmB;AAE7C,MAAI,MAAM,MAAM;AACd,kBAAe,KAAK,EAAE;AACtB,kBAAe,KAAK,IAAI,cAAc,iBAAiB;AACvD,sBAAmB;aACV,eAAe,EAAE,EAAE;AAC5B,gBAAa,KAAK,IAAI,EAAE;AACxB,kBAAe,KAAK,IAAI,cAAc,iBAAiB;AACvD,sBAAmB;aACV,iBAAiB,EAAE,EAAE;AAC9B,gBAAa,KAAK,EAAE;AACpB,kBAAe,KAAK,IAAI,cAAc,iBAAiB;AACvD,sBAAmB;aACV,IAAI,EACb,qBAAoB;;AAGxB,gBAAe,KAAK,IAAI,cAAc,iBAAiB;AAEvD,QAAO;EACL;EACA;EACA;EACA,YAAY,UAAU;EACtB;EACA;EACA;EACA;EACA;EACD;;;;;;;;;;;;AAiBH,SAAgB,kBAAkB,UAAwB,OAAuB;AAC/E,KAAI,SAAS,EAAG,QAAO;AACvB,KAAI,SAAS,cAAc,SAAS,SAAS,eAAe,WAAW,EAAG,QAAO;AACjF,QAAO,SAAS,SAAS,MAAM,OAAO,MAAM,KAAK,CAAC;;;;;;;;;;;AAgBpD,SAAgB,gBAAgB,UAAwB,UAA0B;AAChF,KAAI,YAAY,EAAG,QAAO;CAC1B,MAAM,kBAAkB,kBAAkB,UAAU,SAAS;AAC7D,KAAI,mBAAmB,EACrB,QAAO,KAAK,IAAI,KAAK,KAAK,SAAS,WAAW,EAAE,SAAS;CAK3D,IAAI,KAAK,KAAK,IAAI,GAAG,SAAS,iBAAiB;CAC/C,IAAI,KAAK;AAGT,KAAI,MAAM,GAAI,QAAO,KAAK,IAAI,IAAI,SAAS;AAE3C,QAAO,KAAK,IAAI;EACd,MAAM,MAAO,KAAK,MAAO;AACzB,MAAI,kBAAkB,UAAU,IAAI,IAAI,gBACtC,MAAK;MAEL,MAAK,MAAM;;AAKf,QAAO,KAAK,IAAI,IAAI,SAAS;;;;;;;;;;;AA8C/B,SAAgB,iBAAiB,UAAwB,OAAyB;AAChF,KAAI,SAAS,EAAG,QAAO,EAAE;AACzB,KAAI,SAAS,cAAc,SAAS,SAAS,eAAe,WAAW,EAAG,QAAO,EAAE;CAGnF,MAAM,EAAE,gBAAgB,cAAc;CACtC,MAAM,YAAsB,EAAE;CAE9B,MAAM,kBAAkB,CAAC,EAAE;AAC3B,MAAK,MAAM,MAAM,eACf,iBAAgB,KAAK,KAAK,EAAE;AAG9B,MAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;EAC/C,MAAM,SAAS,gBAAgB;EAC/B,MAAM,OAAO,IAAI,IAAI,gBAAgB,SAAS,gBAAgB,IAAI,KAAM,IAAI,UAAU;AAEtF,MAAI,UAAU,KAAM;EAEpB,MAAM,SAAS,uBAAuB,UAAU,QAAQ,MAAM,MAAM;AACpE,YAAU,KAAK,GAAG,OAAO;AAGzB,MAAI,IAAI,gBAAgB,SAAS,KAAK,OAAO,UAAU,OACrD,WAAU,KAAK,OAAO,EAAE;;AAI5B,QAAO;;;AAIT,SAAS,uBACP,UACA,QACA,MACA,OACU;CACV,MAAM,EAAE,WAAW,cAAc,QAAQ,cAAc;CAGvD,MAAM,aAAuB,CAAC,OAAO;AACrC,MAAK,MAAM,MAAM,aACf,KAAI,KAAK,UAAU,MAAM,KAAM,YAAW,KAAK,GAAG;AAEpD,YAAW,KAAK,KAAK;CAErB,MAAM,IAAI,WAAW;AACrB,KAAI,KAAK,EAAG,QAAO,EAAE;CAErB,MAAM,OAAO,IAAI,MAAc,EAAE,CAAC,KAAK,SAAS;CAChD,MAAM,OAAO,IAAI,MAAc,EAAE,CAAC,KAAK,GAAG;AAC1C,MAAK,IAAI,KAAK;AAEd,MAAK,IAAI,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK;EAC/B,MAAM,YAAY,WAAW;EAC7B,MAAM,eAAe,UAAU;AAE/B,OAAK,IAAI,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;GAI9B,IAAI,UAHY,WAAW;AAI3B,UAAO,UAAU,WAAW;IAC1B,MAAM,QAAQ,UAAU,UAAU;AAElC,QADc,OAAO,UAAU,OACjB,GAAG;AACf;AACA;;AAEF,QAAI,UAAU,OAAO,UAAU,KAAM;AACnC;AACA;;AAEF;;GAEF,MAAM,YAAY,UAAU,WAAY;AAExC,OAAI,YAAY,MAAO;GAEvB,MAAM,WAAW,QAAQ;GAEzB,MAAM,aADW,MAAM,IAAI,IAAI,IAAI,WAAW,YACjB,KAAK;AAElC,OAAI,YAAY,KAAK,IAAK;AACxB,SAAK,KAAK;AACV,SAAK,KAAK;;;;AAMhB,KAAI,KAAK,OAAO,SAAU,QAAO,EAAE;CAGnC,MAAM,SAAmB,EAAE;CAC3B,IAAI,MAAM;AACV,QAAO,MAAM,IAAI,KAAK,KAAK,QAAS,GAAG;AACrC,QAAM,KAAK;AACX,MAAI,MAAM,IAAI,EACZ,QAAO,KAAK,WAAW,KAAM;;AAIjC,QAAO;;;;;;;AAQT,SAAgB,YAAY,MAAc,UAAwB,OAAyB;CACzF,MAAM,SAAS,iBAAiB,UAAU,MAAM;AAChD,KAAI,OAAO,WAAW,GAAG;AAEvB,MAAI,SAAS,cAAc,SAAS,SAAS,eAAe,WAAW,EAAG,QAAO,CAAC,KAAK;AACvF,SAAO,SAAS,MAAM,OAAO,MAAM,KAAK;;CAG1C,MAAM,EAAE,WAAW,WAAW;CAC9B,MAAM,QAAkB,EAAE;CAC1B,IAAI,YAAY;AAEhB,MAAK,MAAM,MAAM,QAAQ;EAEvB,IAAI,UAAU;AACd,SAAO,UAAU,WAAW;AAE1B,OADU,OAAO,UAAU,OACjB,GAAG;AACX;AACA;;GAEF,MAAM,IAAI,UAAU,UAAU;AAC9B,OAAI,MAAM,OAAO,MAAM,OAAQ,MAAM,MAAM;AACzC;AACA;;AAEF;;AAEF,QAAM,KAAK,UAAU,MAAM,WAAW,QAAQ,CAAC,KAAK,GAAG,CAAC;AAGxD,cAAY;AACZ,SAAO,YAAY,UAAU,QAAQ;GACnC,MAAM,IAAI,UAAU;AACpB,OAAI,MAAM,OAAO,MAAM,KAAM;AAC3B;AACA;;AAEF;;;AAKJ,KAAI,YAAY,UAAU,OACxB,OAAM,KAAK,UAAU,MAAM,UAAU,CAAC,KAAK,GAAG,CAAC;AAGjD,QAAO;;;;;;;ACzWT,SAAgB,WAAW,OAKzB;AACA,QAAO;EACL,KAAK,MAAM,cAAc,MAAM,YAAY,MAAM,WAAW;EAC5D,QAAQ,MAAM,iBAAiB,MAAM,YAAY,MAAM,WAAW;EAClE,MAAM,MAAM,eAAe,MAAM,YAAY,MAAM,WAAW;EAC9D,OAAO,MAAM,gBAAgB,MAAM,YAAY,MAAM,WAAW;EACjE;;;;;;;AAQH,SAAgB,cAAc,OAK5B;AACA,KAAI,CAAC,MAAM,eAAe,qBAAqB,GAAG,EAChD,QAAO;EAAE,KAAK;EAAG,QAAQ;EAAG,MAAM;EAAG,OAAO;EAAG;AAEjD,QAAO;EACL,KAAK,MAAM,cAAc,QAAQ,IAAI;EACrC,QAAQ,MAAM,iBAAiB,QAAQ,IAAI;EAC3C,MAAM,MAAM,eAAe,QAAQ,IAAI;EACvC,OAAO,MAAM,gBAAgB,QAAQ,IAAI;EAC1C;;;;;;;;;;AClBH,SAAgB,aAAa,MAAc,KAA6B;AACtE,gBAAa,OAAO,SAAS;AAE3B,MAAI,CAAC,KAAK,WAAY;EAEtB,MAAM,QAAQ,KAAK;EAOnB,MAAM,gBAAgB,MAAM,UAAU;EACtC,MAAM,qBAAqB,MAAM,WAAW;AAE5C,MAAI,iBAAiB,oBAAoB;GAIvC,IAAI;GAEJ,IAAI,qBADiB,OAAO,MAAM,UAAU,YAE1B,qBACX,MAAM,QACP,OAAO,MAAM,aAAa,WACvB,MAAM,WACP,KAAA;AACR,OAAI,uBAAuB,KAAA,EACzB,sBAAqB,0BAA0B,KAAK;AAEtD,OAAI,uBAAuB,KAAA,GAAW;IACpC,MAAM,UAAU,WAAW,MAAM;AACjC,qBAAiB,qBAAqB,QAAQ,OAAO,QAAQ;AAC7D,QAAI,MAAM,aAAa;KACrB,MAAM,SAAS,cAAc,MAAM;AACnC,uBAAkB,OAAO,OAAO,OAAO;;AAEzC,QAAI,iBAAiB,EAAG,kBAAiB;;AAG3C,OAAI,eAAe;IAIjB,MAAM,cAAc,wBAAwB,MAHtB,qBAAqB,MAAM,KAAK,eAAe,CAGL,OAAO,IAAI;AAG3E,SAAK,WAAW,YAAY,YAAY;;AAE1C,OAAI,oBAAoB;IACtB,MAAM,gBAAgB,qBAAqB,MAAM,KAAK,eAAe;AACrE,SAAK,WAAW,UAAU,cAAc,OAAO;;;GAGnD;;;;;;;;;;;AAYJ,SAAS,qBACP,MACA,KACA,gBAIA;CACA,MAAM,QAAQ,KAAK;AAGnB,KAAI,MAAM,YAAY,OACpB,QAAO;EAAE,OAAO;EAAG,QAAQ;EAAG;AAGhC,KAAI,KAAK,SAAS,gBAAgB;EAChC,MAAM,YAAY;EAElB,MAAM,SAAS,mBAAmB,KAAK;EACvC,IAAI;AACJ,MAAI,OACF,QAAO,OAAO;OACT;AACL,UAAOC,iBAAmB,KAAK;GAC/B,MAAM,aAAa,KAAK,MAAM,MAAM,EAAE,UAAU,KAAK;AACrD,sBAAmB,MAAM,MAAM,UAAU;;EAK3C,MAAM,YAAY,UAAU;EAC5B,IAAI;AAEJ,MAAI,mBAAmB,KAAA,KAAa,iBAAiB,KAAK,cAAc,UAAU,KAAK,CAErF,SAAQ,MACJ,IAAI,SAAS,SAAS,MAAM,gBAAgB,MAAM,KAAK,GACvD,SAAS,MAAM,gBAAgB,MAAM,KAAK;MAE9C,SAAQ,KAAK,MAAM,KAAK;AAG1B,MAAI,UACF,SAAQ,MAAM,KAAK,MAAM,UAAU,UAAU,MAAM,MAAM,CAAC;AAI5D,SAAO;GACL,OAFY,KAAK,IAAI,GAAG,MAAM,KAAK,SAASC,eAAa,MAAM,IAAI,CAAC,CAAC;GAGrE,QAAQ,MAAM,SAAS,qBAAqB;GAC7C;;CAIH,MAAM,QAAQ,MAAM,kBAAkB,SAAS,MAAM,kBAAkB;CAEvE,IAAI,QAAQ;CACZ,IAAI,SAAS;CAEb,IAAI,aAAa;AACjB,MAAK,MAAM,SAAS,KAAK,UAAU;EACjC,MAAM,YAAY,qBAAqB,OAAO,KAAK,eAAe;AAClE;AAEA,MAAI,OAAO;AACT,YAAS,UAAU;AACnB,YAAS,KAAK,IAAI,QAAQ,UAAU,OAAO;SACtC;AACL,WAAQ,KAAK,IAAI,OAAO,UAAU,MAAM;AACxC,aAAU,UAAU;;;CAKxB,MAAM,MAAO,MAAM,OAAkB;AACrC,KAAI,MAAM,KAAK,aAAa,GAAG;EAC7B,MAAM,WAAW,OAAO,aAAa;AACrC,MAAI,MACF,UAAS;MAET,WAAU;;CAKd,MAAM,UAAU,WAAW,MAAM;AACjC,UAAS,QAAQ,OAAO,QAAQ;AAChC,WAAU,QAAQ,MAAM,QAAQ;AAGhC,KAAI,MAAM,aAAa;EACrB,MAAM,SAAS,cAAc,MAAM;AACnC,WAAS,OAAO,OAAO,OAAO;AAC9B,YAAU,OAAO,MAAM,OAAO;;AAGhC,QAAO;EAAE;EAAO;EAAQ;;;;;AAM1B,SAAS,cAAc,MAAkC;AACvD,QACE,SAAS,UAAU,SAAS,UAAU,SAAS,UAAU,SAAS,QAAQ,SAAS,KAAA;;;;;;;AASvF,SAAS,wBACP,MACA,iBACA,KACQ;CACR,MAAM,QAAQ,KAAK;CAKnB,IAAI,WAAW;CACf,MAAM,UAAU,WAAW,MAAM;AACjC,aAAY,QAAQ,OAAO,QAAQ;AACnC,KAAI,MAAM,aAAa;EACrB,MAAM,SAAS,cAAc,MAAM;AACnC,cAAY,OAAO,OAAO,OAAO;;CAEnC,MAAM,eAAe,kBAAkB;CAGvC,IAAI,WAAW,kBAAkB,KAAK;AACtC,KAAI,CAAC,UAAU;EACb,MAAM,SAAS,mBAAmB,KAAK;EACvC,MAAM,OAAO,SAAS,OAAO,OAAOD,iBAAmB,KAAK;AAE5D,aAAW,kBAAkB,MADZ,KAAK,UAAU,eAAe,KAAK,IAAI,SAAS,IAAI,cACzB;AAC5C,oBAAkB,MAAM,SAAS;AACjC,MAAI,CAAC,OAEH,oBAAmB,MAAM,OADN,KAAK,MAAM,MAAM,EAAE,UAAU,KAAK,EACZ;;AAK7C,QAAO,gBAAgB,UAAU,aAAa,GAAG;;;;;;;;AASnD,SAAS,0BAA0B,MAAkC;CACnE,IAAI,UAAU,KAAK;AACnB,QAAO,SAAS;EACd,MAAM,IAAI,QAAQ;AAClB,MAAI,OAAO,EAAE,UAAU,UAAU;GAC/B,IAAI,QAAQ,EAAE;GACd,MAAM,UAAU,WAAW,EAAE;AAC7B,YAAS,QAAQ,OAAO,QAAQ;AAChC,OAAI,EAAE,aAAa;IACjB,MAAM,SAAS,cAAc,EAAE;AAC/B,aAAS,OAAO,OAAO,OAAO;;AAEhC,UAAO,QAAQ,IAAI,QAAQ;;AAE7B,MAAI,OAAO,EAAE,aAAa,UAAU;GAClC,IAAI,QAAQ,EAAE;GACd,MAAM,UAAU,WAAW,EAAE;AAC7B,YAAS,QAAQ,OAAO,QAAQ;AAChC,OAAI,EAAE,aAAa;IACjB,MAAM,SAAS,cAAc,EAAE;AAC/B,aAAS,OAAO,OAAO,OAAO;;AAEhC,UAAO,QAAQ,IAAI,QAAQ;;AAE7B,YAAU,QAAQ;;;;;;AAQtB,SAASE,eAAa,MAAc,UAAwC;AAC1E,UAAS,KAAK;AACd,MAAK,MAAM,SAAS,KAAK,SACvB,gBAAa,OAAO,SAAS;;;;;;AAQjC,SAASD,eAAa,MAAc,KAA+B;AACjE,KAAI,IAAK,QAAO,IAAI,SAAS,iBAAiB,KAAK;AACnD,QAAO,iBAAiB,KAAK;;;;;;;;;AC5Q/B,MAAME,QAAM,aAAa,iBAAiB;;;;;;;;AAS1C,SAAgB,YAAY,MAAc,OAAe,QAAsB;CAE7E,MAAM,aAAa,KAAK;CACxB,MAAM,oBACJ,eAAe,WAAW,UAAU,SAAS,WAAW,WAAW;AAKrE,KAAI,CAAC,qBAAqB,CAAC,KAAK,YAAY,SAAS,EAAE;AAMrD,MAAI,QAAQ,KAAK,WAAW,KAAK,YAAA,GAAwB,CACvD,wBAAuB,KAAK;AAE9B;;AAGF,KAAI,KAAK,YAAY;EACnB,MAAM,YAAY,WAAW,KAAK;AAClC,eAAa,OAAO;EACpB,MAAM,KAAK,KAAK,KAAK;AACrB,OAAK,WAAW,gBAAgB,OAAO,OAAO;EAC9C,MAAM,UAAU,KAAK,KAAK,GAAG;AAC7B,QAAI,QACF,oBAAoB,QAAQ,MAAM,UAAU,yBAAyB,aAAa,MAAM,QAAQ,aAAa,UAAU,YAAY,aAAa,aAAa,gBAAgB,aAAa,oBAC3L;;AAUH,iBAAgB,MAAM,GAAG,GADD,CAAC,kBACmB;;;;;AAU9C,SAAS,WAAW,MAAsB;CACxC,IAAI,QAAQ;AACZ,MAAK,MAAM,SAAS,KAAK,SACvB,UAAS,WAAW,MAAM;AAE5B,QAAO;;;;;;;;;;;;;;;;;;;;;;;;AAyBT,SAAS,gBACP,MACA,SACA,SACA,iBACM;AAEN,KAAI,CAAC,KAAK,YAAY;AAEpB,OAAK,aAAa,KAAK;AAOvB,OAAK,UANc;GACjB,GAAG;GACH,GAAG;GACH,OAAO;GACP,QAAQ;GACT;AAGD,OAAK,MAAM,SAAS,KAAK,SACvB,iBAAgB,OAAO,SAAS,SAAS,gBAAgB;AAE3D;;CAIF,MAAM,OAAa;EACjB,GAAG,UAAU,KAAK,WAAW,iBAAiB;EAC9C,GAAG,UAAU,KAAK,WAAW,gBAAgB;EAC7C,OAAO,KAAK,WAAW,kBAAkB;EACzC,QAAQ,KAAK,WAAW,mBAAmB;EAC5C;AAiBD,KACE,mBACA,KAAK,WACL,CAAC,QAAQ,KAAK,WAAW,KAAK,YAAA,GAAwB,IACtD,CAAC,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAyB;MAGrD,KAAK,MAAM,KAAK,QAAQ,KACxB,KAAK,MAAM,KAAK,QAAQ,KACxB,KAAK,UAAU,KAAK,QAAQ,SAC5B,KAAK,WAAW,KAAK,QAAQ,OAE7B;;AAOJ,MAAK,aAAa,KAAK;AACvB,MAAK,UAAU;AAOf,MAAK,yBADmB,CAAC,EAAE,KAAK,cAAc,CAAC,UAAU,KAAK,YAAY,KAAK,QAAQ,IACvC,gBAAgB,GAAA;AAKhE,KAAI,SAAS,KAAK,kBAAkB,eAAe,KAAK,uBAAuB;MACzE,UAAU,KAAK,YAAY,KAAK,QAAQ,EAAE;GAC5C,MAAM,QAAQ,KAAK;AACnB,SAAM,IAAI,MACR,qFACY,MAAM,MAAM,KAAK,KAAK,UAAU,KAAK,UAAU,KAAK,QAAQ,CAAC,GAC1E;;;AAQL,KAAI,eAAe,KAAK,uBAAuB,EAAE;EAC/C,MAAM,QAAQ,gBAAgB;EAC9B,IAAI,WAAW,KAAK;AACpB,SAAO,YAAY,CAAC,QAAQ,SAAS,WAAW,SAAS,YAAA,GAAwB,EAAE;AACjF,OAAI,SAAS,eAAe,OAAO;AACjC,aAAS,YAAA;AACT,aAAS,aAAa;SAEtB,UAAS,aAAA;AAEX,cAAW,SAAS;;;AAKxB,MAAK,MAAM,SAAS,KAAK,SACvB,iBAAgB,OAAO,KAAK,GAAG,KAAK,GAAG,gBAAgB;AAOzD,KAAI,QAAQ,KAAK,WAAW,KAAK,YAAA,GAAwB,IAAI,KAAK,SAAS,SAAS,GAAG;EACrF,MAAM,QAAQ,gBAAgB;EAK9B,MAAM,WAAW,yBAAyB,KAAK,SAAS;EAIxD,MAAM,eAAe,8BAA8B,MAAM,KAAK;EAG9D,IAAI,OAAO,KAAK;AAChB,MAAI,SAAU,SAAA;MACT,SAAQ;AACb,MAAI,aAAc,SAAA;MACb,SAAQ;AACb,OAAK,YAAY;AACjB,OAAK,aAAa;YAGd,KAAK,eAAe,gBAAgB,CACtC,MAAK,aAAa;;;;;;;;;;;;;;;AAkBxB,SAAS,uBAAuB,MAAoB;AAClD,KAAI,CAAC,QAAQ,KAAK,WAAW,KAAK,YAAA,GAAwB,CAAE;AAC5D,KAAI,CAAC,KAAK,YAAY,KAAK,SAAS,WAAW,EAAG;AAGlD,MAAK,MAAM,SAAS,KAAK,SACvB,KAAI,QAAQ,MAAM,WAAW,MAAM,YAAA,GAAwB,CACzD,wBAAuB,MAAM;CAKjC,MAAM,QAAQ,gBAAgB;CAC9B,MAAM,WAAW,yBAAyB,KAAK,SAAS;CACxD,MAAM,eAAe,KAAK,UAAU,8BAA8B,MAAM,KAAK,QAAQ,GAAG;CAExF,IAAI,OAAO,KAAK;AAChB,KAAI,SAAU,SAAA;KACT,SAAQ;AACb,KAAI,aAAc,SAAA;KACb,SAAQ;AACb,MAAK,YAAY;AACjB,MAAK,aAAa;;;;;AAMpB,SAAS,yBAAyB,UAAsC;AACtE,MAAK,MAAM,SAAS,SAElB,KADW,MAAM,MAEZ,aAAa,eACf,QAAQ,MAAM,WAAW,MAAM,YAAA,EAAyB,IACvD,eAAe,MAAM,uBAAuB,IAC5C,yBAAyB,MAAM,EAEjC,QAAO;AAGX,QAAO;;;;;AAMT,SAAS,yBAAyB,MAAuB;AACvD,MAAK,MAAM,SAAS,KAAK,SACvB,KAAI,MAAM,WAAW,MAAM;MACrB,MAAM,QAAQ,MAAM,MAAM,WAAW,KAAK,MAAM,QAAQ,MAAM,MAAM,WAAW,EACjF,QAAO;;AAIb,QAAO;;;;;;AAOT,SAAS,8BAA8B,MAAc,MAAqB;AACxE,QAAO,yBACL,KAAK,UACL,KAAK,GACL,KAAK,GACL,KAAK,IAAI,KAAK,OACd,KAAK,IAAI,KAAK,OACf;;AAGH,SAAS,yBACP,UACA,UACA,SACA,WACA,YACS;AACT,MAAK,MAAM,SAAS,UAAU;AAC5B,MAAI,MAAM,cAAc,eAAe,MAAM,uBAAuB,EAAE;GACpE,MAAM,OAAO,MAAM;AACnB,OACE,KAAK,IAAI,KAAK,QAAQ,aACtB,KAAK,IAAI,KAAK,SAAS,cACvB,KAAK,IAAI,YACT,KAAK,IAAI,QAET,QAAO;;AAGX,MAAI,QAAQ,MAAM,WAAW,MAAM,YAAA,GAAwB,IAAI,MAAM,aAAa,KAAA;OAC5E,yBAAyB,MAAM,UAAU,UAAU,SAAS,WAAW,WAAW,CACpF,QAAO;;;AAIb,QAAO;;;;;;;;;;;;;;AAeT,SAAgB,wBAAwB,MAAoB;AAElC,WAAU,KAAK,YAAY,KAAK,QAAQ;AACzC,WAAU,KAAK,gBAAgB,KAAK,WAAW;AAC/C,WAAU,KAAK,gBAAgB,KAAK,WAAW;AAItE,iBAAgB,KAAK;AAGrB,MAAK,MAAM,SAAS,KAAK,SACvB,yBAAwB,MAAM;;;;;;;;;;;;;;;AAqBlC,SAAgB,0BAA0B,MAAoB;CAC5D,MAAM,SAAS,SAAS,KAAK;AAC7B,KAAI,CAAC,OAAQ;CAEb,MAAM,cAAc,WAAW;CAE/B,SAAS,KAAK,MAAoB;AAChC,OAAK,MAAM,SAAS,KAAK,UAAU;AACjC,OAAI,MAAM,WAAW,KAAK,SAAS;IACjC,MAAM,aAAa,MAAM;AAGzB,QAAI,WAAW,aAAa,YAAY;AACtC,UAAK,MAAM;AACX;;IAGF,MAAM,cAAc,KAAK;AAGzB,QAAI,YAAY,aAAa,YAAY,YAAY,aAAa,UAAU;AAC1E,UAAK,MAAM;AACX;;IAIF,MAAM,SAAS,YAAY,cACvB,cAAc,YAAY,GAC1B;KAAE,KAAK;KAAG,QAAQ;KAAG,MAAM;KAAG,OAAO;KAAG;IAC5C,MAAM,UAAU,WAAW,YAAY;IACvC,MAAM,mBACJ,KAAK,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,QAAQ,OAAO,OAAO,OAAO;AAE3E,QAAI,MAAM,QAAQ,QAAQ,kBAAkB;KAC1C,MAAM,UAAW,WAAmB,MAAM,MAAM;KAChD,MAAM,WAAY,YAAoB,MAAM,KAAK;KACjD,MAAM,MACJ,4CAA4C,QAAQ,UAAU,MAAM,QAAQ,MAAM,mBAC/D,SAAS,gBAAgB,iBAAiB,gBAC7C,KAAK,QAAQ,MAAM,YAAY,OAAO,KAAK,GAAG,OAAO,MAAM,aAAa,QAAQ,KAAK,GAAG,QAAQ,MAAM;AAExH,SAAI,YACF,OAAM,IAAI,MAAM,IAAI;SAEpB,SAAQ,KAAK,IAAI;;;AAKvB,QAAK,MAAM;;;AAIf,MAAK,KAAK;;;;;;;;AA4BZ,SAAgB,YAAY,MAAc,UAA8B,EAAE,EAAQ;CAChF,MAAM,EAAE,mBAAmB,UAAU;AACrC,cAAa,OAAO,SAAS;EAC3B,MAAM,QAAQ,KAAK;AACnB,MAAI,MAAM,aAAa,SAAU;AAGjC,uBAAqB,MAAM,OAAO,iBAAiB;GACnD;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BJ,SAAS,qBACP,WACA,gBACA,QACQ;AACR,KAAI,aAAa,EAAG,QAAO;CAK3B,IAAI,eAAe;AACnB,MAAK,MAAM,MAAM,gBAAgB;AAC/B,MAAI,GAAG,SAAU;AACjB,MAAI,GAAG,QAAQ,GAAG,OAAQ;AAC1B,MAAI,GAAG,MAAM,aAAa,GAAG,OAAO,OAAO;OACrC,iBAAiB,MAAM,GAAG,MAAM,aAClC,gBAAe,GAAG;;;AAIxB,KAAI,iBAAiB,GAAI,QAAO;CAGhC,MAAM,UAAU,eAAe;AAE/B,QAAO,WAAW,YAAY,UAAU;;;;;AAM1C,SAAS,qBAAqB,MAAc,OAAiB,kBAAiC;CAC5F,MAAM,SAAS,KAAK;AACpB,KAAI,CAAC,UAAU,CAAC,KAAK,WAAY;CAGjC,MAAM,SAAS,MAAM,cAAc,cAAc,MAAM,GAAG;EAAE,KAAK;EAAG,QAAQ;EAAG,MAAM;EAAG,OAAO;EAAG;CAClG,MAAM,UAAU,WAAW,MAAM;CAEjC,MAAM,oBACJ,OAAO,SAAS,OAAO,MAAM,OAAO,SAAS,QAAQ,MAAM,QAAQ;CAGrE,IAAI,gBAAgB;CACpB,MAAM,iBAQA,EAAE;AAER,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,SAAS,QAAQ,KAAK;EAC7C,MAAM,QAAQ,KAAK,SAAS;AAC5B,MAAI,CAAC,MAAM,cAAc,CAAC,MAAM,QAAS;EAEzC,MAAM,WAAW,MAAM,QAAQ,IAAI,OAAO,IAAI,OAAO,MAAM,QAAQ;EACnE,MAAM,cAAc,WAAW,MAAM,QAAQ;EAC7C,MAAM,aAAa,MAAM;AAEzB,iBAAe,KAAK;GACX;GACP,KAAK;GACL,QAAQ;GACR,OAAO;GACP,UAAU,WAAW,aAAa;GAClC,WAAW,WAAW;GACtB,cAAc,WAAW;GAC1B,CAAC;AAEF,kBAAgB,KAAK,IAAI,eAAe,YAAY;;CAGtD,MAAM,iBAAiB;CAQvB,MAAM,mBAF0B,MAAM,sBAAsB,QAAQ,CAAC,MAAM,eACvD,gBAAgB,oBAC8B,IAAI;CAUtE,MAAM,aAAa,KAAK,aAAa;CAErC,IAAI,eADmB,MAAM,gBACQ,cAAc;CACnD,MAAM,WAAW,MAAM;AAEvB,KAAI,aAAa,KAAA,KAAa,YAAY,KAAK,WAAW,eAAe,QAAQ;EAE/E,MAAM,SAAS,eAAe,MAAM,MAAM,EAAE,UAAU,SAAS;AAC/D,MAAI,QAAQ;GAIV,MAAM,kBAAkB,iBAAiB;GACzC,MAAM,aAAa;GACnB,MAAM,gBAAgB,eAAe;AAGrC,OAAI,OAAO,MAAM,WAUf,gBAAe,OAAO,MAAM,IAAI,OAAO,MAAM,IAAI;YACxC,OAAO,SAAS,cAUzB,gBAAe,qBADG,OAAO,SAAS,iBACa,gBAAgB,OAAO;;;AAQ5E,gBAAe,KAAK,IAAI,GAAG,aAAa;AACxC,gBAAe,KAAK,IAAI,cAAc,KAAK,IAAI,GAAG,gBAAgB,eAAe,CAAC;CAKlF,MAAM,aAAa;CACnB,MAAM,gBAAgB,eAAe,iBAAiB;CAEtD,IAAI,eAAe;CACnB,IAAI,cAAc;CAClB,IAAI,cAAc;CAClB,IAAI,cAAc;AAElB,MAAK,MAAM,MAAM,gBAAgB;AAE/B,MAAI,GAAG,UAAU;AACf,OAAI,iBAAiB,GAAI,gBAAe,GAAG;AAC3C,iBAAc,KAAK,IAAI,aAAa,GAAG,MAAM;AAC7C;;AAOF,MAAI,GAAG,QAAQ,GAAG,OAChB;AAGF,MAAI,GAAG,UAAU,WACf;WACS,GAAG,OAAO,cACnB;WACS,GAAG,MAAM,YAAY;AAG9B,OAAI,iBAAiB,GAAI,gBAAe,GAAG;AAC3C,iBAAc,KAAK,IAAI,aAAa,GAAG,MAAM;aACpC,GAAG,SAAS,eAAe;AAMpC,OAAI,iBAAiB,GAAI,gBAAe,GAAG;AAC3C,iBAAc,GAAG;AAGjB,OAAI,mBAAmB,EACrB;SAEG;AAEL,OAAI,iBAAiB,GAAI,gBAAe,GAAG;AAC3C,iBAAc,GAAG;;;CAKrB,MAAM,iBAAuE,EAAE;AAE/E,MAAK,MAAM,MAAM,gBAAgB;AAC/B,MAAI,CAAC,GAAG,SAAU;EAElB,MAAM,cAAc,GAAG,SAAS,GAAG;EACnC,MAAM,YAAY,GAAG,aAAa;EAClC,MAAM,eAAe,GAAG;EAGxB,MAAM,iBAAiB,GAAG,MAAM;EAEhC,IAAI;AAEJ,MAAI,iBAAiB,KAAA,GAAW;GAE9B,MAAM,oBAAoB,iBAAiB,eAAe;AAE1D,kBAAe,KAAK,IAAI,gBAAgB,kBAAkB;aACjD,kBAAkB,UAE3B,gBAAe;WACN,cAAc,eAIvB,gBAAe,KAAK,IAAI,iBAAiB,aAAa,eAAe;MAGrE,gBAAe;AAQjB,MADmB,iBAAiB,eAElC,KAAI,cAAc,eAChB,gBAAe,KAAK,IAAI,iBAAiB,aAAa,aAAa;MAEnE,gBAAe,KAAK,IAAI,GAAG,KAAK,IAAI,cAAc,iBAAiB,YAAY,CAAC;AAMpF,MAAI,eAAe,eAAe,KAAK,gBAAgB,eAAgB;AAEvE,iBAAe,KAAK;GAClB,OAAO,GAAG;GACV;GACA,YAAY,GAAG;GACf,QAAQ;GACT,CAAC;;AAIJ,KAAI,iBAAkB;CAGtB,MAAM,mBAAmB,KAAK,aAAa,qBAAqB;CAChE,MAAM,kBAAkB,KAAK,aAAa,oBAAoB;AAK9D,KACE,iBAAiB,cACjB,iBAAiB,oBACjB,gBAAgB,iBAChB;EACA,MAAM,QAAQ,gBAAgB;AAC9B,MAAI,KAAK,eAAe,OAAO;AAC7B,QAAK,YAAA;AACL,QAAK,aAAa;QAElB,MAAK,aAAA;;AAKT,MAAK,cAAc;EACjB,QAAQ;EACR,YAAY,cAAc;EAC1B;EACA;EACA,mBAAmB;EACnB,kBAAkB;EAClB,uBAAuB;EACvB,sBAAsB;EACtB;EACA;EACA,gBAAgB,eAAe,SAAS,IAAI,iBAAiB,KAAA;EAC9D;;;;;;;;;;;;;AAkBH,SAAgB,YAAY,MAAoB;AAC9C,cAAa,OAAO,SAAS;EAC3B,MAAM,QAAQ,KAAK;AAEnB,MAAI,MAAM,aAAa,SAAU;EAGjC,IAAI,oBAAoB;AACxB,OAAK,MAAM,SAAS,KAAK,UAAU;GACjC,MAAM,aAAa,MAAM;AACzB,OAAI,WAAW,aAAa,YAAY,WAAW,iBAAiB,KAAA,GAAW;AAC7E,wBAAoB;AACpB;;;AAIJ,MAAI,CAAC,mBAAmB;AAEtB,OAAI,KAAK,mBAAmB,KAAA,GAAW;AACrC,SAAK,iBAAiB,KAAA;IACtB,MAAM,QAAQ,gBAAgB;AAC9B,QAAI,KAAK,eAAe,OAAO;AAC7B,UAAK,YAAA;AACL,UAAK,aAAa;UAElB,MAAK,aAAA;;AAGT;;EAGF,MAAM,SAAS,KAAK;AACpB,MAAI,CAAC,UAAU,CAAC,KAAK,WAAY;EAEjC,MAAM,SAAS,MAAM,cACjB,cAAc,MAAM,GACpB;GAAE,KAAK;GAAG,QAAQ;GAAG,MAAM;GAAG,OAAO;GAAG;EAC5C,MAAM,UAAU,WAAW,MAAM;EACjC,MAAM,sBACJ,OAAO,SAAS,OAAO,MAAM,OAAO,SAAS,QAAQ,MAAM,QAAQ;EAErE,MAAM,oBAA2D,EAAE;AAEnE,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,SAAS,QAAQ,KAAK;GAC7C,MAAM,QAAQ,KAAK,SAAS;GAC5B,MAAM,aAAa,MAAM;AACzB,OAAI,WAAW,aAAa,SAAU;AACtC,OAAI,WAAW,iBAAiB,KAAA,EAAW;AAE3C,OAAI,CAAC,MAAM,QAAS;GAGpB,MAAM,WAAW,MAAM,QAAQ,IAAI,OAAO,IAAI,OAAO,MAAM,QAAQ;GACnE,MAAM,cAAc,MAAM,QAAQ;GAIlC,MAAM,YAAY,sBAHG,WAAW,eAGuB;GAGvD,MAAM,eAAe,KAAK,IAAI,UAAU,UAAU;AAElD,qBAAkB,KAAK;IACrB,OAAO;IACP;IACA,YAAY;IACZ,QAAQ;IACT,CAAC;;EAIJ,MAAM,OAAO,KAAK;EAClB,MAAM,OAAO,kBAAkB,SAAS,IAAI,oBAAoB,KAAA;EAEhE,MAAM,UAAU,CAAC,oBAAoB,MAAM,KAAK;AAChD,OAAK,iBAAiB;AAEtB,MAAI,SAAS;GACX,MAAM,QAAQ,gBAAgB;AAC9B,OAAI,KAAK,eAAe,OAAO;AAC7B,SAAK,YAAA;AACL,SAAK,aAAa;SAElB,MAAK,aAAA;;GAGT;;;;;AAMJ,SAAS,oBAAoB,GAA6B,GAAsC;AAC9F,KAAI,MAAM,EAAG,QAAO;AACpB,KAAI,CAAC,KAAK,CAAC,EAAG,QAAO;AACrB,KAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,MAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;EACjC,MAAM,KAAK,EAAE;EACb,MAAM,KAAK,EAAE;AACb,MACE,GAAG,UAAU,GAAG,SAChB,GAAG,iBAAiB,GAAG,gBACvB,GAAG,eAAe,GAAG,cACrB,GAAG,WAAW,GAAG,OAEjB,QAAO;;AAGX,QAAO;;;;;AAMT,SAAS,aAAa,MAAc,UAAwC;AAC1E,UAAS,KAAK;AACd,MAAK,MAAM,SAAS,KAAK,SACvB,cAAa,OAAO,SAAS;;;;;;;;;;;;;;AAoBjC,SAAgB,gBAAgB,MAAoB;AAClD,qBAAoB,MAAM,EAAE;;;;;;;;;AAU9B,SAAgB,sBAAsB,MAAoB;AACxD,2BAA0B,KAAK;;;;;;;;AASjC,SAAS,oBAAoB,MAAc,sBAAoC;AAE7E,MAAK,iBAAiB,KAAK;AAC3B,MAAK,iBAAiB,KAAK;CAE3B,MAAM,UAAU,KAAK;AACrB,KAAI,CAAC,SAAS;AACZ,OAAK,aAAa;AAClB,OAAK,aAAa;AAClB,OAAK,MAAM,SAAS,KAAK,SACvB,qBAAoB,OAAO,qBAAqB;AAElD;;AAIF,MAAK,aAAa;EAChB,GAAG,QAAQ;EACX,GAAG,QAAQ,IAAI;EACf,OAAO,QAAQ;EACf,QAAQ,QAAQ;EACjB;AAGD,MAAK,aAAa,KAAK;CAIvB,MAAM,oBAAoB,wBADL,KAAK,aAAa,UAAU;AAOjD,0BAAyB,KAAK;AAG9B,MAAK,MAAM,SAAS,KAAK,SACvB,qBAAoB,OAAO,kBAAkB;;;;;;;;;;;;AAcjD,SAAS,yBAAyB,QAAsB;CAEtD,MAAM,aAAa,OAAO,aAAa,kBAAkB,OAAO;AAChE,KAAI,CAAC,cAAc,WAAW,WAAW,EAAG;CAG5C,MAAM,mBAAmB,OAAO;AAChC,KAAI,CAAC,iBAAkB;CAEvB,MAAM,QAAQ,OAAO;CACrB,MAAM,SAAS,MAAM,cAAc,cAAc,MAAM,GAAG;EAAE,KAAK;EAAG,QAAQ;EAAG,MAAM;EAAG,OAAO;EAAG;CAClG,MAAM,UAAU,WAAW,MAAM;CACjC,MAAM,iBAAiB,iBAAiB,IAAI,OAAO,MAAM,QAAQ;AAEjE,MAAK,MAAM,UAAU,YAAY;EAC/B,MAAM,QAAQ,OAAO,SAAS,OAAO;AACrC,MAAI,CAAC,OAAO,WAAY;AAIxB,QAAM,aAAa;GACjB,GAAG,MAAM,WAAW;GACpB,GAAG,iBAAiB,OAAO;GAC3B,OAAO,MAAM,WAAW;GACxB,QAAQ,MAAM,WAAW;GAC1B;;;;;;;;AAaL,SAAS,0BAA0B,MAAoB;AACrD,MAAK,iBAAiB,KAAK;AAC3B,MAAK,iBAAiB,KAAK;CAE3B,MAAM,UAAU,KAAK;AACrB,KAAI,CAAC,SAAS;AACZ,OAAK,aAAa;AAClB,OAAK,aAAa;AAClB,OAAK,MAAM,SAAS,KAAK,SACvB,2BAA0B,MAAM;AAElC;;AAIF,MAAK,aAAa;EAChB,GAAG,QAAQ;EACX,GAAG,QAAQ;EACX,OAAO,QAAQ;EACf,QAAQ,QAAQ;EACjB;AACD,MAAK,aAAa,KAAK;AAEvB,MAAK,MAAM,SAAS,KAAK,SACvB,2BAA0B,MAAM;;;;;;;;;AA8BpC,SAAgB,uBAAuB,MAAgC;CACrE,IAAI,YAAY;CAChB,IAAI,YAAY;CAEhB,SAAS,KAAK,MAAoB;EAChC,MAAM,QAAQ,KAAK;AACnB,MAAI,MAAM,aAAa,SAAU,aAAY;AAC7C,MAAI,MAAM,aAAa,SAAU,aAAY;AAE7C,MAAI,aAAa,UAAW;AAC5B,OAAK,MAAM,SAAS,KAAK,UAAU;AACjC,QAAK,MAAM;AACX,OAAI,aAAa,UAAW;;;AAIhC,MAAK,KAAK;AACV,QAAO;EAAE;EAAW;EAAW;;;;;;;;;;;;;;;AClmCjC,MAAM,eAAsB;;AAG5B,SAAgB,iBAAwB;AACtC,QAAO,cAAc,SAAS,IAAI,cAAc,cAAc,SAAS,KAAM;;AAsB/E,IAAI,oBAAsC;;AAG1C,SAAgB,oBAAoB,OAA+B;AACjE,qBAAoB;;;AAItB,SAAgB,sBAAwC;AACtD,QAAO;;;;;;;;;;;;;AAkBT,MAAM,gBAAyB,EAAE;;AAGjC,SAAgB,iBAAiB,OAAoB;AACnD,eAAc,KAAK,MAAM;;;AAI3B,SAAgB,kBAAwB;AACtC,eAAc,KAAK;;;;;;;;;;;;;;;;UCpF4B;AAcjD,MAAM,cAAsC;CAC1C,OAAO;CACP,KAAK;CACL,OAAO;CACP,QAAQ;CACR,MAAM;CACN,SAAS;CACT,MAAM;CACN,OAAO;CACP,MAAM;CACN,MAAM;CACN,aAAa;CACb,WAAW;CACX,aAAa;CACb,cAAc;CACd,YAAY;CACZ,eAAe;CACf,YAAY;CACZ,aAAa;CACd;;;;;;AAOD,SAAS,YACP,IACA,IACA,GACqC;AACrC,QAAO;EACL,GAAG,KAAK,MAAM,GAAG,KAAK,IAAI,KAAK,GAAG,IAAI,EAAE;EACxC,GAAG,KAAK,MAAM,GAAG,KAAK,IAAI,KAAK,GAAG,IAAI,EAAE;EACxC,GAAG,KAAK,MAAM,GAAG,KAAK,IAAI,KAAK,GAAG,IAAI,EAAE;EACzC;;;;;;AAOH,SAAgB,WAAW,OAAsB;AAK/C,KAAI,UAAU,aAAa,UAAU,eAAgB,QAAO;AAG5D,KAAI,UAAU,WAAY,QAAO;AAMjC,KAAI,MAAM,WAAW,OAAO,IAAI,MAAM,SAAS,IAAI,EAAE;EACnD,MAAM,QAAQ,MAAM,MAAM,GAAG,GAAG;EAEhC,MAAM,OAAiB,EAAE;EACzB,IAAI,QAAQ;EACZ,IAAI,QAAQ;AACZ,OAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,IAChC,KAAI,MAAM,OAAO,IAAK;WACb,MAAM,OAAO,IAAK;WAClB,MAAM,OAAO,OAAO,UAAU,GAAG;AACxC,QAAK,KAAK,MAAM,MAAM,OAAO,EAAE,CAAC,MAAM,CAAC;AACvC,WAAQ,IAAI;;AAGhB,OAAK,KAAK,MAAM,MAAM,MAAM,CAAC,MAAM,CAAC;AAEpC,MAAI,KAAK,WAAW,GAAG;GACrB,MAAM,KAAK,WAAW,KAAK,GAAI;GAC/B,MAAM,KAAK,WAAW,KAAK,GAAI;GAC/B,MAAM,YAAY,KAAK;GAGvB,IAAI;AACJ,OAAI,UAAU,SAAS,IAAI,CACzB,KAAI,OAAO,WAAW,UAAU,MAAM,GAAG,GAAG,CAAC,GAAG;OAEhD,KAAI,OAAO,WAAW,UAAU;AAIlC,OACE,OAAO,QACP,OAAO,QACP,OAAO,OAAO,YACd,OAAO,OAAO,YACd,CAAC,OAAO,MAAM,EAAE,CAEhB,QAAO,YAAY,IAAI,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,EAAE,CAAC,CAAC;AAEzD,UAAO;;;AAQX,KAAI,MAAM,WAAW,IAAI,EAAE;AAKzB,MAAI,qBAAqB,KAAK,OAAQ,QAAO;EAC7C,MAAM,WAAW,kBAAkB,OAAO,gBAAgB,CAAC;AAC3D,MAAI,YAAY,aAAa,MAAO,QAAO,WAAW,SAAS;AAC/D,SAAO;;AAGT,KAAI,SAAS,YACX,QAAO,YAAY;AAIrB,KAAI,MAAM,WAAW,IAAI,EAAE;EACzB,MAAM,MAAM,MAAM,MAAM,EAAE;AAC1B,MAAI,IAAI,WAAW,EAIjB,QAAO;GAAE,GAHC,OAAO,SAAS,IAAI,KAAM,IAAI,IAAK,GAAG;GAGpC,GAFF,OAAO,SAAS,IAAI,KAAM,IAAI,IAAK,GAAG;GAEjC,GADL,OAAO,SAAS,IAAI,KAAM,IAAI,IAAK,GAAG;GAC9B;AAEpB,MAAI,IAAI,WAAW,EAIjB,QAAO;GAAE,GAHC,OAAO,SAAS,IAAI,MAAM,GAAG,EAAE,EAAE,GAAG;GAGlC,GAFF,OAAO,SAAS,IAAI,MAAM,GAAG,EAAE,EAAE,GAAG;GAE/B,GADL,OAAO,SAAS,IAAI,MAAM,GAAG,EAAE,EAAE,GAAG;GAC5B;;CAKtB,MAAM,WAAW,MAAM,MAAM,mDAAmD;AAChF,KAAI,SACF,QAAO;EACL,GAAG,OAAO,SAAS,SAAS,IAAK,GAAG;EACpC,GAAG,OAAO,SAAS,SAAS,IAAK,GAAG;EACpC,GAAG,OAAO,SAAS,SAAS,IAAK,GAAG;EACrC;CAIH,MAAM,eAAe,MAAM,MAAM,+BAA+B;AAChE,KAAI,aACF,QAAO,OAAO,SAAS,aAAa,IAAK,GAAG;AAG9C,QAAO;;;;;;AAWT,MAAM,UAAqE;CACzE,QAAQ;EACN,SAAS;EACT,UAAU;EACV,YAAY;EACZ,aAAa;EACb,YAAY;EACZ,UAAU;EACX;CACD,QAAQ;EACN,SAAS;EACT,UAAU;EACV,YAAY;EACZ,aAAa;EACb,YAAY;EACZ,UAAU;EACX;CACD,OAAO;EACL,SAAS;EACT,UAAU;EACV,YAAY;EACZ,aAAa;EACb,YAAY;EACZ,UAAU;EACX;CACD,MAAM;EACJ,SAAS;EACT,UAAU;EACV,YAAY;EACZ,aAAa;EACb,YAAY;EACZ,UAAU;EACX;CACD,cAAc;EACZ,SAAS;EACT,UAAU;EACV,YAAY;EACZ,aAAa;EACb,YAAY;EACZ,UAAU;EACX;CACD,cAAc;EACZ,SAAS;EACT,UAAU;EACV,YAAY;EACZ,aAAa;EACb,YAAY;EACZ,UAAU;EACX;CACD,SAAS;EACP,SAAS;EACT,UAAU;EACV,YAAY;EACZ,aAAa;EACb,YAAY;EACZ,UAAU;EACX;CACF;;;;AAKD,SAAgB,eAAe,OAA6C;AAC1E,KAAI,SAAS,OAAO,UAAU,UAAU;EAGtC,MAAM,MAAM;EACZ,MAAM,gBAAgB,IAAI,OAAO,IAAI,cAAc;EACnD,MAAM,eAAe,IAAI,QAAQ,IAAI,YAAY;AACjD,SAAO;GACL,SAAS,IAAI,WAAW;GACxB,UAAU,IAAI,YAAY;GAC1B,YAAY,IAAI,cAAc;GAC9B,aAAa,IAAI,eAAe;GAChC,YAAY;GACZ,UAAU;GACV,kBAAkB,IAAI,UAAU,IAAI,WAAW,gBAAgB,IAAI,SAAS,KAAA;GAC5E,eAAe,IAAI,SAAS,IAAI,UAAU,eAAe,IAAI,QAAQ,KAAA;GACtE;;AAEH,QAAO,QAAQ,SAAS;;;;;;;;;;;;;AAkB1B,SAAS,iBAAiB,OAA2B,MAA2B;AAC9E,KAAI,CAAC,MAAO;CACZ,MAAM,QAAQ,wBAAwB,OAAO,gBAAgB,CAAC;AAC9D,KAAI,CAAC,MAAO;AACZ,MAAK,MAAM,KAAK,MAAO,MAAK,IAAI,EAAE;;;;;AAMpC,SAAgB,aAAa,OAAyB;CAEpD,IAAI;AACJ,KAAI,MAAM,mBAAmB,KAAA,EAC3B,kBAAiB,MAAM;UACd,MAAM,UACf,kBAAiB;CAInB,IAAI,OAAO,MAAM;CACjB,IAAI,MAAM,MAAM,OAAO,MAAM;CAC7B,IAAI,SAAS,MAAM;CACnB,IAAI,YAAY,MAAM,aAAa,CAAC,CAAC;CACrC,IAAI,gBAAgB,MAAM;CAC1B,IAAI,UAAU,MAAM;AAMpB,KAAI,qBAAqB,KAAK,QAAQ;EACpC,MAAM,4BAAY,IAAI,KAAe;AACrC,mBAAiB,MAAM,OAAO,UAAU;AACxC,mBAAiB,MAAM,iBAAiB,UAAU;AAClD,MAAI,UAAU,IAAI,OAAO,CAAE,QAAO;AAClC,MAAI,UAAU,IAAI,MAAM,CAAE,OAAM;AAChC,MAAI,UAAU,IAAI,SAAS,CAAE,UAAS;AACtC,MAAI,UAAU,IAAI,YAAY,EAAE;AAC9B,eAAY;AACZ,OAAI,CAAC,eAAgB,kBAAiB;;AAExC,MAAI,UAAU,IAAI,gBAAgB,CAAE,iBAAgB;AACpD,MAAI,UAAU,IAAI,UAAU,CAAE,WAAU;;AAG1C,QAAO;EACL,IAAI,MAAM,QAAQ,WAAW,MAAM,MAAM,GAAG;EAC5C,IAAI,MAAM,kBAAkB,WAAW,MAAM,gBAAgB,GAAG;EAChE,gBAAgB,MAAM,iBAAiB,WAAW,MAAM,eAAe,GAAG;EAC1E,OAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACF;;;;;;;;;;AAeH,SAAgB,aAAa,MAAc,KAA+B;AACxE,KAAI,IAAK,QAAO,IAAI,SAAS,iBAAiB,KAAK;AACnD,QAAO,iBAAiB,KAAK;;;;aC3Vb;AA8BlB,MAAMC,QAAM,aAAa,kBAAkB;;AAO3C,IAAI,wBAAwC;CAC1C,MAAM,MACJ,OAAO,YAAY,cAAc,QAAQ,IAAI,qBAAqB,aAAa,GAAG,KAAA;AACpF,KAAI,QAAQ,YAAY,QAAQ,UAAU,QAAQ,QAAS,QAAO;AAClE,QAAO;IACL;;;;AAKJ,SAAS,oBAAoC;AAC3C,QAAO;;AAWT,MAAM,oCAAoB,IAAI,KAAa;;AAG3C,SAAS,sBACP,GACQ;AACR,KAAI,MAAM,QAAQ,MAAM,KAAA,EAAW,QAAO;AAC1C,KAAI,OAAO,MAAM,UAAU;AAEzB,MAAI,IAAI,UAAW;GACjB,MAAM,IAAK,KAAK,KAAM;GACtB,MAAM,IAAK,KAAK,IAAK;GACrB,MAAM,IAAI,IAAI;AACd,UAAO,IAAI,EAAE,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,GAAG,EAAE,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,GAAG,EAAE,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;;AAqBhH,SAlBsC;GACpC,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACN,CACY,MAAM,WAAW,EAAE;;AAElC,QAAO,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE,EAAE;;;;;;;;AASlC,SAAgB,0BAAgC;AAC9C,mBAAkB,OAAO;;;;;;;;;AA+B3B,SAAS,YAAY,OAA6B;CAChD,MAAM,QAAkB,EAAE;AAG1B,KAAI,MAAM,OAAO;EACf,MAAM,QAAQ,WAAW,MAAM,MAAM;AACrC,MAAI,UAAU,KACZ,KAAI,OAAO,UAAU,SACnB,OAAM,KAAK,QAAQ,QAAQ;MAE3B,OAAM,KAAK,QAAQ,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,IAAI;;AAUzD,KAAI,MAAM,KAAM,OAAM,KAAK,IAAI;AAC/B,KAAI,MAAM,IAAK,OAAM,KAAK,IAAI;AAC9B,KAAI,MAAM,OAAQ,OAAM,KAAK,IAAI;AAEjC,KAAI,MAAM,eAQR,OAAM,KAPmC;EACvC,QAAQ;EACR,QAAQ;EACR,OAAO;EACP,QAAQ;EACR,QAAQ;EACT,CACmB,MAAM,mBAAmB,IAAI;UACxC,MAAM,UACf,OAAM,KAAK,IAAI;AAKjB,KAAI,MAAM,gBAAgB;EACxB,MAAM,kBACJ,MAAM,mBAAmB,kBAAkB,MAAM,mBAAmB,YAChE,MAAM,QACN,MAAM;AACZ,MAAI,iBAAiB;GACnB,MAAM,UAAU,WAAW,gBAAgB;AAC3C,OAAI,YAAY,KACd,KAAI,OAAO,YAAY,SACrB,OAAM,KAAK,QAAQ,UAAU;OAE7B,OAAM,KAAK,QAAQ,QAAQ,EAAE,GAAG,QAAQ,EAAE,GAAG,QAAQ,IAAI;;;AAKjE,KAAI,MAAM,QAAS,OAAM,KAAK,IAAI;AAClC,KAAI,MAAM,cAAe,OAAM,KAAK,IAAI;AAExC,KAAI,MAAM,WAAW,EACnB,QAAO;AAGT,QAAO,QAAQ,MAAM,KAAK,IAAI,CAAC;;;;;;AAOjC,SAAS,kBAAkB,QAAsB,YAAqC;AAOpF,QAAO;EACL,QAHuB,WAAW,UAAU,aAAa,WAAW,UAAU,iBACjC,OAAO,QAAQ,WAAW,UAEzC,OAAO;EACrC,iBAAiB,WAAW,mBAAmB,OAAO;EACtD,MAAM,WAAW,QAAQ,OAAO;EAChC,KAAK,WAAW,OAAO,WAAW,YAAY,OAAO;EACrD,QAAQ,WAAW,UAAU,OAAO;EACpC,WACG,WAAW,aAAc,WAAmB,iBAAkB,OAAO,OAAO;EAC/E,gBAAiB,WAAmB,kBAAkB,OAAO;EAC7D,gBAAiB,WAAmB,kBAAkB,OAAO;EAC7D,SAAS,WAAW,WAAW,OAAO;EACtC,eAAe,WAAW,iBAAiB,OAAO;EACnD;;;;;;;;;;AAWH,SAAS,mBACP,MACA,YACA,aACQ;AACR,KAAI,CAAC,KACH,QAAO;CAGT,MAAM,YAAY,YAAY,WAAW;CACzC,MAAM,aAAa,YAAY,YAAY;AAG3C,KAAI,CAAC,UACH,QAAO;AAKT,QAAO,GAAG,YAAY,KAAK,SAAS;;;;;;;;;;;;;;;;;;AAqHtC,SAAS,kBACP,MACA,gBAA8B,EAAE,EAChC,SAAS,GACT,iBACA,KACY;AAEZ,KAAI,KAAK,gBAAgB,KAAA,GAAW;EAClC,IAAI,OAAO,KAAK;AAEhB,MAAI,oBAAoB,KAAA;OACR,aAAa,MAAM,IAAI,GACzB,gBAEV,SADgB,MAAM,IAAI,SAAS,eAAe,cACnC,MAAM,gBAAgB;;EAOzC,MAAM,WAAW,aAAa,MAAM,IAAI;AACxC,SAAO;GAAE;GAAM,YAAY,EAAE;GAAE,YAAY,EAAE;GAAE;GAAU;;CAG3D,IAAI,SAAS;CACb,MAAM,aAA0B,EAAE;CAClC,MAAM,aAA0B,EAAE;CAClC,IAAI,gBAAgB;CACpB,IAAI,wBAAwB;AAE5B,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,SAAS,QAAQ,KAAK;EAC7C,MAAM,QAAQ,KAAK,SAAS;AAE5B,MAAI,oBAAoB,KAAA,KAAa,yBAAyB,gBAAiB;EAG/E,MAAM,cACJ,oBAAoB,KAAA,IAAY,kBAAkB,wBAAwB,KAAA;AAE5E,MAAI,MAAM,SAAS,kBAAkB,MAAM,SAAS,CAAC,MAAM,YAAY;GACrE,MAAM,aAAa,MAAM;GACzB,MAAM,eAAe,kBAAkB,eAAe,WAAW;GAGjE,MAAM,cAAc,kBAAkB,OAAO,cAAc,eAAe,aAAa,IAAI;GAK3F,MAAM,iBAAkB,WAAmB;AAC3C,OAAI,kBAAkB,YAAY,KAAK,SAAS,EAC9C,aAAY,OAAO,eAAe,YAAY,MAAM,EAAE;GAIxD,MAAM,aAAa,mBAAmB,YAAY,MAAM,cAAc,cAAc;AACpF,aAAU;AAKV,OAAI,aAAa,iBAAiB;IAChC,MAAM,KAAK,WAAW,aAAa,gBAAgB;AACnD,QAAI,OAAO;SACL,YAAY,WAAW,EACzB,YAAW,KAAK;MACd,OAAO;MACP,KAAK,gBAAgB,YAAY;MACjC;MACD,CAAC;;cAGG,WAAW,oBAAoB,MAAM,YAAY,WAAW,EAIrE,YAAW,KAAK;IACd,OAAO;IACP,KAAK,gBAAgB,YAAY;IACjC,IAAI;IACL,CAAC;AAIJ,OAAI,YAAY,WAAW,EACzB,YAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,gBAAgB,YAAY;IAClC,CAAC;AAIJ,cAAW,KAAK,GAAG,YAAY,WAAW;AAC1C,cAAW,KAAK,GAAG,YAAY,WAAW;AAG1C,oBAAiB,YAAY;AAC7B,4BAAyB,YAAY;SAChC;GAEL,MAAM,cAAc,kBAAkB,OAAO,eAAe,eAAe,aAAa,IAAI;AAC5F,aAAU,YAAY;AACtB,cAAW,KAAK,GAAG,YAAY,WAAW;AAC1C,cAAW,KAAK,GAAG,YAAY,WAAW;AAC1C,oBAAiB,YAAY;AAC7B,4BAAyB,YAAY;;;AAIzC,QAAO;EAAE,MAAM;EAAQ;EAAY;EAAY,UAAU;EAAuB;;;;;;;;;;;;;;;;;AAkBlF,SAAS,sBACP,QACA,GACA,GACA,UACA,eACA,aACA,YACA,KACM;AACN,KAAI,WAAW,WAAW,EAAG;AAC7B,KAAI,IAAI,KAAK,KAAK,OAAO,OAAQ;CAGjC,MAAM,SAAS,mBAAmB;CAClC,MAAM,WAAW,MAAM,IAAI,SAAS,gBAAgB;AAIpD,MAAK,MAAM,OAAO,YAAY;EAE5B,MAAM,eAAe,KAAK,IAAI,IAAI,OAAO,cAAc;EACvD,MAAM,aAAa,KAAK,IAAI,IAAI,KAAK,YAAY;AACjD,MAAI,gBAAgB,WAAY;EAKhC,MAAM,WAAW,eAAe;EAChC,MAAM,SAAS,aAAa;EAI5B,IAAI,MAAM;EACV,MAAM,YAAY,eAAe,QAAQ,SAAS,GAAG,eAAe,SAAS,GAAG,SAAS;AAEzF,OAAK,MAAM,YAAY,WAAW;GAChC,MAAM,SAAS,SAAS,SAAS;AACjC,OAAI,WAAW,EAAG;GAElB,MAAM,gBAAgB,MAAM;AAC5B,OAAI,iBAAiB,YAAY,gBAAgB,QAAQ;AAGvD,WAAO,aAAa,KAAK,GAAG,OAAO;AACnC,WAAO,KAAK,IAAI;AAChB,WAAO,QAAQ,KAAK,GAAG,OAAO;AAC9B,QAAI,WAAW,KAAK,MAAM,IAAI,OAAO,OAAO;AAC1C,YAAO,aAAa,MAAM,GAAG,GAAG,OAAO;AACvC,YAAO,KAAK,IAAI;AAChB,YAAO,QAAQ,MAAM,GAAG,GAAG,OAAO;;;AAItC,UAAO;AACP,OAAI,MAAM,KAAK,OAAQ;;;;;;;AAQ7B,SAAS,eAAe,MAAsB;AAC5C,QAAO,KACJ,QAAQ,4BAA4B,GAAG,CACvC,QAAQ,sCAAsC,GAAG,CACjD,QAAQ,gBAAgB,GAAG,CAC3B,QAAQ,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;AAqB5B,SAAS,sBACP,cACA,gBACA,KACuC;CAIvC,MAAM,cAFgB,QAAQ,aAAa,GAAG,eAAe,aAAa,GAAG,cAE5C,QAAQ,OAAO,OAAO;CAEvD,MAAM,SAAgD,EAAE;CACxD,IAAI,aAAa;CACjB,IAAI,gBAAgB;AAEpB,MAAK,MAAM,QAAQ,gBAAgB;EACjC,MAAM,YAAY,QAAQ,KAAK,GAAG,eAAe,KAAK,GAAG;EAGzD,MAAM,YAAY,cAAc,YAAY,WAAW,WAAW;AAIlE,MAAI,YAAY,YAAY;GAC1B,MAAM,UAAU,WAAW,MAAM,YAAY,UAAU;AACvD,oBAAiB,aAAa,SAAS,IAAI;;EAI7C,MAAM,mBAAmB,aAAa,WAAW,IAAI;AACrD,SAAO,KAAK;GAAE,OAAO;GAAe,KAAK,gBAAgB;GAAkB,CAAC;AAI5E,eAAa,YADG,KAAK,IAAI,UAAU,QAAQ,WAAW,SAAS,UAAU;AAEzE,mBAAiB;;AAGnB,QAAO;;;;;;;;;AAUT,SAAS,cAAc,YAAoB,WAAmB,YAA4B;AACxF,KAAI,UAAU,WAAW,GAAG;EAE1B,IAAI,MAAM;AACV,SAAO,MAAM,WAAW,UAAU,WAAW,SAAS,KACpD;AAEF,SAAO;;AAKT,KAAI,WAAW,WAAW,WAAW,WAAW,CAC9C,QAAO;CAMT,MAAM,cAAc,UAAU,QADb,IAC8B;CAC/C,MAAM,kBAAkB,cAAc,IAAI,UAAU,MAAM,GAAG,YAAY,GAAG;AAE5E,KAAI,mBAAmB,WAAW,WAAW,iBAAiB,WAAW,CACvE,QAAO;CAIT,IAAI,MAAM;AACV,QAAO,MAAM,WAAW,QAAQ;EAC9B,MAAM,KAAK,WAAW;AACtB,MAAI,OAAO,QAAQ,OAAO,KAAK;AAC7B;AACA;;AAGF,MAAI,WAAW,WAAW,WAAW,IAAI,CACvC,QAAO;AAGT,MAAI,mBAAmB,WAAW,WAAW,iBAAiB,IAAI,CAChE,QAAO;AAET;;AAIF,QAAO;;;;;;;;;AAcT,SAAgB,gBACd,MACA,OACA,MACA,KACA,OAAO,MACG;AAGV,KAAI,SAAS,EACX,QAAO,EAAE;CAIX,MAAM,iBAAiB,KAAK,QAAQ,OAAO,OAAO;CAClD,MAAM,QAAQ,eAAe,MAAM,KAAK;AAGxC,KAAI,SAAS,QAAQ;EACnB,MAAM,UAAU,MAAM,IAAI,SAAS,eAAe;AAClD,SAAO,MAAM,KAAK,SAAS;AACzB,OAAI,aAAa,MAAM,IAAI,IAAI,MAAO,QAAO;AAC7C,UAAO,QAAQ,MAAM,MAAM;IAC3B;;AAQJ,KAAI,SAAS,QAAQ;EACnB,MAAM,UAAU,MAAM,IAAI,SAAS,eAAe;EAClD,MAAM,MAAgB,EAAE;AACxB,OAAK,MAAM,QAAQ,OAAO;AACxB,OAAI,SAAS,IAAI;AACf,QAAI,KAAK,GAAG;AACZ;;GAEF,IAAI,YAAY;AAIhB,UAAO,aAAa,WAAW,IAAI,GAAG,OAAO;IAC3C,MAAM,OAAO,QAAQ,WAAW,MAAM;AACtC,QAAI,KAAK,WAAW,EAAG;AACvB,QAAI,KAAK,KAAK;AACd,gBAAY,UAAU,MAAM,KAAK,OAAO;;AAE1C,OAAI,KAAK,UAAU;;AAErB,SAAO;;AAIT,KAAI,SAAS,SAAS,SAAS,kBAAkB,SAAS,WACxD,QAAO,MAAM,KAAK,SAAS,aAAa,MAAM,OAAO,OAAO,IAAI,CAAC;AAGnE,KAAI,SAAS,iBACX,QAAO,MAAM,KAAK,SAAS,aAAa,MAAM,OAAO,SAAS,IAAI,CAAC;AAGrE,KAAI,SAAS,kBACX,QAAO,MAAM,KAAK,SAAS,aAAa,MAAM,OAAO,UAAU,IAAI,CAAC;AAKtE,KAAI,SAAS,OAGX,QAAO,YAAY,gBADF,kBAAkB,gBADlB,KAAK,UAAU,eAAe,KAAK,IAAI,SAAS,IAAI,cACT,EACf,MAAM;AAYrD,KAAI,IAAK,QAAO,IAAI,SAAS,SAAS,gBAAgB,OAAO,MAAM,KAAK;AACxE,QAAO,SAAS,gBAAgB,OAAO,MAAM,KAAK;;;;;AAMpD,SAAgB,aACd,MACA,OACA,MACA,KACQ;AAER,KADkB,aAAa,MAAM,IAAI,IACxB,MAAO,QAAO;CAE/B,MAAM,WAAW;CACjB,MAAM,iBAAiB,QAAQ;AAE/B,KAAI,kBAAkB,EACpB,QAAO,QAAQ,IAAI,WAAW;CAGhC,MAAM,UAAU,MAAM,IAAI,SAAS,eAAe;CAClD,MAAM,aAAa,MAAM,IAAI,SAAS,sBAAsB;AAE5D,KAAI,SAAS,MACX,QAAO,QAAQ,MAAM,eAAe,GAAG;AAGzC,KAAI,SAAS,QACX,QAAO,WAAW,WAAW,MAAM,eAAe;CAIpD,MAAM,YAAY,KAAK,MAAM,iBAAiB,EAAE;CAChD,MAAM,YAAY,QAAQ,MAAM,UAAU;CAC1C,MAAM,UAAU,WAAW,MAAM,iBAAiB,UAAU;AAC5D,QAAO,YAAY,WAAW;;;;;;;;;;;;AAiBhC,SAAgB,eACd,QACA,GACA,GACA,MACA,WACA,QACA,aACA,KACM;AAEN,KAAI,QAAQ,KAAK,EAAE;AACjB,qBAAmB,QAAQ,GAAG,GAAG,MAAM,WAAW,QAAQ,aAAa,IAAI;AAC3E;;AAGF,iBAAgB,QAAQ,eAAe,KAAK,EAAE,GAAG,GAAG,WAAW,QAAQ,aAAa,IAAI;;;;;;AAO1F,SAAS,qBACP,QACA,GACA,GACA,MACA,WACA,QACA,aACA,KACA,QACQ;AACR,KAAI,QAAQ,KAAK,CACf,QAAO,yBAAyB,QAAQ,GAAG,GAAG,MAAM,WAAW,QAAQ,aAAa,KAAK,OAAO;AAElG,QAAO,gBACL,QACA,eAAe,KAAK,EACpB,GACA,GACA,WACA,QACA,aACA,KACA,OACD;;;;;;;;;;;;;;;;;;;AAoBH,SAAS,gBACP,QACA,WACA,UACA,GACA,OACA,QACA,aACA,KACA,QACQ;CACR,IAAI,MAAM;CAEV,MAAM,YAAY,WAAW,KAAA,IAAY,KAAK,IAAI,QAAQ,OAAO,MAAM,GAAG,OAAO;CAEjF,MAAM,WAAW,WAAW,KAAA,IAAY,KAAK,IAAI,QAAQ,EAAE,GAAG;CAC9D,MAAM,WAAW,MAAM,IAAI,SAAS,gBAAgB;AAEpD,MAAK,MAAM,YAAY,WAAW;AAChC,MAAI,OAAO,UAAW;EAEtB,MAAM,QAAQ,SAAS,SAAS;AAChC,MAAI,UAAU,EAAG;AAMjB,MAAI,MAAM,SAAS,UAAU;AAC3B,UAAO;AACP;;AAMF,MAAI,MAAM,UAAU;AAGlB,SAAM;AAEN;;EAQF,MAAM,aACJ,MAAM,OAAO,OACT,MAAM,KACN,gBAAgB,KAAA,IACd,cACA,OAAO,UAAU,KAAK,EAAE;AAShC,MAAI,UAAU,KAAK,MAAM,KAAK,WAAW;AACvC,UAAO,QAAQ,KAAK,GAAG;IACrB,MAAM;IACN,IAAI,MAAM;IACV,IAAI;IACJ,gBAAgB,MAAM,kBAAkB;IACxC,OAAO,MAAM;IACb,MAAM;IACN,cAAc;IACd,WAAW,MAAM;IAClB,CAAC;AACF,UAAO;AACP;;EAIF,MAAM,aAAa,UAAU,IAAI,wBAAwB,SAAS,GAAG;AAErE,SAAO,QAAQ,KAAK,GAAG;GACrB,MAAM;GACN,IAAI,MAAM;GACV,IAAI;GACJ,gBAAgB,MAAM,kBAAkB;GACxC,OAAO,MAAM;GACb,MAAM,UAAU;GAChB,cAAc;GACd,WAAW,MAAM;GAClB,CAAC;AAEF,MAAI,UAAU,KAAK,MAAM,IAAI,OAAO,OAAO;GACzC,MAAM,cACJ,MAAM,OAAO,OACT,MAAM,KACN,gBAAgB,KAAA,IACd,cACA,OAAO,UAAU,MAAM,GAAG,EAAE;AACpC,UAAO,QAAQ,MAAM,GAAG,GAAG;IACzB,MAAM;IACN,IAAI,MAAM;IACV,IAAI;IACJ,gBAAgB,MAAM,kBAAkB;IACxC,OAAO,MAAM;IACb,MAAM;IACN,cAAc;IACd,WAAW,MAAM;IAClB,CAAC;AACF,UAAO;QAEP,QAAO;;AAIX,QAAO;;;;;;AAOT,SAAgB,mBACd,QACA,GACA,GACA,MACA,WACA,QACA,aACA,KACM;AACN,0BAAyB,QAAQ,GAAG,GAAG,MAAM,WAAW,QAAQ,aAAa,IAAI;;;;;AAMnF,SAAS,yBACP,QACA,GACA,GACA,MACA,WACA,QACA,aACA,KACA,QACQ;CACR,MAAM,WAAW,cAAc,KAAK;CACpC,IAAI,MAAM;AAEV,MAAK,MAAM,WAAW,UAAU;EAE9B,MAAM,QAAQ,eAAe,WAAW,QAAQ;EAKhD,MAAM,0BAA0B,KAAK,kBAAkB,mBAAmB;AAC1E,MACE,4BAA4B,YAC5B,CAAC,QAAQ,cACT,QAAQ,OAAO,KAAA,KACf,QAAQ,OAAO,MACf;GAEA,MAAM,gBAAgB,MAAM,OAAO,QAAQ,OAAO,UAAU,KAAK,EAAE,GAAG;AAGtE,OAFsB,UAAU,OAAO,QAAQ,kBAAkB,MAE9C;IACjB,MAAM,UAAU,QAAQ,KAAK,MAAM,GAAG,GAAG;IACzC,MAAM,UAAU,sBAAsB,QAAQ,GAAG;IACjD,MAAM,YACJ,UAAU,OAAO,OACb,WAAW,sBAAsB,UAAU,GAAG,KAC9C,YAAY,sBAAsB,cAAc;IAEtD,MAAM,cAAc,KAAK,SAAS,KAAK,KAAK,MAAM,GAAG,GAAG,GAAG,MAAM;IACjE,MAAM,MAAM,qCAAqC,IAAI,GAAG,EAAE,cAAc,QAAQ,cAAc,UAAU,WAAW,UAAU,QAAQ,KAAK,SAAS,KAAK,MAAM,GAAG,0BAA0B,KAAK,UAAU,YAAY,CAAC;AAEvN,QAAI,4BAA4B,QAC9B,OAAM,IAAI,MAAM,IAAI;IAGtB,MAAM,6BAA6B,KAAK,qBAAqB;IAC7D,MAAM,MAAM,GAAG,KAAK,UAAU,cAAc,CAAC,GAAG,QAAQ,GAAG,GAAG;AAC9D,QAAI,CAAC,2BAA2B,IAAI,IAAI,EAAE;AACxC,gCAA2B,IAAI,IAAI;AACnC,WAAI,OAAO,IAAI;;;;AAKrB,QAAM,gBACJ,QACA,eAAe,QAAQ,KAAK,EAC5B,KACA,GACA,OACA,QACA,aACA,KACA,OACD;;AAEH,QAAO;;;;;;;;;;;;;;;;AAuCT,SAAgB,YACd,MACA,SACA,UAA8B,EAAE,EACzB;CACP,MAAM,EAAE,sBAAsB,MAAM,mBAAmB,SAAS;CAEhE,MAAM,YAAY,KAAK,SAAS,EAAE;CAClC,MAAM,eAAe,QAAQ,SAAS,EAAE;CAGxC,MAAM,QAAmB,EAAE;AAG3B,KAAI,qBAAqB;EAEvB,MAAM,mBAAmB,UAAU,aAAa,UAAU;EAC1D,MAAM,sBAAsB,aAAa,aAAa,aAAa;AACnE,MAAI,oBAAoB,qBAAqB;AAC3C,SAAM,YAAY;AAElB,SAAM,iBAAiB,aAAa,kBAAkB,UAAU,kBAAkB;;AAEpF,QAAM,gBAAgB,aAAa,iBAAiB,UAAU;QACzD;AACL,QAAM,YAAY,aAAa,aAAa,UAAU;AACtD,QAAM,iBAAiB,aAAa,kBAAkB,UAAU;AAChE,QAAM,gBAAgB,aAAa,iBAAiB,UAAU;;AAIhE,KAAI,kBAAkB;AACpB,QAAM,OAAO,aAAa,QAAQ,UAAU;AAC5C,QAAM,MAAM,aAAa,OAAO,UAAU;AAC1C,QAAM,SAAS,aAAa,UAAU,UAAU;QAC3C;AACL,QAAM,OAAO,aAAa,QAAQ,UAAU;AAC5C,QAAM,MAAM,aAAa,OAAO,UAAU;AAC1C,QAAM,SAAS,aAAa,UAAU,UAAU;;AAIlD,OAAM,UAAU,aAAa;AAC7B,OAAM,SAAS,aAAa;AAC5B,OAAM,QAAQ,aAAa;AAE3B,QAAO;EAEL,IAAI,QAAQ,MAAM,KAAK;EACvB,IAAI,QAAQ,MAAM,KAAK;EAEvB,gBAAgB,QAAQ,kBAAkB,KAAK;EAC/C;EACD;;;;;;AAWH,SAAS,eACP,MACA,SACA,UAA8B,EAAE,EACzB;CACP,MAAM,EAAE,sBAAsB,MAAM,mBAAmB,SAAS;CAGhE,IAAI,KAAY,KAAK;CACrB,IAAI,KAAY,KAAK;CACrB,IAAI,iBAAwB,KAAK,kBAAkB;AAEnD,KAAI,QAAQ,OAAO,KAAA,KAAa,QAAQ,OAAO,KAC7C,MAAK,iBAAiB,QAAQ,GAAG;AAEnC,KAAI,QAAQ,OAAO,KAAA,KAAa,QAAQ,OAAO,KAC7C,MAAK,iBAAiB,QAAQ,GAAG;AAEnC,KAAI,QAAQ,mBAAmB,KAAA,KAAa,QAAQ,mBAAmB,KACrE,kBAAiB,iBAAiB,QAAQ,eAAe;CAI3D,MAAM,eAA0B,EAAE;AAClC,KAAI,QAAQ,SAAS,KAAA,EAAW,cAAa,OAAO,QAAQ;AAC5D,KAAI,QAAQ,QAAQ,KAAA,EAAW,cAAa,MAAM,QAAQ;AAC1D,KAAI,QAAQ,WAAW,KAAA,EAAW,cAAa,SAAS,QAAQ;AAChE,KAAI,QAAQ,cAAc,KAAA,EACxB,cAAa,YAAY,QAAQ;AAEnC,KAAI,QAAQ,mBAAmB,KAAA,EAC7B,cAAa,iBAAiB,QAAQ;AAExC,KAAI,QAAQ,YAAY,KAAA,EAAW,cAAa,UAAU,QAAQ;CAGlE,MAAM,SAAS,YACb,MACA;EAAE;EAAI;EAAI;EAAgB,OAAO;EAAc,EAC/C;EAAE;EAAqB;EAAkB,CAC1C;AAGD,KAAI,QAAQ,UACV,QAAO,YAAY,QAAQ;AAG7B,QAAO;;;;;;AAOT,SAAS,iBAAiB,MAAqB;AAE7C,KAAI,QAAQ,SAIV,QAAO;EAAE,GAHE,QAAQ,KAAM;EAGb,GAFD,QAAQ,IAAK;EAET,GADL,OAAO;EACC;AAIpB,KAAI,OAAO,MAAO,QAAQ,MAAM,OAAO,MAAQ,QAAQ,MAAM,OAAO,GAElE,QAAO;AAIT,KAAI,QAAQ,MAAM,QAAQ,GACxB,QAAO,OAAO;AAIhB,KAAI,QAAQ,MAAM,QAAQ,GACxB,QAAO,OAAO;AAIhB,KAAI,QAAQ,MAAM,QAAQ,GACxB,QAAO,OAAO,KAAK;AAIrB,KAAI,QAAQ,OAAO,QAAQ,IACzB,QAAO,OAAO,MAAM;AAGtB,QAAO;;;;;;;;;AAcT,SAAgB,WACd,MACA,QACA,QACA,OACA,WACA,aACA,aACA,KACM;CACN,MAAM,EAAE,cAAc,eAAe;CACrC,MAAM,EAAE,GAAG,OAAO,WAAW;CAC7B,IAAI,EAAE,MAAM;AAGZ,MAAK;AAOL,KAAI,MAAM,oBAAoB,GAC5B,eAAc;AAIhB,KAAI,YAAY;AACd,MAAI,IAAI,UAAU,WAAW,OAAO,KAAK,WAAW,OAClD;AAEF,MAAI,WAAW,SAAS,KAAA,KAAa,WAAW,UAAU,KAAA;OACpD,IAAI,SAAS,WAAW,QAAQ,KAAK,WAAW,MAClD;;;CASN,IAAI;AAMJ,MAJE,MAAM,SAAS,SACf,MAAM,SAAS,kBACf,MAAM,SAAS,cACf,MAAM,SAAS,WACI,QAAQ,GAAG;EAC9B,MAAM,cAAc,mBAAmB,KAAK;EAC5C,IAAI;AACJ,MAAI,YACF,aAAY,YAAY;OACnB;GACL,MAAM,YAAY,iBAAiB,KAAK;AACxC,gBAAa,UAAU,MAAM,MAAM,EAAE,UAAU,KAAK;AACpD,sBAAmB,MAAM,WAAW,UAAU;;AAEhD,qBAAmB,QAAQ,KAAK;;CAOlC,IAAI;CACJ,IAAI;CACJ,IAAI;CAMJ,MAAM,cAA4B;EAChC,OAAO,MAAM;EACb,iBAAiB,MAAM;EACvB,MAAM,MAAM;EACZ,KAAK,MAAM,OAAO,MAAM;EACxB,QAAQ,MAAM;EACd,WAAW,CAAC,EAAE,MAAM,aAAa,MAAM;EACvC,gBAAgB,MAAM;EACtB,gBAAgB,MAAM;EACtB,SAAS,MAAM;EACf,eAAe,MAAM;EACtB;CAOD,MAAM,eAAe,gBAAgB;CACrC,MAAM,kBAAkB,uBAAuB,MAAM,iBAAiB,aAAa;AACnF,KAAI,iBAAiB;AACnB,SAAO,gBAAgB;AACvB,eAAa,gBAAgB;AAC7B,eAAa,gBAAgB;QACxB;EACL,MAAM,YAAY,kBAAkB,MAAM,aAAa,GAAG,iBAAiB,IAAI;AAC/E,SAAO,UAAU;AACjB,eAAa,UAAU;AACvB,eAAa,UAAU;AACvB,yBAAuB,MAAM,WAAW,iBAAiB,aAAa;;CAKxE,MAAM,QAAQ,aAAa,MAAM;AACjC,KAAI,MAAM,OAAO,QAAQ,gBAAgB,KAAA,EACvC,OAAM,KAAK;AAMb,KAAI,MAAM,mBAAmB,kBAAkB,MAAM,mBAAmB,UACtE,OAAM,iBAAiB,MAAM;CAS/B,MAAM,OAAO,EAHX,MAAM,OAAO,QACb,WAAW,SAAS,KACnB,gBAAgB,KAAA,KAAa,gBAAgB;CAEhD,MAAM,oBAAoB,MAAM;CAEhC,IAAI;CACJ,IAAI;CAGJ,MAAM,YAAY,CAAC,oBAAoB,gBAAgB,MAAM,OAAO,MAAM,MAAM,KAAK,GAAG;AACxF,KAAI,WAAW;AACb,UAAQ,UAAU;AAClB,gBAAc,UAAU,iBAAiB,UAAU,cAAc,EAAE;QAC9D;AACL,UAAQ,gBAAgB,MAAM,OAAO,MAAM,MAAM,KAAK,KAAK;AAC3D,MAAI,kBACF,SAAQ,MAAM,KAAK,MAAM,UAAU,kBAAkB,MAAM,MAAM,CAAC;EAEpE,MAAM,kBAAkB,WAAW,SAAS,KAAK,WAAW,SAAS;AACrE,gBAAc,kBAAkB,sBAAsB,MAAM,OAAO,IAAI,GAAG,EAAE;AAC5E,MAAI,CAAC,kBACH,iBAAgB,MAAM,OAAO,MAAM,MAAM,MAAM,OAAO,aAAa,gBAAgB;;AAKvF,MAAK,IAAI,UAAU,GAAG,UAAU,MAAM,UAAU,UAAU,QAAQ,WAAW;EAC3E,MAAM,QAAQ,IAAI;AAElB,MAAI,eAAe,QAAQ,WAAW,OAAO,SAAS,WAAW,QAC/D;EAEF,MAAM,OAAO,MAAM;EAQnB,MAAM,cAAc,oBAAoB,OAAO,QAAQ,IAAI;EAC3D,MAAM,SACJ,cAAc,WAAW,cAAc,WAAW,UAAU,KAAA,IACxD,KAAK,IAAI,aAAa,WAAW,MAAM,GACvC;EAKN,MAAM,SACJ,cAAc,UAAU,cAAc,WAAW,SAAS,KAAA,IACtD,WAAW,OACX,KAAA;EACN,MAAM,SAAS,qBACb,QACA,GACA,OACA,MACA,OACA,QACA,aACA,KACA,OACD;EASD,MAAM,aAAa,WAAW,KAAA,IAAY,KAAK,IAAI,QAAQ,OAAO,GAAG;AACrE,MAAI,aAAa,QAAQ;GACvB,MAAM,UAAU,eAAe;AAC/B,QAAK,IAAI,KAAK,YAAY,KAAK,UAAU,KAAK,OAAO,OAAO,KAC1D,QAAO,QAAQ,IAAI,OAAO;IACxB,MAAM;IACN,IAAI,MAAM;IACV,IAAI;IACJ,gBAAgB;IAChB,OAAO;KACL,MAAM;KACN,KAAK;KACL,QAAQ;KACR,WAAW;KACX,SAAS;KACT,eAAe;KACf,OAAO;KACP,QAAQ;KACT;IACD,MAAM;IACN,cAAc;IACf,CAAC;;AAON,MAAI,WAAW,SAAS,KAAK,UAAU,YAAY,QAAQ;GACzD,MAAM,EAAE,OAAO,QAAQ,YAAY;AACnC,yBAAsB,QAAQ,GAAG,OAAO,MAAM,OAAO,KAAK,YAAY,IAAI;;;AAO9E,KAAI,WAAW,SAAS,KAAK,YAAY,SAAS,EAChD,oBAAmB,YAAY,aAAa,GAAG,GAAG,MAAM,QAAQ,OAAO;;;;;;;;;;;;;;AAgB3E,SAAS,mBACP,YACA,aACA,SACA,SACA,WACA,WACM;AACN,MAAK,MAAM,QAAQ,YAAY;EAC7B,MAAM,QAAwE,EAAE;AAEhF,OAAK,IAAI,UAAU,GAAG,UAAU,aAAa,UAAU,WAAW,WAAW;GAC3E,MAAM,aAAa,YAAY;AAC/B,OAAI,CAAC,WAAY;GAGjB,MAAM,eAAe,KAAK,IAAI,KAAK,OAAO,WAAW,MAAM;GAC3D,MAAM,aAAa,KAAK,IAAI,KAAK,KAAK,WAAW,IAAI;AACrD,OAAI,gBAAgB,WAAY;GAGhC,MAAM,QAAQ,WAAW,eAAe,WAAW;GACnD,MAAM,QAAQ,UAAU;GACxB,MAAM,YAAY,aAAa;AAE/B,SAAM,KAAK;IAAE,GAAG;IAAO,GAAG;IAAO,OAAO;IAAW,QAAQ;IAAG,CAAC;;AAGjE,OAAK,KAAK,cAAc,MAAM,SAAS,IAAI,QAAQ;;;;;;;;;;;;ACnjDvD,SAAgB,eAAe,OAAqC;AAClE,KAAI,MAAM,gBAAiB,QAAO,MAAM;AACxC,KAAI,MAAM,OAAO;EACf,MAAM,QAAQ,MAAM;EACpB,MAAM,aAAa,MAAM;AACzB,MAAI,OAAO,eAAe,SAAU,QAAO;EAC3C,MAAM,WAAW,MAAM;AACvB,MAAI,OAAO,aAAa,SAAU,QAAO;;;;;;AAY7C,SAAgB,UACd,OACA,QACA,QACA,OACA,WACA,aAAa,OACb,aACA,eAAe,OACf,aACM;CACN,MAAM,EAAE,cAAc,eAAe;CACrC,MAAM,EAAE,GAAG,OAAO,WAAW;CAE7B,MAAM,IAAI,OAAO,IAAI;AAGrB,KAAI,YAAY;AACd,MAAI,IAAI,UAAU,WAAW,OAAO,KAAK,WAAW,OAAQ;AAC5D,MAAI,WAAW,SAAS,KAAA,KAAa,WAAW,UAAU,KAAA;OACpD,IAAI,SAAS,WAAW,QAAQ,KAAK,WAAW,MAAO;;;CAa/D,MAAM,iBAAiB,eAAe,MAAM;AAC5C,KAAI,kBAAkB,CAAC,YAAY;EACjC,MAAM,KAAK,WAAW,eAAe;AAErC,MAAI,YAAY;GACd,MAAM,WAAW,KAAK,IAAI,GAAG,WAAW,IAAI;GAC5C,MAAM,gBAAgB,KAAK,IAAI,IAAI,QAAQ,WAAW,OAAO,GAAG;GAChE,IAAI,WAAW;GACf,IAAI,eAAe;AACnB,OAAI,WAAW,SAAS,KAAA,KAAa,WAAW,UAAU,KAAA,GAAW;AACnE,eAAW,KAAK,IAAI,GAAG,WAAW,KAAK;AACvC,mBAAe,KAAK,IAAI,IAAI,OAAO,WAAW,MAAM,GAAG;;AAEzD,OAAI,gBAAgB,KAAK,eAAe,EACtC,KAAI,aACF,QAAO,OAAO,UAAU,UAAU,cAAc,eAAe,GAAG;OAElE,QAAO,KAAK,UAAU,UAAU,cAAc,eAAe,EAAE,IAAI,CAAC;aAIpE,aACF,QAAO,OAAO,GAAG,GAAG,OAAO,QAAQ,GAAG;MAEtC,QAAO,KAAK,GAAG,GAAG,OAAO,QAAQ,EAAE,IAAI,CAAC;;AAM9C,KAAI,MAAM,YACR,cAAa,QAAQ,GAAG,GAAG,OAAO,QAAQ,OAAO,YAAY,aAAa,YAAY;;;;;AAW1F,SAAgB,aACd,QACA,GACA,GACA,OACA,QACA,OACA,YACA,aACA,aACM;CACN,MAAM,QAAQ,eAAe,MAAM,eAAe,SAAS;CAI3D,IAAI;AACJ,KAAI,MAAM,gBAAgB,kBAAkB,MAAM,gBAAgB,UAChE,SAAQ,MAAM,QAAQ,WAAW,MAAM,MAAM,GAAI,eAAe;KAEhE,SAAQ,MAAM,cAAc,WAAW,MAAM,YAAY,GAAG;CAK9D,MAAM,SAAS,MAAM,kBAAkB,WAAW,MAAM,gBAAgB,GAAI,eAAe;CAI3F,MAAM,cAAe,MAAmB;CACxC,MAAM,eAAe,cAAc,WAAW,YAAY,GAAG;CAC7D,MAAM,iBAAkB,MAAmB;CAC3C,MAAM,oBAAqB,MAAmB;CAC9C,MAAM,kBAAmB,MAAmB;CAC5C,MAAM,mBAAoB,MAAmB;CAC7C,MAAM,QAAQ,iBAAiB,WAAW,eAAe,GAAG;CAC5D,MAAM,WAAW,oBAAoB,WAAW,kBAAkB,GAAG;CACrE,MAAM,SAAS,kBAAkB,WAAW,gBAAgB,GAAG;CAC/D,MAAM,UAAU,mBAAmB,WAAW,iBAAiB,GAAG;CAElE,MAAM,UAAU,MAAM,cAAc;CACpC,MAAM,aAAa,MAAM,iBAAiB;CAC1C,MAAM,WAAW,MAAM,eAAe;CACtC,MAAM,YAAY,MAAM,gBAAgB;CAGxC,MAAM,gBAAgB,QAAyB;AAC7C,MAAI,CAAC,WAAY,QAAO,OAAO,KAAK,MAAM,OAAO;AACjD,SAAO,OAAO,WAAW,OAAO,MAAM,WAAW,UAAU,MAAM,OAAO;;CAI1E,MAAM,gBAAgB,QAAyB;AAC7C,MAAI,YAAY,SAAS,KAAA,KAAa,WAAW,UAAU,KAAA,EACzD,QAAO,OAAO,KAAK,MAAM,OAAO;AAClC,SAAO,OAAO,WAAW,QAAQ,MAAM,WAAW,SAAS,MAAM,OAAO;;AAI1E,KAAI,WAAW,aAAa,EAAE,EAAE;AAC9B,MAAI,YAAY,aAAa,EAAE,CAC7B,QAAO,QAAQ,GAAG,GAAG;GAAE,MAAM,MAAM;GAAS,IAAI;GAAO,IAAI;GAAO,CAAC;EACrE,MAAM,SAAS,WAAW,IAAI,IAAI;EAClC,MAAM,OAAO,YAAY,IAAI,QAAQ,IAAI,IAAI;AAC7C,OAAK,IAAI,MAAM,QAAQ,MAAM,QAAQ,MAAM,OAAO,OAAO,MACvD,KAAI,aAAa,IAAI,CACnB,QAAO,QAAQ,KAAK,GAAG;GAAE,MAAM,MAAM;GAAY,IAAI;GAAO,IAAI;GAAO,CAAC;AAE5E,MAAI,aAAa,IAAI,QAAQ,IAAI,OAAO,SAAS,aAAa,IAAI,QAAQ,EAAE,CAC1E,QAAO,QAAQ,IAAI,QAAQ,GAAG,GAAG;GAAE,MAAM,MAAM;GAAU,IAAI;GAAO,IAAI;GAAO,CAAC;;CAKpF,MAAM,gBAAgB,MAAM,iBAAiB,MAAM;CACnD,MAAM,YAAY,UAAU,IAAI,IAAI;CACpC,MAAM,UAAU,aAAa,IAAI,SAAS,IAAI,IAAI;AAClD,MAAK,IAAI,MAAM,WAAW,MAAM,SAAS,OAAO;AAC9C,MAAI,CAAC,aAAa,IAAI,CAAE;AACxB,MAAI,YAAY,aAAa,EAAE,CAC7B,QAAO,QAAQ,GAAG,KAAK;GAAE,MAAM,MAAM;GAAU,IAAI;GAAO,IAAI;GAAQ,CAAC;AACzE,MAAI,aAAa,IAAI,QAAQ,IAAI,OAAO,SAAS,aAAa,IAAI,QAAQ,EAAE,CAC1E,QAAO,QAAQ,IAAI,QAAQ,GAAG,KAAK;GAAE,MAAM;GAAe,IAAI;GAAO,IAAI;GAAS,CAAC;;CAKvF,MAAM,mBAAmB,MAAM,oBAAoB,MAAM;CACzD,MAAM,UAAU,IAAI,SAAS;AAC7B,KAAI,cAAc,aAAa,QAAQ,EAAE;AACvC,MAAI,YAAY,aAAa,EAAE,CAC7B,QAAO,QAAQ,GAAG,SAAS;GAAE,MAAM,MAAM;GAAY,IAAI;GAAO,IAAI;GAAU,CAAC;EAEjF,MAAM,SAAS,WAAW,IAAI,IAAI;EAClC,MAAM,OAAO,YAAY,IAAI,QAAQ,IAAI,IAAI;AAC7C,OAAK,IAAI,MAAM,QAAQ,MAAM,QAAQ,MAAM,OAAO,OAAO,MACvD,KAAI,aAAa,IAAI,CACnB,QAAO,QAAQ,KAAK,SAAS;GAAE,MAAM;GAAkB,IAAI;GAAO,IAAI;GAAU,CAAC;AAErF,MAAI,aAAa,IAAI,QAAQ,IAAI,OAAO,SAAS,aAAa,IAAI,QAAQ,EAAE,CAC1E,QAAO,QAAQ,IAAI,QAAQ,GAAG,SAAS;GACrC,MAAM,MAAM;GACZ,IAAI;GACJ,IAAI;GACL,CAAC;;;;;;;;;;;;;AAmBR,SAAgB,cACd,QACA,GACA,GACA,OACA,QACA,OACA,YACA,aACM;CACN,MAAM,QAAQ,eAAe,MAAM,gBAAgB,SAAS;CAC5D,MAAM,QAAQ,MAAM,eAAe,WAAW,MAAM,aAAa,GAAG;CACpE,MAAM,KAAK,MAAM,kBAAkB,WAAW,MAAM,gBAAgB,GAAI,eAAe;CACvF,MAAM,QAAQ,MAAM,kBAAkB,EAAE,KAAK,MAAM,GAAG,EAAE;CAGxD,MAAM,KAAK,IAAI;CACf,MAAM,KAAK,IAAI;CACf,MAAM,KAAK,QAAQ;CACnB,MAAM,KAAK,SAAS;CAGpB,MAAM,gBAAgB,QAAyB;AAC7C,MAAI,CAAC,WAAY,QAAO,OAAO,KAAK,MAAM,OAAO;AACjD,SAAO,OAAO,WAAW,OAAO,MAAM,WAAW,UAAU,MAAM,OAAO;;CAI1E,MAAM,gBAAgB,QAAyB;AAC7C,MAAI,YAAY,SAAS,KAAA,KAAa,WAAW,UAAU,KAAA,EACzD,QAAO,OAAO,KAAK,MAAM,OAAO;AAClC,SAAO,OAAO,WAAW,QAAQ,MAAM,WAAW,SAAS,MAAM,OAAO;;CAG1E,MAAM,UAAU,MAAM,eAAe;CACrC,MAAM,aAAa,MAAM,kBAAkB;CAC3C,MAAM,WAAW,MAAM,gBAAgB;CACvC,MAAM,YAAY,MAAM,iBAAiB;AAGzC,KAAI,WAAW,aAAa,GAAG,EAAE;AAC/B,MAAI,YAAY,aAAa,GAAG,CAC9B,QAAO,QAAQ,IAAI,IAAI;GAAE,MAAM,MAAM;GAAS,IAAI;GAAO;GAAI;GAAO,CAAC;AACvE,OAAK,IAAI,MAAM,KAAK,GAAG,MAAM,KAAK,KAAK,KAAK,MAAM,OAAO,OAAO,MAC9D,KAAI,aAAa,IAAI,CACnB,QAAO,QAAQ,KAAK,IAAI;GAAE,MAAM,MAAM;GAAY,IAAI;GAAO;GAAI;GAAO,CAAC;AAE7E,MAAI,aAAa,KAAK,KAAK,IAAI,OAAO,SAAS,aAAa,KAAK,KAAK,EAAE,CACtE,QAAO,QAAQ,KAAK,KAAK,GAAG,IAAI;GAAE,MAAM,MAAM;GAAU,IAAI;GAAO;GAAI;GAAO,CAAC;;CAKnF,MAAM,uBAAuB,MAAM,iBAAiB,MAAM;CAC1D,MAAM,YAAY,UAAU,KAAK,IAAI;CACrC,MAAM,UAAU,aAAa,KAAK,KAAK,IAAI,KAAK;AAChD,MAAK,IAAI,MAAM,WAAW,MAAM,SAAS,OAAO;AAC9C,MAAI,CAAC,aAAa,IAAI,CAAE;AACxB,MAAI,YAAY,aAAa,GAAG,CAC9B,QAAO,QAAQ,IAAI,KAAK;GAAE,MAAM,MAAM;GAAU,IAAI;GAAO;GAAI;GAAO,CAAC;AACzE,MAAI,aAAa,KAAK,KAAK,IAAI,OAAO,SAAS,aAAa,KAAK,KAAK,EAAE,CACtE,QAAO,QAAQ,KAAK,KAAK,GAAG,KAAK;GAAE,MAAM;GAAsB,IAAI;GAAO;GAAI;GAAO,CAAC;;CAK1F,MAAM,0BAA0B,MAAM,oBAAoB,MAAM;CAChE,MAAM,UAAU,KAAK,KAAK;AAC1B,KAAI,cAAc,aAAa,QAAQ,EAAE;AACvC,MAAI,YAAY,aAAa,GAAG,CAC9B,QAAO,QAAQ,IAAI,SAAS;GAAE,MAAM,MAAM;GAAY,IAAI;GAAO;GAAI;GAAO,CAAC;AAE/E,OAAK,IAAI,MAAM,KAAK,GAAG,MAAM,KAAK,KAAK,KAAK,MAAM,OAAO,OAAO,MAC9D,KAAI,aAAa,IAAI,CACnB,QAAO,QAAQ,KAAK,SAAS;GAAE,MAAM;GAAyB,IAAI;GAAO;GAAI;GAAO,CAAC;AAEzF,MAAI,aAAa,KAAK,KAAK,IAAI,OAAO,SAAS,aAAa,KAAK,KAAK,EAAE,CACtE,QAAO,QAAQ,KAAK,KAAK,GAAG,SAAS;GACnC,MAAM,MAAM;GACZ,IAAI;GACJ;GACA;GACD,CAAC;;;;;;;;;;;;;AAmBR,SAAgB,uBACd,OACA,QACA,QACA,OACA,IACA,KACM;CACN,MAAM,SAAS,MAAM,cAAc,cAAc,MAAM,GAAG;EAAE,KAAK;EAAG,QAAQ;EAAG,MAAM;EAAG,OAAO;EAAG;CAGlG,MAAM,iBAAwB;EAC5B,IAAI;EACJ,IAAI;EACJ,OAAO,EAAE;EACV;CAGD,MAAM,iBAAiB,MAAM,sBAAsB;AAGnD,KAAI,GAAG,cAAc,GAAG;EACtB,MAAM,YAAY,SAAS,GAAG;AAE9B,MAAI,OAAO,MAAM,GAAG;GAElB,MAAM,eAAe,OAAO,QAAQ,OAAO,OAAO,OAAO;GACzD,MAAM,MAAM,UAAU,WAAW,aAAa;GAC9C,MAAM,IAAI,OAAO,IAAI,OAAO;GAC5B,MAAM,IAAI,OAAO;AAEjB,kBAAe,QAAQ,GAAG,GAAG,KAAK,gBADnB,IAAI,cACuC,KAAA,GAAW,IAAI;aAChE,gBAAgB;GAEzB,MAAM,UAAU,WAAW,MAAM;GACjC,MAAM,eAAe,OAAO,QAAQ,QAAQ,OAAO,QAAQ;GAC3D,MAAM,MAAM,UAAU,WAAW,aAAa;GAC9C,MAAM,IAAI,OAAO,IAAI,QAAQ;AAG7B,kBAAe,QAAQ,GAFb,OAAO,IAAI,QAAQ,KAEA,KAAK,gBADnB,IAAI,cACuC,KAAA,GAAW,IAAI;;;AAK7E,KAAI,GAAG,cAAc,GAAG;EACtB,MAAM,YAAY,SAAS,GAAG;AAE9B,MAAI,OAAO,SAAS,GAAG;GAErB,MAAM,eAAe,OAAO,QAAQ,OAAO,OAAO,OAAO;GACzD,MAAM,MAAM,UAAU,WAAW,aAAa;GAC9C,MAAM,IAAI,OAAO,IAAI,OAAO;AAG5B,kBAAe,QAAQ,GAFb,OAAO,IAAI,OAAO,SAAS,GAER,KAAK,gBADnB,IAAI,cACuC,KAAA,GAAW,IAAI;aAChE,gBAAgB;GAEzB,MAAM,UAAU,WAAW,MAAM;GACjC,MAAM,eAAe,OAAO,QAAQ,QAAQ,OAAO,QAAQ;GAC3D,MAAM,MAAM,UAAU,WAAW,aAAa;GAC9C,MAAM,IAAI,OAAO,IAAI,QAAQ;AAG7B,kBAAe,QAAQ,GAFb,OAAO,IAAI,OAAO,SAAS,QAAQ,SAAS,GAEzB,KAAK,gBADnB,IAAI,cACuC,KAAA,GAAW,IAAI;;;;;;AAO/E,SAAS,UAAU,MAAc,OAAuB;AACtD,KAAI,SAAS,EAAG,QAAO;AACvB,KAAI,KAAK,SAAS,MAAO,QAAO,KAAK,MAAM,GAAG,MAAM;AACpD,KAAI,KAAK,WAAW,MAAO,QAAO;CAClC,MAAM,UAAU,KAAK,OAAO,QAAQ,KAAK,UAAU,EAAE;CACrD,MAAM,WAAW,QAAQ,KAAK,SAAS;AACvC,QAAO,IAAI,OAAO,QAAQ,GAAG,OAAO,IAAI,OAAO,SAAS;;;;;;;;;;ACvW1D,SAAgB,sBAAsB,QAA8B;CAClE,MAAM,YAAY,OAAO;AACzB,KAAI,CAAC,aAAa,UAAU,WAAW,EAAG;AAC1C,MAAK,MAAM,QAAQ,UACjB,QAAO,QAAQ,KAAK,GAAG,KAAK,GAAG,KAAK,KAAK;AAE3C,QAAO,mBAAmB,EAAE;;;;;;;;;;;AAY9B,SAAgB,qBAAqB,QAAwB,MAAoB;CAC/E,MAAM,YAAmC,EAAE;AAC3C,MAAK,MAAM,QAAQ,GAAG,KAAA,GAAW,EAAE,OAAO,MAAM,EAAE,UAAU;AAC5D,QAAO,mBAAmB;;;;;;;AAQ5B,SAAS,KACP,MACA,QACA,cACA,YACA,aACA,WACM;AAGN,KAAI,CAAC,KAAK,WAAY;CACtB,MAAM,SAAS,KAAK;AACpB,KAAI,CAAC,OAAQ;AAGb,KAAI,KAAK,OAAQ;CACjB,MAAM,QAAQ,KAAK;AACnB,KAAI,MAAM,YAAY,OAAQ;CAG9B,MAAM,IAAI,OAAO,IAAI;AAKrB,KAAI,KAAK,OAAO,UAAU,IAAI,OAAO,UAAU,EAAG;CAIlD,MAAM,cAAc,eAAe,MAAM;CACzC,MAAM,QAAQ,MAAM;CACpB,MAAM,UACJ,SAAS,OAAO,MAAM,0BAA0B,WAC3C,MAAM,wBACP,SAAS,OAAO,MAAM,UAAU,WAC7B,MAAM,QACP,KAAA;CACR,MAAM,mBAAqC,cACvC,EAAE,OAAO,WAAW,YAAY,EAAE,GAClC,YAAY,KAAA,IACV,EAAE,OAAO,WAAW,QAAQ,EAAE,GAC9B;AAKN,KAAI,KAAK,SAAS,iBAAiB,MAAM,cAAc;EACrD,MAAM,iBAAiB,cAAc,KAAA,IAAY,YAAY;EAC7D,MAAM,YAAY,oBAChB,OAAO,GACP,GACA,OAAO,OACP,OAAO,QACP,OACA,YACA,OACD;AACD,OAAK,MAAM,OAAO,UAEhB,WAAU,KAAK;GAAE,GAAG,IAAI;GAAG,GAAG,IAAI;GAAG,MAAM,OAAO,QAAQ,IAAI,GAAG,IAAI,EAAE;GAAE,CAAC;AAE5E,gBACE,QACA,OAAO,GACP,GACA,OAAO,OACP,OAAO,QACP,OACA,YACA,eACD;;AAOH,KAAI,KAAK,SAAS,WAAW,EAAG;CAEhC,MAAM,oBAAoB,MAAM,aAAa,YAAY,KAAK;CAC9D,MAAM,SAAS,MAAM,aAAa,MAAM,cAAc;CACtD,MAAM,SAAS,MAAM,aAAa,MAAM,cAAc;AAEtD,KAAI,mBAAmB;EACrB,MAAM,KAAK,KAAK;EAChB,MAAM,YAAY,iBAAiB,QAAQ,OAAO,YAAY,GAAG,OAAO,KAAK;AAE7E,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,SAAS,QAAQ,KAAK;GAC7C,MAAM,QAAQ,KAAK,SAAS;AAC5B,OAAI,CAAC,MAAO;AAEZ,OADW,MAAM,MACV,aAAa,SAAU;AAC9B,OAAI,IAAI,GAAG,qBAAqB,IAAI,GAAG,iBAAkB;AACzD,QAAK,OAAO,QAAQ,GAAG,QAAQ,WAAW,kBAAkB,UAAU;;AAGxE,MAAI,GAAG,eACL,MAAK,MAAM,UAAU,GAAG,gBAAgB;GACtC,MAAM,QAAQ,KAAK,SAAS,OAAO;AACnC,OAAI,CAAC,MAAO;AAEZ,QAAK,OAAO,QADS,OAAO,aAAa,OAAO,cACd,WAAW,kBAAkB,UAAU;;QAGxE;EACL,MAAM,YACJ,SAAS,QACL,iBAAiB,QAAQ,OAAO,YAAY,cAAc,OAAO,MAAM,GACvE;AACN,OAAK,MAAM,SAAS,KAAK,SACvB,MAAK,OAAO,QAAQ,cAAc,WAAW,kBAAkB,UAAU;;;;;;;;;;;;;AAe/E,SAAS,oBACP,GACA,GACA,OACA,QACA,OACA,YACA,QAC4B;CAC5B,MAAM,MAAkC,EAAE;CAC1C,MAAM,KAAK,IAAI;CACf,MAAM,KAAK,IAAI;CACf,MAAM,KAAK,QAAQ;CACnB,MAAM,KAAK,SAAS;CAEpB,MAAM,gBAAgB,QAAyB;AAC7C,MAAI,CAAC,WAAY,QAAO,OAAO,KAAK,MAAM,OAAO;AACjD,SAAO,OAAO,WAAW,OAAO,MAAM,WAAW,UAAU,MAAM,OAAO;;CAE1E,MAAM,gBAAgB,QAAyB;AAC7C,MAAI,YAAY,SAAS,KAAA,KAAa,WAAW,UAAU,KAAA,EACzD,QAAO,OAAO,KAAK,MAAM,OAAO;AAClC,SAAO,OAAO,WAAW,QAAQ,MAAM,WAAW,SAAS,MAAM,OAAO;;CAG1E,MAAM,UAAU,MAAM,eAAe;CACrC,MAAM,aAAa,MAAM,kBAAkB;CAC3C,MAAM,WAAW,MAAM,gBAAgB;CACvC,MAAM,YAAY,MAAM,iBAAiB;AAGzC,KAAI,WAAW,aAAa,GAAG,EAAE;AAC/B,MAAI,YAAY,aAAa,GAAG,CAAE,KAAI,KAAK;GAAE,GAAG;GAAI,GAAG;GAAI,CAAC;AAC5D,OAAK,IAAI,MAAM,KAAK,GAAG,MAAM,KAAK,KAAK,KAAK,MAAM,OAAO,OAAO,MAC9D,KAAI,aAAa,IAAI,CAAE,KAAI,KAAK;GAAE,GAAG;GAAK,GAAG;GAAI,CAAC;AAEpD,MAAI,aAAa,KAAK,KAAK,IAAI,OAAO,SAAS,aAAa,KAAK,KAAK,EAAE,CACtE,KAAI,KAAK;GAAE,GAAG,KAAK,KAAK;GAAG,GAAG;GAAI,CAAC;;CAKvC,MAAM,YAAY,UAAU,KAAK,IAAI;CACrC,MAAM,UAAU,aAAa,KAAK,KAAK,IAAI,KAAK;AAChD,MAAK,IAAI,MAAM,WAAW,MAAM,SAAS,OAAO;AAC9C,MAAI,CAAC,aAAa,IAAI,CAAE;AACxB,MAAI,YAAY,aAAa,GAAG,CAAE,KAAI,KAAK;GAAE,GAAG;GAAI,GAAG;GAAK,CAAC;AAC7D,MAAI,aAAa,KAAK,KAAK,IAAI,OAAO,SAAS,aAAa,KAAK,KAAK,EAAE,CACtE,KAAI,KAAK;GAAE,GAAG,KAAK,KAAK;GAAG,GAAG;GAAK,CAAC;;CAKxC,MAAM,UAAU,KAAK,KAAK;AAC1B,KAAI,cAAc,aAAa,QAAQ,EAAE;AACvC,MAAI,YAAY,aAAa,GAAG,CAAE,KAAI,KAAK;GAAE,GAAG;GAAI,GAAG;GAAS,CAAC;AACjE,OAAK,IAAI,MAAM,KAAK,GAAG,MAAM,KAAK,KAAK,KAAK,MAAM,OAAO,OAAO,MAC9D,KAAI,aAAa,IAAI,CAAE,KAAI,KAAK;GAAE,GAAG;GAAK,GAAG;GAAS,CAAC;AAEzD,MAAI,aAAa,KAAK,KAAK,IAAI,OAAO,SAAS,aAAa,KAAK,KAAK,EAAE,CACtE,KAAI,KAAK;GAAE,GAAG,KAAK,KAAK;GAAG,GAAG;GAAS,CAAC;;AAI5C,QAAO;;;;;;;AAQT,SAAS,iBACP,QACA,OACA,YACA,cACA,YACA,UACY;CACZ,MAAM,SAAS,MAAM,cAAc,cAAc,MAAM,GAAG;EAAE,KAAK;EAAG,QAAQ;EAAG,MAAM;EAAG,OAAO;EAAG;CAClG,MAAM,UAAU,WAAW,MAAM;CACjC,MAAM,YAAY,OAAO,IAAI;CAC7B,MAAM,WAAuB,WACzB;EACE,KAAK,YAAY,OAAO,MAAM,QAAQ;EACtC,QAAQ,YAAY,OAAO,SAAS,OAAO,SAAS,QAAQ;EAC7D,GACD;EAAE,KAAK;EAAW,QAAQ;EAAU;AACxC,KAAI,YAAY;AACd,WAAS,OAAO,OAAO,IAAI,OAAO,OAAO,QAAQ;AACjD,WAAS,QAAQ,OAAO,IAAI,OAAO,QAAQ,OAAO,QAAQ,QAAQ;;AAEpE,KAAI,CAAC,WAAY,QAAO;CACxB,MAAM,SAAqB;EACzB,KAAK,WAAW,KAAK,IAAI,WAAW,KAAK,SAAS,IAAI,GAAG,WAAW;EACpE,QAAQ,WAAW,KAAK,IAAI,WAAW,QAAQ,SAAS,OAAO,GAAG,WAAW;EAC9E;AACD,KAAI,cAAc,SAAS,SAAS,KAAA,KAAa,SAAS,UAAU,KAAA,GAAW;AAC7E,SAAO,OAAO,KAAK,IAAI,WAAW,QAAQ,GAAG,SAAS,KAAK;AAC3D,SAAO,QAAQ,KAAK,IAAI,WAAW,SAAS,UAAU,SAAS,MAAM;YAC5D,WAAW,SAAS,KAAA,KAAa,WAAW,UAAU,KAAA,GAAW;AAC1E,SAAO,OAAO,WAAW;AACzB,SAAO,QAAQ,WAAW;;AAE5B,QAAO;;;;;;;;;;AC2NT,SAAgB,eAAe,QAAuC;CACpE,MAAM,EACJ,eACA,cACA,iBACA,eACA,cACA,eACA,sBACA,uBACA,iBACA,SACA,YACA,YACA,sBACA,8BACE;CAGJ,MAAM,uBACJ,iBACA,CAAC,gBACD,CAAC,mBACD,CAAC,iBACD,CAAC,gBACD,CAAC,iBACD,CAAC,wBACD,CAAC;CAMH,MAAM,sBACJ,gBACA,iBACA,wBACA,iBACA,WARqB,cAAc,mBAUnC,wBACA;CA4BF,MAAM,eAAe;CAGrB,MAAM,iBAAiB,iBAAiB,CAAC,uBAAuB,gBAAgB;AAgBhF,QAAO;EACL;EACA;EACA;EACA,uBAfC,iBAAiB,oBAAoB,uBAAuB,CAAC;EAgB9D,YAbiB,iBAAiB,CAAC,mBAAmB,CAAC,uBAAuB,CAAC;EAc/E,0BARC,iBAAiB,qBAAqB,uBAAuB,mBAAmB;EASjF;EACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC1hBH,SAAgB,0BAA6C;CAE3D,MAAM,eAAe,OAAO,MAAM;CAClC,MAAM,kBAAkB,OAAO,MAAM;CACrC,MAAM,UAAU,OAAO,MAAM;CAC7B,MAAM,gBAAgB,OAAO,MAAM;CACnC,MAAM,eAAe,OAAO,MAAM;CAClC,MAAM,gBAAgB,OAAO,MAAM;CAGnC,MAAM,gBAAgB,OAAO,MAAM;CACnC,MAAM,uBAAuB,OAAO,MAAM;CAC1C,MAAM,wBAAwB,OAAO,MAAM;CAC3C,MAAM,kBAAkB,OAAO,MAAM;CACrC,MAAM,aAAa,OAAO,MAAM;CAChC,MAAM,aAAa,OAAO,MAAM;CAChC,MAAM,uBAAuB,OAAO,MAAM;CAC1C,MAAM,4BAA4B,OAAO,MAAM;CAI/C,MAAM,uBAAuB,eAEzB,eAAe,IACf,CAAC,cAAc,IACf,CAAC,iBAAiB,IAClB,CAAC,eAAe,IAChB,CAAC,cAAc,IACf,CAAC,eAAe,IAChB,CAAC,sBAAsB,IACvB,CAAC,uBAAuB,CAC3B;CAED,MAAM,iBAAiB,eAAe,YAAY,IAAI,iBAAiB,CAAC;CAExE,MAAM,sBAAsB,eAExB,cAAc,IACd,eAAe,IACf,sBAAsB,IACtB,eAAe,IACf,SAAS,IACT,gBAAgB,IAChB,sBAAsB,IACtB,2BAA2B,CAC9B;CAID,MAAM,eAAe,eAAe,MAAM;CAE1C,MAAM,iBAAiB,eACf,eAAe,IAAI,CAAC,qBAAqB,IAAI,cAAc,IAAI,YAAY,CAClF;AAiBD,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,sBAlC2B,gBACpB,eAAe,IAAI,iBAAiB,KAAK,qBAAqB,IAAI,CAAC,YAAY,CACvF;EAiCC,YA/BiB,eACX,eAAe,IAAI,CAAC,iBAAiB,IAAI,CAAC,qBAAqB,IAAI,CAAC,gBAAgB,CAC3F;EA8BC,yBA5B8B,gBAE3B,eAAe,IAAI,iBAAiB,MACpC,qBAAqB,IAAI,gBAAgB,KAC1C,CAAC,cAAc,CAClB;EAwBC;EACD;;;;;;;;;AAcH,SAAgB,cACd,OACA,MACA,KAUM;AAEN,OAAM,aAAa,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAwB,CAAC;AACzE,OAAM,gBAAgB,QAAQ,KAAK,WAAW,KAAK,YAAA,EAA4B,CAAC;AAChF,OAAM,QAAQ,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAmB,CAAC;AAC/D,OAAM,cAAc,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAyB,CAAC;AAC3E,OAAM,aAAa,QAAQ,KAAK,WAAW,KAAK,YAAA,GAAwB,CAAC;AACzE,OAAM,cAAc,IAAI,cAAc;AAGtC,OAAM,cAAc,IAAI,cAAc;AACtC,OAAM,qBAAqB,IAAI,qBAAqB;AACpD,OAAM,sBAAsB,IAAI,sBAAsB;AACtD,OAAM,gBAAgB,IAAI,gBAAgB;AAC1C,OAAM,WAAW,KAAK,SAAS,eAAe;AAC9C,OAAM,WAAW,IAAI,WAAW;AAChC,OAAM,qBAAqB,IAAI,qBAAqB;AACpD,OAAM,0BAA0B,IAAI,0BAA0B;;;;;;;AAYhE,SAAgB,oBAAoB,OAA0C;AAC5E,QAAO;EACL,sBAAsB,MAAM,sBAAsB;EAClD,qBAAqB,MAAM,qBAAqB;EAChD,gBAAgB,MAAM,gBAAgB;EACtC,sBAAsB,MAAM,sBAAsB;EAClD,YAAY,MAAM,YAAY;EAC9B,yBAAyB,MAAM,yBAAyB;EACxD,cAAc,MAAM,cAAc;EACnC;;;;;;;;AAaH,SAAgB,4BACd,OACA,QACA,QACM;CACN,MAAM,SAAmC;EACvC;EACA;EACA;EACA;EACA;EACA;EACA;EACD;CAED,MAAM,aAAuB,EAAE;AAC/B,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,gBAAgB,MAAM,QAAQ;EACpC,MAAM,cAAc,OAAO;AAC3B,MAAI,kBAAkB,YACpB,YAAW,KAAK,KAAK,MAAM,aAAa,cAAc,WAAW,cAAc;;AAInF,KAAI,WAAW,SAAS,EACtB,OAAM,IAAI,MACR,kCAAkC,UAAU,YAAY,KAAK,WAAW,KAAK,KAAK,GACnF;;;;;;;;AAcL,MAAM,6BAAa,IAAI,SAAoC;;;;;;AAO3D,SAAgB,iBAAiB,MAAiC;CAChE,IAAI,QAAQ,WAAW,IAAI,KAAK;AAChC,KAAI,CAAC,OAAO;AACV,UAAQ,yBAAyB;AACjC,aAAW,IAAI,MAAM,MAAM;;AAE7B,QAAO;;;;;;;;;;;;;;;;;;;;;aC3SiC;AA0C1C,MAAM,aAAa,aAAa,kBAAkB;AAClD,MAAM,WAAW,aAAa,wBAAwB;AACtD,MAAM,UAAU,aAAa,uBAAuB;;;;;;;;AASpD,SAAgB,YACd,MACA,YACA,KACgB;CAChB,MAAM,SAAS,KAAK;AACpB,KAAI,CAAC,OACH,OAAM,IAAI,MAAM,yCAAyC;CAG3D,MAAM,QAAQ,uBAAuB,IAAI;CAGzC,MAAM,gBACJ,cAAc,WAAW,UAAU,OAAO,SAAS,WAAW,WAAW,OAAO;AAUlF,KACE,iBACA,CAAC,WAAW,KAAK,WAAW,KAAK,WAAW,IAC5C,CAAC,eAAe,KAAK,uBAAuB,EAC5C;AACA,MAAI,MAAM,QAAS,OAAM,MAAM,YAAY;AAC3C,sBAAoB;AACpB,SAAO;;AAGT,KAAI,MAAM,SAAS;AACjB,QAAM,MAAM,kBAAkB,cAAc,OAAO,IAAI;AACvD,QAAM,MAAM,yBAAyB,cAAc,CAAC,gBAAgB,IAAI;AACxE,QAAM,MAAM,iBAAiB,gBAAgB,IAAI;AACjD,QAAM,MAAM,WAAW,OAAO;AAC9B,QAAM,MAAM,WAAW,OAAO;AAC9B,QAAM,MAAM,SAAS,YAAY,SAAS;AAC1C,QAAM,MAAM,SAAS,YAAY,UAAU;;CAG7C,MAAM,KAAK,MAAM,UAAU,YAAY,KAAK,GAAG;CAC/C,MAAM,SAAS,gBACX,WAAW,OAAO,GAClB,IAAI,eAAe,OAAO,OAAO,OAAO,OAAO;CACnD,MAAM,SAAS,MAAM,UAAU,YAAY,KAAK,GAAG,KAAK;AAIxD,QAAO,kBAAkB,KAAK;AAS9B,uBAAsB,OAAO;CAE7B,MAAM,KAAK,MAAM,UAAU,YAAY,KAAK,GAAG;AAC/C,oBACE,MACA,QACA;EACE,cAAc;EACd,YAAY,KAAA;EACZ,eAAe,CAAC,CAAC;EACjB,iBAAiB;EACjB,gBAAgB,CAAC,CAAC;EAClB,uBAAuB;EACvB,aAAa;GAAE,OAAO;GAAM,cAAc;GAAM;EAChD,aAAa;EACd,EACD,IACD;CACD,MAAM,UAAU,MAAM,UAAU,YAAY,KAAK,GAAG,KAAK;AAMzD,sBAAqB,QAAQ,KAAK;AAElC,KAAI,MAAM,QACR,sBAAqB,MAAM,OAAO,MAAM,WAAW,MAAM,kBAAkB,QAAQ,QAAQ;AAU7F,gBAAe,MAFb,eAAe,KAAK,uBAAuB,IAC3C,QAAQ,KAAK,WAAW,KAAK,YAAA,GAAwB,IACd,CAAC,cAAc;AAKxD,qBAAoB;AAEpB,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;AA0BT,SAAS,eAAe,MAAc,gBAA+B;AAGnE,KAAI,CAAC,eAAgB;CAErB,MAAM,QAAkB,CAAC,KAAK;AAC9B,QAAO,MAAM,SAAS,GAAG;EACvB,MAAM,OAAO,MAAM,KAAK;AACxB,OAAK,aAAa,KAAK;EACvB,MAAM,WAAW,KAAK;AACtB,OAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,IACxC,OAAM,KAAK,SAAS,GAAI;;;;AAM9B,SAAS,UAAU,KAAkC;AACnD,QAAO,CAAC,CAAC,OAAO,QAAQ,OAAO,QAAQ;;;AAIzC,MAAM,qBACJ,OAAO,YAAY,gBAClB,UAAU,QAAQ,KAAK,eAAe,IAAI,UAAU,QAAQ,KAAK,mBAAmB;;AAGvF,MAAM,oBAAsC;CAC1C,cAAc;CACd,eAAe;CACf,cAAc;CACd,WAAW;CACX,UAAU;CACV,UAAU;CACV,cAAc;CACd,kBAAkB;CAClB,qBAAqB;CACrB,mBAAmB;CACnB,kBAAkB;CAClB,mBAAmB;CACnB,0BAA0B;CAC1B,2BAA2B;CAC3B,sBAAsB;CACtB,uBAAuB;CACvB,mBAAmB;CACnB,uBAAuB;CACvB,qBAAqB;CACrB,iBAAiB;CACjB,cAAc;CACd,WAAW;CACX,iBAAiB;CACjB,wBAAwB;CACxB,gBAAgB;CAChB,UAAU;CACV,UAAU;CACV,QAAQ;CACR,QAAQ;CACR,YAAY;CACb;AAED,IAAI,wBAAwB;;AAG5B,MAAM,aAA+B,EAAE;AACvC,MAAM,oBAAoB,OAAO,YAAY,eAAe,UAAU,QAAQ,KAAK,eAAe;;;;;;AAOlG,IAAI,mBAAmB,OAAO,YAAY,eAAe,QAAQ,KAAK,qBAAqB;AAC3F,IAAI,yBACF,oBAAoB,OAAO,YAAY,eAAe,UAAU,QAAQ,KAAK,eAAe;;AAsB9F,SAAS,uBAAuB,KAAwC;AACtE,QAAO;EACL,SAAS,KAAK,qBAAqB;EACnC,OAAO,KAAK,SAAS;EACrB,WAAW,KAAK,aAAa;EAC7B,kBAAkB,KAAK,oBAAoB;EAC5C;;;AAOH,SAAS,eAAsC;AAC7C,QAAQ,WAAmB;;;AAI7B,SAAS,gBACP,SACA,GACA,GACA,OACA,QACS;AACT,QAAO,KAAK,QAAQ,KAAK,IAAI,QAAQ,QAAQ,KAAK,KAAK,QAAQ,KAAK,IAAI,SAAS,QAAQ;;;AAI3F,SAAS,cAAc,MAAsB;CAC3C,IAAI,QAAQ;CACZ,IAAI,IAAmB,KAAK;AAC5B,QAAO,GAAG;AACR;AACA,MAAI,EAAE;;AAER,QAAO;;;;;;AAOT,SAAS,qBACP,OACA,WACA,kBACA,QACA,SACM;AACN;AACA,OAAM,aAAa;CACnB,MAAM,OAAO;EACX,OAAO;EACP,QAAQ;EACR,GAAG,gBAAgB,MAAM;EAC1B;AAEC,YAAmB,2BAA2B;AAEhD,EADa,WAAoB,0BAA0B,EAAE,EACzD,KAAK,KAAK;AAEd,YAAW,QACT,SAAS,KAAK,WAAW,IAAI,KAAK,cAAc,GAAG,KAAK,aAAa,aAAa,KAAK,aAAa,YAAY,OAAO,QAAQ,EAAE,CAAC,YAAY,QAAQ,QAAQ,EAAE,CAAC,YAClK;AACD,MAAK,MAAM,OAAO,OAAO,KAAK,MAAM,CAChC,OAAc,OAAO;AAEzB,OAAM,kBAAkB;AACxB,OAAM,eAAe;AACrB,OAAM,oBAAoB;AAC1B,OAAM,sBAAsB;AAG5B,KAAI,oBAAoB,UAAU,SAAS,GAAG;AAE5C,GADkB,WAAoB,yBAAyB,EAAE,EACxD,KAAK,CAAC,GAAG,UAAU,CAAC;AAC7B,WAAS,QAAQ,GAAG,UAAU,OAAO,eAAe;AACpD,YAAU,SAAS;;;;;;AAcvB,SAAS,mBACP,MACA,QACA,WACA,KACM;CACN,MAAM,EACJ,cACA,YACA,eACA,iBACA,gBACA,wBAAwB,UACtB;CACJ,MAAM,QAAQ,uBAAuB,IAAI;AACzC,KAAI,MAAM,QAAS,OAAM,MAAM;CAC/B,MAAM,SAAS,KAAK;AACpB,KAAI,CAAC,OAAQ;AAIb,KAAI,CAAC,KAAK,YAAY;AACpB,wBAAsB,KAAK;AAC3B;;AAIF,KAAI,KAAK,QAAQ;AACf,kBAAgB,KAAK;AACrB;;CAGF,MAAM,QAAQ,KAAK;CAGnB,MAAM,qBAAqB,OAAO,mBAAmB;CACrD,MAAM,aAAa,MAAM;AACzB,KAAI,eAAe,OACjB,QAAO,kBAAkB,MAAM;UACtB,eAAe,UAAU,eAAe,UACjD,QAAO,kBAAkB,KAAK;AAIhC,KAAI,MAAM,YAAY,QAAQ;AAC5B,kBAAgB,KAAK;AACrB,SAAO,kBAAkB,mBAAmB;AAC5C;;CAMF,MAAM,UAAU,OAAO,IAAI;AAC3B,KAAI,WAAW,OAAO,UAAU,UAAU,OAAO,UAAU,GAAG;AAC5D,SAAO,kBAAkB,mBAAmB;AAC5C;;CAKF,MAAM,gBAAgB,eAAe,KAAK,uBAAuB;CACjE,MAAM,uBAAuB,CAAC,EAAE,iBAAiB,CAAC,iBAAiB,wBAAwB,KAAK;CAChG,MAAM,sBAAsB,CAAC,EAC3B,KAAK,eAAe,KAAK,YAAY,WAAW,KAAK,YAAY;CAGnE,MAAM,uBACJ,iBACA,CAAC,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAwB,IACtD,CAAC,QAAQ,KAAK,WAAW,KAAK,YAAA,EAA4B,IAC1D,CAAC,iBACD,CAAC,QAAQ,KAAK,WAAW,KAAK,YAAA,GAAwB,IACtD,CAAC,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAyB,IACvD,CAAC,wBACD,CAAC,yBACD,CAAC;CAGH,MAAM,UAAU,MAAM,UAAY,MAAM,MAA6B,KAAM;CAC3E,MAAM,aAAa,MAAM,WAAW,MAAM,oBAAoB;CAC9D,MAAM,WAAW,cAAc;CAC/B,MAAM,iBACJ,YAAY,gBAAgB,UAAU,OAAO,GAAG,SAAS,OAAO,OAAO,OAAO,OAAO;CACvF,MAAM,kBACJ,YACA,KAAK,cACL,gBACE,UACA,KAAK,WAAW,GAChB,KAAK,WAAW,IAAI,cACpB,KAAK,WAAW,OAChB,KAAK,WAAW,OACjB;AAEH,KAAI,sBAAsB;AACxB,MAAI,aAAa,kBAAkB,kBAAkB;GACnD,MAAM,KAAM,MAAM,MAAiB,KAAK;GACxC,MAAM,QAAQ,cAAc,KAAK;GACjC,MAAM,OAAO,KAAK;GAClB,MAAM,MACJ,QAAQ,GAAG,GAAG,MAAM,QAAQ,OAAO,EAAE,GAAG,QAAQ,GAAG,OAAO,MAAM,GAAG,OAAO,OAAA,QACjE,OAAO,GAAG,KAAK,EAAE,GAAG,KAAK,IAAI,aAAa,GAAG,KAAK,MAAM,GAAG,KAAK,WAAW,OAAA,aACtE,eAAe,cAAc;AAC7C,YAAS,IAAI,KAAK,IAAI;AACtB,WAAQ,QAAQ,IAAI;;AAEtB,MAAI,MAAM,SAAS;AACjB,SAAM,MAAM;AACZ,OAAI,WACF,OAAM,UAAU,KAAK;IACnB,IAAI;IACJ,MAAM,KAAK;IACX,OAAO,cAAc,KAAK;IAC1B,MAAM,GAAG,OAAO,EAAE,GAAG,OAAO,EAAE,GAAG,OAAO,MAAM,GAAG,OAAO;IACxD,YAAY,KAAK,aACb,GAAG,KAAK,WAAW,EAAE,GAAG,KAAK,WAAW,EAAE,GAAG,KAAK,WAAW,MAAM,GAAG,KAAK,WAAW,WACtF;IACJ,SAAS;IACT;IACA,OAAO;IACP,UAAU;IACV;IACD,CAAC;;AAGN,kBAAgB,KAAK;AACrB,SAAO,kBAAkB,mBAAmB;AAC5C;;AAEF,KAAI,MAAM,SAAS;AACjB,QAAM,MAAM;AACZ,MAAI,CAAC,cAAe,OAAM,MAAM;AAChC,MAAI,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAwB,CAAE,OAAM,MAAM;AACvE,MAAI,QAAQ,KAAK,WAAW,KAAK,YAAA,EAA4B,CAAE,OAAM,MAAM;AAC3E,MAAI,cAAe,OAAM,MAAM;AAC/B,MAAI,QAAQ,KAAK,WAAW,KAAK,YAAA,GAAwB,CAAE,OAAM,MAAM;AACvE,MAAI,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAyB,CAAE,OAAM,MAAM;AACxE,MAAI,qBAAsB,OAAM,MAAM;AACtC,MAAI,sBAAuB,OAAM,MAAM;;CAMzC,MAAM,YAAa,MAAmB;AACtC,KAAI,UACF,kBAAiB,UAAU;AAE7B,KAAI;EAEF,MAAM,oBAAoB,MAAM,aAAa,YAAY,KAAK;EAG9D,MAAM,EAAE,sBAAsB,8BAA8B,mBAC1D,MACA,cACD;EAID,MAAM,gBAAgB;GACpB;GACA,cAAc,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAwB;GACnE,iBAAiB,QAAQ,KAAK,WAAW,KAAK,YAAA,EAA4B;GAC1E;GACA,cAAc,QAAQ,KAAK,WAAW,KAAK,YAAA,GAAwB;GACnE,eAAe,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAyB;GACrE;GACA;GACA;GACA,SAAS,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAmB;GACzD,YAAY,KAAK,SAAS;GAC1B,YAAY,CAAC,CAAC,eAAe,MAAM;GACnC;GACA;GACD;EACD,IAAI;AACJ,MAAI,kBAAkB;GACpB,MAAM,gBAAgB,iBAAiB,KAAK;AAC5C,iBAAc,eAAe,MAAM;IACjC;IACA;IACA;IACA;IACA;IACA;IACA;IACA,YAAY,cAAc;IAC3B,CAAC;AACF,aAAU,oBAAoB,cAAc;AAG5C,OAAI,uBACF,6BACE,eACA,eAAe,cAAc,EAC5B,MAAM,MAAiB,KAAK,KAC9B;QAGH,WAAU,eAAe,cAAc;AAMzC,MAAI,QAAQ,gBAAgB,oBAAoB,KAAK,EAAE;GACrD,MAAM,2BACH,iBAAiB,qBACjB,QAAQ,uBAAuB,QAAQ;AAC1C,aAAU;IAAE,GAAG;IAAS,cAAc;IAAO;IAAyB;;EAExE,MAAM,EAAE,sBAAsB,YAAY,4BAA4B;AAGtE,MAAI,MAAM,WAAY,aAAa,kBAAkB,iBACnD,qBACE,MACA,OACA,QACA,SACA,cACA,eACA,iBACA,eACA,sBACA,SACA,SACA,YACA,UACA,gBACA,iBACA,MAAM,SACN,MAAM,OACN,MAAM,UACP;EAKH,MAAM,uBAAuB;AAK7B,wBACE,MACA,QACA,QACA,cACA,YACA,gBACA,eAC+B,sBAC/B,2BACA,MAAM,SACN,MAAM,OACN,UAAU,YACX;EAOD,MAAM,kBACJ,CAAC,iBACD,mBACA,yBACA,QAAQ,uBACR,QAAQ,KAAK,WAAW,KAAK,YAAA,EAA4B,IACzD,QAAQ;AAMR,OAAK,SAAS,iBAAiB,CAAC,eAAe,MAAM,IACjD,UAAU,YAAY;AAE5B,MAAI,gBACF,kBACE,MACA,QACA,QACA,OACA,WACA,YACA,MAAM,SACN,MAAM,OACN,KACA,QAAQ,cACR,qBACD;EAKH,MAAM,cAAc,eAAe,MAAM;EACzC,MAAM,mBAAmB,cACrB;GAAE,OAAO,WAAW,YAAY;GAAE,cAAc,KAAK;GAAS,GAC9D,YACE;GAAE,OAAO,WAAW,UAAU,GAAG;GAAE,cAAc,KAAK;GAAS,GAC/D,UAAU;EAOhB,MAAM,mBADmB,MAAM,UAAU,aAAa,MAAM,UAAU,iBAElE,UAAU,cACV,MAAM,QACJ,WAAW,MAAM,MAAM,GACvB,YACE,WAAW,UAAU,GAAG,GACxB,UAAU;EAGlB,MAAM,aAA8B;GAClC,GAAG;GACH,aAAa;GACb,aAAa;GACd;AACD,MAAI,mBAAmB;AACrB,iCACE,MACA,QACA,OACA,YACA,sBACA,yBACA,IACD;AAKD,0BAAuB,MAAM,QAAQ,QAAQ,OAAO,KAAK,aAAc,IAAI;QAE3E,sBACE,MACA,QACA,OACA,YACA,sBACA,sBACA,yBACA,IACD;AASH,sBAAoB,KAAK;WACjB;AAER,MAAI,UAAW,kBAAiB;AAEhC,SAAO,kBAAkB,mBAAmB;;;;;;;;;;AAehD,SAAS,mBACP,MACA,eACuE;AACvE,KACE,CAAC,iBACD,CAAC,QAAQ,KAAK,WAAW,KAAK,YAAA,GAAwB,IACtD,KAAK,aAAa,KAAA,EAElB,QAAO;EAAE,sBAAsB;EAAO,2BAA2B;EAAO;AAK1E,QAAO;EACL,sBAAsB,QAAQ,KAAK,WAAW,KAAK,YAAA,GAA0B;EAC7E,2BAA2B,QAAQ,KAAK,WAAW,KAAK,YAAA,GAA8B;EACvF;;;;;;;;AAaH,SAAS,oBACP,MACA,OACA,QACA,SACA,cACA,eACA,iBACA,eACA,sBACA,SACA,SACA,YACA,UACA,gBACA,iBACA,mBACA,OACA,WACM;CACN,MAAM,EAAE,qBAAqB,sBAAsB,YAAY,4BAA4B;AAG3F,KAAI,mBAAmB;AACrB,MAAI,YAAY;GACd,MAAM,UAAU;IACd,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAwB,IAAI;IACzD,QAAQ,KAAK,WAAW,KAAK,YAAA,EAA4B,IAAI;IAC7D,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAmB,IAAI;IACpD,QAAQ,KAAK,WAAW,KAAK,YAAA,GAAwB,IAAI;IACzD,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAyB,IAAI;IAC1D,wBAAwB;IACzB,CACE,OAAO,QAAQ,CACf,KAAK,IAAI;GAKZ,MAAM,gBAHJ,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAyB,IACtD,wBACA,0BAC2C,QAAQ;GACrD,MAAM,wBACJ,wBAAyB,mBAAmB,CAAC,eAAe,MAAM;AACpE,aAAU,KAAK;IACb,IAAI;IACJ,MAAM,KAAK;IACX,OAAO,cAAc,KAAK;IAC1B,MAAM,GAAG,OAAO,EAAE,GAAG,OAAO,EAAE,GAAG,OAAO,MAAM,GAAG,OAAO;IACxD,YAAY,KAAK,aACb,GAAG,KAAK,WAAW,EAAE,GAAG,KAAK,WAAW,EAAE,GAAG,KAAK,WAAW,MAAM,GAAG,KAAK,WAAW,WACtF;IACJ,SAAS;IACT;IACA,OAAO;IACP,UAAU;IACV;IACA;IACA;IACA;IACA,cAAc;IACd,sBAAsB;IACtB;IACA,SAAS,MAAM;IAChB,CAAC;;AAEJ,MAAI,2BAA2B,KAAK,SAAS,SAAS,GAAG;GACvD,MAAM,QAAQ,cAAc,KAAK;AACjC,OAAI,QAAQ,MAAM,gBAChB,OAAM,kBAAkB;GAY1B,MAAM,QAAQ,GAVF,KAAK,MAAkC,MAAM,KAAK,KAU1C,GAAG,MAAM,GATf;IACZ,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAwB,IAAI;IACzD,QAAQ,KAAK,WAAW,KAAK,YAAA,EAA4B,IAAI;IAC7D,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAyB,IAAI;IAC1D,iBAAiB;IACjB,wBAAwB;IACzB,CACE,OAAO,QAAQ,CACf,KAAK,GAAG,CAC2B,GAAG,KAAK,SAAS,OAAO;AAC9D,SAAM,iBAAiB,MAAM,eAAe,MAAM,MAAM;;;AAK5D,KAAI,aAAa,kBAAkB,kBAAkB;EACnD,MAAM,KAAM,MAAM,MAAiB,KAAK;EACxC,MAAM,QAAQ,cAAc,KAAK;EACjC,MAAM,OAAO,KAAK;EAClB,MAAM,QAAQ;GACZ,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAwB,IAAI;GACzD,QAAQ,KAAK,WAAW,KAAK,YAAA,EAA4B,IAAI;GAC7D,iBAAiB;GACjB,QAAQ,KAAK,WAAW,KAAK,YAAA,GAAwB,IAAI;GACzD,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAyB,IAAI;GAC1D,wBAAwB;GACxB,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAmB,IAAI;GACrD,CACE,OAAO,QAAQ,CACf,KAAK,IAAI;EACZ,MAAM,MACJ,UAAU,GAAG,GAAG,MAAM,QAAQ,OAAO,EAAE,GAAG,QAAQ,GAAG,OAAO,MAAM,GAAG,OAAO,OAAA,QACnE,OAAO,GAAG,KAAK,EAAE,GAAG,KAAK,IAAI,aAAa,GAAG,KAAK,MAAM,GAAG,KAAK,WAAW,OAAA,UACzE,MAAM,YAAY,cAAc,UAAU,gBAAA,OAC7C,oBAAoB,OAAO,qBAAqB,OAAO,wBAAA,aACjD,eAAe,cAAc,gBAAA,MACpC,MAAM,mBAAmB;AAClC,WAAS,IAAI,KAAK,IAAI;AACtB,UAAQ,QAAQ,IAAI;;;;;;;;;;;;;AAkBxB,SAAS,sBACP,MACA,QACA,QACA,cACA,YACA,gBACA,eACA,sBACA,2BACA,mBACA,OACA,qBACM;AACN,KAAI,sBAAsB;AACxB,MAAI,kBAAmB,OAAM;AAC7B,kBACE,MACA,QACA,QACA,cACA,YACA,eACA,oBACD;YACQ,kBAAkB,iBAAiB,KAAK,WAajD,iBACE,MACA,QACA,QACA,cACA,YACA,eACA,oBACD;AAQH,KAAI,0BACF,gCACE,MACA,QACA,QACA,cACA,YACA,oBACD;;;;;;;;;;AAoBL,SAAS,iBACP,MACA,QACA,QACA,OACA,WACA,YACA,mBACA,OACA,KACA,eAAe,OACf,uBAAuB,OACJ;CAEnB,MAAM,iBACJ,KAAK,SAAS,iBAAiB,CAAC,eAAe,MAAM,GAAG,UAAU,YAAY,QAAQ,KAAA;AAExF,KAAI,KAAK,SAAS,eAAe;AAC/B,MAAI,kBAAmB,OAAM;AAE7B,YACE,MACA,QACA,QACA,OACA,WACA,YACA,gBACA,cACA,UAAU,YACX;YACQ,KAAK,SAAS,gBAAgB;AACvC,MAAI,kBAAmB,OAAM;EAM7B,MAAM,kBAAkB,UAAU,YAAY;EAC9C,MAAM,kBAAkB,UAAU;AAelC,MAAI,sBAAsB;GACxB,MAAM,QAAQ,aAAa,MAAM;AACjC,OAAI,MAAM,OAAO,QAAQ,oBAAoB,KAAA,EAC3C,OAAM,KAAK;GAEb,MAAM,cAAc,MAAM,OAAO,OAAO,MAAM,KAAM,mBAAmB;GACvE,MAAM,EAAE,GAAG,OAAO,WAAW;GAC7B,MAAM,IAAI,OAAO,IAAI,UAAU;AAC/B,UAAO,cAAc,GAAG,GAAG,OAAO,QAAQ;IACxC,IAAI,MAAM;IACV,IAAI;IACJ,gBAAgB,MAAM,kBAAkB;IACxC,OAAO,MAAM;IACd,CAAC;QAEF,YAAW,MAAM,QAAQ,QAAQ,OAAO,WAAW,iBAAiB,iBAAiB,IAAI;;AAI7F,QAAO;;;;;;;;;;;;;;;;AA8DT,SAAgB,iBAAiB,QAAsC;CACrE,MAAM,EACJ,qBACA,qBACA,mBACA,yBACA,eACA,eACA,iBACA,sBACA,aACE;CAIJ,MAAM,aACJ,iBACA,uBACA,CAAC,iBACD,CAAC,2BACD,CAAC,qBACD,CAAC;CAGH,MAAM,qBACJ,iBACA,CAAC,eACA,uBAAuB,iBAAiB,2BAA2B;CAItE,MAAM,qBAAqB,qBAAqB,iBAAiB,CAAC;CAGlE,MAAM,UAAoB,EAAE;AAC5B,KAAI,WAAY,SAAQ,KAAK,QAAQ;AACrC,KAAI,oBAAoB;AACtB,MAAI,oBAAqB,SAAQ,KAAK,eAAe;AACrD,MAAI,cAAe,SAAQ,KAAK,gBAAgB;AAChD,MAAI,wBAAyB,SAAQ,KAAK,0BAA0B;AACpE,MAAI,oBAAqB,SAAQ,KAAK,sBAAsB;;AAE9D,KAAI,mBAAoB,SAAQ,KAAK,qBAAqB;AAO1D,QAAO;EACL,MANuB,aAAa,UAAU,qBAAqB,UAAU;EAO7E,SAAS,cAAc,qBAAqB,WAAW;EACvD,cANmB,qBAAqB,QAAQ;EAOhD,sBAN2B,qBAAqB,OAAO,mBAAmB;EAO1E;EACA;EACD;;;;;AAMH,SAAS,8BACP,MACA,QACA,OACA,WACA,uBAAuB,OACvB,0BAA0B,OAC1B,KACM;CACN,MAAM,EACJ,YACA,eACA,iBACA,gBACA,uBACA,aACA,gBACE;CACJ,MAAM,QAAQ,uBAAuB,IAAI;CACzC,MAAM,SAAS,KAAK;CACpB,MAAM,KAAK,KAAK;AAChB,KAAI,CAAC,UAAU,CAAC,GAAI;CAEpB,MAAM,SAAS,MAAM,cAAc,cAAc,MAAM,GAAG;EAAE,KAAK;EAAG,QAAQ;EAAG,MAAM;EAAG,OAAO;EAAG;CAClG,MAAM,UAAU,WAAW,MAAM;CAIjC,MAAM,qBAAqB,uBACzB,QACA,OACA,YACA,GACiB,OACF,KAChB;CAaD,MAAM,kBAD0B,MAAM,sBAAsB,QAAQ,CAAC,MAAM,gBAE7C,GAAG,cAAc,KAAK,GAAG,cAAc,KAC/D;EACE,GAAG;EACH,KAAK,GAAG,cAAc,IAAI,mBAAmB,MAAM,IAAI,mBAAmB;EAC1E,QAAQ,GAAG,cAAc,IAAI,mBAAmB,SAAS,IAAI,mBAAmB;EACjF,GACD;CAGN,MAAM,sBAAsB,GAAG,WAAW,GAAG;CAC7C,MAAM,oBAAoB,CAAC,EAAE,GAAG,kBAAkB,GAAG,eAAe,SAAS;CAC7E,MAAM,sBACJ,GAAG,sBAAsB,GAAG,yBAC5B,GAAG,qBAAqB,GAAG;CAO7B,MAAM,SAAS,mBAAmB;CAClC,MAAM,cAAc,mBAAmB,SAAS,mBAAmB;CACnE,MAAM,WAAW,OAAO,IAAI,OAAO,OAAO,QAAQ;CAClD,MAAM,eAAe,OAAO,QAAQ,OAAO,OAAO,OAAO,QAAQ,QAAQ,OAAO,QAAQ;CAGxF,MAAM,WACJ,uBACA,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAyB,IACtD,2BACA,sBACI,eAAe,MAAM,GACnB,WAAW,eAAe,MAAM,CAAE,GAClC,YAAY,QACd;CAGN,MAAM,OAAO,iBAAiB;EAC5B;EACA;EACA;EACA;EACA,eAAe,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAyB;EACrE;EACA;EACA;EACA;EACD,CAAC;CACF,MAAM,EAAE,MAAM,uBAAuB;CACrC,MAAM,sBAAsB,KAAK;CACjC,MAAM,8BAA8B,KAAK;AAEzC,KAAI,MAAM,SAAS;AACjB,QAAM,MAAM;AACZ,MAAI,SAAS,kBAAkB,oBAAoB;AACjD,SAAM,MAAM;GACZ,MAAM,UAAU,CAAC,GAAG,KAAK,QAAQ;AACjC,OAAI,oBAAqB,SAAQ,KAAK,gBAAgB,GAAG,WAAW,IAAI,GAAG,OAAO,GAAG;AACrF,WAAQ,KACN,MAAM,GAAG,eAAe,WAAW,GAAG,cAAc,OAAO,GAAG,kBAAkB,GAAG,GAAG,mBACvF;AACD,SAAM,MAAM,oBAAoB,QAAQ,KAAK,IAAI;;;AAKrD,KAAI,SAAS,KAAK,kBAAkB,SAAS,WAAW,kBACtD,OAAM,IAAI,MACR,uFACa,MAAM,MAA6B,KAAK,KAAK,iBACxC,GAAG,gBAAgB,UAAU,EAAE,GAClD;CAIH,MAAM,cAAc,GAAG,UAAU,GAAG,cAAc,GAAG;AACrD,KAAI,SAAS,WAAW,cAAc,GAAG;AAIvC,MADuB,MAAM,sBAAsB,QAC7B,CAAC,OAAO,OAAO,CAAC,OAAO,QAAQ;GACnD,MAAM,gBAAgB;GACtB,MAAM,mBAAmB,SAAS,cAAc;AAChD,OAAI,GAAG,cAAc,QAAQ,GAAG,aAAa,EAC3C,QAAO,KAAK,UAAU,eAAe,cAAc,GAAG;IAAE,MAAM;IAAK,IAAI,KAAK;IAAS,CAAC;AAExF,UAAO,KAAK,UAAU,kBAAkB,cAAc,GAAG;IAAE,MAAM;IAAK,IAAI,KAAK;IAAS,CAAC;;AAE3F,SAAO,aAAa,UAAU,QAAQ,cAAc,aAAa,aAAa;GAC5E,MAAM;GACN,IAAI,KAAK;GACV,CAAC;;AAGJ,KAAI,SAAS,WAAW,cAAc,EACpC,QAAO,KAAK,UAAU,QAAQ,cAAc,aAAa;EACvD,MAAM;EACN,IAAI,KAAK;EACV,CAAC;AAKJ,KAAI,sBAAsB,cAAc,EACtC,QAAO,KAAK,UAAU,QAAQ,cAAc,aAAa;EAAE,MAAM;EAAK,IAAI;EAAM,CAAC;CAInF,MAAM,6BACJ,eAAe,KAAK,uBAAuB,IAAI,CAAC,CAAC;CAInD,MAAM,aAAa,GAAG,cAAc,GAAG;CACvC,MAAM,gBAAgB,aAAa,GAAG;AAGtC,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,SAAS,QAAQ,KAAK;EAC7C,MAAM,QAAQ,KAAK,SAAS;AAC5B,MAAI,CAAC,MAAO;AAIZ,MAHmB,MAAM,MAGV,aAAa,SAC1B;AAIF,MAAI,IAAI,GAAG,qBAAqB,IAAI,GAAG,iBACrC;EAIF,IAAI,mBAAmB;EACvB,IAAI,2BAA2B;AAC/B,MAAI,SAAS,SAAS;GAEpB,MAAM,YAAY,MAAM;AACxB,OAAI,WAAW;IACb,MAAM,WAAW,UAAU,IAAI,OAAO,IAAI,OAAO,MAAM,QAAQ;IAC/D,MAAM,cAAc,WAAW,UAAU;IACzC,MAAM,kBAAkB,YAAY,cAAc,eAAe;AACjE,uBAAmB;AAGnB,+BAA2B,kBAAkB,mBAAmB,uBAAuB;;;AAK3F,MAAI,sBAAsB,kBAAkB;AAC1C,sBAAmB;AACnB,8BAA2B;;AAI7B,MAAI,oBAAoB,OAAO,kBAAkB,2BAA2B,CAC1E;AAIF,qBACE,OACA,QACA;GACE,cAAc,GAAG;GACjB,YAAY;GACZ,eAAe;GACf,iBAAiB;GACjB;GACA,uBAAuB;GACvB;GACA;GACD,EACD,IACD;;AAKH,KAAI,GAAG,eACL,MAAK,MAAM,UAAU,GAAG,gBAAgB;EACtC,MAAM,QAAQ,KAAK,SAAS,OAAO;AACnC,MAAI,CAAC,OAAO,QAAS;AAoBrB,qBACE,OACA,QACA;GACE,cAnBuB,OAAO,aAAa,OAAO;GAoBlD,YAAY;GACZ,eAAe;GACf,iBAAiB;GACjB;GACA,uBAAuB;GACvB;GACA;GACD,EACD,IACD;;;;;;AAQP,SAAS,qBACP,MACA,QACA,OACA,WACA,uBAAuB,OACvB,uBAAuB,OACvB,0BAA0B,OAC1B,KACM;CACN,MAAM,EACJ,cACA,YACA,eACA,iBACA,gBACA,uBACA,aACA,gBACE;CACJ,MAAM,QAAQ,uBAAuB,IAAI;CACzC,MAAM,SAAS,KAAK;AACpB,KAAI,CAAC,OAAQ;CAIb,MAAM,SAAS,MAAM,aAAa,MAAM,cAAc;CACtD,MAAM,SAAS,MAAM,aAAa,MAAM,cAAc;CACtD,MAAM,sBACJ,SAAS,QACL,uBAAuB,QAAQ,OAAO,YAAY,cAAc,OAAO,MAAM,GAC7E;CAMN,MAAM,oBAAoB,CAAC,EAAE,KAAK,kBAAkB,KAAK,eAAe,SAAS;CAMjF,MAAM,qBAAqB,qBAAqB;AAOhD,KAAI,oBAAoB;EACtB,MAAM,SAAS,MAAM,cACjB,cAAc,MAAM,GACpB;GAAE,KAAK;GAAG,QAAQ;GAAG,MAAM;GAAG,OAAO;GAAG;EAC5C,MAAM,UAAU,WAAW,MAAM;EACjC,IAAI,SAAS,OAAO,IAAI,OAAO,OAAO,QAAQ;EAC9C,IAAI,SAAS,OAAO,IAAI,eAAe,OAAO,MAAM,QAAQ;EAC5D,IAAI,SAAS,OAAO,QAAQ,OAAO,OAAO,OAAO,QAAQ,QAAQ,OAAO,QAAQ;EAChF,IAAI,SAAS,OAAO,SAAS,OAAO,MAAM,OAAO,SAAS,QAAQ,MAAM,QAAQ;AAEhF,MAAI,YAAY;GACd,MAAM,UAAU,WAAW;GAC3B,MAAM,aAAa,WAAW;AAC9B,OAAI,SAAS,SAAS;AACpB,cAAU,UAAU;AACpB,aAAS;;AAEX,OAAI,SAAS,SAAS,WACpB,UAAS,aAAa;AAExB,OAAI,WAAW,SAAS,KAAA,KAAa,SAAS,WAAW,MAAM;AAC7D,cAAU,WAAW,OAAO;AAC5B,aAAS,WAAW;;AAEtB,OAAI,WAAW,UAAU,KAAA,KAAa,SAAS,SAAS,WAAW,MACjE,UAAS,WAAW,QAAQ;;AAGhC,MAAI,SAAS,KAAK,SAAS,EACzB,QAAO,KAAK,QAAQ,QAAQ,QAAQ,QAAQ;GAAE,MAAM;GAAK,IAAI;GAAM,CAAC;;CAMxE,MAAM,sBACJ,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAyB,IACtD,wBACA;AACF,KAAI,MAAM,WAAW,uBAAuB,eAAe;AACzD,QAAM,MAAM;EACZ,MAAM,UAAoB,EAAE;AAC5B,MAAI,QAAQ,KAAK,WAAW,KAAK,YAAA,EAAyB,CAAE,SAAQ,KAAK,gBAAgB;AACzF,MAAI,qBAAsB,SAAQ,KAAK,uBAAuB;AAC9D,MAAI,wBAAyB,SAAQ,KAAK,0BAA0B;AACpE,QAAM,MAAM,sBAAsB,QAAQ,KAAK,IAAI;;CAErD,IAAI,eAAe,sBAAsB,QAAQ;CAQjD,IAAI,uBAAuB,wBAAyB,mBAAmB,CAAC,eAAe,MAAM;CAK7F,MAAM,6BACJ,eAAe,KAAK,uBAAuB,IAAI,CAAC,CAAC;AAInD,KAAI,oBAAoB;AACtB,iBAAe;AACf,yBAAuB;;CAczB,IAAI,sBAAsB;AAG1B,MAAK,MAAM,SAAS,KAAK,UAAU;EACjC,MAAM,aAAa,MAAM;AACzB,MAAI,WAAW,aAAa,YAAY;AACtC,yBAAsB;AACtB;;AAEF,MAAI,qBAAqB,WAAW,aAAa,SAC/C;AAMF,MAAI,oBAAoB,OAAO,cAAc,2BAA2B,CACtE;AAGF,qBACE,OACA,QACA;GACE;GACA,YAAY;GACZ,eAAe;GACf,iBAAiB;GACjB;GACA,uBAAuB;GACvB;GACA;GACD,EACD,IACD;;AAKH,KAAI,KAAK,eACP,MAAK,MAAM,UAAU,KAAK,gBAAgB;EACxC,MAAM,QAAQ,KAAK,SAAS,OAAO;AACnC,MAAI,CAAC,OAAO,QAAS;AAgBrB,qBACE,OACA,QACA;GACE,cAfuB,OAAO,aAAa,OAAO;GAgBlD,YAAY;GACZ,eAAe;GACf,iBAAiB;GACjB;GACA,uBAAuB;GACvB;GACA;GACD,EACD,IACD;;AAKL,KAAI,oBACF,MAAK,MAAM,SAAS,KAAK,UAAU;AAEjC,MADmB,MAAM,MACV,aAAa,WAAY;AAexC,qBACE,OACA,QACA;GACE;GACA,YAAY;GACZ,eAAe;GACf,iBAAiB;GACjB;GACA,uBAAuB;GACvB;GACA;GACD,EACD,IACD;;;;;;;;;;;;;;;;;;;;;AA2BP,SAAS,oBACP,OACA,cACA,4BACS;AAET,KAAI,CAAC,aAAc,QAAO;AAE1B,KAAI,2BAA4B,QAAO;AAEvC,KAAI,QAAQ,MAAM,WAAW,MAAM,YAAA,GAAwB,CAAE,QAAO;AAGpE,KAAI,eAAe,MAAM,uBAAuB,CAAE,QAAO;AAEzD,KAAI,MAAM,eAAe,MAAM,YAAY,WAAW,MAAM,YAAY,WAAY,QAAO;AAC3F,QAAO;;;;;;;;;;;AAgBT,SAAS,oBAAoB,MAAoB;AAC/C,MAAK,YAAY;AACjB,MAAK,aAAA;AACL,MAAK,yBAAA;;;;;AAMP,SAAS,gBAAgB,MAAoB;AAC3C,qBAAoB,KAAK;AACzB,MAAK,MAAM,SAAS,KAAK,SACvB,KAAI,MAAM,WACR,iBAAgB,MAAM;KAItB,uBAAsB,MAAM;;;;;;;;;AAYlC,SAAS,sBAAsB,MAAoB;AACjD,qBAAoB,KAAK;AACzB,MAAK,MAAM,SAAS,KAAK,SACvB,uBAAsB,MAAM;;;;;;;AAShC,SAAS,wBAAwB,MAAuB;AACtD,MAAK,MAAM,SAAS,KAAK,SACvB,KAAI,MAAM,WAAW,MAAM;MACrB,MAAM,QAAQ,MAAM,MAAM,WAAW,KAAK,MAAM,QAAQ,MAAM,MAAM,WAAW,EACjF,QAAO;;AAIb,QAAO;;;;;;;;;;;;;;;;;AAqBT,SAAS,oBAAoB,MAAuB;AAClD,MAAK,MAAM,SAAS,KAAK,UAAU;AACjC,MAAI,eAAe,MAAM,MAAkB,CAAE,QAAO;AACpD,MAAI,MAAM,SAAS,SAAS,KAAK,oBAAoB,MAAM,CAAE,QAAO;;AAEtE,QAAO;;;;;;AAuBT,SAAS,uBACP,QACA,OACA,YACA,eAAe,GAEf,aAAa,MAIb,WAAW,MACC;CACZ,MAAM,SAAS,MAAM,cAAc,cAAc,MAAM,GAAG;EAAE,KAAK;EAAG,QAAQ;EAAG,MAAM;EAAG,OAAO;EAAG;CAClG,MAAM,UAAU,WAAW,MAAM;CACjC,MAAM,YAAY,OAAO,IAAI;CAC7B,MAAM,WAAuB,WACzB;EACE,KAAK,YAAY,OAAO,MAAM,QAAQ;EACtC,QAAQ,YAAY,OAAO,SAAS,OAAO,SAAS,QAAQ;EAC7D,GACD;EAAE,KAAK;EAAW,QAAQ;EAAU;AACxC,KAAI,YAAY;AACd,WAAS,OAAO,OAAO,IAAI,OAAO,OAAO,QAAQ;AACjD,WAAS,QAAQ,OAAO,IAAI,OAAO,QAAQ,OAAO,QAAQ,QAAQ;;AAEpE,KAAI,CAAC,WAAY,QAAO;CACxB,MAAM,SAAqB;EACzB,KAAK,WAAW,KAAK,IAAI,WAAW,KAAK,SAAS,IAAI,GAAG,WAAW;EACpE,QAAQ,WAAW,KAAK,IAAI,WAAW,QAAQ,SAAS,OAAO,GAAG,WAAW;EAC9E;AACD,KAAI,cAAc,SAAS,SAAS,KAAA,KAAa,SAAS,UAAU,KAAA,GAAW;AAC7E,SAAO,OAAO,KAAK,IAAI,WAAW,QAAQ,GAAG,SAAS,KAAK;AAC3D,SAAO,QAAQ,KAAK,IAAI,WAAW,SAAS,UAAU,SAAS,MAAM;YAC5D,WAAW,SAAS,KAAA,KAAa,WAAW,UAAU,KAAA,GAAW;AAE1E,SAAO,OAAO,WAAW;AACzB,SAAO,QAAQ,WAAW;;AAE5B,QAAO;;;;;;;;;;;;;;;;;;AAuBT,SAAS,+BACP,MACA,QACA,QACA,cACA,YACA,qBACM;CACN,MAAM,UAAU,oBAAoB;CACpC,MAAM,YAAY,OAAO,IAAI,OAAO;CACpC,MAAM,aAAa,OAAO,IAAI,eAAe,OAAO;CACpD,MAAM,WAAW,OAAO;CACxB,MAAM,UAAU,OAAO,IAAI;AAE3B,0BACE,KAAK,UACL,QACA,UACA,SACA,WACA,YACA,cACA,YACA,QACD;;AAGH,SAAS,yBACP,UACA,QACA,UACA,SACA,WACA,YACA,cACA,YACA,SACM;AACN,MAAK,MAAM,SAAS,UAAU;AAC5B,MAAI,MAAM,cAAc,eAAe,MAAM,uBAAuB,EAAE;GACpE,MAAM,OAAO,MAAM;GACnB,MAAM,YAAY,KAAK,IAAI,KAAK;GAChC,MAAM,aAAa,KAAK,IAAI,eAAe,KAAK;GAChD,MAAM,UAAU,KAAK,IAAI;AAGzB,OAAI,YAAY,WAAW;IACzB,MAAM,YAAY;IAClB,MAAM,gBAAgB,KAAK,IAAI,WAAW,OAAO,MAAM,GAAG;IAC1D,MAAM,cAAc,KAAK,IAAI,SAAS,YAAY,OAAO,EAAE;IAC3D,MAAM,iBAAiB,KAAK,IAAI,YAAY,YAAY,UAAU,OAAO,OAAO;AAChF,QAAI,gBAAgB,KAAK,iBAAiB,YACxC,QAAO,KAAK,WAAW,aAAa,eAAe,iBAAiB,aAAa;KAC/E,MAAM;KACN,IAAI;KACL,CAAC;;AAIN,OAAI,aAAa,YAAY;IAC3B,MAAM,cAAc,KAAK,IAAI,YAAY,YAAY,OAAO,EAAE;IAC9D,MAAM,iBAAiB,KAAK,IAAI,YAAY,YAAY,UAAU,OAAO,OAAO;IAChF,MAAM,YAAY,KAAK,IAAI,KAAK,GAAG,YAAY,QAAQ,EAAE;IACzD,MAAM,gBAAgB,KAAK,IAAI,WAAW,YAAY,SAAS,OAAO,MAAM,GAAG;AAC/E,QAAI,gBAAgB,KAAK,iBAAiB,YACxC,QAAO,KAAK,WAAW,aAAa,eAAe,iBAAiB,aAAa;KAC/E,MAAM;KACN,IAAI;KACL,CAAC;;AAIN,OAAI,KAAK,IAAI,UAAU;IACrB,MAAM,YAAY,KAAK,IAAI,KAAK,GAAG,EAAE;IACrC,MAAM,gBAAgB,KAAK,IAAI,UAAU,OAAO,MAAM,GAAG;IACzD,MAAM,cAAc,KAAK,IAAI,SAAS,YAAY,OAAO,EAAE;IAC3D,MAAM,iBAAiB,KAAK,IAAI,YAAY,YAAY,UAAU,OAAO,OAAO;AAChF,QAAI,gBAAgB,KAAK,iBAAiB,YACxC,QAAO,KAAK,WAAW,aAAa,eAAe,iBAAiB,aAAa;KAC/E,MAAM;KACN,IAAI;KACL,CAAC;;AAIN,OAAI,UAAU,SAAS;IACrB,MAAM,cAAc,KAAK,IAAI,SAAS,YAAY,OAAO,EAAE;IAC3D,MAAM,iBAAiB,KAAK,IAAI,SAAS,YAAY,UAAU,OAAO,OAAO;IAC7E,MAAM,YAAY,KAAK,IAAI,KAAK,GAAG,YAAY,QAAQ,EAAE;IACzD,MAAM,gBAAgB,KAAK,IAAI,WAAW,YAAY,SAAS,OAAO,MAAM,GAAG;AAC/E,QAAI,gBAAgB,KAAK,iBAAiB,YACxC,QAAO,KAAK,WAAW,aAAa,eAAe,iBAAiB,aAAa;KAC/E,MAAM;KACN,IAAI;KACL,CAAC;;;AAKR,MAAI,QAAQ,MAAM,WAAW,MAAM,YAAA,GAAwB,IAAI,MAAM,aAAa,KAAA,EAChF,0BACE,MAAM,UACN,QACA,UACA,SACA,WACA,YACA,cACA,YACA,QACD;;;;;;;;;;AAYP,SAAS,gBACP,MACA,QACA,QACA,cACA,YACA,eACA,qBACM;CACN,MAAM,YAAY;CAClB,MAAM,UAAU,UAAU;CAC1B,MAAM,UAAU,OAAO,IAAI;CAI3B,MAAM,aAAa,KAAK,QAAQ;CAChC,MAAM,eAAe,aAAa,WAAW,IAAI,eAAe,WAAW,SAAS,KAAA;CAEpF,MAAM,SAAS,aAAa,KAAK,IAAI,SAAS,WAAW,IAAI,GAAG;CAChE,IAAI,cAAc,aACd,KAAK,IAAI,UAAU,OAAO,QAAQ,WAAW,OAAO,GACpD,UAAU,OAAO;AACrB,KAAI,iBAAiB,KAAA,EACnB,eAAc,KAAK,IAAI,aAAa,aAAa;CAKnD,IAAI,SAAS,OAAO;CACpB,IAAI,aAAa,OAAO;AACxB,KAAI,YAAY,SAAS,KAAA,KAAa,WAAW,UAAU,KAAA,GAAW;AACpE,MAAI,SAAS,WAAW,MAAM;AAC5B,iBAAc,WAAW,OAAO;AAChC,YAAS,WAAW;;AAEtB,MAAI,SAAS,aAAa,WAAW,MACnC,cAAa,KAAK,IAAI,GAAG,WAAW,QAAQ,OAAO;;AAGvD,KAAI,UAAU,cAAc;EAC1B,MAAM,gBAAgB,UAAU,aAAa,IAAI,UAAU,aAAa;EACxE,MAAM,eAAe,UAAU,aAAa;AAC5C,MAAI,SAAS,cAAc;AACzB,iBAAc,eAAe;AAC7B,YAAS;;AAEX,MAAI,SAAS,aAAa,cACxB,cAAa,KAAK,IAAI,GAAG,gBAAgB,OAAO;;CAIpD,MAAM,cAAc,cAAc;AAClC,KAAI,cAAc,KAAK,aAAa,GAAG;EACrC,MAAM,YAAY,cAAc;AAChC,MAAI,aAAa,gBAAgB,WAAW,QAAQ,QAAQ,YAAY,YAAY,EAAE;GAEpF,MAAM,MAAM,gBADC,KAAK,MAAkC,MAAiB,KAAK,KAC3C,QAAQ,OAAO,GAAG,OAAO,GAAG,WAAW,GAAG,YAAY,MAAM,OAAO,QAAQ,CAAC;AAC3G,aAAU,IAAI,KAAK,IAAI;AACvB,WAAQ,QAAQ,IAAI;;AAEtB,SAAO,KAAK,QAAQ,QAAQ,YAAY,aAAa;GACnD,MAAM;GACN,IAAI;GACL,CAAC;;AAIJ,iBAAgB,MAAM,QAAQ,QAAQ,cAAc,YAAY,eAAe,UAAU;;;;;;;;;;;;;;;;;;;AAoB3F,SAAS,gBACP,MACA,QACA,QACA,cACA,YACA,eACA,WACM;AACN,KAAI,CAAC,iBAAiB,CAAC,KAAK,WAAY;CACxC,MAAM,OAAO,KAAK;CAElB,MAAM,YAAY,cAAc;CAChC,MAAM,mBACJ,aAAa,gBAAgB,WAAW,KAAK,GAAG,KAAK,IAAI,cAAc,KAAK,OAAO,KAAK,OAAO;AAGjG,KAAI,KAAK,SAAS,OAAO,SAAS,KAAK,UAAU,OAAO,QAAQ;AAC9D,MAAI,aAAa,kBAAkB;GAEjC,MAAM,MACJ,yBAFW,KAAK,MAAkC,MAAiB,KAAK,KAE5C,QAAQ,KAAK,EAAE,GAAG,KAAK,IAAI,aAAa,GAAG,KAAK,MAAM,GAAG,KAAK,OAAA,OAClF,OAAO,EAAE,GAAG,OAAO,IAAI,aAAa,GAAG,OAAO,MAAM,GAAG,OAAO;AACxE,aAAU,IAAI,KAAK,IAAI;AACvB,WAAQ,QAAQ,IAAI;;AAEtB;;AAYF,KAAI,KAAK,MAAM,OAAO,KAAK,KAAK,MAAM,OAAO,GAAG;AAC9C,MAAI,aAAa,kBAAkB;GAEjC,MAAM,MACJ,qBAFW,KAAK,MAAkC,MAAiB,KAAK,KAEhD,QAAQ,KAAK,EAAE,GAAG,KAAK,IAAI,aAAa,GAAG,KAAK,MAAM,GAAG,KAAK,OAAA,OAC9E,OAAO,EAAE,GAAG,OAAO,IAAI,aAAa,GAAG,OAAO,MAAM,GAAG,OAAO,OAAA,OAC9D,OAAO,IAAI,KAAK,EAAE,MAAM,OAAO,IAAI,KAAK,EAAE;AACpD,aAAU,IAAI,KAAK,IAAI;AACvB,WAAQ,QAAQ,IAAI;;AAEtB;;CAGF,MAAM,UAAU,UAAU;CAC1B,MAAM,UAAU,OAAO,IAAI;CAC3B,MAAM,cAAc,KAAK,IAAI;CAM7B,MAAM,WAAW,UAAU,gBAAgB,KAAK,QAAQ;AACxD,KAAI,CAAC,SAAU;CAGf,IAAI,iBADoB,SAAS,IAAI,eACE,SAAS;CAChD,IAAI,gBAAgB,SAAS,IAAI,SAAS;CAQ1C,MAAM,SAAS,KAAK;AACpB,KAAI,QAAQ,SAAS;EACnB,MAAM,cAAc,OAAO;EAC3B,MAAM,SAAS,cAAc,YAAY;EACzC,MAAM,UAAU,WAAW,YAAY;EACvC,MAAM,cAAc,OAAO,QAAQ,IAAI,OAAO,QAAQ,QAAQ,OAAO,QAAQ,QAAQ;EACrF,MAAM,eACJ,OAAO,QAAQ,IAAI,eAAe,OAAO,QAAQ,SAAS,OAAO,SAAS,QAAQ;AACpF,kBAAgB,KAAK,IAAI,eAAe,YAAY;AACpD,mBAAiB,KAAK,IAAI,gBAAgB,aAAa;;AAIzD,KAAI,KAAK,QAAQ,OAAO,OAAO;EAC7B,MAAM,UAAU,OAAO,IAAI,OAAO;EAClC,IAAI,cAAc,KAAK,QAAQ,OAAO;AAItC,MAAI,UAAU,cAAc,cAC1B,eAAc,KAAK,IAAI,GAAG,gBAAgB,QAAQ;AAEpD,MAAI,cAAc,EAChB,aACE,QACA,SACA,aACA,aACA,cAAc,KAAK,QACnB,YACA,gBACA,QACD;;AAKL,KAAI,KAAK,SAAS,OAAO,QAAQ;EAC/B,IAAI,cAAc,KAAK;AAEvB,MAAI,OAAO,IAAI,cAAc,cAC3B,eAAc,KAAK,IAAI,GAAG,gBAAgB,OAAO,EAAE;AAErD,cACE,QACA,OAAO,GACP,aACA,UAAU,OAAO,QACjB,cAAc,KAAK,QACnB,YACA,gBACA,QACD;;;;AAKL,SAAS,YACP,QACA,GACA,OACA,KACA,QACA,YACA,aACA,IACM;CACN,MAAM,aAAa,aAAa,KAAK,IAAI,KAAK,WAAW,IAAI,GAAG;CAChE,MAAM,gBAAgB,KAAK,IACzB,aAAa,KAAK,IAAI,QAAQ,WAAW,OAAO,GAAG,QACnD,YACD;CACD,IAAI,WAAW;CACf,IAAI,eAAe;AACnB,KAAI,YAAY,SAAS,KAAA,KAAa,WAAW,UAAU,KAAA,GAAW;AACpE,MAAI,WAAW,WAAW,MAAM;AAC9B,mBAAgB,WAAW,OAAO;AAClC,cAAW,WAAW;;AAExB,MAAI,WAAW,eAAe,WAAW,MACvC,gBAAe,KAAK,IAAI,GAAG,WAAW,QAAQ,SAAS;;CAG3D,MAAM,SAAS,gBAAgB;AAC/B,KAAI,SAAS,KAAK,eAAe,EAC/B,QAAO,KAAK,UAAU,YAAY,cAAc,QAAQ;EAAE,MAAM;EAAK;EAAI,CAAC;;;;aCjvEV;;AAcpE,SAAgB,WAAW,OAA+B;AACxD,KAAI,UAAU,KAAM,QAAO;AAC3B,KAAI,OAAO,UAAU,UAAU;EAC7B,MAAM,MAAM,aAAa,MAAM;AAC/B,SAAO,SAAS,IAAI,GAAG,IAAI,GAAG,IAAI,EAAE;;AAEtC,KAAI,YAAY,MAAM,CAAE,QAAO;AAC/B,QAAO,SAAS,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE;;AAG5C,SAAgB,SAAS,GAAW,GAAW,GAAqB;CAClE,MAAM,SAAS,MAAsB;AAEnC,SADU,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC,CAAC,CAC1C,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;;AAExC,QAAO,IAAI,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE;;;;;;;;;;AAW3C,SAAgBC,WAAS,KAAyD;AAChF,KAAI,OAAO,QAAQ,SAAU,QAAO;CACpC,IAAI,IAAI,IAAI,MAAM,CAAC,aAAa;AAChC,KAAI,EAAE,WAAW,IAAI,CAAE,KAAI,EAAE,MAAM,EAAE;AACrC,KAAI,EAAE,WAAW,GAAG;AAClB,MAAI,CAAC,gBAAgB,KAAK,EAAE,CAAE,QAAO;AACrC,MAAI,EAAE,KAAM,EAAE,KAAM,EAAE,KAAM,EAAE,KAAM,EAAE,KAAM,EAAE;YACrC,CAAC,gBAAgB,KAAK,EAAE,CACjC,QAAO;AAET,QAAO;EACL,GAAG,SAAS,EAAE,MAAM,GAAG,EAAE,EAAE,GAAG;EAC9B,GAAG,SAAS,EAAE,MAAM,GAAG,EAAE,EAAE,GAAG;EAC9B,GAAG,SAAS,EAAE,MAAM,GAAG,EAAE,EAAE,GAAG;EAC/B;;;;;;;;;;;;;AAcH,SAAgB,aAAa,KAAiD;AAC5E,KAAI,QAAQ,QAAQ,QAAQ,KAAA,EAAW,QAAO;CAC9C,MAAM,MAAMA,WAAS,IAAI;AACzB,KAAI,CAAC,IAAK,QAAO;AACjB,QAAO,SAAS,IAAI,GAAG,IAAI,GAAG,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC8BtC,MAAa,qBAAqB;;AAElC,MAAa,6BAA6B;;;;;;;;AAS1C,MAAa,2BAA2B;;AAGxC,MAAa,aAAuB;AACpC,MAAa,cAAwB;;;;;;;;;AAUrC,MAAa,iBAAiB;;AAuG9B,MAAa,gBAAsB,OAAO,OAAO;CAC/C,QAAQ;CACR,QAAQ;CACR,OAAO;CACP,WAAW;CACX,WAAW;CACX,UAAU,OAAO,OAAO,EAAE,CAAC;CAC3B,UAAU,OAAO,OAAO,EAAE,CAAC;CAC3B,cAAc;CACd,kBAAkB;CAClB,cAAc;CACf,CAAC;;;;;;;AAQF,SAAgB,mBAAmB,MAAuB;CACxD,MAAM,QAAQ,KAAK;AACnB,KAAI,MAAA,0BAA8B,KAAA,KAAa,MAAA,mCAAsC,KAAA,EACnF,QAAO;AAET,MAAK,MAAM,SAAS,KAAK,SACvB,KAAI,mBAAmB,MAAM,CAAE,QAAO;AAExC,QAAO;;;;;;;;;;;;;;AAeT,SAAgB,UAAU,MAAc,SAAiC;AAEvE,MAD+B,SAAS,cAAc,iBACnC,OAAQ,QAAO;CAIlC,MAAM,WAAuB,EAAE;CAC/B,MAAM,WAAuB,EAAE;CAC/B,MAAM,iBAA2B,EAAE;CACnC,MAAM,iBAA2B,EAAE;AACnC,wBAAuB,MAAM,UAAU,UAAU,gBAAgB,eAAe;AAEhF,KAAI,SAAS,WAAW,KAAK,SAAS,WAAW,EAAG,QAAO;CAc3D,MAAM,YAAY,aAAa,SAAS,aAAa,KAAK;CAC1D,MAAM,gBAAgB,SAAS;CAO/B,MAAM,SADJ,OAAO,kBAAkB,YAAY,kBAAkB,SAAS,aAAa,cAAc,GAAG,SACjE,qBAAqB,UAAU;CAM9D,MAAM,mBAAmB,aAAa,MAAM;CAK5C,MAAM,YACJ,aAAa,SAAS,UAAU,KAAK,UAAU,OAAO,OAAO,mBAAA,YAAA;CAK/D,MAAM,EAAE,QAAQ,oBAAoB,mBAAmB,gBAAgB,eAAe;AAMtF,QAAO;EACL,QAAQ;EACR;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA,cAZmB,SAAS,kBAAkB,QAAQ,UAAU;EAajE;;;;;;;;;;;;AAaH,SAAS,mBACP,gBACA,gBAC8C;CAC9C,MAAM,QACJ,eAAe,SAAS,IACpB,eAAe,KACf,eAAe,SAAS,IACtB,eAAe,KACf;CACR,IAAI,kBAAkB;AACtB,MAAK,MAAM,KAAK,eACd,KAAI,KAAK,IAAI,IAAI,MAAM,GAAG,MAAM;AAC9B,oBAAkB;AAClB;;AAGJ,KAAI,CAAC;OACE,MAAM,KAAK,eACd,KAAI,KAAK,IAAI,IAAI,MAAM,GAAG,MAAM;AAC9B,qBAAkB;AAClB;;;AAIN,QAAO;EAAE,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,MAAM,CAAC;EAAE;EAAiB;;;;;;;;AASrE,SAAS,qBAAqB,IAAsC;AAClE,KAAI,CAAC,GAAI,QAAO;CAChB,MAAM,MAAM,kBAAkB,GAAG;AACjC,KAAI,QAAQ,KAAM,QAAO;AACzB,QAAO,MAAA,MAAiC,aAAa;;;;;;;;;AAUvD,SAAS,aAAa,OAAiC;AACrD,KAAI,UAAU,KAAM,QAAO;CAC3B,MAAM,MAAM,kBAAkB,MAAM;AACpC,KAAI,QAAQ,KAAM,QAAO;AACzB,QAAO,OAAO;;AAGhB,SAAS,uBACP,MACA,UACA,UACA,gBACA,gBACM;CACN,MAAM,QAAQ,KAAK;CACnB,MAAM,aAAa,MAAM;CACzB,MAAM,aAAa,MAAM;AAEzB,KAAI,eAAe,KAAA,KAAa,eAAe,KAAA,GAAW;EACxD,MAAM,OAAO,KAAK,cAAc,KAAK,cAAc,KAAK;AACxD,MAAI,QAAQ,KAAK,QAAQ,KAAK,KAAK,SAAS,GAAG;GAC7C,MAAM,MAAM,UAAU,WAAW;AACjC,OAAI,QAAQ,MAAM;AAChB,aAAS,KAAK,EAAE,MAAM,CAAC;AACvB,mBAAe,KAAK,IAAI;;GAE1B,MAAM,MAAM,UAAU,WAAW;AACjC,OAAI,QAAQ,MAAM;AAChB,aAAS,KAAK,EAAE,MAAM,CAAC;AACvB,mBAAe,KAAK,IAAI;;;;AAK9B,MAAK,MAAM,SAAS,KAAK,SACvB,wBAAuB,OAAO,UAAU,UAAU,gBAAgB,eAAe;;;;;;;;;;;;;;;;;;;;;;;AAyBrF,SAAS,UAAU,KAA6B;AAC9C,KAAI,QAAQ,KAAA,KAAa,QAAQ,KAAM,QAAO;AAC9C,KAAI,QAAQ,MAAO,QAAO;AAC1B,KAAI,QAAQ,KAAM,QAAO;AACzB,KAAI,QAAQ,GAAI,QAAO;CACvB,MAAM,IAAI,OAAO,QAAQ,WAAW,MAAM,OAAO,IAAI;AACrD,KAAI,CAAC,OAAO,SAAS,EAAE,CAAE,QAAO;AAChC,KAAI,KAAK,EAAG,QAAO;AACnB,QAAO,IAAI,IAAI,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AChcrB,SAAS,aAAa,GAAW,GAAW,GAAmB;CAC7D,MAAM,KAAKC,WAAS,EAAE;CACtB,MAAM,KAAKA,WAAS,EAAE;AACtB,KAAI,CAAC,MAAM,CAAC,GAAI,QAAO;CACvB,MAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,EAAE,CAAC;AAIrC,QAAO,SAHG,GAAG,KAAK,IAAI,KAAK,GAAG,IAAI,GACxB,GAAG,KAAK,IAAI,KAAK,GAAG,IAAI,GACvB,GAAG,KAAK,IAAI,KAAK,GAAG,IAAI,EACV;;;AAQ3B,MAAa,UALY,SAAgD,WAKc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCvF,SAAS,4BAA4B,KAAa,QAAgB,aAA8B;CAC9F,MAAM,IAAI,mBAAmB,IAAI;AACjC,KAAI,CAAC,EAAG,QAAO;CACf,MAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC;CAC1C,MAAM,gBAAgB,IAAI,MAAM,IAAI;CACpC,MAAM,IAAI,cAAc,EAAE,KAAK,IAAI,EAAE,KAAK,IAAI,EAAE,KAAK,IAAI;AACzD,QAAO,mBAAmB;EACxB,GAAG,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,EAAE,CAAC;EAC9B,GAAG,KAAK,IAAI,GAAG,EAAE,IAAI,aAAa;EAClC,GAAG,EAAE;EACN,CAAC;;AAOJ,MAAM,qBAAsB,SAAgD;AAK5E,MAAM,qBAAsB,SAAgD;AAkB5E,MAAa,yBALsB,SAChC,0BAK4B;;;;;;;;;;;;;;;;;;;ACvE/B,SAAgB,sBACd,aACA,cACA,UACA,UACA,OACQ;AACR,KAAI,eAAe,KAAK,gBAAgB,EAAG,QAAO;AAClD,KAAI,SAAS,WAAW,KAAK,SAAS,WAAW,EAAG,QAAO;CAE3D,MAAM,OAAO,IAAI,WAAW,cAAc,aAAa;CACvD,IAAI,QAAQ;CAEZ,MAAM,QAAQ,GAAW,MAAoB;EAC3C,MAAM,IAAI,IAAI,cAAc;AAC5B,MAAI,KAAK,OAAO,EAAG;AACnB,OAAK,KAAK;AACV,WAAS;AACT,QAAM,GAAG,EAAE;;AAGb,MAAK,MAAM,EAAE,UAAU,UAAU;EAC/B,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,EAAE;EAC9B,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,EAAE;EAC9B,MAAM,KAAK,KAAK,IAAI,aAAa,KAAK,IAAI,KAAK,MAAM;EACrD,MAAM,KAAK,KAAK,IAAI,cAAc,KAAK,IAAI,KAAK,OAAO;AACvD,MAAI,MAAM,MAAM,MAAM,GAAI;AAC1B,OAAK,IAAI,IAAI,IAAI,IAAI,IAAI,IACvB,MAAK,IAAI,IAAI,IAAI,IAAI,IAAI,IAAK,MAAK,GAAG,EAAE;;AAI5C,KAAI,SAAS,SAAS,GAAG;EAUvB,MAAM,UAAqE,EAAE;AAC7E,OAAK,MAAM,EAAE,UAAU,UAAU;GAC/B,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,EAAE;GAC9B,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,EAAE;GAC9B,MAAM,KAAK,KAAK,IAAI,aAAa,KAAK,IAAI,KAAK,MAAM;GACrD,MAAM,KAAK,KAAK,IAAI,cAAc,KAAK,IAAI,KAAK,OAAO;AACvD,OAAI,KAAK,MAAM,KAAK,GAAI,SAAQ,KAAK;IAAE;IAAI;IAAI;IAAI;IAAI,CAAC;;AAE1D,MAAI,QAAQ,SAAS,EACnB,MAAK,IAAI,IAAI,GAAG,IAAI,cAAc,IAChC,MAAK,IAAI,IAAI,GAAG,IAAI,aAAa,KAAK;GACpC,IAAI,mBAAmB;AACvB,QAAK,MAAM,KAAK,QACd,KAAI,KAAK,EAAE,MAAM,IAAI,EAAE,MAAM,KAAK,EAAE,MAAM,IAAI,EAAE,IAAI;AAClD,uBAAmB;AACnB;;AAGJ,OAAI,CAAC,iBAAkB,MAAK,GAAG,EAAE;;MAQrC,MAAK,IAAI,IAAI,GAAG,IAAI,cAAc,IAChC,MAAK,IAAI,IAAI,GAAG,IAAI,aAAa,IAAK,MAAK,GAAG,EAAE;;AAKtD,QAAO;;;;;;;;;;;;;;;;;;;ACzET,SAAgB,gBAAgB,MAAY,QAAiC;AAC3E,KAAI,CAAC,KAAK,OAAQ,QAAO;AACzB,KAAI,KAAK,UAAU,EAAG,QAAO;CAE7B,IAAI,WAAW;AACf,uBAAsB,OAAO,OAAO,OAAO,QAAQ,KAAK,UAAU,KAAK,WAAW,GAAG,MAAM;AACzF,MAAI,SAAS,QAAQ,GAAG,GAAG,KAAK,CAAE,YAAW;GAC7C;AACF,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CT,SAAS,SAAS,QAAwB,GAAW,GAAW,MAAqB;AAGnF,KAAI,OAAO,mBAAmB,GAAG,EAAE,CAAE,QAAO;CAE5C,MAAM,OAAO,OAAO,QAAQ,GAAG,EAAE;CAOjC,MAAM,eAAe,KAAK,QAAQ,cAAc,KAAK,QAAQ,GAAG;AAOhE,KAAI,KAAK,gBAAgB,aAAc,QAAO;CAE9C,MAAM,EAAE,QAAQ,OAAO,WAAW,WAAW,qBAAqB;CAClE,MAAM,WAAW,WAAW,KAAK,GAAG;AAEpC,KAAI,UAAU,MAAM;EAYlB,MAAM,QACJ,YAAY,cAAc,mBAAA,YAAA;EAQ5B,MAAM,QAAyB,WAAW,KAAK,GAAG,IAAI;EACtD,MAAM,aAAa,UAAU,OAAO,QAAQ,OAAO,OAAO,OAAO,GAAG;EACpE,MAAM,UAAU,eAAe,OAAOC,WAAS,WAAW,GAAG;EAM7D,MAAM,gBAAgB;EACtB,MAAM,WAAW,iBAAiB,CAAC,KAAK,MAAM,MAAM;GAAE,GAAG,KAAK;GAAO,KAAK;GAAM,GAAG,KAAK;EAOxF,MAAM,UAAUA,WADU,uBAAuB,OAAO,QAAQ,iBAAiB,CACtC;AAE3C,MAAI,SAAS;AACX,OAAI,SAAS;AACX,WAAO,QAAQ,GAAG,GAAG;KAAE,GAAG;KAAM,IAAI;KAAS,IAAI;KAAS,OAAO;KAAU,CAAC;AAC5E,4BAAwB,QAAQ,MAAM,GAAG,GAAG;KAAE,IAAI;KAAS,KAAK;KAAe,CAAC;AAChF,WAAO;;AAET,UAAO,QAAQ,GAAG,GAAG;IAAE,GAAG;IAAM,IAAI;IAAS,OAAO;IAAU,CAAC;AAC/D,OAAI,cAAe,yBAAwB,QAAQ,MAAM,GAAG,GAAG,EAAE,KAAK,MAAM,CAAC;AAC7E,UAAO;;AAKT,MAAI,SAAS;AACX,UAAO,QAAQ,GAAG,GAAG;IAAE,GAAG;IAAM,IAAI;IAAS,OAAO;IAAU,CAAC;AAC/D,2BAAwB,QAAQ,MAAM,GAAG,GAAG;IAAE,IAAI;IAAS,KAAK;IAAe,CAAC;AAChF,UAAO;;AAET,MAAI,KAAK,MAAM,IAAK,QAAO;AAC3B,SAAO,QAAQ,GAAG,GAAG;GAAE,GAAG;GAAM,OAAO;IAAE,GAAG,KAAK;IAAO,KAAK;IAAM;GAAE,CAAC;AACtE,SAAO;;CAGT,MAAM,QAAQ;CAGd,MAAM,QAAQ,WAAW,KAAK,GAAG;AAEjC,KAAI,SAAS,OAAO;EAElB,MAAM,WAAWA,WADA,QAAQ,OAAO,OAAO,OAAO,CACX;AACnC,MAAI,CAAC,SAAU,QAAO;AAKtB,MAAI,cAAc;GAChB,MAAM,WAAW,KAAK,MAAM,MAAM,KAAK,QAAQ;IAAE,GAAG,KAAK;IAAO,KAAK;IAAM;AAC3E,UAAO,QAAQ,GAAG,GAAG;IAAE,GAAG;IAAM,IAAI;IAAU,OAAO;IAAU,CAAC;AAChE,2BAAwB,QAAQ,MAAM,GAAG,GAAG,EAAE,KAAK,MAAM,CAAC;AAC1D,UAAO;;AAET,SAAO,QAAQ,GAAG,GAAG;GAAE,GAAG;GAAM,IAAI;GAAU,CAAC;AAC/C,SAAO;;AAKT,KAAI,KAAK,MAAM,IAAK,QAAO;AAC3B,QAAO,QAAQ,GAAG,GAAG;EAAE,GAAG;EAAM,OAAO;GAAE,GAAG,KAAK;GAAO,KAAK;GAAM;EAAE,CAAC;AACtE,KAAI,KAAK,QAAQ,IAAI,IAAI,OAAO,OAAO;EACrC,MAAM,OAAO,OAAO,QAAQ,IAAI,GAAG,EAAE;AACrC,MAAI,CAAC,KAAK,MAAM,IACd,QAAO,QAAQ,IAAI,GAAG,GAAG;GAAE,GAAG;GAAM,OAAO;IAAE,GAAG,KAAK;IAAO,KAAK;IAAM;GAAE,CAAC;;AAG9E,QAAO;;;;;;;;;;;;;;AAeT,SAAS,wBACP,QACA,UACA,GACA,GACA,OACM;AACN,KAAI,CAAC,SAAS,KAAM;AACpB,KAAI,IAAI,KAAK,OAAO,MAAO;CAC3B,MAAM,OAAO,OAAO,QAAQ,IAAI,GAAG,EAAE;AACrC,KAAI,CAAC,KAAK,aAAc;CAExB,MAAM,WAAW,MAAM,QAAQ,QAAQ,CAAC,KAAK,MAAM;CACnD,MAAM,UAAU,MAAM,OAAO,KAAA;AAG7B,KAAI,CAAC,YAAY,CAAC,QAAS;CAE3B,MAAM,QAAQ,WAAW;EAAE,GAAG,KAAK;EAAO,KAAK;EAAM,GAAG,KAAK;AAC7D,KAAI,QACF,QAAO,QAAQ,IAAI,GAAG,GAAG;EAAE,GAAG;EAAM,IAAI,MAAM;EAAI;EAAO,CAAC;KAE1D,QAAO,QAAQ,IAAI,GAAG,GAAG;EAAE,GAAG;EAAM;EAAO,CAAC;;;;UC7O1B;;;;;;;;;;;;;;;;AAsBtB,SAAgB,eAAe,MAAY,QAAgC;AACzE,KAAI,CAAC,KAAK,UAAU,CAAC,KAAK,gBAAgB,KAAK,UAAU,KAAK,KAAK,UAAU,KAAM,QAAO;CAE1F,MAAM,QAAQ,8BAA8B,QAAQ,KAAK;CAMzD,MAAM,OAAOC,WADG,KAAK,SAAS,KAAK,aAAa,UAClB,IAAI;EAAE,GAAG;EAAG,GAAG;EAAG,GAAG;EAAG;CACtD,MAAM,aAAa,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC;CAE5E,MAAM,QAAkB,EAAE;AAC1B,OAAM,KAAA,QAAiB;AAEvB,KAAI,MAAM,WAAW,GAAG;AAGtB,QAAM,KAAK,+BAA+B,CAAC;AAC3C,QAAM,KAAA,QAAoB;AAC1B,SAAO,MAAM,KAAK,GAAG;;CAGvB,MAAM,SAAS,iBAAiB,MAAM,WAAW;AACjD,OAAM,KAAK,sBAAsB,QAAQ,GAAG,EAAE,CAAC;AAC/C,OAAM,KAAK,+BAA+B,CAAC;AAE3C,MAAK,MAAM,EAAE,GAAG,OAAO,OAAO;AAC5B,QAAM,KAAK,MAAM,GAAG,EAAE,CAAC;AACvB,QAAM,KACJ,aAAa;GACX,aAAa,oBAAoB,GAAG,EAAE;GACtC,MAAM;GACN,MAAM;GACN,GAAG;GACJ,CAAC,CACH;;AAEH,OAAM,KAAA,QAAoB;AAC1B,QAAO,MAAM,KAAK,GAAG;;;;;;;;;;;;;;AAevB,SAAS,8BACP,QACA,MACiC;CACjC,MAAM,MAAuC,EAAE;AAC/C,uBAAsB,OAAO,OAAO,OAAO,QAAQ,KAAK,UAAU,KAAK,WAAW,GAAG,MAAM;AACzF,MAAI,IAAI,KAAK,OAAO,MAAO;AAC3B,MAAI,CAAC,OAAO,WAAW,GAAG,EAAE,CAAE;AAC9B,MAAI,OAAO,mBAAmB,GAAG,EAAE,CAAE;AAErC,MAAI,CAAC,cADQ,OAAO,QAAQ,GAAG,EAAE,CACT,QAAQ,GAAG,CAAE;AACrC,MAAI,KAAK;GAAE;GAAG;GAAG,CAAC;GAClB;AACF,QAAO;;;;ACLT,MAAM,eAA+B,OAAO,OAAO;CACjD,UAAU;CACV,SAAS;CACV,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BF,SAAgB,cACd,MACA,QACA,SACgB;CAChB,MAAM,OAAO,UAAU,MAAM,QAAQ;AAKrC,KAAI,KAAK,gBAAgB,QAAQ,IAAI,aAAa,aAEhD,SAAQ,KACN,gEAAgE,KAAK,OAAO,wFAE7E;AAGH,KAAI,CAAC,KAAK,OAAQ,QAAO;AASzB,QAAO;EAAE,UAPQ,gBAAgB,MAAM,OAAO;EAO3B,SAFH,KAAK,eAAe,eAAe,MAAM,OAAO,GAAG;EAEvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aCjJiC;UAmB2B;AAI1F,MAAM,MAAM,aAAa,iBAAiB;AAC1C,MAAM,UAAU,aAAa,oBAAoB;;;;;;;;;;;;;;;AAwJjD,SAAS,gBAAgB,MAA6B;CACpD,MAAM,QAAQ,KAAK;AACnB,KAAI,MAAM,OAAO;EACf,MAAM,QAAQ,MAAM;EACpB,MAAM,aAAa,MAAM;AACzB,MAAI,OAAO,eAAe,SAAU,QAAO;EAC3C,MAAM,WAAW,MAAM;AACvB,MAAI,OAAO,aAAa,SAAU,QAAO;;AAE3C,MAAK,MAAM,SAAS,KAAK,UAAU;EACjC,MAAM,QAAQ,gBAAgB,MAAM;AACpC,MAAI,UAAU,KAAM,QAAO;;AAE7B,QAAO;;;;;;;;;;;;;;;;;;;;;;;AAwBT,SAAS,gCAAyC;CAChD,MAAM,MACJ,OAAO,YAAY,cAAc,QAAQ,MAAO,EAAE;CAEpD,MAAM,WAAW,IAAI;AACrB,KAAI,aAAa,OAAO,aAAa,QAAS,QAAO;AACrD,KAAI,aAAa,OAAO,aAAa,OAAQ,QAAO;AAKpD,KAAI,IAAI,KAAM,QAAO;CAErB,MAAM,UAAU,IAAI,gBAAgB;AACpC,KAAI,YAAY,aAAa,YAAY,aAAa,YAAY,UAAW,QAAO;AAGpF,MADa,IAAI,QAAQ,IAChB,SAAS,QAAQ,CAAE,QAAO;AAEnC,KAAI,IAAI,gBAAiB,QAAO;AAEhC,QAAO;;AAOT,SAAgB,SAAS,MAAc,SAA+B;CACpE,MAAM,WAAW,SAAS;CAC1B,MAAM,aAAiC,SAAS,cAAc;CAK9D,MAAM,gBACJ,SAAS,kBAAkB,KAAA,IAAY,QAAQ,gBAAgB,+BAA+B;CAChG,MAAM,MAAmC,WAAW,EAAE,UAAU,GAAG,KAAA;CACnE,IAAI,cAAqC;CAMzC,IAAI,eAAe;CAKnB,IAAI,YAAY;CAChB,IAAI,YAAY;CAEhB,SAAS,SACP,MACA,MACA,MAC8F;;;GAO9F,MAAM,iBAAiB,KAAK;AAG5B,OAAI,EADF,mBAAmB,eAAe,UAAU,QAAQ,eAAe,WAAW,UACtD,CAAC,KAAK,YAAY,SAAS,IAAI,CAAC,gBAAgB,EAAE;AAC1E,QAAI,QAAQ,wEAAwE;AAKpF,gBAAY,MAAM,MAAM,KAAK;AAC7B,WAAO;KAAE,UAAU;KAAG,SAAS;KAAG,SAAS;KAAG,aAAa;KAAG,SAAS;KAAG;;GAG5E,MAAM,SAAA,YAAA,EAAS,QAAQ,KAAK,YAAY;IAAE,OAAO;IAAM,QAAQ;IAAM,CAAC,CAAA;GAEtE,IAAI;AACJ,OAAA;;AACQ,eAAA,EAAK,OAAO,KAAK,UAAU,CAAA;IACjC,MAAM,IAAI,YAAY,KAAK;AAC3B,iBAAa,MAAM,IAAI;AACvB,eAAW,YAAY,KAAK,GAAG;AAC/B,QAAI,QAAQ,YAAY,SAAS,QAAQ,EAAE,CAAC,IAAI;;;;;;GAGlD,IAAI;AACJ,OAAA;;AACQ,eAAA,EAAK,OAAO,KAAK,SAAS,CAAA;IAChC,MAAM,IAAI,YAAY,KAAK;AAC3B,gBAAY,MAAM,MAAM,KAAK;AAC7B,cAAU,YAAY,KAAK,GAAG;AAC9B,QAAI,QAAQ,WAAW,QAAQ,QAAQ,EAAE,CAAC,IAAI;;;;;;AAKhD,6BAA0B,KAAK;AAI/B,OAAI,CAAC,aAAa,CAAC,WAAW;IAC5B,MAAM,WAAW,uBAAuB,KAAK;AAC7C,QAAI,SAAS,UAAW,aAAY;AACpC,QAAI,SAAS,UAAW,aAAY;;GAGtC,IAAI;AACJ,OAAI,UAAW,KAAA;;AACP,eAAA,EAAK,OAAO,KAAK,SAAS,CAAA;IAChC,MAAM,IAAI,YAAY,KAAK;AAC3B,gBAAY,MAAM,EAAE,kBAAkB,MAAM,wBAAwB,CAAC;AACrE,cAAU,YAAY,KAAK,GAAG;;;;;;OAE9B,WAAU;AAGZ,OAAI,UACF,aAAY,KAAK;GAGnB,IAAI;AACJ,OAAA;;AACQ,eAAA,EAAK,OAAO,KAAK,aAAa,CAAA;IACpC,MAAM,IAAI,YAAY,KAAK;AAC3B,QAAI,aAAa,UACf,iBAAgB,KAAK;QAIrB,uBAAsB,KAAK;AAE7B,kBAAc,YAAY,KAAK,GAAG;;;;;;GAGpC,IAAI,UAAU;AACd,OAAI,CAAC,MAAM,wBAAyB,KAAA;;AAC5B,eAAA,EAAK,OAAO,KAAK,SAAS,CAAA;IAChC,MAAM,IAAI,YAAY,KAAK;AAC3B,4BAAwB,KAAK;AAC7B,cAAU,YAAY,KAAK,GAAG;;;;;;GAMhC,MAAM,MAAO,WAAmB;AAChC,OAAI,KAAK;AACP,QAAI,WAAW;AACf,QAAI,UAAU;AACd,QAAI,UAAU;AACd,QAAI,cAAc;AAClB,QAAI,UAAU;AACd,QAAI,eAAe,WAAW,UAAU,UAAU,cAAc;;AAGlE,UAAO;IAAE;IAAU;IAAS;IAAS;IAAa;IAAS;;;;;;;CAG7D,SAAS,SAAS,MAA+D;AAC/E,2BAAyB;EACzB,MAAM,aAAa,MAAM,QACrB,OACA,MAAM,eAAe,KAAA,IACnB,KAAK,aACL;EAEN,IAAI;EACJ,IAAI;EACJ;GACE,MAAM,IAAI,YAAY,KAAK;AAC3B,YAAS,YAAY,MAAM,YAAY,IAAI;AAC3C,cAAW,YAAY,KAAK,GAAG;AAC/B,OAAI,QAAQ,YAAY,SAAS,QAAQ,EAAE,CAAC,IAAI;;EAgBlD,IAAI;EACJ,IAAI,UAAU;EACd,MAAM,iBAAiB,mBAAmB,KAAK;AAC/C,MAAI,gBAAgB;AAClB,wBAAqB,OAAO,OAAO;AACnC,OAAI,CAAC,MAAM,MACT,eAAc;GAEhB,MAAM,YAAY,gBAAgB,KAAK,IAAI,KAAA;AAM3C,aALe,cAAc,MAAM,QAAQ;IACzC;IACA;IACA;IACD,CAAC,CACe;SACZ;AACL,wBAAqB;AACrB,OAAI,CAAC,MAAM,MACT,eAAc;;EAalB,MAAM,uBAAuB,kBAAkB,QAAQ,SAAS;AAChE,MAAI,gBAAgB,CAAC,qBACnB,WAAA,UAAwB,+BAA+B,GAAA;AAEzD,iBAAe;AAKf,sBAAoB;EAGpB,MAAM,MAAO,WAAmB;AAChC,MAAI,KAAK;AACP,OAAI,WAAW;AACf,OAAI,eAAe;;AAIrB,SAAO;GAAE,OADK,gBAAgB,OAAO;GACrB;GAAQ;GAAoB;GAAY;GAAU;GAAS;;CAO7E,SAAS,aAAa,MAAkB,OAAwC;AAG9E,SAAO;GACL;GACA;GACA,UAAU,EAAE;GACZ,QAAQ;GACR,YAPa,iBAAiB,CACN,YAAY;GAOpC,SAAS;GACT,YAAY;GACZ,YAAY;GACZ,YAAY;GACZ,gBAAgB;GAChB,gBAAgB;GAChB,wBAAA;GACA,WAAA;GACA,YAAY,gBAAgB;GAC7B;;CAGH,SAAS,cAAc,QAAgB,OAAe,OAAqB;AAEzE,MAAI,MAAM,OACR,eAAc,MAAM,QAAQ,MAAM;AAIpC,SAAO,SAAS,OAAO,OAAO,GAAG,MAAM;AACvC,QAAM,SAAS;AAGf,MAAI,OAAO,cAAc,MAAM,YAAY;GAEzC,MAAM,cAAc,OAAO,SACxB,MAAM,GAAG,MAAM,CACf,QAAQ,MAAM,EAAE,eAAe,KAAK,CAAC;AACxC,UAAO,WAAW,YAAY,MAAM,YAAY,YAAY;;;CAIhE,SAAS,cAAc,QAAgB,OAAqB;EAC1D,MAAM,QAAQ,OAAO,SAAS,QAAQ,MAAM;AAC5C,MAAI,UAAU,GAAI;AAElB,SAAO,SAAS,OAAO,OAAO,EAAE;AAEhC,MAAI,OAAO,cAAc,MAAM,YAAY;AACzC,UAAO,WAAW,YAAY,MAAM,WAAW;AAC/C,SAAM,WAAW,MAAM;;AAGzB,QAAM,SAAS;;AAGjB,QAAO;EACL;EAGA,OAAO,MAAM,SAAS;AACpB,OAAI,SACF,iBAAgB,gBAAgB,SAAS,KAAK,MAAM,KAAK,MAAM,QAAQ,CAAC;OAExE,UAAS,KAAK,MAAM,KAAK,MAAM,QAAQ;;EAI3C,OAAO,SAAS;GACd,MAAM,SAAS,WACX,gBAAgB,gBAAgB,SAAS,QAAQ,CAAC,GAClD,SAAS,QAAQ;AACrB,UAAO;IACL,OAAO,OAAO;IACd,QAAQ,OAAO;IACf,oBAAoB,OAAO;IAC3B,YAAY,OAAO;IACnB,SAAS,OAAO;IACjB;;EAGH,cAAc;AACZ,iBAAc;;EAIhB,YAAY;EACZ,aAAa;EACb,aAAa;EAEb,YAAY,MAAM,OAAO,UAAU;AACjC,QAAK,QAAQ;AACb,OAAI,KAAK,WACP,MAAK,WAAW,WAAW;;EAI/B,QAAQ,MAAM,MAAM;AAChB,QAAa,cAAc;GAC7B,MAAM,QAAQ,gBAAgB;GAC9B,MAAM,OAAA;AACN,QAAK,YAAY,KAAK,eAAe,QAAQ,OAAO,KAAK,YAAY;AACrE,QAAK,aAAa;AAClB,OAAI,KAAK,WACP,MAAK,WAAW,WAAW;;EAI/B,WAAW;AACT,UAAO,YAAY,KAAK,KAAK,YAAY,KAAK,SAAS,OAAO;;EAEjE;;;;;;;;;;;;;;;;;;;;;;AChkBH,MAAa,mBAAmB,WAAW;CACzC,GAAG;CACH,mBAAmB;CACpB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;aCI6F;AA0F/F,IAAI,oBAAoB;AAExB,eAAe,qBAAoC;AACjD,KAAI,qBAAqB,2BAA2B,CAClD;CAGF,MAAM,EAAE,8BAA8B,MAAM,OAAO;AACnD,OAAM,2BAA2B;AACjC,qBAAoB;;;;;;;;;;;;;;;;;;AAuBtB,eAAsB,aACpB,SACA,UAA+B,EAAE,EAChB;AACjB,OAAM,oBAAoB;AAC1B,QAAO,iBAAiB,SAAS,QAAQ;;;;;;;;;;;;;;;;;;;AAoB3C,SAAgB,iBAAiB,SAAuB,UAA+B,EAAE,EAAU;AACjG,KAAI,CAAC,2BAA2B,CAC9B,OAAM,IAAI,MACR,kGACD;CAGH,MAAM,EACJ,QAAQ,IACR,SAAS,IACT,QAAQ,OACR,gBACA,yBAAyB,MACzB,iBAAiB,MACjB,iBACA,eAAe,UACb;CAGJ,IAAI,iBAAiB;CACrB,MAAM,YAAY,sBAAsB;AACtC,mBAAiB;GACjB;CAGF,IAAI,gBAAyB;CAC7B,MAAM,mBAAmB,UAAmB;AAC1C,kBAAgB;;CAIlB,MAAM,YAAY,iBAAiB,gBACjC,WACA,GACA,MACA,OACA,MACA,IACA,uBACM,UACA,IACN,KACD;CAGD,MAAM,aAAa;EACjB,SAAS;EACT,MAAM;EACN,aAAa;EACb,OAAO;EACP,UAAU;EACV,WAAW;EACX,YAAY;EACZ,sBAAsB;EACtB,mBAAmB;EACpB;CAGD,MAAM,WAAW,WAAW,EAAE,OAAO,QAAQ,OAAO,aAAa,CAAC;CAGlE,MAAM,UAAU,MAAM,cACpB,YAAY,UACZ,EAAE,OAAO,UAAU,EACnB,MAAM,cACJ,cAAc,UACd,EACE,OAAO;EACL,QAAQ;EACR,aAAa;EACd,EACF,EACD,MAAM,cACJ,cAAc,UACd,EACE,OAAO;EACL,QAAQ,QAAQ;EAChB,QAAQ,SAAiB;AACvB,WAAQ,OAAO,MAAM,KAAK;;EAE7B,EACF,EACD,QACD,CACF,CACF;AAGD,0BAAyB;AACvB,YAAU;AACR,oBAAiB,oBAAoB,SAAS,WAAW,MAAM,KAAK;AACpE,oBAAiB,eAAe;IAChC;GACF;AAGF,KAAI,cACF,OAAM,yBAAyB,QAAQ,gBAAgB,IAAI,MAAM,OAAO,cAAc,CAAC;CAMzF,IAAI;CACJ,IAAI;CACJ,MAAM,iBAAiB;AACvB,MAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,KAAK;AACvC,mBAAiB;AACjB,2BAAyB;AACvB,aAAU;IACR,MAAM,OAAO,iBAAiB,UAAU;AACxC,eAAW;IACX,MAAM,WAAW,gBAAgB;IACjC,MAAM,iBAAiB;KACrB,MAAM,KAAK,SAAS,MAAM,EAAE,UAAU,CAAC;AACvC,QAAG,OAAO;MAAE,MAAM;MAAO,MAAM;MAAQ,CAAC;AACxC,YAAO,GAAG,QAAQ;;AAGpB,cADe,WAAW,gBAAgB,UAAU,SAAS,GAAG,UAAU,EAC1D;KAChB;AACF,OAAI,CAAC,eACH,WAAU;AACR,qBAAiB,eAAe;KAChC;IAEJ;AACF,MAAI,CAAC,eAAgB;;AAMvB,KAAI,mBAAmB,UAAU;EAC/B,IAAI,YAAY;EAChB,IAAI,cAAc;AAClB,OAAK,MAAM,SAAS,SAAS,SAC3B,KAAI,MAAM,SAAS;AACjB,iBAAc;GACd,MAAM,QAAQ,MAAM;GACpB,MAAM,KACH,MAAM,gBACN,MAAM,WACN,MAAM,UACP;GACF,MAAM,cAAc,MAAM,QAAQ,IAAI,MAAM,QAAQ,SAAS;AAC7D,OAAI,cAAc,UAAW,aAAY;;AAG7C,kBAAgB,cAAc,YAAY,EAAE;;AAI9C,0BAAyB;AACvB,YAAU;AACR,oBAAiB,oBAAoB,MAAM,WAAW,MAAM,KAAK;AACjE,oBAAiB,eAAe;IAChC;GACF;AAEF,QAAO,SAAS,CAAC,eACb,aAAa,QAAQ;EAAE;EAAwB;EAAgB,CAAC,GAChE,mBAAmB,QAAQ;EAAE;EAAwB;EAAgB,CAAC;;;;;;AAW5E,SAAS,mBAAmB,IAAsB;CAChD,MAAM,OAAQ,WAAmB;AAC/B,YAAmB,2BAA2B;AAChD,KAAI;AACF,MAAI;WACI;AACN,aAAmB,2BAA2B"}
|