silvery 0.19.2 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/README.md +9 -4
  2. package/dist/Text-Lq0dmj8-.mjs +239 -0
  3. package/dist/Text-Lq0dmj8-.mjs.map +1 -0
  4. package/dist/UPNG-Bo33r8rA.mjs +3 -0
  5. package/dist/UPNG-DosRPdF4.mjs +5075 -0
  6. package/dist/UPNG-DosRPdF4.mjs.map +1 -0
  7. package/dist/__vite-browser-external-2447137e-D_JM6skp.mjs +6 -0
  8. package/dist/__vite-browser-external-2447137e-D_JM6skp.mjs.map +1 -0
  9. package/dist/{animation-Cn64yepo.mjs → animation-ZMN2_XKv.mjs} +2 -2
  10. package/dist/animation-ZMN2_XKv.mjs.map +1 -0
  11. package/dist/{ansi-Cc33mW54.d.mts → ansi-2Xn0yatP.d.mts} +1 -1
  12. package/dist/{ansi-Cc33mW54.d.mts.map → ansi-2Xn0yatP.d.mts.map} +1 -1
  13. package/dist/{ansi-CLOitHKx.mjs → ansi-D1KQMAbf.mjs} +1 -1
  14. package/dist/{ansi-CLOitHKx.mjs.map → ansi-D1KQMAbf.mjs.map} +1 -1
  15. package/dist/ansi-yC4RyBNY.mjs +22441 -0
  16. package/dist/ansi-yC4RyBNY.mjs.map +1 -0
  17. package/dist/apng-CR08rIaH.mjs +58 -0
  18. package/dist/apng-CR08rIaH.mjs.map +1 -0
  19. package/dist/apng-DaHfVaVI.mjs +3 -0
  20. package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
  21. package/dist/assets/skia.darwin-arm64-DQs5sT6N.node +0 -0
  22. package/dist/backend-B-WYLUib.mjs +13396 -0
  23. package/dist/backend-B-WYLUib.mjs.map +1 -0
  24. package/dist/backends-CUtan80W.mjs +3 -0
  25. package/dist/backends-DIVYzKqd.mjs +1083 -0
  26. package/dist/backends-DIVYzKqd.mjs.map +1 -0
  27. package/dist/bound-term-0sPrrzH1.d.mts +4640 -0
  28. package/dist/bound-term-0sPrrzH1.d.mts.map +1 -0
  29. package/dist/canvas-1v7dPT-_.mjs +3 -0
  30. package/dist/canvas-CSuPOMNt.mjs +1442 -0
  31. package/dist/canvas-CSuPOMNt.mjs.map +1 -0
  32. package/dist/{chunk-Vs_PY4HZ.mjs → chunk-BSw8zbkd.mjs} +1 -1
  33. package/dist/cli-dvo0r2fs.mjs +4 -0
  34. package/dist/compare-CQodSH4G.mjs +376 -0
  35. package/dist/compare-CQodSH4G.mjs.map +1 -0
  36. package/dist/compare-DHlcxEYA.mjs +3 -0
  37. package/dist/context-BU5LkkIy.mjs.map +1 -1
  38. package/dist/devtools-CJdt5H0X.mjs +2 -0
  39. package/dist/{devtools-DxkSLXDA.mjs → devtools-DcQjgyjL.mjs} +5 -4
  40. package/dist/{devtools-DxkSLXDA.mjs.map → devtools-DcQjgyjL.mjs.map} +1 -1
  41. package/dist/easing-BI-ASGMO.d.mts +24 -0
  42. package/dist/easing-BI-ASGMO.d.mts.map +1 -0
  43. package/dist/{eta-Bb3RH3wh.mjs → eta-CJlGH06n.mjs} +1 -1
  44. package/dist/{eta-Bb3RH3wh.mjs.map → eta-CJlGH06n.mjs.map} +1 -1
  45. package/dist/flexily-zero-adapter-C3Vj0fPt.mjs +306 -0
  46. package/dist/flexily-zero-adapter-C3Vj0fPt.mjs.map +1 -0
  47. package/dist/{flexily-zero-adapter-CMxXhdOL.mjs → flexily-zero-adapter-C4lW_Ov5.mjs} +1 -1
  48. package/dist/fonts-BFmhXDv7.mjs +88 -0
  49. package/dist/fonts-BFmhXDv7.mjs.map +1 -0
  50. package/dist/gif-C_AjaT9d.mjs +188 -0
  51. package/dist/gif-C_AjaT9d.mjs.map +1 -0
  52. package/dist/gif-DaC4XrxA.mjs +3 -0
  53. package/dist/gifenc-BOUT-KFB.mjs +730 -0
  54. package/dist/gifenc-BOUT-KFB.mjs.map +1 -0
  55. package/dist/image-C2Birh2x.mjs +1252 -0
  56. package/dist/image-C2Birh2x.mjs.map +1 -0
  57. package/dist/index-BUMxS65f.d.mts +453 -0
  58. package/dist/index-BUMxS65f.d.mts.map +1 -0
  59. package/dist/{index-D3saHouR.d.mts → index-CSQf13CI.d.mts} +1057 -1133
  60. package/dist/index-CSQf13CI.d.mts.map +1 -0
  61. package/dist/{index-BXslOebb.d.mts → index-Cl9KKjQ_.d.mts} +4919 -3921
  62. package/dist/index-Cl9KKjQ_.d.mts.map +1 -0
  63. package/dist/index-XbNrPhWl.d.mts +336 -0
  64. package/dist/index-XbNrPhWl.d.mts.map +1 -0
  65. package/dist/index.d.mts +8 -5
  66. package/dist/index.d.mts.map +1 -1
  67. package/dist/index.mjs +14 -12
  68. package/dist/index.mjs.map +1 -1
  69. package/dist/key-mapping-CS-YD_cD.mjs +132 -0
  70. package/dist/key-mapping-CS-YD_cD.mjs.map +1 -0
  71. package/dist/key-mapping-Yn-Jgrij.mjs +3 -0
  72. package/dist/{layout-engine-B6Cdz1yZ.mjs → layout-engine-C07LEXWT.mjs} +1 -1
  73. package/dist/layout-engine-C2px0RJE.mjs +67 -0
  74. package/dist/layout-engine-C2px0RJE.mjs.map +1 -0
  75. package/dist/layout-signals-Cnw6xk8Q.mjs +988 -0
  76. package/dist/layout-signals-Cnw6xk8Q.mjs.map +1 -0
  77. package/dist/mouse-events-Dki3ISIp.mjs +1044 -0
  78. package/dist/mouse-events-Dki3ISIp.mjs.map +1 -0
  79. package/dist/{multi-progress-Bq9Oi_WI.mjs → multi-progress-CIRjrzma.mjs} +3 -3
  80. package/dist/{multi-progress-Bq9Oi_WI.mjs.map → multi-progress-CIRjrzma.mjs.map} +1 -1
  81. package/dist/{multi-progress-DAQC7eap.d.mts → multi-progress-DHZ2xUT2.d.mts} +2 -2
  82. package/dist/{multi-progress-DAQC7eap.d.mts.map → multi-progress-DHZ2xUT2.d.mts.map} +1 -1
  83. package/dist/{node-BeWlnCPY.mjs → node-CjM5Rt-M.mjs} +4 -4
  84. package/dist/node-CjM5Rt-M.mjs.map +1 -0
  85. package/dist/playwright-D5YiZcNS.mjs +76397 -0
  86. package/dist/playwright-D5YiZcNS.mjs.map +1 -0
  87. package/dist/png-codec-Dp84742B.mjs +36 -0
  88. package/dist/png-codec-Dp84742B.mjs.map +1 -0
  89. package/dist/png-codec-QwOtJ8Zs.mjs +3 -0
  90. package/dist/progress-DB_Xo071.mjs +675 -0
  91. package/dist/progress-DB_Xo071.mjs.map +1 -0
  92. package/dist/{progress-bar-CXE5Qfkd.mjs → progress-bar-oJwq22CR.mjs} +4 -4
  93. package/dist/{progress-bar-CXE5Qfkd.mjs.map → progress-bar-oJwq22CR.mjs.map} +1 -1
  94. package/dist/rasterizer-BRXrDdWx.mjs +3 -0
  95. package/dist/rasterizer-CpEhJvdR.mjs +296 -0
  96. package/dist/rasterizer-CpEhJvdR.mjs.map +1 -0
  97. package/dist/reconciler-DldIJB93.mjs +2083 -0
  98. package/dist/reconciler-DldIJB93.mjs.map +1 -0
  99. package/dist/{render-string-CDCeYkS3.mjs → render-string-BcoCpjCB.mjs} +1 -1
  100. package/dist/{render-string-Darrg7ku.mjs → render-string-DkQacASz.mjs} +2707 -549
  101. package/dist/render-string-DkQacASz.mjs.map +1 -0
  102. package/dist/resvg-js-DkOndZI3.mjs +203 -0
  103. package/dist/resvg-js-DkOndZI3.mjs.map +1 -0
  104. package/dist/runtime.d.mts +3 -2
  105. package/dist/runtime.mjs +3 -3
  106. package/dist/schemes-JjNp4aSl.mjs +2611 -0
  107. package/dist/schemes-JjNp4aSl.mjs.map +1 -0
  108. package/dist/{spinner-CGo34vyR.d.mts → spinner-CZINHpkV.d.mts} +2 -2
  109. package/dist/{spinner-CGo34vyR.d.mts.map → spinner-CZINHpkV.d.mts.map} +1 -1
  110. package/dist/{spinner-CeOmcuw_.mjs → spinner-D9lrHr8s.mjs} +7 -7
  111. package/dist/spinner-D9lrHr8s.mjs.map +1 -0
  112. package/dist/src-5w9QR6_8.mjs +1071 -0
  113. package/dist/src-5w9QR6_8.mjs.map +1 -0
  114. package/dist/src-BNTToU7l.mjs +4387 -0
  115. package/dist/src-BNTToU7l.mjs.map +1 -0
  116. package/dist/{src-CF-6UN01.mjs → src-BR4xNwdG.mjs} +10436 -2622
  117. package/dist/src-BR4xNwdG.mjs.map +1 -0
  118. package/dist/{types-Bk2yw9Qj.mjs → src-DKp-_OFG.mjs} +34 -94
  119. package/dist/src-DKp-_OFG.mjs.map +1 -0
  120. package/dist/src-bt8wSrfJ.mjs +258 -0
  121. package/dist/src-bt8wSrfJ.mjs.map +1 -0
  122. package/dist/src-e33Y6kNJ.mjs +3 -0
  123. package/dist/src-iDwu25UD.mjs +1814 -0
  124. package/dist/src-iDwu25UD.mjs.map +1 -0
  125. package/dist/steps-Bp2uNqnn.d.mts +202 -0
  126. package/dist/steps-Bp2uNqnn.d.mts.map +1 -0
  127. package/dist/svg-15lZZzxq.mjs +486 -0
  128. package/dist/svg-15lZZzxq.mjs.map +1 -0
  129. package/dist/svg-Cz0UXcDj.mjs +255 -0
  130. package/dist/svg-Cz0UXcDj.mjs.map +1 -0
  131. package/dist/svg-DY72a4HK.mjs +3 -0
  132. package/dist/svg-g1D6ErwR.d.mts +82 -0
  133. package/dist/svg-g1D6ErwR.d.mts.map +1 -0
  134. package/dist/term.d.mts +3 -0
  135. package/dist/term.mjs +9 -0
  136. package/dist/term.mjs.map +1 -0
  137. package/dist/theme.d.mts +95 -2
  138. package/dist/theme.d.mts.map +1 -0
  139. package/dist/theme.mjs +9 -3
  140. package/dist/theme.mjs.map +1 -0
  141. package/dist/{types-BH_v3iMT.d.mts → types-kt_fKR37.d.mts} +2 -15
  142. package/dist/types-kt_fKR37.d.mts.map +1 -0
  143. package/dist/ui/animation.d.mts +2 -1
  144. package/dist/ui/animation.mjs +1 -1
  145. package/dist/ui/ansi.d.mts +1 -1
  146. package/dist/ui/ansi.mjs +1 -1
  147. package/dist/ui/cli.d.mts +3 -3
  148. package/dist/ui/cli.mjs +5 -5
  149. package/dist/ui/display.d.mts +1 -1
  150. package/dist/ui/image.d.mts +2 -2
  151. package/dist/ui/image.mjs +2 -2
  152. package/dist/ui/input.d.mts +1 -1
  153. package/dist/ui/input.mjs +4 -2
  154. package/dist/ui/input.mjs.map +1 -1
  155. package/dist/ui/progress.d.mts +5 -249
  156. package/dist/ui/progress.mjs +5 -858
  157. package/dist/ui/react.d.mts +1 -1
  158. package/dist/ui/react.mjs +2 -2
  159. package/dist/ui/recording-chrome-react.d.mts +21 -0
  160. package/dist/ui/recording-chrome-react.d.mts.map +1 -0
  161. package/dist/ui/recording-chrome-react.mjs +105 -0
  162. package/dist/ui/recording-chrome-react.mjs.map +1 -0
  163. package/dist/ui/recording-chrome.d.mts +2 -0
  164. package/dist/ui/recording-chrome.mjs +2 -0
  165. package/dist/ui/utils.mjs +1 -1
  166. package/dist/ui/wrappers.d.mts +3 -3
  167. package/dist/ui/wrappers.mjs +2 -2
  168. package/dist/ui.d.mts +7 -6
  169. package/dist/ui.mjs +8 -7
  170. package/dist/{useLatest-Bg2x4bfP.d.mts → useLatest-DRDDVwjh.d.mts} +5 -25
  171. package/dist/useLatest-DRDDVwjh.d.mts.map +1 -0
  172. package/dist/{with-text-input-CRfoiFFG.d.mts → with-text-input-YeohVLeo.d.mts} +4 -55
  173. package/dist/with-text-input-YeohVLeo.d.mts.map +1 -0
  174. package/dist/wrapper-C70ATkVv.mjs +3527 -0
  175. package/dist/wrapper-C70ATkVv.mjs.map +1 -0
  176. package/dist/{wrappers-UTADQkSY.mjs → wrappers-BCUYITrY.mjs} +5 -157
  177. package/dist/wrappers-BCUYITrY.mjs.map +1 -0
  178. package/dist/{yoga-adapter-8oRGRw8V.mjs → yoga-adapter-BnZX1PAY.mjs} +28 -2
  179. package/dist/yoga-adapter-BnZX1PAY.mjs.map +1 -0
  180. package/dist/yoga-adapter-DxgsQ_gg.mjs +2 -0
  181. package/dist/zipBundle-3nqeDRtm.mjs +3 -0
  182. package/dist/zipBundle-VNAYFmqJ.mjs +2003 -0
  183. package/dist/zipBundle-VNAYFmqJ.mjs.map +1 -0
  184. package/package.json +20 -9
  185. package/dist/animation-Cn64yepo.mjs.map +0 -1
  186. package/dist/cli-BKp0YtBD.mjs +0 -4
  187. package/dist/devtools-9QY4teqI.mjs +0 -2
  188. package/dist/flexily-zero-adapter-BlQa46nr.mjs +0 -3385
  189. package/dist/flexily-zero-adapter-BlQa46nr.mjs.map +0 -1
  190. package/dist/image-CTII5QWI.mjs +0 -477
  191. package/dist/image-CTII5QWI.mjs.map +0 -1
  192. package/dist/index-BXslOebb.d.mts.map +0 -1
  193. package/dist/index-BnA7mNpo.d.mts +0 -175
  194. package/dist/index-BnA7mNpo.d.mts.map +0 -1
  195. package/dist/index-D3saHouR.d.mts.map +0 -1
  196. package/dist/layout-engine-ClUgv6jB.mjs +0 -50
  197. package/dist/layout-engine-ClUgv6jB.mjs.map +0 -1
  198. package/dist/node-BeWlnCPY.mjs.map +0 -1
  199. package/dist/reconciler-Cwgm8hRR.mjs +0 -8459
  200. package/dist/reconciler-Cwgm8hRR.mjs.map +0 -1
  201. package/dist/render-string-Darrg7ku.mjs.map +0 -1
  202. package/dist/spinner-CeOmcuw_.mjs.map +0 -1
  203. package/dist/src-B5GjfG7g.mjs +0 -4305
  204. package/dist/src-B5GjfG7g.mjs.map +0 -1
  205. package/dist/src-CChwjk0Z.mjs +0 -738
  206. package/dist/src-CChwjk0Z.mjs.map +0 -1
  207. package/dist/src-CF-6UN01.mjs.map +0 -1
  208. package/dist/src-NCKb8kE5.mjs +0 -2660
  209. package/dist/src-NCKb8kE5.mjs.map +0 -1
  210. package/dist/types-BH_v3iMT.d.mts.map +0 -1
  211. package/dist/types-Bk2yw9Qj.mjs.map +0 -1
  212. package/dist/ui/progress.d.mts.map +0 -1
  213. package/dist/ui/progress.mjs.map +0 -1
  214. package/dist/useLatest-Bg2x4bfP.d.mts.map +0 -1
  215. package/dist/with-text-input-CRfoiFFG.d.mts.map +0 -1
  216. package/dist/wrappers-UTADQkSY.mjs.map +0 -1
  217. package/dist/yoga-adapter-8oRGRw8V.mjs.map +0 -1
  218. package/dist/yoga-adapter-D_CcxSt5.mjs +0 -2
@@ -0,0 +1,4640 @@
1
+ import { M as RGB$1, N as UnderlineStyle$2, P as TerminalCaps, j as ColorLevel, mt as Theme, yt as TerminalEmulator, z as TerminalProfile } from "./index-CSQf13CI.mjs";
2
+ import * as _$react from "react";
3
+
4
+ //#region packages/ag/src/viewport-types.d.ts
5
+ /**
6
+ * Read-only cell-grid view — the source-of-truth for a Viewport's painted
7
+ * content. Written by a {@link ForeignSource}, blitted into the parent buffer
8
+ * at output-phase time.
9
+ *
10
+ * Structurally a read-only subset of the silvery `TerminalBuffer` but with
11
+ * `cols`/`rows` naming (matching {@link ViewportProps}) instead of
12
+ * `width`/`height`. The Cell shape is reused verbatim from `@silvery/ag` so
13
+ * sources can construct buffers without depending on a separate cell vocabulary.
14
+ */
15
+ interface CellBuffer {
16
+ readonly cols: number;
17
+ readonly rows: number;
18
+ /** Read a single cell at Viewport-local `(col, row)`. */
19
+ getCell(col: number, row: number): Cell$1;
20
+ }
21
+ /**
22
+ * Rectangle within a Viewport's cell grid. Origin `(0, 0)` is the top-left
23
+ * cell of the Viewport's content area — NOT an absolute terminal coordinate.
24
+ *
25
+ * Kept distinct from the global {@link Rect} so the pipeline can translate
26
+ * Viewport-local rects to absolute cells at blit time without ambiguity.
27
+ *
28
+ * Field naming uses `row`/`col` (not `x`/`y`) for the same reason: the global
29
+ * Rect uses Cartesian terminology; cells are addressed in (row, col) order
30
+ * across the rest of the cell-buffer surface.
31
+ */
32
+ interface ViewportRect {
33
+ readonly row: number;
34
+ readonly col: number;
35
+ readonly width: number;
36
+ readonly height: number;
37
+ }
38
+ /**
39
+ * Viewport-internal cursor style hint. The Viewport's cursor is painted INTO
40
+ * its cells (the source decides where), then composited into the parent
41
+ * frame. Independent of the silvery host cursor that lives in
42
+ * {@link LayoutSignals}.
43
+ */
44
+ type ViewportCursorStyle = "block" | "underline" | "bar";
45
+ /**
46
+ * Input mode requested by a {@link ForeignSource}. The parent owns global
47
+ * terminal modes (mouse-tracking SGR, raw mode, bracketed paste, focus
48
+ * reporting) and multiplexes events to whichever Viewport is currently
49
+ * focused. Sources declare which event classes they care about so the parent
50
+ * can switch protocol modes deterministically.
51
+ *
52
+ * - `"none"`: source consumes no input (replay frames, static snapshots).
53
+ * - `"keys"`: source wants forwarded key events when its Viewport is focused.
54
+ * - `"mouse"`: source wants normalized `(row, col)` mouse events.
55
+ * - `"all"`: source wants both.
56
+ */
57
+ type ViewportInputMode = "none" | "keys" | "mouse" | "all";
58
+ /**
59
+ * Frozen color palette handed to a Viewport at mount. Independent of the
60
+ * parent silvery theme — a Viewport speaks raw colors, not `$tokens`.
61
+ *
62
+ * The source is responsible for mapping its own color model (xtermjs 256-color
63
+ * indices, replay-frame RGB, etc.) onto this palette when it needs theme
64
+ * coherence with the host. Sources that have their own complete color
65
+ * vocabulary (mirroring a real terminal session) may ignore this entirely.
66
+ */
67
+ interface ViewportPalette {
68
+ /** Default background color (any silvery-acceptable color string). */
69
+ background: string;
70
+ /** Default foreground color. */
71
+ foreground: string;
72
+ /** Optional 16-color ANSI map. Index `0..7` = standard, `8..15` = bright. */
73
+ ansi16?: readonly string[];
74
+ }
75
+ /**
76
+ * Handle passed to a {@link ForeignSource} at `connect()` time — the
77
+ * thin remote the source uses to push cell content, move the cursor, and
78
+ * negotiate input mode with the parent Viewport.
79
+ *
80
+ * One `ViewportContext` exists per mounted Viewport. The source uses it for
81
+ * the lifetime of the connection; after `disconnect()` the context is
82
+ * invalidated and calls become no-ops.
83
+ */
84
+ interface ViewportContext {
85
+ /** Current Viewport dimensions in cells. */
86
+ dimensions(): {
87
+ cols: number;
88
+ rows: number;
89
+ };
90
+ /**
91
+ * Blit cell content into the Viewport's buffer at the given dirty rects.
92
+ * Rects are in Viewport-local coordinates (origin = `(0, 0)` at top-left).
93
+ * The buffer's `(col, row)` indices are absolute within the buffer — the
94
+ * source decides what cells to read for each rect.
95
+ */
96
+ blit(dirtyRects: readonly ViewportRect[], buffer: CellBuffer): void;
97
+ /** Move the Viewport's internal cursor. */
98
+ setCursor(pos: {
99
+ row: number;
100
+ col: number;
101
+ }, style?: ViewportCursorStyle): void;
102
+ /** Force a full Viewport repaint on the next frame. */
103
+ invalidateAll(): void;
104
+ /**
105
+ * Ask the parent to route input events of the given mode into this
106
+ * Viewport when it's focused. Parent owns global terminal modes; the
107
+ * request is advisory unless the Viewport is the focused leaf.
108
+ */
109
+ requestInputMode(mode: ViewportInputMode): void;
110
+ /**
111
+ * Optional: source emits a window title (xtermjs OSC 0/2). Reserved for
112
+ * future — host may surface it via app chrome, ignored otherwise.
113
+ */
114
+ emitTitle?(title: string): void;
115
+ }
116
+ /**
117
+ * The contract a foreign rendering engine implements to live inside a
118
+ * Viewport.
119
+ *
120
+ * Lifecycle: `<Viewport>` calls `connect(ctx)` on mount and `disconnect()` on
121
+ * unmount. The source then writes into the context at its own cadence —
122
+ * input-driven (xtermjs PTY mirror), timer-driven (replay), or
123
+ * frame-driven (snapshot). The Viewport never polls the source.
124
+ *
125
+ * Implementations (planned):
126
+ * - `XtermAdapter` (Phase B): wraps `@xterm/headless`, mirrors a PTY child.
127
+ * - `ReplaySource`: animation frames for inline previews.
128
+ * - `SnapshotSource`: static frames (GIF encoder, test fixtures).
129
+ * - `LocalSource` (post-MVP): render a silvery subtree INTO a Viewport buffer.
130
+ */
131
+ interface ForeignSource {
132
+ /** Called once at mount. Source captures `ctx` for the connection lifetime. */
133
+ connect(ctx: ViewportContext): void;
134
+ /** Called once at unmount. Source releases all resources tied to the context. */
135
+ disconnect(): void;
136
+ /**
137
+ * Optional intrinsic size hint. The Viewport MAY snap its dimensions to
138
+ * this on mount — apps that want pixel-perfect chrome should set explicit
139
+ * `cols`/`rows` on `<Viewport>` and ignore the hint.
140
+ */
141
+ desiredSize?(): {
142
+ cols: number;
143
+ rows: number;
144
+ };
145
+ }
146
+ /**
147
+ * Imperative handle returned by `<Viewport ref={...}>`. Apps use this to push
148
+ * content into the Viewport without binding a {@link ForeignSource} — useful
149
+ * for one-shot snapshot blits, test fixtures, or app-driven mirroring where a
150
+ * full source lifecycle would be over-engineered.
151
+ *
152
+ * Both paths can coexist: a source binding and a ref handle on the same
153
+ * Viewport write into the same underlying buffer (last-write-wins per cell).
154
+ */
155
+ interface ViewportRef {
156
+ /** Write cells into the Viewport at the given dirty rects. */
157
+ writeCells(dirtyRects: readonly ViewportRect[], buffer: CellBuffer): void;
158
+ /**
159
+ * Convenience: feed raw ANSI bytes. The Viewport's internal terminal
160
+ * emulator (xtermjs in v1) parses them and updates the cell buffer.
161
+ * Apps that already speak the cell vocabulary should prefer
162
+ * {@link writeCells} — `writeAnsi` is for the "I have a PTY producing
163
+ * ANSI bytes" case.
164
+ */
165
+ writeAnsi(chunk: Uint8Array): void;
166
+ /** Move the Viewport's internal cursor. */
167
+ setCursor(pos: {
168
+ row: number;
169
+ col: number;
170
+ }, style?: ViewportCursorStyle): void;
171
+ /**
172
+ * Resize the Viewport (re-runs parent layout; `onResize` fires; bound
173
+ * {@link ForeignSource} sees new dimensions on its next `blit()`).
174
+ */
175
+ resize(cols: number, rows: number): void;
176
+ /**
177
+ * Capture the current Viewport buffer as an immutable {@link CellBuffer}
178
+ * snapshot. Used by GIF encoders, snapshot tests, and replay capture.
179
+ */
180
+ snapshot(): CellBuffer;
181
+ }
182
+ /**
183
+ * Per-instance state attached to a `silvery-viewport` AgNode. Owned by the
184
+ * `<Viewport>` React component; read by the pipeline render phase to blit
185
+ * cells into the parent buffer.
186
+ *
187
+ * Lazily created at mount (the host node has no `viewportState` until
188
+ * `<Viewport>` runs its mount effect). After unmount the slot may be
189
+ * cleared, but the AgNode is also torn down at that point.
190
+ *
191
+ * @internal — public callers should not touch this directly; the props +
192
+ * ref handle on `<Viewport>` are the supported surface.
193
+ */
194
+ interface ViewportNodeState {
195
+ /** Backing cell buffer (mutable; the renderer reads via the `CellBuffer` upcast). */
196
+ buffer: CellBuffer;
197
+ /** Latest internal cursor position (in Viewport-local cells), or null when hidden. */
198
+ cursor: {
199
+ row: number;
200
+ col: number;
201
+ style: ViewportCursorStyle;
202
+ } | null;
203
+ /** Whether the Viewport's internal cursor should paint at all. */
204
+ cursorVisible: boolean;
205
+ /** Last input mode the source asked for (or "none" if no source). */
206
+ inputMode: ViewportInputMode;
207
+ }
208
+ /**
209
+ * Public props for `<Viewport>` (v1 — termless-rec target).
210
+ *
211
+ * The MVP shape. See bead `@km/silvery/15513-surface-nested-composition-primitive`
212
+ * for the full defer list (transparency, nested viewports, `LocalSource`,
213
+ * IME composition, full bidi).
214
+ */
215
+ interface ViewportProps {
216
+ /** Viewport width in cells. Required — Viewport is a leaf with fixed size. */
217
+ cols: number;
218
+ /** Viewport height in cells. */
219
+ rows: number;
220
+ /**
221
+ * Optional ForeignSource bound at mount. May be omitted when the app pushes
222
+ * content imperatively via {@link ViewportRef}.
223
+ */
224
+ source?: ForeignSource;
225
+ /** Whether the Viewport can receive focus. Default: `false`. */
226
+ focusable?: boolean;
227
+ /**
228
+ * Input modes the Viewport requests when focused. The parent enables the
229
+ * matching protocol modes (mouse SGR, raw input, etc.) only when this
230
+ * Viewport is the focused leaf. Default: `"none"`.
231
+ */
232
+ captureInput?: ViewportInputMode;
233
+ /**
234
+ * Scrollback line count for the internal cell buffer. Default: `0`
235
+ * (overlays don't need history; pure mirror use cases keep memory tight).
236
+ */
237
+ scrollback?: number;
238
+ /**
239
+ * Clip overflow content to the Viewport's rect (vs. allowing oversize
240
+ * content to escape into the parent). Default: `true`.
241
+ */
242
+ clip?: boolean;
243
+ /** Show the Viewport's internal cursor. Default: `true`. */
244
+ cursorVisible?: boolean;
245
+ /**
246
+ * Frozen palette for the Viewport's internal color resolution. Set ONCE at
247
+ * mount — does NOT cascade from the parent theme. Pass a derived value if
248
+ * theme coherence with the host is desired; pass `undefined` for the
249
+ * Viewport to use its own defaults (the source decides).
250
+ */
251
+ palette?: ViewportPalette;
252
+ /** Fired when the Viewport is resized (parent layout change or `ref.resize`). */
253
+ onResize?: (cols: number, rows: number) => void;
254
+ /**
255
+ * Fired when an external consumer (GIF encoder, snapshot test) requests
256
+ * a buffer capture. Returns the current cell buffer for projection.
257
+ */
258
+ onSnapshot?: () => CellBuffer;
259
+ }
260
+ //#endregion
261
+ //#region packages/ag/src/island-types.d.ts
262
+ /**
263
+ * Lifecycle signals emitted by an {@link IslandGuest} via `ctx.emit()`.
264
+ *
265
+ * The host subscribes via `createIsland({ onSignal })`. Always serializable
266
+ * — replay guests reconstruct sessions by replaying these.
267
+ */
268
+ type IslandSignal = {
269
+ type: "ready";
270
+ } | {
271
+ type: "exit";
272
+ code?: number;
273
+ reason?: string;
274
+ } | {
275
+ type: "error";
276
+ error: Error;
277
+ };
278
+ /**
279
+ * Capabilities a guest declares to the host. Drives whether the host renders
280
+ * input routing, resize negotiation, and palette ownership for this island.
281
+ *
282
+ * Per-island prop capability overrides (set on `<Island capabilities={...}>`)
283
+ * intersect with per-guest capability declarations — intersection wins
284
+ * (host never offers a capability the guest can't fulfill).
285
+ */
286
+ interface IslandCapabilities {
287
+ /** Guest accepts input events from the host (key / mouse / paste). */
288
+ input?: boolean;
289
+ /** Guest manages cursor-shape / alt-screen / bracketed-paste / mouse-tracking modes. */
290
+ modes?: boolean;
291
+ /** Guest can resize dynamically (host calls {@link IslandSizeOwner.requestResize}). */
292
+ resize?: boolean;
293
+ /** Guest owns its palette (OSC 4 / 10 / 11). Default: host freezes palette. */
294
+ palette?: boolean;
295
+ }
296
+ /**
297
+ * Per-island hydration policy — Astro-borrowed. Default: `"load"`.
298
+ *
299
+ * - `"load"`: guest.init() fires synchronously at mount.
300
+ * - `"idle"`: defer until `requestIdleCallback` (or microtask fallback).
301
+ * - `"visible"`: defer until the island's rect intersects the viewport.
302
+ * - `"only-on-focus"`: defer until the island first receives focus; tear
303
+ * down on blur. Cheapest for multi-pane hosts (silvercode panes).
304
+ */
305
+ type IslandHydrate = "load" | "idle" | "visible" | "only-on-focus";
306
+ /**
307
+ * Palette ownership policy per island.
308
+ *
309
+ * - `"freeze"` — host snapshots the current theme palette at mount; guest
310
+ * sees a frozen view. Default for PTY / snapshot guests (compositing
311
+ * isolation; theme drift cannot leak into recorded content).
312
+ * - `"inherit"` — guest inherits the host theme palette; theme changes
313
+ * cascade live. Default for sub-silvery / Vue / Solid guests (semantic
314
+ * theme coherence is the point).
315
+ * - `{ custom }` — explicit {@link ViewportPalette}. Overrides both.
316
+ */
317
+ type IslandPalettePolicy = "freeze" | "inherit" | {
318
+ custom: ViewportPalette;
319
+ };
320
+ /**
321
+ * Size owner — exposes guest dimensions; host requests resize via
322
+ * `requestResize()`; guest acknowledges by emitting on its next paint.
323
+ *
324
+ * Two-phase resize protocol (the P0 landmine /pro caught):
325
+ * 1. Host calls `requestResize(cols, rows)` (advisory).
326
+ * 2. Guest decides; if accepted, writes content at new dims on its next
327
+ * `output.writeCells()`.
328
+ * 3. Host reads new `cols` / `rows` after the guest acknowledges via
329
+ * `output` — never assumes resize was accepted synchronously.
330
+ *
331
+ * `island-resize-race` STRICT slug catches violations of this protocol.
332
+ */
333
+ interface IslandSizeOwner {
334
+ readonly cols: number;
335
+ readonly rows: number;
336
+ /** Subscribe to size changes (alien-signals compatible). */
337
+ subscribe(listener: (size: {
338
+ cols: number;
339
+ rows: number;
340
+ }) => void): () => void;
341
+ /** Host-side: ask the guest to resize. Guest acknowledges via next paint. */
342
+ requestResize(cols: number, rows: number): void;
343
+ }
344
+ /**
345
+ * Output owner — guest writes cells / cursor / mode hints to the host.
346
+ * Host renders via the pipeline render phase.
347
+ *
348
+ * The buffer is read-only from the host's perspective (upcast to
349
+ * {@link CellBuffer}). Guests own the underlying mutable buffer.
350
+ *
351
+ * `island-paint-oob` STRICT slug catches guest writes outside the island's
352
+ * declared rect; `island-paint-budget` STRICT slug catches runaway paint
353
+ * cadence (per-frame byte budget).
354
+ */
355
+ interface IslandOutputOwner {
356
+ /** Current cell buffer (read-only upcast for host blit). */
357
+ readonly buffer: CellBuffer;
358
+ /**
359
+ * Last-known guest cursor position + style; null when hidden.
360
+ * Host renders this WITHIN the island's rect; the host cursor is
361
+ * suppressed inside an island (cursor un-apply on blur — see Modes owner).
362
+ */
363
+ readonly cursor: IslandCursorState | null;
364
+ /** True if guest wants its cursor painted in the host frame. */
365
+ readonly cursorVisible: boolean;
366
+ /**
367
+ * Subscribe to output-relevant changes (new cells, cursor move, mode
368
+ * change). Host marks the island's rect dirty on each callback.
369
+ */
370
+ subscribe(listener: () => void): () => void;
371
+ /**
372
+ * Guest-side: write cells at the given island-local dirty rects. The
373
+ * supplied buffer is the guest's source; cells outside the dirty rects
374
+ * are unchanged.
375
+ *
376
+ * Origin `(0, 0)` = top-left of island content area (NOT absolute
377
+ * terminal). Host translates at blit time.
378
+ */
379
+ writeCells(dirtyRects: readonly ViewportRect[], buffer: CellBuffer): void;
380
+ /** Guest-side: force a full island repaint on the next frame. */
381
+ invalidateAll(): void;
382
+ }
383
+ /**
384
+ * Guest-internal cursor descriptor (style + position).
385
+ * Style values match {@link import("./viewport-types").ViewportCursorStyle}.
386
+ */
387
+ interface IslandCursorState {
388
+ row: number;
389
+ col: number;
390
+ style: "block" | "underline" | "bar";
391
+ }
392
+ /**
393
+ * Input owner — host routes input events to the guest when the island is
394
+ * focused. Exposes typed `on*` callbacks (canonical) AND an `events()`
395
+ * AsyncIterable (restored ergonomic wrapper from pre-14991; zero behavioral
396
+ * cost — see `@km/silvery/15646` decision row "Restore input.events()").
397
+ *
398
+ * The host translates host-coordinate mouse events to island-local
399
+ * `(row, col)` before delivery — the P0 landmine /pro caught.
400
+ *
401
+ * Synchronous focus severance: when focus moves to a different island, the
402
+ * host stops delivering events to the previous island AT the focus-change
403
+ * tick. No queue / drop / forward-after-blur surprise modes.
404
+ *
405
+ * `input.sendEof()` / `signals.sendSigint()` / `signals.sendSigtstp()` are
406
+ * DISTINCT: Ctrl-D ≠ Ctrl-C ≠ Ctrl-Z. The 15645 sketch wrongly mapped
407
+ * Ctrl-D → "interrupt"; islands gets it right.
408
+ */
409
+ interface IslandInputOwner {
410
+ /** Key event (mapped through host's Kitty / mouse / focus protocol layers). */
411
+ onKey?(handler: (event: IslandKeyEvent) => void): () => void;
412
+ /** Mouse event with island-local coordinates (host-translated). */
413
+ onMouse?(handler: (event: IslandMouseEvent) => void): () => void;
414
+ /** Bracketed-paste content (host-decoded; no \x1b[200~ sequences). */
415
+ onPaste?(handler: (text: string) => void): () => void;
416
+ /**
417
+ * Raw byte feed for guests that speak ANSI directly (PTY pipe). Most
418
+ * guests should prefer typed `on*` events; `feed()` is the escape hatch
419
+ * for "I have an xterm.js process and want every byte."
420
+ */
421
+ feed?(bytes: Uint8Array): void;
422
+ /**
423
+ * AsyncIterable view over all input events. Restored ergonomic wrapper
424
+ * for the `for await (const ev of island.input.events()) {...}` idiom.
425
+ * Yields the same event objects as the typed `on*` callbacks.
426
+ */
427
+ events?(): AsyncIterable<IslandInputEvent>;
428
+ /**
429
+ * Send EOT (Ctrl-D, U+0004) to the guest. Distinct from `signals.sendSigint()`.
430
+ * EOT closes the guest's stdin (or signals end-of-stream); signal verbs
431
+ * deliver actual POSIX signals.
432
+ */
433
+ sendEof?(): void;
434
+ }
435
+ /** Discriminated union of all input event types. */
436
+ type IslandInputEvent = (IslandKeyEvent & {
437
+ kind: "key";
438
+ }) | (IslandMouseEvent & {
439
+ kind: "mouse";
440
+ }) | {
441
+ kind: "paste";
442
+ text: string;
443
+ } | {
444
+ kind: "feed";
445
+ bytes: Uint8Array;
446
+ };
447
+ /**
448
+ * Key event delivered to a focused island. Mirrors the host's parsed
449
+ * {@link import("./keys").Key} shape — re-exported here so guests don't
450
+ * have to depend on `@silvery/ag/keys` for its public surface.
451
+ */
452
+ interface IslandKeyEvent {
453
+ /** Plain character (for printable keys), or empty for special keys. */
454
+ input: string;
455
+ /** Named key (e.g. "escape", "enter", "tab", "f1"); empty for printable. */
456
+ name?: string;
457
+ /** Modifier state. */
458
+ ctrl?: boolean;
459
+ meta?: boolean;
460
+ alt?: boolean;
461
+ shift?: boolean;
462
+ super?: boolean;
463
+ /** Event type — only "press" / "repeat" delivered; "release" filtered by host. */
464
+ eventType?: "press" | "repeat";
465
+ }
466
+ /**
467
+ * Mouse event with ISLAND-LOCAL coordinates. Origin `(0, 0)` = top-left of
468
+ * island content area. Host translates from absolute terminal coords before
469
+ * delivery — guests never see host-relative positions.
470
+ */
471
+ interface IslandMouseEvent {
472
+ /** Island-local row (0 = top of island content). */
473
+ row: number;
474
+ /** Island-local column (0 = left of island content). */
475
+ col: number;
476
+ /** Button: "left" | "middle" | "right" | "wheel-up" | "wheel-down" | "release". */
477
+ button: string;
478
+ /** Held modifiers at event time. */
479
+ ctrl?: boolean;
480
+ shift?: boolean;
481
+ alt?: boolean;
482
+ }
483
+ /**
484
+ * Modes owner — host queries which protocol modes the guest currently wants
485
+ * active (alt-screen, bracketed-paste, mouse-tracking SGR, Kitty keyboard,
486
+ * focus-reporting, cursor shape + visibility).
487
+ *
488
+ * Host AGGREGATES modes from all focused-subtree islands into one global
489
+ * protocol-mode set. When focus moves, the aggregator recomputes; modes
490
+ * the new focus wants are enabled, modes only the previous focus wanted
491
+ * are disabled. THIS is what replaces the 15 `!inputDisabled` gating sites
492
+ * in `create-app.tsx` (Unit C deletion).
493
+ *
494
+ * `island-mode-leak` STRICT slug catches modes that stay enabled after
495
+ * the requesting island unmounts or loses focus.
496
+ */
497
+ interface IslandModesOwner {
498
+ /** Current desired modes (host reads). */
499
+ readonly modes: IslandProtocolModes;
500
+ /** Subscribe to mode changes. Host re-aggregates on each callback. */
501
+ subscribe(listener: (modes: IslandProtocolModes) => void): () => void;
502
+ }
503
+ /**
504
+ * Protocol modes a focused island can request the host enable. None are
505
+ * defaults — host enables only modes some focused island asks for.
506
+ */
507
+ interface IslandProtocolModes {
508
+ altScreen?: boolean;
509
+ bracketedPaste?: boolean;
510
+ mouseTracking?: "off" | "click" | "drag" | "any";
511
+ kittyKeyboard?: boolean;
512
+ focusReporting?: boolean;
513
+ /** Guest's desired cursor shape + visibility. Un-applied on blur. */
514
+ cursor?: {
515
+ shape: "block" | "underline" | "bar";
516
+ visible: boolean;
517
+ };
518
+ }
519
+ /**
520
+ * Signals owner — delivers POSIX signals to the guest. PTY-backed guests
521
+ * forward to the child process; snapshot / replay guests typically have
522
+ * no signal handlers and ignore (capabilities.input = false hides this
523
+ * owner from the host).
524
+ *
525
+ * Distinct verbs per signal — explicitly NOT a single `send(signal)`
526
+ * because the call sites have semantic differences the host needs to
527
+ * route correctly (Ctrl-C from `signals.sendSigint()` is different from
528
+ * EOT via `input.sendEof()`).
529
+ */
530
+ interface IslandSignalsOwner {
531
+ /** Send SIGINT (Ctrl-C). */
532
+ sendSigint(): void;
533
+ /** Send SIGTSTP (Ctrl-Z, suspend). */
534
+ sendSigtstp(): void;
535
+ /** Send SIGTERM (graceful termination). */
536
+ sendSigterm(): void;
537
+ /** Send SIGKILL (immediate). Last-resort. */
538
+ sendSigkill(): void;
539
+ /** Exit-code stream (resolves when guest reports exit). */
540
+ readonly exit: Promise<{
541
+ code?: number;
542
+ reason?: string;
543
+ }>;
544
+ }
545
+ /**
546
+ * Palette owner — OSC 4 / 10 / 11 query + response + snapshot. Present only
547
+ * when `capabilities.palette = true` (guest owns palette) AND the island's
548
+ * `palettePolicy !== "freeze"`.
549
+ *
550
+ * Frozen-palette islands (the default for PTY / snapshot guests) get a
551
+ * read-only snapshot at mount and no palette owner — palette queries
552
+ * inside the guest are responded to from the snapshot, not the live host.
553
+ */
554
+ interface IslandPaletteOwner {
555
+ /** Current palette (live, or the frozen snapshot if `palettePolicy="freeze"`). */
556
+ readonly palette: ViewportPalette;
557
+ /** Subscribe to palette changes (only fires when not frozen). */
558
+ subscribe(listener: (palette: ViewportPalette) => void): () => void;
559
+ /**
560
+ * Guest-side: respond to an OSC 4 / 10 / 11 query. Host typically routes
561
+ * these to a real terminal probe; islands compose by chaining through
562
+ * the `sandbox` wrapper.
563
+ */
564
+ respondToQuery?(query: string): string | undefined;
565
+ }
566
+ /**
567
+ * Imperative handle returned by an {@link IslandGuest}'s `init()`. The host
568
+ * stores this on the AgNode's {@link IslandNodeState} and reads through it
569
+ * each render frame.
570
+ *
571
+ * Sub-owners are optional per `capabilities`: a snapshot guest with no
572
+ * input may return `{ size, output, dispose }` and nothing else. The
573
+ * `<Island>` React binding propagates the right defaults; the host
574
+ * aggregator (Unit C) treats absent owners as "no requested modes /
575
+ * no input routing."
576
+ */
577
+ interface IslandHandle {
578
+ /** Required — every island has a size. */
579
+ readonly size: IslandSizeOwner;
580
+ /** Required — every island has output (even if empty). */
581
+ readonly output: IslandOutputOwner;
582
+ /** Present when `capabilities.input = true`. */
583
+ readonly input?: IslandInputOwner;
584
+ /** Present when `capabilities.modes = true`. */
585
+ readonly modes?: IslandModesOwner;
586
+ /** Present when the guest can deliver signals (PTY-backed). */
587
+ readonly signals?: IslandSignalsOwner;
588
+ /**
589
+ * Present when `capabilities.palette = true` AND `palettePolicy !== "freeze"`.
590
+ * Frozen-palette islands respond to OSC queries from the snapshot — no
591
+ * live owner needed.
592
+ */
593
+ readonly palette?: IslandPaletteOwner;
594
+ /**
595
+ * Tear down the guest. Called on unmount (`<Island>` cleanup), on focus
596
+ * loss for `hydrate: "only-on-focus"` islands, and on ErrorBoundary
597
+ * catches. MUST be idempotent.
598
+ *
599
+ * `island-dispose-leak` STRICT slug catches guests that retain resources
600
+ * (timers, sockets, FDs) past dispose.
601
+ */
602
+ dispose(): void | Promise<void>;
603
+ }
604
+ /**
605
+ * Context passed to {@link IslandGuest.init}. The guest captures this for
606
+ * the lifetime of its connection and uses it to push lifecycle signals,
607
+ * request resize, execute host-fulfilled OSC ops, and read monotonic time.
608
+ *
609
+ * One `IslandContext` exists per mounted island. `abortSignal` fires on
610
+ * unmount (or on focus-loss for `hydrate: "only-on-focus"` islands) — the
611
+ * guest MUST release resources tied to this context when it aborts.
612
+ */
613
+ interface IslandContext {
614
+ /** Initial island dimensions in cells. */
615
+ readonly cols: number;
616
+ readonly rows: number;
617
+ /**
618
+ * Emit a lifecycle signal. Host forwards to the `onSignal` callback set
619
+ * on `createIsland({ onSignal })`.
620
+ */
621
+ emit(signal: IslandSignal): void;
622
+ /**
623
+ * Ask the host to resize the island. Host confirms via {@link
624
+ * IslandSizeOwner} on the next layout tick — guest MUST wait for the
625
+ * confirmation before writing content at new dims (two-phase protocol;
626
+ * `island-resize-race` STRICT slug catches violations).
627
+ */
628
+ requestResize(cols: number, rows: number): void;
629
+ /**
630
+ * Host-fulfilled OS side-effect: guest sends an OSC string (e.g.
631
+ * `\x1b]52;c;<base64>\x07` for clipboard), host parses + executes +
632
+ * returns the response (if any). Otherwise OSC 52 / 4 / 10 / 11 ops
633
+ * from inside the guest would vanish into the island's cell grid.
634
+ */
635
+ execOSC(command: string): Promise<string | void>;
636
+ /**
637
+ * Aborts on unmount (or on focus-loss for `hydrate: "only-on-focus"`).
638
+ * Guests MUST release resources on signal — sockets closed, FDs freed,
639
+ * timers cleared.
640
+ */
641
+ readonly abortSignal: AbortSignal;
642
+ /**
643
+ * Monotonic time source. Replay guests use this for deterministic
644
+ * playback; live guests can use `performance.now()` directly.
645
+ */
646
+ now(): number;
647
+ }
648
+ /**
649
+ * The runtime-agnostic guest contract. Current factories include
650
+ * `snapshotGuest` and `sandbox(guest)` in `@silvery/ag/island-guests`, plus
651
+ * package-specific guests such as `xtermGuest` and silvermux's `tmuxGuest`.
652
+ * Planned or external guests include `replayGuest`, `silveryGuest` (embedded
653
+ * sub-instance), `inkGuest` (legacy adapter), `vueGuest`, `solidGuest`, and
654
+ * other author-provided implementations.
655
+ *
656
+ * Silvery does NOT ship per-framework adapters beyond core helpers such as
657
+ * `snapshotGuest` / a `sandbox(guest)` wrapper. Community frameworks
658
+ * implement the contract directly; the contract is the integration surface.
659
+ *
660
+ * `init()` returns `Promise` externally — backend authors get one clear
661
+ * shape (the /pro decision); sync internals are handled by `Promise.resolve()`
662
+ * at mount.
663
+ */
664
+ interface IslandGuest {
665
+ /**
666
+ * Initialize the guest. Called once at mount (or on first focus for
667
+ * `hydrate: "only-on-focus"`). Returns an {@link IslandHandle} the host
668
+ * uses to render the island.
669
+ *
670
+ * If `init()` rejects, the silvery ErrorBoundary catches; if `onError`
671
+ * is set on the `<Island>`, it receives the error; otherwise it throws
672
+ * up to the parent boundary.
673
+ */
674
+ init(ctx: IslandContext): Promise<IslandHandle>;
675
+ /**
676
+ * Capabilities this guest CAN provide. Host intersects with per-island
677
+ * prop overrides — guest never has to fulfill what it didn't declare.
678
+ *
679
+ * Omitted = no capabilities (snapshot-only).
680
+ */
681
+ capabilities?: IslandCapabilities;
682
+ }
683
+ /**
684
+ * Per-instance state attached to a `silvery-island` AgNode. Owned by the
685
+ * `createIsland()` factory (or the `<Island>` React binding); read by the
686
+ * pipeline render phase to blit the guest's cell buffer at the node's
687
+ * `boxRect` and route input/mode aggregation.
688
+ *
689
+ * Lazily created at mount (the host node has no `islandState` until the
690
+ * factory runs its mount effect). After unmount the slot may be cleared,
691
+ * but the AgNode is also torn down at that point.
692
+ *
693
+ * Mirrors {@link import("./viewport-types").ViewportNodeState} structurally
694
+ * — the migration story is: `Viewport` is `Island`'s special-case for
695
+ * "snapshot-only" + "no input"; Island generalizes by exposing the full
696
+ * sub-owner contract.
697
+ *
698
+ * @internal — public callers should use the `<Island>` props + ref handle;
699
+ * direct AgNode access is for the pipeline + STRICT-mode checks only.
700
+ */
701
+ interface IslandNodeState {
702
+ /**
703
+ * The guest's handle, or `null` until `init()` resolves (deferred-hydrate
704
+ * islands hold `null` until first focus / visibility).
705
+ */
706
+ handle: IslandHandle | null;
707
+ /**
708
+ * The guest contract — kept on the node so deferred-hydrate islands can
709
+ * re-init on focus / visibility transitions.
710
+ */
711
+ guest: IslandGuest;
712
+ /**
713
+ * Effective capabilities (per-island intersection of guest declarations
714
+ * with per-island prop overrides). Computed once at mount; recomputed
715
+ * on capability prop change.
716
+ */
717
+ capabilities: IslandCapabilities;
718
+ /** Whether this island can receive focus. Read by host focus manager. */
719
+ focusable: boolean;
720
+ /** True iff this island is currently in the focused subtree. */
721
+ focused: boolean;
722
+ /**
723
+ * Effective palette policy. Frozen palette: snapshot held in
724
+ * `frozenPalette`. Inherit: `null` (host theme cascades).
725
+ */
726
+ palettePolicy: IslandPalettePolicy;
727
+ /** Frozen palette snapshot (set only when policy = "freeze"). */
728
+ frozenPalette: ViewportPalette | null;
729
+ /** Hydration policy; drives when `init()` fires. */
730
+ hydrate: IslandHydrate;
731
+ /**
732
+ * Lifecycle state — drives the render phase + STRICT mode checks.
733
+ * - `"pending"`: handle not yet created (deferred hydrate, or init in flight).
734
+ * - `"ready"`: handle live, guest producing content.
735
+ * - `"errored"`: init or runtime threw; ErrorBoundary handles display.
736
+ * - `"disposed"`: dispose() called; AgNode awaiting unmount.
737
+ */
738
+ lifecycle: "pending" | "ready" | "errored" | "disposed";
739
+ /** Last error reported by the guest (set in `"errored"` state). */
740
+ lastError: Error | null;
741
+ /**
742
+ * Abort controller fed to the guest's `IslandContext.abortSignal`. Host
743
+ * aborts on unmount / focus-loss (for "only-on-focus" hydrate) / dispose.
744
+ */
745
+ abortController: AbortController;
746
+ }
747
+ //#endregion
748
+ //#region packages/ag/src/drag-event-types.d.ts
749
+ /**
750
+ * Drag event payload passed to handler props.
751
+ */
752
+ interface DragEventPayload {
753
+ /** The node being dragged */
754
+ source: AgNode;
755
+ /** Current terminal position of the pointer */
756
+ position: {
757
+ x: number;
758
+ y: number;
759
+ };
760
+ /** The node under the cursor (the drop target receiving this event) */
761
+ dropTarget: AgNode | null;
762
+ }
763
+ interface DragEventProps {
764
+ /** Fired when a dragged node enters this node's bounds */
765
+ onDragEnter?: (event: DragEventPayload) => void;
766
+ /** Fired when a dragged node leaves this node's bounds */
767
+ onDragLeave?: (event: DragEventPayload) => void;
768
+ /** Fired repeatedly as a dragged node moves over this node */
769
+ onDragOver?: (event: DragEventPayload) => void;
770
+ /** Fired when a dragged node is dropped on this node */
771
+ onDrop?: (event: DragEventPayload) => void;
772
+ }
773
+ //#endregion
774
+ //#region packages/ag/src/keys.d.ts
775
+ /**
776
+ * Keyboard Constants and Utilities
777
+ *
778
+ * Single source of truth for all key parsing, mapping, and matching in silvery.
779
+ *
780
+ * ## Two-Layer Input Architecture
781
+ *
782
+ * parseKey() returns `[input, key]` where these serve DIFFERENT purposes:
783
+ *
784
+ * - `input` is **normalized for keybinding matching**. Shifted punctuation is
785
+ * decomposed: '#' becomes input='3' with key.shift=true, so keybindings
786
+ * like 'shift-3' can match. Uppercase letters become lowercase + shift.
787
+ *
788
+ * - `key.text` is the **actual typed character** (pre-normalization). For text
789
+ * insertion, always use `key.text ?? input` — this ensures Shift+3 inserts
790
+ * '#', opt+e inserts '´', and IME output inserts the composed string.
791
+ *
792
+ * Rule: keybinding resolution uses `input`. Text insertion uses `key.text`.
793
+ * Never reconstruct characters from key codes — trust what the terminal sent.
794
+ *
795
+ * - KEY_MAP: Playwright key names -> ANSI sequences (for sending input)
796
+ * - CODE_TO_KEY: ANSI escape suffixes -> key names (for parsing input)
797
+ * - Key interface: structured key object with boolean flags
798
+ * - parseKeypress(): raw terminal input -> ParsedKeypress
799
+ * - parseKey(): raw terminal input -> [input, Key]
800
+ * - keyToAnsi(): Playwright key string -> ANSI sequence
801
+ * - keyToName(): Key object -> named key string
802
+ * - keyToModifiers(): Key object -> modifier flags
803
+ * - parseHotkey(): "ctrl+shift+a" -> ParsedHotkey
804
+ * - matchHotkey(): match ParsedHotkey against Key
805
+ *
806
+ * @example
807
+ * ```tsx
808
+ * import { keyToAnsi } from '@silvery/test'
809
+ *
810
+ * // Convert key names to ANSI
811
+ * keyToAnsi('Enter') // '\r'
812
+ * keyToAnsi('ArrowUp') // '\x1b[A'
813
+ * keyToAnsi('Control+c') // '\x03'
814
+ * keyToAnsi('a') // 'a'
815
+ * ```
816
+ */
817
+ /**
818
+ * Key object describing which special keys/modifiers were pressed.
819
+ */
820
+ interface Key {
821
+ /** Up arrow key was pressed */
822
+ upArrow: boolean;
823
+ /** Down arrow key was pressed */
824
+ downArrow: boolean;
825
+ /** Left arrow key was pressed */
826
+ leftArrow: boolean;
827
+ /** Right arrow key was pressed */
828
+ rightArrow: boolean;
829
+ /** Page Down key was pressed */
830
+ pageDown: boolean;
831
+ /** Page Up key was pressed */
832
+ pageUp: boolean;
833
+ /** Home key was pressed */
834
+ home: boolean;
835
+ /** End key was pressed */
836
+ end: boolean;
837
+ /** Return (Enter) key was pressed */
838
+ return: boolean;
839
+ /** Escape key was pressed */
840
+ escape: boolean;
841
+ /** Ctrl key was pressed */
842
+ ctrl: boolean;
843
+ /** Shift key was pressed */
844
+ shift: boolean;
845
+ /** Tab key was pressed */
846
+ tab: boolean;
847
+ /** Backspace key was pressed */
848
+ backspace: boolean;
849
+ /** Delete key was pressed */
850
+ delete: boolean;
851
+ /** Meta key (Alt/Option on macOS, Alt on other platforms) was pressed */
852
+ meta: boolean;
853
+ /** Super key (Cmd on macOS, Win on Windows) was pressed. Requires Kitty protocol. */
854
+ super: boolean;
855
+ /** Hyper key was pressed. Requires Kitty protocol. */
856
+ hyper: boolean;
857
+ /** CapsLock is active. Requires Kitty protocol. */
858
+ capsLock: boolean;
859
+ /** NumLock is active. Requires Kitty protocol. */
860
+ numLock: boolean;
861
+ /** Kitty event type. Only set with Kitty flag 2 (report events). */
862
+ eventType?: "press" | "repeat" | "release";
863
+ /** The actual text character typed (pre-normalization). For text insertion,
864
+ * use this instead of the normalized `input` which maps shifted chars to base keys. */
865
+ text?: string;
866
+ /** True when the key is a modifier pressed alone (Shift, Ctrl, Alt, Super, Hyper, Meta).
867
+ * Set during parsing for Kitty protocol modifier-only events. */
868
+ isModifierOnly?: boolean;
869
+ /**
870
+ * Internal Kitty key name (e.g. "leftsuper", "rightcontrol",
871
+ * "leftshift") for modifier-only events. Surfaced so the modifier-state
872
+ * tracker (`useModifierKeys`) can drop the matching flag on release —
873
+ * Kitty release events keep the modifier bit set, so the bit alone
874
+ * doesn't say which key was lifted.
875
+ *
876
+ * Optional + only set for modifier-only events to avoid bloating the
877
+ * public surface for non-modifier keys (use the existing flag fields
878
+ * `upArrow`, `escape`, etc. for those). Bead:
879
+ * @km/silvery/keydown-keyup-test-primitives.
880
+ */
881
+ modifierName?: string;
882
+ }
883
+ /**
884
+ * Input handler callback type.
885
+ * Return 'exit' to exit the app.
886
+ */
887
+ type InputHandler = (input: string, key: Key) => void | "exit";
888
+ /**
889
+ * Parsed hotkey from a string like "ctrl+shift+a" or "Control+ArrowUp".
890
+ */
891
+ interface ParsedHotkey {
892
+ key: string;
893
+ ctrl: boolean;
894
+ meta: boolean;
895
+ shift: boolean;
896
+ alt: boolean;
897
+ super: boolean;
898
+ hyper: boolean;
899
+ }
900
+ interface ParsedKeypress {
901
+ name: string;
902
+ ctrl: boolean;
903
+ meta: boolean;
904
+ shift: boolean;
905
+ option: boolean;
906
+ super: boolean;
907
+ hyper: boolean;
908
+ /** Kitty event type. Only set with Kitty flag 2 (report events). */
909
+ eventType?: "press" | "repeat" | "release";
910
+ /** The character when Shift is held. From Kitty shifted_codepoint. */
911
+ shiftedKey?: string;
912
+ /** The key on a standard US layout (for non-Latin keyboards). From Kitty base_layout_key. */
913
+ baseLayoutKey?: string;
914
+ /** CapsLock is active. Kitty modifier bit 6. */
915
+ capsLock?: boolean;
916
+ /** NumLock is active. Kitty modifier bit 7. */
917
+ numLock?: boolean;
918
+ /** Decoded text from Kitty REPORT_TEXT mode. */
919
+ associatedText?: string;
920
+ sequence: string;
921
+ /** Raw input string, identical to sequence for most keys. */
922
+ raw?: string;
923
+ code?: string;
924
+ /** Whether this key was parsed from the Kitty keyboard protocol. */
925
+ isKittyProtocol?: boolean;
926
+ /**
927
+ * Whether this key represents printable text input.
928
+ * When false, the key is a control/function/modifier key that should not
929
+ * produce text input (e.g., arrows, function keys, capslock, media keys).
930
+ * Only set by the kitty protocol parser.
931
+ */
932
+ isPrintable?: boolean;
933
+ /**
934
+ * Text associated with the key.
935
+ * For printable kitty keys, defaults to the character from the codepoint.
936
+ * When REPORT_TEXT flag is active, contains the decoded text-as-codepoints.
937
+ */
938
+ text?: string;
939
+ }
940
+ /**
941
+ * Parse a raw input sequence into a structured keypress object.
942
+ * Accepts string or Buffer (Buffer support for stdin compatibility).
943
+ */
944
+ declare function parseKeypress(s: string | Buffer): ParsedKeypress;
945
+ /**
946
+ * Parse raw terminal input into a Key object and cleaned input string.
947
+ *
948
+ * @param rawInput Raw terminal input (string or Buffer)
949
+ * @returns Tuple of [cleanedInput, Key]
950
+ */
951
+ declare function parseKey(rawInput: string | Buffer): [string, Key];
952
+ /**
953
+ * Create an empty Key object (all fields false).
954
+ */
955
+ declare function emptyKey(): Key;
956
+ /**
957
+ * Convert a Key object to a named key string.
958
+ *
959
+ * Returns the Playwright-compatible name for special keys (ArrowUp, Enter, etc.)
960
+ * or "" if no special key is pressed.
961
+ */
962
+ declare function keyToName(key: Key): string;
963
+ /**
964
+ * Extract modifier flags from a Key object.
965
+ * `alt` is always false (terminals cannot distinguish alt from meta).
966
+ */
967
+ declare function keyToModifiers(key: Key): {
968
+ ctrl: boolean;
969
+ meta: boolean;
970
+ shift: boolean;
971
+ alt: boolean;
972
+ super: boolean;
973
+ hyper: boolean;
974
+ };
975
+ /**
976
+ * Parse a hotkey string into base key and modifiers.
977
+ *
978
+ * Supports Playwright-style ("Control+c", "Shift+ArrowUp") and
979
+ * lowercase aliases ("ctrl+c", "shift+tab", "cmd+a").
980
+ *
981
+ * @example
982
+ * ```tsx
983
+ * parseHotkey('j') // { key: 'j', ctrl: false, meta: false, shift: false, alt: false }
984
+ * parseHotkey('Control+c') // { key: 'c', ctrl: true, ... }
985
+ * parseHotkey('Shift+ArrowUp') // { key: 'ArrowUp', shift: true, ... }
986
+ * parseHotkey('⌘j') // { key: 'j', super: true, ... } (macOS symbol prefix)
987
+ * parseHotkey('⌃⇧a') // { key: 'a', ctrl: true, shift: true, ... }
988
+ * ```
989
+ */
990
+ declare function parseHotkey(keyStr: string): ParsedHotkey;
991
+ /**
992
+ * Match a parsed hotkey against a Key object and input string.
993
+ *
994
+ * @param hotkey Parsed hotkey to match
995
+ * @param key Key object from input event
996
+ * @param input Optional input string (for matching character keys)
997
+ * @returns true if the hotkey matches the key event
998
+ */
999
+ declare function matchHotkey(hotkey: ParsedHotkey, key: Key, input?: string): boolean;
1000
+ //#endregion
1001
+ //#region packages/ag/src/focus-events.d.ts
1002
+ /**
1003
+ * Synthetic keyboard event, mirroring React.KeyboardEvent / DOM KeyboardEvent.
1004
+ */
1005
+ interface SilveryKeyEvent {
1006
+ /** The printable character, or "" for non-printable keys */
1007
+ key: string;
1008
+ /** Raw terminal input string */
1009
+ input: string;
1010
+ /** Modifier keys */
1011
+ ctrl: boolean;
1012
+ meta: boolean;
1013
+ shift: boolean;
1014
+ super: boolean;
1015
+ hyper: boolean;
1016
+ /** Kitty event type */
1017
+ eventType?: "press" | "repeat" | "release";
1018
+ /** Deepest focusable node that received this event */
1019
+ target: AgNode;
1020
+ /** Node whose handler is currently firing (changes during capture/bubble) */
1021
+ currentTarget: AgNode;
1022
+ /** Stop event from propagating further */
1023
+ stopPropagation(): void;
1024
+ /** Prevent default behavior */
1025
+ preventDefault(): void;
1026
+ /** Whether stopPropagation() was called */
1027
+ readonly propagationStopped: boolean;
1028
+ /** Whether preventDefault() was called */
1029
+ readonly defaultPrevented: boolean;
1030
+ /** Raw parsed key data */
1031
+ nativeEvent: {
1032
+ input: string;
1033
+ key: Key;
1034
+ };
1035
+ }
1036
+ /**
1037
+ * Synthetic focus event, mirroring React.FocusEvent / DOM FocusEvent.
1038
+ */
1039
+ interface SilveryFocusEvent {
1040
+ /** The node gaining or losing focus */
1041
+ target: AgNode;
1042
+ /** The other node involved (losing focus on 'focus', gaining on 'blur') */
1043
+ relatedTarget: AgNode | null;
1044
+ /** Event type */
1045
+ type: "focus" | "blur";
1046
+ /** Node whose handler is currently firing (changes during bubble) */
1047
+ currentTarget: AgNode;
1048
+ /** Stop event from bubbling to parent nodes */
1049
+ stopPropagation(): void;
1050
+ /** Whether stopPropagation() was called */
1051
+ readonly propagationStopped: boolean;
1052
+ }
1053
+ interface FocusEventProps {
1054
+ /** Whether this node can receive focus */
1055
+ focusable?: boolean;
1056
+ /** Whether this node should receive focus on mount */
1057
+ autoFocus?: boolean;
1058
+ /** Whether this node creates a focus scope (focus trapping boundary) */
1059
+ focusScope?: boolean;
1060
+ /** ID of the node to focus when pressing Up from this node */
1061
+ nextFocusUp?: string;
1062
+ /** ID of the node to focus when pressing Down from this node */
1063
+ nextFocusDown?: string;
1064
+ /** ID of the node to focus when pressing Left from this node */
1065
+ nextFocusLeft?: string;
1066
+ /** ID of the node to focus when pressing Right from this node */
1067
+ nextFocusRight?: string;
1068
+ /** Called when this node gains focus */
1069
+ onFocus?: (event: SilveryFocusEvent) => void;
1070
+ /** Called when this node loses focus */
1071
+ onBlur?: (event: SilveryFocusEvent) => void;
1072
+ /** Called on key down (bubble phase) */
1073
+ onKeyDown?: (event: SilveryKeyEvent, dispatch?: (msg: unknown) => void) => void;
1074
+ /** Called on key up (bubble phase) */
1075
+ onKeyUp?: (event: SilveryKeyEvent, dispatch?: (msg: unknown) => void) => void;
1076
+ /** Called on key down (capture phase — fires before target) */
1077
+ onKeyDownCapture?: (event: SilveryKeyEvent) => void;
1078
+ }
1079
+ /**
1080
+ * Create a synthetic keyboard event.
1081
+ */
1082
+ declare function createKeyEvent(input: string, key: Key, target: AgNode): SilveryKeyEvent;
1083
+ /**
1084
+ * Create a synthetic focus event.
1085
+ */
1086
+ declare function createFocusEvent(type: "focus" | "blur", target: AgNode, relatedTarget: AgNode | null): SilveryFocusEvent;
1087
+ /**
1088
+ * Dispatch a keyboard event through the render tree with DOM-style
1089
+ * capture/target/bubble phases.
1090
+ *
1091
+ * For press/repeat events:
1092
+ * 1. Capture phase: root → target (onKeyDownCapture props)
1093
+ * 2. Target phase: target's onKeyDown
1094
+ * 3. Bubble phase: target parent → root (onKeyDown props)
1095
+ *
1096
+ * For release events:
1097
+ * 1. Target phase: target's onKeyUp
1098
+ * 2. Bubble phase: target parent → root (onKeyUp props)
1099
+ * (No capture phase for keyUp — deliberate simplification; React DOM has onKeyUpCapture)
1100
+ *
1101
+ * stopPropagation() halts traversal at any phase.
1102
+ */
1103
+ declare function dispatchKeyEvent(event: SilveryKeyEvent, dispatch?: (msg: unknown) => void): void;
1104
+ /**
1105
+ * Dispatch a focus event through the render tree.
1106
+ *
1107
+ * Fires onFocus/onBlur on the target, then bubbles to ancestors.
1108
+ */
1109
+ declare function dispatchFocusEvent(event: SilveryFocusEvent): void;
1110
+ //#endregion
1111
+ //#region packages/ag/src/layout-types.d.ts
1112
+ /**
1113
+ * Layout Type Abstractions
1114
+ *
1115
+ * Pure type interfaces for layout engines (Yoga, Flexily, etc.)
1116
+ * These live in @silvery/ag because they're used by core types (AgNode).
1117
+ * The runtime layout engine management lives in @silvery/ag-term/layout-engine.
1118
+ */
1119
+ /**
1120
+ * Measure mode determines how the width/height constraint should be interpreted.
1121
+ *
1122
+ * - "undefined": no constraint — return max-content (natural unconstrained size).
1123
+ * - "at-most": constrain to at most `width`/`height`. Used for Yoga/CSS
1124
+ * shrink-wrap and for cross-axis sizing in column/row layouts.
1125
+ * - "exactly": exactly `width`/`height` — used when a definite size has
1126
+ * been resolved.
1127
+ * - "min-content": flexily-only extension. Asks the measurer for its
1128
+ * CSS min-content size — longest unbreakable token for
1129
+ * wrappable text, naturalWidth for non-wrappable. Used by
1130
+ * CSS §4.5 auto-min-size derivation. Measurers that don't
1131
+ * recognize this mode should treat it as "at-most" with
1132
+ * width/height = 0 (the conservative fallback).
1133
+ */
1134
+ type MeasureMode = "undefined" | "exactly" | "at-most" | "min-content";
1135
+ /**
1136
+ * Measure function callback for intrinsic sizing.
1137
+ * Called when a node needs to determine its size based on content.
1138
+ */
1139
+ type MeasureFunc = (width: number, widthMode: MeasureMode, height: number, heightMode: MeasureMode) => {
1140
+ width: number;
1141
+ height: number;
1142
+ };
1143
+ /**
1144
+ * A fit-width lane entry: either a plain number (cells, treated as CSS points)
1145
+ * or a `{ value, unit }` object for container-query units.
1146
+ *
1147
+ * String values (`"100cqi"`) are parsed at the React seam (`applyBoxProps`)
1148
+ * into this shape before reaching the LayoutNode adapter; the adapter itself
1149
+ * receives only the parsed form.
1150
+ */
1151
+ type FitWidthLane = number | {
1152
+ value: number;
1153
+ unit: "cqi" | "cqmin";
1154
+ };
1155
+ /**
1156
+ * Abstract layout node interface.
1157
+ * Represents a single node in the layout tree.
1158
+ */
1159
+ interface LayoutNode {
1160
+ insertChild(child: LayoutNode, index: number): void;
1161
+ removeChild(child: LayoutNode): void;
1162
+ free(): void;
1163
+ setMeasureFunc(measureFunc: MeasureFunc): void;
1164
+ markDirty(): void;
1165
+ isDirty(): boolean;
1166
+ setWidth(value: number): void;
1167
+ setWidthPercent(value: number): void;
1168
+ setWidthAuto(): void;
1169
+ setWidthFitContent(): void;
1170
+ setWidthSnugContent(): void;
1171
+ setHeight(value: number): void;
1172
+ setHeightPercent(value: number): void;
1173
+ setHeightAuto(): void;
1174
+ setMinWidth(value: number): void;
1175
+ setMinWidthPercent(value: number): void;
1176
+ setMinHeight(value: number): void;
1177
+ setMinHeightPercent(value: number): void;
1178
+ setMaxWidth(value: number): void;
1179
+ setMaxWidthPercent(value: number): void;
1180
+ setMaxHeight(value: number): void;
1181
+ setMaxHeightPercent(value: number): void;
1182
+ setFlexGrow(value: number): void;
1183
+ setFlexShrink(value: number): void;
1184
+ setFlexBasis(value: number): void;
1185
+ setFlexBasisPercent(value: number): void;
1186
+ setFlexBasisAuto(): void;
1187
+ setFlexDirection(direction: number): void;
1188
+ setFlexWrap(wrap: number): void;
1189
+ setAlignItems(align: number): void;
1190
+ setAlignSelf(align: number): void;
1191
+ setAlignContent(align: number): void;
1192
+ setJustifyContent(justify: number): void;
1193
+ setPadding(edge: number, value: number): void;
1194
+ setMargin(edge: number, value: number): void;
1195
+ setBorder(edge: number, value: number): void;
1196
+ setGap(gutter: number, value: number): void;
1197
+ setDisplay(display: number): void;
1198
+ setPositionType(positionType: number): void;
1199
+ setPosition(edge: number, value: number): void;
1200
+ setPositionPercent(edge: number, value: number): void;
1201
+ setOverflow(overflow: number): void;
1202
+ setAspectRatio(value: number): void;
1203
+ /** CSS container-type. Maps to flexily's CONTAINER_TYPE_NORMAL | CONTAINER_TYPE_INLINE_SIZE. */
1204
+ setContainerType(containerType: number): void;
1205
+ /** CSS contain: size. True = block intrinsic propagation on the inline axis. */
1206
+ setContainSize(value: boolean): void;
1207
+ /**
1208
+ * Fit-width lanes (A0.2). Single-pass lane snap; consumes children's
1209
+ * max-content and picks the smallest lane that fits (else last). Each
1210
+ * entry is either a plain number (cells) or a `{ value, unit }` object
1211
+ * for cqi/cqmin entries. Pass `undefined` to disable.
1212
+ *
1213
+ * Engine requirement: this maps to flexily's `setFitWidth` directly.
1214
+ * Adapters without the capability MUST implement as a no-op; the user-facing
1215
+ * throw fires at the React layer via `requireCapability("fitWidth", ...)`.
1216
+ */
1217
+ setFitWidth(lanes: readonly FitWidthLane[] | undefined): void;
1218
+ calculateLayout(width: number, height: number, direction?: number): void;
1219
+ getComputedLeft(): number;
1220
+ getComputedTop(): number;
1221
+ getComputedWidth(): number;
1222
+ getComputedHeight(): number;
1223
+ }
1224
+ //#endregion
1225
+ //#region packages/ag/src/mouse-event-types.d.ts
1226
+ /**
1227
+ * Synthetic mouse event, mirroring React.MouseEvent / DOM MouseEvent.
1228
+ */
1229
+ interface SilveryMouseEvent {
1230
+ /**
1231
+ * Silvery layout X coordinate, comparable to Rect.x.
1232
+ * In terminal renderers this is measured in terminal cells and may be
1233
+ * fractional when SGR-Pixels mouse mode is active.
1234
+ */
1235
+ x: number;
1236
+ /**
1237
+ * Silvery layout Y coordinate, comparable to Rect.y.
1238
+ * In terminal renderers this is measured in terminal cells and may be
1239
+ * fractional when SGR-Pixels mouse mode is active.
1240
+ */
1241
+ y: number;
1242
+ /** Physical pixel X coordinate when the backend provides one. */
1243
+ clientX?: number;
1244
+ /** Physical pixel Y coordinate when the backend provides one. */
1245
+ clientY?: number;
1246
+ /** Mouse button: 0=left, 1=middle, 2=right */
1247
+ button: number;
1248
+ /** Modifier keys */
1249
+ altKey: boolean;
1250
+ ctrlKey: boolean;
1251
+ metaKey: boolean;
1252
+ shiftKey: boolean;
1253
+ /** Deepest node under cursor */
1254
+ target: AgNode;
1255
+ /** Node whose handler is currently firing (changes during bubble) */
1256
+ currentTarget: AgNode;
1257
+ /** Event type */
1258
+ type: "click" | "dblclick" | "tripleclick" | "mousedown" | "mouseup" | "mousemove" | "mouseenter" | "mouseleave" | "wheel";
1259
+ /** Monotonic timestamp for the input event. */
1260
+ timeStamp?: number;
1261
+ /** Monotonic id shared by events parsed from the same terminal input chunk. */
1262
+ inputBatchId?: number;
1263
+ /**
1264
+ * Click count for `click` / `dblclick` / `tripleclick` events
1265
+ * (mirrors DOM `MouseEvent.detail`).
1266
+ *
1267
+ * - 1 on a fresh click (`type === "click"`)
1268
+ * - 2 on a double-click (`type === "dblclick"`)
1269
+ * - 3 on a triple-click (`type === "tripleclick"`)
1270
+ * - undefined on non-click events
1271
+ */
1272
+ detail?: 1 | 2 | 3;
1273
+ /** Stop event from bubbling to parent nodes */
1274
+ stopPropagation(): void;
1275
+ /** Prevent default behavior */
1276
+ preventDefault(): void;
1277
+ /** Whether stopPropagation() was called */
1278
+ readonly propagationStopped: boolean;
1279
+ /** Whether preventDefault() was called */
1280
+ readonly defaultPrevented: boolean;
1281
+ /** Raw parsed mouse data from terminal protocol */
1282
+ nativeEvent: unknown;
1283
+ }
1284
+ /**
1285
+ * Synthetic wheel event, extending SilveryMouseEvent with scroll deltas.
1286
+ */
1287
+ interface SilveryWheelEvent extends SilveryMouseEvent {
1288
+ /** Vertical scroll: -1 (up) or +1 (down) */
1289
+ deltaY: number;
1290
+ /** Horizontal scroll: always 0 for terminals */
1291
+ deltaX: number;
1292
+ }
1293
+ interface MouseEventProps {
1294
+ onClick?: (event: SilveryMouseEvent) => void;
1295
+ onDoubleClick?: (event: SilveryMouseEvent) => void;
1296
+ /** Triple-click handler — fires after `onDoubleClick` when the user
1297
+ * produces a third click within 300ms / 2 cells of the first two. */
1298
+ onTripleClick?: (event: SilveryMouseEvent) => void;
1299
+ onMouseDown?: (event: SilveryMouseEvent) => void;
1300
+ onMouseUp?: (event: SilveryMouseEvent) => void;
1301
+ onMouseMove?: (event: SilveryMouseEvent) => void;
1302
+ onMouseEnter?: (event: SilveryMouseEvent) => void;
1303
+ onMouseLeave?: (event: SilveryMouseEvent) => void;
1304
+ onWheel?: (event: SilveryWheelEvent) => void;
1305
+ }
1306
+ //#endregion
1307
+ //#region packages/ag/src/types.d.ts
1308
+ /**
1309
+ * CSS user-select equivalent for controlling text selectability.
1310
+ * - "auto": inherit from parent (root resolves to "text")
1311
+ * - "none": not selectable
1312
+ * - "text": force selectable (overrides parent "none")
1313
+ * - "contain": selectable, but selection cannot escape this node's bounds
1314
+ *
1315
+ * Mouse selection is document/tree-aware by default: the active scope is the
1316
+ * nearest common selectable ancestor of the drag anchor and focus. `contain`
1317
+ * keeps its CSS meaning as an explicit hard boundary.
1318
+ */
1319
+ type UserSelect = "auto" | "none" | "text" | "contain";
1320
+ /**
1321
+ * A rectangle with position and size.
1322
+ * All values are in terminal columns/rows (integers).
1323
+ */
1324
+ interface Rect {
1325
+ /** X position (0-indexed terminal column) */
1326
+ x: number;
1327
+ /** Y position (0-indexed terminal row) */
1328
+ y: number;
1329
+ /** Width in terminal columns */
1330
+ width: number;
1331
+ /** Height in terminal rows */
1332
+ height: number;
1333
+ }
1334
+ /**
1335
+ * Terminal cursor shape (DECSCUSR).
1336
+ *
1337
+ * @deprecated Target-specific. Lives in core only as a back-compat alias for the
1338
+ * `CursorOffset.shape` deprecation cycle (see {@link CursorOffset.shape}). The
1339
+ * canonical home is `@silvery/ag-term/output#CursorShape`. Cross-target
1340
+ * renderers (canvas / DOM) must not branch on this enum — instead, they read
1341
+ * the focused-editable bit from `LayoutSignals` and map to whatever caret
1342
+ * concept their target supports. Removed in the next cycle.
1343
+ *
1344
+ * Lower-case names match the DECSCUSR vocabulary: `block` (steady #2),
1345
+ * `underline` (steady #4), `bar` (steady #6).
1346
+ */
1347
+ type CursorShape = "block" | "underline" | "bar";
1348
+ /**
1349
+ * Component-relative caret position declared as a Box prop.
1350
+ *
1351
+ * When set on a Box, the layout phase computes the absolute terminal
1352
+ * coordinates by adding the parent's `scrollRect` + the box's border + padding
1353
+ * + this offset. The result is exposed via `LayoutSignals.cursorRect` and read
1354
+ * by the scheduler's cursor-suffix emission. The caret naming reflects the
1355
+ * cross-target nature: in the terminal it manifests as the hardware cursor,
1356
+ * but on canvas/DOM targets it's the text-input caret rectangle.
1357
+ *
1358
+ * This is the "caret as layout output" path — it bypasses the React effect
1359
+ * chain entirely (`useCursor` → `useScrollRect` → `setCursorState`) so the
1360
+ * very first frame after mount emits the correct caret positioning ANSI.
1361
+ * See bead `km-silvery.view-as-layout-output` (Phase 2),
1362
+ * `km-silvery.cursor-invariants`, and `km-silvercode.cursor-startup-position`.
1363
+ */
1364
+ interface CursorOffset {
1365
+ /** Column offset within the box's content area (0-indexed) */
1366
+ col: number;
1367
+ /** Row offset within the box's content area (0-indexed) */
1368
+ row: number;
1369
+ /** Whether the caret should be visible. Default: true */
1370
+ visible?: boolean;
1371
+ /**
1372
+ * Terminal cursor shape (DECSCUSR).
1373
+ *
1374
+ * @deprecated Target-specific — DO NOT use in new code. The terminal layer
1375
+ * (`@silvery/ag-term`) derives the shape from focus + editable state at
1376
+ * scheduler/output time via the caretStyle map. Cross-target consumers
1377
+ * (canvas / DOM) ignore this field. Accepted for one cycle for back-compat;
1378
+ * removed in the next major. See `km-silvery.cursor-invariants` invariant 6.
1379
+ */
1380
+ shape?: CursorShape;
1381
+ }
1382
+ /**
1383
+ * Semantic selection intent declared on a Box — the user's "selected
1384
+ * substring" within this node's text content, expressed as character offsets.
1385
+ *
1386
+ * This is the **input** half of the selection-as-overlay model (Phase 4b of
1387
+ * `km-silvery.view-as-layout-output`):
1388
+ *
1389
+ * - **Input** (this type): `selectionIntent` — what the user wants selected.
1390
+ * - **Output** (`LayoutSignals.selectionFragments`): the resolved list of
1391
+ * rectangles (one per visual line spanned). Computed during the layout
1392
+ * pass; consumed by the selection renderer to paint highlight bg.
1393
+ *
1394
+ * Mirrors `CursorOffset`'s shape: a small declarative payload on the owning
1395
+ * Box. The layout phase runs `computeSelectionFragments(node)` to derive the
1396
+ * geometric fragments and pushes them onto the per-node signal. Components
1397
+ * like `TextArea`, `Text` (when selectable), or any node with a selected
1398
+ * substring can declare this prop.
1399
+ *
1400
+ * **Rules**:
1401
+ * - `from` and `to` are character offsets into the rendered text content of
1402
+ * the owning node (post-render, post-wrap). The fragment computation
1403
+ * walks the node's text layout to map offsets to visual rectangles.
1404
+ * - `from <= to`. A collapsed selection (`from === to`) produces zero
1405
+ * fragments — caret rendering is `cursorOffset`'s job.
1406
+ * - `null`/`undefined` on a Box means "no selection on this node" — that
1407
+ * node contributes no fragments.
1408
+ * - Multiple Boxes may declare `selectionIntent` simultaneously; the
1409
+ * aggregator (`findActiveSelectionFragments(root)`) concatenates fragments
1410
+ * from all currently-mounted declarers (Phase 4b — multi-node selection
1411
+ * is left for a future enhancement; v1 concatenation already covers the
1412
+ * "two adjacent nodes both selected" case).
1413
+ *
1414
+ * **Cross-target hygiene**: this type is purely semantic (offsets only). The
1415
+ * resolved `Rect[]` output and the actual highlight bg color stay terminal-
1416
+ * specific (or canvas/DOM-specific in future targets). Tracking bead:
1417
+ * `km-silvery.phase4-split-focus-selection`.
1418
+ */
1419
+ interface SelectionIntent {
1420
+ /**
1421
+ * Inclusive start offset (character index into the owning node's rendered
1422
+ * text content). Must be `>= 0` and `<= text.length`.
1423
+ */
1424
+ from: number;
1425
+ /**
1426
+ * Exclusive end offset (character index). Must be `>= from` and
1427
+ * `<= text.length`. When `from === to` the selection is collapsed and
1428
+ * produces zero fragments.
1429
+ */
1430
+ to: number;
1431
+ }
1432
+ /**
1433
+ * Twelve-placement vocabulary for floating decorations relative to an anchor
1434
+ * rect. The first segment names the side of the anchor the floating element
1435
+ * lives on; the second segment names the alignment along the perpendicular
1436
+ * axis (start, center, end). Mirrors Floating UI / Popper.js's vocabulary so
1437
+ * apps moving between targets can carry placement intent verbatim.
1438
+ *
1439
+ * The placement string maps deterministically to a rect via `placeFloating`.
1440
+ * Collision-aware auto-flip + auto-shift are layered on top by
1441
+ * `resolveFloatingPlacement` and opt in via `Decoration.collisionStrategy`.
1442
+ */
1443
+ type Placement = "top-start" | "top-center" | "top-end" | "bottom-start" | "bottom-center" | "bottom-end" | "left-start" | "left-center" | "left-end" | "right-start" | "right-center" | "right-end";
1444
+ /**
1445
+ * Collision policy for floating overlays. Mirrors the common Floating UI /
1446
+ * Popper progression while staying terminal-cell deterministic:
1447
+ *
1448
+ * - `"none"`: preserve the requested placement even if it overflows.
1449
+ * - `"flip"`: try the opposite side when the requested side overflows.
1450
+ * - `"shift"`: keep the requested side, but clamp the rect inside the boundary.
1451
+ * - `"flip-then-shift"`: flip if that improves the side-axis overflow, then clamp.
1452
+ * - `"hide"`: emit no rect when the requested placement cannot fit.
1453
+ */
1454
+ type CollisionStrategy = "none" | "flip" | "shift" | "flip-then-shift" | "hide";
1455
+ /**
1456
+ * Declarative overlay attached to a Box. The substrate v1 shipped here covers
1457
+ * three kinds — popover, tooltip, highlight — that share the "decoration
1458
+ * derived from semantic intent during layout" shape. Caret / focus / selection
1459
+ * keep their dedicated BoxProps (`cursorOffset`, `focused`, `selectionIntent`)
1460
+ * for ergonomic + back-compat reasons; everything else routes through
1461
+ * `decorations`.
1462
+ *
1463
+ * **`kind`** drives the geometry computation:
1464
+ * - `"popover"` and `"tooltip"`: anchor-relative placement via
1465
+ * `placeFloating(anchorRect, size, placement)`. The `placement` field is
1466
+ * required (no implicit default — apps must say where they want it).
1467
+ * `content` is opaque to the substrate — the renderer owns rendering.
1468
+ * - `"highlight"`: a rect-list output describing visible-line fragments
1469
+ * within the owning Box's content area. v1 ships only the bounding rect;
1470
+ * soft-wrap aware fragmentation is implemented in the same way as
1471
+ * `selectionFragments` (Phase 4b) and arrives in v2 once the find/replace
1472
+ * match-highlight first consumer ships.
1473
+ *
1474
+ * **`id`** is app-chosen, must be unique within a frame, and stable across
1475
+ * re-renders (consumers may key React-side state off it).
1476
+ *
1477
+ * Coordinate space is the same absolute terminal-cell space used by every
1478
+ * other rect signal (`cursorRect`, `selectionFragments`, etc.).
1479
+ *
1480
+ * **Out of scope for v1** (deferred to v2):
1481
+ * - Generic `kind: "custom"` extension hook
1482
+ * - Z-index / paint-order overrides (paint order is fixed: caret > focus >
1483
+ * selection > decorations > anchors)
1484
+ *
1485
+ * See `hub/silvery/design/overlay-anchor-system.md` for the design context.
1486
+ */
1487
+ type Decoration = {
1488
+ kind: "popover";
1489
+ id: string; /** Anchor target by id, looked up via `findAnchor(root, anchorId)`. */
1490
+ anchorId?: string;
1491
+ placement?: Placement; /** Intrinsic size for the floating rect (cells). Required for placement math. */
1492
+ size?: {
1493
+ width: number;
1494
+ height: number;
1495
+ }; /** Optional gap along the placement axis (cells). */
1496
+ offset?: number; /** Optional offset along the alignment axis (cells). */
1497
+ alignOffset?: number; /** Optional viewport collision policy. Default: "none". */
1498
+ collisionStrategy?: CollisionStrategy; /** Renderer-owned content. The substrate doesn't inspect this. */
1499
+ content?: unknown;
1500
+ } | {
1501
+ kind: "tooltip";
1502
+ id: string;
1503
+ anchorId?: string;
1504
+ placement?: Placement;
1505
+ size?: {
1506
+ width: number;
1507
+ height: number;
1508
+ };
1509
+ offset?: number;
1510
+ alignOffset?: number;
1511
+ collisionStrategy?: CollisionStrategy;
1512
+ content?: unknown;
1513
+ } | {
1514
+ kind: "highlight";
1515
+ id: string;
1516
+ /**
1517
+ * Highlight rect within the owning Box's content area, expressed in the
1518
+ * Box's local content-relative coordinates (origin = contentRect.{x,y},
1519
+ * size in cells). v1 emits one rect; soft-wrap fragmentation lands in
1520
+ * v2 alongside the find/replace consumer.
1521
+ */
1522
+ rect?: {
1523
+ x: number;
1524
+ y: number;
1525
+ width: number;
1526
+ height: number;
1527
+ };
1528
+ };
1529
+ /**
1530
+ * Layout-anchor identifier — names this Box as a lookup target so other
1531
+ * Boxes' decorations can reference it via `Decoration.anchorId`.
1532
+ *
1533
+ * The id is app-chosen, must be unique within a tree, and stable enough across
1534
+ * re-renders to survive React reconciliation without identity churn (use a
1535
+ * literal string from props, not a `useId()` value, unless you persist it).
1536
+ *
1537
+ * Anchors are recorded into a tree-scoped map at the end of layout phase by
1538
+ * `findAnchor(root, id)`. The map's value is the Box's `contentRect` (full
1539
+ * inner area) — placement math then derives edge rects via `placeFloating`.
1540
+ */
1541
+ interface AnchorRef {
1542
+ id: string;
1543
+ }
1544
+ /**
1545
+ * Per-node interactive state — written by pointer/selection/focus state machines,
1546
+ * read by theme/render for automatic styling.
1547
+ *
1548
+ * These are plain mutable booleans, NOT reactive signals. State machines set them
1549
+ * synchronously during event processing, and the next render reads them.
1550
+ * React re-renders are driven by the event processing, not signal subscriptions.
1551
+ *
1552
+ * The object is lazily created on first write to avoid overhead on non-interactive nodes.
1553
+ */
1554
+ interface InteractiveState {
1555
+ /** Pointer is over this node (mouseenter/mouseleave) */
1556
+ hovered: boolean;
1557
+ /** Pointer-down on this node, awaiting pointer-up (will receive click) */
1558
+ armed: boolean;
1559
+ /** Node is in the current selection set */
1560
+ selected: boolean;
1561
+ /** Node has keyboard focus */
1562
+ focused: boolean;
1563
+ /** A drag operation is hovering over this node */
1564
+ dropTarget: boolean;
1565
+ }
1566
+ /**
1567
+ * Silvery node types - the primitive elements in the render tree.
1568
+ *
1569
+ * - `silvery-viewport` is a leaf node hosting a foreign cell domain
1570
+ * (xtermjs PTY, replay frames, snapshot). See {@link viewport-types.ts}
1571
+ * and bead `@km/silvery/15513-surface-nested-composition-primitive`.
1572
+ * - `silvery-island` is the runtime-agnostic cell-grid mount primitive —
1573
+ * sibling of `silvery-box` / `silvery-text`. Holds an
1574
+ * {@link import("./island-types").IslandHandle} ref + per-node lifecycle.
1575
+ * Supersedes `silvery-viewport` in epic
1576
+ * `@km/silvery/15646-islands` (Phase 4 deletes `silvery-viewport`).
1577
+ */
1578
+ type AgNodeType = "silvery-root" | "silvery-box" | "silvery-text" | "silvery-viewport" | "silvery-island";
1579
+ /**
1580
+ * Flexbox properties that can be applied to Box nodes.
1581
+ */
1582
+ interface FlexboxProps {
1583
+ width?: number | string;
1584
+ height?: number | string;
1585
+ minWidth?: number | string;
1586
+ minHeight?: number | string;
1587
+ maxWidth?: number | string;
1588
+ maxHeight?: number | string;
1589
+ flexGrow?: number;
1590
+ flexShrink?: number;
1591
+ flexBasis?: number | string;
1592
+ flexDirection?: "row" | "column" | "row-reverse" | "column-reverse";
1593
+ flexWrap?: "nowrap" | "wrap" | "wrap-reverse";
1594
+ alignItems?: "flex-start" | "flex-end" | "center" | "stretch" | "baseline";
1595
+ alignSelf?: "auto" | "flex-start" | "flex-end" | "center" | "stretch" | "baseline";
1596
+ alignContent?: "flex-start" | "flex-end" | "center" | "stretch" | "space-between" | "space-around" | "space-evenly";
1597
+ justifyContent?: "flex-start" | "flex-end" | "center" | "space-between" | "space-around" | "space-evenly";
1598
+ padding?: number;
1599
+ paddingTop?: number;
1600
+ paddingBottom?: number;
1601
+ paddingLeft?: number;
1602
+ paddingRight?: number;
1603
+ paddingX?: number;
1604
+ paddingY?: number;
1605
+ margin?: number;
1606
+ marginTop?: number;
1607
+ marginBottom?: number;
1608
+ marginLeft?: number;
1609
+ marginRight?: number;
1610
+ marginX?: number;
1611
+ marginY?: number;
1612
+ gap?: number;
1613
+ columnGap?: number;
1614
+ rowGap?: number;
1615
+ position?: "relative" | "absolute" | "sticky" | "static";
1616
+ top?: number | string;
1617
+ left?: number | string;
1618
+ bottom?: number | string;
1619
+ right?: number | string;
1620
+ stickyTop?: number;
1621
+ stickyBottom?: number;
1622
+ aspectRatio?: number;
1623
+ display?: "flex" | "none";
1624
+ overflow?: "visible" | "hidden" | "scroll";
1625
+ overflowX?: "visible" | "hidden";
1626
+ overflowY?: "visible" | "hidden";
1627
+ /**
1628
+ * Child index to ensure visible. Declarative — the Box fires edge-based
1629
+ * ensure-visible when this value CHANGES (or on mount). Re-renders with
1630
+ * the same value are no-ops; content-height changes do not re-trigger
1631
+ * the ensure-visible pass.
1632
+ *
1633
+ * This "fire on change" semantic prevents viewport jumps when a visible
1634
+ * child grows (e.g. user clicks to expand a collapsible row): the Box no
1635
+ * longer re-anchors on every render. Matches the convention used by
1636
+ * `@tanstack/virtual`, `react-window`, iOS `UIScrollView.setContentOffset`
1637
+ * — imperative intent is separate from declarative anchor state.
1638
+ *
1639
+ * To re-fire ensure-visible against the SAME target (e.g. "scroll to
1640
+ * cursor, even though cursor didn't change"), toggle the value via undefined
1641
+ * first, or drive scroll via the explicit `scrollOffset` prop.
1642
+ */
1643
+ scrollTo?: number;
1644
+ /** Explicit scroll offset in rows (used when scrollTo is undefined for frozen scroll state) */
1645
+ scrollOffset?: number;
1646
+ }
1647
+ /**
1648
+ * Props for testing and identification.
1649
+ * These props are stored in the node for DOM query access.
1650
+ */
1651
+ interface TestProps {
1652
+ /** Element ID for DOM queries and visual debugging */
1653
+ id?: string;
1654
+ /** Test ID for querying nodes (like Playwright's data-testid) */
1655
+ testID?: string;
1656
+ /** Allow arbitrary data-* attributes for testing */
1657
+ [key: `data-${string}`]: unknown;
1658
+ }
1659
+ /**
1660
+ * Underline style variants (SGR 4:x codes).
1661
+ * - false: no underline
1662
+ * - 'single': standard underline (SGR 4 or 4:1)
1663
+ * - 'double': double underline (SGR 4:2)
1664
+ * - 'curly': curly/wavy underline (SGR 4:3)
1665
+ * - 'dotted': dotted underline (SGR 4:4)
1666
+ * - 'dashed': dashed underline (SGR 4:5)
1667
+ */
1668
+ type UnderlineStyle$1 = false | "single" | "double" | "curly" | "dotted" | "dashed";
1669
+ /**
1670
+ * Named underline styles — the string half of `underline: boolean | UnderlineStyleName`.
1671
+ * Excludes `false` so the boolean-or-string union doesn't have two falsy branches.
1672
+ */
1673
+ type UnderlineStyleName = Exclude<UnderlineStyle$1, false>;
1674
+ /**
1675
+ * Style properties for text rendering.
1676
+ */
1677
+ interface StyleProps {
1678
+ color?: string;
1679
+ backgroundColor?: string;
1680
+ bold?: boolean;
1681
+ italic?: boolean;
1682
+ /**
1683
+ * Enable underline. Accepts:
1684
+ * - `true` — standard single underline (equivalent to `"single"`)
1685
+ * - `false` — no underline
1686
+ * - `"single" | "double" | "curly" | "dotted" | "dashed"` — specific style variant
1687
+ *
1688
+ * A style name is equivalent to setting `underline=true` with that style.
1689
+ */
1690
+ underline?: boolean | UnderlineStyleName;
1691
+ /**
1692
+ * @deprecated Pass the style name directly to `underline` instead
1693
+ * (e.g. `underline="curly"`). `underlineStyle` is retained for backwards
1694
+ * compatibility and still takes precedence over `underline` when both are set.
1695
+ * Will be removed in a future major.
1696
+ */
1697
+ underlineStyle?: UnderlineStyle$1;
1698
+ /**
1699
+ * Underline color (independent of text color).
1700
+ * Uses SGR 58 (underline color). Falls back to text color if not specified.
1701
+ */
1702
+ underlineColor?: string;
1703
+ /**
1704
+ * Overline the cell — SGR 53/55. Independent of underline.
1705
+ *
1706
+ * SGR 53 places a line ABOVE the character cell; SGR 55 removes it.
1707
+ * Use this for top-edge indicators (e.g. overscroll-at-top), where an
1708
+ * underline on the first row would read as "this row is underlined" rather
1709
+ * than "you're bumped against the top". Overline on the top row and
1710
+ * underline on the bottom row are the semantically correct pair.
1711
+ *
1712
+ * Supported by most modern terminals (Ghostty, iTerm2, xterm with
1713
+ * `allowExtendedUnderlines` equivalent). The output phase skips SGR 53/55
1714
+ * when {@link TerminalCaps#overline} is false.
1715
+ */
1716
+ overline?: boolean;
1717
+ /**
1718
+ * Overline color — reserved. Currently not plumbed through the pipeline;
1719
+ * see bead `km-silvery.overline-color` for the follow-up that mirrors
1720
+ * {@link underlineColor}'s SGR 58 wiring for overline. Setting this today
1721
+ * is a no-op.
1722
+ */
1723
+ overlineColor?: string;
1724
+ strikethrough?: boolean;
1725
+ inverse?: boolean;
1726
+ /**
1727
+ * Text size scale factor via OSC 66 (Kitty v0.40+).
1728
+ *
1729
+ * Float multiplier: 2.0 = double (headings), 1.0 = normal, 0.5 = half (small print).
1730
+ * The terminal renders subsequent text at this scale until reset.
1731
+ * Requires a terminal that supports the kitty text sizing protocol.
1732
+ * Terminals without support silently ignore the escape sequence.
1733
+ */
1734
+ textSize?: number;
1735
+ }
1736
+ /**
1737
+ * Props for Box component.
1738
+ */
1739
+ interface BoxProps extends FlexboxProps, StyleProps, TestProps, MouseEventProps, DragEventProps, FocusEventProps {
1740
+ /** Text truncation mode for child text content (passed through to Text children). */
1741
+ wrap?: "wrap" | "wrap-truncate" | "hard" | "even" | "truncate" | "truncate-start" | "truncate-middle" | "truncate-end" | "clip" | boolean;
1742
+ borderStyle?: "single" | "double" | "round" | "bold" | "singleDouble" | "doubleSingle" | "classic";
1743
+ borderColor?: string;
1744
+ /** Background color for all border sides (shorthand). Per-side props override this. */
1745
+ borderBackgroundColor?: string;
1746
+ /** Background color for the top border (overrides borderBackgroundColor). */
1747
+ borderTopBackgroundColor?: string;
1748
+ /** Background color for the bottom border (overrides borderBackgroundColor). */
1749
+ borderBottomBackgroundColor?: string;
1750
+ /** Background color for the left border (overrides borderBackgroundColor). */
1751
+ borderLeftBackgroundColor?: string;
1752
+ /** Background color for the right border (overrides borderBackgroundColor). */
1753
+ borderRightBackgroundColor?: string;
1754
+ borderTop?: boolean;
1755
+ borderBottom?: boolean;
1756
+ borderLeft?: boolean;
1757
+ borderRight?: boolean;
1758
+ /**
1759
+ * Outline style — renders border characters OUTSIDE the box without affecting layout.
1760
+ *
1761
+ * Unlike `borderStyle` which adds border dimensions inside the box (shrinking the
1762
+ * content area), `outlineStyle` draws one cell beyond each edge — in the gap/margin
1763
+ * space between siblings. The layout engine sees no border at all.
1764
+ *
1765
+ * This matches CSS `outline` semantics: outside the border box, no layout impact.
1766
+ *
1767
+ * Use cases: focus rings, hover highlights, selection indicators, edit bounds —
1768
+ * anything that should visually frame a box without affecting layout or content.
1769
+ */
1770
+ outlineStyle?: "single" | "double" | "round" | "bold" | "singleDouble" | "doubleSingle" | "classic";
1771
+ /** Foreground color for the outline */
1772
+ outlineColor?: string;
1773
+ /** Apply dim styling to the outline */
1774
+ outlineDimColor?: boolean;
1775
+ /** Show top outline edge (default: true) */
1776
+ outlineTop?: boolean;
1777
+ /** Show bottom outline edge (default: true) */
1778
+ outlineBottom?: boolean;
1779
+ /** Show left outline edge (default: true) */
1780
+ outlineLeft?: boolean;
1781
+ /** Show right outline edge (default: true) */
1782
+ outlineRight?: boolean;
1783
+ /**
1784
+ * Override theme for this subtree — $token colors resolve against this theme.
1785
+ * Pushed onto the context theme stack during render phase tree walk.
1786
+ */
1787
+ theme?: Theme;
1788
+ /** CSS pointer-events equivalent. "none" makes this node and its subtree invisible to hit testing. */
1789
+ pointerEvents?: "auto" | "none";
1790
+ /**
1791
+ * CSS user-select equivalent. Controls whether text in this node is selectable.
1792
+ * - "auto" (default): inherit from parent. Root resolves to "text".
1793
+ * - "none": not selectable. Mouse-drag on this node does not start text selection.
1794
+ * - "text": force selectable, even if parent is "none".
1795
+ * - "contain": selectable, but selection range cannot escape this node's bounds.
1796
+ */
1797
+ userSelect?: UserSelect;
1798
+ /**
1799
+ * Whether this node can be dragged via mouse.
1800
+ * When true, mousedown + drag past threshold initiates a node drag gesture
1801
+ * instead of text selection. Not inherited — only the node with draggable=true
1802
+ * is draggable, not its children.
1803
+ */
1804
+ draggable?: boolean;
1805
+ /**
1806
+ * Capture pointer-style mouse events after mousedown.
1807
+ *
1808
+ * When true, a mousedown inside this node makes subsequent mousemove and
1809
+ * mouseup events for that press bubble from this node even if the cursor
1810
+ * leaves its one-cell hit box. Hover enter/leave still follows the real
1811
+ * cursor target. This is the terminal equivalent of pointer capture for
1812
+ * narrow draggable controls such as scrollbars.
1813
+ */
1814
+ mouseCapture?: boolean;
1815
+ onLayout?: (layout: Rect) => void;
1816
+ /**
1817
+ * Show scroll overflow indicators (▲N / ▼N) for scrollable containers.
1818
+ *
1819
+ * For bordered containers, indicators appear on the border.
1820
+ * For borderless containers, indicators overlay the content at top-right/bottom-right.
1821
+ *
1822
+ * Only applies when overflow='scroll'.
1823
+ */
1824
+ overflowIndicator?: boolean;
1825
+ /**
1826
+ * Declarative focus marker — "this Box is focused." When set on a Box, the
1827
+ * layout phase writes the node's id (or testID) to `LayoutSignals.focusedNodeId`
1828
+ * and the focus-renderer reads from that signal to paint the focus ring /
1829
+ * dim styling — bypassing the `useFocus` → `FocusManager` → `useSyncExternalStore`
1830
+ * chain on the first frame after mount.
1831
+ *
1832
+ * This is the **focus-as-layout-output** path (Phase 4a of
1833
+ * `km-silvery.view-as-layout-output`). It mirrors `cursorOffset` exactly:
1834
+ * a semantic boolean declared on the outer Box, resolved into a layout
1835
+ * signal during `syncRectSignals`, with a tree-walk lookup
1836
+ * (`findActiveFocusedNodeId`) that the renderer / scheduler consumes.
1837
+ *
1838
+ * **Precedence across nodes** (mirrors cursor invariant 1):
1839
+ * 1. Deepest visible focused declarer in paint-order wins. If two siblings
1840
+ * both have `focused === true`, the post-order tree walk picks the
1841
+ * later-rendered one — consistent with cursor's deepest-wins fallback.
1842
+ * 2. Otherwise null.
1843
+ *
1844
+ * **Identity**: the signal value is the node's `id` if present, else its
1845
+ * `testID`, else null. Apps that need stable focus identity should set one
1846
+ * of those props alongside `focused={true}`.
1847
+ *
1848
+ * **Cross-target hygiene**: `focused` is a semantic boolean. Terminal-specific
1849
+ * focus styling (dim, bold borders, focus ring) lives in `@silvery/ag-term`
1850
+ * or component-level styling. Canvas/DOM targets read the same id-level
1851
+ * signal but render their own focus indicator.
1852
+ *
1853
+ * **Back-compat**: `useFocus` continues to work as a deprecated wrapper that
1854
+ * routes through the legacy `FocusManager` path. Migrate to `focused={…}`
1855
+ * to opt into the layout-output path.
1856
+ */
1857
+ focused?: boolean;
1858
+ /**
1859
+ * Component-relative caret position. When set, the layout phase computes
1860
+ * absolute terminal coordinates (border + padding + offset relative to the
1861
+ * box's `scrollRect`) and writes them to `LayoutSignals.cursorRect`. The
1862
+ * scheduler reads this value to emit caret positioning ANSI on the very
1863
+ * first frame after mount — bypassing the React effect chain that
1864
+ * `useCursor` relies on.
1865
+ *
1866
+ * This is the "caret as layout output" path. The legacy `useCursor` hook
1867
+ * remains as a back-compat wrapper but its signal-effect bridge is unsafe
1868
+ * across conditional mounts (see `km-silvercode.cursor-startup-position`).
1869
+ *
1870
+ * **Precedence across nodes** (locked by `km-silvery.cursor-invariants` #1):
1871
+ * 1. Focused-editable wins — a Box that is `focused` AND has visible
1872
+ * `cursorOffset` always beats a non-focused declarer.
1873
+ * 2. Otherwise deepest-in-paint-order (post-order tree walk) wins.
1874
+ * 3. Otherwise null.
1875
+ *
1876
+ * **Clipping** (invariant #4): if the caret falls outside the nearest
1877
+ * `overflow="scroll"` / `"hidden"` ancestor's visible region, it is hidden
1878
+ * (no caret ANSI emitted, signal returns null). Caret rect at the exact
1879
+ * clip edge is treated as visible.
1880
+ */
1881
+ cursorOffset?: CursorOffset;
1882
+ /**
1883
+ * Semantic selection intent — the user's selected substring within this
1884
+ * Box's text content, declared as character offsets `{ from, to }`. The
1885
+ * layout phase resolves this into a list of rectangles
1886
+ * (`LayoutSignals.selectionFragments`) that the selection renderer reads
1887
+ * to paint highlight bg.
1888
+ *
1889
+ * This is the **selection-as-overlay** path (Phase 4b of
1890
+ * `km-silvery.view-as-layout-output`). It mirrors `cursorOffset` exactly:
1891
+ * a semantic declaration on the outer Box, resolved into geometric output
1892
+ * during `syncRectSignals`, with a tree-walk lookup
1893
+ * (`findActiveSelectionFragments`) that the renderer consumes.
1894
+ *
1895
+ * **Geometry**:
1896
+ * - Collapsed (`from === to`) → zero fragments. Caret rendering is
1897
+ * `cursorOffset`'s responsibility, not selection's.
1898
+ * - Single visual line → one rectangle from `from` to `to`.
1899
+ * - Multi-line (text contains `\n` characters) → one rectangle per visual
1900
+ * line: the first runs from `from` to end-of-line, middle lines span the
1901
+ * full content area width, the last runs from start-of-line to `to`.
1902
+ * - Wrap-aware fragment computation across word-wrapped visual lines is
1903
+ * limited in v1 — only embedded `\n` produces multi-line fragments. A
1904
+ * future iteration will register a wrap measurer so soft-wrapped text
1905
+ * produces the correct per-visual-line fragments. Track at
1906
+ * `km-silvery.overlay-anchor-system` (Phase 4c).
1907
+ *
1908
+ * **Multi-node selection**: each Box declares its own intent;
1909
+ * `findActiveSelectionFragments(root)` concatenates fragments across all
1910
+ * mounted declarers. Two adjacent nodes both selected is supported. Full
1911
+ * cross-node range selection (selecting from middle of node A through
1912
+ * node B) is a future enhancement.
1913
+ *
1914
+ * **Cross-target hygiene**: `selectionIntent` is purely semantic. The
1915
+ * resolved `Rect[]` is purely geometric. Terminal-specific bg highlight
1916
+ * styling lives in `@silvery/ag-term` (selection-renderer); canvas/DOM
1917
+ * targets read the same fragments and render their own highlight.
1918
+ *
1919
+ * **Back-compat**: `useSelection` continues to work as a deprecated
1920
+ * wrapper that reads from the legacy `SelectionFeature` capability.
1921
+ * Migrate to `selectionIntent={…}` to opt into the layout-output path.
1922
+ */
1923
+ selectionIntent?: SelectionIntent;
1924
+ /**
1925
+ * Names this Box as a layout-anchor lookup target. Other Boxes' decorations
1926
+ * can reference the id via `Decoration.anchorId` and the substrate resolves
1927
+ * the position via `findAnchor(root, id)`.
1928
+ *
1929
+ * Phase 4c of `km-silvery.view-as-layout-output` (overlay-anchor v1). The
1930
+ * registered rect is the Box's `contentRect` (border + padding excluded);
1931
+ * edge-specific rects are derived by `placeFloating` at consumption time.
1932
+ *
1933
+ * Pass a string for the simple case (`anchorRef="dropdown-trigger"`) or an
1934
+ * AnchorRef object if a future v2 wants to extend with edge metadata.
1935
+ *
1936
+ * **Stability**: ids should be stable across re-renders — React reconciler
1937
+ * preserves the AgNode identity, but registering a new id per render makes
1938
+ * `findAnchor` flap and breaks decoration layout. Use a literal string from
1939
+ * props, not a `useId()` value, unless persisted.
1940
+ */
1941
+ anchorRef?: string | AnchorRef;
1942
+ /**
1943
+ * Declarative overlays attached to this Box — popovers, tooltips,
1944
+ * highlights. Each entry is resolved into a geometric `DecorationRect` in
1945
+ * `LayoutSignals.decorationRects` during layout phase, and aggregated into
1946
+ * the per-frame `OverlayLayer` artifact returned alongside `term.frame`.
1947
+ *
1948
+ * Phase 4c of `km-silvery.view-as-layout-output` (overlay-anchor v1).
1949
+ *
1950
+ * **Paint order** is fixed (no z-index): caret > focus > selection >
1951
+ * decorations > anchors. Within `decorations` itself, list order determines
1952
+ * paint order (later entries paint on top of earlier ones).
1953
+ *
1954
+ * **Anchor lookup**: popover/tooltip kinds reference an `anchorId`; the
1955
+ * substrate calls `findAnchor(root, id)` at layout time. If the anchor
1956
+ * isn't found this frame, the decoration emits an empty rect list (the
1957
+ * renderer skips it). By default placement is fixed; opt into viewport
1958
+ * collision handling with `collisionStrategy`.
1959
+ *
1960
+ * **Stable identity**: pass a memoized array if React's referential equality
1961
+ * matters for downstream consumers; the substrate itself recomputes
1962
+ * decoration rects every layout pass, so referential identity isn't load-
1963
+ * bearing on the substrate side.
1964
+ */
1965
+ decorations?: readonly Decoration[];
1966
+ /**
1967
+ * Virtualization-internal: set only by virtual list placeholders (e.g.
1968
+ * ListView's leading/trailing spacer Boxes). **Do not set on ordinary Box
1969
+ * children** — the default (1 visual = 1 logical item) is correct.
1970
+ *
1971
+ * For a child of an `overflow="scroll"` container: declare that this child
1972
+ * is a placeholder representing multiple logical items. When the child is
1973
+ * fully scrolled out above/below the viewport, the parent's
1974
+ * `hiddenAbove`/`hiddenBelow` count is incremented by this value instead of
1975
+ * 1, so `▲N`/`▼N` indicators reflect real items rather than rendered
1976
+ * placeholder boxes.
1977
+ *
1978
+ * Only read by the parent scroll container. Defaults to 1 (treat as a
1979
+ * single visual item). Must be >= 0.
1980
+ *
1981
+ * @internal
1982
+ */
1983
+ representsItems?: number;
1984
+ /**
1985
+ * Declare this Box as a container-query container (A0.1).
1986
+ *
1987
+ * Descendants' `cqi` / `cqmin` values resolve against this Box's frozen
1988
+ * inline-size, captured during layout Pass 1. Combined with `containSize`,
1989
+ * this Box's inline-size is invariant under children's CQ branch flips.
1990
+ *
1991
+ * `"inline-size"` — equivalent to CSS `container-type: inline-size`. Maps to
1992
+ * `flexily.CONTAINER_TYPE_INLINE_SIZE` at the engine layer.
1993
+ * `"normal"` — opt out (default). Phase 1 supports only the two values.
1994
+ */
1995
+ containerType?: "normal" | "inline-size";
1996
+ /**
1997
+ * Enable CSS `contain: size` on this Box (A0.1).
1998
+ *
1999
+ * When true, children's intrinsic sizes do not propagate into this Box's
2000
+ * inline-size. Required pairing with `containerType: "inline-size"` for a
2001
+ * sound CQ container — without it, child sizes feed back into container
2002
+ * size and break the two-phase invariance guarantee. The dev-mode
2003
+ * `intrinsic-leak` assertion throws on the unsound configuration.
2004
+ *
2005
+ * Phase 1 contains inline-size only.
2006
+ */
2007
+ containSize?: boolean;
2008
+ /**
2009
+ * Container name (CSS `container-name`) — referenced by `@container <name>`
2010
+ * queries. Phase 1 is informational only (single anonymous container per
2011
+ * subtree); named-container resolution arrives with the silvery-layer CQ
2012
+ * matcher.
2013
+ */
2014
+ containerName?: string;
2015
+ /**
2016
+ * Container-query branches — declarative responsive styling against this
2017
+ * Box's frozen inline-size (A0.1 substrate). Phase 1 ships the engine hook;
2018
+ * the matcher that interprets `containerQueries` and applies branch styles
2019
+ * to children lands as part of Phase A (silvery layer).
2020
+ *
2021
+ * Until the matcher ships, the prop is reserved for forward-compat — passing
2022
+ * it does not yet apply branch styles. Use cqi units directly for now.
2023
+ */
2024
+ containerQueries?: readonly ContainerQueryBranch[];
2025
+ /**
2026
+ * Single-pass fit-content lane snap (A0.2). The engine-resolved lane
2027
+ * primitive — no React round-trip, no measurement subtree.
2028
+ *
2029
+ * Box's inline-size snaps to the smallest lane that fits its children's
2030
+ * max-content. Lane entries accept numbers (treated as cells) or strings
2031
+ * like `"100cqi"` / `"50cqi"` / `"100cqmin"` (parsed at this layer into
2032
+ * the engine's unit form).
2033
+ *
2034
+ * If max-content exceeds every lane, the LAST lane is used — by convention
2035
+ * place lanes in ascending order so this behaves as "biggest lane wins".
2036
+ *
2037
+ * Example (chat content lanes):
2038
+ * <Box fitWidth={[80, 120, "100cqi"]}>
2039
+ * {messageBlocks}
2040
+ * </Box>
2041
+ *
2042
+ * Engine requirement: flexily-only. Under yoga, throws at first paint via
2043
+ * `requireCapability("fitWidth", ...)`.
2044
+ */
2045
+ fitWidth?: readonly (number | string)[];
2046
+ }
2047
+ /**
2048
+ * A container-query branch (A0.1 substrate; matcher in Phase A).
2049
+ *
2050
+ * Matches against the container's frozen inline-size. Phase 1 supports
2051
+ * width-based conditions only.
2052
+ */
2053
+ interface ContainerQueryBranch {
2054
+ /** Match when container inline-size is at least this many cells. */
2055
+ readonly minWidth?: number;
2056
+ /** Match when container inline-size is at most this many cells. */
2057
+ readonly maxWidth?: number;
2058
+ /** Match when container inline-size equals this many cells. */
2059
+ readonly width?: number;
2060
+ /**
2061
+ * Branch styles applied to descendants when this condition matches. Subset
2062
+ * of `BoxProps` / `TextProps` style fields; full schema arrives with the
2063
+ * matcher.
2064
+ */
2065
+ readonly apply: Record<string, unknown>;
2066
+ }
2067
+ /**
2068
+ * Props for Text component.
2069
+ */
2070
+ /**
2071
+ * Flex item subset of FlexboxProps — the props that make sense on a leaf
2072
+ * (Text). Box accepts the full FlexboxProps; Text only accepts the props
2073
+ * that affect how it participates as a flex item (sizing, growth, shrink)
2074
+ * — not props that affect how it lays out its non-existent children
2075
+ * (flexDirection, justifyContent, alignItems, gap, ...).
2076
+ *
2077
+ * This is the canonical CSS escape hatch: instead of wrapping Text in a
2078
+ * Box to apply `flexShrink={0}` or `minWidth={0}`, set them directly on
2079
+ * the Text. See bead km-silvery.text-intrinsic-vs-render.
2080
+ */
2081
+ interface TextFlexItemProps {
2082
+ /** CSS `flex-grow` — proportion of free positive space along main axis. */
2083
+ flexGrow?: number;
2084
+ /** CSS `flex-shrink` — proportion of negative free space along main axis. */
2085
+ flexShrink?: number;
2086
+ /** CSS `flex-basis` — initial main-size before grow/shrink distribution. */
2087
+ flexBasis?: number | string;
2088
+ /** Cross-axis self-alignment override. */
2089
+ alignSelf?: "auto" | "flex-start" | "flex-end" | "center" | "stretch" | "baseline";
2090
+ /** CSS `min-width` — floor for shrink distribution. */
2091
+ minWidth?: number | string;
2092
+ /** CSS `min-height`. */
2093
+ minHeight?: number | string;
2094
+ /** CSS `max-width` — ceiling for grow distribution. */
2095
+ maxWidth?: number | string;
2096
+ /** CSS `max-height`. */
2097
+ maxHeight?: number | string;
2098
+ }
2099
+ interface TextProps extends StyleProps, TextFlexItemProps, TestProps, MouseEventProps {
2100
+ children?: React.ReactNode;
2101
+ /**
2102
+ * Wrap / truncate mode. Each value bundles the CSS-equivalent
2103
+ * `white-space` + `overflow-wrap` + `text-overflow` axes into one
2104
+ * named composite. See `vendor/silvery/docs/components/Text.md` for the
2105
+ * full table.
2106
+ *
2107
+ * - `"wrap"` (default): word-aware multi-line wrap, soft-break separators
2108
+ * for path-style tokens, character wrap as last-resort fallback.
2109
+ * - `"wrap-truncate"`: same as `"wrap"` but ellipsis-truncates the
2110
+ * offending line when an unbreakable atomic token would otherwise
2111
+ * character-wrap. CSS analogue
2112
+ * `overflow-wrap: break-word` + `text-overflow: ellipsis`.
2113
+ * - `"truncate"` / `"truncate-end"` / `"truncate-start"` /
2114
+ * `"truncate-middle"`: single-line, ellipsis at the named position.
2115
+ * - `"clip"`: single-line, hard clip without ellipsis.
2116
+ * - `"hard"`: character-wrap regardless of word boundaries (Ink compat).
2117
+ * - `"even"`: optimal Knuth-Plass wrapping (minimize raggedness).
2118
+ * - `false`: no wrap / no clip (overflows container — avoid in bordered
2119
+ * cells).
2120
+ */
2121
+ wrap?: "wrap" | "wrap-truncate" | "hard" | "even" | "truncate" | "truncate-start" | "truncate-middle" | "truncate-end" | "clip" | boolean;
2122
+ /** Internal transform function applied to each rendered line. Used by Transform component. */
2123
+ internal_transform?: (line: string, index: number) => string;
2124
+ /**
2125
+ * Per-node background-conflict policy. Overrides the global / context
2126
+ * `BgConflictMode` for the cells this Text node paints.
2127
+ *
2128
+ * Background conflicts (ANSI bg in text content layered over an silvery
2129
+ * `backgroundColor`) normally `throw` — that strictness is the right
2130
+ * safety net for an silvery app's own pipeline bugs. But a component
2131
+ * mirroring arbitrary EXTERNAL ANSI (e.g. `<Terminal>` re-encoding a
2132
+ * captured terminal grid, where chalk status bars are conflict-rich by
2133
+ * nature) *expects* conflicts. Such a component sets `bgConflict="ignore"`
2134
+ * on the `<Text>` rows it paints so the global throw stays a safety net
2135
+ * everywhere else.
2136
+ *
2137
+ * - `"ignore"`: no detection for this node's cells.
2138
+ * - `"warn"`: log a deduplicated warning instead of throwing.
2139
+ * - `"throw"`: throw (the default behavior when unset).
2140
+ *
2141
+ * When unset, the pipeline falls back to the context mode, then the
2142
+ * global mode (`SILVERY_BG_CONFLICT`, default `"throw"`).
2143
+ */
2144
+ bgConflict?: "ignore" | "warn" | "throw";
2145
+ }
2146
+ /**
2147
+ * The core Silvery node - represents an element in the render tree.
2148
+ *
2149
+ * Each node has:
2150
+ * - A Yoga node for layout calculation
2151
+ * - Computed layout after Yoga runs
2152
+ * - Subscribers that get notified when layout changes
2153
+ * - Dirty flags for incremental updates
2154
+ */
2155
+ interface AgNode {
2156
+ /** Node type */
2157
+ type: AgNodeType;
2158
+ /** Props passed to this node */
2159
+ props: BoxProps | TextProps | Record<string, unknown>;
2160
+ /** Child nodes */
2161
+ children: AgNode[];
2162
+ /** Parent node (null for root) */
2163
+ parent: AgNode | null;
2164
+ /** The layout node for layout calculation (null for raw text nodes) */
2165
+ layoutNode: LayoutNode | null;
2166
+ /** Computed layout from previous render (for change detection) */
2167
+ prevLayout: Rect | null;
2168
+ /**
2169
+ * Content-relative position (like CSS offsetTop/offsetLeft).
2170
+ * Position within the scrollable content, ignoring scroll offsets.
2171
+ * Set after layout phase.
2172
+ */
2173
+ boxRect: Rect | null;
2174
+ /**
2175
+ * Screen-relative position (like CSS getBoundingClientRect).
2176
+ * Actual position on the terminal screen, accounting for scroll offsets.
2177
+ * Set after screen rect phase.
2178
+ *
2179
+ * Note: For sticky children, this reflects the node's layout position
2180
+ * adjusted for scroll offsets, NOT the actual render position. Use
2181
+ * `screenRect` for the actual pixel position on screen.
2182
+ */
2183
+ scrollRect: Rect | null;
2184
+ /** Previous screen rect (for change detection in notifyLayoutSubscribers) */
2185
+ prevScrollRect: Rect | null;
2186
+ /**
2187
+ * Actual render position on the terminal screen.
2188
+ * For non-sticky nodes, this equals `scrollRect`.
2189
+ * For sticky nodes (position="sticky"), this accounts for sticky render
2190
+ * offsets — the position where pixels are actually painted.
2191
+ *
2192
+ * Use this for hit testing, cursor positioning, and any feature that
2193
+ * needs to know where a node visually appears on screen.
2194
+ * Set after screen rect phase.
2195
+ */
2196
+ screenRect: Rect | null;
2197
+ /** Previous render rect (for change detection) */
2198
+ prevScreenRect: Rect | null;
2199
+ /** Epoch when layout changed (position or size).
2200
+ * Set by propagateLayout in layout phase. Compared against renderEpoch by render phase.
2201
+ * This is the authoritative signal for "did layout change?" — unlike
2202
+ * !rectEqual(prevLayout, boxRect) which becomes stale when layout
2203
+ * phase skips (no dirty nodes).
2204
+ * Value: renderEpoch when dirty, INITIAL_EPOCH (-1) when clean. */
2205
+ layoutChangedThisFrame: number;
2206
+ /**
2207
+ * Bit-packed dirty flags for the current epoch.
2208
+ *
2209
+ * Seven dirty flags packed into a single number:
2210
+ * bit 0 (CONTENT_BIT): content changed (text content or content-affecting props)
2211
+ * bit 1 (STYLE_PROPS_BIT): visual props changed (color, bg, border, etc.)
2212
+ * bit 2 (BG_BIT): backgroundColor specifically changed
2213
+ * bit 3 (CHILDREN_BIT): direct children added/removed/reordered
2214
+ * bit 4 (SUBTREE_BIT): this node or any descendant has dirty content/layout
2215
+ * bit 5 (ABS_CHILD_BIT): absolute child had structural changes
2216
+ * bit 6 (DESC_OVERFLOW_BIT): descendant overflow changed
2217
+ *
2218
+ * Outlines do NOT get a dirty bit — the decoration phase redraws them
2219
+ * every frame with per-cell snapshots (see pipeline/decoration-phase.ts).
2220
+ *
2221
+ * Check: `isDirty(node.dirtyBits, node.dirtyEpoch, BIT)`
2222
+ * Set: `node.dirtyBits = setDirtyBit(node.dirtyBits, node.dirtyEpoch, BIT); node.dirtyEpoch = getRenderEpoch()`
2223
+ * Clear: `advanceRenderEpoch()` — all nodes instantly become clean
2224
+ *
2225
+ * NOTE: measure phase may clear CONTENT_BIT — STYLE_PROPS_BIT acts as the
2226
+ * surviving witness for style changes. See render-phase.ts contentAreaAffected.
2227
+ */
2228
+ dirtyBits: number;
2229
+ /**
2230
+ * Epoch when dirtyBits was last written.
2231
+ * When `dirtyEpoch !== renderEpoch`, all bits are stale (node is clean).
2232
+ * Value: renderEpoch when any bit is dirty, INITIAL_EPOCH (-1) when clean.
2233
+ */
2234
+ dirtyEpoch: number;
2235
+ /** Text content for text nodes */
2236
+ textContent?: string;
2237
+ /** True if this is a raw text node (created by createTextInstance) */
2238
+ isRawText?: boolean;
2239
+ /** True if this node is hidden (for Suspense support) */
2240
+ hidden?: boolean;
2241
+ /** Sticky children with computed render positions (for non-scroll containers).
2242
+ * When a parent has sticky children but is NOT a scroll container, this array
2243
+ * holds the computed render offsets. Same shape as scrollState.stickyChildren. */
2244
+ stickyChildren?: Array<{
2245
+ /** Index of the sticky child */index: number; /** Computed Y offset to render at (relative to parent content area) */
2246
+ renderOffset: number; /** Original natural Y position (relative to parent content area) */
2247
+ naturalTop: number; /** Height of the sticky element */
2248
+ height: number;
2249
+ }>;
2250
+ /** Inline rects for virtual text nodes (no layout node). Computed during text rendering.
2251
+ * Array for wrapped text (one rect per line fragment). Enables hit testing on nested Text. */
2252
+ inlineRects?: Array<{
2253
+ x: number;
2254
+ y: number;
2255
+ width: number;
2256
+ height: number;
2257
+ }> | null;
2258
+ /**
2259
+ * Render-phase flag: "did this Box have an attr overlay (underline /
2260
+ * strikethrough / etc.) applied in the previous frame?" Written by the
2261
+ * render phase after `applyBoxAttrOverlay`. Read next frame to decide
2262
+ * whether `stylePropsDirty` on the Box must escalate to `contentAreaAffected`
2263
+ * (so the prev-frame merge-attr bits can be cleared via re-render).
2264
+ *
2265
+ * `mergeAttrsInRect` OR-combines — it can't clear bits. So when a Box's
2266
+ * attr overlay goes away (true → false, or style change), the clone buffer
2267
+ * still carries the old attr bits. This flag lets us detect the "had overlay
2268
+ * in prev frame" case without storing prev props.
2269
+ *
2270
+ * Only meaningful for silvery-box nodes. Defaults to undefined / false.
2271
+ * @internal
2272
+ */
2273
+ hadBoxAttrOverlay?: boolean;
2274
+ /**
2275
+ * Interactive state signals — written by pointer/selection/focus state machines,
2276
+ * read by theme/render for automatic styling (hover highlights, focus rings, etc.).
2277
+ *
2278
+ * Lazily created on first write. Null means no interactive state has been set.
2279
+ * See InteractiveState for field docs.
2280
+ */
2281
+ interactiveState?: InteractiveState | null;
2282
+ /**
2283
+ * Viewport state for silvery-viewport nodes. Lazily created by the
2284
+ * `<Viewport>` React component at mount; read by the pipeline render
2285
+ * phase to blit the foreign cell buffer at the node's boxRect.
2286
+ * See `viewport-types.ts` and bead `@km/silvery/15513`.
2287
+ *
2288
+ * Deprecated by `<Island>` / {@link islandState} in epic
2289
+ * `@km/silvery/15646-islands`; Phase 4 deletes this slot.
2290
+ */
2291
+ viewportState?: ViewportNodeState | null;
2292
+ /**
2293
+ * Island state for silvery-island nodes. Lazily created by `createIsland()`
2294
+ * / `<Island>` at mount; read by the pipeline render phase to blit the
2295
+ * guest's cell buffer at the node's boxRect, and by the host aggregator
2296
+ * (create-app.tsx) to derive focused-subtree protocol modes.
2297
+ * See `island-types.ts` and bead `@km/silvery/15646-islands`.
2298
+ */
2299
+ islandState?: IslandNodeState | null;
2300
+ /** Scroll state for overflow='scroll' containers */
2301
+ scrollState?: {
2302
+ /** Current scroll offset (in terminal rows) */offset: number; /** Previous scroll offset from last render (for incremental rendering) */
2303
+ prevOffset: number;
2304
+ /**
2305
+ * The `scrollTo` prop value processed in the previous frame.
2306
+ *
2307
+ * Used to distinguish "new intent" (scrollTo changed — user pressed a key
2308
+ * or an external setter moved the target) from "same intent" (scrollTo
2309
+ * unchanged — this frame is just a re-render caused by content growth or
2310
+ * style changes).
2311
+ *
2312
+ * Edge-based ensure-visible fires for NEW intent. Same intent skips
2313
+ * re-anchoring when the target's top edge is still in the viewport —
2314
+ * otherwise growing a visible item would shift the viewport down and
2315
+ * push content above it out of view ("the whole page jumps on click").
2316
+ */
2317
+ prevScrollTo?: number; /** Total content height (all children) */
2318
+ contentHeight: number; /** Visible height (container height minus borders/padding) */
2319
+ viewportHeight: number; /** Index of first visible child */
2320
+ firstVisibleChild: number; /** Index of last visible child */
2321
+ lastVisibleChild: number; /** Previous first visible child from last render (for incremental rendering) */
2322
+ prevFirstVisibleChild: number; /** Previous last visible child from last render (for incremental rendering) */
2323
+ prevLastVisibleChild: number; /** Count of items hidden above viewport */
2324
+ hiddenAbove: number; /** Count of items hidden below viewport */
2325
+ hiddenBelow: number; /** Sticky children with their computed render positions */
2326
+ stickyChildren?: Array<{
2327
+ /** Index of the sticky child */index: number; /** Computed Y offset to render at (relative to viewport, not content) */
2328
+ renderOffset: number; /** Original natural Y position (before sticky adjustment) */
2329
+ naturalTop: number; /** Height of the sticky element */
2330
+ height: number;
2331
+ }>;
2332
+ };
2333
+ }
2334
+ /**
2335
+ * Text attributes that can be applied to a cell.
2336
+ */
2337
+ interface CellAttrs$1 {
2338
+ bold?: boolean;
2339
+ dim?: boolean;
2340
+ italic?: boolean;
2341
+ /** Simple underline flag (for backwards compatibility) */
2342
+ underline?: boolean;
2343
+ /**
2344
+ * Underline style: 'single' | 'double' | 'curly' | 'dotted' | 'dashed'.
2345
+ * When set, takes precedence over the underline boolean.
2346
+ */
2347
+ underlineStyle?: UnderlineStyle$1;
2348
+ strikethrough?: boolean;
2349
+ inverse?: boolean;
2350
+ }
2351
+ /**
2352
+ * A single cell in the terminal buffer.
2353
+ */
2354
+ interface Cell$1 {
2355
+ /** The character (grapheme cluster) in this cell */
2356
+ char: string;
2357
+ /** Foreground color (ANSI code or RGB) */
2358
+ fg: string | null;
2359
+ /** Background color (ANSI code or RGB) */
2360
+ bg: string | null;
2361
+ /** Text attributes */
2362
+ attrs: CellAttrs$1;
2363
+ /** True if this is a wide character (CJK) that takes 2 cells */
2364
+ wide: boolean;
2365
+ /** True if this cell is the continuation of a wide character */
2366
+ continuation: boolean;
2367
+ }
2368
+ /**
2369
+ * Keyboard event with key information and modifiers.
2370
+ */
2371
+ interface KeyEvent$1 {
2372
+ type: "key";
2373
+ /** The key pressed (character or key name like 'ArrowUp') */
2374
+ key: string;
2375
+ /** Ctrl modifier was held */
2376
+ ctrl?: boolean;
2377
+ /** Meta/Alt modifier was held */
2378
+ meta?: boolean;
2379
+ /** Shift modifier was held */
2380
+ shift?: boolean;
2381
+ /** Alt/Option modifier was held */
2382
+ alt?: boolean;
2383
+ /** Super/Cmd modifier was held. Requires Kitty protocol. */
2384
+ super?: boolean;
2385
+ /** Hyper modifier was held. Requires Kitty protocol. */
2386
+ hyper?: boolean;
2387
+ /** Kitty event type. Requires Kitty flag 2. */
2388
+ eventType?: "press" | "repeat" | "release";
2389
+ /** CapsLock is active. Kitty modifier bit 6. */
2390
+ capsLock?: boolean;
2391
+ /** NumLock is active. Kitty modifier bit 7. */
2392
+ numLock?: boolean;
2393
+ }
2394
+ /**
2395
+ * Mouse event with position and button information.
2396
+ */
2397
+ interface MouseEvent {
2398
+ type: "mouse";
2399
+ /** X position in terminal columns (0-indexed) */
2400
+ x: number;
2401
+ /** Y position in terminal rows (0-indexed) */
2402
+ y: number;
2403
+ /** Mouse button (0=left, 1=middle, 2=right) */
2404
+ button: number;
2405
+ /** Event action */
2406
+ action: "down" | "up" | "move" | "wheel";
2407
+ /** Wheel delta for scroll events */
2408
+ delta?: number;
2409
+ }
2410
+ /**
2411
+ * Terminal resize event.
2412
+ */
2413
+ interface ResizeEvent {
2414
+ type: "resize";
2415
+ /** New width in columns */
2416
+ width: number;
2417
+ /** New height in rows */
2418
+ height: number;
2419
+ }
2420
+ /**
2421
+ * Terminal focus event.
2422
+ */
2423
+ interface FocusEvent$1 {
2424
+ type: "focus";
2425
+ }
2426
+ /**
2427
+ * Terminal blur event.
2428
+ */
2429
+ interface BlurEvent {
2430
+ type: "blur";
2431
+ }
2432
+ /**
2433
+ * Signal event (SIGINT, SIGTERM, etc.).
2434
+ */
2435
+ interface SignalEvent {
2436
+ type: "signal";
2437
+ /** Signal name (e.g., 'SIGINT', 'SIGTERM') */
2438
+ signal: string;
2439
+ }
2440
+ /**
2441
+ * Custom event for extensibility.
2442
+ */
2443
+ interface CustomEvent {
2444
+ type: "custom";
2445
+ /** Event name */
2446
+ name: string;
2447
+ /** Event data */
2448
+ data: unknown;
2449
+ }
2450
+ /**
2451
+ * Union of all event types.
2452
+ *
2453
+ * Events drive the render loop in interactive mode. When events are present,
2454
+ * the render loop runs until exit() is called. When events are absent,
2455
+ * the render completes when the UI is stable.
2456
+ */
2457
+ type Event = KeyEvent$1 | MouseEvent | ResizeEvent | FocusEvent$1 | BlurEvent | SignalEvent | CustomEvent;
2458
+ /**
2459
+ * Event source that can be subscribed to and unsubscribed from.
2460
+ */
2461
+ interface EventSource {
2462
+ /** Subscribe to events, returns unsubscribe function */
2463
+ subscribe(handler: (event: Event) => void): () => void;
2464
+ /** Convert to async iterable */
2465
+ [Symbol.asyncIterator](): AsyncIterator<Event>;
2466
+ }
2467
+ //#endregion
2468
+ //#region packages/ag-term/src/mouse.d.ts
2469
+ /**
2470
+ * SGR mouse event parsing (mode 1006) and SGR-Pixels parsing (mode 1016).
2471
+ *
2472
+ * SGR format: CSI < button;x;y M (press) or CSI < button;x;y m (release)
2473
+ *
2474
+ * Button encoding:
2475
+ * - Bits 0-1: 0=left, 1=middle, 2=right, 3=release (X10 only, not SGR)
2476
+ * - Bit 2 (+4): Shift held
2477
+ * - Bit 3 (+8): Meta/Alt held
2478
+ * - Bit 4 (+16): Ctrl held
2479
+ * - Bit 5 (+32): Motion event (mouse moved while button held)
2480
+ * - Bits 6-7: 64=wheel-up, 65=wheel-down, 66=wheel-left, 67=wheel-right
2481
+ */
2482
+ /**
2483
+ * Parsed mouse event from SGR mouse protocol.
2484
+ */
2485
+ interface ParsedMouse {
2486
+ /** Mouse button: 0=left, 1=middle, 2=right */
2487
+ button: number;
2488
+ /**
2489
+ * Silvery layout X coordinate, in terminal cells.
2490
+ * Integer in SGR 1006 mode; fractional when parsed from SGR-Pixels 1016.
2491
+ */
2492
+ x: number;
2493
+ /**
2494
+ * Silvery layout Y coordinate, in terminal cells.
2495
+ * Integer in SGR 1006 mode; fractional when parsed from SGR-Pixels 1016.
2496
+ */
2497
+ y: number;
2498
+ /** Physical pixel X coordinate, present only for SGR-Pixels 1016. */
2499
+ clientX?: number;
2500
+ /** Physical pixel Y coordinate, present only for SGR-Pixels 1016. */
2501
+ clientY?: number;
2502
+ /** Coordinate mode used by the parser. */
2503
+ coordinateMode: "cell" | "pixel";
2504
+ /** Event action */
2505
+ action: "down" | "up" | "move" | "wheel";
2506
+ /** Wheel delta: -1 for up, +1 for down */
2507
+ delta?: number;
2508
+ /** Shift was held */
2509
+ shift: boolean;
2510
+ /** Alt/Meta was held */
2511
+ meta: boolean;
2512
+ /** Ctrl was held */
2513
+ ctrl: boolean;
2514
+ /** Monotonic timestamp when the terminal input chunk was received. */
2515
+ receivedAt?: number;
2516
+ /** Monotonic id shared by events parsed from the same terminal input chunk. */
2517
+ inputBatchId?: number;
2518
+ }
2519
+ interface ParseMouseOptions {
2520
+ coordinateMode?: "cell" | "pixel";
2521
+ cellSize?: {
2522
+ width: number;
2523
+ height: number;
2524
+ };
2525
+ }
2526
+ /**
2527
+ * Parse an SGR mouse sequence.
2528
+ *
2529
+ * Return semantics (see ProtocolError in @silvery/ansi for the full contract):
2530
+ * - `null` — input does not match the SGR mouse shape `CSI < B;X;Y [Mm]`.
2531
+ * No "committed but malformed" branch exists here: either the full SGR
2532
+ * shape matches (parse succeeds) or it doesn't (null = next-parser-please).
2533
+ *
2534
+ * The bead 15127 audit listed this parser for review, but the regex-based
2535
+ * shape match means there's no place where the parser commits to "this is
2536
+ * a mouse event" and then fails on body validation — both happen at the
2537
+ * same point. Loud-error tightening here would require a stricter
2538
+ * sub-grammar (e.g. validating button-code ranges), tracked separately.
2539
+ *
2540
+ * @returns ParsedMouse or null if not a valid mouse sequence
2541
+ */
2542
+ declare function parseMouseSequence(input: string, options?: ParseMouseOptions): ParsedMouse | null;
2543
+ /** Check if a raw input string is a mouse sequence */
2544
+ declare function isMouseSequence(input: string): boolean;
2545
+ //#endregion
2546
+ //#region packages/ag-term/src/ansi/types.d.ts
2547
+ /**
2548
+ * Console method names that can be intercepted.
2549
+ */
2550
+ type ConsoleMethod = "log" | "info" | "warn" | "error" | "debug";
2551
+ /**
2552
+ * Entry captured from console.
2553
+ */
2554
+ interface ConsoleEntry {
2555
+ method: ConsoleMethod;
2556
+ args: unknown[];
2557
+ stream: "stdout" | "stderr";
2558
+ }
2559
+ /**
2560
+ * Options for createTerm().
2561
+ */
2562
+ interface CreateTermOptions {
2563
+ stdout?: NodeJS.WriteStream;
2564
+ stdin?: NodeJS.ReadStream;
2565
+ colorLevel?: ColorLevel | null;
2566
+ unicode?: boolean;
2567
+ cursor?: boolean;
2568
+ caps?: Partial<TerminalCaps>;
2569
+ /** Mouse parser options for terminal-backed input owners. */
2570
+ mouse?: ParseMouseOptions;
2571
+ /**
2572
+ * Opt out of silvery's stdin ownership entirely.
2573
+ *
2574
+ * When `false`, `term.input` resolves to `undefined`. The lazy
2575
+ * `InputOwner` is never constructed: raw mode is never flipped, no
2576
+ * data listener is attached to stdin, and no probe writes touch
2577
+ * stdin. The Term still owns stdout, modes, size, signals, and
2578
+ * console — only stdin ownership is suppressed.
2579
+ *
2580
+ * Use case: a host process needs to pipe its own stdin to a child
2581
+ * (e.g. a PTY for a recording overlay) while still using silvery to
2582
+ * render visuals around the child's grid. Without this opt-out the
2583
+ * host and silvery race for stdin and silvery's `setRawMode(true)`
2584
+ * eats the child's keystrokes.
2585
+ *
2586
+ * Pair with `render(..., term, { input: false })` to mirror the
2587
+ * opt-out at the render pipeline level — the runtime then skips the
2588
+ * text-sizing + width-detection probes and never attaches its own
2589
+ * stdin listener.
2590
+ *
2591
+ * Default: undefined (silvery owns stdin via the lazy InputOwner —
2592
+ * the canonical contract). Only `false` is meaningful — `true` is the
2593
+ * default and not a separate code path, so it is not accepted.
2594
+ *
2595
+ * See `docs/design/terminal-component.md` § "render({ input: false })"
2596
+ * for the full rationale.
2597
+ */
2598
+ input?: false;
2599
+ /**
2600
+ * Trailing-edge debounce for stdout `resize` events, in ms.
2601
+ *
2602
+ * Real terminals fire SIGWINCH bursts (tmux/cmux/Ghostty multiplexer
2603
+ * resizes can produce 4-6 events at ~80 ms intervals). The default
2604
+ * (200 ms) coalesces these bursts to a single layout reflow.
2605
+ *
2606
+ * Test and emulator paths drive resize explicitly via `term.resize(...)`
2607
+ * and don't experience SIGWINCH bursts — they want the resize signal to
2608
+ * propagate immediately so layout reflows before the test's settle window
2609
+ * expires. Pass `0` (or any value shorter than the test's settle) to opt
2610
+ * out of debouncing.
2611
+ *
2612
+ * See `createSize` in `runtime/devices/size.ts` for the underlying contract.
2613
+ */
2614
+ resizeCoalesceMs?: number;
2615
+ }
2616
+ /**
2617
+ * A screen region — duck-type matching termless RegionView.
2618
+ * Provides text content, line access, and cell-level queries for assertions.
2619
+ */
2620
+ interface TermScreen {
2621
+ getText(): string;
2622
+ getLines(): string[];
2623
+ containsText?(text: string): boolean;
2624
+ /** Cell-level access — row-first order. Returns resolved RGB colors.
2625
+ * Only available on emulator-backed terms (createTermless). */
2626
+ cell?(row: number, col: number): {
2627
+ readonly fg: unknown;
2628
+ readonly bg: unknown;
2629
+ readonly char: string;
2630
+ };
2631
+ }
2632
+ /**
2633
+ * A terminal emulator — duck-type matching termless Terminal.
2634
+ * Accepts ANSI output, provides screen/scrollback for inspection.
2635
+ */
2636
+ interface TermEmulator {
2637
+ readonly cols: number;
2638
+ readonly rows: number;
2639
+ readonly screen: TermScreen;
2640
+ readonly scrollback: TermScreen;
2641
+ feed(data: Uint8Array | string): void;
2642
+ resize(cols: number, rows: number): void;
2643
+ close(): Promise<void>;
2644
+ }
2645
+ /**
2646
+ * A terminal emulator backend — duck-type matching termless TerminalBackend.
2647
+ * Raw backend that needs initialization. Pass to createTerm(backend, { cols, rows }).
2648
+ *
2649
+ * @example
2650
+ * ```ts
2651
+ * import { createXtermBackend } from "@termless/xtermjs"
2652
+ * using term = createTerm(createXtermBackend(), { cols: 80, rows: 24 })
2653
+ * ```
2654
+ */
2655
+ interface TermEmulatorBackend {
2656
+ readonly name: string;
2657
+ init(opts: {
2658
+ cols: number;
2659
+ rows: number;
2660
+ scrollbackLimit?: number;
2661
+ }): void;
2662
+ destroy(): void;
2663
+ feed(data: Uint8Array): void;
2664
+ resize(cols: number, rows: number): void;
2665
+ }
2666
+ //#endregion
2667
+ //#region packages/ag/src/text-frame.d.ts
2668
+ /**
2669
+ * TextFrame — Unified interface for a rectangular area of styled terminal text.
2670
+ *
2671
+ * Used by App, RunHandle, term.screen, term.scrollback — one shape everywhere.
2672
+ * Provides plain text, ANSI-styled text, per-line access, cell-level queries,
2673
+ * and text search.
2674
+ *
2675
+ * @packageDocumentation
2676
+ */
2677
+ /**
2678
+ * RGB color value (0-255 per channel).
2679
+ */
2680
+ interface RGB {
2681
+ r: number;
2682
+ g: number;
2683
+ b: number;
2684
+ }
2685
+ /**
2686
+ * A single cell in a TextFrame with resolved styling.
2687
+ *
2688
+ * Colors are resolved to RGB (or null for default/inherit).
2689
+ * Attributes are flattened booleans for easy testing.
2690
+ */
2691
+ interface FrameCell {
2692
+ /** The character/grapheme in this cell */
2693
+ readonly char: string;
2694
+ /** Resolved foreground color, or null for default */
2695
+ readonly fg: RGB | null;
2696
+ /** Resolved background color, or null for default */
2697
+ readonly bg: RGB | null;
2698
+ /** Bold attribute */
2699
+ readonly bold: boolean;
2700
+ /** Dim/faint attribute */
2701
+ readonly dim: boolean;
2702
+ /** Italic attribute */
2703
+ readonly italic: boolean;
2704
+ /** Underline style — false if none */
2705
+ readonly underline: UnderlineStyle$1;
2706
+ /** Underline color (independent of fg), or null to use fg */
2707
+ readonly underlineColor: RGB | null;
2708
+ /** Overline attribute — SGR 53. Independent of underline. */
2709
+ readonly overline: boolean;
2710
+ /** Strikethrough attribute */
2711
+ readonly strikethrough: boolean;
2712
+ /** Inverse/reverse video attribute */
2713
+ readonly inverse: boolean;
2714
+ /** Blink attribute */
2715
+ readonly blink: boolean;
2716
+ /** Hidden/invisible attribute */
2717
+ readonly hidden: boolean;
2718
+ /** True if this is a wide character (CJK, emoji) */
2719
+ readonly wide: boolean;
2720
+ /** True if this cell is the continuation of a wide character */
2721
+ readonly continuation: boolean;
2722
+ /** OSC 8 hyperlink URL, or null if none */
2723
+ readonly hyperlink: string | null;
2724
+ }
2725
+ /**
2726
+ * Unified interface for a rectangular area of styled terminal text.
2727
+ *
2728
+ * Implemented by App, RunHandle, and terminal views (screen, scrollback).
2729
+ * Provides consistent access to text content, styling, and cell-level data.
2730
+ */
2731
+ interface TextFrame {
2732
+ /** Plain text content (no ANSI codes). Lines separated by newlines. */
2733
+ readonly text: string;
2734
+ /** Text with ANSI styling escape codes. */
2735
+ readonly ansi: string;
2736
+ /** Per-line plain text array (no ANSI codes). */
2737
+ readonly lines: string[];
2738
+ /** Frame width in terminal columns. */
2739
+ readonly width: number;
2740
+ /** Frame height in terminal rows. */
2741
+ readonly height: number;
2742
+ /** Get the cell at the given column and row. */
2743
+ cell(col: number, row: number): FrameCell;
2744
+ /** Check whether the plain text contains the given substring. */
2745
+ containsText(text: string): boolean;
2746
+ }
2747
+ //#endregion
2748
+ //#region packages/ag-term/src/buffer.d.ts
2749
+ /**
2750
+ * Terminal buffer implementation for Silvery.
2751
+ *
2752
+ * Uses packed Uint32Array for efficient cell metadata storage,
2753
+ * with separate string array for character storage (needed for
2754
+ * multi-byte Unicode graphemes and combining characters).
2755
+ */
2756
+ /**
2757
+ * Underline style variants (SGR 4:x codes).
2758
+ * - false: no underline
2759
+ * - 'single': standard underline (SGR 4 or 4:1)
2760
+ * - 'double': double underline (SGR 4:2)
2761
+ * - 'curly': curly/wavy underline (SGR 4:3)
2762
+ * - 'dotted': dotted underline (SGR 4:4)
2763
+ * - 'dashed': dashed underline (SGR 4:5)
2764
+ */
2765
+ type UnderlineStyle = false | "single" | "double" | "curly" | "dotted" | "dashed";
2766
+ /**
2767
+ * Text attributes that can be applied to a cell.
2768
+ */
2769
+ interface CellAttrs {
2770
+ bold?: boolean;
2771
+ dim?: boolean;
2772
+ italic?: boolean;
2773
+ /** Simple underline flag (for backwards compatibility) */
2774
+ underline?: boolean;
2775
+ /**
2776
+ * Underline style: 'single' | 'double' | 'curly' | 'dotted' | 'dashed'.
2777
+ * When set, takes precedence over the underline boolean.
2778
+ */
2779
+ underlineStyle?: UnderlineStyle;
2780
+ /**
2781
+ * Overline (SGR 53/55). A line ABOVE the character cell, independent of
2782
+ * underline. Used for top-edge indicators where underline would read as
2783
+ * "this row is underlined content" instead of "you're at the top".
2784
+ */
2785
+ overline?: boolean;
2786
+ blink?: boolean;
2787
+ inverse?: boolean;
2788
+ hidden?: boolean;
2789
+ strikethrough?: boolean;
2790
+ }
2791
+ /**
2792
+ * Color representation.
2793
+ * - number: 256-color index (0-255)
2794
+ * - RGB object: true color
2795
+ * - null: default/inherit
2796
+ * - DEFAULT_BG: terminal's default background (SGR 49), opaque but uses terminal's own bg color
2797
+ */
2798
+ type Color = number | {
2799
+ r: number;
2800
+ g: number;
2801
+ b: number;
2802
+ } | null;
2803
+ /**
2804
+ * A single cell in the terminal buffer.
2805
+ */
2806
+ interface Cell {
2807
+ /** The character/grapheme in this cell */
2808
+ char: string;
2809
+ /** Foreground color */
2810
+ fg: Color;
2811
+ /** Background color */
2812
+ bg: Color;
2813
+ /**
2814
+ * Underline color (independent of fg).
2815
+ * Uses SGR 58. If null, underline uses fg color.
2816
+ */
2817
+ underlineColor: Color;
2818
+ /** Text attributes */
2819
+ attrs: CellAttrs;
2820
+ /** True if this is a wide character (CJK, emoji, etc.) */
2821
+ wide: boolean;
2822
+ /** True if this is the continuation cell after a wide character */
2823
+ continuation: boolean;
2824
+ /**
2825
+ * OSC 8 hyperlink URL.
2826
+ * When set, the cell is part of a clickable hyperlink in supporting terminals.
2827
+ */
2828
+ hyperlink?: string;
2829
+ }
2830
+ /**
2831
+ * Partial cell update accepted by buffer write APIs.
2832
+ *
2833
+ * `selectable` is render metadata, not a visual cell property: it controls the
2834
+ * SELECTABLE_FLAG bit used by app-level semantic text selection. It is carried
2835
+ * on write payloads so selectability is explicit per mutation rather than an
2836
+ * ambient buffer mode.
2837
+ */
2838
+ type CellPatch = Partial<Cell> & {
2839
+ selectable?: boolean;
2840
+ };
2841
+ /**
2842
+ * Style information for a cell (excludes char and position flags).
2843
+ */
2844
+ interface Style {
2845
+ fg: Color;
2846
+ bg: Color;
2847
+ /**
2848
+ * Underline color (independent of fg).
2849
+ * Uses SGR 58. If null, underline uses fg color.
2850
+ */
2851
+ underlineColor?: Color;
2852
+ attrs: CellAttrs;
2853
+ /**
2854
+ * OSC 8 hyperlink URL.
2855
+ * When set, the cell is part of a clickable hyperlink in supporting terminals.
2856
+ */
2857
+ hyperlink?: string;
2858
+ }
2859
+ /**
2860
+ * Per-row metadata for text extraction correctness.
2861
+ * Maintained by the render phase during text rendering.
2862
+ */
2863
+ interface RowMetadata {
2864
+ /** True if this row continues on the next row (soft wrap, not hard break) */
2865
+ softWrapped: boolean;
2866
+ /** Rightmost column with non-space content (for trailing space trimming) */
2867
+ lastContentCol: number;
2868
+ }
2869
+ /**
2870
+ * Efficient terminal cell buffer.
2871
+ *
2872
+ * Uses packed Uint32Array for cell metadata and separate string array
2873
+ * for characters. This allows efficient diffing while supporting
2874
+ * full Unicode grapheme clusters.
2875
+ */
2876
+ declare class TerminalBuffer {
2877
+ /** Packed cell metadata */
2878
+ private cells;
2879
+ /** Character storage (one per cell, may be multi-byte grapheme) */
2880
+ private chars;
2881
+ /** True color foreground storage (only for cells with true color fg) */
2882
+ private fgColors;
2883
+ /** True color background storage (only for cells with true color bg) */
2884
+ private bgColors;
2885
+ /** Underline color storage (independent of fg, for SGR 58) */
2886
+ private underlineColors;
2887
+ /** OSC 8 hyperlink URL storage (only for cells that are part of a hyperlink) */
2888
+ private hyperlinks;
2889
+ /**
2890
+ * Per-row dirty tracking for diff optimization.
2891
+ * When set, diffBuffers() can skip clean rows entirely.
2892
+ * 0 = clean (unchanged since last resetDirtyRows), 1 = dirty (modified).
2893
+ */
2894
+ private _dirtyRows;
2895
+ /** Bounding box: first dirty row (inclusive). -1 when no rows are dirty. */
2896
+ private _minDirtyRow;
2897
+ /** Bounding box: last dirty row (inclusive). -1 when no rows are dirty. */
2898
+ private _maxDirtyRow;
2899
+ /**
2900
+ * Per-row metadata for text extraction (soft wrap, last content column).
2901
+ * Set by the render phase, read by extractText.
2902
+ */
2903
+ private _rowMetadata;
2904
+ readonly width: number;
2905
+ readonly height: number;
2906
+ constructor(width: number, height: number);
2907
+ /**
2908
+ * Get the index for a cell position.
2909
+ */
2910
+ private index;
2911
+ /**
2912
+ * Check if coordinates are within bounds.
2913
+ */
2914
+ inBounds(x: number, y: number): boolean;
2915
+ /**
2916
+ * Get a cell at the given position.
2917
+ */
2918
+ getCell(x: number, y: number): Cell;
2919
+ /**
2920
+ * Count cells that are not default-empty.
2921
+ *
2922
+ * A cell is "painted" iff any of these differs from default-empty:
2923
+ * - char !== " "
2924
+ * - packed metadata is non-zero (any fg, bg, attr, wide/cont/truecolor flag)
2925
+ *
2926
+ * Used by the degenerate-frame canary in `render()` (renderer.ts) and by
2927
+ * test helpers that want to assert a fixture renders meaningful content.
2928
+ *
2929
+ * O(W*H) — single pass over the packed Uint32Array + chars array.
2930
+ */
2931
+ countPaintedCells(): number;
2932
+ /**
2933
+ * Get just the character at a cell position (no object allocation).
2934
+ * Returns " " for out-of-bounds positions.
2935
+ */
2936
+ getCellChar(x: number, y: number): string;
2937
+ /**
2938
+ * Get just the background color at a cell position (no object allocation).
2939
+ * Returns null for out-of-bounds positions.
2940
+ */
2941
+ getCellBg(x: number, y: number): Color;
2942
+ /**
2943
+ * Get just the foreground color at a cell position (no object allocation).
2944
+ * Returns null for out-of-bounds positions.
2945
+ */
2946
+ getCellFg(x: number, y: number): Color;
2947
+ /**
2948
+ * Get the raw packed metadata at a cell position (no unpackAttrs allocation).
2949
+ * Returns 0 for out-of-bounds positions. The packed value contains color
2950
+ * indices, attr bits, underline style, and flags in a single Uint32.
2951
+ */
2952
+ getCellAttrs(x: number, y: number): number;
2953
+ /**
2954
+ * Check if a cell is a wide character (no object allocation).
2955
+ * Returns false for out-of-bounds positions.
2956
+ */
2957
+ isCellWide(x: number, y: number): boolean;
2958
+ /**
2959
+ * Check if a cell is a continuation of a wide character (no object allocation).
2960
+ * Returns false for out-of-bounds positions.
2961
+ */
2962
+ isCellContinuation(x: number, y: number): boolean;
2963
+ /**
2964
+ * Check if a cell is selectable (SELECTABLE_FLAG is set, no object allocation).
2965
+ * Returns false for out-of-bounds positions.
2966
+ */
2967
+ isCellSelectable(x: number, y: number): boolean;
2968
+ /**
2969
+ * Set metadata for a row (soft wrap, last content column).
2970
+ * Called by the render phase during text rendering.
2971
+ */
2972
+ setRowMeta(row: number, meta: Partial<RowMetadata>): void;
2973
+ /**
2974
+ * Get metadata for a row. Returns default values for out-of-bounds rows.
2975
+ */
2976
+ getRowMeta(row: number): RowMetadata;
2977
+ /**
2978
+ * Get the full row metadata array (for bulk access during text extraction).
2979
+ */
2980
+ getRowMetadataArray(): readonly RowMetadata[];
2981
+ /**
2982
+ * Read cell data into a caller-provided Cell object (zero-allocation).
2983
+ * For hot loops that need the full Cell, reuse a single object:
2984
+ *
2985
+ * const cell = createMutableCell()
2986
+ * for (...) { buffer.readCellInto(x, y, cell) }
2987
+ *
2988
+ * Returns the same `out` object for chaining convenience.
2989
+ */
2990
+ readCellInto(x: number, y: number, out: Cell): Cell;
2991
+ /**
2992
+ * Set a cell at the given position.
2993
+ *
2994
+ * Optimized: resolves defaults and packs metadata inline to avoid
2995
+ * allocating an intermediate Cell object.
2996
+ */
2997
+ setCell(x: number, y: number, cell: CellPatch): void;
2998
+ /**
2999
+ * Fill a region with a cell.
3000
+ *
3001
+ * Optimized: packs cell metadata once and assigns directly to arrays,
3002
+ * avoiding O(width*height) intermediate object allocations from setCell().
3003
+ */
3004
+ fill(x: number, y: number, width: number, height: number, cell: CellPatch): void;
3005
+ /**
3006
+ * Restyle a rectangular region — update fg, bg, and attrs on existing cells
3007
+ * without changing character content (char, wide, continuation, hyperlink).
3008
+ *
3009
+ * This is the style-only fast path: when only visual props changed (color,
3010
+ * bold, dim, inverse, etc.) but text content and layout are identical, we
3011
+ * can skip text collection/formatting and just update the style metadata.
3012
+ *
3013
+ * @param x Left column (inclusive)
3014
+ * @param y Top row (inclusive)
3015
+ * @param width Region width
3016
+ * @param height Region height
3017
+ * @param style New style to apply (fg, bg, attrs, underlineColor)
3018
+ */
3019
+ restyleRegion(x: number, y: number, width: number, height: number, style: Style): void;
3020
+ /**
3021
+ * Fill only the background color of a region — update bg on existing cells
3022
+ * without changing character content, foreground, or attributes.
3023
+ *
3024
+ * Unlike fill() which writes space characters, this preserves existing chars.
3025
+ * Used by the style-only fast path: when a Box's backgroundColor changes but
3026
+ * children are unchanged, fillBg() paints the new bg without destroying child
3027
+ * chars. Clean children can then be skipped (their chars are correct from the
3028
+ * clone, their bg was updated by fillBg).
3029
+ *
3030
+ * @param x Left column (inclusive)
3031
+ * @param y Top row (inclusive)
3032
+ * @param width Region width
3033
+ * @param height Region height
3034
+ * @param bg New background color
3035
+ */
3036
+ fillBg(x: number, y: number, width: number, height: number, bg: Color): void;
3037
+ /**
3038
+ * OR-combine SGR attribute bits into every cell in a rectangular region —
3039
+ * WITHOUT modifying glyphs, fg, bg, wide flags, selectable flag, or any
3040
+ * other per-cell state.
3041
+ *
3042
+ * This is the transparent-overlay primitive. It lets a Box (or any caller)
3043
+ * layer an underline / strikethrough / bold / etc. onto existing content
3044
+ * without overwriting what's underneath.
3045
+ *
3046
+ * Semantics:
3047
+ * - `attrs.underlineStyle` (or `attrs.underline: true` via attrsToNumber)
3048
+ * REPLACES any existing underline style on each cell — 3-bit field, one
3049
+ * value wins. This matches CSS `text-decoration` overlay semantics where
3050
+ * a decoration container sets the decoration.
3051
+ * - All other attr bits (bold/dim/italic/blink/inverse/hidden/strikethrough)
3052
+ * OR-in — existing attrs are preserved, new attrs add to them.
3053
+ * - `underlineColor` is applied only when explicitly provided; otherwise
3054
+ * each cell keeps its existing underline color.
3055
+ *
3056
+ * Use cases:
3057
+ * - Overscroll indicator: `<Box underline="single" position="absolute" />`
3058
+ * overlays an underline on the last row without touching the text.
3059
+ * - Error squiggly across a paragraph: `<Box underline="curly">`.
3060
+ * - Heading visual emphasis: `<Box underline="double">`.
3061
+ *
3062
+ * @param x Left column (inclusive)
3063
+ * @param y Top row (inclusive)
3064
+ * @param width Region width
3065
+ * @param height Region height
3066
+ * @param attrs Attributes to merge onto every cell
3067
+ * @param underlineColor Optional underline color — when provided, replaces the
3068
+ * existing underlineColor on each cell. `null` clears it. `undefined`
3069
+ * leaves it untouched.
3070
+ */
3071
+ mergeAttrsInRect(x: number, y: number, width: number, height: number, attrs: CellAttrs, underlineColor?: Color): void;
3072
+ /**
3073
+ * Clear the buffer (fill with empty cells).
3074
+ */
3075
+ clear(): void;
3076
+ /**
3077
+ * Copy a region from another buffer.
3078
+ */
3079
+ copyFrom(source: TerminalBuffer, srcX: number, srcY: number, destX: number, destY: number, width: number, height: number): void;
3080
+ /**
3081
+ * Shift content within a rectangular region vertically by `delta` rows.
3082
+ * Positive delta = shift content UP (scroll down), negative = shift DOWN (scroll up).
3083
+ * Exposed rows (at the bottom for positive delta, top for negative) are filled
3084
+ * with the given background cell.
3085
+ *
3086
+ * Uses Uint32Array.copyWithin for the packed cells (native memcpy) and
3087
+ * Array splice for the character array.
3088
+ */
3089
+ scrollRegion(x: number, y: number, regionWidth: number, regionHeight: number, delta: number, clearCell?: CellPatch): void;
3090
+ /**
3091
+ * Clone this buffer.
3092
+ */
3093
+ clone(): TerminalBuffer;
3094
+ /**
3095
+ * Check if a row has been modified since the last resetDirtyRows() call.
3096
+ * Used by diffBuffers() to skip unchanged rows.
3097
+ */
3098
+ isRowDirty(y: number): boolean;
3099
+ /** First dirty row (inclusive), or -1 if no rows are dirty. */
3100
+ get minDirtyRow(): number;
3101
+ /** Last dirty row (inclusive), or -1 if no rows are dirty. */
3102
+ get maxDirtyRow(): number;
3103
+ /**
3104
+ * Reset all dirty row flags to clean.
3105
+ * Call after diffing to prepare for the next frame's modifications.
3106
+ */
3107
+ resetDirtyRows(): void;
3108
+ /**
3109
+ * Mark all rows as dirty.
3110
+ * Used when the buffer's dirty rows may not cover all changes relative
3111
+ * to a different prev buffer (e.g., after multiple doRender calls where
3112
+ * the runtime's prevBuffer skipped intermediate buffers).
3113
+ */
3114
+ markAllRowsDirty(): void;
3115
+ /**
3116
+ * Check if two cells at given positions are equal.
3117
+ * Used for diffing.
3118
+ */
3119
+ cellEquals(x: number, y: number, other: TerminalBuffer): boolean;
3120
+ /**
3121
+ * Fast check: are all packed metadata values identical for a row?
3122
+ * This is a bulk pre-check before per-cell comparison. If metadata differs,
3123
+ * we still need per-cell diffing. If metadata matches, we only need to
3124
+ * check chars, true color maps, underline colors, and hyperlinks.
3125
+ * Returns true if all packed 32-bit values in the row are identical.
3126
+ */
3127
+ rowMetadataEquals(y: number, other: TerminalBuffer): boolean;
3128
+ /**
3129
+ * Fast check: are all characters identical for a row?
3130
+ * Companion to rowMetadataEquals for a two-phase row comparison.
3131
+ */
3132
+ rowCharsEquals(y: number, other: TerminalBuffer): boolean;
3133
+ /**
3134
+ * Check Map-based extras for a row: true color fg/bg, underline colors, hyperlinks.
3135
+ * Must be called AFTER rowMetadataEquals confirms packed metadata matches.
3136
+ * Only checks cells that have true color flags set (the Maps are only populated
3137
+ * for those cells). Also checks underline colors and hyperlinks for all cells.
3138
+ */
3139
+ rowExtrasEquals(y: number, other: TerminalBuffer): boolean;
3140
+ }
3141
+ //#endregion
3142
+ //#region packages/signals/src/index.d.ts
3143
+ /**
3144
+ * A reactive value — callable getter/setter.
3145
+ *
3146
+ * - `sig()` reads the current value (and subscribes the active effect/computed).
3147
+ * - `sig(next)` writes a new value; subscribers re-run only if `next !== current`.
3148
+ *
3149
+ * Matches the return shape of `signal<T>(initial)` from alien-signals, so any
3150
+ * `signal()` result is assignable to `Signal<T>`.
3151
+ */
3152
+ type Signal<T> = {
3153
+ (): T;
3154
+ (value: T): void;
3155
+ };
3156
+ /**
3157
+ * A read-only reactive value — callable getter that subscribes the active
3158
+ * effect/computed but cannot be written to. Matches the return shape of
3159
+ * `computed<T>(fn)` from alien-signals.
3160
+ */
3161
+ type ReadSignal<T> = () => T;
3162
+ //#endregion
3163
+ //#region packages/ag-term/src/runtime/devices/modes.d.ts
3164
+ /**
3165
+ * Kitty keyboard protocol flags (bitfield).
3166
+ *
3167
+ * | Flag | Bit | Description |
3168
+ * | ---- | --- | ----------------------------------------- |
3169
+ * | 1 | 0 | Disambiguate escape codes |
3170
+ * | 2 | 1 | Report event types (press/repeat/release) |
3171
+ * | 4 | 2 | Report alternate keys |
3172
+ * | 8 | 3 | Report all keys as escape codes |
3173
+ * | 16 | 4 | Report associated text |
3174
+ */
3175
+ declare const KittyFlags: {
3176
+ readonly DISAMBIGUATE: 1;
3177
+ readonly REPORT_EVENTS: 2;
3178
+ readonly REPORT_ALTERNATE: 4;
3179
+ readonly REPORT_ALL_KEYS: 8;
3180
+ readonly REPORT_TEXT: 16;
3181
+ };
3182
+ /**
3183
+ * Mode names that can be passed to `modes.enable(...)`. These cover every
3184
+ * toggleable mode on the `Modes` owner. `kittyKeyboard` accepts a bitfield
3185
+ * rather than `true` and is handled via the per-mode signal directly (see
3186
+ * the `kittyKeyboard` signal below) — it's intentionally excluded from
3187
+ * `enable()` because the "on" value is not a single fixed boolean.
3188
+ */
3189
+ type ModeName = "rawMode" | "altScreen" | "bracketedPaste" | "mouse" | "focusReporting";
3190
+ type MouseTrackingMode = boolean | "pixel";
3191
+ /**
3192
+ * Terminal protocol modes sub-owner.
3193
+ *
3194
+ * Each property is a callable alien-signals `Signal`:
3195
+ * - read: `modes.altScreen()` → `boolean`
3196
+ * - write: `modes.altScreen(true)` — internal effect emits ANSI on change
3197
+ * - subscribe: `effect(() => modes.altScreen())`
3198
+ *
3199
+ * `dispose()` writes `false` to every signal that was ever activated, which
3200
+ * drives the same effects to emit the disable ANSI. Mode signals that were
3201
+ * never touched stay `false` — no ANSI is emitted for them, matching the
3202
+ * shared-stdin safety contract.
3203
+ *
3204
+ * For scope-style ownership (`scope.use(modes.enable("altScreen"))`), use
3205
+ * `enable()` — it returns a `Disposable` that restores the mode to whatever
3206
+ * it was before the call, independent of the owner's full `dispose()`.
3207
+ */
3208
+ interface Modes extends Disposable {
3209
+ /**
3210
+ * stdin raw mode.
3211
+ *
3212
+ * wasRaw note: prefer a single `modes.rawMode(true)` at session start; do
3213
+ * not capture-and-restore around async work. See
3214
+ * `vendor/silvery/CLAUDE.md` "Anti-pattern: wasRaw".
3215
+ */
3216
+ readonly rawMode: Signal<boolean>;
3217
+ /** Alternate screen buffer (DEC 1049). */
3218
+ readonly altScreen: Signal<boolean>;
3219
+ /** Bracketed paste (DEC 2004). */
3220
+ readonly bracketedPaste: Signal<boolean>;
3221
+ /**
3222
+ * Kitty keyboard protocol flags. Bitfield (see `KittyFlags`) to enable,
3223
+ * `false` to disable. A change from one non-false bitfield to another
3224
+ * emits a fresh `CSI > flags u` write.
3225
+ */
3226
+ readonly kittyKeyboard: Signal<number | false>;
3227
+ /** SGR mouse tracking (xterm modes 1003 + 1006, or 1003 + 1006 + 1016). */
3228
+ readonly mouse: Signal<MouseTrackingMode>;
3229
+ /** Focus-in / focus-out reporting (DEC 1004). */
3230
+ readonly focusReporting: Signal<boolean>;
3231
+ /**
3232
+ * Enable a mode and return a `Disposable` that restores the mode to its
3233
+ * prior value on disposal. Complements the per-mode signals for callers
3234
+ * that want scope-style ownership:
3235
+ *
3236
+ * ```ts
3237
+ * scope.use(term.modes.enable("altScreen"))
3238
+ * // …later, when the scope disposes, altScreen flips back to its
3239
+ * // pre-enable value.
3240
+ * ```
3241
+ *
3242
+ * Idempotent across repeated `enable(name)` calls (alien-signals equality
3243
+ * short-circuits same-value writes). Disposing the returned handle twice
3244
+ * is a no-op. The kitty keyboard bitfield is intentionally not covered —
3245
+ * write `modes.kittyKeyboard(flags)` directly for that shape.
3246
+ */
3247
+ enable(name: ModeName): Disposable;
3248
+ }
3249
+ /**
3250
+ * Options for `createModes()`.
3251
+ *
3252
+ * The owner needs:
3253
+ * - a write function for ANSI sequences (routes through Output if activated,
3254
+ * else bare `stdout.write`)
3255
+ * - the stdin stream (for `rawMode` — termios toggle, not ANSI)
3256
+ */
3257
+ interface CreateModesOptions {
3258
+ /** Write raw ANSI bytes to stdout. */
3259
+ write: (data: string) => void;
3260
+ /** stdin stream — used only for raw-mode termios toggles. */
3261
+ stdin: NodeJS.ReadStream;
3262
+ }
3263
+ /**
3264
+ * Create a `Modes` sub-owner. Does not emit any ANSI at construction — all
3265
+ * sequences are written lazily on the first change to a mode signal.
3266
+ */
3267
+ declare function createModes(opts: CreateModesOptions): Modes;
3268
+ //#endregion
3269
+ //#region packages/ag-term/src/runtime/input-owner.d.ts
3270
+ /** Structured key event — input string + parsed Key metadata. */
3271
+ interface KeyEvent {
3272
+ input: string;
3273
+ key: Key;
3274
+ }
3275
+ /** Structured paste event — the text that was pasted (without markers). */
3276
+ interface PasteEvent {
3277
+ text: string;
3278
+ }
3279
+ /** Structured focus event — whether the terminal gained or lost focus. */
3280
+ interface FocusEvent {
3281
+ focused: boolean;
3282
+ }
3283
+ interface InputOwner extends Disposable {
3284
+ /**
3285
+ * Write a query to stdout, accumulate stdin response bytes, run `parse`
3286
+ * against the accumulated buffer on each chunk. Resolves with the first
3287
+ * non-null parse result; resolves with `null` if `timeoutMs` elapses first.
3288
+ *
3289
+ * Consumed bytes (`consumed` from the parse result) are spliced out of the
3290
+ * shared buffer. Bytes before/after the consumed region remain available
3291
+ * to subsequent probes and/or the event parser.
3292
+ */
3293
+ probe<T>(opts: {
3294
+ /** Bytes to write to stdout. May be "" for pure-listen probes. */query: string;
3295
+ /**
3296
+ * Run on the accumulated buffer each time new bytes arrive.
3297
+ * Return `null` when the buffer doesn't contain a parseable response yet;
3298
+ * return `{ result, consumed }` to resolve the probe with `result` and
3299
+ * splice `consumed` bytes out of the buffer.
3300
+ *
3301
+ * NOTE: `consumed` need not equal the full buffer length; probes may
3302
+ * consume a prefix or a middle slice. The owner splices the FIRST
3303
+ * `consumed` bytes from the buffer — parsers that match a non-prefix
3304
+ * region should locate + return the exact consumed prefix length.
3305
+ */
3306
+ parse: (acc: string) => {
3307
+ result: T;
3308
+ consumed: number;
3309
+ } | null; /** Maximum wait in ms before resolving with `null`. */
3310
+ timeoutMs: number;
3311
+ }): Promise<T | null>;
3312
+ /**
3313
+ * Subscribe to parsed key events (press, repeat, release — handler filters
3314
+ * as needed). Returns an unsubscribe function.
3315
+ */
3316
+ onKey(handler: (event: KeyEvent) => void): () => void;
3317
+ /**
3318
+ * Subscribe to parsed mouse events (SGR-encoded button + motion). Returns
3319
+ * an unsubscribe function.
3320
+ */
3321
+ onMouse(handler: (event: ParsedMouse) => void): () => void;
3322
+ /**
3323
+ * Subscribe to bracketed-paste events. The `text` field holds the pasted
3324
+ * content with markers stripped. Returns an unsubscribe function.
3325
+ */
3326
+ onPaste(handler: (event: PasteEvent) => void): () => void;
3327
+ /**
3328
+ * Subscribe to focus-in / focus-out events (CSI I / CSI O). Returns an
3329
+ * unsubscribe function.
3330
+ */
3331
+ onFocus(handler: (event: FocusEvent) => void): () => void;
3332
+ /**
3333
+ * Inject a synthetic key event. Used by emulator-backed terms
3334
+ * (`createTerm({ cols, rows, emulator })`) and test helpers to fan out to
3335
+ * the same subscribers as real stdin parsing would.
3336
+ */
3337
+ sendKey(event: KeyEvent): void;
3338
+ /**
3339
+ * Inject a synthetic mouse event (same rationale as sendKey).
3340
+ */
3341
+ sendMouse(event: ParsedMouse): void;
3342
+ /**
3343
+ * Inject a synthetic paste event (same rationale as sendKey).
3344
+ */
3345
+ sendPaste(event: PasteEvent): void;
3346
+ /**
3347
+ * Inject a synthetic focus event (same rationale as sendKey).
3348
+ */
3349
+ sendFocus(event: FocusEvent): void;
3350
+ /** True once construction succeeded and dispose() hasn't run. */
3351
+ readonly active: boolean;
3352
+ /** Number of probes successfully resolved (result, not null) since activation. */
3353
+ readonly resolvedCount: number;
3354
+ /** Number of probes that timed out since activation. */
3355
+ readonly timedOutCount: number;
3356
+ dispose(): void;
3357
+ [Symbol.dispose](): void;
3358
+ }
3359
+ interface InputOwnerOptions {
3360
+ /**
3361
+ * Alternate writer for outgoing query bytes (e.g. `output.write`). Defaults
3362
+ * to `stdout.write.bind(stdout)`.
3363
+ */
3364
+ writeStdout?: (data: string) => boolean | void;
3365
+ /**
3366
+ * When true, `dispose()` does NOT drop raw mode. The listener is still
3367
+ * removed and pending probes still resolve with null, but raw mode stays
3368
+ * set so the next owner (typically the term-provider's events() generator)
3369
+ * can take over seamlessly.
3370
+ *
3371
+ * Use this when the owner is the pre-session probe window AND a follow-up
3372
+ * stdin consumer will re-set raw=true immediately.
3373
+ */
3374
+ retainRawModeOnDispose?: boolean;
3375
+ /**
3376
+ * Shared Modes owner (from Term). When provided, the input owner drives
3377
+ * `stdin.setRawMode` + bracketed paste through `modes.rawMode(true/false)`
3378
+ * + `modes.bracketedPaste(true/false)` so there is exactly one writer.
3379
+ * Fallback to direct stdin calls + no bracketed-paste toggle when absent
3380
+ * keeps the standalone/tests path working without a full Term.
3381
+ */
3382
+ modes?: Modes;
3383
+ /**
3384
+ * Enable bracketed paste at construction. Defaults to true when `modes`
3385
+ * is provided and the owner is TTY-backed. Set to false for unit tests
3386
+ * that don't want any protocol bytes written to stdout.
3387
+ */
3388
+ enableBracketedPaste?: boolean;
3389
+ /**
3390
+ * Mouse coordinate parser options. Use this when the terminal has been put
3391
+ * into SGR-Pixels mode 1016 and cell metrics are known.
3392
+ */
3393
+ mouse?: ParseMouseOptions;
3394
+ }
3395
+ declare function createInputOwner(stdin: NodeJS.ReadStream, stdout: NodeJS.WriteStream, options?: InputOwnerOptions): InputOwner;
3396
+ //#endregion
3397
+ //#region packages/ag-term/src/runtime/devices/console-router.d.ts
3398
+ /** Shape a tap handler receives on every console.* call. */
3399
+ interface ConsoleCall {
3400
+ method: ConsoleMethod;
3401
+ args: unknown[];
3402
+ }
3403
+ /** Sink policy registered by a consumer (e.g. Output's alt-screen guard). */
3404
+ interface ConsoleSinkPolicy {
3405
+ /**
3406
+ * If true, the call is dropped (no forward to original, no redirect).
3407
+ * Default: false.
3408
+ */
3409
+ suppress?: boolean;
3410
+ /**
3411
+ * If set, a single-line formatted copy of the call is written via
3412
+ * `fs.writeSync(redirectFd, …)`. Typical use: redirect console.* to
3413
+ * DEBUG_LOG during alt-screen. Mutually exclusive with `suppress: true`
3414
+ * at the semantic level (if both are set, `suppress` wins).
3415
+ */
3416
+ redirectFd?: number | null;
3417
+ }
3418
+ /**
3419
+ * Public shape of a ConsoleRouter.
3420
+ */
3421
+ interface ConsoleRouter extends Disposable {
3422
+ /**
3423
+ * Register a tap: called on every console.* call in registration order,
3424
+ * before sink policy. Tap handlers are pure observers. Returns an
3425
+ * unregister function.
3426
+ */
3427
+ registerTap(handler: (call: ConsoleCall) => void): () => void;
3428
+ /**
3429
+ * Register a sink policy. The most recently registered sink is the
3430
+ * active one (stack semantics). Unregister pops the stack back to the
3431
+ * prior sink. Returns an unregister function.
3432
+ */
3433
+ registerSink(policy: ConsoleSinkPolicy): () => void;
3434
+ /** True while at least one tap OR sink is registered (i.e. wrappers installed). */
3435
+ readonly active: boolean;
3436
+ /** Dispose — restore originals, clear taps + sinks. Idempotent. */
3437
+ dispose(): void;
3438
+ [Symbol.dispose](): void;
3439
+ }
3440
+ //#endregion
3441
+ //#region packages/ag-term/src/runtime/devices/output.d.ts
3442
+ interface Output extends Disposable {
3443
+ /** Write data to stdout. When active, bypasses the intercept (silvery's render
3444
+ * pipeline writes go through here). When inactive, forwards to the raw
3445
+ * stdout.write. */
3446
+ write(data: string | Uint8Array): boolean;
3447
+ /**
3448
+ * Whether intercepts are currently installed — a `ReadSignal<boolean>`.
3449
+ * Call `output.active()` to read; subscribe with
3450
+ * `effect(() => output.active())`. The owner writes it internally from
3451
+ * `activate()` / `deactivate()`.
3452
+ */
3453
+ readonly active: ReadSignal<boolean>;
3454
+ /** Activate intercepts: installs stdout/stderr/console patches. Idempotent —
3455
+ * no-op if already active. Options override those passed at construction. */
3456
+ activate(options?: OutputOptions): void;
3457
+ /** Deactivate intercepts: restores original stdout/stderr/console methods.
3458
+ * Idempotent. Closes stderr log fd if open. */
3459
+ deactivate(): void;
3460
+ /** Number of stdout writes suppressed since construction (cumulative across
3461
+ * activate/deactivate cycles). Plain getter — changes on every write, not
3462
+ * worth the reactive cost. */
3463
+ readonly suppressedCount: number;
3464
+ /** Number of stderr writes redirected since construction (cumulative across
3465
+ * activate/deactivate cycles). Plain getter — changes on every write. */
3466
+ readonly redirectedCount: number;
3467
+ /** Final cleanup: deactivates + any teardown. Idempotent. */
3468
+ dispose(): void;
3469
+ [Symbol.dispose](): void;
3470
+ }
3471
+ interface OutputOptions {
3472
+ /** File path to redirect stderr to (default: process.env.DEBUG_LOG) */
3473
+ stderrLog?: string;
3474
+ /** If true, buffer stderr and flush on deactivate instead of redirecting to file */
3475
+ bufferStderr?: boolean;
3476
+ }
3477
+ //#endregion
3478
+ //#region packages/ag-term/src/runtime/devices/size.d.ts
3479
+ /** Snapshot of terminal dimensions. */
3480
+ interface SizeSnapshot {
3481
+ readonly cols: number;
3482
+ readonly rows: number;
3483
+ }
3484
+ /**
3485
+ * Terminal size sub-owner.
3486
+ *
3487
+ * `cols`, `rows`, and `snapshot` are alien-signals `ReadSignal`s — call them
3488
+ * as functions to read the current value and, inside an `effect`, to
3489
+ * subscribe to changes. The first read (inside or outside an effect)
3490
+ * installs the lazy `stdout.on("resize")` listener.
3491
+ *
3492
+ * ```ts
3493
+ * // Read once
3494
+ * const { cols, rows } = term.size.snapshot()
3495
+ *
3496
+ * // Subscribe to changes
3497
+ * effect(() => {
3498
+ * layout(term.size.cols(), term.size.rows()) // re-runs on every resize
3499
+ * })
3500
+ *
3501
+ * // React
3502
+ * const cols = useSignal(term.size.cols)
3503
+ * ```
3504
+ */
3505
+ interface Size extends Disposable {
3506
+ /** Current terminal width in columns. */
3507
+ readonly cols: ReadSignal<number>;
3508
+ /** Current terminal height in rows. */
3509
+ readonly rows: ReadSignal<number>;
3510
+ /** Current dimensions as a plain snapshot. */
3511
+ readonly snapshot: ReadSignal<SizeSnapshot>;
3512
+ }
3513
+ //#endregion
3514
+ //#region packages/ag-term/src/runtime/devices/signals.d.ts
3515
+ /**
3516
+ * term.signals — single SignalScope per Term lifetime with topologically-ordered
3517
+ * teardown.
3518
+ *
3519
+ * ## Why
3520
+ *
3521
+ * The 2026-04-22 shared-global audit found 78 `process.on("SIGINT" | "SIGTERM" |
3522
+ * "SIGTSTP" | "SIGWINCH" | "exit" | "beforeExit" | …)` registrations across km
3523
+ * + silvery with no documented cleanup order. Handlers fire in registration
3524
+ * order. If an earlier handler crashes, the Node.js default behaviour may skip
3525
+ * later handlers or the process may exit before their cleanup runs — the same
3526
+ * class of resource-leak race that the `wasRaw` finding exposed for raw mode.
3527
+ *
3528
+ * Signals is the same META-fix as Output / Modes / Input: one owner per Term
3529
+ * mediates every registration for a given signal. On dispose (or on signal
3530
+ * delivery) handlers fire in priority / dependency order, each wrapped in
3531
+ * try/catch so a single failing handler doesn't block the rest.
3532
+ *
3533
+ * ## API shape
3534
+ *
3535
+ * const unregister = term.signals.on("SIGINT", () => closeDb(), {
3536
+ * priority: 10, // lower = runs first
3537
+ * before: ["flush-logs"], // or explicit dep graph
3538
+ * after: ["save-state"],
3539
+ * name: "close-db", // optional handle for before/after
3540
+ * })
3541
+ * term.signals.dispose() // cascades from term.dispose()
3542
+ *
3543
+ * `priority` is a simple sort key (number, default 0). `before` / `after`
3544
+ * reference handler `name`s. `dispose()` runs every registered handler in
3545
+ * topological order, catches errors, and is idempotent.
3546
+ *
3547
+ * The return value of `on()` is both callable (`unregister()`) and
3548
+ * `Disposable` (`using sub = term.signals.on(...)` and
3549
+ * `scope.use(term.signals.on(...))`). All three forms unregister the handler
3550
+ * without firing it.
3551
+ *
3552
+ * ## Not covered by this owner
3553
+ *
3554
+ * - `apps/km-tui/src/state/raw-signals.ts::restoreTerminal` stays as the
3555
+ * emergency last-ditch crash handler (runs on `uncaughtException`). It
3556
+ * must run even if the Term (and its Signals) has already been disposed.
3557
+ *
3558
+ * Bead: km-silvery.term-sub-owners (Phase 6).
3559
+ */
3560
+ /** Process signal / lifecycle event the owner understands. */
3561
+ type SignalName = NodeJS.Signals | "exit" | "beforeExit" | "uncaughtException" | "unhandledRejection";
3562
+ /** Options per registration. */
3563
+ interface SignalOnOptions {
3564
+ /**
3565
+ * Sort key — lower runs first on dispose / signal delivery. Default 0.
3566
+ *
3567
+ * Rule of thumb:
3568
+ * - 0–9: app-level cleanup (close DB, cancel pending work)
3569
+ * - 10–19: runtime cleanup (stop schedulers, drain queues)
3570
+ * - 20–29: terminal cleanup (restore modes, leave alt screen)
3571
+ * - 30+: emergency / last-ditch
3572
+ *
3573
+ * `before` / `after` take precedence — priority is only a tiebreaker.
3574
+ */
3575
+ priority?: number;
3576
+ /**
3577
+ * Handler name — required if you want `before`/`after` to reference this
3578
+ * handler. If omitted, a unique id is generated.
3579
+ */
3580
+ name?: string;
3581
+ /** Handler names that must run AFTER this one. */
3582
+ before?: string[];
3583
+ /** Handler names that must run BEFORE this one. */
3584
+ after?: string[];
3585
+ /**
3586
+ * If `true`, the handler also runs on `dispose()` (before any process-level
3587
+ * signal handler is detached). Default: true — dispose is the primary
3588
+ * teardown path.
3589
+ */
3590
+ onDispose?: boolean;
3591
+ /**
3592
+ * If `true`, the handler runs when the named signal is delivered to the
3593
+ * process (via `process.on(signal, …)`). Default: true.
3594
+ */
3595
+ onSignal?: boolean;
3596
+ }
3597
+ /**
3598
+ * Unregister function returned by `signals.on(...)`. Callable as a plain
3599
+ * function (backward-compat: `unregister()`) and also implements both
3600
+ * `Symbol.dispose` and `Symbol.asyncDispose` so it works with sync `using`,
3601
+ * async `await using`, and `Scope` (which is `AsyncDisposableStack`-based
3602
+ * and only accepts values with `Symbol.asyncDispose`). All three forms
3603
+ * remove the handler from the owner's registry without firing it.
3604
+ */
3605
+ type SignalUnregister = (() => void) & Disposable & AsyncDisposable;
3606
+ /**
3607
+ * Signals sub-owner.
3608
+ *
3609
+ * One per Term. Mediates every `process.on(signalName, …)` registration for
3610
+ * the Term's lifetime, running handlers in priority/dependency order on
3611
+ * signal delivery or on `dispose()`.
3612
+ */
3613
+ interface Signals extends Disposable {
3614
+ /**
3615
+ * Register a handler for a process signal or lifecycle event. Returns a
3616
+ * `SignalUnregister` — a callable `Disposable`. Either `unregister()` or
3617
+ * `unregister[Symbol.dispose]()` (implicitly via `using` / `scope.use`)
3618
+ * removes the handler from this owner's registry (does not fire the handler).
3619
+ *
3620
+ * The first registration for a given signal installs a shared
3621
+ * `process.on(signal, …)` listener; subsequent registrations reuse it.
3622
+ * `dispose()` removes the shared listener.
3623
+ */
3624
+ on(signal: SignalName, handler: () => void | Promise<void>, opts?: SignalOnOptions): SignalUnregister;
3625
+ /**
3626
+ * Synchronous teardown. Runs every registered handler (with `onDispose:
3627
+ * true`, the default) in priority/dependency order, catching errors so one
3628
+ * failing handler can't block the rest. Idempotent.
3629
+ *
3630
+ * Handlers that return Promises are awaited best-effort via a microtask —
3631
+ * but `dispose()` itself is synchronous because it runs under `using` /
3632
+ * Symbol.dispose semantics, and on signal delivery the process may exit
3633
+ * before any async work resolves.
3634
+ */
3635
+ dispose(): void;
3636
+ /** True after `dispose()` / `Symbol.dispose` has run. */
3637
+ readonly isDisposed: boolean;
3638
+ /** Number of live registrations across all signals. */
3639
+ readonly size: number;
3640
+ }
3641
+ /**
3642
+ * Options for `createSignals`.
3643
+ */
3644
+ interface CreateSignalsOptions {
3645
+ /**
3646
+ * Override the process-level event source. Tests pass a fake `process`
3647
+ * to drive signal delivery without touching real signals.
3648
+ * Defaults to Node's `process` global.
3649
+ */
3650
+ process?: NodeJS.Process;
3651
+ /**
3652
+ * Error hook — called for every handler that throws. Default: swallow.
3653
+ * Tests use this to assert isolation semantics.
3654
+ */
3655
+ onError?: (error: unknown, entry: {
3656
+ name: string;
3657
+ signal: SignalName;
3658
+ }) => void;
3659
+ }
3660
+ /**
3661
+ * Create a `Signals` sub-owner. Installs no process-level listeners until the
3662
+ * first `on()` call; removes them on `dispose()`.
3663
+ */
3664
+ declare function createSignals(opts?: CreateSignalsOptions): Signals;
3665
+ //#endregion
3666
+ //#region packages/ag-term/src/runtime/devices/console.d.ts
3667
+ /**
3668
+ * Aggregate counts of captured console output by severity.
3669
+ */
3670
+ interface ConsoleStats {
3671
+ total: number;
3672
+ errors: number;
3673
+ warnings: number;
3674
+ }
3675
+ /**
3676
+ * Options for `console.capture()`.
3677
+ */
3678
+ interface ConsoleCaptureOptions {
3679
+ /**
3680
+ * Suppress forwarding to the original console methods.
3681
+ * Use in TUI / alt-screen mode where the raw output would corrupt the display.
3682
+ * Default: false.
3683
+ */
3684
+ suppress?: boolean;
3685
+ /**
3686
+ * Store full entries in memory (default: true).
3687
+ * Set false for count-only mode — `getSnapshot()` returns empty, but
3688
+ * `getStats()` still tracks counts. Avoids unbounded memory growth for
3689
+ * long-running sessions where you only care about warning/error badges.
3690
+ */
3691
+ capture?: boolean;
3692
+ }
3693
+ /**
3694
+ * Console — single-owner console.* capture + replay for a silvery session.
3695
+ *
3696
+ * Constructed lazily by `createTerm()` (no patching until `capture()`).
3697
+ * `subscribe` + `getSnapshot` are shaped for React's `useSyncExternalStore` —
3698
+ * each change produces a new array reference.
3699
+ */
3700
+ interface Console extends Disposable {
3701
+ /**
3702
+ * Start patching `console.log/info/warn/error/debug`. Idempotent — calling
3703
+ * while already capturing is a no-op (options are ignored on re-entry;
3704
+ * `restore()` then `capture()` again to change behaviour).
3705
+ */
3706
+ capture(options?: ConsoleCaptureOptions): void;
3707
+ /**
3708
+ * Restore original console methods. Idempotent. Subscribers survive; you can
3709
+ * `capture()` again without re-subscribing. `dispose()` is the terminal
3710
+ * variant that also clears subscribers.
3711
+ */
3712
+ restore(): void;
3713
+ /**
3714
+ * Whether `capture()` is currently active — a `ReadSignal<boolean>`.
3715
+ * Call `console.capturing()` to read; subscribe via
3716
+ * `effect(() => console.capturing())`. The owner writes it internally from
3717
+ * `capture()` / `restore()`.
3718
+ */
3719
+ readonly capturing: ReadSignal<boolean>;
3720
+ /**
3721
+ * Notification signal — increments by 1 per captured entry (stats.total).
3722
+ * Cheap: no array copy, no object allocation. Consumers subscribe via
3723
+ * `effect(() => console.count())` and pull the full list lazily (via
3724
+ * `entriesSnapshot()`) only when they need it — typically on a debounce
3725
+ * flush in a React hook. Replaces the per-entry frozen-slice publish that
3726
+ * degraded to O(n²) for long sessions (Pro review 2026-04-22 P1-9).
3727
+ */
3728
+ readonly count: ReadSignal<number>;
3729
+ /**
3730
+ * Return a frozen snapshot of captured entries at this moment. Slices on
3731
+ * demand — callers pay O(n) only when they actually need the list, not
3732
+ * on every log. Returns an empty frozen array when `capture=false` was
3733
+ * passed.
3734
+ */
3735
+ entriesSnapshot(): readonly ConsoleEntry[];
3736
+ /** Aggregate counts. Tracked even when `capture=false`. */
3737
+ getStats(): ConsoleStats;
3738
+ /**
3739
+ * Replay captured entries to explicit streams (typically `process.stdout` +
3740
+ * `process.stderr` after exiting alt-screen). Entries whose stream was
3741
+ * `'stderr'` go to the stderr stream; the rest go to stdout. Does not clear
3742
+ * entries — call this alongside `dispose()` at TUI exit.
3743
+ */
3744
+ replay(stdout: NodeJS.WriteStream, stderr: NodeJS.WriteStream): void;
3745
+ dispose(): void;
3746
+ [Symbol.dispose](): void;
3747
+ }
3748
+ /**
3749
+ * Create a Console owner backed by a ConsoleRouter. Starts in the restored
3750
+ * (non-capturing) state — call `capture()` to register the tap and (when
3751
+ * `suppress: true`) also push a suppress sink policy on the router.
3752
+ *
3753
+ * When no router is provided, Console constructs a private one patched
3754
+ * against the given `target` console global. Production Term factories
3755
+ * should pass a shared router so Console's tap and Output's sink layer
3756
+ * deterministically via a single patch site.
3757
+ */
3758
+ declare function createConsole(target?: globalThis.Console, router?: ConsoleRouter): Console;
3759
+ //#endregion
3760
+ //#region packages/ag-term/src/ansi/term.d.ts
3761
+ /**
3762
+ * All chalk style method names that can be chained.
3763
+ */
3764
+ type ChalkStyleName = "reset" | "bold" | "dim" | "italic" | "underline" | "overline" | "inverse" | "hidden" | "strikethrough" | "visible" | "black" | "red" | "green" | "yellow" | "blue" | "magenta" | "cyan" | "white" | "gray" | "grey" | "blackBright" | "redBright" | "greenBright" | "yellowBright" | "blueBright" | "magentaBright" | "cyanBright" | "whiteBright" | "bgBlack" | "bgRed" | "bgGreen" | "bgYellow" | "bgBlue" | "bgMagenta" | "bgCyan" | "bgWhite" | "bgGray" | "bgGrey" | "bgBlackBright" | "bgRedBright" | "bgGreenBright" | "bgYellowBright" | "bgBlueBright" | "bgMagentaBright" | "bgCyanBright" | "bgWhiteBright";
3765
+ /**
3766
+ * StyleChain provides chainable styling methods.
3767
+ * Each property returns a new chain, and the chain is callable.
3768
+ */
3769
+ type StyleChain = {
3770
+ /**
3771
+ * Apply styles to text.
3772
+ */
3773
+ (text: string): string;
3774
+ (template: TemplateStringsArray, ...values: unknown[]): string;
3775
+ /**
3776
+ * RGB foreground color.
3777
+ */
3778
+ rgb(r: number, g: number, b: number): StyleChain;
3779
+ /**
3780
+ * Hex foreground color.
3781
+ */
3782
+ hex(color: string): StyleChain;
3783
+ /**
3784
+ * 256-color foreground.
3785
+ */
3786
+ ansi256(code: number): StyleChain;
3787
+ /**
3788
+ * RGB background color.
3789
+ */
3790
+ bgRgb(r: number, g: number, b: number): StyleChain;
3791
+ /**
3792
+ * Hex background color.
3793
+ */
3794
+ bgHex(color: string): StyleChain;
3795
+ /**
3796
+ * 256-color background.
3797
+ */
3798
+ bgAnsi256(code: number): StyleChain;
3799
+ } & {
3800
+ /**
3801
+ * Chainable style properties.
3802
+ */
3803
+ readonly [K in ChalkStyleName]: StyleChain } & {
3804
+ curlyUnderline(text: string): string;
3805
+ dottedUnderline(text: string): string;
3806
+ dashedUnderline(text: string): string;
3807
+ doubleUnderline(text: string): string;
3808
+ underlineColor(r: number, g: number, b: number, text: string): string;
3809
+ styledUnderline(name: UnderlineStyle$2, rgb: RGB$1, text: string): string;
3810
+ };
3811
+ /**
3812
+ * Term — the central abstraction for terminal interaction.
3813
+ *
3814
+ * Term is both a styling helper (chainable ANSI via Proxy) and the umbrella
3815
+ * for typed sub-owners (input / output / modes / size / signals / console).
3816
+ * Pass it to `run()` or `createApp()`.
3817
+ *
3818
+ * Provides:
3819
+ * - Capability detection (cached on creation)
3820
+ * - Dimensions (shorthand getters over `term.size`)
3821
+ * - I/O (write, writeLine + the per-resource sub-owners)
3822
+ * - Sub-owners: input (stdin/probes/events), output (stdout guard),
3823
+ * modes (raw/alt-screen/paste/kitty/mouse/focus), size (dims + resize),
3824
+ * signals (process signal scope), console (console.* capture)
3825
+ * - Styling (chainable via Proxy)
3826
+ * - Disposable lifecycle
3827
+ *
3828
+ * @example
3829
+ * ```ts
3830
+ * using term = createTerm()
3831
+ * await run(<App />, term)
3832
+ * ```
3833
+ */
3834
+ interface Term extends Disposable, StyleChain {
3835
+ /**
3836
+ * Terminal capabilities profile.
3837
+ *
3838
+ * Always populated — every Term constructor commits to a full TerminalCaps.
3839
+ * Node-backed Terms with TTY stdin detect from the environment; non-TTY
3840
+ * Node terms, headless Terms, and emulator-backed Terms use sensible
3841
+ * deterministic defaults (`defaultCaps()` — truecolor, unicode, no kitty
3842
+ * keyboard). Override via `createTerm({ caps: { … } })`.
3843
+ *
3844
+ * Post km-silvery.terminal-profile-plateau Phase 2 this is non-optional —
3845
+ * callers no longer need `term.caps ?? detectTerminalCaps()` guards.
3846
+ *
3847
+ * Equivalent to `term.profile.caps`. The two views are guaranteed identical
3848
+ * — every Term constructor seeds `profile` from `caps` (or vice versa) via
3849
+ * `createTerminalProfile({ caps })` so there's only one source of truth.
3850
+ */
3851
+ readonly caps: TerminalCaps;
3852
+ /**
3853
+ * Fully-resolved {@link TerminalProfile} for this Term — `caps`, `colorLevel`,
3854
+ * `colorForced`, and `colorProvenance` bundled into the single value
3855
+ * downstream consumers should pass through the pipeline.
3856
+ *
3857
+ * Every Term variant owns its profile. Node-backed Terms build it from the
3858
+ * TTY/env detection that populated `caps`; headless and emulator-backed
3859
+ * Terms build it from their deterministic caps. The profile's
3860
+ * `colorProvenance` is always `"caller-caps"` (and `colorForced` is `false`)
3861
+ * when the Term constructed it from `caps` — Term construction is not an
3862
+ * opportunity for env precedence (that happens at `run()` / `createApp()`
3863
+ * where `colorLevel` / `NO_COLOR` are applied).
3864
+ *
3865
+ * Prefer this over `term.caps` when calling downstream pipeline entry
3866
+ * points that accept a profile. run.tsx's Term branch reads it directly
3867
+ * instead of rebuilding via `createTerminalProfile({ caps: term.caps })`
3868
+ * — one detection, one profile, no double-pass.
3869
+ *
3870
+ * Post km-silvery.plateau-term-owns-profile (H15 of the /big review
3871
+ * 2026-04-23).
3872
+ */
3873
+ readonly profile: TerminalProfile;
3874
+ /**
3875
+ * Environment identity — `program`, `version`, `TERM`. Convenience mirror
3876
+ * of `profile.emulator`. Callers that only need "who is the terminal?"
3877
+ * (diagnostics, probe-cache keys) read this instead of sitting on the full
3878
+ * protocol-flags surface of {@link caps}.
3879
+ *
3880
+ * Post km-silvery.plateau-naming-polish (2026-04-23): renamed from
3881
+ * `term.identity`; `TerminalIdentity` → `TerminalEmulator`.
3882
+ */
3883
+ readonly emulator: TerminalEmulator;
3884
+ /**
3885
+ * Terminal width in columns.
3886
+ * Undefined if not a TTY or dimensions unavailable.
3887
+ */
3888
+ readonly cols: number | undefined;
3889
+ /**
3890
+ * Terminal height in rows.
3891
+ * Undefined if not a TTY or dimensions unavailable.
3892
+ */
3893
+ readonly rows: number | undefined;
3894
+ /**
3895
+ * Input owner — mediates ALL stdin reads + raw-mode + data subscription.
3896
+ * Use `term.input.probe(…)` for terminal queries (color, cursor, kitty, etc.)
3897
+ * and `term.input.onData(…)` for primary key/mouse stream consumers.
3898
+ *
3899
+ * Replaces direct `process.stdin.setRawMode` / `stdin.on('data', …)` —
3900
+ * those patterns race under async (the 2026-04-22 wasRaw class).
3901
+ *
3902
+ * Lazily constructed on first access for Node-backed Terms.
3903
+ * Undefined for headless Terms (no stdin to own).
3904
+ */
3905
+ readonly input: InputOwner | undefined;
3906
+ /**
3907
+ * Output owner — single-owner stdout/stderr/console mediator.
3908
+ * Use `term.output.write(…)` for render-pipeline output.
3909
+ *
3910
+ * When activated (after protocol setup), intercepts `process.stdout` /
3911
+ * `process.stderr` / `console.*` so only silvery's render pipeline reaches
3912
+ * the terminal. Non-silvery writes are suppressed (stdout) or redirected to
3913
+ * `DEBUG_LOG` / buffered (stderr). One stable owner per Term, toggled via
3914
+ * `activate()` / `deactivate()` for pause/resume cycles.
3915
+ *
3916
+ * Lazily constructed on first access for Node-backed Terms.
3917
+ * Undefined for headless and emulator-backed Terms (no real stdout to own).
3918
+ */
3919
+ readonly output: Output | undefined;
3920
+ /**
3921
+ * Size owner — single source of truth for terminal dimensions, exposed as
3922
+ * alien-signals `ReadSignal`s.
3923
+ *
3924
+ * `term.size.cols()` / `term.size.rows()` / `term.size.snapshot()` read the
3925
+ * current value and, inside `computed` / `effect`, subscribe to changes.
3926
+ * The first read installs the stdout `resize` listener; SIGWINCH bursts
3927
+ * coalesce through the Size owner's 200ms trailing debounce.
3928
+ *
3929
+ * Replaces direct `process.stdout.columns` / `stdout.rows` reads — those
3930
+ * return stale snapshots under concurrent resize and scatter coalescing
3931
+ * logic across every consumer.
3932
+ *
3933
+ * `term.cols` / `term.rows` remain as shorthand getters that delegate to
3934
+ * this owner; they are slated for removal alongside `term.stdin/stdout` in
3935
+ * Phase 8.
3936
+ */
3937
+ readonly size: Size;
3938
+ /**
3939
+ * Modes owner — single authority for terminal protocol modes, exposed as
3940
+ * alien-signals `Signal<T>`s.
3941
+ *
3942
+ * Each mode is a callable signal: `term.modes.rawMode`, `altScreen`,
3943
+ * `bracketedPaste`, `kittyKeyboard` (`number | false`), `mouse`,
3944
+ * `focusReporting`. Read via `modes.altScreen()`, write via
3945
+ * `modes.altScreen(true)`, subscribe via `effect(() => modes.altScreen())`.
3946
+ * Same-value writes don't re-emit ANSI (alien-signals equality). `dispose`
3947
+ * restores exactly what this owner activated.
3948
+ *
3949
+ * Replaces the scattered `enableMouse()` / `enableKittyKeyboard()` /
3950
+ * `enableBracketedPaste()` / `enableFocusReporting()` call sites that
3951
+ * previously toggled terminal state from every subsystem. Those shared
3952
+ * globals are the same leak vector that produced the 2026-04-22 wasRaw
3953
+ * race class — concentrating them behind one owner makes them race-free.
3954
+ */
3955
+ readonly modes: Modes;
3956
+ /**
3957
+ * Signals owner — single coordinator for every process-signal handler
3958
+ * bound to this Term's lifetime.
3959
+ *
3960
+ * `term.signals.on("SIGINT", handler, { priority, before, after, name })`
3961
+ * registers a teardown handler. One shared `process.on(signal, …)` listener
3962
+ * is installed per signal, regardless of how many handlers the owner
3963
+ * manages. On `dispose()` (called from `term[Symbol.dispose]`), every
3964
+ * handler runs in priority / dependency order, each wrapped in try/catch
3965
+ * so one failure doesn't block the rest.
3966
+ *
3967
+ * Replaces ad-hoc `process.on("SIGINT", …)` / `process.once("SIGTERM", …)`
3968
+ * call sites scattered across runtime + apps. The 2026-04-22 shared-global
3969
+ * audit found 78 such sites with no documented cleanup order — late
3970
+ * handlers could crash while earlier handlers' resources leaked. The owner
3971
+ * gives every Term exactly one entry-point to the signal graph.
3972
+ *
3973
+ * Present on every Term variant — even headless / emulator-backed — since
3974
+ * signal handling is cross-cutting and benefits from consistent teardown
3975
+ * semantics in tests as well as production.
3976
+ */
3977
+ readonly signals: Signals;
3978
+ /**
3979
+ * Console owner — single-owner console.* interceptor for the Term's lifetime.
3980
+ *
3981
+ * Starts inert. Call `term.console.capture({suppress:true})` once the alt
3982
+ * screen is active to route `console.log/info/warn/error/debug` into a
3983
+ * buffer instead of the screen; then `term.console.replay(stdout, stderr)`
3984
+ * on exit to re-emit captured entries to the normal streams. React apps
3985
+ * read via `subscribe` + `getSnapshot` (see `<Console>` + `useConsole`).
3986
+ *
3987
+ * Replaces the standalone console-patching helper — same implementation,
3988
+ * Term-owned lifecycle. Undefined for Terms that don't own a real console
3989
+ * (headless dims + emulator-backed), which never render through the global
3990
+ * terminal and therefore have nothing to corrupt.
3991
+ */
3992
+ readonly console: Console | undefined;
3993
+ /**
3994
+ * Write string to stdout.
3995
+ */
3996
+ write(str: string): void;
3997
+ /**
3998
+ * Write string followed by newline to stdout.
3999
+ */
4000
+ writeLine(str: string): void;
4001
+ /**
4002
+ * Strip ANSI escape codes from string.
4003
+ */
4004
+ stripAnsi(str: string): string;
4005
+ /**
4006
+ * Visible screen region. Only available when created with a terminal backend.
4007
+ * Provides getText(), getLines(), containsText() for assertions.
4008
+ */
4009
+ readonly screen?: TermScreen;
4010
+ /**
4011
+ * Scrollback region. Only available when created with a terminal backend.
4012
+ * Provides getText(), getLines(), containsText() for assertions.
4013
+ */
4014
+ readonly scrollback?: TermScreen;
4015
+ /**
4016
+ * Cell-level access for the visible screen — row-first order.
4017
+ * Returns resolved RGB colors, attributes, wide-char info.
4018
+ * Only available on emulator-backed terms (createTermless).
4019
+ */
4020
+ cell?(row: number, col: number): {
4021
+ readonly fg: unknown;
4022
+ readonly bg: unknown;
4023
+ readonly char: string;
4024
+ };
4025
+ /**
4026
+ * Row-level access for the visible screen.
4027
+ * Only available on emulator-backed terms (createTermless).
4028
+ */
4029
+ row?(n: number): {
4030
+ getText(): string;
4031
+ cell(col: number): {
4032
+ readonly fg: unknown;
4033
+ readonly bg: unknown;
4034
+ readonly char: string;
4035
+ };
4036
+ };
4037
+ /**
4038
+ * Resize the terminal emulator. Only available when created with a terminal backend.
4039
+ * Resizes the underlying emulator and triggers a re-render in the app.
4040
+ */
4041
+ resize?(cols: number, rows: number): void;
4042
+ /**
4043
+ * Paint a rendered buffer to produce ANSI output.
4044
+ * Diffs buffer against prev (fresh render if prev is null).
4045
+ * Updates term.frame with an immutable TextFrame snapshot.
4046
+ * Returns the ANSI output string.
4047
+ * For emulator backends, also feeds the output to the emulator.
4048
+ * For headless terms, returns empty string.
4049
+ */
4050
+ paint?(buffer: TerminalBuffer, prev: TerminalBuffer | null): string;
4051
+ /**
4052
+ * Last painted TextFrame. Set after each paint() call.
4053
+ * Immutable snapshot with cell-level access and resolved RGB colors.
4054
+ */
4055
+ readonly frame?: TextFrame;
4056
+ }
4057
+ /**
4058
+ * Create a Term instance.
4059
+ *
4060
+ * Factory overloads:
4061
+ * - `createTerm()` — Node.js terminal (auto-detect from process.stdin/stdout)
4062
+ * - `createTerm({ stdout, stdin, ... })` — Node.js with custom streams/overrides
4063
+ * - `createTerm({ cols, rows })` — Headless for testing (no I/O, fixed dims)
4064
+ * - `createTerm(backend, { cols, rows })` — Terminal emulator backend (termless) for testing
4065
+ * - `createTerm(emulator)` — Pre-created termless Terminal
4066
+ *
4067
+ * Detection results are cached at creation time for consistency.
4068
+ *
4069
+ * @example
4070
+ * ```ts
4071
+ * // Full terminal app
4072
+ * using term = createTerm()
4073
+ * await run(<App />, term)
4074
+ *
4075
+ * // Headless for testing
4076
+ * const term = createTerm({ cols: 80, rows: 24 })
4077
+ *
4078
+ * // Terminal emulator (termless) for full ANSI testing
4079
+ * using term = createTerm(createXtermBackend(), { cols: 80, rows: 24 })
4080
+ * await run(<App />, term)
4081
+ * expect(term.screen).toContainText("Hello")
4082
+ *
4083
+ * // Custom streams
4084
+ * const term = createTerm({ stdout: customStream })
4085
+ * ```
4086
+ */
4087
+ declare function createTerm(options?: CreateTermOptions): Term;
4088
+ declare function createTerm(dims: {
4089
+ cols: number;
4090
+ rows: number;
4091
+ caps?: Partial<TerminalCaps>;
4092
+ }): Term;
4093
+ declare function createTerm(backend: TermEmulatorBackend, dims: {
4094
+ cols: number;
4095
+ rows: number;
4096
+ caps?: Partial<TerminalCaps>;
4097
+ }): Term;
4098
+ declare function createTerm(emulator: TermEmulator, opts?: {
4099
+ caps?: Partial<TerminalCaps>;
4100
+ }): Term;
4101
+ //#endregion
4102
+ //#region packages/ag-term/src/ansi/index.d.ts
4103
+ declare const term: Term;
4104
+ //#endregion
4105
+ //#region packages/ag/src/focus-manager.d.ts
4106
+ type FocusOrigin = "keyboard" | "mouse" | "programmatic";
4107
+ /**
4108
+ * Callback fired when focus changes. Used by the runtime to dispatch
4109
+ * DOM-level focus/blur events without coupling FocusManager to the event system.
4110
+ *
4111
+ * @param oldNode - The node losing focus (null if nothing was focused)
4112
+ * @param newNode - The node gaining focus (null on blur)
4113
+ * @param origin - How focus was acquired
4114
+ */
4115
+ type FocusChangeCallback = (oldNode: AgNode | null, newNode: AgNode | null, origin: FocusOrigin | null) => void;
4116
+ interface FocusSnapshot {
4117
+ activeId: string | null;
4118
+ previousId: string | null;
4119
+ focusOrigin: FocusOrigin | null;
4120
+ scopeStack: readonly string[];
4121
+ /** The currently active peer scope (WPF FocusScope model) */
4122
+ activeScopeId: string | null;
4123
+ }
4124
+ interface FocusManagerOptions {
4125
+ /** Called when focus changes — wire up event dispatch here */
4126
+ onFocusChange?: FocusChangeCallback;
4127
+ }
4128
+ /**
4129
+ * Options for registering a hook-based (virtual) focusable.
4130
+ *
4131
+ * Hook focusables are registered via React hooks (e.g. `useFocus()` in the
4132
+ * Ink compat layer) rather than by the `focusable` prop on a tree node. They
4133
+ * participate in Tab cycling but don't have a backing `AgNode` — activeId
4134
+ * tracking is by id only, and `activeElement` is null when a hook focusable
4135
+ * is the active target.
4136
+ */
4137
+ interface HookFocusableOptions {
4138
+ /** Registration is inert when false — skipped in tab order, never reports focused */
4139
+ isActive?: boolean;
4140
+ /** Focus this id when registered (only when isActive !== false) */
4141
+ autoFocus?: boolean;
4142
+ }
4143
+ interface FocusManager {
4144
+ /** Currently focused node */
4145
+ readonly activeElement: AgNode | null;
4146
+ /** testID of the currently focused node */
4147
+ readonly activeId: string | null;
4148
+ /** Previously focused node */
4149
+ readonly previousElement: AgNode | null;
4150
+ /** testID of the previously focused node */
4151
+ readonly previousId: string | null;
4152
+ /** How focus was most recently acquired */
4153
+ readonly focusOrigin: FocusOrigin | null;
4154
+ /** Stack of active focus scope IDs */
4155
+ readonly scopeStack: readonly string[];
4156
+ /** Map of scope ID -> last focused testID within that scope */
4157
+ readonly scopeMemory: Readonly<Record<string, string>>;
4158
+ /** Focus a specific node */
4159
+ focus(node: AgNode, origin?: FocusOrigin): void;
4160
+ /** Focus a node by testID (requires root for tree search) */
4161
+ focusById(id: string, root: AgNode, origin?: FocusOrigin): void;
4162
+ /**
4163
+ * Focus a hook-registered (virtual) id directly without tree traversal.
4164
+ * Unlike `focusById`, this never needs a root — used by `useFocus()` hooks
4165
+ * that track focus by id only.
4166
+ */
4167
+ focusVirtualId(id: string, origin?: FocusOrigin): void;
4168
+ /** Clear focus */
4169
+ blur(): void;
4170
+ /**
4171
+ * Register a hook-based focusable id (e.g. from `useFocus()` in Ink compat).
4172
+ *
4173
+ * Hook focusables form a flat list alongside the tree-based focusables.
4174
+ * `focusNext`/`focusPrev` interleave: tree focusables come first (document
4175
+ * order), then hook focusables (registration order). A single unified tab
4176
+ * cycle walks both.
4177
+ *
4178
+ * Returns an unregister callback (safe to call on effect cleanup).
4179
+ */
4180
+ registerHookFocusable(id: string, options?: HookFocusableOptions): () => void;
4181
+ /** Update an existing hook-focusable's active state. */
4182
+ setHookFocusableActive(id: string, isActive: boolean): void;
4183
+ /** Whether any hook focusables are currently registered. */
4184
+ readonly hasHookFocusables: boolean;
4185
+ /**
4186
+ * Global focus enable (Ink compat). When false, `focusNext`/`focusPrev`
4187
+ * become no-ops for hook-registered focusables. Tree-based focusables
4188
+ * ignore this flag — apps using `useFocusable` are not affected.
4189
+ */
4190
+ readonly hookFocusEnabled: boolean;
4191
+ setHookFocusEnabled(enabled: boolean): void;
4192
+ /**
4193
+ * Handle a subtree being removed from the tree.
4194
+ * If the focused node (or previous node) is within the removed subtree,
4195
+ * clear the reference to prevent dead node retention and broken navigation.
4196
+ */
4197
+ handleSubtreeRemoved(removedRoot: AgNode): void;
4198
+ /** Push a focus scope onto the stack */
4199
+ enterScope(scopeId: string): void;
4200
+ /** Pop the current focus scope */
4201
+ exitScope(): void;
4202
+ /** The currently active peer scope ID (WPF FocusScope model) */
4203
+ readonly activeScopeId: string | null;
4204
+ /**
4205
+ * Activate a peer focus scope. Saves current focus in the old scope's memory,
4206
+ * switches to the new scope, and restores the remembered focus (or focuses
4207
+ * the first focusable element in the scope subtree).
4208
+ */
4209
+ activateScope(scopeId: string, root: AgNode): void;
4210
+ /** Get the testID path from focused node to root */
4211
+ getFocusPath(root: AgNode): string[];
4212
+ /** Check if a subtree rooted at testID contains the focused node */
4213
+ hasFocusWithin(root: AgNode, testID: string): boolean;
4214
+ /** Focus the next focusable node in tab order */
4215
+ focusNext(root: AgNode, scope?: AgNode): void;
4216
+ /** Focus the previous focusable node in tab order */
4217
+ focusPrev(root: AgNode, scope?: AgNode): void;
4218
+ /** Focus in a spatial direction (up/down/left/right) */
4219
+ focusDirection(root: AgNode, direction: "up" | "down" | "left" | "right", layoutFn?: (node: AgNode) => Rect | null): void;
4220
+ /** Subscribe for React integration (useSyncExternalStore) */
4221
+ subscribe(listener: () => void): () => void;
4222
+ /** Get immutable snapshot for useSyncExternalStore */
4223
+ getSnapshot(): FocusSnapshot;
4224
+ }
4225
+ declare function createFocusManager(options?: FocusManagerOptions): FocusManager;
4226
+ //#endregion
4227
+ //#region packages/headless/src/selection.d.ts
4228
+ interface SelectionPosition {
4229
+ col: number;
4230
+ row: number;
4231
+ }
4232
+ interface SelectionRange {
4233
+ anchor: SelectionPosition;
4234
+ head: SelectionPosition;
4235
+ }
4236
+ /**
4237
+ * Rectangular boundary for scoped selection.
4238
+ * Derived by the runtime from the active document-selection ancestor's
4239
+ * scrollRect, or from a `userSelect="contain"` hard boundary.
4240
+ */
4241
+ interface SelectionScope {
4242
+ top: number;
4243
+ bottom: number;
4244
+ left: number;
4245
+ right: number;
4246
+ }
4247
+ type SelectionGranularity = "character" | "word" | "line";
4248
+ interface TerminalSelectionState {
4249
+ range: SelectionRange | null;
4250
+ /** True while mouse button is held */
4251
+ selecting: boolean;
4252
+ /** Who initiated the selection */
4253
+ source: "mouse" | "keyboard" | null;
4254
+ /** Current selection granularity */
4255
+ granularity: SelectionGranularity;
4256
+ /** Active document/contain boundary — selection range is clamped to this rect */
4257
+ scope: SelectionScope | null;
4258
+ }
4259
+ //#endregion
4260
+ //#region packages/ag-term/src/mouse-events.d.ts
4261
+ /**
4262
+ * Create a synthetic mouse event.
4263
+ *
4264
+ * Modifier keys are merged from two sources:
4265
+ * - SGR mouse protocol: reports Ctrl, Alt/Meta, Shift (reliable)
4266
+ * - Keyboard tracking: reports Super/Cmd, Hyper, CapsLock, NumLock (via Kitty protocol)
4267
+ *
4268
+ * `metaKey` = keyboard-tracked Super (Cmd on macOS). SGR "meta" maps to `altKey`.
4269
+ */
4270
+ declare function createMouseEvent(type: SilveryMouseEvent["type"], x: number, y: number, target: AgNode, parsed: ParsedMouse, keyboardMods?: KeyboardModifierState): SilveryMouseEvent;
4271
+ /**
4272
+ * Create a synthetic wheel event.
4273
+ */
4274
+ declare function createWheelEvent(x: number, y: number, target: AgNode, parsed: ParsedMouse, keyboardMods?: KeyboardModifierState): SilveryWheelEvent;
4275
+ /**
4276
+ * Tree-based hit test: find the deepest node whose scrollRect contains (x, y).
4277
+ *
4278
+ * Uses reverse child order (last sibling wins = highest z-order, like DOM).
4279
+ * Respects overflow:hidden clipping and pointerEvents="none".
4280
+ *
4281
+ * ### Absolute-positioned nodes escape parent bounds
4282
+ *
4283
+ * Absolute descendants participate in hit-testing by GEOMETRY, not by
4284
+ * tree order / parent rect containment. An absolute child can be placed
4285
+ * outside its parent's bounding rect (e.g., a popover anchored near a
4286
+ * viewport edge); it still occupies screen cells at its own geometry and
4287
+ * must be hittable.
4288
+ *
4289
+ * The hit test runs an "absolute pass" first that walks the whole subtree
4290
+ * for absolute descendants and returns the latest-in-tree hit (matching
4291
+ * the three-pass render order where absolute children paint on top of
4292
+ * normal + sticky content). If no absolute descendant covers the point,
4293
+ * it falls through to standard in-flow DFS.
4294
+ *
4295
+ * A recursive sub-call (via `hitTest(absolute, ...)`) would re-run the
4296
+ * absolute pass on that absolute's subtree — which is correct: nested
4297
+ * absolutes also need geometry-based hit testing.
4298
+ */
4299
+ declare function hitTest(node: AgNode, x: number, y: number): AgNode | null;
4300
+ /**
4301
+ * Dispatch a mouse event through the render tree with DOM-style bubbling.
4302
+ *
4303
+ * Bubbles from target → root, calling the appropriate handler on each node.
4304
+ * stopPropagation() halts bubbling. mouseenter/mouseleave do NOT bubble (DOM spec).
4305
+ */
4306
+ declare function dispatchMouseEvent(event: SilveryMouseEvent): void;
4307
+ /**
4308
+ * Click-count state tracker.
4309
+ *
4310
+ * Counts up to 3 consecutive clicks within `MULTI_CLICK_TIME_MS` and
4311
+ * `MULTI_CLICK_DISTANCE` cells of each other on the same button. After
4312
+ * count reaches 3, the next click resets to 1 (matching DOM behavior:
4313
+ * `MouseEvent.detail` increments to 3, then a new click chain starts).
4314
+ *
4315
+ * `DoubleClickState` is kept as a backwards-compatible alias.
4316
+ */
4317
+ interface ClickCountState {
4318
+ lastClickTime: number;
4319
+ lastClickX: number;
4320
+ lastClickY: number;
4321
+ lastClickButton: number;
4322
+ /** Number of consecutive clicks in the current chain (1, 2, or 3). */
4323
+ count: number;
4324
+ }
4325
+ /** @deprecated Use `ClickCountState` instead — kept as an alias for callers
4326
+ * that haven't migrated to the count-based API. */
4327
+ type DoubleClickState = ClickCountState;
4328
+ declare function createClickCountState(): ClickCountState;
4329
+ /** @deprecated Use `createClickCountState()` instead. */
4330
+ declare const createDoubleClickState: typeof createClickCountState;
4331
+ /**
4332
+ * Check if a click qualifies as a double-click. Backwards-compatible
4333
+ * wrapper around `checkClickCount`.
4334
+ *
4335
+ * @deprecated Use `checkClickCount` and inspect the returned count
4336
+ * (`=== 2` for dblclick, `=== 3` for tripleclick).
4337
+ */
4338
+ declare function checkDoubleClick(state: ClickCountState, x: number, y: number, button: number, now?: number): boolean;
4339
+ /**
4340
+ * Compute mouseenter/mouseleave transitions between two ancestor paths.
4341
+ *
4342
+ * Returns { entered, left } — arrays of nodes that were entered or left.
4343
+ * Mirrors the DOM spec: fire mouseleave on nodes in prevPath not in nextPath,
4344
+ * and mouseenter on nodes in nextPath not in prevPath.
4345
+ */
4346
+ declare function computeEnterLeave(prevPath: AgNode[], nextPath: AgNode[]): {
4347
+ entered: AgNode[];
4348
+ left: AgNode[];
4349
+ };
4350
+ /**
4351
+ * Options for creating a mouse event processor.
4352
+ */
4353
+ interface MouseEventProcessorOptions {
4354
+ /** Optional focus manager — enables click-to-focus behavior.
4355
+ * On mousedown, the deepest focusable ancestor of the hit target is focused. */
4356
+ focusManager?: FocusManager;
4357
+ }
4358
+ /**
4359
+ * State for the mouse event processor.
4360
+ */
4361
+ /**
4362
+ * Keyboard modifier state tracked from Kitty protocol key events.
4363
+ * Merged into mouse events to provide accurate modifier detection
4364
+ * (SGR mouse protocol reports Ctrl/Alt/Shift but NOT Cmd/Super).
4365
+ */
4366
+ interface KeyboardModifierState {
4367
+ super: boolean;
4368
+ hyper: boolean;
4369
+ capsLock: boolean;
4370
+ numLock: boolean;
4371
+ }
4372
+ interface MouseEventProcessorState {
4373
+ doubleClick: DoubleClickState;
4374
+ /** Previous hover path (for enter/leave tracking) */
4375
+ hoverPath: AgNode[];
4376
+ /** Whether the left button is currently down (for click detection) */
4377
+ mouseDownTarget: AgNode | null;
4378
+ /** Optional ancestor that captures move/up for the active mouse press. */
4379
+ mouseCaptureTarget: AgNode | null;
4380
+ /** Grace timer for captured drags that briefly leave the terminal bounds. */
4381
+ outsideCaptureReleaseTimer: ReturnType<typeof setTimeout> | null;
4382
+ /** Last no-target mouse event observed while the grace timer is armed. */
4383
+ outsideCaptureReleaseMouse: ParsedMouse | null;
4384
+ /** Optional focus manager for click-to-focus */
4385
+ focusManager?: FocusManager;
4386
+ /** Modifier state from Kitty keyboard events, merged into mouse events */
4387
+ keyboardModifiers: KeyboardModifierState;
4388
+ /** Aggregate `defaultPrevented` from the most recent click/dblclick/tripleclick
4389
+ * dispatch chain. Set by `processMouseEvent` on every mouseup so callers
4390
+ * (e.g., the runtime selection wiring) can gate auto-select on whether the
4391
+ * component tree consumed the click. Reset to false at the start of each
4392
+ * mouseup dispatch. */
4393
+ lastClickPrevented: boolean;
4394
+ /** Last observed pointer coordinates (terminal cells). Updated on every
4395
+ * mouse event so consumers can re-hit-test after layout changes — e.g.
4396
+ * scroll-wheel events that reposition content under a stationary cursor.
4397
+ * null means the pointer has left the terminal bounds (clearHoverPath
4398
+ * ran) or no mouse event has arrived yet. */
4399
+ lastPointer: {
4400
+ x: number;
4401
+ y: number;
4402
+ } | null;
4403
+ }
4404
+ declare function createMouseEventProcessor(options?: MouseEventProcessorOptions): MouseEventProcessorState;
4405
+ /**
4406
+ * Process a raw ParsedMouse event and dispatch DOM-level events on the render tree.
4407
+ *
4408
+ * Call this for every SGR mouse event received. It handles:
4409
+ * - mousedown / mouseup
4410
+ * - click (on mouseup if same target as mousedown)
4411
+ * - dblclick (based on timing)
4412
+ * - mousemove + mouseenter/mouseleave
4413
+ * - wheel
4414
+ */
4415
+ declare function processMouseEvent(state: MouseEventProcessorState, parsed: ParsedMouse, root: AgNode): boolean;
4416
+ //#endregion
4417
+ //#region packages/ag-term/src/hit-registry-core.d.ts
4418
+ /**
4419
+ * Hit Registry Core — Pure logic for mouse hit testing.
4420
+ *
4421
+ * This module contains the React-free core of the hit registry:
4422
+ * types, registry class, z-index constants, and ID counter.
4423
+ *
4424
+ * React hooks and context live in ./hit-registry (which re-exports everything
4425
+ * from here plus adds useHitRegion, useHitRegionCallback, HitRegistryContext).
4426
+ *
4427
+ * The @silvery/ag-term barrel imports from this file to stay React-free.
4428
+ * Consumers who need React hooks should import from @silvery/ag-term/hit-registry.
4429
+ */
4430
+ /**
4431
+ * Target type for hit testing.
4432
+ * Each type represents a different clickable element in the UI.
4433
+ */
4434
+ interface HitTarget {
4435
+ /** The type of element that was clicked */
4436
+ type: "node" | "fold-toggle" | "link" | "column-header" | "scroll-area" | "button";
4437
+ /** Column index (for column-header, or items within a column) */
4438
+ colIndex?: number;
4439
+ /** Card index within a column */
4440
+ cardIndex?: number;
4441
+ /** Sub-item index within a card (e.g., checklist items) */
4442
+ subIndex?: number;
4443
+ /** Node ID for node-specific targets */
4444
+ nodeId?: string;
4445
+ /** URL for link targets */
4446
+ linkUrl?: string;
4447
+ /** Custom action identifier */
4448
+ action?: string;
4449
+ }
4450
+ /**
4451
+ * A registered hit region with position, size, target, and z-index.
4452
+ */
4453
+ interface HitRegion {
4454
+ /** X position on screen (0-indexed column) */
4455
+ x: number;
4456
+ /** Y position on screen (0-indexed row) */
4457
+ y: number;
4458
+ /** Width in columns */
4459
+ width: number;
4460
+ /** Height in rows */
4461
+ height: number;
4462
+ /** The target to return when this region is clicked */
4463
+ target: HitTarget;
4464
+ /** Z-index for layering (higher values are on top) */
4465
+ zIndex: number;
4466
+ }
4467
+ /**
4468
+ * Registry for managing hit regions.
4469
+ *
4470
+ * Components register their screen regions with targets, and the registry
4471
+ * resolves mouse clicks to the appropriate target based on position and z-index.
4472
+ *
4473
+ * @example
4474
+ * ```typescript
4475
+ * const registry = new HitRegistry();
4476
+ *
4477
+ * // Register a card region
4478
+ * registry.register('card-1', {
4479
+ * x: 10, y: 5, width: 30, height: 8,
4480
+ * target: { type: 'node', nodeId: 'abc123' },
4481
+ * zIndex: 10
4482
+ * });
4483
+ *
4484
+ * // Hit test a click
4485
+ * const target = registry.hitTest(15, 7);
4486
+ * // Returns { type: 'node', nodeId: 'abc123' }
4487
+ * ```
4488
+ */
4489
+ declare class HitRegistry {
4490
+ private regions;
4491
+ /**
4492
+ * Register a hit region with a unique ID.
4493
+ *
4494
+ * @param id - Unique identifier for the region (used for unregistration)
4495
+ * @param region - The region definition including position, size, target, and z-index
4496
+ */
4497
+ register(id: string, region: HitRegion): void;
4498
+ /**
4499
+ * Unregister a hit region by ID.
4500
+ *
4501
+ * @param id - The ID used when registering the region
4502
+ */
4503
+ unregister(id: string): void;
4504
+ /**
4505
+ * Clear all registered regions.
4506
+ * Useful when the UI is completely redrawn.
4507
+ */
4508
+ clear(): void;
4509
+ /**
4510
+ * Get the number of registered regions.
4511
+ * Useful for debugging.
4512
+ */
4513
+ get size(): number;
4514
+ /**
4515
+ * Test a screen position and return the highest z-index matching target.
4516
+ *
4517
+ * @param screenX - X position on screen (0-indexed column)
4518
+ * @param screenY - Y position on screen (0-indexed row)
4519
+ * @returns The target of the highest z-index region containing the point, or null if none
4520
+ */
4521
+ hitTest(screenX: number, screenY: number): HitTarget | null;
4522
+ /**
4523
+ * Get all regions that contain a point, sorted by z-index (highest first).
4524
+ * Useful for debugging or when you need to know all overlapping elements.
4525
+ *
4526
+ * @param screenX - X position on screen (0-indexed column)
4527
+ * @param screenY - Y position on screen (0-indexed row)
4528
+ * @returns Array of matching regions, sorted by z-index descending
4529
+ */
4530
+ hitTestAll(screenX: number, screenY: number): HitRegion[];
4531
+ /**
4532
+ * Debug helper: get all registered regions.
4533
+ */
4534
+ getAllRegions(): Map<string, HitRegion>;
4535
+ }
4536
+ /**
4537
+ * Reset the ID counter (useful for testing).
4538
+ */
4539
+ declare function resetHitRegionIdCounter(): void;
4540
+ /**
4541
+ * Recommended z-index values for different UI layers.
4542
+ */
4543
+ declare const Z_INDEX: {
4544
+ /** Background elements */readonly BACKGROUND: 0; /** Column headers */
4545
+ readonly COLUMN_HEADER: 5; /** Cards in the main view */
4546
+ readonly CARD: 10; /** Fold toggles (above cards for easier clicking) */
4547
+ readonly FOLD_TOGGLE: 15; /** Links within cards */
4548
+ readonly LINK: 20; /** Floating elements */
4549
+ readonly FLOATING: 50; /** Modal dialogs */
4550
+ readonly DIALOG: 100; /** Dropdown menus */
4551
+ readonly DROPDOWN: 150; /** Tooltips */
4552
+ readonly TOOLTIP: 200;
4553
+ };
4554
+ //#endregion
4555
+ //#region packages/ag-term/src/hit-registry.d.ts
4556
+ /**
4557
+ * Context for accessing the HitRegistry.
4558
+ * Components use this to register their hit regions.
4559
+ */
4560
+ declare const HitRegistryContext: _$react.Context<HitRegistry | null>;
4561
+ /**
4562
+ * Hook to get the HitRegistry from context.
4563
+ *
4564
+ * @returns The HitRegistry instance, or null if not in a HitRegistryContext
4565
+ */
4566
+ declare function useHitRegistry(): HitRegistry | null;
4567
+ /**
4568
+ * Hook to register a hit region based on component's screen position.
4569
+ *
4570
+ * Automatically registers on mount and when position changes,
4571
+ * and unregisters on unmount.
4572
+ *
4573
+ * @param target - The target to return when this region is clicked
4574
+ * @param rect - The screen rectangle (from useScrollRect or similar)
4575
+ * @param zIndex - Z-index for layering (default: 0)
4576
+ * @param enabled - Whether the region is active (default: true)
4577
+ *
4578
+ * @example
4579
+ * ```tsx
4580
+ * function Card({ nodeId }: { nodeId: string }) {
4581
+ * const rect = useScrollRect();
4582
+ *
4583
+ * useHitRegion(
4584
+ * { type: 'node', nodeId },
4585
+ * rect,
4586
+ * 10 // z-index for cards
4587
+ * );
4588
+ *
4589
+ * return <Box>...</Box>;
4590
+ * }
4591
+ * ```
4592
+ */
4593
+ declare function useHitRegion(target: HitTarget, rect: Rect | null, zIndex?: number, enabled?: boolean): void;
4594
+ /**
4595
+ * Hook to register a hit region using a callback for screen position.
4596
+ *
4597
+ * Similar to useHitRegion but works with useScrollRect for
4598
+ * better performance in large lists (avoids re-renders).
4599
+ *
4600
+ * @param target - The target to return when this region is clicked
4601
+ * @param zIndex - Z-index for layering (default: 0)
4602
+ * @param enabled - Whether the region is active (default: true)
4603
+ * @returns A callback to pass to useScrollRect
4604
+ *
4605
+ * @example
4606
+ * ```tsx
4607
+ * function Card({ nodeId }: { nodeId: string }) {
4608
+ * const onLayout = useHitRegionCallback(
4609
+ * { type: 'node', nodeId },
4610
+ * 10 // z-index
4611
+ * );
4612
+ *
4613
+ * useScrollRect(onLayout);
4614
+ *
4615
+ * return <Box>...</Box>;
4616
+ * }
4617
+ * ```
4618
+ */
4619
+ declare function useHitRegionCallback(target: HitTarget, zIndex?: number, enabled?: boolean): (rect: Rect) => void;
4620
+ //#endregion
4621
+ //#region packages/ag-term/src/bound-term.d.ts
4622
+ /**
4623
+ * BoundTerm interface - terminal with node awareness
4624
+ */
4625
+ interface BoundTerm {
4626
+ /** Get cell at screen coordinates */
4627
+ cell(x: number, y: number): Cell;
4628
+ /** Get node at screen coordinates */
4629
+ nodeAt(x: number, y: number): AgNode | null;
4630
+ /** Get visible text (plain, no ANSI) */
4631
+ readonly text: string;
4632
+ /** Terminal dimensions */
4633
+ readonly columns: number;
4634
+ readonly rows: number;
4635
+ /** Access underlying buffer */
4636
+ readonly buffer: TerminalBuffer;
4637
+ }
4638
+ //#endregion
4639
+ export { TerminalBuffer as $, IslandGuest as $t, term as A, MeasureFunc as At, SignalOnOptions as B, Key as Bt, TerminalSelectionState as C, ResizeEvent as Ct, FocusOrigin as D, SilveryMouseEvent as Dt, FocusManagerOptions as E, MouseEventProps as Et, ConsoleCaptureOptions as F, createFocusEvent as Ft, InputOwnerOptions as G, keyToName as Gt, Signals as H, ParsedKeypress as Ht, ConsoleStats as I, createKeyEvent as It, KittyFlags as J, parseKey as Jt, createInputOwner as K, matchHotkey as Kt, createConsole as L, dispatchFocusEvent as Lt, Term as M, FocusEventProps as Mt, createTerm as N, SilveryFocusEvent as Nt, FocusSnapshot as O, SilveryWheelEvent as Ot, Console as P, SilveryKeyEvent as Pt, Style as Q, IslandCursorState as Qt, CreateSignalsOptions as R, dispatchKeyEvent as Rt, SelectionRange as S, Rect as St, FocusManager as T, TextProps as Tt, createSignals as U, emptyKey as Ut, SignalUnregister as V, ParsedHotkey as Vt, InputOwner as W, keyToModifiers as Wt, Modes as X, IslandCapabilities as Xt, ModeName as Y, parseKeypress as Yt, createModes as Z, IslandContext as Zt, createMouseEventProcessor as _, ViewportProps as _n, FocusEvent$1 as _t, useHitRegistry as a, IslandModesOwner as an, isMouseSequence as at, hitTest as b, MouseEvent as bt, HitTarget as c, IslandOutputOwner as cn, AgNodeType as ct, MouseEventProcessorOptions as d, IslandProtocolModes as dn, CollisionStrategy as dt, IslandHandle as en, UnderlineStyle as et, MouseEventProcessorState as f, IslandSignal as fn, CursorShape as ft, createMouseEvent as g, ViewportPalette as gn, EventSource as gt, createDoubleClickState as h, ForeignSource as hn, Event as ht, useHitRegionCallback as i, IslandKeyEvent as in, ParsedMouse as it, StyleChain as j, MeasureMode as jt, createFocusManager as k, LayoutNode as kt, Z_INDEX as l, IslandPaletteOwner as ln, BlurEvent as lt, computeEnterLeave as m, IslandSizeOwner as mn, Decoration as mt, HitRegistryContext as n, IslandInputEvent as nn, ConsoleEntry as nt, HitRegion as o, IslandMouseEvent as on, parseMouseSequence as ot, checkDoubleClick as p, IslandSignalsOwner as pn, CustomEvent as pt, CreateModesOptions as q, parseHotkey as qt, useHitRegion as r, IslandInputOwner as rn, ParseMouseOptions as rt, HitRegistry as s, IslandNodeState as sn, AgNode as st, BoundTerm as t, IslandHydrate as tn, FrameCell as tt, resetHitRegionIdCounter as u, IslandPalettePolicy as un, BoxProps as ut, createWheelEvent as v, ViewportRef as vn, InteractiveState as vt, FocusChangeCallback as w, SignalEvent as wt, processMouseEvent as x, Placement as xt, dispatchMouseEvent as y, KeyEvent$1 as yt, SignalName as z, InputHandler as zt };
4640
+ //# sourceMappingURL=bound-term-0sPrrzH1.d.mts.map