@silvery/examples 0.17.3 → 0.17.4

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 (112) hide show
  1. package/dist/UPNG-Cy7ViL8f.mjs +5074 -0
  2. package/dist/__vite-browser-external-2447137e-BML7CYau.mjs +4 -0
  3. package/dist/_banner-DLPxCqVy.mjs +44 -0
  4. package/dist/ansi-CCE2pVS0.mjs +16397 -0
  5. package/dist/apng-HhhBjRGt.mjs +68 -0
  6. package/dist/apng-mwUQbTTF.mjs +3 -0
  7. package/dist/apps/aichat/index.mjs +1299 -0
  8. package/dist/apps/app-todo.mjs +139 -0
  9. package/dist/apps/async-data.mjs +204 -0
  10. package/dist/apps/cli-wizard.mjs +339 -0
  11. package/dist/apps/clipboard.mjs +198 -0
  12. package/dist/apps/components.mjs +864 -0
  13. package/dist/apps/data-explorer.mjs +483 -0
  14. package/dist/apps/dev-tools.mjs +397 -0
  15. package/dist/apps/explorer.mjs +698 -0
  16. package/dist/apps/gallery.mjs +766 -0
  17. package/dist/apps/inline-bench.mjs +115 -0
  18. package/dist/apps/kanban.mjs +280 -0
  19. package/dist/apps/layout-ref.mjs +187 -0
  20. package/dist/apps/outline.mjs +203 -0
  21. package/dist/apps/paste-demo.mjs +189 -0
  22. package/dist/apps/scroll.mjs +86 -0
  23. package/dist/apps/search-filter.mjs +287 -0
  24. package/dist/apps/selection.mjs +355 -0
  25. package/dist/apps/spatial-focus-demo.mjs +388 -0
  26. package/dist/apps/task-list.mjs +258 -0
  27. package/dist/apps/terminal-caps-demo.mjs +315 -0
  28. package/dist/apps/terminal.mjs +872 -0
  29. package/dist/apps/text-selection-demo.mjs +254 -0
  30. package/dist/apps/textarea.mjs +178 -0
  31. package/dist/apps/theme.mjs +661 -0
  32. package/dist/apps/transform.mjs +215 -0
  33. package/dist/apps/virtual-10k.mjs +422 -0
  34. package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
  35. package/dist/backends-Bahh9mKN.mjs +1179 -0
  36. package/dist/backends-CCtCDQ94.mjs +3 -0
  37. package/dist/{cli.mjs → bin/cli.mjs} +15 -19
  38. package/dist/chunk-BSw8zbkd.mjs +37 -0
  39. package/dist/components/counter.mjs +48 -0
  40. package/dist/components/hello.mjs +31 -0
  41. package/dist/components/progress-bar.mjs +59 -0
  42. package/dist/components/select-list.mjs +85 -0
  43. package/dist/components/spinner.mjs +57 -0
  44. package/dist/components/text-input.mjs +62 -0
  45. package/dist/components/virtual-list.mjs +51 -0
  46. package/dist/flexily-zero-adapter-UB-ra8fR.mjs +3374 -0
  47. package/dist/gif-BZaqPPVX.mjs +3 -0
  48. package/dist/gif-BtnXuxLF.mjs +71 -0
  49. package/dist/gifenc-CLRW41dk.mjs +728 -0
  50. package/dist/jsx-runtime-dMs_8fNu.mjs +241 -0
  51. package/dist/key-mapping-5oYQdAQE.mjs +3 -0
  52. package/dist/key-mapping-D4LR1go6.mjs +130 -0
  53. package/dist/layout/dashboard.mjs +1204 -0
  54. package/dist/layout/live-resize.mjs +303 -0
  55. package/dist/layout/overflow.mjs +70 -0
  56. package/dist/layout/text-layout.mjs +335 -0
  57. package/dist/node-NuJ94BWl.mjs +1083 -0
  58. package/dist/plugins-D1KtkT4a.mjs +3057 -0
  59. package/dist/resvg-js-C_8Wps1F.mjs +201 -0
  60. package/dist/src-BTEVGpd9.mjs +23538 -0
  61. package/dist/src-CUUOuRH6.mjs +5322 -0
  62. package/dist/src-CzfRafCQ.mjs +814 -0
  63. package/dist/usingCtx-CsEf0xO3.mjs +57 -0
  64. package/dist/yoga-adapter-BVtQ5OJR.mjs +237 -0
  65. package/package.json +18 -13
  66. package/_banner.tsx +0 -60
  67. package/apps/aichat/components.tsx +0 -469
  68. package/apps/aichat/index.tsx +0 -220
  69. package/apps/aichat/script.ts +0 -460
  70. package/apps/aichat/state.ts +0 -325
  71. package/apps/aichat/types.ts +0 -19
  72. package/apps/app-todo.tsx +0 -201
  73. package/apps/async-data.tsx +0 -196
  74. package/apps/cli-wizard.tsx +0 -332
  75. package/apps/clipboard.tsx +0 -183
  76. package/apps/components.tsx +0 -658
  77. package/apps/data-explorer.tsx +0 -490
  78. package/apps/dev-tools.tsx +0 -395
  79. package/apps/explorer.tsx +0 -731
  80. package/apps/gallery.tsx +0 -653
  81. package/apps/inline-bench.tsx +0 -138
  82. package/apps/kanban.tsx +0 -265
  83. package/apps/layout-ref.tsx +0 -173
  84. package/apps/outline.tsx +0 -160
  85. package/apps/panes/index.tsx +0 -203
  86. package/apps/paste-demo.tsx +0 -185
  87. package/apps/scroll.tsx +0 -80
  88. package/apps/search-filter.tsx +0 -240
  89. package/apps/selection.tsx +0 -346
  90. package/apps/spatial-focus-demo.tsx +0 -372
  91. package/apps/task-list.tsx +0 -271
  92. package/apps/terminal-caps-demo.tsx +0 -317
  93. package/apps/terminal.tsx +0 -784
  94. package/apps/text-selection-demo.tsx +0 -193
  95. package/apps/textarea.tsx +0 -155
  96. package/apps/theme.tsx +0 -515
  97. package/apps/transform.tsx +0 -229
  98. package/apps/virtual-10k.tsx +0 -405
  99. package/apps/vterm-demo/index.tsx +0 -216
  100. package/components/counter.tsx +0 -49
  101. package/components/hello.tsx +0 -38
  102. package/components/progress-bar.tsx +0 -52
  103. package/components/select-list.tsx +0 -54
  104. package/components/spinner.tsx +0 -44
  105. package/components/text-input.tsx +0 -61
  106. package/components/virtual-list.tsx +0 -56
  107. package/dist/cli.d.mts +0 -1
  108. package/dist/cli.mjs.map +0 -1
  109. package/layout/dashboard.tsx +0 -953
  110. package/layout/live-resize.tsx +0 -282
  111. package/layout/overflow.tsx +0 -51
  112. package/layout/text-layout.tsx +0 -283
@@ -0,0 +1,3057 @@
1
+ import { a as __toCommonJS, i as __require } from "./chunk-BSw8zbkd.mjs";
2
+ import { A as buffer_exports, C as detectTextSizingSupport, F as enableFocusReporting, I as isModifierOnlyEvent, L as keyToAnsi, N as init_buffer, O as bufferToStyledText, P as createTermProvider, R as keyToKittyAnsi, T as isTextSizingLikelySupported, a as IncrementalRenderMismatchError, i as outputPhase, k as bufferToText, n as createTerm, r as createOutputPhase, t as createOutputGuard, w as getCachedProbeResult, z as parseKey } from "./ansi-CCE2pVS0.mjs";
3
+ import { a as init_ThemeContext, m as createColorSchemeDetector, r as init_detect } from "./src-CUUOuRH6.mjs";
4
+ import { A as createFocusManager, B as isAnyDirty, C as enterAlternateScreen, D as getAncestorPath, E as createPipeline, F as reconciler, G as FocusManagerContext, H as isLayoutEngineInitialized, I as setOnNodeRemoved, J as StdoutContext, K as RuntimeContext, L as executeRender, M as createContainer, N as createFiberRoot, O as CursorProvider, P as getContainerRoot, R as createAg, S as enableMouse, T as resetCursorStyle, U as CacheBackendContext, V as ensureDefaultLayoutEngine, W as CapabilityRegistryContext, Y as TermContext, _ as detectKittyFromStdio, a as renderSelectionOverlay, b as disableMouse, c as terminalSelectionUpdate, d as hitTest, f as processMouseEvent, g as createWidthDetector, h as applyWidthConfig, i as createVirtualScrollback, j as findByTestID, k as createCursorStore, l as createMouseEventProcessor, m as updateKeyboardModifiers, n as renderSearchBar, o as createTerminalSelectionState, p as selectionHitTest, q as StderrContext, r as searchUpdate, s as extractText, t as createSearchState, u as findContainBoundary, v as KittyFlags, w as leaveAlternateScreen, x as enableKittyKeyboard, y as disableKittyKeyboard, z as signal } from "./src-BTEVGpd9.mjs";
5
+ import { t as _usingCtx } from "./usingCtx-CsEf0xO3.mjs";
6
+ import { t as require_jsx_runtime } from "./jsx-runtime-dMs_8fNu.mjs";
7
+ import React, { Component, createContext, useContext, useEffect, useRef } from "react";
8
+ import { createLogger } from "loggily";
9
+ import * as fs from "node:fs";
10
+ import { writeSync } from "node:fs";
11
+ import "node:path";
12
+ import process$1 from "node:process";
13
+ //#region ../packages/ag-term/src/runtime/layout.ts
14
+ init_buffer();
15
+ /**
16
+ * Ensure layout engine is initialized.
17
+ * Must be called before layout() in async contexts.
18
+ */
19
+ async function ensureLayoutEngine() {
20
+ if (!isLayoutEngineInitialized()) await ensureDefaultLayoutEngine();
21
+ }
22
+ //#endregion
23
+ //#region ../packages/ag-term/src/runtime/diff.ts
24
+ /**
25
+ * Pure diff function for silvery-loop.
26
+ *
27
+ * Takes prev and next buffers, returns minimal ANSI patch.
28
+ * This is an internal function used by the runtime.
29
+ */
30
+ /**
31
+ * Compute the minimal ANSI diff between two buffers.
32
+ *
33
+ * @param prev Previous buffer (null on first render)
34
+ * @param next Current buffer
35
+ * @param mode Render mode (fullscreen or inline)
36
+ * @returns ANSI escape sequence string to transform prev into next
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * import { diff, layout } from '@silvery/ag-term/runtime'
41
+ *
42
+ * const prev = layout(<Text>Hello</Text>, dims)
43
+ * const next = layout(<Text>World</Text>, dims)
44
+ * const patch = diff(prev, next)
45
+ * process.stdout.write(patch)
46
+ * ```
47
+ */
48
+ function diff(prev, next, mode = "fullscreen", scrollbackOffset = 0, termRows) {
49
+ const prevBuffer = prev?._buffer ?? null;
50
+ const nextBuffer = next._buffer;
51
+ return outputPhase(prevBuffer, nextBuffer, mode, scrollbackOffset, termRows);
52
+ }
53
+ //#endregion
54
+ //#region ../packages/ag-term/src/runtime/create-buffer.ts
55
+ init_buffer();
56
+ function createBuffer(termBuffer, nodes) {
57
+ let _text;
58
+ let _ansi;
59
+ return {
60
+ get text() {
61
+ return _text ??= bufferToText(termBuffer);
62
+ },
63
+ get ansi() {
64
+ return _ansi ??= bufferToStyledText(termBuffer);
65
+ },
66
+ nodes,
67
+ _buffer: termBuffer
68
+ };
69
+ }
70
+ //#endregion
71
+ //#region ../packages/create/src/streams/index.ts
72
+ /**
73
+ * AsyncIterable stream helpers for event-driven TUI architecture.
74
+ *
75
+ * These are pure functions over AsyncIterables - no EventEmitters, no callbacks.
76
+ * All helpers properly handle cleanup via return() on early break.
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * const keys = term.keys()
81
+ * const resizes = term.resizes()
82
+ *
83
+ * // Merge multiple sources
84
+ * const events = merge(
85
+ * map(keys, k => ({ type: 'key', ...k })),
86
+ * map(resizes, r => ({ type: 'resize', ...r }))
87
+ * )
88
+ *
89
+ * // Consume until ctrl+c
90
+ * for await (const event of events) {
91
+ * if (event.type === 'key' && event.key === 'ctrl+c') break
92
+ * }
93
+ * ```
94
+ */
95
+ /**
96
+ * Merge multiple AsyncIterables into one.
97
+ *
98
+ * Values are emitted in arrival order (first-come). When all sources complete,
99
+ * the merged iterable completes. If any source throws, the error propagates
100
+ * and remaining sources are cleaned up.
101
+ *
102
+ * IMPORTANT: Each call to merge() creates a fresh iterable. Don't share
103
+ * the same merged iterable between multiple consumers.
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * const merged = merge(keys, resizes, ticks)
108
+ * for await (const event of merged) {
109
+ * // Process events from any source
110
+ * }
111
+ * ```
112
+ */
113
+ async function* merge(...sources) {
114
+ if (sources.length === 0) return;
115
+ const iterators = sources.map((source) => source[Symbol.asyncIterator]());
116
+ const pending = /* @__PURE__ */ new Map();
117
+ async function nextWithIndex(idx) {
118
+ const iterator = iterators[idx];
119
+ if (!iterator) throw new Error(`No iterator at index ${idx}`);
120
+ return {
121
+ index: idx,
122
+ result: await iterator.next()
123
+ };
124
+ }
125
+ for (let i = 0; i < iterators.length; i++) pending.set(i, nextWithIndex(i));
126
+ try {
127
+ while (pending.size > 0) {
128
+ const { index, result } = await Promise.race(pending.values());
129
+ if (result.done) pending.delete(index);
130
+ else {
131
+ yield result.value;
132
+ pending.set(index, nextWithIndex(index));
133
+ }
134
+ }
135
+ } finally {
136
+ await Promise.all(iterators.map((it) => it.return ? it.return() : Promise.resolve()));
137
+ }
138
+ }
139
+ /**
140
+ * Transform each value from an AsyncIterable.
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * const keyEvents = map(keys, k => ({ type: 'key' as const, key: k }))
145
+ * ```
146
+ */
147
+ async function* map(source, fn) {
148
+ const iterator = source[Symbol.asyncIterator]();
149
+ try {
150
+ for await (const value of { [Symbol.asyncIterator]: () => iterator }) yield fn(value);
151
+ } finally {
152
+ if (iterator.return) await iterator.return();
153
+ }
154
+ }
155
+ /**
156
+ * Take values until an AbortSignal fires.
157
+ *
158
+ * When the signal aborts, the iterator completes gracefully (no error thrown).
159
+ * The source iterator is properly cleaned up.
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * const controller = new AbortController()
164
+ * const events = takeUntil(allEvents, controller.signal)
165
+ *
166
+ * // Later: controller.abort() will end the iteration
167
+ * ```
168
+ */
169
+ async function* takeUntil(source, signal) {
170
+ if (signal.aborted) return;
171
+ const iterator = source[Symbol.asyncIterator]();
172
+ let abortResolve;
173
+ const abortPromise = new Promise((resolve) => {
174
+ abortResolve = resolve;
175
+ });
176
+ const onAbort = () => abortResolve();
177
+ signal.addEventListener("abort", onAbort, { once: true });
178
+ try {
179
+ while (!signal.aborted) {
180
+ const result = await Promise.race([iterator.next(), abortPromise.then(() => ({
181
+ done: true,
182
+ value: void 0
183
+ }))]);
184
+ if (result.done) break;
185
+ yield result.value;
186
+ }
187
+ } finally {
188
+ signal.removeEventListener("abort", onAbort);
189
+ if (iterator.return) await iterator.return();
190
+ }
191
+ }
192
+ //#endregion
193
+ //#region ../packages/ag-term/src/runtime/create-runtime.ts
194
+ /**
195
+ * Create the silvery-loop runtime kernel.
196
+ *
197
+ * The runtime owns the event loop, diffing, and output. Users interact via:
198
+ * - events() - AsyncIterable of all events (keys, resize, effects)
199
+ * - schedule() - Queue effects for async execution
200
+ * - render() - Output a buffer (diffing handled internally)
201
+ *
202
+ * NOTE: This runtime is designed for single-consumer use. Calling events()
203
+ * multiple times concurrently will cause events to be split between consumers.
204
+ * Each call returns a fresh AsyncIterable, but they share the underlying queue.
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * using runtime = createRuntime({ target: termTarget })
209
+ *
210
+ * for await (const event of runtime.events()) {
211
+ * state = reducer(state, event)
212
+ * runtime.render(layout(view(state), runtime.getDims()))
213
+ * }
214
+ * ```
215
+ */
216
+ /**
217
+ * Create an event channel that bridges callbacks to AsyncIterable.
218
+ *
219
+ * This is the single point where callbacks (resize, effect completion)
220
+ * are converted to the async iterable pattern. External sources like
221
+ * keyboard events are already AsyncIterable and merged at a higher level.
222
+ */
223
+ function createEventChannel(signal) {
224
+ const queue = [];
225
+ let pendingResolve;
226
+ let disposed = false;
227
+ const onAbort = () => {
228
+ if (pendingResolve) {
229
+ pendingResolve(null);
230
+ pendingResolve = void 0;
231
+ }
232
+ };
233
+ signal.addEventListener("abort", onAbort, { once: true });
234
+ return {
235
+ push(event) {
236
+ if (disposed || signal.aborted) return;
237
+ if (pendingResolve) {
238
+ const r = pendingResolve;
239
+ pendingResolve = void 0;
240
+ r(event);
241
+ } else queue.push(event);
242
+ },
243
+ events() {
244
+ return { [Symbol.asyncIterator]() {
245
+ return { async next() {
246
+ if (disposed || signal.aborted) return {
247
+ done: true,
248
+ value: void 0
249
+ };
250
+ if (queue.length > 0) return {
251
+ done: false,
252
+ value: queue.shift()
253
+ };
254
+ const event = await new Promise((resolve) => {
255
+ pendingResolve = resolve;
256
+ });
257
+ if (event === null || disposed || signal.aborted) return {
258
+ done: true,
259
+ value: void 0
260
+ };
261
+ return {
262
+ done: false,
263
+ value: event
264
+ };
265
+ } };
266
+ } };
267
+ },
268
+ dispose() {
269
+ disposed = true;
270
+ signal.removeEventListener("abort", onAbort);
271
+ if (pendingResolve) {
272
+ pendingResolve(null);
273
+ pendingResolve = void 0;
274
+ }
275
+ }
276
+ };
277
+ }
278
+ /**
279
+ * Create a runtime kernel.
280
+ *
281
+ * @param options Runtime configuration
282
+ * @returns Runtime instance implementing Symbol.dispose
283
+ */
284
+ function createRuntime(options) {
285
+ const { target, signal: externalSignal, mode = "fullscreen" } = options;
286
+ const fallbackOutputPhase = mode === "inline" ? createOutputPhase({}) : void 0;
287
+ let outputPhaseFn = options.outputPhaseFn ?? fallbackOutputPhase;
288
+ const controller = new AbortController();
289
+ const signal = controller.signal;
290
+ let externalAbortHandler;
291
+ if (externalSignal) if (externalSignal.aborted) controller.abort();
292
+ else {
293
+ externalAbortHandler = () => controller.abort();
294
+ externalSignal.addEventListener("abort", externalAbortHandler, { once: true });
295
+ }
296
+ let prevBuffer = null;
297
+ let scrollbackOffset = 0;
298
+ let disposed = false;
299
+ const eventChannel = createEventChannel(signal);
300
+ let unsubscribeResize;
301
+ if (target.onResize) unsubscribeResize = target.onResize((dims) => {
302
+ eventChannel.push({
303
+ type: "resize",
304
+ cols: dims.cols,
305
+ rows: dims.rows
306
+ });
307
+ });
308
+ let effectId = 0;
309
+ return {
310
+ events() {
311
+ return takeUntil(eventChannel.events(), signal);
312
+ },
313
+ schedule(effect, opts) {
314
+ if (disposed) return;
315
+ const id = `effect-${effectId++}`;
316
+ const effectSignal = opts?.signal;
317
+ if (effectSignal?.aborted) return;
318
+ const execute = async () => {
319
+ let abortHandler;
320
+ try {
321
+ if (effectSignal) {
322
+ const aborted = new Promise((_resolve, reject) => {
323
+ abortHandler = () => reject(/* @__PURE__ */ new Error("Effect aborted"));
324
+ effectSignal.addEventListener("abort", abortHandler, { once: true });
325
+ });
326
+ const result = await Promise.race([effect(), aborted]);
327
+ if (abortHandler) effectSignal.removeEventListener("abort", abortHandler);
328
+ eventChannel.push({
329
+ type: "effect",
330
+ id,
331
+ result
332
+ });
333
+ } else {
334
+ const result = await effect();
335
+ eventChannel.push({
336
+ type: "effect",
337
+ id,
338
+ result
339
+ });
340
+ }
341
+ } catch (error) {
342
+ if (abortHandler && effectSignal) effectSignal.removeEventListener("abort", abortHandler);
343
+ if (error instanceof Error && (error.message === "Effect aborted" || error.name === "AbortError")) return;
344
+ eventChannel.push({
345
+ type: "error",
346
+ error: error instanceof Error ? error : new Error(String(error))
347
+ });
348
+ }
349
+ };
350
+ queueMicrotask(() => {
351
+ execute();
352
+ });
353
+ },
354
+ render(buffer) {
355
+ if (disposed) return;
356
+ const offset = scrollbackOffset;
357
+ scrollbackOffset = 0;
358
+ const termRows = target.getDims().rows;
359
+ let patch;
360
+ if (outputPhaseFn) {
361
+ const prevBuf = prevBuffer?._buffer ?? null;
362
+ const nextBuf = buffer._buffer;
363
+ patch = outputPhaseFn(prevBuf, nextBuf, mode, offset, termRows);
364
+ } else patch = diff(prevBuffer, buffer, mode, offset, termRows);
365
+ prevBuffer = buffer;
366
+ if (process.env.SILVERY_CAPTURE_RAW) try {
367
+ __require("fs").appendFileSync("/tmp/silvery-runtime-raw.ansi", patch);
368
+ } catch {}
369
+ target.write(patch);
370
+ },
371
+ addScrollbackLines(lines) {
372
+ if (mode !== "inline" || lines <= 0) return;
373
+ scrollbackOffset += lines;
374
+ },
375
+ invalidate() {
376
+ prevBuffer = null;
377
+ },
378
+ setOutputPhaseFn(fn) {
379
+ if (fn) outputPhaseFn = fn;
380
+ },
381
+ resetInlineCursor() {
382
+ outputPhaseFn?.resetInlineState?.();
383
+ },
384
+ getInlineCursorRow() {
385
+ return outputPhaseFn?.getInlineCursorRow?.() ?? -1;
386
+ },
387
+ promoteScrollback(content, lines) {
388
+ outputPhaseFn?.promoteScrollback?.(content, lines);
389
+ },
390
+ getDims() {
391
+ return target.getDims();
392
+ },
393
+ [Symbol.dispose]() {
394
+ if (disposed) return;
395
+ disposed = true;
396
+ controller.abort();
397
+ if (externalAbortHandler && externalSignal) externalSignal.removeEventListener("abort", externalAbortHandler);
398
+ if (unsubscribeResize) unsubscribeResize();
399
+ eventChannel.dispose();
400
+ }
401
+ };
402
+ }
403
+ //#endregion
404
+ //#region ../packages/create/src/signal-store.ts
405
+ /**
406
+ * Signal Store — Zustand StoreApi-compatible store backed by alien-signals.
407
+ *
408
+ * Drop-in replacement for Zustand's createStore(). Provides the same
409
+ * StoreApi<T> interface so useApp()/StoreContext keep working unchanged.
410
+ *
411
+ * Also re-exports StateCreator for backward compatibility with code
412
+ * that imported it from "zustand".
413
+ */
414
+ function createStore(factory) {
415
+ const listeners = /* @__PURE__ */ new Set();
416
+ const state$ = signal(void 0);
417
+ let initialState;
418
+ const setState = (partial, replace) => {
419
+ const prev = state$();
420
+ const raw = typeof partial === "function" ? partial(prev) : partial;
421
+ let next;
422
+ if (!replace && raw !== null && typeof raw === "object" && !Array.isArray(raw)) next = {
423
+ ...prev,
424
+ ...raw
425
+ };
426
+ else next = raw;
427
+ if (Object.is(prev, next)) return;
428
+ state$(next);
429
+ for (const listener of listeners) listener(next, prev);
430
+ };
431
+ const getState = () => state$();
432
+ const getInitialState = () => initialState;
433
+ const subscribe = (listener) => {
434
+ listeners.add(listener);
435
+ return () => {
436
+ listeners.delete(listener);
437
+ };
438
+ };
439
+ const api = {
440
+ setState,
441
+ getState,
442
+ getInitialState,
443
+ subscribe
444
+ };
445
+ const created = factory(setState, getState, api);
446
+ state$(created);
447
+ initialState = created;
448
+ return api;
449
+ }
450
+ //#endregion
451
+ //#region ../packages/ag-react/src/error-boundary.tsx
452
+ /**
453
+ * ErrorBoundary — Built-in error boundary for silvery apps.
454
+ *
455
+ * Catches render errors in the component tree and displays a rich
456
+ * error message with source location, code excerpt, and stack trace
457
+ * using low-level silvery host elements (no dependency on Box/Text
458
+ * from @silvery/ag-react/ui). This is the default root wrapper for all
459
+ * silvery apps — createApp() and run() wrap the element tree with it
460
+ * automatically.
461
+ *
462
+ * Uses `silvery-box` and `silvery-text` host elements directly to
463
+ * avoid circular deps with higher-level component libraries.
464
+ *
465
+ * @packageDocumentation
466
+ */
467
+ /**
468
+ * Parse a stack line to extract function name, file, line, column.
469
+ * Handles both `at Foo (file:line:col)` and `at file:line:col` formats.
470
+ */
471
+ function parseStackLine(line) {
472
+ const trimmed = line.trim();
473
+ if (!trimmed.startsWith("at ")) return null;
474
+ const rest = trimmed.slice(3);
475
+ const match1 = rest.match(/^(.+?)\s+\((.+?):(\d+):(\d+)\)$/);
476
+ if (match1) return {
477
+ function: match1[1],
478
+ file: match1[2],
479
+ line: Number(match1[3]),
480
+ column: Number(match1[4])
481
+ };
482
+ const match2 = rest.match(/^(.+?):(\d+):(\d+)$/);
483
+ if (match2) return {
484
+ file: match2[1],
485
+ line: Number(match2[2]),
486
+ column: Number(match2[3])
487
+ };
488
+ return null;
489
+ }
490
+ /**
491
+ * Clean up file path by removing cwd prefix and file:// protocol.
492
+ */
493
+ function cleanupPath(filePath) {
494
+ if (!filePath) return filePath;
495
+ let p = filePath;
496
+ const cwdPath = process.cwd();
497
+ p = p.replace(/^file:\/\//, "");
498
+ for (const prefix of [cwdPath, `/private${cwdPath}`]) if (p.startsWith(`${prefix}/`)) {
499
+ p = p.slice(prefix.length + 1);
500
+ break;
501
+ }
502
+ return p;
503
+ }
504
+ /**
505
+ * Get source code excerpt around a line number (±3 lines).
506
+ */
507
+ function getCodeExcerpt(filePath, line) {
508
+ try {
509
+ if (!fs.existsSync(filePath)) return null;
510
+ const lines = fs.readFileSync(filePath, "utf8").split("\n");
511
+ const start = Math.max(0, line - 4);
512
+ const end = Math.min(lines.length, line + 3);
513
+ const result = [];
514
+ for (let i = start; i < end; i++) result.push({
515
+ line: i + 1,
516
+ value: (lines[i] ?? "").replace(/\t/g, " ")
517
+ });
518
+ return result;
519
+ } catch {
520
+ return null;
521
+ }
522
+ }
523
+ /**
524
+ * Rich error boundary for silvery's runtime layer.
525
+ *
526
+ * Must be a class component (React limitation for error boundaries).
527
+ * Renders error info using silvery-box/silvery-text host elements — no Box/Text dependency.
528
+ * Shows: ERROR label, error message, file location, source code excerpt, and stack trace.
529
+ */
530
+ var SilveryErrorBoundary = class extends Component {
531
+ state = { error: null };
532
+ static getDerivedStateFromError(error) {
533
+ return { error };
534
+ }
535
+ componentDidCatch(error) {
536
+ this.props.onError?.(error);
537
+ }
538
+ render() {
539
+ if (this.state.error) {
540
+ const err = this.state.error;
541
+ const stack = err.stack ? err.stack.split("\n").slice(1) : [];
542
+ const origin = stack.length > 0 ? parseStackLine(stack[0]) : null;
543
+ const filePath = cleanupPath(origin?.file);
544
+ let excerpt = null;
545
+ let lineWidth = 0;
546
+ if (filePath && origin?.line) {
547
+ excerpt = getCodeExcerpt(filePath, origin.line);
548
+ if (excerpt) for (const { line } of excerpt) lineWidth = Math.max(lineWidth, String(line).length);
549
+ }
550
+ const children = [];
551
+ children.push(React.createElement("silvery-box", { key: "header" }, React.createElement("silvery-text", {
552
+ backgroundColor: "red",
553
+ color: "white"
554
+ }, " ERROR "), React.createElement("silvery-text", {}, ` ${err.message}`)));
555
+ if (filePath && origin) children.push(React.createElement("silvery-box", {
556
+ key: "location",
557
+ marginTop: 1
558
+ }, React.createElement("silvery-text", { dimColor: true }, `${filePath}:${origin.line}:${origin.column}`)));
559
+ if (excerpt && origin) {
560
+ const codeLines = excerpt.map(({ line, value }) => {
561
+ const lineNum = String(line).padStart(lineWidth, " ");
562
+ return React.createElement("silvery-box", { key: `code-${line}` }, React.createElement("silvery-text", {
563
+ dimColor: line !== origin.line,
564
+ backgroundColor: line === origin.line ? "red" : void 0,
565
+ color: line === origin.line ? "white" : void 0
566
+ }, `${lineNum}:`), React.createElement("silvery-text", {
567
+ backgroundColor: line === origin.line ? "red" : void 0,
568
+ color: line === origin.line ? "white" : void 0
569
+ }, ` ${value}`));
570
+ });
571
+ children.push(React.createElement("silvery-box", {
572
+ key: "code",
573
+ marginTop: 1,
574
+ flexDirection: "column"
575
+ }, ...codeLines));
576
+ }
577
+ if (stack.length > 0) {
578
+ const stackLines = stack.map((line, i) => {
579
+ const parsed = parseStackLine(line);
580
+ if (!parsed) return React.createElement("silvery-box", { key: `stack-${i}` }, React.createElement("silvery-text", { dimColor: true }, `- ${line.trim()}`));
581
+ const cleanFile = cleanupPath(parsed.file);
582
+ return React.createElement("silvery-box", { key: `stack-${i}` }, React.createElement("silvery-text", { dimColor: true }, "- "), React.createElement("silvery-text", {
583
+ dimColor: true,
584
+ bold: true
585
+ }, parsed.function ?? ""), React.createElement("silvery-text", {
586
+ dimColor: true,
587
+ color: "gray"
588
+ }, ` (${cleanFile ?? ""}:${parsed.line}:${parsed.column})`));
589
+ });
590
+ children.push(React.createElement("silvery-box", {
591
+ key: "stack",
592
+ marginTop: 1,
593
+ flexDirection: "column"
594
+ }, ...stackLines));
595
+ }
596
+ return React.createElement("silvery-box", {
597
+ flexDirection: "column",
598
+ padding: 1
599
+ }, ...children);
600
+ }
601
+ return this.props.children;
602
+ }
603
+ };
604
+ //#endregion
605
+ //#region ../packages/ag/src/focus-events.ts
606
+ /**
607
+ * Create a synthetic keyboard event.
608
+ */
609
+ function createKeyEvent(input, key, target) {
610
+ let propagationStopped = false;
611
+ let defaultPrevented = false;
612
+ return {
613
+ key: input,
614
+ input,
615
+ ctrl: key.ctrl,
616
+ meta: key.meta,
617
+ shift: key.shift,
618
+ super: key.super,
619
+ hyper: key.hyper,
620
+ eventType: key.eventType,
621
+ target,
622
+ currentTarget: target,
623
+ nativeEvent: {
624
+ input,
625
+ key
626
+ },
627
+ get propagationStopped() {
628
+ return propagationStopped;
629
+ },
630
+ get defaultPrevented() {
631
+ return defaultPrevented;
632
+ },
633
+ stopPropagation() {
634
+ propagationStopped = true;
635
+ },
636
+ preventDefault() {
637
+ defaultPrevented = true;
638
+ }
639
+ };
640
+ }
641
+ /**
642
+ * Create a synthetic focus event.
643
+ */
644
+ function createFocusEvent(type, target, relatedTarget) {
645
+ let propagationStopped = false;
646
+ return {
647
+ type,
648
+ target,
649
+ relatedTarget,
650
+ currentTarget: target,
651
+ get propagationStopped() {
652
+ return propagationStopped;
653
+ },
654
+ stopPropagation() {
655
+ propagationStopped = true;
656
+ }
657
+ };
658
+ }
659
+ /**
660
+ * Dispatch a keyboard event through the render tree with DOM-style
661
+ * capture/target/bubble phases.
662
+ *
663
+ * For press/repeat events:
664
+ * 1. Capture phase: root → target (onKeyDownCapture props)
665
+ * 2. Target phase: target's onKeyDown
666
+ * 3. Bubble phase: target parent → root (onKeyDown props)
667
+ *
668
+ * For release events:
669
+ * 1. Target phase: target's onKeyUp
670
+ * 2. Bubble phase: target parent → root (onKeyUp props)
671
+ * (No capture phase for keyUp — deliberate simplification; React DOM has onKeyUpCapture)
672
+ *
673
+ * stopPropagation() halts traversal at any phase.
674
+ */
675
+ function dispatchKeyEvent(event, dispatch) {
676
+ const path = getAncestorPath(event.target);
677
+ const mutableEvent = event;
678
+ const isRelease = event.eventType === "release";
679
+ const handlerProp = isRelease ? "onKeyUp" : "onKeyDown";
680
+ if (!isRelease) for (let i = path.length - 1; i > 0; i--) {
681
+ if (event.propagationStopped) return;
682
+ const node = path[i];
683
+ const handler = node.props.onKeyDownCapture;
684
+ if (handler) {
685
+ mutableEvent.currentTarget = node;
686
+ handler(event);
687
+ }
688
+ }
689
+ if (!event.propagationStopped) {
690
+ const target = path[0];
691
+ mutableEvent.currentTarget = target;
692
+ const handler = target.props[handlerProp];
693
+ if (handler) handler(event, dispatch);
694
+ }
695
+ for (let i = 1; i < path.length; i++) {
696
+ if (event.propagationStopped) return;
697
+ const node = path[i];
698
+ const handler = node.props[handlerProp];
699
+ if (handler) {
700
+ mutableEvent.currentTarget = node;
701
+ handler(event, dispatch);
702
+ }
703
+ }
704
+ }
705
+ /**
706
+ * Dispatch a focus event through the render tree.
707
+ *
708
+ * Fires onFocus/onBlur on the target, then bubbles to ancestors.
709
+ */
710
+ function dispatchFocusEvent(event) {
711
+ const handlerProp = event.type === "focus" ? "onFocus" : "onBlur";
712
+ const path = getAncestorPath(event.target);
713
+ const mutableEvent = event;
714
+ for (const node of path) {
715
+ if (event.propagationStopped) break;
716
+ const handler = node.props[handlerProp];
717
+ if (handler) {
718
+ mutableEvent.currentTarget = node;
719
+ handler(event);
720
+ }
721
+ }
722
+ }
723
+ //#endregion
724
+ //#region ../packages/ag-term/src/scheduler.ts
725
+ init_buffer();
726
+ createLogger("silvery:scheduler");
727
+ process.env.SILVERY_SYNC_UPDATE === "1" || process.env.SILVERY_SYNC_UPDATE;
728
+ //#endregion
729
+ //#region ../packages/ag-term/src/runtime/event-handlers.ts
730
+ /**
731
+ * Build the EventHandlerContext passed to user-defined event handlers.
732
+ * Shared by runEventHandler() and press().
733
+ *
734
+ * When the store was created with `tea()` middleware, `dispatch` is
735
+ * automatically wired from the store state.
736
+ */
737
+ function createHandlerContext(store, focusManager, container) {
738
+ const state = store.getState();
739
+ const teaDispatch = typeof state.dispatch === "function" ? state.dispatch : void 0;
740
+ return {
741
+ set: store.setState,
742
+ get: store.getState,
743
+ focusManager,
744
+ focus(testID) {
745
+ const root = getContainerRoot(container);
746
+ focusManager.focusById(testID, root, "programmatic");
747
+ },
748
+ activateScope(scopeId) {
749
+ const root = getContainerRoot(container);
750
+ focusManager.activateScope(scopeId, root);
751
+ },
752
+ getFocusPath() {
753
+ const root = getContainerRoot(container);
754
+ return focusManager.getFocusPath(root);
755
+ },
756
+ dispatch: teaDispatch,
757
+ hitTest(x, y) {
758
+ return hitTest(getContainerRoot(container), x, y);
759
+ }
760
+ };
761
+ }
762
+ /**
763
+ * Dispatch a key event through the focus system and handle default
764
+ * focus navigation (Tab, Shift+Tab, Enter scope, Escape scope).
765
+ *
766
+ * Returns "consumed" if the focus system handled the event (caller should
767
+ * render and return), or "continue" if the event should proceed to app handlers.
768
+ */
769
+ function handleFocusNavigation(input, parsedKey, focusManager, container) {
770
+ if (focusManager.activeElement) {
771
+ const keyEvent = createKeyEvent(input, parsedKey, focusManager.activeElement);
772
+ dispatchKeyEvent(keyEvent);
773
+ if (keyEvent.propagationStopped || keyEvent.defaultPrevented) return "consumed";
774
+ }
775
+ const root = getContainerRoot(container);
776
+ if (parsedKey.tab && !parsedKey.shift) {
777
+ focusManager.focusNext(root);
778
+ return "consumed";
779
+ }
780
+ if (parsedKey.tab && parsedKey.shift) {
781
+ focusManager.focusPrev(root);
782
+ return "consumed";
783
+ }
784
+ if (parsedKey.return && focusManager.activeElement) {
785
+ const activeEl = focusManager.activeElement;
786
+ const props = activeEl.props;
787
+ const testID = typeof props.testID === "string" ? props.testID : null;
788
+ if (props.focusScope && testID) {
789
+ focusManager.enterScope(testID);
790
+ focusManager.focusNext(root, activeEl);
791
+ return "consumed";
792
+ }
793
+ }
794
+ if (parsedKey.escape) {
795
+ if (focusManager.scopeStack.length > 0) {
796
+ const scopeId = focusManager.scopeStack[focusManager.scopeStack.length - 1];
797
+ focusManager.exitScope();
798
+ const scopeNode = findByTestID(root, scopeId);
799
+ if (scopeNode) focusManager.focus(scopeNode, "keyboard");
800
+ return "consumed";
801
+ }
802
+ }
803
+ return "continue";
804
+ }
805
+ /**
806
+ * Dispatch a DOM-level mouse event to the node tree.
807
+ * Called from runEventHandler for mouse events.
808
+ */
809
+ function dispatchMouseEventToTree(event, mouseEventState, root) {
810
+ if (event.event !== "mouse" || !event.data) return false;
811
+ const mouseData = event.data;
812
+ return processMouseEvent(mouseEventState, {
813
+ button: mouseData.button,
814
+ x: mouseData.x,
815
+ y: mouseData.y,
816
+ action: mouseData.action,
817
+ delta: mouseData.delta,
818
+ shift: mouseData.shift,
819
+ meta: mouseData.meta,
820
+ ctrl: mouseData.ctrl
821
+ }, root);
822
+ }
823
+ /**
824
+ * Invoke the namespaced handler for a single event (state mutation only, no render).
825
+ * Returns true to continue, false to exit, or "flush" for a render barrier.
826
+ *
827
+ * Also dispatches DOM-level mouse events when applicable.
828
+ */
829
+ function invokeEventHandler(event, handlers, ctx, mouseEventState, container) {
830
+ if (dispatchMouseEventToTree(event, mouseEventState, getContainerRoot(container))) return true;
831
+ const namespacedHandler = handlers?.[event.type];
832
+ if (namespacedHandler && typeof namespacedHandler === "function") {
833
+ const result = namespacedHandler(event.data, ctx);
834
+ if (result === "exit") return false;
835
+ if (result === "flush") return "flush";
836
+ }
837
+ return true;
838
+ }
839
+ /**
840
+ * Dispatch a term:key event to app handlers (namespaced + legacy).
841
+ * Returns "exit" if the handler signaled exit, undefined otherwise.
842
+ */
843
+ function dispatchKeyToHandlers(input, parsedKey, handlers, ctx) {
844
+ const namespacedHandler = handlers?.["term:key"];
845
+ if (namespacedHandler && typeof namespacedHandler === "function") {
846
+ if (namespacedHandler({
847
+ input,
848
+ key: parsedKey
849
+ }, ctx) === "exit") return "exit";
850
+ }
851
+ if (handlers?.key) {
852
+ if (handlers.key(input, parsedKey, ctx) === "exit") return "exit";
853
+ }
854
+ }
855
+ //#endregion
856
+ //#region ../packages/ag-term/src/runtime/terminal-lifecycle.ts
857
+ /**
858
+ * Terminal Lifecycle Events
859
+ *
860
+ * Handles suspend/resume (Ctrl+Z/SIGCONT) and interrupt (Ctrl+C) for TUI apps.
861
+ * When stdin is in raw mode, the terminal does not generate SIGTSTP/SIGINT for
862
+ * Ctrl+Z/Ctrl+C. This module intercepts the raw bytes and manages the full
863
+ * terminal state save/restore cycle.
864
+ *
865
+ * Inspired by ncurses (endwin/refresh), bubbletea, and Textual.
866
+ *
867
+ * Protocols managed:
868
+ * - Raw mode (stdin)
869
+ * - Alternate screen buffer (DEC private mode 1049)
870
+ * - Cursor visibility (DEC private mode 25)
871
+ * - Mouse tracking (modes 1000, 1002, 1006)
872
+ * - Kitty keyboard protocol (CSI > flags u / CSI < u)
873
+ * - Bracketed paste (DEC private mode 2004)
874
+ * - SGR attributes (reset via CSI 0 m)
875
+ */
876
+ /**
877
+ * Capture the current terminal protocol state.
878
+ *
879
+ * This builds a TerminalState from the options passed to run()/createApp(),
880
+ * since terminal state is not directly queryable from the OS.
881
+ */
882
+ function captureTerminalState(opts) {
883
+ return {
884
+ rawMode: opts.rawMode ?? true,
885
+ alternateScreen: opts.alternateScreen ?? false,
886
+ cursorHidden: opts.cursorHidden ?? true,
887
+ mouseEnabled: opts.mouse ?? false,
888
+ kittyEnabled: opts.kitty ?? false,
889
+ kittyFlags: opts.kittyFlags ?? 11,
890
+ bracketedPaste: opts.bracketedPaste ?? false,
891
+ focusReporting: opts.focusReporting ?? false
892
+ };
893
+ }
894
+ /**
895
+ * Restore terminal to normal state before suspending or exiting.
896
+ *
897
+ * Uses writeSync for reliability during signal handling (async write
898
+ * may not complete before the process suspends).
899
+ *
900
+ * Order matters: disable protocols first, then show cursor, then exit
901
+ * alternate screen, then disable raw mode.
902
+ */
903
+ function restoreTerminalState(stdout, stdin) {
904
+ try {
905
+ stdin.removeAllListeners("data");
906
+ stdin.pause();
907
+ } catch {}
908
+ const sequences = [
909
+ "\x1B[0m",
910
+ "\x1B[?1004l",
911
+ disableMouse(),
912
+ disableKittyKeyboard(),
913
+ "\x1B[?2004l",
914
+ resetCursorStyle(),
915
+ "\x1B[?25h",
916
+ "\x1B[?1049l"
917
+ ].join("");
918
+ if (stdout === process.stdout) try {
919
+ writeSync(stdout.fd, sequences);
920
+ } catch {
921
+ try {
922
+ stdout.write(sequences);
923
+ } catch {}
924
+ }
925
+ else try {
926
+ stdout.write(sequences);
927
+ } catch {}
928
+ try {
929
+ stdin.resume();
930
+ while (stdin.read() !== null);
931
+ stdin.pause();
932
+ } catch {}
933
+ if (stdin.isTTY && stdin.isRaw) try {
934
+ stdin.setRawMode(false);
935
+ } catch {}
936
+ }
937
+ /**
938
+ * Re-enter TUI mode after resuming from suspend (SIGCONT).
939
+ *
940
+ * Restores all protocols that were active before suspend, in the correct
941
+ * order: raw mode first, then alternate screen, then protocols, then
942
+ * trigger a full redraw via synthetic resize.
943
+ */
944
+ function resumeTerminalState(state, stdout, stdin) {
945
+ if (state.rawMode && stdin.isTTY) try {
946
+ stdin.setRawMode(true);
947
+ stdin.resume();
948
+ } catch {}
949
+ const sequences = [];
950
+ if (state.alternateScreen) sequences.push("\x1B[?1049h");
951
+ sequences.push("\x1B[2J\x1B[H");
952
+ if (state.cursorHidden) sequences.push("\x1B[?25l");
953
+ if (state.kittyEnabled) sequences.push(enableKittyKeyboard(state.kittyFlags));
954
+ if (state.mouseEnabled) sequences.push(enableMouse());
955
+ if (state.bracketedPaste) sequences.push("\x1B[?2004h");
956
+ if (state.focusReporting) sequences.push("\x1B[?1004h");
957
+ const joined = sequences.join("");
958
+ if (stdout === process.stdout) try {
959
+ writeSync(stdout.fd, joined);
960
+ } catch {
961
+ try {
962
+ stdout.write(joined);
963
+ } catch {}
964
+ }
965
+ else try {
966
+ stdout.write(joined);
967
+ } catch {}
968
+ stdout.emit("resize");
969
+ }
970
+ /**
971
+ * Execute the full suspend flow: save state, restore terminal, SIGTSTP,
972
+ * and set up SIGCONT handler to resume.
973
+ *
974
+ * @param state - Terminal state snapshot to restore on resume
975
+ * @param stdout - Output stream
976
+ * @param stdin - Input stream
977
+ * @param onResume - Optional callback after resume
978
+ */
979
+ function performSuspend(state, stdout, stdin, onResume) {
980
+ restoreTerminalState(stdout, stdin);
981
+ process.once("SIGCONT", () => {
982
+ resumeTerminalState(state, stdout, stdin);
983
+ onResume?.();
984
+ });
985
+ process.kill(process.pid, "SIGTSTP");
986
+ }
987
+ //#endregion
988
+ //#region ../packages/ag-term/src/features/selection.ts
989
+ /**
990
+ * Create a bridge SelectionFeature that delegates to an external state owner.
991
+ *
992
+ * Used by create-app to expose its inline selection state to React hooks
993
+ * (useSelection) and copy-mode (which calls setRange/clear) without
994
+ * duplicating the state machine. Mouse handlers are no-ops — the external
995
+ * owner (create-app's event loop) handles mouse events directly.
996
+ */
997
+ function createSelectionBridge(options) {
998
+ return {
999
+ get state() {
1000
+ return options.getState();
1001
+ },
1002
+ subscribe(listener) {
1003
+ return options.subscribe(listener);
1004
+ },
1005
+ handleMouseDown(_col, _row, _altKey) {},
1006
+ handleMouseMove(_col, _row) {},
1007
+ handleMouseUp(_col, _row) {},
1008
+ setRange(range) {
1009
+ options.setRange(range);
1010
+ },
1011
+ clear() {
1012
+ options.clear();
1013
+ },
1014
+ dispose() {}
1015
+ };
1016
+ }
1017
+ //#endregion
1018
+ //#region ../packages/create/src/internal/capability-registry.ts
1019
+ /** Create a new capability registry (one per app instance). */
1020
+ function createCapabilityRegistry() {
1021
+ const capabilities = /* @__PURE__ */ new Map();
1022
+ return {
1023
+ register(key, capability) {
1024
+ capabilities.set(key, capability);
1025
+ },
1026
+ get(key) {
1027
+ return capabilities.get(key);
1028
+ }
1029
+ };
1030
+ }
1031
+ //#endregion
1032
+ //#region ../packages/create/src/internal/capabilities.ts
1033
+ /**
1034
+ * Capability symbols — well-known keys for the CapabilityRegistry.
1035
+ *
1036
+ * Features register themselves under these symbols so other parts
1037
+ * of the composition chain can discover and interact with them.
1038
+ *
1039
+ * @internal Not exported from the public barrel.
1040
+ */
1041
+ /** Selection feature: text selection state + mouse handling. */
1042
+ const SELECTION_CAPABILITY = Symbol.for("silvery.selection");
1043
+ /** Clipboard feature: copy/paste via OSC 52 or other backends. */
1044
+ const CLIPBOARD_CAPABILITY = Symbol.for("silvery.clipboard");
1045
+ /** Input router: priority-based event dispatch for interaction features. */
1046
+ const INPUT_ROUTER = Symbol.for("silvery.input-router");
1047
+ //#endregion
1048
+ //#region ../packages/ag-term/src/runtime/perf.ts
1049
+ /**
1050
+ * Keypress performance instrumentation.
1051
+ *
1052
+ * Zero-overhead when TRACE is not set — all logging uses optional chaining.
1053
+ * When TRACE=silvery:perf is set, emits span timing for each keypress cycle
1054
+ * and a summary on exit.
1055
+ *
1056
+ * @example
1057
+ * ```bash
1058
+ * TRACE=silvery:perf bun km view ~/vault
1059
+ * # → SPAN silvery:perf:keypress (5ms) {key: "j"}
1060
+ * # → on exit: keypress summary: 42 presses, mean=4.2ms, p95=12.1ms, max=18.3ms, overruns=2
1061
+ * ```
1062
+ */
1063
+ /** Exported for ?. chaining in hot paths: `perfLog.span?.("keypress", { key })` */
1064
+ const perfLog = createLogger("silvery:perf");
1065
+ let samples = null;
1066
+ let budgetOverruns = 0;
1067
+ /**
1068
+ * Record a completed keypress and check budget.
1069
+ * Only records when tracing is active (samples array initialized by startTracking).
1070
+ * Call after the keypress cycle completes (render done).
1071
+ */
1072
+ function checkBudget(key, durationMs, budgetMs = 16) {
1073
+ if (samples) samples.push({
1074
+ key,
1075
+ durationMs
1076
+ });
1077
+ if (durationMs > budgetMs) {
1078
+ budgetOverruns++;
1079
+ perfLog.warn?.(`keypress over budget: ${key} took ${durationMs.toFixed(1)}ms (budget: ${budgetMs}ms)`);
1080
+ }
1081
+ }
1082
+ /** Call once when first span is created to start accumulating samples. */
1083
+ function startTracking() {
1084
+ if (!samples) samples = [];
1085
+ }
1086
+ /**
1087
+ * Log a summary of all recorded keypress spans.
1088
+ *
1089
+ * Call when the app unmounts/exits. Only produces output when TRACE is
1090
+ * enabled and at least one span was recorded.
1091
+ */
1092
+ function logExitSummary() {
1093
+ if (!samples || samples.length === 0) return;
1094
+ const durations = samples.map((s) => s.durationMs).sort((a, b) => a - b);
1095
+ const total = samples.length;
1096
+ const mean = durations.reduce((sum, d) => sum + d, 0) / total;
1097
+ const p95 = durations[Math.min(Math.floor(total * .95), total - 1)];
1098
+ const max = durations[total - 1];
1099
+ perfLog.info?.(`keypress summary: ${total} presses, mean=${mean.toFixed(1)}ms, p95=${p95.toFixed(1)}ms, max=${max.toFixed(1)}ms, overruns=${budgetOverruns}`);
1100
+ samples = null;
1101
+ budgetOverruns = 0;
1102
+ }
1103
+ //#endregion
1104
+ //#region ../packages/ag-term/src/runtime/create-app.tsx
1105
+ /**
1106
+ * createApp() - Layer 3 entry point for silvery-loop
1107
+ *
1108
+ * Provides signal-backed store integration with unified providers.
1109
+ * Providers are stores (getState/subscribe) + event sources (events()).
1110
+ *
1111
+ * @example
1112
+ * ```tsx
1113
+ * import { createApp, useApp } from '@silvery/create/create-app'
1114
+ * import { createTermProvider } from '@silvery/ag-term/runtime'
1115
+ *
1116
+ * const app = createApp(
1117
+ * // Store factory
1118
+ * ({ term }) => (set, get) => ({
1119
+ * count: 0,
1120
+ * increment: () => set(s => ({ count: s.count + 1 })),
1121
+ * }),
1122
+ * // Event handlers - namespaced as 'provider:event'
1123
+ * {
1124
+ * 'term:key': ({ input, key }, { set }) => {
1125
+ * if (input === 'j') set(s => ({ count: s.count + 1 }))
1126
+ * if (input === 'q') return 'exit'
1127
+ * },
1128
+ * 'term:resize': ({ cols, rows }, { set }) => {
1129
+ * // handle resize
1130
+ * },
1131
+ * }
1132
+ * )
1133
+ *
1134
+ * function Counter() {
1135
+ * const count = useApp(s => s.count)
1136
+ * return <Text>Count: {count}</Text>
1137
+ * }
1138
+ *
1139
+ * const term = createTermProvider(process.stdin, process.stdout)
1140
+ * await app.run(<Counter />, { term })
1141
+ *
1142
+ * // Frame iteration:
1143
+ * for await (const frame of app.run(<Counter />, { term })) {
1144
+ * expect(frame.text).toContain('Count:')
1145
+ * }
1146
+ * ```
1147
+ */
1148
+ var import_jsx_runtime = require_jsx_runtime();
1149
+ const log = createLogger("silvery:app");
1150
+ const ENV = typeof process$1 !== "undefined" ? process$1.env : void 0;
1151
+ const NO_INCREMENTAL = ENV?.SILVERY_NO_INCREMENTAL === "1";
1152
+ const STRICT_MODE = (() => {
1153
+ const v = ENV?.SILVERY_STRICT;
1154
+ return !!v && v !== "0" && v !== "false";
1155
+ })();
1156
+ const CELL_DEBUG = (() => {
1157
+ const v = ENV?.SILVERY_CELL_DEBUG;
1158
+ if (!v || !v.includes(",")) return null;
1159
+ const [cx, cy] = v.split(",").map(Number);
1160
+ if (!Number.isFinite(cx) || !Number.isFinite(cy)) return null;
1161
+ return {
1162
+ x: cx,
1163
+ y: cy
1164
+ };
1165
+ })();
1166
+ const INSTRUMENTED = STRICT_MODE || CELL_DEBUG !== null;
1167
+ /**
1168
+ * Check if value is a Provider with events (full interface).
1169
+ */
1170
+ function isFullProvider(value) {
1171
+ if (value === null || value === void 0) return false;
1172
+ if (typeof value !== "object" && typeof value !== "function") return false;
1173
+ return "getState" in value && "subscribe" in value && "events" in value && typeof value.getState === "function" && typeof value.subscribe === "function" && typeof value.events === "function";
1174
+ }
1175
+ /**
1176
+ * Check if value is a basic Provider (just getState/subscribe, Zustand-compatible).
1177
+ */
1178
+ function isBasicProvider(value) {
1179
+ if (value === null || value === void 0) return false;
1180
+ if (typeof value !== "object" && typeof value !== "function") return false;
1181
+ return "getState" in value && "subscribe" in value && typeof value.getState === "function" && typeof value.subscribe === "function";
1182
+ }
1183
+ const StoreContext = createContext(null);
1184
+ /**
1185
+ * Hook for accessing app state with selectors.
1186
+ *
1187
+ * @example
1188
+ * ```tsx
1189
+ * const count = useApp(s => s.count)
1190
+ * const { count, increment } = useApp(s => ({ count: s.count, increment: s.increment }))
1191
+ * ```
1192
+ */
1193
+ function useApp(selector) {
1194
+ const store = useContext(StoreContext);
1195
+ if (!store) throw new Error("useApp must be used within createApp().run()");
1196
+ const [state, setState] = React.useState(() => selector(store.getState()));
1197
+ const selectorRef = useRef(selector);
1198
+ selectorRef.current = selector;
1199
+ useEffect(() => {
1200
+ return store.subscribe((newState) => {
1201
+ const next = selectorRef.current(newState);
1202
+ setState((prev) => Object.is(prev, next) ? prev : next);
1203
+ });
1204
+ }, [store]);
1205
+ return state;
1206
+ }
1207
+ /**
1208
+ * Create an app with Zustand store and provider integration.
1209
+ *
1210
+ * This is Layer 3 - it provides:
1211
+ * - Zustand store with fine-grained subscriptions
1212
+ * - Providers as unified stores + event sources
1213
+ * - Event handlers namespaced as 'provider:event'
1214
+ *
1215
+ * @param factory Store factory function that receives providers
1216
+ * @param handlers Optional event handlers (namespaced as 'provider:event')
1217
+ */
1218
+ function createApp(factory, handlers) {
1219
+ return { run(element, options = {}) {
1220
+ let handlePromise = null;
1221
+ const init = () => {
1222
+ if (handlePromise) return handlePromise;
1223
+ handlePromise = initApp(factory, handlers, element, options);
1224
+ return handlePromise;
1225
+ };
1226
+ return {
1227
+ then(onfulfilled, onrejected) {
1228
+ return init().then(onfulfilled, onrejected);
1229
+ },
1230
+ [Symbol.asyncIterator]() {
1231
+ let handle = null;
1232
+ let iterator = null;
1233
+ let started = false;
1234
+ return {
1235
+ async next() {
1236
+ if (!started) {
1237
+ started = true;
1238
+ handle = await init();
1239
+ iterator = handle[Symbol.asyncIterator]();
1240
+ }
1241
+ return iterator.next();
1242
+ },
1243
+ async return() {
1244
+ if (handle) handle.unmount();
1245
+ return {
1246
+ done: true,
1247
+ value: void 0
1248
+ };
1249
+ }
1250
+ };
1251
+ }
1252
+ };
1253
+ } };
1254
+ }
1255
+ /**
1256
+ * Initialize the app — extracted from run() for clarity.
1257
+ */
1258
+ async function initApp(factory, handlers, element, options) {
1259
+ const { cols: explicitCols, rows: explicitRows, stdout: explicitStdout, stdin = process$1.stdin, signal: externalSignal, alternateScreen = false, kittyMode: explicitKittyMode, kitty: kittyOption, mouse: mouseOption = false, virtualInline: virtualInlineOption = false, suspendOnCtrlZ: suspendOption = true, exitOnCtrlC: exitOnCtrlCOption = true, onSuspend: onSuspendHook, onResume: onResumeHook, onInterrupt: onInterruptHook, textSizing: textSizingOption, widthDetection: widthDetectionOption, focusReporting: focusReportingOption = false, selection: selectionOption, caps: capsOption, guardOutput: guardOutputOption, Root: RootComponent, capabilityRegistry: capabilityRegistryOption, writable: explicitWritable, onResize: explicitOnResize, ...injectValues } = options;
1260
+ const useKittyMode = explicitKittyMode ?? !!kittyOption;
1261
+ const headless = explicitCols != null && explicitRows != null && !explicitStdout || explicitWritable != null;
1262
+ const cols = explicitCols ?? process$1.stdout.columns ?? 80;
1263
+ const rows = explicitRows ?? process$1.stdout.rows ?? 24;
1264
+ const stdout = explicitStdout ?? process$1.stdout;
1265
+ const isRealStdout = stdout === process$1.stdout;
1266
+ const shouldGuardOutput = guardOutputOption ?? (alternateScreen && !headless && isRealStdout);
1267
+ let outputGuard = null;
1268
+ await ensureLayoutEngine();
1269
+ const controller = new AbortController();
1270
+ const signal = controller.signal;
1271
+ if (externalSignal) if (externalSignal.aborted) controller.abort();
1272
+ else externalSignal.addEventListener("abort", () => controller.abort(), { once: true });
1273
+ const providers = {};
1274
+ const plainValues = {};
1275
+ const providerCleanups = [];
1276
+ let termProvider = null;
1277
+ if (!("term" in injectValues) || !isFullProvider(injectValues.term)) {
1278
+ const resizeListeners = /* @__PURE__ */ new Set();
1279
+ const termStdout = headless ? {
1280
+ columns: cols,
1281
+ rows,
1282
+ write: () => true,
1283
+ isTTY: false,
1284
+ on(event, handler) {
1285
+ if (event === "resize") resizeListeners.add(handler);
1286
+ return termStdout;
1287
+ },
1288
+ off(event, handler) {
1289
+ if (event === "resize") resizeListeners.delete(handler);
1290
+ return termStdout;
1291
+ }
1292
+ } : stdout;
1293
+ const termStdin = headless ? {
1294
+ isTTY: false,
1295
+ on: () => termStdin,
1296
+ off: () => termStdin,
1297
+ setRawMode: () => {},
1298
+ resume: () => {},
1299
+ pause: () => {},
1300
+ setEncoding: () => {}
1301
+ } : stdin;
1302
+ termProvider = createTermProvider(termStdin, termStdout, {
1303
+ cols,
1304
+ rows
1305
+ });
1306
+ providers.term = termProvider;
1307
+ providerCleanups.push(() => termProvider[Symbol.dispose]());
1308
+ if (headless && explicitOnResize) {
1309
+ const unsub = explicitOnResize((dims) => {
1310
+ currentDims = dims;
1311
+ termStdout.columns = dims.cols;
1312
+ termStdout.rows = dims.rows;
1313
+ for (const listener of resizeListeners) listener();
1314
+ });
1315
+ providerCleanups.push(unsub);
1316
+ }
1317
+ }
1318
+ for (const [name, value] of Object.entries(injectValues)) if (isFullProvider(value)) providers[name] = value;
1319
+ else plainValues[name] = value;
1320
+ const inject = {
1321
+ ...providers,
1322
+ ...plainValues
1323
+ };
1324
+ const stateUnsubscribes = [];
1325
+ const store = createStore((set, get, api) => {
1326
+ const mergedState = { ...factory(inject)(set, get, api) };
1327
+ for (const [name, provider] of Object.entries(providers)) {
1328
+ mergedState[name] = provider;
1329
+ if (isBasicProvider(provider)) {
1330
+ const unsub = provider.subscribe((_providerState) => {});
1331
+ stateUnsubscribes.push(unsub);
1332
+ }
1333
+ }
1334
+ for (const [name, value] of Object.entries(plainValues)) mergedState[name] = value;
1335
+ return mergedState;
1336
+ });
1337
+ let currentDims = {
1338
+ cols,
1339
+ rows
1340
+ };
1341
+ if (!headless) {
1342
+ const onStdoutResize = () => {
1343
+ currentDims = {
1344
+ cols: stdout.columns || 80,
1345
+ rows: stdout.rows || 24
1346
+ };
1347
+ for (const listener of mockTermSubscribers) listener(currentDims);
1348
+ };
1349
+ stdout.on("resize", onStdoutResize);
1350
+ providerCleanups.push(() => stdout.off("resize", onStdoutResize));
1351
+ }
1352
+ let shouldExit = false;
1353
+ let renderPaused = false;
1354
+ let isRendering = false;
1355
+ let inEventHandler = false;
1356
+ let pendingRerender = false;
1357
+ const _ansiTrace = !headless && process$1.env?.SILVERY_TRACE === "1";
1358
+ let _traceSeq = 0;
1359
+ const _traceStart = performance.now();
1360
+ let _origStdoutWrite;
1361
+ if (_ansiTrace) {
1362
+ const fs = __require("node:fs");
1363
+ fs.writeFileSync("/tmp/silvery-trace.log", `=== SILVERY TRACE START ===\n`);
1364
+ _origStdoutWrite = stdout.write.bind(stdout);
1365
+ const symbolize = (s) => s.replace(/\x1b\[\?1049h/g, "⟨ALT_ON⟩").replace(/\x1b\[\?1049l/g, "⟨ALT_OFF⟩").replace(/\x1b\[2J/g, "⟨CLEAR⟩").replace(/\x1b\[H/g, "⟨HOME⟩").replace(/\x1b\[\?25l/g, "⟨CUR_HIDE⟩").replace(/\x1b\[\?25h/g, "⟨CUR_SHOW⟩").replace(/\x1b\[\?2026h/g, "⟨SYNC_ON⟩").replace(/\x1b\[\?2026l/g, "⟨SYNC_OFF⟩").replace(/\x1b\[\?2004h/g, "⟨BPASTE_ON⟩").replace(/\x1b\[\?2004l/g, "⟨BPASTE_OFF⟩").replace(/\x1b\[0m/g, "⟨RST⟩").replace(/\x1b\[(\d+);(\d+)H/g, "⟨GO $1,$2⟩").replace(/\x1b\[38;5;(\d+)m/g, "⟨F$1⟩").replace(/\x1b\[48;5;(\d+)m/g, "⟨B$1⟩").replace(/\x1b\[38;2;(\d+);(\d+);(\d+)m/g, "⟨FR$1,$2,$3⟩").replace(/\x1b\[48;2;(\d+);(\d+);(\d+)m/g, "⟨BR$1,$2,$3⟩").replace(/\x1b\[1m/g, "⟨BOLD⟩").replace(/\x1b\[2m/g, "⟨DIM⟩").replace(/\x1b\[3m/g, "⟨ITAL⟩").replace(/\x1b\[4m/g, "⟨UL⟩").replace(/\x1b\[7m/g, "⟨INV⟩").replace(/\x1b\[22m/g, "⟨/BOLD⟩").replace(/\x1b\[23m/g, "⟨/ITAL⟩").replace(/\x1b\[24m/g, "⟨/UL⟩").replace(/\x1b\[27m/g, "⟨/INV⟩").replace(/\x1b\[39m/g, "⟨/FG⟩").replace(/\x1b\[49m/g, "⟨/BG⟩").replace(/\x1b\[([0-9;]*)([A-Za-z])/g, "⟨CSI $1$2⟩").replace(/\x1b([^\[])/, "⟨ESC $1⟩");
1366
+ const traceWrite = function(chunk, ...args) {
1367
+ const str = typeof chunk === "string" ? chunk : String(chunk);
1368
+ const seq = ++_traceSeq;
1369
+ const ms = (performance.now() - _traceStart).toFixed(0);
1370
+ const decoded = symbolize(str);
1371
+ const preview = decoded.length > 400 ? decoded.slice(0, 200) + ` ...[${decoded.length}ch]... ` + decoded.slice(-100) : decoded;
1372
+ fs.appendFileSync("/tmp/silvery-trace.log", `[${String(seq).padStart(4, "0")}] +${ms}ms (${str.length}b): ${preview}\n`);
1373
+ return _origStdoutWrite.call(this, chunk, ...args);
1374
+ };
1375
+ stdout.write = traceWrite;
1376
+ providerCleanups.push(() => {
1377
+ if (_origStdoutWrite) stdout.write = _origStdoutWrite;
1378
+ });
1379
+ }
1380
+ const target = headless ? {
1381
+ write(frame) {
1382
+ if (explicitWritable) explicitWritable.write(frame);
1383
+ },
1384
+ getDims: () => currentDims
1385
+ } : {
1386
+ write(frame) {
1387
+ if (_perfLog) __require("node:fs").appendFileSync("/tmp/silvery-perf.log", `TARGET.write: ${frame.length} bytes (paused=${renderPaused})\n`);
1388
+ if (!renderPaused) if (outputGuard) outputGuard.writeStdout(frame);
1389
+ else stdout.write(frame);
1390
+ },
1391
+ getDims() {
1392
+ return currentDims;
1393
+ },
1394
+ onResize(handler) {
1395
+ const onResize = () => {
1396
+ currentDims = {
1397
+ cols: stdout.columns || 80,
1398
+ rows: stdout.rows || 24
1399
+ };
1400
+ handler(currentDims);
1401
+ };
1402
+ stdout.on("resize", onResize);
1403
+ return () => stdout.off("resize", onResize);
1404
+ }
1405
+ };
1406
+ const heuristicSupported = capsOption?.textSizingSupported ?? isTextSizingLikelySupported();
1407
+ const shouldProbe = textSizingOption === "probe" || textSizingOption === "auto" && heuristicSupported;
1408
+ const cachedProbe = shouldProbe ? getCachedProbeResult() : void 0;
1409
+ let textSizingEnabled;
1410
+ if (textSizingOption === true) textSizingEnabled = true;
1411
+ else if (textSizingOption === "probe") textSizingEnabled = cachedProbe?.supported ?? false;
1412
+ else if (textSizingOption === "auto") if (cachedProbe !== void 0) textSizingEnabled = cachedProbe.supported;
1413
+ else textSizingEnabled = heuristicSupported;
1414
+ else textSizingEnabled = false;
1415
+ const needsProbe = shouldProbe && cachedProbe === void 0 && !headless;
1416
+ const needsWidthDetection = !headless && (widthDetectionOption === true || widthDetectionOption === "auto" && capsOption != null);
1417
+ let effectiveCaps = capsOption ? {
1418
+ ...capsOption,
1419
+ textSizingSupported: textSizingEnabled
1420
+ } : void 0;
1421
+ let pipelineConfig = effectiveCaps ? createPipeline({ caps: effectiveCaps }) : void 0;
1422
+ const runtime = createRuntime({
1423
+ target,
1424
+ signal,
1425
+ mode: alternateScreen ? "fullscreen" : "inline",
1426
+ outputPhaseFn: pipelineConfig?.outputPhaseFn
1427
+ });
1428
+ let cleanedUp = false;
1429
+ let storeUnsubscribeFn = null;
1430
+ let kittyEnabled = false;
1431
+ const defaultKittyFlags = KittyFlags.DISAMBIGUATE | KittyFlags.REPORT_EVENTS | KittyFlags.REPORT_ALL_KEYS;
1432
+ let kittyFlags = defaultKittyFlags;
1433
+ let mouseEnabled = false;
1434
+ let focusReportingEnabled = false;
1435
+ const selectionEnabled = selectionOption ?? false;
1436
+ let selectionState = createTerminalSelectionState();
1437
+ const selectionListeners = /* @__PURE__ */ new Set();
1438
+ /** Notify useSelection() subscribers that selection state changed. */
1439
+ function notifySelectionListeners() {
1440
+ for (const listener of selectionListeners) listener();
1441
+ }
1442
+ const capabilityRegistry = capabilityRegistryOption ?? createCapabilityRegistry();
1443
+ let selectionBridge;
1444
+ if (selectionEnabled) {
1445
+ selectionBridge = createSelectionBridge({
1446
+ getState: () => selectionState,
1447
+ subscribe: (listener) => {
1448
+ selectionListeners.add(listener);
1449
+ return () => {
1450
+ selectionListeners.delete(listener);
1451
+ };
1452
+ },
1453
+ setRange: (range) => {
1454
+ if (range === null) {
1455
+ const [next] = terminalSelectionUpdate({ type: "clear" }, selectionState);
1456
+ selectionState = next;
1457
+ } else {
1458
+ const [s1] = terminalSelectionUpdate({
1459
+ type: "start",
1460
+ col: range.anchor.col,
1461
+ row: range.anchor.row,
1462
+ source: "keyboard"
1463
+ }, selectionState);
1464
+ const [s2] = terminalSelectionUpdate({
1465
+ type: "extend",
1466
+ col: range.head.col,
1467
+ row: range.head.row
1468
+ }, s1);
1469
+ const [s3] = terminalSelectionUpdate({ type: "finish" }, s2);
1470
+ selectionState = s3;
1471
+ }
1472
+ notifySelectionListeners();
1473
+ if (currentBuffer) runtime.invalidate();
1474
+ },
1475
+ clear: () => {
1476
+ const [next] = terminalSelectionUpdate({ type: "clear" }, selectionState);
1477
+ selectionState = next;
1478
+ notifySelectionListeners();
1479
+ if (currentBuffer) runtime.invalidate();
1480
+ }
1481
+ });
1482
+ capabilityRegistry.register(SELECTION_CAPABILITY, selectionBridge);
1483
+ }
1484
+ const scrollback = virtualInlineOption ? createVirtualScrollback() : null;
1485
+ let virtualScrollOffset = 0;
1486
+ let searchState = createSearchState();
1487
+ const focusManager = createFocusManager({ onFocusChange(oldNode, newNode, _origin) {
1488
+ if (oldNode) dispatchFocusEvent(createFocusEvent("blur", oldNode, newNode));
1489
+ if (newNode) dispatchFocusEvent(createFocusEvent("focus", newNode, oldNode));
1490
+ } });
1491
+ setOnNodeRemoved((removedNode) => focusManager.handleSubtreeRemoved(removedNode));
1492
+ const cursorStore = createCursorStore();
1493
+ const mouseEventState = createMouseEventProcessor({ focusManager });
1494
+ const cleanup = () => {
1495
+ if (cleanedUp) return;
1496
+ cleanedUp = true;
1497
+ logExitSummary();
1498
+ try {
1499
+ reconciler.updateContainerSync(null, fiberRoot, null, () => {});
1500
+ reconciler.flushSyncWork();
1501
+ } catch {}
1502
+ setOnNodeRemoved(null);
1503
+ if (storeUnsubscribeFn) storeUnsubscribeFn();
1504
+ stateUnsubscribes.forEach((unsub) => {
1505
+ try {
1506
+ unsub();
1507
+ } catch {}
1508
+ });
1509
+ if (outputGuard) {
1510
+ outputGuard.dispose();
1511
+ outputGuard = null;
1512
+ }
1513
+ if (!headless && stdin.isTTY) {
1514
+ stdin.removeAllListeners("data");
1515
+ stdin.pause();
1516
+ const sequences = [
1517
+ "\x1B[?1004l",
1518
+ disableMouse(),
1519
+ disableKittyKeyboard(),
1520
+ "\x1B[?2004l",
1521
+ "\x1B[0m",
1522
+ resetCursorStyle(),
1523
+ "\x1B[?25h",
1524
+ alternateScreen ? "\x1B[?1049l" : ""
1525
+ ].join("");
1526
+ if (stdout === process$1.stdout) {
1527
+ try {
1528
+ writeSync(stdout.fd, sequences);
1529
+ } catch {
1530
+ try {
1531
+ stdout.write(sequences);
1532
+ } catch {}
1533
+ }
1534
+ try {
1535
+ stdin.resume();
1536
+ while (stdin.read() !== null);
1537
+ stdin.pause();
1538
+ } catch {}
1539
+ } else try {
1540
+ stdout.write(sequences);
1541
+ } catch {}
1542
+ try {
1543
+ stdin.setRawMode(false);
1544
+ } catch {}
1545
+ } else if (!headless) {
1546
+ const sequences = [
1547
+ "\x1B[?1004l",
1548
+ disableMouse(),
1549
+ disableKittyKeyboard(),
1550
+ "\x1B[?2004l",
1551
+ "\x1B[0m",
1552
+ resetCursorStyle(),
1553
+ "\x1B[?25h",
1554
+ alternateScreen ? "\x1B[?1049l" : ""
1555
+ ].join("");
1556
+ try {
1557
+ stdout.write(sequences);
1558
+ } catch {}
1559
+ }
1560
+ providerCleanups.forEach((fn) => {
1561
+ try {
1562
+ fn();
1563
+ } catch {}
1564
+ });
1565
+ runtime[Symbol.dispose]();
1566
+ };
1567
+ let exit;
1568
+ const container = createContainer(() => {
1569
+ if (shouldExit) return;
1570
+ if (inEventHandler) {
1571
+ pendingRerender = true;
1572
+ return;
1573
+ }
1574
+ if (!pendingRerender) {
1575
+ pendingRerender = true;
1576
+ queueMicrotask(() => {
1577
+ if (!pendingRerender) return;
1578
+ pendingRerender = false;
1579
+ if (!shouldExit && !isRendering) {
1580
+ isRendering = true;
1581
+ try {
1582
+ currentBuffer = doRender();
1583
+ runtime.render(currentBuffer);
1584
+ } finally {
1585
+ isRendering = false;
1586
+ }
1587
+ }
1588
+ });
1589
+ }
1590
+ });
1591
+ const fiberRoot = createFiberRoot(container);
1592
+ let currentBuffer;
1593
+ const mockStdout = {
1594
+ columns: cols,
1595
+ rows,
1596
+ write: () => true,
1597
+ isTTY: false,
1598
+ on: () => mockStdout,
1599
+ off: () => mockStdout,
1600
+ once: () => mockStdout,
1601
+ removeListener: () => mockStdout,
1602
+ addListener: () => mockStdout
1603
+ };
1604
+ const baseMockTerm = createTerm({ color: "truecolor" });
1605
+ const mockTermSubscribers = /* @__PURE__ */ new Set();
1606
+ const mockTerm = Object.create(baseMockTerm, {
1607
+ getState: { value: () => currentDims },
1608
+ subscribe: { value: (listener) => {
1609
+ mockTermSubscribers.add(listener);
1610
+ return () => mockTermSubscribers.delete(listener);
1611
+ } }
1612
+ });
1613
+ const runtimeInputListeners = [];
1614
+ const runtimePasteListeners = [];
1615
+ const runtimeFocusListeners = [];
1616
+ const runtimeEventListeners = /* @__PURE__ */ new Map();
1617
+ runtimeEventListeners.set("input", runtimeInputListeners);
1618
+ runtimeEventListeners.set("paste", runtimePasteListeners);
1619
+ runtimeEventListeners.set("focus", runtimeFocusListeners);
1620
+ const runtimeContextValue = {
1621
+ on(event, handler) {
1622
+ let listeners = runtimeEventListeners.get(event);
1623
+ if (!listeners) {
1624
+ listeners = [];
1625
+ runtimeEventListeners.set(event, listeners);
1626
+ }
1627
+ listeners.push(handler);
1628
+ return () => {
1629
+ const idx = listeners.indexOf(handler);
1630
+ if (idx >= 0) listeners.splice(idx, 1);
1631
+ };
1632
+ },
1633
+ emit(event, ...args) {
1634
+ const listeners = runtimeEventListeners.get(event);
1635
+ if (listeners) for (const listener of listeners) listener(...args);
1636
+ },
1637
+ exit: () => exit()
1638
+ };
1639
+ const Root = RootComponent ?? React.Fragment;
1640
+ const cacheBackend = !alternateScreen ? "terminal" : virtualInlineOption ? "virtual" : "retain";
1641
+ const wrappedElement = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SilveryErrorBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CursorProvider, {
1642
+ store: cursorStore,
1643
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CacheBackendContext.Provider, {
1644
+ value: cacheBackend,
1645
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TermContext.Provider, {
1646
+ value: mockTerm,
1647
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StdoutContext.Provider, {
1648
+ value: {
1649
+ stdout: mockStdout,
1650
+ write: () => {},
1651
+ notifyScrollback: (lines) => runtime.addScrollbackLines(lines),
1652
+ promoteScrollback: (content, lines) => runtime.promoteScrollback(content, lines),
1653
+ resetInlineCursor: () => runtime.resetInlineCursor(),
1654
+ getInlineCursorRow: () => runtime.getInlineCursorRow()
1655
+ },
1656
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StderrContext.Provider, {
1657
+ value: {
1658
+ stderr: process$1.stderr,
1659
+ write: (data) => {
1660
+ process$1.stderr.write(data);
1661
+ }
1662
+ },
1663
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FocusManagerContext.Provider, {
1664
+ value: focusManager,
1665
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RuntimeContext.Provider, {
1666
+ value: runtimeContextValue,
1667
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CapabilityRegistryContext.Provider, {
1668
+ value: capabilityRegistry,
1669
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Root, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StoreContext.Provider, {
1670
+ value: store,
1671
+ children: element
1672
+ }) })
1673
+ })
1674
+ })
1675
+ })
1676
+ })
1677
+ })
1678
+ })
1679
+ })
1680
+ }) });
1681
+ let _renderCount = 0;
1682
+ let _eventStart = 0;
1683
+ const _perfLog = typeof process$1 !== "undefined" && process$1.env?.DEBUG?.includes("silvery:perf");
1684
+ const _noIncremental = NO_INCREMENTAL;
1685
+ let _ag = null;
1686
+ let _lastTermBuffer = null;
1687
+ function doRender() {
1688
+ _renderCount++;
1689
+ if (_ansiTrace) __require("node:fs").appendFileSync("/tmp/silvery-trace.log", `--- doRender #${_renderCount} (ag=${_ag ? "reuse" : "create"}, incremental=${!_noIncremental}) ---\n`);
1690
+ const renderStart = performance.now();
1691
+ reconciler.updateContainerSync(wrappedElement, fiberRoot, null, () => {});
1692
+ reconciler.flushSyncWork();
1693
+ const reconcileMs = performance.now() - renderStart;
1694
+ {
1695
+ const acc = globalThis.__silvery_bench_phases;
1696
+ if (acc) acc.reconcile += reconcileMs;
1697
+ }
1698
+ const pipelineStart = performance.now();
1699
+ const rootNode = getContainerRoot(container);
1700
+ const dims = runtime.getDims();
1701
+ const isInline = !alternateScreen;
1702
+ if (!_ag) _ag = createAg(rootNode, { measurer: pipelineConfig?.measurer });
1703
+ if (_ag) {
1704
+ const lastBuffer = _lastTermBuffer;
1705
+ if (lastBuffer) {
1706
+ const widthChanged = dims.cols !== lastBuffer.width;
1707
+ const heightChanged = !isInline && dims.rows !== lastBuffer.height;
1708
+ if (widthChanged || heightChanged) {
1709
+ _ag.resetBuffer();
1710
+ runtime.invalidate();
1711
+ }
1712
+ }
1713
+ }
1714
+ if (INSTRUMENTED) {
1715
+ globalThis.__silvery_content_all = void 0;
1716
+ globalThis.__silvery_node_trace = void 0;
1717
+ globalThis.__silvery_cell_debug = CELL_DEBUG !== null ? {
1718
+ x: CELL_DEBUG.x,
1719
+ y: CELL_DEBUG.y,
1720
+ log: []
1721
+ } : void 0;
1722
+ }
1723
+ const rootHasDirty = rootNode.layoutDirty || isAnyDirty(rootNode.dirtyBits, rootNode.dirtyEpoch);
1724
+ const dimsChanged = _lastTermBuffer != null && (dims.cols !== _lastTermBuffer.width || dims.rows !== _lastTermBuffer.height);
1725
+ if (!rootHasDirty && !dimsChanged && _lastTermBuffer && currentBuffer) return currentBuffer;
1726
+ if (_noIncremental) _ag.resetBuffer();
1727
+ _ag.layout(dims);
1728
+ const { buffer: termBuffer, prevBuffer: agPrevBuffer } = _ag.render();
1729
+ _lastTermBuffer = termBuffer;
1730
+ const wasIncremental = !_noIncremental && agPrevBuffer !== null;
1731
+ const pipelineMs = performance.now() - pipelineStart;
1732
+ globalThis.__silvery_last_pipeline = {
1733
+ layout: pipelineMs,
1734
+ output: 0,
1735
+ total: pipelineMs,
1736
+ incremental: wasIncremental
1737
+ };
1738
+ globalThis.__silvery_render_count = (globalThis.__silvery_render_count ?? 0) + 1;
1739
+ {
1740
+ const acc = globalThis.__silvery_bench_phases;
1741
+ if (acc) {
1742
+ acc.total += pipelineMs;
1743
+ acc.pipelineCalls += 1;
1744
+ }
1745
+ }
1746
+ if (STRICT_MODE && wasIncremental) {
1747
+ const { buffer: freshBuffer } = executeRender(rootNode, dims.cols, dims.rows, null, {
1748
+ skipLayoutNotifications: true,
1749
+ skipScrollStateUpdates: true
1750
+ }, pipelineConfig);
1751
+ const { cellEquals, bufferToText } = (init_buffer(), __toCommonJS(buffer_exports));
1752
+ for (let y = 0; y < termBuffer.height; y++) for (let x = 0; x < termBuffer.width; x++) {
1753
+ const a = termBuffer.getCell(x, y);
1754
+ const b = freshBuffer.getCell(x, y);
1755
+ if (!cellEquals(a, b)) {
1756
+ let cellDebugInfo = "";
1757
+ const savedCellDbg = globalThis.__silvery_cell_debug;
1758
+ if (savedCellDbg && savedCellDbg.x === x && savedCellDbg.y === y && savedCellDbg.log.length > 0) cellDebugInfo = `\nCELL DEBUG (${savedCellDbg.log.length} entries for (${x},${y})):\n${savedCellDbg.log.join("\n")}\n`;
1759
+ else if (savedCellDbg && savedCellDbg.x === x && savedCellDbg.y === y) cellDebugInfo = `\nCELL DEBUG: No nodes cover (${x},${y}) during incremental render\n`;
1760
+ else cellDebugInfo = `\nCELL DEBUG: Target cell (${x},${y}) differs from debug cell (${savedCellDbg?.x},${savedCellDbg?.y})\n`;
1761
+ let trapInfo = "";
1762
+ const trap = {
1763
+ x,
1764
+ y,
1765
+ log: []
1766
+ };
1767
+ globalThis.__silvery_write_trap = trap;
1768
+ try {
1769
+ executeRender(rootNode, dims.cols, dims.rows, null, {
1770
+ skipLayoutNotifications: true,
1771
+ skipScrollStateUpdates: true
1772
+ }, pipelineConfig);
1773
+ } catch {}
1774
+ globalThis.__silvery_write_trap = null;
1775
+ if (trap.log.length > 0) trapInfo = `\nWRITE TRAP (${trap.log.length} writes to (${x},${y})):\n${trap.log.join("\n")}\n`;
1776
+ else trapInfo = `\nWRITE TRAP: NO WRITES to (${x},${y})\n`;
1777
+ const incText = bufferToText(termBuffer);
1778
+ const freshText = bufferToText(freshBuffer);
1779
+ const cellStr = (c) => `char=${JSON.stringify(c.char)} fg=${c.fg} bg=${c.bg} ulColor=${c.underlineColor} wide=${c.wide} cont=${c.continuation} attrs={bold=${c.attrs.bold},dim=${c.attrs.dim},italic=${c.attrs.italic},ul=${c.attrs.underline},ulStyle=${c.attrs.underlineStyle},blink=${c.attrs.blink},inv=${c.attrs.inverse},hidden=${c.attrs.hidden},strike=${c.attrs.strikethrough}}`;
1780
+ const contentAll = globalThis.__silvery_content_all;
1781
+ const statsStr = contentAll ? `\n--- render phase stats (${contentAll.length} calls) ---\n` + contentAll.map((s, i) => ` #${i}: visited=${s.nodesVisited} rendered=${s.nodesRendered} skipped=${s.nodesSkipped} clearOps=${s.clearOps} cascade="${s.cascadeNodes}" flags={C=${s.flagContentDirty} P=${s.flagStylePropsDirty} L=${s.flagLayoutChanged} S=${s.flagSubtreeDirty} Ch=${s.flagChildrenDirty} CP=${s.flagChildPositionChanged} AL=${s.flagAncestorLayoutChanged} noPrev=${s.noPrevBuffer}} scroll={containers=${s.scrollContainerCount} cleared=${s.scrollViewportCleared} reason="${s.scrollClearReason}"} normalRepaint="${s.normalRepaintReason}" prevBuf={null=${s._prevBufferNull} dimMismatch=${s._prevBufferDimMismatch} hasPrev=${s._hasPrevBuffer} layout=${s._layoutW}x${s._layoutH} prev=${s._prevW}x${s._prevH}}`).join("\n") : "";
1782
+ const msg = `SILVERY_STRICT (createApp): MISMATCH at (${x}, ${y}) on render #${_renderCount}\n incremental: ${cellStr(a)}\n fresh: ${cellStr(b)}` + statsStr + (() => {
1783
+ const traces = globalThis.__silvery_node_trace;
1784
+ if (!traces || traces.length === 0) return "";
1785
+ let out = "\n--- node trace ---";
1786
+ for (let ti = 0; ti < traces.length; ti++) {
1787
+ out += `\n renderPhase #${ti}:`;
1788
+ for (const t of traces[ti]) {
1789
+ out += `\n ${t.decision} ${t.id}(${t.type})@${t.depth} rect=${t.rect} prev=${t.prevLayout}`;
1790
+ out += ` hasPrev=${t.hasPrev} ancClr=${t.ancestorCleared} flags=[${t.flags}] layout∆=${t.layoutChanged}`;
1791
+ if (t.decision === "RENDER") {
1792
+ out += ` caa=${t.contentAreaAffected} crc=${t.contentRegionCleared} cnfr=${t.childrenNeedFreshRender}`;
1793
+ out += ` childPrev=${t.childHasPrev} childAnc=${t.childAncestorCleared} skipBg=${t.skipBgFill} bg=${t.bgColor ?? "none"}`;
1794
+ }
1795
+ }
1796
+ }
1797
+ return out;
1798
+ })() + cellDebugInfo + trapInfo + `\n--- incremental ---\n${incText}\n--- fresh ---\n${freshText}`;
1799
+ __require("node:fs").appendFileSync("/tmp/silvery-perf.log", msg + "\n");
1800
+ throw new IncrementalRenderMismatchError(msg);
1801
+ }
1802
+ }
1803
+ if (_perfLog) __require("node:fs").appendFileSync("/tmp/silvery-perf.log", `SILVERY_STRICT (createApp): render #${_renderCount} OK\n`);
1804
+ }
1805
+ const buf = createBuffer(termBuffer, rootNode);
1806
+ if (_perfLog) {
1807
+ const renderDuration = performance.now() - renderStart;
1808
+ const phases = globalThis.__silvery_last_pipeline;
1809
+ const detail = globalThis.__silvery_content_detail;
1810
+ const phaseStr = phases ? ` [measure=${phases.measure.toFixed(1)} layout=${phases.layout.toFixed(1)} content=${phases.content.toFixed(1)} output=${phases.output.toFixed(1)}]` : "";
1811
+ const detailStr = detail ? ` {visited=${detail.nodesVisited} rendered=${detail.nodesRendered} skipped=${detail.nodesSkipped} noPrev=${detail.noPrevBuffer ?? 0} dirty=${detail.flagContentDirty ?? 0} paint=${detail.flagStylePropsDirty ?? 0} layoutChg=${detail.flagLayoutChanged ?? 0} subtree=${detail.flagSubtreeDirty ?? 0} children=${detail.flagChildrenDirty ?? 0} childPos=${detail.flagChildPositionChanged ?? 0} scroll=${detail.scrollContainerCount ?? 0}/${detail.scrollViewportCleared ?? 0}${detail.scrollClearReason ? `(${detail.scrollClearReason})` : ""}}${detail.cascadeNodes ? ` CASCADE[minDepth=${detail.cascadeMinDepth} ${detail.cascadeNodes}]` : ""}` : "";
1812
+ __require("node:fs").appendFileSync("/tmp/silvery-perf.log", `doRender #${_renderCount}: ${renderDuration.toFixed(1)}ms (reconcile=${reconcileMs.toFixed(1)}ms pipeline=${pipelineMs.toFixed(1)}ms ${dims.cols}x${dims.rows})${phaseStr}${detailStr}\n`);
1813
+ }
1814
+ return buf;
1815
+ }
1816
+ if (_ansiTrace) __require("node:fs").appendFileSync("/tmp/silvery-trace.log", "=== INITIAL RENDER ===\n");
1817
+ currentBuffer = doRender();
1818
+ if (!headless) {
1819
+ if (_ansiTrace) __require("node:fs").appendFileSync("/tmp/silvery-trace.log", "=== ALT SCREEN + CLEAR ===\n");
1820
+ if (alternateScreen) {
1821
+ stdout.write("\x1B[?1049h");
1822
+ stdout.write("\x1B[2J\x1B[H");
1823
+ }
1824
+ stdout.write("\x1B[?25l");
1825
+ if (kittyOption != null && kittyOption !== false) if (kittyOption === true) {
1826
+ if ((await detectKittyFromStdio(stdout, stdin)).supported) {
1827
+ stdout.write(enableKittyKeyboard(defaultKittyFlags));
1828
+ kittyEnabled = true;
1829
+ kittyFlags = defaultKittyFlags;
1830
+ }
1831
+ } else {
1832
+ stdout.write(enableKittyKeyboard(kittyOption));
1833
+ kittyEnabled = true;
1834
+ kittyFlags = kittyOption;
1835
+ }
1836
+ else if (kittyOption == null) {
1837
+ stdout.write(enableKittyKeyboard(defaultKittyFlags));
1838
+ kittyEnabled = true;
1839
+ kittyFlags = defaultKittyFlags;
1840
+ }
1841
+ if (mouseOption) {
1842
+ stdout.write(enableMouse());
1843
+ mouseEnabled = true;
1844
+ }
1845
+ }
1846
+ if (_ansiTrace) __require("node:fs").appendFileSync("/tmp/silvery-trace.log", "=== RUNTIME.RENDER (initial) ===\n");
1847
+ runtime.render(currentBuffer);
1848
+ if (_perfLog) __require("node:fs").appendFileSync("/tmp/silvery-perf.log", `STARTUP: initial render done (render #${_renderCount}, incremental=${!_noIncremental})\n`);
1849
+ if (shouldGuardOutput) outputGuard = createOutputGuard();
1850
+ if (!headless) {
1851
+ runtimeContextValue.pause = () => {
1852
+ renderPaused = true;
1853
+ if (outputGuard) {
1854
+ outputGuard.dispose();
1855
+ outputGuard = null;
1856
+ }
1857
+ if (alternateScreen) stdout.write(leaveAlternateScreen());
1858
+ };
1859
+ runtimeContextValue.resume = () => {
1860
+ if (alternateScreen) stdout.write(enterAlternateScreen());
1861
+ renderPaused = false;
1862
+ if (shouldGuardOutput && !outputGuard) outputGuard = createOutputGuard();
1863
+ runtime.invalidate();
1864
+ _ag?.resetBuffer();
1865
+ if (!isRendering) {
1866
+ currentBuffer = doRender();
1867
+ runtime.render(currentBuffer);
1868
+ }
1869
+ };
1870
+ }
1871
+ let exitResolve;
1872
+ let exitResolved = false;
1873
+ const exitPromise = new Promise((resolve) => {
1874
+ exitResolve = () => {
1875
+ if (!exitResolved) {
1876
+ exitResolved = true;
1877
+ resolve();
1878
+ }
1879
+ };
1880
+ });
1881
+ exit = () => {
1882
+ if (shouldExit) return;
1883
+ shouldExit = true;
1884
+ if (!headless && stdout.isTTY) {
1885
+ const earlyDisable = [
1886
+ disableKittyKeyboard(),
1887
+ disableMouse(),
1888
+ "\x1B[?1004l"
1889
+ ].join("");
1890
+ try {
1891
+ writeSync(stdout.fd, earlyDisable);
1892
+ } catch {
1893
+ try {
1894
+ stdout.write(earlyDisable);
1895
+ } catch {}
1896
+ }
1897
+ }
1898
+ controller.abort();
1899
+ if (!inEventHandler) {
1900
+ cleanup();
1901
+ exitResolve();
1902
+ }
1903
+ };
1904
+ runtimeContextValue.exit = exit;
1905
+ let frameResolve = null;
1906
+ let framesDone = false;
1907
+ function emitFrame(buf) {
1908
+ if (frameResolve) {
1909
+ const resolve = frameResolve;
1910
+ frameResolve = null;
1911
+ resolve(buf);
1912
+ }
1913
+ }
1914
+ storeUnsubscribeFn = store.subscribe(() => {
1915
+ if (shouldExit) return;
1916
+ if (_ansiTrace) {
1917
+ const _case = inEventHandler ? "1:event" : isRendering ? "2:rendering" : "3:standalone";
1918
+ const stack = (/* @__PURE__ */ new Error()).stack?.split("\n").slice(1, 5).join("\n") ?? "";
1919
+ __require("node:fs").appendFileSync("/tmp/silvery-trace.log", `=== SUBSCRIPTION (case ${_case}, render #${_renderCount + 1}) ===\n${stack}\n`);
1920
+ }
1921
+ if (inEventHandler) {
1922
+ pendingRerender = true;
1923
+ return;
1924
+ }
1925
+ if (isRendering) {
1926
+ if (!pendingRerender) {
1927
+ pendingRerender = true;
1928
+ queueMicrotask(() => {
1929
+ if (!pendingRerender) return;
1930
+ pendingRerender = false;
1931
+ if (!shouldExit && !isRendering) {
1932
+ if (_perfLog) __require("node:fs").appendFileSync("/tmp/silvery-perf.log", `SUBSCRIPTION: deferred microtask render (case 2, render #${_renderCount + 1})\n`);
1933
+ isRendering = true;
1934
+ try {
1935
+ currentBuffer = doRender();
1936
+ runtime.render(currentBuffer);
1937
+ } finally {
1938
+ isRendering = false;
1939
+ }
1940
+ }
1941
+ });
1942
+ }
1943
+ return;
1944
+ }
1945
+ if (_perfLog) __require("node:fs").appendFileSync("/tmp/silvery-perf.log", `SUBSCRIPTION: immediate render (case 3, render #${_renderCount + 1})\n`);
1946
+ isRendering = true;
1947
+ try {
1948
+ currentBuffer = doRender();
1949
+ runtime.render(currentBuffer);
1950
+ } finally {
1951
+ isRendering = false;
1952
+ }
1953
+ });
1954
+ function createProviderEventStream(name, provider) {
1955
+ return map(provider.events(), (event) => ({
1956
+ type: `${name}:${String(event.type)}`,
1957
+ provider: name,
1958
+ event: String(event.type),
1959
+ data: event.data
1960
+ }));
1961
+ }
1962
+ /**
1963
+ * Write selection overlay to stdout after a render.
1964
+ * Appends inverse-video ANSI sequences over selected cells.
1965
+ */
1966
+ function writeSelectionOverlay() {
1967
+ if (!selectionEnabled || !selectionState.range || !currentBuffer) return;
1968
+ const mode = alternateScreen ? "fullscreen" : "inline";
1969
+ const overlay = renderSelectionOverlay(selectionState.range, currentBuffer._buffer, mode, selectionState.scope);
1970
+ if (overlay) target.write(overlay);
1971
+ }
1972
+ /**
1973
+ * Push the current rendered frame to the virtual scrollback buffer.
1974
+ */
1975
+ function pushToScrollback() {
1976
+ if (!scrollback || !currentBuffer) return;
1977
+ const lines = currentBuffer.text.split("\n");
1978
+ scrollback.push(lines);
1979
+ }
1980
+ /**
1981
+ * Render the virtual scrollback view (historical content) to the terminal.
1982
+ * When scrolled up, replaces the live app content with historical rows.
1983
+ */
1984
+ function renderVirtualScrollbackView() {
1985
+ if (!scrollback || virtualScrollOffset <= 0) return;
1986
+ const dims = target.getDims();
1987
+ const rows = scrollback.getVisibleRows(virtualScrollOffset, dims.rows);
1988
+ let out = "";
1989
+ for (let row = 0; row < rows.length; row++) out += `\x1b[${row + 1};1H\x1b[2K${rows[row] ?? ""}`;
1990
+ const indicator = ` ↑ ${virtualScrollOffset} lines `;
1991
+ const indicatorCol = Math.max(1, dims.cols - indicator.length + 1);
1992
+ out += `\x1b[1;${indicatorCol}H\x1b[7m${indicator}\x1b[27m`;
1993
+ target.write(out);
1994
+ }
1995
+ /**
1996
+ * Render search highlights for the current match with inverse video.
1997
+ */
1998
+ function renderSearchHighlights() {
1999
+ if (!searchState.active || searchState.currentMatch < 0) return;
2000
+ const match = searchState.matches[searchState.currentMatch];
2001
+ if (!match) return;
2002
+ const dims = target.getDims();
2003
+ let screenRow;
2004
+ if (scrollback && virtualScrollOffset > 0) {
2005
+ const firstVisibleLine = scrollback.totalLines - virtualScrollOffset - dims.rows;
2006
+ screenRow = match.row - firstVisibleLine;
2007
+ } else screenRow = match.row;
2008
+ if (screenRow < 0 || screenRow >= dims.rows) return;
2009
+ let out = `\x1b[${screenRow + 1};${match.startCol + 1}H\x1b[7m`;
2010
+ for (let col = match.startCol; col <= match.endCol; col++) if (currentBuffer && virtualScrollOffset <= 0) out += currentBuffer._buffer.getCell(col, screenRow).char;
2011
+ else out += searchState.query[col - match.startCol] ?? " ";
2012
+ out += "\x1B[27m";
2013
+ target.write(out);
2014
+ }
2015
+ /**
2016
+ * Render the search bar at the bottom of the screen.
2017
+ */
2018
+ function renderSearchBarOverlay() {
2019
+ if (!searchState.active) return;
2020
+ const dims = target.getDims();
2021
+ const bar = renderSearchBar(searchState, dims.cols);
2022
+ target.write(`\x1b[${dims.rows};1H${bar}`);
2023
+ }
2024
+ /**
2025
+ * Search function for virtual scrollback — converts line matches to SearchMatch[].
2026
+ */
2027
+ function searchScrollback(query) {
2028
+ if (!scrollback || !query) return [];
2029
+ const matchingLines = scrollback.search(query);
2030
+ const lowerQuery = query.toLowerCase();
2031
+ const matches = [];
2032
+ for (const lineIdx of matchingLines) {
2033
+ const plain = (scrollback.getVisibleRows(scrollback.totalLines - lineIdx - 1, 1)[0] ?? "").replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
2034
+ let col = plain.toLowerCase().indexOf(lowerQuery);
2035
+ while (col !== -1) {
2036
+ matches.push({
2037
+ row: lineIdx,
2038
+ startCol: col,
2039
+ endCol: col + query.length - 1
2040
+ });
2041
+ col = plain.toLowerCase().indexOf(lowerQuery, col + 1);
2042
+ }
2043
+ }
2044
+ return matches;
2045
+ }
2046
+ /**
2047
+ * Run a single event's handler (state mutation only, no render).
2048
+ * Returns true if processing should continue, false if app should exit.
2049
+ *
2050
+ * Intercepts mouse events for selection and virtual inline mode.
2051
+ */
2052
+ function runEventHandler(event) {
2053
+ if (scrollback && searchState.active && event.type === "term:key") {
2054
+ const data = event.data;
2055
+ if (data.key.escape) {
2056
+ const [next] = searchUpdate({ type: "close" }, searchState);
2057
+ searchState = next;
2058
+ virtualScrollOffset = 0;
2059
+ return true;
2060
+ }
2061
+ if (data.key.return && !data.key.shift) {
2062
+ const [next, effects] = searchUpdate({ type: "nextMatch" }, searchState, searchScrollback);
2063
+ searchState = next;
2064
+ for (const eff of effects) if (eff.type === "scrollTo") virtualScrollOffset = Math.max(0, scrollback.totalLines - eff.row - target.getDims().rows);
2065
+ return true;
2066
+ }
2067
+ if (data.key.return && data.key.shift) {
2068
+ const [next, effects] = searchUpdate({ type: "prevMatch" }, searchState, searchScrollback);
2069
+ searchState = next;
2070
+ for (const eff of effects) if (eff.type === "scrollTo") virtualScrollOffset = Math.max(0, scrollback.totalLines - eff.row - target.getDims().rows);
2071
+ return true;
2072
+ }
2073
+ if (data.key.backspace) {
2074
+ const [next, effects] = searchUpdate({ type: "backspace" }, searchState, searchScrollback);
2075
+ searchState = next;
2076
+ for (const eff of effects) if (eff.type === "scrollTo") virtualScrollOffset = Math.max(0, scrollback.totalLines - eff.row - target.getDims().rows);
2077
+ return true;
2078
+ }
2079
+ if (data.key.leftArrow) {
2080
+ const [next] = searchUpdate({ type: "cursorLeft" }, searchState);
2081
+ searchState = next;
2082
+ return true;
2083
+ }
2084
+ if (data.key.rightArrow) {
2085
+ const [next] = searchUpdate({ type: "cursorRight" }, searchState);
2086
+ searchState = next;
2087
+ return true;
2088
+ }
2089
+ if (data.input && !data.key.ctrl && !data.key.meta) {
2090
+ const [next, effects] = searchUpdate({
2091
+ type: "input",
2092
+ char: data.input
2093
+ }, searchState, searchScrollback);
2094
+ searchState = next;
2095
+ for (const eff of effects) if (eff.type === "scrollTo") virtualScrollOffset = Math.max(0, scrollback.totalLines - eff.row - target.getDims().rows);
2096
+ return true;
2097
+ }
2098
+ }
2099
+ if (scrollback && event.type === "term:key") {
2100
+ const data = event.data;
2101
+ if (data.input === "f" && data.key.ctrl) {
2102
+ const [next] = searchUpdate({ type: "open" }, searchState);
2103
+ searchState = next;
2104
+ return true;
2105
+ }
2106
+ }
2107
+ if (scrollback && event.event === "mouse" && event.data) {
2108
+ const mouseData = event.data;
2109
+ if (mouseData.action === "wheel") {
2110
+ const scrollLines = 3;
2111
+ if (mouseData.delta && mouseData.delta < 0) virtualScrollOffset = Math.min(virtualScrollOffset + scrollLines, Math.max(0, scrollback.totalLines - target.getDims().rows));
2112
+ else virtualScrollOffset = Math.max(0, virtualScrollOffset - scrollLines);
2113
+ return true;
2114
+ }
2115
+ }
2116
+ if (selectionEnabled && event.event === "mouse" && event.data) {
2117
+ const mouseData = event.data;
2118
+ if (mouseData.button === 0) {
2119
+ if (mouseData.action === "down") {
2120
+ if (selectionState.range) {
2121
+ const [cleared] = terminalSelectionUpdate({ type: "clear" }, selectionState);
2122
+ selectionState = cleared;
2123
+ }
2124
+ const agRoot = getContainerRoot(container);
2125
+ const hit = agRoot ? selectionHitTest(agRoot, mouseData.x, mouseData.y) : null;
2126
+ const scope = hit ? findContainBoundary(hit) : null;
2127
+ const [next] = terminalSelectionUpdate({
2128
+ type: "start",
2129
+ col: mouseData.x,
2130
+ row: mouseData.y,
2131
+ scope
2132
+ }, selectionState);
2133
+ selectionState = next;
2134
+ notifySelectionListeners();
2135
+ if (currentBuffer) {
2136
+ runtime.invalidate();
2137
+ currentBuffer = doRender();
2138
+ runtime.render(currentBuffer);
2139
+ writeSelectionOverlay();
2140
+ }
2141
+ } else if (mouseData.action === "move" && selectionState.selecting) {
2142
+ const [next] = terminalSelectionUpdate({
2143
+ type: "extend",
2144
+ col: mouseData.x,
2145
+ row: mouseData.y
2146
+ }, selectionState);
2147
+ selectionState = next;
2148
+ notifySelectionListeners();
2149
+ if (currentBuffer) {
2150
+ runtime.render(currentBuffer);
2151
+ writeSelectionOverlay();
2152
+ }
2153
+ return true;
2154
+ } else if (mouseData.action === "up" && selectionState.selecting) {
2155
+ const [next] = terminalSelectionUpdate({ type: "finish" }, selectionState);
2156
+ selectionState = next;
2157
+ notifySelectionListeners();
2158
+ if (next.range && currentBuffer) {
2159
+ const text = extractText(currentBuffer._buffer, next.range, { scope: next.scope });
2160
+ if (text.length > 0) {
2161
+ const base64 = globalThis.Buffer.from(text).toString("base64");
2162
+ target.write(`\x1b]52;c;${base64}\x07`);
2163
+ }
2164
+ }
2165
+ if (currentBuffer) {
2166
+ runtime.render(currentBuffer);
2167
+ writeSelectionOverlay();
2168
+ }
2169
+ }
2170
+ }
2171
+ }
2172
+ if (selectionEnabled && event.type === "term:key" && selectionState.range) {
2173
+ const [next] = terminalSelectionUpdate({ type: "clear" }, selectionState);
2174
+ selectionState = next;
2175
+ notifySelectionListeners();
2176
+ if (currentBuffer) {
2177
+ runtime.invalidate();
2178
+ currentBuffer = doRender();
2179
+ runtime.render(currentBuffer);
2180
+ }
2181
+ }
2182
+ if (scrollback && virtualScrollOffset > 0 && event.type === "term:key") {
2183
+ virtualScrollOffset = 0;
2184
+ return true;
2185
+ }
2186
+ return invokeEventHandler(event, handlers, createHandlerContext(store, focusManager, container), mouseEventState, container);
2187
+ }
2188
+ /**
2189
+ * Process a batch of events — run all handlers, then render once.
2190
+ *
2191
+ * This is the key optimization for press-and-hold / auto-repeat keys.
2192
+ * When events arrive faster than renders (e.g., 30/sec auto-repeat vs
2193
+ * 50ms renders), we batch all pending handlers into a single render pass.
2194
+ *
2195
+ * For a batch of 3 'j' presses: handler1 → handler2 → handler3 → render.
2196
+ * The cursor moves 3 positions, but we only pay one render cost.
2197
+ */
2198
+ async function processEventBatch(events) {
2199
+ try {
2200
+ var _usingCtx$1 = _usingCtx();
2201
+ if (shouldExit || events.length === 0) return null;
2202
+ _renderCount = 0;
2203
+ _eventStart = performance.now();
2204
+ const _perfSpan = _usingCtx$1.u(perfLog.span?.("keypress", (() => {
2205
+ startTracking();
2206
+ const keyEvents = events.filter((e) => e.type === "term:key");
2207
+ return { key: keyEvents.length > 0 ? keyEvents.map((e) => e.data.input).join(",") : events[0]?.type ?? "unknown" };
2208
+ })()));
2209
+ if (!headless) {
2210
+ for (let i = events.length - 1; i >= 0; i--) {
2211
+ const event = events[i];
2212
+ if (event.type !== "term:key") continue;
2213
+ const data = event.data;
2214
+ if (data.input === "z" && data.key.ctrl && suspendOption) if (!(onSuspendHook?.() === false)) {
2215
+ events.splice(i, 1);
2216
+ performSuspend(captureTerminalState({
2217
+ alternateScreen,
2218
+ cursorHidden: true,
2219
+ mouse: mouseEnabled,
2220
+ kitty: kittyEnabled,
2221
+ kittyFlags,
2222
+ bracketedPaste: true,
2223
+ rawMode: true,
2224
+ focusReporting: focusReportingEnabled
2225
+ }), stdout, stdin, () => {
2226
+ runtime.invalidate();
2227
+ onResumeHook?.();
2228
+ });
2229
+ } else events.splice(i, 1);
2230
+ if (data.input === "c" && data.key.ctrl && exitOnCtrlCOption) {
2231
+ if (!(onInterruptHook?.() === false)) {
2232
+ exit();
2233
+ return null;
2234
+ }
2235
+ events.splice(i, 1);
2236
+ }
2237
+ }
2238
+ if (events.length === 0) return null;
2239
+ }
2240
+ inEventHandler = true;
2241
+ isRendering = true;
2242
+ for (const event of events) {
2243
+ if (event.type === "term:key") {
2244
+ const { input, key: parsedKey } = event.data;
2245
+ updateKeyboardModifiers(mouseEventState, parsedKey);
2246
+ if (parsedKey.eventType === "release" || isModifierOnlyEvent(input, parsedKey)) {
2247
+ for (const listener of runtimeInputListeners) listener(input, parsedKey);
2248
+ if (shouldExit) {
2249
+ inEventHandler = false;
2250
+ return null;
2251
+ }
2252
+ continue;
2253
+ }
2254
+ let focusConsumed = false;
2255
+ if (focusManager.activeElement) focusConsumed = handleFocusNavigation(input, parsedKey, focusManager, container) === "consumed";
2256
+ if (!focusConsumed) for (const listener of runtimeInputListeners) listener(input, parsedKey);
2257
+ } else if (event.type === "term:paste") {
2258
+ const { text } = event.data;
2259
+ for (const listener of runtimePasteListeners) listener(text);
2260
+ } else if (event.type === "term:focus") {
2261
+ const { focused } = event.data;
2262
+ for (const listener of runtimeFocusListeners) listener(focused);
2263
+ }
2264
+ if (shouldExit) {
2265
+ inEventHandler = false;
2266
+ return null;
2267
+ }
2268
+ if (event.type === "term:key") {
2269
+ const { input, key: k } = event.data;
2270
+ if (k.eventType === "release") continue;
2271
+ if (isModifierOnlyEvent(input, k)) continue;
2272
+ }
2273
+ const result = runEventHandler(event);
2274
+ if (result === false) {
2275
+ isRendering = false;
2276
+ inEventHandler = false;
2277
+ exit();
2278
+ return null;
2279
+ }
2280
+ if (result === "flush") {
2281
+ pendingRerender = false;
2282
+ currentBuffer = doRender();
2283
+ runtime.render(currentBuffer);
2284
+ await Promise.resolve();
2285
+ if (pendingRerender) {
2286
+ pendingRerender = false;
2287
+ currentBuffer = doRender();
2288
+ runtime.render(currentBuffer);
2289
+ }
2290
+ }
2291
+ }
2292
+ pendingRerender = false;
2293
+ try {
2294
+ currentBuffer = doRender();
2295
+ } finally {
2296
+ isRendering = false;
2297
+ }
2298
+ let flushCount = 0;
2299
+ const maxFlushes = 5;
2300
+ while (flushCount < maxFlushes) {
2301
+ await Promise.resolve();
2302
+ if (!pendingRerender) break;
2303
+ pendingRerender = false;
2304
+ isRendering = true;
2305
+ try {
2306
+ currentBuffer = doRender();
2307
+ } finally {
2308
+ isRendering = false;
2309
+ }
2310
+ flushCount++;
2311
+ }
2312
+ currentBuffer._buffer.markAllRowsDirty();
2313
+ inEventHandler = false;
2314
+ const runtimeStart = performance.now();
2315
+ runtime.render(currentBuffer);
2316
+ pushToScrollback();
2317
+ if (virtualScrollOffset > 0) renderVirtualScrollbackView();
2318
+ writeSelectionOverlay();
2319
+ renderSearchHighlights();
2320
+ renderSearchBarOverlay();
2321
+ const runtimeMs = performance.now() - runtimeStart;
2322
+ if (_perfLog) {
2323
+ const totalMs = performance.now() - _eventStart;
2324
+ __require("node:fs").appendFileSync("/tmp/silvery-perf.log", `EVENT batch(${events.length} ${events[0]?.type}): ${totalMs.toFixed(1)}ms total, ${_renderCount} doRender() calls, runtime.render=${runtimeMs.toFixed(1)}ms\n---\n`);
2325
+ }
2326
+ if (_perfSpan) checkBudget(events[0]?.type ?? "batch", performance.now() - _eventStart);
2327
+ return currentBuffer;
2328
+ } catch (_) {
2329
+ _usingCtx$1.e = _;
2330
+ } finally {
2331
+ _usingCtx$1.d();
2332
+ }
2333
+ }
2334
+ const eventQueue = [];
2335
+ let eventQueueResolve = null;
2336
+ const eventLoop = async () => {
2337
+ const allEvents = merge(...Object.entries(providers).map(([name, provider]) => createProviderEventStream(name, provider)));
2338
+ const pumpEvents = async () => {
2339
+ try {
2340
+ for await (const event of takeUntil(allEvents, signal)) {
2341
+ eventQueue.push(event);
2342
+ if (eventQueueResolve) {
2343
+ const resolve = eventQueueResolve;
2344
+ eventQueueResolve = null;
2345
+ resolve();
2346
+ }
2347
+ if (shouldExit) break;
2348
+ }
2349
+ } finally {
2350
+ if (eventQueueResolve) {
2351
+ const resolve = eventQueueResolve;
2352
+ eventQueueResolve = null;
2353
+ resolve();
2354
+ }
2355
+ }
2356
+ };
2357
+ if (needsProbe) try {
2358
+ const wasRaw = stdin.isRaw;
2359
+ if (stdin.isTTY && !wasRaw) {
2360
+ stdin.setRawMode(true);
2361
+ stdin.resume();
2362
+ stdin.setEncoding("utf8");
2363
+ }
2364
+ const probeRead = () => new Promise((resolve) => {
2365
+ const onData = (data) => {
2366
+ stdin.off("data", onData);
2367
+ resolve(data);
2368
+ };
2369
+ stdin.on("data", onData);
2370
+ });
2371
+ const probeResult = await detectTextSizingSupport((data) => outputGuard ? outputGuard.writeStdout(data) : stdout.write(data), probeRead, 500);
2372
+ if (probeResult.supported !== textSizingEnabled) {
2373
+ textSizingEnabled = probeResult.supported;
2374
+ if (effectiveCaps) {
2375
+ effectiveCaps = {
2376
+ ...effectiveCaps,
2377
+ textSizingSupported: textSizingEnabled
2378
+ };
2379
+ pipelineConfig = createPipeline({ caps: effectiveCaps });
2380
+ runtime.setOutputPhaseFn(pipelineConfig.outputPhaseFn);
2381
+ }
2382
+ _ag = null;
2383
+ runtime.invalidate();
2384
+ if (!isRendering) {
2385
+ isRendering = true;
2386
+ try {
2387
+ currentBuffer = doRender();
2388
+ runtime.render(currentBuffer);
2389
+ } finally {
2390
+ isRendering = false;
2391
+ }
2392
+ }
2393
+ }
2394
+ if (stdin.isTTY && !wasRaw) {
2395
+ stdin.setRawMode(false);
2396
+ stdin.pause();
2397
+ }
2398
+ } catch {}
2399
+ if (needsWidthDetection) try {
2400
+ const wasRaw = stdin.isRaw;
2401
+ if (stdin.isTTY && !wasRaw) {
2402
+ stdin.setRawMode(true);
2403
+ stdin.resume();
2404
+ stdin.setEncoding("utf8");
2405
+ }
2406
+ const stdinHandlers = [];
2407
+ const stdinListener = (data) => {
2408
+ for (const handler of stdinHandlers) handler(data);
2409
+ };
2410
+ stdin.on("data", stdinListener);
2411
+ const detector = createWidthDetector({
2412
+ write: (data) => outputGuard ? outputGuard.writeStdout(data) : stdout.write(data),
2413
+ onData: (handler) => {
2414
+ stdinHandlers.push(handler);
2415
+ return () => {
2416
+ const idx = stdinHandlers.indexOf(handler);
2417
+ if (idx >= 0) stdinHandlers.splice(idx, 1);
2418
+ };
2419
+ },
2420
+ timeoutMs: 200
2421
+ });
2422
+ const widthConfig = await detector.detect();
2423
+ detector.dispose();
2424
+ stdin.off("data", stdinListener);
2425
+ if (effectiveCaps) {
2426
+ const updatedCaps = applyWidthConfig(effectiveCaps, widthConfig);
2427
+ if (updatedCaps.textEmojiWide !== effectiveCaps.textEmojiWide || updatedCaps.textSizingSupported !== effectiveCaps.textSizingSupported) {
2428
+ effectiveCaps = updatedCaps;
2429
+ pipelineConfig = createPipeline({ caps: effectiveCaps });
2430
+ runtime.setOutputPhaseFn(pipelineConfig.outputPhaseFn);
2431
+ _ag = null;
2432
+ runtime.invalidate();
2433
+ if (!isRendering) {
2434
+ isRendering = true;
2435
+ try {
2436
+ currentBuffer = doRender();
2437
+ runtime.render(currentBuffer);
2438
+ } finally {
2439
+ isRendering = false;
2440
+ }
2441
+ }
2442
+ }
2443
+ }
2444
+ if (stdin.isTTY && !wasRaw) {
2445
+ stdin.setRawMode(false);
2446
+ stdin.pause();
2447
+ }
2448
+ } catch {}
2449
+ pumpEvents().catch((err) => log.error?.(`pumpEvents failed: ${err}`));
2450
+ if (focusReportingOption && !focusReportingEnabled) {
2451
+ enableFocusReporting((s) => outputGuard ? outputGuard.writeStdout(s) : stdout.write(s));
2452
+ focusReportingEnabled = true;
2453
+ }
2454
+ try {
2455
+ while (!shouldExit && !signal.aborted) {
2456
+ if (eventQueue.length === 0) await new Promise((resolve) => {
2457
+ eventQueueResolve = resolve;
2458
+ signal.addEventListener("abort", () => resolve(), { once: true });
2459
+ });
2460
+ if (shouldExit || signal.aborted) break;
2461
+ if (eventQueue.length === 0) continue;
2462
+ const maxDrainSpins = 32;
2463
+ let drainSpins = 0;
2464
+ const yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
2465
+ await yieldToEventLoop();
2466
+ let prevLen = eventQueue.length;
2467
+ while (drainSpins < maxDrainSpins) {
2468
+ await yieldToEventLoop();
2469
+ const curLen = eventQueue.length;
2470
+ if (curLen === prevLen) break;
2471
+ prevLen = curLen;
2472
+ drainSpins++;
2473
+ }
2474
+ if (_perfLog) __require("node:fs").appendFileSync("/tmp/silvery-perf.log", `DRAIN: spins=${drainSpins}, batch=${eventQueue.length}\n`);
2475
+ const _g = globalThis;
2476
+ _g.__silvery_last_drain_spins = drainSpins;
2477
+ _g.__silvery_last_batch_size = eventQueue.length;
2478
+ _g.__silvery_batch_count = (_g.__silvery_batch_count ?? 0) + 1;
2479
+ const buf = await processEventBatch(eventQueue.splice(0));
2480
+ if (buf) emitFrame(buf);
2481
+ }
2482
+ } finally {
2483
+ framesDone = true;
2484
+ if (frameResolve) {
2485
+ const resolve = frameResolve;
2486
+ frameResolve = null;
2487
+ resolve(null);
2488
+ }
2489
+ if (shouldExit && !cleanedUp && !headless && stdin.isTTY) try {
2490
+ stdin.removeAllListeners("data");
2491
+ stdin.resume();
2492
+ await new Promise((resolve) => setTimeout(resolve, 15));
2493
+ while (stdin.read() !== null);
2494
+ stdin.pause();
2495
+ } catch {}
2496
+ cleanup();
2497
+ exitResolve();
2498
+ }
2499
+ };
2500
+ eventLoop().catch((err) => log.error?.(`eventLoop failed: ${err}`));
2501
+ return {
2502
+ get text() {
2503
+ return currentBuffer.text;
2504
+ },
2505
+ get root() {
2506
+ return getContainerRoot(container);
2507
+ },
2508
+ get buffer() {
2509
+ return currentBuffer?._buffer ?? null;
2510
+ },
2511
+ get store() {
2512
+ return store;
2513
+ },
2514
+ waitUntilExit() {
2515
+ return exitPromise;
2516
+ },
2517
+ unmount() {
2518
+ exit();
2519
+ },
2520
+ [Symbol.dispose]() {
2521
+ exit();
2522
+ },
2523
+ async press(rawKey) {
2524
+ try {
2525
+ var _usingCtx3 = _usingCtx();
2526
+ const pressStart = performance.now();
2527
+ const [input, parsedKey] = parseKey(useKittyMode ? keyToKittyAnsi(rawKey) : keyToAnsi(rawKey));
2528
+ const _perfSpan = _usingCtx3.u(perfLog.span?.("keypress", (() => {
2529
+ startTracking();
2530
+ return { key: input || rawKey };
2531
+ })()));
2532
+ if (input === "c" && parsedKey.ctrl && exitOnCtrlCOption) {
2533
+ if (!(onInterruptHook?.() === false)) {
2534
+ exit();
2535
+ return;
2536
+ }
2537
+ }
2538
+ for (const listener of runtimeInputListeners) listener(input, parsedKey);
2539
+ inEventHandler = true;
2540
+ isRendering = true;
2541
+ if (handleFocusNavigation(input, parsedKey, focusManager, container) === "consumed") {
2542
+ pendingRerender = false;
2543
+ isRendering = false;
2544
+ inEventHandler = false;
2545
+ doRender();
2546
+ await Promise.resolve();
2547
+ if (_perfSpan) checkBudget(input || rawKey, performance.now() - pressStart);
2548
+ return;
2549
+ }
2550
+ if (dispatchKeyToHandlers(input, parsedKey, handlers, createHandlerContext(store, focusManager, container)) === "exit") {
2551
+ isRendering = false;
2552
+ inEventHandler = false;
2553
+ exit();
2554
+ return;
2555
+ }
2556
+ pendingRerender = false;
2557
+ try {
2558
+ currentBuffer = doRender();
2559
+ } finally {
2560
+ isRendering = false;
2561
+ }
2562
+ let flushCount = 0;
2563
+ const maxFlushes = 5;
2564
+ while (flushCount < maxFlushes) {
2565
+ await Promise.resolve();
2566
+ if (!pendingRerender) break;
2567
+ pendingRerender = false;
2568
+ isRendering = true;
2569
+ try {
2570
+ currentBuffer = doRender();
2571
+ } finally {
2572
+ isRendering = false;
2573
+ }
2574
+ flushCount++;
2575
+ }
2576
+ if (flushCount > 0) currentBuffer._buffer.markAllRowsDirty();
2577
+ inEventHandler = false;
2578
+ runtime.render(currentBuffer);
2579
+ if (_perfSpan) checkBudget(input || rawKey, performance.now() - pressStart);
2580
+ } catch (_) {
2581
+ _usingCtx3.e = _;
2582
+ } finally {
2583
+ _usingCtx3.d();
2584
+ }
2585
+ },
2586
+ [Symbol.asyncIterator]() {
2587
+ return {
2588
+ async next() {
2589
+ if (framesDone || shouldExit) return {
2590
+ done: true,
2591
+ value: void 0
2592
+ };
2593
+ const buf = await new Promise((resolve) => {
2594
+ if (framesDone || shouldExit) {
2595
+ resolve(null);
2596
+ return;
2597
+ }
2598
+ frameResolve = resolve;
2599
+ });
2600
+ if (!buf) return {
2601
+ done: true,
2602
+ value: void 0
2603
+ };
2604
+ return {
2605
+ done: false,
2606
+ value: buf
2607
+ };
2608
+ },
2609
+ async return() {
2610
+ exit();
2611
+ return {
2612
+ done: true,
2613
+ value: void 0
2614
+ };
2615
+ }
2616
+ };
2617
+ }
2618
+ };
2619
+ }
2620
+ //#endregion
2621
+ //#region ../packages/ag-term/src/runtime/run.tsx
2622
+ init_detect();
2623
+ init_ThemeContext();
2624
+ //#endregion
2625
+ //#region ../packages/create/src/pipe.ts
2626
+ function pipe(base, ...plugins) {
2627
+ let result = base;
2628
+ for (const plugin of plugins) result = plugin(result);
2629
+ return result;
2630
+ }
2631
+ //#endregion
2632
+ //#region ../packages/ag-react/src/with-react.ts
2633
+ /**
2634
+ * Associate a React element with an app for rendering.
2635
+ *
2636
+ * In pipe() composition, this captures the element so that subsequent
2637
+ * plugins and the final run() know what to render.
2638
+ *
2639
+ * The plugin wraps `run()` to automatically pass the element:
2640
+ * - Before: `app.run(<Board />, options)`
2641
+ * - After: `app.run()` (element already bound)
2642
+ *
2643
+ * @param element - The React element to render
2644
+ * @returns Plugin function that binds the element to the app
2645
+ */
2646
+ function withReact(element) {
2647
+ return (app) => {
2648
+ const originalRun = app.run;
2649
+ return Object.assign(Object.create(app), {
2650
+ element,
2651
+ run(...args) {
2652
+ if (args.length === 0 || typeof args[0] !== "object" || args[0] === null || !("type" in args[0])) return originalRun.call(app, element, ...args);
2653
+ return originalRun.apply(app, args);
2654
+ }
2655
+ });
2656
+ };
2657
+ }
2658
+ //#endregion
2659
+ //#region ../packages/ag-term/src/features/clipboard-capability.ts
2660
+ /**
2661
+ * Create an OSC 52 clipboard capability.
2662
+ *
2663
+ * Encodes text as base64 and writes the OSC 52 sequence directly.
2664
+ * This is a standalone factory that doesn't require the full ClipboardBackend.
2665
+ */
2666
+ function createOSC52Clipboard(write) {
2667
+ return { copy(text) {
2668
+ write(`\x1b]52;c;${Buffer.from(text, "utf-8").toString("base64")}\x07`);
2669
+ } };
2670
+ }
2671
+ //#endregion
2672
+ //#region ../packages/ag-term/src/plugins/with-terminal.ts
2673
+ /**
2674
+ * withTerminal(process, opts?) — Plugin: ALL terminal I/O
2675
+ *
2676
+ * This plugin represents the terminal I/O layer in silvery's plugin
2677
+ * composition model. It wraps all terminal concerns:
2678
+ * - stdin → typed events (term:key, term:mouse, term:paste)
2679
+ * - stdout → alternate screen, raw mode, incremental diff output
2680
+ * - SIGWINCH → term:resize
2681
+ * - Lifecycle (Ctrl+Z suspend/resume, Ctrl+C exit)
2682
+ * - Protocols (SGR mouse, Kitty keyboard, bracketed paste)
2683
+ *
2684
+ * In the current architecture, terminal I/O is handled by createApp()
2685
+ * and the TermProvider. This plugin provides the declarative interface
2686
+ * for pipe() composition:
2687
+ *
2688
+ * ```tsx
2689
+ * const app = pipe(
2690
+ * createApp(store),
2691
+ * withReact(<Board />),
2692
+ * withTerminal(process, { mouse: true, kitty: true }),
2693
+ * withFocus(),
2694
+ * withDomEvents(),
2695
+ * )
2696
+ * ```
2697
+ *
2698
+ * @example
2699
+ * ```tsx
2700
+ * import { pipe, withTerminal } from '@silvery/create'
2701
+ *
2702
+ * // All protocols enabled by default
2703
+ * const app = pipe(baseApp, withTerminal(process))
2704
+ *
2705
+ * // Customize terminal options
2706
+ * const app = pipe(baseApp, withTerminal(process, {
2707
+ * mouse: true,
2708
+ * kitty: true,
2709
+ * paste: true,
2710
+ * onSuspend: () => saveState(),
2711
+ * onResume: () => restoreState(),
2712
+ * }))
2713
+ * ```
2714
+ */
2715
+ /**
2716
+ * Configure terminal I/O for an app.
2717
+ *
2718
+ * In pipe() composition, this captures the process streams and options
2719
+ * so that run() configures terminal I/O correctly.
2720
+ *
2721
+ * The plugin wraps `run()` to inject terminal options:
2722
+ * - stdin/stdout from the process object
2723
+ * - Protocol options (mouse, kitty, paste)
2724
+ * - Lifecycle handlers (suspend, resume, interrupt)
2725
+ *
2726
+ * @param proc - Process object with stdin/stdout (typically `process`)
2727
+ * @param options - Terminal configuration
2728
+ * @returns Plugin function that binds terminal config to the app
2729
+ */
2730
+ function withTerminal(proc, options = {}) {
2731
+ const termConfig = {
2732
+ mouse: options.mouse ?? true,
2733
+ kitty: options.kitty ?? true,
2734
+ paste: options.paste ?? true,
2735
+ alternateScreen: options.alternateScreen ?? true,
2736
+ suspendOnCtrlZ: options.suspendOnCtrlZ ?? true,
2737
+ exitOnCtrlC: options.exitOnCtrlC ?? true,
2738
+ ...options,
2739
+ proc
2740
+ };
2741
+ return (app) => {
2742
+ const originalRun = app.run;
2743
+ const registry = app.capabilityRegistry ?? createCapabilityRegistry();
2744
+ const clipboard = createOSC52Clipboard((data) => {
2745
+ proc.stdout.write(data);
2746
+ });
2747
+ registry.register(CLIPBOARD_CAPABILITY, clipboard);
2748
+ const autoDetect = termConfig.autoDetect ?? false;
2749
+ const timeoutMs = termConfig.autoDetectTimeoutMs ?? 200;
2750
+ let colorSchemeDetector;
2751
+ let widthDetector;
2752
+ let detectionReady;
2753
+ if (autoDetect && proc.stdin.isTTY) {
2754
+ const write = (data) => {
2755
+ proc.stdout.write(data);
2756
+ };
2757
+ const onData = (handler) => {
2758
+ const bufferHandler = (chunk) => {
2759
+ handler(typeof chunk === "string" ? chunk : chunk.toString());
2760
+ };
2761
+ proc.stdin.on("data", bufferHandler);
2762
+ return () => {
2763
+ proc.stdin.removeListener("data", bufferHandler);
2764
+ };
2765
+ };
2766
+ colorSchemeDetector = createColorSchemeDetector({
2767
+ write,
2768
+ onData,
2769
+ timeoutMs
2770
+ });
2771
+ colorSchemeDetector.start();
2772
+ widthDetector = createWidthDetector({
2773
+ write,
2774
+ onData,
2775
+ timeoutMs
2776
+ });
2777
+ detectionReady = widthDetector.detect().then((config) => {
2778
+ if (enhanced.terminalOptions?.proc) enhanced._detectedWidthConfig = config;
2779
+ }).catch(() => {});
2780
+ } else detectionReady = Promise.resolve();
2781
+ const enhanced = Object.assign(Object.create(app), {
2782
+ terminalOptions: termConfig,
2783
+ capabilityRegistry: registry,
2784
+ clipboardCapability: clipboard,
2785
+ colorSchemeDetector,
2786
+ widthDetector,
2787
+ detectionReady,
2788
+ _detectedWidthConfig: null,
2789
+ run(...args) {
2790
+ const runOptions = {};
2791
+ let existingOptions;
2792
+ if (args.length > 0 && typeof args[args.length - 1] === "object" && args[args.length - 1] !== null) {
2793
+ existingOptions = args[args.length - 1];
2794
+ if ("type" in existingOptions && "props" in existingOptions) existingOptions = void 0;
2795
+ }
2796
+ Object.assign(runOptions, existingOptions ?? {}, {
2797
+ stdin: proc.stdin,
2798
+ stdout: proc.stdout,
2799
+ mouse: termConfig.mouse,
2800
+ kitty: termConfig.kitty,
2801
+ alternateScreen: termConfig.alternateScreen,
2802
+ suspendOnCtrlZ: termConfig.suspendOnCtrlZ,
2803
+ exitOnCtrlC: termConfig.exitOnCtrlC,
2804
+ textSizing: termConfig.textSizing,
2805
+ widthDetection: termConfig.widthDetection ?? "auto",
2806
+ focusReporting: termConfig.focusReporting,
2807
+ onSuspend: termConfig.onSuspend,
2808
+ onResume: termConfig.onResume,
2809
+ onInterrupt: termConfig.onInterrupt,
2810
+ capabilityRegistry: registry
2811
+ });
2812
+ if (existingOptions) {
2813
+ const newArgs = [...args];
2814
+ newArgs[newArgs.length - 1] = runOptions;
2815
+ return originalRun.apply(app, newArgs);
2816
+ }
2817
+ return originalRun.call(app, ...args, runOptions);
2818
+ }
2819
+ });
2820
+ return enhanced;
2821
+ };
2822
+ }
2823
+ //#endregion
2824
+ //#region ../packages/create/src/internal/input-router.ts
2825
+ /**
2826
+ * Compare handler entries for dispatch order:
2827
+ * - Higher priority dispatches first
2828
+ * - Same priority: lower insertion order (first registered) wins
2829
+ */
2830
+ function compareEntries(a, b) {
2831
+ if (a.priority !== b.priority) return b.priority - a.priority;
2832
+ return a.order - b.order;
2833
+ }
2834
+ /**
2835
+ * Create a priority-based input router.
2836
+ *
2837
+ * The `invalidate` callback is injected by the caller (typically wired to the
2838
+ * store/render pipeline by withDomEvents). This keeps the router decoupled
2839
+ * from silvery internals.
2840
+ */
2841
+ function createInputRouter(options) {
2842
+ const { invalidate } = options;
2843
+ let nextOrder = 0;
2844
+ const mouseHandlers = [];
2845
+ const keyHandlers = [];
2846
+ const overlays = [];
2847
+ function addEntry(list, priority, handler) {
2848
+ const entry = {
2849
+ priority,
2850
+ order: nextOrder++,
2851
+ handler
2852
+ };
2853
+ list.push(entry);
2854
+ list.sort(compareEntries);
2855
+ return () => {
2856
+ const idx = list.indexOf(entry);
2857
+ if (idx !== -1) list.splice(idx, 1);
2858
+ };
2859
+ }
2860
+ function dispatch(list, event) {
2861
+ for (const entry of list) if (entry.handler(event)) return true;
2862
+ return false;
2863
+ }
2864
+ return {
2865
+ registerMouseHandler(priority, handler) {
2866
+ return addEntry(mouseHandlers, priority, handler);
2867
+ },
2868
+ dispatchMouse(event) {
2869
+ return dispatch(mouseHandlers, event);
2870
+ },
2871
+ registerKeyHandler(priority, handler) {
2872
+ return addEntry(keyHandlers, priority, handler);
2873
+ },
2874
+ dispatchKey(event) {
2875
+ return dispatch(keyHandlers, event);
2876
+ },
2877
+ invalidate,
2878
+ registerOverlay(priority, renderer) {
2879
+ return addEntry(overlays, priority, renderer);
2880
+ },
2881
+ getOverlays() {
2882
+ return overlays.map((entry) => entry.handler);
2883
+ }
2884
+ };
2885
+ }
2886
+ //#endregion
2887
+ //#region ../packages/ag-term/src/plugins/with-dom-events.ts
2888
+ /**
2889
+ * Add DOM-style mouse event dispatch to an App.
2890
+ *
2891
+ * This plugin creates a mouse event processor and ensures that
2892
+ * click(), doubleClick(), and wheel() methods on the app dispatch
2893
+ * events through the render tree with proper bubbling.
2894
+ *
2895
+ * The App's buildApp() already sets up mouse event processing.
2896
+ * This plugin is provided for explicit composition via pipe()
2897
+ * and ensures the focus manager is connected for click-to-focus.
2898
+ *
2899
+ * @param options - Configuration (focusManager for click-to-focus)
2900
+ * @returns Plugin function that enhances an App with DOM event dispatch
2901
+ */
2902
+ function withDomEvents(options = {}) {
2903
+ return (app) => {
2904
+ const fm = options.focusManager ?? app.focusManager;
2905
+ const processorOptions = {};
2906
+ if (fm) processorOptions.focusManager = fm;
2907
+ const mouseState = createMouseEventProcessor(processorOptions);
2908
+ const existingRegistry = app.capabilityRegistry;
2909
+ const registry = options.capabilityRegistry ?? existingRegistry ?? createCapabilityRegistry();
2910
+ let invalidateCallback = () => {};
2911
+ const router = createInputRouter({ invalidate: () => invalidateCallback() });
2912
+ registry.register(INPUT_ROUTER, router);
2913
+ const enhanced = new Proxy(app, { get(target, prop, receiver) {
2914
+ if (prop === "capabilityRegistry") return registry;
2915
+ if (prop === "inputRouter") return router;
2916
+ if (prop === "click") return async function enhancedClick(x, y, clickOptions) {
2917
+ const button = clickOptions?.button ?? 0;
2918
+ if (!router.dispatchMouse({
2919
+ x,
2920
+ y,
2921
+ button,
2922
+ type: "mousedown"
2923
+ })) {
2924
+ const root = target.getContainer();
2925
+ processMouseEvent(mouseState, {
2926
+ button,
2927
+ x,
2928
+ y,
2929
+ action: "down",
2930
+ shift: false,
2931
+ meta: false,
2932
+ ctrl: false
2933
+ }, root);
2934
+ }
2935
+ if (!router.dispatchMouse({
2936
+ x,
2937
+ y,
2938
+ button,
2939
+ type: "mouseup"
2940
+ })) {
2941
+ const root = target.getContainer();
2942
+ processMouseEvent(mouseState, {
2943
+ button,
2944
+ x,
2945
+ y,
2946
+ action: "up",
2947
+ shift: false,
2948
+ meta: false,
2949
+ ctrl: false
2950
+ }, root);
2951
+ }
2952
+ await Promise.resolve();
2953
+ return receiver;
2954
+ };
2955
+ if (prop === "doubleClick") return async function enhancedDoubleClick(x, y, clickOptions) {
2956
+ const button = clickOptions?.button ?? 0;
2957
+ const root = target.getContainer();
2958
+ const parsed = {
2959
+ button,
2960
+ x,
2961
+ y,
2962
+ action: "down",
2963
+ shift: false,
2964
+ meta: false,
2965
+ ctrl: false
2966
+ };
2967
+ processMouseEvent(mouseState, parsed, root);
2968
+ processMouseEvent(mouseState, {
2969
+ ...parsed,
2970
+ action: "up"
2971
+ }, root);
2972
+ processMouseEvent(mouseState, parsed, root);
2973
+ processMouseEvent(mouseState, {
2974
+ ...parsed,
2975
+ action: "up"
2976
+ }, root);
2977
+ await Promise.resolve();
2978
+ return receiver;
2979
+ };
2980
+ if (prop === "wheel") return async function enhancedWheel(x, y, delta) {
2981
+ if (!router.dispatchMouse({
2982
+ x,
2983
+ y,
2984
+ button: 0,
2985
+ type: "wheel"
2986
+ })) {
2987
+ const root = target.getContainer();
2988
+ processMouseEvent(mouseState, {
2989
+ button: 0,
2990
+ x,
2991
+ y,
2992
+ action: "wheel",
2993
+ delta,
2994
+ shift: false,
2995
+ meta: false,
2996
+ ctrl: false
2997
+ }, root);
2998
+ }
2999
+ await Promise.resolve();
3000
+ return receiver;
3001
+ };
3002
+ if (prop === "run") {
3003
+ const originalRun = Reflect.get(target, prop, receiver);
3004
+ if (typeof originalRun === "function") return function enhancedRun(...args) {
3005
+ const inject = { capabilityRegistry: registry };
3006
+ if (args.length === 0) return originalRun.call(target, inject);
3007
+ else if (args.length === 1) {
3008
+ const arg = args[0];
3009
+ if (arg && typeof arg === "object" && "type" in arg) return originalRun.call(target, arg, inject);
3010
+ else return originalRun.call(target, {
3011
+ ...arg,
3012
+ ...inject
3013
+ });
3014
+ } else {
3015
+ const opts = {
3016
+ ...args[1],
3017
+ ...inject
3018
+ };
3019
+ return originalRun.call(target, args[0], opts);
3020
+ }
3021
+ };
3022
+ }
3023
+ return Reflect.get(target, prop, receiver);
3024
+ } });
3025
+ const appAny = app;
3026
+ if (typeof appAny.store?.setState === "function") invalidateCallback = () => {
3027
+ appAny.store.setState((prev) => ({
3028
+ ...prev,
3029
+ _inv: (prev._inv ?? 0) + 1
3030
+ }));
3031
+ };
3032
+ return enhanced;
3033
+ };
3034
+ }
3035
+ //#endregion
3036
+ //#region ../packages/test/src/compare-buffers.ts
3037
+ init_buffer();
3038
+ //#endregion
3039
+ //#region ../packages/ag-term/src/plugins/with-diagnostics.ts
3040
+ init_buffer();
3041
+ createContext(null);
3042
+ createContext({
3043
+ activeId: void 0,
3044
+ isFocusEnabled: true,
3045
+ add() {},
3046
+ remove() {},
3047
+ activate() {},
3048
+ deactivate() {},
3049
+ enableFocus() {},
3050
+ disableFocus() {},
3051
+ focusNext() {},
3052
+ focusPrevious() {},
3053
+ focus() {},
3054
+ blur() {}
3055
+ });
3056
+ //#endregion
3057
+ export { createApp as a, pipe as i, withTerminal as n, useApp as o, withReact as r, withDomEvents as t };