silvery 0.19.2 → 0.21.1

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