silvery 0.19.1 → 0.21.0
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/README.md +9 -4
- package/dist/Text-Lq0dmj8-.mjs +239 -0
- package/dist/Text-Lq0dmj8-.mjs.map +1 -0
- package/dist/UPNG-Bo33r8rA.mjs +3 -0
- package/dist/UPNG-DosRPdF4.mjs +5075 -0
- package/dist/UPNG-DosRPdF4.mjs.map +1 -0
- package/dist/__vite-browser-external-2447137e-D_JM6skp.mjs +6 -0
- package/dist/__vite-browser-external-2447137e-D_JM6skp.mjs.map +1 -0
- package/dist/{animation-Cn64yepo.mjs → animation-ZMN2_XKv.mjs} +2 -2
- package/dist/animation-ZMN2_XKv.mjs.map +1 -0
- package/dist/{ansi-Cc33mW54.d.mts → ansi-2Xn0yatP.d.mts} +1 -1
- package/dist/{ansi-Cc33mW54.d.mts.map → ansi-2Xn0yatP.d.mts.map} +1 -1
- package/dist/{ansi-CLOitHKx.mjs → ansi-D1KQMAbf.mjs} +1 -1
- package/dist/{ansi-CLOitHKx.mjs.map → ansi-D1KQMAbf.mjs.map} +1 -1
- package/dist/ansi-yC4RyBNY.mjs +22441 -0
- package/dist/ansi-yC4RyBNY.mjs.map +1 -0
- package/dist/apng-CR08rIaH.mjs +58 -0
- package/dist/apng-CR08rIaH.mjs.map +1 -0
- package/dist/apng-DaHfVaVI.mjs +3 -0
- package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
- package/dist/assets/skia.darwin-arm64-DQs5sT6N.node +0 -0
- package/dist/backend-B-WYLUib.mjs +13396 -0
- package/dist/backend-B-WYLUib.mjs.map +1 -0
- package/dist/backends-CUtan80W.mjs +3 -0
- package/dist/backends-DIVYzKqd.mjs +1083 -0
- package/dist/backends-DIVYzKqd.mjs.map +1 -0
- package/dist/bound-term-0sPrrzH1.d.mts +4640 -0
- package/dist/bound-term-0sPrrzH1.d.mts.map +1 -0
- package/dist/canvas-1v7dPT-_.mjs +3 -0
- package/dist/canvas-CSuPOMNt.mjs +1442 -0
- package/dist/canvas-CSuPOMNt.mjs.map +1 -0
- package/dist/{chunk-Vs_PY4HZ.mjs → chunk-BSw8zbkd.mjs} +1 -1
- package/dist/cli-dvo0r2fs.mjs +4 -0
- package/dist/compare-CQodSH4G.mjs +376 -0
- package/dist/compare-CQodSH4G.mjs.map +1 -0
- package/dist/compare-DHlcxEYA.mjs +3 -0
- package/dist/context-BU5LkkIy.mjs.map +1 -1
- package/dist/devtools-CJdt5H0X.mjs +2 -0
- package/dist/{devtools-DxkSLXDA.mjs → devtools-DcQjgyjL.mjs} +5 -4
- package/dist/{devtools-DxkSLXDA.mjs.map → devtools-DcQjgyjL.mjs.map} +1 -1
- package/dist/easing-BI-ASGMO.d.mts +24 -0
- package/dist/easing-BI-ASGMO.d.mts.map +1 -0
- package/dist/{eta-Bb3RH3wh.mjs → eta-CJlGH06n.mjs} +1 -1
- package/dist/{eta-Bb3RH3wh.mjs.map → eta-CJlGH06n.mjs.map} +1 -1
- package/dist/flexily-zero-adapter-C3Vj0fPt.mjs +306 -0
- package/dist/flexily-zero-adapter-C3Vj0fPt.mjs.map +1 -0
- package/dist/{flexily-zero-adapter-CMxXhdOL.mjs → flexily-zero-adapter-C4lW_Ov5.mjs} +1 -1
- package/dist/fonts-BFmhXDv7.mjs +88 -0
- package/dist/fonts-BFmhXDv7.mjs.map +1 -0
- package/dist/gif-C_AjaT9d.mjs +188 -0
- package/dist/gif-C_AjaT9d.mjs.map +1 -0
- package/dist/gif-DaC4XrxA.mjs +3 -0
- package/dist/gifenc-BOUT-KFB.mjs +730 -0
- package/dist/gifenc-BOUT-KFB.mjs.map +1 -0
- package/dist/image-C2Birh2x.mjs +1252 -0
- package/dist/image-C2Birh2x.mjs.map +1 -0
- package/dist/index-BUMxS65f.d.mts +453 -0
- package/dist/index-BUMxS65f.d.mts.map +1 -0
- package/dist/{index-D3saHouR.d.mts → index-CSQf13CI.d.mts} +1057 -1133
- package/dist/index-CSQf13CI.d.mts.map +1 -0
- package/dist/{index-BXslOebb.d.mts → index-Cl9KKjQ_.d.mts} +4919 -3921
- package/dist/index-Cl9KKjQ_.d.mts.map +1 -0
- package/dist/index-XbNrPhWl.d.mts +336 -0
- package/dist/index-XbNrPhWl.d.mts.map +1 -0
- package/dist/index.d.mts +8 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +14 -12
- package/dist/index.mjs.map +1 -1
- package/dist/key-mapping-CS-YD_cD.mjs +132 -0
- package/dist/key-mapping-CS-YD_cD.mjs.map +1 -0
- package/dist/key-mapping-Yn-Jgrij.mjs +3 -0
- package/dist/{layout-engine-B6Cdz1yZ.mjs → layout-engine-C07LEXWT.mjs} +1 -1
- package/dist/layout-engine-C2px0RJE.mjs +67 -0
- package/dist/layout-engine-C2px0RJE.mjs.map +1 -0
- package/dist/layout-signals-Cnw6xk8Q.mjs +988 -0
- package/dist/layout-signals-Cnw6xk8Q.mjs.map +1 -0
- package/dist/mouse-events-Dki3ISIp.mjs +1044 -0
- package/dist/mouse-events-Dki3ISIp.mjs.map +1 -0
- package/dist/{multi-progress-Bq9Oi_WI.mjs → multi-progress-CIRjrzma.mjs} +3 -3
- package/dist/{multi-progress-Bq9Oi_WI.mjs.map → multi-progress-CIRjrzma.mjs.map} +1 -1
- package/dist/{multi-progress-DAQC7eap.d.mts → multi-progress-DHZ2xUT2.d.mts} +2 -2
- package/dist/{multi-progress-DAQC7eap.d.mts.map → multi-progress-DHZ2xUT2.d.mts.map} +1 -1
- package/dist/{node-BeWlnCPY.mjs → node-CjM5Rt-M.mjs} +4 -4
- package/dist/node-CjM5Rt-M.mjs.map +1 -0
- package/dist/playwright-D5YiZcNS.mjs +76397 -0
- package/dist/playwright-D5YiZcNS.mjs.map +1 -0
- package/dist/png-codec-Dp84742B.mjs +36 -0
- package/dist/png-codec-Dp84742B.mjs.map +1 -0
- package/dist/png-codec-QwOtJ8Zs.mjs +3 -0
- package/dist/progress-DB_Xo071.mjs +675 -0
- package/dist/progress-DB_Xo071.mjs.map +1 -0
- package/dist/{progress-bar-CXE5Qfkd.mjs → progress-bar-oJwq22CR.mjs} +4 -4
- package/dist/{progress-bar-CXE5Qfkd.mjs.map → progress-bar-oJwq22CR.mjs.map} +1 -1
- package/dist/rasterizer-BRXrDdWx.mjs +3 -0
- package/dist/rasterizer-CpEhJvdR.mjs +296 -0
- package/dist/rasterizer-CpEhJvdR.mjs.map +1 -0
- package/dist/reconciler-DldIJB93.mjs +2083 -0
- package/dist/reconciler-DldIJB93.mjs.map +1 -0
- package/dist/{render-string-0mN37DLf.mjs → render-string-BcoCpjCB.mjs} +1 -1
- package/dist/{render-string-X-CxpTdZ.mjs → render-string-DkQacASz.mjs} +2728 -555
- package/dist/render-string-DkQacASz.mjs.map +1 -0
- package/dist/resvg-js-DkOndZI3.mjs +203 -0
- package/dist/resvg-js-DkOndZI3.mjs.map +1 -0
- package/dist/runtime.d.mts +3 -2
- package/dist/runtime.mjs +3 -3
- package/dist/schemes-JjNp4aSl.mjs +2611 -0
- package/dist/schemes-JjNp4aSl.mjs.map +1 -0
- package/dist/{spinner-CGo34vyR.d.mts → spinner-CZINHpkV.d.mts} +2 -2
- package/dist/{spinner-CGo34vyR.d.mts.map → spinner-CZINHpkV.d.mts.map} +1 -1
- package/dist/{spinner-CeOmcuw_.mjs → spinner-D9lrHr8s.mjs} +7 -7
- package/dist/spinner-D9lrHr8s.mjs.map +1 -0
- package/dist/src-5w9QR6_8.mjs +1071 -0
- package/dist/src-5w9QR6_8.mjs.map +1 -0
- package/dist/src-BNTToU7l.mjs +4387 -0
- package/dist/src-BNTToU7l.mjs.map +1 -0
- package/dist/{src-Bd7ezSgG.mjs → src-BR4xNwdG.mjs} +10436 -2622
- package/dist/src-BR4xNwdG.mjs.map +1 -0
- package/dist/{types-Bk2yw9Qj.mjs → src-DKp-_OFG.mjs} +34 -94
- package/dist/src-DKp-_OFG.mjs.map +1 -0
- package/dist/src-bt8wSrfJ.mjs +258 -0
- package/dist/src-bt8wSrfJ.mjs.map +1 -0
- package/dist/src-e33Y6kNJ.mjs +3 -0
- package/dist/src-iDwu25UD.mjs +1814 -0
- package/dist/src-iDwu25UD.mjs.map +1 -0
- package/dist/steps-Bp2uNqnn.d.mts +202 -0
- package/dist/steps-Bp2uNqnn.d.mts.map +1 -0
- package/dist/svg-15lZZzxq.mjs +486 -0
- package/dist/svg-15lZZzxq.mjs.map +1 -0
- package/dist/svg-Cz0UXcDj.mjs +255 -0
- package/dist/svg-Cz0UXcDj.mjs.map +1 -0
- package/dist/svg-DY72a4HK.mjs +3 -0
- package/dist/svg-g1D6ErwR.d.mts +82 -0
- package/dist/svg-g1D6ErwR.d.mts.map +1 -0
- package/dist/term.d.mts +3 -0
- package/dist/term.mjs +9 -0
- package/dist/term.mjs.map +1 -0
- package/dist/theme.d.mts +95 -2
- package/dist/theme.d.mts.map +1 -0
- package/dist/theme.mjs +9 -3
- package/dist/theme.mjs.map +1 -0
- package/dist/{types-BH_v3iMT.d.mts → types-kt_fKR37.d.mts} +2 -15
- package/dist/types-kt_fKR37.d.mts.map +1 -0
- package/dist/ui/animation.d.mts +2 -1
- package/dist/ui/animation.mjs +1 -1
- package/dist/ui/ansi.d.mts +1 -1
- package/dist/ui/ansi.mjs +1 -1
- package/dist/ui/cli.d.mts +3 -3
- package/dist/ui/cli.mjs +5 -5
- package/dist/ui/display.d.mts +1 -1
- package/dist/ui/image.d.mts +2 -2
- package/dist/ui/image.mjs +2 -2
- package/dist/ui/input.d.mts +1 -1
- package/dist/ui/input.mjs +4 -2
- package/dist/ui/input.mjs.map +1 -1
- package/dist/ui/progress.d.mts +5 -249
- package/dist/ui/progress.mjs +5 -858
- package/dist/ui/react.d.mts +1 -1
- package/dist/ui/react.mjs +2 -2
- package/dist/ui/recording-chrome-react.d.mts +21 -0
- package/dist/ui/recording-chrome-react.d.mts.map +1 -0
- package/dist/ui/recording-chrome-react.mjs +105 -0
- package/dist/ui/recording-chrome-react.mjs.map +1 -0
- package/dist/ui/recording-chrome.d.mts +2 -0
- package/dist/ui/recording-chrome.mjs +2 -0
- package/dist/ui/utils.mjs +1 -1
- package/dist/ui/wrappers.d.mts +3 -3
- package/dist/ui/wrappers.mjs +2 -2
- package/dist/ui.d.mts +7 -6
- package/dist/ui.mjs +8 -7
- package/dist/{useLatest-Bg2x4bfP.d.mts → useLatest-DRDDVwjh.d.mts} +5 -25
- package/dist/useLatest-DRDDVwjh.d.mts.map +1 -0
- package/dist/{with-text-input-CRfoiFFG.d.mts → with-text-input-YeohVLeo.d.mts} +4 -55
- package/dist/with-text-input-YeohVLeo.d.mts.map +1 -0
- package/dist/wrapper-C70ATkVv.mjs +3527 -0
- package/dist/wrapper-C70ATkVv.mjs.map +1 -0
- package/dist/{wrappers-UTADQkSY.mjs → wrappers-BCUYITrY.mjs} +5 -157
- package/dist/wrappers-BCUYITrY.mjs.map +1 -0
- package/dist/{yoga-adapter-8oRGRw8V.mjs → yoga-adapter-BnZX1PAY.mjs} +28 -2
- package/dist/yoga-adapter-BnZX1PAY.mjs.map +1 -0
- package/dist/yoga-adapter-DxgsQ_gg.mjs +2 -0
- package/dist/zipBundle-3nqeDRtm.mjs +3 -0
- package/dist/zipBundle-VNAYFmqJ.mjs +2003 -0
- package/dist/zipBundle-VNAYFmqJ.mjs.map +1 -0
- package/package.json +20 -9
- package/dist/animation-Cn64yepo.mjs.map +0 -1
- package/dist/cli-BKp0YtBD.mjs +0 -4
- package/dist/devtools-9QY4teqI.mjs +0 -2
- package/dist/flexily-zero-adapter-BlQa46nr.mjs +0 -3385
- package/dist/flexily-zero-adapter-BlQa46nr.mjs.map +0 -1
- package/dist/image-CTII5QWI.mjs +0 -477
- package/dist/image-CTII5QWI.mjs.map +0 -1
- package/dist/index-BXslOebb.d.mts.map +0 -1
- package/dist/index-BnA7mNpo.d.mts +0 -175
- package/dist/index-BnA7mNpo.d.mts.map +0 -1
- package/dist/index-D3saHouR.d.mts.map +0 -1
- package/dist/layout-engine-ClUgv6jB.mjs +0 -50
- package/dist/layout-engine-ClUgv6jB.mjs.map +0 -1
- package/dist/node-BeWlnCPY.mjs.map +0 -1
- package/dist/reconciler-Cwgm8hRR.mjs +0 -8459
- package/dist/reconciler-Cwgm8hRR.mjs.map +0 -1
- package/dist/render-string-X-CxpTdZ.mjs.map +0 -1
- package/dist/spinner-CeOmcuw_.mjs.map +0 -1
- package/dist/src-B5GjfG7g.mjs +0 -4305
- package/dist/src-B5GjfG7g.mjs.map +0 -1
- package/dist/src-Bd7ezSgG.mjs.map +0 -1
- package/dist/src-CChwjk0Z.mjs +0 -738
- package/dist/src-CChwjk0Z.mjs.map +0 -1
- package/dist/src-NCKb8kE5.mjs +0 -2660
- package/dist/src-NCKb8kE5.mjs.map +0 -1
- package/dist/types-BH_v3iMT.d.mts.map +0 -1
- package/dist/types-Bk2yw9Qj.mjs.map +0 -1
- package/dist/ui/progress.d.mts.map +0 -1
- package/dist/ui/progress.mjs.map +0 -1
- package/dist/useLatest-Bg2x4bfP.d.mts.map +0 -1
- package/dist/with-text-input-CRfoiFFG.d.mts.map +0 -1
- package/dist/wrappers-UTADQkSY.mjs.map +0 -1
- package/dist/yoga-adapter-8oRGRw8V.mjs.map +0 -1
- package/dist/yoga-adapter-D_CcxSt5.mjs +0 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-string-DkQacASz.mjs","names":["defaultGraphemeWidth","collectTextContent","getTextWidth","traverseTree","log","log","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/runtime/pass-cause.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/strict-mode.ts","../packages/ag-term/src/strict-bordered-rect.ts","../packages/ag-term/src/pipeline/render-sink.ts","../packages/ag-term/src/pipeline/render-text.ts","../packages/ag-term/src/pipeline/render-box.ts","../packages/ag-term/src/strict-island.ts","../packages/ag-term/src/pipeline/render-viewport.ts","../packages/ag-term/src/pipeline/decoration-phase.ts","../packages/ag-term/src/pipeline/render-post-state.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/render-plan.ts","../packages/ag-term/src/pipeline/backdrop/color.ts","../packages/ag-term/src/pipeline/backdrop/plan.ts","../packages/ag-term/src/pipeline/backdrop/color-shim.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 isSoftBreakPoint,\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 (isSoftBreakPoint(g) && i + 1 < len) {\n // Soft-punct (/ \\ . _ : ,) emits the break AFTER the punctuation,\n // not before. `commands/run` wraps to `commands/` + `run`, never\n // `commands` + `/run`. This matches chenglou/pretext's convention\n // and avoids the lone-punct-line failure mode (`/` alone on a line).\n // The grapheme itself stays in `currentWordWidth` of the LEFT\n // compound (it belongs visually to the preceding token).\n // Tracking: @km/silvery/15132-pretext-break-kind.\n currentWordWidth += w\n breakIndices.push(i + 1)\n maxWordWidth = Math.max(maxWordWidth, currentWordWidth)\n currentWordWidth = 0\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 * Per-line width function: takes the 0-based line index, returns the\n * available width for that line. Use to express CSS-float-equivalent\n * layouts (e.g. line 0 narrowed by a top-right pill, lines 1+ at full\n * container width). Inspired by chenglou's original Pretext, which\n * supports per-line widths to wrap text around floated images.\n */\nexport type WidthFn = (lineIndex: number) => number\n\n/**\n * Options for the Knuth-Plass break selector and `optimalWrap`.\n *\n * - `maxLines`: caps the wrap to ≤ N lines. When the DP can't fit the\n * text in that budget at the requested width, `knuthPlassBreaks` returns\n * an empty array (infeasibility signal) and `optimalWrap` falls back to\n * greedy wrapping clipped to `maxLines` with the final line truncated\n * and `truncationSuffix` appended.\n *\n * Tracking: @km/silvery/15130-pretext-maxlines-aware. Without `maxLines`\n * the DP behaves exactly as before — same line count, same break points.\n */\nexport interface PretextOpts {\n /** Maximum number of wrapped lines. Omit for unbounded (default). */\n maxLines?: number\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 * `width` accepts either a constant number (uniform width — fast path)\n * or a `WidthFn(lineIndex)` for per-line widths. Per-line uses a\n * forward DP that tracks line index in state.\n *\n * Pass `opts.maxLines` to cap the wrap to ≤ N lines (a 2D DP over\n * candidates × lines-used). When infeasible the function returns `[]`\n * and the caller (`optimalWrap`) is responsible for the truncation\n * fallback.\n *\n * O(breakpoints²) for the constant-width unbounded case;\n * O(breakpoints² × maxLines) for per-line or when `maxLines` is set.\n * All variants are negligible for terminal-scale text.\n */\nexport function knuthPlassBreaks(\n analysis: TextAnalysis,\n width: number | WidthFn,\n opts?: PretextOpts,\n): number[] {\n // Quick reject: empty or single-line at the widest available width.\n // For per-line, use width(0) as the conservative single-line check —\n // if the whole text fits on line 0, no breaks are needed regardless\n // of what subsequent lines would have permitted.\n const w0 = typeof width === \"function\" ? width(0) : width\n if (w0 <= 0) return []\n if (analysis.totalWidth <= w0 && analysis.newlineIndices.length === 0) return []\n\n // Split into paragraphs at newlines and process each independently.\n // Note: per-line width is paragraph-LOCAL — line 0 of each paragraph\n // is \"line 0\" for width purposes. Most callers pass single-paragraph\n // text (titles, labels) so this matches expectations; multi-paragraph\n // callers wanting absolute line indexing should pre-split + offset.\n //\n // maxLines semantics with multi-paragraph input: the budget is applied\n // PER-PARAGRAPH. Multi-paragraph callers needing global line capping\n // should pre-split. (Title callers — the primary maxLines consumer —\n // always pass single-paragraph text, so this is a non-issue in practice.)\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 =\n typeof width === \"function\"\n ? knuthPlassForParagraphPerLine(analysis, pStart, pEnd, width, opts)\n : knuthPlassForParagraph(analysis, pStart, pEnd, width, opts)\n // When maxLines is set and the paragraph is infeasible, the per-\n // paragraph DP returns [] (no breaks) — but the paragraph may still\n // be non-empty (totalWidth > width). The caller (`optimalWrap`)\n // distinguishes \"single-line OK\" from \"infeasible-under-cap\" by\n // re-running the line-count check post hoc; signalling infeasibility\n // here would require a separate channel. Keeping the [] convention\n // matches the existing greedy-fallback contract.\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// ============================================================================\n// Wrap-quality penalties\n// ============================================================================\n\n/**\n * Minimum visible-character count for a non-final line before the\n * orphan penalty kicks in. A \"token\" here is loosely \"the visible width\n * of the line\"; the penalty is tuned for terminal card widths (≈20-40\n * cols), where any line under 8 cols is visually a stranded fragment.\n */\nconst ORPHAN_MIN_WIDTH = 8\n\n/** Penalty (added to leftover²) for a non-final line below ORPHAN_MIN_WIDTH. */\nconst ORPHAN_PENALTY = 500\n\n/**\n * Threshold for a hyphenated compound to count as \"short\" on either side.\n * Breaking inside `cmd-hover` (3-5 split) or `auto-` (4 chars left, 9\n * chars right of the hyphen) reads worse than letting the compound\n * stay intact. Penalty is gentler than the orphan penalty because\n * breaking after a hyphen IS sometimes the right call (long compounds,\n * narrow widths).\n */\nconst HYPHEN_COMPOUND_MIN_SIDE = 5\n\n/** Penalty (added to leftover²) for a break inside a short hyphenated compound. */\nconst HYPHEN_COMPOUND_PENALTY = 250\n\n/**\n * Score the *quality* of a non-final wrapped line.\n *\n * Knuth-Plass's classic cost (leftover²) gives the line layout a global\n * raggedness optimum but doesn't know about typographical taboos —\n * single-word \"hover\" lines, breaks inside short hyphenated compounds\n * like `cmd-hover`, etc. The penalty is added to the squared-leftover\n * cost and tuned so it dominates *only* when the alternative is visually\n * better; if the alternative has worse raggedness, the leftover²\n * differential will still pick the better wrap.\n *\n * Parameters:\n * - `lineStart` — grapheme index where the line begins (a break candidate)\n * - `lineEnd` — grapheme index where the next line begins (a break candidate)\n * - `trimEnd` — `lineEnd` after trailing whitespace/ANSI trim (= visible end)\n */\nfunction wrapQualityPenalty(\n analysis: TextAnalysis,\n lineStart: number,\n lineEnd: number,\n trimEnd: number,\n): number {\n let penalty = 0\n const { cumWidths, graphemes } = analysis\n\n // Orphan/widow penalty: tax non-final lines whose visible content is\n // narrower than ORPHAN_MIN_WIDTH. Width is measured between lineStart\n // and trimEnd (= line content without trailing whitespace). This pushes\n // the DP to prefer \"TUI\" + \"(cmd-hover also no-op)\" over\n // \"TUI (cmd-\" + \"hover\" + \"also no-op)\" when both fit.\n const visibleWidth = cumWidths[trimEnd]! - cumWidths[lineStart]!\n if (visibleWidth < ORPHAN_MIN_WIDTH) {\n penalty += ORPHAN_PENALTY\n }\n\n // Hyphen-compound penalty: when the break point lands AFTER a hyphen\n // (graphemes[lineEnd - 1] === '-'), inspect the compound that hyphen\n // belongs to and tax breaks that produce short halves on either side.\n //\n // Compound left half: scan backward from the hyphen, stopping at the\n // previous word boundary or compound separator. Compound right half:\n // scan forward from `lineEnd` (= position after the hyphen) until the\n // next word boundary. If either half is shorter than\n // HYPHEN_COMPOUND_MIN_SIDE, apply the penalty.\n if (lineEnd > 0 && graphemes[lineEnd - 1] === \"-\") {\n // Left-half length: count non-boundary graphemes immediately before the hyphen.\n let leftLen = 0\n for (let k = lineEnd - 2; k >= 0; k--) {\n const g = graphemes[k]!\n if (g === \" \" || g === \"\\t\" || g === \"-\" || g === \"\\n\") break\n leftLen++\n if (leftLen >= HYPHEN_COMPOUND_MIN_SIDE) break\n }\n\n // Right-half length: count non-boundary graphemes immediately after the hyphen.\n let rightLen = 0\n for (let k = lineEnd; k < graphemes.length; k++) {\n const g = graphemes[k]!\n if (g === \" \" || g === \"\\t\" || g === \"-\" || g === \"\\n\") break\n rightLen++\n if (rightLen >= HYPHEN_COMPOUND_MIN_SIDE) break\n }\n\n if (leftLen < HYPHEN_COMPOUND_MIN_SIDE || rightLen < HYPHEN_COMPOUND_MIN_SIDE) {\n penalty += HYPHEN_COMPOUND_PENALTY\n }\n }\n\n return penalty\n}\n\n/**\n * DP for a single paragraph (no newlines). Constant-width.\n *\n * Without `opts.maxLines`: backward 1D DP, `cost[i]` = min cost to reach\n * the end starting at candidate `i`. Final answer = `cost[0]`.\n *\n * With `opts.maxLines`: forward 2D DP, `f[i][k]` = min cost to reach\n * candidate `i` after consuming exactly `k` lines. See\n * `knuthPlassForParagraph2D` below — same shape as the per-line variant\n * but with a constant `width` for every line.\n */\nfunction knuthPlassForParagraph(\n analysis: TextAnalysis,\n pStart: number,\n pEnd: number,\n width: number,\n opts?: PretextOpts,\n): number[] {\n if (opts?.maxLines !== undefined) {\n return knuthPlassForParagraph2D(analysis, pStart, pEnd, () => width, opts.maxLines)\n }\n\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 isLastLine = j === n - 1\n let lineCost = isLastLine ? 0 : leftover * leftover\n\n if (!isLastLine) {\n lineCost += wrapQualityPenalty(analysis, lineStart, lineEnd, trimEnd)\n }\n\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 * DP for a single paragraph (no newlines), per-line width.\n *\n * Forward DP — `f[i]` is the min cost to reach candidate i with `line[i]`\n * tracking the line index of position i in the optimal path. Each segment\n * (j → i) is on line `line[j]`, so its allowed width is `widthFn(line[j])`.\n *\n * O(n² × m) where m is max meaningful line index. For titles n is small\n * (~30 candidates, ~5 lines) so this is trivial. The constant-width path\n * above stays as the fast path for callers that don't need per-line.\n *\n * Dispatches to the 2D variant when `opts.maxLines` is set — needed because\n * the optimal path through candidate `i` for line-count `k` may differ from\n * the path for `k+1`, so the single-state `line[]` shortcut is unsafe under\n * a hard line cap.\n */\nfunction knuthPlassForParagraphPerLine(\n analysis: TextAnalysis,\n pStart: number,\n pEnd: number,\n widthFn: WidthFn,\n opts?: PretextOpts,\n): number[] {\n if (opts?.maxLines !== undefined) {\n return knuthPlassForParagraph2D(analysis, pStart, pEnd, widthFn, opts.maxLines)\n }\n\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 // Forward DP: f[i] = min cost to reach candidate i; from[i] = optimal predecessor\n // line[i] = line index of candidate i in the optimal path (= breaks taken before i)\n const f = new Array<number>(n).fill(Infinity)\n const from = new Array<number>(n).fill(-1)\n const line = new Array<number>(n).fill(0)\n f[0] = 0\n // line[0] = 0\n\n for (let i = 1; i < n; i++) {\n const lineEnd = candidates[i]!\n // Trim trailing whitespace on the segment ending at i\n let trimEnd = lineEnd\n while (trimEnd > 0) {\n const prevG = graphemes[trimEnd - 1]\n const prevW = widths[trimEnd - 1]\n if (prevW === 0) {\n trimEnd--\n continue\n }\n if (prevG === \" \" || prevG === \"\\t\") {\n trimEnd--\n continue\n }\n break\n }\n const trimEndCum = cumWidths[trimEnd]!\n\n for (let j = 0; j < i; j++) {\n if (f[j] === Infinity) continue\n const lineStart = candidates[j]!\n const lineWidth = trimEndCum - cumWidths[lineStart]!\n\n // The segment (j → i) is the (line[j])-th line in the optimal path\n // through j (0-indexed). Its allowed width comes from widthFn at that\n // index.\n const segLineIndex = line[j]!\n const allowedWidth = widthFn(segLineIndex)\n if (lineWidth > allowedWidth) continue\n\n const leftover = allowedWidth - lineWidth\n const isLastLine = i === n - 1\n let lineCost = isLastLine ? 0 : leftover * leftover\n\n if (!isLastLine) {\n lineCost += wrapQualityPenalty(analysis, lineStart, lineEnd, trimEnd)\n }\n\n const totalCost = f[j]! + lineCost\n\n if (totalCost < f[i]!) {\n f[i] = totalCost\n from[i] = j\n line[i] = segLineIndex + 1\n }\n }\n }\n\n if (f[n - 1] === Infinity) return [] // infeasible — caller falls back to greedy\n\n // Trace back: collect break candidate indices (excluding sentinel start/end)\n const breaks: number[] = []\n let idx = n - 1\n while (from[idx]! > 0) {\n idx = from[idx]!\n breaks.push(candidates[idx]!)\n }\n breaks.reverse()\n return breaks\n}\n\n/**\n * DP for a single paragraph with a hard line-budget cap.\n *\n * Forward 2D DP — `f[i][k]` = min cost to reach candidate `i` using\n * exactly `k` lines. Transition:\n *\n * f[i][k+1] = min over j<i of (f[j][k] + lineCost(j → i, line k))\n *\n * The line index `k` is consumed by the (j → i) segment, so its allowed\n * width is `widthFn(k)`. Final answer = `argmin_{k <= maxLines} f[n-1][k]`,\n * with `cost = 0` on the last line (no raggedness penalty on the final\n * line, matching the 1D contracts above).\n *\n * Returns `[]` when no path fits within `maxLines` lines (infeasibility\n * signal — `optimalWrap` falls back to truncation).\n *\n * Complexity: O(n² × maxLines). For terminal titles n ≈ 30 candidates and\n * maxLines ≈ 4-6, so ≤ 5400 ops per paragraph — negligible. Memory: one\n * 2D Float64Array sized `n × (maxLines + 1)`.\n *\n * Tracking: @km/silvery/15130-pretext-maxlines-aware.\n */\nfunction knuthPlassForParagraph2D(\n analysis: TextAnalysis,\n pStart: number,\n pEnd: number,\n widthFn: WidthFn,\n maxLines: number,\n): number[] {\n if (maxLines <= 0) return []\n\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) {\n // Single segment, no breaks needed — but still subject to the line cap.\n // A 1-line wrap is always feasible under maxLines >= 1.\n return []\n }\n\n // K = maxLines + 1 (so we index k = 0..maxLines).\n // f[i * K + k] = min cost to reach candidate i using exactly k lines.\n // from[i * K + k] = predecessor candidate index (-1 if unreached).\n // fromK[i * K + k] = predecessor line count (= k - 1) — stored\n // for symmetry with from[]; both make traceback trivial.\n const K = maxLines + 1\n const f = new Float64Array(n * K).fill(Infinity)\n const from = new Int32Array(n * K).fill(-1)\n // f[(0 * K) + 0] = state \"reached candidate 0 using 0 lines\"\n f[0] = 0\n\n // Pre-compute trimEnd for each candidate (the visible end of a line\n // ending at that candidate — strips trailing whitespace and ANSI).\n const trimEnds = new Array<number>(n)\n for (let i = 0; i < n; i++) {\n const lineEnd = candidates[i]!\n let trimEnd = lineEnd\n while (trimEnd > 0) {\n const prevG = graphemes[trimEnd - 1]\n const prevW = widths[trimEnd - 1]\n if (prevW === 0) {\n trimEnd--\n continue\n }\n if (prevG === \" \" || prevG === \"\\t\") {\n trimEnd--\n continue\n }\n break\n }\n trimEnds[i] = trimEnd\n }\n\n // Forward DP: for each (j, k) state with finite cost, extend to every i > j\n // landing at line count k+1.\n for (let j = 0; j < n - 1; j++) {\n const lineStart = candidates[j]!\n const lineStartCum = cumWidths[lineStart]!\n\n for (let k = 0; k < maxLines; k++) {\n const fjk = f[j * K + k]!\n if (fjk === Infinity) continue\n const allowedWidth = widthFn(k)\n if (allowedWidth <= 0) continue\n\n for (let i = j + 1; i < n; i++) {\n const lineEnd = candidates[i]!\n const trimEnd = trimEnds[i]!\n const lineWidth = cumWidths[trimEnd]! - lineStartCum\n\n // Constant-width fast-path optimization: when widthFn returns the\n // same value for every line, candidates are visited in ascending\n // order of cumWidths, so once a candidate's width exceeds the\n // allowed width all subsequent ones do too. With per-line widths\n // we can't break — `continue` is the safe move. (The performance\n // cost is small at terminal scales.)\n if (lineWidth > allowedWidth) continue\n\n const leftover = allowedWidth - lineWidth\n const isLastLine = i === n - 1\n let lineCost = isLastLine ? 0 : leftover * leftover\n\n if (!isLastLine) {\n lineCost += wrapQualityPenalty(analysis, lineStart, lineEnd, trimEnd)\n }\n\n const total = fjk + lineCost\n const target = i * K + (k + 1)\n if (total < f[target]!) {\n f[target] = total\n from[target] = j\n }\n }\n }\n }\n\n // Pick the best terminal state — min f[n-1][k] over k = 1..maxLines.\n let bestK = -1\n let bestCost = Infinity\n for (let k = 1; k <= maxLines; k++) {\n const c = f[(n - 1) * K + k]!\n if (c < bestCost) {\n bestCost = c\n bestK = k\n }\n }\n if (bestK === -1 || bestCost === Infinity) return [] // infeasible under cap\n\n // Trace back: walk from (n-1, bestK) to (0, 0) using `from`.\n const breaks: number[] = []\n let curI = n - 1\n let curK = bestK\n while (curK > 0) {\n const prev = from[curI * K + curK]!\n if (prev < 0) break // safety; shouldn't happen with bestCost finite\n if (prev > 0) breaks.push(candidates[prev]!)\n curI = prev\n curK = curK - 1\n }\n breaks.reverse()\n return breaks\n}\n\n/**\n * Options for `optimalWrap`. Extends `PretextOpts` with caller-side\n * presentation knobs (truncation suffix on overflow).\n */\nexport interface OptimalWrapOpts extends PretextOpts {\n /**\n * Suffix appended to the final visible line when `maxLines` is exceeded\n * and the wrap falls back to truncation. Default is `\"…\"` (single\n * horizontal-ellipsis). Set to `\"\"` to truncate without a suffix.\n *\n * The suffix is counted against the available width when truncating —\n * the last line is cut so that `displayLength(head + suffix) <= width`.\n */\n truncationSuffix?: string\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 *\n * `width` accepts a constant `number` (uniform width) OR a `WidthFn(lineIndex)`\n * for per-line widths (e.g. line 0 narrowed by a top-right pill, lines 1+\n * full width — CSS-float-equivalent layouts).\n *\n * `opts.maxLines` caps the wrap to ≤ N lines. When the DP can't fit the\n * text in that budget, falls back to a greedy wrap clipped to `maxLines`\n * with the final visible line truncated (head trimmed at a grapheme\n * boundary so `head + truncationSuffix` fits within the budget). Default\n * suffix is `\"…\"`.\n *\n * Tracking: @km/silvery/15130-pretext-maxlines-aware.\n */\nexport function optimalWrap(\n text: string,\n analysis: TextAnalysis,\n width: number | WidthFn,\n opts?: OptimalWrapOpts,\n): string[] {\n const maxLines = opts?.maxLines\n const truncationSuffix = opts?.truncationSuffix ?? \"…\"\n const breaks = knuthPlassBreaks(\n analysis,\n width,\n maxLines !== undefined ? { maxLines } : undefined,\n )\n // For greedy fallback, use line-0 width as the conservative single value\n const fallbackWidth = typeof width === \"function\" ? width(0) : width\n if (breaks.length === 0) {\n // No breaks — either single-line, multi-line-infeasible, OR DP infeasible\n // under `maxLines` cap. Disambiguate via line-count check.\n const singleLine = analysis.totalWidth <= fallbackWidth && analysis.newlineIndices.length === 0\n if (singleLine) return [text]\n const greedy = wrapText(text, fallbackWidth, true, true)\n if (maxLines !== undefined && greedy.length > maxLines) {\n return clampToMaxLines(greedy, maxLines, fallbackWidth, truncationSuffix)\n }\n return greedy\n }\n\n const { graphemes, widths } = analysis\n const lines: string[] = []\n let lineStart = 0\n // ANSI tokens scanned past while skipping leading whitespace on the next\n // line — carried forward so the styling re-establishes at column 0.\n let pendingAnsiPrefix = \"\"\n\n for (const bp of breaks) {\n // Trim trailing whitespace, but CAPTURE zero-width ANSI tokens.\n // Without capture, an ANSI OFF token sitting between the last visible\n // grapheme and the break point falls into the gap [lineEnd, bp) and is\n // silently dropped from the output — the styling state then bleeds\n // into the next line and beyond. Capture preserves source order via\n // prepend (we walk backward).\n let lineEnd = bp\n let trailingAnsi = \"\"\n while (lineEnd > lineStart) {\n const w = widths[lineEnd - 1]!\n if (w === 0) {\n trailingAnsi = graphemes[lineEnd - 1]! + trailingAnsi\n lineEnd--\n continue\n }\n const g = graphemes[lineEnd - 1]!\n if (g === \" \" || g === \"\\t\" || g === \"\\n\") {\n lineEnd--\n continue\n }\n break\n }\n lines.push(pendingAnsiPrefix + graphemes.slice(lineStart, lineEnd).join(\"\") + trailingAnsi)\n pendingAnsiPrefix = \"\"\n\n // Skip leading whitespace AND zero-width ANSI tokens at line start.\n // Without skipping ANSI, a wrap landing after [ANSI-on][word][ANSI-off]\n // would leave the post-ANSI space as a line-start slug. Symmetric with\n // the trailing-side skip above. ANSI tokens scanned past are preserved\n // as pendingAnsiPrefix so the next line's styling is re-established at\n // column 0 (dropping them would lose color/attributes).\n lineStart = bp\n while (lineStart < graphemes.length) {\n const g = graphemes[lineStart]!\n const w = widths[lineStart]!\n if (g === \" \" || g === \"\\t\") {\n lineStart++\n continue\n }\n if (w === 0) {\n pendingAnsiPrefix += g\n lineStart++\n continue\n }\n break\n }\n }\n\n // Last line\n if (lineStart < graphemes.length) {\n lines.push(pendingAnsiPrefix + graphemes.slice(lineStart).join(\"\"))\n } else if (pendingAnsiPrefix !== \"\") {\n // No content after the last break, but we have stashed ANSI tokens —\n // append them to the last emitted line so styling state isn't lost.\n if (lines.length > 0) {\n lines[lines.length - 1] = lines[lines.length - 1]! + pendingAnsiPrefix\n }\n }\n\n // Defensive: the DP enforces `maxLines` by construction, but if a future\n // edit introduces a path where breaks > maxLines - 1 leaks through (e.g.\n // multi-paragraph callers passing newlines), clamp + ellipsize here so\n // the contract \"≤ maxLines visible rows\" is honored unconditionally.\n if (maxLines !== undefined && lines.length > maxLines) {\n return clampToMaxLines(lines, maxLines, fallbackWidth, truncationSuffix)\n }\n\n return lines\n}\n\n/**\n * Clamp a wrapped output to `maxLines` rows. The final visible line is\n * truncated so that `head + suffix` fits within `width`; the head is cut\n * at a grapheme-aware boundary (using `splitGraphemesAnsiAware` to keep\n * ANSI tokens intact).\n *\n * Used by `optimalWrap` when the DP cannot satisfy `opts.maxLines` and\n * falls back to greedy + truncate, AND as a defensive net at the end of\n * `optimalWrap` (the DP already enforces the cap by construction).\n *\n * Tracking: @km/silvery/15130-pretext-maxlines-aware.\n */\nfunction clampToMaxLines(\n lines: string[],\n maxLines: number,\n width: number,\n suffix: string,\n): string[] {\n if (lines.length <= maxLines) return lines\n if (maxLines <= 0) return []\n const kept = lines.slice(0, maxLines)\n const lastIdx = maxLines - 1\n const last = kept[lastIdx]!\n kept[lastIdx] = appendTruncationSuffix(last, width, suffix)\n return kept\n}\n\n/**\n * Append `suffix` to `line`, trimming graphemes off the visible end if\n * needed so that `displayLength(line + suffix) <= width`. ANSI tokens\n * (zero-width) are preserved across the truncation boundary so styling\n * state isn't dropped.\n *\n * If `suffix` itself is wider than `width`, returns just the suffix\n * (truncated to width via the same grapheme walk). Callers using a\n * pathological width should size-check on their side.\n */\nfunction appendTruncationSuffix(line: string, width: number, suffix: string): string {\n if (width <= 0) return \"\"\n // Fast path: line already fits with suffix appended.\n const suffixGraphemes = splitGraphemesAnsiAware(suffix)\n const suffixWidth = suffixGraphemes.reduce((acc, g) => acc + defaultGraphemeWidth(g), 0)\n\n const lineGraphemes = splitGraphemesAnsiAware(line)\n const lineWidth = lineGraphemes.reduce((acc, g) => acc + defaultGraphemeWidth(g), 0)\n\n if (lineWidth + suffixWidth <= width) return line + suffix\n\n // Walk backward over visible graphemes, dropping until head + suffix fits.\n // ANSI tokens are preserved — we only count visible width but keep all\n // tokens in source order (so trailing styling-off sequences survive).\n const budget = Math.max(0, width - suffixWidth)\n let acc = 0\n let cutoff = 0\n for (let i = 0; i < lineGraphemes.length; i++) {\n const g = lineGraphemes[i]!\n const w = defaultGraphemeWidth(g)\n if (acc + w > budget) break\n acc += w\n cutoff = i + 1\n }\n // Capture any trailing zero-width ANSI tokens that sit just past the cutoff,\n // so styling-off sequences aren't dropped at the truncation boundary.\n let tail = \"\"\n for (let i = cutoff; i < lineGraphemes.length; i++) {\n if (defaultGraphemeWidth(lineGraphemes[i]!) === 0) {\n tail += lineGraphemes[i]\n } else {\n break\n }\n }\n return lineGraphemes.slice(0, cutoff).join(\"\") + tail + suffix\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 * pass-cause.ts — Renderer feedback-edge instrumentation, loggily-native.\n *\n * Captures the cause of each render/layout pass beyond pass 1. The convergence\n * loop in `renderer.ts` (singlePassLayout / classic) and `create-app.tsx`\n * (processEventBatch flush loop) iterate until `hadReactCommit` is false. Each\n * extra pass is a \"feedback edge\": some computation in pass N caused React\n * state to change, which forces pass N+1.\n *\n * This module attributes each extra pass to a category (PassCause) and an\n * originating identity (node id, signal name, etc.) so we can answer \"what\n * keeps the loop alive?\". The data feeds C3b (bounded-convergence) — once we\n * know which feedback edges dominate, we can replace MAX_SINGLE_PASS_ITERATIONS\n * with attributed bounds.\n *\n * ## Design (post dual-pro review, 2026-04-26)\n *\n * Pass-causes are CATEGORICAL events, not duration spans. Both GPT-5.4 Pro\n * and Kimi K2.6 converged on:\n *\n * - Use `log.debug?.(\"pass\", { cause, edge, nodeId, producerPhase })` —\n * not `log.span()`. Spans imply duration; a pass-cause is instantaneous\n * and would log spurious 0ms entries that pollute traces.\n * - Aggregate via a custom pipeline `Stage` that captures `LogEvent.props`.\n * The `Stage` type is part of loggily's public API (`loggily/index.ts`\n * re-exports it from `./pipeline.js`) — it's the framework's intended\n * extension point for non-duration aggregation.\n * - Do NOT use `loggily/metrics`. `MetricsCollector` is intentionally\n * duration-only: `recordSpan(data)` only sees `{name, durationMs}`,\n * props are dropped at the collector boundary, and `summary()` returns\n * `\"name: N spans, mean=Xms, p50=Yms\"`. For pass-causes the durations\n * would all be ~0ms, and we would lose the edge/node breakdown that\n * C3b consumes.\n * - Emit on a parent + child sub-namespace (`silvery:passes` and\n * `silvery:passes:<cause>`) so `DEBUG=silvery:passes:layout-invalidate`\n * filters by cause without losing the canonical histogram (which reads\n * from the parent-namespace stage).\n * - Default-off via the silvery-side `SILVERY_INSTRUMENT=1` constant-fold\n * gate (zero overhead when unset). Setting it also sets the loggily\n * `LOG_LEVEL=debug` for `silvery:passes` (see logger setup below).\n * - For tests: the aggregator stage is composable; tests can call\n * `createPassCauseAggregator()` for a fresh aggregator and pass\n * `.stage` into a test-local `createLogger(\"silvery:passes\", ...)`.\n *\n * Tracking: km-silvery.renderer-feedback-trace, km-silvery.feedback-trace-loggily\n */\n\nimport { createLogger, type ConditionalLogger, type Stage, type Event } from \"loggily\"\n\n/**\n * Categories of feedback edges that can trigger an extra render/layout pass.\n *\n * The taxonomy is **constrained to categories with a real producer path** in\n * the silvery pipeline. The C3a v2 enum scaffolded 14 prospective categories\n * after a dual-pro review (GPT-5.4 + Kimi K2.6, 2026-04-26); C3b's audit\n * confirmed 9 of those had no production emitter and were either subsumed\n * by an existing category or required a feature silvery does not have\n * (font fallback, async images, decoration loops). Those are removed —\n * keeping unused enum members bloats the discriminated-union and signals\n * \"we expect to emit this\" when no path will.\n *\n * Subscriber feedback (genuine pass causes):\n * - layout-invalidate: rect signal value changed AND a subscriber consumed\n * it (gated on `hasLayoutSignals(node)` — see notifyLayoutSubscribers).\n *\n * Measure-phase feedback:\n * - intrinsic-shrinkwrap: fit-content / snug-content sizing changed parent\n * dimensions across passes (the binary-search-then-resize edge).\n *\n * Layout side-effects:\n * - scrollto-settle: scrollTo prop change forced an offset adjustment.\n * - sticky-resettle: sticky child offsets caused another pass.\n *\n * Convergence-loop root triggers (depth-0; not feedback per se):\n * - viewport-resize: terminal dims changed; treated as a root trigger.\n *\n * Synthesized catch-all:\n * - unknown: pass committed React work but no specific cause was emitted\n * (synthesized by `notePassCommit`). Observed at 0.006% of records in\n * the v2/v3 baseline corpus, confirming the enum is essentially\n * exhaustive for the wired emit sites. Should always be 0 in\n * steady-state — non-zero count signals a missing PassCause category\n * or a pure-React feedback loop the pipeline isn't aware of.\n *\n * Removed (no producer path; either deletion or subsumed by existing\n * category — see hub/silvery/design/convergence-bounds.md for the audit):\n * - wrap-reflow: subsumed by intrinsic-shrinkwrap (silvery wraps inside\n * computeSnugContentWidth, no separate producer)\n * - font-metrics-changed: terminals have fixed cell width, no font fallback\n * - decoration-remap: useDecorations subscribers fire as layout-invalidate\n * - focus-scroll-into-view: programmatic scroll already fires scrollto-settle\n * - async-image-size: silvery's Kitty graphics protocol sets dims at register\n * time, not lazy\n * - theme-metric-changed: setState path, not a within-frame loop\n * - resize-resettle: rect changes after resize already fire as layout-invalidate\n * - viewport-dependent: legacy bucket replaced by viewport-resize\n * - text-measurement-feedback: legacy bucket replaced by intrinsic-shrinkwrap\n */\nexport type PassCause =\n | \"layout-invalidate\"\n | \"intrinsic-shrinkwrap\"\n | \"scrollto-settle\"\n | \"sticky-resettle\"\n | \"viewport-resize\"\n | \"unknown\"\n\n/** Phase that produced the feedback edge. */\nexport type ProducerPhase =\n | \"measure\"\n | \"layout\"\n | \"scroll\"\n | \"sticky\"\n | \"scrollrect\"\n | \"decoration\"\n | \"content\"\n | \"output\"\n | \"renderer\"\n | \"react-effect\"\n\nexport interface PassCauseRecord {\n cause: PassCause\n /** Originating node id (or other stable identity). */\n nodeId?: string | number\n /** Edge name (e.g. signal name, prop name). */\n edge?: string\n /** Pipeline phase that produced the feedback. */\n producerPhase?: ProducerPhase\n /** Free-form detail (kept short — not for prose). */\n detail?: string\n}\n\nexport interface PassHistogramEntry {\n cause: PassCause\n count: number\n topEdges: { edge: string; count: number }[]\n topNodes: { nodeId: string | number; count: number }[]\n}\n\nexport interface PassHistogram {\n totalRecords: number\n perPass: number[]\n byCause: PassHistogramEntry[]\n}\n\n/**\n * Module-level instrumentation gate. Constant so JS engines can fold the\n * `if (INSTRUMENT) { ... }` blocks at every emit site out of the hot path.\n *\n * Mapping to loggily:\n * - SILVERY_INSTRUMENT=1 implies LOG_LEVEL=debug + DEBUG=silvery:passes\n * for the silvery:passes namespace, so the loggily pipeline accepts\n * debug-level events (default level is info, which would drop them).\n *\n * The two gates compose:\n * - INSTRUMENT controls the silvery side (emit-call evaluation, prop\n * allocation).\n * - DEBUG/LOG_LEVEL controls the loggily side (which sinks/stages see\n * the event).\n */\nexport const INSTRUMENT = process.env.SILVERY_INSTRUMENT === \"1\"\nconst instrumentEnabled = INSTRUMENT\n\n// Note on env-var mapping: an earlier draft auto-set DEBUG=silvery:passes\n// when SILVERY_INSTRUMENT=1 for one-switch UX, but that broke ~4 vendor\n// tests in `box-in-text-warning` / `input-owner` that intercept\n// `console.warn`. Setting DEBUG globally appears to interact with how\n// silvery routes warn output through the dev-debug pipeline. Keep the two\n// switches independent: SILVERY_INSTRUMENT controls the silvery-side gate\n// (no overhead when off); DEBUG=silvery:passes (or :<cause>) controls the\n// loggily-side filter when the user wants to additionally pipe events to\n// console. The aggregator stage doesn't need DEBUG to be set — it's\n// always wired into the pipeline.\n\n/** True when the SILVERY_INSTRUMENT env var is set to \"1\". */\nexport function isInstrumentEnabled(): boolean {\n return instrumentEnabled\n}\n\n// =============================================================================\n// Aggregator — captures categorical pass-cause data from log events.\n// =============================================================================\n\ninterface AggregatorState {\n records: PassCauseRecord[]\n perPass: number[]\n}\n\n/**\n * Pipeline-stage-shaped aggregator. The `stage` is attached to a loggily\n * pipeline; it captures `pass` events emitted under the `silvery:passes`\n * namespace into its categorical store.\n *\n * Tests can construct fresh aggregators and attach them to test loggers;\n * the module's default singleton `passAggregator` is what production\n * (and the vitest setup) uses.\n */\nexport interface PassCauseAggregator {\n /** Loggily pipeline stage — attach to logger config to capture events. */\n readonly stage: Stage\n /** Snapshot the current histogram. */\n getHistogram(): PassHistogram\n /** Format histogram as a human-readable summary. */\n formatSummary(): string\n /** Clear all captured records. */\n reset(): void\n /** Record a per-pass-index commit (so we know convergence depth). */\n notePassCommit(passIndex: number): void\n /** Mark the start of a pass (used for unknown synthesis). */\n beginPass(passIndex: number): void\n /** Reset before a fresh convergence loop. */\n beginConvergenceLoop(): void\n /** Append a JSON snapshot to a file (test-runner exit hook). */\n appendJson(file: string): void\n}\n\nexport function createPassCauseAggregator(): PassCauseAggregator {\n let state: AggregatorState = { records: [], perPass: [] }\n let recordsAtPassStart = 0\n\n const stage: Stage = (event: Event): Event => {\n // Match parent (`silvery:passes`) and child (`silvery:passes:<cause>`)\n // namespaces — emit happens on the child namespace so that\n // `DEBUG=silvery:passes:layout-invalidate` filters by cause.\n if (event.kind !== \"log\") return event\n if (event.namespace !== \"silvery:passes\" && !event.namespace.startsWith(\"silvery:passes:\")) {\n return event\n }\n if (event.message !== \"pass\") return event\n const props = event.props as Partial<PassCauseRecord> | undefined\n if (!props?.cause) return event\n state.records.push({\n cause: props.cause,\n nodeId: props.nodeId,\n edge: props.edge,\n producerPhase: props.producerPhase,\n detail: props.detail,\n })\n return event\n }\n\n function getHistogram(): PassHistogram {\n const byCauseMap = new Map<\n PassCause,\n {\n count: number\n edges: Map<string, number>\n nodes: Map<string | number, number>\n }\n >()\n for (const r of state.records) {\n let entry = byCauseMap.get(r.cause)\n if (!entry) {\n entry = { count: 0, edges: new Map(), nodes: new Map() }\n byCauseMap.set(r.cause, entry)\n }\n entry.count += 1\n if (r.edge) entry.edges.set(r.edge, (entry.edges.get(r.edge) ?? 0) + 1)\n if (r.nodeId !== undefined) {\n entry.nodes.set(r.nodeId, (entry.nodes.get(r.nodeId) ?? 0) + 1)\n }\n }\n const byCause: PassHistogramEntry[] = []\n for (const [cause, entry] of byCauseMap) {\n const topEdges = [...entry.edges.entries()]\n .map(([edge, count]) => ({ edge, count }))\n .sort((a, b) => b.count - a.count)\n .slice(0, 8)\n const topNodes = [...entry.nodes.entries()]\n .map(([nodeId, count]) => ({ nodeId, count }))\n .sort((a, b) => b.count - a.count)\n .slice(0, 8)\n byCause.push({ cause, count: entry.count, topEdges, topNodes })\n }\n byCause.sort((a, b) => b.count - a.count)\n return {\n totalRecords: state.records.length,\n perPass: state.perPass.slice(),\n byCause,\n }\n }\n\n function formatSummary(): string {\n const h = getHistogram()\n if (h.totalRecords === 0) return \"pass-cause histogram: no extra passes recorded\"\n const lines: string[] = []\n lines.push(`pass-cause histogram: ${h.totalRecords} extra-pass causes`)\n if (h.perPass.length > 0) {\n const perPassLine = h.perPass\n .map((c, i) => (c > 0 ? `pass${i}=${c}` : null))\n .filter((s) => s !== null)\n .join(\" \")\n if (perPassLine) lines.push(` per-pass commits: ${perPassLine}`)\n }\n for (const entry of h.byCause) {\n const pct = ((entry.count / h.totalRecords) * 100).toFixed(1)\n lines.push(` ${entry.cause}: ${entry.count} (${pct}%)`)\n if (entry.topEdges.length > 0) {\n lines.push(\" edges: \" + entry.topEdges.map((e) => `${e.edge}×${e.count}`).join(\", \"))\n }\n if (entry.topNodes.length > 0) {\n lines.push(\" nodes: \" + entry.topNodes.map((n) => `${n.nodeId}×${n.count}`).join(\", \"))\n }\n }\n return lines.join(\"\\n\")\n }\n\n function reset(): void {\n state = { records: [], perPass: [] }\n recordsAtPassStart = 0\n }\n\n function notePassCommit(passIndex: number): void {\n while (state.perPass.length <= passIndex) state.perPass.push(0)\n state.perPass[passIndex] = (state.perPass[passIndex] ?? 0) + 1\n // If no specific cause was emitted between beginPass and notePassCommit,\n // synthesize an \"unknown\" record. This converts \"no data for this pass\"\n // into observable signal — C3b can read the unknown count to know\n // whether the enum is missing categories for some commit-causing edge.\n if (state.records.length === recordsAtPassStart) {\n state.records.push({\n cause: \"unknown\",\n detail: `pass-${passIndex}-uncategorized-commit`,\n })\n }\n }\n\n function beginPass(_passIndex: number): void {\n recordsAtPassStart = state.records.length\n }\n\n function beginConvergenceLoop(): void {\n recordsAtPassStart = state.records.length\n }\n\n function appendJson(file: string): void {\n const h = getHistogram()\n if (h.totalRecords === 0) return\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const fs = require(\"node:fs\") as typeof import(\"node:fs\")\n fs.appendFileSync(file, JSON.stringify(h) + \"\\n\")\n }\n\n return {\n stage,\n getHistogram,\n formatSummary,\n reset,\n notePassCommit,\n beginPass,\n beginConvergenceLoop,\n appendJson,\n }\n}\n\n// =============================================================================\n// Module-singleton logger + aggregator.\n// =============================================================================\n//\n// Wired together at module load. The logger's pipeline includes:\n// - level: debug for the silvery:passes namespace\n// - the aggregator stage (which captures categorical data)\n//\n// Default behaviour (SILVERY_INSTRUMENT unset): the aggregator stage is\n// still wired in but no events ever reach it because emit sites are gated\n// by INSTRUMENT.\n//\n// When SILVERY_INSTRUMENT=1, emit sites call `passLog.debug?.(\"pass\", ...)`\n// and loggily's pipeline routes the event through the aggregator stage.\n\nconst passAggregator = createPassCauseAggregator()\n\n/** The singleton aggregator — used by tests and tooling to read counts. */\nexport function getPassAggregator(): PassCauseAggregator {\n return passAggregator\n}\n\n/**\n * Pass-cause loggers. Parent namespace is `silvery:passes`. Each cause gets\n * a child namespace `silvery:passes:<cause>` so users can filter at any\n * granularity:\n * DEBUG=silvery:passes → all causes\n * DEBUG=silvery:passes:layout-invalidate → only layout-invalidate\n * DEBUG=silvery:passes:scrollto-settle → only scrollto-settle\n *\n * Both the parent and every child share the same pipeline (level + the\n * aggregator stage). Children are cached on first use to avoid logger\n * construction in the hot path.\n *\n * The aggregator stage is always attached — it's a no-op when no events\n * arrive, and the INSTRUMENT gate at emit sites prevents events when the\n * env var is unset.\n */\nconst passLogConfig = [\n // When SILVERY_INSTRUMENT=1, raise this namespace's level to debug so\n // emit sites' `?.debug` calls actually dispatch.\n { level: instrumentEnabled ? \"debug\" : \"info\" } as const,\n passAggregator.stage,\n] as const\n\nconst passLog: ConditionalLogger = createLogger(\"silvery:passes\", [...passLogConfig])\n\nconst childLoggers = new Map<PassCause, ConditionalLogger>()\nfunction loggerForCause(cause: PassCause): ConditionalLogger {\n let child = childLoggers.get(cause)\n if (!child) {\n child = createLogger(`silvery:passes:${cause}`, [...passLogConfig])\n childLoggers.set(cause, child)\n }\n return child\n}\n\n// =============================================================================\n// Public API — back-compat shape preserved so existing call sites in\n// renderer.ts / layout-phase.ts / measure-phase.ts / runtime/renderer.ts /\n// runtime/create-app.tsx don't need to change.\n// =============================================================================\n\n/**\n * Reset the per-loop pass-index tracker. Called at the top of each\n * convergence loop in renderer.ts / runtime/create-app.tsx.\n */\nexport function beginConvergenceLoop(): void {\n if (!instrumentEnabled) return\n passAggregator.beginConvergenceLoop()\n}\n\n/** Mark the start of pass N (0-based). */\nexport function beginPass(passIndex: number): void {\n if (!instrumentEnabled) return\n passAggregator.beginPass(passIndex)\n}\n\n/**\n * Record that a feedback edge fired during the current pass.\n *\n * Emits on the per-cause child namespace so that\n * `DEBUG=silvery:passes:<cause>` filters by cause; the aggregator stage\n * captures both parent and child events into the canonical histogram.\n */\nexport function logPass(record: PassCauseRecord): void {\n if (!instrumentEnabled) return\n const log = loggerForCause(record.cause)\n log.debug?.(\"pass\", record as unknown as Record<string, unknown>)\n}\n\n/**\n * Re-export of the parent-namespace logger for callers that need to emit\n * generic `silvery:passes`-namespaced messages (e.g. tooling that reads\n * DEBUG=silvery:passes for an aggregate trace).\n */\nexport function getPassLog(): ConditionalLogger {\n return passLog\n}\n\n/**\n * Mark that pass N committed React work that will require pass N+1.\n * Bumps the per-pass-index counter and synthesizes an \"unknown\" record\n * if no specific cause was emitted during pass N.\n */\nexport function notePassCommit(passIndex: number): void {\n if (!instrumentEnabled) return\n passAggregator.notePassCommit(passIndex)\n}\n\n/** Snapshot the current pass-cause histogram. */\nexport function getPassHistogram(): PassHistogram {\n if (!instrumentEnabled) {\n return { totalRecords: 0, perPass: [], byCause: [] }\n }\n return passAggregator.getHistogram()\n}\n\n/** Clear all captured records. */\nexport function resetPassHistogram(): void {\n passAggregator.reset()\n}\n\n/** Format the histogram as a one-screen text summary. */\nexport function formatPassHistogram(_h?: PassHistogram): string {\n return passAggregator.formatSummary()\n}\n\n/**\n * Print histogram to stderr (test runner / app teardown). No-op if\n * instrumentation is disabled or no records exist.\n *\n * In test environments where stdout/stderr are intercepted, write to\n * SILVERY_INSTRUMENT_FILE instead.\n */\nexport function printPassHistogram(): void {\n if (!instrumentEnabled) return\n const h = passAggregator.getHistogram()\n if (h.totalRecords === 0) return\n const formatted = passAggregator.formatSummary()\n const file = process.env.SILVERY_INSTRUMENT_FILE\n if (file) {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const fs = require(\"node:fs\") as typeof import(\"node:fs\")\n fs.appendFileSync(file, \"\\n\" + formatted + \"\\n\")\n return\n }\n process.stderr.write(\"\\n\" + formatted + \"\\n\")\n}\n\n/** Append a JSON snapshot to a file (vitest exit hook). */\nexport function appendHistogramJson(file: string): void {\n if (!instrumentEnabled) return\n passAggregator.appendJson(file)\n}\n\n// =============================================================================\n// Bounded-convergence (C3b)\n// =============================================================================\n\n/**\n * Per-cause convergence bound: maximum extra passes (beyond the initial pass)\n * each PassCause category can structurally trigger before settling. Each\n * bound is an invariant of how the cause works in the pipeline — not a\n * histogram observation — and the math is documented in\n * `hub/silvery/design/convergence-bounds.md`.\n *\n * Empirical baseline (C3a v3 corpus, 105 termless app teardowns,\n * 11 538 records): no test reached pass 1+. The structural ceiling matches:\n * sum(PASS_CAUSE_BOUNDS) = 0, plus 1 initial pass + 1 settle pass = MAX=2.\n *\n * Why every cause's bound is 0:\n * - layout-invalidate: when a subscriber re-renders, the new layout produces\n * the SAME rects (idempotent given same input) so the second\n * notifyLayoutSubscribers reports no change. The settle pass drains the\n * subscribers' commits; it doesn't seed new feedback.\n * - intrinsic-shrinkwrap: measurement is content-deterministic; same tree\n * produces same shrunkWidth / fitContentHeight on the next pass.\n * - scrollto-settle: prevScrollTo === scrollTo guard prevents the same intent\n * from re-firing; the recovery edge is a one-shot \"target moved offscreen\".\n * - sticky-resettle: stickyChildren is recomputed deterministically from\n * layout positions — once layout is stable, sticky offsets are stable.\n * - viewport-resize: dim-change is a depth-0 root trigger, not a feedback\n * edge. Once the new dims are observed, no further resize fires.\n * - unknown: any non-zero unknown count is a regression to surface, not\n * budget to consume.\n *\n * The total convergence ceiling = 1 (initial) + 1 (settle) + sum of per-cause\n * bounds. Per the v3 corpus and the audit above, the per-cause bounds are\n * ALL 0 — each cause is satisfied within the single settle pass. Total = 2.\n */\nexport const PASS_CAUSE_BOUNDS: Readonly<Record<PassCause, number>> = {\n \"layout-invalidate\": 0,\n \"intrinsic-shrinkwrap\": 0,\n \"scrollto-settle\": 0,\n \"sticky-resettle\": 0,\n \"viewport-resize\": 0,\n unknown: 0,\n} as const\n\n/**\n * Total subscriber-feedback bound: 1 (initial) + 1 (one settle pass) + sum\n * of PASS_CAUSE_BOUNDS = 2. This applies to loops where pipeline output and\n * React effects flush in **separate phases** — singlePassLayout (one\n * runPipeline + a separate effect-flush loop), the effect-flush loop\n * itself, and the production processEventBatch flush.\n *\n * The `classic` loop in renderer.ts is structurally different: it runs\n * runPipeline + flushSyncWork **interleaved** within the same loop, so a\n * single iteration may emit a rect-signal change AND drain a virtualizer\n * re-render that mounts new items, which then need ANOTHER iteration to\n * lay out. That's not subscriber feedback — it's layout-vs-React\n * stabilisation, and the historical empirical bound for it is 5. Tests\n * that exercise virtualizer + scroll convergence (e.g.\n * list-view-refinements with 100 items + indexed virtualization) actually\n * use 3-4 of those iterations to settle the rendered window.\n *\n * Both bounds replace the four prior magic constants\n * (MAX_SINGLE_PASS_ITERATIONS=15, MAX_LAYOUT_ITERATIONS=5,\n * MAX_EFFECT_FLUSHES=5, maxFlushes=5) — the 15-pass cap is gone, and the\n * 5 is now an explicit per-loop documented value, not a guess.\n */\nexport const MAX_CONVERGENCE_PASSES =\n 1 + // initial pass\n 1 + // canonical settle pass — drains subscriber commits within the settle\n PASS_CAUSE_BOUNDS[\"layout-invalidate\"] +\n PASS_CAUSE_BOUNDS[\"intrinsic-shrinkwrap\"] +\n PASS_CAUSE_BOUNDS[\"scrollto-settle\"] +\n PASS_CAUSE_BOUNDS[\"sticky-resettle\"] +\n PASS_CAUSE_BOUNDS[\"viewport-resize\"] +\n PASS_CAUSE_BOUNDS.unknown\n\n/**\n * Initial-render pass cap. Historical: the test renderer's initial render\n * used to run with this wider cap so hooks like useBoxRect could\n * subscribe → layout → forceUpdate → re-render before the first frame was\n * stable. As of `@km/silvery/test-harness-convergence-cap-parity` the\n * test harness uses the same `MAX_CONVERGENCE_PASSES` cap as production\n * on first paint — tests asserting post-convergence state opt in via\n * `await app.waitForLayoutStable()`.\n *\n * The constant is retained for the structural ceiling assertions in\n * `tests/pipeline/bounded-convergence*.test.ts`, but is no longer wired\n * into any production or test runtime code path. The layout-signals\n * refactor (`@km/silvery/listview-layout-signals-from-getlayoutsignals`)\n * eliminated the structural 3+ pass convergence requirement that\n * motivated the wider cap in the first place.\n *\n * @deprecated Not used by any runtime path as of 2026-05-11. Will be\n * removed once the asserting tests in `bounded-convergence*.test.ts`\n * migrate to a pure-constant fixture.\n */\nexport const INITIAL_RENDER_MAX_PASSES = 5\n\n/**\n * Loops that wrap their iterations in the bound assertion. Used as the\n * `loopName` argument to `assertBoundedConvergence` so an over-budget\n * regression names the offending loop.\n */\nexport type ConvergenceLoopName = \"layout-pass\" | \"effect-flush\" | \"production-flush\"\n\n/**\n * Assert the convergence loop did not exceed its cap. Called from the\n * renderer's loops + create-app's processEventBatch flush loop. No-op\n * outside SILVERY_STRICT — the loop bound itself is the production\n * safety net.\n *\n * STRICT=2: throws with a per-cause breakdown so a regression names the\n * responsible edge instead of just \"exhausted N iterations\".\n * STRICT=1: emits a stderr warning with the same breakdown.\n */\nexport function assertBoundedConvergence(\n passCount: number,\n loopName: ConvergenceLoopName,\n cap: number,\n): void {\n if (passCount <= cap) return\n const strict = process?.env?.SILVERY_STRICT\n if (!strict) return\n const h = getPassHistogram()\n const breakdown = h.byCause\n .map((e) => `${e.cause}=${e.count}(bound=${PASS_CAUSE_BOUNDS[e.cause]})`)\n .join(\", \")\n const msg =\n `convergence bound exceeded in ${loopName}: ${passCount} passes ran ` +\n `but cap=${cap} (default MAX_CONVERGENCE_PASSES=${MAX_CONVERGENCE_PASSES}). ` +\n `Per-cause breakdown: ${breakdown || \"(no records — INSTRUMENT off)\"}. ` +\n `Either a feedback edge broke its per-cause invariant, or a new edge ` +\n `needs a PassCause category in pass-cause.ts.`\n if (strict === \"2\") throw new Error(msg)\n process.stderr.write(`[silvery] ${msg}\\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\"\nimport { INSTRUMENT, logPass } from \"../runtime/pass-cause\"\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 const prevWidth = node.boxRect?.width\n node.layoutNode.setMaxWidth(shrunkWidth)\n if (INSTRUMENT && prevWidth !== undefined && prevWidth !== shrunkWidth) {\n // Width changed since last frame's layout — this measure-phase pass\n // produced a different intrinsic size. That's a feedback edge: a\n // subsequent layout pass will use the new constraint.\n logPass({\n cause: \"intrinsic-shrinkwrap\",\n edge: \"snug-content:width\",\n producerPhase: \"measure\",\n detail: `${prevWidth}→${shrunkWidth}`,\n })\n }\n }\n if (isHeightFitContent) {\n const intrinsicSize = measureIntrinsicSize(node, ctx, availableWidth)\n const prevHeight = node.boxRect?.height\n node.layoutNode.setHeight(intrinsicSize.height)\n if (INSTRUMENT && prevHeight !== undefined && prevHeight !== intrinsicSize.height) {\n logPass({\n cause: \"intrinsic-shrinkwrap\",\n edge: \"fit-content:height\",\n producerPhase: \"measure\",\n detail: `${prevHeight}→${intrinsicSize.height}`,\n })\n }\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. For\n // `wrap-truncate`, thread the truncate-atomic-overflow flag so the\n // intrinsic line count matches what render emits (single ellipsis\n // line) instead of the char-wrap line count.\n const truncateAtomic = textProps.wrap === \"wrap-truncate\"\n lines = ctx\n ? ctx.measurer.wrapText(text, availableWidth, true, true, truncateAtomic)\n : wrapText(text, availableWidth, true, true, truncateAtomic)\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\" ||\n wrap === \"wrap-truncate\" ||\n wrap === \"hard\" ||\n wrap === \"even\" ||\n wrap === true ||\n 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 {\n syncDecorationRects,\n syncRectSignals,\n hasLayoutSignals,\n hasObservedLayoutSignal,\n} from \"@silvery/ag/layout-signals\"\nimport { logPass, INSTRUMENT } from \"../runtime/pass-cause\"\n\nconst log = createLogger(\"silvery:layout\")\n\n/**\n * Stable-ish identity string for an AgNode used by pass-cause records.\n * Prefers explicit identity props (testid / id / name / nodeId) and falls\n * back to type. Keeps the histogram readable without paying a Map lookup\n * per node when SILVERY_INSTRUMENT is unset (call site is gated by the\n * inert no-op in `logPass`).\n */\nfunction nodeIdent(node: AgNode): string {\n const props = node.props as Record<string, unknown> | undefined\n const ident =\n (props?.[\"testid\"] as string | undefined) ??\n (props?.[\"id\"] as string | undefined) ??\n (props?.[\"name\"] as string | undefined) ??\n (props?.[\"nodeId\"] as string | undefined)\n return ident ? `${node.type}#${ident}` : node.type\n}\n\nfunction nodePath(node: AgNode): string {\n const path: string[] = []\n let current: AgNode | null = node\n while (current) {\n path.push(nodeIdent(current))\n current = current.parent\n }\n return path.reverse().join(\" > \")\n}\n\nfunction layoutPropsForLog(node: AgNode): Record<string, unknown> {\n const props = (node.props ?? {}) as Record<string, unknown>\n return {\n width: props.width,\n maxWidth: props.maxWidth,\n minWidth: props.minWidth,\n flexGrow: props.flexGrow,\n flexShrink: props.flexShrink,\n flexBasis: props.flexBasis,\n alignSelf: props.alignSelf,\n position: props.position,\n overflow: props.overflow,\n borderStyle: props.borderStyle,\n padding: props.padding,\n paddingLeft: props.paddingLeft,\n paddingRight: props.paddingRight,\n }\n}\n\nfunction normalizeScrollOffset(offset: number): number {\n if (!Number.isFinite(offset)) return 0\n return Math.round(offset)\n}\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 // CLS instrumentation moved out of layout phase 2026-05-13 (Option C\n // consolidation, bead @km/silvery/cls-instrumentation-primitive Phase 9b).\n // The boxRect-domain hook here missed scroll- and sticky-induced shifts —\n // exactly the user-visible flicker class CLS exists to catch. CLS now\n // reads screenRect (post-scroll, sticky-aware) via cls-monitor.onCommit\n // at the renderer-level commit boundary (renderer.ts doRender).\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 * Lightweight `getEffectiveBg` for the layout phase. Mirrors the\n * production helper in `render-box.ts` (Sterling-aware: `bg-surface-default`\n * → legacy `bg`). Inlined here to avoid a layout→render-box import cycle.\n *\n * The render phase still uses its own `getEffectiveBg` for the cascade\n * formulas; this duplicate exists ONLY so the layout phase can flag\n * \"bg-bearing descendant moved/shrank\" without dragging render-box into\n * the layout module graph.\n */\nfunction _layoutGetEffectiveBg(props: BoxProps): string | undefined {\n if (props.backgroundColor) return props.backgroundColor as string\n const theme = props.theme as Record<string, unknown> | undefined\n if (theme) {\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 * True iff `prev` has any cells NOT covered by `cur` (shrink, move, or any\n * combination thereof). This is the \"bg residue\" predicate: cells the\n * painter occupied before but doesn't occupy now. Equivalent to\n * `prev \\ cur ≠ ∅`.\n */\nfunction _prevHasResidueOutside(\n prev: Rect,\n cur: { x: number; y: number; width: number; height: number },\n): boolean {\n return (\n prev.x < cur.x ||\n prev.y < cur.y ||\n prev.x + prev.width > cur.x + cur.width ||\n prev.y + prev.height > cur.y + cur.height\n )\n}\n\n/**\n * Check if any descendant was overflowing THIS node's rect and had its layout\n * change OR if any bg-bearing descendant shrank/moved within this node's rect\n * (leaving stale bg residue inside).\n *\n * Two related cases:\n *\n * 1. **Outside-overflow** (original): descendant's prev rect extended\n * BEYOND `node`'s current rect. The descendant either grew past\n * `node`'s edge before and shrank back inside, or moved out and back —\n * either way `node`'s border/padding cells outside the descendant's\n * new rect carry stale pixels.\n *\n * 2. **Inside-bg-residue** (added 2026-05-07, bead\n * @km/silvery/incremental-bg-residue-shrink-move): descendant has\n * `effectiveBg` AND its prev rect has cells NOT covered by its\n * current rect (any shrink/move). The painter's prev paint covered\n * those cells with its bg; on this frame nothing in the painter's\n * subtree paints them. If those cells are within `node`'s rect,\n * `node` must clear them via its own `clearNodeRegion` (when\n * transparent) or `renderBox` fill (when bg-bearing). Without this\n * flag, `node`'s contentAreaAffected may be FALSE — the painter's\n * `clearExcessArea` SKIPS due to the position-change guard, and\n * `node`'s fast-path-with-subtreeDirty doesn't trigger any cleanup.\n * The cell carries forward bg from the prev frame indefinitely.\n *\n * Why on `node` and not just the immediate parent: the residue\n * cells must be covered by SOME ancestor that clears or fills.\n * The closest transparent ancestor that contains the residue cells\n * must clear them; if every ancestor up to a bg-bearing one is\n * transparent, that bg-bearing ancestor's renderBox fill suffices.\n * Setting the flag at every ancestor whose rect contains residue\n * cells guarantees coverage regardless of where the bg-break is.\n *\n * Recursive: follows subtreeDirty paths for efficiency. Returns early on\n * first match. Performance: only runs when subtreeDirty (matches the\n * `_hasAbsoluteChildMutated` guard); for typical UI updates the search\n * touches < 10 nodes before terminating.\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 // Case 1: prev extended outside `node`'s current rect.\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 // Case 2: bg-bearing descendant shrank/moved within `node`'s rect,\n // leaving residue cells that nothing in its subtree will repaint.\n // The intersection of (prev \\ cur) with `node`'s rect is the residue\n // inside `node`. We over-approximate with `prev \\ cur ≠ ∅` AND\n // `descendant has effectiveBg` — if any residue cell is inside\n // `node`, we must flag.\n if (child.boxRect) {\n const props = child.props as BoxProps\n if (\n _layoutGetEffectiveBg(props) !== undefined &&\n _prevHasResidueOutside(prev, child.boxRect)\n ) {\n // Residue exists somewhere in `prev \\ cur`. Check if any of it\n // intersects `node`'s rect. Since `prev` is the painter's old\n // rect (which was within or overlapping `node` last frame),\n // and `node`'s current rect contains the painter's CURRENT\n // rect (or at least overlaps it), the residue is almost always\n // inside `node`. Use the conservative test: prev ∩ node ≠ ∅.\n const ix1 = Math.max(prev.x, nodeLeft)\n const iy1 = Math.max(prev.y, nodeTop)\n const ix2 = Math.min(prev.x + prev.width, nodeRight)\n const iy2 = Math.min(prev.y + prev.height, nodeBottom)\n if (ix2 > ix1 && iy2 > iy1) {\n return true\n }\n }\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 sizeChanged =\n (node.prevLayout?.width ?? 0) !== (node.boxRect?.width ?? 0) ||\n (node.prevLayout?.height ?? 0) !== (node.boxRect?.height ?? 0)\n const screenChanged = !rectEqual(node.prevScrollRect, node.scrollRect)\n const renderChanged = !rectEqual(node.prevScreenRect, node.screenRect)\n\n // Pass-cause emit: when a rect signal value actually changes AND a\n // subscriber is present (signals lazily allocate via getLayoutSignals\n // when useBoxRect/useScrollRect mount), useSyncExternalStore consumers\n // can forceUpdate(), which the convergence loop sees as `hadReactCommit`\n // and answers with another pass.\n //\n // The hasLayoutSignals(node) gate is the dual-pro-recommended fix for\n // the \"99.83% layout-invalidate noise\" problem: an unsubscribed rect\n // sync is bookkeeping volume, not a feedback edge. Without this gate\n // the bucket buries every other category. With this gate the histogram\n // measures actual subscriber-driven feedback that bounded-convergence\n // (C3b) needs to bound.\n //\n // Gated on INSTRUMENT (module-level constant) so V8/JSC fold the entire\n // block out of the hot path when SILVERY_INSTRUMENT is unset.\n if (INSTRUMENT) {\n if (contentChanged || screenChanged || renderChanged) {\n // hasLayoutSignals returns true iff getLayoutSignals(node) was\n // called previously — which happens lazily on first subscriber.\n const observed = hasLayoutSignals(node)\n if (observed) {\n const ident = nodeIdent(node)\n if (contentChanged && hasObservedLayoutSignal(node, \"boxRect\")) {\n logPass({\n cause: \"layout-invalidate\",\n edge: \"boxRect\",\n nodeId: ident,\n producerPhase: \"layout\",\n })\n }\n if (sizeChanged && hasObservedLayoutSignal(node, \"boxSize\")) {\n logPass({\n cause: \"layout-invalidate\",\n edge: \"boxSize\",\n nodeId: ident,\n producerPhase: \"layout\",\n })\n }\n if (screenChanged && hasObservedLayoutSignal(node, \"scrollRect\")) {\n logPass({\n cause: \"layout-invalidate\",\n edge: \"scrollRect\",\n nodeId: ident,\n producerPhase: \"layout\",\n })\n }\n if (renderChanged && hasObservedLayoutSignal(node, \"screenRect\")) {\n logPass({\n cause: \"layout-invalidate\",\n edge: \"screenRect\",\n nodeId: ident,\n producerPhase: \"layout\",\n })\n }\n }\n }\n }\n\n // Sync current rect values into layout signals. The signal layer also\n // tracks which rect fields were actually read, so instrumentation can\n // avoid counting measurement-only boxes as scroll/screen subscribers.\n syncRectSignals(node)\n\n // Recurse to children\n for (const child of node.children) {\n notifyLayoutSubscribers(child)\n }\n\n // After every node's rect signals (including anchorRect) are populated,\n // run a second pass at the ROOT to resolve `decorations`. The two-pass\n // shape is required because decoration resolution calls\n // `findAnchor(root, id)`, which needs every anchorRect populated for the\n // current frame. A single recursive pass would let a popover declared\n // shallow in the tree miss an anchor declared deeper. Phase 4c of\n // `km-silvery.view-as-layout-output` (overlay-anchor v1).\n if (node.parent === null) {\n syncDecorationRects(node)\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 detail = {\n child: nodeIdent(child),\n parent: nodeIdent(node),\n childPath: nodePath(child),\n childBox: child.boxRect,\n parentBox: node.boxRect,\n parentInnerWidth,\n border,\n padding,\n childProps: layoutPropsForLog(child),\n parentProps: layoutPropsForLog(node),\n }\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}, path: ${detail.childPath})`\n\n if (shouldThrow) {\n throw new Error(msg)\n } else {\n log.debug?.(\"layout overflow\", detail)\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: {\n child: AgNode\n top: number\n bottom: number\n index: number\n isSticky: boolean\n }[],\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 prevScrollTo = node.scrollState?.prevScrollTo\n const explicitOffset = props.scrollOffset\n let scrollOffset = explicitOffset ?? prevOffset ?? 0\n const scrollTo = props.scrollTo\n\n // Distinguish \"new intent\" from \"same intent\":\n //\n // NEW intent — scrollTo changed since last frame (user pressed a key,\n // cursor moved, external setter jumped target). Fire the\n // full edge-based ensure-visible so the new target lands\n // inside the viewport. First render is also NEW intent\n // (prevScrollTo === undefined, scrollTo is defined → differ).\n //\n // SAME intent — scrollTo unchanged from last frame. This render was\n // triggered by something else (state change, content\n // growth, theme flip, wheel scroll, etc.). Skip ensure-\n // visible entirely — the offset persists from prevOffset\n // (or explicitOffset wins). This is the critical guard\n // that prevents \"viewport jumps on click-to-expand\" —\n // growing a visible item must not shift the viewport.\n //\n // This mirrors the already-landed fix in `useVirtualizer` (commit\n // 50d13d41 — `scrollToChanged` guard). Box's ensure-visible is the\n // sibling layer; same pattern, same rationale.\n //\n // For imperative \"scroll to this index NOW (even if it's the same value)\",\n // callers should toggle scrollTo off and on, or use an imperative API.\n const scrollToChanged = prevScrollTo !== scrollTo\n\n // \"Same intent\" recovery: even when scrollToChanged is false, fire ensure-\n // visible if the cached offset has the target COMPLETELY off-screen. This\n // happens during multi-pass layout convergence — the first pass sets offset\n // based on partial measurements (small contentHeight), then later passes\n // grow contentHeight as items measure, leaving the cached offset clamped\n // far away from the now-correctly-positioned target. Without this recovery,\n // the offset stays stuck and STRICT invariants (scrollTo target intersects\n // viewport) fire correctly-detected violations.\n //\n // Conservative: only re-fires when target has NO intersection with the raw\n // viewport. Partial visibility (target grew, target.bottom > visibleBottom\n // but target.top < visibleBottom) is left alone — that's the\n // \"click-to-expand should not yank viewport\" guarantee.\n let targetCompletelyOffscreen = false\n if (\n !scrollToChanged &&\n scrollTo !== undefined &&\n scrollTo >= 0 &&\n scrollTo < childPositions.length\n ) {\n const target = childPositions.find((c) => c.index === scrollTo)\n if (target && target.top !== target.bottom) {\n const visTop = scrollOffset\n const visBottom = scrollOffset + viewportHeight\n const intersects = target.bottom > visTop && target.top < visBottom\n if (!intersects) targetCompletelyOffscreen = true\n }\n }\n\n if (\n scrollTo !== undefined &&\n scrollTo >= 0 &&\n scrollTo < childPositions.length &&\n (scrollToChanged || targetCompletelyOffscreen)\n ) {\n // Find the target child\n const target = childPositions.find((c) => c.index === scrollTo)\n if (target) {\n // scrollTo settle: an offset adjustment may shift child layout, which\n // in turn may invalidate rect signals for descendants. Attribute to\n // the originating scrollTo prop so C3b can bound this edge.\n if (INSTRUMENT) {\n logPass({\n cause: \"scrollto-settle\",\n edge: targetCompletelyOffscreen ? \"scrollTo:recovery\" : \"scrollTo:newIntent\",\n nodeId: nodeIdent(node),\n producerPhase: \"scroll\",\n detail: `target=${scrollTo}`,\n })\n }\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 //\n // \"Too tall to fit\" must be handled FIRST: when the target is taller\n // than the effective viewport, no offset can satisfy both\n // `target.top >= visibleTop` AND `target.bottom <= visibleBottom`.\n // Without this branch, the two branches below alternate across\n // iterations of the layout loop — `target.top - 1` exposes the top\n // edge, which then makes `target.bottom > visibleBottom` true, so\n // the next iteration flips to the snap-to-bottom branch, whose offset\n // makes `target.top < visibleTop` true again, and so on. The offset\n // pingpongs, exhausting the 5-iteration budget and forcing downstream\n // consumers (e.g. the virtualizer) to route around the instability.\n //\n // Show the TOP of the oversized target. That matches \"cursor on tall\n // outlier\" intent (the user wants to see the card they moved to) and\n // is stable — no subsequent branch fires because only one is eligible.\n const targetHeight = target.bottom - target.top\n if (targetHeight > effectiveHeight) {\n scrollOffset = target.top > 0 ? target.top - 1 : 0\n } else 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 = normalizeScrollOffset(scrollOffset)\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 // Read `representsItems` from a child's props — defaults to 1 (a single\n // visual item). Virtualized lists set this on their leading/trailing\n // placeholder Boxes so the parent's hiddenAbove/hiddenBelow count reflects\n // real items rather than placeholder boxes.\n const logicalCount = (cp: { child: AgNode }): number => {\n const cps = cp.child.props as BoxProps\n const r = cps.representsItems\n return r !== undefined && r >= 0 ? r : 1\n }\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 += logicalCount(cp)\n } else if (cp.top >= visibleBottom) {\n hiddenBelow += logicalCount(cp)\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. But discriminate\n // three cases for the LAST child:\n // (a) cp.bottom > raw viewport bottom → content is truly truncated,\n // indicator should fire (e.g. 10 cards × 3 rows in viewport=29 →\n // last card's bottom row is cut off; ▼N expected)\n // (b) cp.bottom ≤ raw viewport bottom but > effective (reserve-adjusted)\n // bottom → the reserve row \"steals\" from an otherwise-visible last\n // card, producing a PHANTOM ▼1 at scrollTo=lastIndex when nothing\n // lies beyond. Skip the increment in this case.\n // (c) childHeight > viewportHeight AND scrollTo === last index → the\n // too-tall-to-fit branch above placed the oversized target's top at\n // row 1 (below the top indicator reserve), so its bottom extends\n // far past rawViewportBottom. Case (a)'s check fires, but the user\n // IS at the last item — nothing lies beyond. Skip the increment\n // (the bottom reserve is legitimately stealing from the target's\n // tail, not from a hidden below-item).\n const isLastChild = cp.index === childPositions[childPositions.length - 1]?.index\n const rawViewportBottom = scrollOffset + viewportHeight\n const childHeight = cp.bottom - cp.top\n const isPhantomReserveCut = isLastChild && cp.bottom <= rawViewportBottom\n const isOversizedLastAtEnd =\n isLastChild && childHeight > viewportHeight && scrollTo === cp.index\n if (indicatorReserve > 0 && !isPhantomReserveCut && !isOversizedLastAtEnd) {\n hiddenBelow += logicalCount(cp)\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 // STRICT invariants (run BEFORE skipStateUpdates so fresh-render comparisons\n // catch violations too).\n //\n // Rationale: the column-top-disappears bug class (2026-04-20, ≥4 sessions)\n // arose because scroll state carried subtly illegal values (offset past max,\n // firstVisibleChild pointing at a child that didn't actually intersect the\n // viewport, sticky render offset clipped beyond legal bounds). STRICT mode\n // verifies incremental==fresh but cannot catch drift that's consistent\n // between both passes. Per-coordination-point invariants plug that gap.\n //\n // SILVERY_STRICT=1 → console.warn on violation\n // SILVERY_STRICT=2 → throw on violation (regression test gate)\n //\n // All invariants are generic (no virtualizer knowledge) — ListView-specific\n // invariants live in ListView itself.\n strictScrollInvariants(\n node,\n props,\n scrollOffset,\n contentHeight,\n viewportHeight,\n indicatorReserve,\n childPositions,\n firstVisible,\n lastVisible,\n stickyChildren,\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 // Remember the scrollTo value we processed this frame so next frame can\n // distinguish \"new intent\" (scrollTo changed) from \"same intent\" (same\n // value, re-render for another reason). See the guard above.\n prevScrollTo: scrollTo,\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 * Runtime invariants for scroll state. Gated on SILVERY_STRICT.\n *\n * Invariant set (all violations use `[SILVERY_STRICT]` prefix, L1 warn / L2 throw):\n * 1. scrollOffset is clamped: 0 ≤ scrollOffset ≤ max(0, contentHeight - viewportHeight).\n * 2. If scrollTo is a valid index, the target child intersects the effective\n * viewport (viewport minus indicatorReserve) after the offset calc.\n * 3. firstVisibleChild / lastVisibleChild correspond to children that actually\n * intersect the effective viewport (not spacer indices outside).\n * 4. Sticky child renderOffset stays within legal viewport bounds — specifically\n * `renderOffset + height > 0 && renderOffset < viewportHeight` (sticky row\n * not clipped entirely out, which `calculateScrollState` already filters,\n * but we also verify the sticky row's bottom doesn't sit ABOVE 0 for\n * isSticking=true entries since clamping should have prevented that).\n *\n * These are GENERIC scroll invariants — no virtualizer knowledge. Any divergence\n * points to a bug in the scroll/sticky math itself.\n */\nfunction strictScrollInvariants(\n node: AgNode,\n props: BoxProps,\n scrollOffset: number,\n contentHeight: number,\n viewportHeight: number,\n indicatorReserve: number,\n childPositions: {\n child: AgNode\n top: number\n bottom: number\n index: number\n isSticky: boolean\n stickyTop?: number\n stickyBottom?: number\n }[],\n firstVisible: number,\n lastVisible: number,\n stickyChildren: NonNullable<AgNode[\"scrollState\"]>[\"stickyChildren\"] & object,\n): void {\n const strict = process?.env?.SILVERY_STRICT\n if (!strict) return\n\n const shouldThrow = strict === \"2\"\n const nodeId = (props as any).id ?? node.type\n const report = (msg: string): void => {\n const full = `[SILVERY_STRICT] ${msg} (node: ${nodeId})`\n if (shouldThrow) throw new Error(full)\n else console.warn(full)\n }\n\n // Invariant 1: scrollOffset clamping\n const maxOffset = Math.max(0, contentHeight - viewportHeight)\n if (scrollOffset < 0) {\n report(`scrollOffset ${scrollOffset} < 0`)\n } else if (scrollOffset > maxOffset) {\n report(\n `scrollOffset ${scrollOffset} exceeds max ${maxOffset} ` +\n `(contentHeight=${contentHeight}, viewportHeight=${viewportHeight})`,\n )\n }\n\n // Invariant 2: scrollTo target intersects the RAW viewport (not the\n // indicator-reserved effective viewport). The indicator overlays the\n // reserved row rather than hiding content, so a target whose top lands\n // exactly at the reserved row is still visible through the overlay —\n // a legitimate edge case at scrollTo=last-item.\n const scrollTo = props.scrollTo\n if (scrollTo !== undefined && scrollTo >= 0 && scrollTo < childPositions.length) {\n const target = childPositions.find((c) => c.index === scrollTo)\n if (target && target.top !== target.bottom) {\n // Zero-height children are exempt — they can't intersect anything.\n const visibleTop = scrollOffset\n const visibleBottom = scrollOffset + viewportHeight\n const intersects = target.bottom > visibleTop && target.top < visibleBottom\n if (!intersects) {\n report(\n `scrollTo target index=${scrollTo} does not intersect viewport ` +\n `(target [${target.top},${target.bottom}), visible [${visibleTop},${visibleBottom}), ` +\n `indicatorReserve=${indicatorReserve})`,\n )\n }\n }\n }\n\n // Invariant 3: firstVisible / lastVisible correspond to intersecting children\n const visibleTop = scrollOffset\n const visibleBottom = scrollOffset + viewportHeight - indicatorReserve\n const checkVisible = (label: string, idx: number): void => {\n if (idx < 0) return // -1 = nothing visible, legal\n const cp = childPositions.find((c) => c.index === idx)\n if (!cp) {\n report(`${label}=${idx} but no child at that index`)\n return\n }\n if (cp.isSticky) return // sticky children are always \"visible\" by design\n if (cp.top === cp.bottom) {\n // Zero-height children shouldn't be chosen as first/last visible.\n report(`${label}=${idx} references zero-height child`)\n return\n }\n // Child must actually intersect the effective viewport (partial counts).\n const intersects = cp.bottom > visibleTop && cp.top < visibleBottom\n if (!intersects) {\n report(\n `${label}=${idx} does not intersect effective viewport ` +\n `(child [${cp.top},${cp.bottom}), visible [${visibleTop},${visibleBottom}))`,\n )\n }\n }\n checkVisible(\"firstVisibleChild\", firstVisible)\n checkVisible(\"lastVisibleChild\", lastVisible)\n\n // Invariant 4: sticky child renderOffset is within legal viewport bounds.\n // calculateScrollState already filters out sticky children that end up entirely\n // off-screen (renderOffset+h ≤ 0 or renderOffset ≥ viewportHeight), so every\n // entry here MUST at least partially intersect [0, viewportHeight).\n for (const sc of stickyChildren) {\n const topRow = sc.renderOffset\n const bottomRow = sc.renderOffset + sc.height\n if (bottomRow <= 0 || topRow >= viewportHeight) {\n report(\n `sticky child index=${sc.index} renderOffset=${topRow} height=${sc.height} ` +\n `outside viewport [0,${viewportHeight})`,\n )\n }\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 if (INSTRUMENT) {\n // Sticky child offsets changed since last frame — the parent is now\n // marked dirty and a follow-on layout pass will reflow children.\n logPass({\n cause: \"sticky-resettle\",\n edge: \"stickyChildren\",\n nodeId: nodeIdent(node),\n producerPhase: \"sticky\",\n detail: `${prev?.length ?? 0}→${next?.length ?? 0}`,\n })\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 `\"mono\"` (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 *\n * Post km-silvery.terminal-profile-plateau Phase 1 this is an alias for the\n * canonical {@link ColorLevel} — the `ActiveColorLevel` name is retained for\n * backwards compat with consumers.\n */\nexport type ActiveColorLevel = import(\"@silvery/ansi\").ColorLevel\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() === \"mono\") 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. Precedence:\n // 1. `underlineStyle` (deprecated, explicit)\n // 2. `underline: \"curly\" | \"double\" | ...` (string form of the unified prop)\n // 3. `underline: true` → \"single\"\n // 4. `underline: false` / undefined → no underline\n let underlineStyle: UnderlineStyle | undefined\n if (props.underlineStyle !== undefined) {\n underlineStyle = props.underlineStyle\n } else if (typeof props.underline === \"string\") {\n underlineStyle = props.underline\n } else if (props.underline === true) {\n underlineStyle = \"single\"\n }\n\n // Start with the user-specified attrs.\n let bold = props.bold\n let dim: boolean | undefined\n let italic = props.italic\n let underline = !!props.underline || !!underlineStyle\n let strikethrough = props.strikethrough\n let inverse = props.inverse\n const overline = !!props.overline\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() === \"mono\") {\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 overline,\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 * SILVERY_STRICT — single canonical strictness gate.\n *\n * Contract: the `SILVERY_STRICT` env var is the only knob for runtime\n * verification. It accepts a comma-separated list where each entry is\n * either a numeric tier (1/2/3, ascending strictness) or a check slug\n * (e.g. \"canary\", \"residue\", \"incremental\"). Empty / \"0\" / unset is off.\n *\n * SILVERY_STRICT=1 // all canonical checks at tier 1\n * SILVERY_STRICT=2 // tier 1 + every-action checks\n * SILVERY_STRICT=canary // only the degenerate-frame canary\n * SILVERY_STRICT=residue,1 // tier 1 + explicit residue (redundant; same as 1)\n * SILVERY_STRICT=1,!canary // tier 1 minus the canary (per-check skip)\n *\n * The \"do NOT add new SILVERY_* enable env vars\" rule lives here as code:\n * every check threads through `isStrictEnabled(slug, minTier)`. New checks\n * pick a slug + a tier and inherit the umbrella behavior — `bun run\n * test:fast` (which sets `SILVERY_STRICT=1` by default) gets every new\n * check without anyone touching env config.\n */\n\nexport interface StrictModeQuery {\n /** Slug name for this check (e.g. \"canary\", \"residue\"). */\n slug: string\n /** Minimum numeric tier at which this check is implicitly enabled. */\n minTier?: number\n}\n\ninterface ParsedStrict {\n enabledTiers: ReadonlySet<number>\n enabledSlugs: ReadonlySet<string>\n disabledSlugs: ReadonlySet<string>\n raw: string | undefined\n}\n\nlet cachedRaw: string | undefined = undefined\nlet cachedParsed: ParsedStrict | null = null\n\nfunction parseStrict(raw: string | undefined): ParsedStrict {\n if (!raw || raw === \"0\") {\n return {\n enabledTiers: new Set(),\n enabledSlugs: new Set(),\n disabledSlugs: new Set(),\n raw,\n }\n }\n const tiers = new Set<number>()\n const slugs = new Set<string>()\n const disabled = new Set<string>()\n for (const part of raw.split(\",\")) {\n const trimmed = part.trim()\n if (!trimmed) continue\n if (trimmed.startsWith(\"!\")) {\n disabled.add(trimmed.slice(1))\n continue\n }\n const n = Number(trimmed)\n if (Number.isFinite(n) && n >= 1) {\n tiers.add(n)\n continue\n }\n slugs.add(trimmed)\n }\n return { enabledTiers: tiers, enabledSlugs: slugs, disabledSlugs: disabled, raw }\n}\n\nfunction getParsed(): ParsedStrict {\n const raw = process.env.SILVERY_STRICT\n if (raw !== cachedRaw || cachedParsed === null) {\n cachedRaw = raw\n cachedParsed = parseStrict(raw)\n }\n return cachedParsed\n}\n\n/**\n * Returns true if `slug` should fire under the current `SILVERY_STRICT`.\n *\n * - `!slug` always wins (per-check skip)\n * - explicit slug match wins\n * - any tier >= minTier (default 1) implies the check\n */\nexport function isStrictEnabled(slug: string, minTier: number = 1): boolean {\n const p = getParsed()\n if (p.disabledSlugs.has(slug)) return false\n if (p.enabledSlugs.has(slug)) return true\n for (const tier of p.enabledTiers) {\n if (tier >= minTier) return true\n }\n return false\n}\n\n/** True if SILVERY_STRICT is set to anything that turns checking on. */\nexport function isStrictAnyEnabled(): boolean {\n const p = getParsed()\n return p.enabledTiers.size > 0 || p.enabledSlugs.size > 0\n}\n\n/** Reset the cache — for tests that mutate process.env mid-run. */\nexport function resetStrictCache(): void {\n cachedRaw = undefined\n cachedParsed = null\n}\n","/**\n * STRICT bordered-rect-clip — assert every painted cell stays inside the\n * nearest bordered ancestor's inner content rect.\n *\n * Bead: @km/silvery/cell-outside-rect-strict-check (P2, 2026-05-08).\n *\n * The pattern this catches: a `<Box borderStyle=\"round\">` containing a\n * `<Text>` whose content overflows the box's inner content area. Without\n * `overflow=\"hidden\"` on the box (and without `wrap=\"truncate\"` on the\n * text), the text engine paints cells past the box's inner rect, blowing\n * through the border or bleeding into siblings. The bug shape is silent:\n * `expect(buffer.text).toContain(\"...\")` may still pass — the offending\n * cells just sit at unexpected positions.\n *\n * Mechanic in detail:\n *\n * 1. Push the current rendering AgNode onto a module-level stack right\n * before `renderOwnContent` dispatches to renderBox/renderText. Pop\n * after.\n * 2. The `BufferSink.emitSetCell` hook (the single canonical\n * single-cell paint site) reads the top of stack. Walk node.parent\n * until a node with `borderStyle != null` is found. That's the\n * bordered ancestor.\n * 3. Skip the assertion when the bordered ancestor has `overflow=\"hidden\"`\n * / `overflowX/overflowY=\"hidden\"` — `computeChildClipBounds`\n * already enforces the clip there (render-phase.ts:1826-1831).\n * Those nodes are safe by construction.\n * 4. Otherwise: assert (x, y) lies inside the inner rect:\n * innerLeft = boxRect.x + (borderLeft ? 1 : 0)\n * innerTop = boxRect.y + (borderTop ? 1 : 0)\n * innerRight = boxRect.x + W - (borderRight ? 1 : 0)\n * innerBottom = boxRect.y + H - (borderBottom ? 1 : 0)\n * Throws `BorderedRectClipError` on first violation.\n *\n * Per-call memoization: every emitSetCell during a single `renderText` /\n * `renderBox` call shares the same rendering node, so the bordered-ancestor\n * walk is cached against that node identity. Cost amortizes to O(depth)\n * per renderer dispatch, not O(depth × cells).\n *\n * Tier 2 (paranoid). `SILVERY_STRICT=2` enables, `SILVERY_STRICT=1` does\n * NOT (would be too noisy if any pre-existing offender slipped through —\n * see test:fast smoke at the bottom of this file).\n *\n * Why a module-level stack instead of threading the node into the sink:\n * every renderer (renderBox, renderText, decoration helpers) constructs\n * its own local sink via `createFrameSink(buffer)`. Threading would touch\n * 50+ call sites. The stack pattern mirrors the already-shipped\n * `_frameCapturePlanSink` in render-sink.ts — same shape, same lifetime\n * discipline (push around the dispatch, pop in `finally`).\n */\n\nimport type { AgNode, BoxProps, Rect } from \"@silvery/ag/types\"\nimport { isStrictEnabled } from \"./strict-mode.js\"\n\n/** SILVERY_STRICT slug for the bordered-rect clip check. Tier 2 by design. */\nexport const BORDERED_RECT_CLIP_SLUG = \"bordered-rect-clip\"\nexport const BORDERED_RECT_CLIP_MIN_TIER = 2\n\n/** Returns true when the bordered-rect clip check should fire. */\nexport function isBorderedRectClipEnabled(): boolean {\n return isStrictEnabled(BORDERED_RECT_CLIP_SLUG, BORDERED_RECT_CLIP_MIN_TIER)\n}\n\n/**\n * Error thrown when a cell is painted outside the nearest bordered\n * ancestor's inner content rect under SILVERY_STRICT=bordered-rect-clip.\n *\n * Like `IncrementalRenderMismatchError`, this should NOT be caught by\n * general error handlers — it indicates a real layout/clip bug that\n * needs a structural fix (`overflow=\"hidden\"` on the bordered Box, or\n * `wrap=\"truncate\"` on the inner Text).\n */\nexport class BorderedRectClipError extends Error {\n constructor(message: string) {\n super(message)\n this.name = \"BorderedRectClipError\"\n }\n}\n\n// ---------------------------------------------------------------------------\n// Rendering-node stack\n// ---------------------------------------------------------------------------\n\nconst renderingStack: AgNode[] = []\n\n/** Push the node currently being painted onto the rendering stack. */\nexport function pushRenderingNode(node: AgNode): void {\n renderingStack.push(node)\n}\n\n/** Pop the most recently pushed rendering node. */\nexport function popRenderingNode(): void {\n renderingStack.pop()\n}\n\n/** Peek the topmost rendering node, or null if the stack is empty. */\nexport function peekRenderingNode(): AgNode | null {\n return renderingStack.length === 0 ? null : (renderingStack[renderingStack.length - 1] ?? null)\n}\n\n// ---------------------------------------------------------------------------\n// Bordered-ancestor helper\n// ---------------------------------------------------------------------------\n\n/**\n * Walk `node.parent` until we find a node with `borderStyle != null`.\n * Returns null when no ancestor is bordered (the outer canvas itself\n * has no border to honor).\n *\n * Excludes the starting node — a bordered Box's own border cells sit on\n * its perimeter (outside its OWN inner rect). The relevant constraint is\n * \"stay inside the parent that borders me\", not \"stay inside myself\".\n */\nexport function findBorderedAncestor(node: AgNode): AgNode | null {\n let current: AgNode | null = node.parent\n while (current) {\n const props = current.props as BoxProps | undefined\n if (props && props.borderStyle != null) return current\n current = current.parent\n }\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Per-renderer-call memoization\n// ---------------------------------------------------------------------------\n\n/**\n * Bordered-ancestor lookup result, cached against the topmost rendering\n * node identity. The renderer dispatches one `renderText` / `renderBox`\n * call per AgNode, and inside that call every emitSetCell shares the\n * same currently-rendering node. So a single-slot cache amortizes the\n * O(depth) walk to once per dispatch, not per cell.\n *\n * `cachedFor === null` means \"no entry\"; `cachedAncestor === null`\n * means \"no bordered ancestor exists\" (legitimate — outer-canvas paint).\n */\nlet cachedFor: AgNode | null = null\nlet cachedAncestor: AgNode | null = null\nlet cachedSkip: boolean = false\n\nfunction getBorderedAncestorCached(node: AgNode): {\n ancestor: AgNode | null\n skip: boolean\n} {\n if (cachedFor === node) return { ancestor: cachedAncestor, skip: cachedSkip }\n cachedFor = node\n cachedAncestor = findBorderedAncestor(node)\n cachedSkip = cachedAncestor != null && hasOverflowHidden(cachedAncestor)\n return { ancestor: cachedAncestor, skip: cachedSkip }\n}\n\nfunction hasOverflowHidden(node: AgNode): boolean {\n const p = node.props as BoxProps | undefined\n if (!p) return false\n if (p.overflow === \"hidden\") return true\n if (p.overflowX === \"hidden\" && p.overflowY === \"hidden\") return true\n return false\n}\n\n/** Reset the per-render cache. Called at the boundary of each rendering\n * push/pop so a stale identity from a previous frame never leaks. */\nexport function resetBorderedRectClipCache(): void {\n cachedFor = null\n cachedAncestor = null\n cachedSkip = false\n}\n\n// ---------------------------------------------------------------------------\n// Inner-rect math\n// ---------------------------------------------------------------------------\n\ninterface InnerRect {\n left: number\n top: number\n right: number // exclusive\n bottom: number // exclusive\n}\n\nfunction computeInnerRect(box: Rect, props: BoxProps): InnerRect {\n // Mirrors getBorderSize in pipeline/helpers.ts: each side contributes 1\n // cell when the side is not explicitly disabled. We're not in pixel\n // mode here (line-height check is for canvas/DOM targets); the strict\n // check fires in terminal renders only.\n const top = props.borderTop !== false ? 1 : 0\n const bottom = props.borderBottom !== false ? 1 : 0\n const left = props.borderLeft !== false ? 1 : 0\n const right = props.borderRight !== false ? 1 : 0\n return {\n left: box.x + left,\n top: box.y + top,\n right: box.x + box.width - right,\n bottom: box.y + box.height - bottom,\n }\n}\n\n// ---------------------------------------------------------------------------\n// The assertion\n// ---------------------------------------------------------------------------\n\ninterface AssertCellOpts {\n char?: string\n bg?: unknown\n fg?: unknown\n}\n\n/**\n * Assert that cell (x, y) lies inside the nearest bordered ancestor's\n * inner content rect. No-op when:\n * - the strict slug is not enabled\n * - no rendering node is on the stack (paint outside a renderer\n * dispatch — e.g. early init clear)\n * - no bordered ancestor exists\n * - the bordered ancestor has overflow=\"hidden\" (clip enforced\n * elsewhere by computeChildClipBounds)\n *\n * Throws `BorderedRectClipError` on violation.\n */\nexport function assertCellInsideBorderedRect(\n x: number,\n y: number,\n opts: AssertCellOpts = {},\n): void {\n if (!isBorderedRectClipEnabled()) return\n const node = peekRenderingNode()\n if (!node) return\n const { ancestor, skip } = getBorderedAncestorCached(node)\n if (!ancestor || skip) return\n // Use screenRect (post-scroll, post-sticky) — NOT boxRect (content-space).\n // The painted (x, y) is in screen coordinates: a node inside an\n // overflow=\"scroll\" container can have boxRect.y far below the\n // viewport while its screenRect.y sits in the visible region. Using\n // boxRect here would wrongly flag every scrolled child paint.\n // Falls back to scrollRect (== screenRect for non-sticky), then\n // boxRect (early-frame fallback) — but the fallback shouldn't fire\n // in well-formed renders.\n const box = ancestor.screenRect ?? ancestor.scrollRect ?? ancestor.boxRect\n if (!box) return\n const props = ancestor.props as BoxProps\n const inner = computeInnerRect(box, props)\n if (x >= inner.left && x < inner.right && y >= inner.top && y < inner.bottom) return\n\n // Out of bounds — build the diagnostic.\n const ancestorId = describeNode(ancestor)\n const nodeId = describeNode(node)\n const path = describeDepthPath(node)\n const charDesc = opts.char ? `'${opts.char}'` : \"(unknown)\"\n const bgDesc = formatColor(opts.bg)\n const fgDesc = formatColor(opts.fg)\n const msg =\n `STRICT bordered-rect-clip: cell painted outside ancestor's bordered rect\\n` +\n ` cell: (x=${x}, y=${y}) char=${charDesc} bg=${bgDesc} fg=${fgDesc}\\n` +\n ` parent box: id='${ancestorId}' rect=(${box.x},${box.y},${box.width},${box.height}) borderStyle='${props.borderStyle}'\\n` +\n ` inner rect: (left=${inner.left}, top=${inner.top}, right=${inner.right}, bottom=${inner.bottom}) [exclusive]\\n` +\n ` painter: '${nodeId}'\\n` +\n ` depth path: ${path}\\n` +\n `\\n` +\n ` Suggested fix: set \\`overflow=\"hidden\"\\` on the bordered Box,\\n` +\n ` or use \\`wrap=\"wrap\"\\` / \\`wrap=\"truncate\"\\` on the inner Text.\\n` +\n `\\n` +\n ` Slug: SILVERY_STRICT=${BORDERED_RECT_CLIP_SLUG} (tier ${BORDERED_RECT_CLIP_MIN_TIER}+).\\n` +\n ` Per-test opt-out: SILVERY_STRICT=2,!${BORDERED_RECT_CLIP_SLUG}.`\n throw new BorderedRectClipError(msg)\n}\n\nfunction describeNode(node: AgNode): string {\n const props = node.props as { key?: string | number } | undefined\n const key = props?.key\n if (key !== undefined) return `${node.type}[key=${String(key)}]`\n if (node.type === \"silvery-text\" && typeof node.textContent === \"string\") {\n const t = node.textContent.length > 20 ? `${node.textContent.slice(0, 20)}…` : node.textContent\n return `${node.type}[${JSON.stringify(t)}]`\n }\n return `${node.type}`\n}\n\nfunction describeDepthPath(node: AgNode): string {\n const parts: string[] = []\n let cur: AgNode | null = node\n let depth = 0\n while (cur && depth < 8) {\n const props = cur.props as BoxProps | undefined\n let label = cur.type\n if (props?.borderStyle) label += `[border=${props.borderStyle}]`\n parts.unshift(label)\n cur = cur.parent\n depth++\n }\n if (cur) parts.unshift(\"…\")\n return parts.join(\" > \")\n}\n\nfunction formatColor(c: unknown): string {\n if (c === null || c === undefined) return \"default\"\n if (typeof c === \"number\") return `${c}`\n if (typeof c === \"object\") {\n const o = c as { r: number; g: number; b: number }\n if (typeof o.r === \"number\") {\n const hex = ((o.r << 16) | (o.g << 8) | o.b).toString(16).padStart(6, \"0\")\n return `#${hex}`\n }\n }\n return String(c)\n}\n","/**\n * RenderSink — Phase 2 Step 3 of paint-clear-invariant recast\n * (km-silvery.paint-clear-invariant). Per pro/Kimi K2.6 review\n * (2026-04-27), the renderer must mark intent at emission time so the\n * commit step knows clear from paint without a heuristic. The sink\n * interface is the seam.\n *\n * Two adapters land in this file:\n *\n * - `BufferSink`: forwards every emit to the corresponding\n * `TerminalBuffer` mutator (e.g. `emitClear → buffer.fill(...,{char,\n * bg})`). Behaviour-equivalent to direct buffer mutation; used by the\n * legacy path during the Phase 2 transition.\n * - `PlanSink`: builds a `SectionedRenderPlan` directly. Routes ops\n * into `transferOps / cleanupOps / paintOps / overlayOps /\n * postStateOps` based on the method called. No heuristics.\n *\n * Migration: renderers currently call `buffer.fill(...)` /\n * `buffer.setCell(...)` directly. Phase 2 Step 3 replaces those calls\n * with sink calls. The `BufferSink` is functionally identical to direct\n * buffer mutation, so a single-call migration of one renderer (e.g.\n * `renderBox`) can land WITHOUT changing default behaviour. Once every\n * call site routes through a sink, the SILVERY_RENDER_PLAN flag swaps\n * the sink (BufferSink → PlanSink) at one entry point in `ag.ts` and\n * the production wire-through closes the Check 1 derisking gap from\n * Phase 1.\n *\n * NOT yet covered (REMAINING for follow-up sessions):\n * - Renderer migration: every `buffer.fill` / `buffer.setCell` site in\n * render-box.ts / render-text.ts / render-phase.ts /\n * decoration-phase.ts must route through the sink. Estimated 50-100\n * call sites. Each call site requires intent review (clear vs\n * paint vs transfer).\n * - Intra-frame buffer reads (`getCellBg`, dirty rows) are unaffected\n * by the sink — the renderer still reads from the working buffer.\n * The PlanSink papers over this by also writing to a backing buffer\n * for reads. Phase 2 Step 6 audits and eliminates these reads.\n *\n * COMPLETED IN STEP 5 (paint-clear-l5-step5-outline-snapshots):\n * - Outline snapshots no longer live on `TerminalBuffer`. They moved to\n * `RenderPostState` (see render-post-state.ts), owned by `createAg`\n * and threaded through `renderPhase(root, prev, ctx, postState)`.\n * The `setOutlineSnapshots` sink method survives so the plan-shape\n * can capture outline-snapshot intent for parity tests, but the\n * BufferSink implementation is now a no-op — the buffer no longer\n * carries cross-frame state.\n */\n\nimport type { CellAttrs, CellPatch, Color, Style } from \"../buffer\"\nimport { TerminalBuffer } from \"../buffer\"\nimport { assertCellInsideBorderedRect } from \"../strict-bordered-rect.js\"\nimport type {\n ClearOp,\n OverlayOp,\n PaintOp,\n PostStateOp,\n SelectableCellPatch,\n SectionedRenderPlan,\n TransferOp,\n} from \"./render-plan\"\n\nfunction cloneCellPatch(cell: SelectableCellPatch): SelectableCellPatch {\n const out: SelectableCellPatch = { ...cell }\n if (cell.attrs) out.attrs = { ...cell.attrs }\n return out\n}\n\nfunction cloneCell(cell: CellPatch): CellPatch {\n return { ...cell, attrs: { ...cell.attrs } }\n}\n\nfunction cloneStyle(style: Style): Style {\n return { ...style, attrs: { ...style.attrs } }\n}\n\n/**\n * The renderer's emission target. Each method classifies the intent of\n * the operation; the implementation decides what to do with it\n * (mutate a buffer, build a plan, both).\n *\n * Method names mirror the section the op lands in:\n * - emitTransfer* → transferOps\n * - emitClear* → cleanupOps\n * - emitPaint* → paintOps\n * - emitOverlay* → overlayOps\n * - setPostState* → postStateOps\n *\n * Phase 2 Step 6.1: the sink also carries the frame's `width` / `height`\n * so renderers don't need to read those off `buffer.width` /\n * `buffer.height`. This eliminates ~30 universal read sites and is the\n * prerequisite for `BackedPlanSink` (a sink that stands alone — no\n * BufferSink fallback for reads).\n */\nexport interface RenderSink {\n /** Frame width in cells. Replaces `buffer.width` reads in renderers. */\n readonly width: number\n /** Frame height in cells. Replaces `buffer.height` reads in renderers. */\n readonly height: number\n\n // -- transfer ops ---------------------------------------------------------\n\n /**\n * Shift existing prev-frame pixels by `delta` rows (negative = up,\n * positive = down). Used by scroll Tier 1 buffer-shift. Runs FIRST so\n * subsequent paints into the shifted region land on the right cells.\n */\n emitScrollRegion(\n x: number,\n y: number,\n width: number,\n height: number,\n delta: number,\n clearCell?: CellPatch,\n selectable?: boolean,\n ): void\n\n // -- cleanup ops ----------------------------------------------------------\n\n /**\n * Remove stale prev-frame pixels in `rect`, writing space chars with\n * the inherited bg `bg`. Destructive; lives in `cleanupOps`. Used by\n * `clearNodeRegion`, `clearExcessArea`, `clearDescendantOverflowRegions`,\n * scroll viewport clears.\n */\n emitClearRect(\n x: number,\n y: number,\n width: number,\n height: number,\n bg: Color,\n selectable?: boolean,\n ): void\n\n /**\n * Generalized clear with an explicit cell shape. Kept as an escape\n * hatch for clear sites that need attrs/fg too (rare; most clears use\n * `emitClearRect`). Lives in `cleanupOps`.\n */\n emitClearCells(\n x: number,\n y: number,\n width: number,\n height: number,\n cell: CellPatch,\n selectable?: boolean,\n ): void\n\n // -- paint ops ------------------------------------------------------------\n\n /**\n * Paint a single cell. Lives in `paintOps`. Used by text rendering and\n * border drawing.\n */\n emitSetCell(x: number, y: number, cell: CellPatch, selectable?: boolean): void\n\n /**\n * Paint an opaque bg fill. The cell payload typically has `bg` set and\n * `char` defaults to space; this is renderBox's effective-bg paint.\n * Lives in `paintOps`. Distinct from `emitClearCells` (same buffer\n * mutation, different intent).\n */\n emitPaintFill(\n x: number,\n y: number,\n width: number,\n height: number,\n cell: CellPatch,\n selectable?: boolean,\n ): void\n\n /**\n * Paint just the bg without disturbing existing chars (cell-style\n * paint). Used by the bg-only fast path where children's content from\n * the prev clone must be preserved. Lives in `paintOps`.\n */\n emitFillBg(x: number, y: number, width: number, height: number, bg: Color): void\n\n /**\n * Restyle existing cells in `rect` (fg / attrs only — bg via fillBg).\n * Used by the text style-only fast path. Lives in `paintOps`.\n */\n emitRestyleRegion(x: number, y: number, width: number, height: number, style: Style): void\n\n // -- overlay ops ----------------------------------------------------------\n\n /**\n * Merge attrs (bold / underline / strikethrough / etc.) into existing\n * cells in `rect`. Lives in `overlayOps` — applied AFTER paint so the\n * cells exist to be merged into.\n */\n emitMergeAttrs(\n x: number,\n y: number,\n width: number,\n height: number,\n attrs: CellAttrs,\n underlineColor?: Color,\n ): void\n\n // -- post state -----------------------------------------------------------\n\n /**\n * Row metadata (soft-wrap, last content col). Lives in `postStateOps`.\n */\n setRowMeta(row: number, meta: { softWrapped?: boolean; lastContentCol?: number }): void\n\n /**\n * Outline snapshots — cross-frame state captured during the decoration\n * pass so the next frame can restore the under-cells before drawing the\n * new outlines. Lives in `postStateOps` for plan-shape capture.\n *\n * Step 5 of paint-clear-invariant L5 retired the buffer-side storage:\n * the authoritative snapshot list now lives on `RenderPostState` (see\n * render-post-state.ts), owned by `createAg` and mutated directly by\n * `renderDecorationPass` / `clearPreviousOutlines`. This sink method is\n * retained for plan capture only — `BufferSink.setOutlineSnapshots` is\n * a no-op, `PlanSink.setOutlineSnapshots` records the op for parity\n * tests, and the parity replay path treats the op as informational\n * (the parity buffer is throwaway, so no carrier mutation is needed).\n */\n setOutlineSnapshots(snapshots: ReadonlyArray<{ x: number; y: number; cell: CellPatch }>): void\n}\n\n/**\n * `RenderSink` implementation that mutates a `TerminalBuffer` directly.\n * Behavior-equivalent to the legacy direct-mutation path. Used during\n * the Phase 2 transition: renderers route through the sink, but with\n * `BufferSink` the actual buffer mutation is unchanged.\n *\n * Once every renderer call site routes through a sink, swapping\n * `BufferSink` for `PlanSink` at one entry point flips the renderer\n * onto the plan/commit substrate.\n */\nexport class BufferSink implements RenderSink {\n constructor(private readonly buffer: TerminalBuffer) {}\n\n private selectableCell(cell: CellPatch, selectable: boolean | undefined): CellPatch {\n return selectable === undefined ? cell : { ...cell, selectable }\n }\n\n get width(): number {\n return this.buffer.width\n }\n get height(): number {\n return this.buffer.height\n }\n\n emitScrollRegion(\n x: number,\n y: number,\n width: number,\n height: number,\n delta: number,\n clearCell: CellPatch = {},\n selectable?: boolean,\n ): void {\n this.buffer.scrollRegion(x, y, width, height, delta, this.selectableCell(clearCell, selectable))\n }\n\n emitClearRect(\n x: number,\n y: number,\n width: number,\n height: number,\n bg: Color,\n selectable?: boolean,\n ): void {\n this.buffer.fill(x, y, width, height, { char: \" \", bg, selectable })\n }\n\n emitClearCells(\n x: number,\n y: number,\n width: number,\n height: number,\n cell: CellPatch,\n selectable?: boolean,\n ): void {\n this.buffer.fill(x, y, width, height, this.selectableCell(cell, selectable))\n }\n\n emitSetCell(x: number, y: number, cell: CellPatch, selectable?: boolean): void {\n // STRICT bordered-rect-clip (tier 2): assert this single-cell paint\n // stays inside the nearest bordered ancestor's inner content rect.\n // No-op when SILVERY_STRICT does not include the slug; cheap O(1)\n // gate read otherwise. See strict-bordered-rect.ts for the full\n // contract and the per-renderer-call memoization.\n assertCellInsideBorderedRect(x, y, { char: cell.char, fg: cell.fg, bg: cell.bg })\n this.buffer.setCell(x, y, this.selectableCell(cell, selectable))\n }\n\n emitPaintFill(\n x: number,\n y: number,\n width: number,\n height: number,\n cell: CellPatch,\n selectable?: boolean,\n ): void {\n this.buffer.fill(x, y, width, height, this.selectableCell(cell, selectable))\n }\n\n emitFillBg(x: number, y: number, width: number, height: number, bg: Color): void {\n this.buffer.fillBg(x, y, width, height, bg)\n }\n\n emitRestyleRegion(x: number, y: number, width: number, height: number, style: Style): void {\n this.buffer.restyleRegion(x, y, width, height, style)\n }\n\n emitMergeAttrs(\n x: number,\n y: number,\n width: number,\n height: number,\n attrs: CellAttrs,\n underlineColor?: Color,\n ): void {\n this.buffer.mergeAttrsInRect(x, y, width, height, attrs, underlineColor)\n }\n\n setRowMeta(row: number, meta: { softWrapped?: boolean; lastContentCol?: number }): void {\n this.buffer.setRowMeta(row, meta)\n }\n\n setOutlineSnapshots(_snapshots: ReadonlyArray<{ x: number; y: number; cell: CellPatch }>): void {\n // Phase 2 Step 5 of paint-clear-invariant L5 retired\n // `buffer.outlineSnapshots`. Snapshots now live on the `RenderPostState`\n // carrier owned by `createAg` and are written directly by\n // `renderDecorationPass` / `clearPreviousOutlines` (see\n // decoration-phase.ts). The sink op is preserved in the API so the\n // PlanSink path can still capture outline-snapshot intent for parity\n // tests, but the BufferSink side is now a no-op — the buffer no longer\n // carries this state.\n }\n}\n\n/**\n * `RenderSink` that fans every emission out to a PRIMARY sink and a\n * SECONDARY sink. Used by the SILVERY_RENDER_PLAN production wiring:\n *\n * - primary = `BufferSink(buffer)`: keeps direct buffer mutation so\n * intra-frame buffer reads (`getCellBg`, dirty rows, snapshot reads)\n * return correct values during the render walk. Read-elimination is\n * the Phase 2 Step 6 follow-up.\n * - secondary = `PlanSink`: captures the same emissions into a\n * `SectionedRenderPlan` for parity verification or for committing\n * onto a fresh clone in plan-mode.\n *\n * The Tee fans every method to both sinks unconditionally. Since both\n * are in lock-step, the plan captures exactly the ops that were applied\n * to the buffer.\n */\nexport class TeeSink implements RenderSink {\n constructor(\n private readonly primary: RenderSink,\n private readonly secondary: RenderSink,\n ) {}\n\n get width(): number {\n return this.primary.width\n }\n get height(): number {\n return this.primary.height\n }\n\n emitScrollRegion(\n x: number,\n y: number,\n width: number,\n height: number,\n delta: number,\n clearCell?: CellPatch,\n selectable?: boolean,\n ): void {\n this.primary.emitScrollRegion(x, y, width, height, delta, clearCell, selectable)\n this.secondary.emitScrollRegion(x, y, width, height, delta, clearCell, selectable)\n }\n\n emitClearRect(\n x: number,\n y: number,\n width: number,\n height: number,\n bg: Color,\n selectable?: boolean,\n ): void {\n this.primary.emitClearRect(x, y, width, height, bg, selectable)\n this.secondary.emitClearRect(x, y, width, height, bg, selectable)\n }\n\n emitClearCells(\n x: number,\n y: number,\n width: number,\n height: number,\n cell: CellPatch,\n selectable?: boolean,\n ): void {\n this.primary.emitClearCells(x, y, width, height, cell, selectable)\n this.secondary.emitClearCells(x, y, width, height, cell, selectable)\n }\n\n emitSetCell(x: number, y: number, cell: CellPatch, selectable?: boolean): void {\n this.primary.emitSetCell(x, y, cell, selectable)\n this.secondary.emitSetCell(x, y, cell, selectable)\n }\n\n emitPaintFill(\n x: number,\n y: number,\n width: number,\n height: number,\n cell: CellPatch,\n selectable?: boolean,\n ): void {\n this.primary.emitPaintFill(x, y, width, height, cell, selectable)\n this.secondary.emitPaintFill(x, y, width, height, cell, selectable)\n }\n\n emitFillBg(x: number, y: number, width: number, height: number, bg: Color): void {\n this.primary.emitFillBg(x, y, width, height, bg)\n this.secondary.emitFillBg(x, y, width, height, bg)\n }\n\n emitRestyleRegion(x: number, y: number, width: number, height: number, style: Style): void {\n this.primary.emitRestyleRegion(x, y, width, height, style)\n this.secondary.emitRestyleRegion(x, y, width, height, style)\n }\n\n emitMergeAttrs(\n x: number,\n y: number,\n width: number,\n height: number,\n attrs: CellAttrs,\n underlineColor?: Color,\n ): void {\n this.primary.emitMergeAttrs(x, y, width, height, attrs, underlineColor)\n this.secondary.emitMergeAttrs(x, y, width, height, attrs, underlineColor)\n }\n\n setRowMeta(row: number, meta: { softWrapped?: boolean; lastContentCol?: number }): void {\n this.primary.setRowMeta(row, meta)\n this.secondary.setRowMeta(row, meta)\n }\n\n setOutlineSnapshots(snapshots: ReadonlyArray<{ x: number; y: number; cell: CellPatch }>): void {\n this.primary.setOutlineSnapshots(snapshots)\n this.secondary.setOutlineSnapshots(snapshots)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Frame-shared sink factory\n// ---------------------------------------------------------------------------\n\n/**\n * The PlanSink that the current frame's BufferSink instances should also\n * record to (when SILVERY_RENDER_PLAN is enabled). Set by `withPlanCapture`\n * around a render-phase call; null at all other times.\n *\n * Module-level state because every renderer in render-phase / render-box /\n * render-text / decoration-phase constructs its own local sink. The\n * factory below reads this so all of them emit to the same plan.\n */\nlet _frameCapturePlanSink: PlanSink | null = null\n\n/**\n * Run `fn` with frame-level plan capture enabled. Returns the plan\n * captured during `fn` along with whatever `fn` returned.\n *\n * Production wire path for SILVERY_RENDER_PLAN: ag.ts wraps the\n * `renderPhase()` call in `withPlanCapture(...)` and gets back both the\n * rendered buffer (already mutated by BufferSink-on-real-buffer) and the\n * matching `SectionedRenderPlan`. The plan can then be replayed onto a\n * fresh clone for parity verification, or used directly when buffer-read\n * elimination (Step 6) lets PlanSink stand alone.\n */\nexport function withPlanCapture<T>(\n width: number,\n height: number,\n fn: () => T,\n): { result: T; plan: SectionedRenderPlan } {\n const captured = new PlanSink(width, height)\n const prev = _frameCapturePlanSink\n _frameCapturePlanSink = captured\n try {\n const result = fn()\n return { result, plan: captured.toPlan() }\n } finally {\n _frameCapturePlanSink = prev\n }\n}\n\n/**\n * Construct the right sink for a renderer call site. By default\n * (`SILVERY_RENDER_PLAN` not set), returns a plain `BufferSink(buffer)`.\n *\n * When inside a `withPlanCapture` scope, returns a `TeeSink` that fans\n * every emission to BOTH the BufferSink AND the frame-shared PlanSink.\n * This is how every local sink construction in render-phase /\n * render-box / render-text / decoration-phase contributes to a single\n * SectionedRenderPlan for the frame.\n */\nexport function createFrameSink(buffer: TerminalBuffer): RenderSink {\n const direct = new BufferSink(buffer)\n if (_frameCapturePlanSink === null) return direct\n return new TeeSink(direct, _frameCapturePlanSink)\n}\n\n/**\n * `RenderSink` implementation that builds a `SectionedRenderPlan` directly\n * — no heuristics, no backing buffer for reads (yet — see file header\n * REMAINING). Each emit method routes the op into the corresponding\n * section.\n *\n * This is the L4 substrate: ops land in their section by API call, not\n * by classification of post-hoc mutations. Wrong-order paint becomes\n * unrepresentable because a clear API call cannot land in `paintOps` —\n * it goes through `emitClearRect` which writes to `cleanupOps`.\n *\n * Limitation: the sink has no buffer to back reads. Renderers that\n * currently read from the buffer mid-frame (`getCellBg`, dirty row\n * checks, outline snapshot reads) will not work with `PlanSink`\n * directly. Phase 2 Step 4 audits and eliminates those reads. Until\n * then, `PlanSink` is for synthetic plans + tests; production routing\n * waits on Step 4.\n */\nexport class PlanSink implements RenderSink {\n readonly transferOps: TransferOp[] = []\n readonly cleanupOps: ClearOp[] = []\n readonly paintOps: PaintOp[] = []\n readonly overlayOps: OverlayOp[] = []\n readonly postStateOps: PostStateOp[] = []\n\n constructor(\n readonly width: number,\n readonly height: number,\n ) {}\n\n emitScrollRegion(\n x: number,\n y: number,\n width: number,\n height: number,\n delta: number,\n clearCell?: CellPatch,\n selectable?: boolean,\n ): void {\n this.transferOps.push({\n kind: \"scrollRegion\",\n x,\n y,\n width,\n height,\n delta,\n clearCell: clearCell ? this.selectableCell(clearCell, selectable) : undefined,\n selectable,\n })\n }\n\n emitClearRect(\n x: number,\n y: number,\n width: number,\n height: number,\n bg: Color,\n selectable?: boolean,\n ): void {\n this.cleanupOps.push({ kind: \"clearRect\", x, y, width, height, bg, selectable })\n }\n\n emitClearCells(\n x: number,\n y: number,\n width: number,\n height: number,\n cell: CellPatch,\n selectable?: boolean,\n ): void {\n this.cleanupOps.push({\n kind: \"clearCells\",\n x,\n y,\n width,\n height,\n cell: this.selectableCell(cell, selectable),\n })\n }\n\n emitSetCell(x: number, y: number, cell: CellPatch, selectable?: boolean): void {\n this.paintOps.push({ kind: \"setCell\", x, y, cell: this.selectableCell(cell, selectable) })\n }\n\n emitPaintFill(\n x: number,\n y: number,\n width: number,\n height: number,\n cell: CellPatch,\n selectable?: boolean,\n ): void {\n this.paintOps.push({\n kind: \"paintFill\",\n x,\n y,\n width,\n height,\n cell: this.selectableCell(cell, selectable),\n })\n }\n\n emitFillBg(x: number, y: number, width: number, height: number, bg: Color): void {\n this.paintOps.push({ kind: \"fillBg\", x, y, width, height, bg })\n }\n\n emitRestyleRegion(x: number, y: number, width: number, height: number, style: Style): void {\n this.paintOps.push({ kind: \"restyleRegion\", x, y, width, height, style: cloneStyle(style) })\n }\n\n emitMergeAttrs(\n x: number,\n y: number,\n width: number,\n height: number,\n attrs: CellAttrs,\n underlineColor?: Color,\n ): void {\n this.overlayOps.push({\n kind: \"mergeAttrsInRect\",\n x,\n y,\n width,\n height,\n attrs: { ...attrs },\n underlineColor,\n })\n }\n\n setRowMeta(row: number, meta: { softWrapped?: boolean; lastContentCol?: number }): void {\n this.postStateOps.push({ kind: \"setRowMeta\", row, ...meta })\n }\n\n setOutlineSnapshots(snapshots: ReadonlyArray<{ x: number; y: number; cell: CellPatch }>): void {\n this.postStateOps.push({\n kind: \"setOutlineSnapshots\",\n snapshots: snapshots.map((snapshot) => ({ ...snapshot, cell: cloneCell(snapshot.cell) })),\n })\n }\n\n private selectableCell(cell: CellPatch, selectable: boolean | undefined): SelectableCellPatch {\n const cloned = cloneCellPatch(cell)\n return selectable === undefined ? cloned : { ...cloned, selectable }\n }\n\n /**\n * Snapshot the accumulated emissions as an immutable\n * `SectionedRenderPlan`. After calling this, further emissions on the\n * sink do not affect the returned plan.\n */\n toPlan(): SectionedRenderPlan {\n return {\n width: this.width,\n height: this.height,\n transferOps: this.transferOps.slice(),\n cleanupOps: this.cleanupOps.slice(),\n paintOps: this.paintOps.slice(),\n overlayOps: this.overlayOps.slice(),\n postStateOps: this.postStateOps.slice(),\n }\n }\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 { createFrameSink, type RenderSink } from \"./render-sink\"\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 overline?: boolean\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 if (style.overline) parts.push(\"53\")\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\n // Normalize unified `underline: boolean | UnderlineStyleName` on the child.\n // `underlineStyle` (deprecated) wins when both are set; otherwise string form\n // promotes to both underline=true AND underlineStyle=<name>.\n const childUl = childProps.underline\n const childUlStyle = (childProps as any).underlineStyle\n let childUnderline: boolean | undefined\n let childUnderlineStyle: string | false | undefined\n if (childUlStyle !== undefined) {\n childUnderline = childUlStyle === false ? false : true\n childUnderlineStyle = childUlStyle\n } else if (typeof childUl === \"string\") {\n childUnderline = true\n childUnderlineStyle = childUl\n } else if (childUl !== undefined) {\n childUnderline = childUl\n childUnderlineStyle = undefined\n }\n\n return {\n color: effectiveChildColor ?? parent.color,\n backgroundColor: childProps.backgroundColor ?? parent.backgroundColor,\n bold: childProps.bold ?? parent.bold,\n dim: parent.dim,\n italic: childProps.italic ?? parent.italic,\n underline: childUnderline ?? parent.underline,\n underlineStyle: childUnderlineStyle ?? parent.underlineStyle,\n underlineColor: (childProps as any).underlineColor ?? parent.underlineColor,\n overline: (childProps as any).overline ?? parent.overline,\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 maxCol?: number,\n minCol?: number,\n selectable = false,\n): void {\n if (bgSegments.length === 0) return\n const sink: RenderSink = createFrameSink(buffer)\n if (y < 0 || y >= sink.height) return\n\n // Clip horizontally to the parent's visible region. Without this, bg paint\n // walks the full natural-flow width of the line — which, when the Text node\n // is laid out wider than the visible parent (e.g. wrap=wrap inside an\n // overflow=hidden card narrower than the text's natural width), produces\n // bg cells past the parent's right border into the empty area beyond the\n // card. The chars themselves are clipped by renderGraphemes's rightEdge\n // check, leaving bg cells with empty content — the cyan-strip residue.\n // See bead km-silvery.render-light-blue-bg-strip-residue Round 11.\n const rightClip = maxCol !== undefined ? Math.min(maxCol, sink.width) : sink.width\n const leftClip = minCol !== undefined ? Math.max(minCol, 0) : 0\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 // Clip to the parent's visible region: skip cells past rightClip\n // or before leftClip. This keeps bg paint aligned with what\n // renderGraphemes actually writes — preventing bg leaks past the\n // visible boundary in overflow=hidden / clipped-text scenarios.\n if (col < rightClip && col >= leftClip) {\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 // Note: this is a paint (bg overlay on existing chars). The read\n // is an intra-frame buffer read that Phase 2 Step 6 will eliminate.\n buffer.readCellInto(col, y, bgCell)\n bgCell.bg = seg.bg\n sink.emitSetCell(col, y, bgCell, selectable)\n if (gWidth === 2 && col + 1 < sink.width && col + 1 < rightClip) {\n buffer.readCellInto(col + 1, y, bgCell)\n bgCell.bg = seg.bg\n sink.emitSetCell(col + 1, y, bgCell, selectable)\n }\n }\n }\n\n col += gWidth\n if (col - x >= relEnd) break\n if (col >= rightClip) 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 === \"wrap-truncate\" — body-text wrap that ellipsis-truncates the\n // offending line when an atomic token can't break (no soft-break\n // separator, would otherwise character-wrap). CSS-equivalent\n // `overflow-wrap: break-word` + `text-overflow: ellipsis`. Tracks\n // `@km/silvery/card-body-truncate-ellipsis`.\n if (wrap === \"wrap-truncate\") {\n if (ctx) return ctx.measurer.wrapText(normalizedText, width, true, false, true)\n return wrapText(normalizedText, width, true, true, true)\n }\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 selectable = false,\n): void {\n // Check if text contains ANSI escape sequences\n if (hasAnsi(text)) {\n renderAnsiTextLine(buffer, x, y, text, baseStyle, maxCol, inheritedBg, ctx, selectable)\n return\n }\n\n renderGraphemes(\n buffer,\n splitGraphemes(text),\n x,\n y,\n baseStyle,\n maxCol,\n inheritedBg,\n ctx,\n undefined,\n selectable,\n )\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 selectable = false,\n /**\n * Per-node bg-conflict policy from the owning Text node's `bgConflict`\n * prop. When set, it overrides the context / global `BgConflictMode`\n * for this line — used by `<Terminal>` to opt its external-ANSI cells\n * out of the throw.\n */\n bgConflictOverride?: BgConflictMode,\n): number {\n if (hasAnsi(text)) {\n return renderAnsiTextLineReturn(\n buffer,\n x,\n y,\n text,\n baseStyle,\n maxCol,\n inheritedBg,\n ctx,\n minCol,\n selectable,\n bgConflictOverride,\n )\n }\n return renderGraphemes(\n buffer,\n splitGraphemes(text),\n x,\n y,\n baseStyle,\n maxCol,\n inheritedBg,\n ctx,\n minCol,\n selectable,\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 selectable = false,\n): number {\n const sink: RenderSink = createFrameSink(buffer)\n let col = startCol\n // Effective right boundary: text node's layout edge or buffer edge\n const rightEdge = maxCol !== undefined ? Math.min(maxCol, sink.width) : sink.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) null.\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 // Phase 2 Step 6 / paint-clear-l5-final Step 1b: the legacy `getCellBg`\n // fallback is removed. All in-walk callers (renderText) thread inheritedBg\n // explicitly; external callers (scroll indicators in render-box.ts) pass\n // `style.bg` directly so they never reach the inheritedBg branch.\n const existingBg = style.bg !== null ? style.bg : inheritedBg !== undefined ? inheritedBg : null\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 sink.emitSetCell(\n col,\n y,\n {\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 false,\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 sink.emitSetCell(\n col,\n y,\n {\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 selectable,\n )\n\n if (width === 2 && col + 1 < sink.width) {\n const existingBg2 =\n style.bg !== null ? style.bg : inheritedBg !== undefined ? inheritedBg : null\n sink.emitSetCell(\n col + 1,\n y,\n {\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 selectable,\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 selectable = false,\n): void {\n renderAnsiTextLineReturn(\n buffer,\n x,\n y,\n text,\n baseStyle,\n maxCol,\n inheritedBg,\n ctx,\n undefined,\n selectable,\n )\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 selectable = false,\n /**\n * Per-node bg-conflict policy (the owning Text node's `bgConflict`\n * prop). Highest-precedence override: when set, it takes priority over\n * `ctx.bgConflictMode` and the module-global mode. `<Terminal>` passes\n * `\"ignore\"` here so its re-encoded external-ANSI cells are exempt from\n * the global throw, while real silvery-app pipeline bugs elsewhere\n * still throw.\n */\n bgConflictOverride?: BgConflictMode,\n): number {\n const sink: RenderSink = createFrameSink(buffer)\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) inherited bg from ancestor Box.\n // Skip if segment has bgOverride flag (explicit opt-out via ansi.bgOverride).\n // Phase 2 Step 6 / paint-clear-l5-final Step 1b: the diagnostic now uses\n // `inheritedBg` (threaded from the render walk) instead of `buffer.getCellBg`,\n // matching the rendered-output bg priority chain (style.bg → inheritedBg → null).\n // Precedence: per-node `bgConflict` prop → context mode → global mode.\n const effectiveBgConflictMode = bgConflictOverride ?? 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 ancestor Box).\n const existingBufBg = inheritedBg !== undefined ? inheritedBg : 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 selectable,\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 attrs.overline = overlayAttrs.overline || baseAttrs.overline\n } else {\n attrs.underline = overlayAttrs.underline ?? baseAttrs.underline\n attrs.underlineStyle = overlayAttrs.underlineStyle ?? baseAttrs.underlineStyle\n attrs.strikethrough = overlayAttrs.strikethrough ?? baseAttrs.strikethrough\n attrs.overline = overlayAttrs.overline ?? baseAttrs.overline\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 if (segment.strikethrough !== undefined) overlayAttrs.strikethrough = segment.strikethrough\n if (segment.overline !== undefined) overlayAttrs.overline = segment.overline\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 // Phase 2 Step 6.1: dimension reads route through sink instead of\n // buffer (buffer.width / buffer.height eliminated).\n const sink: RenderSink = createFrameSink(buffer)\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 // Keep enough overflow to prove the line needs ellipsizing. `width + 1`\n // works for ASCII, but wide graphemes can slice back to exactly `width`\n // before formatTextLines runs, making CJK overflow look like exact-fit\n // text. Terminal graphemes are at most two cells wide here, so +2 keeps\n // one extra visible grapheme without collecting unbounded hidden content.\n maxDisplayWidth = (width + 2) * 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 // Normalize unified `underline: boolean | UnderlineStyleName` prop.\n // Precedence matches getTextStyle / AdapterStyleContext:\n // 1. underlineStyle (deprecated) wins when set\n // 2. underline: \"string\" → both boolean + style name\n // 3. underline: true → boolean only (style name undefined)\n let rootUnderline: boolean | undefined\n let rootUnderlineStyle: string | false | undefined\n if (props.underlineStyle !== undefined) {\n rootUnderline = props.underlineStyle !== false\n rootUnderlineStyle = props.underlineStyle\n } else if (typeof props.underline === \"string\") {\n rootUnderline = true\n rootUnderlineStyle = props.underline\n } else if (props.underline !== undefined) {\n rootUnderline = props.underline\n }\n\n const rootContext: StyleContext = {\n color: props.color,\n backgroundColor: props.backgroundColor,\n bold: props.bold,\n italic: props.italic,\n underline: rootUnderline,\n underlineStyle: rootUnderlineStyle,\n underlineColor: props.underlineColor,\n overline: props.overline,\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 ? sink.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 lineTextForSelection = hasAnsi(line) ? stripAnsiForBg(line) : line\n const lineSelectable = nodeState.selectableMode && /\\S/.test(lineTextForSelection)\n const endCol = renderTextLineReturn(\n buffer,\n x,\n lineY,\n line,\n style,\n maxCol,\n inheritedBg,\n ctx,\n minCol,\n lineSelectable,\n props.bgConflict,\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 //\n // Phase 2 Step 4c: routes through sink.emitClearCells (intent: clear stale\n // pixels in the trailing cells of a shrunk text node — destructive).\n // Uses emitClearCells (not emitClearRect) because it preserves fg/attrs\n // shape; the existing logic writes a fully-shaped cell with explicit\n // attrs that emitClearRect's `{char:\" \", bg}` would normalize away.\n const clearStart = minCol !== undefined ? Math.max(endCol, minCol) : endCol\n if (clearStart < maxCol) {\n const clearBg = inheritedBg ?? null\n const clearCell = {\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 overline: false,\n inverse: false,\n strikethrough: false,\n blink: false,\n hidden: false,\n },\n wide: false,\n continuation: false,\n }\n for (let cx = clearStart; cx < maxCol && cx < sink.width; cx++) {\n sink.emitClearCells(cx, lineY, 1, 1, clearCell)\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 // Pass maxCol/minCol so bg paint clips to the same visible region as\n // renderGraphemes — without this, bg leaks past the parent's\n // overflow=hidden border into the empty area beyond the card (the\n // cyan-strip residue bug, km-silvery.render-light-blue-bg-strip-residue).\n if (bgSegments.length > 0 && lineIdx < lineOffsets.length) {\n const { start, end } = lineOffsets[lineIdx]!\n applyBgSegmentsToLine(\n buffer,\n x,\n lineY,\n line,\n start,\n end,\n bgSegments,\n ctx,\n maxCol,\n minCol,\n lineSelectable,\n )\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 { createFrameSink, type RenderSink } from \"./render-sink\"\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 // Phase 2 Step 4b: paint emissions route through a RenderSink so the\n // intent of each op is declared at the call site (paintFill vs fillBg).\n // BufferSink is behavior-equivalent to direct buffer mutation; once\n // every renderer routes through a sink, swapping in PlanSink at one\n // entry point will flip onto the plan/commit substrate.\n const sink: RenderSink = createFrameSink(buffer)\n\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 sink.emitFillBg(clippedX, clippedY, clippedWidth, clippedHeight, bg)\n } else {\n sink.emitPaintFill(clippedX, clippedY, clippedWidth, clippedHeight, { bg })\n }\n }\n } else {\n if (bgOnlyChange) {\n sink.emitFillBg(x, y, width, height, bg)\n } else {\n sink.emitPaintFill(x, y, width, height, { bg })\n }\n }\n }\n\n // Render border if set\n if (props.borderStyle) {\n renderBorder(buffer, sink, 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 sink: RenderSink,\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 < sink.height\n return row >= clipBounds.top && row < clipBounds.bottom && row < sink.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 < sink.width\n return col >= clipBounds.left && col < clipBounds.right && col < sink.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 sink.emitSetCell(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 < sink.width; col++) {\n if (isColVisible(col))\n sink.emitSetCell(col, y, { char: chars.horizontal, fg: color, bg: topBg })\n }\n if (showRight && x + width - 1 < sink.width && isColVisible(x + width - 1)) {\n sink.emitSetCell(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 sink.emitSetCell(x, row, { char: chars.vertical, fg: color, bg: leftBg })\n if (showRight && x + width - 1 < sink.width && isColVisible(x + width - 1)) {\n sink.emitSetCell(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 sink.emitSetCell(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 < sink.width; col++) {\n if (isColVisible(col))\n sink.emitSetCell(col, bottomY, { char: bottomHorizontal, fg: color, bg: bottomBg })\n }\n if (showRight && x + width - 1 < sink.width && isColVisible(x + width - 1)) {\n sink.emitSetCell(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 sink: RenderSink,\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 < sink.height\n return row >= clipBounds.top && row < clipBounds.bottom && row < sink.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 < sink.width\n return col >= clipBounds.left && col < clipBounds.right && col < sink.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 sink.emitSetCell(ox, oy, { char: chars.topLeft, fg: color, bg, attrs })\n for (let col = ox + 1; col < ox + ow - 1 && col < sink.width; col++) {\n if (isColVisible(col))\n sink.emitSetCell(col, oy, { char: chars.horizontal, fg: color, bg, attrs })\n }\n if (showRight && ox + ow - 1 < sink.width && isColVisible(ox + ow - 1)) {\n sink.emitSetCell(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 sink.emitSetCell(ox, row, { char: chars.vertical, fg: color, bg, attrs })\n if (showRight && ox + ow - 1 < sink.width && isColVisible(ox + ow - 1)) {\n sink.emitSetCell(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 sink.emitSetCell(ox, bottomY, { char: chars.bottomLeft, fg: color, bg, attrs })\n }\n for (let col = ox + 1; col < ox + ow - 1 && col < sink.width; col++) {\n if (isColVisible(col))\n sink.emitSetCell(col, bottomY, { char: outlineBottomHorizontal, fg: color, bg, attrs })\n }\n if (showRight && ox + ow - 1 < sink.width && isColVisible(ox + ow - 1)) {\n sink.emitSetCell(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 indicator on top border line\n const contentWidth = layout.width - border.left - border.right\n const x = layout.x + border.left\n const y = layout.y\n const maxCol = x + contentWidth\n renderCenteredIndicator(buffer, x, y, indicator, indicatorStyle, contentWidth, maxCol, ctx)\n } else if (showBorderless) {\n // Borderless: render centered inverse indicator on first content row\n const padding = getPadding(props)\n const contentWidth = layout.width - padding.left - padding.right\n const x = layout.x + padding.left\n const y = layout.y + padding.top\n const maxCol = x + contentWidth\n renderCenteredIndicator(buffer, x, y, indicator, indicatorStyle, contentWidth, maxCol, 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 indicator on bottom border line\n const contentWidth = layout.width - border.left - border.right\n const x = layout.x + border.left\n const y = layout.y + layout.height - 1\n const maxCol = x + contentWidth\n renderCenteredIndicator(buffer, x, y, indicator, indicatorStyle, contentWidth, maxCol, 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 x = layout.x + padding.left\n const y = layout.y + layout.height - padding.bottom - 1\n const maxCol = x + contentWidth\n renderCenteredIndicator(buffer, x, y, indicator, indicatorStyle, contentWidth, maxCol, ctx)\n }\n }\n}\n\nfunction renderCenteredIndicator(\n buffer: TerminalBuffer,\n x: number,\n y: number,\n indicator: string,\n style: Style,\n width: number,\n maxCol: number,\n ctx?: PipelineContext,\n): void {\n if (width <= 0) return\n const text = indicator.length > width ? indicator.slice(0, width) : indicator\n const indicatorX = x + Math.max(0, Math.floor((width - text.length) / 2))\n // Clear the whole indicator row first. The viewport window can replace an\n // item row with an overflow-indicator row after scrolling; without explicit\n // clears, incremental output leaves stale item glyphs around the centered\n // token. Keep the clears unstyled so fresh and incremental buffers agree on\n // the surrounding blank cells.\n renderTextLine(buffer, x, y, \" \".repeat(width), { fg: null, bg: null, attrs: {} }, maxCol, undefined, ctx)\n renderTextLine(buffer, indicatorX, y, text, style, Math.min(maxCol, indicatorX + text.length), undefined, ctx)\n}\n","/**\n * Runtime checks for `<Island>` strict slugs.\n *\n * The Island contract lives in `@silvery/ag`, but SILVERY_STRICT is a\n * terminal-target runtime gate. Keep the instrumentation here so core remains\n * target-agnostic while the terminal renderer can still enforce the contract.\n */\n\nimport { isStrictEnabled } from \"./strict-mode\"\nimport { graphemeWidth } from \"./unicode\"\nimport type { AgNode, Rect } from \"@silvery/ag/types\"\nimport type {\n IslandHandle,\n IslandInputOwner,\n IslandMouseEvent,\n IslandNodeState,\n} from \"@silvery/ag/island-types\"\nimport type { CellBuffer, ViewportRect } from \"@silvery/ag/viewport-types\"\n\n/** Tier-2 paint budget: one 256x256 island frame. */\nexport const ISLAND_PAINT_BUDGET_CELLS = 65_536\n\nconst instrumentedHandles = new WeakSet<IslandHandle>()\n\ntype SubscriptionOwner<T extends readonly unknown[]> = {\n subscribe(listener: (...args: T) => void): () => void\n}\n\n/**\n * Attach strict wrappers to a live island handle once.\n *\n * This is called from the render phase when the terminal target first sees a\n * handle. Wrapping at the target avoids pulling SILVERY_STRICT into `@silvery/ag`.\n */\nexport function ensureIslandStrictInstrumentation(node: AgNode): void {\n if (!needsIslandStrictInstrumentation()) return\n const state = node.islandState\n const handle = state?.handle\n if (!state || !handle || instrumentedHandles.has(handle)) return\n instrumentedHandles.add(handle)\n\n wrapWriteCells(handle)\n wrapSubscription(handle.output, state, \"output\")\n wrapSubscription(handle.size, state, \"size\")\n if (handle.modes) wrapSubscription(handle.modes, state, \"modes\")\n if (handle.input) wrapMouseInput(handle.input, handle)\n}\n\nfunction needsIslandStrictInstrumentation(): boolean {\n return (\n isStrictEnabled(\"island-paint-oob\", 2) ||\n isStrictEnabled(\"island-dispose-leak\", 2) ||\n isStrictEnabled(\"island-boundary-limits\", 2)\n )\n}\n\n/** Render-phase checks that do not depend on guest mutation APIs. */\nexport function assertIslandRenderInvariants(node: AgNode, layout: Rect): void {\n const state = node.islandState\n const handle = state?.handle\n if (!state || !handle) return\n\n const source = handle.output.buffer\n assertResizeRace(handle, source)\n assertPaintBudget(source, layout)\n assertGraphemeWidth(source)\n assertCursorBoundary(handle)\n assertNoDisposedRender(state)\n}\n\nfunction wrapWriteCells(handle: IslandHandle): void {\n const original = handle.output.writeCells.bind(handle.output)\n handle.output.writeCells = (dirtyRects, source) => {\n assertDirtyRectsInsideIsland(handle, dirtyRects, source)\n return original(dirtyRects, source)\n }\n}\n\nfunction wrapSubscription<T extends readonly unknown[]>(\n owner: SubscriptionOwner<T>,\n state: IslandNodeState,\n label: string,\n): void {\n const original = owner.subscribe.bind(owner)\n owner.subscribe = ((listener: (...args: T) => void) => {\n return original((...args: T) => {\n assertNoPostDisposeCallback(state, label)\n listener(...args)\n })\n }) as SubscriptionOwner<T>[\"subscribe\"]\n}\n\nfunction wrapMouseInput(input: IslandInputOwner, handle: IslandHandle): void {\n if (!input.onMouse) return\n const original = input.onMouse.bind(input)\n input.onMouse = (handler) => {\n return original((event) => {\n assertMouseInsideIsland(handle, event)\n handler(event)\n })\n }\n}\n\nfunction assertDirtyRectsInsideIsland(\n handle: IslandHandle,\n dirtyRects: readonly ViewportRect[],\n source: CellBuffer,\n): void {\n if (!isStrictEnabled(\"island-paint-oob\", 2)) return\n const cols = Math.min(handle.size.cols, source.cols)\n const rows = Math.min(handle.size.rows, source.rows)\n for (const rect of dirtyRects) {\n const c1 = rect.col + rect.width\n const r1 = rect.row + rect.height\n if (\n !Number.isInteger(rect.col) ||\n !Number.isInteger(rect.row) ||\n !Number.isInteger(rect.width) ||\n !Number.isInteger(rect.height) ||\n rect.col < 0 ||\n rect.row < 0 ||\n rect.width < 0 ||\n rect.height < 0 ||\n c1 > cols ||\n r1 > rows\n ) {\n throw new Error(\n `[SILVERY_STRICT=island-paint-oob] guest dirty rect ${formatRect(rect)} escapes island ` +\n `${cols}x${rows}`,\n )\n }\n }\n}\n\nfunction assertResizeRace(handle: IslandHandle, source: CellBuffer): void {\n if (!isStrictEnabled(\"island-resize-race\", 2)) return\n if (source.cols <= handle.size.cols && source.rows <= handle.size.rows) return\n throw new Error(\n `[SILVERY_STRICT=island-resize-race] guest output ${source.cols}x${source.rows} ` +\n `exceeds size-owner acknowledgement ${handle.size.cols}x${handle.size.rows}`,\n )\n}\n\nfunction assertPaintBudget(source: CellBuffer, layout: Rect): void {\n if (!isStrictEnabled(\"island-paint-budget\", 2)) return\n const paintedCols = Math.max(0, Math.min(layout.width, source.cols))\n const paintedRows = Math.max(0, Math.min(layout.height, source.rows))\n const cells = paintedCols * paintedRows\n if (cells <= ISLAND_PAINT_BUDGET_CELLS) return\n throw new Error(\n `[SILVERY_STRICT=island-paint-budget] island paints ${cells} cells in one frame ` +\n `(budget ${ISLAND_PAINT_BUDGET_CELLS})`,\n )\n}\n\nfunction assertGraphemeWidth(source: CellBuffer): void {\n if (!isStrictEnabled(\"island-grapheme-width\", 2)) return\n for (let row = 0; row < source.rows; row++) {\n for (let col = 0; col < source.cols; col++) {\n const cell = source.getCell(col, row)\n if (cell.continuation) {\n if (col === 0 || !source.getCell(col - 1, row).wide) {\n throw new Error(\n `[SILVERY_STRICT=island-grapheme-width] continuation cell at ${col},${row} ` +\n `has no leading wide cell`,\n )\n }\n continue\n }\n\n const width = graphemeWidth(cell.char)\n if (width === 2) {\n const next = col + 1 < source.cols ? source.getCell(col + 1, row) : null\n if (!cell.wide || !next?.continuation) {\n throw new Error(\n `[SILVERY_STRICT=island-grapheme-width] wide grapheme ${JSON.stringify(\n cell.char,\n )} at ${col},${row} is missing wide+continuation metadata`,\n )\n }\n continue\n }\n\n if (cell.wide || width > 2) {\n throw new Error(\n `[SILVERY_STRICT=island-grapheme-width] cell ${JSON.stringify(\n cell.char,\n )} at ${col},${row} declares wide=${String(cell.wide)} but measures width ${width}`,\n )\n }\n }\n }\n}\n\nfunction assertCursorBoundary(handle: IslandHandle): void {\n if (!isStrictEnabled(\"island-boundary-limits\", 2)) return\n const cursor = handle.output.cursor\n if (!handle.output.cursorVisible || !cursor) return\n if (\n cursor.row >= 0 &&\n cursor.row < handle.size.rows &&\n cursor.col >= 0 &&\n cursor.col < handle.size.cols\n ) {\n return\n }\n throw new Error(\n `[SILVERY_STRICT=island-boundary-limits] guest cursor ${cursor.col},${cursor.row} ` +\n `escapes island ${handle.size.cols}x${handle.size.rows}`,\n )\n}\n\nfunction assertMouseInsideIsland(handle: IslandHandle, event: IslandMouseEvent): void {\n if (!isStrictEnabled(\"island-boundary-limits\", 2)) return\n if (\n event.row >= 0 &&\n event.row < handle.size.rows &&\n event.col >= 0 &&\n event.col < handle.size.cols\n ) {\n return\n }\n throw new Error(\n `[SILVERY_STRICT=island-boundary-limits] mouse event ${event.col},${event.row} ` +\n `escapes island ${handle.size.cols}x${handle.size.rows}`,\n )\n}\n\nfunction assertNoPostDisposeCallback(state: IslandNodeState, label: string): void {\n if (!isStrictEnabled(\"island-dispose-leak\", 2)) return\n if (state.lifecycle !== \"disposed\" && !state.abortController.signal.aborted) return\n throw new Error(\n `[SILVERY_STRICT=island-dispose-leak] ${label} callback fired after island dispose`,\n )\n}\n\nfunction assertNoDisposedRender(state: IslandNodeState): void {\n if (!isStrictEnabled(\"island-dispose-leak\", 2)) return\n if (state.lifecycle !== \"disposed\") return\n throw new Error(\"[SILVERY_STRICT=island-dispose-leak] disposed island reached render phase\")\n}\n\nfunction formatRect(rect: ViewportRect): string {\n return `{col:${rect.col},row:${rect.row},width:${rect.width},height:${rect.height}}`\n}\n","/**\n * Render a `silvery-viewport` node into the parent {@link TerminalBuffer}.\n *\n * A viewport is an OPAQUE blit: the foreign cell domain owned by the viewport's\n * {@link ViewportNodeState} is copied 1:1 into the parent buffer at the node's\n * `boxRect`. The viewport does NOT participate in bg-coherence with the parent\n * — the bg-conflict throw in `render-text.ts` is structurally side-stepped\n * because viewport cells route through the render-sink directly, never through\n * {@link renderText}.\n *\n * IMPORTANT: writes go through {@link RenderSink.emitSetCell}, NOT\n * `buffer.setCell`. Under `SILVERY_RENDER_PLAN` (default ON) the silvery\n * pipeline captures sink emissions into a plan and commits them onto a\n * replay buffer — direct buffer mutations are silently dropped. Routing\n * through the sink keeps viewport cells in the plan so they survive\n * commitSectionedPlan.\n *\n * See {@link viewport-types.ts} in `@silvery/ag` and bead\n * `@km/silvery/15513-surface-nested-composition-primitive`.\n */\n\nimport type { TerminalBuffer, CellPatch, Color } from \"../buffer\"\nimport type { AgNode, Cell, Rect } from \"@silvery/ag/types\"\nimport type { RenderSink } from \"./render-sink\"\nimport { parseColor } from \"./render-helpers\"\nimport { assertIslandRenderInvariants, ensureIslandStrictInstrumentation } from \"../strict-island\"\n\n/**\n * Blit the foreign cell buffer at `node.viewportState.buffer` into `buffer`\n * (via `sink.emitSetCell`) at `layout` (the viewport's content rect in\n * absolute parent-buffer coordinates). Cells outside `buffer`'s bounds are\n * silently clipped — the Viewport rect's right/bottom may extend off-screen\n * and that's fine.\n */\nexport function renderViewport(\n node: AgNode,\n buffer: TerminalBuffer,\n sink: RenderSink,\n layout: Rect,\n scrollOffset: number,\n): void {\n const state = node.viewportState\n if (!state) return\n const src = state.buffer\n const baseX = layout.x\n const baseY = layout.y - scrollOffset\n\n // Clip blit region to the intersection of the viewport rect and the\n // foreign buffer's grid. We don't enlarge to the foreign buffer's\n // dimensions if the layout rect is smaller — the parent layout decides\n // visible bounds.\n const drawW = Math.min(layout.width, src.cols)\n const drawH = Math.min(layout.height, src.rows)\n\n for (let r = 0; r < drawH; r++) {\n const dstY = baseY + r\n if (dstY < 0 || dstY >= buffer.height) continue\n for (let c = 0; c < drawW; c++) {\n const dstX = baseX + c\n if (dstX < 0 || dstX >= buffer.width) continue\n const cell = src.getCell(c, r)\n sink.emitSetCell(dstX, dstY, viewportCellToPatch(cell))\n }\n }\n}\n\n/**\n * Convert a viewport {@link Cell} (string-colored, framework-agnostic shape\n * from `@silvery/ag/types`) to a {@link CellPatch} the parent\n * {@link TerminalBuffer} accepts (Color = `number | RGB | null`). String\n * colors are parsed once per cell — the upcoming xterm adapter writes\n * pre-resolved RGB strings, so parseColor's fast path runs.\n *\n * Reused by {@link renderIsland} — both viewport and island share the same\n * Cell shape from `@silvery/ag/types`. Islands pass an inherited background\n * so snapshot guests can leave cell.bg null and still sit on host chrome.\n */\nfunction viewportCellToPatch(cell: Cell, inheritedBg: Color = null): CellPatch {\n return {\n char: cell.char,\n fg: cell.fg === null ? null : (parseColor(cell.fg) as Color),\n bg: cell.bg === null ? inheritedBg : (parseColor(cell.bg) as Color),\n attrs: cell.attrs,\n wide: cell.wide,\n continuation: cell.continuation,\n }\n}\n\n/**\n * Blit a `silvery-island` node's guest cell buffer into the parent buffer.\n *\n * Sibling of {@link renderViewport}: reads from\n * `node.islandState.handle.output.buffer` (the guest's read-only output\n * surface from {@link IslandOutputOwner}) instead of\n * `node.viewportState.buffer`. Same routing — through {@link RenderSink}'s\n * `emitSetCell`, not direct buffer writes — so the cells survive\n * `commitSectionedPlan` under `SILVERY_RENDER_PLAN`.\n *\n * Bails when the host node has no `islandState` (factory still mounting) or\n * the guest's `init()` hasn't resolved yet (`handle === null`, lifecycle\n * `\"pending\"` / `\"errored\"` / `\"disposed\"`). In that case the parent's\n * `clearNodeRegion` (or inherited bg fill) has already painted blanks at\n * the island's rect, so we paint nothing and the host chrome shows through.\n *\n * Cursor handling: `IslandOutputOwner.cursor` is the guest's internal\n * cursor descriptor. v1 (Phase 1) does NOT render the guest cursor into the\n * host frame — the host cursor sits OUTSIDE the island, and the\n * `IslandModesOwner` contract un-applies the host cursor on focus blur to\n * the island. Phase 3 of `@km/silvery/15646-islands` wires the guest cursor\n * into the host's cursor signal (separate epic unit); until then, the\n * cursor field is read by the focus aggregator, not the blit.\n *\n * Clipping: same as viewport — out-of-bounds cells are silently dropped.\n * Both axes (right + bottom) clip; an island whose `cols×rows` overshoots\n * the parent buffer paints only its in-bounds intersection.\n *\n * See {@link island-types.ts} in `@silvery/ag` and bead\n * `@km/silvery/15646-islands`.\n */\nexport function renderIsland(\n node: AgNode,\n buffer: TerminalBuffer,\n sink: RenderSink,\n layout: Rect,\n scrollOffset: number,\n inheritedBg: Color = null,\n): void {\n const state = node.islandState\n if (!state) return\n const handle = state.handle\n // Deferred-hydrate or async-init islands have no handle until the\n // guest's `init()` resolves. STRICT slug `island-paint-oob` (tier 2)\n // would catch a guest writing past its rect; the null-handle bail is a\n // structural guard that runs at every paint regardless of STRICT.\n if (!handle) return\n ensureIslandStrictInstrumentation(node)\n assertIslandRenderInvariants(node, layout)\n const src = handle.output.buffer\n const baseX = layout.x\n const baseY = layout.y - scrollOffset\n\n // Clip blit region to the intersection of the island rect and the guest's\n // current cell grid. We don't enlarge to the guest's dimensions if the\n // layout rect is smaller — the parent layout decides visible bounds (the\n // two-phase resize protocol means the guest may temporarily lag the\n // host's reported cols/rows after a resize).\n const drawW = Math.min(layout.width, src.cols)\n const drawH = Math.min(layout.height, src.rows)\n\n for (let r = 0; r < drawH; r++) {\n const dstY = baseY + r\n if (dstY < 0 || dstY >= buffer.height) continue\n for (let c = 0; c < drawW; c++) {\n const dstX = baseX + c\n if (dstX < 0 || dstX >= buffer.width) continue\n const cell = src.getCell(c, r)\n sink.emitSetCell(dstX, dstY, viewportCellToPatch(cell, inheritedBg))\n }\n }\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 live on a `RenderPostState` carrier owned by `createAg`\n * (Phase 2 Step 5 of paint-clear-invariant L5; see render-post-state.ts).\n * They no longer travel with the TerminalBuffer — that decoupling lets the\n * decoration phase run against either the BufferSink-mutated buffer OR a\n * PlanSink-committed buffer interchangeably, which is the prerequisite for\n * Step 7 (PlanSink-authoritative output).\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, 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 { createFrameSink, type RenderSink } from \"./render-sink\"\nimport type { RenderPostState, OutlineCellSnapshot } from \"./render-post-state\"\nimport type { ClipBounds } from \"./types\"\n\nexport type { OutlineCellSnapshot } from \"./render-post-state\"\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 *\n * Phase 2 Step 5 (paint-clear-invariant L5): snapshots are read from the\n * `RenderPostState` carrier owned by `createAg`, NOT from the buffer. The\n * sink still receives a `setOutlineSnapshots([])` op so the plan-shape\n * captures the cleanup intent for parity tests; the authoritative state\n * lives in `postState.outlineSnapshots`.\n */\nexport function clearPreviousOutlines(buffer: TerminalBuffer, postState: RenderPostState): void {\n const snapshots = postState.outlineSnapshots\n if (snapshots.length === 0) return\n const sink: RenderSink = createFrameSink(buffer)\n for (const snap of snapshots) {\n sink.emitSetCell(snap.x, snap.y, snap.cell)\n }\n // Clear the post-state directly so the next phase (renderDecorationPass)\n // starts from an empty list. The sink op records the intent in the plan\n // for parity tests but does NOT mutate the carrier — `renderDecorationPass`\n // will overwrite postState.outlineSnapshots with the fresh snapshot list.\n postState.outlineSnapshots = []\n sink.setOutlineSnapshots([])\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 *\n * Phase 2 Step 5 (paint-clear-invariant L5): snapshots are written to the\n * `RenderPostState` carrier; the sink op is recorded in the plan for parity\n * tests only. After this call, `postState.outlineSnapshots` holds exactly\n * the cells that need restoring on the next frame.\n */\nexport function renderDecorationPass(\n buffer: TerminalBuffer,\n root: AgNode,\n postState: RenderPostState,\n): void {\n const snapshots: OutlineCellSnapshot[] = []\n const sink: RenderSink = createFrameSink(buffer)\n walk(root, buffer, sink, 0, undefined, { color: null }, snapshots)\n postState.outlineSnapshots = snapshots\n sink.setOutlineSnapshots(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 sink: RenderSink,\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 >= sink.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 sink.width,\n sink.height,\n )\n for (const pos of positions) {\n // Snapshot the cell BEFORE the outline overwrites it.\n snapshots.push({\n x: pos.x,\n y: pos.y,\n cell: {\n ...buffer.getCell(pos.x, pos.y),\n selectable: buffer.isCellSelectable(pos.x, pos.y),\n },\n })\n }\n renderOutline(\n buffer,\n sink,\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, sink, 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, sink, 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, sink, 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 bufferWidth: number,\n bufferHeight: number,\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 < bufferHeight\n return row >= clipBounds.top && row < clipBounds.bottom && row < bufferHeight\n }\n const isColVisible = (col: number): boolean => {\n if (clipBounds?.left === undefined || clipBounds.right === undefined)\n return col >= 0 && col < bufferWidth\n return col >= clipBounds.left && col < clipBounds.right && col < bufferWidth\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 < bufferWidth; col++) {\n if (isColVisible(col)) out.push({ x: col, y: oy })\n }\n if (showRight && ox + ow - 1 < bufferWidth && 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 < bufferWidth && 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 < bufferWidth; col++) {\n if (isColVisible(col)) out.push({ x: col, y: bottomY })\n }\n if (showRight && ox + ow - 1 < bufferWidth && 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 * Phase 2 Step 5 of paint-clear-invariant L5 (km-silvery.paint-clear-l5-bufferssink-retire).\n *\n * `RenderPostState` is the home for per-Ag, cross-frame state that survives\n * outside the `TerminalBuffer`. Historically `outlineSnapshots` lived on\n * `TerminalBuffer` and travelled forward via `buffer.clone()`. That coupling\n * forced the decoration phase to read intra-frame buffer state (an explicit\n * goal of the L5 plan to retire — see render-plan.ts:31). Hoisting snapshots\n * onto a dedicated post-state object decouples the decoration phase from any\n * particular buffer instance: it can run against the BufferSink-mutated buffer\n * OR the PlanSink-committed buffer interchangeably, with cleanup state owned\n * by the orchestrator (`createAg`) rather than the buffer.\n *\n * Steps 6 and 7 follow in subsequent sessions:\n * - Step 6: eliminate intra-frame buffer reads (`getCellBg`, dirty rows,\n * applyBgSegmentsToLine readCellInto in render-text.ts).\n * - Step 7: switch authoritative output to the PlanSink-committed buffer in\n * ag.ts and retire the BufferSink path entirely.\n *\n * The shape is intentionally minimal: one mutable field today\n * (`outlineSnapshots`). Future cross-frame state (scroll-shift book-keeping,\n * sticky decoration cache, etc.) can land here without re-plumbing the\n * decoration / render-phase / sink seam.\n */\n\nimport type { CellPatch } from \"../buffer\"\n\n/**\n * Snapshot of a single cell, used to restore it when the overlaying outline\n * is removed on the next frame. Mirrors `OutlineCellSnapshot` from\n * decoration-phase.ts; declared here to break the circular dependency between\n * decoration-phase and render-plan / sink modules.\n */\nexport interface OutlineCellSnapshot {\n x: number\n y: number\n cell: CellPatch\n}\n\n/**\n * Cross-frame state that travels with the orchestrator (`createAg`), not with\n * the buffer. The decoration phase reads/writes this object directly; the\n * sink layer captures parallel post-state ops only for plan-shape parity\n * verification (the parity replay is throwaway, so plan-side application of\n * `setOutlineSnapshots` is a no-op — see commitSectionedPlan).\n */\nexport interface RenderPostState {\n /**\n * Cells underlying outlines drawn on the previous frame. Read by\n * `clearPreviousOutlines` at the start of each render to restore\n * pre-outline pixels; rewritten by `renderDecorationPass` after content\n * rendering. Empty array on the first frame.\n */\n outlineSnapshots: OutlineCellSnapshot[]\n}\n\n/**\n * Construct an empty `RenderPostState`. Called once per `createAg` lifetime\n * (and on `resetBuffer` to mirror prevBuffer reset semantics).\n */\nexport function createRenderPostState(): RenderPostState {\n return {\n outlineSnapshots: [],\n }\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, Rect, TextProps } from \"@silvery/ag/types\"\nimport { getBorderSize, getPadding } from \"./helpers\"\nimport { renderBox, renderScrollIndicators, getEffectiveBg } from \"./render-box\"\nimport { renderIsland, renderViewport } from \"./render-viewport\"\nimport { clearPreviousOutlines, renderDecorationPass } from \"./decoration-phase\"\nimport { getTextStyle, parseColor } from \"./render-helpers\"\nimport { clearBgConflictWarnings, renderText, setBgConflictMode } from \"./render-text\"\nimport { createFrameSink, type RenderSink } from \"./render-sink\"\nimport {\n popRenderingNode,\n pushRenderingNode,\n resetBorderedRectClipCache,\n} from \"../strict-bordered-rect.js\"\nimport { type RenderPostState, createRenderPostState } from \"./render-post-state\"\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 {\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 * @param ctx Pipeline context (instrumentation, measurer)\n * @param postState Cross-frame post-state carrier (outline snapshots, etc.).\n * Owned by `createAg`; passed in here rather than read from the buffer so\n * the decoration phase can run against either a BufferSink-mutated buffer\n * or a PlanSink-committed buffer (Phase 2 Step 5 of paint-clear-invariant L5).\n * When omitted (legacy callers), a fresh empty post-state is used per call —\n * correct for fresh renders, but means outline cleanup is lost across frames\n * if the caller doesn't hold the post-state externally.\n * @returns A TerminalBuffer with the rendered content\n */\nexport function renderPhase(\n root: AgNode,\n prevBuffer?: TerminalBuffer | null,\n ctx?: PipelineContext,\n postState: RenderPostState = createRenderPostState(),\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 // Single frame-shared RenderSink. Smell #2 from the 2026-04-27 dual-pro\n // review (Kimi K2.6 winner): every helper used to call createFrameSink(buffer)\n // ad hoc, fabricating up to 7 sinks per frame. With BufferSink that was\n // behaviorally idempotent because all sinks point-mutate the same buffer.\n // With PlanSink (Step 3, paint-clear-l5-final), independent sinks would\n // build 7 fragmented plans. Threading ONE sink through the tree walk makes\n // PlanSink-authoritative a signature change rather than a multi-site refactor.\n const frameSink: RenderSink = createFrameSink(buffer)\n\n // Default selection state is semantic, not a blanket paint mode: root\n // userSelect defaults to \"text\", but only text-rendering operations stamp\n // SELECTABLE_FLAG. Box backgrounds, padding, and cleanup writes stay\n // structural and non-selectable.\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 live on the `postState` carrier\n // (Phase 2 Step 5 of paint-clear-invariant L5) — they no longer travel\n // with the cloned buffer. No-op when the carrier holds an empty list\n // (fresh render, or no outlines on the previous frame).\n clearPreviousOutlines(buffer, postState)\n\n const t1 = instr.enabled ? performance.now() : 0\n renderNodeToBuffer(\n root,\n buffer,\n frameSink,\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 selectableMode: true,\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 `postState` carrier for the next frame's\n // `clearPreviousOutlines` (Phase 2 Step 5 — snapshots no longer live on\n // the buffer).\n renderDecorationPass(buffer, root, postState)\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 siblingOverlapForced: 0,\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 *\n * `sink` is the frame-shared RenderSink threaded from `renderPhase`. All\n * post-state writes (selectableMode), cleanup ops (executeRegionClearing),\n * paint ops (renderOwnContent inner sinks), and overlay ops\n * (applyBoxAttrOverlay) route through this single sink. See Smell #2 from\n * the 2026-04-27 dual-pro review for the rationale.\n */\nfunction renderNodeToBuffer(\n node: AgNode,\n buffer: TerminalBuffer,\n sink: RenderSink,\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 // Skip display=\"none\" nodes\n if (props.display === \"none\") {\n clearDirtyFlags(node)\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 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 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 // Resolve userSelect for SELECTABLE_FLAG stamping.\n // Smell #1 from the 2026-04-27 dual-pro review (Kimi K2.6 winner): keep this\n // block AFTER all early-return paths so the single try/finally below is the\n // only restoration site. Adding a future early-return above (before try) no\n // longer requires manual restore plumbing.\n // Selectability remains threaded through nodeState so children don't need\n // buffer reads; the sink handles translating mode changes into direct buffer\n // mutation or per-op plan metadata.\n const prevSelectableMode = nodeState.selectableMode\n const userSelect = props.userSelect\n let currentSelectableMode = prevSelectableMode\n if (userSelect === \"none\") {\n currentSelectableMode = false\n } else if (userSelect === \"text\" || userSelect === \"contain\") {\n currentSelectableMode = true\n }\n const currentNodeState: NodeRenderState =\n currentSelectableMode === prevSelectableMode\n ? nodeState\n : { ...nodeState, selectableMode: currentSelectableMode }\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\n // Box attr-overlay safety: when stylePropsDirty on a silvery-box and the\n // overlay is either PRESENT NOW or WAS present last frame, the cloned\n // buffer may carry stale merged attrs. mergeAttrsInRect is OR-combining —\n // it cannot clear bits on its own. Force contentAreaAffected so the region\n // is cleared, children re-render fresh, and the merge applies the correct\n // new attrs (which may be \"none\").\n //\n // `node.hadBoxAttrOverlay` is set at the end of applyBoxAttrOverlay when\n // the Box had ANY overlay attr, so the next frame's stylePropsDirty knows\n // the prev frame baked attrs into its cells.\n //\n // Gated to stylePropsDirty to avoid unnecessary cascades. Boxes that\n // never had overlay (both current and prev frame) stay on the fast path.\n if (\n node.type === \"silvery-box\" &&\n isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT) &&\n (mayHaveBoxAttrOverlay(props as BoxProps) || node.hadBoxAttrOverlay)\n ) {\n const contentRegionCleared = (hasPrevBuffer || ancestorCleared) && !getEffectiveBg(props)\n const childrenNeedFreshRender = hasPrevBuffer || ancestorCleared\n cascade = {\n ...cascade,\n contentAreaAffected: true,\n bgOnlyChange: false,\n contentRegionCleared,\n childrenNeedFreshRender,\n }\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 sink,\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 hasPrevBuffer,\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 if (needsOwnRepaint) {\n // STRICT bordered-rect-clip: track which AgNode is currently\n // emitting cells so the BufferSink hook can resolve \"the nearest\n // bordered ancestor\" when SILVERY_STRICT=bordered-rect-clip is set.\n // Stack discipline mirrors withPlanCapture in render-sink.ts.\n pushRenderingNode(node)\n resetBorderedRectClipCache()\n try {\n renderOwnContent(\n node,\n buffer,\n sink,\n layout,\n props,\n currentNodeState,\n skipBgFill,\n instr.enabled,\n instr.stats,\n ctx,\n cascade.bgOnlyChange,\n useTextStyleFastPath,\n )\n } finally {\n popRenderingNode()\n resetBorderedRectClipCache()\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/selectableMode so children\n // don't walk the parent chain or read buffer state.\n const childState: NodeRenderState = {\n ...currentNodeState,\n inheritedBg: childInheritedBg,\n inheritedFg: childInheritedFg,\n selectableMode: currentSelectableMode,\n }\n if (isScrollContainer) {\n renderScrollContainerChildren(\n node,\n buffer,\n sink,\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 sink,\n props,\n childState,\n childPositionChanged,\n contentRegionCleared,\n childrenNeedFreshRender,\n ctx,\n )\n }\n\n // Attribute overlay: when a Box has unified attr props (underline /\n // strikethrough / etc.) that don't belong to the Text-node rendering path,\n // apply them to every cell in the box rect AFTER children render. This is\n // the transparent-overlay primitive (see buffer.mergeAttrsInRect).\n //\n // Gated to silvery-box so Text nodes keep using their existing style path\n // (attrs baked into cell writes during renderText).\n //\n // Sets `node.hadBoxAttrOverlay` so next frame's cascade can detect the\n // \"overlay was removed\" case and trigger a clear.\n if (node.type === \"silvery-box\") {\n const applied = applyBoxAttrOverlay(buffer, sink, layout, props, scrollOffset, clipBounds)\n node.hadBoxAttrOverlay = applied\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 }\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 sink: RenderSink,\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 hasPrevBuffer: boolean,\n): void {\n // `sink` is the frame-shared RenderSink threaded from renderPhase.\n // (km-silvery.paint-clear-invariant Phase 2 Step 4a; Smell #2 from the\n // 2026-04-27 dual-pro review consolidates ad-hoc local sinks onto this\n // single threaded one so PlanSink-authoritative is a signature change.)\n //\n // Smell #3 (Kimi K2.6 winner) — single coordinator pattern:\n // - clearNodeRegion handles \"fill the node's NEW rect with inherited bg\n // because we have stale pixels and no own bg fill\". One responsibility.\n // - clearExcessArea handles \"node shrank, fill the OLD-minus-NEW area\n // with inherited bg\". Different responsibility, different geometry.\n // - Both are bounded by the SAME stale-pixel precondition\n // (bufferIsCloned && hasPrevBuffer). Pre-decouple, clearNodeRegion\n // unconditionally tail-called clearExcessArea — meaning excess\n // clearing could fire on a fresh buffer (no stale pixels) when\n // contentRegionCleared=true && hasPrevBuffer=false && ancestorCleared=true.\n // - Post-decouple, executeRegionClearing is the SOLE coordinator: it\n // calls clearNodeRegion when its own gate is true, and INDEPENDENTLY\n // calls clearExcessArea when the shrink-cleanup gate is true. Step 4\n // of paint-clear-l5-final (delete clearExcessArea entirely, fold into\n // sectioned cleanupOps) becomes a single-call-site edit.\n\n if (contentRegionCleared) {\n if (instrumentEnabled) stats.clearOps++\n clearNodeRegion(node, sink, layout, scrollOffset, clipBounds, threadedInheritedBg)\n }\n // Promote the four-condition runtime guard to a structural invariant:\n // requireExcessClearGate is the SOLE constructor of ExcessClearGate, and\n // clearExcessArea takes the gate as its first parameter. The wrong-order\n // call site (excess-clear during a second-pass dispatch where\n // hasPrevBuffer=false but bufferIsCloned=true) is unrepresentable —\n // requireExcessClearGate returns null in that case, so the typed `if\n // (excessGate)` check is the only path to the call.\n // Tracking: km-silvery.paint-clear-invariant.\n const excessGate = requireExcessClearGate(\n bufferIsCloned,\n layoutChanged,\n node.prevLayout,\n hasPrevBuffer,\n )\n if (excessGate) {\n clearExcessArea(\n excessGate,\n node,\n buffer,\n sink,\n layout,\n scrollOffset,\n clipBounds,\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 sink,\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 sink: RenderSink,\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-viewport\") {\n // Opaque blit of the foreign cell domain. The viewport doesn't participate\n // in bg-coherence with the parent — `renderText`'s bg-conflict throw is\n // never reached because viewport cells route through `sink.emitSetCell`\n // directly. See bead @km/silvery/15513.\n renderViewport(node, buffer, sink, layout, nodeState.scrollOffset)\n } else if (node.type === \"silvery-island\") {\n // Sibling of silvery-viewport — opaque blit of the guest's cell buffer.\n // The island generalises Viewport with the runtime-agnostic IslandGuest\n // contract (PTY, snapshot, replay, embedded silvery sub-instance, etc.).\n // Bails cleanly when the guest hasn't initialised yet (`handle === null`).\n // See bead @km/silvery/15646-islands.\n renderIsland(node, buffer, sink, layout, nodeState.scrollOffset, nodeState.inheritedBg.color)\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 // Phase 2 Step 4d: text style-only fast path → sink.emitRestyleRegion\n // (paint op — restyles existing cells in place). Smell #2: routes\n // through the frame-shared `sink` rather than fabricating a local one.\n sink.emitRestyleRegion(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// Box Attr Overlay (transparent underline / strikethrough / etc.)\n// ============================================================================\n\n/**\n * Cheap check: is ANY attr-overlay prop present on this Box right now?\n *\n * Used by the cascade to decide whether `stylePropsDirty` on a silvery-box\n * should escalate to `contentAreaAffected`. We can't reliably inspect the\n * PREVIOUS frame's props from here, so we conservatively escalate whenever\n * EITHER the current frame OR a neighbor frame might have had overlay.\n * The single-call common case (Box with no attr overlay ever) stays on the\n * fast path because this returns false both frames and the stylePropsDirty\n * gate doesn't fire (stylePropsDirty requires a prop-change reconciler event,\n * which won't happen for a Box that never had these props).\n */\nfunction mayHaveBoxAttrOverlay(props: BoxProps): boolean {\n return !!(\n props.underline ||\n props.underlineStyle ||\n props.underlineColor ||\n props.overline ||\n props.bold ||\n props.italic ||\n props.inverse ||\n props.strikethrough\n )\n}\n\n/**\n * Detect which attr props are active on a Box. Only returns the overlay\n * subset — attrs that benefit from merge-over-children semantics.\n *\n * bold / dim / italic / inverse / strikethrough / underline / underlineColor\n * all qualify. Text color, bg color, and border-related props are handled\n * elsewhere and are NOT attr-overlay props.\n *\n * Returns null when no overlay attrs are present (fast path — most Boxes).\n */\nfunction computeBoxAttrOverlay(props: BoxProps): {\n attrs: import(\"../buffer\").CellAttrs\n underlineColor: Color | undefined\n} | null {\n // Resolve underline: accept `underline: boolean | UnderlineStyleName` and\n // the legacy `underlineStyle: UnderlineStyle`. `underlineStyle` wins when\n // both are set (matches getTextStyle() precedence).\n let underlineStyle: Exclude<import(\"@silvery/ag/types\").UnderlineStyle, false> | undefined\n if (props.underlineStyle !== undefined && props.underlineStyle !== false) {\n underlineStyle = props.underlineStyle\n } else if (typeof props.underline === \"string\") {\n underlineStyle = props.underline\n } else if (props.underline === true) {\n underlineStyle = \"single\"\n }\n const hasUnderline = !!underlineStyle\n\n const bold = !!props.bold\n const italic = !!props.italic\n const inverse = !!props.inverse\n const strikethrough = !!props.strikethrough\n const overline = !!props.overline\n\n if (!hasUnderline && !bold && !italic && !inverse && !strikethrough && !overline) {\n // No overlay attrs — skip.\n return null\n }\n\n const attrs: import(\"../buffer\").CellAttrs = {\n bold: bold || undefined,\n italic: italic || undefined,\n inverse: inverse || undefined,\n strikethrough: strikethrough || undefined,\n overline: overline || undefined,\n underline: hasUnderline || undefined,\n underlineStyle: hasUnderline ? underlineStyle : undefined,\n }\n\n const underlineColor = props.underlineColor ? parseColor(props.underlineColor) : undefined\n\n return { attrs, underlineColor: underlineColor ?? undefined }\n}\n\n/**\n * Apply a Box's attr overlay to its rect, respecting scroll offset and clip\n * bounds. Runs AFTER children render so attrs layer on top of content without\n * overwriting glyphs/fg/bg.\n *\n * @returns true when an overlay was actually applied — used by the caller to\n * set `node.hadBoxAttrOverlay`, which next frame's cascade reads to detect\n * overlay removal.\n */\nfunction applyBoxAttrOverlay(\n buffer: TerminalBuffer,\n sink: RenderSink,\n layout: NonNullable<AgNode[\"boxRect\"]>,\n props: BoxProps,\n scrollOffset: number,\n clipBounds: ClipBounds | undefined,\n): boolean {\n const overlay = computeBoxAttrOverlay(props)\n if (!overlay) return false\n\n const x = layout.x\n const y = layout.y - scrollOffset\n let rx = x\n let rw = layout.width\n let ry = y\n let rh = layout.height\n\n if (clipBounds) {\n const topClipped = Math.max(ry, clipBounds.top)\n const bottomClipped = Math.min(ry + rh, clipBounds.bottom)\n if (topClipped >= bottomClipped) return false\n ry = topClipped\n rh = bottomClipped - topClipped\n\n if (clipBounds.left !== undefined && clipBounds.right !== undefined) {\n const leftClipped = Math.max(rx, clipBounds.left)\n const rightClipped = Math.min(rx + rw, clipBounds.right)\n if (leftClipped >= rightClipped) return false\n rx = leftClipped\n rw = rightClipped - leftClipped\n }\n }\n\n // Phase 2 Step 4d: box attr overlay → sink.emitMergeAttrs (overlay op,\n // applied AFTER paint so existing cells are merged in place). Smell #2:\n // routes through the frame-shared `sink` rather than fabricating a local one.\n sink.emitMergeAttrs(rx, ry, rw, rh, overlay.attrs, overlay.underlineColor)\n return true\n}\n\n// ============================================================================\n// Scroll Tier Planner\n// ============================================================================\n\n/**\n * Check whether any transparent sibling of this scroll container is an\n * absolute-positioned Box whose rect overlaps with the scroll container's rect.\n *\n * This matters for Tier 1 (buffer shift) safety: when a transparent sibling\n * absolute child paints into our rect (e.g., an overlay glyph at col=width-1),\n * the shift would smear those pixels along with the content. Opaque absolute\n * siblings are safe because the normal third pass always re-renders absolute\n * children after the scroll container and repaints their whole rect.\n *\n * O(siblings) — typically 1-3 siblings under common parents (ListView outer\n * Box has scroll container + scrollbar + edge-bump indicator). Returns false\n * for the common case (no parent or no absolute siblings).\n */\nfunction checkOverlappingAbsoluteSibling(node: AgNode, scrollRect: Rect): boolean {\n const parent = node.parent\n if (!parent) return false\n const siblings = parent.children\n if (!siblings) return false\n const sLeft = scrollRect.x\n const sTop = scrollRect.y\n const sRight = scrollRect.x + scrollRect.width\n const sBottom = scrollRect.y + scrollRect.height\n for (const sib of siblings) {\n if (sib === node) continue\n const sp = sib.props as BoxProps | undefined\n if (sp?.position !== \"absolute\") continue\n if (getEffectiveBg(sp)) continue\n const r = sib.boxRect\n if (!r) continue\n // AABB overlap test (half-open intervals).\n if (r.x < sRight && r.x + r.width > sLeft && r.y < sBottom && r.y + r.height > sTop) {\n return true\n }\n }\n return false\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 * A sibling absolute child overlays this scroll container's rect. When true,\n * Tier 1 (buffer shift) is unsafe — the shift would smear the absolute\n * sibling's pixels along with content. Common case: ListView's scrollbar\n * thumb at col=width-1, sibling of the scroll Box.\n */\n hasOverlappingAbsoluteSibling?: boolean\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, sticky/overlaid scroll, or parent region\n * 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 hasOverlappingAbsoluteSibling = false,\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 // Unsafe with overlapping absolute siblings (the shift would smear their\n // pixels along with content — common case: ListView scrollbar thumb).\n // Safe with visibleRangeChanged: the shift moves existing pixels, while the\n // per-child pass below force-renders newly exposed children whose previous\n // rect was outside the old viewport.\n const scrollOnly =\n hasPrevBuffer &&\n scrollOffsetChanged &&\n !childrenDirty &&\n !childrenNeedFreshRender &&\n !hasStickyChildren &&\n !hasOverlappingAbsoluteSibling\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 if (hasOverlappingAbsoluteSibling) reasons.push(\"overlappingAbsoluteSibling\")\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 sink: RenderSink,\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 selectableMode,\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 // Tier 1 (buffer shift) is unsafe when a sibling absolute child overlays\n // this scroll container's rect. The shift moves pixels in our rect — but\n // those pixels include any absolute overlay painted in the previous frame\n // by a sibling under the same parent (e.g., ListView's scrollbar thumb\n // sitting at col=width-1 above the scroll container). Without this gate,\n // the absolute overlay's pixels smear with the content shift and the\n // STRICT incremental-vs-fresh comparison detects the corruption.\n //\n // Bead: km-silvery.listview-test-failures.\n const hasOverlappingAbsoluteSibling = checkOverlappingAbsoluteSibling(node, layout)\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 hasOverlappingAbsoluteSibling,\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 // Phase 2 Step 4d: scroll-tier ops route through sink.\n // - Tier 1 shift: scrollRegion is a TRANSFER (shifts prev pixels);\n // leading indicator-row clears are CLEANUP (clear stale indicator).\n // - Tier 2 clear: viewport clear is CLEANUP.\n // - stickyForceRefresh: viewport pre-clear (CLEANUP).\n // Smell #2: routes through the frame-shared `sink` rather than fabricating a\n // local one — see Smell #2 from the 2026-04-27 dual-pro review.\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 sink.emitClearRect(contentX, topIndicatorY, contentWidth, 1, plan.clearBg)\n }\n sink.emitClearRect(contentX, bottomIndicatorY, contentWidth, 1, plan.clearBg)\n }\n sink.emitScrollRegion(contentX, clearY, contentWidth, clearHeight, scrollDelta, {\n char: \" \",\n bg: plan.clearBg,\n })\n }\n\n if (tier === \"clear\" && clearHeight > 0) {\n sink.emitClearRect(contentX, clearY, contentWidth, clearHeight, plan.clearBg)\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 sink.emitClearRect(contentX, clearY, contentWidth, clearHeight, 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 if (!wasFullyVisible) {\n clearShiftedNewChildRegion(childRect, ss.offset, childClipBounds, sink, plan.clearBg)\n }\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 sink,\n {\n scrollOffset: ss.offset,\n clipBounds: childClipBounds,\n hasPrevBuffer: thisChildHasPrev,\n ancestorCleared: thisChildAncestorCleared,\n bufferIsCloned,\n ancestorLayoutChanged: childAncestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n selectableMode,\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 sink,\n {\n scrollOffset: stickyScrollOffset,\n clipBounds: childClipBounds,\n hasPrevBuffer: false,\n ancestorCleared: false,\n bufferIsCloned,\n ancestorLayoutChanged: childAncestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n selectableMode,\n },\n ctx,\n )\n }\n }\n}\n\n/**\n * Tier-1 scroll shifting reuses previous-frame pixels. A child that was not fully\n * visible in the previous viewport is rendered like a fresh subtree, but the\n * shifted buffer under its transparent cells may still contain unrelated pixels\n * from a previous row. Clear the child's currently visible rectangle first so\n * transparent boxes/text render onto the same blank base as a fresh render.\n */\nfunction clearShiftedNewChildRegion(\n childRect: Rect,\n scrollOffset: number,\n clipBounds: ClipBounds | undefined,\n sink: RenderSink,\n bg: Color | null,\n): void {\n let x = childRect.x\n let y = childRect.y - scrollOffset\n let width = childRect.width\n let height = childRect.height\n\n if (clipBounds) {\n if (clipBounds.left !== undefined && x < clipBounds.left) {\n width -= clipBounds.left - x\n x = clipBounds.left\n }\n if (clipBounds.right !== undefined && x + width > clipBounds.right) {\n width = clipBounds.right - x\n }\n if (y < clipBounds.top) {\n height -= clipBounds.top - y\n y = clipBounds.top\n }\n if (y + height > clipBounds.bottom) {\n height = clipBounds.bottom - y\n }\n }\n\n if (width > 0 && height > 0) {\n sink.emitClearRect(x, y, width, height, bg)\n }\n}\n\n/**\n * Render children of a normal (non-scroll) container.\n */\nfunction renderNormalChildren(\n node: AgNode,\n buffer: TerminalBuffer,\n sink: RenderSink,\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 selectableMode,\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 // Phase 2 Step 4d: sticky-force-refresh viewport pre-clear (CLEANUP).\n // Smell #2: routes through the frame-shared `sink`.\n sink.emitClearRect(clearX, clearY, clearW, clearH, 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 // Sibling-overlap dirtying. CSS paint order is DOM order: sibling N's\n // pixels appear ON TOP of sibling N-1's pixels at any overlap. Fresh\n // renders preserve this naturally because every child paints. Incremental\n // renders break it when sibling N is clean (skipped) but sibling N-1\n // re-renders — sibling N-1's painting wipes sibling N's pixels in the\n // cloned buffer, and the (skipped) sibling N never restores them.\n //\n // The fix: pre-scan the first-pass children. Any child whose rect\n // intersects an EARLIER first-pass child that will re-render must also\n // re-render. We propagate this forward in a single linear pass: if child\n // i will render, every later j>i with overlapping rect is forced to\n // render too. (Transitive: if j is forced and k>j overlaps j, k is also\n // forced — handled by checking all earlier-rendered indices.)\n //\n // Real-world repro: km-tui Board layout where the column container\n // (overflow=\"scroll\" with bottom scroll indicator at row N) ends up 1\n // row taller than its allotted space. The column's row N overlaps the\n // bottom-bar's row N. On cursor move, only the column re-renders; its\n // scroll indicator overwrites the bottom-bar's \"MEM 📋 NNN\" text without\n // the bottom-bar repainting to restore it. STRICT_OUTPUT mismatches at\n // (col, N) char='M' vs ' '. See bead km-all.fix-sweep-strict-cluster.\n const firstPassChildren: AgNode[] = []\n for (const child of node.children) {\n const childProps = child.props as BoxProps\n if (childProps.position === \"absolute\") {\n hasAbsoluteChildren = true\n continue\n }\n if (hasStickyChildren && childProps.position === \"sticky\") {\n continue\n }\n firstPassChildren.push(child)\n }\n\n // Determine which children are \"going to render\" (skip-path miss) and\n // forward-propagate force-render to overlapping later siblings.\n const willRender: boolean[] = firstPassChildren.map(\n (child) => !canSkipChildSubtree(child, childHasPrev, childAncestorLayoutChanged),\n )\n // Only do overlap propagation when we have prev buffer state (otherwise\n // the canSkipChildSubtree check returns false for everything anyway and\n // the forward propagation is a no-op).\n if (childHasPrev) {\n // The \"paint extent\" of a child is the union of its boxRect with any\n // descendant boxRect that overflows it. Using boxRect alone misses the\n // case where a deeper descendant (e.g. a column inside a board) extends\n // beyond its parent's rect into a great-aunt's territory. We compute\n // paint extent lazily and only for children that will render — clean\n // skipped children don't need the walk.\n for (let i = 0; i < firstPassChildren.length; i++) {\n if (!willRender[i]) continue\n const childI = firstPassChildren[i]!\n const ei = computeSubtreePaintExtent(childI)\n if (!ei || ei.width <= 0 || ei.height <= 0) continue\n for (let j = i + 1; j < firstPassChildren.length; j++) {\n if (willRender[j]) continue\n // Use the later sibling's boxRect (cheap) for the receiver side —\n // any descendant of j inside its boxRect is at risk of being\n // overwritten by i's paint extent. Descendants of j that overflow\n // j wouldn't be at the row painted by i unless they're also inside\n // ei — and our recursion-from-i covers any descendant of j that ALSO\n // happens to fall within ei. We don't need j's full subtree extent.\n const rj = firstPassChildren[j]!.boxRect\n if (!rj || rj.width <= 0 || rj.height <= 0) continue\n const overlapX = ei.x < rj.x + rj.width && rj.x < ei.x + ei.width\n const overlapY = ei.y < rj.y + rj.height && rj.y < ei.y + ei.height\n if (overlapX && overlapY) {\n willRender[j] = true\n if (instr.enabled) instr.stats.siblingOverlapForced++\n }\n }\n }\n }\n\n // First pass: render normal-flow children in DOM order.\n for (let i = 0; i < firstPassChildren.length; i++) {\n if (!willRender[i]) continue\n const child = firstPassChildren[i]!\n\n // For overlap-forced children (clean by their own flags but overlapped\n // by an earlier-rendered sibling) we must NOT trust the cloned buffer\n // at their position — the earlier sibling has just painted there.\n // Treat them like a fresh render at their position: hasPrevBuffer=false,\n // ancestorCleared=false. This matches the second/third-pass treatment\n // for sticky/absolute children, which face the same situation.\n const ownDirty = !canSkipChildSubtree(child, childHasPrev, childAncestorLayoutChanged)\n const overlapForced = !ownDirty\n const useChildHasPrev = overlapForced ? false : childHasPrev\n const useChildAncestorCleared = overlapForced ? false : childAncestorCleared\n\n renderNodeToBuffer(\n child,\n buffer,\n sink,\n {\n scrollOffset,\n clipBounds: effectiveClipBounds,\n hasPrevBuffer: useChildHasPrev,\n ancestorCleared: useChildAncestorCleared,\n bufferIsCloned,\n ancestorLayoutChanged: childAncestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n selectableMode,\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 sink,\n {\n scrollOffset: stickyScrollOffset,\n clipBounds: effectiveClipBounds,\n hasPrevBuffer: false,\n ancestorCleared: false,\n bufferIsCloned,\n ancestorLayoutChanged: childAncestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n selectableMode,\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 sink,\n {\n scrollOffset,\n clipBounds: effectiveClipBounds,\n hasPrevBuffer: false,\n ancestorCleared: false,\n bufferIsCloned,\n ancestorLayoutChanged: childAncestorLayoutChanged,\n inheritedBg,\n inheritedFg,\n selectableMode,\n },\n ctx,\n )\n }\n }\n}\n\n// ============================================================================\n// Subtree Paint Extent\n// ============================================================================\n\n/**\n * Compute the screen-space rectangle that bounds every paint within this\n * subtree. Used by sibling-overlap detection in renderNormalChildren so\n * that a descendant overflowing its ancestor still triggers force-repaint\n * of any later sibling at the painted row.\n *\n * Returns the union of `node.boxRect` with the recursive paint extent of\n * each child whose `boxRect` is set. Returns null if `node.boxRect` is\n * unset (e.g. abandoned subtree). Skipping descendants without a boxRect\n * is safe because they never paint.\n *\n * Cost: O(N) per call where N is the subtree's node count. Only called\n * for first-pass children that will render AND only when their parent has\n * a prev buffer (i.e. real incremental render frames). Skipped children\n * never trigger the walk.\n */\nfunction computeSubtreePaintExtent(node: AgNode): NonNullable<AgNode[\"boxRect\"]> | null {\n const own = node.boxRect\n if (!own) return null\n let minX = own.x\n let minY = own.y\n let maxX = own.x + own.width\n let maxY = own.y + own.height\n const children = node.children\n if (children) {\n for (const child of children) {\n const sub = computeSubtreePaintExtent(child)\n if (!sub) continue\n if (sub.x < minX) minX = sub.x\n if (sub.y < minY) minY = sub.y\n if (sub.x + sub.width > maxX) maxX = sub.x + sub.width\n if (sub.y + sub.height > maxY) maxY = sub.y + sub.height\n }\n }\n return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }\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 * 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 sink: RenderSink,\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 sink,\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 sink: RenderSink,\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 sink.emitClearRect(\n overflowX,\n overflowTop,\n overflowWidth,\n overflowBottom - overflowTop,\n 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 sink.emitClearRect(\n overflowX,\n overflowTop,\n overflowWidth,\n overflowBottom - overflowTop,\n 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 sink.emitClearRect(\n overflowX,\n overflowTop,\n overflowWidth,\n overflowBottom - overflowTop,\n 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 sink.emitClearRect(\n overflowX,\n overflowTop,\n overflowWidth,\n overflowBottom - overflowTop,\n 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 sink,\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 CURRENT region with inherited bg when it has no backgroundColor.\n *\n * Single responsibility: fill the node's `layout` rect with `clearBg`,\n * clipped to the parent's bounds and to the colored ancestor's bounds.\n *\n * Smell #3 (paint-clear-step2-followups) — pre-decouple, this function\n * tail-called `clearExcessArea` unconditionally. That meant excess\n * clearing could fire on a fresh buffer when contentRegionCleared=true\n * but bufferIsCloned/hasPrevBuffer were false. Post-decouple,\n * `executeRegionClearing` is the SOLE coordinator: it calls\n * `clearNodeRegion` AND `clearExcessArea` independently when their\n * respective gates are true.\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 sink: RenderSink,\n layout: NonNullable<AgNode[\"boxRect\"]>,\n scrollOffset: number,\n clipBounds: ClipBounds | undefined,\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 sink.emitClearRect(clearX, clearY, clearWidth, clearHeight, clearBg)\n }\n\n // Excess-area clearing is the responsibility of executeRegionClearing —\n // see Smell #3 in paint-clear-step2-followups. clearNodeRegion no longer\n // tail-calls clearExcessArea so that Step 4 (delete clearExcessArea\n // entirely once the sectioned cleanupOps land) is a single edit site.\n}\n\n/**\n * Brand-typed witness that the four excess-clear preconditions hold:\n * bufferIsCloned && layoutChanged && node.prevLayout != null && hasPrevBuffer\n *\n * Only constructed by `requireExcessClearGate`. `clearExcessArea` requires it\n * as its first parameter, so the wrong-order call site (excess-clear during a\n * second-pass walk where hasPrevBuffer=false but bufferIsCloned=true) cannot\n * be expressed — TypeScript narrowing forbids passing `null` where the brand\n * is required, and there is no other way to obtain a value of this type.\n *\n * Why brand instead of a plain boolean: a boolean parameter can be derived\n * incorrectly at the call site (e.g., a future refactor flattens the four\n * conditions into three and forgets the second-pass distinction). The brand\n * forces every call to flow through `requireExcessClearGate`, where the\n * derivation is centralized and audited. A second-pass dispatch site that\n * tries to call `clearExcessArea` directly will not type-check because it\n * cannot construct the gate without going through the factory, which always\n * returns null when `hasPrevBuffer === false`.\n *\n * Tracking: km-silvery.paint-clear-invariant. Promotes the runtime guard at\n * silvery 168b4989 (the `&& hasPrevBuffer` term in `needsExcessClear`) to a\n * structural invariant. The full L5 win — deleting clearExcessArea entirely\n * in favor of sectioned cleanupOps — depends on Phase 2 Steps 5/6 retiring\n * BufferSink as authoritative output (see render-plan.ts).\n */\ndeclare const ExcessClearGateBrand: unique symbol\nexport type ExcessClearGate = {\n readonly [ExcessClearGateBrand]: never\n readonly prevLayout: NonNullable<AgNode[\"prevLayout\"]>\n}\n\n/**\n * Construct an `ExcessClearGate` if and only if all four preconditions hold.\n * Returns null otherwise. This is the SOLE constructor for the brand.\n *\n * Preconditions (each enforces one structural invariant):\n * - bufferIsCloned: there are stale pixels to clear (false for fresh buffers).\n * - layoutChanged: the node's rect changed this frame (no shrink → no excess).\n * - prevLayout != null: we have an old rect to compute the excess from.\n * - hasPrevBuffer: the buffer at this node's position contains the *previous\n * frame's* pixels (not first-pass-painted current-frame siblings — the\n * latter is what second-pass absolute/sticky/overlap-forced dispatch sites\n * pass with hasPrevBuffer=false). Excess-clearing on top of fresh sibling\n * paint corrupts the in-progress frame; see km-silvery.ai-chat-incremental-mismatch.\n */\nexport function requireExcessClearGate(\n bufferIsCloned: boolean,\n layoutChanged: boolean,\n prevLayout: AgNode[\"prevLayout\"],\n hasPrevBuffer: boolean,\n): ExcessClearGate | null {\n if (!bufferIsCloned) return null\n if (!layoutChanged) return null\n if (prevLayout == null) return null\n if (!hasPrevBuffer) return null\n // The brand field is compile-time-only (declared as a unique symbol with\n // no runtime emission); `as ExcessClearGate` is the canonical assertion\n // pattern and is local to this single factory function.\n return { prevLayout } as unknown as ExcessClearGate\n}\n\n/**\n * Clear the excess area when a node shrinks (old bounds were larger than new).\n *\n * Requires an `ExcessClearGate` as the first parameter — only obtainable via\n * `requireExcessClearGate`, which checks the four preconditions. This makes\n * the wrong-order second-pass call site (the bug that motivated silvery\n * 168b4989) structurally unrepresentable: a caller cannot write\n * `clearExcessArea(...)` without first flowing through the factory, which\n * returns null when hasPrevBuffer=false (the second-pass case).\n *\n * Clips to the COLORED ANCESTOR's content area (not immediate parent's full\n * rect) to prevent inherited color from bleeding into sibling areas with\n * 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\n * chars.\n */\nfunction clearExcessArea(\n gate: ExcessClearGate,\n node: AgNode,\n buffer: TerminalBuffer,\n sink: RenderSink,\n layout: NonNullable<AgNode[\"boxRect\"]>,\n scrollOffset: number,\n clipBounds: ClipBounds | undefined,\n inherited: NodeRenderState[\"inheritedBg\"],\n): void {\n // The gate's existence is the proof of preconditions; `prevLayout` comes\n // from the gate so the type system guarantees it's non-null here.\n const prev = gate.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 sink,\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 sink,\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 sink: RenderSink,\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 sink.emitClearRect(clippedX, clippedTop, clippedWidth, height, bg)\n }\n}\n","/**\n * Phase 1 of paint-clear-invariant recast (km-silvery.paint-clear-invariant).\n *\n * Render plan + commit substrate. The render phase produces an ordered list\n * of buffer mutations (`RenderOp[]`) and `commitPlan` replays them into a\n * fresh prevBuffer clone. Phase 1 is opt-in via SILVERY_RENDER_PLAN and is\n * proven against the existing imperative renderer by a capture-and-replay\n * parity test. Phase 2 rewrites renderers to emit ops directly; Phase 3\n * deletes clearExcessArea's hasPrevBuffer guard (silvery 168b4989) once the\n * call site that needs it is unreachable.\n *\n * REVIEW STATUS (pro/Kimi K2.6 — see km-silvery.paint-clear-invariant bead +\n * /tmp/llm-cc081a9a-review-this-phase-1-ig4f.txt). Both reviewers\n * independently flagged that:\n *\n * 1. The structural fix is a plan SHAPE (transfer / cleanup / paint /\n * overlay / post-state), NOT a global op-kind priority. The current\n * `COMMIT_PRIORITY` map is a transitional helper used by Phase 1 tests\n * only — Phase 2 must replace it with a sectioned RenderPlan.\n * 2. `fill` MUST split into `clearRect` + `paintFill` (destructive vs\n * constructive) before Phase 2 ships. Otherwise the priority bucket\n * reintroduces the sibling-stomp at commit time.\n * 3. `scrollRegion` is a transfer of prev pixels and runs FIRST, not last.\n * Fixed in COMMIT_PRIORITY below.\n * 4. Selectability must travel on each cell-writing op, not as hidden\n * mutable buffer state.\n * 5. `outlineSnapshots` was non-cell buffer state surviving across\n * frames. Phase 2 Step 5 (paint-clear-l5-step5-outline-snapshots)\n * hoisted them onto `RenderPostState` (see render-post-state.ts);\n * the buffer is now a pure cell store. The PostStateOp survives in\n * the plan-shape for parity capture but writes through to the\n * orchestrator's carrier, not the buffer.\n * 6. Intra-frame buffer reads (`getCellBg`, dirty rows) are the real\n * Phase 2 blocker — every read site must be audited and converted\n * to derive from node state / op stream before the renderer can\n * emit ops without a backing buffer.\n *\n * Design doc: hub/silvery/design/render-plan-commit.md (in km workspace,\n * not bundled with vendor package). Review notes preserved in the bead.\n */\n\nimport type { CellAttrs, CellPatch, Color, Style } from \"../buffer\"\nimport { TerminalBuffer } from \"../buffer\"\n\nexport type SelectableCellPatch = CellPatch\n\nexport type RenderOp =\n | { kind: \"setCell\"; x: number; y: number; cell: SelectableCellPatch }\n | {\n kind: \"fill\"\n x: number\n y: number\n width: number\n height: number\n cell: SelectableCellPatch\n }\n | {\n kind: \"fillBg\"\n x: number\n y: number\n width: number\n height: number\n bg: Color\n }\n | {\n kind: \"restyleRegion\"\n x: number\n y: number\n width: number\n height: number\n style: Style\n }\n | {\n kind: \"scrollRegion\"\n x: number\n y: number\n width: number\n height: number\n delta: number\n clearCell?: SelectableCellPatch\n }\n | {\n kind: \"mergeAttrsInRect\"\n x: number\n y: number\n width: number\n height: number\n attrs: CellAttrs\n underlineColor?: Color\n }\n | { kind: \"setRowMeta\"; row: number; softWrapped?: boolean; lastContentCol?: number }\n\nexport interface RenderPlan {\n readonly width: number\n readonly height: number\n readonly ops: readonly RenderOp[]\n}\n\n// Phase 2 commit-priority intent. **NOT** the right model on its own — see\n// pro/Kimi review notes in the bead km-silvery.paint-clear-invariant +\n// /tmp/llm-cc081a9a-review-this-phase-1-ig4f.txt. The structural fix is a\n// plan SHAPE (transfer / cleanup / paint / overlay / post-state), not a\n// global op-kind priority. This map exists so `commitPlanByPriority` has\n// deterministic behaviour for tests and so the comment trail makes the\n// intended transitions visible to the next implementer.\n//\n// Critical corrections from review (do NOT skip when wiring Phase 2):\n// - `fill` must SPLIT into `clearRect` (destructive, runs before bg) and\n// `paintFill` (constructive, runs with content). Single-kind `fill`\n// reintroduces the sibling-stomp at commit time.\n// - `scrollRegion` is a transfer of prev-frame pixels and MUST run before\n// any paint into the shifted region. It belongs early, not late.\n// - `fillBg` is opaque background paint (sibling z-layer); it is NOT\n// \"lowest layer paint everywhere\" — later siblings can legitimately\n// cover earlier siblings' overflow text. Within bucket, plan order\n// decides; do not assume all bg goes first globally.\n// - `mergeAttrsInRect` (Box attr overlay: bold/underline/strikethrough)\n// is NOT yet recorded by RecordingBuffer — silent divergence risk.\n//\n// Phase 2 should replace this map with a `RenderPlan` SHAPE that has named\n// sections (transferOps / cleanupOps / paintOps / overlayOps / postState).\n// The current map is kept for the transitional `commitPlanByPriority`\n// helper used by tests.\nconst COMMIT_PRIORITY: Record<RenderOp[\"kind\"], number> = {\n // 0: shift existing prev-frame pixels before any new write to the\n // shifted region (Tier 1 scroll containers).\n scrollRegion: 0,\n // 1: clears (currently bundled into `fill` — Phase 2 splits this into\n // a dedicated `clearRect` kind). Until then `commitPlanByPriority`\n // applies `fill` ops in plan order, which is NOT a structural fix.\n fill: 1,\n // 2: opaque background paint. WITHIN the bucket, plan order decides —\n // do not globally pre-paint all bg before all content.\n fillBg: 2,\n // 3: content (text, borders, individual cell writes).\n setCell: 3,\n // 4: restyle existing cells (depends on cells already being written).\n restyleRegion: 4,\n // 5: attribute overlay (Box bold/underline/strikethrough). Merges into\n // existing cells so it depends on cells being written first.\n mergeAttrsInRect: 5,\n // 6: book-keeping that travels with cells.\n setRowMeta: 6,\n}\n\n/**\n * Recording proxy around a real `TerminalBuffer`. Forwards every mutating\n * call to the underlying buffer and appends a corresponding `RenderOp` to\n * `ops`. Reads pass through unchanged so the existing render-phase code\n * (which does read its own writes through `getCellBg`, dirty rows, etc.)\n * sees a fully-functional buffer.\n *\n * Phase 1 uses the recorder so we can run the existing renderer unchanged\n * and still capture a `RenderPlan` for the parity test. Phase 2 will\n * remove the recorder once renderers emit ops directly.\n *\n * Recording starts in the disabled state so the initial state-copy from a\n * source buffer (via `copyBufferState`) does not generate spurious ops.\n * `startRecording()` is called by `wrapPrevBufferForRecording` once the\n * buffer is ready to be handed to the renderer.\n */\nexport class RecordingBuffer extends TerminalBuffer {\n readonly ops: RenderOp[] = []\n private recording = false\n\n startRecording(): void {\n this.recording = true\n }\n\n stopRecording(): void {\n this.recording = false\n }\n\n override setCell(x: number, y: number, cell: CellPatch): void {\n super.setCell(x, y, cell)\n if (this.recording) this.ops.push({ kind: \"setCell\", x, y, cell: cloneCellPatch(cell) })\n }\n\n override fill(x: number, y: number, width: number, height: number, cell: CellPatch): void {\n super.fill(x, y, width, height, cell)\n if (this.recording)\n this.ops.push({ kind: \"fill\", x, y, width, height, cell: cloneCellPatch(cell) })\n }\n\n override fillBg(x: number, y: number, width: number, height: number, bg: Color): void {\n super.fillBg(x, y, width, height, bg)\n if (this.recording) this.ops.push({ kind: \"fillBg\", x, y, width, height, bg })\n }\n\n override restyleRegion(x: number, y: number, width: number, height: number, style: Style): void {\n super.restyleRegion(x, y, width, height, style)\n if (this.recording)\n this.ops.push({ kind: \"restyleRegion\", x, y, width, height, style: { ...style } })\n }\n\n override scrollRegion(\n x: number,\n y: number,\n width: number,\n height: number,\n delta: number,\n clearCell: CellPatch = {},\n ): void {\n super.scrollRegion(x, y, width, height, delta, clearCell)\n if (this.recording)\n this.ops.push({\n kind: \"scrollRegion\",\n x,\n y,\n width,\n height,\n delta,\n clearCell: cloneCellPatch(clearCell),\n })\n }\n\n override mergeAttrsInRect(\n x: number,\n y: number,\n width: number,\n height: number,\n attrs: CellAttrs,\n underlineColor?: Color,\n ): void {\n super.mergeAttrsInRect(x, y, width, height, attrs, underlineColor)\n if (this.recording)\n this.ops.push({\n kind: \"mergeAttrsInRect\",\n x,\n y,\n width,\n height,\n attrs: { ...attrs },\n underlineColor,\n })\n }\n\n override setRowMeta(row: number, meta: { softWrapped?: boolean; lastContentCol?: number }): void {\n super.setRowMeta(row, meta)\n if (this.recording) this.ops.push({ kind: \"setRowMeta\", row, ...meta })\n }\n\n /**\n * Snapshot the recorded ops as an immutable `RenderPlan`. Subsequent\n * mutations on the recorder do not affect the returned plan.\n */\n toPlan(): RenderPlan {\n return {\n width: this.width,\n height: this.height,\n ops: this.ops.slice(),\n }\n }\n}\n\nfunction cloneCellPatch(cell: SelectableCellPatch): SelectableCellPatch {\n const out: SelectableCellPatch = { ...cell }\n if (cell.attrs) out.attrs = { ...cell.attrs }\n return out\n}\n\n/**\n * Wrap an existing buffer so that calling `.clone()` on it returns a\n * `RecordingBuffer`. The prevBuffer fed into `renderPhase` ends up cloned\n * as the working buffer — wrapping it lets the recorder capture every\n * mutation the existing renderer makes without modifying the renderer.\n *\n * The wrapper is a thin subclass that shares the underlying packed-cell\n * arrays via the regular constructor + clone pathway. Callers should\n * treat the wrapper as opaque — any mutations they make to it before\n * `clone()` propagate normally.\n */\nexport function wrapPrevBufferForRecording(prev: TerminalBuffer): TerminalBuffer {\n return new RecordingPrevBuffer(prev)\n}\n\n/**\n * Subclass of `TerminalBuffer` whose `clone()` returns a\n * `RecordingBuffer`. Used by `wrapPrevBufferForRecording` to seed the\n * Phase 1 capture-and-replay path.\n */\nclass RecordingPrevBuffer extends TerminalBuffer {\n constructor(source: TerminalBuffer) {\n super(source.width, source.height)\n // Copy state from source via clone(). The base TerminalBuffer's clone\n // returns a plain TerminalBuffer, so we copy field-by-field through\n // its public surface. We use a clone from the source then copy its\n // private state in via the subclassed accessor below.\n const copy = source.clone()\n copyBufferState(this, copy)\n }\n\n override clone(): TerminalBuffer {\n const dst = new RecordingBuffer(this.width, this.height)\n copyBufferState(dst, super.clone())\n dst.startRecording()\n return dst\n }\n\n /**\n * Resolve to a `RecordingBuffer` after the renderer has run. The\n * renderer obtains its working buffer by calling `prevBuffer.clone()`,\n * so the recorder is what actually receives the writes — callers can\n * keep a handle to the cloned buffer they fed to the renderer.\n */\n}\n\n/**\n * Replace `dst`'s internal state with a snapshot of `src`. Used when we\n * need to up-cast a freshly-cloned buffer into a subclass instance\n * (RecordingBuffer / RecordingPrevBuffer). Mirrors the field set in\n * `TerminalBuffer.clone()` so any future field added there must be added\n * here too.\n */\nfunction copyBufferState(dst: TerminalBuffer, src: TerminalBuffer): void {\n // We rely on TerminalBuffer being internally consistent: the only way\n // to populate dst with src's state without exposing private fields is\n // to walk the public read API and reapply via setCell/fill. That is\n // O(width*height) and runs once per recorded frame — acceptable for\n // Phase 1 (opt-in flag) and replaced by Phase 2's emit-only renderer.\n for (let y = 0; y < src.height; y++) {\n for (let x = 0; x < src.width; x++) {\n dst.setCell(x, y, src.getCell(x, y))\n }\n }\n for (let y = 0; y < src.height; y++) {\n const meta = src.getRowMeta(y)\n dst.setRowMeta(y, { softWrapped: meta.softWrapped, lastContentCol: meta.lastContentCol })\n }\n // Phase 2 Step 5 of paint-clear-invariant L5 retired\n // `buffer.outlineSnapshots`; cross-frame outline state lives on\n // `RenderPostState` (see render-post-state.ts), owned by `createAg`.\n // No buffer-side state to copy here.\n}\n\n/**\n * Apply a render plan to a target buffer. Ops are partitioned by kind into\n * priority buckets and applied in `COMMIT_PRIORITY` order; within a bucket\n * ops apply in plan order. This makes paint-then-clear unrepresentable\n * once renderers emit ops directly (Phase 2): the commit step decides the\n * order from data, not from walk order.\n *\n * In Phase 1 the recorder captures ops in the same order as the existing\n * imperative renderer, so to prove parity we apply ops in plan order\n * (priority within kind is preserved by stable sort). Phase 2 will switch\n * to true priority-bucketed application once the renderer no longer\n * mutates a buffer directly and ordering is the only ordering signal.\n */\nexport function commitPlan(target: TerminalBuffer, plan: RenderPlan): void {\n if (target.width !== plan.width || target.height !== plan.height) {\n throw new Error(\n `commitPlan: buffer dimensions ${target.width}x${target.height} ` +\n `do not match plan ${plan.width}x${plan.height}`,\n )\n }\n // Phase 1: apply ops in emission order to preserve byte-for-byte parity\n // with the imperative renderer. Phase 2 will switch to bucket-by-kind\n // application after renderers emit ops directly. We expose\n // commitPlanByPriority below so the priority order can be exercised by\n // tests independently.\n for (const op of plan.ops) applyOp(target, op)\n}\n\n/**\n * Apply ops grouped by `COMMIT_PRIORITY`. Within a priority bucket ops\n * apply in plan order. This is the long-run target ordering — used by\n * Phase 2 once renderers emit ops directly. Exposed in Phase 1 so tests\n * can pin the bucket ordering and so that downstream developers can\n * inspect the substrate.\n */\nexport function commitPlanByPriority(target: TerminalBuffer, plan: RenderPlan): void {\n if (target.width !== plan.width || target.height !== plan.height) {\n throw new Error(\n `commitPlanByPriority: buffer dimensions ${target.width}x${target.height} ` +\n `do not match plan ${plan.width}x${plan.height}`,\n )\n }\n const buckets: RenderOp[][] = Array.from(\n { length: Object.keys(COMMIT_PRIORITY).length },\n () => [],\n )\n for (const op of plan.ops) buckets[COMMIT_PRIORITY[op.kind]]!.push(op)\n for (const bucket of buckets) for (const op of bucket) applyOp(target, op)\n}\n\nfunction applyOp(buffer: TerminalBuffer, op: RenderOp): void {\n switch (op.kind) {\n case \"setCell\":\n buffer.setCell(op.x, op.y, op.cell)\n return\n case \"fill\":\n buffer.fill(op.x, op.y, op.width, op.height, op.cell)\n return\n case \"fillBg\":\n buffer.fillBg(op.x, op.y, op.width, op.height, op.bg)\n return\n case \"restyleRegion\":\n buffer.restyleRegion(op.x, op.y, op.width, op.height, op.style)\n return\n case \"scrollRegion\":\n buffer.scrollRegion(op.x, op.y, op.width, op.height, op.delta, op.clearCell ?? {})\n return\n case \"mergeAttrsInRect\":\n buffer.mergeAttrsInRect(op.x, op.y, op.width, op.height, op.attrs, op.underlineColor)\n return\n case \"setRowMeta\": {\n const meta: { softWrapped?: boolean; lastContentCol?: number } = {}\n if (op.softWrapped !== undefined) meta.softWrapped = op.softWrapped\n if (op.lastContentCol !== undefined) meta.lastContentCol = op.lastContentCol\n buffer.setRowMeta(op.row, meta)\n return\n }\n }\n}\n\n/**\n * Read the SILVERY_RENDER_PLAN env var. Returns true (default) UNLESS\n * the user explicitly opts out with `SILVERY_RENDER_PLAN=0` /\n * `SILVERY_RENDER_PLAN=false`.\n *\n * Phase 1: opt-in only (default off).\n * Phase 2: opt-in via flag for testing.\n * Phase 3 (current): default ON, opt-out for one release. The plan\n * capture + sectioned commit is now load-bearing — it runs every\n * frame, the captured plan is committed onto a fresh clone for parity\n * (verified by tests/features/render-plan-fuzz.test.tsx +\n * tests/features/render-plan-production.test.tsx). The opt-out exists\n * solely to roll back if a regression slips through; the next release\n * removes the env var entirely.\n */\nexport function isRenderPlanEnabled(): boolean {\n const env: Record<string, string | undefined> | undefined =\n typeof process !== \"undefined\" ? process.env : undefined\n const v = env?.SILVERY_RENDER_PLAN\n // Default ON. Only \"0\" / \"false\" / \"\" / \"off\" / \"no\" disable it.\n if (v === undefined) return true\n const lower = v.toLowerCase()\n return lower !== \"0\" && lower !== \"false\" && lower !== \"\" && lower !== \"off\" && lower !== \"no\"\n}\n\n// ---------------------------------------------------------------------------\n// Phase 2 Step 1: sectioned RenderPlan\n// ---------------------------------------------------------------------------\n//\n// Per pro/Kimi review (2026-04-27), the structural fix is a plan SHAPE that\n// separates stale-prev cleanup from final-frame paint. Wrong-order paint\n// becomes unrepresentable when the commit step routes ops by SECTION, not\n// by op kind:\n//\n// transfer → cleanup → paint → overlay → postState\n//\n// The bucketed types below encode this at the type level: `ClearOp[]` and\n// `PaintOp[]` are distinct types, so a renderer cannot put a clear into the\n// paint bucket or vice versa — the type system rejects it. This is the L4\n// quality target for km-silvery.paint-clear-invariant.\n//\n// Phase 2 Step 1 (this commit) lands the types + a classifier from flat\n// `RenderOp[]` → `SectionedRenderPlan` + a `commitSectionedPlan` that\n// applies in the structural order. The classifier uses heuristics that hold\n// for the CURRENT renderer (every `buffer.fill` in the pipeline writes\n// space chars and is therefore a clear). Phase 2 Step 2 introduces a\n// `RenderSink` interface so renderers emit explicitly-classified ops\n// instead of going through the heuristic — closes the residual ceremony in\n// the classifier.\n\n/**\n * A clear (destructive cleanup) op. Lives in `cleanupOps`. Removes stale\n * pixels from the prev-frame clone before any final-frame paint covers\n * them. The structural property is \"all clears commit before any paints\"\n * — the commit step enforces this by section ordering, regardless of the\n * order in which the renderer emitted them.\n */\nexport type ClearOp =\n | {\n kind: \"clearRect\"\n x: number\n y: number\n width: number\n height: number\n bg: Color\n selectable?: boolean\n }\n | {\n kind: \"clearCells\"\n x: number\n y: number\n width: number\n height: number\n cell: SelectableCellPatch\n }\n\n/**\n * A paint (constructive content) op. Lives in `paintOps`. WITHIN this\n * section, plan-emission order preserves CSS paint order so later\n * siblings can legitimately cover earlier siblings' overflow text.\n */\nexport type PaintOp =\n | { kind: \"setCell\"; x: number; y: number; cell: SelectableCellPatch }\n | { kind: \"fillBg\"; x: number; y: number; width: number; height: number; bg: Color }\n | {\n kind: \"paintFill\"\n x: number\n y: number\n width: number\n height: number\n cell: SelectableCellPatch\n }\n | {\n kind: \"restyleRegion\"\n x: number\n y: number\n width: number\n height: number\n style: Style\n }\n\n/**\n * A transfer op. Lives in `transferOps`. Shifts existing prev-frame\n * pixels (e.g. scroll Tier 1 buffer-shift). Runs FIRST so subsequent\n * paints into the shifted region land on the right cells.\n */\nexport type TransferOp = {\n kind: \"scrollRegion\"\n x: number\n y: number\n width: number\n height: number\n delta: number\n clearCell?: SelectableCellPatch\n selectable?: boolean\n}\n\n/**\n * An overlay op. Lives in `overlayOps`. Applied AFTER paint — Box attr\n * overlays (bold/underline/strikethrough) merge into existing cells, so\n * those cells must already be written.\n */\nexport type OverlayOp = {\n kind: \"mergeAttrsInRect\"\n x: number\n y: number\n width: number\n height: number\n attrs: CellAttrs\n underlineColor?: Color\n}\n\n/**\n * Buffer book-keeping that survives across frames or that must be in\n * place before any cell write within a row.\n *\n * Selectability is deliberately not post-state. It is encoded into every\n * clear/paint/transfer op that writes cells so sectioned replay stamps the\n * same SELECTABLE_FLAG bits as the direct BufferSink path.\n */\nexport type PostStateOp =\n | { kind: \"setRowMeta\"; row: number; softWrapped?: boolean; lastContentCol?: number }\n | {\n // Outline snapshots — cells captured during the decoration pass so\n // the next frame can restore the under-cells before drawing the new\n // outlines (see decoration-phase.ts).\n //\n // Phase 2 Step 5 (paint-clear-l5-step5-outline-snapshots) hoisted\n // the authoritative storage off the buffer onto `RenderPostState`\n // (render-post-state.ts), owned by `createAg`. The op is preserved\n // in the plan-shape so PlanSink captures outline-snapshot intent\n // for parity tests; `applyPostState` treats it as a no-op (the\n // parity replay buffer is throwaway and the postState is owned\n // by the orchestrator).\n kind: \"setOutlineSnapshots\"\n snapshots: ReadonlyArray<{ x: number; y: number; cell: CellPatch }>\n }\n\n/**\n * Sectioned RenderPlan — the L4 target for km-silvery.paint-clear-invariant.\n *\n * Each section is a typed list, so the type system rejects mixing kinds.\n * Commit applies sections in fixed order:\n *\n * 1. transferOps — shift prev pixels (scroll Tier 1)\n * 2. cleanupOps — destructive clears (excess, overflow, viewport)\n * 3. paintOps — final-frame content (bg fills, text, borders)\n * 4. overlayOps — attr overlays (merge into existing cells)\n * 5. postStateOps — row metadata\n *\n * Wrong-order sibling-stomp becomes unrepresentable: a `ClearOp` cannot\n * end up in `paintOps`. The plan-shape itself is the invariant.\n */\nexport interface SectionedRenderPlan {\n readonly width: number\n readonly height: number\n readonly transferOps: readonly TransferOp[]\n readonly cleanupOps: readonly ClearOp[]\n readonly paintOps: readonly PaintOp[]\n readonly overlayOps: readonly OverlayOp[]\n readonly postStateOps: readonly PostStateOp[]\n}\n\n/**\n * Classify a flat `RenderPlan` (Phase 1 substrate) into a sectioned plan.\n *\n * **CLASSIFIER IS NOT PRODUCTION-CORRECT.** Wiring this through `ag.ts`\n * was attempted (see commit on feat/render-plan-commit-phase1 branch) and\n * produced wrong output for: `absolute-shrink-bg-preserve` (the very bug\n * we're trying to fix structurally), `backdrop-fade`, and\n * `outline-incremental`. The classifier IS sound enough for substrate\n * tests (it produces the same buffer the legacy renderer does on the\n * current parity-test scenes) but a heuristic cannot reliably tell a\n * \"clear\" `buffer.fill` from a \"paint\" `buffer.fill` — both use the same\n * shape (space char + bg). The split is intent, not data.\n *\n * Per pro/Kimi review: Phase 2 Step 3 must introduce an explicit\n * `RenderSink` interface so the renderer marks intent at emission time\n * (`sink.emitClear(...)` vs `sink.emitPaint(...)`). The classifier is\n * retired once Step 3 lands; until then it's a transitional helper for\n * tests of the sectioned commit machinery.\n *\n * Current heuristic (NOT a structural invariant — limitations above):\n *\n * - `fill` with `char === \" \"` (or undefined) → `clearCells`.\n * This is wrong for `render-box.ts`'s opaque bg paints\n * (`buffer.fill(rect, {bg})`), which use space char but ARE paints.\n * The audit test `Phase 2: classifier audit — every fill in current\n * renderer uses space char` pins the char-shape; it does NOT pin\n * intent. Phase 2 Step 3 closes this.\n * - `fillBg` → `fillBg` paint.\n * - `setCell`, `restyleRegion` → paint.\n * - `scrollRegion` → transfer.\n * - `mergeAttrsInRect` → overlay.\n * - `setRowMeta` → postState.\n */\nexport function classifyPlan(flat: RenderPlan): SectionedRenderPlan {\n const transferOps: TransferOp[] = []\n const cleanupOps: ClearOp[] = []\n const paintOps: PaintOp[] = []\n const overlayOps: OverlayOp[] = []\n const postStateOps: PostStateOp[] = []\n\n for (const op of flat.ops) {\n switch (op.kind) {\n case \"scrollRegion\":\n transferOps.push(op)\n break\n case \"fill\": {\n // Heuristic: space char => clear; otherwise paint. See classifier\n // doc comment + tests/features/render-plan-parity.test.tsx for\n // the audit that justifies this.\n const ch = op.cell.char\n if (ch === undefined || ch === \" \") {\n cleanupOps.push({\n kind: \"clearCells\",\n x: op.x,\n y: op.y,\n width: op.width,\n height: op.height,\n cell: op.cell,\n })\n } else {\n paintOps.push({\n kind: \"paintFill\",\n x: op.x,\n y: op.y,\n width: op.width,\n height: op.height,\n cell: op.cell,\n })\n }\n break\n }\n case \"fillBg\":\n case \"setCell\":\n case \"restyleRegion\":\n paintOps.push(op)\n break\n case \"mergeAttrsInRect\":\n overlayOps.push(op)\n break\n case \"setRowMeta\":\n postStateOps.push(op)\n break\n }\n }\n\n return {\n width: flat.width,\n height: flat.height,\n transferOps,\n cleanupOps,\n paintOps,\n overlayOps,\n postStateOps,\n }\n}\n\n/**\n * Apply a sectioned plan to a target buffer in structural order:\n * transfer → cleanup → paint → overlay → postState. Within each section,\n * ops apply in plan-emission order (which preserves CSS paint order for\n * paintOps: later sibling bg can legitimately cover earlier sibling\n * overflow text).\n *\n * This is the L4 commit path: wrong-order sibling-stomp is unrepresentable\n * by construction because a `ClearOp` cannot end up in `paintOps` — the\n * type system rejects it.\n */\nexport function commitSectionedPlan(target: TerminalBuffer, plan: SectionedRenderPlan): void {\n if (target.width !== plan.width || target.height !== plan.height) {\n throw new Error(\n `commitSectionedPlan: buffer dimensions ${target.width}x${target.height} ` +\n `do not match plan ${plan.width}x${plan.height}`,\n )\n }\n for (const op of plan.transferOps) applyTransfer(target, op)\n for (const op of plan.cleanupOps) applyClear(target, op)\n for (const op of plan.paintOps) applyPaint(target, op)\n for (const op of plan.overlayOps) applyOverlay(target, op)\n for (const op of plan.postStateOps) applyPostState(target, op)\n}\n\nfunction applyTransfer(buffer: TerminalBuffer, op: TransferOp): void {\n buffer.scrollRegion(\n op.x,\n op.y,\n op.width,\n op.height,\n op.delta,\n selectablePatch(op.clearCell ?? {}, op.selectable),\n )\n}\n\nfunction applyClear(buffer: TerminalBuffer, op: ClearOp): void {\n if (op.kind === \"clearRect\") {\n buffer.fill(op.x, op.y, op.width, op.height, {\n char: \" \",\n bg: op.bg,\n selectable: op.selectable,\n })\n } else {\n buffer.fill(op.x, op.y, op.width, op.height, op.cell)\n }\n}\n\nfunction applyPaint(buffer: TerminalBuffer, op: PaintOp): void {\n switch (op.kind) {\n case \"setCell\":\n buffer.setCell(op.x, op.y, op.cell)\n return\n case \"fillBg\":\n buffer.fillBg(op.x, op.y, op.width, op.height, op.bg)\n return\n case \"paintFill\":\n buffer.fill(op.x, op.y, op.width, op.height, op.cell)\n return\n case \"restyleRegion\":\n buffer.restyleRegion(op.x, op.y, op.width, op.height, op.style)\n return\n }\n}\n\nfunction applyOverlay(buffer: TerminalBuffer, op: OverlayOp): void {\n buffer.mergeAttrsInRect(op.x, op.y, op.width, op.height, op.attrs, op.underlineColor)\n}\n\nfunction applyPostState(buffer: TerminalBuffer, op: PostStateOp): void {\n switch (op.kind) {\n case \"setRowMeta\": {\n const meta: { softWrapped?: boolean; lastContentCol?: number } = {}\n if (op.softWrapped !== undefined) meta.softWrapped = op.softWrapped\n if (op.lastContentCol !== undefined) meta.lastContentCol = op.lastContentCol\n buffer.setRowMeta(op.row, meta)\n return\n }\n case \"setOutlineSnapshots\":\n // Phase 2 Step 5 of paint-clear-invariant L5: outline snapshots no\n // longer live on `TerminalBuffer`. The authoritative carrier is\n // `RenderPostState` (see render-post-state.ts), owned by `createAg`\n // and written directly by `renderDecorationPass`. The sectioned\n // plan still captures `setOutlineSnapshots` ops in `postStateOps`\n // for parity tests (the captured plan must include every emission)\n // but the parity replay buffer is throwaway, so applying the op\n // here is a no-op. If a future test wants to inspect snapshot ops\n // from a plan, iterate `plan.postStateOps` directly.\n return\n }\n}\n\nfunction selectablePatch(\n cell: SelectableCellPatch,\n selectable: boolean | undefined,\n): SelectableCellPatch {\n return selectable === undefined ? cell : { ...cell, selectable }\n}\n\n/**\n * Run the existing render phase against a recording prevBuffer, returning\n * the rendered buffer along with the captured `RenderPlan`. The plan can\n * then be replayed by `commitPlan` (or `commitPlanByPriority`) into a\n * fresh clone of `prevBuffer` to verify the substrate produces an\n * identical result.\n *\n * Phase 1 entry point. Wired by `renderPhaseWithOptionalPlan` in\n * `render-phase-adapter.ts` when `SILVERY_RENDER_PLAN` is set, and by the\n * parity test directly. Falls back to the standard path when `prevBuffer`\n * is null (fresh render) — the recorder needs an existing prevBuffer to\n * clone into a `RecordingBuffer`. Phase 2 will lift that restriction by\n * making renderers emit ops directly instead of via the recorder.\n */\nexport function captureRenderPlan(\n runRenderer: (prev: TerminalBuffer) => TerminalBuffer,\n prevBuffer: TerminalBuffer,\n): { buffer: TerminalBuffer; plan: RenderPlan } {\n const wrapped = wrapPrevBufferForRecording(prevBuffer)\n const buffer = runRenderer(wrapped)\n if (!(buffer instanceof RecordingBuffer)) {\n throw new Error(\n \"captureRenderPlan: renderer did not call prev.clone() — \" +\n \"RecordingBuffer was not produced. This breaks the Phase 1 capture \" +\n \"contract; use the legacy path or extend the recorder.\",\n )\n }\n return { buffer, plan: buffer.toPlan() }\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. Math ops that have an upstream\n * equivalent (`mixSrgb`, `deemphasize`) are imported from `@silvery/color`\n * directly. The local polarity-aware variant lives in `./color-shim.ts`.\n *\n * @see ./color-shim.ts for `deemphasizeOklchToward` (deletion-pending).\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 pass that walks the tree, collects\n * `data-backdrop-fade` / `data-backdrop-fade-excluded` markers, enforces\n * the single-amount invariant, and resolves the scrim + default colors.\n * It returns a `TerminalPlan` (`CorePlan` + `kittyEnabled`); call\n * `buildCorePlan` for a framework-agnostic plan that strips terminal\n * capability flags. The realizers (`./realize-buffer.ts`,\n * `./realize-kitty.ts`) trust the plan: they do NOT re-walk the tree,\n * re-resolve the scrim, or re-validate amounts. This module is the\n * 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-shim.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\"\nexport type { ColorLevel } from \"@silvery/ansi\"\nimport type { ColorLevel } from \"@silvery/ansi\"\nimport { type HexColor, normalizeHex } from \"./color\"\n\nexport interface BackdropOptions {\n /**\n * Terminal color tier. `\"mono\"` 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 | \"auto\" | string\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\"`. Accepts any string; `normalizeHex` validates\n * and normalizes inside `buildPlan`.\n */\n defaultBg?: 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 * Accepts any string; `normalizeHex` validates and normalizes inside\n * `buildPlan`.\n */\n defaultFg?: 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 * Framework-agnostic description of a backdrop frame. Does NOT carry\n * terminal-specific capability flags (`kittyEnabled`, `colorLevel`). A\n * canvas/DOM target with its own overlay strategy can realize a `CorePlan`\n * without touching terminal fields.\n *\n * Realizers trust the plan: they do NOT re-walk the tree, re-resolve the\n * scrim, or re-validate amounts. `buildCorePlan` / `buildPlan` is the\n * single source of truth.\n *\n * ### Invariants enforced by `buildCorePlan`\n *\n * - `active = includes.length > 0 || excludes.length > 0` whenever a\n * non-zero fade marker is present.\n * - `amount ∈ [0, 1]`, clamped, and identical across all collected rects\n * (single-amount invariant — `mixedAmounts=true` surfaces the dev warn,\n * prod falls back to first).\n * - `scrim` is either a normalized `#rrggbb` hex (with a known theme bg or\n * an explicit `scrimColor`) or `null` (legacy fallback where the buffer\n * realizer mixes fg toward cell.bg without a scrim).\n * - `defaultBg` / `defaultFg` are resolved for stage-2 passes.\n * - `scrimTowardLight` records whether the scrim is on the light side of\n * the luminance threshold. Null-scrim plans default to `false`.\n * - Active plans are frozen (`Object.isFrozen(plan) === true`); so are\n * `includes` and `excludes`. `PlanRect.rect` is cloned so mutating the\n * source AgNode's rect does not affect the plan.\n */\nexport interface CorePlan {\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\n/**\n * Terminal-specific plan. Adds the `kittyEnabled` capability flag the\n * terminal realizers consume. Extends `CorePlan`.\n *\n * - `kittyEnabled` is DERIVED from `options.kittyGraphics === true &&\n * scrim !== null`. The Kitty overlay needs a resolvable tint to\n * composite, so a null-scrim plan can't use the Kitty path even when\n * the caller has the capability enabled. Realizers read this directly\n * (no re-derivation at call sites).\n */\nexport interface TerminalPlan extends CorePlan {\n readonly kittyEnabled: boolean\n}\n\n/**\n * Legacy alias retained so callers importing `Plan` keep compiling. All\n * existing call sites pass a TerminalPlan (via `buildPlan`). New code\n * should prefer `CorePlan` + `TerminalPlan` explicitly.\n */\nexport type Plan = TerminalPlan\n\n/** Sentinel \"nothing to do\" plan — reused across frames to avoid allocations. */\nexport const INACTIVE_PLAN: TerminalPlan = 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 * Syntactic check: does the tree contain any `data-backdrop-fade*`\n * attribute? Returns true even when the marker amounts are 0 (which\n * yields an inactive plan). For \"is the plan active this frame?\" use\n * `buildPlan(...).active`.\n *\n * Used as a gate so we don't clone the buffer every frame when no\n * backdrop markers are mounted. Walks the full tree once (O(N)) — the\n * alternative (tracking dirty markers in the reconciler) is more complex\n * 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/** Inactive CorePlan sentinel (TerminalPlan minus the Kitty field). */\nconst INACTIVE_CORE_PLAN: CorePlan = 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})\n\n/**\n * Stage 1 (core) — build the framework-agnostic `CorePlan`.\n *\n * Pure function of `(tree markers, options)`. No buffer access, no\n * terminal capability knowledge, no console I/O. Suitable for any target\n * (terminal, canvas, DOM) — the realizer decides how to paint the plan.\n *\n * Returns a frozen inactive plan when:\n * - `colorLevel === \"mono\"` (no-op, e.g. monochrome terminal).\n * - The tree has no backdrop markers, OR all markers have `amount <= 0`.\n *\n * Implemented as a thin wrapper over `buildPlan` that strips the\n * terminal-specific `kittyEnabled` field. The stripping (and\n * `kittyGraphics` option suppression) keeps `CorePlan` JSON-serializable\n * to a subset that can be reconstructed on a non-terminal target.\n */\nexport function buildCorePlan(root: AgNode, options?: BackdropOptions): CorePlan {\n // Suppress kittyGraphics so derived flags don't leak through.\n const coreOptions: BackdropOptions | undefined =\n options === undefined ? undefined : { ...options, kittyGraphics: false }\n const plan = buildPlan(root, coreOptions)\n if (!plan.active) return INACTIVE_CORE_PLAN\n // Strip the terminal-specific field. Using object-rest to drop the key\n // entirely (so JSON.stringify doesn't include it) rather than setting it\n // to undefined.\n const { kittyEnabled: _kittyEnabled, ...core } = plan\n return Object.freeze(core)\n}\n\n/**\n * Stage 1 (terminal) — build the immutable `TerminalPlan`.\n *\n * Same as `buildCorePlan` plus the derived `kittyEnabled` capability flag.\n * `kittyEnabled = options.kittyGraphics === true && scrim !== null` — the\n * overlay needs a resolvable tint to composite, so a null-scrim plan\n * cannot use the Kitty path even when the caller has the capability\n * enabled.\n */\nexport function buildPlan(root: AgNode, options?: BackdropOptions): TerminalPlan {\n const colorLevel: ColorLevel = options?.colorLevel ?? \"truecolor\"\n if (colorLevel === \"mono\") 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 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\"\n ? normalizeHex(scrimColorOpt)\n : 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).\n const scrimTowardLight = isLightScrim(scrim)\n\n // Default fg fallback: opposite of the scrim polarity.\n const defaultFg =\n normalizeHex(options?.defaultFg) ??\n (scrim === null ? null : scrimTowardLight ? DARK_SCRIM : LIGHT_SCRIM)\n\n // Single-amount invariant: one scrim image per frame at one alpha.\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 // Freeze the rect arrays so realizers (and external observers) cannot\n // mutate them. The plan itself is also frozen below.\n Object.freeze(includes)\n Object.freeze(excludes)\n\n return Object.freeze({\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 sourceRect = node.screenRect ?? node.scrollRect ?? node.boxRect\n if (sourceRect && sourceRect.width > 0 && sourceRect.height > 0) {\n // Clone the rect — the source aliases the AgNode's live `screenRect`\n // / `scrollRect` / `boxRect`, which the layout phase mutates next\n // frame. Without a clone, mid-frame layout changes would silently\n // shift the plan's rects.\n const rect = Object.freeze({\n x: sourceRect.x,\n y: sourceRect.y,\n width: sourceRect.width,\n height: sourceRect.height,\n })\n const inc = parseFade(includeRaw)\n if (inc !== null) {\n includes.push(Object.freeze({ rect }))\n includeAmounts.push(inc)\n }\n const exc = parseFade(excludeRaw)\n if (exc !== null) {\n excludes.push(Object.freeze({ 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` polarity-aware deemphasize shim.\n *\n * `@silvery/color` exports `deemphasize` (dark-only, drifts L toward 0) and\n * `mixSrgb` directly. Use those whenever possible. The light-theme-aware\n * variant `deemphasizeOklchToward` (drifts L toward 0 OR 1 by polarity) is\n * NOT yet upstream — this file ships it locally.\n *\n * **Deletion condition**: when `@silvery/color` exports\n * `deemphasizeOklchToward`, delete this file entirely and update\n * `./realize-buffer.ts` to import directly from `@silvery/color`. Audit\n * with `rg \"from .*color-shim\"` — should drop to zero.\n *\n * @see ./color.ts for hex↔rgb adapter helpers and `HexColor` type.\n */\n\nimport { hexToOklch, oklchToHex } from \"@silvery/color\"\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.\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 */\nexport function deemphasizeOklchToward(hex: string, amount: number, towardLight: boolean): string {\n const o = hexToOklch(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 oklchToHex({\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/**\n * Dark-theme deemphasize. Convenience alias so existing callers don't\n * have to thread `towardLight=false` everywhere. New code should prefer\n * `deemphasizeOklchToward` directly so the polarity is explicit at the\n * call site, OR `deemphasize` from `@silvery/color` (byte-identical).\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 * `forEachBackdropCell` 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 forEachBackdropCell(\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 `forEachBackdropCell` 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 @silvery/color for `mixSrgb`.\n * @see ./color-shim.ts for `deemphasizeOklchToward` (polarity-aware,\n * not yet upstream).\n * @see ./region.ts for the shared include/exclude region walker.\n */\n\nimport { mixSrgb } from \"@silvery/color\"\nimport type { TerminalBuffer } from \"../../buffer\"\nimport { isLikelyEmoji } from \"../../unicode\"\nimport { colorToHex, type HexColor, hexToRgb } from \"./color\"\nimport { deemphasizeOklchToward } from \"./color-shim\"\nimport { DARK_SCRIM, LIGHT_SCRIM, type Plan } from \"./plan\"\nimport { forEachBackdropCell } 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 `forEachBackdropCell` 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 forEachBackdropCell(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 = 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 `forEachBackdropCell` 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 { forEachBackdropCell } 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 * `forEachBackdropCell`), 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 forEachBackdropCell(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 tree walk. Collects\n * `data-backdrop-fade` / `data-backdrop-fade-excluded` markers,\n * enforces the single-amount invariant, resolves the scrim + default\n * colors, and derives `plan.kittyEnabled` from\n * `options.kittyGraphics` + scrim availability. Returns a\n * `TerminalPlan` (`CorePlan` + `kittyEnabled`); call `buildCorePlan`\n * for a framework-agnostic plan. 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-shim.ts — local `deemphasizeOklchToward` (polarity-aware\n * dark/light variant). Delete once upstream\n * `@silvery/color` exports the polarity API; until\n * then `mixSrgb` + `deemphasize` are imported from\n * upstream directly.\n * ./index.ts — this file: applyBackdrop orchestrator + barrel\n *\n * ## Naming policy\n *\n * - Public surface uses the `Backdrop` prefix: `BackdropOptions`,\n * `BackdropResult`, `applyBackdrop`, `hasBackdropMarkers`,\n * `forEachBackdropCell` — disambiguates from \"fade\" which is too\n * generic at the framework level (animation, theme, etc.).\n * - `Plan` / `PlanRect` / `CorePlan` / `TerminalPlan` are intentionally\n * UNPREFIXED. The pass already lives in `./pipeline/backdrop/`; the\n * namespace makes the prefix redundant and the names cleaner.\n * - `hasBackdropMarkers` is purely SYNTACTIC — true whenever the tree\n * has any `data-backdrop-fade*` attribute, even with `amount=0` (which\n * produces an inactive plan). Callers that need \"is the plan active\n * this frame?\" should test `plan.active` after `buildPlan`.\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 — this is an INTERNAL pipeline module. Only export\n// what callers (renderer.ts, ag.ts, scheduler.ts, tests-by-public-API)\n// actually need. Internals like `forEachBackdropCell`, `mixSrgb`, and\n// `deemphasizeOklch[Toward]` live in their own files; tests that need\n// them import from those paths directly.\n//\n// See km-silvery.backdrop-hardening.slim-barrel.\nexport {\n buildCorePlan,\n buildPlan,\n DEFAULT_AMOUNT,\n hasBackdropMarkers,\n INACTIVE_PLAN,\n type BackdropOptions,\n type ColorLevel,\n type CorePlan,\n type Plan,\n type PlanRect,\n type TerminalPlan,\n} from \"./plan\"\nexport { type HexColor, normalizeHex } from \"./color\"\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; plan.amount ` +\n `collapsed to the first observed (${plan.amount}). Visuals may differ ` +\n `from intent. 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 { 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 classifyPlan,\n commitSectionedPlan,\n isRenderPlanEnabled,\n RecordingBuffer,\n wrapPrevBufferForRecording,\n} from \"./pipeline/render-plan\"\nimport { withPlanCapture } from \"./pipeline/render-sink\"\nimport { createRenderPostState, type RenderPostState } from \"./pipeline/render-post-state\"\nexport { createRenderPostState, type RenderPostState } from \"./pipeline/render-post-state\"\nimport { applyBackdrop, hasBackdropMarkers, type ColorLevel } 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 * Override post-state carrier (outline snapshots) for this render. When\n * provided, both reads (clearPreviousOutlines at the start of the render)\n * and writes (renderDecorationPass after content) target this carrier\n * instead of the Ag-internal `_postState`.\n *\n * Required for callers that create a fresh `Ag` instance per frame\n * (renderer.ts, scheduler.ts) — without it, every frame starts with an\n * empty snapshot list and stale outline pixels from `prevBuffer.clone()`\n * leak through the incremental render. Long-lived-Ag callers\n * (runtime/renderer.ts) can omit this and let the Ag manage `_postState`\n * internally.\n *\n * Mutated in place: after `render()` returns, the carrier holds the\n * current frame's outline snapshots, ready to be passed in again on the\n * next frame so `clearPreviousOutlines` can restore those cells.\n */\n postState?: RenderPostState\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 `\"ansi16\"` at ANSI 16 tier\n * (SGR 2 dim) or `\"mono\"` to disable the pass entirely.\n */\n colorLevel?: ColorLevel\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 * Post-state carrier with this frame's snapshots (outline cells, etc.).\n *\n * When the caller passed `options.postState` into `render()`, this is the\n * SAME reference (mutated in place) — exposed here so the call site doesn't\n * have to hold its own carrier separately. When no carrier was passed, this\n * is the Ag-internal `_postState`.\n *\n * Per-frame-Ag callers (renderer.ts, scheduler.ts) should hold their own\n * carrier and pass it back in next frame; reading from the result is fine\n * but is the same object they already own.\n */\n readonly postState: RenderPostState\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?: ColorLevel\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: ColorLevel = 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 // Cross-frame post-state (outline snapshots, etc.). Phase 2 Step 5 of\n // paint-clear-invariant L5 hoisted outline snapshots off `TerminalBuffer`\n // onto a dedicated carrier owned here, alongside `_prevBuffer`. The\n // decoration phase reads/writes this object directly; the sink layer\n // captures parallel post-state ops only for plan-shape parity tests.\n // Reset semantics mirror `_prevBuffer`: cleared by `resetBuffer()` so a\n // resize discards stale snapshots (their cell coordinates would be wrong\n // at the new dimensions).\n let _postState: RenderPostState = createRenderPostState()\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 // Resolve the post-state carrier for this render.\n //\n // Three cases:\n // 1. `opts.fresh` → throw-away carrier. Fresh renders must not consume\n // the cross-frame snapshots (there's no prev buffer to clear from)\n // and must not stomp the carried-forward snapshots (the next\n // incremental render still needs them).\n // 2. `opts.postState` provided → use the caller-owned carrier. Required\n // for renderer.ts / scheduler.ts which create a fresh Ag per frame\n // and hold the carrier alongside `prevBuffer` at the instance level.\n // 3. Default → use the Ag-internal `_postState`. Correct for\n // long-lived-Ag callers (runtime/renderer.ts) that reuse the same\n // Ag instance across frames.\n const postState: RenderPostState = opts?.fresh\n ? createRenderPostState()\n : (opts?.postState ?? _postState)\n\n let tContent: number\n let buffer: TerminalBuffer\n {\n const t = performance.now()\n // Phase 2 Step 7: SILVERY_RENDER_PLAN wires the plan/commit substrate\n // at the production entry point. Closes the Check 1 gap from Phase 1\n // derisking (the flag was previously read by tests only).\n //\n // When enabled, the render-phase walk is wrapped in `withPlanCapture`\n // which TeeSinks every emission to BOTH the BufferSink (for reads\n // and direct mutation — cells are still on the buffer for getCellBg /\n // outline snapshot capture / dirty-row inspection) AND a frame-shared\n // PlanSink that builds the SectionedRenderPlan in lock-step.\n //\n // After the walk, the sectioned plan is committed onto a fresh frame\n // buffer and becomes the authoritative result. The BufferSink-mutated\n // buffer remains the read substrate during the walk so renderers that\n // still need intra-frame reads keep working, but final pixels now use\n // the structural order: transfer → cleanup → paint → overlay →\n // post-state. That makes cleanup/paint interleaving bugs\n // unrepresentable at the frame boundary.\n if (isRenderPlanEnabled()) {\n const layout = root.boxRect\n if (!layout) {\n throw new Error(\"doRender: SILVERY_RENDER_PLAN enabled but root has no boxRect\")\n }\n const captured = withPlanCapture(layout.width, layout.height, () =>\n renderPhase(root, prevBuffer, ctx, postState),\n )\n void captured.result\n const replay =\n prevBuffer && prevBuffer.width === layout.width && prevBuffer.height === layout.height\n ? prevBuffer.clone()\n : new TerminalBuffer(layout.width, layout.height)\n commitSectionedPlan(replay, captured.plan)\n buffer = replay\n } else {\n buffer = renderPhase(root, prevBuffer, ctx, postState)\n }\n tContent = performance.now() - t\n log.debug?.(`content: ${tContent.toFixed(2)}ms`)\n }\n // Substrate references retained for the parity-test suite + Phase 6/7.\n void wrapPrevBufferForRecording\n void RecordingBuffer\n void classifyPlan\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, postState }\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 postState: result.postState,\n }\n },\n\n resetBuffer() {\n _prevBuffer = null\n // Outline snapshots reference cell coordinates in the OLD buffer's\n // dimensions; if a resize is the reason resetBuffer is called, the\n // snapshots would point at the wrong cells. Reset both together.\n _postState = createRenderPostState()\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({ colorLevel: 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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"}
|