silvery 0.21.1 → 0.23.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 (171) hide show
  1. package/dist/{Text-BRf-59j2.mjs → Text-Ci6UCSTL.mjs} +46 -46
  2. package/dist/Text-Ci6UCSTL.mjs.map +1 -0
  3. package/dist/{ag-BeFC4S2N.mjs → ag-q9lRlKlt.mjs} +42 -19
  4. package/dist/ag-q9lRlKlt.mjs.map +1 -0
  5. package/dist/{animation-N8MFybTk.mjs → animation-CZ4x2R7C.mjs} +1 -1
  6. package/dist/{animation-N8MFybTk.mjs.map → animation-CZ4x2R7C.mjs.map} +1 -1
  7. package/dist/{ansi-C6Qs1Wn2.mjs → ansi-CLmIMnop.mjs} +1 -1
  8. package/dist/{ansi-C6Qs1Wn2.mjs.map → ansi-CLmIMnop.mjs.map} +1 -1
  9. package/dist/{ansi-CBkam1ty.d.mts → ansi-zmNzgkPB.d.mts} +1 -1
  10. package/dist/{ansi-CBkam1ty.d.mts.map → ansi-zmNzgkPB.d.mts.map} +1 -1
  11. package/dist/backend-DSiSIc-h.mjs +13396 -0
  12. package/dist/backend-DSiSIc-h.mjs.map +1 -0
  13. package/dist/chain-bridge-CkYcMxlq.mjs +4149 -0
  14. package/dist/chain-bridge-CkYcMxlq.mjs.map +1 -0
  15. package/dist/{chunk-hT5z_Zn9.mjs → chunk-BEJ448es.mjs} +1 -1
  16. package/dist/cli-VNqUKlXw.mjs +4 -0
  17. package/dist/{context-BU5LkkIy.mjs → context-CEhuD1R_.mjs} +1 -1
  18. package/dist/{context-BU5LkkIy.mjs.map → context-CEhuD1R_.mjs.map} +1 -1
  19. package/dist/devtools-BF4IMumr.mjs +2 -0
  20. package/dist/{devtools-9zfhpFyG.mjs → devtools-td2AjFwc.mjs} +5 -4
  21. package/dist/{devtools-9zfhpFyG.mjs.map → devtools-td2AjFwc.mjs.map} +1 -1
  22. package/dist/{easing-B0oZKDki.d.mts → easing-C5MrdO7V.d.mts} +1 -1
  23. package/dist/{easing-B0oZKDki.d.mts.map → easing-C5MrdO7V.d.mts.map} +1 -1
  24. package/dist/{eta-DGOuC8yU.mjs → eta-BwYWEjfS.mjs} +1 -5
  25. package/dist/{eta-DGOuC8yU.mjs.map → eta-BwYWEjfS.mjs.map} +1 -1
  26. package/dist/{flexily-zero-adapter-D6hcFgrH.mjs → flexily-zero-adapter-DktX3HsX.mjs} +1 -1
  27. package/dist/{flexily-zero-adapter-CEJOcbNp.mjs → flexily-zero-adapter-VoBGHG14.mjs} +1 -1
  28. package/dist/{flexily-zero-adapter-CEJOcbNp.mjs.map → flexily-zero-adapter-VoBGHG14.mjs.map} +1 -1
  29. package/dist/hit-registry-D_nxOivS.d.mts +71 -0
  30. package/dist/hit-registry-D_nxOivS.d.mts.map +1 -0
  31. package/dist/{image-1nRyKa60.mjs → image-CIF75xMr.mjs} +24 -24
  32. package/dist/image-CIF75xMr.mjs.map +1 -0
  33. package/dist/{index-2E1jYgak.d.mts → index-377zdPLP.d.mts} +35 -12
  34. package/dist/index-377zdPLP.d.mts.map +1 -0
  35. package/dist/{index-DnuadDNL.d.mts → index-BcSr0RAQ.d.mts} +8590 -11173
  36. package/dist/index-BcSr0RAQ.d.mts.map +1 -0
  37. package/dist/{index-Dg1YaeJb.d.mts → index-DLnpxDAm.d.mts} +2 -2
  38. package/dist/{index-Dg1YaeJb.d.mts.map → index-DLnpxDAm.d.mts.map} +1 -1
  39. package/dist/{index-Bi4Jdz5g.d.mts → index-DlNmckTq.d.mts} +2 -2
  40. package/dist/{index-Bi4Jdz5g.d.mts.map → index-DlNmckTq.d.mts.map} +1 -1
  41. package/dist/index.d.mts +12 -8
  42. package/dist/index.d.mts.map +1 -1
  43. package/dist/index.mjs +18 -17
  44. package/dist/index.mjs.map +1 -1
  45. package/dist/{layout-engine-Ca_nbtfL.mjs → layout-engine-BgR58vL0.mjs} +3 -3
  46. package/dist/{layout-engine-Ca_nbtfL.mjs.map → layout-engine-BgR58vL0.mjs.map} +1 -1
  47. package/dist/{layout-engine-CgsoBRIn.mjs → layout-engine-DNjTha8f.mjs} +1 -1
  48. package/dist/{layout-signals-Dch2EiCy.mjs → layout-signals-CGYbHmxy.mjs} +2 -2
  49. package/dist/layout-signals-CGYbHmxy.mjs.map +1 -0
  50. package/dist/{bound-term-BumfuXXW.d.mts → mouse-events-B2z5tbsL.d.mts} +540 -401
  51. package/dist/mouse-events-B2z5tbsL.d.mts.map +1 -0
  52. package/dist/{mouse-events-hnbJZRwK.mjs → mouse-events-WQ2h-uqG.mjs} +14 -6
  53. package/dist/mouse-events-WQ2h-uqG.mjs.map +1 -0
  54. package/dist/{multi-progress-Bg4ngK80.d.mts → multi-progress-BaEFfHKP.d.mts} +2 -2
  55. package/dist/{multi-progress-Bg4ngK80.d.mts.map → multi-progress-BaEFfHKP.d.mts.map} +1 -1
  56. package/dist/{multi-progress-CaXTuL9G.mjs → multi-progress-zCWHEm9P.mjs} +3 -3
  57. package/dist/{multi-progress-CaXTuL9G.mjs.map → multi-progress-zCWHEm9P.mjs.map} +1 -1
  58. package/dist/{node-BiFu8I9Y.mjs → node-CJ3J_rW-.mjs} +4 -4
  59. package/dist/node-CJ3J_rW-.mjs.map +1 -0
  60. package/dist/{progress-B_UPy6zk.mjs → progress-6vJ7nxUg.mjs} +4 -4
  61. package/dist/{progress-B_UPy6zk.mjs.map → progress-6vJ7nxUg.mjs.map} +1 -1
  62. package/dist/{progress-bar-DmIMPdL0.mjs → progress-bar-BlFNsZL8.mjs} +4 -4
  63. package/dist/{progress-bar-DmIMPdL0.mjs.map → progress-bar-BlFNsZL8.mjs.map} +1 -1
  64. package/dist/{reconciler-NBDSEm8k.mjs → reconciler--_-biN23.mjs} +111 -150
  65. package/dist/reconciler--_-biN23.mjs.map +1 -0
  66. package/dist/{render-string-BntLj7Xq.mjs → render-string-DaghHpLN.mjs} +1 -1
  67. package/dist/{render-string-B4h4SmK7.mjs → render-string-DtHZP0cU.mjs} +15 -11
  68. package/dist/render-string-DtHZP0cU.mjs.map +1 -0
  69. package/dist/runtime.d.mts +3 -2
  70. package/dist/runtime.mjs +3 -2
  71. package/dist/{schemes-DYt2ushj.mjs → schemes-DWQPxXAi.mjs} +2 -2
  72. package/dist/{schemes-DYt2ushj.mjs.map → schemes-DWQPxXAi.mjs.map} +1 -1
  73. package/dist/{spinner-Py8_-hn9.mjs → spinner-B50hILqx.mjs} +3 -3
  74. package/dist/{spinner-Py8_-hn9.mjs.map → spinner-B50hILqx.mjs.map} +1 -1
  75. package/dist/{spinner-CLgzJ_QF.d.mts → spinner-DN10UfF0.d.mts} +2 -2
  76. package/dist/{spinner-CLgzJ_QF.d.mts.map → spinner-DN10UfF0.d.mts.map} +1 -1
  77. package/dist/{src-Oe6x5PrS.mjs → src-B8lf1kiR.mjs} +190 -83
  78. package/dist/src-B8lf1kiR.mjs.map +1 -0
  79. package/dist/{src-Dvq-s8iD.mjs → src-BOLdi3WG.mjs} +3 -3
  80. package/dist/{src-Dvq-s8iD.mjs.map → src-BOLdi3WG.mjs.map} +1 -1
  81. package/dist/{src-B9S_woYc.mjs → src-BXeTHHlL.mjs} +20289 -23262
  82. package/dist/src-BXeTHHlL.mjs.map +1 -0
  83. package/dist/{src-WeA_J4BV.mjs → src-BqeBFB3X.mjs} +90 -47
  84. package/dist/src-BqeBFB3X.mjs.map +1 -0
  85. package/dist/{src-CbhWmUnF.mjs → src-DbqcSrhy.mjs} +1400 -1427
  86. package/dist/src-DbqcSrhy.mjs.map +1 -0
  87. package/dist/{steps-DYrzCUCK.d.mts → steps-B2kBmFpb.d.mts} +1 -1
  88. package/dist/{steps-DYrzCUCK.d.mts.map → steps-B2kBmFpb.d.mts.map} +1 -1
  89. package/dist/{svg-DhxQkz-O.mjs → svg-NmV9dgB9.mjs} +1 -1
  90. package/dist/{svg-DhxQkz-O.mjs.map → svg-NmV9dgB9.mjs.map} +1 -1
  91. package/dist/{svg-Hk7lIl4F.d.mts → svg-VUr78l5d.d.mts} +1 -1
  92. package/dist/{svg-Hk7lIl4F.d.mts.map → svg-VUr78l5d.d.mts.map} +1 -1
  93. package/dist/term.d.mts +2 -2
  94. package/dist/term.mjs +3 -3
  95. package/dist/test.d.mts +673 -0
  96. package/dist/test.d.mts.map +1 -0
  97. package/dist/test.mjs +2829 -0
  98. package/dist/test.mjs.map +1 -0
  99. package/dist/theme.d.mts +2 -2
  100. package/dist/theme.d.mts.map +1 -1
  101. package/dist/theme.mjs +3 -3
  102. package/dist/{types-Bx-XZNbE.d.mts → types-D_E0FbqA.d.mts} +1 -1
  103. package/dist/{types-Bx-XZNbE.d.mts.map → types-D_E0FbqA.d.mts.map} +1 -1
  104. package/dist/ui/animation.d.mts +2 -2
  105. package/dist/ui/animation.mjs +1 -1
  106. package/dist/ui/ansi.d.mts +1 -1
  107. package/dist/ui/ansi.mjs +1 -1
  108. package/dist/ui/cli.d.mts +3 -3
  109. package/dist/ui/cli.mjs +5 -5
  110. package/dist/ui/image.d.mts +1 -1
  111. package/dist/ui/image.mjs +1 -1
  112. package/dist/ui/progress.d.mts +4 -4
  113. package/dist/ui/progress.mjs +4 -4
  114. package/dist/ui/react.d.mts +1 -1
  115. package/dist/ui/react.mjs +2 -2
  116. package/dist/ui/react.mjs.map +1 -1
  117. package/dist/ui/recording-chrome-react.d.mts +1 -1
  118. package/dist/ui/recording-chrome-react.mjs +2 -2
  119. package/dist/ui/recording-chrome.d.mts +1 -1
  120. package/dist/ui/recording-chrome.mjs +1 -1
  121. package/dist/ui/utils.mjs +1 -1
  122. package/dist/ui/wrappers.d.mts +2 -2
  123. package/dist/ui/wrappers.mjs +1 -1
  124. package/dist/ui.d.mts +6 -6
  125. package/dist/ui.mjs +7 -7
  126. package/dist/{ansi-OYRLxxZQ.mjs → unicode-CxyxVGmB.mjs} +7317 -6909
  127. package/dist/unicode-CxyxVGmB.mjs.map +1 -0
  128. package/dist/{useLatest-DC8i7guK.d.mts → useLatest-UOvWy0ZW.d.mts} +2 -2
  129. package/dist/{useLatest-DC8i7guK.d.mts.map → useLatest-UOvWy0ZW.d.mts.map} +1 -1
  130. package/dist/{useLayout-BKsQl2Or.mjs → useLayout-D5cE8OCI.mjs} +4 -4
  131. package/dist/{useLayout-BKsQl2Or.mjs.map → useLayout-D5cE8OCI.mjs.map} +1 -1
  132. package/dist/{with-text-input-DG4f7JII.d.mts → with-text-input-DLoSCBrm.d.mts} +3 -3
  133. package/dist/{with-text-input-DG4f7JII.d.mts.map → with-text-input-DLoSCBrm.d.mts.map} +1 -1
  134. package/dist/wrap-measurer-registration-IV2HtcCd.d.mts +3106 -0
  135. package/dist/wrap-measurer-registration-IV2HtcCd.d.mts.map +1 -0
  136. package/dist/{wrapper-D7gNSsgf.mjs → wrapper-CD29afOs.mjs} +17 -37
  137. package/dist/wrapper-CD29afOs.mjs.map +1 -0
  138. package/dist/{wrappers-CypAzrMO.mjs → wrappers-CInBJm44.mjs} +6 -6
  139. package/dist/{wrappers-CypAzrMO.mjs.map → wrappers-CInBJm44.mjs.map} +1 -1
  140. package/dist/{yoga-adapter-SsEIqMc1.mjs → yoga-adapter-9uab5Qn-.mjs} +2 -2
  141. package/dist/{yoga-adapter-SsEIqMc1.mjs.map → yoga-adapter-9uab5Qn-.mjs.map} +1 -1
  142. package/dist/yoga-adapter-yqTl1aXK.mjs +2 -0
  143. package/package.json +20 -15
  144. package/dist/Text-BRf-59j2.mjs.map +0 -1
  145. package/dist/ag-BeFC4S2N.mjs.map +0 -1
  146. package/dist/ansi-OYRLxxZQ.mjs.map +0 -1
  147. package/dist/bound-term-BumfuXXW.d.mts.map +0 -1
  148. package/dist/cli-DG6zsfsS.mjs +0 -4
  149. package/dist/devtools-nmPUmYU_.mjs +0 -2
  150. package/dist/image-1nRyKa60.mjs.map +0 -1
  151. package/dist/index-2E1jYgak.d.mts.map +0 -1
  152. package/dist/index-DnuadDNL.d.mts.map +0 -1
  153. package/dist/layout-signals-Dch2EiCy.mjs.map +0 -1
  154. package/dist/mouse-events-hnbJZRwK.mjs.map +0 -1
  155. package/dist/node-BiFu8I9Y.mjs.map +0 -1
  156. package/dist/reconciler-NBDSEm8k.mjs.map +0 -1
  157. package/dist/render-string-B4h4SmK7.mjs.map +0 -1
  158. package/dist/src-B9S_woYc.mjs.map +0 -1
  159. package/dist/src-CbhWmUnF.mjs.map +0 -1
  160. package/dist/src-Oe6x5PrS.mjs.map +0 -1
  161. package/dist/src-WeA_J4BV.mjs.map +0 -1
  162. package/dist/ui/display.d.mts +0 -35
  163. package/dist/ui/display.d.mts.map +0 -1
  164. package/dist/ui/display.mjs +0 -123
  165. package/dist/ui/display.mjs.map +0 -1
  166. package/dist/ui/input.d.mts +0 -184
  167. package/dist/ui/input.d.mts.map +0 -1
  168. package/dist/ui/input.mjs +0 -283
  169. package/dist/ui/input.mjs.map +0 -1
  170. package/dist/wrapper-D7gNSsgf.mjs.map +0 -1
  171. package/dist/yoga-adapter-1ex8r0ws.mjs +0 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src-DbqcSrhy.mjs","names":["getTabOrder","DEFAULT_CONFIG","BORDER_CHARS","ANSI_COLORS","resolveColor"],"sources":["../packages/ag/src/focus-manager.ts","../packages/headless/src/selection.ts","../packages/ag-term/src/drag-events.ts","../packages/ag-react/src/hooks/useCursor.ts","../packages/ag-term/src/pipeline/adapter-pipeline.ts","../packages/ag-term/src/adapters/canvas-adapter.ts","../packages/ag-term/src/adapters/dom-adapter.ts","../packages/ag-term/src/caret-style.ts","../packages/ag-term/src/osc-markers.ts","../packages/ag-term/src/kitty-detect.ts","../packages/ag-term/src/termtest.ts","../packages/ag-term/src/cursor-query.ts","../packages/ag-term/src/ansi/width-detection.ts","../packages/ag-term/src/device-attrs.ts","../packages/ag-term/src/mode-query.ts","../packages/ag-term/src/pixel-size.ts","../packages/ag-term/src/term-def.ts","../packages/ag-term/src/hit-registry-core.ts","../packages/ag-term/src/non-tty.ts","../packages/ag-term/src/inspector.ts","../packages/ag-term/src/measurer.ts","../packages/ag-term/src/scroll-utils.ts","../packages/ag-term/src/scroll-region.ts","../packages/ag-term/src/pane-manager.ts","../packages/ag-term/src/selection-renderer.ts","../packages/ag-term/src/virtual-scrollback.ts","../packages/ag-term/src/history-buffer.ts","../packages/ag-term/src/viewport-compositor.ts","../packages/ag-term/src/list-document.ts","../packages/ag-term/src/text-surface.ts","../packages/ag-term/src/search-overlay.ts"],"sourcesContent":["/**\n * Focus Manager — standalone state container for the silvery focus system.\n *\n * Pure TypeScript, no React dependency. The subscribe/getSnapshot pattern\n * enables useSyncExternalStore in hooks.\n *\n * Replaces the flat focus list in context.ts (FocusContext with focusables Map).\n */\n\nimport type { AgNode, Rect } from \"./types\"\nimport {\n findByTestID,\n findFocusableAncestor,\n getTabOrder,\n findSpatialTarget,\n getExplicitFocusLink,\n isFocusHidden,\n isFocusable,\n} from \"./focus-queries\"\nimport { setFocused } from \"./interactive-signals\"\nimport { syncFocusedSignal } from \"./layout-signals\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type FocusOrigin = \"keyboard\" | \"mouse\" | \"programmatic\"\n\n/**\n * Callback fired when focus changes. Used by the runtime to dispatch\n * DOM-level focus/blur events without coupling FocusManager to the event system.\n *\n * @param oldNode - The node losing focus (null if nothing was focused)\n * @param newNode - The node gaining focus (null on blur)\n * @param origin - How focus was acquired\n */\nexport type FocusChangeCallback = (\n oldNode: AgNode | null,\n newNode: AgNode | null,\n origin: FocusOrigin | null,\n) => void\n\nexport interface FocusSnapshot {\n activeId: string | null\n previousId: string | null\n focusOrigin: FocusOrigin | null\n scopeStack: readonly string[]\n /** The currently active peer scope (WPF FocusScope model) */\n activeScopeId: string | null\n}\n\nexport interface FocusManagerOptions {\n /** Called when focus changes — wire up event dispatch here */\n onFocusChange?: FocusChangeCallback\n}\n\n/**\n * Options for registering a hook-based (virtual) focusable.\n *\n * Hook focusables are registered via React hooks (e.g. `useFocus()` in the\n * Ink compat layer) rather than by the `focusable` prop on a tree node. They\n * participate in Tab cycling but don't have a backing `AgNode` — activeId\n * tracking is by id only, and `activeElement` is null when a hook focusable\n * is the active target.\n */\nexport interface HookFocusableOptions {\n /** Registration is inert when false — skipped in tab order, never reports focused */\n isActive?: boolean\n /** Focus this id when registered (only when isActive !== false) */\n autoFocus?: boolean\n}\n\nexport interface FocusManager {\n /** Currently focused node */\n readonly activeElement: AgNode | null\n /** testID of the currently focused node */\n readonly activeId: string | null\n /** Previously focused node */\n readonly previousElement: AgNode | null\n /** testID of the previously focused node */\n readonly previousId: string | null\n /** How focus was most recently acquired */\n readonly focusOrigin: FocusOrigin | null\n /** Stack of active focus scope IDs */\n readonly scopeStack: readonly string[]\n /** Map of scope ID -> last focused testID within that scope */\n readonly scopeMemory: Readonly<Record<string, string>>\n\n /** Focus a specific node */\n focus(node: AgNode, origin?: FocusOrigin): void\n /** Focus a node by testID (requires root for tree search) */\n focusById(id: string, root: AgNode, origin?: FocusOrigin): void\n /**\n * Focus a hook-registered (virtual) id directly without tree traversal.\n * Unlike `focusById`, this never needs a root — used by `useFocus()` hooks\n * that track focus by id only.\n */\n focusVirtualId(id: string, origin?: FocusOrigin): void\n /** Clear focus */\n blur(): void\n\n // ---- Hook-based (virtual) focusables ----\n\n /**\n * Register a hook-based focusable id (e.g. from `useFocus()` in Ink compat).\n *\n * Hook focusables form a flat list alongside the tree-based focusables.\n * `focusNext`/`focusPrev` interleave: tree focusables come first (document\n * order), then hook focusables (registration order). A single unified tab\n * cycle walks both.\n *\n * Returns an unregister callback (safe to call on effect cleanup).\n */\n registerHookFocusable(id: string, options?: HookFocusableOptions): () => void\n /** Update an existing hook-focusable's active state. */\n setHookFocusableActive(id: string, isActive: boolean): void\n /** Whether any hook focusables are currently registered. */\n readonly hasHookFocusables: boolean\n /**\n * Global focus enable (Ink compat). When false, `focusNext`/`focusPrev`\n * become no-ops for hook-registered focusables. Tree-based focusables\n * ignore this flag — apps using `useFocusable` are not affected.\n */\n readonly hookFocusEnabled: boolean\n setHookFocusEnabled(enabled: boolean): void\n\n /**\n * Handle a subtree being removed from the tree.\n * If the focused node (or previous node) is within the removed subtree,\n * clear the reference to prevent dead node retention and broken navigation.\n */\n handleSubtreeRemoved(removedRoot: AgNode): void\n\n /**\n * Handle a mounted node's props/state being updated.\n * If the active focus target is no longer focusable or has become hidden,\n * clear focus so focused-element input dispatch falls back to host handlers.\n * Symmetrically, if VIRTUAL focus is pending (`activeId` set, no\n * `activeElement`) and this update made the matching node focusable, promote\n * it to real focus (Law 3 — looks-focused ≡ receives-input; 20992 f2).\n */\n handleNodeUpdated(updatedNode: AgNode): void\n\n /**\n * Handle a subtree being ATTACHED to the tree (commit-phase mount or move).\n * If virtual focus is pending and the subtree carries a focusable node whose\n * testID matches `activeId`, promote it to real focus — the intent landed\n * before the node existed, and no consumer retry should be needed (the hab\n * 20989 looks-focused-but-no-input class; 20992 f2). O(1) unless virtual\n * focus is actually pending.\n */\n handleSubtreeAttached(attachedRoot: AgNode): void\n\n /** Push a focus scope onto the stack */\n enterScope(scopeId: string): void\n /** Pop the current focus scope */\n exitScope(): void\n\n /** The currently active peer scope ID (WPF FocusScope model) */\n readonly activeScopeId: string | null\n /**\n * Activate a peer focus scope. Saves current focus in the old scope's memory,\n * switches to the new scope, and restores the remembered focus (or focuses\n * the first focusable element in the scope subtree).\n */\n activateScope(scopeId: string, root: AgNode): void\n\n /** Get the testID path from focused node to root */\n getFocusPath(root: AgNode): string[]\n /** Check if a subtree rooted at testID contains the focused node */\n hasFocusWithin(root: AgNode, testID: string): boolean\n\n /** Focus the next focusable node in tab order */\n focusNext(root: AgNode, scope?: AgNode): void\n /** Focus the previous focusable node in tab order */\n focusPrev(root: AgNode, scope?: AgNode): void\n /** Focus in a spatial direction (up/down/left/right) */\n focusDirection(\n root: AgNode,\n direction: \"up\" | \"down\" | \"left\" | \"right\",\n layoutFn?: (node: AgNode) => Rect | null,\n ): void\n\n /** Subscribe for React integration (useSyncExternalStore) */\n subscribe(listener: () => void): () => void\n /** Get immutable snapshot for useSyncExternalStore */\n getSnapshot(): FocusSnapshot\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\n/**\n * Internal shape for a hook-registered focusable.\n * Order in the list reflects registration order (Ink-compatible tab order).\n */\ninterface HookFocusable {\n id: string\n isActive: boolean\n}\n\nexport function createFocusManager(options?: FocusManagerOptions): FocusManager {\n const onFocusChange = options?.onFocusChange\n\n // Internal state\n let activeElement: AgNode | null = null\n let activeId: string | null = null\n let previousElement: AgNode | null = null\n let previousId: string | null = null\n let focusOrigin: FocusOrigin | null = null\n const scopeStack: string[] = []\n const scopeMemory: Record<string, string> = {}\n let activeScopeId: string | null = null\n\n // Hook-registered focusables (flat list, Ink-style).\n const hookFocusables: HookFocusable[] = []\n // Global focus enable (Ink compat — `enableFocus()` / `disableFocus()` knob).\n let hookFocusEnabled = true\n\n // Subscriber management\n const listeners = new Set<() => void>()\n let snapshot: FocusSnapshot | null = null\n /** Counter incremented on every notify(); used by activateScope to detect inner notifications. */\n let notifyCount = 0\n\n function notify(): void {\n snapshot = null // Invalidate cached snapshot\n notifyCount++\n for (const listener of listeners) {\n listener()\n }\n }\n\n function getTestID(node: AgNode): string | null {\n const props = node.props as Record<string, unknown>\n return typeof props.testID === \"string\" ? props.testID : null\n }\n\n // ---- Focus operations ----\n\n function focus(node: AgNode, origin: FocusOrigin = \"programmatic\"): void {\n // Skip if already focused on this node\n if (activeElement === node) {\n // Still update origin if different\n if (focusOrigin !== origin) {\n focusOrigin = origin\n notify()\n }\n return\n }\n\n const oldElement = activeElement\n previousElement = activeElement\n previousId = activeId\n activeElement = node\n activeId = getTestID(node)\n focusOrigin = origin\n\n // Update interactive state on affected nodes\n if (oldElement) {\n setFocused(oldElement, false)\n syncFocusedSignal(oldElement, false)\n }\n setFocused(node, true)\n syncFocusedSignal(node, true)\n\n // Remember this focus in the current scope\n if (activeId && scopeStack.length > 0) {\n scopeMemory[scopeStack[scopeStack.length - 1]!] = activeId\n }\n\n notify()\n\n // Fire focus change callback (after state is updated)\n onFocusChange?.(oldElement, node, origin)\n }\n\n function focusById(id: string, root: AgNode, origin: FocusOrigin = \"programmatic\"): void {\n const node = findByTestID(root, id)\n if (node) {\n // Walk up to the nearest focusable ancestor if the found node isn't focusable\n const focusable = findFocusableAncestor(node)\n if (focusable) {\n focus(focusable, origin)\n return\n }\n }\n // Virtual focus: set the ID without a DOM node. This enables named focus\n // targets (e.g. \"board-area\", \"detail-pane\") without requiring wrapper Boxes\n // that would disrupt layout.\n\n // Idempotent: skip if already virtually focused on this ID (prevents infinite\n // re-render loops when focus() is called during render).\n if (activeId === id && !activeElement) return\n\n const oldElement = activeElement\n previousElement = activeElement\n previousId = activeId\n activeElement = null\n activeId = id\n focusOrigin = origin\n\n // Update interactive state — old element loses focus, no new node to set\n if (oldElement) {\n setFocused(oldElement, false)\n syncFocusedSignal(oldElement, false)\n }\n\n notify()\n\n // Fire focus change callback (old element blurs, no new node for virtual focus)\n onFocusChange?.(oldElement, null, origin)\n }\n\n function blur(): void {\n if (!activeElement && !activeId) return\n\n const oldElement = activeElement\n previousElement = activeElement\n previousId = activeId\n activeElement = null\n activeId = null\n focusOrigin = null\n\n // Update interactive state — old element loses focus\n if (oldElement) {\n setFocused(oldElement, false)\n syncFocusedSignal(oldElement, false)\n }\n\n notify()\n\n // Fire focus change callback (after state is updated)\n onFocusChange?.(oldElement, null, null)\n }\n\n // ---- Hook-based (virtual) focusables ----\n\n /**\n * Set the focused target to a hook-registered id directly (no tree lookup).\n * activeElement becomes null (no backing node), activeId is the hook id.\n */\n function focusVirtualId(id: string, origin: FocusOrigin = \"programmatic\"): void {\n if (activeId === id && !activeElement) {\n if (focusOrigin !== origin) {\n focusOrigin = origin\n notify()\n }\n return\n }\n const oldElement = activeElement\n previousElement = activeElement\n previousId = activeId\n activeElement = null\n activeId = id\n focusOrigin = origin\n\n if (oldElement) {\n setFocused(oldElement, false)\n syncFocusedSignal(oldElement, false)\n }\n\n notify()\n\n onFocusChange?.(oldElement, null, origin)\n }\n\n function unregisterHookFocusable(id: string): void {\n const idx = hookFocusables.findIndex((f) => f.id === id)\n if (idx === -1) return\n hookFocusables.splice(idx, 1)\n // Clear active focus if the removed id was focused.\n if (activeId === id && !activeElement) {\n previousId = activeId\n activeId = null\n focusOrigin = null\n notify()\n onFocusChange?.(null, null, null)\n return\n }\n notify()\n }\n\n function registerHookFocusable(id: string, opts: HookFocusableOptions = {}): () => void {\n const { isActive = true, autoFocus = false } = opts\n\n // Dedup by id — last registration wins (matches Ink behavior on re-mount).\n const existing = hookFocusables.findIndex((f) => f.id === id)\n if (existing !== -1) {\n // Skip notification if nothing changed (prevents render loops)\n if (hookFocusables[existing]!.isActive === isActive && !autoFocus) {\n return () => unregisterHookFocusable(id)\n }\n hookFocusables[existing] = { id, isActive }\n } else {\n hookFocusables.push({ id, isActive })\n }\n\n if (autoFocus && isActive && activeId === null) {\n focusVirtualId(id, \"programmatic\")\n } else {\n notify()\n }\n\n return () => unregisterHookFocusable(id)\n }\n\n function setHookFocusableActive(id: string, isActive: boolean): void {\n const entry = hookFocusables.find((f) => f.id === id)\n if (!entry) return\n if (entry.isActive === isActive) return\n entry.isActive = isActive\n // If the active virtual id was deactivated, clear it.\n if (!isActive && activeId === id && !activeElement) {\n previousId = activeId\n activeId = null\n focusOrigin = null\n notify()\n onFocusChange?.(null, null, null)\n return\n }\n notify()\n }\n\n function setHookFocusEnabled(enabled: boolean): void {\n if (hookFocusEnabled === enabled) return\n hookFocusEnabled = enabled\n notify()\n }\n\n // ---- Subtree removal ----\n\n /**\n * Check if a node is the given target or contains it as a descendant.\n */\n function subtreeContains(subtreeRoot: AgNode, target: AgNode): boolean {\n if (subtreeRoot === target) return true\n for (const child of subtreeRoot.children) {\n if (subtreeContains(child, target)) return true\n }\n return false\n }\n\n function updateInvalidatesFocusedNode(updatedNode: AgNode, focusedNode: AgNode): boolean {\n if (!subtreeContains(updatedNode, focusedNode)) return false\n\n let current: AgNode | null = focusedNode\n while (current) {\n if (isFocusHidden(current)) return true\n if (current === updatedNode) break\n current = current.parent\n }\n\n return updatedNode === focusedNode && !isFocusable(focusedNode)\n }\n\n /**\n * Handle a subtree being removed from the tree. If the active or previous\n * element lives within the removed subtree, clear the dangling reference.\n * This prevents dead node retention and broken navigation (indexOf → -1).\n */\n function handleSubtreeRemoved(removedRoot: AgNode): void {\n let changed = false\n\n if (activeElement && subtreeContains(removedRoot, activeElement)) {\n const oldElement = activeElement\n // Clear interactive focus state before removing reference\n setFocused(oldElement, false)\n syncFocusedSignal(oldElement, false)\n previousElement = activeElement\n previousId = activeId\n activeElement = null\n activeId = null\n focusOrigin = null\n changed = true\n onFocusChange?.(oldElement, null, null)\n }\n\n if (previousElement && subtreeContains(removedRoot, previousElement)) {\n previousElement = null\n previousId = null\n changed = true\n }\n\n if (changed) {\n notify()\n }\n }\n\n /**\n * Handle a mounted node update. React can update a focused node from\n * `focusable` to non-focusable without unmounting it; leaving that node as\n * `activeElement` keeps the focused dispatch lane alive and routes later keys\n * into an input owner the app has already deactivated.\n */\n function handleNodeUpdated(updatedNode: AgNode): void {\n let changed = false\n\n if (activeElement && updateInvalidatesFocusedNode(updatedNode, activeElement)) {\n const oldElement = activeElement\n setFocused(oldElement, false)\n syncFocusedSignal(oldElement, false)\n previousElement = activeElement\n previousId = activeId\n activeElement = null\n activeId = null\n focusOrigin = null\n changed = true\n onFocusChange?.(oldElement, null, null)\n }\n\n if (previousElement && updateInvalidatesFocusedNode(updatedNode, previousElement)) {\n previousElement = null\n previousId = null\n changed = true\n }\n\n if (changed) {\n notify()\n }\n\n // Promotion window (b): the node existed when focus was requested but was\n // not focusable yet (e.g. a gated `focusable` prop committing later), so\n // the request fell into the virtual arm. This update may have flipped it.\n maybePromoteVirtualFocus(updatedNode)\n }\n\n /**\n * Promote pending VIRTUAL focus (activeId set, activeElement null) to the\n * real node when a matching focusable target exists under `searchRoot`.\n * Never steals: a real activeElement short-circuits immediately. Resolution\n * mirrors focusById exactly (findByTestID + nearest focusable ancestor), so\n * promoted focus is indistinguishable from a direct hit.\n */\n function maybePromoteVirtualFocus(searchRoot: AgNode): void {\n if (activeId === null || activeElement !== null) return\n const node = findByTestID(searchRoot, activeId)\n if (!node) return\n const focusable = findFocusableAncestor(node)\n if (!focusable) return\n focus(focusable, focusOrigin ?? \"programmatic\")\n }\n\n function handleSubtreeAttached(attachedRoot: AgNode): void {\n // Promotion window (a): the focus intent landed before the subtree carrying\n // the target was committed. O(1) early-exit unless virtual focus is pending.\n maybePromoteVirtualFocus(attachedRoot)\n }\n\n // ---- Scope management ----\n\n function enterScope(scopeId: string): void {\n scopeStack.push(scopeId)\n notify()\n }\n\n function exitScope(): void {\n const exited = scopeStack.pop()\n if (exited === undefined) return\n\n // Restore focus to the remembered element in the parent scope\n // (Caller is responsible for providing root to restore if needed)\n notify()\n }\n\n // ---- Peer scope activation (WPF FocusScope model) ----\n\n function activateScope(scopeId: string, root: AgNode): void {\n // Save current focus in the outgoing scope's memory\n if (activeScopeId && activeId) {\n scopeMemory[activeScopeId] = activeId\n }\n\n // Switch scope\n activeScopeId = scopeId\n\n // Restore focus: remembered element, or first focusable in scope.\n // Track whether notify() fired during focus/focusById to avoid double-notify.\n const countBefore = notifyCount\n const remembered = scopeMemory[scopeId]\n if (remembered) {\n focusById(remembered, root, \"programmatic\")\n } else {\n const scopeNode = findByTestID(root, scopeId)\n if (scopeNode) {\n const order = getTabOrder(root, scopeNode)\n if (order.length > 0) {\n focus(order[0]!, \"programmatic\")\n }\n }\n }\n\n // Only notify if focus/focusById didn't already notify.\n if (notifyCount === countBefore) {\n notify()\n }\n }\n\n // ---- Tree queries ----\n\n function getFocusPath(root: AgNode): string[] {\n if (!activeElement) return []\n\n const path: string[] = []\n let current: AgNode | null = activeElement\n while (current && current !== root.parent) {\n const id = getTestID(current)\n if (id) path.push(id)\n current = current.parent\n }\n return path\n }\n\n function hasFocusWithin(root: AgNode, testID: string): boolean {\n if (!activeElement) return false\n\n // Find the node with the given testID\n const target = findByTestID(root, testID)\n if (!target) return false\n\n // Walk up from activeElement to see if we pass through target\n let current: AgNode | null = activeElement\n while (current) {\n if (current === target) return true\n current = current.parent\n }\n return false\n }\n\n // ---- Navigation ----\n\n /**\n * Resolve the effective scope node for tab navigation.\n * If an explicit scope is provided, use it. Otherwise, if the scopeStack\n * is non-empty, find the topmost scope node in the tree by testID.\n */\n function resolveScope(root: AgNode, explicitScope?: AgNode): AgNode | undefined {\n if (explicitScope) return explicitScope\n\n if (scopeStack.length > 0) {\n const scopeId = scopeStack[scopeStack.length - 1]!\n const scopeNode = findByTestID(root, scopeId)\n if (scopeNode) return scopeNode\n }\n\n return undefined\n }\n\n /**\n * Unified tab entry — either a tree-based AgNode or a hook-registered id.\n * Tree focusables come first (document order), then hook focusables\n * (registration order). Inactive hook focusables are excluded.\n */\n type TabEntry = { kind: \"node\"; node: AgNode } | { kind: \"hook\"; id: string }\n\n function buildTabEntries(root: AgNode, scope?: AgNode): TabEntry[] {\n const effectiveScope = resolveScope(root, scope)\n const nodes = getTabOrder(root, effectiveScope)\n const entries: TabEntry[] = nodes.map((node) => ({ kind: \"node\", node }))\n // Hook focusables are only included when no explicit tree scope is active —\n // they're inherently scope-less. Skip inactive or when globally disabled.\n // Dedup: skip hook focusables whose id matches a tree node's testID —\n // the component is already in the tab order via the tree scan.\n if (!effectiveScope && hookFocusEnabled) {\n const treeIds = new Set(\n nodes\n .map((n) => (n.props as Record<string, unknown>).testID as string | undefined)\n .filter(Boolean),\n )\n for (const entry of hookFocusables) {\n if (entry.isActive && !treeIds.has(entry.id)) entries.push({ kind: \"hook\", id: entry.id })\n }\n }\n return entries\n }\n\n function currentTabIndex(entries: TabEntry[]): number {\n if (activeElement) {\n for (let i = 0; i < entries.length; i++) {\n const e = entries[i]!\n if (e.kind === \"node\" && e.node === activeElement) return i\n }\n return -1\n }\n if (activeId) {\n for (let i = 0; i < entries.length; i++) {\n const e = entries[i]!\n if (e.kind === \"hook\" && e.id === activeId) return i\n }\n }\n return -1\n }\n\n function focusTabEntry(entry: TabEntry, origin: FocusOrigin): void {\n if (entry.kind === \"node\") {\n focus(entry.node, origin)\n } else {\n focusVirtualId(entry.id, origin)\n }\n }\n\n function focusNext(root: AgNode, scope?: AgNode): void {\n const entries = buildTabEntries(root, scope)\n if (entries.length === 0) return\n\n const currentIndex = currentTabIndex(entries)\n if (currentIndex === -1) {\n // Nothing focused — focus the first entry\n focusTabEntry(entries[0]!, \"keyboard\")\n return\n }\n\n const nextIndex = (currentIndex + 1) % entries.length\n focusTabEntry(entries[nextIndex]!, \"keyboard\")\n }\n\n function focusPrev(root: AgNode, scope?: AgNode): void {\n const entries = buildTabEntries(root, scope)\n if (entries.length === 0) return\n\n const currentIndex = currentTabIndex(entries)\n if (currentIndex === -1) {\n // Nothing focused — focus the last entry\n focusTabEntry(entries[entries.length - 1]!, \"keyboard\")\n return\n }\n\n const prevIndex = currentIndex <= 0 ? entries.length - 1 : currentIndex - 1\n focusTabEntry(entries[prevIndex]!, \"keyboard\")\n }\n\n function focusDirection(\n root: AgNode,\n direction: \"up\" | \"down\" | \"left\" | \"right\",\n layoutFn?: (node: AgNode) => Rect | null,\n ): void {\n if (!activeElement) return\n\n // Check for explicit focus link first\n const explicitTarget = getExplicitFocusLink(activeElement, direction)\n if (explicitTarget) {\n focusById(explicitTarget, root, \"keyboard\")\n return\n }\n\n // Fall back to spatial navigation\n const candidates = getTabOrder(root)\n const resolvedLayoutFn = layoutFn ?? ((node: AgNode) => node.scrollRect)\n const target = findSpatialTarget(activeElement, direction, candidates, resolvedLayoutFn)\n if (target) {\n focus(target, \"keyboard\")\n }\n }\n\n // ---- Subscribe/snapshot for useSyncExternalStore ----\n\n function subscribe(listener: () => void): () => void {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n }\n\n function getSnapshot(): FocusSnapshot {\n if (!snapshot) {\n snapshot = {\n activeId,\n previousId,\n focusOrigin,\n scopeStack: [...scopeStack],\n activeScopeId,\n }\n }\n return snapshot\n }\n\n // ---- Public interface ----\n\n return {\n get activeElement() {\n return activeElement\n },\n get activeId() {\n return activeId\n },\n get previousElement() {\n return previousElement\n },\n get previousId() {\n return previousId\n },\n get focusOrigin() {\n return focusOrigin\n },\n get scopeStack() {\n return [...scopeStack] as readonly string[]\n },\n get scopeMemory() {\n return scopeMemory as Readonly<Record<string, string>>\n },\n get activeScopeId() {\n return activeScopeId\n },\n\n focus,\n focusById,\n focusVirtualId,\n blur,\n handleSubtreeRemoved,\n handleNodeUpdated,\n handleSubtreeAttached,\n\n enterScope,\n exitScope,\n activateScope,\n\n getFocusPath,\n hasFocusWithin,\n\n focusNext,\n focusPrev,\n focusDirection,\n\n subscribe,\n getSnapshot,\n\n // Hook-based focusables (Ink compat)\n registerHookFocusable,\n setHookFocusableActive,\n get hasHookFocusables() {\n return hookFocusables.length > 0\n },\n get hookFocusEnabled() {\n return hookFocusEnabled\n },\n setHookFocusEnabled,\n }\n}\n","/**\n * Selection state machine — pure TEA `(action, state) → [state, effects[]]`.\n *\n * Buffer-level text selection (like native terminal selection).\n * Operates on terminal buffer coordinates, not the React tree.\n *\n * Supports three granularity levels:\n * - character: default drag selection\n * - word: double-click then drag extends by words\n * - line: triple-click then drag extends by lines\n */\n\nimport type { TerminalBuffer, RowMetadata } from \"@silvery/ag-term/buffer\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface SelectionPosition {\n col: number\n row: number\n}\n\nexport interface SelectionRange {\n anchor: SelectionPosition\n head: SelectionPosition\n}\n\n/**\n * Rectangular boundary for scoped selection.\n * Derived by the runtime from the active document-selection ancestor's\n * scrollRect, or from a `userSelect=\"contain\"` hard boundary.\n */\nexport interface SelectionScope {\n top: number\n bottom: number\n left: number\n right: number\n}\n\nexport type SelectionGranularity = \"character\" | \"word\" | \"line\"\n\nexport interface TerminalSelectionState {\n range: SelectionRange | null\n /** True while mouse button is held */\n selecting: boolean\n /** Who initiated the selection */\n source: \"mouse\" | \"keyboard\" | null\n /** Current selection granularity */\n granularity: SelectionGranularity\n /** Active document/contain boundary — selection range is clamped to this rect */\n scope: SelectionScope | null\n}\n\nexport type SelectionAction =\n | {\n type: \"start\"\n col: number\n row: number\n scope?: SelectionScope | null\n source?: \"mouse\" | \"keyboard\"\n }\n | {\n type: \"startWord\"\n col: number\n row: number\n buffer: TerminalBuffer\n scope?: SelectionScope | null\n source?: \"mouse\" | \"keyboard\"\n }\n | {\n type: \"startLine\"\n col: number\n row: number\n buffer: TerminalBuffer\n scope?: SelectionScope | null\n source?: \"mouse\" | \"keyboard\"\n }\n | {\n type: \"extend\"\n col: number\n row: number\n buffer?: TerminalBuffer\n scope?: SelectionScope | null\n }\n | { type: \"finish\" }\n | { type: \"clear\" }\n\nexport type SelectionEffect = { type: \"copy\"; text: string } | { type: \"render\" }\n\n// ============================================================================\n// Word Boundary Detection\n// ============================================================================\n\n/**\n * Check if a character is a word character (not whitespace/punctuation).\n * Word chars: letters, digits, underscore (matching \\w).\n */\nfunction isWordChar(ch: string): boolean {\n return /\\w/.test(ch)\n}\n\n/**\n * Find word boundaries at a given column in the buffer row.\n * Returns { startCol, endCol } inclusive of the word.\n * If the position is on whitespace/punctuation, selects that single char.\n */\nexport function findWordBoundary(\n buffer: TerminalBuffer,\n col: number,\n row: number,\n): { startCol: number; endCol: number } {\n const width = buffer.width\n const ch = buffer.getCell(col, row).char\n\n if (isWordChar(ch)) {\n // Walk backwards to find word start\n let startCol = col\n while (startCol > 0 && isWordChar(buffer.getCell(startCol - 1, row).char)) {\n startCol--\n }\n // Walk forwards to find word end\n let endCol = col\n while (endCol < width - 1 && isWordChar(buffer.getCell(endCol + 1, row).char)) {\n endCol++\n }\n return { startCol, endCol }\n }\n\n // Non-word char: select just that character\n return { startCol: col, endCol: col }\n}\n\n// ============================================================================\n// Line Boundary Detection\n// ============================================================================\n\n/**\n * Find line boundaries for a given row.\n * Returns { startCol, endCol } spanning from first content to last content.\n * If the row is empty, returns { startCol: 0, endCol: width - 1 }.\n */\nexport function findLineBoundary(\n buffer: TerminalBuffer,\n row: number,\n): { startCol: number; endCol: number } {\n const width = buffer.width\n\n // Find last non-space column\n let endCol = width - 1\n while (endCol > 0 && buffer.getCell(endCol, row).char.trim() === \"\") {\n endCol--\n }\n\n // Find first non-space column\n let startCol = 0\n while (startCol < endCol && buffer.getCell(startCol, row).char.trim() === \"\") {\n startCol++\n }\n\n // If entirely blank (including when startCol == endCol and that cell is blank), select the full row\n if (startCol >= endCol && buffer.getCell(startCol, row).char.trim() === \"\") {\n return { startCol: 0, endCol: width - 1 }\n }\n\n return { startCol, endCol }\n}\n\n// ============================================================================\n// Granularity-Aware Extend\n// ============================================================================\n\n/**\n * Extend the head position according to the current granularity.\n * For word granularity: snaps to word boundaries.\n * For line granularity: snaps to line boundaries.\n */\nfunction extendByGranularity(\n col: number,\n row: number,\n anchor: SelectionPosition,\n granularity: SelectionGranularity,\n buffer?: TerminalBuffer,\n): SelectionPosition {\n if (granularity === \"character\" || !buffer) {\n return { col, row }\n }\n\n if (granularity === \"word\") {\n const { startCol, endCol } = findWordBoundary(buffer, col, row)\n // Extend towards the anchor direction\n const isForward = row > anchor.row || (row === anchor.row && col >= anchor.col)\n return isForward ? { col: endCol, row } : { col: startCol, row }\n }\n\n if (granularity === \"line\") {\n const { startCol, endCol } = findLineBoundary(buffer, row)\n const isForward = row > anchor.row || (row === anchor.row && col >= anchor.col)\n return isForward ? { col: endCol, row } : { col: startCol, row }\n }\n\n return { col, row }\n}\n\n// ============================================================================\n// State\n// ============================================================================\n\nexport function createTerminalSelectionState(): TerminalSelectionState {\n return { range: null, selecting: false, source: null, granularity: \"character\", scope: null }\n}\n\n// ============================================================================\n// Update\n// ============================================================================\n\n/**\n * Clamp a position to a scope boundary.\n */\nfunction clampToScope(col: number, row: number, scope: SelectionScope | null): SelectionPosition {\n if (!scope) return { col, row }\n return {\n col: Math.max(scope.left, Math.min(scope.right, col)),\n row: Math.max(scope.top, Math.min(scope.bottom, row)),\n }\n}\n\nexport function terminalSelectionUpdate(\n action: SelectionAction,\n state: TerminalSelectionState,\n): [TerminalSelectionState, SelectionEffect[]] {\n switch (action.type) {\n case \"start\": {\n const scope = action.scope ?? null\n const pos = clampToScope(action.col, action.row, scope)\n return [\n {\n range: { anchor: pos, head: pos },\n selecting: true,\n source: action.source ?? \"mouse\",\n granularity: \"character\",\n scope,\n },\n [{ type: \"render\" }],\n ]\n }\n\n case \"startWord\": {\n const scope = action.scope ?? null\n const { startCol, endCol } = findWordBoundary(action.buffer, action.col, action.row)\n const anchorPos = clampToScope(startCol, action.row, scope)\n const headPos = clampToScope(endCol, action.row, scope)\n return [\n {\n range: { anchor: anchorPos, head: headPos },\n selecting: true,\n source: action.source ?? \"mouse\",\n granularity: \"word\",\n scope,\n },\n [{ type: \"render\" }],\n ]\n }\n\n case \"startLine\": {\n const scope = action.scope ?? null\n const { startCol, endCol } = findLineBoundary(action.buffer, action.row)\n const anchorPos = clampToScope(startCol, action.row, scope)\n const headPos = clampToScope(endCol, action.row, scope)\n return [\n {\n range: { anchor: anchorPos, head: headPos },\n selecting: true,\n source: action.source ?? \"mouse\",\n granularity: \"line\",\n scope,\n },\n [{ type: \"render\" }],\n ]\n }\n\n case \"extend\": {\n if (!state.selecting) return [state, []]\n const scope = \"scope\" in action ? (action.scope ?? null) : state.scope\n const extended = extendByGranularity(\n action.col,\n action.row,\n state.range!.anchor,\n state.granularity,\n action.buffer,\n )\n const head = clampToScope(extended.col, extended.row, scope)\n return [\n {\n ...state,\n range: { anchor: state.range!.anchor, head },\n scope,\n selecting: true,\n },\n [{ type: \"render\" }],\n ]\n }\n\n case \"finish\": {\n if (!state.range) return [{ ...state, selecting: false }, []]\n return [{ ...state, range: state.range, selecting: false }, []]\n }\n\n case \"clear\": {\n const hadRange = state.range !== null\n return [createTerminalSelectionState(), hadRange ? [{ type: \"render\" }] : []]\n }\n }\n}\n\n// ============================================================================\n// Range Normalization\n// ============================================================================\n\nexport function normalizeRange(range: SelectionRange): {\n startRow: number\n startCol: number\n endRow: number\n endCol: number\n} {\n const { anchor, head } = range\n\n if (anchor.row < head.row || (anchor.row === head.row && anchor.col <= head.col)) {\n return { startRow: anchor.row, startCol: anchor.col, endRow: head.row, endCol: head.col }\n }\n\n return { startRow: head.row, startCol: head.col, endRow: anchor.row, endCol: anchor.col }\n}\n\n// ============================================================================\n// Text Extraction\n// ============================================================================\n\nexport interface ExtractTextOptions {\n /**\n * Whether to skip cells without the SELECTABLE_FLAG (margins, gutters,\n * padding, borders, chrome). **Default: `true`** — copy is *semantic* by\n * contract (see docs/guide/text-selection.md: \"Copy uses this semantic\n * metadata\"), matching the highlight, which filters the same way\n * (`composeSelectionCells(..., respectSelectableFlag=true)`). Pass `false`\n * ONLY for raw screen-rectangle extraction — i.e. Shift+drag buffer\n * selection, which deliberately copies exactly what is on screen.\n *\n * The default was `false`, which silently let every copy path leak the\n * padded screen rectangle until each remembered to opt in — the\n * defaults-contract bug class. The default now matches the documented\n * contract; raw extraction opts out.\n */\n respectSelectableFlag?: boolean\n /** Row metadata for soft-wrap handling and precise trailing space trimming */\n rowMetadata?: readonly RowMetadata[]\n /**\n * Active selection scope. When set, every row's col range is clamped to\n * `[scope.left, scope.right]` so extracted text cannot include cells outside\n * the document-selection region or `userSelect=\"contain\"` boundary.\n */\n scope?: SelectionScope | null\n}\n\n/**\n * Extract text from a buffer within a selection range.\n *\n * Handles:\n * - Soft-wrap joining (via RowMetadata.softWrapped) — rejoins wrapped visual\n * rows into their logical line, reinserting the word-wrap break space\n * (RowMetadata.wrapJoinSpace) that trim-mode rendering stripped\n * - Trailing fill exclusion (clamp to RowMetadata.lastContentCol; falls back\n * to a trailing-whitespace trim when a row carries no metadata)\n * - Blank line preservation within selection\n * - Wide-char continuation cell skipping\n * - SELECTABLE_FLAG filtering (when respectSelectableFlag is true)\n * - Contain-scope clipping (when options.scope is set)\n */\nexport function extractText(\n buffer: TerminalBuffer,\n range: SelectionRange,\n options?: ExtractTextOptions,\n): string {\n const { startRow, startCol, endRow, endCol } = normalizeRange(range)\n // Semantic-by-default: copy matches the highlight (which filters by\n // SELECTABLE_FLAG). Raw screen-rectangle extraction (Shift+drag) opts out.\n const respectSelectable = options?.respectSelectableFlag ?? true\n const rowMeta = options?.rowMetadata\n const scope = options?.scope\n\n const parts: string[] = []\n\n for (let row = startRow; row <= endRow; row++) {\n const meta = rowMeta?.[row]\n let colStart = row === startRow ? startCol : 0\n let colEnd = row === endRow ? endCol : buffer.width - 1\n // Clip to contain scope on every row, not just the anchor/head rows.\n // Without this, multi-row selections inside a contain ancestor would\n // still grab full-width cells on interior rows.\n if (scope) {\n colStart = Math.max(colStart, scope.left)\n colEnd = Math.min(colEnd, scope.right)\n if (colStart > colEnd) {\n // This row is entirely outside the scope — emit an empty line to\n // preserve row counts, then continue.\n if (!(meta?.softWrapped && row < endRow)) {\n parts.push(\"\")\n if (row < endRow) parts.push(\"\\n\")\n }\n continue\n }\n }\n\n // Don't read past the row's real content into clear-to-edge background\n // fill. `lastContentCol` is the last column the text producer actually\n // wrote: authored trailing spaces are included, but the fill cells a\n // short/wrapped row paints out to the screen edge are not. Bounding the\n // loop (instead of trailing-trimming the built string) stays correct when\n // continuation / non-selectable cells are skipped mid-row, and preserves\n // authored trailing whitespace that a blanket trim would eat.\n if (meta && meta.lastContentCol >= 0) {\n colEnd = Math.min(colEnd, meta.lastContentCol)\n }\n\n let line = \"\"\n for (let col = colStart; col <= colEnd; col++) {\n // Skip wide-char continuation cells\n if (buffer.isCellContinuation(col, row)) continue\n\n // Skip non-selectable cells when flag checking is enabled\n if (respectSelectable && !buffer.isCellSelectable(col, row)) continue\n\n line += buffer.getCellChar(col, row)\n }\n\n // No producer metadata (a direct extractText caller that didn't pass\n // rowMetadata): fall back to a trailing-whitespace trim so raw fill spaces\n // don't leak. With metadata, the colEnd clamp above already excluded the\n // fill — no trim needed (or wanted: it would eat authored trailing spaces\n // that are real selectable content).\n if (!meta || meta.lastContentCol < 0) {\n line = line.replace(/\\s+$/, \"\")\n }\n\n // Preserve blank lines within selection (don't drop them).\n // Soft-wrapped rows rejoin into their logical line with NO newline; a\n // word-wrap break consumed a space (trimmed from both rows) so reinsert a\n // single separator space, while a forced mid-word break (`wrapJoinSpace`\n // false/undefined) rejoins with nothing. Hard breaks keep their newline.\n if (meta?.softWrapped && row < endRow) {\n parts.push(line)\n if (meta.wrapJoinSpace) parts.push(\" \")\n } else {\n parts.push(line)\n // Add newline separator unless this is the last row\n if (row < endRow) {\n parts.push(\"\\n\")\n }\n }\n }\n\n return parts.join(\"\")\n}\n","/**\n * Drag Event Types — types and utilities for the drag-and-drop system.\n *\n * Drag events are separate from mouse events — they have different semantics.\n * Mouse events fire on the node under the cursor; drag events track the\n * dragged source node and the drop target.\n *\n * The pointer state machine (pointer-state.ts) decides when a drag starts;\n * the integration layer (DragFeature) uses these types to dispatch\n * drag events to the appropriate nodes.\n *\n * Prop types (DragEventProps, DragEventPayload) live in @silvery/ag/drag-event-types\n * since BoxProps needs them. This module provides runtime state and utilities.\n */\n\nimport type { AgNode } from \"@silvery/ag/types\"\nimport type { DragEventProps } from \"@silvery/ag/drag-event-types\"\nimport type { Position } from \"@silvery/headless/pointer\"\n\n// Re-export prop types for convenience\nexport type { DragEventProps, DragEventPayload } from \"@silvery/ag/drag-event-types\"\n\n// ============================================================================\n// Drag State\n// ============================================================================\n\n/**\n * Active drag session state.\n * Created when pointer state machine emits startDrag, updated on updateDrag,\n * cleared on finishDrag/cancelDrag.\n */\nexport interface DragState {\n /** Whether a drag is currently active */\n active: boolean\n /** The node being dragged (has draggable=true) */\n source: AgNode\n /** Terminal position where the drag started */\n startPos: Position\n /** Current drag position (updated on every pointer move) */\n currentPos: Position\n /** Node currently under the cursor that accepts drops (has onDrop handler), or null */\n dropTarget: AgNode | null\n}\n\n// ============================================================================\n// Drag Event\n// ============================================================================\n\n/**\n * Runtime drag event — structurally identical to DragEventPayload.\n * Used by the integration layer for dispatching.\n */\nexport interface DragEvent {\n /** The node being dragged */\n source: AgNode\n /** Current terminal position of the pointer */\n position: Position\n /** The node under the cursor (the drop target receiving this event) */\n dropTarget: AgNode | null\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\n/**\n * Create a DragEvent payload.\n */\nexport function createDragEvent(\n source: AgNode,\n position: Position,\n dropTarget: AgNode | null,\n): DragEvent {\n return { source, position, dropTarget }\n}\n\n/**\n * Create initial DragState when a drag starts.\n */\nexport function createDragState(source: AgNode, startPos: Position): DragState {\n return {\n active: true,\n source,\n startPos,\n currentPos: startPos,\n dropTarget: null,\n }\n}\n\n/**\n * Check if a node accepts drops (has any drag event handler).\n */\nexport function isDropTarget(node: AgNode | null): boolean {\n if (!node) return false\n const props = node.props as DragEventProps\n return !!(props.onDragEnter || props.onDragLeave || props.onDragOver || props.onDrop)\n}\n\n/**\n * Find the nearest ancestor (including self) that is a drop target.\n * Returns null if no ancestor accepts drops.\n */\nexport function findDropTarget(node: AgNode | null): AgNode | null {\n let current = node\n while (current) {\n if (isDropTarget(current)) return current\n current = current.parent\n }\n return null\n}\n","/**\n * useCursor - Show and position the terminal's blinking cursor.\n *\n * Maps component-relative (col, row) to absolute terminal coordinates\n * using useScrollRect. Per-instance last-writer-wins: only one cursor\n * can be active at a time per silvery instance (the terminal has one hardware cursor).\n *\n * Cursor state is isolated per silvery instance via CursorContext. Each runtime\n * (run(), createApp(), test renderer) provides its own CursorProvider so\n * multiple silvery instances don't fight over cursor position.\n *\n * Falls back to module-level globals when no CursorProvider is present\n * (backward compatibility / deprecation path).\n */\n\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useLayoutEffect,\n useRef,\n type ReactNode,\n} from \"react\"\nimport type { CursorShape } from \"@silvery/ag-term/output\"\nimport type { BoxProps } from \"@silvery/ag/types\"\nimport { NodeContext } from \"../context\"\nimport { useScrollRect, type Rect } from \"./useLayout\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface CursorPosition {\n /** Column offset within the component (0-indexed) */\n col: number\n /** Row offset within the component (0-indexed) */\n row: number\n /** Whether the cursor should be visible. Default: true */\n visible?: boolean\n /** Terminal cursor shape (DECSCUSR). Default: terminal default */\n shape?: CursorShape\n}\n\nexport interface CursorState {\n /** Absolute terminal X position (0-indexed) */\n x: number\n /** Absolute terminal Y position (0-indexed) */\n y: number\n /** Whether cursor is visible */\n visible: boolean\n /** Terminal cursor shape (DECSCUSR) */\n shape?: CursorShape\n /**\n * Identity of the `useCursor` instance that last wrote this state. Set\n * automatically by the hook on each store write. Used by the\n * clear-on-unmount + clear-on-hide branches to skip the clear when\n * THIS instance no longer owns the state — preventing the last-writer-\n * wins stomp where an inactive sibling instance clears the active\n * one's cursor. See bead `@km/silvery/13011-usecursor-race`. Optional\n * because legacy external writes (Ink compat, direct store pokes) may\n * omit it.\n */\n owner?: number\n}\n\n/**\n * Unique-id counter for `useCursor` instances. Module-level so the\n * counter is shared across ALL instances in the process — guarantees\n * uniqueness regardless of CursorProvider scope. Wraps at MAX_SAFE_INTEGER\n * (practically infinite for terminal-UI lifetimes).\n */\nlet cursorInstanceIdCounter = 0\n\n// ============================================================================\n// Cursor Accessors — imperative interface for non-React consumers\n// ============================================================================\n\n/**\n * Imperative cursor accessors for non-React code (scheduler, output phase).\n * Created by createCursorStore() and threaded to consumers that can't use hooks.\n */\nexport interface CursorAccessors {\n getCursorState(): CursorState | null\n subscribeCursor(listener: () => void): () => void\n}\n\n// ============================================================================\n// Cursor Store — per-instance state + accessors\n// ============================================================================\n\nexport interface CursorStore {\n state: CursorState | null\n listeners: Set<() => void>\n accessors: CursorAccessors\n setCursorState(state: CursorState | null): void\n}\n\n/**\n * Create an isolated cursor store. Each silvery instance gets one.\n * Returns the store (for CursorProvider) and accessors (for scheduler/output).\n */\nexport function createCursorStore(): CursorStore {\n const store: CursorStore = {\n state: null,\n listeners: new Set(),\n accessors: null!,\n setCursorState(s: CursorState | null) {\n store.state = s\n for (const listener of store.listeners) listener()\n },\n }\n store.accessors = {\n getCursorState: () => store.state,\n subscribeCursor: (listener: () => void) => {\n store.listeners.add(listener)\n return () => {\n store.listeners.delete(listener)\n }\n },\n }\n return store\n}\n\n// ============================================================================\n// React Context\n// ============================================================================\n\nconst CursorCtx = createContext<CursorStore | null>(null)\n\n/**\n * Provider that gives its subtree an isolated cursor store.\n * Wrap your silvery app root in this to isolate cursor state per instance.\n */\nexport function CursorProvider({ store, children }: { store: CursorStore; children?: ReactNode }) {\n return React.createElement(CursorCtx.Provider, { value: store }, children)\n}\n\n// ============================================================================\n// Module-level Fallback (deprecated — for bare render() without provider)\n// ============================================================================\n\nlet _globalCursorState: CursorState | null = null\nlet _globalCursorListeners = new Set<() => void>()\n\nfunction globalSetCursorState(state: CursorState | null): void {\n _globalCursorState = state\n for (const listener of _globalCursorListeners) listener()\n}\n\nfunction globalGetCursorState(): CursorState | null {\n return _globalCursorState\n}\n\nfunction globalSubscribeCursor(listener: () => void): () => void {\n _globalCursorListeners.add(listener)\n return () => {\n _globalCursorListeners.delete(listener)\n }\n}\n\n/** For testing -- reset global fallback state between tests. */\nexport function resetCursorState(): void {\n _globalCursorState = null\n _globalCursorListeners = new Set()\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\n/**\n * Show and position the terminal's blinking cursor within this component.\n *\n * The cursor position is relative to the component's screen position.\n * Only one cursor can be active per silvery instance -- last caller with visible=true wins.\n *\n * Uses CursorContext if a CursorProvider is present (per-instance isolation).\n * Falls back to module-level globals otherwise (backward compat).\n *\n * @deprecated Phase 2 of `km-silvery.view-as-layout-output` migrated cursor\n * positioning from a React-effect-chain (`useCursor` → `useScrollRect` →\n * `setCursorState`) to a layout-output prop (`<Box cursorOffset={…}>`). The\n * prop path resolves absolute coordinates synchronously during the layout\n * phase, so the very first frame after a conditional mount emits correct\n * cursor ANSI — fixing `km-silvercode.cursor-startup-position` end-to-end.\n *\n * Migrate consumers by deleting the `useCursor({col, row, visible})` call\n * and adding `cursorOffset={{col, row, visible}}` to the surrounding Box. The\n * layout phase applies border + padding offsets automatically, so consumers\n * no longer need to add `borderColOffset` / `borderRowOffset` manually.\n *\n * This hook remains as a back-compat wrapper that writes to the cursor\n * store; the scheduler still reads the store as a fallback when no\n * `cursorOffset` prop is set on any node. Slated for deletion once all\n * in-tree consumers migrate (TextArea + TextInput already migrated; Ink\n * compat goes through the store directly and is unaffected). See bead\n * `km-silvery.delete-cursor-globals`.\n */\nexport function useCursor(position: CursorPosition): void {\n const { col, row, visible = true, shape } = position\n const store = useContext(CursorCtx)\n const node = useContext(NodeContext)\n\n // Resolve set/get functions from context or global fallback\n const set = store ? store.setCursorState.bind(store) : globalSetCursorState\n const get = store ? store.accessors.getCursorState : globalGetCursorState\n\n // Per-instance identity — stamped on every store write so the clear-\n // on-unmount + clear-on-hide branches can detect \"we don't own the\n // cursor anymore\" and skip the clear. Fixes the multi-instance race\n // documented in `@km/silvery/13011-usecursor-race`.\n const instanceIdRef = useRef<number>(0)\n if (instanceIdRef.current === 0) {\n cursorInstanceIdCounter += 1\n instanceIdRef.current = cursorInstanceIdCounter\n }\n\n // Compute content area offset from the parent node's border + padding.\n // useScrollRect provides the node's border-box rect, but cursor\n // col/row are relative to the content area (inside border + padding).\n const props = node?.props as BoxProps | undefined\n const padLeft = props?.paddingLeft ?? props?.paddingX ?? props?.padding ?? 0\n const padTop = props?.paddingTop ?? props?.paddingY ?? props?.padding ?? 0\n const borderLeft = props?.borderStyle ? 1 : 0\n const borderTop = props?.borderStyle ? 1 : 0\n const contentOffsetX = borderLeft + padLeft\n const contentOffsetY = borderTop + padTop\n\n // Keep current args in refs so the callback always reads fresh values\n const colRef = useRef(col)\n const rowRef = useRef(row)\n const visibleRef = useRef(visible)\n const shapeRef = useRef(shape)\n const setRef = useRef(set)\n const getRef = useRef(get)\n const lastRectRef = useRef<Rect | null>(null)\n const contentOffsetXRef = useRef(contentOffsetX)\n const contentOffsetYRef = useRef(contentOffsetY)\n colRef.current = col\n rowRef.current = row\n visibleRef.current = visible\n shapeRef.current = shape\n setRef.current = set\n getRef.current = get\n contentOffsetXRef.current = contentOffsetX\n contentOffsetYRef.current = contentOffsetY\n\n // useScrollRect returns the deferred (committed) rect — one frame late\n // vs the in-flight layout, idempotent across convergence passes. The\n // useEffect below reads it and pushes the cursor position to the store.\n // Same observable result as the previous callback form, except the\n // cursor lands on the new position one frame after a layout change\n // (matches the semantics every other reactive layout consumer now sees\n // post the deferred-only switch).\n //\n // Skip when there's no NodeContext — useScrollRect returns the empty\n // sentinel `{0,0,0,0}` in that case, and the test contract for `useCursor`\n // outside a Box parent requires the cursor store to stay untouched.\n const scrollRect = useScrollRect()\n const hasNode = node != null\n useEffect(() => {\n if (!hasNode) return\n lastRectRef.current = scrollRect\n if (!visibleRef.current) {\n return\n }\n setRef.current({\n x: scrollRect.x + contentOffsetXRef.current + colRef.current,\n y: scrollRect.y + contentOffsetYRef.current + rowRef.current,\n visible: true,\n shape: shapeRef.current,\n owner: instanceIdRef.current,\n })\n }, [hasNode, scrollRect.x, scrollRect.y, scrollRect.width, scrollRect.height, scrollRect])\n\n // When col/row/shape change without a layout change, update cursor\n // position from the last known screen rect. This handles the common case\n // where typing moves the cursor within a component but the component's\n // layout position stays the same (e.g., TextInput cursor moves on keystroke).\n // Write + clear-on-hide in ONE useLayoutEffect — keeps the two\n // operations on the same effect-ordering tier (no cross-tier race\n // where a previous useEffect cleanup nukes the just-written state\n // from a later useLayoutEffect). Owner check guards the clear so an\n // inactive sibling never stomps the active one. Tracking:\n // @km/silvery/13011-usecursor-race.\n const owns = (): boolean => {\n const current = getRef.current()\n if (!current) return false\n // Legacy writes (Ink compat, direct store pokes) may omit owner —\n // treat any owner-less state as \"anyone may clear\" to preserve\n // backwards compat for non-useCursor writers.\n if (current.owner === undefined) return true\n return current.owner === instanceIdRef.current\n }\n const ownsRef = useRef(owns)\n ownsRef.current = owns\n\n useLayoutEffect(() => {\n const rect = lastRectRef.current\n if (!rect) return\n if (visible) {\n set({\n x: rect.x + contentOffsetX + col,\n y: rect.y + contentOffsetY + row,\n visible: true,\n shape,\n owner: instanceIdRef.current,\n })\n } else if (ownsRef.current()) {\n // Hiding — clear only if we still own the cursor (another sibling\n // may have already overwritten it).\n set(null)\n }\n }, [col, row, contentOffsetX, contentOffsetY, shape, visible, set])\n\n // Unmount-only cleanup. Empty deps → cleanup fires ONLY when the\n // component unmounts (not on every visible-prop change). Owner check\n // prevents an inactive sibling's unmount from clearing the active\n // sibling's cursor.\n useEffect(() => {\n return () => {\n if (ownsRef.current()) {\n setRef.current(null)\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- intentional unmount-only\n }, [])\n}\n\n// The legacy `getCursorState` / `subscribeCursor` public wrappers were\n// removed in km-silvery.delete-cursor-globals (the\n// `@km/silvery/12393-delete-cursor-globals` bead). Scheduler callers that\n// want cursor visibility wire `cursorAccessors` from `createCursorStore()`\n// explicitly; the scheduler falls back to `() => null` when no accessors\n// are provided. The module-level `_globalCursorState` /\n// `globalSetCursorState` / `globalGetCursorState` / `globalSubscribeCursor`\n// helpers above are kept for the @deprecated `useCursor` hook's\n// no-provider path (own deletion tracked at km-silvery.delete-use-cursor),\n// but are no longer part of the public surface.\n","/**\n * Adapter-aware render pipeline.\n *\n * Runs the full measure → layout → scroll → render → flush pipeline\n * using the current RenderAdapter (terminal, canvas, DOM, etc.).\n *\n * Split out from pipeline/index.ts — the adapter path orchestrates\n * phases directly (no createAg) since it uses renderPhaseAdapter\n * and RenderBuffer instead of the terminal-specific renderPhase.\n */\n\nimport { createLogger } from \"loggily\"\nimport type { AgNode } from \"@silvery/ag/types\"\nimport type { ExecuteRenderOptions } from \"./index\"\nimport { type RenderBuffer, getRenderAdapter, hasRenderAdapter } from \"../render-adapter\"\nimport { renderPhaseAdapter } from \"./render-phase-adapter\"\nimport { clearBgConflictWarnings } from \"./render-phase\"\nimport {\n layoutPhase,\n notifyLayoutSubscribers,\n scrollrectPhase,\n scrollPhase,\n stickyPhase,\n} from \"./layout-phase\"\nimport { measurePhase } from \"./measure-phase\"\n\nconst log = createLogger(\"silvery:render\")\nconst baseLog = createLogger(\"@silvery/ag-react\")\n\n/**\n * Execute the full render pipeline using the current RenderAdapter.\n *\n * This version works with any adapter (terminal, canvas, etc.) and returns\n * a RenderBuffer instead of a TerminalBuffer.\n *\n * @param root The root SilveryNode\n * @param width Width in adapter units (cells for terminal, pixels for canvas)\n * @param height Height in adapter units\n * @param prevBuffer Previous buffer for diffing (null on first render)\n * @param options Render options\n * @returns Object with output (if any) and current buffer\n */\nexport function executeRenderAdapter(\n root: AgNode,\n width: number,\n height: number,\n prevBuffer: RenderBuffer | null,\n options: ExecuteRenderOptions | \"fullscreen\" | \"inline\" = \"fullscreen\",\n): { output: string | void; buffer: RenderBuffer } {\n if (!hasRenderAdapter()) {\n throw new Error(\"executeRenderAdapter called without a render adapter set\")\n }\n\n const opts: ExecuteRenderOptions = typeof options === \"string\" ? { mode: options } : options\n const { skipLayoutNotifications = false } = opts\n const start = Date.now()\n const adapter = getRenderAdapter()\n\n using render = baseLog.span?.(\"pipeline-adapter\", {\n width,\n height,\n adapter: adapter.name,\n })\n\n // Clear per-render caches\n clearBgConflictWarnings()\n\n // Phase 1: Measure\n {\n using _measure = render?.span(\"measure\")\n measurePhase(root)\n log.debug?.(`measure phase complete`)\n }\n\n // Phase 2: Layout\n {\n using _layout = render?.span(\"layout\")\n layoutPhase(root, width, height)\n log.debug?.(`layout phase complete`)\n }\n\n // Phase 2.5: Scroll calculation\n {\n using _scroll = render?.span(\"scroll\")\n scrollPhase(root)\n }\n\n // Phase 2.55: Sticky phase (non-scroll container sticky children)\n stickyPhase(root)\n\n // Phase 2.6: Screen rect calculation\n {\n using _scrollrect = render?.span(\"scrollRect\")\n scrollrectPhase(root)\n }\n\n // Phase 2.7: Notify layout subscribers\n if (!skipLayoutNotifications) {\n using _notify = render?.span(\"notify\")\n notifyLayoutSubscribers(root)\n }\n\n // Phase 3: Content render (adapter-aware)\n let buffer: RenderBuffer\n {\n using _content = render?.span(\"content\")\n buffer = renderPhaseAdapter(root)\n log.debug?.(`content phase complete`)\n }\n\n // Phase 4: Flush via adapter\n let output: string | void\n {\n using outputSpan = render?.span(\"output\")\n output = adapter.flush(buffer, prevBuffer)\n if (typeof output === \"string\" && outputSpan) {\n outputSpan.spanData.bytes = output.length\n }\n log.debug?.(`output phase complete`)\n }\n\n log.debug?.(`total pipeline: ${Date.now() - start}ms`)\n\n return { output, buffer }\n}\n","/**\n * Canvas Render Adapter\n *\n * Two modes:\n * - **monospace** (default): Layout in cell units (cols x rows), convert to pixels at draw time.\n * - **proportional** (monospace: false): Layout in pixel units, draw at pixel coordinates directly.\n * Uses canvas measureText() for real font metrics. Enables proportional fonts (Inter, SF Pro, etc.)\n */\n\nimport type {\n BorderChars,\n RenderAdapter,\n RenderBuffer,\n RenderStyle,\n TextMeasureResult,\n TextMeasureStyle,\n TextMeasurer,\n} from \"../render-adapter\"\nimport type { Measurer } from \"../unicode\"\nimport { wrapTextWithMeasurer, stripAnsi } from \"../unicode\"\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nexport interface CanvasAdapterConfig {\n /** Font size in pixels (default: 14) */\n fontSize?: number\n /** Font family (default: 'monospace') */\n fontFamily?: string\n /** Line height multiplier (default: 1.2) */\n lineHeight?: number\n /** Background color (default: '#1e1e1e') */\n backgroundColor?: string\n /** Default foreground color (default: '#d4d4d4') */\n foregroundColor?: string\n /** Monospace mode (default: true). When false, uses proportional font measurement. */\n monospace?: boolean\n /** Device pixel ratio for sharp rendering on HiDPI displays (default: 1) */\n dpr?: number\n}\n\nconst DEFAULT_CONFIG: Required<CanvasAdapterConfig> = {\n fontSize: 14,\n fontFamily: \"monospace\",\n lineHeight: 1.2,\n backgroundColor: \"#1e1e1e\",\n foregroundColor: \"#d4d4d4\",\n monospace: true,\n dpr: 1,\n}\n\n// ============================================================================\n// Border Characters (same as terminal for consistency)\n// ============================================================================\n\nconst BORDER_CHARS: Record<string, BorderChars> = {\n single: {\n topLeft: \"┌\",\n topRight: \"┐\",\n bottomLeft: \"└\",\n bottomRight: \"┘\",\n horizontal: \"─\",\n vertical: \"│\",\n },\n double: {\n topLeft: \"╔\",\n topRight: \"╗\",\n bottomLeft: \"╚\",\n bottomRight: \"╝\",\n horizontal: \"═\",\n vertical: \"║\",\n },\n round: {\n topLeft: \"╭\",\n topRight: \"╮\",\n bottomLeft: \"╰\",\n bottomRight: \"╯\",\n horizontal: \"─\",\n vertical: \"│\",\n },\n bold: {\n topLeft: \"┏\",\n topRight: \"┓\",\n bottomLeft: \"┗\",\n bottomRight: \"┛\",\n horizontal: \"━\",\n vertical: \"┃\",\n },\n}\n\n// ============================================================================\n// Canvas Measurer\n// ============================================================================\n\n/** Create a scratch canvas 2D context for text measurement. */\nfunction createMeasureContext(\n fontSize: number,\n fontFamily: string,\n): CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D {\n // Prefer document.createElement(\"canvas\") over OffscreenCanvas for measurement —\n // OffscreenCanvas may not have access to web fonts loaded via <link> or @font-face.\n // A regular canvas element shares the document's font loading state.\n const canvas =\n typeof document !== \"undefined\"\n ? document.createElement(\"canvas\")\n : typeof OffscreenCanvas !== \"undefined\"\n ? new OffscreenCanvas(1, 1)\n : null\n if (!canvas) throw new Error(\"Canvas not available for text measurement\")\n const ctx = canvas.getContext(\"2d\")\n if (!ctx) throw new Error(\"Could not get 2d context for measurement\")\n ;(ctx as CanvasRenderingContext2D).font = `${fontSize}px ${fontFamily}`\n return ctx as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D\n}\n\nfunction createCanvasMeasurer(config: Required<CanvasAdapterConfig>): TextMeasurer {\n if (config.monospace) {\n return {\n measureText(text: string, _style?: TextMeasureStyle): TextMeasureResult {\n return { width: text.length, height: 1 }\n },\n getLineHeight(_style?: TextMeasureStyle): number {\n return 1\n },\n }\n }\n\n const lineHeightPx = config.fontSize * config.lineHeight\n const ctx = createMeasureContext(config.fontSize, config.fontFamily)\n\n return {\n measureText(text: string, _style?: TextMeasureStyle): TextMeasureResult {\n return { width: ctx.measureText(text).width, height: lineHeightPx }\n },\n getLineHeight(_style?: TextMeasureStyle): number {\n return lineHeightPx\n },\n }\n}\n\n// ============================================================================\n// Proportional (Pixel) Measurer\n// ============================================================================\n\n/** Font config for pixel measurement — only what's needed. */\nexport interface CanvasPixelMeasurerConfig {\n fontSize: number\n fontFamily: string\n lineHeight: number\n}\n\n/**\n * Create a pipeline Measurer that uses canvas font metrics for pixel-accurate widths.\n * All measurements return pixel values. Wrapping reuses silvery's wrapTextWithMeasurer.\n */\nexport function createCanvasPixelMeasurer(config: CanvasPixelMeasurerConfig): Measurer {\n const ctx = createMeasureContext(config.fontSize, config.fontFamily)\n const lineHeightPx = config.fontSize * config.lineHeight\n\n // Simple cache with full eviction at capacity (5000 entries, mostly single graphemes)\n const cache = new Map<string, number>()\n\n function pixelWidth(text: string): number {\n if (text.length === 0) return 0\n const cached = cache.get(text)\n if (cached !== undefined) return cached\n if (cache.size >= 5000) cache.clear()\n const w = ctx.measureText(text).width\n cache.set(text, w)\n return w\n }\n\n // Use Intl.Segmenter for proper grapheme iteration (emoji, CJK, combining marks)\n const segmenter = new Intl.Segmenter(undefined, { granularity: \"grapheme\" })\n\n function sliceGraphemes(text: string, maxWidth: number, fromEnd: boolean): string {\n const stripped = stripAnsi(text)\n if (pixelWidth(stripped) <= maxWidth) return text\n const graphemes = [...segmenter.segment(stripped)].map((s) => s.segment)\n let width = 0\n if (fromEnd) {\n for (let i = graphemes.length - 1; i >= 0; i--) {\n const gw = pixelWidth(graphemes[i]!)\n if (width + gw > maxWidth) return graphemes.slice(i + 1).join(\"\")\n width += gw\n }\n return stripped\n }\n for (let i = 0; i < graphemes.length; i++) {\n const gw = pixelWidth(graphemes[i]!)\n if (width + gw > maxWidth) return graphemes.slice(0, i).join(\"\")\n width += gw\n }\n return stripped\n }\n\n const measurer: Measurer = {\n maybeWideEmojis: false,\n textSizing: false,\n lineHeight: lineHeightPx,\n displayWidth(text: string): number {\n return pixelWidth(stripAnsi(text))\n },\n displayWidthAnsi(text: string): number {\n return pixelWidth(stripAnsi(text))\n },\n graphemeWidth(grapheme: string): number {\n return pixelWidth(grapheme)\n },\n wrapText(text: string, width: number, trim?: boolean, hard?: boolean): string[] {\n return wrapTextWithMeasurer(text, width, measurer, trim ?? false, hard ?? false)\n },\n sliceByWidth(text: string, maxWidth: number): string {\n return sliceGraphemes(text, maxWidth, false)\n },\n sliceByWidthFromEnd(text: string, maxWidth: number): string {\n return sliceGraphemes(text, maxWidth, true)\n },\n }\n\n return measurer\n}\n\n// ============================================================================\n// Color Conversion\n// ============================================================================\n\n// ANSI 256-color palette (standard 16 colors)\nconst ANSI_COLORS: Record<string, string> = {\n black: \"#000000\",\n red: \"#cd0000\",\n green: \"#00cd00\",\n yellow: \"#cdcd00\",\n blue: \"#0000ee\",\n magenta: \"#cd00cd\",\n cyan: \"#00cdcd\",\n white: \"#e5e5e5\",\n gray: \"#7f7f7f\",\n grey: \"#7f7f7f\",\n brightBlack: \"#7f7f7f\",\n brightRed: \"#ff0000\",\n brightGreen: \"#00ff00\",\n brightYellow: \"#ffff00\",\n brightBlue: \"#5c5cff\",\n brightMagenta: \"#ff00ff\",\n brightCyan: \"#00ffff\",\n brightWhite: \"#ffffff\",\n}\n\nfunction resolveColor(color: string | undefined, fallback: string): string {\n if (!color) return fallback\n\n // Already a CSS color (hex, rgb, etc.)\n if (color.startsWith(\"#\") || color.startsWith(\"rgb\")) {\n return color\n }\n\n // Named ANSI color\n const named = ANSI_COLORS[color.toLowerCase()]\n if (named) return named\n\n // Pass through (might be a CSS color name like 'cyan')\n return color\n}\n\n// ============================================================================\n// Canvas Render Buffer\n// ============================================================================\n\nexport class CanvasRenderBuffer implements RenderBuffer {\n readonly width: number\n readonly height: number\n readonly canvas: OffscreenCanvas | HTMLCanvasElement\n private ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D\n private config: Required<CanvasAdapterConfig>\n\n // Cell-to-pixel conversion factors\n private readonly charWidth: number\n private readonly cellHeight: number\n\n constructor(width: number, height: number, config: Required<CanvasAdapterConfig>) {\n this.width = width\n this.height = height\n this.config = config\n\n const dpr = config.dpr\n\n let cssWidth: number\n let cssHeight: number\n\n if (config.monospace) {\n // Monospace: layout in cell units, convert to pixels at draw time\n this.charWidth = config.fontSize * 0.6\n this.cellHeight = config.fontSize * config.lineHeight\n cssWidth = width * this.charWidth\n cssHeight = height * this.cellHeight\n } else {\n // Proportional: layout already in pixels, no conversion needed\n this.charWidth = 1\n this.cellHeight = 1\n cssWidth = width\n cssHeight = height\n }\n\n // Create canvas at native resolution (CSS pixels * DPR) for sharp HiDPI rendering.\n // The context transform scales all drawing by DPR, so coordinates stay in CSS pixels.\n const nativeWidth = Math.ceil(cssWidth * dpr)\n const nativeHeight = Math.ceil(cssHeight * dpr)\n\n if (typeof OffscreenCanvas !== \"undefined\") {\n this.canvas = new OffscreenCanvas(nativeWidth, nativeHeight)\n } else if (typeof document !== \"undefined\") {\n this.canvas = document.createElement(\"canvas\")\n this.canvas.width = nativeWidth\n this.canvas.height = nativeHeight\n } else {\n throw new Error(\"Canvas not available\")\n }\n\n const ctx = this.canvas.getContext(\"2d\")\n if (!ctx) throw new Error(\"Could not get 2d context\")\n this.ctx = ctx as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D\n\n // Scale context so all drawing uses CSS pixel coordinates\n if (dpr !== 1) {\n ;(this.ctx as CanvasRenderingContext2D).setTransform(dpr, 0, 0, dpr, 0, 0)\n }\n\n // Initialize with background\n this.ctx.fillStyle = config.backgroundColor\n this.ctx.fillRect(0, 0, cssWidth, cssHeight)\n }\n\n fillRect(x: number, y: number, width: number, height: number, style: RenderStyle): void {\n if (style.bg) {\n // Convert cell coordinates to pixel coordinates\n const px = x * this.charWidth\n const py = y * this.cellHeight\n const pw = width * this.charWidth\n const ph = height * this.cellHeight\n this.ctx.fillStyle = resolveColor(style.bg, this.config.backgroundColor)\n this.ctx.fillRect(px, py, pw, ph)\n }\n }\n\n drawText(x: number, y: number, text: string, style: RenderStyle): void {\n // Convert cell coordinates to pixel coordinates\n const px = x * this.charWidth\n\n const attrs = style.attrs ?? {}\n\n // Build font string\n const weight = attrs.bold ? \"bold\" : \"normal\"\n const fontStyle = attrs.italic ? \"italic\" : \"normal\"\n this.ctx.font = `${fontStyle} ${weight} ${this.config.fontSize}px ${this.config.fontFamily}`\n\n let py = y * this.cellHeight\n\n // Set colors\n this.ctx.fillStyle = resolveColor(style.fg, this.config.foregroundColor)\n\n if (!this.config.monospace) {\n // Match CSS line-height centering exactly.\n // CSS positions text by placing the alphabetic baseline at:\n // y + halfLeading + fontAscent\n // where halfLeading = (lineHeight - contentArea) / 2\n // and contentArea = fontBoundingBoxAscent + fontBoundingBoxDescent.\n // fontBoundingBox metrics are font-wide (not glyph-specific), matching CSS.\n const metrics = this.ctx.measureText(\"Hg\")\n const fontAscent = metrics.fontBoundingBoxAscent\n const fontDescent = metrics.fontBoundingBoxDescent\n const contentArea = fontAscent + fontDescent\n const lineHeightPx = this.config.fontSize * this.config.lineHeight\n const halfLeading = (lineHeightPx - contentArea) / 2\n this.ctx.textBaseline = \"alphabetic\"\n py += halfLeading + fontAscent\n } else {\n this.ctx.textBaseline = \"top\"\n }\n\n // Draw text\n this.ctx.fillText(text, px, py)\n\n // Handle underline\n if (attrs.underline) {\n this.drawUnderline(px, py, text, style)\n }\n\n // Handle strikethrough\n if (attrs.strikethrough) {\n const metrics = this.ctx.measureText(text)\n const textWidth = metrics.width\n const strikeY = py + this.config.fontSize * 0.5\n\n this.ctx.strokeStyle = resolveColor(style.fg, this.config.foregroundColor)\n this.ctx.lineWidth = 1\n this.ctx.beginPath()\n this.ctx.moveTo(px, strikeY)\n this.ctx.lineTo(px + textWidth, strikeY)\n this.ctx.stroke()\n }\n }\n\n /**\n * Draw underline decorations at pixel coordinates.\n * Note: px, py are already in pixel coordinates.\n */\n private drawUnderline(px: number, py: number, text: string, style: RenderStyle): void {\n const attrs = style.attrs ?? {}\n const metrics = this.ctx.measureText(text)\n const textWidth = metrics.width\n const underlineY = py + this.config.fontSize * 0.9\n\n const underlineColor = resolveColor(\n attrs.underlineColor ?? style.fg,\n this.config.foregroundColor,\n )\n\n this.ctx.strokeStyle = underlineColor\n this.ctx.lineWidth = 1\n\n const underlineStyle = attrs.underlineStyle ?? \"single\"\n\n switch (underlineStyle) {\n case \"double\":\n // Two parallel lines\n this.ctx.beginPath()\n this.ctx.moveTo(px, underlineY - 1)\n this.ctx.lineTo(px + textWidth, underlineY - 1)\n this.ctx.moveTo(px, underlineY + 1)\n this.ctx.lineTo(px + textWidth, underlineY + 1)\n this.ctx.stroke()\n break\n\n case \"curly\":\n // Wavy line using bezier curves\n this.ctx.beginPath()\n this.ctx.moveTo(px, underlineY)\n const waveLength = 4\n const amplitude = 2\n for (let wx = 0; wx < textWidth; wx += waveLength * 2) {\n this.ctx.quadraticCurveTo(\n px + wx + waveLength / 2,\n underlineY - amplitude,\n px + wx + waveLength,\n underlineY,\n )\n this.ctx.quadraticCurveTo(\n px + wx + (waveLength * 3) / 2,\n underlineY + amplitude,\n px + wx + waveLength * 2,\n underlineY,\n )\n }\n this.ctx.stroke()\n break\n\n case \"dotted\":\n this.ctx.setLineDash([2, 2])\n this.ctx.beginPath()\n this.ctx.moveTo(px, underlineY)\n this.ctx.lineTo(px + textWidth, underlineY)\n this.ctx.stroke()\n this.ctx.setLineDash([])\n break\n\n case \"dashed\":\n this.ctx.setLineDash([4, 2])\n this.ctx.beginPath()\n this.ctx.moveTo(px, underlineY)\n this.ctx.lineTo(px + textWidth, underlineY)\n this.ctx.stroke()\n this.ctx.setLineDash([])\n break\n\n default: // 'single'\n this.ctx.beginPath()\n this.ctx.moveTo(px, underlineY)\n this.ctx.lineTo(px + textWidth, underlineY)\n this.ctx.stroke()\n }\n }\n\n drawChar(x: number, y: number, char: string, style: RenderStyle): void {\n // For canvas, drawChar is essentially drawText for single chars\n this.drawText(x, y, char, style)\n }\n\n inBounds(x: number, y: number): boolean {\n return x >= 0 && x < this.width && y >= 0 && y < this.height\n }\n\n fillRoundedRect(\n x: number,\n y: number,\n width: number,\n height: number,\n radius: number,\n fill: string | undefined,\n stroke: string | undefined,\n lineWidth = 1,\n ): void {\n const px = x * this.charWidth\n const py = y * this.cellHeight\n const pw = width * this.charWidth\n const ph = height * this.cellHeight\n const r = Math.min(radius, pw / 2, ph / 2)\n\n this.ctx.beginPath()\n this.ctx.moveTo(px + r, py)\n this.ctx.lineTo(px + pw - r, py)\n this.ctx.quadraticCurveTo(px + pw, py, px + pw, py + r)\n this.ctx.lineTo(px + pw, py + ph - r)\n this.ctx.quadraticCurveTo(px + pw, py + ph, px + pw - r, py + ph)\n this.ctx.lineTo(px + r, py + ph)\n this.ctx.quadraticCurveTo(px, py + ph, px, py + ph - r)\n this.ctx.lineTo(px, py + r)\n this.ctx.quadraticCurveTo(px, py, px + r, py)\n this.ctx.closePath()\n\n if (fill) {\n this.ctx.fillStyle = fill\n this.ctx.fill()\n }\n if (stroke) {\n this.ctx.strokeStyle = stroke\n this.ctx.lineWidth = lineWidth\n this.ctx.stroke()\n }\n }\n}\n\n// ============================================================================\n// Canvas Adapter Factory\n// ============================================================================\n\nexport function createCanvasAdapter(config: CanvasAdapterConfig = {}): RenderAdapter {\n const cfg = { ...DEFAULT_CONFIG, ...config }\n const measurer = createCanvasMeasurer(cfg)\n\n return {\n name: \"canvas\",\n measurer,\n\n createBuffer(width: number, height: number): RenderBuffer {\n return new CanvasRenderBuffer(width, height, cfg)\n },\n\n flush(_buffer: RenderBuffer, _prevBuffer: RenderBuffer | null): void {\n // Canvas draws directly to the buffer during render.\n // The caller (renderToCanvas) copies the buffer to the visible canvas.\n },\n\n getBorderChars(style: string): BorderChars {\n return BORDER_CHARS[style] ?? BORDER_CHARS.single!\n },\n }\n}\n","/**\n * DOM Render Adapter\n *\n * Implements the RenderAdapter interface for browser DOM output.\n * Uses a line-based approach: one <div> per row, <span> elements for styled text runs.\n * The layout engine operates in cell units (columns x rows). This adapter\n * converts cell coordinates to pixel coordinates when rendering to the DOM,\n * using charWidth (fontSize * 0.6) and cellHeight (fontSize * lineHeight).\n *\n * Advantages over Canvas:\n * - Native text selection and copying\n * - Screen reader accessibility\n * - Browser font rendering (subpixel antialiasing, ligatures)\n * - CSS integration (theming, hover states)\n * - DevTools inspection\n *\n * Architecture follows xterm.js DOM renderer approach.\n * @see https://github.com/xtermjs/xterm.js/issues/3271\n */\n\nimport type {\n BorderChars,\n RenderAdapter,\n RenderBuffer,\n RenderStyle,\n TextMeasureResult,\n TextMeasureStyle,\n TextMeasurer,\n} from \"../render-adapter\"\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nexport interface DOMAdapterConfig {\n /** Font size in pixels (default: 14) */\n fontSize?: number\n /** Font family (default: 'monospace') */\n fontFamily?: string\n /** Line height multiplier (default: 1.2) */\n lineHeight?: number\n /** Background color (default: '#1e1e1e') */\n backgroundColor?: string\n /** Default foreground color (default: '#d4d4d4') */\n foregroundColor?: string\n /** CSS class prefix (default: 'silvery') */\n classPrefix?: string\n}\n\nconst DEFAULT_CONFIG: Required<DOMAdapterConfig> = {\n fontSize: 14,\n fontFamily: \"monospace\",\n lineHeight: 1.2,\n backgroundColor: \"#1e1e1e\",\n foregroundColor: \"#d4d4d4\",\n classPrefix: \"silvery\",\n}\n\n// ============================================================================\n// Border Characters (same as terminal/canvas for consistency)\n// ============================================================================\n\nconst BORDER_CHARS: Record<string, BorderChars> = {\n single: {\n topLeft: \"┌\",\n topRight: \"┐\",\n bottomLeft: \"└\",\n bottomRight: \"┘\",\n horizontal: \"─\",\n vertical: \"│\",\n },\n double: {\n topLeft: \"╔\",\n topRight: \"╗\",\n bottomLeft: \"╚\",\n bottomRight: \"╝\",\n horizontal: \"═\",\n vertical: \"║\",\n },\n round: {\n topLeft: \"╭\",\n topRight: \"╮\",\n bottomLeft: \"╰\",\n bottomRight: \"╯\",\n horizontal: \"─\",\n vertical: \"│\",\n },\n bold: {\n topLeft: \"┏\",\n topRight: \"┓\",\n bottomLeft: \"┗\",\n bottomRight: \"┛\",\n horizontal: \"━\",\n vertical: \"┃\",\n },\n}\n\n// ============================================================================\n// Color Conversion\n// ============================================================================\n\nconst ANSI_COLORS: Record<string, string> = {\n black: \"#000000\",\n red: \"#cd0000\",\n green: \"#00cd00\",\n yellow: \"#cdcd00\",\n blue: \"#0000ee\",\n magenta: \"#cd00cd\",\n cyan: \"#00cdcd\",\n white: \"#e5e5e5\",\n gray: \"#7f7f7f\",\n grey: \"#7f7f7f\",\n brightblack: \"#7f7f7f\",\n brightred: \"#ff0000\",\n brightgreen: \"#00ff00\",\n brightyellow: \"#ffff00\",\n brightblue: \"#5c5cff\",\n brightmagenta: \"#ff00ff\",\n brightcyan: \"#00ffff\",\n brightwhite: \"#ffffff\",\n}\n\nfunction resolveColor(color: string | undefined, fallback: string): string {\n if (!color) return fallback\n if (color.startsWith(\"#\") || color.startsWith(\"rgb\")) return color\n const named = ANSI_COLORS[color.toLowerCase()]\n return named ?? color\n}\n\n// ============================================================================\n// DOM Measurer\n// ============================================================================\n\nfunction createDOMMeasurer(_config: Required<DOMAdapterConfig>): TextMeasurer {\n // The layout engine operates in cell units (columns x rows), matching the\n // terminal convention. For monospace fonts, text width = character count\n // and line height = 1 row.\n return {\n measureText(text: string, _style?: TextMeasureStyle): TextMeasureResult {\n // For monospace fonts, width is simply the character count (one cell per char)\n return {\n width: text.length,\n height: 1,\n }\n },\n\n getLineHeight(_style?: TextMeasureStyle): number {\n return 1\n },\n }\n}\n\n// ============================================================================\n// Styled Text Run\n// ============================================================================\n\ninterface TextRun {\n text: string\n style: RenderStyle\n x: number\n}\n\n// ============================================================================\n// DOM Render Buffer\n// ============================================================================\n\nexport class DOMRenderBuffer implements RenderBuffer {\n readonly width: number\n readonly height: number\n\n private config: Required<DOMAdapterConfig>\n private lines: Map<number, TextRun[]>\n private backgrounds: Map<string, { x: number; y: number; w: number; h: number; color: string }>\n\n // Cell-to-pixel conversion factors\n private readonly charWidth: number\n private readonly cellHeight: number\n\n // Container element (set when flushing)\n private container: HTMLElement | null = null\n\n constructor(width: number, height: number, config: Required<DOMAdapterConfig>) {\n this.width = width\n this.height = height\n this.config = config\n this.lines = new Map()\n this.backgrounds = new Map()\n\n // Compute cell dimensions for coordinate conversion.\n // Width/height are in cell units (cols/rows); rendering converts to pixels.\n this.charWidth = config.fontSize * 0.6\n this.cellHeight = config.fontSize * config.lineHeight\n }\n\n /**\n * Set the container element for rendering.\n */\n setContainer(container: HTMLElement): void {\n this.container = container\n }\n\n /**\n * Get the container element.\n */\n getContainer(): HTMLElement | null {\n return this.container\n }\n\n fillRect(x: number, y: number, width: number, height: number, style: RenderStyle): void {\n if (style.bg) {\n const key = `${x},${y},${width},${height}`\n this.backgrounds.set(key, {\n x,\n y,\n w: width,\n h: height,\n color: resolveColor(style.bg, this.config.backgroundColor),\n })\n }\n }\n\n drawText(x: number, y: number, text: string, style: RenderStyle): void {\n if (!this.lines.has(y)) {\n this.lines.set(y, [])\n }\n this.lines.get(y)!.push({ text, style, x })\n }\n\n drawChar(x: number, y: number, char: string, style: RenderStyle): void {\n this.drawText(x, y, char, style)\n }\n\n inBounds(x: number, y: number): boolean {\n return x >= 0 && x < this.width && y >= 0 && y < this.height\n }\n\n /**\n * Render the buffer to the container element.\n * Coordinates in the buffer are in cell units (cols/rows).\n * This method converts them to pixel coordinates for DOM positioning.\n */\n render(): void {\n if (!this.container) {\n throw new Error(\"DOMRenderBuffer: No container set. Call setContainer() first.\")\n }\n\n const container = this.container\n const cw = this.charWidth\n const ch = this.cellHeight\n\n // Container dimensions in pixels (convert cell units back to pixels)\n const containerWidthPx = this.width * cw\n const containerHeightPx = this.height * ch\n\n // Clear previous content\n container.innerHTML = \"\"\n\n // Set container styles\n container.style.cssText = `\n\t\t\tposition: relative;\n\t\t\tfont-family: ${this.config.fontFamily};\n\t\t\tfont-size: ${this.config.fontSize}px;\n\t\t\tline-height: ${this.config.lineHeight};\n\t\t\tbackground-color: ${this.config.backgroundColor};\n\t\t\tcolor: ${this.config.foregroundColor};\n\t\t\twhite-space: pre;\n\t\t\toverflow: hidden;\n\t\t\twidth: ${containerWidthPx}px;\n\t\t\theight: ${containerHeightPx}px;\n\t\t`\n\n // Render background rectangles (convert cell coords to pixels)\n for (const bg of this.backgrounds.values()) {\n const bgDiv = document.createElement(\"div\")\n bgDiv.className = `${this.config.classPrefix}-bg`\n bgDiv.style.cssText = `\n\t\t\t\tposition: absolute;\n\t\t\t\tleft: ${bg.x * cw}px;\n\t\t\t\ttop: ${bg.y * ch}px;\n\t\t\t\twidth: ${bg.w * cw}px;\n\t\t\t\theight: ${bg.h * ch}px;\n\t\t\t\tbackground-color: ${bg.color};\n\t\t\t`\n container.appendChild(bgDiv)\n }\n\n // Render text lines (convert cell coords to pixels)\n const sortedLines = Array.from(this.lines.entries()).sort((a, b) => a[0] - b[0])\n\n for (const [y, runs] of sortedLines) {\n const lineDiv = document.createElement(\"div\")\n lineDiv.className = `${this.config.classPrefix}-line`\n lineDiv.style.cssText = `\n\t\t\t\tposition: absolute;\n\t\t\t\tleft: 0;\n\t\t\t\ttop: ${y * ch}px;\n\t\t\t\theight: ${ch}px;\n\t\t\t\twhite-space: pre;\n\t\t\t`\n\n // Sort runs by x position\n const sortedRuns = runs.sort((a, b) => a.x - b.x)\n\n for (const run of sortedRuns) {\n const span = document.createElement(run.style.hyperlink === undefined ? \"span\" : \"a\")\n span.className = `${this.config.classPrefix}-text`\n span.textContent = run.text\n if (run.style.hyperlink !== undefined) span.setAttribute(\"href\", run.style.hyperlink)\n\n // Apply styles (convert cell x to pixel x)\n const styles: string[] = [`position: absolute`, `left: ${run.x * cw}px`]\n\n if (run.style.fg) {\n styles.push(`color: ${resolveColor(run.style.fg, this.config.foregroundColor)}`)\n }\n if (run.style.bg) {\n styles.push(\n `background-color: ${resolveColor(run.style.bg, this.config.backgroundColor)}`,\n )\n }\n\n const attrs = run.style.attrs\n if (attrs) {\n if (attrs.bold) styles.push(\"font-weight: bold\")\n if (attrs.dim) styles.push(\"opacity: 0.5\")\n if (attrs.italic) styles.push(\"font-style: italic\")\n\n // Underline handling\n if (attrs.underline || attrs.underlineStyle) {\n const underlineStyle = attrs.underlineStyle ?? \"single\"\n const underlineColor = attrs.underlineColor\n ? resolveColor(attrs.underlineColor, this.config.foregroundColor)\n : \"currentColor\"\n\n switch (underlineStyle) {\n case \"double\":\n styles.push(`text-decoration: underline double ${underlineColor}`)\n break\n case \"curly\":\n styles.push(`text-decoration: underline wavy ${underlineColor}`)\n break\n case \"dotted\":\n styles.push(`text-decoration: underline dotted ${underlineColor}`)\n break\n case \"dashed\":\n styles.push(`text-decoration: underline dashed ${underlineColor}`)\n break\n default:\n styles.push(`text-decoration: underline solid ${underlineColor}`)\n }\n }\n\n if (attrs.strikethrough) {\n const existing = styles.find((s) => s.startsWith(\"text-decoration:\"))\n if (existing) {\n const idx = styles.indexOf(existing)\n styles[idx] = existing.replace(\"underline\", \"underline line-through\")\n } else {\n styles.push(\"text-decoration: line-through\")\n }\n }\n\n if (attrs.inverse) {\n // Swap foreground/background\n const fg = run.style.fg\n ? resolveColor(run.style.fg, this.config.foregroundColor)\n : this.config.foregroundColor\n const bg = run.style.bg\n ? resolveColor(run.style.bg, this.config.backgroundColor)\n : this.config.backgroundColor\n styles.push(`color: ${bg}`, `background-color: ${fg}`)\n }\n }\n\n span.style.cssText = styles.join(\"; \")\n lineDiv.appendChild(span)\n }\n\n container.appendChild(lineDiv)\n }\n }\n\n /**\n * Clear the buffer.\n */\n clear(): void {\n this.lines.clear()\n this.backgrounds.clear()\n }\n}\n\n// ============================================================================\n// DOM Adapter Factory\n// ============================================================================\n\nexport function createDOMAdapter(config: DOMAdapterConfig = {}): RenderAdapter {\n const cfg = { ...DEFAULT_CONFIG, ...config }\n const measurer = createDOMMeasurer(cfg)\n\n return {\n name: \"dom\",\n measurer,\n\n createBuffer(width: number, height: number): RenderBuffer {\n return new DOMRenderBuffer(width, height, cfg)\n },\n\n flush(buffer: RenderBuffer, _prevBuffer: RenderBuffer | null): void {\n // DOM buffer renders directly when render() is called\n const domBuffer = buffer as DOMRenderBuffer\n if (domBuffer.getContainer()) {\n domBuffer.render()\n }\n },\n\n getBorderChars(style: string): BorderChars {\n return BORDER_CHARS[style] ?? BORDER_CHARS.single!\n },\n }\n}\n\n// ============================================================================\n// Inject Global Styles (Optional)\n// ============================================================================\n\nlet stylesInjected = false\n\n/**\n * Inject global CSS styles for silvery DOM rendering.\n * Call once at application startup if you want default styling.\n */\nexport function injectDOMStyles(classPrefix = \"silvery\"): void {\n if (stylesInjected || typeof document === \"undefined\") return\n\n const style = document.createElement(\"style\")\n style.textContent = `\n\t\t.${classPrefix}-container {\n\t\t\tfont-family: monospace;\n\t\t\twhite-space: pre;\n\t\t\toverflow: hidden;\n\t\t}\n\t\t.${classPrefix}-line {\n\t\t\twhite-space: pre;\n\t\t}\n\t\t.${classPrefix}-text {\n\t\t\twhite-space: pre;\n\t\t}\n\t\t/* Selection styling */\n\t\t.${classPrefix}-text::selection {\n\t\t\tbackground-color: rgba(100, 150, 255, 0.3);\n\t\t}\n\t`\n document.head.appendChild(style)\n stylesInjected = true\n}\n","/**\n * Caret style — terminal-target mapping from focus + editable state to a\n * `CursorShape` (DECSCUSR).\n *\n * Phase of `km-silvery.cursor-invariants` (invariant 6): the\n * framework-agnostic core (`@silvery/ag`) must not branch on terminal-only\n * concepts like block/underline/bar. The terminal layer owns the mapping\n * from \"this caret belongs to a focused editable\" → DECSCUSR shape.\n *\n * **Default mapping**:\n * - focused editable → `bar` (matches DECSCUSR bar/I-beam)\n * - non-focused / unknown → `block` (matches the historical default)\n *\n * Consumers override by passing an explicit `shape` field on the legacy\n * `BoxProps.cursorOffset.shape` slot (deprecated — see invariant 6) or, for\n * direct stores, in `CursorState.shape`.\n *\n * Cross-target renderers (canvas / DOM) must not depend on this — they read\n * focus state from the active cursor node directly and decide their own\n * caret rendering. This module is `@silvery/ag-term` only.\n */\n\nimport type { CursorShape } from \"./output\"\nimport type { AgNode, BoxProps } from \"@silvery/ag/types\"\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Resolve the terminal caret shape for the active cursor node.\n *\n * @param activeNode - The AgNode whose `cursorOffset` produced the active\n * `CursorRect`. Pass null when there is no active layout-output cursor\n * (e.g., the legacy `cursorStore` path is in use).\n * @param explicitShape - Shape explicitly set by the caller (the deprecated\n * `cursorOffset.shape` field or the legacy `CursorState.shape`). When\n * present, takes precedence over the focus-derived default.\n * @returns A `CursorShape` value the scheduler/output layer can pass to\n * `setCursorStyle(shape)` (DECSCUSR), or `null` to defer to the terminal\n * default via `resetCursorStyle()`.\n */\nexport function resolveCaretStyle(\n activeNode: AgNode | null,\n explicitShape?: CursorShape | undefined,\n): CursorShape | null {\n // Explicit shape (deprecated path) wins for one cycle of back-compat.\n if (explicitShape) return explicitShape\n\n if (!activeNode) {\n // Legacy cursorStore path or no caret at all — defer to terminal default.\n return null\n }\n\n const props = activeNode.props as BoxProps | undefined\n // Box is editable when it actually declares a cursorOffset. (We don't have\n // a separate \"editable\" prop; declaring cursor is the proxy.) When the\n // owning Box also has focus, present a bar/I-beam — the conventional\n // editing affordance across modern terminals.\n if (props?.cursorOffset && activeNode.interactiveState?.focused) {\n return \"bar\"\n }\n\n // Non-focused declarer: defer to terminal default. The legacy\n // historical behaviour of the silvery scheduler was to NOT emit\n // `setCursorStyle` when shape was undefined — equivalent to the terminal\n // default — so returning null here preserves that behaviour.\n return null\n}\n\n/**\n * Walk a tree and return the AgNode whose `cursorOffset` produced the active\n * caret rect (matching the precedence rules of `findActiveCursorRect`).\n *\n * Used by the scheduler to feed `resolveCaretStyle`. Re-walks the tree to\n * keep `findActiveCursorRect` returning a value-only (data) result rather\n * than coupling it to the AgNode reference.\n *\n * Returns null when no caret is active.\n */\nexport function findActiveCursorNode(root: AgNode): AgNode | null {\n let focusedNode: AgNode | null = null\n let focusedSuppressesFallback = false\n let fallbackNode: AgNode | null = null\n\n function walk(node: AgNode): void {\n for (const child of node.children) walk(child)\n const props = node.props as BoxProps | undefined\n if (!props?.cursorOffset) return\n const offset = props.cursorOffset\n if (props.focused === true || node.interactiveState?.focused) {\n focusedSuppressesFallback = true\n focusedNode = node\n return\n }\n if (offset.visible !== false) {\n fallbackNode = node\n }\n }\n\n walk(root)\n return focusedNode ?? (focusedSuppressesFallback ? null : fallbackNode)\n}\n","/**\n * OSC 133 Semantic Prompt Markers\n *\n * Shell integration protocol that marks prompts and commands in terminal output.\n * Terminals like iTerm2, Kitty, and WezTerm use these markers to provide\n * \"jump to previous/next prompt\" navigation (Cmd+Up/Cmd+Down in iTerm2).\n *\n * Protocol: OSC 133\n * - Prompt start: ESC ] 133 ; A BEL (before user input)\n * - Prompt end: ESC ] 133 ; B BEL (after user input, before command output)\n * - Command output start: ESC ] 133 ; C BEL (before command output)\n * - Command output end: ESC ] 133 ; D ; N BEL (after command output, N = exit code)\n *\n * For a chat-style app, each \"exchange\" (user prompt + assistant response) maps to:\n * - 133;A before the user's message\n * - 133;B after the user's message\n * - 133;C before the assistant's response\n * - 133;D;0 after the assistant's response\n *\n * Supported by: iTerm2, Kitty, WezTerm, foot, Ghostty\n */\n\nexport const OSC133 = {\n /** Mark prompt start (before user input) */\n promptStart: \"\\x1b]133;A\\x07\",\n /** Mark prompt end (after user input, before command output) */\n promptEnd: \"\\x1b]133;B\\x07\",\n /** Mark command output start */\n commandStart: \"\\x1b]133;C\\x07\",\n /** Mark command output end (exit code defaults to 0 = success) */\n commandEnd: (exitCode?: number) => `\\x1b]133;D;${exitCode ?? 0}\\x07`,\n} as const\n","/**\n * Kitty keyboard protocol detection.\n *\n * Sends CSI ? u and parses the response to determine whether the terminal\n * supports the Kitty keyboard protocol and which flags it reports.\n */\n\nimport type { ProbeInputOwner } from \"@silvery/ansi\"\nimport { queryKittyKeyboard } from \"./output\"\n\nexport interface KittyDetectResult {\n /** Whether the terminal responded to the Kitty protocol query */\n supported: boolean\n /** Bitfield of KittyFlags the terminal reported supporting (0 if unsupported) */\n flags: number\n /** Any non-response data that was read during detection (regular input that arrived) */\n buffered?: string\n}\n\n/** Regex to match a Kitty keyboard query response: CSI ? <flags> u */\nconst KITTY_RESPONSE_RE = /\\x1b\\[\\?(\\d+)u/\n\n/**\n * Detect Kitty keyboard protocol support.\n *\n * Sends CSI ? u to the terminal and waits for a response.\n * Supported terminals respond with CSI ? flags u.\n * Unsupported terminals either ignore the query or echo it.\n *\n * @param write Function to write to stdout\n * @param read Function to read a chunk from stdin (should resolve with data or null on timeout)\n * @param timeoutMs How long to wait for response (default: 200ms)\n */\nexport async function detectKittySupport(\n write: (data: string) => void,\n read: (timeoutMs: number) => Promise<string | null>,\n timeoutMs = 200,\n): Promise<KittyDetectResult> {\n write(queryKittyKeyboard())\n\n const data = await read(timeoutMs)\n if (data == null) {\n return { supported: false, flags: 0 }\n }\n\n const parsed = parseKittyResponse(data)\n if (!parsed) {\n return { supported: false, flags: 0, buffered: data }\n }\n\n // Anything outside the matched response is buffered input\n const before = data.slice(0, parsed.start)\n const after = data.slice(parsed.end)\n const buffered = before + after\n return { ...parsed.result, buffered: buffered || undefined }\n}\n\n/** Query Kitty support through the session's canonical stdin owner. */\nexport async function detectKittyWithProbe(\n input: ProbeInputOwner,\n timeoutMs = 200,\n): Promise<KittyDetectResult> {\n return (\n (await input.probe({\n query: queryKittyKeyboard(),\n timeoutMs,\n parse: (acc) => {\n const parsed = parseKittyResponse(acc)\n return parsed ? { result: parsed.result, consumed: parsed.end } : null\n },\n })) ?? { supported: false, flags: 0 }\n )\n}\n\nfunction parseKittyResponse(\n data: string,\n): { result: KittyDetectResult; start: number; end: number } | null {\n const match = KITTY_RESPONSE_RE.exec(data)\n if (!match) return null\n return {\n result: { supported: true, flags: parseInt(match[1]!, 10) },\n start: match.index,\n end: match.index + match[0].length,\n }\n}\n\n/**\n * Detect Kitty support using real stdin/stdout.\n * Convenience wrapper around detectKittySupport.\n */\nexport async function detectKittyFromStdio(\n stdout: { write: (s: string) => boolean | void },\n stdin: NodeJS.ReadStream,\n timeoutMs = 200,\n): Promise<KittyDetectResult> {\n // Race-safe rawMode toggle — see probeColors comment in\n // vendor/silvery/packages/ansi/src/theme/detect.ts. If another consumer\n // (e.g. silvery's term-provider) is on stdin, leave raw mode alone.\n const otherListeners = stdin.listenerCount(\"data\") > 0\n const wasRaw = stdin.isRaw\n let didSetRaw = false\n if (!wasRaw && !otherListeners) {\n stdin.setRawMode(true)\n didSetRaw = true\n }\n\n try {\n const write = (s: string) => {\n stdout.write(s)\n }\n\n const read = (ms: number): Promise<string | null> =>\n new Promise((resolve) => {\n const timer = setTimeout(() => {\n stdin.removeListener(\"data\", onData)\n resolve(null)\n }, ms)\n\n function onData(chunk: Buffer) {\n clearTimeout(timer)\n stdin.removeListener(\"data\", onData)\n resolve(chunk.toString())\n }\n\n stdin.on(\"data\", onData)\n })\n\n return await detectKittySupport(write, read, timeoutMs)\n } finally {\n if (didSetRaw) stdin.setRawMode(false)\n }\n}\n","/**\n * Terminal Capability Test\n *\n * Renders labeled test patterns for each terminal feature.\n * Run in any terminal to visually verify what it supports.\n *\n * Usage:\n * import { runTermtest } from \"@silvery/ag-react\"\n * runTermtest() // all sections\n * runTermtest({ sections: [\"emoji\", \"colors\"] }) // specific sections\n *\n * Compare output across terminals to build/verify profiles.\n */\n\nimport { createTerminalProfile } from \"@silvery/ansi\"\n\nconst ESC = \"\\x1b\"\nconst CSI = `${ESC}[`\nconst RESET = `${CSI}0m`\n\nfunction sgr(...codes: (string | number)[]): string {\n return `${CSI}${codes.join(\";\")}m`\n}\n\nfunction sectionHeader(title: string): string {\n return `\\n${sgr(1)}═══ ${title} ═══${RESET}\\n`\n}\n\nfunction row(label: string, content: string): string {\n return ` ${label.padEnd(24)} ${content}${RESET}`\n}\n\n/** Available test sections */\nexport const TERMTEST_SECTIONS = [\n \"sgr\",\n \"underline\",\n \"colors\",\n \"256\",\n \"truecolor\",\n \"unicode\",\n \"emoji\",\n \"borders\",\n \"inverse\",\n \"profile\",\n] as const\n\nexport type TermtestSection = (typeof TERMTEST_SECTIONS)[number]\n\nexport interface TermtestOptions {\n /** Writable stream (defaults to process.stdout) */\n output?: { write(s: string): boolean }\n /** Show only these sections. Omit or empty = all sections. */\n sections?: TermtestSection[]\n}\n\n/**\n * Run the terminal capability test.\n * Pass section names to filter: `runTermtest({ sections: [\"emoji\"] })`\n */\nexport function runTermtest(options?: TermtestOptions): void {\n const w = options?.output ?? process.stdout\n const filter = options?.sections\n const show = (s: TermtestSection) => !filter || filter.length === 0 || filter.includes(s)\n\n // Post km-silvery.plateau-delete-legacy-shims (H6): read caps through the\n // canonical profile factory. termtest has no Term in scope (it's a CLI\n // one-shot), so the zero-arg variant auto-detects from process.env /\n // process.stdout — same behavior the deleted `detectTerminalCaps()` shim\n // provided.\n // Post km-silvery.plateau-naming-polish: 2-layer profile (caps + emulator);\n // heuristic fields live on caps with a `maybe` prefix.\n const { caps, emulator } = createTerminalProfile()\n\n w.write(`\\n${sgr(1)}Terminal Capability Test${RESET}\\n`)\n w.write(` Program: ${emulator.program || \"(unknown)\"}\\n`)\n w.write(` TERM: ${emulator.TERM || \"(unknown)\"}\\n`)\n w.write(` COLORTERM: ${process.env.COLORTERM || \"(unset)\"}\\n`)\n w.write(\n ` Detected: color=${caps.colorLevel} maybe-dark=${caps.maybeDarkBackground} maybe-nerdfont=${caps.maybeNerdFont}\\n`,\n )\n w.write(` Underline: styles=[${caps.underlineStyles.join(\", \")}] color=${caps.underlineColor}\\n`)\n w.write(` Maybe wide emojis: ${caps.maybeWideEmojis}\\n`)\n\n if (show(\"sgr\")) {\n w.write(sectionHeader(\"SGR Text Attributes\"))\n w.write(row(\"Bold\", `${sgr(1)}The quick brown fox${RESET}`) + \"\\n\")\n w.write(row(\"Dim\", `${sgr(2)}The quick brown fox${RESET}`) + \"\\n\")\n w.write(row(\"Italic\", `${sgr(3)}The quick brown fox${RESET}`) + \"\\n\")\n w.write(row(\"Underline\", `${sgr(4)}The quick brown fox${RESET}`) + \"\\n\")\n w.write(row(\"Strikethrough\", `${sgr(9)}The quick brown fox${RESET}`) + \"\\n\")\n w.write(row(\"Inverse\", `${sgr(7)}The quick brown fox${RESET}`) + \"\\n\")\n w.write(row(\"Blink\", `${sgr(5)}The quick brown fox${RESET}`) + \"\\n\")\n w.write(row(\"Bold+Italic\", `${sgr(1, 3)}The quick brown fox${RESET}`) + \"\\n\")\n }\n\n if (show(\"underline\")) {\n w.write(sectionHeader(\"SGR 4:x Underline Styles (Terminal.app BREAKS here)\"))\n w.write(row(\"4:1 Single\", `${CSI}4:1mThe quick brown fox${RESET}`) + \"\\n\")\n w.write(row(\"4:2 Double\", `${CSI}4:2mThe quick brown fox${RESET}`) + \"\\n\")\n w.write(row(\"4:3 Curly\", `${CSI}4:3mThe quick brown fox${RESET}`) + \"\\n\")\n w.write(row(\"4:4 Dotted\", `${CSI}4:4mThe quick brown fox${RESET}`) + \"\\n\")\n w.write(row(\"4:5 Dashed\", `${CSI}4:5mThe quick brown fox${RESET}`) + \"\\n\")\n w.write(\n row(\n \"After 4:x (clean?)\",\n `${CSI}4:3m${RESET}This text should be normal — if garbled, 4:x corrupted SGR state`,\n ) + \"\\n\",\n )\n\n w.write(sectionHeader(\"SGR 58 Underline Color (Terminal.app BREAKS here)\"))\n w.write(row(\"58;5;1 Red UL\", `${sgr(4)}${CSI}58;5;1mThe quick brown fox${RESET}`) + \"\\n\")\n w.write(row(\"58;5;2 Green UL\", `${sgr(4)}${CSI}58;5;2mThe quick brown fox${RESET}`) + \"\\n\")\n w.write(row(\"58;5;4 Blue UL\", `${sgr(4)}${CSI}58;5;4mThe quick brown fox${RESET}`) + \"\\n\")\n w.write(\n row(\"58;2;R;G;B TC UL\", `${sgr(4)}${CSI}58;2;255;128;0mThe quick brown fox${RESET}`) + \"\\n\",\n )\n w.write(\n row(\n \"After SGR 58 (clean?)\",\n `${sgr(4)}${CSI}58;5;1m${RESET}This text should be normal — if garbled, 58 corrupted SGR state`,\n ) + \"\\n\",\n )\n }\n\n if (show(\"colors\")) {\n w.write(sectionHeader(\"ANSI 16 Colors\"))\n const colorNames = [\"Black\", \"Red\", \"Green\", \"Yellow\", \"Blue\", \"Magenta\", \"Cyan\", \"White\"]\n let fgLine = \" FG: \"\n for (let i = 0; i < 8; i++) fgLine += `${sgr(30 + i)} ${colorNames[i]}${RESET}`\n w.write(fgLine + \"\\n\")\n let fgBrLine = \" Br: \"\n for (let i = 0; i < 8; i++) fgBrLine += `${sgr(90 + i)} ${colorNames[i]}${RESET}`\n w.write(fgBrLine + \"\\n\")\n let bgLine = \" BG: \"\n for (let i = 0; i < 8; i++) bgLine += `${sgr(40 + i)} ${colorNames[i]} ${RESET}`\n w.write(bgLine + \"\\n\")\n let bgBrLine = \" BrBG:\"\n for (let i = 0; i < 8; i++) bgBrLine += `${sgr(100 + i)} ${colorNames[i]} ${RESET}`\n w.write(bgBrLine + \"\\n\")\n }\n\n if (show(\"256\")) {\n w.write(sectionHeader(\"256 Colors (indices 0-15, 16-231, 232-255)\"))\n let stdLine = \" 0-15: \"\n for (let i = 0; i < 16; i++) stdLine += `${CSI}48;5;${i}m ${RESET}`\n w.write(stdLine + \"\\n\")\n let cubeLine = \" Cube: \"\n for (let i = 16; i < 52; i++) cubeLine += `${CSI}48;5;${i}m ${RESET}`\n w.write(cubeLine + \"\\n\")\n let grayLine = \" Gray: \"\n for (let i = 232; i < 256; i++) grayLine += `${CSI}48;5;${i}m ${RESET}`\n w.write(grayLine + \"\\n\")\n }\n\n if (show(\"truecolor\")) {\n w.write(sectionHeader(\"Truecolor (38;2;R;G;B / 48;2;R;G;B)\"))\n let tcLine = \" Gradient: \"\n for (let i = 0; i < 40; i++) {\n const r = Math.round((i / 39) * 255)\n const g = Math.round(((39 - i) / 39) * 128)\n tcLine += `${CSI}48;2;${r};${g};80m ${RESET}`\n }\n w.write(tcLine + \"\\n\")\n w.write(row(\"If solid blocks →\", \"Truecolor NOT supported (256-color fallback)\") + \"\\n\")\n }\n\n if (show(\"unicode\")) {\n w.write(sectionHeader(\"Unicode, Emoji, PUA (Nerd Fonts)\"))\n w.write(row(\"ASCII\", \"Hello World! 0123456789\") + \"\\n\")\n w.write(row(\"Latin Extended\", \"àéîõü ñ ß ø å\") + \"\\n\")\n w.write(row(\"CJK\", \"你好世界 日本語 한국어\") + \"\\n\")\n w.write(row(\"Box Drawing\", \"┌─┬─┐ ╔═╦═╗ ╭─╮\") + \"\\n\")\n w.write(row(\"Block Elements\", \"▀▄█▌▐░▒▓\") + \"\\n\")\n w.write(row(\"Symbols\", \"● ○ ◉ ▶ ◀ ⚠ ✓ ✗ ⋮ §\") + \"\\n\")\n w.write(row(\"Emoji\", \"🎉 🚀 📁 📄 ⭐ 🔥 👍\") + \"\\n\")\n w.write(row(\"Nerd Font PUA\", \"\\uF114 folder \\uF0F6 file \\uE0B0 arrow \\uF013 gear\") + \"\\n\")\n w.write(row(\"If PUA = boxes →\", \"Nerd Fonts not installed\") + \"\\n\")\n }\n\n if (show(\"emoji\")) {\n // Each test line places a character then fills to exactly 10 visible columns.\n // If the _'s don't align with the ruler, the terminal's character width\n // disagrees with the expected width (shown in parentheses).\n w.write(sectionHeader(\"Emoji Width Alignment (_'s should align with ruler)\"))\n w.write(\" Ruler: |1234567890|\\n\")\n w.write(\" ASCII 'A': |A_________| (w=1)\\n\")\n w.write(\" CJK '你': |你________| (w=2)\\n\")\n w.write(\" Flag 🇨🇦: |🇨🇦________| (w=2)\\n\")\n w.write(\" Flag 🇺🇸: |🇺🇸________| (w=2)\\n\")\n w.write(\" Emoji 📁: |📁________| (w=2)\\n\")\n w.write(\" Emoji 📄: |📄________| (w=2)\\n\")\n w.write(\" Emoji 📋: |📋________| (w=2)\\n\")\n w.write(\" Emoji ⚠️: |⚠️________| (w=2)\\n\")\n w.write(\" Text ⚠: |⚠_________| (w=1 text, 2 emoji)\\n\")\n w.write(\" Text ⭐: |⭐________| (w=1 text, 2 emoji)\\n\")\n w.write(\" Text ☑: |☑_________| (w=1 text, 2 emoji)\\n\")\n w.write(\" Emoji 🏠: |🏠________| (w=2)\\n\")\n w.write(\" Emoji 👓: |👓________| (w=2)\\n\")\n w.write(\" ZWJ 👨🏻‍💻: |👨🏻‍💻________| (w=2)\\n\")\n w.write(\" Arrow →: |→_________| (w=1)\\n\")\n w.write(\" Arrow ▸: |▸_________| (w=1)\\n\")\n w.write(\" Circle ○: |○_________| (w=1)\\n\")\n w.write(\" Square □: |□_________| (w=1)\\n\")\n w.write(\" Check ✓: |✓_________| (w=1)\\n\")\n }\n\n if (show(\"borders\")) {\n w.write(sectionHeader(\"Box Drawing Borders\"))\n w.write(\" ┌──────────┐ ╔══════════╗ ╭──────────╮\\n\")\n w.write(\" │ single │ ║ double ║ │ round │\\n\")\n w.write(\" └──────────┘ ╚══════════╝ ╰──────────╯\\n\")\n }\n\n if (show(\"inverse\")) {\n w.write(sectionHeader(\"Inverse + Background (potential artifact source)\"))\n w.write(row(\"Red FG + Inverse\", `${sgr(31, 7)}This should have red background${RESET}`) + \"\\n\")\n w.write(row(\"Cyan BG + White FG\", `${sgr(46, 37)}Cyan background, white text${RESET}`) + \"\\n\")\n w.write(row(\"Black BG + White FG\", `${sgr(40, 97)}Black bg, bright white text${RESET}`) + \"\\n\")\n w.write(row(\"White BG + Black FG\", `${sgr(107, 30)}White bg, black text${RESET}`) + \"\\n\")\n\n w.write(sectionHeader(\"Reset Sanity Check\"))\n w.write(\" This line should be completely normal with no formatting artifacts.\\n\")\n w.write(\" If you see colors, underlines, or other styling above, the terminal\\n\")\n w.write(\" failed to process an SGR reset (\\\\x1b[0m) correctly.\\n\")\n }\n\n if (show(\"profile\")) {\n w.write(sectionHeader(\"Detected Terminal Profile\"))\n const entries = Object.entries(caps) as [string, unknown][]\n for (const [key, value] of entries) {\n const indicator = value === true ? \"✓\" : value === false ? \"✗\" : String(value)\n w.write(` ${key.padEnd(20)} ${indicator}\\n`)\n }\n }\n\n w.write(\"\\n\")\n}\n","/**\n * CSI 6n Cursor Position Query\n *\n * Queries the terminal for the current cursor position using the standard\n * Device Status Report (DSR) mechanism.\n *\n * Protocol:\n * - Query: CSI 6 n (\\x1b[6n)\n * - Response: CSI {row} ; {col} R (\\x1b[{row};{col}R)\n *\n * Row and column are 1-indexed in the protocol response.\n *\n * Supported by: virtually all terminals (VT100+)\n */\n\n/** Regex to match a CPR response: CSI row ; col R */\nconst CPR_RESPONSE_RE = /\\x1b\\[(\\d+);(\\d+)R/\n\n/**\n * Query the terminal cursor position.\n *\n * Sends CSI 6n and parses the CPR response.\n * Returns 1-indexed row and column.\n *\n * @param write Function to write to stdout\n * @param read Function to read a chunk from stdin (resolves with data or null on timeout)\n * @param timeoutMs How long to wait for response (default: 200ms)\n */\nexport async function queryCursorPosition(\n write: (data: string) => void,\n read: (timeoutMs: number) => Promise<string | null>,\n timeoutMs = 200,\n): Promise<{ row: number; col: number } | null> {\n write(\"\\x1b[6n\")\n\n const data = await read(timeoutMs)\n if (data == null) return null\n\n const match = CPR_RESPONSE_RE.exec(data)\n if (!match) return null\n\n return {\n row: parseInt(match[1]!, 10),\n col: parseInt(match[2]!, 10),\n }\n}\n\n/**\n * Query cursor position using real stdin/stdout.\n * Convenience wrapper around queryCursorPosition.\n */\nexport async function queryCursorFromStdio(\n stdout: { write: (s: string) => boolean | void },\n stdin: NodeJS.ReadStream,\n timeoutMs = 200,\n): Promise<{ row: number; col: number } | null> {\n // Only flip raw mode if no other consumer is on stdin — see probeColors\n // (vendor/silvery/packages/ansi/src/theme/detect.ts) for the same fix:\n // re-setting raw=false in the finally based on a stale `wasRaw` capture\n // kills the host TUI's input.\n const otherListeners = stdin.listenerCount(\"data\") > 0\n const wasRaw = stdin.isRaw\n let didSetRaw = false\n if (!wasRaw && !otherListeners) {\n stdin.setRawMode(true)\n didSetRaw = true\n }\n\n try {\n const write = (s: string) => {\n stdout.write(s)\n }\n\n const read = (ms: number): Promise<string | null> =>\n new Promise((resolve) => {\n const timer = setTimeout(() => {\n stdin.removeListener(\"data\", onData)\n resolve(null)\n }, ms)\n\n function onData(chunk: Buffer) {\n clearTimeout(timer)\n stdin.removeListener(\"data\", onData)\n resolve(chunk.toString())\n }\n\n stdin.on(\"data\", onData)\n })\n\n return await queryCursorPosition(write, read, timeoutMs)\n } finally {\n if (didSetRaw) stdin.setRawMode(false)\n }\n}\n","/**\n * DEC Width Mode Detection (xterm patch #407)\n *\n * Queries the terminal for its character width settings using DECRQM\n * (DEC Private Mode Request). This replaces guesswork with definitive\n * answers from the terminal itself.\n *\n * Modes:\n * - 1020: UTF-8 mode\n * - 1021: CJK ambiguous width (1 or 2 cells)\n * - 1022: Emoji width (1 or 2 cells)\n * - 1023: Private-use area width (1 or 2 cells)\n *\n * Protocol:\n * - Query: CSI ? {mode} $ p (DECRQM)\n * - Response: CSI ? {mode} ; {Ps} $ y (DECRPM)\n *\n * Where Ps is:\n * 1 = set (enabled / wide / 2-cell)\n * 2 = reset (disabled / narrow / 1-cell)\n * 0 = not recognized\n * 3 = permanently set\n * 4 = permanently reset\n *\n * @see https://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n */\n\nimport type { ProbeInputOwner } from \"@silvery/ansi\"\n\n/** Well-known xterm width DEC mode numbers. */\nexport const WidthMode = {\n /** UTF-8 mode */\n UTF8: 1020,\n /** CJK ambiguous character width */\n CJK_WIDTH: 1021,\n /** Emoji width */\n EMOJI_WIDTH: 1022,\n /** Private-use area width */\n PRIVATE_USE_WIDTH: 1023,\n} as const\n\n/** Terminal-reported character width configuration. */\nexport interface TerminalWidthConfig {\n /** Whether terminal uses UTF-8 mode */\n utf8: boolean\n /** How terminal handles CJK ambiguous width (1 or 2) */\n cjkWidth: 1 | 2\n /** How terminal handles emoji width (1 or 2) */\n emojiWidth: 1 | 2\n /** How terminal handles private-use area width (1 or 2) */\n privateUseWidth: 1 | 2\n}\n\n/** Width detector with async detect() and cleanup. */\nexport interface WidthDetector {\n /** Detected configuration (null until detection completes) */\n readonly config: TerminalWidthConfig | null\n /** Query terminal for width settings */\n detect(): Promise<TerminalWidthConfig>\n /** Clean up resources */\n dispose(): void\n}\n\n/** Options for creating a width detector. */\nexport interface WidthDetectorOptions {\n /** Write data to the terminal */\n write: (data: string) => void\n /** Subscribe to terminal input data; returns unsubscribe function */\n onData: (handler: (data: string) => void) => () => void\n /** Per-mode timeout in milliseconds (default: 200) */\n timeoutMs?: number\n}\n\n/** Default configuration when detection fails or times out. */\nexport const DEFAULT_WIDTH_CONFIG: TerminalWidthConfig = {\n utf8: true,\n cjkWidth: 1,\n emojiWidth: 2,\n privateUseWidth: 1,\n}\n\n/** Regex for DECRPM response: CSI ? mode ; Ps $ y */\nconst DECRPM_RE = /\\x1b\\[\\?(\\d+);(\\d+)\\$y/g\n\n/**\n * Parse a DECRPM response value into a boolean (set/reset).\n * 1 and 3 (permanently set) = true, everything else = false.\n */\nfunction isSet(ps: number): boolean {\n return ps === 1 || ps === 3\n}\n\n/**\n * Query a single DEC width mode via DECRQM.\n * Returns the Ps value from the DECRPM response, or null on timeout.\n */\nfunction queryWidthMode(\n write: (data: string) => void,\n onData: (handler: (data: string) => void) => () => void,\n mode: number,\n timeoutMs: number,\n): Promise<number | null> {\n return new Promise<number | null>((resolve) => {\n let timer: ReturnType<typeof setTimeout> | null = null\n let unsubscribe: (() => void) | null = null\n let buffer = \"\"\n\n function cleanup() {\n if (timer !== null) {\n clearTimeout(timer)\n timer = null\n }\n if (unsubscribe !== null) {\n unsubscribe()\n unsubscribe = null\n }\n }\n\n unsubscribe = onData((data: string) => {\n buffer += data\n // Try to find DECRPM response for our mode\n DECRPM_RE.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = DECRPM_RE.exec(buffer)) !== null) {\n const reportedMode = parseInt(match[1]!, 10)\n if (reportedMode === mode) {\n const ps = parseInt(match[2]!, 10)\n cleanup()\n resolve(ps)\n return\n }\n }\n })\n\n timer = setTimeout(() => {\n cleanup()\n resolve(null)\n }, timeoutMs)\n\n // Send DECRQM query: CSI ? {mode} $ p\n write(`\\x1b[?${mode}$p`)\n })\n}\n\n/**\n * Query width modes through the session's canonical stdin owner.\n *\n * Unlike `createWidthDetector`'s standalone transport adapter, this path\n * never attaches another `data` listener: responses are consumed by the\n * owner's probe lane before normal key/mouse parsing.\n */\nexport function detectWidthConfigWithProbe(\n input: ProbeInputOwner,\n timeoutMs = 200,\n): Promise<TerminalWidthConfig> {\n return detectWidthConfig((mode) =>\n input.probe({\n query: `\\x1b[?${mode}$p`,\n timeoutMs,\n parse: (acc) => parseWidthModeResponse(acc, mode),\n }),\n )\n}\n\nfunction parseWidthModeResponse(\n acc: string,\n expectedMode: number,\n): { result: number; consumed: number } | null {\n DECRPM_RE.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = DECRPM_RE.exec(acc)) !== null) {\n if (parseInt(match[1]!, 10) !== expectedMode) continue\n return {\n result: parseInt(match[2]!, 10),\n consumed: match.index + match[0].length,\n }\n }\n return null\n}\n\nasync function detectWidthConfig(\n query: (mode: number) => Promise<number | null>,\n): Promise<TerminalWidthConfig> {\n const utf8Ps = await query(WidthMode.UTF8)\n const cjkPs = await query(WidthMode.CJK_WIDTH)\n const emojiPs = await query(WidthMode.EMOJI_WIDTH)\n const puaPs = await query(WidthMode.PRIVATE_USE_WIDTH)\n\n return {\n utf8: utf8Ps !== null ? isSet(utf8Ps) : DEFAULT_WIDTH_CONFIG.utf8,\n cjkWidth: cjkPs !== null ? (isSet(cjkPs) ? 2 : 1) : DEFAULT_WIDTH_CONFIG.cjkWidth,\n emojiWidth: emojiPs !== null ? (isSet(emojiPs) ? 2 : 1) : DEFAULT_WIDTH_CONFIG.emojiWidth,\n privateUseWidth: puaPs !== null ? (isSet(puaPs) ? 2 : 1) : DEFAULT_WIDTH_CONFIG.privateUseWidth,\n }\n}\n\n/**\n * Apply detected width configuration to terminal capabilities.\n *\n * Post km-silvery.plateau-naming-polish (2026-04-23): `maybeWideEmojis` and\n * `textSizing` both live on caps, so the overlay returns a single updated\n * caps object. The mapping is:\n * - emojiWidth=2 → caps.maybeWideEmojis=true\n * - privateUseWidth=2 → caps.textSizing=true (PUA treated as 2-wide)\n *\n * CJK width and UTF-8 mode are informational — they don't yet map to caps\n * fields but are available in TerminalWidthConfig for consumers.\n */\nexport function applyWidthConfig<C extends { textSizing: boolean; maybeWideEmojis: boolean }>(\n caps: C,\n config: TerminalWidthConfig,\n): C {\n return {\n ...caps,\n textSizing: config.privateUseWidth === 2,\n maybeWideEmojis: config.emojiWidth === 2,\n }\n}\n\n/**\n * Create a width detector that queries the terminal for DEC modes 1020-1023.\n *\n * @example\n * ```ts\n * const detector = createWidthDetector({\n * write: (data) => process.stdout.write(data),\n * onData: (handler) => {\n * process.stdin.on('data', (chunk) => handler(chunk.toString()))\n * return () => process.stdin.removeListener('data', handler)\n * },\n * })\n *\n * const config = await detector.detect()\n * console.log(config.emojiWidth) // 1 or 2\n * detector.dispose()\n * ```\n */\nexport function createWidthDetector(options: WidthDetectorOptions): WidthDetector {\n const { write, onData, timeoutMs = 200 } = options\n let config: TerminalWidthConfig | null = null\n let disposed = false\n\n return {\n get config() {\n return config\n },\n\n async detect(): Promise<TerminalWidthConfig> {\n if (disposed) return config ?? { ...DEFAULT_WIDTH_CONFIG }\n if (config !== null) return config\n\n // Query all 4 modes sequentially (each waits for its response).\n config = await detectWidthConfig((mode) => queryWidthMode(write, onData, mode, timeoutMs))\n\n return config\n },\n\n dispose() {\n disposed = true\n },\n }\n}\n","/**\n * Device Attributes (DA1/DA2/DA3) + XTVERSION Queries\n *\n * Provides functions to query terminal identity and capabilities using\n * the standard VT device attribute escape sequences.\n *\n * Protocols:\n *\n * DA1 (Primary Device Attributes):\n * Query: CSI c (\\x1b[c)\n * Response: CSI ? Ps ; Ps ; ... c\n *\n * DA2 (Secondary Device Attributes):\n * Query: CSI > c (\\x1b[>c)\n * Response: CSI > Pt ; Pv ; Pc c\n * Where Pt=terminal type, Pv=firmware version, Pc=ROM cartridge id\n *\n * DA3 (Tertiary Device Attributes):\n * Query: CSI = c (\\x1b[=c)\n * Response: DCS ! | hex-encoded-id ST (\\x1bP!|{hex}\\x1b\\\\)\n *\n * XTVERSION (Terminal Name + Version):\n * Query: CSI > 0 q (\\x1b[>0q)\n * Response: DCS > | name(version) ST (\\x1bP>|{text}\\x1b\\\\)\n *\n * Supported by: xterm, Ghostty, Kitty, WezTerm, foot, VTE-based terminals\n */\n\n/** Regex for DA1 response: CSI ? params c */\nconst DA1_RESPONSE_RE = /\\x1b\\[\\?([\\d;]+)c/\n\n/** Regex for DA2 response: CSI > params c */\nconst DA2_RESPONSE_RE = /\\x1b\\[>([\\d;]+)c/\n\n/** Regex for DA3 response: DCS ! | hex ST */\nconst DA3_RESPONSE_RE = /\\x1bP!\\|([0-9a-fA-F]*)\\x1b\\\\/\n\n/** Regex for XTVERSION response: DCS > | text ST */\nconst XTVERSION_RESPONSE_RE = /\\x1bP>\\|([^\\x1b]*)\\x1b\\\\/\n\n// ============================================================================\n// DA1 — Primary Device Attributes\n// ============================================================================\n\n/**\n * Query primary device attributes (DA1).\n *\n * Returns the list of attribute parameters the terminal reports.\n * Common params: 1=132-cols, 4=sixel, 6=selective-erase, 22=ANSI-color\n *\n * @param write Function to write to stdout\n * @param read Function to read a chunk from stdin\n * @param timeoutMs How long to wait for response (default: 200ms)\n */\nexport async function queryPrimaryDA(\n write: (data: string) => void,\n read: (timeoutMs: number) => Promise<string | null>,\n timeoutMs = 200,\n): Promise<{ params: number[] } | null> {\n write(\"\\x1b[c\")\n\n const data = await read(timeoutMs)\n if (data == null) return null\n\n const match = DA1_RESPONSE_RE.exec(data)\n if (!match) return null\n\n const params = match[1]!.split(\";\").map((s) => parseInt(s, 10))\n return { params }\n}\n\n// ============================================================================\n// DA2 — Secondary Device Attributes\n// ============================================================================\n\n/**\n * Query secondary device attributes (DA2).\n *\n * Returns terminal type, firmware version, and ROM cartridge id.\n * Common type values: 0=VT100, 1=VT220, 41=xterm, 65=VT500\n *\n * @param write Function to write to stdout\n * @param read Function to read a chunk from stdin\n * @param timeoutMs How long to wait for response (default: 200ms)\n */\nexport async function querySecondaryDA(\n write: (data: string) => void,\n read: (timeoutMs: number) => Promise<string | null>,\n timeoutMs = 200,\n): Promise<{ type: number; version: number; id: number } | null> {\n write(\"\\x1b[>c\")\n\n const data = await read(timeoutMs)\n if (data == null) return null\n\n const match = DA2_RESPONSE_RE.exec(data)\n if (!match) return null\n\n const parts = match[1]!.split(\";\")\n if (parts.length < 3) return null\n\n return {\n type: parseInt(parts[0]!, 10),\n version: parseInt(parts[1]!, 10),\n id: parseInt(parts[2]!, 10),\n }\n}\n\n// ============================================================================\n// DA3 — Tertiary Device Attributes\n// ============================================================================\n\n/**\n * Query tertiary device attributes (DA3).\n *\n * Returns a hex-encoded unit ID string. Decode with Buffer.from(hex, 'hex').\n *\n * @param write Function to write to stdout\n * @param read Function to read a chunk from stdin\n * @param timeoutMs How long to wait for response (default: 200ms)\n */\nexport async function queryTertiaryDA(\n write: (data: string) => void,\n read: (timeoutMs: number) => Promise<string | null>,\n timeoutMs = 200,\n): Promise<string | null> {\n write(\"\\x1b[=c\")\n\n const data = await read(timeoutMs)\n if (data == null) return null\n\n const match = DA3_RESPONSE_RE.exec(data)\n if (!match) return null\n\n return match[1]!\n}\n\n// ============================================================================\n// XTVERSION — Terminal Name + Version\n// ============================================================================\n\n/**\n * Query the terminal name and version via XTVERSION.\n *\n * Returns the version string as reported by the terminal, e.g.:\n * - \"xterm(388)\"\n * - \"tmux 3.4\"\n * - \"WezTerm 20230712-072601-f4abf8fd\"\n *\n * @param write Function to write to stdout\n * @param read Function to read a chunk from stdin\n * @param timeoutMs How long to wait for response (default: 200ms)\n */\nexport async function queryTerminalVersion(\n write: (data: string) => void,\n read: (timeoutMs: number) => Promise<string | null>,\n timeoutMs = 200,\n): Promise<string | null> {\n write(\"\\x1b[>0q\")\n\n const data = await read(timeoutMs)\n if (data == null) return null\n\n const match = XTVERSION_RESPONSE_RE.exec(data)\n if (!match) return null\n\n return match[1]!\n}\n\n// ============================================================================\n// Combined Query\n// ============================================================================\n\n/** Combined device attributes result. */\nexport interface DeviceAttributes {\n da1: { params: number[] } | null\n da2: { type: number; version: number; id: number } | null\n version: string | null\n}\n\n/**\n * Query all device attributes: DA1, DA2, and XTVERSION.\n *\n * Convenience wrapper that queries all three sequentially.\n * DA3 is omitted from the combined query as it's rarely needed.\n *\n * @param stdout Writable stream (e.g., process.stdout)\n * @param stdin Readable stream (e.g., process.stdin)\n * @param timeoutMs Per-query timeout (default: 200ms)\n */\nexport async function queryDeviceAttributes(\n stdout: { write: (s: string) => boolean | void },\n stdin: NodeJS.ReadStream,\n timeoutMs = 200,\n): Promise<DeviceAttributes> {\n // Race-safe rawMode toggle — see probeColors comment in\n // vendor/silvery/packages/ansi/src/theme/detect.ts. If another consumer\n // (e.g. silvery's term-provider) is on stdin, leave raw mode alone.\n const otherListeners = stdin.listenerCount(\"data\") > 0\n const wasRaw = stdin.isRaw\n let didSetRaw = false\n if (!wasRaw && !otherListeners) {\n stdin.setRawMode(true)\n didSetRaw = true\n }\n\n try {\n const write = (s: string) => {\n stdout.write(s)\n }\n\n const read = (ms: number): Promise<string | null> =>\n new Promise((resolve) => {\n const timer = setTimeout(() => {\n stdin.removeListener(\"data\", onData)\n resolve(null)\n }, ms)\n\n function onData(chunk: Buffer) {\n clearTimeout(timer)\n stdin.removeListener(\"data\", onData)\n resolve(chunk.toString())\n }\n\n stdin.on(\"data\", onData)\n })\n\n const da1 = await queryPrimaryDA(write, read, timeoutMs)\n const da2 = await querySecondaryDA(write, read, timeoutMs)\n const version = await queryTerminalVersion(write, read, timeoutMs)\n\n return { da1, da2, version }\n } finally {\n if (didSetRaw) stdin.setRawMode(false)\n }\n}\n","/**\n * DECRQM — DEC Private Mode Query\n *\n * Queries the terminal for the state of DEC private modes.\n *\n * Protocol:\n * - Query: CSI ? {mode} $ p\n * - Response: CSI ? {mode} ; {Ps} $ y\n *\n * Where Ps is:\n * 1 = set (mode is enabled)\n * 2 = reset (mode is disabled)\n * 0 = not recognized (unknown mode)\n * 3 = permanently set\n * 4 = permanently reset\n *\n * We normalize 3→\"set\" and 4→\"reset\" for simplicity.\n *\n * Supported by: xterm, Ghostty, Kitty, WezTerm, foot, VTE-based terminals\n */\n\n/** Regex for DECRPM response: CSI ? mode ; Ps $ y */\nconst DECRPM_RESPONSE_RE = /\\x1b\\[\\?(\\d+);(\\d+)\\$y/\n\n/**\n * Regex for matching a DECRPM response anywhere in a chunk.\n *\n * Terminals may flush a DECRPM echo interleaved with other bytes (OSC color\n * reports, prior queries, cursor moves). The standalone {@link decodeDecrpmResponse}\n * helper accepts a haystack and locates the first DECRPM match, rather than\n * insisting the chunk start with one.\n *\n * `Ps` is constrained to a single digit (0-9 only) — the DECRPM specification\n * defines values 0/1/2/3/4 and reserves the rest. Multi-digit Ps strings are\n * not valid DECRPM responses and should be ignored.\n */\nconst DECRPM_RESPONSE_FIND_RE = /\\x1b\\[\\?(\\d+);(\\d)\\$y/\n\n/** Well-known DEC private mode constants. */\nexport const DecMode = {\n /** DEC cursor visible (DECTCEM) */\n CURSOR_VISIBLE: 25,\n /** Alternate screen buffer (DECSET 1049) */\n ALT_SCREEN: 1049,\n /** Normal mouse tracking (X10) */\n MOUSE_TRACKING: 1000,\n /** Bracketed paste mode */\n BRACKETED_PASTE: 2004,\n /** Synchronized output */\n SYNC_OUTPUT: 2026,\n /** Focus reporting */\n FOCUS_REPORTING: 1004,\n} as const\n\ntype ModeState = \"set\" | \"reset\" | \"unknown\"\n\n/**\n * Query the state of a single DEC private mode.\n *\n * @param write Function to write to stdout\n * @param read Function to read a chunk from stdin\n * @param mode DEC private mode number (e.g., DecMode.ALT_SCREEN)\n * @param timeoutMs How long to wait for response (default: 200ms)\n * @returns \"set\", \"reset\", or \"unknown\"\n */\nexport async function queryMode(\n write: (data: string) => void,\n read: (timeoutMs: number) => Promise<string | null>,\n mode: number,\n timeoutMs = 200,\n): Promise<ModeState> {\n write(`\\x1b[?${mode}$p`)\n\n const data = await read(timeoutMs)\n if (data == null) return \"unknown\"\n\n const match = DECRPM_RESPONSE_RE.exec(data)\n if (!match) return \"unknown\"\n\n const reportedMode = parseInt(match[1]!, 10)\n if (reportedMode !== mode) return \"unknown\"\n\n const ps = parseInt(match[2]!, 10)\n switch (ps) {\n case 1:\n case 3:\n return \"set\"\n case 2:\n case 4:\n return \"reset\"\n default:\n return \"unknown\"\n }\n}\n\n/**\n * Query the state of multiple DEC private modes.\n *\n * Queries each mode sequentially and returns a Map of results.\n *\n * @param write Function to write to stdout\n * @param read Function to read a chunk from stdin\n * @param modes Array of DEC private mode numbers\n * @param timeoutMs Per-query timeout (default: 200ms)\n */\nexport async function queryModes(\n write: (data: string) => void,\n read: (timeoutMs: number) => Promise<string | null>,\n modes: number[],\n timeoutMs = 200,\n): Promise<Map<number, ModeState>> {\n const results = new Map<number, ModeState>()\n\n for (const mode of modes) {\n const state = await queryMode(write, read, mode, timeoutMs)\n results.set(mode, state)\n }\n\n return results\n}\n\n// =============================================================================\n// Standalone DECRPM response decoder (15127 GAP 8)\n// =============================================================================\n\n/**\n * Decoded DECRPM response — the mode the terminal reported on, and its state.\n *\n * `state: \"unknown\"` means the terminal responded but indicated it does not\n * recognize the mode (DECRPM Ps=0, or any unspecified Ps). Distinguish this\n * from `decodeDecrpmResponse` returning `null`, which means no DECRPM\n * response was found in the chunk at all.\n */\nexport interface DecrpmResponse {\n readonly mode: number\n readonly state: ModeState\n}\n\n/**\n * Decode a DECRPM response (CSI ? {mode} ; {Ps} $ y) from a byte chunk.\n *\n * Optional, side-effect-free, never throws. Designed for the \"the terminal\n * may or may not echo state back\" path — sync update mode (DEC 2026) is the\n * canonical case: some terminals respond to `CSI ?2026h` with an echoed\n * state, most just apply it silently. Consumers route incoming bytes through\n * this decoder; absence of a match is normal and silently passes through.\n *\n * Returns the decoded {@link DecrpmResponse} when a well-formed response is\n * located anywhere in `data`, or `null` when the chunk contains no DECRPM\n * payload (or only a malformed one). `expectedMode`, when provided, narrows\n * acceptance: a response for any other mode returns `null` rather than\n * reporting on the wrong mode.\n *\n * @param data Raw bytes received from the terminal (may include unrelated\n * OSC/CSI noise — only the first valid DECRPM match is returned)\n * @param expectedMode If set, only DECRPM responses for this mode are accepted\n */\nexport function decodeDecrpmResponse(data: string, expectedMode?: number): DecrpmResponse | null {\n if (!data) return null\n\n const match = DECRPM_RESPONSE_FIND_RE.exec(data)\n if (!match) return null\n\n const mode = parseInt(match[1]!, 10)\n if (!Number.isFinite(mode)) return null\n if (expectedMode !== undefined && mode !== expectedMode) return null\n\n const ps = parseInt(match[2]!, 10)\n let state: ModeState\n switch (ps) {\n case 1:\n case 3:\n state = \"set\"\n break\n case 2:\n case 4:\n state = \"reset\"\n break\n default:\n // Ps=0 (mode not recognized) and any other digit fall through to\n // \"unknown\" — the terminal answered, but the answer isn't a usable\n // set/reset signal.\n state = \"unknown\"\n break\n }\n\n return { mode, state }\n}\n\n/**\n * Decode a Synchronized Update Mode (DEC 2026) echo response, if present.\n *\n * Convenience over {@link decodeDecrpmResponse} narrowed to mode 2026:\n *\n * - `\"set\"` — terminal echoed state=set (Ps=1 or Ps=3)\n * - `\"reset\"` — terminal echoed state=reset (Ps=2 or Ps=4)\n * - `null` — no usable echo (no DECRPM response, response was for a different\n * mode, or Ps=0/unknown indicating the terminal does not recognize sync mode)\n *\n * Returning `null` is the common case — most terminals apply `CSI ?2026h/l`\n * silently and never echo state. Callers route input bytes through this\n * decoder and update a local \"observed sync state\" gauge only when it returns\n * a definite \"set\"/\"reset\".\n */\nexport function decodeSyncUpdateResponse(data: string): \"set\" | \"reset\" | null {\n const response = decodeDecrpmResponse(data, DecMode.SYNC_OUTPUT)\n if (response === null) return null\n if (response.state === \"unknown\") return null\n return response.state\n}\n","/**\n * CSI 14t/18t — Terminal Pixel and Text Area Size Queries\n *\n * Queries the terminal for window dimensions in pixels and characters.\n *\n * Protocols:\n *\n * Text Area Pixels (CSI 14t):\n * Query: CSI 14 t\n * Response: CSI 4 ; height ; width t\n *\n * Text Area Size in Characters (CSI 18t):\n * Query: CSI 18 t\n * Response: CSI 8 ; rows ; cols t\n *\n * Cell size can be derived by dividing pixel dimensions by character dimensions.\n *\n * Supported by: xterm, Ghostty, Kitty, WezTerm, foot, iTerm2\n */\n\n/** Regex for CSI 4 ; height ; width t (pixel size response) */\nconst PIXEL_RESPONSE_RE = /\\x1b\\[4;(\\d+);(\\d+)t/\n\n/** Regex for CSI 8 ; rows ; cols t (text area size response) */\nconst TEXT_AREA_RESPONSE_RE = /\\x1b\\[8;(\\d+);(\\d+)t/\n\n// ============================================================================\n// Pixel Size Query\n// ============================================================================\n\n/**\n * Query the terminal text area size in pixels.\n *\n * @param write Function to write to stdout\n * @param read Function to read a chunk from stdin\n * @param timeoutMs How long to wait for response (default: 200ms)\n * @returns Width and height in pixels, or null on timeout/unsupported\n */\nexport async function queryTextAreaPixels(\n write: (data: string) => void,\n read: (timeoutMs: number) => Promise<string | null>,\n timeoutMs = 200,\n): Promise<{ width: number; height: number } | null> {\n write(\"\\x1b[14t\")\n\n const data = await read(timeoutMs)\n if (data == null) return null\n\n const match = PIXEL_RESPONSE_RE.exec(data)\n if (!match) return null\n\n return {\n height: parseInt(match[1]!, 10),\n width: parseInt(match[2]!, 10),\n }\n}\n\n// ============================================================================\n// Text Area Size Query (characters)\n// ============================================================================\n\n/**\n * Query the terminal text area size in characters (rows x columns).\n *\n * @param write Function to write to stdout\n * @param read Function to read a chunk from stdin\n * @param timeoutMs How long to wait for response (default: 200ms)\n * @returns Rows and columns, or null on timeout/unsupported\n */\nexport async function queryTextAreaSize(\n write: (data: string) => void,\n read: (timeoutMs: number) => Promise<string | null>,\n timeoutMs = 200,\n): Promise<{ cols: number; rows: number } | null> {\n write(\"\\x1b[18t\")\n\n const data = await read(timeoutMs)\n if (data == null) return null\n\n const match = TEXT_AREA_RESPONSE_RE.exec(data)\n if (!match) return null\n\n return {\n rows: parseInt(match[1]!, 10),\n cols: parseInt(match[2]!, 10),\n }\n}\n\n// ============================================================================\n// Cell Size (derived)\n// ============================================================================\n\n/**\n * Query the terminal cell size in pixels by querying both pixel\n * dimensions and character dimensions, then dividing.\n *\n * @param write Function to write to stdout\n * @param read Function to read a chunk from stdin\n * @param timeoutMs Per-query timeout (default: 200ms)\n * @returns Cell width and height in pixels, or null if either query fails\n */\nexport async function queryCellSize(\n write: (data: string) => void,\n read: (timeoutMs: number) => Promise<string | null>,\n timeoutMs = 200,\n): Promise<{ width: number; height: number } | null> {\n const pixels = await queryTextAreaPixels(write, read, timeoutMs)\n if (pixels == null) return null\n\n const size = await queryTextAreaSize(write, read, timeoutMs)\n if (size == null) return null\n\n if (size.cols === 0 || size.rows === 0) return null\n\n return {\n width: pixels.width / size.cols,\n height: pixels.height / size.rows,\n }\n}\n","/**\n * TermDef Resolution\n *\n * Converts TermDef (minimal render config) into resolved values for rendering.\n * Handles auto-detection of events from stdin, dimension defaults, etc.\n */\n\nimport type { ColorLevel, Term } from \"./ansi/index\"\nimport { createTerminalProfile } from \"@silvery/ansi\"\nimport { getInternalStreams } from \"./runtime/term-internal\"\nimport type { Event, EventSource } from \"@silvery/ag/types\"\n\n// ============================================================================\n// Terminal-Specific Types (moved from @silvery/ag/types)\n// ============================================================================\n\n/**\n * Minimal surface for configuring render().\n *\n * TermDef provides a simple way to configure rendering without requiring\n * a full Term instance. It's useful for:\n * - Static rendering (just width/height, no events)\n * - Testing (mock dimensions and events)\n * - Quick scripts (auto-detect everything from stdin/stdout)\n *\n * The presence of `events` (or `stdin` which auto-creates events)\n * determines the render mode:\n * - No events → static mode (render until stable)\n * - Has events → interactive mode (render until exit() called)\n *\n * @example\n * ```tsx\n * // Static render with custom width\n * const output = await render(<App />, { width: 100 })\n *\n * // Interactive with stdin/stdout\n * await render(<App />, { stdin: process.stdin, stdout: process.stdout })\n *\n * // Custom events\n * await render(<App />, { events: myEventSource })\n * ```\n */\nexport interface TermDef {\n // -------------------------------------------------------------------------\n // Output Configuration\n // -------------------------------------------------------------------------\n\n /** Output stream (used for dimensions if not specified) */\n stdout?: NodeJS.WriteStream\n\n /** Width in columns (default: stdout?.columns ?? 80) */\n width?: number\n\n /** Height in rows (default: stdout?.rows ?? 24) */\n height?: number\n\n /**\n * Color support (true=detect, false=mono, or specific {@link ColorLevel}).\n *\n * `null` is accepted as a compat alias for `\"mono\"` (pre-terminal-profile-plateau\n * no-color spelling).\n */\n colors?: boolean | ColorLevel | null\n\n // -------------------------------------------------------------------------\n // Input Configuration\n // -------------------------------------------------------------------------\n\n /**\n * Event source for interactive mode.\n *\n * When present, render runs until exit() is called.\n * When absent, render completes when UI is stable.\n */\n events?: AsyncIterable<Event> | EventSource\n\n /**\n * Standard input stream.\n *\n * When provided (and events is not), automatically creates input events\n * from stdin, enabling interactive mode.\n */\n stdin?: NodeJS.ReadStream\n}\n\n/**\n * Options passed to the render function.\n */\nexport interface RenderOptions {\n stdout?: NodeJS.WriteStream\n stdin?: NodeJS.ReadStream\n exitOnCtrlC?: boolean\n debug?: boolean\n}\n\n/**\n * The render instance returned by render().\n */\nexport interface RenderInstance {\n /** Re-render with new element */\n rerender: (element: unknown) => void\n /** Unmount and clean up */\n unmount: () => void\n /** Wait for render to complete */\n waitUntilExit: () => Promise<void>\n /** Clear terminal output */\n clear: () => void\n}\n\n// ============================================================================\n// Resolved TermDef\n// ============================================================================\n\n/**\n * Resolved values from a TermDef, ready for use by the render system.\n */\nexport interface ResolvedTermDef {\n /** Output stream (may be mock for static rendering) */\n stdout: NodeJS.WriteStream | null\n\n /** Width in columns */\n width: number\n\n /** Height in rows */\n height: number\n\n /** Resolved color tier. `\"mono\"` = no colors. */\n colors: ColorLevel\n\n /** Event source (null = static mode) */\n events: AsyncIterable<Event> | null\n\n /** Whether this is static mode (no events = render until stable) */\n isStatic: boolean\n}\n\n// ============================================================================\n// Resolution Logic\n// ============================================================================\n\n/**\n * Default dimensions when not detectable.\n */\nconst DEFAULT_WIDTH = 80\nconst DEFAULT_HEIGHT = 24\n\n/**\n * Check if a value is a Term instance (duck typing).\n *\n * Post km-silvery.caps-restructure Phase 7: the legacy hasCursor/hasInput/\n * hasColor methods were removed — caps/profile/write/size are the canonical\n * Term surface.\n */\nexport function isTerm(value: unknown): value is Term {\n // Term can be a callable Proxy (typeof === 'function') or object\n if (!value || (typeof value !== \"object\" && typeof value !== \"function\")) {\n return false\n }\n const obj = value as Record<string, unknown>\n return (\n typeof obj.caps === \"object\" &&\n obj.caps !== null &&\n typeof obj.write === \"function\" &&\n typeof obj.size === \"object\"\n )\n}\n\n/**\n * Check if a value is a TermDef (not a Term).\n */\nexport function isTermDef(value: unknown): value is TermDef {\n if (!value || typeof value !== \"object\") return false\n // TermDef doesn't have a `caps` field — that's a Term-only marker.\n const obj = value as Record<string, unknown>\n return obj.caps === undefined\n}\n\n/**\n * Resolve a TermDef into concrete values.\n *\n * @param def - TermDef to resolve\n * @returns Resolved values ready for rendering\n */\nexport function resolveTermDef(def: TermDef): ResolvedTermDef {\n // Resolve dimensions\n const width = def.width ?? def.stdout?.columns ?? DEFAULT_WIDTH\n const height = def.height ?? def.stdout?.rows ?? DEFAULT_HEIGHT\n\n // Resolve colors. `false` / `null` → `\"mono\"` (no color). `true` or\n // undefined → auto-detect. Explicit tier passes through.\n let colors: ColorLevel\n if (def.colors === true) {\n colors = detectColorLevel(def.stdout)\n } else if (def.colors === false || def.colors === null) {\n colors = \"mono\"\n } else if (def.colors) {\n colors = def.colors\n } else {\n colors = detectColorLevel(def.stdout)\n }\n\n // Resolve events\n let events: AsyncIterable<Event> | null = null\n if (def.events) {\n // Explicit events provided\n events = def.events\n } else if (def.stdin) {\n // Auto-create events from stdin\n events = createInputEvents(def.stdin)\n }\n\n return {\n stdout: def.stdout ?? null,\n width,\n height,\n colors,\n events,\n isStatic: events === null,\n }\n}\n\n/**\n * Resolve a Term instance into ResolvedTermDef.\n *\n * @param term - Term instance\n * @returns Resolved values\n */\nexport function resolveFromTerm(term: Term): ResolvedTermDef {\n // Phase 8b of km-silvery.term-sub-owners: public Term no longer exposes\n // stdin / stdout. The legacy ResolvedTermDef shape still wants raw streams,\n // so the runtime adapter reads them via the internal accessor.\n const { stdin, stdout } = getInternalStreams(term)\n return {\n stdout,\n width: term.cols ?? DEFAULT_WIDTH,\n height: term.rows ?? DEFAULT_HEIGHT,\n // Post caps-restructure Phase 7: hasColor() is gone — read colorLevel\n // from the canonical profile. `ResolvedTermDef.colors: ColorLevel`\n // already includes `\"mono\"` as the no-color state, so return the tier\n // verbatim (no null coercion — `ColorLevel` is the canonical spelling).\n colors: term.profile.colorLevel,\n // Term instances always have interactive capabilities\n events: createInputEvents(stdin),\n isStatic: false,\n }\n}\n\n// ============================================================================\n// Color Detection\n// ============================================================================\n\n/**\n * Detect the color tier from a stdout stream.\n *\n * Phase 3 of km-silvery.terminal-profile-plateau: this is now a single-line\n * delegate into `createTerminalProfile` — no more local detection copy.\n */\nfunction detectColorLevel(stdout?: NodeJS.WriteStream): ColorLevel {\n return createTerminalProfile({ stdout: stdout ?? { isTTY: false } }).colorLevel\n}\n\n// ============================================================================\n// Input Events\n// ============================================================================\n\n/**\n * Create an async iterable of input events from a stdin stream.\n *\n * This enables interactive mode by providing a source of keyboard events.\n */\nexport function createInputEvents(stdin: NodeJS.ReadStream): AsyncIterable<Event> {\n return {\n [Symbol.asyncIterator](): AsyncIterator<Event> {\n const buffer: Event[] = []\n let resolveNext: ((value: IteratorResult<Event>) => void) | null = null\n let done = false\n\n // Set up stdin reading\n const handleData = (chunk: Buffer | string) => {\n const data = typeof chunk === \"string\" ? chunk : chunk.toString(\"utf8\")\n\n // Convert raw input to key events\n // This is simplified - real implementation would parse ANSI sequences\n for (const char of data) {\n const event: Event = {\n type: \"key\",\n key: char,\n ctrl: char.charCodeAt(0) < 32 && char !== \"\\r\" && char !== \"\\n\" && char !== \"\\t\",\n }\n\n if (resolveNext) {\n resolveNext({ value: event, done: false })\n resolveNext = null\n } else {\n buffer.push(event)\n }\n }\n }\n\n const handleEnd = () => {\n done = true\n if (resolveNext) {\n resolveNext({ value: undefined as unknown as Event, done: true })\n resolveNext = null\n }\n }\n\n // Only set up if stdin supports raw mode\n if (stdin.isTTY && typeof stdin.setRawMode === \"function\") {\n stdin.setEncoding(\"utf8\")\n stdin.on(\"data\", handleData)\n stdin.on(\"end\", handleEnd)\n }\n\n return {\n next(): Promise<IteratorResult<Event>> {\n // Return buffered event if available\n const buffered = buffer.shift()\n if (buffered) {\n return Promise.resolve({ value: buffered, done: false })\n }\n\n // If done, return done\n if (done) {\n return Promise.resolve({\n value: undefined as unknown as Event,\n done: true,\n })\n }\n\n // Wait for next event\n return new Promise((resolve) => {\n resolveNext = resolve\n })\n },\n\n return(): Promise<IteratorResult<Event>> {\n done = true\n stdin.off(\"data\", handleData)\n stdin.off(\"end\", handleEnd)\n return Promise.resolve({\n value: undefined as unknown as Event,\n done: true,\n })\n },\n }\n },\n }\n}\n","/**\n * Hit Registry Core — Pure logic for mouse hit testing.\n *\n * This module contains the React-free core of the hit registry:\n * types, registry class, z-index constants, and ID counter.\n *\n * React hooks and context live in ./hit-registry (which re-exports everything\n * from here plus adds useHitRegion, useHitRegionCallback, HitRegistryContext).\n *\n * The @silvery/ag-term barrel imports from this file to stay React-free.\n * Consumers who need React hooks should import from @silvery/ag-term/hit-registry.\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Target type for hit testing.\n * Each type represents a different clickable element in the UI.\n */\nexport interface HitTarget {\n /** The type of element that was clicked */\n type: \"node\" | \"fold-toggle\" | \"link\" | \"column-header\" | \"scroll-area\" | \"button\"\n /** Column index (for column-header, or items within a column) */\n colIndex?: number\n /** Card index within a column */\n cardIndex?: number\n /** Sub-item index within a card (e.g., checklist items) */\n subIndex?: number\n /** Node ID for node-specific targets */\n nodeId?: string\n /** URL for link targets */\n linkUrl?: string\n /** Custom action identifier */\n action?: string\n}\n\n/**\n * A registered hit region with position, size, target, and z-index.\n */\nexport interface HitRegion {\n /** X position on screen (0-indexed column) */\n x: number\n /** Y position on screen (0-indexed row) */\n y: number\n /** Width in columns */\n width: number\n /** Height in rows */\n height: number\n /** The target to return when this region is clicked */\n target: HitTarget\n /** Z-index for layering (higher values are on top) */\n zIndex: number\n}\n\n// ============================================================================\n// HitRegistry Class\n// ============================================================================\n\n/**\n * Registry for managing hit regions.\n *\n * Components register their screen regions with targets, and the registry\n * resolves mouse clicks to the appropriate target based on position and z-index.\n *\n * @example\n * ```typescript\n * const registry = new HitRegistry();\n *\n * // Register a card region\n * registry.register('card-1', {\n * x: 10, y: 5, width: 30, height: 8,\n * target: { type: 'node', nodeId: 'abc123' },\n * zIndex: 10\n * });\n *\n * // Hit test a click\n * const target = registry.hitTest(15, 7);\n * // Returns { type: 'node', nodeId: 'abc123' }\n * ```\n */\nexport class HitRegistry {\n private regions = new Map<string, HitRegion>()\n\n /**\n * Register a hit region with a unique ID.\n *\n * @param id - Unique identifier for the region (used for unregistration)\n * @param region - The region definition including position, size, target, and z-index\n */\n register(id: string, region: HitRegion): void {\n this.regions.set(id, region)\n }\n\n /**\n * Unregister a hit region by ID.\n *\n * @param id - The ID used when registering the region\n */\n unregister(id: string): void {\n this.regions.delete(id)\n }\n\n /**\n * Clear all registered regions.\n * Useful when the UI is completely redrawn.\n */\n clear(): void {\n this.regions.clear()\n }\n\n /**\n * Get the number of registered regions.\n * Useful for debugging.\n */\n get size(): number {\n return this.regions.size\n }\n\n /**\n * Test a screen position and return the highest z-index matching target.\n *\n * @param screenX - X position on screen (0-indexed column)\n * @param screenY - Y position on screen (0-indexed row)\n * @returns The target of the highest z-index region containing the point, or null if none\n */\n hitTest(screenX: number, screenY: number): HitTarget | null {\n let bestMatch: HitRegion | null = null\n\n for (const region of this.regions.values()) {\n // Check if point is within region bounds\n if (\n screenX >= region.x &&\n screenX < region.x + region.width &&\n screenY >= region.y &&\n screenY < region.y + region.height\n ) {\n // Keep the highest z-index match\n if (!bestMatch || region.zIndex > bestMatch.zIndex) {\n bestMatch = region\n }\n }\n }\n\n return bestMatch?.target ?? null\n }\n\n /**\n * Get all regions that contain a point, sorted by z-index (highest first).\n * Useful for debugging or when you need to know all overlapping elements.\n *\n * @param screenX - X position on screen (0-indexed column)\n * @param screenY - Y position on screen (0-indexed row)\n * @returns Array of matching regions, sorted by z-index descending\n */\n hitTestAll(screenX: number, screenY: number): HitRegion[] {\n const matches: HitRegion[] = []\n\n for (const region of this.regions.values()) {\n if (\n screenX >= region.x &&\n screenX < region.x + region.width &&\n screenY >= region.y &&\n screenY < region.y + region.height\n ) {\n matches.push(region)\n }\n }\n\n // Sort by z-index descending (highest first)\n return matches.sort((a, b) => b.zIndex - a.zIndex)\n }\n\n /**\n * Debug helper: get all registered regions.\n */\n getAllRegions(): Map<string, HitRegion> {\n return new Map(this.regions)\n }\n}\n\n// ============================================================================\n// ID Counter\n// ============================================================================\n\n/**\n * Generate a unique ID for hit region registration.\n */\nlet hitRegionIdCounter = 0\nexport function generateHitRegionId(): string {\n return `hit-${++hitRegionIdCounter}`\n}\n\n/**\n * Reset the ID counter (useful for testing).\n */\nexport function resetHitRegionIdCounter(): void {\n hitRegionIdCounter = 0\n}\n\n// ============================================================================\n// Z-Index Constants\n// ============================================================================\n\n/**\n * Recommended z-index values for different UI layers.\n */\nexport const Z_INDEX = {\n /** Background elements */\n BACKGROUND: 0,\n /** Column headers */\n COLUMN_HEADER: 5,\n /** Cards in the main view */\n CARD: 10,\n /** Fold toggles (above cards for easier clicking) */\n FOLD_TOGGLE: 15,\n /** Links within cards */\n LINK: 20,\n /** Floating elements */\n FLOATING: 50,\n /** Modal dialogs */\n DIALOG: 100,\n /** Dropdown menus */\n DROPDOWN: 150,\n /** Tooltips */\n TOOLTIP: 200,\n} as const\n","/**\n * Non-TTY Mode Support for Silvery\n *\n * Provides detection and rendering modes for non-interactive environments:\n * - Piped output (process.stdout.isTTY === false)\n * - CI environments\n * - TERM=dumb\n *\n * When in non-TTY mode, silvery avoids cursor positioning codes that garble\n * output in non-interactive environments.\n *\n * Modes:\n * - 'auto': Detect based on environment (default)\n * - 'tty': Force TTY mode (normal cursor positioning)\n * - 'line-by-line': Simple newline-separated output, no cursor movement\n * - 'static': Single output at end (no updates)\n * - 'plain': Strip all ANSI codes\n */\n\nimport { createTerminalProfile } from \"@silvery/ansi\"\nimport { stripAnsi } from \"./unicode\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Non-TTY rendering mode.\n *\n * - 'auto': Auto-detect based on environment\n * - 'tty': Force TTY mode with cursor positioning\n * - 'line-by-line': Output lines without cursor repositioning\n * - 'static': Single final output only\n * - 'plain': Strip all ANSI escape codes\n */\nexport type NonTTYMode = \"auto\" | \"tty\" | \"line-by-line\" | \"static\" | \"plain\"\n\n/**\n * Options for non-TTY output.\n */\nexport interface NonTTYOptions {\n /** The rendering mode. Default: 'auto' */\n mode?: NonTTYMode\n /** Output stream to check for TTY status. Default: process.stdout */\n stdout?: NodeJS.WriteStream\n}\n\n/**\n * Resolved non-TTY mode after auto-detection.\n */\nexport type ResolvedNonTTYMode = Exclude<NonTTYMode, \"auto\">\n\n// ============================================================================\n// Detection\n// ============================================================================\n\n/**\n * Check if the environment is a TTY.\n *\n * Returns false if:\n * - stdout.isTTY is false or undefined\n * - TERM=dumb\n * - CI environment variables are set\n *\n * Pass `emulator` (from `term.emulator` or a TerminalEmulator fixture) when\n * available to avoid a redundant env read. Without it, the TERM=dumb check\n * delegates to {@link createTerminalProfile} — the canonical single-source-\n * of-truth entry in `@silvery/ansi/profile`. CI env vars remain read\n * directly because they are orthogonal to terminal capabilities (a CI\n * runner's TTY status doesn't describe what the terminal can render).\n */\nexport function isTTY(\n stdout: NodeJS.WriteStream = process.stdout,\n /** Structural emulator — `{ TERM }` is the only field this helper reads. */\n emulator?: { TERM: string },\n): boolean {\n // Check stdout.isTTY\n if (!stdout.isTTY) {\n return false\n }\n\n // Check TERM=dumb via the passed emulator (preferred) or the canonical\n // profile factory.\n const TERM = emulator?.TERM ?? createTerminalProfile().emulator.TERM\n if (TERM === \"dumb\") {\n return false\n }\n\n // Check common CI environment variables.\n // Note: these are *not* terminal-signal env vars — they describe the host\n // environment (is this CI?), not terminal capabilities. They remain a\n // direct env read by design; the lint in scripts/lint-env-reads.ts does\n // not flag them.\n if (\n process.env.CI ||\n process.env.GITHUB_ACTIONS ||\n process.env.GITLAB_CI ||\n process.env.JENKINS_URL ||\n process.env.BUILDKITE ||\n process.env.CIRCLECI ||\n process.env.TRAVIS\n ) {\n return false\n }\n\n return true\n}\n\n/**\n * Resolve the non-TTY mode based on options and environment.\n *\n * When mode is 'auto':\n * - If TTY detected: returns 'tty'\n * - If non-TTY detected: returns 'line-by-line'\n */\nexport function resolveNonTTYMode(options: NonTTYOptions = {}): ResolvedNonTTYMode {\n const { mode = \"auto\", stdout = process.stdout } = options\n\n if (mode !== \"auto\") {\n return mode\n }\n\n // Auto-detect based on environment\n return isTTY(stdout) ? \"tty\" : \"line-by-line\"\n}\n\n// Re-export stripAnsi from unicode.ts (canonical implementation)\nexport { stripAnsi } from \"./unicode\"\n\n// ============================================================================\n// Line-by-Line Output\n// ============================================================================\n\n/**\n * Convert buffer output to line-by-line format.\n *\n * Instead of using cursor positioning, outputs each line with a simple\n * carriage return and clear-to-end-of-line sequence.\n *\n * @param content The rendered content (may contain ANSI codes but no cursor positioning)\n * @param prevLineCount Number of lines in the previous frame (for clearing)\n * @returns Output string suitable for non-TTY rendering\n */\nexport function toLineByLineOutput(content: string, prevLineCount: number): string {\n const lines = content.split(\"\\n\")\n let output = \"\"\n\n // Move cursor up to overwrite previous content (if any)\n if (prevLineCount > 0) {\n // Move to start of first line\n output += \"\\r\"\n // Move up\n if (prevLineCount > 1) {\n output += `\\x1b[${prevLineCount - 1}A`\n }\n }\n\n // Output each line\n for (let i = 0; i < lines.length; i++) {\n if (i > 0) {\n output += \"\\n\"\n }\n output += lines[i]\n // Clear to end of line (removes leftover content from longer previous lines)\n output += \"\\x1b[K\"\n }\n\n // Clear any remaining lines from previous frame\n const extraLines = prevLineCount - lines.length\n if (extraLines > 0) {\n for (let i = 0; i < extraLines; i++) {\n output += \"\\n\\x1b[K\"\n }\n // Move cursor back up to end of content\n output += `\\x1b[${extraLines}A`\n }\n\n return output\n}\n\n/**\n * Convert buffer output to plain text format.\n *\n * Strips all ANSI codes and outputs simple newline-separated text.\n * No cursor movement or clearing.\n *\n * @param content The rendered content\n * @param prevLineCount Number of lines in the previous frame (unused in plain mode)\n * @returns Plain text output\n */\nexport function toPlainOutput(content: string, _prevLineCount: number): string {\n // Strip ANSI codes\n const plain = stripAnsi(content)\n\n // Trim trailing whitespace from each line but preserve structure\n const lines = plain.split(\"\\n\").map((line) => line.trimEnd())\n\n // Remove trailing empty lines\n while (lines.length > 0 && lines[lines.length - 1] === \"\") {\n lines.pop()\n }\n\n return lines.join(\"\\n\")\n}\n\n// ============================================================================\n// Output Helpers\n// ============================================================================\n\n/**\n * Create an output transformer based on the non-TTY mode.\n *\n * @param mode The resolved non-TTY mode\n * @returns A function that transforms output based on the mode\n */\nexport function createOutputTransformer(\n mode: ResolvedNonTTYMode,\n): (content: string, prevLineCount: number) => string {\n switch (mode) {\n case \"tty\":\n // Pass through unchanged\n return (content) => content\n\n case \"line-by-line\":\n return toLineByLineOutput\n\n case \"static\":\n // For static mode, we return empty string for intermediate renders\n // The final render is handled by the caller\n return () => \"\"\n\n case \"plain\":\n return toPlainOutput\n }\n}\n\n/**\n * Count the number of lines in a string.\n */\nexport function countLines(str: string): number {\n if (!str) return 0\n return str.split(\"\\n\").length\n}\n","/**\n * silvery Inspector — Debug introspection for rendering pipeline.\n *\n * Activate with SILVERY_DEV=1 env var or by calling enableInspector().\n * Outputs debug info to stderr or a log file (never to the TUI stdout).\n *\n * Features:\n * - Component tree dump (with layout rects)\n * - Focus path display\n * - Render stats (frame time, dirty rows, cell changes)\n * - Dirty region visualization\n *\n * This is DISTINCT from React DevTools (devtools.ts). This inspector provides\n * silvery-specific introspection: render pipeline stats, focus tree, dirty regions,\n * layout info.\n */\n\nimport type { RenderStats } from \"./scheduler\"\nimport type { AgNode } from \"@silvery/ag/types\"\nimport {\n isDirty,\n CONTENT_BIT,\n STYLE_PROPS_BIT,\n BG_BIT,\n SUBTREE_BIT,\n CHILDREN_BIT,\n} from \"@silvery/ag/epoch\"\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface InspectorOptions {\n /** Output stream (default: process.stderr) */\n output?: NodeJS.WritableStream\n /** Log file path (overrides output stream) */\n logFile?: string\n /** Include layout rects in tree dump */\n showLayout?: boolean\n /** Include style info in tree dump */\n showStyles?: boolean\n}\n\n// =============================================================================\n// State\n// =============================================================================\n\nlet inspectorEnabled = false\nlet inspectorOutput: NodeJS.WritableStream = process.stderr\n\n// =============================================================================\n// Public API\n// =============================================================================\n\n/** Enable the silvery inspector. */\nexport function enableInspector(options?: InspectorOptions): void {\n inspectorEnabled = true\n if (options?.logFile) {\n // sync-node-builtin: multi-target lazy guard via getBuiltinModule (ESM-only wave-3).\n // Avoids pulling node:fs into browser/canvas bundles for non-inspector users.\n const fs = process.getBuiltinModule(\"node:fs\")\n inspectorOutput = fs.createWriteStream(options.logFile, { flags: \"a\" })\n } else if (options?.output) {\n inspectorOutput = options.output\n } else {\n inspectorOutput = process.stderr\n }\n}\n\n/** Disable the inspector. */\nexport function disableInspector(): void {\n inspectorEnabled = false\n}\n\n/** Check if inspector is active. */\nexport function isInspectorEnabled(): boolean {\n return inspectorEnabled\n}\n\n/**\n * Log render stats after each frame.\n *\n * Called by the scheduler after doRender completes. When the inspector\n * is disabled this is a no-op (zero overhead).\n */\nexport function inspectFrame(stats: RenderStats): void {\n if (!inspectorEnabled) return\n const line =\n `[silvery] frame #${stats.renderCount} ` +\n `${stats.lastRenderTime.toFixed(1)}ms ` +\n `avg=${stats.avgRenderTime.toFixed(1)}ms ` +\n `skipped=${stats.skippedCount}\\n`\n inspectorOutput.write(line)\n}\n\n/**\n * Dump the component tree structure as indented text.\n *\n * Walks the SilveryNode tree and formats each node with its type, testID,\n * layout rect, and dirty flags.\n */\nexport function inspectTree(\n rootNode: AgNode,\n options?: { depth?: number; showLayout?: boolean },\n): string {\n const maxDepth = options?.depth ?? 10\n const showLayout = options?.showLayout ?? true\n const lines: string[] = []\n\n function walk(node: AgNode, indent: number): void {\n if (indent > maxDepth) return\n\n const prefix = \" \".repeat(indent)\n const type = node.type\n const testID = (node.props as Record<string, unknown>)?.testID\n const idStr = testID ? ` #${testID}` : \"\"\n\n // Layout rect from computed layout node or boxRect\n let rectStr = \"\"\n if (showLayout) {\n if (node.boxRect) {\n const r = node.boxRect\n rectStr = ` [${r.x},${r.y} ${r.width}x${r.height}]`\n } else if (node.layoutNode) {\n const ln = node.layoutNode\n rectStr = ` [${ln.getComputedLeft()},${ln.getComputedTop()} ${ln.getComputedWidth()}x${ln.getComputedHeight()}]`\n }\n }\n\n // Dirty flags\n const dirtyFlags: string[] = []\n if (node.layoutNode?.isDirty()) dirtyFlags.push(\"layout\")\n if (isDirty(node.dirtyBits, node.dirtyEpoch, CONTENT_BIT)) dirtyFlags.push(\"content\")\n if (isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT)) dirtyFlags.push(\"paint\")\n if (isDirty(node.dirtyBits, node.dirtyEpoch, BG_BIT)) dirtyFlags.push(\"bg\")\n if (isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT)) dirtyFlags.push(\"subtree\")\n if (isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT)) dirtyFlags.push(\"children\")\n const dirtyStr = dirtyFlags.length > 0 ? ` dirty=[${dirtyFlags.join(\",\")}]` : \"\"\n\n // Text content (for text nodes)\n const textStr = node.textContent\n ? ` \"${node.textContent.slice(0, 30)}${node.textContent.length > 30 ? \"...\" : \"\"}\"`\n : \"\"\n\n lines.push(`${prefix}${type}${idStr}${rectStr}${dirtyStr}${textStr}`)\n\n for (const child of node.children) {\n walk(child, indent + 1)\n }\n }\n\n walk(rootNode, 0)\n return lines.join(\"\\n\")\n}\n\n/**\n * Auto-enable if SILVERY_DEV=1 is set.\n *\n * Call this at startup to respect the environment variable convention.\n */\nexport function autoEnableInspector(): void {\n if (process.env.SILVERY_DEV === \"1\" || process.env.SILVERY_DEV === \"true\") {\n const logFile = process.env.SILVERY_DEV_LOG\n enableInspector(logFile ? { logFile } : undefined)\n }\n}\n","/**\n * Measurer composition layer.\n *\n * Creates term-scoped measurers and pipeline configs.\n * Bridges ansi Term with silvery measurement capabilities.\n */\n\nimport type { Term, TerminalCaps } from \"./ansi/index\"\nimport { createMeasurer, type Measurer } from \"./unicode\"\nimport { createOutputPhase } from \"./pipeline/output-phase\"\nimport { setActiveColorLevel } from \"./pipeline/state\"\nimport type { PipelineConfig } from \"./pipeline\"\n\nexport type { Measurer } from \"./unicode\"\n\n/**\n * Term extended with measurement capabilities.\n */\nexport interface MeasuredTerm extends Term, Measurer {}\n\n/**\n * Extend a Term with measurement capabilities.\n *\n * Creates a width measurer from the term's caps and adds measurement\n * methods (displayWidth, graphemeWidth, wrapText, etc.) to the term.\n */\nexport function withMeasurer(term: Term): MeasuredTerm {\n const caps = term.caps\n // caps is a structural superset of the measurer's input — name alignment\n // (maybeWideEmojis, textSizing) lets the whole caps object flow through.\n // Excess fields are ignored.\n const measurer = createMeasurer(caps ?? {})\n\n return Object.create(term, {\n maybeWideEmojis: { get: () => measurer.maybeWideEmojis, enumerable: true },\n textSizing: { get: () => measurer.textSizing, enumerable: true },\n displayWidth: { value: measurer.displayWidth.bind(measurer), enumerable: true },\n displayWidthAnsi: { value: measurer.displayWidthAnsi.bind(measurer), enumerable: true },\n graphemeWidth: { value: measurer.graphemeWidth.bind(measurer), enumerable: true },\n wrapText: { value: measurer.wrapText.bind(measurer), enumerable: true },\n sliceByWidth: { value: measurer.sliceByWidth.bind(measurer), enumerable: true },\n sliceByWidthFromEnd: { value: measurer.sliceByWidthFromEnd.bind(measurer), enumerable: true },\n }) as MeasuredTerm\n}\n\n/**\n * Create a pipeline configuration from caps and/or measurer.\n *\n * This is the single factory for PipelineConfig -- use it instead of\n * manually constructing { measurer, outputPhaseFn }.\n *\n * @param options.caps - Terminal capabilities (for output phase SGR generation).\n * Reads `caps.maybeWideEmojis` (text-presentation emoji width) and\n * `caps.textSizing` for measurement.\n * @param options.measurer - Explicit measurer (if omitted, created from caps)\n */\nexport function createPipeline(\n options: {\n caps?: TerminalCaps\n measurer?: Measurer\n } = {},\n): PipelineConfig {\n const { caps, measurer: explicitMeasurer } = options\n // caps is a structural superset of createMeasurer's input — the aligned\n // field names (`maybeWideEmojis`, `textSizing`) let it spread through as a\n // no-op. Excess caps fields are ignored.\n const measurer = explicitMeasurer ?? createMeasurer(caps ?? {})\n const outputPhaseFn = createOutputPhase(\n caps\n ? {\n // Post km-silvery.text-box-attr-props (2026-04-23): output phase\n // performs per-style downgrade, so it wants the full per-style\n // array here (not a boolean).\n underlineStyles: caps.underlineStyles,\n underlineColor: caps.underlineColor,\n overline: caps.overline,\n colorLevel: caps.colorLevel,\n }\n : {},\n measurer,\n )\n // Mirror colorLevel into module-scoped theme state so render-helpers\n // (parseColor, getTextStyle) can dispatch on tier without access to\n // OutputContext. At mono tier (\"none\"), $tokens resolve to null fg/bg and\n // getTextStyle injects per-token SGR attrs from DEFAULT_MONO_ATTRS so apps\n // keep hierarchy (bold / dim / italic / underline / inverse) when color is\n // unavailable.\n if (caps?.colorLevel) setActiveColorLevel(caps.colorLevel)\n return { measurer, outputPhaseFn }\n}\n","/**\n * Scroll Utilities\n *\n * Shared functions for edge-based scrolling behavior across VirtualList,\n * HorizontalVirtualList, and other scroll-aware components.\n */\n\n/**\n * Calculate edge-based scroll offset.\n *\n * Only scrolls when cursor approaches the edge of the visible area.\n * This provides smoother scrolling by starting to scroll before hitting\n * the absolute edge, maintaining context around the selected item.\n *\n * ## Algorithm\n *\n * The viewport is divided into zones:\n * ```\n * |<padding>|<------ safe zone ------>|<padding>|\n * | scroll | no scroll needed | scroll |\n * | if < | | if > |\n * ```\n *\n * When the selected item enters a padding zone, the viewport scrolls\n * to keep the item visible with margin.\n *\n * ## Asymmetry Note\n *\n * The +1 in the \"scroll down/right\" case is intentional:\n * - Offset points to the TOP/LEFT of the viewport\n * - We want the selected item to be `padding` items from the BOTTOM/RIGHT\n * - Formula: `selectedIndex - visibleCount + padding + 1`\n *\n * Example: visibleCount=10, padding=2, selectedIndex=15\n * offset = 15 - 10 + 2 + 1 = 8\n * viewport shows items 8-17, selected item 15 is at position 7 (2 from bottom)\n *\n * @param selectedIndex - Currently selected item index\n * @param currentOffset - Current scroll offset (topmost/leftmost visible item)\n * @param visibleCount - Number of items visible in viewport\n * @param totalCount - Total number of items\n * @param padding - Items to keep visible before/after cursor (default: 1)\n * @returns New scroll offset\n */\nexport function calcEdgeBasedScrollOffset(\n selectedIndex: number,\n currentOffset: number,\n visibleCount: number,\n totalCount: number,\n padding = 1,\n): number {\n // If everything fits, no scrolling needed\n if (totalCount <= visibleCount) return 0\n\n // Reduce padding when viewport is too small to have a non-empty safe zone.\n // With padding=1 and visibleCount=2, paddedStart > paddedEnd (inverted zone),\n // causing every re-render to trigger a scroll in one direction.\n const effectivePadding = padding * 2 >= visibleCount ? 0 : padding\n\n // Calculate visible range\n const visibleStart = currentOffset\n const visibleEnd = currentOffset + visibleCount - 1\n\n // Define the \"safe zone\" where cursor doesn't trigger scroll\n const paddedStart = visibleStart + effectivePadding\n const paddedEnd = visibleEnd - effectivePadding\n\n let newOffset = currentOffset\n\n if (selectedIndex < paddedStart) {\n // Scrolling UP/LEFT: place item `effectivePadding` rows from top\n newOffset = Math.max(0, selectedIndex - effectivePadding)\n } else if (\n effectivePadding === 0 &&\n selectedIndex === paddedStart &&\n currentOffset > 0 &&\n // Only scroll back if the viewport is large enough to show both the\n // context item and the selected item. When visibleCount <= padding,\n // scrolling back pushes the selected item out of view, which triggers\n // a forward scroll on the next render → infinite oscillation.\n visibleCount > padding\n ) {\n // Small viewport (effectivePadding forced to 0): cursor at the very first visible\n // position should still scroll back to provide context. Without this, scrolling\n // right works (cursor past last visible triggers scroll) but scrolling left doesn't\n // (cursor at first visible doesn't trigger), creating asymmetric behavior.\n // Use original padding for the offset formula to show context before the cursor.\n newOffset = Math.max(0, selectedIndex - padding)\n } else if (selectedIndex > paddedEnd) {\n // Scrolling DOWN/RIGHT: place item `effectivePadding` rows from bottom\n // The +1 converts from 0-indexed offset to correct position\n newOffset = Math.min(\n totalCount - visibleCount,\n selectedIndex - visibleCount + effectivePadding + 1,\n )\n }\n\n // Clamp to valid range\n return Math.max(0, Math.min(newOffset, totalCount - visibleCount))\n}\n","/**\n * Terminal scroll region (DECSTBM) utilities.\n *\n * Scroll regions tell the terminal to natively scroll content within\n * a defined row range, which is faster than re-rendering all cells.\n *\n * DECSTBM (DEC Set Top and Bottom Margins) is supported by most modern\n * terminal emulators: xterm, iTerm2, Kitty, Ghostty, WezTerm, etc.\n */\n\nimport { createTerminalProfile } from \"@silvery/ansi\"\n\nconst ESC = \"\\x1b\"\n\n/** Set terminal scroll region (1-indexed top and bottom rows). */\nexport function setScrollRegion(stdout: NodeJS.WriteStream, top: number, bottom: number): void {\n stdout.write(`${ESC}[${top};${bottom}r`)\n}\n\n/** Reset scroll region to full terminal. */\nexport function resetScrollRegion(stdout: NodeJS.WriteStream): void {\n stdout.write(`${ESC}[r`)\n}\n\n/** Scroll content up by N lines within the current scroll region. */\nexport function scrollUp(stdout: NodeJS.WriteStream, lines: number = 1): void {\n stdout.write(`${ESC}[${lines}S`)\n}\n\n/** Scroll content down by N lines within the current scroll region. */\nexport function scrollDown(stdout: NodeJS.WriteStream, lines: number = 1): void {\n stdout.write(`${ESC}[${lines}T`)\n}\n\n/** Move cursor to a specific position (1-indexed row and column). */\nexport function moveCursor(stdout: NodeJS.WriteStream, row: number, col: number): void {\n stdout.write(`${ESC}[${row};${col}H`)\n}\n\nexport interface ScrollRegionConfig {\n /** Top row of the scrollable area (0-indexed). */\n top: number\n /** Bottom row of the scrollable area (0-indexed). */\n bottom: number\n /** Whether scroll region optimization is enabled. */\n enabled: boolean\n}\n\n/**\n * Detect if the terminal likely supports DECSTBM scroll regions.\n *\n * Most modern terminals do (xterm, iTerm2, Kitty, Ghostty, WezTerm, etc.)\n * but some (e.g., Linux console) may not handle them correctly.\n *\n * Pass `emulator` (via `term.emulator` or a test fixture) when in scope.\n * Without it, this falls back to {@link createTerminalProfile} — the canonical\n * single-source-of-truth entry point in `@silvery/ansi/profile`. Direct\n * reads of terminal-signal env vars (TERM / TERM_PROGRAM / …) are banned\n * outside that module — see `scripts/lint-env-reads.ts`.\n */\nexport function supportsScrollRegions(\n /** Structural emulator — `{ program, TERM }` is all this helper reads. */\n emulator?: {\n program: string\n TERM: string\n },\n): boolean {\n const resolved = emulator ?? createTerminalProfile().emulator\n const term = resolved.TERM\n const termProgram = resolved.program\n\n // Known-good terminal programs\n if (\n termProgram === \"iTerm.app\" ||\n termProgram === \"WezTerm\" ||\n termProgram === \"ghostty\" ||\n termProgram === \"Ghostty\" ||\n termProgram === \"vscode\"\n )\n return true\n\n // Known-good TERM values\n if (\n term.includes(\"xterm\") ||\n term.includes(\"screen\") ||\n term.includes(\"tmux\") ||\n term.includes(\"kitty\")\n )\n return true\n\n // Linux console doesn't support DECSTBM\n if (term === \"linux\") return false\n\n // Default: assume support for any term that's not empty\n return term !== \"\"\n}\n","/**\n * Pane Manager - Pure functions for manipulating split layout trees.\n *\n * All functions are pure: they return new layout trees, never mutate.\n * The layout tree is a binary tree where leaves are panes and internal\n * nodes are splits (horizontal or vertical) with a ratio.\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type LayoutNode =\n | { type: \"leaf\"; id: string }\n | {\n type: \"split\"\n direction: \"horizontal\" | \"vertical\"\n ratio: number\n first: LayoutNode\n second: LayoutNode\n }\n\n// ============================================================================\n// Construction\n// ============================================================================\n\n/** Create a single-pane layout */\nexport function createLeaf(id: string): LayoutNode {\n return { type: \"leaf\", id }\n}\n\n// ============================================================================\n// Tree Queries\n// ============================================================================\n\n/** Get all leaf pane IDs in depth-first left-to-right order */\nexport function getPaneIds(layout: LayoutNode): string[] {\n if (layout.type === \"leaf\") return [layout.id]\n return [...getPaneIds(layout.first), ...getPaneIds(layout.second)]\n}\n\n/** Find the next/previous pane in tab order (depth-first left-to-right) */\nexport function getTabOrder(layout: LayoutNode): string[] {\n return getPaneIds(layout)\n}\n\n// ============================================================================\n// Tree Mutations (Pure)\n// ============================================================================\n\n/**\n * Split a pane into two. Returns new layout tree with the target pane split.\n * The original pane becomes the first child; the new pane becomes the second.\n */\nexport function splitPane(\n layout: LayoutNode,\n targetPaneId: string,\n direction: \"horizontal\" | \"vertical\",\n newPaneId: string,\n ratio = 0.5,\n): LayoutNode {\n const clampedRatio = clampRatio(ratio)\n\n if (layout.type === \"leaf\") {\n if (layout.id === targetPaneId) {\n return {\n type: \"split\",\n direction,\n ratio: clampedRatio,\n first: { type: \"leaf\", id: targetPaneId },\n second: { type: \"leaf\", id: newPaneId },\n }\n }\n return layout\n }\n\n // Recurse into children\n const newFirst = splitPane(layout.first, targetPaneId, direction, newPaneId, ratio)\n const newSecond = splitPane(layout.second, targetPaneId, direction, newPaneId, ratio)\n\n if (newFirst === layout.first && newSecond === layout.second) return layout\n\n return { ...layout, first: newFirst, second: newSecond }\n}\n\n/**\n * Remove a pane from the layout. The sibling takes the full space.\n * Returns null if the removed pane was the last one.\n */\nexport function removePane(layout: LayoutNode, paneId: string): LayoutNode | null {\n if (layout.type === \"leaf\") {\n return layout.id === paneId ? null : layout\n }\n\n // Check if either direct child is the target leaf\n if (layout.first.type === \"leaf\" && layout.first.id === paneId) {\n return layout.second\n }\n if (layout.second.type === \"leaf\" && layout.second.id === paneId) {\n return layout.first\n }\n\n // Recurse\n const newFirst = removePane(layout.first, paneId)\n const newSecond = removePane(layout.second, paneId)\n\n // If a subtree collapsed, promote the survivor\n if (newFirst === null) return newSecond\n if (newSecond === null) return newFirst\n\n if (newFirst === layout.first && newSecond === layout.second) return layout\n\n return { ...layout, first: newFirst, second: newSecond }\n}\n\n/** Swap two panes' positions in the layout */\nexport function swapPanes(layout: LayoutNode, paneId1: string, paneId2: string): LayoutNode {\n if (layout.type === \"leaf\") {\n if (layout.id === paneId1) return { type: \"leaf\", id: paneId2 }\n if (layout.id === paneId2) return { type: \"leaf\", id: paneId1 }\n return layout\n }\n\n const newFirst = swapPanes(layout.first, paneId1, paneId2)\n const newSecond = swapPanes(layout.second, paneId1, paneId2)\n\n if (newFirst === layout.first && newSecond === layout.second) return layout\n\n return { ...layout, first: newFirst, second: newSecond }\n}\n\n/**\n * Resize a split: adjust the ratio of the nearest ancestor split\n * that contains the target pane as its first child.\n * Positive delta = grow (first child gets more), negative = shrink.\n */\nexport function resizeSplit(layout: LayoutNode, paneId: string, delta: number): LayoutNode {\n if (layout.type === \"leaf\") return layout\n\n const firstIds = getPaneIds(layout.first)\n\n if (firstIds.includes(paneId)) {\n // Pane is in the first child — adjust this split's ratio\n const newRatio = clampRatio(layout.ratio + delta)\n if (newRatio === layout.ratio) return layout\n\n return { ...layout, ratio: newRatio }\n }\n\n const secondIds = getPaneIds(layout.second)\n\n if (secondIds.includes(paneId)) {\n // Pane is in the second child — shrink ratio (give less to first)\n const newRatio = clampRatio(layout.ratio - delta)\n if (newRatio === layout.ratio) return layout\n\n return { ...layout, ratio: newRatio }\n }\n\n return layout\n}\n\n// ============================================================================\n// Navigation\n// ============================================================================\n\n/**\n * Find the pane adjacent to the given pane in a direction.\n *\n * For left/right: looks for siblings in horizontal splits.\n * For up/down: looks for siblings in vertical splits.\n *\n * Returns null if no adjacent pane exists in that direction.\n */\nexport function findAdjacentPane(\n layout: LayoutNode,\n paneId: string,\n direction: \"left\" | \"right\" | \"up\" | \"down\",\n): string | null {\n const path = findPath(layout, paneId)\n if (!path) return null\n\n const splitDirection = direction === \"left\" || direction === \"right\" ? \"horizontal\" : \"vertical\"\n const goToSecond = direction === \"right\" || direction === \"down\"\n\n // Walk up the path looking for a relevant split\n for (let i = path.length - 1; i >= 0; i--) {\n const step = path[i]!\n if (step.node.type !== \"split\") continue\n if (step.node.direction !== splitDirection) continue\n\n // We came from 'first' and want to go to second (right/down)\n if (step.side === \"first\" && goToSecond) {\n return firstLeaf(step.node.second)\n }\n\n // We came from 'second' and want to go to first (left/up)\n if (step.side === \"second\" && !goToSecond) {\n return lastLeaf(step.node.first)\n }\n }\n\n return null\n}\n\n// ============================================================================\n// Internal Helpers\n// ============================================================================\n\nfunction clampRatio(ratio: number): number {\n return Math.max(0.1, Math.min(0.9, ratio))\n}\n\n/** Get the first (leftmost/topmost) leaf ID */\nfunction firstLeaf(node: LayoutNode): string {\n if (node.type === \"leaf\") return node.id\n return firstLeaf(node.first)\n}\n\n/** Get the last (rightmost/bottommost) leaf ID */\nfunction lastLeaf(node: LayoutNode): string {\n if (node.type === \"leaf\") return node.id\n return lastLeaf(node.second)\n}\n\ninterface PathStep {\n node: LayoutNode\n side: \"first\" | \"second\"\n}\n\n/** Find the path from root to a leaf, recording which side we took at each split */\nfunction findPath(layout: LayoutNode, paneId: string): PathStep[] | null {\n if (layout.type === \"leaf\") {\n return layout.id === paneId ? [] : null\n }\n\n const firstPath = findPath(layout.first, paneId)\n if (firstPath !== null) {\n return [{ node: layout, side: \"first\" }, ...firstPath]\n }\n\n const secondPath = findPath(layout.second, paneId)\n if (secondPath !== null) {\n return [{ node: layout, side: \"second\" }, ...secondPath]\n }\n\n return null\n}\n","/**\n * Selection style composition.\n *\n * Applies selection highlight as a cell-style transform in the rendering pipeline,\n * NOT as an ANSI overlay. Selection composes correctly with existing cell styles\n * (fg/bg/attrs), handles already-inverted content, and flows through the normal\n * diff/output renderer.\n *\n * Called after render phase, before output phase.\n */\n\nimport type { Color, TerminalBuffer } from \"./buffer\"\nimport { parseColor } from \"./pipeline/render-helpers\"\nimport {\n type SelectionRange,\n type SelectionScope,\n normalizeRange,\n} from \"@silvery/headless/selection\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Selection theme tokens. If provided, these override the fallback fg/bg swap.\n */\nexport type SelectionColor = Color | string\n\nexport interface SelectionTheme {\n /** Foreground color for selected text */\n selectionFg?: SelectionColor\n /** Background color for selected text */\n selectionBg?: SelectionColor\n}\n\n/**\n * A single cell change produced by selection composition.\n * Used as a sparse overlay — only cells within the selection range are affected.\n *\n * When `inverseAttr` is true, applying the change toggles the cell's inverse\n * attribute (SGR 7) — this is the legacy parity fallback for cells with\n * default fg/bg, where swapping null↔null would produce no visible change.\n * The terminal handles the actual visual swap at display time.\n *\n * When `inverseAttr` is false (or absent), applying the change overwrites the\n * cell's fg/bg directly — used for explicit theme tokens (selectionFg /\n * selectionBg) or fallback fg/bg swap when both colors are non-null.\n */\nexport interface SelectionCellChange {\n col: number\n row: number\n fg: SelectionColor\n bg: SelectionColor\n inverseAttr?: boolean\n}\n\nfunction normalizeSelectionColor(color: SelectionColor | undefined): Color | undefined {\n if (color === undefined) return undefined\n if (typeof color === \"string\") return parseColor(color)\n return color\n}\n\n// ============================================================================\n// Style Composition\n// ============================================================================\n\n/**\n * Compute selection style changes for all cells within a selection range.\n *\n * Returns a sparse list of cell changes (fg/bg only). The caller applies these\n * to the buffer before the output phase diffs it. This approach:\n * - Composes correctly with existing cell styles\n * - Handles already-inverted content (swaps back to normal instead of double-inverting)\n * - Respects SELECTABLE_FLAG per cell (skip non-selectable)\n * - Works with the normal diff/output renderer (no separate ANSI pass)\n *\n * @param buffer The rendered buffer (post-render-phase)\n * @param selection Current selection range, or null\n * @param theme Optional selection theme colors\n * @param respectSelectableFlag When true, skip cells without SELECTABLE_FLAG\n */\nexport function composeSelectionCells(\n buffer: TerminalBuffer,\n selection: SelectionRange | null,\n theme?: SelectionTheme,\n respectSelectableFlag = false,\n scope?: SelectionScope | null,\n): SelectionCellChange[] {\n if (!selection) return []\n\n const { startRow, startCol, endRow, endCol } = normalizeRange(selection)\n const changes: SelectionCellChange[] = []\n\n for (let row = startRow; row <= endRow; row++) {\n let colStart = row === startRow ? startCol : 0\n let colEnd = row === endRow ? endCol : buffer.width - 1\n\n const rowMeta = buffer.getRowMeta(row)\n if (rowMeta.lastContentCol >= 0) {\n colEnd = Math.min(colEnd, rowMeta.lastContentCol)\n }\n if (colStart > colEnd) continue\n\n // Clip to contain scope on every row so selection highlight never paints\n // outside a `userSelect=\"contain\"` ancestor, even on interior rows.\n if (scope) {\n colStart = Math.max(colStart, scope.left)\n colEnd = Math.min(colEnd, scope.right)\n if (colStart > colEnd) continue\n }\n const firstContentCol = findFirstContentCol(\n buffer,\n row,\n colStart,\n colEnd,\n respectSelectableFlag,\n )\n if (firstContentCol === null) continue\n colStart = Math.max(colStart, firstContentCol)\n\n for (let col = colStart; col <= colEnd; col++) {\n // Skip continuation cells (second half of wide chars)\n if (buffer.isCellContinuation(col, row)) continue\n\n // Skip non-selectable cells when flag checking is enabled\n if (respectSelectableFlag && !buffer.isCellSelectable(col, row)) continue\n\n const cellFg = buffer.getCellFg(col, row)\n const cellBg = buffer.getCellBg(col, row)\n\n let newFg: SelectionColor\n let newBg: SelectionColor\n let inverseAttr = false\n\n if (theme?.selectionBg != null) {\n // Use theme tokens\n newFg = theme.selectionFg ?? cellFg\n newBg = theme.selectionBg\n } else if (cellFg == null || cellBg == null) {\n // Either side is default: a direct fg↔bg swap would lose the\n // visible side (e.g. cellBg=panel-color + cellFg=null → swap\n // produces fg=panel-color + bg=null, an invisible space).\n // Use SGR 7 inverse toggle so the terminal flips fg/bg at display\n // time using its own defaults for the unset side. This matches the\n // legacy `renderSelectionOverlay` (`\\x1b[7m`) behavior and renders\n // trailing-whitespace cells inside bg-colored boxes as fully\n // inversed alongside content cells.\n newFg = cellFg\n newBg = cellBg\n inverseAttr = true\n } else {\n // Both sides explicit: direct swap is safe and handles\n // already-inverted content correctly.\n newFg = cellBg\n newBg = cellFg\n }\n\n changes.push({ col, row, fg: newFg, bg: newBg, inverseAttr })\n }\n }\n\n return changes\n}\n\nfunction findFirstContentCol(\n buffer: TerminalBuffer,\n row: number,\n colStart: number,\n colEnd: number,\n respectSelectableFlag: boolean,\n): number | null {\n for (let col = colStart; col <= colEnd; col++) {\n if (buffer.isCellContinuation(col, row)) continue\n if (respectSelectableFlag && !buffer.isCellSelectable(col, row)) continue\n if (buffer.getCell(col, row).char.trim().length > 0) return col\n }\n return null\n}\n\n/**\n * Apply selection style changes to a buffer.\n *\n * Modifies the buffer in-place by setting fg/bg on affected cells.\n * Call this after the render phase and before the output phase.\n *\n * @param buffer The rendered buffer to modify\n * @param changes Cell changes from composeSelectionCells\n */\nexport function applySelectionToBuffer(\n buffer: TerminalBuffer,\n changes: SelectionCellChange[],\n): void {\n for (const change of changes) {\n const cell = buffer.getCell(change.col, change.row)\n if (change.inverseAttr) {\n // Toggle the inverse attr — terminal handles fg/bg swap at display.\n // Used when both colors are default (null) so direct swap would no-op.\n buffer.setCell(change.col, change.row, {\n ...cell,\n attrs: { ...cell.attrs, inverse: !cell.attrs.inverse },\n })\n } else {\n buffer.setCell(change.col, change.row, {\n ...cell,\n fg: normalizeSelectionColor(change.fg) ?? null,\n bg: normalizeSelectionColor(change.bg) ?? null,\n })\n }\n }\n}\n\n// Legacy `renderSelectionOverlay` (ANSI past the buffer) was deleted on\n// 2026-04-24 — see km-silvery.delete-render-selection-overlay. The visual\n// \"selection only extends, never shrinks\" bug class is structurally\n// impossible with the compose+apply pattern above: selection styling lives\n// in the buffer cells, so the output diff engine tracks lifecycle (cells\n// that were styled last frame but aren't this frame get repainted).\n\n// ============================================================================\n// Search Highlight Composition (sibling of selection composition)\n// ============================================================================\n\n/**\n * A single search-match highlight to apply on the painted buffer.\n *\n * The caller has already mapped the (scrollback-relative) match row into a\n * screen row; this struct carries the result. `startCol` and `endCol` are\n * inclusive column bounds, mirroring `SearchMatch` in `search-overlay.ts`.\n *\n * Sibling of the selection range — the rendering pipeline uses the same\n * compose+apply pattern. See `composeSearchHighlightCells`.\n */\nexport interface SearchHighlight {\n /** Screen row (0-indexed) where the highlight lands. */\n screenRow: number\n /** Inclusive start column. */\n startCol: number\n /** Inclusive end column. */\n endCol: number\n}\n\n/**\n * Compute search-highlight style changes for a list of highlight ranges.\n *\n * Returns a sparse list of cell changes (fg/bg only) — apply them with\n * `applySelectionToBuffer` (the apply step is style-source-agnostic — it\n * just sets fg/bg or toggles inverse on the cells you give it).\n *\n * Default behaviour mirrors the legacy `renderSearchHighlights` that wrote\n * `\\x1b[7m...\\x1b[27m` past the buffer: cells with default fg/bg get the\n * inverse-attr toggle (so the terminal handles the SGR 7 swap at display).\n * Cells with explicit fg/bg get a direct fg↔bg swap. A theme can override\n * with explicit `selectionFg` / `selectionBg` tokens.\n *\n * Tracking: km-silvery.delete-search-overlay-ansi\n */\nexport function composeSearchHighlightCells(\n buffer: TerminalBuffer,\n highlights: SearchHighlight[],\n theme?: SelectionTheme,\n): SelectionCellChange[] {\n if (highlights.length === 0) return []\n const changes: SelectionCellChange[] = []\n\n for (const h of highlights) {\n const row = h.screenRow\n if (row < 0 || row >= buffer.height) continue\n\n const colStart = Math.max(0, h.startCol)\n const colEnd = Math.min(buffer.width - 1, h.endCol)\n if (colStart > colEnd) continue\n\n for (let col = colStart; col <= colEnd; col++) {\n // Skip continuation cells (second half of wide chars)\n if (buffer.isCellContinuation(col, row)) continue\n\n const cellFg = buffer.getCellFg(col, row)\n const cellBg = buffer.getCellBg(col, row)\n\n let newFg: SelectionColor\n let newBg: SelectionColor\n let inverseAttr = false\n\n if (theme?.selectionBg != null) {\n newFg = theme.selectionFg ?? cellFg\n newBg = theme.selectionBg\n } else if (cellFg == null || cellBg == null) {\n // Either side is default: SGR 7 toggle so the terminal swaps using\n // its own defaults for the unset side. Direct swap would lose the\n // visible side (see same-shape comment in composeSelectionCells).\n // Matches the deleted `renderSearchHighlights` overlay's `\\x1b[7m`.\n newFg = cellFg\n newBg = cellBg\n inverseAttr = true\n } else {\n // Both sides explicit: direct swap handles already-inverted\n // content correctly.\n newFg = cellBg\n newBg = cellFg\n }\n\n changes.push({ col, row, fg: newFg, bg: newBg, inverseAttr })\n }\n }\n\n return changes\n}\n\n// Legacy `renderSearchHighlights` (ANSI past the buffer) deleted in\n// km-silvery.delete-search-overlay-ansi. Same bug class as the deleted\n// `renderSelectionOverlay`: the canonical buffer never recorded the\n// inverse styling, so when `currentMatch` moved to a different position\n// (n/N navigation, query edit) the previously-highlighted cells stayed\n// inverse on screen until something else forced a row repaint. With\n// compose+apply the highlight lives in the painted clone's cells, so the\n// diff engine tracks lifecycle correctly.\n","/**\n * Virtual scrollback buffer for storing historical rendered content.\n *\n * Used by virtual inline mode to maintain scrollable history while\n * rendering in altscreen (which normally has no scrollback).\n *\n * Implementation uses a circular buffer for O(1) push and bounded memory.\n */\n\nimport { stripAnsi } from \"./unicode\"\n\nexport interface VirtualScrollbackOptions {\n /** Maximum number of lines to store. Default: 10000 */\n maxLines?: number\n}\n\nexport interface VirtualScrollback {\n /** Push rendered lines into history */\n push(lines: string[]): void\n /** Get visible lines at a scroll offset. offset=0 = most recent (bottom) */\n getVisibleRows(offset: number, count: number): string[]\n /** Total number of lines stored */\n readonly totalLines: number\n /** Search for text across all stored lines. Returns indices of matching lines (0 = oldest). */\n search(query: string): number[]\n /** Clear all stored content */\n clear(): void\n}\n\nexport function createVirtualScrollback(options?: VirtualScrollbackOptions): VirtualScrollback {\n const maxLines = options?.maxLines ?? 10_000\n const ansiLines: string[] = new Array(maxLines)\n const plainLines: string[] = new Array(maxLines)\n let head = 0 // next write position\n let count = 0 // total lines stored (capped at maxLines)\n\n return {\n push(lines: string[]): void {\n for (const line of lines) {\n ansiLines[head] = line\n plainLines[head] = stripAnsi(line)\n head = (head + 1) % maxLines\n if (count < maxLines) count++\n }\n },\n\n getVisibleRows(offset: number, rowCount: number): string[] {\n const result: string[] = []\n for (let i = 0; i < rowCount; i++) {\n const logicalIndex = count - offset - rowCount + i\n if (logicalIndex < 0 || logicalIndex >= count) {\n result.push(\"\")\n } else {\n // Convert logical index (0=oldest) to physical position\n const physical = (head - count + logicalIndex + maxLines) % maxLines\n result.push(ansiLines[physical]!)\n }\n }\n return result\n },\n\n get totalLines(): number {\n return count\n },\n\n search(query: string): number[] {\n if (!query) return []\n const lowerQuery = query.toLowerCase()\n const matches: number[] = []\n for (let i = 0; i < count; i++) {\n // logical index i (0=oldest), convert to physical\n const physical = (head - count + i + maxLines) % maxLines\n if (plainLines[physical]!.toLowerCase().includes(lowerQuery)) {\n matches.push(i)\n }\n }\n return matches\n },\n\n clear(): void {\n head = 0\n count = 0\n },\n }\n}\n","/**\n * Ring buffer for frozen scrollback items.\n *\n * Each item represents a rendered list entry (card, row, etc.) with its\n * ANSI snapshot, split rows, and plain-text rows for searching.\n * When total rows or cached ANSI bytes exceed capacity, the oldest items are\n * evicted. The byte count tracks the serialized string payload; it is a\n * deterministic cache budget, not an exact process heap measurement.\n */\n\nimport { stripAnsi } from \"./unicode\"\n\nexport interface HistoryItem {\n key: string | number\n ansi: string\n rows: string[]\n plainTextRows: string[]\n width: number\n}\n\nexport interface HistoryBuffer {\n push(item: HistoryItem): void\n readonly totalRows: number\n readonly totalBytes: number\n readonly itemCount: number\n getRows(startRow: number, count: number): string[]\n getPlainTextRows(startRow: number, count: number): string[]\n search(query: string): number[]\n getItemAtRow(row: number): { item: HistoryItem; localRow: number } | null\n clear(): void\n readonly capacity: number\n readonly capacityBytes: number | null\n}\n\nexport interface HistoryBufferOptions {\n capacityRows?: number\n /** Maximum cached ANSI payload bytes. Omit for no byte cap. */\n capacityBytes?: number\n}\n\nexport function createHistoryItem(key: string | number, ansi: string, width: number): HistoryItem {\n const rows = ansi.split(\"\\n\")\n const plainTextRows = rows.map((r) => stripAnsi(r))\n return { key, ansi, rows, plainTextRows, width }\n}\n\nexport function createHistoryBuffer(\n options: number | HistoryBufferOptions = 10_000,\n): HistoryBuffer {\n const capacity = typeof options === \"number\" ? options : (options.capacityRows ?? 10_000)\n const capacityBytes =\n typeof options === \"number\" ? null : normalizeCapacityBytes(options.capacityBytes)\n // Store items in insertion order; evict from front when over budget.\n let items: HistoryItem[] = []\n let _totalRows = 0\n let _totalBytes = 0\n\n function evict(): void {\n while (\n items.length > 0 &&\n (_totalRows > capacity || (capacityBytes !== null && _totalBytes > capacityBytes))\n ) {\n const removed = items.shift()!\n _totalRows -= removed.rows.length\n _totalBytes -= estimateHistoryItemBytes(removed)\n }\n }\n\n /** Walk items to find which item contains the given document row. */\n function resolveRow(row: number): { itemIndex: number; localRow: number } | null {\n if (row < 0 || row >= _totalRows) return null\n let cumulative = 0\n for (let i = 0; i < items.length; i++) {\n const itemRows = items[i]!.rows.length\n if (row < cumulative + itemRows) {\n return { itemIndex: i, localRow: row - cumulative }\n }\n cumulative += itemRows\n }\n return null\n }\n\n return {\n push(item: HistoryItem): void {\n items.push(item)\n _totalRows += item.rows.length\n _totalBytes += estimateHistoryItemBytes(item)\n evict()\n },\n\n get totalRows(): number {\n return _totalRows\n },\n\n get totalBytes(): number {\n return _totalBytes\n },\n\n get itemCount(): number {\n return items.length\n },\n\n get capacity(): number {\n return capacity\n },\n\n get capacityBytes(): number | null {\n return capacityBytes\n },\n\n getRows(startRow: number, count: number): string[] {\n const result: string[] = []\n for (let r = startRow; r < startRow + count; r++) {\n const resolved = resolveRow(r)\n if (resolved) {\n result.push(items[resolved.itemIndex]!.rows[resolved.localRow]!)\n } else {\n result.push(\"\")\n }\n }\n return result\n },\n\n getPlainTextRows(startRow: number, count: number): string[] {\n const result: string[] = []\n for (let r = startRow; r < startRow + count; r++) {\n const resolved = resolveRow(r)\n if (resolved) {\n result.push(items[resolved.itemIndex]!.plainTextRows[resolved.localRow]!)\n } else {\n result.push(\"\")\n }\n }\n return result\n },\n\n search(query: string): number[] {\n if (!query) return []\n const lowerQuery = query.toLowerCase()\n const matches: number[] = []\n let rowOffset = 0\n for (const item of items) {\n for (let r = 0; r < item.plainTextRows.length; r++) {\n if (item.plainTextRows[r]!.toLowerCase().includes(lowerQuery)) {\n matches.push(rowOffset + r)\n }\n }\n rowOffset += item.rows.length\n }\n return matches\n },\n\n getItemAtRow(row: number): { item: HistoryItem; localRow: number } | null {\n const resolved = resolveRow(row)\n if (!resolved) return null\n return { item: items[resolved.itemIndex]!, localRow: resolved.localRow }\n },\n\n clear(): void {\n items = []\n _totalRows = 0\n _totalBytes = 0\n },\n }\n}\n\nfunction normalizeCapacityBytes(value: number | undefined): number | null {\n return Number.isFinite(value) && value !== undefined && value > 0 ? Math.floor(value) : null\n}\n\nconst utf8Encoder = new TextEncoder()\n\nfunction estimateHistoryItemBytes(item: HistoryItem): number {\n return utf8Encoder.encode(item.ansi).byteLength\n}\n","/**\n * Viewport Compositor - Merges frozen history rows with the live viewport.\n *\n * When a user scrolls up into history, the compositor provides the\n * history rows that should be shown. When at the tail (scrollOffset=0),\n * the live React-rendered content is shown instead.\n *\n * scrollOffset uses bottom-origin semantics: scrollOffset=N means\n * \"show rows starting at totalHistory - N from the tail\".\n *\n * This does NOT replace the React rendering pipeline. It provides\n * overlay data that the rendering layer can use when scrolled up.\n */\n\nimport type { HistoryBuffer } from \"./history-buffer\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ViewportCompositorConfig {\n /** The history buffer containing frozen items */\n history: HistoryBuffer\n /** Height of the viewport in rows */\n viewportHeight: number\n /** Current scroll offset into history (0 = at tail/live) */\n scrollOffset: number\n}\n\nexport interface ComposedViewport {\n /** History rows to overlay at top of viewport */\n overlayRows: string[]\n /** Number of top rows occupied by history */\n overlayRowCount: number\n /** Number of bottom rows for live content */\n liveRowsVisible: number\n /** Whether viewing history */\n isScrolledUp: boolean\n /** Total scrollable height */\n totalHeight: number\n}\n\n// ============================================================================\n// Compositor\n// ============================================================================\n\nexport function composeViewport(config: ViewportCompositorConfig): ComposedViewport {\n const { history, viewportHeight, scrollOffset } = config\n\n const totalHistory = history.totalRows\n\n if (scrollOffset <= 0 || totalHistory === 0) {\n return {\n overlayRows: [],\n overlayRowCount: 0,\n liveRowsVisible: viewportHeight,\n isScrolledUp: false,\n totalHeight: totalHistory + viewportHeight,\n }\n }\n\n // scrollOffset from tail: show rows starting at (totalHistory - clampedOffset)\n const clampedOffset = Math.min(scrollOffset, totalHistory)\n const startRow = Math.max(0, totalHistory - clampedOffset)\n const rowsToShow = Math.min(viewportHeight, totalHistory - startRow)\n const overlayRows = history.getRows(startRow, rowsToShow)\n const liveRowsVisible = viewportHeight - rowsToShow\n\n return {\n overlayRows,\n overlayRowCount: rowsToShow,\n liveRowsVisible,\n isScrolledUp: true,\n totalHeight: totalHistory + viewportHeight,\n }\n}\n","/**\n * Canonical row model spanning frozen history + live content.\n *\n * Frozen rows (from HistoryBuffer) occupy rows 0..frozenRows-1.\n * Live rows follow at frozenRows..totalRows-1.\n * All row indices are document-global.\n */\n\nimport type { HistoryBuffer } from \"./history-buffer\"\nimport type { SearchMatch } from \"./search-overlay\"\n\nexport interface LiveItemBlock {\n key: string | number\n itemIndex: number\n rows: string[]\n plainTextRows: string[]\n}\n\nexport interface DocumentSource {\n type: \"frozen\" | \"live\"\n itemKey?: string | number\n itemIndex?: number\n localRow: number\n}\n\nexport interface ListDocument {\n readonly totalRows: number\n readonly frozenRows: number\n readonly liveRows: number\n getRows(startRow: number, count: number): string[]\n getPlainTextRows(startRow: number, count: number): string[]\n getSource(row: number): DocumentSource | null\n search(query: string): SearchMatch[]\n}\n\nexport function createListDocument(\n history: HistoryBuffer,\n getLiveItems: () => LiveItemBlock[],\n): ListDocument {\n function liveRowCount(): number {\n let total = 0\n for (const block of getLiveItems()) {\n total += block.rows.length\n }\n return total\n }\n\n return {\n get totalRows(): number {\n return history.totalRows + liveRowCount()\n },\n\n get frozenRows(): number {\n return history.totalRows\n },\n\n get liveRows(): number {\n return liveRowCount()\n },\n\n getRows(startRow: number, count: number): string[] {\n const frozen = history.totalRows\n const result: string[] = []\n for (let r = startRow; r < startRow + count; r++) {\n if (r < 0 || r >= this.totalRows) {\n result.push(\"\")\n } else if (r < frozen) {\n result.push(...history.getRows(r, 1))\n } else {\n let liveRow = r - frozen\n let found = false\n for (const block of getLiveItems()) {\n if (liveRow < block.rows.length) {\n result.push(block.rows[liveRow]!)\n found = true\n break\n }\n liveRow -= block.rows.length\n }\n if (!found) result.push(\"\")\n }\n }\n return result\n },\n\n getPlainTextRows(startRow: number, count: number): string[] {\n const frozen = history.totalRows\n const result: string[] = []\n for (let r = startRow; r < startRow + count; r++) {\n if (r < 0 || r >= this.totalRows) {\n result.push(\"\")\n } else if (r < frozen) {\n result.push(...history.getPlainTextRows(r, 1))\n } else {\n let liveRow = r - frozen\n let found = false\n for (const block of getLiveItems()) {\n if (liveRow < block.plainTextRows.length) {\n result.push(block.plainTextRows[liveRow]!)\n found = true\n break\n }\n liveRow -= block.plainTextRows.length\n }\n if (!found) result.push(\"\")\n }\n }\n return result\n },\n\n getSource(row: number): DocumentSource | null {\n const frozen = history.totalRows\n if (row < 0 || row >= this.totalRows) return null\n if (row < frozen) {\n const hit = history.getItemAtRow(row)\n if (!hit) return null\n return {\n type: \"frozen\",\n itemKey: hit.item.key,\n localRow: hit.localRow,\n }\n }\n // Live: walk item blocks\n const liveItems = getLiveItems()\n let liveRow = row - frozen\n for (const block of liveItems) {\n if (liveRow < block.rows.length) {\n return { type: \"live\", itemIndex: block.itemIndex, localRow: liveRow }\n }\n liveRow -= block.rows.length\n }\n return null\n },\n\n search(query: string): SearchMatch[] {\n if (!query) return []\n const lowerQuery = query.toLowerCase()\n const matches: SearchMatch[] = []\n const frozen = history.totalRows\n\n // Search frozen rows\n const frozenRowMatches = history.search(query)\n for (const row of frozenRowMatches) {\n const plainRows = history.getPlainTextRows(row, 1)\n const line = plainRows[0]!.toLowerCase()\n let pos = line.indexOf(lowerQuery)\n while (pos !== -1) {\n matches.push({ row, startCol: pos, endCol: pos + query.length })\n pos = line.indexOf(lowerQuery, pos + 1)\n }\n }\n\n // Search live rows (walk item blocks)\n let rowOffset = 0\n for (const block of getLiveItems()) {\n for (let i = 0; i < block.plainTextRows.length; i++) {\n const line = block.plainTextRows[i]!.toLowerCase()\n let pos = line.indexOf(lowerQuery)\n while (pos !== -1) {\n matches.push({ row: frozen + rowOffset + i, startCol: pos, endCol: pos + query.length })\n pos = line.indexOf(lowerQuery, pos + 1)\n }\n }\n rowOffset += block.plainTextRows.length\n }\n\n return matches\n },\n }\n}\n","/**\n * Read/query facade over a ListDocument.\n *\n * Provides text extraction, search, hit-testing, and reveal for\n * pane-based scrollback UIs. Pure data — no React, no rendering.\n */\n\nimport type { ListDocument } from \"./list-document\"\nimport type { SearchMatch } from \"./search-overlay\"\nimport { stripAnsi } from \"./unicode\"\n\nexport interface SurfaceCapabilities {\n paneSafe: boolean\n searchableHistory: boolean\n selectableHistory: boolean\n overlayHistory: boolean\n}\n\nexport interface TextSurface {\n readonly id: string\n readonly document: ListDocument\n getText(startRow: number, startCol: number, endRow: number, endCol: number): string\n search(query: string): SearchMatch[]\n hitTest(viewportRow: number, viewportCol: number): { row: number; col: number } | null\n reveal(row: number): void\n /** Notify subscribers that content has changed (search results may be stale) */\n notifyContentChange(): void\n subscribe(listener: () => void): () => void\n readonly capabilities: SurfaceCapabilities\n}\n\nexport function createTextSurface(config: {\n id: string\n document: ListDocument\n viewportToDocument: (viewportRow: number) => number\n onReveal: (documentRow: number) => void\n capabilities: SurfaceCapabilities\n}): TextSurface {\n const listeners = new Set<() => void>()\n\n function notify(): void {\n for (const fn of listeners) fn()\n }\n\n return {\n get id(): string {\n return config.id\n },\n\n get document(): ListDocument {\n return config.document\n },\n\n getText(startRow: number, startCol: number, endRow: number, endCol: number): string {\n const rows = config.document.getRows(startRow, endRow - startRow + 1)\n const lines: string[] = []\n for (let i = 0; i < rows.length; i++) {\n const plain = stripAnsi(rows[i]!)\n const row = startRow + i\n if (row === startRow && row === endRow) {\n lines.push(plain.slice(startCol, endCol))\n } else if (row === startRow) {\n lines.push(plain.slice(startCol))\n } else if (row === endRow) {\n lines.push(plain.slice(0, endCol))\n } else {\n lines.push(plain)\n }\n }\n return lines.join(\"\\n\")\n },\n\n search(query: string): SearchMatch[] {\n return config.document.search(query)\n },\n\n hitTest(viewportRow: number, viewportCol: number): { row: number; col: number } | null {\n const docRow = config.viewportToDocument(viewportRow)\n if (docRow < 0 || docRow >= config.document.totalRows) return null\n return { row: docRow, col: viewportCol }\n },\n\n reveal(row: number): void {\n config.onReveal(row)\n notify()\n },\n\n notifyContentChange(): void {\n notify()\n },\n\n subscribe(listener: () => void): () => void {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n },\n\n get capabilities(): SurfaceCapabilities {\n return config.capabilities\n },\n }\n}\n","/**\n * Search overlay state machine for virtual inline mode.\n *\n * Pure TEA: (action, state) -> [state, effects[]]\n * Provides incremental search-as-you-type with match navigation.\n */\n\nexport interface SearchMatch {\n row: number\n startCol: number\n endCol: number\n}\n\n/**\n * A single contiguous character range within a string that matched a search\n * query. `start` is inclusive, `end` is exclusive — same convention as\n * `String.prototype.slice(start, end)`.\n */\nexport interface MatchRange {\n start: number\n end: number\n}\n\n/**\n * Find all case-insensitive occurrences of `query` inside `text` and return\n * their character offsets. Empty query or empty text returns `[]`.\n *\n * This is the canonical silvery search-match algorithm. The same logic runs\n * inside `ListView`'s registered Searchable (to find matches across items)\n * and is exposed to consumers that render multi-segment items — a LogRow\n * whose searchable text is a concatenation of field values, but whose visual\n * rendering splits those fields across separate Text nodes, needs per-segment\n * ranges to highlight without re-implementing the semantics.\n *\n * Ranges are returned in ascending `start` order; overlapping matches are\n * not produced (`indexOf(..., start = last.start + 1)` advances past each\n * match start, not past its full length, preserving overlapping runs of\n * short queries — e.g. `\"aa\"` in `\"aaaa\"` yields `[0..2], [1..3], [2..4]`).\n *\n * Offsets are CHARACTER offsets into the input string. They are not column\n * offsets — multi-column wide glyphs are counted as one character here, the\n * same way the SearchProvider's match-row computation does.\n */\nexport function computeMatchRanges(text: string, query: string): MatchRange[] {\n if (query === \"\" || text === \"\") return []\n const lowerText = text.toLowerCase()\n const lowerQuery = query.toLowerCase()\n const ranges: MatchRange[] = []\n let cursor = 0\n while (cursor < lowerText.length) {\n const found = lowerText.indexOf(lowerQuery, cursor)\n if (found === -1) break\n ranges.push({ start: found, end: found + query.length })\n cursor = found + 1\n }\n return ranges\n}\n\nexport interface SearchState {\n active: boolean\n query: string\n matches: SearchMatch[]\n currentMatch: number // Index into matches, -1 when no matches\n cursorPosition: number // Cursor position within query string\n}\n\nexport type SearchAction =\n | { type: \"open\" }\n | { type: \"close\" }\n | { type: \"input\"; char: string }\n | { type: \"backspace\" }\n | { type: \"nextMatch\" } // Enter\n | { type: \"prevMatch\" } // Shift+Enter\n | { type: \"cursorLeft\" }\n | { type: \"cursorRight\" }\n | { type: \"cursorToStart\" } // Ctrl+A / Home\n | { type: \"cursorToEnd\" } // Ctrl+E / End\n | { type: \"deleteWordBack\" } // Ctrl+W / Alt+Backspace\n | { type: \"deleteToStart\" } // Ctrl+U\n\nexport type SearchEffect = { type: \"scrollTo\"; row: number } | { type: \"render\" }\n\nexport function createSearchState(): SearchState {\n return {\n active: false,\n query: \"\",\n matches: [],\n currentMatch: -1,\n cursorPosition: 0,\n }\n}\n\n/**\n * Update search state. searchFn is called when query changes to find matches.\n */\nexport function searchUpdate(\n action: SearchAction,\n state: SearchState,\n searchFn?: (query: string) => SearchMatch[],\n): [SearchState, SearchEffect[]] {\n switch (action.type) {\n case \"open\":\n return [\n { ...state, active: true, query: \"\", matches: [], currentMatch: -1, cursorPosition: 0 },\n [{ type: \"render\" }],\n ]\n\n case \"close\":\n // Close the bar but KEEP the results. `n` / `N` (next/prev) must\n // still cycle through matches after the bar closes — that's the\n // whole point of 'search then step through' (less / vim idiom).\n // A subsequent `open` starts a fresh query and wipes matches.\n return [{ ...state, active: false, query: \"\", cursorPosition: 0 }, [{ type: \"render\" }]]\n\n case \"input\": {\n const query =\n state.query.slice(0, state.cursorPosition) +\n action.char +\n state.query.slice(state.cursorPosition)\n const cursorPosition = state.cursorPosition + 1\n const matches = searchFn ? searchFn(query) : []\n const currentMatch = matches.length > 0 ? 0 : -1\n const effects: SearchEffect[] = [{ type: \"render\" }]\n if (currentMatch >= 0) {\n effects.push({ type: \"scrollTo\", row: matches[0]!.row })\n }\n return [{ ...state, query, cursorPosition, matches, currentMatch }, effects]\n }\n\n case \"backspace\": {\n if (state.cursorPosition === 0) return [state, []]\n const query =\n state.query.slice(0, state.cursorPosition - 1) + state.query.slice(state.cursorPosition)\n const cursorPosition = state.cursorPosition - 1\n const matches = searchFn ? searchFn(query) : []\n const currentMatch = matches.length > 0 ? 0 : -1\n const effects: SearchEffect[] = [{ type: \"render\" }]\n if (currentMatch >= 0) {\n effects.push({ type: \"scrollTo\", row: matches[0]!.row })\n }\n return [{ ...state, query, cursorPosition, matches, currentMatch }, effects]\n }\n\n case \"nextMatch\": {\n if (state.matches.length === 0) return [state, []]\n const currentMatch = (state.currentMatch + 1) % state.matches.length\n return [\n { ...state, currentMatch },\n [{ type: \"scrollTo\", row: state.matches[currentMatch]!.row }, { type: \"render\" }],\n ]\n }\n\n case \"prevMatch\": {\n if (state.matches.length === 0) return [state, []]\n const currentMatch = (state.currentMatch - 1 + state.matches.length) % state.matches.length\n return [\n { ...state, currentMatch },\n [{ type: \"scrollTo\", row: state.matches[currentMatch]!.row }, { type: \"render\" }],\n ]\n }\n\n case \"cursorLeft\":\n if (state.cursorPosition === 0) return [state, []]\n return [{ ...state, cursorPosition: state.cursorPosition - 1 }, []]\n\n case \"cursorRight\":\n if (state.cursorPosition >= state.query.length) return [state, []]\n return [{ ...state, cursorPosition: state.cursorPosition + 1 }, []]\n\n case \"cursorToStart\":\n if (state.cursorPosition === 0) return [state, []]\n return [{ ...state, cursorPosition: 0 }, []]\n\n case \"cursorToEnd\":\n if (state.cursorPosition >= state.query.length) return [state, []]\n return [{ ...state, cursorPosition: state.query.length }, []]\n\n case \"deleteWordBack\": {\n if (state.cursorPosition === 0) return [state, []]\n // Find the start of the previous word (skip trailing whitespace, then non-whitespace)\n let pos = state.cursorPosition\n while (pos > 0 && /\\s/.test(state.query[pos - 1] ?? \"\")) pos--\n while (pos > 0 && !/\\s/.test(state.query[pos - 1] ?? \"\")) pos--\n const query = state.query.slice(0, pos) + state.query.slice(state.cursorPosition)\n const cursorPosition = pos\n const matches = searchFn ? searchFn(query) : []\n const currentMatch = matches.length > 0 ? 0 : -1\n const effects: SearchEffect[] = [{ type: \"render\" }]\n if (currentMatch >= 0) {\n effects.push({ type: \"scrollTo\", row: matches[0]!.row })\n }\n return [{ ...state, query, cursorPosition, matches, currentMatch }, effects]\n }\n\n case \"deleteToStart\": {\n if (state.cursorPosition === 0) return [state, []]\n const query = state.query.slice(state.cursorPosition)\n const cursorPosition = 0\n const matches = searchFn ? searchFn(query) : []\n const currentMatch = matches.length > 0 ? 0 : -1\n const effects: SearchEffect[] = [{ type: \"render\" }]\n if (currentMatch >= 0) {\n effects.push({ type: \"scrollTo\", row: matches[0]!.row })\n }\n return [{ ...state, query, cursorPosition, matches, currentMatch }, effects]\n }\n }\n}\n\n/**\n * Render the search bar as a plain (no ANSI) string padded to `cols` width.\n * Format: \" / query [2/15]\" or \" / query [no matches]\" — padded with spaces.\n *\n * Caller decides how to display it — wrap with `\\x1b[7m...\\x1b[27m` for the\n * legacy overlay path, or stamp into a buffer's cells with the inverse\n * attribute for the compose+apply path\n * (see `applySearchBarToPaintBuffer` in `runtime/renderer.ts`).\n */\nexport function renderSearchBarPlain(state: SearchState, cols: number): string {\n const prefix = \" / \"\n const matchInfo =\n state.matches.length > 0\n ? ` [${state.currentMatch + 1}/${state.matches.length}]`\n : state.query\n ? \" [no matches]\"\n : \"\"\n\n const content = prefix + state.query + matchInfo\n return content.padEnd(cols)\n}\n\n/**\n * Render the search bar as an ANSI string with inverse video wrapping.\n * Format: \" / query [2/15]\" or \" / query [no matches]\"\n *\n * Kept as a self-contained \"give me the bar's ANSI representation\" helper\n * for tests and ad-hoc callers. The runtime paint path uses\n * `renderSearchBarPlain` + `applySearchBarToPaintBuffer` (see\n * `runtime/renderer.ts`) — buffer cells with the inverse attribute, not\n * raw `\\x1b[7m...\\x1b[27m` past the buffer. Don't add new runtime callers\n * of this function — they will reintroduce the\n * km-silvery.delete-search-overlay-ansi bug class.\n */\nexport function renderSearchBar(state: SearchState, cols: number): string {\n // Inverse video: ESC[7m ... ESC[27m\n return `\\x1b[7m${renderSearchBarPlain(state, cols)}\\x1b[27m`\n}\n"],"mappings":"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"}