@silvery/examples 0.17.3 → 0.17.5

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 (111) hide show
  1. package/dist/UPNG-ShUlaTDh.mjs +5074 -0
  2. package/dist/__vite-browser-external-2447137e-Bopa5BFR.mjs +4 -0
  3. package/dist/_banner-A70_y2Vi.mjs +43 -0
  4. package/dist/ansi-0VXlUmNn.mjs +16397 -0
  5. package/dist/apng-B0gRaDVT.mjs +3 -0
  6. package/dist/apng-BTRDTfDW.mjs +68 -0
  7. package/dist/apps/aichat/index.mjs +1298 -0
  8. package/dist/apps/app-todo.mjs +138 -0
  9. package/dist/apps/async-data.mjs +203 -0
  10. package/dist/apps/cli-wizard.mjs +338 -0
  11. package/dist/apps/clipboard.mjs +197 -0
  12. package/dist/apps/components.mjs +863 -0
  13. package/dist/apps/data-explorer.mjs +482 -0
  14. package/dist/apps/dev-tools.mjs +396 -0
  15. package/dist/apps/explorer.mjs +697 -0
  16. package/dist/apps/gallery.mjs +765 -0
  17. package/dist/apps/inline-bench.mjs +115 -0
  18. package/dist/apps/kanban.mjs +279 -0
  19. package/dist/apps/layout-ref.mjs +186 -0
  20. package/dist/apps/outline.mjs +202 -0
  21. package/dist/apps/paste-demo.mjs +188 -0
  22. package/dist/apps/scroll.mjs +85 -0
  23. package/dist/apps/search-filter.mjs +286 -0
  24. package/dist/apps/selection.mjs +354 -0
  25. package/dist/apps/spatial-focus-demo.mjs +387 -0
  26. package/dist/apps/task-list.mjs +257 -0
  27. package/dist/apps/terminal-caps-demo.mjs +314 -0
  28. package/dist/apps/terminal.mjs +871 -0
  29. package/dist/apps/text-selection-demo.mjs +253 -0
  30. package/dist/apps/textarea.mjs +177 -0
  31. package/dist/apps/theme.mjs +660 -0
  32. package/dist/apps/transform.mjs +214 -0
  33. package/dist/apps/virtual-10k.mjs +421 -0
  34. package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
  35. package/dist/backends-Dj-11kZF.mjs +1179 -0
  36. package/dist/backends-U3QwStfO.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 +47 -0
  40. package/dist/components/hello.mjs +30 -0
  41. package/dist/components/progress-bar.mjs +58 -0
  42. package/dist/components/select-list.mjs +84 -0
  43. package/dist/components/spinner.mjs +56 -0
  44. package/dist/components/text-input.mjs +61 -0
  45. package/dist/components/virtual-list.mjs +50 -0
  46. package/dist/flexily-zero-adapter-ByVzLTFP.mjs +3374 -0
  47. package/dist/gif-B6NGH5gs.mjs +3 -0
  48. package/dist/gif-CfkOF-iG.mjs +71 -0
  49. package/dist/gifenc-BI4ihP_T.mjs +728 -0
  50. package/dist/key-mapping-5oYQdAQE.mjs +3 -0
  51. package/dist/key-mapping-D4LR1go6.mjs +130 -0
  52. package/dist/layout/dashboard.mjs +1203 -0
  53. package/dist/layout/live-resize.mjs +302 -0
  54. package/dist/layout/overflow.mjs +69 -0
  55. package/dist/layout/text-layout.mjs +334 -0
  56. package/dist/node-nsrAOjH4.mjs +1083 -0
  57. package/dist/plugins-CT0DdV_E.mjs +3056 -0
  58. package/dist/resvg-js-Cnk2o49d.mjs +201 -0
  59. package/dist/src-9ZhfQyzD.mjs +814 -0
  60. package/dist/src-CUUOuRH6.mjs +5322 -0
  61. package/dist/src-jO3Zuzjj.mjs +23538 -0
  62. package/dist/usingCtx-CsEf0xO3.mjs +57 -0
  63. package/dist/yoga-adapter-BSQHuMV9.mjs +237 -0
  64. package/package.json +21 -14
  65. package/_banner.tsx +0 -60
  66. package/apps/aichat/components.tsx +0 -469
  67. package/apps/aichat/index.tsx +0 -220
  68. package/apps/aichat/script.ts +0 -460
  69. package/apps/aichat/state.ts +0 -325
  70. package/apps/aichat/types.ts +0 -19
  71. package/apps/app-todo.tsx +0 -201
  72. package/apps/async-data.tsx +0 -196
  73. package/apps/cli-wizard.tsx +0 -332
  74. package/apps/clipboard.tsx +0 -183
  75. package/apps/components.tsx +0 -658
  76. package/apps/data-explorer.tsx +0 -490
  77. package/apps/dev-tools.tsx +0 -395
  78. package/apps/explorer.tsx +0 -731
  79. package/apps/gallery.tsx +0 -653
  80. package/apps/inline-bench.tsx +0 -138
  81. package/apps/kanban.tsx +0 -265
  82. package/apps/layout-ref.tsx +0 -173
  83. package/apps/outline.tsx +0 -160
  84. package/apps/panes/index.tsx +0 -203
  85. package/apps/paste-demo.tsx +0 -185
  86. package/apps/scroll.tsx +0 -80
  87. package/apps/search-filter.tsx +0 -240
  88. package/apps/selection.tsx +0 -346
  89. package/apps/spatial-focus-demo.tsx +0 -372
  90. package/apps/task-list.tsx +0 -271
  91. package/apps/terminal-caps-demo.tsx +0 -317
  92. package/apps/terminal.tsx +0 -784
  93. package/apps/text-selection-demo.tsx +0 -193
  94. package/apps/textarea.tsx +0 -155
  95. package/apps/theme.tsx +0 -515
  96. package/apps/transform.tsx +0 -229
  97. package/apps/virtual-10k.tsx +0 -405
  98. package/apps/vterm-demo/index.tsx +0 -216
  99. package/components/counter.tsx +0 -49
  100. package/components/hello.tsx +0 -38
  101. package/components/progress-bar.tsx +0 -52
  102. package/components/select-list.tsx +0 -54
  103. package/components/spinner.tsx +0 -44
  104. package/components/text-input.tsx +0 -61
  105. package/components/virtual-list.tsx +0 -56
  106. package/dist/cli.d.mts +0 -1
  107. package/dist/cli.mjs.map +0 -1
  108. package/layout/dashboard.tsx +0 -953
  109. package/layout/live-resize.tsx +0 -282
  110. package/layout/overflow.tsx +0 -51
  111. package/layout/text-layout.tsx +0 -283
@@ -0,0 +1,3056 @@
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-0VXlUmNn.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-jO3Zuzjj.mjs";
5
+ import { t as _usingCtx } from "./usingCtx-CsEf0xO3.mjs";
6
+ import React, { Component, createContext, useContext, useEffect, useRef } from "react";
7
+ import { createLogger } from "loggily";
8
+ import * as fs from "node:fs";
9
+ import { writeSync } from "node:fs";
10
+ import "node:path";
11
+ import process$1 from "node:process";
12
+ import { jsx } from "react/jsx-runtime";
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
+ const log = createLogger("silvery:app");
1149
+ const ENV = typeof process$1 !== "undefined" ? process$1.env : void 0;
1150
+ const NO_INCREMENTAL = ENV?.SILVERY_NO_INCREMENTAL === "1";
1151
+ const STRICT_MODE = (() => {
1152
+ const v = ENV?.SILVERY_STRICT;
1153
+ return !!v && v !== "0" && v !== "false";
1154
+ })();
1155
+ const CELL_DEBUG = (() => {
1156
+ const v = ENV?.SILVERY_CELL_DEBUG;
1157
+ if (!v || !v.includes(",")) return null;
1158
+ const [cx, cy] = v.split(",").map(Number);
1159
+ if (!Number.isFinite(cx) || !Number.isFinite(cy)) return null;
1160
+ return {
1161
+ x: cx,
1162
+ y: cy
1163
+ };
1164
+ })();
1165
+ const INSTRUMENTED = STRICT_MODE || CELL_DEBUG !== null;
1166
+ /**
1167
+ * Check if value is a Provider with events (full interface).
1168
+ */
1169
+ function isFullProvider(value) {
1170
+ if (value === null || value === void 0) return false;
1171
+ if (typeof value !== "object" && typeof value !== "function") return false;
1172
+ return "getState" in value && "subscribe" in value && "events" in value && typeof value.getState === "function" && typeof value.subscribe === "function" && typeof value.events === "function";
1173
+ }
1174
+ /**
1175
+ * Check if value is a basic Provider (just getState/subscribe, Zustand-compatible).
1176
+ */
1177
+ function isBasicProvider(value) {
1178
+ if (value === null || value === void 0) return false;
1179
+ if (typeof value !== "object" && typeof value !== "function") return false;
1180
+ return "getState" in value && "subscribe" in value && typeof value.getState === "function" && typeof value.subscribe === "function";
1181
+ }
1182
+ const StoreContext = createContext(null);
1183
+ /**
1184
+ * Hook for accessing app state with selectors.
1185
+ *
1186
+ * @example
1187
+ * ```tsx
1188
+ * const count = useApp(s => s.count)
1189
+ * const { count, increment } = useApp(s => ({ count: s.count, increment: s.increment }))
1190
+ * ```
1191
+ */
1192
+ function useApp(selector) {
1193
+ const store = useContext(StoreContext);
1194
+ if (!store) throw new Error("useApp must be used within createApp().run()");
1195
+ const [state, setState] = React.useState(() => selector(store.getState()));
1196
+ const selectorRef = useRef(selector);
1197
+ selectorRef.current = selector;
1198
+ useEffect(() => {
1199
+ return store.subscribe((newState) => {
1200
+ const next = selectorRef.current(newState);
1201
+ setState((prev) => Object.is(prev, next) ? prev : next);
1202
+ });
1203
+ }, [store]);
1204
+ return state;
1205
+ }
1206
+ /**
1207
+ * Create an app with Zustand store and provider integration.
1208
+ *
1209
+ * This is Layer 3 - it provides:
1210
+ * - Zustand store with fine-grained subscriptions
1211
+ * - Providers as unified stores + event sources
1212
+ * - Event handlers namespaced as 'provider:event'
1213
+ *
1214
+ * @param factory Store factory function that receives providers
1215
+ * @param handlers Optional event handlers (namespaced as 'provider:event')
1216
+ */
1217
+ function createApp(factory, handlers) {
1218
+ return { run(element, options = {}) {
1219
+ let handlePromise = null;
1220
+ const init = () => {
1221
+ if (handlePromise) return handlePromise;
1222
+ handlePromise = initApp(factory, handlers, element, options);
1223
+ return handlePromise;
1224
+ };
1225
+ return {
1226
+ then(onfulfilled, onrejected) {
1227
+ return init().then(onfulfilled, onrejected);
1228
+ },
1229
+ [Symbol.asyncIterator]() {
1230
+ let handle = null;
1231
+ let iterator = null;
1232
+ let started = false;
1233
+ return {
1234
+ async next() {
1235
+ if (!started) {
1236
+ started = true;
1237
+ handle = await init();
1238
+ iterator = handle[Symbol.asyncIterator]();
1239
+ }
1240
+ return iterator.next();
1241
+ },
1242
+ async return() {
1243
+ if (handle) handle.unmount();
1244
+ return {
1245
+ done: true,
1246
+ value: void 0
1247
+ };
1248
+ }
1249
+ };
1250
+ }
1251
+ };
1252
+ } };
1253
+ }
1254
+ /**
1255
+ * Initialize the app — extracted from run() for clarity.
1256
+ */
1257
+ async function initApp(factory, handlers, element, options) {
1258
+ 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;
1259
+ const useKittyMode = explicitKittyMode ?? !!kittyOption;
1260
+ const headless = explicitCols != null && explicitRows != null && !explicitStdout || explicitWritable != null;
1261
+ const cols = explicitCols ?? process$1.stdout.columns ?? 80;
1262
+ const rows = explicitRows ?? process$1.stdout.rows ?? 24;
1263
+ const stdout = explicitStdout ?? process$1.stdout;
1264
+ const isRealStdout = stdout === process$1.stdout;
1265
+ const shouldGuardOutput = guardOutputOption ?? (alternateScreen && !headless && isRealStdout);
1266
+ let outputGuard = null;
1267
+ await ensureLayoutEngine();
1268
+ const controller = new AbortController();
1269
+ const signal = controller.signal;
1270
+ if (externalSignal) if (externalSignal.aborted) controller.abort();
1271
+ else externalSignal.addEventListener("abort", () => controller.abort(), { once: true });
1272
+ const providers = {};
1273
+ const plainValues = {};
1274
+ const providerCleanups = [];
1275
+ let termProvider = null;
1276
+ if (!("term" in injectValues) || !isFullProvider(injectValues.term)) {
1277
+ const resizeListeners = /* @__PURE__ */ new Set();
1278
+ const termStdout = headless ? {
1279
+ columns: cols,
1280
+ rows,
1281
+ write: () => true,
1282
+ isTTY: false,
1283
+ on(event, handler) {
1284
+ if (event === "resize") resizeListeners.add(handler);
1285
+ return termStdout;
1286
+ },
1287
+ off(event, handler) {
1288
+ if (event === "resize") resizeListeners.delete(handler);
1289
+ return termStdout;
1290
+ }
1291
+ } : stdout;
1292
+ const termStdin = headless ? {
1293
+ isTTY: false,
1294
+ on: () => termStdin,
1295
+ off: () => termStdin,
1296
+ setRawMode: () => {},
1297
+ resume: () => {},
1298
+ pause: () => {},
1299
+ setEncoding: () => {}
1300
+ } : stdin;
1301
+ termProvider = createTermProvider(termStdin, termStdout, {
1302
+ cols,
1303
+ rows
1304
+ });
1305
+ providers.term = termProvider;
1306
+ providerCleanups.push(() => termProvider[Symbol.dispose]());
1307
+ if (headless && explicitOnResize) {
1308
+ const unsub = explicitOnResize((dims) => {
1309
+ currentDims = dims;
1310
+ termStdout.columns = dims.cols;
1311
+ termStdout.rows = dims.rows;
1312
+ for (const listener of resizeListeners) listener();
1313
+ });
1314
+ providerCleanups.push(unsub);
1315
+ }
1316
+ }
1317
+ for (const [name, value] of Object.entries(injectValues)) if (isFullProvider(value)) providers[name] = value;
1318
+ else plainValues[name] = value;
1319
+ const inject = {
1320
+ ...providers,
1321
+ ...plainValues
1322
+ };
1323
+ const stateUnsubscribes = [];
1324
+ const store = createStore((set, get, api) => {
1325
+ const mergedState = { ...factory(inject)(set, get, api) };
1326
+ for (const [name, provider] of Object.entries(providers)) {
1327
+ mergedState[name] = provider;
1328
+ if (isBasicProvider(provider)) {
1329
+ const unsub = provider.subscribe((_providerState) => {});
1330
+ stateUnsubscribes.push(unsub);
1331
+ }
1332
+ }
1333
+ for (const [name, value] of Object.entries(plainValues)) mergedState[name] = value;
1334
+ return mergedState;
1335
+ });
1336
+ let currentDims = {
1337
+ cols,
1338
+ rows
1339
+ };
1340
+ if (!headless) {
1341
+ const onStdoutResize = () => {
1342
+ currentDims = {
1343
+ cols: stdout.columns || 80,
1344
+ rows: stdout.rows || 24
1345
+ };
1346
+ for (const listener of mockTermSubscribers) listener(currentDims);
1347
+ };
1348
+ stdout.on("resize", onStdoutResize);
1349
+ providerCleanups.push(() => stdout.off("resize", onStdoutResize));
1350
+ }
1351
+ let shouldExit = false;
1352
+ let renderPaused = false;
1353
+ let isRendering = false;
1354
+ let inEventHandler = false;
1355
+ let pendingRerender = false;
1356
+ const _ansiTrace = !headless && process$1.env?.SILVERY_TRACE === "1";
1357
+ let _traceSeq = 0;
1358
+ const _traceStart = performance.now();
1359
+ let _origStdoutWrite;
1360
+ if (_ansiTrace) {
1361
+ const fs = __require("node:fs");
1362
+ fs.writeFileSync("/tmp/silvery-trace.log", `=== SILVERY TRACE START ===\n`);
1363
+ _origStdoutWrite = stdout.write.bind(stdout);
1364
+ 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⟩");
1365
+ const traceWrite = function(chunk, ...args) {
1366
+ const str = typeof chunk === "string" ? chunk : String(chunk);
1367
+ const seq = ++_traceSeq;
1368
+ const ms = (performance.now() - _traceStart).toFixed(0);
1369
+ const decoded = symbolize(str);
1370
+ const preview = decoded.length > 400 ? decoded.slice(0, 200) + ` ...[${decoded.length}ch]... ` + decoded.slice(-100) : decoded;
1371
+ fs.appendFileSync("/tmp/silvery-trace.log", `[${String(seq).padStart(4, "0")}] +${ms}ms (${str.length}b): ${preview}\n`);
1372
+ return _origStdoutWrite.call(this, chunk, ...args);
1373
+ };
1374
+ stdout.write = traceWrite;
1375
+ providerCleanups.push(() => {
1376
+ if (_origStdoutWrite) stdout.write = _origStdoutWrite;
1377
+ });
1378
+ }
1379
+ const target = headless ? {
1380
+ write(frame) {
1381
+ if (explicitWritable) explicitWritable.write(frame);
1382
+ },
1383
+ getDims: () => currentDims
1384
+ } : {
1385
+ write(frame) {
1386
+ if (_perfLog) __require("node:fs").appendFileSync("/tmp/silvery-perf.log", `TARGET.write: ${frame.length} bytes (paused=${renderPaused})\n`);
1387
+ if (!renderPaused) if (outputGuard) outputGuard.writeStdout(frame);
1388
+ else stdout.write(frame);
1389
+ },
1390
+ getDims() {
1391
+ return currentDims;
1392
+ },
1393
+ onResize(handler) {
1394
+ const onResize = () => {
1395
+ currentDims = {
1396
+ cols: stdout.columns || 80,
1397
+ rows: stdout.rows || 24
1398
+ };
1399
+ handler(currentDims);
1400
+ };
1401
+ stdout.on("resize", onResize);
1402
+ return () => stdout.off("resize", onResize);
1403
+ }
1404
+ };
1405
+ const heuristicSupported = capsOption?.textSizingSupported ?? isTextSizingLikelySupported();
1406
+ const shouldProbe = textSizingOption === "probe" || textSizingOption === "auto" && heuristicSupported;
1407
+ const cachedProbe = shouldProbe ? getCachedProbeResult() : void 0;
1408
+ let textSizingEnabled;
1409
+ if (textSizingOption === true) textSizingEnabled = true;
1410
+ else if (textSizingOption === "probe") textSizingEnabled = cachedProbe?.supported ?? false;
1411
+ else if (textSizingOption === "auto") if (cachedProbe !== void 0) textSizingEnabled = cachedProbe.supported;
1412
+ else textSizingEnabled = heuristicSupported;
1413
+ else textSizingEnabled = false;
1414
+ const needsProbe = shouldProbe && cachedProbe === void 0 && !headless;
1415
+ const needsWidthDetection = !headless && (widthDetectionOption === true || widthDetectionOption === "auto" && capsOption != null);
1416
+ let effectiveCaps = capsOption ? {
1417
+ ...capsOption,
1418
+ textSizingSupported: textSizingEnabled
1419
+ } : void 0;
1420
+ let pipelineConfig = effectiveCaps ? createPipeline({ caps: effectiveCaps }) : void 0;
1421
+ const runtime = createRuntime({
1422
+ target,
1423
+ signal,
1424
+ mode: alternateScreen ? "fullscreen" : "inline",
1425
+ outputPhaseFn: pipelineConfig?.outputPhaseFn
1426
+ });
1427
+ let cleanedUp = false;
1428
+ let storeUnsubscribeFn = null;
1429
+ let kittyEnabled = false;
1430
+ const defaultKittyFlags = KittyFlags.DISAMBIGUATE | KittyFlags.REPORT_EVENTS | KittyFlags.REPORT_ALL_KEYS;
1431
+ let kittyFlags = defaultKittyFlags;
1432
+ let mouseEnabled = false;
1433
+ let focusReportingEnabled = false;
1434
+ const selectionEnabled = selectionOption ?? false;
1435
+ let selectionState = createTerminalSelectionState();
1436
+ const selectionListeners = /* @__PURE__ */ new Set();
1437
+ /** Notify useSelection() subscribers that selection state changed. */
1438
+ function notifySelectionListeners() {
1439
+ for (const listener of selectionListeners) listener();
1440
+ }
1441
+ const capabilityRegistry = capabilityRegistryOption ?? createCapabilityRegistry();
1442
+ let selectionBridge;
1443
+ if (selectionEnabled) {
1444
+ selectionBridge = createSelectionBridge({
1445
+ getState: () => selectionState,
1446
+ subscribe: (listener) => {
1447
+ selectionListeners.add(listener);
1448
+ return () => {
1449
+ selectionListeners.delete(listener);
1450
+ };
1451
+ },
1452
+ setRange: (range) => {
1453
+ if (range === null) {
1454
+ const [next] = terminalSelectionUpdate({ type: "clear" }, selectionState);
1455
+ selectionState = next;
1456
+ } else {
1457
+ const [s1] = terminalSelectionUpdate({
1458
+ type: "start",
1459
+ col: range.anchor.col,
1460
+ row: range.anchor.row,
1461
+ source: "keyboard"
1462
+ }, selectionState);
1463
+ const [s2] = terminalSelectionUpdate({
1464
+ type: "extend",
1465
+ col: range.head.col,
1466
+ row: range.head.row
1467
+ }, s1);
1468
+ const [s3] = terminalSelectionUpdate({ type: "finish" }, s2);
1469
+ selectionState = s3;
1470
+ }
1471
+ notifySelectionListeners();
1472
+ if (currentBuffer) runtime.invalidate();
1473
+ },
1474
+ clear: () => {
1475
+ const [next] = terminalSelectionUpdate({ type: "clear" }, selectionState);
1476
+ selectionState = next;
1477
+ notifySelectionListeners();
1478
+ if (currentBuffer) runtime.invalidate();
1479
+ }
1480
+ });
1481
+ capabilityRegistry.register(SELECTION_CAPABILITY, selectionBridge);
1482
+ }
1483
+ const scrollback = virtualInlineOption ? createVirtualScrollback() : null;
1484
+ let virtualScrollOffset = 0;
1485
+ let searchState = createSearchState();
1486
+ const focusManager = createFocusManager({ onFocusChange(oldNode, newNode, _origin) {
1487
+ if (oldNode) dispatchFocusEvent(createFocusEvent("blur", oldNode, newNode));
1488
+ if (newNode) dispatchFocusEvent(createFocusEvent("focus", newNode, oldNode));
1489
+ } });
1490
+ setOnNodeRemoved((removedNode) => focusManager.handleSubtreeRemoved(removedNode));
1491
+ const cursorStore = createCursorStore();
1492
+ const mouseEventState = createMouseEventProcessor({ focusManager });
1493
+ const cleanup = () => {
1494
+ if (cleanedUp) return;
1495
+ cleanedUp = true;
1496
+ logExitSummary();
1497
+ try {
1498
+ reconciler.updateContainerSync(null, fiberRoot, null, () => {});
1499
+ reconciler.flushSyncWork();
1500
+ } catch {}
1501
+ setOnNodeRemoved(null);
1502
+ if (storeUnsubscribeFn) storeUnsubscribeFn();
1503
+ stateUnsubscribes.forEach((unsub) => {
1504
+ try {
1505
+ unsub();
1506
+ } catch {}
1507
+ });
1508
+ if (outputGuard) {
1509
+ outputGuard.dispose();
1510
+ outputGuard = null;
1511
+ }
1512
+ if (!headless && stdin.isTTY) {
1513
+ stdin.removeAllListeners("data");
1514
+ stdin.pause();
1515
+ const sequences = [
1516
+ "\x1B[?1004l",
1517
+ disableMouse(),
1518
+ disableKittyKeyboard(),
1519
+ "\x1B[?2004l",
1520
+ "\x1B[0m",
1521
+ resetCursorStyle(),
1522
+ "\x1B[?25h",
1523
+ alternateScreen ? "\x1B[?1049l" : ""
1524
+ ].join("");
1525
+ if (stdout === process$1.stdout) {
1526
+ try {
1527
+ writeSync(stdout.fd, sequences);
1528
+ } catch {
1529
+ try {
1530
+ stdout.write(sequences);
1531
+ } catch {}
1532
+ }
1533
+ try {
1534
+ stdin.resume();
1535
+ while (stdin.read() !== null);
1536
+ stdin.pause();
1537
+ } catch {}
1538
+ } else try {
1539
+ stdout.write(sequences);
1540
+ } catch {}
1541
+ try {
1542
+ stdin.setRawMode(false);
1543
+ } catch {}
1544
+ } else if (!headless) {
1545
+ const sequences = [
1546
+ "\x1B[?1004l",
1547
+ disableMouse(),
1548
+ disableKittyKeyboard(),
1549
+ "\x1B[?2004l",
1550
+ "\x1B[0m",
1551
+ resetCursorStyle(),
1552
+ "\x1B[?25h",
1553
+ alternateScreen ? "\x1B[?1049l" : ""
1554
+ ].join("");
1555
+ try {
1556
+ stdout.write(sequences);
1557
+ } catch {}
1558
+ }
1559
+ providerCleanups.forEach((fn) => {
1560
+ try {
1561
+ fn();
1562
+ } catch {}
1563
+ });
1564
+ runtime[Symbol.dispose]();
1565
+ };
1566
+ let exit;
1567
+ const container = createContainer(() => {
1568
+ if (shouldExit) return;
1569
+ if (inEventHandler) {
1570
+ pendingRerender = true;
1571
+ return;
1572
+ }
1573
+ if (!pendingRerender) {
1574
+ pendingRerender = true;
1575
+ queueMicrotask(() => {
1576
+ if (!pendingRerender) return;
1577
+ pendingRerender = false;
1578
+ if (!shouldExit && !isRendering) {
1579
+ isRendering = true;
1580
+ try {
1581
+ currentBuffer = doRender();
1582
+ runtime.render(currentBuffer);
1583
+ } finally {
1584
+ isRendering = false;
1585
+ }
1586
+ }
1587
+ });
1588
+ }
1589
+ });
1590
+ const fiberRoot = createFiberRoot(container);
1591
+ let currentBuffer;
1592
+ const mockStdout = {
1593
+ columns: cols,
1594
+ rows,
1595
+ write: () => true,
1596
+ isTTY: false,
1597
+ on: () => mockStdout,
1598
+ off: () => mockStdout,
1599
+ once: () => mockStdout,
1600
+ removeListener: () => mockStdout,
1601
+ addListener: () => mockStdout
1602
+ };
1603
+ const baseMockTerm = createTerm({ color: "truecolor" });
1604
+ const mockTermSubscribers = /* @__PURE__ */ new Set();
1605
+ const mockTerm = Object.create(baseMockTerm, {
1606
+ getState: { value: () => currentDims },
1607
+ subscribe: { value: (listener) => {
1608
+ mockTermSubscribers.add(listener);
1609
+ return () => mockTermSubscribers.delete(listener);
1610
+ } }
1611
+ });
1612
+ const runtimeInputListeners = [];
1613
+ const runtimePasteListeners = [];
1614
+ const runtimeFocusListeners = [];
1615
+ const runtimeEventListeners = /* @__PURE__ */ new Map();
1616
+ runtimeEventListeners.set("input", runtimeInputListeners);
1617
+ runtimeEventListeners.set("paste", runtimePasteListeners);
1618
+ runtimeEventListeners.set("focus", runtimeFocusListeners);
1619
+ const runtimeContextValue = {
1620
+ on(event, handler) {
1621
+ let listeners = runtimeEventListeners.get(event);
1622
+ if (!listeners) {
1623
+ listeners = [];
1624
+ runtimeEventListeners.set(event, listeners);
1625
+ }
1626
+ listeners.push(handler);
1627
+ return () => {
1628
+ const idx = listeners.indexOf(handler);
1629
+ if (idx >= 0) listeners.splice(idx, 1);
1630
+ };
1631
+ },
1632
+ emit(event, ...args) {
1633
+ const listeners = runtimeEventListeners.get(event);
1634
+ if (listeners) for (const listener of listeners) listener(...args);
1635
+ },
1636
+ exit: () => exit()
1637
+ };
1638
+ const Root = RootComponent ?? React.Fragment;
1639
+ const cacheBackend = !alternateScreen ? "terminal" : virtualInlineOption ? "virtual" : "retain";
1640
+ const wrappedElement = /* @__PURE__ */ jsx(SilveryErrorBoundary, { children: /* @__PURE__ */ jsx(CursorProvider, {
1641
+ store: cursorStore,
1642
+ children: /* @__PURE__ */ jsx(CacheBackendContext.Provider, {
1643
+ value: cacheBackend,
1644
+ children: /* @__PURE__ */ jsx(TermContext.Provider, {
1645
+ value: mockTerm,
1646
+ children: /* @__PURE__ */ jsx(StdoutContext.Provider, {
1647
+ value: {
1648
+ stdout: mockStdout,
1649
+ write: () => {},
1650
+ notifyScrollback: (lines) => runtime.addScrollbackLines(lines),
1651
+ promoteScrollback: (content, lines) => runtime.promoteScrollback(content, lines),
1652
+ resetInlineCursor: () => runtime.resetInlineCursor(),
1653
+ getInlineCursorRow: () => runtime.getInlineCursorRow()
1654
+ },
1655
+ children: /* @__PURE__ */ jsx(StderrContext.Provider, {
1656
+ value: {
1657
+ stderr: process$1.stderr,
1658
+ write: (data) => {
1659
+ process$1.stderr.write(data);
1660
+ }
1661
+ },
1662
+ children: /* @__PURE__ */ jsx(FocusManagerContext.Provider, {
1663
+ value: focusManager,
1664
+ children: /* @__PURE__ */ jsx(RuntimeContext.Provider, {
1665
+ value: runtimeContextValue,
1666
+ children: /* @__PURE__ */ jsx(CapabilityRegistryContext.Provider, {
1667
+ value: capabilityRegistry,
1668
+ children: /* @__PURE__ */ jsx(Root, { children: /* @__PURE__ */ jsx(StoreContext.Provider, {
1669
+ value: store,
1670
+ children: element
1671
+ }) })
1672
+ })
1673
+ })
1674
+ })
1675
+ })
1676
+ })
1677
+ })
1678
+ })
1679
+ }) });
1680
+ let _renderCount = 0;
1681
+ let _eventStart = 0;
1682
+ const _perfLog = typeof process$1 !== "undefined" && process$1.env?.DEBUG?.includes("silvery:perf");
1683
+ const _noIncremental = NO_INCREMENTAL;
1684
+ let _ag = null;
1685
+ let _lastTermBuffer = null;
1686
+ function doRender() {
1687
+ _renderCount++;
1688
+ if (_ansiTrace) __require("node:fs").appendFileSync("/tmp/silvery-trace.log", `--- doRender #${_renderCount} (ag=${_ag ? "reuse" : "create"}, incremental=${!_noIncremental}) ---\n`);
1689
+ const renderStart = performance.now();
1690
+ reconciler.updateContainerSync(wrappedElement, fiberRoot, null, () => {});
1691
+ reconciler.flushSyncWork();
1692
+ const reconcileMs = performance.now() - renderStart;
1693
+ {
1694
+ const acc = globalThis.__silvery_bench_phases;
1695
+ if (acc) acc.reconcile += reconcileMs;
1696
+ }
1697
+ const pipelineStart = performance.now();
1698
+ const rootNode = getContainerRoot(container);
1699
+ const dims = runtime.getDims();
1700
+ const isInline = !alternateScreen;
1701
+ if (!_ag) _ag = createAg(rootNode, { measurer: pipelineConfig?.measurer });
1702
+ if (_ag) {
1703
+ const lastBuffer = _lastTermBuffer;
1704
+ if (lastBuffer) {
1705
+ const widthChanged = dims.cols !== lastBuffer.width;
1706
+ const heightChanged = !isInline && dims.rows !== lastBuffer.height;
1707
+ if (widthChanged || heightChanged) {
1708
+ _ag.resetBuffer();
1709
+ runtime.invalidate();
1710
+ }
1711
+ }
1712
+ }
1713
+ if (INSTRUMENTED) {
1714
+ globalThis.__silvery_content_all = void 0;
1715
+ globalThis.__silvery_node_trace = void 0;
1716
+ globalThis.__silvery_cell_debug = CELL_DEBUG !== null ? {
1717
+ x: CELL_DEBUG.x,
1718
+ y: CELL_DEBUG.y,
1719
+ log: []
1720
+ } : void 0;
1721
+ }
1722
+ const rootHasDirty = rootNode.layoutDirty || isAnyDirty(rootNode.dirtyBits, rootNode.dirtyEpoch);
1723
+ const dimsChanged = _lastTermBuffer != null && (dims.cols !== _lastTermBuffer.width || dims.rows !== _lastTermBuffer.height);
1724
+ if (!rootHasDirty && !dimsChanged && _lastTermBuffer && currentBuffer) return currentBuffer;
1725
+ if (_noIncremental) _ag.resetBuffer();
1726
+ _ag.layout(dims);
1727
+ const { buffer: termBuffer, prevBuffer: agPrevBuffer } = _ag.render();
1728
+ _lastTermBuffer = termBuffer;
1729
+ const wasIncremental = !_noIncremental && agPrevBuffer !== null;
1730
+ const pipelineMs = performance.now() - pipelineStart;
1731
+ globalThis.__silvery_last_pipeline = {
1732
+ layout: pipelineMs,
1733
+ output: 0,
1734
+ total: pipelineMs,
1735
+ incremental: wasIncremental
1736
+ };
1737
+ globalThis.__silvery_render_count = (globalThis.__silvery_render_count ?? 0) + 1;
1738
+ {
1739
+ const acc = globalThis.__silvery_bench_phases;
1740
+ if (acc) {
1741
+ acc.total += pipelineMs;
1742
+ acc.pipelineCalls += 1;
1743
+ }
1744
+ }
1745
+ if (STRICT_MODE && wasIncremental) {
1746
+ const { buffer: freshBuffer } = executeRender(rootNode, dims.cols, dims.rows, null, {
1747
+ skipLayoutNotifications: true,
1748
+ skipScrollStateUpdates: true
1749
+ }, pipelineConfig);
1750
+ const { cellEquals, bufferToText } = (init_buffer(), __toCommonJS(buffer_exports));
1751
+ for (let y = 0; y < termBuffer.height; y++) for (let x = 0; x < termBuffer.width; x++) {
1752
+ const a = termBuffer.getCell(x, y);
1753
+ const b = freshBuffer.getCell(x, y);
1754
+ if (!cellEquals(a, b)) {
1755
+ let cellDebugInfo = "";
1756
+ const savedCellDbg = globalThis.__silvery_cell_debug;
1757
+ 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`;
1758
+ else if (savedCellDbg && savedCellDbg.x === x && savedCellDbg.y === y) cellDebugInfo = `\nCELL DEBUG: No nodes cover (${x},${y}) during incremental render\n`;
1759
+ else cellDebugInfo = `\nCELL DEBUG: Target cell (${x},${y}) differs from debug cell (${savedCellDbg?.x},${savedCellDbg?.y})\n`;
1760
+ let trapInfo = "";
1761
+ const trap = {
1762
+ x,
1763
+ y,
1764
+ log: []
1765
+ };
1766
+ globalThis.__silvery_write_trap = trap;
1767
+ try {
1768
+ executeRender(rootNode, dims.cols, dims.rows, null, {
1769
+ skipLayoutNotifications: true,
1770
+ skipScrollStateUpdates: true
1771
+ }, pipelineConfig);
1772
+ } catch {}
1773
+ globalThis.__silvery_write_trap = null;
1774
+ if (trap.log.length > 0) trapInfo = `\nWRITE TRAP (${trap.log.length} writes to (${x},${y})):\n${trap.log.join("\n")}\n`;
1775
+ else trapInfo = `\nWRITE TRAP: NO WRITES to (${x},${y})\n`;
1776
+ const incText = bufferToText(termBuffer);
1777
+ const freshText = bufferToText(freshBuffer);
1778
+ 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}}`;
1779
+ const contentAll = globalThis.__silvery_content_all;
1780
+ 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") : "";
1781
+ const msg = `SILVERY_STRICT (createApp): MISMATCH at (${x}, ${y}) on render #${_renderCount}\n incremental: ${cellStr(a)}\n fresh: ${cellStr(b)}` + statsStr + (() => {
1782
+ const traces = globalThis.__silvery_node_trace;
1783
+ if (!traces || traces.length === 0) return "";
1784
+ let out = "\n--- node trace ---";
1785
+ for (let ti = 0; ti < traces.length; ti++) {
1786
+ out += `\n renderPhase #${ti}:`;
1787
+ for (const t of traces[ti]) {
1788
+ out += `\n ${t.decision} ${t.id}(${t.type})@${t.depth} rect=${t.rect} prev=${t.prevLayout}`;
1789
+ out += ` hasPrev=${t.hasPrev} ancClr=${t.ancestorCleared} flags=[${t.flags}] layout∆=${t.layoutChanged}`;
1790
+ if (t.decision === "RENDER") {
1791
+ out += ` caa=${t.contentAreaAffected} crc=${t.contentRegionCleared} cnfr=${t.childrenNeedFreshRender}`;
1792
+ out += ` childPrev=${t.childHasPrev} childAnc=${t.childAncestorCleared} skipBg=${t.skipBgFill} bg=${t.bgColor ?? "none"}`;
1793
+ }
1794
+ }
1795
+ }
1796
+ return out;
1797
+ })() + cellDebugInfo + trapInfo + `\n--- incremental ---\n${incText}\n--- fresh ---\n${freshText}`;
1798
+ __require("node:fs").appendFileSync("/tmp/silvery-perf.log", msg + "\n");
1799
+ throw new IncrementalRenderMismatchError(msg);
1800
+ }
1801
+ }
1802
+ if (_perfLog) __require("node:fs").appendFileSync("/tmp/silvery-perf.log", `SILVERY_STRICT (createApp): render #${_renderCount} OK\n`);
1803
+ }
1804
+ const buf = createBuffer(termBuffer, rootNode);
1805
+ if (_perfLog) {
1806
+ const renderDuration = performance.now() - renderStart;
1807
+ const phases = globalThis.__silvery_last_pipeline;
1808
+ const detail = globalThis.__silvery_content_detail;
1809
+ const phaseStr = phases ? ` [measure=${phases.measure.toFixed(1)} layout=${phases.layout.toFixed(1)} content=${phases.content.toFixed(1)} output=${phases.output.toFixed(1)}]` : "";
1810
+ 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}]` : ""}` : "";
1811
+ __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`);
1812
+ }
1813
+ return buf;
1814
+ }
1815
+ if (_ansiTrace) __require("node:fs").appendFileSync("/tmp/silvery-trace.log", "=== INITIAL RENDER ===\n");
1816
+ currentBuffer = doRender();
1817
+ if (!headless) {
1818
+ if (_ansiTrace) __require("node:fs").appendFileSync("/tmp/silvery-trace.log", "=== ALT SCREEN + CLEAR ===\n");
1819
+ if (alternateScreen) {
1820
+ stdout.write("\x1B[?1049h");
1821
+ stdout.write("\x1B[2J\x1B[H");
1822
+ }
1823
+ stdout.write("\x1B[?25l");
1824
+ if (kittyOption != null && kittyOption !== false) if (kittyOption === true) {
1825
+ if ((await detectKittyFromStdio(stdout, stdin)).supported) {
1826
+ stdout.write(enableKittyKeyboard(defaultKittyFlags));
1827
+ kittyEnabled = true;
1828
+ kittyFlags = defaultKittyFlags;
1829
+ }
1830
+ } else {
1831
+ stdout.write(enableKittyKeyboard(kittyOption));
1832
+ kittyEnabled = true;
1833
+ kittyFlags = kittyOption;
1834
+ }
1835
+ else if (kittyOption == null) {
1836
+ stdout.write(enableKittyKeyboard(defaultKittyFlags));
1837
+ kittyEnabled = true;
1838
+ kittyFlags = defaultKittyFlags;
1839
+ }
1840
+ if (mouseOption) {
1841
+ stdout.write(enableMouse());
1842
+ mouseEnabled = true;
1843
+ }
1844
+ }
1845
+ if (_ansiTrace) __require("node:fs").appendFileSync("/tmp/silvery-trace.log", "=== RUNTIME.RENDER (initial) ===\n");
1846
+ runtime.render(currentBuffer);
1847
+ if (_perfLog) __require("node:fs").appendFileSync("/tmp/silvery-perf.log", `STARTUP: initial render done (render #${_renderCount}, incremental=${!_noIncremental})\n`);
1848
+ if (shouldGuardOutput) outputGuard = createOutputGuard();
1849
+ if (!headless) {
1850
+ runtimeContextValue.pause = () => {
1851
+ renderPaused = true;
1852
+ if (outputGuard) {
1853
+ outputGuard.dispose();
1854
+ outputGuard = null;
1855
+ }
1856
+ if (alternateScreen) stdout.write(leaveAlternateScreen());
1857
+ };
1858
+ runtimeContextValue.resume = () => {
1859
+ if (alternateScreen) stdout.write(enterAlternateScreen());
1860
+ renderPaused = false;
1861
+ if (shouldGuardOutput && !outputGuard) outputGuard = createOutputGuard();
1862
+ runtime.invalidate();
1863
+ _ag?.resetBuffer();
1864
+ if (!isRendering) {
1865
+ currentBuffer = doRender();
1866
+ runtime.render(currentBuffer);
1867
+ }
1868
+ };
1869
+ }
1870
+ let exitResolve;
1871
+ let exitResolved = false;
1872
+ const exitPromise = new Promise((resolve) => {
1873
+ exitResolve = () => {
1874
+ if (!exitResolved) {
1875
+ exitResolved = true;
1876
+ resolve();
1877
+ }
1878
+ };
1879
+ });
1880
+ exit = () => {
1881
+ if (shouldExit) return;
1882
+ shouldExit = true;
1883
+ if (!headless && stdout.isTTY) {
1884
+ const earlyDisable = [
1885
+ disableKittyKeyboard(),
1886
+ disableMouse(),
1887
+ "\x1B[?1004l"
1888
+ ].join("");
1889
+ try {
1890
+ writeSync(stdout.fd, earlyDisable);
1891
+ } catch {
1892
+ try {
1893
+ stdout.write(earlyDisable);
1894
+ } catch {}
1895
+ }
1896
+ }
1897
+ controller.abort();
1898
+ if (!inEventHandler) {
1899
+ cleanup();
1900
+ exitResolve();
1901
+ }
1902
+ };
1903
+ runtimeContextValue.exit = exit;
1904
+ let frameResolve = null;
1905
+ let framesDone = false;
1906
+ function emitFrame(buf) {
1907
+ if (frameResolve) {
1908
+ const resolve = frameResolve;
1909
+ frameResolve = null;
1910
+ resolve(buf);
1911
+ }
1912
+ }
1913
+ storeUnsubscribeFn = store.subscribe(() => {
1914
+ if (shouldExit) return;
1915
+ if (_ansiTrace) {
1916
+ const _case = inEventHandler ? "1:event" : isRendering ? "2:rendering" : "3:standalone";
1917
+ const stack = (/* @__PURE__ */ new Error()).stack?.split("\n").slice(1, 5).join("\n") ?? "";
1918
+ __require("node:fs").appendFileSync("/tmp/silvery-trace.log", `=== SUBSCRIPTION (case ${_case}, render #${_renderCount + 1}) ===\n${stack}\n`);
1919
+ }
1920
+ if (inEventHandler) {
1921
+ pendingRerender = true;
1922
+ return;
1923
+ }
1924
+ if (isRendering) {
1925
+ if (!pendingRerender) {
1926
+ pendingRerender = true;
1927
+ queueMicrotask(() => {
1928
+ if (!pendingRerender) return;
1929
+ pendingRerender = false;
1930
+ if (!shouldExit && !isRendering) {
1931
+ if (_perfLog) __require("node:fs").appendFileSync("/tmp/silvery-perf.log", `SUBSCRIPTION: deferred microtask render (case 2, render #${_renderCount + 1})\n`);
1932
+ isRendering = true;
1933
+ try {
1934
+ currentBuffer = doRender();
1935
+ runtime.render(currentBuffer);
1936
+ } finally {
1937
+ isRendering = false;
1938
+ }
1939
+ }
1940
+ });
1941
+ }
1942
+ return;
1943
+ }
1944
+ if (_perfLog) __require("node:fs").appendFileSync("/tmp/silvery-perf.log", `SUBSCRIPTION: immediate render (case 3, render #${_renderCount + 1})\n`);
1945
+ isRendering = true;
1946
+ try {
1947
+ currentBuffer = doRender();
1948
+ runtime.render(currentBuffer);
1949
+ } finally {
1950
+ isRendering = false;
1951
+ }
1952
+ });
1953
+ function createProviderEventStream(name, provider) {
1954
+ return map(provider.events(), (event) => ({
1955
+ type: `${name}:${String(event.type)}`,
1956
+ provider: name,
1957
+ event: String(event.type),
1958
+ data: event.data
1959
+ }));
1960
+ }
1961
+ /**
1962
+ * Write selection overlay to stdout after a render.
1963
+ * Appends inverse-video ANSI sequences over selected cells.
1964
+ */
1965
+ function writeSelectionOverlay() {
1966
+ if (!selectionEnabled || !selectionState.range || !currentBuffer) return;
1967
+ const mode = alternateScreen ? "fullscreen" : "inline";
1968
+ const overlay = renderSelectionOverlay(selectionState.range, currentBuffer._buffer, mode, selectionState.scope);
1969
+ if (overlay) target.write(overlay);
1970
+ }
1971
+ /**
1972
+ * Push the current rendered frame to the virtual scrollback buffer.
1973
+ */
1974
+ function pushToScrollback() {
1975
+ if (!scrollback || !currentBuffer) return;
1976
+ const lines = currentBuffer.text.split("\n");
1977
+ scrollback.push(lines);
1978
+ }
1979
+ /**
1980
+ * Render the virtual scrollback view (historical content) to the terminal.
1981
+ * When scrolled up, replaces the live app content with historical rows.
1982
+ */
1983
+ function renderVirtualScrollbackView() {
1984
+ if (!scrollback || virtualScrollOffset <= 0) return;
1985
+ const dims = target.getDims();
1986
+ const rows = scrollback.getVisibleRows(virtualScrollOffset, dims.rows);
1987
+ let out = "";
1988
+ for (let row = 0; row < rows.length; row++) out += `\x1b[${row + 1};1H\x1b[2K${rows[row] ?? ""}`;
1989
+ const indicator = ` ↑ ${virtualScrollOffset} lines `;
1990
+ const indicatorCol = Math.max(1, dims.cols - indicator.length + 1);
1991
+ out += `\x1b[1;${indicatorCol}H\x1b[7m${indicator}\x1b[27m`;
1992
+ target.write(out);
1993
+ }
1994
+ /**
1995
+ * Render search highlights for the current match with inverse video.
1996
+ */
1997
+ function renderSearchHighlights() {
1998
+ if (!searchState.active || searchState.currentMatch < 0) return;
1999
+ const match = searchState.matches[searchState.currentMatch];
2000
+ if (!match) return;
2001
+ const dims = target.getDims();
2002
+ let screenRow;
2003
+ if (scrollback && virtualScrollOffset > 0) {
2004
+ const firstVisibleLine = scrollback.totalLines - virtualScrollOffset - dims.rows;
2005
+ screenRow = match.row - firstVisibleLine;
2006
+ } else screenRow = match.row;
2007
+ if (screenRow < 0 || screenRow >= dims.rows) return;
2008
+ let out = `\x1b[${screenRow + 1};${match.startCol + 1}H\x1b[7m`;
2009
+ for (let col = match.startCol; col <= match.endCol; col++) if (currentBuffer && virtualScrollOffset <= 0) out += currentBuffer._buffer.getCell(col, screenRow).char;
2010
+ else out += searchState.query[col - match.startCol] ?? " ";
2011
+ out += "\x1B[27m";
2012
+ target.write(out);
2013
+ }
2014
+ /**
2015
+ * Render the search bar at the bottom of the screen.
2016
+ */
2017
+ function renderSearchBarOverlay() {
2018
+ if (!searchState.active) return;
2019
+ const dims = target.getDims();
2020
+ const bar = renderSearchBar(searchState, dims.cols);
2021
+ target.write(`\x1b[${dims.rows};1H${bar}`);
2022
+ }
2023
+ /**
2024
+ * Search function for virtual scrollback — converts line matches to SearchMatch[].
2025
+ */
2026
+ function searchScrollback(query) {
2027
+ if (!scrollback || !query) return [];
2028
+ const matchingLines = scrollback.search(query);
2029
+ const lowerQuery = query.toLowerCase();
2030
+ const matches = [];
2031
+ for (const lineIdx of matchingLines) {
2032
+ const plain = (scrollback.getVisibleRows(scrollback.totalLines - lineIdx - 1, 1)[0] ?? "").replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
2033
+ let col = plain.toLowerCase().indexOf(lowerQuery);
2034
+ while (col !== -1) {
2035
+ matches.push({
2036
+ row: lineIdx,
2037
+ startCol: col,
2038
+ endCol: col + query.length - 1
2039
+ });
2040
+ col = plain.toLowerCase().indexOf(lowerQuery, col + 1);
2041
+ }
2042
+ }
2043
+ return matches;
2044
+ }
2045
+ /**
2046
+ * Run a single event's handler (state mutation only, no render).
2047
+ * Returns true if processing should continue, false if app should exit.
2048
+ *
2049
+ * Intercepts mouse events for selection and virtual inline mode.
2050
+ */
2051
+ function runEventHandler(event) {
2052
+ if (scrollback && searchState.active && event.type === "term:key") {
2053
+ const data = event.data;
2054
+ if (data.key.escape) {
2055
+ const [next] = searchUpdate({ type: "close" }, searchState);
2056
+ searchState = next;
2057
+ virtualScrollOffset = 0;
2058
+ return true;
2059
+ }
2060
+ if (data.key.return && !data.key.shift) {
2061
+ const [next, effects] = searchUpdate({ type: "nextMatch" }, searchState, searchScrollback);
2062
+ searchState = next;
2063
+ for (const eff of effects) if (eff.type === "scrollTo") virtualScrollOffset = Math.max(0, scrollback.totalLines - eff.row - target.getDims().rows);
2064
+ return true;
2065
+ }
2066
+ if (data.key.return && data.key.shift) {
2067
+ const [next, effects] = searchUpdate({ type: "prevMatch" }, searchState, searchScrollback);
2068
+ searchState = next;
2069
+ for (const eff of effects) if (eff.type === "scrollTo") virtualScrollOffset = Math.max(0, scrollback.totalLines - eff.row - target.getDims().rows);
2070
+ return true;
2071
+ }
2072
+ if (data.key.backspace) {
2073
+ const [next, effects] = searchUpdate({ type: "backspace" }, searchState, searchScrollback);
2074
+ searchState = next;
2075
+ for (const eff of effects) if (eff.type === "scrollTo") virtualScrollOffset = Math.max(0, scrollback.totalLines - eff.row - target.getDims().rows);
2076
+ return true;
2077
+ }
2078
+ if (data.key.leftArrow) {
2079
+ const [next] = searchUpdate({ type: "cursorLeft" }, searchState);
2080
+ searchState = next;
2081
+ return true;
2082
+ }
2083
+ if (data.key.rightArrow) {
2084
+ const [next] = searchUpdate({ type: "cursorRight" }, searchState);
2085
+ searchState = next;
2086
+ return true;
2087
+ }
2088
+ if (data.input && !data.key.ctrl && !data.key.meta) {
2089
+ const [next, effects] = searchUpdate({
2090
+ type: "input",
2091
+ char: data.input
2092
+ }, searchState, searchScrollback);
2093
+ searchState = next;
2094
+ for (const eff of effects) if (eff.type === "scrollTo") virtualScrollOffset = Math.max(0, scrollback.totalLines - eff.row - target.getDims().rows);
2095
+ return true;
2096
+ }
2097
+ }
2098
+ if (scrollback && event.type === "term:key") {
2099
+ const data = event.data;
2100
+ if (data.input === "f" && data.key.ctrl) {
2101
+ const [next] = searchUpdate({ type: "open" }, searchState);
2102
+ searchState = next;
2103
+ return true;
2104
+ }
2105
+ }
2106
+ if (scrollback && event.event === "mouse" && event.data) {
2107
+ const mouseData = event.data;
2108
+ if (mouseData.action === "wheel") {
2109
+ const scrollLines = 3;
2110
+ if (mouseData.delta && mouseData.delta < 0) virtualScrollOffset = Math.min(virtualScrollOffset + scrollLines, Math.max(0, scrollback.totalLines - target.getDims().rows));
2111
+ else virtualScrollOffset = Math.max(0, virtualScrollOffset - scrollLines);
2112
+ return true;
2113
+ }
2114
+ }
2115
+ if (selectionEnabled && event.event === "mouse" && event.data) {
2116
+ const mouseData = event.data;
2117
+ if (mouseData.button === 0) {
2118
+ if (mouseData.action === "down") {
2119
+ if (selectionState.range) {
2120
+ const [cleared] = terminalSelectionUpdate({ type: "clear" }, selectionState);
2121
+ selectionState = cleared;
2122
+ }
2123
+ const agRoot = getContainerRoot(container);
2124
+ const hit = agRoot ? selectionHitTest(agRoot, mouseData.x, mouseData.y) : null;
2125
+ const scope = hit ? findContainBoundary(hit) : null;
2126
+ const [next] = terminalSelectionUpdate({
2127
+ type: "start",
2128
+ col: mouseData.x,
2129
+ row: mouseData.y,
2130
+ scope
2131
+ }, selectionState);
2132
+ selectionState = next;
2133
+ notifySelectionListeners();
2134
+ if (currentBuffer) {
2135
+ runtime.invalidate();
2136
+ currentBuffer = doRender();
2137
+ runtime.render(currentBuffer);
2138
+ writeSelectionOverlay();
2139
+ }
2140
+ } else if (mouseData.action === "move" && selectionState.selecting) {
2141
+ const [next] = terminalSelectionUpdate({
2142
+ type: "extend",
2143
+ col: mouseData.x,
2144
+ row: mouseData.y
2145
+ }, selectionState);
2146
+ selectionState = next;
2147
+ notifySelectionListeners();
2148
+ if (currentBuffer) {
2149
+ runtime.render(currentBuffer);
2150
+ writeSelectionOverlay();
2151
+ }
2152
+ return true;
2153
+ } else if (mouseData.action === "up" && selectionState.selecting) {
2154
+ const [next] = terminalSelectionUpdate({ type: "finish" }, selectionState);
2155
+ selectionState = next;
2156
+ notifySelectionListeners();
2157
+ if (next.range && currentBuffer) {
2158
+ const text = extractText(currentBuffer._buffer, next.range, { scope: next.scope });
2159
+ if (text.length > 0) {
2160
+ const base64 = globalThis.Buffer.from(text).toString("base64");
2161
+ target.write(`\x1b]52;c;${base64}\x07`);
2162
+ }
2163
+ }
2164
+ if (currentBuffer) {
2165
+ runtime.render(currentBuffer);
2166
+ writeSelectionOverlay();
2167
+ }
2168
+ }
2169
+ }
2170
+ }
2171
+ if (selectionEnabled && event.type === "term:key" && selectionState.range) {
2172
+ const [next] = terminalSelectionUpdate({ type: "clear" }, selectionState);
2173
+ selectionState = next;
2174
+ notifySelectionListeners();
2175
+ if (currentBuffer) {
2176
+ runtime.invalidate();
2177
+ currentBuffer = doRender();
2178
+ runtime.render(currentBuffer);
2179
+ }
2180
+ }
2181
+ if (scrollback && virtualScrollOffset > 0 && event.type === "term:key") {
2182
+ virtualScrollOffset = 0;
2183
+ return true;
2184
+ }
2185
+ return invokeEventHandler(event, handlers, createHandlerContext(store, focusManager, container), mouseEventState, container);
2186
+ }
2187
+ /**
2188
+ * Process a batch of events — run all handlers, then render once.
2189
+ *
2190
+ * This is the key optimization for press-and-hold / auto-repeat keys.
2191
+ * When events arrive faster than renders (e.g., 30/sec auto-repeat vs
2192
+ * 50ms renders), we batch all pending handlers into a single render pass.
2193
+ *
2194
+ * For a batch of 3 'j' presses: handler1 → handler2 → handler3 → render.
2195
+ * The cursor moves 3 positions, but we only pay one render cost.
2196
+ */
2197
+ async function processEventBatch(events) {
2198
+ try {
2199
+ var _usingCtx$1 = _usingCtx();
2200
+ if (shouldExit || events.length === 0) return null;
2201
+ _renderCount = 0;
2202
+ _eventStart = performance.now();
2203
+ const _perfSpan = _usingCtx$1.u(perfLog.span?.("keypress", (() => {
2204
+ startTracking();
2205
+ const keyEvents = events.filter((e) => e.type === "term:key");
2206
+ return { key: keyEvents.length > 0 ? keyEvents.map((e) => e.data.input).join(",") : events[0]?.type ?? "unknown" };
2207
+ })()));
2208
+ if (!headless) {
2209
+ for (let i = events.length - 1; i >= 0; i--) {
2210
+ const event = events[i];
2211
+ if (event.type !== "term:key") continue;
2212
+ const data = event.data;
2213
+ if (data.input === "z" && data.key.ctrl && suspendOption) if (!(onSuspendHook?.() === false)) {
2214
+ events.splice(i, 1);
2215
+ performSuspend(captureTerminalState({
2216
+ alternateScreen,
2217
+ cursorHidden: true,
2218
+ mouse: mouseEnabled,
2219
+ kitty: kittyEnabled,
2220
+ kittyFlags,
2221
+ bracketedPaste: true,
2222
+ rawMode: true,
2223
+ focusReporting: focusReportingEnabled
2224
+ }), stdout, stdin, () => {
2225
+ runtime.invalidate();
2226
+ onResumeHook?.();
2227
+ });
2228
+ } else events.splice(i, 1);
2229
+ if (data.input === "c" && data.key.ctrl && exitOnCtrlCOption) {
2230
+ if (!(onInterruptHook?.() === false)) {
2231
+ exit();
2232
+ return null;
2233
+ }
2234
+ events.splice(i, 1);
2235
+ }
2236
+ }
2237
+ if (events.length === 0) return null;
2238
+ }
2239
+ inEventHandler = true;
2240
+ isRendering = true;
2241
+ for (const event of events) {
2242
+ if (event.type === "term:key") {
2243
+ const { input, key: parsedKey } = event.data;
2244
+ updateKeyboardModifiers(mouseEventState, parsedKey);
2245
+ if (parsedKey.eventType === "release" || isModifierOnlyEvent(input, parsedKey)) {
2246
+ for (const listener of runtimeInputListeners) listener(input, parsedKey);
2247
+ if (shouldExit) {
2248
+ inEventHandler = false;
2249
+ return null;
2250
+ }
2251
+ continue;
2252
+ }
2253
+ let focusConsumed = false;
2254
+ if (focusManager.activeElement) focusConsumed = handleFocusNavigation(input, parsedKey, focusManager, container) === "consumed";
2255
+ if (!focusConsumed) for (const listener of runtimeInputListeners) listener(input, parsedKey);
2256
+ } else if (event.type === "term:paste") {
2257
+ const { text } = event.data;
2258
+ for (const listener of runtimePasteListeners) listener(text);
2259
+ } else if (event.type === "term:focus") {
2260
+ const { focused } = event.data;
2261
+ for (const listener of runtimeFocusListeners) listener(focused);
2262
+ }
2263
+ if (shouldExit) {
2264
+ inEventHandler = false;
2265
+ return null;
2266
+ }
2267
+ if (event.type === "term:key") {
2268
+ const { input, key: k } = event.data;
2269
+ if (k.eventType === "release") continue;
2270
+ if (isModifierOnlyEvent(input, k)) continue;
2271
+ }
2272
+ const result = runEventHandler(event);
2273
+ if (result === false) {
2274
+ isRendering = false;
2275
+ inEventHandler = false;
2276
+ exit();
2277
+ return null;
2278
+ }
2279
+ if (result === "flush") {
2280
+ pendingRerender = false;
2281
+ currentBuffer = doRender();
2282
+ runtime.render(currentBuffer);
2283
+ await Promise.resolve();
2284
+ if (pendingRerender) {
2285
+ pendingRerender = false;
2286
+ currentBuffer = doRender();
2287
+ runtime.render(currentBuffer);
2288
+ }
2289
+ }
2290
+ }
2291
+ pendingRerender = false;
2292
+ try {
2293
+ currentBuffer = doRender();
2294
+ } finally {
2295
+ isRendering = false;
2296
+ }
2297
+ let flushCount = 0;
2298
+ const maxFlushes = 5;
2299
+ while (flushCount < maxFlushes) {
2300
+ await Promise.resolve();
2301
+ if (!pendingRerender) break;
2302
+ pendingRerender = false;
2303
+ isRendering = true;
2304
+ try {
2305
+ currentBuffer = doRender();
2306
+ } finally {
2307
+ isRendering = false;
2308
+ }
2309
+ flushCount++;
2310
+ }
2311
+ currentBuffer._buffer.markAllRowsDirty();
2312
+ inEventHandler = false;
2313
+ const runtimeStart = performance.now();
2314
+ runtime.render(currentBuffer);
2315
+ pushToScrollback();
2316
+ if (virtualScrollOffset > 0) renderVirtualScrollbackView();
2317
+ writeSelectionOverlay();
2318
+ renderSearchHighlights();
2319
+ renderSearchBarOverlay();
2320
+ const runtimeMs = performance.now() - runtimeStart;
2321
+ if (_perfLog) {
2322
+ const totalMs = performance.now() - _eventStart;
2323
+ __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`);
2324
+ }
2325
+ if (_perfSpan) checkBudget(events[0]?.type ?? "batch", performance.now() - _eventStart);
2326
+ return currentBuffer;
2327
+ } catch (_) {
2328
+ _usingCtx$1.e = _;
2329
+ } finally {
2330
+ _usingCtx$1.d();
2331
+ }
2332
+ }
2333
+ const eventQueue = [];
2334
+ let eventQueueResolve = null;
2335
+ const eventLoop = async () => {
2336
+ const allEvents = merge(...Object.entries(providers).map(([name, provider]) => createProviderEventStream(name, provider)));
2337
+ const pumpEvents = async () => {
2338
+ try {
2339
+ for await (const event of takeUntil(allEvents, signal)) {
2340
+ eventQueue.push(event);
2341
+ if (eventQueueResolve) {
2342
+ const resolve = eventQueueResolve;
2343
+ eventQueueResolve = null;
2344
+ resolve();
2345
+ }
2346
+ if (shouldExit) break;
2347
+ }
2348
+ } finally {
2349
+ if (eventQueueResolve) {
2350
+ const resolve = eventQueueResolve;
2351
+ eventQueueResolve = null;
2352
+ resolve();
2353
+ }
2354
+ }
2355
+ };
2356
+ if (needsProbe) try {
2357
+ const wasRaw = stdin.isRaw;
2358
+ if (stdin.isTTY && !wasRaw) {
2359
+ stdin.setRawMode(true);
2360
+ stdin.resume();
2361
+ stdin.setEncoding("utf8");
2362
+ }
2363
+ const probeRead = () => new Promise((resolve) => {
2364
+ const onData = (data) => {
2365
+ stdin.off("data", onData);
2366
+ resolve(data);
2367
+ };
2368
+ stdin.on("data", onData);
2369
+ });
2370
+ const probeResult = await detectTextSizingSupport((data) => outputGuard ? outputGuard.writeStdout(data) : stdout.write(data), probeRead, 500);
2371
+ if (probeResult.supported !== textSizingEnabled) {
2372
+ textSizingEnabled = probeResult.supported;
2373
+ if (effectiveCaps) {
2374
+ effectiveCaps = {
2375
+ ...effectiveCaps,
2376
+ textSizingSupported: textSizingEnabled
2377
+ };
2378
+ pipelineConfig = createPipeline({ caps: effectiveCaps });
2379
+ runtime.setOutputPhaseFn(pipelineConfig.outputPhaseFn);
2380
+ }
2381
+ _ag = null;
2382
+ runtime.invalidate();
2383
+ if (!isRendering) {
2384
+ isRendering = true;
2385
+ try {
2386
+ currentBuffer = doRender();
2387
+ runtime.render(currentBuffer);
2388
+ } finally {
2389
+ isRendering = false;
2390
+ }
2391
+ }
2392
+ }
2393
+ if (stdin.isTTY && !wasRaw) {
2394
+ stdin.setRawMode(false);
2395
+ stdin.pause();
2396
+ }
2397
+ } catch {}
2398
+ if (needsWidthDetection) try {
2399
+ const wasRaw = stdin.isRaw;
2400
+ if (stdin.isTTY && !wasRaw) {
2401
+ stdin.setRawMode(true);
2402
+ stdin.resume();
2403
+ stdin.setEncoding("utf8");
2404
+ }
2405
+ const stdinHandlers = [];
2406
+ const stdinListener = (data) => {
2407
+ for (const handler of stdinHandlers) handler(data);
2408
+ };
2409
+ stdin.on("data", stdinListener);
2410
+ const detector = createWidthDetector({
2411
+ write: (data) => outputGuard ? outputGuard.writeStdout(data) : stdout.write(data),
2412
+ onData: (handler) => {
2413
+ stdinHandlers.push(handler);
2414
+ return () => {
2415
+ const idx = stdinHandlers.indexOf(handler);
2416
+ if (idx >= 0) stdinHandlers.splice(idx, 1);
2417
+ };
2418
+ },
2419
+ timeoutMs: 200
2420
+ });
2421
+ const widthConfig = await detector.detect();
2422
+ detector.dispose();
2423
+ stdin.off("data", stdinListener);
2424
+ if (effectiveCaps) {
2425
+ const updatedCaps = applyWidthConfig(effectiveCaps, widthConfig);
2426
+ if (updatedCaps.textEmojiWide !== effectiveCaps.textEmojiWide || updatedCaps.textSizingSupported !== effectiveCaps.textSizingSupported) {
2427
+ effectiveCaps = updatedCaps;
2428
+ pipelineConfig = createPipeline({ caps: effectiveCaps });
2429
+ runtime.setOutputPhaseFn(pipelineConfig.outputPhaseFn);
2430
+ _ag = null;
2431
+ runtime.invalidate();
2432
+ if (!isRendering) {
2433
+ isRendering = true;
2434
+ try {
2435
+ currentBuffer = doRender();
2436
+ runtime.render(currentBuffer);
2437
+ } finally {
2438
+ isRendering = false;
2439
+ }
2440
+ }
2441
+ }
2442
+ }
2443
+ if (stdin.isTTY && !wasRaw) {
2444
+ stdin.setRawMode(false);
2445
+ stdin.pause();
2446
+ }
2447
+ } catch {}
2448
+ pumpEvents().catch((err) => log.error?.(`pumpEvents failed: ${err}`));
2449
+ if (focusReportingOption && !focusReportingEnabled) {
2450
+ enableFocusReporting((s) => outputGuard ? outputGuard.writeStdout(s) : stdout.write(s));
2451
+ focusReportingEnabled = true;
2452
+ }
2453
+ try {
2454
+ while (!shouldExit && !signal.aborted) {
2455
+ if (eventQueue.length === 0) await new Promise((resolve) => {
2456
+ eventQueueResolve = resolve;
2457
+ signal.addEventListener("abort", () => resolve(), { once: true });
2458
+ });
2459
+ if (shouldExit || signal.aborted) break;
2460
+ if (eventQueue.length === 0) continue;
2461
+ const maxDrainSpins = 32;
2462
+ let drainSpins = 0;
2463
+ const yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
2464
+ await yieldToEventLoop();
2465
+ let prevLen = eventQueue.length;
2466
+ while (drainSpins < maxDrainSpins) {
2467
+ await yieldToEventLoop();
2468
+ const curLen = eventQueue.length;
2469
+ if (curLen === prevLen) break;
2470
+ prevLen = curLen;
2471
+ drainSpins++;
2472
+ }
2473
+ if (_perfLog) __require("node:fs").appendFileSync("/tmp/silvery-perf.log", `DRAIN: spins=${drainSpins}, batch=${eventQueue.length}\n`);
2474
+ const _g = globalThis;
2475
+ _g.__silvery_last_drain_spins = drainSpins;
2476
+ _g.__silvery_last_batch_size = eventQueue.length;
2477
+ _g.__silvery_batch_count = (_g.__silvery_batch_count ?? 0) + 1;
2478
+ const buf = await processEventBatch(eventQueue.splice(0));
2479
+ if (buf) emitFrame(buf);
2480
+ }
2481
+ } finally {
2482
+ framesDone = true;
2483
+ if (frameResolve) {
2484
+ const resolve = frameResolve;
2485
+ frameResolve = null;
2486
+ resolve(null);
2487
+ }
2488
+ if (shouldExit && !cleanedUp && !headless && stdin.isTTY) try {
2489
+ stdin.removeAllListeners("data");
2490
+ stdin.resume();
2491
+ await new Promise((resolve) => setTimeout(resolve, 15));
2492
+ while (stdin.read() !== null);
2493
+ stdin.pause();
2494
+ } catch {}
2495
+ cleanup();
2496
+ exitResolve();
2497
+ }
2498
+ };
2499
+ eventLoop().catch((err) => log.error?.(`eventLoop failed: ${err}`));
2500
+ return {
2501
+ get text() {
2502
+ return currentBuffer.text;
2503
+ },
2504
+ get root() {
2505
+ return getContainerRoot(container);
2506
+ },
2507
+ get buffer() {
2508
+ return currentBuffer?._buffer ?? null;
2509
+ },
2510
+ get store() {
2511
+ return store;
2512
+ },
2513
+ waitUntilExit() {
2514
+ return exitPromise;
2515
+ },
2516
+ unmount() {
2517
+ exit();
2518
+ },
2519
+ [Symbol.dispose]() {
2520
+ exit();
2521
+ },
2522
+ async press(rawKey) {
2523
+ try {
2524
+ var _usingCtx3 = _usingCtx();
2525
+ const pressStart = performance.now();
2526
+ const [input, parsedKey] = parseKey(useKittyMode ? keyToKittyAnsi(rawKey) : keyToAnsi(rawKey));
2527
+ const _perfSpan = _usingCtx3.u(perfLog.span?.("keypress", (() => {
2528
+ startTracking();
2529
+ return { key: input || rawKey };
2530
+ })()));
2531
+ if (input === "c" && parsedKey.ctrl && exitOnCtrlCOption) {
2532
+ if (!(onInterruptHook?.() === false)) {
2533
+ exit();
2534
+ return;
2535
+ }
2536
+ }
2537
+ for (const listener of runtimeInputListeners) listener(input, parsedKey);
2538
+ inEventHandler = true;
2539
+ isRendering = true;
2540
+ if (handleFocusNavigation(input, parsedKey, focusManager, container) === "consumed") {
2541
+ pendingRerender = false;
2542
+ isRendering = false;
2543
+ inEventHandler = false;
2544
+ doRender();
2545
+ await Promise.resolve();
2546
+ if (_perfSpan) checkBudget(input || rawKey, performance.now() - pressStart);
2547
+ return;
2548
+ }
2549
+ if (dispatchKeyToHandlers(input, parsedKey, handlers, createHandlerContext(store, focusManager, container)) === "exit") {
2550
+ isRendering = false;
2551
+ inEventHandler = false;
2552
+ exit();
2553
+ return;
2554
+ }
2555
+ pendingRerender = false;
2556
+ try {
2557
+ currentBuffer = doRender();
2558
+ } finally {
2559
+ isRendering = false;
2560
+ }
2561
+ let flushCount = 0;
2562
+ const maxFlushes = 5;
2563
+ while (flushCount < maxFlushes) {
2564
+ await Promise.resolve();
2565
+ if (!pendingRerender) break;
2566
+ pendingRerender = false;
2567
+ isRendering = true;
2568
+ try {
2569
+ currentBuffer = doRender();
2570
+ } finally {
2571
+ isRendering = false;
2572
+ }
2573
+ flushCount++;
2574
+ }
2575
+ if (flushCount > 0) currentBuffer._buffer.markAllRowsDirty();
2576
+ inEventHandler = false;
2577
+ runtime.render(currentBuffer);
2578
+ if (_perfSpan) checkBudget(input || rawKey, performance.now() - pressStart);
2579
+ } catch (_) {
2580
+ _usingCtx3.e = _;
2581
+ } finally {
2582
+ _usingCtx3.d();
2583
+ }
2584
+ },
2585
+ [Symbol.asyncIterator]() {
2586
+ return {
2587
+ async next() {
2588
+ if (framesDone || shouldExit) return {
2589
+ done: true,
2590
+ value: void 0
2591
+ };
2592
+ const buf = await new Promise((resolve) => {
2593
+ if (framesDone || shouldExit) {
2594
+ resolve(null);
2595
+ return;
2596
+ }
2597
+ frameResolve = resolve;
2598
+ });
2599
+ if (!buf) return {
2600
+ done: true,
2601
+ value: void 0
2602
+ };
2603
+ return {
2604
+ done: false,
2605
+ value: buf
2606
+ };
2607
+ },
2608
+ async return() {
2609
+ exit();
2610
+ return {
2611
+ done: true,
2612
+ value: void 0
2613
+ };
2614
+ }
2615
+ };
2616
+ }
2617
+ };
2618
+ }
2619
+ //#endregion
2620
+ //#region ../packages/ag-term/src/runtime/run.tsx
2621
+ init_detect();
2622
+ init_ThemeContext();
2623
+ //#endregion
2624
+ //#region ../packages/create/src/pipe.ts
2625
+ function pipe(base, ...plugins) {
2626
+ let result = base;
2627
+ for (const plugin of plugins) result = plugin(result);
2628
+ return result;
2629
+ }
2630
+ //#endregion
2631
+ //#region ../packages/ag-react/src/with-react.ts
2632
+ /**
2633
+ * Associate a React element with an app for rendering.
2634
+ *
2635
+ * In pipe() composition, this captures the element so that subsequent
2636
+ * plugins and the final run() know what to render.
2637
+ *
2638
+ * The plugin wraps `run()` to automatically pass the element:
2639
+ * - Before: `app.run(<Board />, options)`
2640
+ * - After: `app.run()` (element already bound)
2641
+ *
2642
+ * @param element - The React element to render
2643
+ * @returns Plugin function that binds the element to the app
2644
+ */
2645
+ function withReact(element) {
2646
+ return (app) => {
2647
+ const originalRun = app.run;
2648
+ return Object.assign(Object.create(app), {
2649
+ element,
2650
+ run(...args) {
2651
+ if (args.length === 0 || typeof args[0] !== "object" || args[0] === null || !("type" in args[0])) return originalRun.call(app, element, ...args);
2652
+ return originalRun.apply(app, args);
2653
+ }
2654
+ });
2655
+ };
2656
+ }
2657
+ //#endregion
2658
+ //#region ../packages/ag-term/src/features/clipboard-capability.ts
2659
+ /**
2660
+ * Create an OSC 52 clipboard capability.
2661
+ *
2662
+ * Encodes text as base64 and writes the OSC 52 sequence directly.
2663
+ * This is a standalone factory that doesn't require the full ClipboardBackend.
2664
+ */
2665
+ function createOSC52Clipboard(write) {
2666
+ return { copy(text) {
2667
+ write(`\x1b]52;c;${Buffer.from(text, "utf-8").toString("base64")}\x07`);
2668
+ } };
2669
+ }
2670
+ //#endregion
2671
+ //#region ../packages/ag-term/src/plugins/with-terminal.ts
2672
+ /**
2673
+ * withTerminal(process, opts?) — Plugin: ALL terminal I/O
2674
+ *
2675
+ * This plugin represents the terminal I/O layer in silvery's plugin
2676
+ * composition model. It wraps all terminal concerns:
2677
+ * - stdin → typed events (term:key, term:mouse, term:paste)
2678
+ * - stdout → alternate screen, raw mode, incremental diff output
2679
+ * - SIGWINCH → term:resize
2680
+ * - Lifecycle (Ctrl+Z suspend/resume, Ctrl+C exit)
2681
+ * - Protocols (SGR mouse, Kitty keyboard, bracketed paste)
2682
+ *
2683
+ * In the current architecture, terminal I/O is handled by createApp()
2684
+ * and the TermProvider. This plugin provides the declarative interface
2685
+ * for pipe() composition:
2686
+ *
2687
+ * ```tsx
2688
+ * const app = pipe(
2689
+ * createApp(store),
2690
+ * withReact(<Board />),
2691
+ * withTerminal(process, { mouse: true, kitty: true }),
2692
+ * withFocus(),
2693
+ * withDomEvents(),
2694
+ * )
2695
+ * ```
2696
+ *
2697
+ * @example
2698
+ * ```tsx
2699
+ * import { pipe, withTerminal } from '@silvery/create'
2700
+ *
2701
+ * // All protocols enabled by default
2702
+ * const app = pipe(baseApp, withTerminal(process))
2703
+ *
2704
+ * // Customize terminal options
2705
+ * const app = pipe(baseApp, withTerminal(process, {
2706
+ * mouse: true,
2707
+ * kitty: true,
2708
+ * paste: true,
2709
+ * onSuspend: () => saveState(),
2710
+ * onResume: () => restoreState(),
2711
+ * }))
2712
+ * ```
2713
+ */
2714
+ /**
2715
+ * Configure terminal I/O for an app.
2716
+ *
2717
+ * In pipe() composition, this captures the process streams and options
2718
+ * so that run() configures terminal I/O correctly.
2719
+ *
2720
+ * The plugin wraps `run()` to inject terminal options:
2721
+ * - stdin/stdout from the process object
2722
+ * - Protocol options (mouse, kitty, paste)
2723
+ * - Lifecycle handlers (suspend, resume, interrupt)
2724
+ *
2725
+ * @param proc - Process object with stdin/stdout (typically `process`)
2726
+ * @param options - Terminal configuration
2727
+ * @returns Plugin function that binds terminal config to the app
2728
+ */
2729
+ function withTerminal(proc, options = {}) {
2730
+ const termConfig = {
2731
+ mouse: options.mouse ?? true,
2732
+ kitty: options.kitty ?? true,
2733
+ paste: options.paste ?? true,
2734
+ alternateScreen: options.alternateScreen ?? true,
2735
+ suspendOnCtrlZ: options.suspendOnCtrlZ ?? true,
2736
+ exitOnCtrlC: options.exitOnCtrlC ?? true,
2737
+ ...options,
2738
+ proc
2739
+ };
2740
+ return (app) => {
2741
+ const originalRun = app.run;
2742
+ const registry = app.capabilityRegistry ?? createCapabilityRegistry();
2743
+ const clipboard = createOSC52Clipboard((data) => {
2744
+ proc.stdout.write(data);
2745
+ });
2746
+ registry.register(CLIPBOARD_CAPABILITY, clipboard);
2747
+ const autoDetect = termConfig.autoDetect ?? false;
2748
+ const timeoutMs = termConfig.autoDetectTimeoutMs ?? 200;
2749
+ let colorSchemeDetector;
2750
+ let widthDetector;
2751
+ let detectionReady;
2752
+ if (autoDetect && proc.stdin.isTTY) {
2753
+ const write = (data) => {
2754
+ proc.stdout.write(data);
2755
+ };
2756
+ const onData = (handler) => {
2757
+ const bufferHandler = (chunk) => {
2758
+ handler(typeof chunk === "string" ? chunk : chunk.toString());
2759
+ };
2760
+ proc.stdin.on("data", bufferHandler);
2761
+ return () => {
2762
+ proc.stdin.removeListener("data", bufferHandler);
2763
+ };
2764
+ };
2765
+ colorSchemeDetector = createColorSchemeDetector({
2766
+ write,
2767
+ onData,
2768
+ timeoutMs
2769
+ });
2770
+ colorSchemeDetector.start();
2771
+ widthDetector = createWidthDetector({
2772
+ write,
2773
+ onData,
2774
+ timeoutMs
2775
+ });
2776
+ detectionReady = widthDetector.detect().then((config) => {
2777
+ if (enhanced.terminalOptions?.proc) enhanced._detectedWidthConfig = config;
2778
+ }).catch(() => {});
2779
+ } else detectionReady = Promise.resolve();
2780
+ const enhanced = Object.assign(Object.create(app), {
2781
+ terminalOptions: termConfig,
2782
+ capabilityRegistry: registry,
2783
+ clipboardCapability: clipboard,
2784
+ colorSchemeDetector,
2785
+ widthDetector,
2786
+ detectionReady,
2787
+ _detectedWidthConfig: null,
2788
+ run(...args) {
2789
+ const runOptions = {};
2790
+ let existingOptions;
2791
+ if (args.length > 0 && typeof args[args.length - 1] === "object" && args[args.length - 1] !== null) {
2792
+ existingOptions = args[args.length - 1];
2793
+ if ("type" in existingOptions && "props" in existingOptions) existingOptions = void 0;
2794
+ }
2795
+ Object.assign(runOptions, existingOptions ?? {}, {
2796
+ stdin: proc.stdin,
2797
+ stdout: proc.stdout,
2798
+ mouse: termConfig.mouse,
2799
+ kitty: termConfig.kitty,
2800
+ alternateScreen: termConfig.alternateScreen,
2801
+ suspendOnCtrlZ: termConfig.suspendOnCtrlZ,
2802
+ exitOnCtrlC: termConfig.exitOnCtrlC,
2803
+ textSizing: termConfig.textSizing,
2804
+ widthDetection: termConfig.widthDetection ?? "auto",
2805
+ focusReporting: termConfig.focusReporting,
2806
+ onSuspend: termConfig.onSuspend,
2807
+ onResume: termConfig.onResume,
2808
+ onInterrupt: termConfig.onInterrupt,
2809
+ capabilityRegistry: registry
2810
+ });
2811
+ if (existingOptions) {
2812
+ const newArgs = [...args];
2813
+ newArgs[newArgs.length - 1] = runOptions;
2814
+ return originalRun.apply(app, newArgs);
2815
+ }
2816
+ return originalRun.call(app, ...args, runOptions);
2817
+ }
2818
+ });
2819
+ return enhanced;
2820
+ };
2821
+ }
2822
+ //#endregion
2823
+ //#region ../packages/create/src/internal/input-router.ts
2824
+ /**
2825
+ * Compare handler entries for dispatch order:
2826
+ * - Higher priority dispatches first
2827
+ * - Same priority: lower insertion order (first registered) wins
2828
+ */
2829
+ function compareEntries(a, b) {
2830
+ if (a.priority !== b.priority) return b.priority - a.priority;
2831
+ return a.order - b.order;
2832
+ }
2833
+ /**
2834
+ * Create a priority-based input router.
2835
+ *
2836
+ * The `invalidate` callback is injected by the caller (typically wired to the
2837
+ * store/render pipeline by withDomEvents). This keeps the router decoupled
2838
+ * from silvery internals.
2839
+ */
2840
+ function createInputRouter(options) {
2841
+ const { invalidate } = options;
2842
+ let nextOrder = 0;
2843
+ const mouseHandlers = [];
2844
+ const keyHandlers = [];
2845
+ const overlays = [];
2846
+ function addEntry(list, priority, handler) {
2847
+ const entry = {
2848
+ priority,
2849
+ order: nextOrder++,
2850
+ handler
2851
+ };
2852
+ list.push(entry);
2853
+ list.sort(compareEntries);
2854
+ return () => {
2855
+ const idx = list.indexOf(entry);
2856
+ if (idx !== -1) list.splice(idx, 1);
2857
+ };
2858
+ }
2859
+ function dispatch(list, event) {
2860
+ for (const entry of list) if (entry.handler(event)) return true;
2861
+ return false;
2862
+ }
2863
+ return {
2864
+ registerMouseHandler(priority, handler) {
2865
+ return addEntry(mouseHandlers, priority, handler);
2866
+ },
2867
+ dispatchMouse(event) {
2868
+ return dispatch(mouseHandlers, event);
2869
+ },
2870
+ registerKeyHandler(priority, handler) {
2871
+ return addEntry(keyHandlers, priority, handler);
2872
+ },
2873
+ dispatchKey(event) {
2874
+ return dispatch(keyHandlers, event);
2875
+ },
2876
+ invalidate,
2877
+ registerOverlay(priority, renderer) {
2878
+ return addEntry(overlays, priority, renderer);
2879
+ },
2880
+ getOverlays() {
2881
+ return overlays.map((entry) => entry.handler);
2882
+ }
2883
+ };
2884
+ }
2885
+ //#endregion
2886
+ //#region ../packages/ag-term/src/plugins/with-dom-events.ts
2887
+ /**
2888
+ * Add DOM-style mouse event dispatch to an App.
2889
+ *
2890
+ * This plugin creates a mouse event processor and ensures that
2891
+ * click(), doubleClick(), and wheel() methods on the app dispatch
2892
+ * events through the render tree with proper bubbling.
2893
+ *
2894
+ * The App's buildApp() already sets up mouse event processing.
2895
+ * This plugin is provided for explicit composition via pipe()
2896
+ * and ensures the focus manager is connected for click-to-focus.
2897
+ *
2898
+ * @param options - Configuration (focusManager for click-to-focus)
2899
+ * @returns Plugin function that enhances an App with DOM event dispatch
2900
+ */
2901
+ function withDomEvents(options = {}) {
2902
+ return (app) => {
2903
+ const fm = options.focusManager ?? app.focusManager;
2904
+ const processorOptions = {};
2905
+ if (fm) processorOptions.focusManager = fm;
2906
+ const mouseState = createMouseEventProcessor(processorOptions);
2907
+ const existingRegistry = app.capabilityRegistry;
2908
+ const registry = options.capabilityRegistry ?? existingRegistry ?? createCapabilityRegistry();
2909
+ let invalidateCallback = () => {};
2910
+ const router = createInputRouter({ invalidate: () => invalidateCallback() });
2911
+ registry.register(INPUT_ROUTER, router);
2912
+ const enhanced = new Proxy(app, { get(target, prop, receiver) {
2913
+ if (prop === "capabilityRegistry") return registry;
2914
+ if (prop === "inputRouter") return router;
2915
+ if (prop === "click") return async function enhancedClick(x, y, clickOptions) {
2916
+ const button = clickOptions?.button ?? 0;
2917
+ if (!router.dispatchMouse({
2918
+ x,
2919
+ y,
2920
+ button,
2921
+ type: "mousedown"
2922
+ })) {
2923
+ const root = target.getContainer();
2924
+ processMouseEvent(mouseState, {
2925
+ button,
2926
+ x,
2927
+ y,
2928
+ action: "down",
2929
+ shift: false,
2930
+ meta: false,
2931
+ ctrl: false
2932
+ }, root);
2933
+ }
2934
+ if (!router.dispatchMouse({
2935
+ x,
2936
+ y,
2937
+ button,
2938
+ type: "mouseup"
2939
+ })) {
2940
+ const root = target.getContainer();
2941
+ processMouseEvent(mouseState, {
2942
+ button,
2943
+ x,
2944
+ y,
2945
+ action: "up",
2946
+ shift: false,
2947
+ meta: false,
2948
+ ctrl: false
2949
+ }, root);
2950
+ }
2951
+ await Promise.resolve();
2952
+ return receiver;
2953
+ };
2954
+ if (prop === "doubleClick") return async function enhancedDoubleClick(x, y, clickOptions) {
2955
+ const button = clickOptions?.button ?? 0;
2956
+ const root = target.getContainer();
2957
+ const parsed = {
2958
+ button,
2959
+ x,
2960
+ y,
2961
+ action: "down",
2962
+ shift: false,
2963
+ meta: false,
2964
+ ctrl: false
2965
+ };
2966
+ processMouseEvent(mouseState, parsed, root);
2967
+ processMouseEvent(mouseState, {
2968
+ ...parsed,
2969
+ action: "up"
2970
+ }, root);
2971
+ processMouseEvent(mouseState, parsed, root);
2972
+ processMouseEvent(mouseState, {
2973
+ ...parsed,
2974
+ action: "up"
2975
+ }, root);
2976
+ await Promise.resolve();
2977
+ return receiver;
2978
+ };
2979
+ if (prop === "wheel") return async function enhancedWheel(x, y, delta) {
2980
+ if (!router.dispatchMouse({
2981
+ x,
2982
+ y,
2983
+ button: 0,
2984
+ type: "wheel"
2985
+ })) {
2986
+ const root = target.getContainer();
2987
+ processMouseEvent(mouseState, {
2988
+ button: 0,
2989
+ x,
2990
+ y,
2991
+ action: "wheel",
2992
+ delta,
2993
+ shift: false,
2994
+ meta: false,
2995
+ ctrl: false
2996
+ }, root);
2997
+ }
2998
+ await Promise.resolve();
2999
+ return receiver;
3000
+ };
3001
+ if (prop === "run") {
3002
+ const originalRun = Reflect.get(target, prop, receiver);
3003
+ if (typeof originalRun === "function") return function enhancedRun(...args) {
3004
+ const inject = { capabilityRegistry: registry };
3005
+ if (args.length === 0) return originalRun.call(target, inject);
3006
+ else if (args.length === 1) {
3007
+ const arg = args[0];
3008
+ if (arg && typeof arg === "object" && "type" in arg) return originalRun.call(target, arg, inject);
3009
+ else return originalRun.call(target, {
3010
+ ...arg,
3011
+ ...inject
3012
+ });
3013
+ } else {
3014
+ const opts = {
3015
+ ...args[1],
3016
+ ...inject
3017
+ };
3018
+ return originalRun.call(target, args[0], opts);
3019
+ }
3020
+ };
3021
+ }
3022
+ return Reflect.get(target, prop, receiver);
3023
+ } });
3024
+ const appAny = app;
3025
+ if (typeof appAny.store?.setState === "function") invalidateCallback = () => {
3026
+ appAny.store.setState((prev) => ({
3027
+ ...prev,
3028
+ _inv: (prev._inv ?? 0) + 1
3029
+ }));
3030
+ };
3031
+ return enhanced;
3032
+ };
3033
+ }
3034
+ //#endregion
3035
+ //#region ../packages/test/src/compare-buffers.ts
3036
+ init_buffer();
3037
+ //#endregion
3038
+ //#region ../packages/ag-term/src/plugins/with-diagnostics.ts
3039
+ init_buffer();
3040
+ createContext(null);
3041
+ createContext({
3042
+ activeId: void 0,
3043
+ isFocusEnabled: true,
3044
+ add() {},
3045
+ remove() {},
3046
+ activate() {},
3047
+ deactivate() {},
3048
+ enableFocus() {},
3049
+ disableFocus() {},
3050
+ focusNext() {},
3051
+ focusPrevious() {},
3052
+ focus() {},
3053
+ blur() {}
3054
+ });
3055
+ //#endregion
3056
+ export { createApp as a, pipe as i, withTerminal as n, useApp as o, withReact as r, withDomEvents as t };