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,2178 @@
1
+ import { m as syncTextContentSignal } from "./layout-signals-Dch2EiCy.mjs";
2
+ import { $ as isSoftBreakPoint, W as getActiveLineHeight, _t as wrapText, z as displayWidth } from "./ansi-OYRLxxZQ.mjs";
3
+ import { O as warnOnce } from "./src-Oe6x5PrS.mjs";
4
+ import { a as requireCapability, n as getConstants, r as getLayoutEngine } from "./layout-engine-Ca_nbtfL.mjs";
5
+ import { createContext } from "react";
6
+ import { createLogger } from "loggily";
7
+ import Reconciler from "react-reconciler";
8
+ import { DefaultEventPriority, DiscreteEventPriority, NoEventPriority } from "react-reconciler/constants.js";
9
+ //#region packages/ag/src/dirty-tracking.ts
10
+ /**
11
+ * Nodes with any content/style dirty flag. Written by reconciler,
12
+ * read by render phase for targeted subtree entry.
13
+ */
14
+ const contentDirtyNodes = /* @__PURE__ */ new Set();
15
+ /**
16
+ * Nodes where ONLY style props changed (no content, layout, or children changes).
17
+ * These are eligible for the style-only fast path in the render phase, which
18
+ * updates cell styles without re-collecting text or re-computing layout.
19
+ *
20
+ * A node is style-only when commitUpdate classifies contentChanged="style"
21
+ * AND layoutChanged=false. The render phase checks this set to decide whether
22
+ * to use restyleRegion() instead of full renderText()/renderBox().
23
+ */
24
+ const styleOnlyDirtyNodes = /* @__PURE__ */ new Set();
25
+ /**
26
+ * Nodes where scrollTo/scrollOffset changed. These don't affect Flexily layout
27
+ * dimensions, but the scroll, sticky, scrollRect, and notify phases must still
28
+ * run to update visible children positions.
29
+ *
30
+ * Written by reconciler (host-config.ts commitUpdate), read by ag.ts
31
+ * layout-on-demand gate.
32
+ */
33
+ const scrollDirtyNodes = /* @__PURE__ */ new Set();
34
+ /** Mark a node as content-dirty. Called when content/style flags are set. */
35
+ function trackContentDirty(node) {
36
+ contentDirtyNodes.add(node);
37
+ }
38
+ /**
39
+ * Mark a node as style-only dirty. Called when commitUpdate sees
40
+ * contentChanged="style" AND layoutChanged=false.
41
+ * If a node is later marked with contentDirty, the render phase ignores
42
+ * the style-only flag (full path takes precedence).
43
+ */
44
+ function trackStyleOnlyDirty(node) {
45
+ styleOnlyDirtyNodes.add(node);
46
+ }
47
+ /** Mark a node as scroll-dirty. Called when scrollTo/scrollOffset props change. */
48
+ function trackScrollDirty(node) {
49
+ scrollDirtyNodes.add(node);
50
+ }
51
+ /** O(1) check: are there any scroll-dirty nodes? */
52
+ function hasScrollDirty() {
53
+ return scrollDirtyNodes.size > 0;
54
+ }
55
+ /** Clear all dirty tracking. Called after each render pass completes. */
56
+ function clearDirtyTracking() {
57
+ contentDirtyNodes.clear();
58
+ styleOnlyDirtyNodes.clear();
59
+ scrollDirtyNodes.clear();
60
+ }
61
+ //#endregion
62
+ //#region packages/ag/src/epoch.ts
63
+ /**
64
+ * The current render epoch. Incremented after each render pass.
65
+ * Reconciler stamps dirty nodes with this value; render phase checks equality.
66
+ */
67
+ let renderEpoch = 0;
68
+ /** Get the current render epoch value. */
69
+ function getRenderEpoch() {
70
+ return renderEpoch;
71
+ }
72
+ /**
73
+ * Advance the render epoch. Called once at the end of each render pass.
74
+ * All nodes stamped with the old epoch instantly become "not dirty".
75
+ */
76
+ function advanceRenderEpoch() {
77
+ renderEpoch++;
78
+ }
79
+ /**
80
+ * Check if an epoch stamp matches the current render epoch (i.e., "is dirty").
81
+ */
82
+ function isCurrentEpoch(epoch) {
83
+ return epoch === renderEpoch;
84
+ }
85
+ /**
86
+ * Check if a specific dirty bit is set for the current epoch.
87
+ * Returns true if dirtyEpoch matches the current render epoch AND the bit is set.
88
+ */
89
+ function isDirty(dirtyBits, dirtyEpoch, bit) {
90
+ return dirtyEpoch === renderEpoch && (dirtyBits & bit) !== 0;
91
+ }
92
+ /**
93
+ * Check if ANY dirty bit is set for the current epoch.
94
+ */
95
+ function isAnyDirty(dirtyBits, dirtyEpoch) {
96
+ return dirtyEpoch === renderEpoch && dirtyBits !== 0;
97
+ }
98
+ //#endregion
99
+ //#region packages/scope/src/trace.ts
100
+ /**
101
+ * @silvery/scope/trace — opt-in leak detector for scopes and disposables.
102
+ *
103
+ * Gated by `SILVERY_SCOPE_TRACE=1`. When enabled, every `createScope()`
104
+ * and `disposable()` call is recorded with its creation stack; dispose
105
+ * unregisters; at process exit any remaining entries are logged.
106
+ *
107
+ * Zero overhead when disabled — the public functions are no-ops behind
108
+ * an early-return guard. The trace registry isn't even allocated.
109
+ *
110
+ * Why: silvery's `Scope` (Phase 0/1/2 of `km-silvery.lifecycle-scope`)
111
+ * makes resource ownership explicit, but adoption is opt-in. This is the
112
+ * runtime backstop for what the ESLint `no-raw-lifecycle` rule can't see
113
+ * (dynamic call sites, third-party paths). Together they make
114
+ * convention-driven leaks structurally impossible.
115
+ *
116
+ * Usage in tests / CI:
117
+ * ```bash
118
+ * SILVERY_SCOPE_TRACE=1 bun run test
119
+ * ```
120
+ *
121
+ * The detector logs to stderr at process exit (or `getTraceSnapshot()`
122
+ * is callable any time for in-test assertions). Production builds — no
123
+ * env var set — skip every code path.
124
+ *
125
+ * @packageDocumentation
126
+ */
127
+ const TRACE_ENV = "SILVERY_SCOPE_TRACE";
128
+ function envEnabled() {
129
+ try {
130
+ return !!globalThis.process?.env?.[TRACE_ENV];
131
+ } catch {
132
+ return false;
133
+ }
134
+ }
135
+ const traceEnabled = envEnabled();
136
+ const live = traceEnabled ? /* @__PURE__ */ new Map() : null;
137
+ /** Internal — register a handle on creation. No-op when tracing is off. */
138
+ function _trackCreate(handle, kind, name) {
139
+ if (!live) return;
140
+ const stack = (/* @__PURE__ */ new Error("(creation stack)")).stack ?? "";
141
+ live.set(handle, {
142
+ kind,
143
+ name,
144
+ createdAt: stack
145
+ });
146
+ }
147
+ /** Internal — unregister a handle on dispose. No-op when tracing is off. */
148
+ function _trackDispose(handle) {
149
+ if (!live) return;
150
+ live.delete(handle);
151
+ }
152
+ /** Force the at-exit report to fire now (for tests / manual diagnostics).
153
+ * Always logs the count, even if zero. No-op when tracing is off. */
154
+ function reportTraceLeaks() {
155
+ if (!live) return 0;
156
+ const count = live.size;
157
+ if (count === 0) {
158
+ console.error("[silvery:scope:trace] no undisposed handles");
159
+ return 0;
160
+ }
161
+ console.error(`[silvery:scope:trace] ${count} undisposed handle(s):`);
162
+ for (const entry of live.values()) {
163
+ const label = entry.kind + (entry.name ? `(${entry.name})` : "");
164
+ console.error(` - ${label}`);
165
+ console.error(entry.createdAt);
166
+ }
167
+ return count;
168
+ }
169
+ /**
170
+ * Print a per-scope handle-delta diagnostic at scope close. Called from
171
+ * `Scope[Symbol.asyncDispose]()` AFTER the inherited stack drains. Per
172
+ * km-silvery.lifecycle-leak-detection Phase 2: previously the trace only
173
+ * fired at process exit, which left in-test scope-close leaks invisible
174
+ * until the worker terminated.
175
+ *
176
+ * `pre` and `post` are snapshot counts of `getAdoptedHandles(scope).length`
177
+ * before and after scope-close. The delta is reported to stderr when
178
+ * tracing is enabled and the scope did NOT balance.
179
+ */
180
+ function reportScopeDelta(scopeName, pre, post) {
181
+ if (!traceEnabled) return;
182
+ if (post === 0) return;
183
+ const label = scopeName ? `Scope(${scopeName})` : "Scope";
184
+ console.error(`[silvery:scope:trace] ${label} close-delta: ${pre} adopted → ${post} undisposed (${pre - post} disposed cleanly)`);
185
+ }
186
+ if (traceEnabled) {
187
+ const proc = globalThis.process;
188
+ if (proc?.on) proc.on("exit", () => {
189
+ reportTraceLeaks();
190
+ });
191
+ }
192
+ //#endregion
193
+ //#region packages/scope/src/handle.ts
194
+ /**
195
+ * Module-private set of every legitimate handle the factory has produced.
196
+ * `WeakSet` keys by reference, so disposed-and-GC'd handles drop out
197
+ * automatically; this is the canonical source of truth for "is this a real
198
+ * silvery handle?" Forged values (created via `as TickHandle` or by hand)
199
+ * are NOT in this set, so `adoptHandle()` and `Scope.use()` reject them.
200
+ */
201
+ const branded = /* @__PURE__ */ new WeakSet();
202
+ const metadata = /* @__PURE__ */ new WeakMap();
203
+ /**
204
+ * Global count of handles currently alive (created but not yet disposed).
205
+ * Incremented in `defineHandle().create()`, decremented once on first
206
+ * dispose (idempotent). WeakSet alone cannot give a count; this integer
207
+ * counter is the parallel deterministic accounting layer.
208
+ *
209
+ * Used by tests to assert structural lifecycle invariants without GC:
210
+ * - After dispose-all: `getActiveHandleCount() === 0`
211
+ * - During N creates: `getActiveHandleCount() === N`
212
+ *
213
+ * Not per-scope (use `getAdoptedHandles(scope)` for per-scope accounting).
214
+ */
215
+ let _activeHandleCount = 0;
216
+ /** True iff `value` was minted by `defineHandle(...).create(...)`. */
217
+ function isBrandedHandle(value) {
218
+ return typeof value === "object" && value !== null && branded.has(value);
219
+ }
220
+ function defineHandle(kind) {
221
+ Symbol(`silvery.handle:${kind}`);
222
+ return {
223
+ /** Diagnostic label for this handle kind. */
224
+ kind,
225
+ /**
226
+ * Create an opaque, frozen handle that wraps `value` with `dispose`
227
+ * cleanup. The returned value is registered in the module-private
228
+ * `branded` WeakSet so the library can authenticate it later.
229
+ *
230
+ * The handle is `Object.freeze`d before return so callers cannot
231
+ * overwrite `[Symbol.asyncDispose]`, `[Symbol.dispose]`, or any
232
+ * surface property the consuming factory adds via `Object.assign`.
233
+ * Add public surface BEFORE returning from your factory.
234
+ */
235
+ create(_value, dispose) {
236
+ const handle = Object.create(null);
237
+ let _disposed = false;
238
+ Object.defineProperty(handle, Symbol.asyncDispose, {
239
+ value: async () => {
240
+ if (!_disposed) {
241
+ _disposed = true;
242
+ _activeHandleCount--;
243
+ }
244
+ await dispose();
245
+ },
246
+ enumerable: false,
247
+ writable: false,
248
+ configurable: false
249
+ });
250
+ Object.defineProperty(handle, Symbol.dispose, {
251
+ value: () => {
252
+ if (!_disposed) {
253
+ _disposed = true;
254
+ _activeHandleCount--;
255
+ }
256
+ dispose();
257
+ },
258
+ enumerable: false,
259
+ writable: false,
260
+ configurable: false
261
+ });
262
+ branded.add(handle);
263
+ metadata.set(handle, { kind });
264
+ _activeHandleCount++;
265
+ return handle;
266
+ }
267
+ };
268
+ }
269
+ /**
270
+ * Finalise a handle's public surface and freeze it. Call this from the
271
+ * resource factory AFTER attaching all public properties (e.g. `iterable`,
272
+ * `emitted`) and BEFORE returning to the consumer.
273
+ *
274
+ * @param handle The branded handle returned by `<defineHandle().create>`.
275
+ * @param surface Public properties to attach (each becomes non-enumerable,
276
+ * non-writable, non-configurable so the freeze is real).
277
+ *
278
+ * @example
279
+ * ```ts
280
+ * const handle = Tick.create(internal, stop)
281
+ * finaliseHandle(handle, { iterable, emitted: () => count })
282
+ * return handle as TickHandle
283
+ * ```
284
+ */
285
+ function finaliseHandle(handle, surface) {
286
+ if (!branded.has(handle)) throw new TypeError("finaliseHandle: not a branded handle (call defineHandle().create first)");
287
+ for (const key of Object.keys(surface)) Object.defineProperty(handle, key, {
288
+ value: surface[key],
289
+ enumerable: true,
290
+ writable: false,
291
+ configurable: false
292
+ });
293
+ for (const sym of Object.getOwnPropertySymbols(surface)) Object.defineProperty(handle, sym, {
294
+ value: surface[sym],
295
+ enumerable: false,
296
+ writable: false,
297
+ configurable: false
298
+ });
299
+ Object.freeze(handle);
300
+ return handle;
301
+ }
302
+ /**
303
+ * Per-scope handle accounting. Keyed by `Scope` instance via WeakMap so we
304
+ * don't have to modify the Scope class signature for the minimum-invasive
305
+ * Phase 1 prototype.
306
+ */
307
+ const ownedHandles = /* @__PURE__ */ new WeakMap();
308
+ const handleOrigins = /* @__PURE__ */ new WeakMap();
309
+ /**
310
+ * Adopt a `Handle` into the given scope's ownership registry. The handle:
311
+ *
312
+ * - is rejected if it isn't in the module-private `branded` WeakSet
313
+ * (forged values fail here)
314
+ * - is rejected if already owned by a different scope
315
+ * - is registered with `scope.use(...)` for LIFO disposal
316
+ * - subscribes to early manual disposal so the registry stays accurate
317
+ * if the consumer calls `handle[Symbol.asyncDispose]()` directly
318
+ *
319
+ * Idempotent: adopting the same handle twice into the same scope is a no-op.
320
+ */
321
+ function adoptHandle(scope, handle) {
322
+ if (scope.disposed) throw new ReferenceError("Cannot adopt handle into a disposed scope");
323
+ if (!branded.has(handle)) throw new TypeError("adoptHandle: value is not a silvery handle (forged via 'as' or wrong factory). Use the resource's createX(scope, ...) factory.");
324
+ let owned = ownedHandles.get(scope);
325
+ if (!owned) {
326
+ owned = /* @__PURE__ */ new Set();
327
+ ownedHandles.set(scope, owned);
328
+ }
329
+ const existingOwner = handleOrigins.get(handle);
330
+ if (existingOwner && existingOwner !== scope) throw new TypeError("Handle already owned by another scope; create a new handle for this scope instead");
331
+ if (owned.has(handle)) return;
332
+ owned.add(handle);
333
+ handleOrigins.set(handle, scope);
334
+ const existing = metadata.get(handle);
335
+ if (existing && !existing.createdAt) metadata.set(handle, {
336
+ ...existing,
337
+ createdAt: captureCreationStack()
338
+ });
339
+ let disposedFlag = false;
340
+ const removeFromRegistry = () => {
341
+ if (disposedFlag) return;
342
+ disposedFlag = true;
343
+ owned.delete(handle);
344
+ handleOrigins.delete(handle);
345
+ };
346
+ scope.use({ [Symbol.asyncDispose]: async () => {
347
+ try {
348
+ if (!disposedFlag) await handle[Symbol.asyncDispose]();
349
+ } finally {
350
+ removeFromRegistry();
351
+ }
352
+ } });
353
+ }
354
+ /**
355
+ * Snapshot the current set of handles still adopted into this scope.
356
+ * Empty when the scope was never used or all handles are disposed.
357
+ */
358
+ function getAdoptedHandles(scope) {
359
+ const owned = ownedHandles.get(scope);
360
+ if (!owned || owned.size === 0) return [];
361
+ const result = [];
362
+ for (const h of owned) {
363
+ const meta = metadata.get(h);
364
+ result.push({
365
+ kind: meta?.kind ?? "unknown",
366
+ createdAt: meta?.createdAt
367
+ });
368
+ }
369
+ return result;
370
+ }
371
+ function captureCreationStack() {
372
+ return ((/* @__PURE__ */ new Error("(handle adoption)")).stack ?? "").split("\n").slice(3).join("\n");
373
+ }
374
+ //#endregion
375
+ //#region packages/scope/src/index.ts
376
+ /**
377
+ * @silvery/scope — Structured concurrency scopes for silvery apps.
378
+ *
379
+ * `Scope` is a subclass of TC39's `AsyncDisposableStack` that adds:
380
+ * - an `AbortSignal` that aborts on disposal (and links to a parent's signal)
381
+ * - a `child(name?)` method that creates child scopes with cascade disposal
382
+ * - an overridden `[Symbol.asyncDispose]()` that disposes children before the
383
+ * inherited user disposer stack
384
+ *
385
+ * All disposer-stack semantics (LIFO, async-await, idempotent dispose,
386
+ * `SuppressedError` on multi-throw, post-dispose `ReferenceError`) come
387
+ * from `AsyncDisposableStack` directly.
388
+ *
389
+ * @example
390
+ * ```ts
391
+ * using scope = createScope("app")
392
+ * const proc = scope.use(disposable(
393
+ * child_process.spawn("claude"),
394
+ * p => p.kill("SIGTERM"),
395
+ * ))
396
+ * ```
397
+ *
398
+ * @packageDocumentation
399
+ */
400
+ var Scope = class Scope extends AsyncDisposableStack {
401
+ signal;
402
+ name;
403
+ #children = /* @__PURE__ */ new Set();
404
+ #parent;
405
+ constructor(parent, name) {
406
+ super();
407
+ this.name = name;
408
+ this.#parent = parent;
409
+ _trackCreate(this, "scope", name);
410
+ const controller = new AbortController();
411
+ this.signal = controller.signal;
412
+ this.defer(() => controller.abort());
413
+ if (parent) {
414
+ if (parent.disposed) throw new ReferenceError("Cannot create child of disposed scope");
415
+ if (parent.signal.aborted) controller.abort();
416
+ else {
417
+ const onAbort = () => controller.abort();
418
+ parent.signal.addEventListener("abort", onAbort, { once: true });
419
+ this.defer(() => parent.signal.removeEventListener("abort", onAbort));
420
+ }
421
+ parent.#children.add(this);
422
+ }
423
+ }
424
+ /** Create a child scope. Child's signal aborts when this scope's signal does. */
425
+ child(name) {
426
+ return new Scope(this, name);
427
+ }
428
+ /**
429
+ * Schedule a one-shot callback owned by this scope. Disposing the scope
430
+ * clears the timer if it has not fired yet. The returned function cancels
431
+ * the timer early and is idempotent.
432
+ */
433
+ timeout(callback, ms, opts) {
434
+ let active = true;
435
+ const timer = setTimeout(() => {
436
+ if (!active) return;
437
+ active = false;
438
+ callback();
439
+ }, ms);
440
+ if (opts?.unref === true) timer.unref?.();
441
+ const cancel = () => {
442
+ if (!active) return;
443
+ active = false;
444
+ clearTimeout(timer);
445
+ };
446
+ this.defer(cancel);
447
+ return cancel;
448
+ }
449
+ /**
450
+ * Schedule a repeating callback owned by this scope. Disposing the scope
451
+ * clears the interval. The returned function cancels the interval early
452
+ * and is idempotent.
453
+ */
454
+ interval(callback, ms, opts) {
455
+ let active = true;
456
+ const timer = setInterval(() => {
457
+ if (active) callback();
458
+ }, ms);
459
+ if (opts?.unref === true) timer.unref?.();
460
+ const cancel = () => {
461
+ if (!active) return;
462
+ active = false;
463
+ clearInterval(timer);
464
+ };
465
+ this.defer(cancel);
466
+ return cancel;
467
+ }
468
+ /**
469
+ * Cancellation-aware sleep. Resolves when the timer fires, when the scope
470
+ * is disposed, or immediately if the scope is already aborted.
471
+ */
472
+ sleep(ms, opts) {
473
+ return new Promise((resolve) => {
474
+ if (this.signal.aborted) {
475
+ resolve();
476
+ return;
477
+ }
478
+ let done = false;
479
+ let cancelTimer = null;
480
+ const finish = () => {
481
+ if (done) return;
482
+ done = true;
483
+ cancelTimer?.();
484
+ this.signal.removeEventListener("abort", finish);
485
+ resolve();
486
+ };
487
+ this.signal.addEventListener("abort", finish, { once: true });
488
+ cancelTimer = this.timeout(finish, ms, opts);
489
+ this.defer(finish);
490
+ });
491
+ }
492
+ /**
493
+ * A debounced wrapper around `fn`, owned by this scope. Each call cancels the
494
+ * pending invocation and reschedules `ms` out, so only the last call in a
495
+ * burst fires. Uses a SINGLE timer slot — no per-call defer accumulation
496
+ * (unlike repeatedly calling {@link timeout}), which is what makes it the
497
+ * right primitive for keystroke/hover debounces. Disposing the scope cancels
498
+ * any pending call; the returned function exposes `.cancel()` to drop a
499
+ * pending call early. Resolves the structured-concurrency adoption need for
500
+ * cancel-and-reschedule timers (@km/all/structured-concurrency).
501
+ */
502
+ debounce(fn, ms, opts) {
503
+ let timer;
504
+ const cancel = () => {
505
+ if (timer !== void 0) {
506
+ clearTimeout(timer);
507
+ timer = void 0;
508
+ }
509
+ };
510
+ this.defer(cancel);
511
+ const debounced = (...args) => {
512
+ if (this.signal.aborted) return;
513
+ cancel();
514
+ timer = setTimeout(() => {
515
+ timer = void 0;
516
+ fn(...args);
517
+ }, ms);
518
+ if (opts?.unref === true) timer.unref?.();
519
+ };
520
+ debounced.cancel = cancel;
521
+ return debounced;
522
+ }
523
+ /**
524
+ * Adopt an opaque {@link Handle} (from `defineHandle()`) into this scope's
525
+ * ownership registry. The handle is added to the inherited disposer stack
526
+ * (LIFO disposal) and tracked separately so {@link assertScopeBalance}
527
+ * can detect leaked handles per-scope without depending on global GC.
528
+ *
529
+ * See `./handle.ts` for the brand-+-registry pattern.
530
+ */
531
+ adoptHandle(handle) {
532
+ adoptHandle(this, handle);
533
+ }
534
+ /**
535
+ * Adopt an `AsyncDisposable` for LIFO teardown.
536
+ *
537
+ * Branded handles (from `defineHandle()`) are routed through
538
+ * {@link adoptHandle} so per-scope accounting catches them.
539
+ * Non-branded `AsyncDisposable` values use the inherited stack directly.
540
+ *
541
+ * This closes the pro/Kimi-flagged "scope.use(handle) bypasses ownership"
542
+ * hole — a forged or hand-rolled `AsyncDisposable` claiming to be a
543
+ * branded handle still goes through the runtime authenticity gate in
544
+ * `adoptHandle`.
545
+ */
546
+ use(value) {
547
+ if (value !== null && value !== void 0 && typeof value === "object" && isBrandedHandle(value)) {
548
+ adoptHandle(this, value);
549
+ return value;
550
+ }
551
+ return super.use(value);
552
+ }
553
+ /**
554
+ * Dispose children first, then the inherited user disposer stack.
555
+ * Collects errors across the tree and surfaces them as `SuppressedError`.
556
+ *
557
+ * When `SILVERY_SCOPE_TRACE=1`, prints a per-scope handle-delta to
558
+ * stderr if any adopted handles remained undisposed after the inherited
559
+ * stack ran (km-silvery.lifecycle-leak-detection Phase 2).
560
+ */
561
+ async [Symbol.asyncDispose]() {
562
+ if (this.disposed) return;
563
+ const errors = [];
564
+ const preCount = getAdoptedHandles(this).length;
565
+ const children = [...this.#children].reverse();
566
+ this.#children.clear();
567
+ for (const c of children) try {
568
+ await c[Symbol.asyncDispose]();
569
+ } catch (e) {
570
+ errors.push(e);
571
+ }
572
+ try {
573
+ await super[Symbol.asyncDispose]();
574
+ } catch (e) {
575
+ errors.push(e);
576
+ }
577
+ const postCount = getAdoptedHandles(this).length;
578
+ reportScopeDelta(this.name, preCount, postCount);
579
+ if (this.#parent) this.#parent.#children.delete(this);
580
+ _trackDispose(this);
581
+ if (errors.length === 1) throw errors[0];
582
+ if (errors.length > 1) throw errors.reduce((suppressed, e) => new SuppressedError(e, suppressed, "multiple dispose errors"));
583
+ }
584
+ /**
585
+ * `AsyncDisposableStack.move()` returns a plain stack that loses Scope's
586
+ * `signal`, `name`, and child registry. Throw rather than silently
587
+ * corrupting invariants. Create a new scope and re-register resources
588
+ * explicitly if you need to relocate ownership.
589
+ */
590
+ move() {
591
+ throw new TypeError("Scope.move() is not supported — create a new scope and re-register resources explicitly");
592
+ }
593
+ };
594
+ /** Create a root scope. Use `scope.child(name?)` for descendants. */
595
+ function createScope(name) {
596
+ return new Scope(void 0, name);
597
+ }
598
+ const SINK_KEY = Symbol.for("@silvery/scope/disposeErrorSink");
599
+ const defaultSink = (error, context) => {
600
+ const name = context.scope?.name ?? "?";
601
+ console.error(`[scope dispose error] phase=${context.phase} scope=${name}`, error);
602
+ };
603
+ const sinkHost = globalThis;
604
+ if (!sinkHost[SINK_KEY]) sinkHost[SINK_KEY] = { sink: defaultSink };
605
+ /**
606
+ * Report a disposal failure from a fire-and-forget context (React unmount,
607
+ * signal handler, app-exit hook). Best-effort; never throws.
608
+ */
609
+ function reportDisposeError(error, context) {
610
+ try {
611
+ sinkHost[SINK_KEY].sink(error, context);
612
+ } catch {}
613
+ }
614
+ //#endregion
615
+ //#region packages/ag-term/src/pipeline/collect-text.ts
616
+ /**
617
+ * Collect plain text from a node tree, applying internal_transform.
618
+ *
619
+ * This is the base traversal used by:
620
+ * - measure-phase.ts (fit-content measurement)
621
+ * - render-text.ts (DOM-level truncation budget)
622
+ *
623
+ * Does NOT filter hidden or display:none nodes — those are handled by
624
+ * the layout engine (display:none gets 0x0 size) or by other phases.
625
+ */
626
+ function collectPlainText(node) {
627
+ if (node.textContent !== void 0) return node.textContent;
628
+ let result = "";
629
+ for (let i = 0; i < node.children.length; i++) {
630
+ const child = node.children[i];
631
+ let childText = collectPlainText(child);
632
+ if (childText.length > 0 && child.props.internal_transform) childText = child.props.internal_transform(childText, i);
633
+ result += childText;
634
+ }
635
+ return result;
636
+ }
637
+ /**
638
+ * Collect plain text from a node tree, skipping hidden children.
639
+ *
640
+ * Used by the reconciler's Yoga measure function where hidden nodes
641
+ * (from Suspense) must not contribute to measured size.
642
+ *
643
+ * Identical to collectPlainText except for the hidden check.
644
+ */
645
+ function collectPlainTextSkipHidden(node) {
646
+ if (node.textContent !== void 0) return node.textContent;
647
+ let result = "";
648
+ for (let i = 0; i < node.children.length; i++) {
649
+ const child = node.children[i];
650
+ if (child.hidden) continue;
651
+ let childText = collectPlainTextSkipHidden(child);
652
+ if (childText.length > 0 && child.props.internal_transform) childText = child.props.internal_transform(childText, i);
653
+ result += childText;
654
+ }
655
+ return result;
656
+ }
657
+ //#endregion
658
+ //#region packages/ag-term/src/pipeline/measure-stats.ts
659
+ /**
660
+ * Profiling counters for measure function performance analysis (dev only).
661
+ *
662
+ * Shared between @silvery/ag-react/reconciler/nodes (where measure happens)
663
+ * and @silvery/ag-term/pipeline/layout-phase (where stats are logged).
664
+ *
665
+ * Lives in @silvery/ag-term to keep the @silvery/ag-term barrel React-free.
666
+ */
667
+ const measureStats = {
668
+ calls: 0,
669
+ cacheHits: 0,
670
+ textCollects: 0,
671
+ displayWidthCalls: 0,
672
+ reset() {
673
+ this.calls = 0;
674
+ this.cacheHits = 0;
675
+ this.textCollects = 0;
676
+ this.displayWidthCalls = 0;
677
+ }
678
+ };
679
+ //#endregion
680
+ //#region packages/ag-react/src/reconciler/helpers.ts
681
+ /**
682
+ * Reconciler Helper Functions
683
+ *
684
+ * Utility functions for props comparison and change detection
685
+ * used by the React reconciler during updates.
686
+ */
687
+ /**
688
+ * Set of layout-affecting props.
689
+ */
690
+ const LAYOUT_PROPS = new Set([
691
+ "width",
692
+ "height",
693
+ "minWidth",
694
+ "minHeight",
695
+ "maxWidth",
696
+ "maxHeight",
697
+ "flexDirection",
698
+ "flexWrap",
699
+ "justifyContent",
700
+ "alignItems",
701
+ "alignContent",
702
+ "alignSelf",
703
+ "flexGrow",
704
+ "flexShrink",
705
+ "flexBasis",
706
+ "padding",
707
+ "paddingX",
708
+ "paddingY",
709
+ "paddingTop",
710
+ "paddingBottom",
711
+ "paddingLeft",
712
+ "paddingRight",
713
+ "margin",
714
+ "marginX",
715
+ "marginY",
716
+ "marginTop",
717
+ "marginBottom",
718
+ "marginLeft",
719
+ "marginRight",
720
+ "gap",
721
+ "columnGap",
722
+ "rowGap",
723
+ "borderStyle",
724
+ "borderTop",
725
+ "borderBottom",
726
+ "borderLeft",
727
+ "borderRight",
728
+ "display",
729
+ "position",
730
+ "top",
731
+ "left",
732
+ "bottom",
733
+ "right",
734
+ "aspectRatio",
735
+ "overflow",
736
+ "overflowX",
737
+ "overflowY",
738
+ "cols",
739
+ "rows"
740
+ ]);
741
+ /**
742
+ * Set of content props that affect layout dimensions (trigger contentDirty + Flexily markDirty()).
743
+ * wrap changes text line count; internal_transform changes text width; truncate
744
+ * is a per-line elision hook whose output changes the painted line, so a change
745
+ * to it must force a content re-render (its identity is unstable across renders,
746
+ * which is also why render-text.ts bypasses the format cache when it is present).
747
+ */
748
+ const TEXT_CONTENT_PROPS = new Set([
749
+ "wrap",
750
+ "internal_transform",
751
+ "truncate"
752
+ ]);
753
+ /**
754
+ * Set of style props that affect content (paint) but NOT layout dimensions.
755
+ * borderColor, color, bold, etc. don't change how much space a node takes.
756
+ * borderStyle is also a layout prop (affects border widths), but it's included
757
+ * here so stylePropsDirty is set — otherwise border add/remove doesn't trigger
758
+ * renderBox to draw/clear border characters.
759
+ */
760
+ const STYLE_PROPS = new Set([
761
+ "color",
762
+ "truncateMarkerColor",
763
+ "backgroundColor",
764
+ "bold",
765
+ "italic",
766
+ "underline",
767
+ "underlineStyle",
768
+ "underlineColor",
769
+ "overline",
770
+ "strikethrough",
771
+ "inverse",
772
+ "borderColor",
773
+ "borderBackgroundColor",
774
+ "borderTopBackgroundColor",
775
+ "borderBottomBackgroundColor",
776
+ "borderLeftBackgroundColor",
777
+ "borderRightBackgroundColor",
778
+ "borderStyle",
779
+ "outlineStyle",
780
+ "outlineColor",
781
+ "outlineDimColor",
782
+ "outlineTop",
783
+ "outlineBottom",
784
+ "outlineLeft",
785
+ "outlineRight",
786
+ "data-subtree-fade",
787
+ "userSelect",
788
+ "theme"
789
+ ]);
790
+ /** Shared singleton for the no-changes fast path (identity check). */
791
+ const NO_CHANGES = {
792
+ anyChanged: false,
793
+ layoutChanged: false,
794
+ contentChanged: false
795
+ };
796
+ /**
797
+ * Classify all prop changes in a single pass over the union of old and new keys.
798
+ *
799
+ * Replaces the previous 3-pass approach (propsEqual + layoutPropsChanged +
800
+ * contentPropsChanged) with one iteration. Includes an identity fast path
801
+ * and early exit when both layout and content flags are fully determined.
802
+ */
803
+ function classifyPropChanges(oldProps, newProps) {
804
+ if (oldProps === newProps) return NO_CHANGES;
805
+ const keysA = Object.keys(oldProps);
806
+ const keysB = Object.keys(newProps);
807
+ const sameKeyCount = keysA.length === keysB.length;
808
+ let layoutChanged = false;
809
+ let contentChanged = false;
810
+ let anyChanged = false;
811
+ for (const key of keysA) if (oldProps[key] !== newProps[key]) {
812
+ anyChanged = true;
813
+ if (LAYOUT_PROPS.has(key)) layoutChanged = true;
814
+ if (contentChanged !== "text") {
815
+ if (key === "children") {
816
+ const oldIsPrimitive = typeof oldProps[key] === "string" || typeof oldProps[key] === "number";
817
+ const newIsPrimitive = typeof newProps[key] === "string" || typeof newProps[key] === "number";
818
+ if (oldIsPrimitive || newIsPrimitive) contentChanged = "text";
819
+ } else if (TEXT_CONTENT_PROPS.has(key)) contentChanged = "text";
820
+ else if (contentChanged !== "style" && STYLE_PROPS.has(key)) contentChanged = "style";
821
+ }
822
+ if (layoutChanged && contentChanged === "text") break;
823
+ }
824
+ if (!sameKeyCount) {
825
+ for (const key of keysB) if (!(key in oldProps)) {
826
+ anyChanged = true;
827
+ if (LAYOUT_PROPS.has(key)) layoutChanged = true;
828
+ if (contentChanged !== "text") {
829
+ if (key === "children") {
830
+ if (typeof newProps[key] === "string" || typeof newProps[key] === "number") contentChanged = "text";
831
+ } else if (TEXT_CONTENT_PROPS.has(key)) contentChanged = "text";
832
+ else if (contentChanged !== "style" && STYLE_PROPS.has(key)) contentChanged = "style";
833
+ }
834
+ if (layoutChanged && contentChanged === "text") break;
835
+ }
836
+ }
837
+ if (!anyChanged && !sameKeyCount) anyChanged = true;
838
+ if (!anyChanged) return NO_CHANGES;
839
+ return {
840
+ anyChanged,
841
+ layoutChanged,
842
+ contentChanged
843
+ };
844
+ }
845
+ //#endregion
846
+ //#region packages/ag-react/src/reconciler/nodes.ts
847
+ /**
848
+ * Node Creation and Layout Application
849
+ *
850
+ * Functions for creating SilveryNodes and applying layout properties.
851
+ */
852
+ const measureLog = createLogger("silvery:measure");
853
+ /**
854
+ * Create a new SilveryNode with a fresh layout node.
855
+ */
856
+ function createNode(type, props, measurer) {
857
+ const layoutNode = getLayoutEngine().createNode();
858
+ const epoch = getRenderEpoch();
859
+ const node = {
860
+ type,
861
+ props,
862
+ children: [],
863
+ parent: null,
864
+ layoutNode,
865
+ boxRect: null,
866
+ scrollRect: null,
867
+ screenRect: null,
868
+ prevLayout: null,
869
+ prevScrollRect: null,
870
+ prevScreenRect: null,
871
+ layoutChangedThisFrame: epoch,
872
+ dirtyBits: 31,
873
+ dirtyEpoch: epoch
874
+ };
875
+ if (type === "silvery-box") applyBoxProps(layoutNode, props);
876
+ else if (type === "silvery-text") applyTextFlexItemProps(layoutNode, props);
877
+ else if (type === "silvery-viewport") applyViewportProps(layoutNode, props);
878
+ else if (type === "silvery-island") applyIslandProps(layoutNode, props);
879
+ if (type === "silvery-text") {
880
+ let cachedText = null;
881
+ const measureCache = /* @__PURE__ */ new Map();
882
+ layoutNode.setMeasureFunc((width, widthMode, height, heightMode) => {
883
+ measureStats.calls++;
884
+ measureLog.debug?.(`measure "${collectPlainTextSkipHidden(node).slice(0, 40)}" width=${width} widthMode=${widthMode} height=${height} heightMode=${heightMode}`);
885
+ const cacheKey = `${width}|${widthMode}|${height}|${heightMode}`;
886
+ const cached = measureCache.get(cacheKey);
887
+ if (cached && cachedText !== null && !isDirty(node.dirtyBits, node.dirtyEpoch, 1)) {
888
+ measureStats.cacheHits++;
889
+ return cached;
890
+ }
891
+ let text;
892
+ if (cachedText !== null && !isDirty(node.dirtyBits, node.dirtyEpoch, 1)) text = cachedText;
893
+ else {
894
+ measureStats.textCollects++;
895
+ const newText = collectPlainTextSkipHidden(node);
896
+ if (newText !== cachedText) measureCache.clear();
897
+ text = newText;
898
+ cachedText = text;
899
+ node.dirtyBits &= -2;
900
+ }
901
+ if (!text) return {
902
+ width: 0,
903
+ height: 0
904
+ };
905
+ const cachedAfterCollect = measureCache.get(cacheKey);
906
+ if (cachedAfterCollect) {
907
+ measureStats.cacheHits++;
908
+ return cachedAfterCollect;
909
+ }
910
+ const lines = text.split("\n");
911
+ const isMinContentQuery = widthMode === "min-content";
912
+ const maxWidth = widthMode === "undefined" || Number.isNaN(width) ? Number.POSITIVE_INFINITY : isMinContentQuery ? 0 : width;
913
+ const { wrap } = node.props;
914
+ const isTruncate = wrap === "truncate" || wrap === "truncate-start" || wrap === "truncate-middle" || wrap === "truncate-end" || wrap === "clip" || wrap === false;
915
+ const isHardWrap = wrap === "hard";
916
+ const isWrapTruncate = wrap === "wrap-truncate";
917
+ const internalTransform = node.props.internal_transform;
918
+ let totalHeight = 0;
919
+ let actualWidth = 0;
920
+ const dw = measurer ? measurer.displayWidth.bind(measurer) : displayWidth;
921
+ const wt = measurer ? measurer.wrapText.bind(measurer) : wrapText;
922
+ const lh = getActiveLineHeight();
923
+ let renderedLineIndex = 0;
924
+ const widthFor = (line) => {
925
+ if (!internalTransform) return dw(line);
926
+ return dw(internalTransform(line, renderedLineIndex));
927
+ };
928
+ for (const line of lines) {
929
+ measureStats.displayWidthCalls++;
930
+ const lineWidth = dw(line);
931
+ if (isTruncate) {
932
+ totalHeight += lh;
933
+ if (isMinContentQuery) actualWidth = Math.max(actualWidth, lineWidth > 0 ? 1 : 0);
934
+ else actualWidth = Math.max(actualWidth, widthFor(line));
935
+ renderedLineIndex++;
936
+ } else if (isHardWrap) if (isMinContentQuery) {
937
+ totalHeight += lh;
938
+ actualWidth = Math.max(actualWidth, lineWidth > 0 ? 1 : 0);
939
+ renderedLineIndex++;
940
+ } else if (lineWidth <= maxWidth) {
941
+ totalHeight += lh;
942
+ actualWidth = Math.max(actualWidth, widthFor(line));
943
+ renderedLineIndex++;
944
+ } else if (Number.isFinite(maxWidth) && maxWidth > 0) {
945
+ const wrappedCount = Math.ceil(lineWidth / maxWidth);
946
+ totalHeight += wrappedCount * lh;
947
+ actualWidth = Math.max(actualWidth, maxWidth);
948
+ renderedLineIndex += wrappedCount;
949
+ } else {
950
+ totalHeight += lh;
951
+ actualWidth = Math.max(actualWidth, widthFor(line));
952
+ renderedLineIndex++;
953
+ }
954
+ else if (isMinContentQuery) {
955
+ let longestSegment = 0;
956
+ let segmentWidth = 0;
957
+ for (let pos = 0; pos < line.length; pos++) {
958
+ const ch = line[pos];
959
+ if (ch === " " || ch === " " || ch === "-") {
960
+ if (segmentWidth > longestSegment) longestSegment = segmentWidth;
961
+ segmentWidth = 0;
962
+ continue;
963
+ }
964
+ segmentWidth += dw(ch);
965
+ if (isSoftBreakPoint(ch)) {
966
+ if (segmentWidth > longestSegment) longestSegment = segmentWidth;
967
+ segmentWidth = 0;
968
+ }
969
+ }
970
+ if (segmentWidth > longestSegment) longestSegment = segmentWidth;
971
+ totalHeight += lh;
972
+ actualWidth = Math.max(actualWidth, longestSegment);
973
+ renderedLineIndex++;
974
+ } else if (lineWidth <= maxWidth) {
975
+ totalHeight += lh;
976
+ actualWidth = Math.max(actualWidth, widthFor(line));
977
+ renderedLineIndex++;
978
+ } else {
979
+ const wrapped = wt(line, maxWidth, false, true, isWrapTruncate);
980
+ totalHeight += wrapped.length * lh;
981
+ for (const wl of wrapped) {
982
+ actualWidth = Math.max(actualWidth, widthFor(wl));
983
+ renderedLineIndex++;
984
+ }
985
+ }
986
+ }
987
+ let resultHeight = Math.max(lh, totalHeight);
988
+ if (heightMode === "exactly" && Number.isFinite(height)) resultHeight = height;
989
+ else if (heightMode === "at-most" && Number.isFinite(height)) resultHeight = Math.min(resultHeight, height);
990
+ const result = {
991
+ width: isMinContentQuery ? actualWidth : isTruncate && true ? actualWidth : Math.min(actualWidth, maxWidth),
992
+ height: resultHeight
993
+ };
994
+ measureCache.set(cacheKey, result);
995
+ return result;
996
+ });
997
+ }
998
+ return node;
999
+ }
1000
+ /**
1001
+ * Create the root node for the Silvery tree.
1002
+ * Root is always column (document flow is top-to-bottom), regardless of
1003
+ * flexily's default flexDirection.
1004
+ */
1005
+ function createRootNode() {
1006
+ const node = createNode("silvery-root", {});
1007
+ const c = getConstants();
1008
+ node.layoutNode.setFlexDirection(c.FLEX_DIRECTION_COLUMN);
1009
+ return node;
1010
+ }
1011
+ /**
1012
+ * Create a virtual text node (for nested text elements).
1013
+ * Virtual text nodes don't have layout nodes and don't participate in layout.
1014
+ * They're used when Text is nested inside another Text.
1015
+ */
1016
+ function createVirtualTextNode(props) {
1017
+ return {
1018
+ type: "silvery-text",
1019
+ props,
1020
+ children: [],
1021
+ parent: null,
1022
+ layoutNode: null,
1023
+ boxRect: null,
1024
+ scrollRect: null,
1025
+ screenRect: null,
1026
+ prevLayout: null,
1027
+ prevScrollRect: null,
1028
+ prevScreenRect: null,
1029
+ layoutChangedThisFrame: -1,
1030
+ dirtyBits: 23,
1031
+ dirtyEpoch: getRenderEpoch(),
1032
+ isRawText: false,
1033
+ inlineRects: null
1034
+ };
1035
+ }
1036
+ /**
1037
+ * Apply ViewportProps to a viewport node's layout node.
1038
+ *
1039
+ * A `<Viewport>` is a leaf with fixed `cols`×`rows` — no flex children, no
1040
+ * measure function. We pin width/height from the props so the parent layout
1041
+ * positions the viewport rect deterministically; if the parent rect is
1042
+ * narrower, flexbox + parent overflow="hidden" clips at paint time.
1043
+ *
1044
+ * See bead `@km/silvery/15513-surface-nested-composition-primitive`.
1045
+ */
1046
+ function applyViewportProps(layoutNode, props, oldProps) {
1047
+ if (props.cols !== void 0) layoutNode.setWidth(props.cols);
1048
+ else if (oldProps?.cols !== void 0) layoutNode.setWidthAuto();
1049
+ if (props.rows !== void 0) layoutNode.setHeight(props.rows);
1050
+ else if (oldProps?.rows !== void 0) layoutNode.setHeightAuto();
1051
+ }
1052
+ /**
1053
+ * Apply IslandProps to an island node's layout node.
1054
+ *
1055
+ * `<Island>` is a leaf — guest content lives off the AgNode's `islandState`
1056
+ * slot, blitted by the render phase at boxRect. Layout-dim resolution:
1057
+ * explicit `width` / `height` wins; otherwise fall back to `cols` / `rows`.
1058
+ *
1059
+ * See bead `@km/silvery/15646-islands` for the two-phase resize protocol
1060
+ * (guest acks via `IslandSizeOwner` after host writes new dims).
1061
+ */
1062
+ function applyIslandProps(layoutNode, props, oldProps) {
1063
+ const c = getConstants();
1064
+ const wasRemoved = (prop) => oldProps?.[prop] !== void 0 && props[prop] === void 0;
1065
+ if (props.width !== void 0) {
1066
+ if (typeof props.width === "string" && props.width.endsWith("%")) layoutNode.setWidthPercent(Number.parseFloat(props.width));
1067
+ else if (typeof props.width === "number") layoutNode.setWidth(props.width);
1068
+ } else if (props.cols !== void 0) layoutNode.setWidth(props.cols);
1069
+ else if (wasRemoved("width") || wasRemoved("cols")) layoutNode.setWidthAuto();
1070
+ if (props.height !== void 0) {
1071
+ if (typeof props.height === "string" && props.height.endsWith("%")) layoutNode.setHeightPercent(Number.parseFloat(props.height));
1072
+ else if (typeof props.height === "number") layoutNode.setHeight(props.height);
1073
+ } else if (props.rows !== void 0) layoutNode.setHeight(props.rows);
1074
+ else if (wasRemoved("height") || wasRemoved("rows")) layoutNode.setHeightAuto();
1075
+ if (props.flexGrow !== void 0) layoutNode.setFlexGrow(props.flexGrow);
1076
+ else if (wasRemoved("flexGrow")) layoutNode.setFlexGrow(0);
1077
+ if (props.flexShrink !== void 0) layoutNode.setFlexShrink(props.flexShrink);
1078
+ else if (wasRemoved("flexShrink")) layoutNode.setFlexShrink(1);
1079
+ if (props.flexBasis !== void 0) {
1080
+ if (typeof props.flexBasis === "string" && props.flexBasis.endsWith("%")) layoutNode.setFlexBasisPercent(Number.parseFloat(props.flexBasis));
1081
+ else if (props.flexBasis === "auto") layoutNode.setFlexBasisAuto();
1082
+ else if (typeof props.flexBasis === "number") layoutNode.setFlexBasis(props.flexBasis);
1083
+ } else if (wasRemoved("flexBasis")) layoutNode.setFlexBasisAuto();
1084
+ if (props.alignSelf !== void 0) if (props.alignSelf === "auto") layoutNode.setAlignSelf(c.ALIGN_AUTO);
1085
+ else layoutNode.setAlignSelf(alignToConstant(props.alignSelf));
1086
+ else if (wasRemoved("alignSelf")) layoutNode.setAlignSelf(c.ALIGN_AUTO);
1087
+ if (props.minWidth !== void 0) {
1088
+ if (typeof props.minWidth === "string" && props.minWidth.endsWith("%")) layoutNode.setMinWidthPercent(Number.parseFloat(props.minWidth));
1089
+ else if (typeof props.minWidth === "number") layoutNode.setMinWidth(props.minWidth);
1090
+ } else if (wasRemoved("minWidth")) layoutNode.setMinWidth(0);
1091
+ if (props.minHeight !== void 0) {
1092
+ if (typeof props.minHeight === "string" && props.minHeight.endsWith("%")) layoutNode.setMinHeightPercent(Number.parseFloat(props.minHeight));
1093
+ else if (typeof props.minHeight === "number") layoutNode.setMinHeight(props.minHeight);
1094
+ } else if (wasRemoved("minHeight")) layoutNode.setMinHeight(0);
1095
+ if (props.maxWidth !== void 0) {
1096
+ if (typeof props.maxWidth === "string" && props.maxWidth.endsWith("%")) layoutNode.setMaxWidthPercent(Number.parseFloat(props.maxWidth));
1097
+ else if (typeof props.maxWidth === "number") layoutNode.setMaxWidth(props.maxWidth);
1098
+ } else if (wasRemoved("maxWidth")) layoutNode.setMaxWidth(Number.POSITIVE_INFINITY);
1099
+ if (props.maxHeight !== void 0) {
1100
+ if (typeof props.maxHeight === "string" && props.maxHeight.endsWith("%")) layoutNode.setMaxHeightPercent(Number.parseFloat(props.maxHeight));
1101
+ else if (typeof props.maxHeight === "number") layoutNode.setMaxHeight(props.maxHeight);
1102
+ } else if (wasRemoved("maxHeight")) layoutNode.setMaxHeight(Number.POSITIVE_INFINITY);
1103
+ }
1104
+ /**
1105
+ * Apply TextFlexItemProps to a Text node's layout node.
1106
+ *
1107
+ * Text is a leaf flex item — it accepts the subset of FlexboxProps that
1108
+ * affect how it participates as a flex item (sizing, growth, shrink),
1109
+ * not the props that affect how it lays out children. This is the
1110
+ * canonical CSS escape hatch: use `flexShrink={0}` to keep a Text rigid,
1111
+ * or `minWidth={0}` to let it shrink fully under a tight parent.
1112
+ *
1113
+ * See `TextFlexItemProps` in @silvery/ag/types.
1114
+ */
1115
+ function applyTextFlexItemProps(layoutNode, props, oldProps) {
1116
+ const c = getConstants();
1117
+ const wasRemoved = (prop) => oldProps?.[prop] !== void 0 && props[prop] === void 0;
1118
+ if (props.flexGrow !== void 0) layoutNode.setFlexGrow(props.flexGrow);
1119
+ else if (wasRemoved("flexGrow")) layoutNode.setFlexGrow(0);
1120
+ if (props.flexShrink !== void 0) layoutNode.setFlexShrink(props.flexShrink);
1121
+ else if (wasRemoved("flexShrink")) layoutNode.setFlexShrink(1);
1122
+ if (props.flexBasis !== void 0) {
1123
+ if (typeof props.flexBasis === "string" && props.flexBasis.endsWith("%")) layoutNode.setFlexBasisPercent(Number.parseFloat(props.flexBasis));
1124
+ else if (props.flexBasis === "auto") layoutNode.setFlexBasisAuto();
1125
+ else if (typeof props.flexBasis === "number") layoutNode.setFlexBasis(props.flexBasis);
1126
+ } else if (wasRemoved("flexBasis")) layoutNode.setFlexBasisAuto();
1127
+ if (props.alignSelf !== void 0) if (props.alignSelf === "auto") layoutNode.setAlignSelf(c.ALIGN_AUTO);
1128
+ else layoutNode.setAlignSelf(alignToConstant(props.alignSelf));
1129
+ else if (wasRemoved("alignSelf")) layoutNode.setAlignSelf(c.ALIGN_AUTO);
1130
+ if (props.minWidth !== void 0) {
1131
+ if (typeof props.minWidth === "string" && props.minWidth.endsWith("%")) layoutNode.setMinWidthPercent(Number.parseFloat(props.minWidth));
1132
+ else if (typeof props.minWidth === "number") layoutNode.setMinWidth(props.minWidth);
1133
+ } else if (wasRemoved("minWidth")) layoutNode.setMinWidth(0);
1134
+ if (props.minHeight !== void 0) {
1135
+ if (typeof props.minHeight === "string" && props.minHeight.endsWith("%")) layoutNode.setMinHeightPercent(Number.parseFloat(props.minHeight));
1136
+ else if (typeof props.minHeight === "number") layoutNode.setMinHeight(props.minHeight);
1137
+ } else if (wasRemoved("minHeight")) layoutNode.setMinHeight(0);
1138
+ if (props.maxWidth !== void 0) {
1139
+ if (typeof props.maxWidth === "string" && props.maxWidth.endsWith("%")) layoutNode.setMaxWidthPercent(Number.parseFloat(props.maxWidth));
1140
+ else if (typeof props.maxWidth === "number") layoutNode.setMaxWidth(props.maxWidth);
1141
+ } else if (wasRemoved("maxWidth")) layoutNode.setMaxWidth(Number.POSITIVE_INFINITY);
1142
+ if (props.maxHeight !== void 0) {
1143
+ if (typeof props.maxHeight === "string" && props.maxHeight.endsWith("%")) layoutNode.setMaxHeightPercent(Number.parseFloat(props.maxHeight));
1144
+ else if (typeof props.maxHeight === "number") layoutNode.setMaxHeight(props.maxHeight);
1145
+ } else if (wasRemoved("maxHeight")) layoutNode.setMaxHeight(Number.POSITIVE_INFINITY);
1146
+ }
1147
+ /**
1148
+ * Parse a `fitWidth` entry from user-facing form (number | string) to the
1149
+ * engine's FitWidthLane shape (number | { value, unit: "cqi" | "cqmin" }).
1150
+ *
1151
+ * Accepted strings: `"100cqi"`, `"50cqmin"` — decimal and integer values both
1152
+ * fine. Numbers pass through unchanged. Invalid strings throw with a clear
1153
+ * pointer to the bug — the layout engine consumes only the parsed form, so
1154
+ * parse-fail at the React seam is the right place.
1155
+ */
1156
+ function parseFitWidthEntry(entry) {
1157
+ if (typeof entry === "number") return entry;
1158
+ const cqiMatch = entry.match(/^(\d+(?:\.\d+)?)cqi$/);
1159
+ if (cqiMatch) return {
1160
+ value: Number.parseFloat(cqiMatch[1]),
1161
+ unit: "cqi"
1162
+ };
1163
+ const cqminMatch = entry.match(/^(\d+(?:\.\d+)?)cqmin$/);
1164
+ if (cqminMatch) return {
1165
+ value: Number.parseFloat(cqminMatch[1]),
1166
+ unit: "cqmin"
1167
+ };
1168
+ throw new Error(`<Box fitWidth>: invalid lane entry ${JSON.stringify(entry)}. Expected a number (cells) or a string like "100cqi" / "50cqmin".`);
1169
+ }
1170
+ /**
1171
+ * Apply BoxProps to a layout node.
1172
+ * This maps Ink/Silvery props to the layout engine API.
1173
+ */
1174
+ function applyBoxProps(layoutNode, props, oldProps) {
1175
+ const c = getConstants();
1176
+ const wasRemoved = (prop) => oldProps?.[prop] !== void 0 && props[prop] === void 0;
1177
+ if (props.width !== void 0) {
1178
+ if (typeof props.width === "string" && props.width.endsWith("%")) layoutNode.setWidthPercent(Number.parseFloat(props.width));
1179
+ else if (typeof props.width === "number") layoutNode.setWidth(props.width);
1180
+ else if (props.width === "auto") layoutNode.setWidthAuto();
1181
+ else if (props.width === "fit-content") layoutNode.setWidthFitContent();
1182
+ else if (props.width === "snug-content") layoutNode.setWidthSnugContent();
1183
+ } else if (wasRemoved("width")) layoutNode.setWidthAuto();
1184
+ if (props.height !== void 0) {
1185
+ if (typeof props.height === "string" && props.height.endsWith("%")) layoutNode.setHeightPercent(Number.parseFloat(props.height));
1186
+ else if (typeof props.height === "number") layoutNode.setHeight(props.height);
1187
+ else if (props.height === "auto") layoutNode.setHeightAuto();
1188
+ } else if (wasRemoved("height")) layoutNode.setHeightAuto();
1189
+ if (props.minWidth !== void 0) {
1190
+ if (typeof props.minWidth === "string" && props.minWidth.endsWith("%")) layoutNode.setMinWidthPercent(Number.parseFloat(props.minWidth));
1191
+ else if (typeof props.minWidth === "number") layoutNode.setMinWidth(props.minWidth);
1192
+ } else if (wasRemoved("minWidth")) layoutNode.setMinWidth(0);
1193
+ if (props.minHeight !== void 0) {
1194
+ if (typeof props.minHeight === "string" && props.minHeight.endsWith("%")) layoutNode.setMinHeightPercent(Number.parseFloat(props.minHeight));
1195
+ else if (typeof props.minHeight === "number") layoutNode.setMinHeight(props.minHeight);
1196
+ } else if (wasRemoved("minHeight")) layoutNode.setMinHeight(0);
1197
+ if (props.maxWidth !== void 0) {
1198
+ if (typeof props.maxWidth === "string" && props.maxWidth.endsWith("%")) layoutNode.setMaxWidthPercent(Number.parseFloat(props.maxWidth));
1199
+ else if (typeof props.maxWidth === "number") layoutNode.setMaxWidth(props.maxWidth);
1200
+ } else if (wasRemoved("maxWidth")) layoutNode.setMaxWidth(Number.POSITIVE_INFINITY);
1201
+ if (props.maxHeight !== void 0) {
1202
+ if (typeof props.maxHeight === "string" && props.maxHeight.endsWith("%")) layoutNode.setMaxHeightPercent(Number.parseFloat(props.maxHeight));
1203
+ else if (typeof props.maxHeight === "number") layoutNode.setMaxHeight(props.maxHeight);
1204
+ } else if (wasRemoved("maxHeight")) layoutNode.setMaxHeight(Number.POSITIVE_INFINITY);
1205
+ if (props.flexGrow !== void 0) layoutNode.setFlexGrow(props.flexGrow);
1206
+ else if (wasRemoved("flexGrow")) layoutNode.setFlexGrow(0);
1207
+ if (props.flexShrink !== void 0) layoutNode.setFlexShrink(props.flexShrink);
1208
+ else if (wasRemoved("flexShrink")) layoutNode.setFlexShrink(1);
1209
+ if (props.flexBasis !== void 0) {
1210
+ if (typeof props.flexBasis === "string" && props.flexBasis.endsWith("%")) layoutNode.setFlexBasisPercent(Number.parseFloat(props.flexBasis));
1211
+ else if (props.flexBasis === "auto") layoutNode.setFlexBasisAuto();
1212
+ else if (typeof props.flexBasis === "number") layoutNode.setFlexBasis(props.flexBasis);
1213
+ } else if (wasRemoved("flexBasis")) layoutNode.setFlexBasisAuto();
1214
+ if (props.flexDirection !== void 0) {
1215
+ const directionMap = {
1216
+ row: c.FLEX_DIRECTION_ROW,
1217
+ column: c.FLEX_DIRECTION_COLUMN,
1218
+ "row-reverse": c.FLEX_DIRECTION_ROW_REVERSE,
1219
+ "column-reverse": c.FLEX_DIRECTION_COLUMN_REVERSE
1220
+ };
1221
+ layoutNode.setFlexDirection(directionMap[props.flexDirection] ?? c.FLEX_DIRECTION_ROW);
1222
+ } else if (wasRemoved("flexDirection")) layoutNode.setFlexDirection(c.FLEX_DIRECTION_ROW);
1223
+ if (props.flexWrap !== void 0) {
1224
+ const wrapMap = {
1225
+ nowrap: c.WRAP_NO_WRAP,
1226
+ wrap: c.WRAP_WRAP,
1227
+ "wrap-reverse": c.WRAP_WRAP_REVERSE
1228
+ };
1229
+ layoutNode.setFlexWrap(wrapMap[props.flexWrap] ?? c.WRAP_NO_WRAP);
1230
+ } else if (wasRemoved("flexWrap")) layoutNode.setFlexWrap(c.WRAP_NO_WRAP);
1231
+ if (props.alignItems !== void 0) layoutNode.setAlignItems(alignToConstant(props.alignItems));
1232
+ else if (wasRemoved("alignItems")) layoutNode.setAlignItems(c.ALIGN_STRETCH);
1233
+ if (props.alignSelf !== void 0) if (props.alignSelf === "auto") layoutNode.setAlignSelf(c.ALIGN_AUTO);
1234
+ else layoutNode.setAlignSelf(alignToConstant(props.alignSelf));
1235
+ else if (wasRemoved("alignSelf")) layoutNode.setAlignSelf(c.ALIGN_AUTO);
1236
+ if (props.alignContent !== void 0) layoutNode.setAlignContent(alignToConstant(props.alignContent));
1237
+ else if (wasRemoved("alignContent")) layoutNode.setAlignContent(c.ALIGN_FLEX_START);
1238
+ if (props.justifyContent !== void 0) layoutNode.setJustifyContent(justifyToConstant(props.justifyContent));
1239
+ else if (wasRemoved("justifyContent")) layoutNode.setJustifyContent(c.JUSTIFY_FLEX_START);
1240
+ applySpacing(layoutNode, "padding", props);
1241
+ applySpacing(layoutNode, "margin", props);
1242
+ if (props.gap !== void 0) layoutNode.setGap(c.GUTTER_ALL, props.gap);
1243
+ else if (wasRemoved("gap")) layoutNode.setGap(c.GUTTER_ALL, 0);
1244
+ if (props.columnGap !== void 0) layoutNode.setGap(c.GUTTER_COLUMN, props.columnGap);
1245
+ else if (wasRemoved("columnGap")) layoutNode.setGap(c.GUTTER_COLUMN, 0);
1246
+ if (props.rowGap !== void 0) layoutNode.setGap(c.GUTTER_ROW, props.rowGap);
1247
+ else if (wasRemoved("rowGap")) layoutNode.setGap(c.GUTTER_ROW, 0);
1248
+ if (props.display !== void 0) layoutNode.setDisplay(props.display === "none" ? c.DISPLAY_NONE : c.DISPLAY_FLEX);
1249
+ else if (wasRemoved("display")) layoutNode.setDisplay(c.DISPLAY_FLEX);
1250
+ if (props.position !== void 0) if (props.position === "absolute") layoutNode.setPositionType(c.POSITION_TYPE_ABSOLUTE);
1251
+ else if (props.position === "static") layoutNode.setPositionType(c.POSITION_TYPE_STATIC);
1252
+ else layoutNode.setPositionType(c.POSITION_TYPE_RELATIVE);
1253
+ else if (wasRemoved("position")) layoutNode.setPositionType(c.POSITION_TYPE_RELATIVE);
1254
+ if (props.position !== "static") {
1255
+ applyPositionOffset(layoutNode, c.EDGE_TOP, props.top);
1256
+ applyPositionOffset(layoutNode, c.EDGE_LEFT, props.left);
1257
+ applyPositionOffset(layoutNode, c.EDGE_BOTTOM, props.bottom);
1258
+ applyPositionOffset(layoutNode, c.EDGE_RIGHT, props.right);
1259
+ }
1260
+ if (props.aspectRatio !== void 0) layoutNode.setAspectRatio(props.aspectRatio);
1261
+ else if (wasRemoved("aspectRatio")) layoutNode.setAspectRatio(NaN);
1262
+ const effectiveOverflow = props.overflow ?? (props.overflowX === "hidden" || props.overflowY === "hidden" ? "hidden" : void 0);
1263
+ if (effectiveOverflow !== void 0) if (effectiveOverflow === "hidden") layoutNode.setOverflow(c.OVERFLOW_HIDDEN);
1264
+ else if (effectiveOverflow === "scroll") layoutNode.setOverflow(c.OVERFLOW_HIDDEN);
1265
+ else layoutNode.setOverflow(c.OVERFLOW_VISIBLE);
1266
+ else if (wasRemoved("overflow") || wasRemoved("overflowX") || wasRemoved("overflowY")) layoutNode.setOverflow(c.OVERFLOW_VISIBLE);
1267
+ if (props.containerType !== void 0) {
1268
+ requireCapability("containerQueries", "<Box containerType>");
1269
+ layoutNode.setContainerType(props.containerType === "inline-size" ? 1 : 0);
1270
+ } else if (wasRemoved("containerType")) layoutNode.setContainerType(0);
1271
+ if (props.containSize !== void 0) {
1272
+ requireCapability("containSize", "<Box containSize>");
1273
+ layoutNode.setContainSize(props.containSize);
1274
+ } else if (wasRemoved("containSize")) layoutNode.setContainSize(false);
1275
+ if (props.containerQueries !== void 0) requireCapability("containerQueries", "<Box containerQueries>");
1276
+ if (props.fitWidth !== void 0) {
1277
+ requireCapability("fitWidth", "<Box fitWidth>");
1278
+ layoutNode.setFitWidth(props.fitWidth.map(parseFitWidthEntry));
1279
+ } else if (wasRemoved("fitWidth")) layoutNode.setFitWidth(void 0);
1280
+ if (props.borderStyle) {
1281
+ const borderWidth = getActiveLineHeight() > 1 ? 0 : 1;
1282
+ if (props.borderTop !== false) layoutNode.setBorder(c.EDGE_TOP, borderWidth);
1283
+ else layoutNode.setBorder(c.EDGE_TOP, 0);
1284
+ if (props.borderBottom !== false) layoutNode.setBorder(c.EDGE_BOTTOM, borderWidth);
1285
+ else layoutNode.setBorder(c.EDGE_BOTTOM, 0);
1286
+ if (props.borderLeft !== false) layoutNode.setBorder(c.EDGE_LEFT, borderWidth);
1287
+ else layoutNode.setBorder(c.EDGE_LEFT, 0);
1288
+ if (props.borderRight !== false) layoutNode.setBorder(c.EDGE_RIGHT, borderWidth);
1289
+ else layoutNode.setBorder(c.EDGE_RIGHT, 0);
1290
+ } else {
1291
+ layoutNode.setBorder(c.EDGE_TOP, 0);
1292
+ layoutNode.setBorder(c.EDGE_BOTTOM, 0);
1293
+ layoutNode.setBorder(c.EDGE_LEFT, 0);
1294
+ layoutNode.setBorder(c.EDGE_RIGHT, 0);
1295
+ }
1296
+ }
1297
+ /**
1298
+ * Apply padding or margin to a layout node.
1299
+ */
1300
+ function applySpacing(layoutNode, type, props) {
1301
+ const c = getConstants();
1302
+ const set = type === "padding" ? layoutNode.setPadding.bind(layoutNode) : layoutNode.setMargin.bind(layoutNode);
1303
+ const all = props[type];
1304
+ const x = props[`${type}X`];
1305
+ const yy = props[`${type}Y`];
1306
+ const top = props[`${type}Top`];
1307
+ const bottom = props[`${type}Bottom`];
1308
+ const left = props[`${type}Left`];
1309
+ const right = props[`${type}Right`];
1310
+ set(c.EDGE_TOP, top ?? yy ?? all ?? 0);
1311
+ set(c.EDGE_BOTTOM, bottom ?? yy ?? all ?? 0);
1312
+ set(c.EDGE_LEFT, left ?? x ?? all ?? 0);
1313
+ set(c.EDGE_RIGHT, right ?? x ?? all ?? 0);
1314
+ }
1315
+ /**
1316
+ * Apply a position offset (top/left/bottom/right) to a layout node.
1317
+ * Supports both numeric (absolute) and percentage string values.
1318
+ */
1319
+ function applyPositionOffset(layoutNode, edge, value) {
1320
+ if (value === void 0) {
1321
+ layoutNode.setPosition(edge, NaN);
1322
+ return;
1323
+ }
1324
+ if (typeof value === "string" && value.endsWith("%")) layoutNode.setPositionPercent(edge, Number.parseFloat(value));
1325
+ else if (typeof value === "number") layoutNode.setPosition(edge, value);
1326
+ }
1327
+ /**
1328
+ * Convert align value to layout constant.
1329
+ */
1330
+ function alignToConstant(align) {
1331
+ const c = getConstants();
1332
+ return {
1333
+ "flex-start": c.ALIGN_FLEX_START,
1334
+ "flex-end": c.ALIGN_FLEX_END,
1335
+ center: c.ALIGN_CENTER,
1336
+ stretch: c.ALIGN_STRETCH,
1337
+ baseline: c.ALIGN_BASELINE,
1338
+ "space-between": c.ALIGN_SPACE_BETWEEN,
1339
+ "space-around": c.ALIGN_SPACE_AROUND,
1340
+ "space-evenly": c.ALIGN_SPACE_EVENLY
1341
+ }[align] ?? c.ALIGN_STRETCH;
1342
+ }
1343
+ /**
1344
+ * Convert justify value to layout constant.
1345
+ */
1346
+ function justifyToConstant(justify) {
1347
+ const c = getConstants();
1348
+ return {
1349
+ "flex-start": c.JUSTIFY_FLEX_START,
1350
+ "flex-end": c.JUSTIFY_FLEX_END,
1351
+ center: c.JUSTIFY_CENTER,
1352
+ "space-between": c.JUSTIFY_SPACE_BETWEEN,
1353
+ "space-around": c.JUSTIFY_SPACE_AROUND,
1354
+ "space-evenly": c.JUSTIFY_SPACE_EVENLY
1355
+ }[justify] ?? c.JUSTIFY_FLEX_START;
1356
+ }
1357
+ //#endregion
1358
+ //#region packages/ag-react/src/reconciler/host-config.ts
1359
+ /**
1360
+ * React Reconciler Host Config
1361
+ *
1362
+ * Defines how React creates, updates, and manages SilveryNodes.
1363
+ * This is the bridge between React's reconciliation algorithm
1364
+ * and our custom terminal node tree.
1365
+ */
1366
+ const log = createLogger("silvery:reconciler");
1367
+ const mountLog = createLogger("silvery:mount");
1368
+ const DEBUG_PROP_NAMES = [
1369
+ "id",
1370
+ "testID",
1371
+ "data-component",
1372
+ "display",
1373
+ "position",
1374
+ "flexDirection",
1375
+ "width",
1376
+ "height",
1377
+ "minWidth",
1378
+ "minHeight",
1379
+ "flexGrow",
1380
+ "flexShrink",
1381
+ "overflow",
1382
+ "overflowX",
1383
+ "overflowY",
1384
+ "overflowIndicator",
1385
+ "scrollTo",
1386
+ "scrollOffset",
1387
+ "scrollbar",
1388
+ "scrollbarVisibility",
1389
+ "follow",
1390
+ "onWheel",
1391
+ "onClick",
1392
+ "onMouseDown",
1393
+ "onMouseUp",
1394
+ "onMouseMove"
1395
+ ];
1396
+ function hostTypeLabel(type) {
1397
+ if (type === "silvery-box") return "Box";
1398
+ if (type === "silvery-text") return "Text";
1399
+ if (type === "silvery-viewport") return "Viewport";
1400
+ if (type === "silvery-island") return "Island";
1401
+ return type;
1402
+ }
1403
+ function getDebugComponentName(node) {
1404
+ const props = node.props;
1405
+ const explicitName = props["data-component"];
1406
+ if (typeof explicitName === "string" && explicitName.length > 0) return explicitName;
1407
+ const base = hostTypeLabel(node.type);
1408
+ const testID = props.testID;
1409
+ if (typeof testID === "string" && testID.length > 0) return `${base}#${testID}`;
1410
+ const id = props.id;
1411
+ if (typeof id === "string" && id.length > 0) return `${base}#${id}`;
1412
+ return base;
1413
+ }
1414
+ function summarizeDebugProp(value) {
1415
+ if (typeof value === "function") return true;
1416
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
1417
+ }
1418
+ function summarizeDebugProps(props) {
1419
+ const summary = {};
1420
+ for (const key of DEBUG_PROP_NAMES) {
1421
+ const value = summarizeDebugProp(props[key]);
1422
+ if (value !== void 0) summary[key] = value;
1423
+ }
1424
+ for (const key of Object.keys(props)) {
1425
+ if (!key.startsWith("data-") || key === "data-component") continue;
1426
+ const value = summarizeDebugProp(props[key]);
1427
+ if (value !== void 0) summary[key] = value;
1428
+ }
1429
+ return summary;
1430
+ }
1431
+ function logNodeLifecycle(event, node) {
1432
+ const props = node.props;
1433
+ const component = getDebugComponentName(node);
1434
+ const text = node.textContent;
1435
+ mountLog.debug?.(`${event} ${component}`, {
1436
+ event,
1437
+ component,
1438
+ type: node.type,
1439
+ props: summarizeDebugProps(props),
1440
+ propKeys: Object.keys(props).filter((key) => key !== "children").sort(),
1441
+ ...text ? { text: text.length > 80 ? `${text.slice(0, 77)}...` : text } : {}
1442
+ });
1443
+ }
1444
+ function logUnmountSubtree(node) {
1445
+ logNodeLifecycle("unmount", node);
1446
+ for (const child of node.children) logUnmountSubtree(child);
1447
+ }
1448
+ /**
1449
+ * Normalize Ink intrinsic element types to Silvery equivalents.
1450
+ * Ink uses `ink-box` / `ink-text` as intrinsic element names;
1451
+ * Silvery uses `silvery-box` / `silvery-text`.
1452
+ */
1453
+ function normalizeNodeType(type) {
1454
+ if (type === "ink-box") return "silvery-box";
1455
+ if (type === "ink-text") return "silvery-text";
1456
+ return type;
1457
+ }
1458
+ const NODE_CONTAINERS_KEY = Symbol.for("@silvery/ag-react/reconciler/nodeContainers");
1459
+ const nodeContainers = globalThis[NODE_CONTAINERS_KEY] ?? (globalThis[NODE_CONTAINERS_KEY] = /* @__PURE__ */ new WeakMap());
1460
+ function bindSubtreeToContainer(node, container) {
1461
+ nodeContainers.set(node, container);
1462
+ for (const child of node.children) bindSubtreeToContainer(child, container);
1463
+ }
1464
+ function unbindSubtreeFromContainer(node) {
1465
+ nodeContainers.delete(node);
1466
+ for (const child of node.children) unbindSubtreeFromContainer(child);
1467
+ }
1468
+ function getOwningContainer(node) {
1469
+ let current = node;
1470
+ while (current) {
1471
+ const container = nodeContainers.get(current);
1472
+ if (container) return container;
1473
+ current = current.parent;
1474
+ }
1475
+ }
1476
+ function registerContainer(container) {
1477
+ bindSubtreeToContainer(container.root, container);
1478
+ }
1479
+ function releaseContainerLifecycle(container) {
1480
+ container.nodeLifecycle = null;
1481
+ unbindSubtreeFromContainer(container.root);
1482
+ }
1483
+ function setContainerNodeLifecycle(container, observer) {
1484
+ container.nodeLifecycle = observer;
1485
+ }
1486
+ function notifyNodeRemoved(container, removedNode) {
1487
+ container?.nodeLifecycle?.onNodeRemoved?.(removedNode);
1488
+ }
1489
+ function notifyNodeUpdated(updatedNode) {
1490
+ getOwningContainer(updatedNode)?.nodeLifecycle?.onNodeUpdated?.(updatedNode);
1491
+ }
1492
+ function notifySubtreeAttached(container, attachedRoot) {
1493
+ container?.nodeLifecycle?.onSubtreeAttached?.(attachedRoot);
1494
+ }
1495
+ const NODE_SCOPES_KEY = Symbol.for("@silvery/ag-react/reconciler/nodeScopes");
1496
+ const nodeScopes = globalThis[NODE_SCOPES_KEY] ?? (globalThis[NODE_SCOPES_KEY] = /* @__PURE__ */ new WeakMap());
1497
+ /**
1498
+ * Dispose any scope attached to `node` and to every descendant. Called
1499
+ * from the reconciler's unmount paths. Fire-and-forget per the design
1500
+ * contract: react commit is synchronous, scope dispose is async, so we
1501
+ * kick off the promise and route rejections through `reportDisposeError`.
1502
+ *
1503
+ * Walks the subtree synchronously so all slots are detached *before* any
1504
+ * dispose promise resolves — this prevents a re-entrant render from
1505
+ * observing a partially torn-down tree with live scope slots.
1506
+ */
1507
+ function disposeSubtreeScopes(node) {
1508
+ const scope = nodeScopes.get(node);
1509
+ if (scope) {
1510
+ nodeScopes.delete(node);
1511
+ scope[Symbol.asyncDispose]().catch((error) => reportDisposeError(error, {
1512
+ phase: "react-unmount",
1513
+ scope
1514
+ }));
1515
+ }
1516
+ for (const child of node.children) disposeSubtreeScopes(child);
1517
+ }
1518
+ /**
1519
+ * Mark this node and all ancestors as having dirty content/layout.
1520
+ * Used to enable fast-path subtree skipping in renderPhase.
1521
+ */
1522
+ function markSubtreeDirty(node) {
1523
+ const epoch = getRenderEpoch();
1524
+ while (node && !isDirty(node.dirtyBits, node.dirtyEpoch, 16)) {
1525
+ if (node.dirtyEpoch !== epoch) {
1526
+ node.dirtyBits = 16;
1527
+ node.dirtyEpoch = epoch;
1528
+ } else node.dirtyBits |= 16;
1529
+ node = node.parent;
1530
+ }
1531
+ }
1532
+ /**
1533
+ * When a child change (append/remove/insert/text-update) occurs inside a
1534
+ * virtual text subtree (no layoutNode), the nearest layout ancestor must be
1535
+ * notified so its measure function re-collects descendant text and the layout
1536
+ * engine recalculates dimensions. Without this, the measure cache stays stale
1537
+ * and renderPhase renders at the wrong size / doesn't clear old content.
1538
+ *
1539
+ * No-op when the node already has a layoutNode (normal path handles it).
1540
+ */
1541
+ function markLayoutAncestorDirty(node) {
1542
+ if (node.layoutNode) return;
1543
+ let ancestor = node.parent;
1544
+ while (ancestor && !ancestor.layoutNode) ancestor = ancestor.parent;
1545
+ if (ancestor?.layoutNode) {
1546
+ const epoch = getRenderEpoch();
1547
+ if (ancestor.dirtyEpoch !== epoch) {
1548
+ ancestor.dirtyBits = 3;
1549
+ ancestor.dirtyEpoch = epoch;
1550
+ } else ancestor.dirtyBits |= 3;
1551
+ ancestor.layoutNode.markDirty();
1552
+ trackContentDirty(ancestor);
1553
+ }
1554
+ }
1555
+ const BOX_INSIDE_TEXT_WARNING_ID = "silvery/ag-react:box-in-text";
1556
+ let currentUpdatePriority = NoEventPriority;
1557
+ /**
1558
+ * Run a callback with DiscreteEventPriority so React treats state
1559
+ * updates inside it as user-interaction priority (synchronous commit).
1560
+ * Use this for keyboard input handling to prevent React's concurrent
1561
+ * scheduler from deferring the commit.
1562
+ */
1563
+ function runWithDiscreteEvent(fn) {
1564
+ const prev = currentUpdatePriority;
1565
+ currentUpdatePriority = DiscreteEventPriority;
1566
+ try {
1567
+ fn();
1568
+ } finally {
1569
+ currentUpdatePriority = prev;
1570
+ }
1571
+ }
1572
+ /**
1573
+ * The React Reconciler host config.
1574
+ * This defines how React creates, updates, and manages our custom SilveryNodes.
1575
+ */
1576
+ const hostConfig = {
1577
+ rendererPackageName: "@silvery/ag-react",
1578
+ rendererVersion: "0.0.1",
1579
+ supportsMutation: true,
1580
+ supportsPersistence: false,
1581
+ supportsHydration: false,
1582
+ isPrimaryRenderer: true,
1583
+ scheduleTimeout: setTimeout,
1584
+ cancelTimeout: clearTimeout,
1585
+ noTimeout: -1,
1586
+ supportsMicrotasks: true,
1587
+ scheduleMicrotask: queueMicrotask,
1588
+ getRootHostContext() {
1589
+ return { isInsideText: false };
1590
+ },
1591
+ getChildHostContext(parentHostContext, type) {
1592
+ const normalizedType = normalizeNodeType(type);
1593
+ const isInsideText = parentHostContext.isInsideText || normalizedType === "silvery-text";
1594
+ if (isInsideText === parentHostContext.isInsideText) return parentHostContext;
1595
+ return { isInsideText };
1596
+ },
1597
+ createInstance(type, props, _rootContainer, hostContext) {
1598
+ type = normalizeNodeType(type);
1599
+ if ("style" in props && props.style && typeof props.style === "object") props = {
1600
+ ...props.style,
1601
+ ...props
1602
+ };
1603
+ if (type === "silvery-box" && hostContext.isInsideText) {
1604
+ if (process.env.NODE_ENV !== "production") warnOnce(BOX_INSIDE_TEXT_WARNING_ID, () => log.warn?.("<Box> cannot be nested inside <Text>. This produces undefined layout behavior."));
1605
+ }
1606
+ if (type === "silvery-text" && hostContext.isInsideText) {
1607
+ const node = createVirtualTextNode(props);
1608
+ logNodeLifecycle("mount", node);
1609
+ return node;
1610
+ }
1611
+ const node = createNode(type, props);
1612
+ logNodeLifecycle("mount", node);
1613
+ return node;
1614
+ },
1615
+ createTextInstance(text, _rootContainer, hostContext) {
1616
+ const epoch = getRenderEpoch();
1617
+ const node = {
1618
+ type: "silvery-text",
1619
+ props: { children: text },
1620
+ children: [],
1621
+ parent: null,
1622
+ layoutNode: null,
1623
+ boxRect: null,
1624
+ scrollRect: null,
1625
+ screenRect: null,
1626
+ prevLayout: null,
1627
+ prevScrollRect: null,
1628
+ prevScreenRect: null,
1629
+ layoutChangedThisFrame: -1,
1630
+ dirtyBits: 23,
1631
+ dirtyEpoch: epoch,
1632
+ textContent: text,
1633
+ isRawText: true
1634
+ };
1635
+ logNodeLifecycle("mount", node);
1636
+ return node;
1637
+ },
1638
+ appendChild(parentInstance, child) {
1639
+ const existingIndex = parentInstance.children.indexOf(child);
1640
+ if (existingIndex !== -1) {
1641
+ parentInstance.children.splice(existingIndex, 1);
1642
+ if (parentInstance.layoutNode && child.layoutNode) parentInstance.layoutNode.removeChild(child.layoutNode);
1643
+ }
1644
+ child.parent = parentInstance;
1645
+ parentInstance.children.push(child);
1646
+ const container = getOwningContainer(parentInstance);
1647
+ if (container) bindSubtreeToContainer(child, container);
1648
+ if (parentInstance.layoutNode && child.layoutNode) {
1649
+ const layoutIndex = parentInstance.children.filter((c) => c.layoutNode !== null).length - 1;
1650
+ parentInstance.layoutNode.insertChild(child.layoutNode, layoutIndex);
1651
+ }
1652
+ {
1653
+ const epoch = getRenderEpoch();
1654
+ const bits = 9;
1655
+ parentInstance.dirtyBits = parentInstance.dirtyEpoch !== epoch ? bits : parentInstance.dirtyBits | bits;
1656
+ parentInstance.dirtyEpoch = epoch;
1657
+ }
1658
+ parentInstance.layoutNode?.markDirty();
1659
+ trackContentDirty(parentInstance);
1660
+ markLayoutAncestorDirty(parentInstance);
1661
+ markSubtreeDirty(parentInstance);
1662
+ notifySubtreeAttached(container, child);
1663
+ },
1664
+ appendInitialChild(parentInstance, child) {
1665
+ child.parent = parentInstance;
1666
+ parentInstance.children.push(child);
1667
+ const container = getOwningContainer(parentInstance);
1668
+ if (container) bindSubtreeToContainer(child, container);
1669
+ if (parentInstance.layoutNode && child.layoutNode) {
1670
+ const layoutIndex = parentInstance.children.filter((c) => c.layoutNode !== null).length - 1;
1671
+ parentInstance.layoutNode.insertChild(child.layoutNode, layoutIndex);
1672
+ }
1673
+ },
1674
+ appendChildToContainer(container, child) {
1675
+ const existingIndex = container.root.children.indexOf(child);
1676
+ if (existingIndex !== -1) {
1677
+ container.root.children.splice(existingIndex, 1);
1678
+ if (container.root.layoutNode && child.layoutNode) container.root.layoutNode.removeChild(child.layoutNode);
1679
+ }
1680
+ child.parent = container.root;
1681
+ container.root.children.push(child);
1682
+ bindSubtreeToContainer(child, container);
1683
+ if (container.root.layoutNode && child.layoutNode) {
1684
+ const layoutIndex = container.root.children.filter((c) => c.layoutNode !== null).length - 1;
1685
+ container.root.layoutNode.insertChild(child.layoutNode, layoutIndex);
1686
+ }
1687
+ {
1688
+ const epoch = getRenderEpoch();
1689
+ const bits = 9;
1690
+ container.root.dirtyBits = container.root.dirtyEpoch !== epoch ? bits : container.root.dirtyBits | bits;
1691
+ container.root.dirtyEpoch = epoch;
1692
+ }
1693
+ container.root.layoutNode?.markDirty();
1694
+ trackContentDirty(container.root);
1695
+ markSubtreeDirty(container.root);
1696
+ notifySubtreeAttached(container, child);
1697
+ },
1698
+ removeChild(parentInstance, child) {
1699
+ const index = parentInstance.children.indexOf(child);
1700
+ if (index !== -1) {
1701
+ notifyNodeRemoved(getOwningContainer(parentInstance) ?? getOwningContainer(child), child);
1702
+ logUnmountSubtree(child);
1703
+ disposeSubtreeScopes(child);
1704
+ parentInstance.children.splice(index, 1);
1705
+ if (parentInstance.layoutNode && child.layoutNode) {
1706
+ parentInstance.layoutNode.removeChild(child.layoutNode);
1707
+ child.layoutNode.free();
1708
+ }
1709
+ unbindSubtreeFromContainer(child);
1710
+ child.parent = null;
1711
+ {
1712
+ const epoch = getRenderEpoch();
1713
+ const bits = 9;
1714
+ parentInstance.dirtyBits = parentInstance.dirtyEpoch !== epoch ? bits : parentInstance.dirtyBits | bits;
1715
+ parentInstance.dirtyEpoch = epoch;
1716
+ }
1717
+ parentInstance.layoutNode?.markDirty();
1718
+ trackContentDirty(parentInstance);
1719
+ markLayoutAncestorDirty(parentInstance);
1720
+ markSubtreeDirty(parentInstance);
1721
+ }
1722
+ },
1723
+ removeChildFromContainer(container, child) {
1724
+ const index = container.root.children.indexOf(child);
1725
+ if (index !== -1) {
1726
+ notifyNodeRemoved(container, child);
1727
+ logUnmountSubtree(child);
1728
+ disposeSubtreeScopes(child);
1729
+ container.root.children.splice(index, 1);
1730
+ if (container.root.layoutNode && child.layoutNode) {
1731
+ container.root.layoutNode.removeChild(child.layoutNode);
1732
+ child.layoutNode.free();
1733
+ }
1734
+ unbindSubtreeFromContainer(child);
1735
+ child.parent = null;
1736
+ {
1737
+ const epoch = getRenderEpoch();
1738
+ const bits = 9;
1739
+ container.root.dirtyBits = container.root.dirtyEpoch !== epoch ? bits : container.root.dirtyBits | bits;
1740
+ container.root.dirtyEpoch = epoch;
1741
+ }
1742
+ container.root.layoutNode?.markDirty();
1743
+ trackContentDirty(container.root);
1744
+ markSubtreeDirty(container.root);
1745
+ }
1746
+ },
1747
+ insertBefore(parentInstance, child, beforeChild) {
1748
+ const existingIndex = parentInstance.children.indexOf(child);
1749
+ if (existingIndex !== -1) {
1750
+ parentInstance.children.splice(existingIndex, 1);
1751
+ if (parentInstance.layoutNode && child.layoutNode) parentInstance.layoutNode.removeChild(child.layoutNode);
1752
+ }
1753
+ const beforeIndex = parentInstance.children.indexOf(beforeChild);
1754
+ if (beforeIndex !== -1) {
1755
+ child.parent = parentInstance;
1756
+ parentInstance.children.splice(beforeIndex, 0, child);
1757
+ const container = getOwningContainer(parentInstance);
1758
+ if (container) bindSubtreeToContainer(child, container);
1759
+ if (parentInstance.layoutNode && child.layoutNode) {
1760
+ const layoutIndex = parentInstance.children.slice(0, beforeIndex).filter((c) => c.layoutNode !== null).length;
1761
+ parentInstance.layoutNode.insertChild(child.layoutNode, layoutIndex);
1762
+ }
1763
+ {
1764
+ const epoch = getRenderEpoch();
1765
+ const bits = 9;
1766
+ parentInstance.dirtyBits = parentInstance.dirtyEpoch !== epoch ? bits : parentInstance.dirtyBits | bits;
1767
+ parentInstance.dirtyEpoch = epoch;
1768
+ }
1769
+ parentInstance.layoutNode?.markDirty();
1770
+ trackContentDirty(parentInstance);
1771
+ markLayoutAncestorDirty(parentInstance);
1772
+ markSubtreeDirty(parentInstance);
1773
+ notifySubtreeAttached(container, child);
1774
+ }
1775
+ },
1776
+ insertInContainerBefore(container, child, beforeChild) {
1777
+ const existingIndex = container.root.children.indexOf(child);
1778
+ if (existingIndex !== -1) {
1779
+ container.root.children.splice(existingIndex, 1);
1780
+ if (container.root.layoutNode && child.layoutNode) container.root.layoutNode.removeChild(child.layoutNode);
1781
+ }
1782
+ const beforeIndex = container.root.children.indexOf(beforeChild);
1783
+ if (beforeIndex !== -1) {
1784
+ child.parent = container.root;
1785
+ container.root.children.splice(beforeIndex, 0, child);
1786
+ bindSubtreeToContainer(child, container);
1787
+ if (container.root.layoutNode && child.layoutNode) {
1788
+ const layoutIndex = container.root.children.slice(0, beforeIndex).filter((c) => c.layoutNode !== null).length;
1789
+ container.root.layoutNode.insertChild(child.layoutNode, layoutIndex);
1790
+ }
1791
+ {
1792
+ const epoch = getRenderEpoch();
1793
+ const bits = 9;
1794
+ container.root.dirtyBits = container.root.dirtyEpoch !== epoch ? bits : container.root.dirtyBits | bits;
1795
+ container.root.dirtyEpoch = epoch;
1796
+ }
1797
+ container.root.layoutNode?.markDirty();
1798
+ trackContentDirty(container.root);
1799
+ markSubtreeDirty(container.root);
1800
+ notifySubtreeAttached(container, child);
1801
+ }
1802
+ },
1803
+ prepareUpdate(_instance, _type, oldProps, newProps) {
1804
+ return classifyPropChanges(oldProps, newProps).anyChanged;
1805
+ },
1806
+ commitUpdate(instance, _type, oldProps, newProps, _finishedWork) {
1807
+ if ("style" in oldProps && oldProps.style && typeof oldProps.style === "object") oldProps = {
1808
+ ...oldProps.style,
1809
+ ...oldProps
1810
+ };
1811
+ if ("style" in newProps && newProps.style && typeof newProps.style === "object") newProps = {
1812
+ ...newProps.style,
1813
+ ...newProps
1814
+ };
1815
+ const { anyChanged, layoutChanged, contentChanged } = classifyPropChanges(oldProps, newProps);
1816
+ if (!anyChanged) {
1817
+ instance.props = newProps;
1818
+ return;
1819
+ }
1820
+ if (layoutChanged) {
1821
+ if (instance.layoutNode) {
1822
+ if (instance.type === "silvery-text") applyTextFlexItemProps(instance.layoutNode, newProps, oldProps);
1823
+ else if (instance.type === "silvery-viewport") applyViewportProps(instance.layoutNode, newProps, oldProps);
1824
+ else if (instance.type === "silvery-island") applyIslandProps(instance.layoutNode, newProps, oldProps);
1825
+ else applyBoxProps(instance.layoutNode, newProps, oldProps);
1826
+ instance.layoutNode.markDirty();
1827
+ }
1828
+ }
1829
+ if (contentChanged) {
1830
+ const epoch = getRenderEpoch();
1831
+ let bits = 2;
1832
+ if (contentChanged === "text") {
1833
+ bits |= 1;
1834
+ if (instance.layoutNode) instance.layoutNode.markDirty();
1835
+ }
1836
+ if (oldProps.backgroundColor !== newProps.backgroundColor) bits |= 4;
1837
+ if (oldProps.borderStyle && !newProps.borderStyle) bits |= 4;
1838
+ if (oldProps.theme !== newProps.theme) bits |= 5;
1839
+ instance.dirtyBits = instance.dirtyEpoch !== epoch ? bits : instance.dirtyBits | bits;
1840
+ instance.dirtyEpoch = epoch;
1841
+ }
1842
+ if (contentChanged) trackContentDirty(instance);
1843
+ if (contentChanged === "style" && !layoutChanged && !isDirty(instance.dirtyBits, instance.dirtyEpoch, 4) && !isDirty(instance.dirtyBits, instance.dirtyEpoch, 1) && !isDirty(instance.dirtyBits, instance.dirtyEpoch, 8)) trackStyleOnlyDirty(instance);
1844
+ instance.props = newProps;
1845
+ logNodeLifecycle("update", instance);
1846
+ notifyNodeUpdated(instance);
1847
+ const scrollToChanged = oldProps.scrollTo !== newProps.scrollTo;
1848
+ const scrollOffsetChanged = oldProps.scrollOffset !== newProps.scrollOffset;
1849
+ if (scrollToChanged || scrollOffsetChanged) trackScrollDirty(instance);
1850
+ if (layoutChanged || contentChanged || scrollToChanged || scrollOffsetChanged) {
1851
+ markLayoutAncestorDirty(instance);
1852
+ markSubtreeDirty(instance);
1853
+ }
1854
+ },
1855
+ commitTextUpdate(textInstance, _oldText, newText) {
1856
+ textInstance.textContent = newText;
1857
+ syncTextContentSignal(textInstance);
1858
+ textInstance.props = { children: newText };
1859
+ const epoch = getRenderEpoch();
1860
+ const bits = 3;
1861
+ textInstance.dirtyBits = textInstance.dirtyEpoch !== epoch ? bits : textInstance.dirtyBits | bits;
1862
+ textInstance.dirtyEpoch = epoch;
1863
+ trackContentDirty(textInstance);
1864
+ markLayoutAncestorDirty(textInstance);
1865
+ markSubtreeDirty(textInstance);
1866
+ },
1867
+ finalizeInitialChildren() {
1868
+ return false;
1869
+ },
1870
+ prepareForCommit() {
1871
+ return null;
1872
+ },
1873
+ resetAfterCommit(container) {
1874
+ const budgetEnv = typeof process !== "undefined" ? process.env.SILVERY_RENDER_BUDGET_MS : void 0;
1875
+ const budget = budgetEnv !== void 0 ? Number(budgetEnv) : 0;
1876
+ if (budget > 0 && Number.isFinite(budget)) {
1877
+ const start = performance.now();
1878
+ container.onRender();
1879
+ const elapsed = performance.now() - start;
1880
+ if (elapsed > budget) console.warn(`[silvery] render commit exceeded budget: ${elapsed.toFixed(0)}ms > ${budget}ms — see @km/board/projection-stability-improvements for cascade-prevention guidance`);
1881
+ } else container.onRender();
1882
+ },
1883
+ getPublicInstance(instance) {
1884
+ return instance;
1885
+ },
1886
+ shouldSetTextContent() {
1887
+ return false;
1888
+ },
1889
+ clearContainer(container) {
1890
+ for (const child of container.root.children) {
1891
+ notifyNodeRemoved(container, child);
1892
+ logUnmountSubtree(child);
1893
+ }
1894
+ disposeSubtreeScopes(container.root);
1895
+ for (const child of container.root.children) {
1896
+ if (container.root.layoutNode && child.layoutNode) {
1897
+ container.root.layoutNode.removeChild(child.layoutNode);
1898
+ child.layoutNode.free();
1899
+ }
1900
+ unbindSubtreeFromContainer(child);
1901
+ }
1902
+ container.root.children = [];
1903
+ {
1904
+ const epoch = getRenderEpoch();
1905
+ const bits = 9;
1906
+ container.root.dirtyBits = container.root.dirtyEpoch !== epoch ? bits : container.root.dirtyBits | bits;
1907
+ container.root.dirtyEpoch = epoch;
1908
+ }
1909
+ container.root.layoutNode?.markDirty();
1910
+ trackContentDirty(container.root);
1911
+ markSubtreeDirty(container.root);
1912
+ },
1913
+ preparePortalMount() {},
1914
+ getCurrentEventPriority() {
1915
+ if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
1916
+ return DefaultEventPriority;
1917
+ },
1918
+ getInstanceFromNode() {
1919
+ return null;
1920
+ },
1921
+ beforeActiveInstanceBlur() {},
1922
+ afterActiveInstanceBlur() {},
1923
+ prepareScopeUpdate() {},
1924
+ getInstanceFromScope() {
1925
+ return null;
1926
+ },
1927
+ detachDeletedInstance(node) {
1928
+ disposeSubtreeScopes(node);
1929
+ unbindSubtreeFromContainer(node);
1930
+ },
1931
+ setCurrentUpdatePriority(newPriority) {
1932
+ currentUpdatePriority = newPriority;
1933
+ },
1934
+ getCurrentUpdatePriority() {
1935
+ return currentUpdatePriority;
1936
+ },
1937
+ resolveUpdatePriority() {
1938
+ if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
1939
+ return DefaultEventPriority;
1940
+ },
1941
+ maySuspendCommit() {
1942
+ return false;
1943
+ },
1944
+ NotPendingTransition: null,
1945
+ HostTransitionContext: createContext(null),
1946
+ resetFormInstance() {},
1947
+ requestPostPaintCallback() {},
1948
+ shouldAttemptEagerTransition() {
1949
+ return false;
1950
+ },
1951
+ trackSchedulerEvent() {},
1952
+ resolveEventType() {
1953
+ return null;
1954
+ },
1955
+ resolveEventTimeStamp() {
1956
+ return -1.1;
1957
+ },
1958
+ preloadInstance() {
1959
+ return true;
1960
+ },
1961
+ startSuspendingCommit() {},
1962
+ suspendInstance() {},
1963
+ waitForCommitToBeReady() {
1964
+ return null;
1965
+ },
1966
+ /**
1967
+ * Hide an instance during Suspense.
1968
+ * Called when React needs to hide content while showing a fallback.
1969
+ *
1970
+ * Must set stylePropsDirty (render phase fast-path skip includes stylePropsDirty check),
1971
+ * layoutNode.markDirty() (hiding changes measured content — the layout engine
1972
+ * must recalculate dimensions), and markLayoutAncestorDirty (virtual text nodes
1973
+ * without layoutNode need the nearest layout ancestor dirty).
1974
+ */
1975
+ hideInstance(instance) {
1976
+ instance.hidden = true;
1977
+ const epoch = getRenderEpoch();
1978
+ const bits = 3;
1979
+ instance.dirtyBits = instance.dirtyEpoch !== epoch ? bits : instance.dirtyBits | bits;
1980
+ instance.dirtyEpoch = epoch;
1981
+ if (instance.layoutNode) instance.layoutNode.markDirty();
1982
+ trackContentDirty(instance);
1983
+ if (instance.parent) {
1984
+ if (instance.parent.dirtyEpoch !== epoch) {
1985
+ instance.parent.dirtyBits = 1;
1986
+ instance.parent.dirtyEpoch = epoch;
1987
+ } else instance.parent.dirtyBits |= 1;
1988
+ trackContentDirty(instance.parent);
1989
+ }
1990
+ markLayoutAncestorDirty(instance);
1991
+ markSubtreeDirty(instance);
1992
+ },
1993
+ /**
1994
+ * Unhide an instance after Suspense resolves.
1995
+ * Called when the suspended content is ready to show.
1996
+ *
1997
+ * Same invalidation as hideInstance — the node's visibility change affects
1998
+ * layout (measured content changes) and paint (content must be re-rendered).
1999
+ */
2000
+ unhideInstance(instance, _props) {
2001
+ instance.hidden = false;
2002
+ const epoch = getRenderEpoch();
2003
+ const bits = 3;
2004
+ instance.dirtyBits = instance.dirtyEpoch !== epoch ? bits : instance.dirtyBits | bits;
2005
+ instance.dirtyEpoch = epoch;
2006
+ if (instance.layoutNode) instance.layoutNode.markDirty();
2007
+ trackContentDirty(instance);
2008
+ if (instance.parent) {
2009
+ if (instance.parent.dirtyEpoch !== epoch) {
2010
+ instance.parent.dirtyBits = 1;
2011
+ instance.parent.dirtyEpoch = epoch;
2012
+ } else instance.parent.dirtyBits |= 1;
2013
+ trackContentDirty(instance.parent);
2014
+ }
2015
+ markLayoutAncestorDirty(instance);
2016
+ markSubtreeDirty(instance);
2017
+ },
2018
+ /**
2019
+ * Hide a text instance during Suspense.
2020
+ *
2021
+ * Text instances don't have layout nodes. markLayoutAncestorDirty walks up
2022
+ * to the nearest layout ancestor and marks it dirty so the measure function
2023
+ * re-collects descendant text (collectNodeTextContent skips hidden children).
2024
+ */
2025
+ hideTextInstance(textInstance) {
2026
+ textInstance.hidden = true;
2027
+ const epoch = getRenderEpoch();
2028
+ const bits = 3;
2029
+ textInstance.dirtyBits = textInstance.dirtyEpoch !== epoch ? bits : textInstance.dirtyBits | bits;
2030
+ textInstance.dirtyEpoch = epoch;
2031
+ trackContentDirty(textInstance);
2032
+ if (textInstance.parent) {
2033
+ if (textInstance.parent.dirtyEpoch !== epoch) {
2034
+ textInstance.parent.dirtyBits = 1;
2035
+ textInstance.parent.dirtyEpoch = epoch;
2036
+ } else textInstance.parent.dirtyBits |= 1;
2037
+ trackContentDirty(textInstance.parent);
2038
+ }
2039
+ markLayoutAncestorDirty(textInstance);
2040
+ markSubtreeDirty(textInstance);
2041
+ },
2042
+ /**
2043
+ * Unhide a text instance after Suspense resolves.
2044
+ *
2045
+ * Same invalidation as hideTextInstance — the text content changes when
2046
+ * hidden children become visible again.
2047
+ */
2048
+ unhideTextInstance(textInstance, _text) {
2049
+ textInstance.hidden = false;
2050
+ const epoch = getRenderEpoch();
2051
+ const bits = 3;
2052
+ textInstance.dirtyBits = textInstance.dirtyEpoch !== epoch ? bits : textInstance.dirtyBits | bits;
2053
+ textInstance.dirtyEpoch = epoch;
2054
+ trackContentDirty(textInstance);
2055
+ if (textInstance.parent) {
2056
+ if (textInstance.parent.dirtyEpoch !== epoch) {
2057
+ textInstance.parent.dirtyBits = 1;
2058
+ textInstance.parent.dirtyEpoch = epoch;
2059
+ } else textInstance.parent.dirtyBits |= 1;
2060
+ trackContentDirty(textInstance.parent);
2061
+ }
2062
+ markLayoutAncestorDirty(textInstance);
2063
+ markSubtreeDirty(textInstance);
2064
+ }
2065
+ };
2066
+ //#endregion
2067
+ //#region packages/ag-react/src/reconciler/index.ts
2068
+ /**
2069
+ * Silvery React Reconciler
2070
+ *
2071
+ * Custom React reconciler that builds a tree of SilveryNodes, each with a Yoga layout node.
2072
+ * This is the core of Silvery's architecture - separating structure (React reconciliation)
2073
+ * from content (terminal rendering).
2074
+ *
2075
+ * The reconciler creates SilveryNodes during React's reconciliation phase,
2076
+ * but actual terminal content is rendered later after Yoga computes layout.
2077
+ */
2078
+ /**
2079
+ * Create the React reconciler instance.
2080
+ */
2081
+ const reconciler = Reconciler(hostConfig);
2082
+ /**
2083
+ * Create a container for rendering.
2084
+ */
2085
+ function createContainer(onRender) {
2086
+ const container = {
2087
+ root: createRootNode(),
2088
+ onRender,
2089
+ nodeLifecycle: null
2090
+ };
2091
+ registerContainer(container);
2092
+ return container;
2093
+ }
2094
+ /**
2095
+ * Create a React fiber root for a container (wraps the 10-argument reconciler call).
2096
+ *
2097
+ * Pass `options.onUncaughtError` to route React-thrown render errors back to
2098
+ * the host's panic path. Without it, render errors are silently swallowed
2099
+ * (the default `() => {}` callback) and the only surface for them is whatever
2100
+ * the host's `console.error` capture decides to do — typically an altscreen
2101
+ * overlay the user can't copy-paste or screenshot reliably.
2102
+ */
2103
+ function createFiberRoot(container, options = {}) {
2104
+ return reconciler.createContainer(container, 1, null, false, null, "", options.onUncaughtError ?? (() => {}), options.onCaughtError ?? (() => {}), options.onRecoverableError ?? (() => {}), null);
2105
+ }
2106
+ /**
2107
+ * Get the root SilveryNode from a container.
2108
+ */
2109
+ function getContainerRoot(container) {
2110
+ return container.root;
2111
+ }
2112
+ /**
2113
+ * Synchronously unmount a fiber root and scrub the container so it can't
2114
+ * keep its closure-captured RenderInstance alive afterward.
2115
+ *
2116
+ * Why both steps are needed:
2117
+ *
2118
+ * 1. `createFiberRoot` uses `ConcurrentRoot` (mode 1). React's async
2119
+ * `updateContainer(null, fiberRoot, ...)` does NOT run layout-effect
2120
+ * cleanups before returning — useLayoutEffect / useBoxRect /
2121
+ * useBoxMetrics / signal-effect disposers are scheduled but may not
2122
+ * fire promptly. That keeps signal subscriptions alive past unmount,
2123
+ * which keeps the React tree reachable, which keeps the host
2124
+ * `RenderInstance` reachable. `updateContainerSync` + `flushSyncWork`
2125
+ * forces all cleanups to run inline.
2126
+ *
2127
+ * 2. Even after the React tree is detached, the `FiberRoot` keeps a
2128
+ * pointer to its `containerInfo` (our `Container`) for some time, and
2129
+ * `Container.onRender` typically closes over the entire enclosing
2130
+ * `RenderInstance`. Without nulling `onRender` and scrubbing the root
2131
+ * AgNode, the instance graph is still reachable through the FiberRoot's
2132
+ * container pointer.
2133
+ *
2134
+ * Call this in every unmount path that uses ConcurrentRoot. The previous
2135
+ * (async) pattern leaked across mount/unmount cycles in tests and likely
2136
+ * in production long-lived host applications too.
2137
+ *
2138
+ * Safe to call multiple times — `releaseContainer` is idempotent (the
2139
+ * scrub fields are nulled and `layoutNode.free()` is best-effort).
2140
+ *
2141
+ * @param fiberRoot — opaque React FiberRoot returned by `createFiberRoot`
2142
+ * @param container — the `Container` paired with that fiberRoot
2143
+ */
2144
+ function unmountFiberRoot(fiberRoot, container) {
2145
+ reconciler.updateContainerSync(null, fiberRoot, null, null);
2146
+ reconciler.flushSyncWork();
2147
+ releaseContainer(container);
2148
+ }
2149
+ /**
2150
+ * Scrub a Container so it can't retain its enclosing render state after
2151
+ * the React tree has been unmounted. See {@link unmountFiberRoot} for the
2152
+ * full rationale; call this directly only if you've already run a sync
2153
+ * unmount through the reconciler and just need the post-commit scrub.
2154
+ */
2155
+ function releaseContainer(container) {
2156
+ disposeSubtreeScopes(container.root);
2157
+ releaseContainerLifecycle(container);
2158
+ container.onRender = () => {};
2159
+ const root = container.root;
2160
+ root.children = [];
2161
+ root.parent = null;
2162
+ root.boxRect = null;
2163
+ root.scrollRect = null;
2164
+ root.screenRect = null;
2165
+ root.prevLayout = null;
2166
+ root.prevScrollRect = null;
2167
+ root.prevScreenRect = null;
2168
+ if (root.layoutNode) {
2169
+ try {
2170
+ root.layoutNode.free();
2171
+ } catch {}
2172
+ root.layoutNode = null;
2173
+ }
2174
+ }
2175
+ //#endregion
2176
+ export { trackContentDirty as S, isAnyDirty as _, unmountFiberRoot as a, clearDirtyTracking as b, setContainerNodeLifecycle as c, createScope as d, reportDisposeError as f, getRenderEpoch as g, advanceRenderEpoch as h, reconciler as i, measureStats as l, finaliseHandle as m, createFiberRoot as n, hostConfig as o, defineHandle as p, getContainerRoot as r, runWithDiscreteEvent as s, createContainer as t, collectPlainText as u, isCurrentEpoch as v, hasScrollDirty as x, isDirty as y };
2177
+
2178
+ //# sourceMappingURL=reconciler-NBDSEm8k.mjs.map