silvery 0.19.2 → 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.
Files changed (218) hide show
  1. package/README.md +9 -4
  2. package/dist/Text-Lq0dmj8-.mjs +239 -0
  3. package/dist/Text-Lq0dmj8-.mjs.map +1 -0
  4. package/dist/UPNG-Bo33r8rA.mjs +3 -0
  5. package/dist/UPNG-DosRPdF4.mjs +5075 -0
  6. package/dist/UPNG-DosRPdF4.mjs.map +1 -0
  7. package/dist/__vite-browser-external-2447137e-D_JM6skp.mjs +6 -0
  8. package/dist/__vite-browser-external-2447137e-D_JM6skp.mjs.map +1 -0
  9. package/dist/{animation-Cn64yepo.mjs → animation-ZMN2_XKv.mjs} +2 -2
  10. package/dist/animation-ZMN2_XKv.mjs.map +1 -0
  11. package/dist/{ansi-Cc33mW54.d.mts → ansi-2Xn0yatP.d.mts} +1 -1
  12. package/dist/{ansi-Cc33mW54.d.mts.map → ansi-2Xn0yatP.d.mts.map} +1 -1
  13. package/dist/{ansi-CLOitHKx.mjs → ansi-D1KQMAbf.mjs} +1 -1
  14. package/dist/{ansi-CLOitHKx.mjs.map → ansi-D1KQMAbf.mjs.map} +1 -1
  15. package/dist/ansi-yC4RyBNY.mjs +22441 -0
  16. package/dist/ansi-yC4RyBNY.mjs.map +1 -0
  17. package/dist/apng-CR08rIaH.mjs +58 -0
  18. package/dist/apng-CR08rIaH.mjs.map +1 -0
  19. package/dist/apng-DaHfVaVI.mjs +3 -0
  20. package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
  21. package/dist/assets/skia.darwin-arm64-DQs5sT6N.node +0 -0
  22. package/dist/backend-B-WYLUib.mjs +13396 -0
  23. package/dist/backend-B-WYLUib.mjs.map +1 -0
  24. package/dist/backends-CUtan80W.mjs +3 -0
  25. package/dist/backends-DIVYzKqd.mjs +1083 -0
  26. package/dist/backends-DIVYzKqd.mjs.map +1 -0
  27. package/dist/bound-term-0sPrrzH1.d.mts +4640 -0
  28. package/dist/bound-term-0sPrrzH1.d.mts.map +1 -0
  29. package/dist/canvas-1v7dPT-_.mjs +3 -0
  30. package/dist/canvas-CSuPOMNt.mjs +1442 -0
  31. package/dist/canvas-CSuPOMNt.mjs.map +1 -0
  32. package/dist/{chunk-Vs_PY4HZ.mjs → chunk-BSw8zbkd.mjs} +1 -1
  33. package/dist/cli-dvo0r2fs.mjs +4 -0
  34. package/dist/compare-CQodSH4G.mjs +376 -0
  35. package/dist/compare-CQodSH4G.mjs.map +1 -0
  36. package/dist/compare-DHlcxEYA.mjs +3 -0
  37. package/dist/context-BU5LkkIy.mjs.map +1 -1
  38. package/dist/devtools-CJdt5H0X.mjs +2 -0
  39. package/dist/{devtools-DxkSLXDA.mjs → devtools-DcQjgyjL.mjs} +5 -4
  40. package/dist/{devtools-DxkSLXDA.mjs.map → devtools-DcQjgyjL.mjs.map} +1 -1
  41. package/dist/easing-BI-ASGMO.d.mts +24 -0
  42. package/dist/easing-BI-ASGMO.d.mts.map +1 -0
  43. package/dist/{eta-Bb3RH3wh.mjs → eta-CJlGH06n.mjs} +1 -1
  44. package/dist/{eta-Bb3RH3wh.mjs.map → eta-CJlGH06n.mjs.map} +1 -1
  45. package/dist/flexily-zero-adapter-C3Vj0fPt.mjs +306 -0
  46. package/dist/flexily-zero-adapter-C3Vj0fPt.mjs.map +1 -0
  47. package/dist/{flexily-zero-adapter-CMxXhdOL.mjs → flexily-zero-adapter-C4lW_Ov5.mjs} +1 -1
  48. package/dist/fonts-BFmhXDv7.mjs +88 -0
  49. package/dist/fonts-BFmhXDv7.mjs.map +1 -0
  50. package/dist/gif-C_AjaT9d.mjs +188 -0
  51. package/dist/gif-C_AjaT9d.mjs.map +1 -0
  52. package/dist/gif-DaC4XrxA.mjs +3 -0
  53. package/dist/gifenc-BOUT-KFB.mjs +730 -0
  54. package/dist/gifenc-BOUT-KFB.mjs.map +1 -0
  55. package/dist/image-C2Birh2x.mjs +1252 -0
  56. package/dist/image-C2Birh2x.mjs.map +1 -0
  57. package/dist/index-BUMxS65f.d.mts +453 -0
  58. package/dist/index-BUMxS65f.d.mts.map +1 -0
  59. package/dist/{index-D3saHouR.d.mts → index-CSQf13CI.d.mts} +1057 -1133
  60. package/dist/index-CSQf13CI.d.mts.map +1 -0
  61. package/dist/{index-BXslOebb.d.mts → index-Cl9KKjQ_.d.mts} +4919 -3921
  62. package/dist/index-Cl9KKjQ_.d.mts.map +1 -0
  63. package/dist/index-XbNrPhWl.d.mts +336 -0
  64. package/dist/index-XbNrPhWl.d.mts.map +1 -0
  65. package/dist/index.d.mts +8 -5
  66. package/dist/index.d.mts.map +1 -1
  67. package/dist/index.mjs +14 -12
  68. package/dist/index.mjs.map +1 -1
  69. package/dist/key-mapping-CS-YD_cD.mjs +132 -0
  70. package/dist/key-mapping-CS-YD_cD.mjs.map +1 -0
  71. package/dist/key-mapping-Yn-Jgrij.mjs +3 -0
  72. package/dist/{layout-engine-B6Cdz1yZ.mjs → layout-engine-C07LEXWT.mjs} +1 -1
  73. package/dist/layout-engine-C2px0RJE.mjs +67 -0
  74. package/dist/layout-engine-C2px0RJE.mjs.map +1 -0
  75. package/dist/layout-signals-Cnw6xk8Q.mjs +988 -0
  76. package/dist/layout-signals-Cnw6xk8Q.mjs.map +1 -0
  77. package/dist/mouse-events-Dki3ISIp.mjs +1044 -0
  78. package/dist/mouse-events-Dki3ISIp.mjs.map +1 -0
  79. package/dist/{multi-progress-Bq9Oi_WI.mjs → multi-progress-CIRjrzma.mjs} +3 -3
  80. package/dist/{multi-progress-Bq9Oi_WI.mjs.map → multi-progress-CIRjrzma.mjs.map} +1 -1
  81. package/dist/{multi-progress-DAQC7eap.d.mts → multi-progress-DHZ2xUT2.d.mts} +2 -2
  82. package/dist/{multi-progress-DAQC7eap.d.mts.map → multi-progress-DHZ2xUT2.d.mts.map} +1 -1
  83. package/dist/{node-BeWlnCPY.mjs → node-CjM5Rt-M.mjs} +4 -4
  84. package/dist/node-CjM5Rt-M.mjs.map +1 -0
  85. package/dist/playwright-D5YiZcNS.mjs +76397 -0
  86. package/dist/playwright-D5YiZcNS.mjs.map +1 -0
  87. package/dist/png-codec-Dp84742B.mjs +36 -0
  88. package/dist/png-codec-Dp84742B.mjs.map +1 -0
  89. package/dist/png-codec-QwOtJ8Zs.mjs +3 -0
  90. package/dist/progress-DB_Xo071.mjs +675 -0
  91. package/dist/progress-DB_Xo071.mjs.map +1 -0
  92. package/dist/{progress-bar-CXE5Qfkd.mjs → progress-bar-oJwq22CR.mjs} +4 -4
  93. package/dist/{progress-bar-CXE5Qfkd.mjs.map → progress-bar-oJwq22CR.mjs.map} +1 -1
  94. package/dist/rasterizer-BRXrDdWx.mjs +3 -0
  95. package/dist/rasterizer-CpEhJvdR.mjs +296 -0
  96. package/dist/rasterizer-CpEhJvdR.mjs.map +1 -0
  97. package/dist/reconciler-DldIJB93.mjs +2083 -0
  98. package/dist/reconciler-DldIJB93.mjs.map +1 -0
  99. package/dist/{render-string-CDCeYkS3.mjs → render-string-BcoCpjCB.mjs} +1 -1
  100. package/dist/{render-string-Darrg7ku.mjs → render-string-DkQacASz.mjs} +2707 -549
  101. package/dist/render-string-DkQacASz.mjs.map +1 -0
  102. package/dist/resvg-js-DkOndZI3.mjs +203 -0
  103. package/dist/resvg-js-DkOndZI3.mjs.map +1 -0
  104. package/dist/runtime.d.mts +3 -2
  105. package/dist/runtime.mjs +3 -3
  106. package/dist/schemes-JjNp4aSl.mjs +2611 -0
  107. package/dist/schemes-JjNp4aSl.mjs.map +1 -0
  108. package/dist/{spinner-CGo34vyR.d.mts → spinner-CZINHpkV.d.mts} +2 -2
  109. package/dist/{spinner-CGo34vyR.d.mts.map → spinner-CZINHpkV.d.mts.map} +1 -1
  110. package/dist/{spinner-CeOmcuw_.mjs → spinner-D9lrHr8s.mjs} +7 -7
  111. package/dist/spinner-D9lrHr8s.mjs.map +1 -0
  112. package/dist/src-5w9QR6_8.mjs +1071 -0
  113. package/dist/src-5w9QR6_8.mjs.map +1 -0
  114. package/dist/src-BNTToU7l.mjs +4387 -0
  115. package/dist/src-BNTToU7l.mjs.map +1 -0
  116. package/dist/{src-CF-6UN01.mjs → src-BR4xNwdG.mjs} +10436 -2622
  117. package/dist/src-BR4xNwdG.mjs.map +1 -0
  118. package/dist/{types-Bk2yw9Qj.mjs → src-DKp-_OFG.mjs} +34 -94
  119. package/dist/src-DKp-_OFG.mjs.map +1 -0
  120. package/dist/src-bt8wSrfJ.mjs +258 -0
  121. package/dist/src-bt8wSrfJ.mjs.map +1 -0
  122. package/dist/src-e33Y6kNJ.mjs +3 -0
  123. package/dist/src-iDwu25UD.mjs +1814 -0
  124. package/dist/src-iDwu25UD.mjs.map +1 -0
  125. package/dist/steps-Bp2uNqnn.d.mts +202 -0
  126. package/dist/steps-Bp2uNqnn.d.mts.map +1 -0
  127. package/dist/svg-15lZZzxq.mjs +486 -0
  128. package/dist/svg-15lZZzxq.mjs.map +1 -0
  129. package/dist/svg-Cz0UXcDj.mjs +255 -0
  130. package/dist/svg-Cz0UXcDj.mjs.map +1 -0
  131. package/dist/svg-DY72a4HK.mjs +3 -0
  132. package/dist/svg-g1D6ErwR.d.mts +82 -0
  133. package/dist/svg-g1D6ErwR.d.mts.map +1 -0
  134. package/dist/term.d.mts +3 -0
  135. package/dist/term.mjs +9 -0
  136. package/dist/term.mjs.map +1 -0
  137. package/dist/theme.d.mts +95 -2
  138. package/dist/theme.d.mts.map +1 -0
  139. package/dist/theme.mjs +9 -3
  140. package/dist/theme.mjs.map +1 -0
  141. package/dist/{types-BH_v3iMT.d.mts → types-kt_fKR37.d.mts} +2 -15
  142. package/dist/types-kt_fKR37.d.mts.map +1 -0
  143. package/dist/ui/animation.d.mts +2 -1
  144. package/dist/ui/animation.mjs +1 -1
  145. package/dist/ui/ansi.d.mts +1 -1
  146. package/dist/ui/ansi.mjs +1 -1
  147. package/dist/ui/cli.d.mts +3 -3
  148. package/dist/ui/cli.mjs +5 -5
  149. package/dist/ui/display.d.mts +1 -1
  150. package/dist/ui/image.d.mts +2 -2
  151. package/dist/ui/image.mjs +2 -2
  152. package/dist/ui/input.d.mts +1 -1
  153. package/dist/ui/input.mjs +4 -2
  154. package/dist/ui/input.mjs.map +1 -1
  155. package/dist/ui/progress.d.mts +5 -249
  156. package/dist/ui/progress.mjs +5 -858
  157. package/dist/ui/react.d.mts +1 -1
  158. package/dist/ui/react.mjs +2 -2
  159. package/dist/ui/recording-chrome-react.d.mts +21 -0
  160. package/dist/ui/recording-chrome-react.d.mts.map +1 -0
  161. package/dist/ui/recording-chrome-react.mjs +105 -0
  162. package/dist/ui/recording-chrome-react.mjs.map +1 -0
  163. package/dist/ui/recording-chrome.d.mts +2 -0
  164. package/dist/ui/recording-chrome.mjs +2 -0
  165. package/dist/ui/utils.mjs +1 -1
  166. package/dist/ui/wrappers.d.mts +3 -3
  167. package/dist/ui/wrappers.mjs +2 -2
  168. package/dist/ui.d.mts +7 -6
  169. package/dist/ui.mjs +8 -7
  170. package/dist/{useLatest-Bg2x4bfP.d.mts → useLatest-DRDDVwjh.d.mts} +5 -25
  171. package/dist/useLatest-DRDDVwjh.d.mts.map +1 -0
  172. package/dist/{with-text-input-CRfoiFFG.d.mts → with-text-input-YeohVLeo.d.mts} +4 -55
  173. package/dist/with-text-input-YeohVLeo.d.mts.map +1 -0
  174. package/dist/wrapper-C70ATkVv.mjs +3527 -0
  175. package/dist/wrapper-C70ATkVv.mjs.map +1 -0
  176. package/dist/{wrappers-UTADQkSY.mjs → wrappers-BCUYITrY.mjs} +5 -157
  177. package/dist/wrappers-BCUYITrY.mjs.map +1 -0
  178. package/dist/{yoga-adapter-8oRGRw8V.mjs → yoga-adapter-BnZX1PAY.mjs} +28 -2
  179. package/dist/yoga-adapter-BnZX1PAY.mjs.map +1 -0
  180. package/dist/yoga-adapter-DxgsQ_gg.mjs +2 -0
  181. package/dist/zipBundle-3nqeDRtm.mjs +3 -0
  182. package/dist/zipBundle-VNAYFmqJ.mjs +2003 -0
  183. package/dist/zipBundle-VNAYFmqJ.mjs.map +1 -0
  184. package/package.json +20 -9
  185. package/dist/animation-Cn64yepo.mjs.map +0 -1
  186. package/dist/cli-BKp0YtBD.mjs +0 -4
  187. package/dist/devtools-9QY4teqI.mjs +0 -2
  188. package/dist/flexily-zero-adapter-BlQa46nr.mjs +0 -3385
  189. package/dist/flexily-zero-adapter-BlQa46nr.mjs.map +0 -1
  190. package/dist/image-CTII5QWI.mjs +0 -477
  191. package/dist/image-CTII5QWI.mjs.map +0 -1
  192. package/dist/index-BXslOebb.d.mts.map +0 -1
  193. package/dist/index-BnA7mNpo.d.mts +0 -175
  194. package/dist/index-BnA7mNpo.d.mts.map +0 -1
  195. package/dist/index-D3saHouR.d.mts.map +0 -1
  196. package/dist/layout-engine-ClUgv6jB.mjs +0 -50
  197. package/dist/layout-engine-ClUgv6jB.mjs.map +0 -1
  198. package/dist/node-BeWlnCPY.mjs.map +0 -1
  199. package/dist/reconciler-Cwgm8hRR.mjs +0 -8459
  200. package/dist/reconciler-Cwgm8hRR.mjs.map +0 -1
  201. package/dist/render-string-Darrg7ku.mjs.map +0 -1
  202. package/dist/spinner-CeOmcuw_.mjs.map +0 -1
  203. package/dist/src-B5GjfG7g.mjs +0 -4305
  204. package/dist/src-B5GjfG7g.mjs.map +0 -1
  205. package/dist/src-CChwjk0Z.mjs +0 -738
  206. package/dist/src-CChwjk0Z.mjs.map +0 -1
  207. package/dist/src-CF-6UN01.mjs.map +0 -1
  208. package/dist/src-NCKb8kE5.mjs +0 -2660
  209. package/dist/src-NCKb8kE5.mjs.map +0 -1
  210. package/dist/types-BH_v3iMT.d.mts.map +0 -1
  211. package/dist/types-Bk2yw9Qj.mjs.map +0 -1
  212. package/dist/ui/progress.d.mts.map +0 -1
  213. package/dist/ui/progress.mjs.map +0 -1
  214. package/dist/useLatest-Bg2x4bfP.d.mts.map +0 -1
  215. package/dist/with-text-input-CRfoiFFG.d.mts.map +0 -1
  216. package/dist/wrappers-UTADQkSY.mjs.map +0 -1
  217. package/dist/yoga-adapter-8oRGRw8V.mjs.map +0 -1
  218. package/dist/yoga-adapter-D_CcxSt5.mjs +0 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout-signals-Cnw6xk8Q.mjs","names":["rectEqual","rectEqual"],"sources":["../packages/ag/src/types.ts","../packages/ag/src/wrap-measurer.ts","../packages/ag/src/place-floating.ts","../packages/ag/src/layout-signals.ts"],"sourcesContent":["/**\n * Silvery Types\n *\n * Core types for the Silvery renderer architecture.\n */\n\nimport type { DragEventProps } from \"./drag-event-types\"\nimport type { FocusEventProps } from \"./focus-events\"\nimport type { LayoutNode } from \"./layout-types\"\nimport type { MouseEventProps } from \"./mouse-event-types\"\n\n// ============================================================================\n// Layout Types\n// ============================================================================\n\n// ============================================================================\n// Selection Types\n// ============================================================================\n\n/**\n * CSS user-select equivalent for controlling text selectability.\n * - \"auto\": inherit from parent (root resolves to \"text\")\n * - \"none\": not selectable\n * - \"text\": force selectable (overrides parent \"none\")\n * - \"contain\": selectable, but selection cannot escape this node's bounds\n *\n * Mouse selection is document/tree-aware by default: the active scope is the\n * nearest common selectable ancestor of the drag anchor and focus. `contain`\n * keeps its CSS meaning as an explicit hard boundary.\n */\nexport type UserSelect = \"auto\" | \"none\" | \"text\" | \"contain\"\n\n// ============================================================================\n// Layout Types\n// ============================================================================\n\n/**\n * A rectangle with position and size.\n * All values are in terminal columns/rows (integers).\n */\nexport interface Rect {\n /** X position (0-indexed terminal column) */\n x: number\n /** Y position (0-indexed terminal row) */\n y: number\n /** Width in terminal columns */\n width: number\n /** Height in terminal rows */\n height: number\n}\n\n/**\n * Check if two rects are equal (same position and size).\n */\nexport function rectEqual(a: Rect | null, b: Rect | null): boolean {\n if (a === b) return true\n if (!a || !b) return false\n return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height\n}\n\n// ============================================================================\n// Caret Types (layout output — peer of boxRect/scrollRect/screenRect)\n// ============================================================================\n\n/**\n * Terminal cursor shape (DECSCUSR).\n *\n * @deprecated Target-specific. Lives in core only as a back-compat alias for the\n * `CursorOffset.shape` deprecation cycle (see {@link CursorOffset.shape}). The\n * canonical home is `@silvery/ag-term/output#CursorShape`. Cross-target\n * renderers (canvas / DOM) must not branch on this enum — instead, they read\n * the focused-editable bit from `LayoutSignals` and map to whatever caret\n * concept their target supports. Removed in the next cycle.\n *\n * Lower-case names match the DECSCUSR vocabulary: `block` (steady #2),\n * `underline` (steady #4), `bar` (steady #6).\n */\nexport type CursorShape = \"block\" | \"underline\" | \"bar\"\n\n/**\n * Component-relative caret position declared as a Box prop.\n *\n * When set on a Box, the layout phase computes the absolute terminal\n * coordinates by adding the parent's `scrollRect` + the box's border + padding\n * + this offset. The result is exposed via `LayoutSignals.cursorRect` and read\n * by the scheduler's cursor-suffix emission. The caret naming reflects the\n * cross-target nature: in the terminal it manifests as the hardware cursor,\n * but on canvas/DOM targets it's the text-input caret rectangle.\n *\n * This is the \"caret as layout output\" path — it bypasses the React effect\n * chain entirely (`useCursor` → `useScrollRect` → `setCursorState`) so the\n * very first frame after mount emits the correct caret positioning ANSI.\n * See bead `km-silvery.view-as-layout-output` (Phase 2),\n * `km-silvery.cursor-invariants`, and `km-silvercode.cursor-startup-position`.\n */\nexport interface CursorOffset {\n /** Column offset within the box's content area (0-indexed) */\n col: number\n /** Row offset within the box's content area (0-indexed) */\n row: number\n /** Whether the caret should be visible. Default: true */\n visible?: boolean\n /**\n * Terminal cursor shape (DECSCUSR).\n *\n * @deprecated Target-specific — DO NOT use in new code. The terminal layer\n * (`@silvery/ag-term`) derives the shape from focus + editable state at\n * scheduler/output time via the caretStyle map. Cross-target consumers\n * (canvas / DOM) ignore this field. Accepted for one cycle for back-compat;\n * removed in the next major. See `km-silvery.cursor-invariants` invariant 6.\n */\n shape?: CursorShape\n}\n\n/**\n * Semantic selection intent declared on a Box — the user's \"selected\n * substring\" within this node's text content, expressed as character offsets.\n *\n * This is the **input** half of the selection-as-overlay model (Phase 4b of\n * `km-silvery.view-as-layout-output`):\n *\n * - **Input** (this type): `selectionIntent` — what the user wants selected.\n * - **Output** (`LayoutSignals.selectionFragments`): the resolved list of\n * rectangles (one per visual line spanned). Computed during the layout\n * pass; consumed by the selection renderer to paint highlight bg.\n *\n * Mirrors `CursorOffset`'s shape: a small declarative payload on the owning\n * Box. The layout phase runs `computeSelectionFragments(node)` to derive the\n * geometric fragments and pushes them onto the per-node signal. Components\n * like `TextArea`, `Text` (when selectable), or any node with a selected\n * substring can declare this prop.\n *\n * **Rules**:\n * - `from` and `to` are character offsets into the rendered text content of\n * the owning node (post-render, post-wrap). The fragment computation\n * walks the node's text layout to map offsets to visual rectangles.\n * - `from <= to`. A collapsed selection (`from === to`) produces zero\n * fragments — caret rendering is `cursorOffset`'s job.\n * - `null`/`undefined` on a Box means \"no selection on this node\" — that\n * node contributes no fragments.\n * - Multiple Boxes may declare `selectionIntent` simultaneously; the\n * aggregator (`findActiveSelectionFragments(root)`) concatenates fragments\n * from all currently-mounted declarers (Phase 4b — multi-node selection\n * is left for a future enhancement; v1 concatenation already covers the\n * \"two adjacent nodes both selected\" case).\n *\n * **Cross-target hygiene**: this type is purely semantic (offsets only). The\n * resolved `Rect[]` output and the actual highlight bg color stay terminal-\n * specific (or canvas/DOM-specific in future targets). Tracking bead:\n * `km-silvery.phase4-split-focus-selection`.\n */\nexport interface SelectionIntent {\n /**\n * Inclusive start offset (character index into the owning node's rendered\n * text content). Must be `>= 0` and `<= text.length`.\n */\n from: number\n /**\n * Exclusive end offset (character index). Must be `>= from` and\n * `<= text.length`. When `from === to` the selection is collapsed and\n * produces zero fragments.\n */\n to: number\n}\n\n// ============================================================================\n// Overlay / Anchor Types (Phase 4c — overlay-anchor v1)\n// ============================================================================\n\n/**\n * Edge of an anchor's content rect that a decoration can attach to. Mirrors\n * CSS Anchor Positioning's edge vocabulary. `\"center\"` is reserved for v2\n * (centerline placement modes); v1 only emits the four cardinal-edge rects.\n *\n * Used by the placement algorithm in `placeFloating` to decide WHERE on the\n * anchor's bounding rect to start measuring from.\n */\nexport type AnchorEdge = \"top\" | \"bottom\" | \"left\" | \"right\"\n\n/**\n * Twelve-placement vocabulary for floating decorations relative to an anchor\n * rect. The first segment names the side of the anchor the floating element\n * lives on; the second segment names the alignment along the perpendicular\n * axis (start, center, end). Mirrors Floating UI / Popper.js's vocabulary so\n * apps moving between targets can carry placement intent verbatim.\n *\n * The placement string maps deterministically to a rect via `placeFloating`.\n * Collision-aware auto-flip + auto-shift are layered on top by\n * `resolveFloatingPlacement` and opt in via `Decoration.collisionStrategy`.\n */\nexport type Placement =\n | \"top-start\"\n | \"top-center\"\n | \"top-end\"\n | \"bottom-start\"\n | \"bottom-center\"\n | \"bottom-end\"\n | \"left-start\"\n | \"left-center\"\n | \"left-end\"\n | \"right-start\"\n | \"right-center\"\n | \"right-end\"\n\n/**\n * Collision policy for floating overlays. Mirrors the common Floating UI /\n * Popper progression while staying terminal-cell deterministic:\n *\n * - `\"none\"`: preserve the requested placement even if it overflows.\n * - `\"flip\"`: try the opposite side when the requested side overflows.\n * - `\"shift\"`: keep the requested side, but clamp the rect inside the boundary.\n * - `\"flip-then-shift\"`: flip if that improves the side-axis overflow, then clamp.\n * - `\"hide\"`: emit no rect when the requested placement cannot fit.\n */\nexport type CollisionStrategy = \"none\" | \"flip\" | \"shift\" | \"flip-then-shift\" | \"hide\"\n\n/**\n * Declarative overlay attached to a Box. The substrate v1 shipped here covers\n * three kinds — popover, tooltip, highlight — that share the \"decoration\n * derived from semantic intent during layout\" shape. Caret / focus / selection\n * keep their dedicated BoxProps (`cursorOffset`, `focused`, `selectionIntent`)\n * for ergonomic + back-compat reasons; everything else routes through\n * `decorations`.\n *\n * **`kind`** drives the geometry computation:\n * - `\"popover\"` and `\"tooltip\"`: anchor-relative placement via\n * `placeFloating(anchorRect, size, placement)`. The `placement` field is\n * required (no implicit default — apps must say where they want it).\n * `content` is opaque to the substrate — the renderer owns rendering.\n * - `\"highlight\"`: a rect-list output describing visible-line fragments\n * within the owning Box's content area. v1 ships only the bounding rect;\n * soft-wrap aware fragmentation is implemented in the same way as\n * `selectionFragments` (Phase 4b) and arrives in v2 once the find/replace\n * match-highlight first consumer ships.\n *\n * **`id`** is app-chosen, must be unique within a frame, and stable across\n * re-renders (consumers may key React-side state off it).\n *\n * Coordinate space is the same absolute terminal-cell space used by every\n * other rect signal (`cursorRect`, `selectionFragments`, etc.).\n *\n * **Out of scope for v1** (deferred to v2):\n * - Generic `kind: \"custom\"` extension hook\n * - Z-index / paint-order overrides (paint order is fixed: caret > focus >\n * selection > decorations > anchors)\n *\n * See `hub/silvery/design/overlay-anchor-system.md` for the design context.\n */\nexport type Decoration =\n | {\n kind: \"popover\"\n id: string\n /** Anchor target by id, looked up via `findAnchor(root, anchorId)`. */\n anchorId?: string\n placement?: Placement\n /** Intrinsic size for the floating rect (cells). Required for placement math. */\n size?: { width: number; height: number }\n /** Optional gap along the placement axis (cells). */\n offset?: number\n /** Optional offset along the alignment axis (cells). */\n alignOffset?: number\n /** Optional viewport collision policy. Default: \"none\". */\n collisionStrategy?: CollisionStrategy\n /** Renderer-owned content. The substrate doesn't inspect this. */\n content?: unknown\n }\n | {\n kind: \"tooltip\"\n id: string\n anchorId?: string\n placement?: Placement\n size?: { width: number; height: number }\n offset?: number\n alignOffset?: number\n collisionStrategy?: CollisionStrategy\n content?: unknown\n }\n | {\n kind: \"highlight\"\n id: string\n /**\n * Highlight rect within the owning Box's content area, expressed in the\n * Box's local content-relative coordinates (origin = contentRect.{x,y},\n * size in cells). v1 emits one rect; soft-wrap fragmentation lands in\n * v2 alongside the find/replace consumer.\n */\n rect?: { x: number; y: number; width: number; height: number }\n }\n\n/**\n * Layout-anchor identifier — names this Box as a lookup target so other\n * Boxes' decorations can reference it via `Decoration.anchorId`.\n *\n * The id is app-chosen, must be unique within a tree, and stable enough across\n * re-renders to survive React reconciliation without identity churn (use a\n * literal string from props, not a `useId()` value, unless you persist it).\n *\n * Anchors are recorded into a tree-scoped map at the end of layout phase by\n * `findAnchor(root, id)`. The map's value is the Box's `contentRect` (full\n * inner area) — placement math then derives edge rects via `placeFloating`.\n */\nexport interface AnchorRef {\n id: string\n}\n\n// ============================================================================\n// Interactive State Types\n// ============================================================================\n\n/**\n * Per-node interactive state — written by pointer/selection/focus state machines,\n * read by theme/render for automatic styling.\n *\n * These are plain mutable booleans, NOT reactive signals. State machines set them\n * synchronously during event processing, and the next render reads them.\n * React re-renders are driven by the event processing, not signal subscriptions.\n *\n * The object is lazily created on first write to avoid overhead on non-interactive nodes.\n */\nexport interface InteractiveState {\n /** Pointer is over this node (mouseenter/mouseleave) */\n hovered: boolean\n /** Pointer-down on this node, awaiting pointer-up (will receive click) */\n armed: boolean\n /** Node is in the current selection set */\n selected: boolean\n /** Node has keyboard focus */\n focused: boolean\n /** A drag operation is hovering over this node */\n dropTarget: boolean\n}\n\n// ============================================================================\n// Node Types\n// ============================================================================\n\n/**\n * Silvery node types - the primitive elements in the render tree.\n *\n * - `silvery-viewport` is a leaf node hosting a foreign cell domain\n * (xtermjs PTY, replay frames, snapshot). See {@link viewport-types.ts}\n * and bead `@km/silvery/15513-surface-nested-composition-primitive`.\n * - `silvery-island` is the runtime-agnostic cell-grid mount primitive —\n * sibling of `silvery-box` / `silvery-text`. Holds an\n * {@link import(\"./island-types\").IslandHandle} ref + per-node lifecycle.\n * Supersedes `silvery-viewport` in epic\n * `@km/silvery/15646-islands` (Phase 4 deletes `silvery-viewport`).\n */\nexport type AgNodeType =\n | \"silvery-root\"\n | \"silvery-box\"\n | \"silvery-text\"\n | \"silvery-viewport\"\n | \"silvery-island\"\n\n/**\n * Flexbox properties that can be applied to Box nodes.\n */\nexport interface FlexboxProps {\n // Size\n width?: number | string\n height?: number | string\n minWidth?: number | string\n minHeight?: number | string\n maxWidth?: number | string\n maxHeight?: number | string\n\n // Flex\n flexGrow?: number\n flexShrink?: number\n flexBasis?: number | string\n flexDirection?: \"row\" | \"column\" | \"row-reverse\" | \"column-reverse\"\n flexWrap?: \"nowrap\" | \"wrap\" | \"wrap-reverse\"\n\n // Alignment\n alignItems?: \"flex-start\" | \"flex-end\" | \"center\" | \"stretch\" | \"baseline\"\n alignSelf?: \"auto\" | \"flex-start\" | \"flex-end\" | \"center\" | \"stretch\" | \"baseline\"\n alignContent?:\n | \"flex-start\"\n | \"flex-end\"\n | \"center\"\n | \"stretch\"\n | \"space-between\"\n | \"space-around\"\n | \"space-evenly\"\n justifyContent?:\n | \"flex-start\"\n | \"flex-end\"\n | \"center\"\n | \"space-between\"\n | \"space-around\"\n | \"space-evenly\"\n\n // Spacing\n padding?: number\n paddingTop?: number\n paddingBottom?: number\n paddingLeft?: number\n paddingRight?: number\n paddingX?: number\n paddingY?: number\n margin?: number\n marginTop?: number\n marginBottom?: number\n marginLeft?: number\n marginRight?: number\n marginX?: number\n marginY?: number\n gap?: number\n columnGap?: number\n rowGap?: number\n\n // Position\n position?: \"relative\" | \"absolute\" | \"sticky\" | \"static\"\n\n // Position offsets (used with position='absolute' or position='relative')\n top?: number | string\n left?: number | string\n bottom?: number | string\n right?: number | string\n\n // Sticky offsets (only used when position='sticky')\n // The element will \"stick\" when it reaches this offset from the container edge\n stickyTop?: number\n stickyBottom?: number\n\n // Aspect ratio\n aspectRatio?: number\n\n // Display\n display?: \"flex\" | \"none\"\n\n // Overflow\n overflow?: \"visible\" | \"hidden\" | \"scroll\"\n overflowX?: \"visible\" | \"hidden\"\n overflowY?: \"visible\" | \"hidden\"\n\n // Scroll control (only used when overflow='scroll')\n /**\n * Child index to ensure visible. Declarative — the Box fires edge-based\n * ensure-visible when this value CHANGES (or on mount). Re-renders with\n * the same value are no-ops; content-height changes do not re-trigger\n * the ensure-visible pass.\n *\n * This \"fire on change\" semantic prevents viewport jumps when a visible\n * child grows (e.g. user clicks to expand a collapsible row): the Box no\n * longer re-anchors on every render. Matches the convention used by\n * `@tanstack/virtual`, `react-window`, iOS `UIScrollView.setContentOffset`\n * — imperative intent is separate from declarative anchor state.\n *\n * To re-fire ensure-visible against the SAME target (e.g. \"scroll to\n * cursor, even though cursor didn't change\"), toggle the value via undefined\n * first, or drive scroll via the explicit `scrollOffset` prop.\n */\n scrollTo?: number\n /** Explicit scroll offset in rows (used when scrollTo is undefined for frozen scroll state) */\n scrollOffset?: number\n}\n\n/**\n * Props for testing and identification.\n * These props are stored in the node for DOM query access.\n */\nexport interface TestProps {\n /** Element ID for DOM queries and visual debugging */\n id?: string\n /** Test ID for querying nodes (like Playwright's data-testid) */\n testID?: string\n /** Allow arbitrary data-* attributes for testing */\n [key: `data-${string}`]: unknown\n}\n\n/**\n * Underline style variants (SGR 4:x codes).\n * - false: no underline\n * - 'single': standard underline (SGR 4 or 4:1)\n * - 'double': double underline (SGR 4:2)\n * - 'curly': curly/wavy underline (SGR 4:3)\n * - 'dotted': dotted underline (SGR 4:4)\n * - 'dashed': dashed underline (SGR 4:5)\n */\nexport type UnderlineStyle = false | \"single\" | \"double\" | \"curly\" | \"dotted\" | \"dashed\"\n\n/**\n * Named underline styles — the string half of `underline: boolean | UnderlineStyleName`.\n * Excludes `false` so the boolean-or-string union doesn't have two falsy branches.\n */\nexport type UnderlineStyleName = Exclude<UnderlineStyle, false>\n\n/**\n * Style properties for text rendering.\n */\nexport interface StyleProps {\n color?: string\n backgroundColor?: string\n bold?: boolean\n italic?: boolean\n /**\n * Enable underline. Accepts:\n * - `true` — standard single underline (equivalent to `\"single\"`)\n * - `false` — no underline\n * - `\"single\" | \"double\" | \"curly\" | \"dotted\" | \"dashed\"` — specific style variant\n *\n * A style name is equivalent to setting `underline=true` with that style.\n */\n underline?: boolean | UnderlineStyleName\n /**\n * @deprecated Pass the style name directly to `underline` instead\n * (e.g. `underline=\"curly\"`). `underlineStyle` is retained for backwards\n * compatibility and still takes precedence over `underline` when both are set.\n * Will be removed in a future major.\n */\n underlineStyle?: UnderlineStyle\n /**\n * Underline color (independent of text color).\n * Uses SGR 58 (underline color). Falls back to text color if not specified.\n */\n underlineColor?: string\n /**\n * Overline the cell — SGR 53/55. Independent of underline.\n *\n * SGR 53 places a line ABOVE the character cell; SGR 55 removes it.\n * Use this for top-edge indicators (e.g. overscroll-at-top), where an\n * underline on the first row would read as \"this row is underlined\" rather\n * than \"you're bumped against the top\". Overline on the top row and\n * underline on the bottom row are the semantically correct pair.\n *\n * Supported by most modern terminals (Ghostty, iTerm2, xterm with\n * `allowExtendedUnderlines` equivalent). The output phase skips SGR 53/55\n * when {@link TerminalCaps#overline} is false.\n */\n overline?: boolean\n /**\n * Overline color — reserved. Currently not plumbed through the pipeline;\n * see bead `km-silvery.overline-color` for the follow-up that mirrors\n * {@link underlineColor}'s SGR 58 wiring for overline. Setting this today\n * is a no-op.\n */\n overlineColor?: string\n strikethrough?: boolean\n inverse?: boolean\n\n /**\n * Text size scale factor via OSC 66 (Kitty v0.40+).\n *\n * Float multiplier: 2.0 = double (headings), 1.0 = normal, 0.5 = half (small print).\n * The terminal renders subsequent text at this scale until reset.\n * Requires a terminal that supports the kitty text sizing protocol.\n * Terminals without support silently ignore the escape sequence.\n */\n textSize?: number\n}\n\n/**\n * Props for Box component.\n */\nexport interface BoxProps\n extends FlexboxProps, StyleProps, TestProps, MouseEventProps, DragEventProps, FocusEventProps {\n /** Text truncation mode for child text content (passed through to Text children). */\n wrap?:\n | \"wrap\"\n | \"wrap-truncate\"\n | \"hard\"\n | \"even\"\n | \"truncate\"\n | \"truncate-start\"\n | \"truncate-middle\"\n | \"truncate-end\"\n | \"clip\"\n | boolean\n borderStyle?: \"single\" | \"double\" | \"round\" | \"bold\" | \"singleDouble\" | \"doubleSingle\" | \"classic\"\n borderColor?: string\n /** Background color for all border sides (shorthand). Per-side props override this. */\n borderBackgroundColor?: string\n /** Background color for the top border (overrides borderBackgroundColor). */\n borderTopBackgroundColor?: string\n /** Background color for the bottom border (overrides borderBackgroundColor). */\n borderBottomBackgroundColor?: string\n /** Background color for the left border (overrides borderBackgroundColor). */\n borderLeftBackgroundColor?: string\n /** Background color for the right border (overrides borderBackgroundColor). */\n borderRightBackgroundColor?: string\n borderTop?: boolean\n borderBottom?: boolean\n borderLeft?: boolean\n borderRight?: boolean\n\n /**\n * Outline style — renders border characters OUTSIDE the box without affecting layout.\n *\n * Unlike `borderStyle` which adds border dimensions inside the box (shrinking the\n * content area), `outlineStyle` draws one cell beyond each edge — in the gap/margin\n * space between siblings. The layout engine sees no border at all.\n *\n * This matches CSS `outline` semantics: outside the border box, no layout impact.\n *\n * Use cases: focus rings, hover highlights, selection indicators, edit bounds —\n * anything that should visually frame a box without affecting layout or content.\n */\n outlineStyle?:\n | \"single\"\n | \"double\"\n | \"round\"\n | \"bold\"\n | \"singleDouble\"\n | \"doubleSingle\"\n | \"classic\"\n /** Foreground color for the outline */\n outlineColor?: string\n /** Apply dim styling to the outline */\n outlineDimColor?: boolean\n /** Show top outline edge (default: true) */\n outlineTop?: boolean\n /** Show bottom outline edge (default: true) */\n outlineBottom?: boolean\n /** Show left outline edge (default: true) */\n outlineLeft?: boolean\n /** Show right outline edge (default: true) */\n outlineRight?: boolean\n\n /**\n * Override theme for this subtree — $token colors resolve against this theme.\n * Pushed onto the context theme stack during render phase tree walk.\n */\n theme?: import(\"@silvery/ansi\").Theme\n\n /** CSS pointer-events equivalent. \"none\" makes this node and its subtree invisible to hit testing. */\n pointerEvents?: \"auto\" | \"none\"\n\n /**\n * CSS user-select equivalent. Controls whether text in this node is selectable.\n * - \"auto\" (default): inherit from parent. Root resolves to \"text\".\n * - \"none\": not selectable. Mouse-drag on this node does not start text selection.\n * - \"text\": force selectable, even if parent is \"none\".\n * - \"contain\": selectable, but selection range cannot escape this node's bounds.\n */\n userSelect?: UserSelect\n\n /**\n * Whether this node can be dragged via mouse.\n * When true, mousedown + drag past threshold initiates a node drag gesture\n * instead of text selection. Not inherited — only the node with draggable=true\n * is draggable, not its children.\n */\n draggable?: boolean\n\n /**\n * Capture pointer-style mouse events after mousedown.\n *\n * When true, a mousedown inside this node makes subsequent mousemove and\n * mouseup events for that press bubble from this node even if the cursor\n * leaves its one-cell hit box. Hover enter/leave still follows the real\n * cursor target. This is the terminal equivalent of pointer capture for\n * narrow draggable controls such as scrollbars.\n */\n mouseCapture?: boolean\n\n onLayout?: (layout: Rect) => void\n\n /**\n * Show scroll overflow indicators (▲N / ▼N) for scrollable containers.\n *\n * For bordered containers, indicators appear on the border.\n * For borderless containers, indicators overlay the content at top-right/bottom-right.\n *\n * Only applies when overflow='scroll'.\n */\n overflowIndicator?: boolean\n\n /**\n * Declarative focus marker — \"this Box is focused.\" When set on a Box, the\n * layout phase writes the node's id (or testID) to `LayoutSignals.focusedNodeId`\n * and the focus-renderer reads from that signal to paint the focus ring /\n * dim styling — bypassing the `useFocus` → `FocusManager` → `useSyncExternalStore`\n * chain on the first frame after mount.\n *\n * This is the **focus-as-layout-output** path (Phase 4a of\n * `km-silvery.view-as-layout-output`). It mirrors `cursorOffset` exactly:\n * a semantic boolean declared on the outer Box, resolved into a layout\n * signal during `syncRectSignals`, with a tree-walk lookup\n * (`findActiveFocusedNodeId`) that the renderer / scheduler consumes.\n *\n * **Precedence across nodes** (mirrors cursor invariant 1):\n * 1. Deepest visible focused declarer in paint-order wins. If two siblings\n * both have `focused === true`, the post-order tree walk picks the\n * later-rendered one — consistent with cursor's deepest-wins fallback.\n * 2. Otherwise null.\n *\n * **Identity**: the signal value is the node's `id` if present, else its\n * `testID`, else null. Apps that need stable focus identity should set one\n * of those props alongside `focused={true}`.\n *\n * **Cross-target hygiene**: `focused` is a semantic boolean. Terminal-specific\n * focus styling (dim, bold borders, focus ring) lives in `@silvery/ag-term`\n * or component-level styling. Canvas/DOM targets read the same id-level\n * signal but render their own focus indicator.\n *\n * **Back-compat**: `useFocus` continues to work as a deprecated wrapper that\n * routes through the legacy `FocusManager` path. Migrate to `focused={…}`\n * to opt into the layout-output path.\n */\n focused?: boolean\n\n /**\n * Component-relative caret position. When set, the layout phase computes\n * absolute terminal coordinates (border + padding + offset relative to the\n * box's `scrollRect`) and writes them to `LayoutSignals.cursorRect`. The\n * scheduler reads this value to emit caret positioning ANSI on the very\n * first frame after mount — bypassing the React effect chain that\n * `useCursor` relies on.\n *\n * This is the \"caret as layout output\" path. The legacy `useCursor` hook\n * remains as a back-compat wrapper but its signal-effect bridge is unsafe\n * across conditional mounts (see `km-silvercode.cursor-startup-position`).\n *\n * **Precedence across nodes** (locked by `km-silvery.cursor-invariants` #1):\n * 1. Focused-editable wins — a Box that is `focused` AND has visible\n * `cursorOffset` always beats a non-focused declarer.\n * 2. Otherwise deepest-in-paint-order (post-order tree walk) wins.\n * 3. Otherwise null.\n *\n * **Clipping** (invariant #4): if the caret falls outside the nearest\n * `overflow=\"scroll\"` / `\"hidden\"` ancestor's visible region, it is hidden\n * (no caret ANSI emitted, signal returns null). Caret rect at the exact\n * clip edge is treated as visible.\n */\n cursorOffset?: CursorOffset\n\n /**\n * Semantic selection intent — the user's selected substring within this\n * Box's text content, declared as character offsets `{ from, to }`. The\n * layout phase resolves this into a list of rectangles\n * (`LayoutSignals.selectionFragments`) that the selection renderer reads\n * to paint highlight bg.\n *\n * This is the **selection-as-overlay** path (Phase 4b of\n * `km-silvery.view-as-layout-output`). It mirrors `cursorOffset` exactly:\n * a semantic declaration on the outer Box, resolved into geometric output\n * during `syncRectSignals`, with a tree-walk lookup\n * (`findActiveSelectionFragments`) that the renderer consumes.\n *\n * **Geometry**:\n * - Collapsed (`from === to`) → zero fragments. Caret rendering is\n * `cursorOffset`'s responsibility, not selection's.\n * - Single visual line → one rectangle from `from` to `to`.\n * - Multi-line (text contains `\\n` characters) → one rectangle per visual\n * line: the first runs from `from` to end-of-line, middle lines span the\n * full content area width, the last runs from start-of-line to `to`.\n * - Wrap-aware fragment computation across word-wrapped visual lines is\n * limited in v1 — only embedded `\\n` produces multi-line fragments. A\n * future iteration will register a wrap measurer so soft-wrapped text\n * produces the correct per-visual-line fragments. Track at\n * `km-silvery.overlay-anchor-system` (Phase 4c).\n *\n * **Multi-node selection**: each Box declares its own intent;\n * `findActiveSelectionFragments(root)` concatenates fragments across all\n * mounted declarers. Two adjacent nodes both selected is supported. Full\n * cross-node range selection (selecting from middle of node A through\n * node B) is a future enhancement.\n *\n * **Cross-target hygiene**: `selectionIntent` is purely semantic. The\n * resolved `Rect[]` is purely geometric. Terminal-specific bg highlight\n * styling lives in `@silvery/ag-term` (selection-renderer); canvas/DOM\n * targets read the same fragments and render their own highlight.\n *\n * **Back-compat**: `useSelection` continues to work as a deprecated\n * wrapper that reads from the legacy `SelectionFeature` capability.\n * Migrate to `selectionIntent={…}` to opt into the layout-output path.\n */\n selectionIntent?: SelectionIntent\n\n /**\n * Names this Box as a layout-anchor lookup target. Other Boxes' decorations\n * can reference the id via `Decoration.anchorId` and the substrate resolves\n * the position via `findAnchor(root, id)`.\n *\n * Phase 4c of `km-silvery.view-as-layout-output` (overlay-anchor v1). The\n * registered rect is the Box's `contentRect` (border + padding excluded);\n * edge-specific rects are derived by `placeFloating` at consumption time.\n *\n * Pass a string for the simple case (`anchorRef=\"dropdown-trigger\"`) or an\n * AnchorRef object if a future v2 wants to extend with edge metadata.\n *\n * **Stability**: ids should be stable across re-renders — React reconciler\n * preserves the AgNode identity, but registering a new id per render makes\n * `findAnchor` flap and breaks decoration layout. Use a literal string from\n * props, not a `useId()` value, unless persisted.\n */\n anchorRef?: string | AnchorRef\n\n /**\n * Declarative overlays attached to this Box — popovers, tooltips,\n * highlights. Each entry is resolved into a geometric `DecorationRect` in\n * `LayoutSignals.decorationRects` during layout phase, and aggregated into\n * the per-frame `OverlayLayer` artifact returned alongside `term.frame`.\n *\n * Phase 4c of `km-silvery.view-as-layout-output` (overlay-anchor v1).\n *\n * **Paint order** is fixed (no z-index): caret > focus > selection >\n * decorations > anchors. Within `decorations` itself, list order determines\n * paint order (later entries paint on top of earlier ones).\n *\n * **Anchor lookup**: popover/tooltip kinds reference an `anchorId`; the\n * substrate calls `findAnchor(root, id)` at layout time. If the anchor\n * isn't found this frame, the decoration emits an empty rect list (the\n * renderer skips it). By default placement is fixed; opt into viewport\n * collision handling with `collisionStrategy`.\n *\n * **Stable identity**: pass a memoized array if React's referential equality\n * matters for downstream consumers; the substrate itself recomputes\n * decoration rects every layout pass, so referential identity isn't load-\n * bearing on the substrate side.\n */\n decorations?: readonly Decoration[]\n\n /**\n * Virtualization-internal: set only by virtual list placeholders (e.g.\n * ListView's leading/trailing spacer Boxes). **Do not set on ordinary Box\n * children** — the default (1 visual = 1 logical item) is correct.\n *\n * For a child of an `overflow=\"scroll\"` container: declare that this child\n * is a placeholder representing multiple logical items. When the child is\n * fully scrolled out above/below the viewport, the parent's\n * `hiddenAbove`/`hiddenBelow` count is incremented by this value instead of\n * 1, so `▲N`/`▼N` indicators reflect real items rather than rendered\n * placeholder boxes.\n *\n * Only read by the parent scroll container. Defaults to 1 (treat as a\n * single visual item). Must be >= 0.\n *\n * @internal\n */\n representsItems?: number\n\n // ==========================================================================\n // Container Queries (A0.1)\n // ==========================================================================\n // Maps to CSS `container-type` / `contain: size` / `@container` rules. Phase 1\n // ships inline-size queries only. Block-size + named containers arrive later.\n //\n // Engine requirement: cqi resolution + the inter-pass mutation hook are\n // flexily-only — under yoga, `containerQueries` / `containSize` / cqi units\n // throw at first paint via `requireCapability`. Switch via SILVERY_ENGINE=flexily.\n\n /**\n * Declare this Box as a container-query container (A0.1).\n *\n * Descendants' `cqi` / `cqmin` values resolve against this Box's frozen\n * inline-size, captured during layout Pass 1. Combined with `containSize`,\n * this Box's inline-size is invariant under children's CQ branch flips.\n *\n * `\"inline-size\"` — equivalent to CSS `container-type: inline-size`. Maps to\n * `flexily.CONTAINER_TYPE_INLINE_SIZE` at the engine layer.\n * `\"normal\"` — opt out (default). Phase 1 supports only the two values.\n */\n containerType?: \"normal\" | \"inline-size\"\n\n /**\n * Enable CSS `contain: size` on this Box (A0.1).\n *\n * When true, children's intrinsic sizes do not propagate into this Box's\n * inline-size. Required pairing with `containerType: \"inline-size\"` for a\n * sound CQ container — without it, child sizes feed back into container\n * size and break the two-phase invariance guarantee. The dev-mode\n * `intrinsic-leak` assertion throws on the unsound configuration.\n *\n * Phase 1 contains inline-size only.\n */\n containSize?: boolean\n\n /**\n * Container name (CSS `container-name`) — referenced by `@container <name>`\n * queries. Phase 1 is informational only (single anonymous container per\n * subtree); named-container resolution arrives with the silvery-layer CQ\n * matcher.\n */\n containerName?: string\n\n /**\n * Container-query branches — declarative responsive styling against this\n * Box's frozen inline-size (A0.1 substrate). Phase 1 ships the engine hook;\n * the matcher that interprets `containerQueries` and applies branch styles\n * to children lands as part of Phase A (silvery layer).\n *\n * Until the matcher ships, the prop is reserved for forward-compat — passing\n * it does not yet apply branch styles. Use cqi units directly for now.\n */\n containerQueries?: readonly ContainerQueryBranch[]\n\n /**\n * Single-pass fit-content lane snap (A0.2). The engine-resolved lane\n * primitive — no React round-trip, no measurement subtree.\n *\n * Box's inline-size snaps to the smallest lane that fits its children's\n * max-content. Lane entries accept numbers (treated as cells) or strings\n * like `\"100cqi\"` / `\"50cqi\"` / `\"100cqmin\"` (parsed at this layer into\n * the engine's unit form).\n *\n * If max-content exceeds every lane, the LAST lane is used — by convention\n * place lanes in ascending order so this behaves as \"biggest lane wins\".\n *\n * Example (chat content lanes):\n * <Box fitWidth={[80, 120, \"100cqi\"]}>\n * {messageBlocks}\n * </Box>\n *\n * Engine requirement: flexily-only. Under yoga, throws at first paint via\n * `requireCapability(\"fitWidth\", ...)`.\n */\n fitWidth?: readonly (number | string)[]\n}\n\n/**\n * A container-query branch (A0.1 substrate; matcher in Phase A).\n *\n * Matches against the container's frozen inline-size. Phase 1 supports\n * width-based conditions only.\n */\nexport interface ContainerQueryBranch {\n /** Match when container inline-size is at least this many cells. */\n readonly minWidth?: number\n /** Match when container inline-size is at most this many cells. */\n readonly maxWidth?: number\n /** Match when container inline-size equals this many cells. */\n readonly width?: number\n /**\n * Branch styles applied to descendants when this condition matches. Subset\n * of `BoxProps` / `TextProps` style fields; full schema arrives with the\n * matcher.\n */\n readonly apply: Record<string, unknown>\n}\n\n/**\n * Props for Text component.\n */\n/**\n * Flex item subset of FlexboxProps — the props that make sense on a leaf\n * (Text). Box accepts the full FlexboxProps; Text only accepts the props\n * that affect how it participates as a flex item (sizing, growth, shrink)\n * — not props that affect how it lays out its non-existent children\n * (flexDirection, justifyContent, alignItems, gap, ...).\n *\n * This is the canonical CSS escape hatch: instead of wrapping Text in a\n * Box to apply `flexShrink={0}` or `minWidth={0}`, set them directly on\n * the Text. See bead km-silvery.text-intrinsic-vs-render.\n */\nexport interface TextFlexItemProps {\n /** CSS `flex-grow` — proportion of free positive space along main axis. */\n flexGrow?: number\n /** CSS `flex-shrink` — proportion of negative free space along main axis. */\n flexShrink?: number\n /** CSS `flex-basis` — initial main-size before grow/shrink distribution. */\n flexBasis?: number | string\n /** Cross-axis self-alignment override. */\n alignSelf?: \"auto\" | \"flex-start\" | \"flex-end\" | \"center\" | \"stretch\" | \"baseline\"\n /** CSS `min-width` — floor for shrink distribution. */\n minWidth?: number | string\n /** CSS `min-height`. */\n minHeight?: number | string\n /** CSS `max-width` — ceiling for grow distribution. */\n maxWidth?: number | string\n /** CSS `max-height`. */\n maxHeight?: number | string\n}\n\nexport interface TextProps extends StyleProps, TextFlexItemProps, TestProps, MouseEventProps {\n children?: React.ReactNode\n /**\n * Wrap / truncate mode. Each value bundles the CSS-equivalent\n * `white-space` + `overflow-wrap` + `text-overflow` axes into one\n * named composite. See `vendor/silvery/docs/components/Text.md` for the\n * full table.\n *\n * - `\"wrap\"` (default): word-aware multi-line wrap, soft-break separators\n * for path-style tokens, character wrap as last-resort fallback.\n * - `\"wrap-truncate\"`: same as `\"wrap\"` but ellipsis-truncates the\n * offending line when an unbreakable atomic token would otherwise\n * character-wrap. CSS analogue\n * `overflow-wrap: break-word` + `text-overflow: ellipsis`.\n * - `\"truncate\"` / `\"truncate-end\"` / `\"truncate-start\"` /\n * `\"truncate-middle\"`: single-line, ellipsis at the named position.\n * - `\"clip\"`: single-line, hard clip without ellipsis.\n * - `\"hard\"`: character-wrap regardless of word boundaries (Ink compat).\n * - `\"even\"`: optimal Knuth-Plass wrapping (minimize raggedness).\n * - `false`: no wrap / no clip (overflows container — avoid in bordered\n * cells).\n */\n wrap?:\n | \"wrap\"\n | \"wrap-truncate\"\n | \"hard\"\n | \"even\"\n | \"truncate\"\n | \"truncate-start\"\n | \"truncate-middle\"\n | \"truncate-end\"\n | \"clip\"\n | boolean\n /** Internal transform function applied to each rendered line. Used by Transform component. */\n internal_transform?: (line: string, index: number) => string\n /**\n * Per-node background-conflict policy. Overrides the global / context\n * `BgConflictMode` for the cells this Text node paints.\n *\n * Background conflicts (ANSI bg in text content layered over an silvery\n * `backgroundColor`) normally `throw` — that strictness is the right\n * safety net for an silvery app's own pipeline bugs. But a component\n * mirroring arbitrary EXTERNAL ANSI (e.g. `<Terminal>` re-encoding a\n * captured terminal grid, where chalk status bars are conflict-rich by\n * nature) *expects* conflicts. Such a component sets `bgConflict=\"ignore\"`\n * on the `<Text>` rows it paints so the global throw stays a safety net\n * everywhere else.\n *\n * - `\"ignore\"`: no detection for this node's cells.\n * - `\"warn\"`: log a deduplicated warning instead of throwing.\n * - `\"throw\"`: throw (the default behavior when unset).\n *\n * When unset, the pipeline falls back to the context mode, then the\n * global mode (`SILVERY_BG_CONFLICT`, default `\"throw\"`).\n */\n bgConflict?: \"ignore\" | \"warn\" | \"throw\"\n}\n\n/**\n * The core Silvery node - represents an element in the render tree.\n *\n * Each node has:\n * - A Yoga node for layout calculation\n * - Computed layout after Yoga runs\n * - Subscribers that get notified when layout changes\n * - Dirty flags for incremental updates\n */\nexport interface AgNode {\n /** Node type */\n type: AgNodeType\n\n /** Props passed to this node */\n props: BoxProps | TextProps | Record<string, unknown>\n\n /** Child nodes */\n children: AgNode[]\n\n /** Parent node (null for root) */\n parent: AgNode | null\n\n /** The layout node for layout calculation (null for raw text nodes) */\n layoutNode: LayoutNode | null\n\n /** Computed layout from previous render (for change detection) */\n prevLayout: Rect | null\n\n /**\n * Content-relative position (like CSS offsetTop/offsetLeft).\n * Position within the scrollable content, ignoring scroll offsets.\n * Set after layout phase.\n */\n boxRect: Rect | null\n\n /**\n * Screen-relative position (like CSS getBoundingClientRect).\n * Actual position on the terminal screen, accounting for scroll offsets.\n * Set after screen rect phase.\n *\n * Note: For sticky children, this reflects the node's layout position\n * adjusted for scroll offsets, NOT the actual render position. Use\n * `screenRect` for the actual pixel position on screen.\n */\n scrollRect: Rect | null\n\n /** Previous screen rect (for change detection in notifyLayoutSubscribers) */\n prevScrollRect: Rect | null\n\n /**\n * Actual render position on the terminal screen.\n * For non-sticky nodes, this equals `scrollRect`.\n * For sticky nodes (position=\"sticky\"), this accounts for sticky render\n * offsets — the position where pixels are actually painted.\n *\n * Use this for hit testing, cursor positioning, and any feature that\n * needs to know where a node visually appears on screen.\n * Set after screen rect phase.\n */\n screenRect: Rect | null\n\n /** Previous render rect (for change detection) */\n prevScreenRect: Rect | null\n\n /** Epoch when layout changed (position or size).\n * Set by propagateLayout in layout phase. Compared against renderEpoch by render phase.\n * This is the authoritative signal for \"did layout change?\" — unlike\n * !rectEqual(prevLayout, boxRect) which becomes stale when layout\n * phase skips (no dirty nodes).\n * Value: renderEpoch when dirty, INITIAL_EPOCH (-1) when clean. */\n layoutChangedThisFrame: number\n\n /**\n * Bit-packed dirty flags for the current epoch.\n *\n * Seven dirty flags packed into a single number:\n * bit 0 (CONTENT_BIT): content changed (text content or content-affecting props)\n * bit 1 (STYLE_PROPS_BIT): visual props changed (color, bg, border, etc.)\n * bit 2 (BG_BIT): backgroundColor specifically changed\n * bit 3 (CHILDREN_BIT): direct children added/removed/reordered\n * bit 4 (SUBTREE_BIT): this node or any descendant has dirty content/layout\n * bit 5 (ABS_CHILD_BIT): absolute child had structural changes\n * bit 6 (DESC_OVERFLOW_BIT): descendant overflow changed\n *\n * Outlines do NOT get a dirty bit — the decoration phase redraws them\n * every frame with per-cell snapshots (see pipeline/decoration-phase.ts).\n *\n * Check: `isDirty(node.dirtyBits, node.dirtyEpoch, BIT)`\n * Set: `node.dirtyBits = setDirtyBit(node.dirtyBits, node.dirtyEpoch, BIT); node.dirtyEpoch = getRenderEpoch()`\n * Clear: `advanceRenderEpoch()` — all nodes instantly become clean\n *\n * NOTE: measure phase may clear CONTENT_BIT — STYLE_PROPS_BIT acts as the\n * surviving witness for style changes. See render-phase.ts contentAreaAffected.\n */\n dirtyBits: number\n\n /**\n * Epoch when dirtyBits was last written.\n * When `dirtyEpoch !== renderEpoch`, all bits are stale (node is clean).\n * Value: renderEpoch when any bit is dirty, INITIAL_EPOCH (-1) when clean.\n */\n dirtyEpoch: number\n\n /** Text content for text nodes */\n textContent?: string\n\n /** True if this is a raw text node (created by createTextInstance) */\n isRawText?: boolean\n\n /** True if this node is hidden (for Suspense support) */\n hidden?: boolean\n\n /** Sticky children with computed render positions (for non-scroll containers).\n * When a parent has sticky children but is NOT a scroll container, this array\n * holds the computed render offsets. Same shape as scrollState.stickyChildren. */\n stickyChildren?: Array<{\n /** Index of the sticky child */\n index: number\n /** Computed Y offset to render at (relative to parent content area) */\n renderOffset: number\n /** Original natural Y position (relative to parent content area) */\n naturalTop: number\n /** Height of the sticky element */\n height: number\n }>\n\n /** Inline rects for virtual text nodes (no layout node). Computed during text rendering.\n * Array for wrapped text (one rect per line fragment). Enables hit testing on nested Text. */\n inlineRects?: Array<{ x: number; y: number; width: number; height: number }> | null\n\n /**\n * Render-phase flag: \"did this Box have an attr overlay (underline /\n * strikethrough / etc.) applied in the previous frame?\" Written by the\n * render phase after `applyBoxAttrOverlay`. Read next frame to decide\n * whether `stylePropsDirty` on the Box must escalate to `contentAreaAffected`\n * (so the prev-frame merge-attr bits can be cleared via re-render).\n *\n * `mergeAttrsInRect` OR-combines — it can't clear bits. So when a Box's\n * attr overlay goes away (true → false, or style change), the clone buffer\n * still carries the old attr bits. This flag lets us detect the \"had overlay\n * in prev frame\" case without storing prev props.\n *\n * Only meaningful for silvery-box nodes. Defaults to undefined / false.\n * @internal\n */\n hadBoxAttrOverlay?: boolean\n\n /**\n * Interactive state signals — written by pointer/selection/focus state machines,\n * read by theme/render for automatic styling (hover highlights, focus rings, etc.).\n *\n * Lazily created on first write. Null means no interactive state has been set.\n * See InteractiveState for field docs.\n */\n interactiveState?: InteractiveState | null\n\n /**\n * Viewport state for silvery-viewport nodes. Lazily created by the\n * `<Viewport>` React component at mount; read by the pipeline render\n * phase to blit the foreign cell buffer at the node's boxRect.\n * See `viewport-types.ts` and bead `@km/silvery/15513`.\n *\n * Deprecated by `<Island>` / {@link islandState} in epic\n * `@km/silvery/15646-islands`; Phase 4 deletes this slot.\n */\n viewportState?: import(\"./viewport-types\").ViewportNodeState | null\n\n /**\n * Island state for silvery-island nodes. Lazily created by `createIsland()`\n * / `<Island>` at mount; read by the pipeline render phase to blit the\n * guest's cell buffer at the node's boxRect, and by the host aggregator\n * (create-app.tsx) to derive focused-subtree protocol modes.\n * See `island-types.ts` and bead `@km/silvery/15646-islands`.\n */\n islandState?: import(\"./island-types\").IslandNodeState | null\n\n /** Scroll state for overflow='scroll' containers */\n scrollState?: {\n /** Current scroll offset (in terminal rows) */\n offset: number\n /** Previous scroll offset from last render (for incremental rendering) */\n prevOffset: number\n /**\n * The `scrollTo` prop value processed in the previous frame.\n *\n * Used to distinguish \"new intent\" (scrollTo changed — user pressed a key\n * or an external setter moved the target) from \"same intent\" (scrollTo\n * unchanged — this frame is just a re-render caused by content growth or\n * style changes).\n *\n * Edge-based ensure-visible fires for NEW intent. Same intent skips\n * re-anchoring when the target's top edge is still in the viewport —\n * otherwise growing a visible item would shift the viewport down and\n * push content above it out of view (\"the whole page jumps on click\").\n */\n prevScrollTo?: number\n /** Total content height (all children) */\n contentHeight: number\n /** Visible height (container height minus borders/padding) */\n viewportHeight: number\n /** Index of first visible child */\n firstVisibleChild: number\n /** Index of last visible child */\n lastVisibleChild: number\n /** Previous first visible child from last render (for incremental rendering) */\n prevFirstVisibleChild: number\n /** Previous last visible child from last render (for incremental rendering) */\n prevLastVisibleChild: number\n /** Count of items hidden above viewport */\n hiddenAbove: number\n /** Count of items hidden below viewport */\n hiddenBelow: number\n /** Sticky children with their computed render positions */\n stickyChildren?: Array<{\n /** Index of the sticky child */\n index: number\n /** Computed Y offset to render at (relative to viewport, not content) */\n renderOffset: number\n /** Original natural Y position (before sticky adjustment) */\n naturalTop: number\n /** Height of the sticky element */\n height: number\n }>\n }\n}\n\n// ============================================================================\n// Terminal Buffer Types\n// ============================================================================\n\n/**\n * Text attributes that can be applied to a cell.\n */\nexport interface CellAttrs {\n bold?: boolean\n dim?: boolean\n italic?: boolean\n /** Simple underline flag (for backwards compatibility) */\n underline?: boolean\n /**\n * Underline style: 'single' | 'double' | 'curly' | 'dotted' | 'dashed'.\n * When set, takes precedence over the underline boolean.\n */\n underlineStyle?: UnderlineStyle\n strikethrough?: boolean\n inverse?: boolean\n}\n\n/**\n * A single cell in the terminal buffer.\n */\nexport interface Cell {\n /** The character (grapheme cluster) in this cell */\n char: string\n /** Foreground color (ANSI code or RGB) */\n fg: string | null\n /** Background color (ANSI code or RGB) */\n bg: string | null\n /** Text attributes */\n attrs: CellAttrs\n /** True if this is a wide character (CJK) that takes 2 cells */\n wide: boolean\n /** True if this cell is the continuation of a wide character */\n continuation: boolean\n}\n\n/**\n * Interface for the terminal buffer.\n */\nexport interface TerminalBuffer {\n readonly width: number\n readonly height: number\n getCell(x: number, y: number): Cell\n setCell(x: number, y: number, cell: Cell): void\n clear(): void\n}\n\n// ============================================================================\n// Event Types\n// ============================================================================\n\n/**\n * Keyboard event with key information and modifiers.\n */\nexport interface KeyEvent {\n type: \"key\"\n /** The key pressed (character or key name like 'ArrowUp') */\n key: string\n /** Ctrl modifier was held */\n ctrl?: boolean\n /** Meta/Alt modifier was held */\n meta?: boolean\n /** Shift modifier was held */\n shift?: boolean\n /** Alt/Option modifier was held */\n alt?: boolean\n /** Super/Cmd modifier was held. Requires Kitty protocol. */\n super?: boolean\n /** Hyper modifier was held. Requires Kitty protocol. */\n hyper?: boolean\n /** Kitty event type. Requires Kitty flag 2. */\n eventType?: \"press\" | \"repeat\" | \"release\"\n /** CapsLock is active. Kitty modifier bit 6. */\n capsLock?: boolean\n /** NumLock is active. Kitty modifier bit 7. */\n numLock?: boolean\n}\n\n/**\n * Mouse event with position and button information.\n */\nexport interface MouseEvent {\n type: \"mouse\"\n /** X position in terminal columns (0-indexed) */\n x: number\n /** Y position in terminal rows (0-indexed) */\n y: number\n /** Mouse button (0=left, 1=middle, 2=right) */\n button: number\n /** Event action */\n action: \"down\" | \"up\" | \"move\" | \"wheel\"\n /** Wheel delta for scroll events */\n delta?: number\n}\n\n/**\n * Terminal resize event.\n */\nexport interface ResizeEvent {\n type: \"resize\"\n /** New width in columns */\n width: number\n /** New height in rows */\n height: number\n}\n\n/**\n * Terminal focus event.\n */\nexport interface FocusEvent {\n type: \"focus\"\n}\n\n/**\n * Terminal blur event.\n */\nexport interface BlurEvent {\n type: \"blur\"\n}\n\n/**\n * Signal event (SIGINT, SIGTERM, etc.).\n */\nexport interface SignalEvent {\n type: \"signal\"\n /** Signal name (e.g., 'SIGINT', 'SIGTERM') */\n signal: string\n}\n\n/**\n * Custom event for extensibility.\n */\nexport interface CustomEvent {\n type: \"custom\"\n /** Event name */\n name: string\n /** Event data */\n data: unknown\n}\n\n/**\n * Union of all event types.\n *\n * Events drive the render loop in interactive mode. When events are present,\n * the render loop runs until exit() is called. When events are absent,\n * the render completes when the UI is stable.\n */\nexport type Event =\n | KeyEvent\n | MouseEvent\n | ResizeEvent\n | FocusEvent\n | BlurEvent\n | SignalEvent\n | CustomEvent\n\n/**\n * Event source that can be subscribed to and unsubscribed from.\n */\nexport interface EventSource {\n /** Subscribe to events, returns unsubscribe function */\n subscribe(handler: (event: Event) => void): () => void\n /** Convert to async iterable */\n [Symbol.asyncIterator](): AsyncIterator<Event>\n}\n","/**\n * Wrap-measurer registry — runtime hook for soft-wrap-aware geometry.\n *\n * `@silvery/ag` is the layout-and-types layer; it deliberately does not own\n * grapheme/wide-char/PUA width tables (those live in `@silvery/ag-term`'s\n * `unicode.ts`). Yet the geometry helpers here — `computeSelectionFragments`\n * being the v1 consumer — need wrap-aware line breaks to emit one rectangle\n * per visual line on a soft-wrapped paragraph.\n *\n * The cross-layer hop is solved by registration, not import: the terminal\n * runtime calls `setWrapMeasurer({ wrapText })` when it boots, and the\n * fragment helper reads `getWrapMeasurer()` at call time. When no measurer\n * is registered (pure `@silvery/ag` unit tests, or a future canvas/DOM\n * adapter that hasn't wired one yet) the fallback is `\\n`-split — same\n * behavior as before this hook existed.\n *\n * **v1 scope**: module-level singleton. `@silvery/ag` is consumed by one\n * `Term` at a time per process, so a single registration is enough. A\n * future multi-Term scenario will need a per-tree binding (likely a\n * fingerprint on the AgNode root); the registry is intentionally one\n * object so the upgrade path stays mechanical.\n *\n * **Test isolation**: tests that exercise the registered path SHOULD call\n * `setWrapMeasurer(null)` in `afterEach` to drain any cross-file leak.\n * Tests that need the `\\n`-only fallback SHOULD assert `null` registration\n * before computing fragments.\n *\n * Tracking: `km-silvery.softwrap-selection-fragments` (closes Phase 4b\n * deferred wrap-spanning) — see also `hub/silvery/design/overlay-anchor-system.md`\n * § 8 (Option B chosen over lifting `wrapText` to a layering-neutral\n * package).\n */\n\n/**\n * One slice of wrapped text — produced by a registered `WrapMeasurer.wrapText`.\n *\n * `text` is the visible content for that visual line. `startOffset` /\n * `endOffset` are character indices into the *original* (un-wrapped) text,\n * so the consumer can clamp a selection range against each slice's window\n * without re-walking the wrap algorithm.\n *\n * Convention: `endOffset` is exclusive (matches `String.slice(start, end)`),\n * so `text === source.slice(startOffset, endOffset)` for hard wraps. For\n * soft wraps that drop trailing whitespace (`trim` mode), `text` may be\n * shorter than `source.slice(startOffset, endOffset)` — use the offsets\n * for selection-range arithmetic, not the slice text length.\n */\nexport interface WrapSlice {\n readonly text: string\n readonly startOffset: number\n readonly endOffset: number\n}\n\n/**\n * Runtime-supplied wrap measurement. Implementations live in `@silvery/ag-term`\n * (terminal grapheme widths) and any future adapters (canvas glyph widths,\n * DOM measureText). `@silvery/ag` consumers call `getWrapMeasurer()` and\n * fall back gracefully when null.\n */\nexport interface WrapMeasurer {\n /**\n * Given `text` and a max display width in cells, return one `WrapSlice`\n * per visual line. An empty array means \"no wrapping happened\" — callers\n * should treat this as a single-line passthrough (using the original\n * text + offsets `[0, text.length]`).\n *\n * The returned slices MUST cover the entire input in order:\n * - First slice's `startOffset === 0`\n * - Each subsequent `startOffset >= prev.endOffset`\n * - Last slice's `endOffset === text.length`\n *\n * Soft wraps that swallow whitespace are still required to advance\n * `startOffset` past the swallowed cells — this is what lets selection\n * fragments map cleanly onto visual rows.\n */\n readonly wrapText: (text: string, maxWidth: number) => readonly WrapSlice[]\n}\n\nlet _measurer: WrapMeasurer | null = null\n\n/**\n * Register the active wrap measurer. Pass `null` to clear (test teardown,\n * or a Term disposing its runtime).\n *\n * Idempotent: setting the same reference twice is a no-op. Setting a new\n * reference replaces the previous one — there's no stack. v1 assumes a\n * single Term-per-process consumer; multi-Term setups need a different\n * dispatch (see file header).\n */\nexport function setWrapMeasurer(m: WrapMeasurer | null): void {\n _measurer = m\n}\n\n/**\n * Read the active wrap measurer, or `null` if none is registered.\n *\n * Geometry helpers (`computeSelectionFragments` is the v1 consumer) call\n * this at compute-time — not at module-load — so the registration order\n * doesn't matter. The fragment helper falls back to `\\n`-split when this\n * returns null.\n */\nexport function getWrapMeasurer(): WrapMeasurer | null {\n return _measurer\n}\n","/**\n * placeFloating — pure rect math for anchor-relative floating decorations.\n *\n * Phase 4c of `km-silvery.view-as-layout-output` (overlay-anchor v1).\n *\n * Given an anchor rect (the Box's contentRect, looked up via `findAnchor`),\n * a floating target's intrinsic size, and a `Placement` directive, return the\n * rect at which the floating element should be painted this frame. No DOM,\n * no canvas, no terminal — just rects → rect. Mirrors Floating UI / Popper.js\n * vocabulary so apps moving between targets carry placement intent verbatim.\n *\n * `placeFloating` is **fixed-placement only**:\n * - The result is deterministic given (anchor, size, placement).\n * - No collision detection, no auto-flip, no auto-shift. Apps that need flip\n * behavior use `resolveFloatingPlacement`.\n * - `offset` and `alignOffset` are simple deterministic nudges, not collision\n * handling.\n *\n * **Placement vocabulary**:\n *\n * \"top-start\" \"top-center\" \"top-end\"\n * ┌───────┐ ┌──────────────┐ ┌───────┐\n * │ │ │ │ │ │\n * └───────┘ └──────────────┘ └───────┘\n * ┌───────────────────────────────────┐\n * \"left-start\" anchor \"right-start\"\n * │ │\n * │ (this is the │\n * \"left-center\" anchor \"right-center\"\n * │ contentRect) │\n * │ │\n * \"left-end\" \"right-end\"\n * └───────────────────────────────────┘\n * ┌───────┐ ┌──────────────┐ ┌───────┐\n * │ │ │ │ │ │\n * └───────┘ └──────────────┘ └───────┘\n * \"bottom-start\" \"bottom-center\" \"bottom-end\"\n *\n * Side: which edge of the anchor the floating element sits ALONG.\n * Alignment: where on the perpendicular axis the floating element starts.\n *\n * - top/bottom side → start aligns floating's left edge to anchor's left,\n * end aligns floating's right edge to anchor's right,\n * center centers along the X axis.\n * - left/right side → start aligns floating's top edge to anchor's top,\n * end aligns floating's bottom edge to anchor's bottom,\n * center centers along the Y axis.\n */\n\nimport type { CollisionStrategy, Placement, Rect } from \"./types\"\n\nexport interface FloatingPlacementOptions {\n /** Gap along the placement axis, in cells. Default: 0. */\n offset?: number\n /** Nudge along the alignment axis, in cells. Default: 0. */\n alignOffset?: number\n}\n\nexport interface FloatingCollisionOptions extends FloatingPlacementOptions {\n /** Boundary rect used for collision handling. Usually the viewport/root rect. */\n boundary?: Rect | null\n /** Collision policy. Default: \"none\". */\n collisionStrategy?: CollisionStrategy\n}\n\nexport interface FloatingPlacementResult {\n /** Final rect. Width/height always equal the requested target size. */\n rect: Rect\n /** Final placement after any flip. */\n placement: Placement\n /** True when collision handling used the opposite side. */\n flipped: boolean\n /** True when collision handling clamped the rect inside the boundary. */\n shifted: boolean\n}\n\ntype Side = \"top\" | \"bottom\" | \"left\" | \"right\"\ntype Align = \"start\" | \"center\" | \"end\"\n\nfunction splitPlacement(placement: Placement): { side: Side; align: Align } {\n const dashIdx = placement.indexOf(\"-\")\n return {\n side: placement.slice(0, dashIdx) as Side,\n align: placement.slice(dashIdx + 1) as Align,\n }\n}\n\nfunction oppositePlacement(placement: Placement): Placement {\n const { side, align } = splitPlacement(placement)\n const opposite: Record<Side, Side> = {\n top: \"bottom\",\n bottom: \"top\",\n left: \"right\",\n right: \"left\",\n }\n return `${opposite[side]}-${align}` as Placement\n}\n\nfunction rectFitsWithin(rect: Rect, boundary: Rect): boolean {\n return (\n rect.x >= boundary.x &&\n rect.y >= boundary.y &&\n rect.x + rect.width <= boundary.x + boundary.width &&\n rect.y + rect.height <= boundary.y + boundary.height\n )\n}\n\nfunction clampAxis(value: number, min: number, max: number): number {\n if (max < min) return min\n return Math.min(max, Math.max(min, value))\n}\n\nfunction shiftIntoBoundary(rect: Rect, boundary: Rect): Rect {\n return {\n x: clampAxis(rect.x, boundary.x, boundary.x + boundary.width - rect.width),\n y: clampAxis(rect.y, boundary.y, boundary.y + boundary.height - rect.height),\n width: rect.width,\n height: rect.height,\n }\n}\n\nfunction rectEqual(a: Rect, b: Rect): boolean {\n return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height\n}\n\nfunction sideOverflow(rect: Rect, boundary: Rect, side: Side): number {\n switch (side) {\n case \"top\":\n return Math.max(0, boundary.y - rect.y)\n case \"bottom\":\n return Math.max(0, rect.y + rect.height - (boundary.y + boundary.height))\n case \"left\":\n return Math.max(0, boundary.x - rect.x)\n case \"right\":\n return Math.max(0, rect.x + rect.width - (boundary.x + boundary.width))\n }\n}\n\n/**\n * Compute the absolute rect at which a floating decoration should be painted\n * relative to its anchor.\n *\n * Inputs:\n * - `anchor`: the anchor's rect (typically `findAnchor(root, id)` →\n * `contentRect`). Origin in the same absolute terminal cell space as\n * other rect signals.\n * - `target.{width, height}`: intrinsic size of the floating decoration.\n * Both must be `>= 0` — this function does not enforce minimums; callers\n * pass through what their renderer asked for.\n * - `placement`: 12-placement vocabulary. See module docstring for the\n * visual reference.\n *\n * Output: `Rect` in the same coordinate space as `anchor`. Width/height equal\n * `target.width`/`target.height` exactly (no clamping, no shifting).\n *\n * **Pure**: no allocation other than the result rect, no I/O, no logging.\n * Suitable for property tests and SILVERY_STRICT cross-checks.\n */\nexport function placeFloating(\n anchor: Rect,\n target: { width: number; height: number },\n placement: Placement,\n options: FloatingPlacementOptions = {},\n): Rect {\n const { x: ax, y: ay, width: aw, height: ah } = anchor\n const tw = target.width\n const th = target.height\n\n // Decompose the placement into side + alignment. The 12 placements split\n // into 4 sides × 3 alignments; we resolve each axis independently.\n const { side, align } = splitPlacement(placement)\n\n let x = 0\n let y = 0\n\n if (side === \"top\" || side === \"bottom\") {\n // Floating sits on the top or bottom edge of the anchor.\n // Y is fixed by the side; X varies by alignment.\n y = side === \"top\" ? ay - th : ay + ah\n if (align === \"start\") x = ax\n else if (align === \"end\") x = ax + aw - tw\n // center: place floating's mid-X on anchor's mid-X.\n else x = ax + Math.round((aw - tw) / 2)\n } else {\n // Floating sits on the left or right edge of the anchor.\n // X is fixed by the side; Y varies by alignment.\n x = side === \"left\" ? ax - tw : ax + aw\n if (align === \"start\") y = ay\n else if (align === \"end\") y = ay + ah - th\n else y = ay + Math.round((ah - th) / 2)\n }\n\n const offset = options.offset ?? 0\n const alignOffset = options.alignOffset ?? 0\n if (side === \"top\") y -= offset\n else if (side === \"bottom\") y += offset\n else if (side === \"left\") x -= offset\n else x += offset\n\n if (side === \"top\" || side === \"bottom\") x += alignOffset\n else y += alignOffset\n\n return { x, y, width: tw, height: th }\n}\n\n/**\n * Resolve a floating rect with optional viewport collision handling.\n *\n * This is the collision-aware peer of `placeFloating`. The fixed-placement\n * helper remains intentionally simple and deterministic; this function adds\n * the behavior needed by declarative popovers/tooltips: gap offsets, alignment\n * nudges, side flipping, viewport shifting, and hide-on-overflow.\n */\nexport function resolveFloatingPlacement(\n anchor: Rect,\n target: { width: number; height: number },\n placement: Placement,\n options: FloatingCollisionOptions = {},\n): FloatingPlacementResult | null {\n const boundary = options.boundary ?? null\n const strategy = options.collisionStrategy ?? \"none\"\n const requested = placeFloating(anchor, target, placement, options)\n if (!boundary || strategy === \"none\") {\n return { rect: requested, placement, flipped: false, shifted: false }\n }\n if (strategy === \"hide\") {\n return rectFitsWithin(requested, boundary)\n ? { rect: requested, placement, flipped: false, shifted: false }\n : null\n }\n\n let finalPlacement = placement\n let rect = requested\n let flipped = false\n\n if (strategy === \"flip\" || strategy === \"flip-then-shift\") {\n const side = splitPlacement(placement).side\n const requestedSideOverflow = sideOverflow(requested, boundary, side)\n if (requestedSideOverflow > 0) {\n const candidatePlacement = oppositePlacement(placement)\n const candidate = placeFloating(anchor, target, candidatePlacement, options)\n const candidateSide = splitPlacement(candidatePlacement).side\n const candidateSideOverflow = sideOverflow(candidate, boundary, candidateSide)\n if (candidateSideOverflow < requestedSideOverflow) {\n finalPlacement = candidatePlacement\n rect = candidate\n flipped = true\n }\n }\n }\n\n let shifted = false\n if (strategy === \"shift\" || strategy === \"flip-then-shift\") {\n const shiftedRect = shiftIntoBoundary(rect, boundary)\n shifted = !rectEqual(shiftedRect, rect)\n rect = shiftedRect\n }\n\n return { rect, placement: finalPlacement, flipped, shifted }\n}\n","/**\n * withLayoutSignals — reactive signal layer for AgNode layout outputs.\n *\n * Composable plugin that wraps an AgNode with reactive signals for layout\n * rects, text content, and focus state. Engine-agnostic — works with\n * Flexily, Yoga, or any future layout engine.\n *\n * Signals are WeakMap-backed and lazily created. Nodes without subscribers\n * pay zero cost. After layout completes, the pipeline calls `syncSignals()`\n * to propagate imperative state into signals.\n *\n * ## Usage\n *\n * ```ts\n * import { getLayoutSignals, syncSignals } from \"@silvery/ag/layout-signals\"\n *\n * // Get (or create) signals for a node\n * const signals = getLayoutSignals(node)\n * signals.boxRect() // read current rect\n * signals.textContent() // read current text\n *\n * // After layout/reconciler mutations, sync imperative → reactive\n * syncSignals(node)\n * ```\n *\n * ## Three-layer stack\n *\n * Layer 0: alien-signals (signal, computed, effect)\n * Layer 1: getLayoutSignals() — this module (@silvery/ag, framework-agnostic)\n * Layer 2: useSignal(signal) — @silvery/ag-react (React bridge)\n * Layer 3: useBoxRect(), useAgNode() — semantic convenience hooks\n */\n\nimport { signal } from \"@silvery/signals\"\nimport type {\n AgNode,\n AnchorEdge,\n AnchorRef,\n BoxProps,\n CursorShape,\n Decoration,\n Rect,\n SelectionIntent,\n} from \"./types\"\nimport { rectEqual } from \"./types\"\nimport { getWrapMeasurer, type WrapSlice } from \"./wrap-measurer\"\nimport { resolveFloatingPlacement } from \"./place-floating\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Writable signal — call with no args to read, call with value to write.\n */\ntype WritableSignal<T> = {\n (): T\n (value: T): void\n}\n\nexport type ObservedLayoutSignalKey = \"boxRect\" | \"boxSize\" | \"scrollRect\" | \"screenRect\"\n\nconst permanentlyObservedLayoutSignals = new WeakMap<AgNode, Set<ObservedLayoutSignalKey>>()\nconst observedLayoutSignals = new WeakMap<AgNode, Set<ObservedLayoutSignalKey>>()\nconst retainedLayoutSignals = new WeakMap<AgNode, Map<ObservedLayoutSignalKey, number>>()\n\nfunction addObservedLayoutSignal(node: AgNode, key: ObservedLayoutSignalKey): void {\n let observed = observedLayoutSignals.get(node)\n if (!observed) {\n observed = new Set()\n observedLayoutSignals.set(node, observed)\n }\n observed.add(key)\n}\n\nexport function markObservedLayoutSignal(node: AgNode, key: ObservedLayoutSignalKey): void {\n addObservedLayoutSignal(node, key)\n let permanent = permanentlyObservedLayoutSignals.get(node)\n if (!permanent) {\n permanent = new Set()\n permanentlyObservedLayoutSignals.set(node, permanent)\n }\n permanent.add(key)\n}\n\nexport function observeLayoutSignal(node: AgNode, key: ObservedLayoutSignalKey): () => void {\n addObservedLayoutSignal(node, key)\n let retained = retainedLayoutSignals.get(node)\n if (!retained) {\n retained = new Map()\n retainedLayoutSignals.set(node, retained)\n }\n retained.set(key, (retained.get(key) ?? 0) + 1)\n\n let released = false\n return () => {\n if (released) return\n released = true\n const current = retainedLayoutSignals.get(node)\n if (!current) return\n const next = (current.get(key) ?? 0) - 1\n if (next > 0) {\n current.set(key, next)\n return\n }\n current.delete(key)\n if (current.size === 0) retainedLayoutSignals.delete(node)\n if (!permanentlyObservedLayoutSignals.get(node)?.has(key)) {\n observedLayoutSignals.get(node)?.delete(key)\n }\n }\n}\n\n/**\n * Reactive projection of `AgNode.scrollState` — the layout-phase's pixel-space\n * truth about what's visible in an `overflow=\"scroll\"` container.\n *\n * This is the **single source of truth** that virtualization consumers (like\n * `useVirtualizer` + `ListView`) read to decide which items to render. By\n * subscribing to this signal instead of independently computing their own\n * visible range, consumers cannot diverge from what layout-phase actually\n * laid out on screen.\n *\n * Fields are pixel-space integers already rounded by the layout engine —\n * re-using them (instead of recomputing via `sumHeights`) guarantees\n * `leadingHeight == scrollOffset` by construction.\n *\n * `null` for non-scroll containers and for scroll containers before the first\n * layout pass (bootstrap state — virtualizers must fall back to estimates).\n */\nexport interface ScrollStateSnapshot {\n /** Current scroll offset in terminal rows (pixel-space, pre-rounded). */\n readonly offset: number\n /** Total content height (all children) in rows. */\n readonly contentHeight: number\n /** Visible height (container height minus borders/padding). */\n readonly viewportHeight: number\n /** Index of the first visible child (flexbox-measured). */\n readonly firstVisibleChild: number\n /** Index of the last visible child (flexbox-measured). */\n readonly lastVisibleChild: number\n /** Count of items hidden above the viewport. */\n readonly hiddenAbove: number\n /** Count of items hidden below the viewport. */\n readonly hiddenBelow: number\n}\n\n/**\n * Caret rect — absolute terminal coordinates of the caret declared on a\n * Box via `cursorOffset`, computed during the layout phase as the peer of\n * `scrollRect` / `screenRect` / `boxRect` / `contentRect`.\n *\n * Width/height are always 1 (the caret occupies a single cell). The\n * `visible` flag is a separate property because layout still computes the\n * coordinates even when the caret is hidden — that lets toggling\n * `visible` re-emit the caret without re-running layout.\n *\n * `shape` is **deprecated** — the terminal layer now derives the shape\n * from focus + editable state via `resolveCaretStyle` in `@silvery/ag-term`.\n * The field is kept for one cycle so external readers that were already\n * forwarding `cursor.shape` to DECSCUSR keep working; new code MUST NOT\n * branch on this field. See bead `km-silvery.cursor-invariants` invariant 6.\n */\nexport interface CursorRect {\n /** Absolute terminal X column (0-indexed) */\n readonly x: number\n /** Absolute terminal Y row (0-indexed) */\n readonly y: number\n /** Whether the caret should be visible on this frame. */\n readonly visible: boolean\n /**\n * @deprecated Target-specific. Read focus state from the active cursor\n * node via `resolveCaretStyle` in `@silvery/ag-term` instead. Removed in\n * the next cycle.\n */\n readonly shape?: CursorShape\n}\n\nfunction cursorRectEqual(a: CursorRect | null, b: CursorRect | null): boolean {\n if (a === b) return true\n if (!a || !b) return false\n return a.x === b.x && a.y === b.y && a.visible === b.visible && a.shape === b.shape\n}\n\n/**\n * Per-field equality on a list of rects. Used to skip selection-fragment\n * signal writes when nothing changed — mirrors the rect-tuple equality\n * pattern used for boxRect/scrollRect/screenRect/contentRect/cursorRect.\n *\n * Reference equality is checked first (the common no-op path); only when\n * lengths match do we walk the entries. An empty array is the canonical\n * \"no fragments\" state — the equality path treats `[]` and `[]` as equal\n * by length-zero, so collapsed/no-selection nodes don't churn the signal.\n */\nfunction selectionFragmentsEqual(a: readonly Rect[], b: readonly Rect[]): boolean {\n if (a === b) return true\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n if (!rectEqual(a[i] ?? null, b[i] ?? null)) return false\n }\n return true\n}\n\n/**\n * Stable empty-rects sentinel — `selectionFragments` defaults to this when a\n * node has no `selectionIntent` declared. Reusing the same array reference\n * means subscribers see reference-stable \"no selection\" frames and can skip\n * downstream re-computation. The array is frozen so accidental mutation\n * never corrupts the sentinel.\n */\nconst EMPTY_FRAGMENTS: readonly Rect[] = Object.freeze([])\n\n/**\n * Stable empty-decorations sentinel — `decorationRects` defaults to this when\n * a node has no `decorations` BoxProp. Same reference-stability story as\n * `EMPTY_FRAGMENTS`.\n */\nconst EMPTY_DECORATION_RECTS: readonly DecorationRect[] = Object.freeze([])\n\n/**\n * All reactive signals for an AgNode.\n *\n * Combined rect signals (layout outputs) + node signals (content/state).\n * One interface, one WeakMap, one sync function.\n */\nexport interface LayoutSignals {\n // Layout rects (synced after layout + scroll + sticky phases)\n //\n // Two-tier model:\n // - `boxRect` / `scrollRect` / `screenRect` are PIPELINE signals — written\n // every layout pass via `syncRectSignals`. They are read internally by\n // the renderer (incremental cascade, decoration phase, scroll math) and\n // are NOT consumed directly by React hooks. Mid-batch updates are part\n // of how the convergence loop reaches a fixed point.\n // - `boxRectCommitted` / `scrollRectCommitted` / `screenRectCommitted` are\n // the SUBSCRIBER signals — written only at event-batch commit boundaries\n // via `commitLayoutSnapshot`. React hooks (`useBoxRect()` /\n // `useScrollRect()` / `useScreenRect()`) subscribe to these and see a\n // value that is invariant across every convergence pass within one\n // batch. forceUpdate fires at most once per batch — there is no\n // feedback edge between read and re-render.\n //\n // See `commitLayoutSnapshot` below and `useBoxRect` in @silvery/ag-react.\n readonly boxRect: WritableSignal<Rect | null>\n readonly scrollRect: WritableSignal<Rect | null>\n readonly screenRect: WritableSignal<Rect | null>\n readonly boxRectCommitted: WritableSignal<Rect | null>\n readonly scrollRectCommitted: WritableSignal<Rect | null>\n readonly screenRectCommitted: WritableSignal<Rect | null>\n\n /**\n * Content-box rect — `boxRect` minus border and padding (CSS content area).\n * Peer of `boxRect`/`scrollRect`/`screenRect`, synced after layout. Null\n * when the node has no boxRect yet (pre-layout) or is not a Box.\n *\n * This is the canonical origin for caret positioning, popover anchors,\n * selection fragments, and any feature that needs to draw \"inside\" a Box's\n * content area without re-deriving the border/padding math at the call\n * site. `computeCursorRect` reads from this rect — Phase 4 / overlay-anchor\n * work will too. See `km-silvery.cursor-invariants` invariant 3.\n */\n readonly contentRect: WritableSignal<Rect | null>\n\n /**\n * Absolute terminal coordinates of the caret declared by this node's\n * `BoxProps.cursorOffset`. Null when the node has no cursorOffset prop, or\n * before the first layout pass populates `scrollRect`.\n *\n * Phase 2 of `km-silvery.view-as-layout-output` — the scheduler reads this\n * signal (rather than `cursorStore.getCursorState()`) to emit caret\n * positioning ANSI. Because layout phase runs synchronously before each\n * render, the very first frame after mount sees the correct caret — no\n * effect-chain stale-read on conditional mounts.\n */\n readonly cursorRect: WritableSignal<CursorRect | null>\n\n /**\n * Focused-node id declared by this node's `BoxProps.focused`. The value is\n * the node's `id` (preferred) or `testID` when `props.focused === true`,\n * else `null`. Phase 4a of `km-silvery.view-as-layout-output` — the\n * focus-renderer reads this signal (and/or `findActiveFocusedNodeId(root)`)\n * to paint focus styling without going through the `useFocus` →\n * `FocusManager` → `useSyncExternalStore` effect chain.\n *\n * Per-node by design (peer of `cursorRect`): each Box that participates in\n * focus carries its own declared id here, and the tree-walk lookup picks\n * the deepest visible declarer. Unmount is handled by WeakMap GC + the\n * per-frame recompute in `syncRectSignals` clearing back to null when the\n * prop is removed. See bead `km-silvery.phase4-split-focus-selection`.\n */\n readonly focusedNodeId: WritableSignal<string | null>\n\n /**\n * Geometric output of `BoxProps.selectionIntent` — the list of rectangles\n * (one per visual line spanned) that the selection renderer should paint\n * with highlight bg this frame. Empty array when the node declares no\n * `selectionIntent` or when the intent is collapsed (`from === to`).\n *\n * Phase 4b of `km-silvery.view-as-layout-output` — peer of `cursorRect`\n * (caret) and `focusedNodeId` (focus). The selection renderer reads this\n * signal (and/or `findActiveSelectionFragments(root)`) to paint highlight\n * styling without going through the legacy `SelectionFeature` capability\n * + `useSelection` `useSyncExternalStore` chain.\n *\n * Per-node by design: each Box that participates in selection carries its\n * own fragments here. The aggregator (`findActiveSelectionFragments`)\n * concatenates fragments across all mounted declarers — multi-node\n * selection composes for free. Stale-cleanup is handled by WeakMap GC +\n * the per-frame recompute in `syncRectSignals` clearing back to the empty\n * sentinel when the prop is removed (mirrors cursor invariant 5 + focus\n * invariant 3). See bead `km-silvery.phase4-split-focus-selection`.\n */\n readonly selectionFragments: WritableSignal<readonly Rect[]>\n\n // Scroll state for overflow=\"scroll\" containers (null otherwise, or until\n // first layout pass). Peer of rect signals — synced by syncRectSignals.\n readonly scrollState: WritableSignal<ScrollStateSnapshot | null>\n\n /**\n * Geometric output of `BoxProps.anchorRef` — the rect that other Boxes'\n * decorations resolve to when they reference this anchor by id. The\n * registered rect is the Box's `contentRect` (border + padding excluded);\n * edge-specific rects are derived by `placeFloating` at consumption time.\n *\n * Phase 4c of `km-silvery.view-as-layout-output` (overlay-anchor v1) — peer\n * of `cursorRect`/`focusedNodeId`/`selectionFragments`. The tree-walk\n * lookup `findAnchor(root, id)` reads this signal (and falls back to\n * `computeAnchorRect` for nodes without signals).\n *\n * Null when:\n * - the node has no `anchorRef` BoxProp, OR\n * - `contentRect` is unavailable (pre-layout / clipped to zero size).\n */\n readonly anchorRect: WritableSignal<Rect | null>\n\n /**\n * Geometric output of `BoxProps.decorations` — the resolved per-decoration\n * rect list this frame. Each entry carries the source kind, id, and rects\n * (popover/tooltip → one rect at the placed position; highlight → one rect\n * within the owning Box's content area).\n *\n * v1 emits one rect per decoration. The list mirrors the BoxProp's order so\n * paint order is deterministic. When an anchor lookup fails (popover/tooltip\n * with no matching `anchorRef`), the entry's `rects` is empty — the\n * renderer skips it.\n *\n * Phase 4c of `km-silvery.view-as-layout-output` (overlay-anchor v1).\n */\n readonly decorationRects: WritableSignal<readonly DecorationRect[]>\n\n // Node state (synced from reconciler + focus manager)\n readonly textContent: WritableSignal<string | undefined>\n readonly focused: WritableSignal<boolean>\n}\n\n/**\n * Geometric output of one `Decoration` entry — the resolved rects for paint\n * time, plus the source kind/id so the renderer can dispatch by kind.\n *\n * Phase 4c of `km-silvery.view-as-layout-output` (overlay-anchor v1).\n *\n * v1 emits one rect per decoration. Soft-wrap aware highlight fragmentation\n * (multiple rects per highlight) is deferred to v2 — same path as\n * `selectionFragments` once the find/replace consumer ships.\n *\n * `rects` may be empty when an anchor lookup failed (popover/tooltip with a\n * missing `anchorId`); consumers SHOULD skip empty entries rather than\n * paint a 0×0 rect.\n */\nexport interface DecorationRect {\n /** Decoration kind from the source `Decoration` entry. */\n readonly kind: Decoration[\"kind\"]\n /** Stable id from the source `Decoration` entry. */\n readonly id: string\n /** Resolved rects in absolute terminal cell space. May be empty. */\n readonly rects: readonly Rect[]\n}\n\n// ============================================================================\n// Cache\n// ============================================================================\n\nconst signalMap = new WeakMap<AgNode, LayoutSignals>()\n\n/**\n * Get or create layout signals for a node.\n *\n * Lazily created on first access. Automatically garbage-collected\n * when the node is removed from the tree (WeakMap semantics).\n */\nexport function getLayoutSignals(node: AgNode): LayoutSignals {\n let s = signalMap.get(node)\n if (!s) {\n s = {\n boxRect: signal<Rect | null>(node.boxRect),\n scrollRect: signal<Rect | null>(node.scrollRect),\n screenRect: signal<Rect | null>(node.screenRect),\n // Committed signals seed identically to in-flight so the first\n // synchronous read (before any layout pass) sees consistent values.\n // After the first commit boundary, they advance independently per pass.\n boxRectCommitted: signal<Rect | null>(node.boxRect),\n scrollRectCommitted: signal<Rect | null>(node.scrollRect),\n screenRectCommitted: signal<Rect | null>(node.screenRect),\n contentRect: signal<Rect | null>(computeContentRect(node)),\n cursorRect: signal<CursorRect | null>(computeCursorRect(node)),\n focusedNodeId: signal<string | null>(computeFocusedNodeId(node)),\n selectionFragments: signal<readonly Rect[]>(computeSelectionFragments(node)),\n scrollState: signal<ScrollStateSnapshot | null>(snapshotScrollState(node)),\n anchorRect: signal<Rect | null>(computeAnchorRect(node)),\n decorationRects: signal<readonly DecorationRect[]>(EMPTY_DECORATION_RECTS),\n textContent: signal<string | undefined>(node.textContent),\n focused: signal<boolean>(node.interactiveState?.focused ?? false),\n }\n signalMap.set(node, s)\n }\n return s\n}\n\n/**\n * Compute the content-box rect for a node — `scrollRect` minus border and\n * padding (CSS content area in absolute terminal coordinates).\n *\n * Returns null when `scrollRect` is not yet populated (pre-layout) or when\n * border + padding would shrink the area to zero/negative width or height\n * (clipped/empty content area).\n *\n * The math is the canonical \"border + padding\" calculation that the layout\n * engine uses internally. Lifted here so consumers (cursor positioning,\n * popover anchors, selection overlays) read one signal instead of re-deriving\n * the offsets at every call site.\n */\nexport function computeContentRect(node: AgNode): Rect | null {\n const props = node.props as BoxProps | undefined\n const scroll = node.scrollRect\n if (!scroll) return null\n\n // Border + padding offsets. Per the layout engine's own precedence:\n // paddingLeft / paddingRight / paddingTop / paddingBottom\n // override paddingX / paddingY which override `padding` shorthand.\n const padLeft = props?.paddingLeft ?? props?.paddingX ?? props?.padding ?? 0\n const padRight = props?.paddingRight ?? props?.paddingX ?? props?.padding ?? 0\n const padTop = props?.paddingTop ?? props?.paddingY ?? props?.padding ?? 0\n const padBottom = props?.paddingBottom ?? props?.paddingY ?? props?.padding ?? 0\n const borderLeft = props?.borderStyle ? 1 : 0\n const borderRight = props?.borderStyle ? 1 : 0\n const borderTop = props?.borderStyle ? 1 : 0\n const borderBottom = props?.borderStyle ? 1 : 0\n\n const x = scroll.x + borderLeft + padLeft\n const y = scroll.y + borderTop + padTop\n const width = scroll.width - borderLeft - borderRight - padLeft - padRight\n const height = scroll.height - borderTop - borderBottom - padTop - padBottom\n\n if (width <= 0 || height <= 0) return null\n return { x, y, width, height }\n}\n\n/**\n * Compute the absolute caret rect for a node based on its `cursorOffset`\n * prop and current `contentRect`. Caret coordinates are content-area-relative\n * (inside border + padding), so this delegates to `computeContentRect` for\n * the origin instead of redoing the border/padding math here.\n *\n * Returns null when:\n * - the node has no `cursorOffset` prop, OR\n * - `scrollRect` is not yet populated (pre-layout), OR\n * - the content box collapsed to zero/negative size (no place to draw).\n *\n * `computeContentRect` keeps cursor positioning and overlay anchoring on the\n * same origin — Phase 4 / overlay-anchor consumers read `contentRect`\n * directly and won't drift from where the caret lands. See bead\n * `km-silvery.cursor-invariants` invariant 3.\n */\nexport function computeCursorRect(node: AgNode): CursorRect | null {\n const props = node.props as BoxProps | undefined\n const offset = props?.cursorOffset\n if (!offset) return null\n const content = computeContentRect(node)\n if (!content) return null\n\n return {\n x: content.x + offset.col,\n y: content.y + offset.row,\n visible: offset.visible !== false,\n // shape is deprecated (invariant 6) — terminal layer derives the shape\n // from focus + editable state via resolveCaretStyle. Forwarded here for\n // one-cycle back-compat with callers still reading cursor.shape.\n shape: offset.shape,\n }\n}\n\n/**\n * Compute the focused-node id for a node based on its `focused` BoxProp.\n *\n * Returns the node's `id` (preferred) or `testID` when `props.focused === true`,\n * else `null`. This is the per-node value carried in\n * `LayoutSignals.focusedNodeId` — the tree-walk lookup\n * `findActiveFocusedNodeId(root)` picks the deepest non-null among all\n * declarers (Phase 4a precedence rule).\n *\n * Identity priority: `id` > `testID`. Apps that want stable focus identity\n * should set one of those props alongside `focused={true}`. When neither is\n * set but `focused === true`, an opaque sentinel (`\"__focused__\"`) is\n * returned so the signal is still observable as \"something is focused\" —\n * downstream consumers should not depend on the sentinel value beyond\n * non-null/null.\n */\nexport function computeFocusedNodeId(node: AgNode): string | null {\n const props = node.props as (BoxProps & { id?: string; testID?: string }) | undefined\n if (!props?.focused) return null\n // Prefer `id` (typed on TestProps) then `testID`, then a sentinel so the\n // signal remains observable even on anonymous focused declarers.\n if (typeof props.id === \"string\" && props.id.length > 0) return props.id\n if (typeof props.testID === \"string\" && props.testID.length > 0) return props.testID\n return \"__focused__\"\n}\n\n// ============================================================================\n// Anchor rect (Phase 4c — overlay-anchor v1)\n// ============================================================================\n\n/**\n * Resolve the `anchorRef` BoxProp into a string id. Accepts the shorthand\n * `anchorRef=\"my-id\"` and the structured `anchorRef={{ id: \"my-id\" }}` form.\n *\n * Returns `null` when no anchorRef is present, or when the prop is malformed\n * (empty id string). Apps that need stable anchor identity should always pass\n * a non-empty string.\n */\nfunction resolveAnchorId(node: AgNode): string | null {\n const props = node.props as BoxProps | undefined\n const ref = props?.anchorRef\n if (!ref) return null\n if (typeof ref === \"string\") return ref.length > 0 ? ref : null\n // Structured form: AnchorRef object with `id` field.\n const ar = ref as AnchorRef\n if (typeof ar.id === \"string\" && ar.id.length > 0) return ar.id\n return null\n}\n\n/**\n * Compute the anchor rect for a Box that declares `anchorRef`. The registered\n * rect is the Box's `contentRect` — the inner area inside border + padding\n * — which is the canonical origin for placement math. Edge-specific rects\n * (top/bottom/left/right) are derived by `placeFloating` at consumption time\n * rather than baked into the registry.\n *\n * Returns `null` when:\n * - the node has no `anchorRef` BoxProp (or it's empty), OR\n * - `contentRect` is unavailable (pre-layout / clipped to zero size).\n *\n * Phase 4c of `km-silvery.view-as-layout-output` (overlay-anchor v1).\n */\nexport function computeAnchorRect(node: AgNode): Rect | null {\n const id = resolveAnchorId(node)\n if (id === null) return null\n return computeContentRect(node)\n}\n\n/**\n * Walk the tree and find the rect for an anchor by id. Returns `null` when\n * no Box declares `anchorRef` with a matching id, or when the matching Box's\n * `contentRect` is unavailable this frame (pre-layout / clipped).\n *\n * **Optional `edge` parameter** — when supplied, returns a 1-cell-thick rect\n * along the requested edge of the anchor (`top`, `bottom`, `left`, `right`).\n * Convenience for callers that want to draw against a specific edge without\n * threading the full content rect through `placeFloating`. Without `edge`,\n * returns the full content rect.\n *\n * **Walk order**: post-order (deepest-first). If two anchors share an id —\n * the contract says they shouldn't, but the substrate doesn't enforce\n * uniqueness — the deeper / later-rendered one wins. This matches the\n * deepest-wins precedence used by cursor and focus walks.\n *\n * Per-node cost: one `props.anchorRef` check + one signal lookup (or one\n * direct compute when no signal is allocated). Trees with no anchors return\n * `null` after a single traversal.\n *\n * Phase 4c of `km-silvery.view-as-layout-output` (overlay-anchor v1).\n */\nexport function findAnchor(root: AgNode, id: string, edge?: AnchorEdge): Rect | null {\n let result: Rect | null = null\n\n function walk(node: AgNode): void {\n for (const child of node.children) {\n walk(child)\n }\n const nodeId = resolveAnchorId(node)\n if (nodeId !== id) return\n const s = signalMap.get(node)\n const rect = s ? s.anchorRect() : computeAnchorRect(node)\n if (rect) {\n // Last-write-wins (deepest in post-order).\n result = rect\n }\n }\n\n walk(root)\n\n if (result === null || edge === undefined) return result\n\n // Slice the requested edge into a 1-cell-thick rect. Coordinates are\n // absolute, mirroring `cursorRect` / `selectionFragments` conventions.\n const r: Rect = result\n switch (edge) {\n case \"top\":\n return { x: r.x, y: r.y, width: r.width, height: 1 }\n case \"bottom\":\n return { x: r.x, y: r.y + Math.max(0, r.height - 1), width: r.width, height: 1 }\n case \"left\":\n return { x: r.x, y: r.y, width: 1, height: r.height }\n case \"right\":\n return { x: r.x + Math.max(0, r.width - 1), y: r.y, width: 1, height: r.height }\n }\n}\n\n// ============================================================================\n// Decoration rects (Phase 4c — overlay-anchor v1)\n// ============================================================================\n\n/**\n * Compute the resolved decoration rects for a node based on its `decorations`\n * BoxProp. Each entry produces one `DecorationRect` whose `rects` may be empty\n * when an anchor lookup fails.\n *\n * **Behavior by kind**:\n * - `popover` / `tooltip`: requires `anchorId` + `placement` + `size`. The\n * anchor rect is looked up via `findAnchor(root, anchorId)`; if found and\n * all required fields are present, `placeFloating` produces the placed\n * rect. Missing anchor or missing required fields → empty rect list.\n * - `highlight`: the `rect` field, if present, is translated from\n * content-relative coordinates into absolute terminal coordinates by\n * adding the owning Box's `contentRect.{x, y}`. Missing rect or no\n * contentRect → empty rect list.\n *\n * **Per-frame**: this runs in the layout-phase notify pass, so anchor rects\n * are populated for the same frame. Anchors declared deeper in the tree\n * resolve correctly because the function takes the root tree as input rather\n * than relying on a separately-built map.\n *\n * Phase 4c of `km-silvery.view-as-layout-output` (overlay-anchor v1).\n */\nexport function computeDecorationRects(node: AgNode, root: AgNode): readonly DecorationRect[] {\n const props = node.props as BoxProps | undefined\n const decos = props?.decorations\n if (!decos || decos.length === 0) return EMPTY_DECORATION_RECTS\n\n const out: DecorationRect[] = []\n const content = computeContentRect(node)\n\n for (const d of decos) {\n if (d.kind === \"popover\" || d.kind === \"tooltip\") {\n // Floating decoration — requires anchor + placement + size.\n if (!d.anchorId || !d.placement || !d.size) {\n out.push({ kind: d.kind, id: d.id, rects: [] })\n continue\n }\n const anchor = findAnchor(root, d.anchorId)\n if (!anchor) {\n out.push({ kind: d.kind, id: d.id, rects: [] })\n continue\n }\n const placed = resolveFloatingPlacement(anchor, d.size, d.placement, {\n offset: d.offset,\n alignOffset: d.alignOffset,\n collisionStrategy: d.collisionStrategy,\n boundary: root.boxRect,\n })\n out.push({ kind: d.kind, id: d.id, rects: placed ? [placed.rect] : [] })\n } else if (d.kind === \"highlight\") {\n if (!d.rect || !content) {\n out.push({ kind: d.kind, id: d.id, rects: [] })\n continue\n }\n // Highlight rect is content-relative; translate to absolute coords.\n out.push({\n kind: d.kind,\n id: d.id,\n rects: [\n {\n x: content.x + d.rect.x,\n y: content.y + d.rect.y,\n width: d.rect.width,\n height: d.rect.height,\n },\n ],\n })\n }\n }\n\n return out.length === 0 ? EMPTY_DECORATION_RECTS : out\n}\n\n/**\n * Per-field equality on a list of `DecorationRect`. Used to skip\n * `decorationRects` signal writes when nothing changed — mirrors the\n * `selectionFragmentsEqual` pattern.\n */\nfunction decorationRectsEqual(a: readonly DecorationRect[], b: readonly DecorationRect[]): boolean {\n if (a === b) return true\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 (ai.kind !== bi.kind) return false\n if (ai.id !== bi.id) return false\n if (ai.rects.length !== bi.rects.length) return false\n for (let j = 0; j < ai.rects.length; j++) {\n if (!rectEqual(ai.rects[j] ?? null, bi.rects[j] ?? null)) return false\n }\n }\n return true\n}\n\n/**\n * Walk the tree and collect every resolved decoration rect. Concatenated in\n * tree order (post-order — same shape as `findActiveSelectionFragments`).\n *\n * Phase 4c of `km-silvery.view-as-layout-output` (overlay-anchor v1).\n *\n * Per-node cost: one `props.decorations` check + one signal lookup. Trees\n * with no decorations return `[]` after a single traversal.\n */\nexport function findActiveDecorationRects(root: AgNode): readonly DecorationRect[] {\n const out: DecorationRect[] = []\n\n function walk(node: AgNode): void {\n for (const child of node.children) {\n walk(child)\n }\n const props = node.props as BoxProps | undefined\n if (props?.decorations && props.decorations.length > 0) {\n const s = signalMap.get(node)\n const rects = s ? s.decorationRects() : computeDecorationRects(node, root)\n for (const r of rects) out.push(r)\n }\n }\n\n walk(root)\n return out\n}\n\n// ============================================================================\n// Selection fragments (Phase 4b — selection as overlay/decoration)\n// ============================================================================\n\n/**\n * Collect the textual content of a selection-declaring Box.\n *\n * The selection-fragment math operates on the rendered text content of the\n * owning Box — `selectionIntent.{from,to}` are character offsets into this\n * string. For Box nodes, the canonical content is the concatenation of\n * descendant `silvery-text` nodes' `textContent` (in tree order), with `\\n`\n * separators between adjacent Text/Box children that introduce visual line\n * breaks.\n *\n * v1 behaviour (kept intentionally minimal):\n * - A Box with `silvery-text` children: concatenates `textContent` strings\n * from those children. Two adjacent text children produce one logical\n * line; if you want a line break, embed `\\n` in the text.\n * - A Box with mixed children: same — only `silvery-text` descendants\n * contribute. Nested Box children don't add line breaks (they're treated\n * as transparent for content purposes).\n * - A `silvery-text` node directly carrying the prop: its own `textContent`\n * is the content.\n *\n * This keeps the v1 model honest: declare `selectionIntent` on a Box (or\n * Text) whose text content is the source of truth for the selection. Apps\n * that want per-line semantics can split the selection across multiple\n * intent declarations.\n */\nfunction collectSelectionText(node: AgNode): string {\n if (node.type === \"silvery-text\") {\n return node.textContent ?? \"\"\n }\n // Box (or root): concatenate descendant text. Walk DFS, gather\n // `silvery-text` content in tree order.\n let out = \"\"\n const stack: AgNode[] = [node]\n while (stack.length) {\n const cur = stack.pop()!\n // Push children in reverse so DFS visits them in order.\n for (let i = cur.children.length - 1; i >= 0; i--) {\n const child = cur.children[i]\n if (child) stack.push(child)\n }\n if (cur === node) continue\n if (cur.type === \"silvery-text\" && cur.textContent !== undefined) {\n out += cur.textContent\n }\n }\n return out\n}\n\n/**\n * Compute the geometric fragments for a node's `selectionIntent` — the list\n * of rectangles (one per visual line spanned) that the selection renderer\n * should paint with highlight bg this frame.\n *\n * Returns:\n * - `[]` when the node has no `selectionIntent` prop, or when the intent is\n * collapsed (`from === to`), or when the content rect is unavailable\n * (pre-layout / clipped to zero size).\n * - `[Rect]` for a single-visual-line selection.\n * - `[Rect, Rect, ...]` for multi-line selections (split per visual line).\n *\n * **Geometry** (mirrors text-editor / ProseMirror conventions):\n * - First line: from `(content.x + fromCol, content.y + fromLine)` to the\n * end of the line. If single-line, runs to `toCol`.\n * - Middle lines: full content-rect width, one row each.\n * - Last line: from `(content.x, content.y + toLine)` to `toCol` chars.\n *\n * Coordinates are absolute terminal cells, matching `cursorRect`'s\n * coordinate space. Width is in cells (one rect per visual line).\n *\n * **Soft-wrap awareness (Option B)**: when a wrap measurer is registered\n * via `setWrapMeasurer({ wrapText })` AND the content rect width is known,\n * this function splits on the measurer's per-visual-line slices — a\n * 60-char paragraph wrapped at width 20 produces 3 fragments rather than\n * one wide rectangle. The terminal runtime (`@silvery/ag-term`) registers\n * its grapheme-aware `wrapText` at startup; pure `@silvery/ag` consumers\n * (no terminal) fall back to `\\n`-only splitting which preserves the\n * pre-Option-B behavior bit-for-bit. See `wrap-measurer.ts` for the\n * registry contract. Closes Phase 4b deferred wrap-spanning (bead\n * `km-silvery.softwrap-selection-fragments`).\n */\nexport function computeSelectionFragments(node: AgNode): readonly Rect[] {\n const props = node.props as (BoxProps & { selectionIntent?: SelectionIntent }) | undefined\n const intent = props?.selectionIntent\n if (!intent) return EMPTY_FRAGMENTS\n // Collapsed selection: caret is rendered separately (cursorOffset).\n if (intent.from >= intent.to) return EMPTY_FRAGMENTS\n const content = computeContentRect(node)\n if (!content) return EMPTY_FRAGMENTS\n\n const text = collectSelectionText(node)\n if (text.length === 0) return EMPTY_FRAGMENTS\n\n // Build the per-visual-line slice list. Two paths:\n // (a) Wrap measurer registered AND content width is known → walk every\n // paragraph (split on `\\n`) through the measurer to get\n // grapheme-correct soft-wrap slices. The measurer returns []\n // for paragraphs that fit unchanged; we fabricate a single-slice\n // passthrough in that case so the downstream loop has uniform\n // input.\n // (b) No measurer (or width <=0) → `\\n`-only split, preserving the\n // pre-Option-B behavior bit-for-bit. Pure-`@silvery/ag` unit tests\n // hit this branch.\n const measurer = getWrapMeasurer()\n const visualLines: VisualLine[] =\n measurer !== null && content.width > 0\n ? buildVisualLinesWithMeasurer(text, content.width, measurer.wrapText)\n : buildVisualLinesNewlineOnly(text)\n\n // Selection range projected onto each visual line. Each line either\n // contributes one rect (clamped to its slice window) or zero (selection\n // doesn't overlap that line).\n const fragments: Rect[] = []\n for (let i = 0; i < visualLines.length; i++) {\n const line = visualLines[i]!\n // Skip lines entirely before the selection start.\n if (line.endOffset <= intent.from) continue\n // Stop once we pass the selection end. (Lines are in order.)\n if (line.startOffset >= intent.to) break\n\n const localFrom = Math.max(0, intent.from - line.startOffset)\n const localTo = Math.min(line.text.length, intent.to - line.startOffset)\n const width = Math.max(0, localTo - localFrom)\n if (width === 0) continue\n\n fragments.push({\n x: content.x + localFrom,\n y: content.y + i,\n width,\n height: 1,\n })\n }\n\n return fragments.length === 0 ? EMPTY_FRAGMENTS : fragments\n}\n\n/**\n * One visual line in the canonical fragment-builder format: the visible\n * text plus the original-text offsets that produced it. Used by both\n * the wrap-measurer path and the `\\n`-only fallback so the projection\n * loop in `computeSelectionFragments` is uniform.\n */\ninterface VisualLine {\n readonly text: string\n readonly startOffset: number\n readonly endOffset: number\n}\n\n/**\n * Walk paragraphs (split on `\\n`) through the registered wrap measurer to\n * produce per-visual-line slices. When a paragraph fits within the width\n * unchanged, the measurer returns `[]` — we synthesize a single-slice\n * passthrough so the downstream loop sees uniform input.\n *\n * Maintains the invariant that visual-line offsets are monotone and cover\n * the full input (including the `\\n` terminator counted as a zero-width\n * boundary so cross-paragraph selections stay aligned).\n */\nfunction buildVisualLinesWithMeasurer(\n text: string,\n width: number,\n wrapText: (text: string, maxWidth: number) => readonly WrapSlice[],\n): VisualLine[] {\n const out: VisualLine[] = []\n // Walk paragraphs. We re-scan the source string ourselves (rather than\n // calling `text.split('\\n')`) because we need per-paragraph offsets to\n // translate measurer offsets (which are paragraph-local) into source\n // offsets (which are what selectionIntent.from/to use).\n let paraStart = 0\n for (let i = 0; i <= text.length; i++) {\n const isEnd = i === text.length\n const isNewline = !isEnd && text.charCodeAt(i) === 10 /* \\n */\n if (!isEnd && !isNewline) continue\n\n const para = text.slice(paraStart, i)\n const slices = wrapText(para, width)\n if (slices.length === 0) {\n // Measurer signaled \"no wrap needed\" — passthrough the paragraph as\n // one visual line. Empty paragraphs (consecutive `\\n`) also land here\n // and produce a zero-length line, which keeps line indexing aligned.\n out.push({\n text: para,\n startOffset: paraStart,\n endOffset: paraStart + para.length,\n })\n } else {\n for (const slice of slices) {\n out.push({\n text: slice.text,\n startOffset: paraStart + slice.startOffset,\n endOffset: paraStart + slice.endOffset,\n })\n }\n }\n\n // Advance past the `\\n` terminator. The newline cell is not its own\n // visual line — it's the boundary between paragraphs — so we just bump\n // `paraStart` to the next paragraph and let the loop continue.\n paraStart = i + 1\n }\n return out\n}\n\n/**\n * Fallback: split on `\\n` only. Preserves pre-Option-B geometry exactly so\n * unit tests that exercise the framework-only layer (no terminal Term\n * registered) keep passing without changes.\n *\n * The `endOffset` of each line is the position of the `\\n` (or `text.length`\n * for the trailing line) — this matches the convention used by\n * `buildVisualLinesWithMeasurer`, where the newline is a zero-width\n * paragraph boundary rather than a visual line of its own.\n */\nfunction buildVisualLinesNewlineOnly(text: string): VisualLine[] {\n const out: VisualLine[] = []\n let lineStart = 0\n for (let i = 0; i <= text.length; i++) {\n if (i === text.length || text.charCodeAt(i) === 10 /* \\n */) {\n out.push({\n text: text.slice(lineStart, i),\n startOffset: lineStart,\n endOffset: i,\n })\n lineStart = i + 1\n }\n }\n return out\n}\n\n/**\n * Project AgNode.scrollState → ScrollStateSnapshot (the subset the virtualizer\n * needs). Returns null if the node has no scroll state yet (non-scroll\n * containers or fresh scroll containers pre-layout).\n *\n * Keeping this projection tight means callers can compare snapshots by\n * per-field equality without pulling the mutable underlying object into\n * consumer code.\n */\nfunction snapshotScrollState(node: AgNode): ScrollStateSnapshot | null {\n const ss = node.scrollState\n if (!ss) return null\n return {\n offset: ss.offset,\n contentHeight: ss.contentHeight,\n viewportHeight: ss.viewportHeight,\n firstVisibleChild: ss.firstVisibleChild,\n lastVisibleChild: ss.lastVisibleChild,\n hiddenAbove: ss.hiddenAbove,\n hiddenBelow: ss.hiddenBelow,\n }\n}\n\n/** Per-field equality check for ScrollStateSnapshot (skips allocation). */\nfunction scrollStateEqual(a: ScrollStateSnapshot | null, b: ScrollStateSnapshot | null): boolean {\n if (a === b) return true\n if (!a || !b) return false\n return (\n a.offset === b.offset &&\n a.contentHeight === b.contentHeight &&\n a.viewportHeight === b.viewportHeight &&\n a.firstVisibleChild === b.firstVisibleChild &&\n a.lastVisibleChild === b.lastVisibleChild &&\n a.hiddenAbove === b.hiddenAbove &&\n a.hiddenBelow === b.hiddenBelow\n )\n}\n\n/** Check whether a node has signals allocated (for testing). */\nexport function hasLayoutSignals(node: AgNode): boolean {\n return signalMap.has(node)\n}\n\nexport function hasObservedLayoutSignal(node: AgNode, key: ObservedLayoutSignalKey): boolean {\n return observedLayoutSignals.get(node)?.has(key) ?? false\n}\n\n// ============================================================================\n// Sync: imperative state → signals\n// ============================================================================\n\n/**\n * Sync all rect signals from the node's current values.\n *\n * Called from notifyLayoutSubscribers after layout + scroll + sticky\n * phases complete. Only syncs nodes that have signals allocated.\n * Reference-equality check prevents unnecessary downstream updates.\n */\nexport function syncRectSignals(node: AgNode): void {\n // For caret-bearing nodes AND focus-declaring nodes, allocate signals\n // lazily so tree-walk lookups (`findActiveCursorRect`,\n // `findActiveFocusedNodeId`) see them via `getLayoutSignals`. Without\n // this, a node that ONLY uses `cursorOffset` / `focused` (no useBoxRect /\n // useScrollRect consumers) would never have signals allocated, and the\n // caret/focus would never reach the renderer. This is the prop-as-output\n // equivalent of `useCursor`'s former `useScrollRect` subscription and\n // `useFocus`'s former `useSyncExternalStore` subscription.\n const props = (node.props as BoxProps | undefined) ?? undefined\n const hasCursorOffset = !!props?.cursorOffset\n const hasFocused = !!props?.focused\n const hasSelectionIntent = !!props?.selectionIntent\n // anchorRef + decorations follow the same lazy-allocation rule as\n // cursorOffset/focused/selectionIntent: a node that ONLY uses anchorRef\n // (no useBoxRect / useScrollRect consumers) must still get signals\n // allocated so `findAnchor` / `findActiveDecorationRects` see it via\n // `getLayoutSignals`. Without this, anchored popovers wouldn't reach the\n // overlay layer until the first useScrollRect subscriber happens to mount.\n const hasAnchorRef = !!props?.anchorRef\n const hasDecorations = !!(props?.decorations && props.decorations.length > 0)\n const s =\n hasCursorOffset || hasFocused || hasSelectionIntent || hasAnchorRef || hasDecorations\n ? getLayoutSignals(node)\n : signalMap.get(node)\n if (!s) return\n\n if (node.boxRect !== s.boxRect()) s.boxRect(node.boxRect)\n if (node.scrollRect !== s.scrollRect()) s.scrollRect(node.scrollRect)\n if (node.screenRect !== s.screenRect()) s.screenRect(node.screenRect)\n\n // Sync contentRect — peer of the rect trio (invariant 3). Recomputed\n // every layout pass because border/padding can change without `scrollRect`\n // changing reference (e.g., theme swap → border style toggle).\n const nextContentRect = computeContentRect(node)\n if (!rectEqual(nextContentRect, s.contentRect())) {\n s.contentRect(nextContentRect)\n }\n\n // Sync cursorRect — peer of the other rect signals, computed from the\n // node's `cursorOffset` BoxProp + contentRect. Only nodes with\n // `cursorOffset` have a non-null cursorRect; clearing back to null when\n // the prop is removed is handled by `computeCursorRect` returning null.\n //\n // **Invariant 2 (prop-change recompute)**: `computeCursorRect` reads from\n // `node.props.cursorOffset` directly, so col/row/visible/shape changes\n // pick up the new value even when `boxRect`/`scrollRect`/`contentRect`\n // didn't change. The reference-inequality writes above are intentionally\n // not gated on rect inequality.\n const nextCursorRect = computeCursorRect(node)\n if (!cursorRectEqual(nextCursorRect, s.cursorRect())) {\n s.cursorRect(nextCursorRect)\n }\n\n // Sync focusedNodeId — Phase 4a peer of cursorRect. `computeFocusedNodeId`\n // reads from `node.props.focused` directly so toggling the prop without\n // any rect change still propagates to subscribers (mirrors cursor\n // invariant 2 — prop-change recompute). When the prop is removed, the\n // computed id becomes null and the signal clears, so per-frame walks\n // can't see ghost focuses on stale nodes (mirrors invariant 5).\n const nextFocusedId = computeFocusedNodeId(node)\n if (nextFocusedId !== s.focusedNodeId()) {\n s.focusedNodeId(nextFocusedId)\n }\n\n // Sync selectionFragments — Phase 4b peer of cursorRect/focusedNodeId.\n // `computeSelectionFragments` reads from `node.props.selectionIntent`\n // directly so toggling `from`/`to` (or removing the prop) propagates to\n // subscribers without any rect change (mirrors cursor invariant 2 + focus\n // invariant 2). Empty intents collapse to the shared EMPTY_FRAGMENTS\n // sentinel — subscribers see reference-stable \"no selection\" frames so\n // downstream `findActiveSelectionFragments` walks short-circuit.\n const nextFragments = computeSelectionFragments(node)\n if (!selectionFragmentsEqual(nextFragments, s.selectionFragments())) {\n s.selectionFragments(nextFragments)\n }\n\n // Sync anchorRect — Phase 4c peer of the rect signals. Driven by\n // `node.props.anchorRef` + `contentRect`; null when the prop is absent or\n // the content rect collapses. Mirrors invariant 2 (prop-change recompute):\n // toggling `anchorRef` clears/reinstates the rect in the same frame.\n // `decorationRects` is NOT synced here — it requires the root tree for\n // anchor lookups and is synced in a second pass below\n // (syncDecorationRects). See `syncRectSignals` JSDoc.\n const nextAnchorRect = computeAnchorRect(node)\n if (!rectEqual(nextAnchorRect, s.anchorRect())) {\n s.anchorRect(nextAnchorRect)\n }\n\n // Sync scrollState signal — projects AgNode.scrollState (layout-phase's\n // pixel-space truth) into a reactive snapshot. `useScrollState` consumers\n // re-render only when a field changes, not on every layout pass.\n //\n // Per-field equality check below means the signal stays reference-stable\n // when layout runs without state changes — critical for avoiding spurious\n // re-renders in virtualizer consumers (they'd otherwise re-evaluate their\n // window on every frame, defeating the point of subscribing).\n const nextScrollState = snapshotScrollState(node)\n if (!scrollStateEqual(nextScrollState, s.scrollState())) {\n s.scrollState(nextScrollState)\n }\n}\n\n/**\n * Second-pass sync for `decorationRects` — must run AFTER `syncRectSignals`\n * has populated every anchor rect this frame, because decoration resolution\n * calls `findAnchor(root, id)` and needs the freshest anchor rects.\n *\n * Walks the tree, recomputes per-node decoration rects, and writes the signal\n * only when the result differs (per-field equality via `decorationRectsEqual`).\n *\n * Phase 4c of `km-silvery.view-as-layout-output` (overlay-anchor v1).\n *\n * Per-node cost: one `props.decorations` length check (zero-allocation\n * short-circuit) + one signal lookup + one decoration recompute when present.\n * Trees without decorations pay only the prop check at every node.\n */\nexport function syncDecorationRects(root: AgNode): void {\n function walk(node: AgNode): void {\n const props = node.props as BoxProps | undefined\n const hasDecorations = !!(props?.decorations && props.decorations.length > 0)\n if (hasDecorations) {\n // Lazy-allocate signals for decoration-bearing nodes (mirrors the\n // anchorRef/cursorOffset/focused/selectionIntent rule in\n // syncRectSignals). Without this, decoration-only Boxes never get\n // their rects published.\n const s = getLayoutSignals(node)\n const next = computeDecorationRects(node, root)\n if (!decorationRectsEqual(next, s.decorationRects())) {\n s.decorationRects(next)\n }\n } else {\n // Clear any previously-allocated signal back to the empty sentinel so\n // removing the prop drops decorations on the same frame (mirrors\n // selectionFragments invariant 2).\n const s = signalMap.get(node)\n if (s && s.decorationRects().length > 0) {\n s.decorationRects(EMPTY_DECORATION_RECTS)\n }\n }\n for (const child of node.children) {\n walk(child)\n }\n }\n walk(root)\n}\n\n// ============================================================================\n// Commit boundary — promote in-flight rects to committed\n// ============================================================================\n\n/**\n * Promote the in-flight rect signals (`boxRect` / `scrollRect` / `screenRect`)\n * to their committed counterparts (`boxRectCommitted` / etc.). Reactive\n * `useBoxRect()` / `useScrollRect()` / `useScreenRect()` consumers subscribe\n * to the committed signals — calling this advances them by one frame.\n *\n * Called by the runtime ONCE per event-batch commit, after the convergence\n * loop has fully drained. Within a single batch, multiple convergence passes\n * may write the in-flight signals (callback-form observers fire each time),\n * but the committed signals advance only here. That's what lets a render\n * which both READS `useBoxRect()` and WRITES a layout-affecting prop converge\n * in one pass: the read returns the same value across every pass in the\n * batch, so the write is idempotent.\n *\n * Reference equality on the underlying alien-signal write means a no-op\n * commit (same rect as last frame) does not fire any subscribers — steady\n * state pays no cost.\n *\n * The walker visits only nodes that already have allocated `LayoutSignals`\n * (i.e. nodes with at least one consumer); a tree with no rect subscribers\n * pays only the WeakMap probe per node.\n *\n * See bead `@km/silvery/use-deferred-box-rect-and-post-commit-observers`.\n */\nexport function commitLayoutSnapshot(root: AgNode): void {\n function walk(node: AgNode): void {\n const s = signalMap.get(node)\n if (s) {\n const nextBox = s.boxRect()\n if (!rectEqual(nextBox, s.boxRectCommitted())) {\n s.boxRectCommitted(nextBox)\n }\n const nextScroll = s.scrollRect()\n if (!rectEqual(nextScroll, s.scrollRectCommitted())) {\n s.scrollRectCommitted(nextScroll)\n }\n const nextScreen = s.screenRect()\n if (!rectEqual(nextScreen, s.screenRectCommitted())) {\n s.screenRectCommitted(nextScreen)\n }\n }\n for (const child of node.children) {\n walk(child)\n }\n }\n walk(root)\n}\n\n// ============================================================================\n// Active cursor lookup (for scheduler)\n// ============================================================================\n\n/**\n * Walk the tree and find the active caret rect — the caret to render this\n * frame, applying the precedence + clipping rules locked by bead\n * `km-silvery.cursor-invariants`. Returns null when no caret should be\n * shown.\n *\n * **Precedence (invariant 1)**:\n * 1. **Focused-editable wins**: a Box with `cursorOffset.visible !== false`\n * AND `interactiveState.focused === true`. If multiple focused-editables\n * exist (rare — typically one input is focused at a time), the deepest\n * one in paint order wins.\n * 2. **Otherwise deepest-in-paint-order**: if no node is focused-editable,\n * fall back to the deepest visible declarer (post-order tree walk).\n * This covers Ink-compat consumers and `useCursor` callers that don't\n * participate in the focus tree.\n * 3. **Otherwise null**: no visible caret declared anywhere.\n *\n * **Clipping (invariant 4)**: at each scroll/clip ancestor (a Box with\n * `overflow=\"scroll\"` / `\"hidden\"` / `overflowY=\"hidden\"`), the caret's\n * position is checked against the ancestor's `scrollRect`. If the caret\n * falls outside the visible region, the caret is treated as not-present.\n * Default behavior is **hide** (no caret ANSI emitted) — never clamp. A\n * caret rect at the exact clip edge is treated as visible.\n *\n * Visited in tree order (depth-first, post-order). Per-node cost is one\n * `props.cursorOffset` check + one signal lookup; trees without any caret\n * declarer return null after a single traversal.\n */\nexport function findActiveCursorRect(root: AgNode): CursorRect | null {\n // Two parallel tracks — focused-editable wins outright (invariant 1.1).\n // Falling back to deepest-visible covers Ink-compat / useCursor consumers\n // (invariant 1.2). We track both during the walk and pick at the end so a\n // shallow focused declarer always wins over a deeper non-focused one.\n let focusedResult: CursorRect | null = null\n let fallbackResult: CursorRect | null = null\n\n // Stack of clip rects (innermost last). A null entry represents \"no clip\n // at this level\" so we don't allocate for every non-clipping Box.\n const clipStack: Array<Rect | null> = []\n\n function isClipped(rect: CursorRect): boolean {\n for (let i = clipStack.length - 1; i >= 0; i--) {\n const clip = clipStack[i]\n if (!clip) continue\n // Caret is a single cell at (x, y). Edge of clip region counts as\n // visible — strict-less-than for upper bounds.\n if (\n rect.x < clip.x ||\n rect.y < clip.y ||\n rect.x >= clip.x + clip.width ||\n rect.y >= clip.y + clip.height\n ) {\n return true\n }\n }\n return false\n }\n\n function isClipAncestor(node: AgNode): boolean {\n const props = node.props as BoxProps | undefined\n if (!props) return false\n if (props.overflow === \"scroll\" || props.overflow === \"hidden\") return true\n if (props.overflowY === \"hidden\") return true\n return false\n }\n\n function walk(node: AgNode): void {\n const isClip = isClipAncestor(node)\n if (isClip) {\n // scrollRect is the rendered viewport for scroll containers (after\n // scroll offset is applied). For overflow=hidden it's the box rect.\n // Either way, scrollRect is the cell range that actually appears on\n // screen for this container.\n clipStack.push(node.scrollRect ?? null)\n }\n\n for (const child of node.children) {\n walk(child)\n }\n\n const props = node.props as BoxProps | undefined\n if (props?.cursorOffset) {\n const s = signalMap.get(node)\n const rect = s ? s.cursorRect() : computeCursorRect(node)\n if (rect && rect.visible && !isClipped(rect)) {\n // Last-write-wins (deeper post-order entries overwrite shallower).\n fallbackResult = rect\n if (node.interactiveState?.focused) {\n focusedResult = rect\n }\n }\n }\n\n if (isClip) {\n clipStack.pop()\n }\n }\n\n walk(root)\n return focusedResult ?? fallbackResult\n}\n\n// ============================================================================\n// Active focus lookup (Phase 4a — focus as layout output)\n// ============================================================================\n\n/**\n * Walk the tree and find the active focused-node id — the node that should\n * be painted with focus styling this frame. Mirrors `findActiveCursorRect`'s\n * shape (post-order walk, deepest-wins). Returns null when no Box declares\n * `focused === true`.\n *\n * **Precedence**: deepest declarer in paint order wins. If multiple nodes\n * have `focused === true` (rare — typically one boundary is focused at a\n * time), the post-order walk picks the later/deeper one. No \"focused-editable\n * tiebreak\" exists for focus itself (cursor's invariant 1 specifically\n * disambiguates between caret declarers, which don't apply here).\n *\n * **Visibility / clipping**: this walk does NOT yet apply scroll/clip\n * filtering. Phase 4a treats focus as a semantic id — actually painting a\n * focus ring is a downstream renderer concern, and that renderer may have\n * its own clipping logic via `screenRect`. The cursor invariants doc covers\n * the rationale (caret pixels must be hidden across clip ancestors; a\n * focused node's id can still be reported even when its border is clipped\n * — the renderer decides what to draw).\n *\n * Per-node cost: one `props.focused` check + one signal lookup. Trees with\n * no focused declarer return null after a single traversal.\n */\nexport function findActiveFocusedNodeId(root: AgNode): string | null {\n let result: string | null = null\n\n function walk(node: AgNode): void {\n for (const child of node.children) {\n walk(child)\n }\n const props = node.props as BoxProps | undefined\n if (props?.focused) {\n const s = signalMap.get(node)\n const id = s ? s.focusedNodeId() : computeFocusedNodeId(node)\n if (id !== null) {\n // Last-write-wins (deeper post-order entries overwrite shallower).\n result = id\n }\n }\n }\n\n walk(root)\n return result\n}\n\n// ============================================================================\n// Active selection lookup (Phase 4b — selection as overlay/decoration)\n// ============================================================================\n\n/**\n * Walk the tree and collect every fragment from every Box that declares\n * `selectionIntent`. Concatenated in tree order (post-order — same shape as\n * `findActiveFocusedNodeId`/`findActiveCursorRect`).\n *\n * Empty array when no node declares a selection or all declarers have\n * collapsed/empty intents. The walk reads from `LayoutSignals.selectionFragments`\n * when allocated (the production fast path), falling back to direct compute\n * for nodes without signals (rare — happens only during teardown).\n *\n * **Multi-node selection** (v1): two adjacent Boxes both declaring\n * `selectionIntent` produce concatenated fragments — the renderer paints\n * highlight bg on every rect. Full cross-node range selection (selecting\n * from middle of node A through node B) is a future enhancement and would\n * be expressed by setting `selectionIntent` on each spanned node, with\n * `to: text.length` on A and `from: 0` on B.\n *\n * **Cleanup on unmount**: the walk only visits currently-mounted nodes, so\n * an unmounted node's stale fragments cannot contribute (mirrors cursor\n * invariant 5 + focus invariant 3). Per-frame recompute in `syncRectSignals`\n * additionally clears the signal back to `EMPTY_FRAGMENTS` when the prop is\n * removed without unmount.\n *\n * Per-node cost: one `props.selectionIntent` check + one signal lookup.\n * Trees with no selection declarer return `[]` after a single traversal.\n */\nexport function findActiveSelectionFragments(root: AgNode): readonly Rect[] {\n const out: Rect[] = []\n\n function walk(node: AgNode): void {\n for (const child of node.children) {\n walk(child)\n }\n const props = node.props as BoxProps | undefined\n if (props?.selectionIntent) {\n const s = signalMap.get(node)\n const fragments = s ? s.selectionFragments() : computeSelectionFragments(node)\n if (fragments.length > 0) {\n // Concat — multi-node selections compose for free.\n for (const r of fragments) out.push(r)\n }\n }\n }\n\n walk(root)\n return out\n}\n\n/**\n * Sync textContent signal from the node's current value.\n *\n * Called from commitTextUpdate in the reconciler.\n */\nexport function syncTextContentSignal(node: AgNode): void {\n const s = signalMap.get(node)\n if (!s) return\n\n if (node.textContent !== s.textContent()) s.textContent(node.textContent)\n}\n\n/**\n * Sync focused signal for a node.\n *\n * Called from FocusManager when focus changes.\n */\nexport function syncFocusedSignal(node: AgNode, focused: boolean): void {\n const s = signalMap.get(node)\n if (!s) return\n\n if (focused !== s.focused()) s.focused(focused)\n}\n"],"mappings":"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"}