silvery 0.23.0 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{Text-Ci6UCSTL.mjs → Text-4tWKabKH.mjs} +5 -5
- package/dist/{Text-Ci6UCSTL.mjs.map → Text-4tWKabKH.mjs.map} +1 -1
- package/dist/{ag-q9lRlKlt.mjs → ag-B-7XAHL8.mjs} +9 -9
- package/dist/{ag-q9lRlKlt.mjs.map → ag-B-7XAHL8.mjs.map} +1 -1
- package/dist/animation-CZ4x2R7C.mjs.map +1 -1
- package/dist/{ansi-zmNzgkPB.d.mts → ansi-qYRTDp5m.d.mts} +1 -1
- package/dist/{ansi-zmNzgkPB.d.mts.map → ansi-qYRTDp5m.d.mts.map} +1 -1
- package/dist/{chain-bridge-CkYcMxlq.mjs → chain-bridge-B7LNJwVF.mjs} +8 -8
- package/dist/{chain-bridge-CkYcMxlq.mjs.map → chain-bridge-B7LNJwVF.mjs.map} +1 -1
- package/dist/{chunk-BEJ448es.mjs → chunk-hT5z_Zn9.mjs} +1 -1
- package/dist/cli-CuNV0hTE.mjs +4 -0
- package/dist/devtools-BuTq07Zb.mjs +2 -0
- package/dist/{devtools-td2AjFwc.mjs → devtools-akTjVxxX.mjs} +4 -5
- package/dist/{devtools-td2AjFwc.mjs.map → devtools-akTjVxxX.mjs.map} +1 -1
- package/dist/{easing-C5MrdO7V.d.mts → easing-8OlNopWv.d.mts} +1 -1
- package/dist/{easing-C5MrdO7V.d.mts.map → easing-8OlNopWv.d.mts.map} +1 -1
- package/dist/{eta-BwYWEjfS.mjs → eta-CPZ45g_P.mjs} +5 -1
- package/dist/{eta-BwYWEjfS.mjs.map → eta-CPZ45g_P.mjs.map} +1 -1
- package/dist/flexily-zero-adapter-VoBGHG14.mjs.map +1 -1
- package/dist/{hit-registry-D_nxOivS.d.mts → hit-registry-DRV2ig-Y.d.mts} +2 -2
- package/dist/{hit-registry-D_nxOivS.d.mts.map → hit-registry-DRV2ig-Y.d.mts.map} +1 -1
- package/dist/{image-CIF75xMr.mjs → image-o2WpbrD1.mjs} +22 -22
- package/dist/image-o2WpbrD1.mjs.map +1 -0
- package/dist/{index-377zdPLP.d.mts → index-3vAxA_EQ.d.mts} +1 -1
- package/dist/{index-377zdPLP.d.mts.map → index-3vAxA_EQ.d.mts.map} +1 -1
- package/dist/{index-BcSr0RAQ.d.mts → index-BKujGENR.d.mts} +6 -6
- package/dist/index-BKujGENR.d.mts.map +1 -0
- package/dist/{index-DlNmckTq.d.mts → index-C6oOd9eE.d.mts} +2 -2
- package/dist/{index-DlNmckTq.d.mts.map → index-C6oOd9eE.d.mts.map} +1 -1
- package/dist/{index-DLnpxDAm.d.mts → index-DhYF3m7V.d.mts} +2 -2
- package/dist/{index-DLnpxDAm.d.mts.map → index-DhYF3m7V.d.mts.map} +1 -1
- package/dist/index.d.mts +9 -9
- package/dist/index.mjs +14 -14
- package/dist/index.mjs.map +1 -1
- package/dist/{layout-engine-BgR58vL0.mjs → layout-engine-BYp49XPD.mjs} +2 -2
- package/dist/{layout-engine-BgR58vL0.mjs.map → layout-engine-BYp49XPD.mjs.map} +1 -1
- package/dist/{layout-engine-DNjTha8f.mjs → layout-engine-D0dXw5fr.mjs} +1 -1
- package/dist/{layout-signals-CGYbHmxy.mjs → layout-signals-uQadGDmO.mjs} +2 -2
- package/dist/{layout-signals-CGYbHmxy.mjs.map → layout-signals-uQadGDmO.mjs.map} +1 -1
- package/dist/{mouse-events-WQ2h-uqG.mjs → mouse-events-Pg-XKR3T.mjs} +24 -3
- package/dist/{mouse-events-WQ2h-uqG.mjs.map → mouse-events-Pg-XKR3T.mjs.map} +1 -1
- package/dist/{mouse-events-B2z5tbsL.d.mts → mouse-events-UoUWNIdS.d.mts} +22 -3
- package/dist/{mouse-events-B2z5tbsL.d.mts.map → mouse-events-UoUWNIdS.d.mts.map} +1 -1
- package/dist/{multi-progress-zCWHEm9P.mjs → multi-progress-Bvd1BYED.mjs} +2 -2
- package/dist/{multi-progress-zCWHEm9P.mjs.map → multi-progress-Bvd1BYED.mjs.map} +1 -1
- package/dist/{multi-progress-BaEFfHKP.d.mts → multi-progress-DPOCjomS.d.mts} +2 -2
- package/dist/{multi-progress-BaEFfHKP.d.mts.map → multi-progress-DPOCjomS.d.mts.map} +1 -1
- package/dist/{node-CJ3J_rW-.mjs → node-Cl-kzQcl.mjs} +4 -4
- package/dist/node-Cl-kzQcl.mjs.map +1 -0
- package/dist/{progress-6vJ7nxUg.mjs → progress-1r6M_ZAP.mjs} +4 -4
- package/dist/{progress-6vJ7nxUg.mjs.map → progress-1r6M_ZAP.mjs.map} +1 -1
- package/dist/{progress-bar-BlFNsZL8.mjs → progress-bar-DzW1DzNj.mjs} +3 -3
- package/dist/{progress-bar-BlFNsZL8.mjs.map → progress-bar-DzW1DzNj.mjs.map} +1 -1
- package/dist/{reconciler--_-biN23.mjs → reconciler-lsTL1y8i.mjs} +45 -5
- package/dist/{reconciler--_-biN23.mjs.map → reconciler-lsTL1y8i.mjs.map} +1 -1
- package/dist/{render-string-DtHZP0cU.mjs → render-string-2ZcV8n3B.mjs} +6 -6
- package/dist/{render-string-DtHZP0cU.mjs.map → render-string-2ZcV8n3B.mjs.map} +1 -1
- package/dist/{render-string-DaghHpLN.mjs → render-string-BJZqo5Rr.mjs} +1 -1
- package/dist/runtime.d.mts +3 -3
- package/dist/runtime.mjs +3 -3
- package/dist/{schemes-DWQPxXAi.mjs → schemes-C4SUgVea.mjs} +2 -2
- package/dist/{schemes-DWQPxXAi.mjs.map → schemes-C4SUgVea.mjs.map} +1 -1
- package/dist/{spinner-B50hILqx.mjs → spinner-Ck3SdCpW.mjs} +2 -2
- package/dist/{spinner-B50hILqx.mjs.map → spinner-Ck3SdCpW.mjs.map} +1 -1
- package/dist/{spinner-DN10UfF0.d.mts → spinner-CpcGtQ9L.d.mts} +2 -2
- package/dist/{spinner-DN10UfF0.d.mts.map → spinner-CpcGtQ9L.d.mts.map} +1 -1
- package/dist/{src-BqeBFB3X.mjs → src-B6MuVlOK.mjs} +3 -3
- package/dist/src-B6MuVlOK.mjs.map +1 -0
- package/dist/{src-DbqcSrhy.mjs → src-BZOfzNeo.mjs} +32 -9
- package/dist/src-BZOfzNeo.mjs.map +1 -0
- package/dist/{src-BXeTHHlL.mjs → src-ChuXo--x.mjs} +25 -23
- package/dist/src-ChuXo--x.mjs.map +1 -0
- package/dist/{src-BOLdi3WG.mjs → src-DdGaRDi9.mjs} +3 -3
- package/dist/{src-BOLdi3WG.mjs.map → src-DdGaRDi9.mjs.map} +1 -1
- package/dist/{src-B8lf1kiR.mjs → src-DorsnS70.mjs} +8 -1
- package/dist/src-DorsnS70.mjs.map +1 -0
- package/dist/{steps-B2kBmFpb.d.mts → steps-DV34gm_Z.d.mts} +1 -1
- package/dist/{steps-B2kBmFpb.d.mts.map → steps-DV34gm_Z.d.mts.map} +1 -1
- package/dist/svg-NmV9dgB9.mjs.map +1 -1
- package/dist/{svg-VUr78l5d.d.mts → svg-wvIEu-RS.d.mts} +1 -1
- package/dist/{svg-VUr78l5d.d.mts.map → svg-wvIEu-RS.d.mts.map} +1 -1
- package/dist/term.d.mts +3 -3
- package/dist/term.mjs +4 -4
- package/dist/test.d.mts +3 -3
- package/dist/test.mjs +9 -9
- package/dist/test.mjs.map +1 -1
- package/dist/theme.d.mts +2 -2
- package/dist/theme.mjs +3 -3
- package/dist/{types-D_E0FbqA.d.mts → types-VyreAPAB.d.mts} +1 -1
- package/dist/{types-D_E0FbqA.d.mts.map → types-VyreAPAB.d.mts.map} +1 -1
- package/dist/ui/animation.d.mts +2 -2
- package/dist/ui/ansi.d.mts +1 -1
- package/dist/ui/cli.d.mts +3 -3
- package/dist/ui/cli.mjs +4 -4
- package/dist/ui/image.d.mts +1 -1
- package/dist/ui/image.mjs +1 -1
- package/dist/ui/progress.d.mts +4 -4
- package/dist/ui/progress.mjs +4 -4
- package/dist/ui/react.d.mts +1 -1
- package/dist/ui/react.mjs +2 -2
- package/dist/ui/react.mjs.map +1 -1
- package/dist/ui/recording-chrome-react.d.mts +1 -1
- package/dist/ui/recording-chrome-react.mjs +1 -1
- package/dist/ui/recording-chrome.d.mts +1 -1
- package/dist/ui/utils.mjs +1 -1
- package/dist/ui/wrappers.d.mts +2 -2
- package/dist/ui/wrappers.mjs +1 -1
- package/dist/ui.d.mts +6 -6
- package/dist/ui.mjs +6 -6
- package/dist/{unicode-CxyxVGmB.mjs → unicode-X43-fdGF.mjs} +3 -3
- package/dist/unicode-X43-fdGF.mjs.map +1 -0
- package/dist/{useLatest-UOvWy0ZW.d.mts → useLatest-D9p0ZDAC.d.mts} +2 -2
- package/dist/{useLatest-UOvWy0ZW.d.mts.map → useLatest-D9p0ZDAC.d.mts.map} +1 -1
- package/dist/{useLayout-D5cE8OCI.mjs → useLayout-Cx01rHVI.mjs} +3 -3
- package/dist/{useLayout-D5cE8OCI.mjs.map → useLayout-Cx01rHVI.mjs.map} +1 -1
- package/dist/{with-text-input-DLoSCBrm.d.mts → with-text-input-CW8MR9Th.d.mts} +3 -3
- package/dist/{with-text-input-DLoSCBrm.d.mts.map → with-text-input-CW8MR9Th.d.mts.map} +1 -1
- package/dist/{wrap-measurer-registration-IV2HtcCd.d.mts → wrap-measurer-registration-DNBmtFNM.d.mts} +3 -3
- package/dist/{wrap-measurer-registration-IV2HtcCd.d.mts.map → wrap-measurer-registration-DNBmtFNM.d.mts.map} +1 -1
- package/dist/{wrapper-CD29afOs.mjs → wrapper-DpMauwdI.mjs} +51 -33
- package/dist/wrapper-DpMauwdI.mjs.map +1 -0
- package/dist/{wrappers-CInBJm44.mjs → wrappers-DHMzvbRo.mjs} +5 -5
- package/dist/{wrappers-CInBJm44.mjs.map → wrappers-DHMzvbRo.mjs.map} +1 -1
- package/dist/yoga-adapter-B-DCP4Bx.mjs +2 -0
- package/dist/{yoga-adapter-9uab5Qn-.mjs → yoga-adapter-CB_7pLyl.mjs} +2 -2
- package/dist/{yoga-adapter-9uab5Qn-.mjs.map → yoga-adapter-CB_7pLyl.mjs.map} +1 -1
- package/package.json +2 -2
- package/dist/backend-DSiSIc-h.mjs +0 -13396
- package/dist/backend-DSiSIc-h.mjs.map +0 -1
- package/dist/cli-VNqUKlXw.mjs +0 -4
- package/dist/devtools-BF4IMumr.mjs +0 -2
- package/dist/image-CIF75xMr.mjs.map +0 -1
- package/dist/index-BcSr0RAQ.d.mts.map +0 -1
- package/dist/node-CJ3J_rW-.mjs.map +0 -1
- package/dist/src-B8lf1kiR.mjs.map +0 -1
- package/dist/src-BXeTHHlL.mjs.map +0 -1
- package/dist/src-BqeBFB3X.mjs.map +0 -1
- package/dist/src-DbqcSrhy.mjs.map +0 -1
- package/dist/unicode-CxyxVGmB.mjs.map +0 -1
- package/dist/wrapper-CD29afOs.mjs.map +0 -1
- package/dist/yoga-adapter-yqTl1aXK.mjs +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chain-bridge-CkYcMxlq.mjs","names":["isModifierOnly","NS","FRAME_HISTORY","PANIC_COOLDOWN_MS","EVICT_OLDER_THAN_MS","defaultWriteFile","NS","log"],"sources":["../packages/ag/src/focus-events.ts","../packages/create/src/pipe.ts","../packages/create/src/internal/capability-registry.ts","../packages/create/src/internal/capabilities.ts","../packages/ag-term/src/features/selection.ts","../packages/create/src/runtime/with-terminal-chain.ts","../packages/create/src/runtime/with-focus-chain.ts","../packages/commands/src/with-commands.ts","../packages/commands/src/with-keybindings.ts","../packages/test/src/compare-buffers.ts","../packages/ag-term/src/plugins/with-diagnostics.ts","../packages/ag-term/src/plugins/with-render.ts","../packages/create/src/runtime/base-app.ts","../packages/ink/src/with-ink-cursor.ts","../packages/ink/src/with-ink-focus.ts","../packages/ag-term/src/bytes-out-monitor.ts","../packages/ag-term/src/flicker-monitor.ts","../packages/ag-term/src/mem-monitor.ts","../packages/test/src/debug-mismatch.ts","../packages/ag-term/src/managed-caret.ts","../packages/ag-term/src/runtime/render-trace.ts","../packages/ag-term/src/scheduler.ts","../packages/create/src/runtime/with-paste-chain.ts","../packages/create/src/runtime/with-input-chain.ts","../packages/create/src/runtime/with-custom-events.ts","../packages/ag-term/src/runtime/wrap-measurer-registration.ts","../packages/ag-react/src/scope-context.ts","../packages/ag-react/src/ScopeProvider.tsx","../packages/ag/src/cls.ts","../packages/ag-term/src/strict-cls.ts","../packages/ag-term/src/runtime/cls-monitor.ts","../packages/ag-react/src/chain-bridge.ts"],"sourcesContent":["/**\n * DOM-level Focus and Keyboard Events for silvery\n *\n * Provides React DOM-compatible focus/keyboard event infrastructure:\n * - SilveryKeyEvent / SilveryFocusEvent synthetic event objects\n * - Event dispatch with capture/target/bubble phases (key events)\n * - Event dispatch with target + bubble (focus events)\n *\n * Follows the same patterns as mouse-events.ts for consistency.\n */\n\nimport type { Key } from \"./keys\"\nimport { getAncestorPath } from \"./tree-utils.js\"\nimport type { AgNode } from \"./types\"\n\n// ============================================================================\n// Event Types\n// ============================================================================\n\n/**\n * Synthetic keyboard event, mirroring React.KeyboardEvent / DOM KeyboardEvent.\n */\nexport interface SilveryKeyEvent {\n /** The printable character, or \"\" for non-printable keys */\n key: string\n /** Raw terminal input string */\n input: string\n /** Modifier keys */\n ctrl: boolean\n meta: boolean\n shift: boolean\n super: boolean\n hyper: boolean\n /** Kitty event type */\n eventType?: \"press\" | \"repeat\" | \"release\"\n /** Deepest focusable node that received this event */\n target: AgNode\n /** Node whose handler is currently firing (changes during capture/bubble) */\n currentTarget: AgNode\n /** Stop event from propagating further */\n stopPropagation(): void\n /** Prevent default behavior */\n preventDefault(): void\n /** Whether stopPropagation() was called */\n readonly propagationStopped: boolean\n /** Whether preventDefault() was called */\n readonly defaultPrevented: boolean\n /** Raw parsed key data */\n nativeEvent: { input: string; key: Key }\n}\n\n/**\n * Synthetic focus event, mirroring React.FocusEvent / DOM FocusEvent.\n */\nexport interface SilveryFocusEvent {\n /** The node gaining or losing focus */\n target: AgNode\n /** The other node involved (losing focus on 'focus', gaining on 'blur') */\n relatedTarget: AgNode | null\n /** Event type */\n type: \"focus\" | \"blur\"\n /** Node whose handler is currently firing (changes during bubble) */\n currentTarget: AgNode\n /** Stop event from bubbling to parent nodes */\n stopPropagation(): void\n /** Whether stopPropagation() was called */\n readonly propagationStopped: boolean\n}\n\n// ============================================================================\n// Focus Event Handler Props (added to BoxProps)\n// ============================================================================\n\nexport interface FocusEventProps {\n /** Whether this node can receive focus */\n focusable?: boolean\n /** Whether this node should receive focus on mount */\n autoFocus?: boolean\n /** Whether this node creates a focus scope (focus trapping boundary) */\n focusScope?: boolean\n /** ID of the node to focus when pressing Up from this node */\n nextFocusUp?: string\n /** ID of the node to focus when pressing Down from this node */\n nextFocusDown?: string\n /** ID of the node to focus when pressing Left from this node */\n nextFocusLeft?: string\n /** ID of the node to focus when pressing Right from this node */\n nextFocusRight?: string\n /** Called when this node gains focus */\n onFocus?: (event: SilveryFocusEvent) => void\n /** Called when this node loses focus */\n onBlur?: (event: SilveryFocusEvent) => void\n /** Called on key down (bubble phase) */\n onKeyDown?: (event: SilveryKeyEvent, dispatch?: (msg: unknown) => void) => void\n /** Called on key up (bubble phase) */\n onKeyUp?: (event: SilveryKeyEvent, dispatch?: (msg: unknown) => void) => void\n /** Called on key down (capture phase — fires before target) */\n onKeyDownCapture?: (event: SilveryKeyEvent) => void\n}\n\n// ============================================================================\n// Event Factories\n// ============================================================================\n\n/**\n * Create a synthetic keyboard event.\n */\nexport function createKeyEvent(input: string, key: Key, target: AgNode): SilveryKeyEvent {\n let propagationStopped = false\n let defaultPrevented = false\n\n return {\n key: input,\n input,\n ctrl: key.ctrl,\n meta: key.meta,\n shift: key.shift,\n super: key.super,\n hyper: key.hyper,\n eventType: key.eventType,\n target,\n currentTarget: target,\n nativeEvent: { input, key },\n get propagationStopped() {\n return propagationStopped\n },\n get defaultPrevented() {\n return defaultPrevented\n },\n stopPropagation() {\n propagationStopped = true\n },\n preventDefault() {\n defaultPrevented = true\n },\n }\n}\n\n/**\n * Create a synthetic focus event.\n */\nexport function createFocusEvent(\n type: \"focus\" | \"blur\",\n target: AgNode,\n relatedTarget: AgNode | null,\n): SilveryFocusEvent {\n let propagationStopped = false\n\n return {\n type,\n target,\n relatedTarget,\n currentTarget: target,\n get propagationStopped() {\n return propagationStopped\n },\n stopPropagation() {\n propagationStopped = true\n },\n }\n}\n\n// ============================================================================\n// Tree Walking\n// ============================================================================\n\n// ============================================================================\n// Event Dispatch\n// ============================================================================\n\n/**\n * Dispatch a keyboard event through the render tree with DOM-style\n * capture/target/bubble phases.\n *\n * For press/repeat events:\n * 1. Capture phase: root → target (onKeyDownCapture props)\n * 2. Target phase: target's onKeyDown\n * 3. Bubble phase: target parent → root (onKeyDown props)\n *\n * For release events:\n * 1. Target phase: target's onKeyUp\n * 2. Bubble phase: target parent → root (onKeyUp props)\n * (No capture phase for keyUp — deliberate simplification; React DOM has onKeyUpCapture)\n *\n * stopPropagation() halts traversal at any phase.\n */\nexport function dispatchKeyEvent(event: SilveryKeyEvent, dispatch?: (msg: unknown) => void): void {\n const path = getAncestorPath(event.target)\n const mutableEvent = event as { currentTarget: AgNode }\n\n // Release events → onKeyUp (no capture phase — deliberate simplification; React DOM has onKeyUpCapture)\n const isRelease = event.eventType === \"release\"\n const handlerProp = isRelease ? \"onKeyUp\" : \"onKeyDown\"\n\n // Capture phase: root → target (onKeyDownCapture — press/repeat only)\n if (!isRelease) {\n for (let i = path.length - 1; i > 0; i--) {\n if (event.propagationStopped) return\n const node = path[i]!\n const handler = (node.props as Record<string, unknown>).onKeyDownCapture as\n | ((e: SilveryKeyEvent) => void)\n | undefined\n if (handler) {\n mutableEvent.currentTarget = node\n handler(event)\n }\n }\n }\n\n // Target phase\n if (!event.propagationStopped) {\n const target = path[0]!\n mutableEvent.currentTarget = target\n const handler = (target.props as Record<string, unknown>)[handlerProp] as\n | ((e: SilveryKeyEvent, d?: (msg: unknown) => void) => void)\n | undefined\n if (handler) {\n handler(event, dispatch)\n }\n }\n\n // Bubble phase: target parent → root\n for (let i = 1; i < path.length; i++) {\n if (event.propagationStopped) return\n const node = path[i]!\n const handler = (node.props as Record<string, unknown>)[handlerProp] as\n | ((e: SilveryKeyEvent, d?: (msg: unknown) => void) => void)\n | undefined\n if (handler) {\n mutableEvent.currentTarget = node\n handler(event, dispatch)\n }\n }\n}\n\n/**\n * Dispatch a focus event through the render tree.\n *\n * Fires onFocus/onBlur on the target, then bubbles to ancestors.\n */\nexport function dispatchFocusEvent(event: SilveryFocusEvent): void {\n const handlerProp = event.type === \"focus\" ? \"onFocus\" : \"onBlur\"\n const path = getAncestorPath(event.target)\n const mutableEvent = event as { currentTarget: AgNode }\n\n for (const node of path) {\n if (event.propagationStopped) break\n\n const handler = (node.props as Record<string, unknown>)[handlerProp] as\n | ((e: SilveryFocusEvent) => void)\n | undefined\n if (handler) {\n mutableEvent.currentTarget = node\n handler(event)\n }\n }\n}\n","/**\n * pipe() — Compose app plugins left-to-right.\n *\n * The foundational composition function for silvery's plugin system.\n * Each plugin is a function `(app) => enhancedApp` that takes an app object\n * and returns an enhanced version with additional capabilities.\n *\n * Plugins compose left-to-right: `pipe(base, p1, p2)` = `p2(p1(base))`\n *\n * @example\n * ```tsx\n * import { pipe, createApp, withReact, withTerminal, withFocus, withDomEvents } from '@silvery/create'\n *\n * const app = pipe(\n * createApp(store),\n * withReact(<Board />),\n * withTerminal(process),\n * withFocus(),\n * withDomEvents(),\n * )\n * await app.run()\n * ```\n *\n * @example Typed plugin\n * ```tsx\n * type MyPlugin = (app: App) => App & { custom: () => void }\n *\n * const withCustom: MyPlugin = (app) => ({\n * ...app,\n * custom: () => console.log('hello'),\n * })\n *\n * const enhanced = pipe(baseApp, withCustom)\n * enhanced.custom() // typed!\n * ```\n */\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * A plugin function that enhances an app.\n *\n * Takes an app of type A and returns an enhanced app of type B.\n * The plugin can add new methods, override existing ones, or\n * wrap behavior via closures.\n */\nexport type AppPlugin<A, B> = (app: A) => B\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/**\n * Compose app plugins left-to-right.\n *\n * `pipe(base, p1, p2, p3)` is equivalent to `p3(p2(p1(base)))`.\n *\n * Each plugin receives the result of the previous plugin, allowing\n * progressive enhancement of the app object.\n *\n * Type inference works through the chain: if p1 adds `.cmd` and p2\n * requires it, TypeScript catches the error at the call site.\n */\nexport function pipe<A>(base: A): A\nexport function pipe<A, B>(base: A, p1: AppPlugin<A, B>): B\nexport function pipe<A, B, C>(base: A, p1: AppPlugin<A, B>, p2: AppPlugin<B, C>): C\nexport function pipe<A, B, C, D>(\n base: A,\n p1: AppPlugin<A, B>,\n p2: AppPlugin<B, C>,\n p3: AppPlugin<C, D>,\n): D\nexport function pipe<A, B, C, D, E>(\n base: A,\n p1: AppPlugin<A, B>,\n p2: AppPlugin<B, C>,\n p3: AppPlugin<C, D>,\n p4: AppPlugin<D, E>,\n): E\nexport function pipe<A, B, C, D, E, F>(\n base: A,\n p1: AppPlugin<A, B>,\n p2: AppPlugin<B, C>,\n p3: AppPlugin<C, D>,\n p4: AppPlugin<D, E>,\n p5: AppPlugin<E, F>,\n): F\nexport function pipe<A, B, C, D, E, F, G>(\n base: A,\n p1: AppPlugin<A, B>,\n p2: AppPlugin<B, C>,\n p3: AppPlugin<C, D>,\n p4: AppPlugin<D, E>,\n p5: AppPlugin<E, F>,\n p6: AppPlugin<F, G>,\n): G\nexport function pipe<A, B, C, D, E, F, G, H>(\n base: A,\n p1: AppPlugin<A, B>,\n p2: AppPlugin<B, C>,\n p3: AppPlugin<C, D>,\n p4: AppPlugin<D, E>,\n p5: AppPlugin<E, F>,\n p6: AppPlugin<F, G>,\n p7: AppPlugin<G, H>,\n): H\nexport function pipe(base: unknown, ...plugins: AppPlugin<unknown, unknown>[]): unknown {\n let result = base\n for (const plugin of plugins) {\n result = plugin(result)\n }\n return result\n}\n","/**\n * Capability Registry — symbol-keyed service registry for cross-feature communication.\n *\n * Each createApp instance gets its own registry. Interaction features use it to\n * discover each other (e.g., selection looks up clipboard capability).\n *\n * @internal Not exported from the public barrel.\n */\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/** Symbol-keyed service registry for cross-feature communication. */\nexport interface CapabilityRegistry {\n /** Register a capability. Overwrites any existing registration for the same key. */\n register<T>(key: symbol, capability: T): void\n\n /** Look up a capability. Returns undefined if not registered. */\n get<T>(key: symbol): T | undefined\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/** Create a new capability registry (one per app instance). */\nexport function createCapabilityRegistry(): CapabilityRegistry {\n const capabilities = new Map<symbol, unknown>()\n\n return {\n register<T>(key: symbol, capability: T): void {\n capabilities.set(key, capability)\n },\n\n get<T>(key: symbol): T | undefined {\n return capabilities.get(key) as T | undefined\n },\n }\n}\n","/**\n * Capability symbols — well-known keys for the CapabilityRegistry.\n *\n * Features register themselves under these symbols so other parts\n * of the composition chain can discover and interact with them.\n *\n * @internal Not exported from the public barrel.\n */\n\n/** Selection feature: text selection state + mouse handling. */\nexport const SELECTION_CAPABILITY = Symbol.for(\"silvery.selection\")\n\n/** Clipboard feature: copy/paste via OSC 52 or other backends. */\nexport const CLIPBOARD_CAPABILITY = Symbol.for(\"silvery.clipboard\")\n\n/** Copy-mode feature: keyboard-driven selection (Esc+v). */\nexport const COPY_MODE_CAPABILITY = Symbol.for(\"silvery.copy-mode\")\n\n/** Find feature: text search (Ctrl+F). */\nexport const FIND_CAPABILITY = Symbol.for(\"silvery.find\")\n\n/** Drag feature: drag-and-drop state + mouse handling. */\nexport const DRAG_CAPABILITY = Symbol.for(\"silvery.drag\")\n\n/** Input router: priority-based event dispatch for interaction features. */\nexport const INPUT_ROUTER = Symbol.for(\"silvery.input-router\")\n\n/** Color scheme detector: Mode 2031 dark/light reactive detection. */\nexport const COLOR_SCHEME_CAPABILITY = Symbol.for(\"silvery.color-scheme\")\n","/**\n * SelectionFeature — service wrapping the headless selection machine.\n *\n * Connects the pure `terminalSelectionUpdate` state machine to ag-term's\n * buffer for text extraction, clipboard for copy-on-select, and the\n * runtime's invalidate callback for render triggering.\n *\n * Mouse event handling:\n * - mousedown → start selection (character granularity)\n * - mousemove while selecting → extend selection range\n * - mouseup → finish selection, copy to clipboard if available\n *\n * run() exposes its inline selection owner through a bridge registered in the\n * CapabilityRegistry under SELECTION_CAPABILITY. Explicit/headless composition\n * can still create a standalone feature for copy mode.\n */\n\nimport {\n terminalSelectionUpdate,\n createTerminalSelectionState,\n extractText,\n type TerminalSelectionState,\n type SelectionRange,\n type SelectionEffect,\n} from \"@silvery/headless/selection\"\nimport type { TerminalBuffer } from \"../buffer\"\nimport type { ClipboardCapability } from \"./clipboard-capability\"\nimport { extractHtml } from \"../extract-html\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Observable selection state + mouse handlers. */\nexport interface SelectionFeature {\n /** Current selection state (getter). */\n readonly state: TerminalSelectionState\n\n /** Subscribe to state changes. Returns an unsubscribe function. */\n subscribe(listener: () => void): () => void\n\n /** Handle mouse down — start selection. */\n handleMouseDown(col: number, row: number, altKey: boolean): void\n\n /** Handle mouse move — extend selection while dragging. */\n handleMouseMove(col: number, row: number): void\n\n /** Handle mouse up — finish selection, trigger copy. */\n handleMouseUp(col: number, row: number): void\n\n /** Programmatically set a selection range (or null to clear). */\n setRange(range: SelectionRange | null): void\n\n /** Clear the current selection. */\n clear(): void\n\n /**\n * Copy the current selection to the clipboard, using the same extraction +\n * write path as mouse/drag copy. Drives keyboard copy-mode `yank`. No-op when\n * there is no selection, buffer, or clipboard.\n */\n copySelection(): void\n\n /** Clean up resources. */\n dispose(): void\n}\n\n/**\n * Options for creating a bridge SelectionFeature that delegates to\n * an external state owner (e.g., create-app's inline selection state).\n */\nexport interface SelectionBridgeOptions {\n /** Get the current selection state. */\n getState: () => TerminalSelectionState\n /** Subscribe to state changes. Returns an unsubscribe function. */\n subscribe: (listener: () => void) => () => void\n /** Set a selection range programmatically (used by copy-mode). */\n setRange: (range: SelectionRange | null) => void\n /** Clear the selection. */\n clear: () => void\n /**\n * Copy the current selection to the clipboard (used by copy-mode `yank`).\n * create-app supplies this to run its inline extract + OSC 52 write — the\n * same path mouse drag-copy uses. Optional: a bridge without it makes\n * `copySelection` a no-op (headless / test usage).\n */\n copy?: () => void\n}\n\n/** Options for creating a SelectionFeature. */\nexport interface SelectionFeatureOptions {\n /** Terminal buffer to extract text from (required for mouse selection / copy). */\n buffer?: TerminalBuffer\n /** Optional clipboard capability for copy-on-select. */\n clipboard?: ClipboardCapability\n /** Callback to trigger a render pass after state changes. */\n invalidate: () => void\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Create a SelectionFeature that wraps the headless selection machine.\n *\n * The feature manages subscriptions, processes effects from the machine,\n * and coordinates with clipboard and render invalidation.\n */\nexport function createSelectionFeature(options: SelectionFeatureOptions): SelectionFeature {\n const { buffer, clipboard, invalidate } = options\n\n let selectionState = createTerminalSelectionState()\n const listeners = new Set<() => void>()\n\n function notifyListeners(): void {\n for (const listener of listeners) {\n listener()\n }\n }\n\n function processEffects(effects: SelectionEffect[], richRange?: SelectionRange | null): void {\n for (const effect of effects) {\n if (effect.type === \"render\") {\n invalidate()\n } else if (effect.type === \"copy\" && clipboard) {\n if (clipboard.copyRich && richRange && buffer) {\n const html = extractHtml(buffer, richRange)\n clipboard.copyRich(effect.text, html)\n } else {\n clipboard.copy(effect.text)\n }\n }\n }\n }\n\n function updateState(\n newState: TerminalSelectionState,\n effects: SelectionEffect[],\n richRange?: SelectionRange | null,\n ): void {\n selectionState = newState\n notifyListeners()\n processEffects(effects, richRange)\n }\n\n return {\n get state(): TerminalSelectionState {\n return selectionState\n },\n\n subscribe(listener: () => void): () => void {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n },\n\n handleMouseDown(col: number, row: number, _altKey: boolean): void {\n const [newState, effects] = terminalSelectionUpdate(\n { type: \"start\", col, row, source: \"mouse\" },\n selectionState,\n )\n updateState(newState, effects)\n },\n\n handleMouseMove(col: number, row: number): void {\n if (!selectionState.selecting) return\n const [newState, effects] = terminalSelectionUpdate(\n { type: \"extend\", col, row, buffer: buffer! },\n selectionState,\n )\n updateState(newState, effects)\n },\n\n handleMouseUp(_col: number, _row: number): void {\n if (!selectionState.selecting) return\n const [newState, effects] = terminalSelectionUpdate({ type: \"finish\" }, selectionState)\n\n // Extract text and copy to clipboard on mouse up\n const copyEffects = [...effects]\n if (newState.range && clipboard && buffer) {\n // Semantic copy is the default (skips non-selectable margins/gutters/\n // padding, matching the highlight). This fallback path has no Shift+drag\n // raw-buffer mode, so it never opts out. rowMetadata joins soft-wrapped\n // rows into their logical line + precise trailing-space trimming.\n const text = extractText(buffer, newState.range, {\n rowMetadata: buffer.getRowMetadataArray(),\n })\n if (text.length > 0) {\n copyEffects.push({ type: \"copy\", text })\n }\n }\n\n updateState(newState, copyEffects, newState.range)\n },\n\n setRange(range: SelectionRange | null): void {\n if (range === null) {\n const [newState, effects] = terminalSelectionUpdate({ type: \"clear\" }, selectionState)\n updateState(newState, effects)\n } else {\n // Set range by starting at anchor and extending to head\n const [startState, startEffects] = terminalSelectionUpdate(\n { type: \"start\", col: range.anchor.col, row: range.anchor.row, source: \"keyboard\" },\n selectionState,\n )\n const [extendState, extendEffects] = terminalSelectionUpdate(\n { type: \"extend\", col: range.head.col, row: range.head.row },\n startState,\n )\n const [finishState, finishEffects] = terminalSelectionUpdate(\n { type: \"finish\" },\n extendState,\n )\n updateState(finishState, [...startEffects, ...extendEffects, ...finishEffects])\n }\n },\n\n clear(): void {\n const [newState, effects] = terminalSelectionUpdate({ type: \"clear\" }, selectionState)\n updateState(newState, effects)\n },\n\n copySelection(): void {\n const range = selectionState.range\n if (!range || !clipboard || !buffer) return\n // Same extraction the mouse-up copy path uses: semantic by default,\n // soft-wrap rejoin + trailing trim via row metadata. Route through the\n // shared processEffects \"copy\" handler so rich (extractHtml) vs plain\n // copy is decided identically to drag-copy.\n const text = extractText(buffer, range, {\n rowMetadata: buffer.getRowMetadataArray(),\n })\n if (text.length > 0) {\n processEffects([{ type: \"copy\", text }], range)\n }\n },\n\n dispose(): void {\n selectionState = createTerminalSelectionState()\n listeners.clear()\n },\n }\n}\n\n// ============================================================================\n// Bridge Implementation\n// ============================================================================\n\n/**\n * Create a bridge SelectionFeature that delegates to an external state owner.\n *\n * Used by create-app to expose its inline selection state to React hooks\n * (useSelection) and copy-mode (which calls setRange/clear) without\n * duplicating the state machine. Mouse handlers are no-ops — the external\n * owner (create-app's event loop) handles mouse events directly.\n */\nexport function createSelectionBridge(options: SelectionBridgeOptions): SelectionFeature {\n return {\n get state(): TerminalSelectionState {\n return options.getState()\n },\n\n subscribe(listener: () => void): () => void {\n return options.subscribe(listener)\n },\n\n // Mouse handlers are no-ops — create-app handles mouse events directly.\n handleMouseDown(_col: number, _row: number, _altKey: boolean): void {},\n handleMouseMove(_col: number, _row: number): void {},\n handleMouseUp(_col: number, _row: number): void {},\n\n setRange(range: SelectionRange | null): void {\n options.setRange(range)\n },\n\n clear(): void {\n options.clear()\n },\n\n copySelection(): void {\n options.copy?.()\n },\n\n dispose(): void {},\n }\n}\n","/**\n * withTerminalChain — apply-chain plugin for terminal observer + lifecycle.\n *\n * This is the substrate counterpart to `@silvery/ag-term/plugins/with-terminal`\n * (which wraps the test-harness `App`). Where that plugin cares about the\n * test-facing handle, this one plugs into the runtime apply chain that\n * `create-app.tsx`'s processEventBatch drives.\n *\n * ## Responsibilities\n *\n * - Observer lane: always update modifier state on `input:key` ops\n * (never consumes; always passes through to the next plugin).\n * - Resize handling: `term:resize` updates the stored dimensions\n * and schedules a `render` effect.\n * - Focus lifecycle: `term:focus` updates `focused` and clears\n * sticky modifiers on blur (a very common source of \"Ctrl stuck\n * down\" bugs after Alt-Tab).\n *\n * ## What this plugin does NOT do\n *\n * - It does NOT intercept key events (observer only).\n * - It does NOT implement raw-mode / alternate-screen / mouse /\n * kitty-keyboard setup — those belong to the terminal provider\n * (`createTermProvider`) that feeds ops *into* the chain.\n * - It does NOT handle Ctrl+C / Ctrl+Z — see\n * `runtime/lifecycle-effects.ts` for those.\n *\n * The separation matters: lifecycle-effects deals with keys that\n * *terminate* the app, while this plugin is a steady-state observer.\n */\n\nimport type { ApplyResult, Effect, Op } from \"../types\"\nimport type { BaseApp } from \"./base-app\"\n\n// ---------------------------------------------------------------------------\n// Store shape\n// ---------------------------------------------------------------------------\n\n/** Snapshot of the modifier state the terminal thinks is currently held. */\nexport interface ModifierState {\n readonly ctrl: boolean\n readonly shift: boolean\n readonly alt: boolean\n readonly meta: boolean\n readonly super: boolean\n readonly hyper: boolean\n}\n\n/** Store slice installed by {@link withTerminalChain}. */\nexport interface TerminalStore {\n /** Columns (from last resize or initial). */\n cols: number\n /** Rows. */\n rows: number\n /** Is the terminal focused right now (OSC 1004 focus reporting)? */\n focused: boolean\n /** Current modifier key state. */\n modifiers: ModifierState\n}\n\n// ---------------------------------------------------------------------------\n// Op payload types (shared with the runner and with other plugins)\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal key shape that withTerminalChain inspects on `input:key` ops.\n *\n * Deliberately structural (not imported from ag-term's `Key`) so this\n * package stays free of terminal imports. Any compatible shape works.\n *\n * `isModifierOnly` is the authoritative modifier-only flag — set by\n * `parseKey()` in @silvery/ag/keys when the key name is one of\n * `leftshift`, `leftctrl`, … (the dedicated modifier-key codepoints in\n * the Kitty keyboard protocol). Heuristics like \"input is empty and\n * some modifier flag is set\" are incorrect — they misclassify\n * Shift+Tab, Shift+Enter, Shift+Arrow, Ctrl+Tab, etc. (input is empty\n * but the event carries a real payload via the `tab` / `return` /\n * `upArrow` / ... flags, not via `input`).\n */\nexport interface KeyShape {\n ctrl?: boolean\n shift?: boolean\n meta?: boolean\n super?: boolean\n hyper?: boolean\n alt?: boolean\n eventType?: \"press\" | \"repeat\" | \"release\" | undefined\n /**\n * True iff the key itself is a modifier key (Shift, Ctrl, …) pressed\n * in isolation. Authoritative — do NOT derive from `input === \"\"` +\n * modifier flags; those are simultaneously true for e.g. Shift+Tab.\n */\n isModifierOnly?: boolean\n}\n\n/** Options accepted by {@link withTerminalChain}. */\nexport interface WithTerminalChainOptions {\n /** Initial columns. Default: 80. */\n cols?: number\n /** Initial rows. Default: 24. */\n rows?: number\n /** Initial focused state. Default: true. */\n focused?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\n/**\n * Install the terminal observer + lifecycle plugin.\n *\n * The returned app exposes the terminal slice at `app.terminal`.\n * Plugins upstream (last in `pipe()`) see ops first; this plugin\n * should go near the BASE of the chain so focused/useInput plugins\n * handle actual key content before the terminal observer peeks at\n * modifier state.\n */\nexport function withTerminalChain(\n options: WithTerminalChainOptions = {},\n): <A extends BaseApp>(app: A) => A & { terminal: TerminalStore } {\n return <A extends BaseApp>(app: A): A & { terminal: TerminalStore } => {\n const store: TerminalStore = {\n cols: options.cols ?? 80,\n rows: options.rows ?? 24,\n focused: options.focused ?? true,\n modifiers: {\n ctrl: false,\n shift: false,\n alt: false,\n meta: false,\n super: false,\n hyper: false,\n },\n }\n const prev = app.apply\n app.apply = (op: Op): ApplyResult => {\n if (op.type === \"input:key\") {\n // Observer lane: peek the modifiers, never consume.\n const key = (op as { key?: KeyShape }).key\n if (key) {\n store.modifiers = {\n ctrl: !!key.ctrl,\n shift: !!key.shift,\n alt: !!key.alt || !!key.meta,\n meta: !!key.meta,\n super: !!key.super,\n hyper: !!key.hyper,\n }\n }\n return prev(op)\n }\n if (op.type === \"term:resize\") {\n const cols = (op as { cols?: number }).cols\n const rows = (op as { rows?: number }).rows\n if (typeof cols === \"number\") store.cols = cols\n if (typeof rows === \"number\") store.rows = rows\n const effects: Effect[] = [{ type: \"render\" }]\n // Chain downstream so anyone else (e.g. layout-aware plugins)\n // can also react.\n const downstream = prev(op)\n if (downstream !== false) effects.push(...downstream)\n return effects\n }\n if (op.type === \"term:focus\") {\n const focused = !!(op as { focused?: boolean }).focused\n store.focused = focused\n if (!focused) {\n // Clear sticky modifiers on blur so a stuck Ctrl from a\n // previous window doesn't bleed into the next session.\n store.modifiers = {\n ctrl: false,\n shift: false,\n alt: false,\n meta: false,\n super: false,\n hyper: false,\n }\n }\n return []\n }\n return prev(op)\n }\n return Object.assign(app, { terminal: store })\n }\n}\n","/**\n * withFocusChain — apply-chain plugin for focused-element key dispatch.\n *\n * This is the production form of the v1r prototype's `withFocus` plugin:\n * it owns the \"focused lane\" of the input pipeline. When the app has an\n * active focus target, keys go to it FIRST. If the target handles the\n * key (stopPropagation / preventDefault / handler returned `true`), we\n * signal \"handled\" to the chain and downstream plugins (`useInput`) skip\n * the event.\n *\n * ## Relation to `@silvery/ag-term/plugins/with-focus`\n *\n * The existing `withFocus` in ag-term wraps the *test harness* `App`\n * (`app.press()`). It's a higher-level construct that drives the same\n * runtime bits via a proxy around `press`.\n *\n * `withFocusChain` is the lower-level substrate: it plugs into the\n * runtime's apply chain so `processEventBatch` can replace its\n * ad-hoc `handleFocusNavigation + runtimeInputListeners` loop with a\n * single `app.dispatch({type:\"input:key\", ...})`.\n *\n * The two layer and stack:\n *\n * - ag-term/plugins/with-focus (test/harness) ──────────────┐\n * │\n * - runtime/with-focus-chain (apply chain) ◀──────────────┘\n *\n * ## Options\n *\n * The production runtime already has a rich `createFocusManager`. Rather\n * than re-implement it here, withFocusChain accepts a pluggable\n * `dispatchKey` function — typically `dispatchKeyEvent(createKeyEvent(...),\n * focusManager.activeElement)` or equivalent. It MUST return a boolean:\n *\n * - `true` — the focused tree consumed the key (stopPropagation)\n * - `false` — the focused tree didn't handle it; fall through\n *\n * This dependency-injection style keeps @silvery/create free of\n * terminal-specific imports while still letting create-app.tsx wire\n * in the real focus manager.\n */\n\nimport type { ApplyResult, Op } from \"../types\"\nimport type { BaseApp } from \"./base-app\"\nimport type { KeyShape } from \"./with-terminal-chain\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Caller-supplied: decide whether the focused tree consumed the key.\n *\n * Must return `true` iff the focused node's `onKeyDown` handler\n * invoked `stopPropagation()` / `preventDefault()` — identical to the\n * current `handleFocusNavigation` return.\n */\nexport type FocusKeyDispatch = (input: string, key: KeyShape) => boolean\n\n/**\n * Caller-supplied: is there an active focus target right now?\n *\n * Checked up-front so we skip the dispatch entirely when nothing is\n * focused (matches create-app's `if (focusManager.activeElement)`\n * short-circuit).\n */\nexport type HasActiveFocus = () => boolean\n\n/** Options for {@link withFocusChain}. */\nexport interface WithFocusChainOptions {\n dispatchKey: FocusKeyDispatch\n hasActiveFocus: HasActiveFocus\n /**\n * Optional: when true, releasing keys and modifier-only events are\n * forwarded to `dispatchKey` too. Default: false (matches\n * create-app.tsx's pre-refactor behaviour where such events skip\n * focused dispatch).\n */\n dispatchReleaseAndModifierOnly?: boolean\n}\n\n/** Store slice installed by {@link withFocusChain}. */\nexport interface FocusChainStore {\n /** Most recent result from `dispatchKey` (for diagnostics / tests). */\n lastConsumed: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction isModifierOnly(_input: string, key: KeyShape | undefined): boolean {\n // Authoritative flag — set by @silvery/ag/keys `parseKey()` for\n // dedicated modifier-only codepoints. See the longer note in\n // with-input-chain.ts; same bug, same fix.\n return key?.isModifierOnly === true\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\n/**\n * Install the focus-dispatch plugin.\n *\n * Place this plugin OUTERMOST in the input-handling chain so focused\n * targets see the key before the `useInput` fallback store.\n */\nexport function withFocusChain(\n options: WithFocusChainOptions,\n): <A extends BaseApp>(app: A) => A & { focusChain: FocusChainStore } {\n return <A extends BaseApp>(app: A): A & { focusChain: FocusChainStore } => {\n const store: FocusChainStore = { lastConsumed: false }\n const prev = app.apply\n app.apply = (op: Op): ApplyResult => {\n if (op.type !== \"input:key\") return prev(op)\n const input = (op as { input?: string }).input ?? \"\"\n const key = (op as { key?: KeyShape }).key\n const isRelease = key?.eventType === \"release\"\n const modOnly = isModifierOnly(input, key)\n if ((isRelease || modOnly) && !options.dispatchReleaseAndModifierOnly) {\n return prev(op)\n }\n // `dispatchKey` is responsible for both the focused-element event\n // dispatch (which is gated on `hasActiveFocus` inside the caller,\n // typically `handleFocusNavigation`) AND focus-navigation fallbacks\n // like \"Tab focuses the first focusable when nothing is active\".\n // Previously this plugin short-circuited via `!hasActiveFocus()`,\n // which wrongly suppressed the Tab-seeds-first-focus behaviour.\n // `hasActiveFocus` is retained on the options type for callers who\n // still inspect it, but we always invoke `dispatchKey` — the caller\n // decides what to consume.\n let consumed = false\n try {\n consumed = !!options.dispatchKey(input, key ?? ({} as KeyShape))\n } catch (err) {\n // A bad focused handler shouldn't break the event loop.\n // eslint-disable-next-line no-console\n console.error(\"[withFocusChain] dispatchKey threw\", err)\n }\n store.lastConsumed = consumed\n if (consumed) {\n // The focused tree consumed the key. Signal \"handled\" with a\n // render request so the runner repaints. Downstream plugins\n // (useInput fallback) are short-circuited.\n return [{ type: \"render\" }]\n }\n return prev(op)\n }\n return Object.assign(app, { focusChain: store })\n }\n}\n","/**\n * withCommands - SlateJS-style plugin for command system\n *\n * Adds a `cmd` object to the App for direct command invocation with metadata.\n *\n * @example\n * ```tsx\n * const app = withCommands(render(<Board />), {\n * registry: commandRegistry,\n * getContext: () => buildCommandContext(state),\n * handleAction: (action) => dispatch(action),\n * getKeybindings: () => keybindings,\n * })\n *\n * // Direct command invocation\n * await app.cmd.down()\n * await app.cmd['cursor_down']()\n *\n * // Command metadata\n * app.cmd.down.id // 'cursor_down'\n * app.cmd.down.name // 'Move Down'\n * app.cmd.down.help // 'Move cursor down'\n * app.cmd.down.keys // ['j', 'ArrowDown']\n *\n * // Introspection\n * app.cmd.all() // All commands with metadata\n * app.getState() // { screen, commands, focus } for AI\n * ```\n *\n * See docs/future/silvery-command-api-research.md for design rationale.\n */\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Minimal app interface required by the command system.\n * The full App type (from @silvery/ag-term/app) satisfies this.\n * Using a minimal interface avoids a layer violation (commands → ag-term).\n */\nexport interface CommandableApp {\n /** Full rendered text (no ANSI codes) — used by getState() for AI introspection */\n readonly text: string\n /**\n * Send a key press — used by withKeybindings to intercept and route to\n * commands. Returns a `PromiseLike` (covers both `Promise<unknown>` and\n * `ChainableApp` from @silvery/ag-term/app post-fluent-chain). Bead:\n * @km/silvery/fluent-chain-actions.\n */\n press(key: string): PromiseLike<unknown>\n}\n\n/**\n * Generic command definition interface.\n * Compatible with @km/commands CommandDef but doesn't require the dependency.\n */\nexport interface CommandDef<TContext = unknown, TAction = unknown> {\n id: string\n name: string\n description: string\n shortcuts?: string[]\n execute: (ctx: TContext) => TAction | TAction[] | null\n}\n\n/**\n * Generic keybinding interface.\n * Compatible with @km/commands Keybinding.\n */\nexport interface KeybindingDef {\n key: string\n commandId: string\n ctrl?: boolean\n alt?: boolean\n opt?: boolean\n shift?: boolean\n cmd?: boolean\n}\n\n/**\n * Generic command registry interface.\n */\nexport interface CommandRegistryLike<TContext = unknown, TAction = unknown> {\n get(id: string): CommandDef<TContext, TAction> | undefined\n getAll(): CommandDef<TContext, TAction>[]\n}\n\n/**\n * Command metadata exposed on the cmd object.\n */\nexport interface CommandInfo {\n id: string\n name: string\n description: string\n keys: readonly string[]\n}\n\n/**\n * A callable command with metadata.\n */\nexport interface Command {\n (): Promise<void>\n readonly id: string\n readonly name: string\n readonly help: string\n readonly keys: readonly string[]\n}\n\n/**\n * The cmd object added to the app.\n *\n * Provides both method-style (`cmd.down()`) and index-style (`cmd['cursor_down']()`)\n * access to commands. Uses Proxy for dynamic lookup.\n */\nexport interface Cmd {\n [key: string]: Command | (() => CommandInfo[]) | (() => string) | undefined\n /** Get all commands with metadata */\n all(): CommandInfo[]\n /** Get human/AI readable description of all commands */\n describe(): string\n}\n\n/**\n * Options for withCommands.\n *\n * @typeParam TContext - The context type passed to command execute()\n * @typeParam TAction - The action type returned by command execute()\n */\nexport interface WithCommandsOptions<TContext, TAction> {\n /** Command registry with get() and getAll() */\n registry: CommandRegistryLike<TContext, TAction>\n /** Build context for command execution */\n getContext: () => TContext\n /** Handle actions returned by command execution */\n handleAction: (action: TAction) => void\n /** Get keybindings for command metadata (optional) */\n getKeybindings?: () => KeybindingDef[]\n}\n\n/**\n * App state for AI introspection.\n */\nexport interface AppState {\n screen: string\n commands: CommandInfo[]\n focus?: { id: string; text: string }\n}\n\n/**\n * App with command system.\n * Generic over the app type — preserves the full type when used with App.\n */\nexport type AppWithCommands<TApp extends CommandableApp = CommandableApp> = TApp & {\n cmd: Cmd\n getState(): AppState\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/**\n * Find command by short name or full id.\n *\n * Supports both:\n * - Exact id match: `cmd['cursor_down']`\n * - Short name match: `cmd.down` (matches 'cursor_down', 'navigation_down', etc.)\n */\nfunction findCommand<TContext, TAction>(\n registry: CommandRegistryLike<TContext, TAction>,\n key: string,\n): CommandDef<TContext, TAction> | undefined {\n // Try exact id match first\n const byId = registry.get(key)\n if (byId) return byId\n\n // Try short name (last segment after underscore or dot)\n const all = registry.getAll()\n return all.find((c) => {\n const shortName = c.id.split(/[._]/).pop()\n return shortName === key\n })\n}\n\n/**\n * Get keys bound to a command.\n */\nfunction getKeysForCommand(commandId: string, keybindings?: KeybindingDef[]): readonly string[] {\n if (!keybindings) return []\n return keybindings\n .filter((kb) => kb.commandId === commandId)\n .map((kb) => {\n const parts: string[] = []\n if (kb.cmd) parts.push(\"Cmd\")\n if (kb.ctrl) parts.push(\"Ctrl\")\n if (kb.alt || kb.opt) parts.push(\"Alt\")\n if (kb.shift) parts.push(\"Shift\")\n parts.push(kb.key)\n return parts.join(\"+\")\n })\n}\n\n/**\n * Format help text for all commands.\n */\nfunction formatHelp<TContext, TAction>(\n registry: CommandRegistryLike<TContext, TAction>,\n keybindings?: KeybindingDef[],\n): string {\n const commands = registry.getAll()\n const lines = commands.map((cmd) => {\n const keys = getKeysForCommand(cmd.id, keybindings)\n const keyStr = keys.length > 0 ? ` [${keys.join(\", \")}]` : \"\"\n return `${cmd.id}${keyStr}: ${cmd.description}`\n })\n return lines.join(\"\\n\")\n}\n\n/**\n * Add command system to an App.\n *\n * Supports two calling styles:\n * - Direct: `withCommands(app, options)` — returns enhanced app immediately\n * - Curried: `withCommands(options)` — returns a plugin for pipe() composition\n *\n * @example Direct\n * ```tsx\n * const app = withCommands(render(<Board />), {\n * registry: commandRegistry,\n * getContext: () => buildContext(state),\n * handleAction: (action) => dispatch(action),\n * })\n * ```\n *\n * @example Curried (pipe)\n * ```tsx\n * const app = pipe(\n * baseApp,\n * withCommands({\n * registry: commandRegistry,\n * getContext: () => buildContext(state),\n * handleAction: (action) => dispatch(action),\n * }),\n * )\n * ```\n */\n// Curried form: withCommands(options) => plugin\nexport function withCommands<TContext, TAction>(\n options: WithCommandsOptions<TContext, TAction>,\n): <TApp extends CommandableApp>(app: TApp) => AppWithCommands<TApp>\n// Direct form: withCommands(app, options) => enhancedApp\nexport function withCommands<TApp extends CommandableApp, TContext, TAction>(\n app: TApp,\n options: WithCommandsOptions<TContext, TAction>,\n): AppWithCommands<TApp>\nexport function withCommands<TContext, TAction>(\n appOrOptions: CommandableApp | WithCommandsOptions<TContext, TAction>,\n maybeOptions?: WithCommandsOptions<TContext, TAction>,\n): AppWithCommands | ((app: CommandableApp) => AppWithCommands) {\n // Curried form: first arg is options (no press/text/ansi = not an App)\n if (maybeOptions === undefined) {\n const options = appOrOptions as WithCommandsOptions<TContext, TAction>\n return (app: CommandableApp) => applyCommands(app, options)\n }\n // Direct form: first arg is app\n return applyCommands(appOrOptions as CommandableApp, maybeOptions)\n}\n\nfunction applyCommands<TContext, TAction>(\n app: CommandableApp,\n options: WithCommandsOptions<TContext, TAction>,\n): AppWithCommands {\n const { registry, getContext, handleAction, getKeybindings } = options\n\n const cmd = new Proxy({} as Cmd, {\n get(_, prop: string | symbol): unknown {\n // Handle symbol access (for JS internals)\n if (typeof prop === \"symbol\") return undefined\n\n // Introspection methods\n if (prop === \"all\") {\n return () => {\n const commands = registry.getAll()\n const keybindings = getKeybindings?.()\n return commands.map((c) => ({\n id: c.id,\n name: c.name,\n description: c.description,\n keys: getKeysForCommand(c.id, keybindings),\n }))\n }\n }\n\n if (prop === \"describe\") {\n return () => formatHelp(registry, getKeybindings?.())\n }\n\n // Look up command by short name or full id\n const def = findCommand(registry, prop)\n if (!def) return undefined\n\n // Build callable with metadata\n const fn = async () => {\n const ctx = getContext()\n const result = def.execute(ctx)\n if (result) {\n const actions = Array.isArray(result) ? result : [result]\n for (const action of actions) {\n handleAction(action)\n }\n }\n // Allow microtask to flush for test synchronization\n await Promise.resolve()\n }\n\n // Attach metadata\n const keybindings = getKeybindings?.()\n Object.defineProperties(fn, {\n id: { value: def.id, enumerable: true },\n name: { value: def.name, enumerable: true },\n help: { value: def.description, enumerable: true },\n keys: {\n value: getKeysForCommand(def.id, keybindings),\n enumerable: true,\n },\n })\n\n return fn as Command\n },\n\n has(_, prop): boolean {\n if (typeof prop === \"symbol\") return false\n if (prop === \"all\" || prop === \"describe\") return true\n return !!findCommand(registry, prop)\n },\n })\n\n // Build getState for AI introspection\n const getState = (): AppState => {\n const commands = registry.getAll()\n const keybindings = getKeybindings?.()\n return {\n screen: app.text,\n commands: commands.map((c) => ({\n id: c.id,\n name: c.name,\n description: c.description,\n keys: getKeysForCommand(c.id, keybindings),\n })),\n // Focus info would require DOM query - leave undefined for now\n focus: undefined,\n }\n }\n\n return Object.assign(app, { cmd, getState }) as AppWithCommands\n}\n","/**\n * withKeybindings - SlateJS-style plugin for keybinding wiring\n *\n * Intercepts `press()` calls and routes them to commands via keybinding resolution.\n * Commands not in the registry fall through to component useInput handlers.\n *\n * @example\n * ```tsx\n * const app = withKeybindings(withCommands(render(<Board />), cmdOpts), {\n * bindings: defaultKeybindings,\n * getKeyContext: () => ({ mode: 'normal', hasSelection: false, ... }),\n * })\n *\n * // Press 'j' → resolves to cursor_down → calls app.cmd.down()\n * await app.press('j')\n *\n * // Press 'x' (no binding) → passes through to useInput handlers\n * await app.press('x')\n * ```\n *\n * See docs/future/silvery-command-api-research.md for design rationale.\n */\n\nimport type { AppWithCommands, KeybindingDef } from \"./with-commands\"\nimport { parseHotkey } from \"@silvery/ag/keys\"\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Context for keybinding resolution.\n * Used to match mode-specific bindings and conditional bindings.\n */\nexport interface KeybindingContext {\n mode: string\n hasSelection: boolean\n [key: string]: unknown\n}\n\n/**\n * Options for withKeybindings.\n */\nexport interface WithKeybindingsOptions {\n /** Keybindings to resolve */\n bindings: KeybindingDef[]\n /** Build context for keybinding resolution */\n getKeyContext: () => KeybindingContext\n}\n\n/**\n * Extended keybinding with mode support.\n */\nexport interface ExtendedKeybindingDef extends KeybindingDef {\n modes?: string[]\n when?: (ctx: KeybindingContext) => boolean\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n// parseKey replaced by parseHotkey from ./keys.js\n\n/**\n * Resolve a key press to a command ID using keybinding lookup.\n */\nfunction resolveKeybinding(\n key: string,\n modifiers: { ctrl: boolean; meta: boolean; shift: boolean; super: boolean },\n bindings: ExtendedKeybindingDef[],\n ctx: KeybindingContext,\n): string | null {\n for (const binding of bindings) {\n // Check key match\n if (binding.key !== key) continue\n\n // Check modifiers\n if (!!binding.ctrl !== !!modifiers.ctrl) continue\n if (!!binding.opt !== !!modifiers.meta) continue\n if (!!binding.cmd !== !!modifiers.super) continue\n\n // For single uppercase letters (A-Z), the shift key is implicit\n const isUppercaseLetter = key.length === 1 && key >= \"A\" && key <= \"Z\" && !binding.shift\n if (!isUppercaseLetter && !!binding.shift !== !!modifiers.shift) continue\n\n // alt removed — macOS uses opt (⌥), matched against modifiers.meta above\n\n // Check mode\n if (binding.modes && binding.modes.length > 0) {\n if (!binding.modes.includes(ctx.mode)) continue\n }\n\n // Check conditional\n if (binding.when && !binding.when(ctx)) continue\n\n return binding.commandId\n }\n return null\n}\n\n/**\n * Wire keybindings to command invocation.\n *\n * Intercepts `press()` and routes matching keys to commands.\n * Non-matching keys pass through to the original press handler.\n *\n * Supports two calling styles:\n * - Direct: `withKeybindings(app, options)` — returns enhanced app immediately\n * - Curried: `withKeybindings(options)` — returns a plugin for pipe() composition\n *\n * @example Direct\n * ```tsx\n * const app = withKeybindings(appWithCmd, {\n * bindings: defaultKeybindings,\n * getKeyContext: () => buildKeybindingContext(state),\n * })\n * ```\n *\n * @example Curried (pipe)\n * ```tsx\n * const app = pipe(\n * baseApp,\n * withCommands(cmdOpts),\n * withKeybindings({\n * bindings: defaultKeybindings,\n * getKeyContext: () => buildKeybindingContext(state),\n * }),\n * )\n * ```\n */\n// Curried form: withKeybindings(options) => plugin\nexport function withKeybindings(\n options: WithKeybindingsOptions,\n): <T extends AppWithCommands>(app: T) => T\n// Direct form: withKeybindings(app, options) => enhancedApp\nexport function withKeybindings<T extends AppWithCommands>(\n app: T,\n options: WithKeybindingsOptions,\n): T\nexport function withKeybindings<T extends AppWithCommands>(\n appOrOptions: T | WithKeybindingsOptions,\n maybeOptions?: WithKeybindingsOptions,\n): T | (<U extends AppWithCommands>(app: U) => U) {\n // Curried form: first arg is options (no press/text/ansi = not an App)\n if (maybeOptions === undefined) {\n const options = appOrOptions as WithKeybindingsOptions\n return <U extends AppWithCommands>(app: U) => applyKeybindings(app, options)\n }\n // Direct form: first arg is app\n return applyKeybindings(appOrOptions as T, maybeOptions)\n}\n\nfunction applyKeybindings<T extends AppWithCommands>(app: T, options: WithKeybindingsOptions): T {\n const { bindings, getKeyContext } = options\n const originalPress = app.press.bind(app)\n\n // Create a proxy to intercept press() while preserving all other properties\n return new Proxy(app, {\n get(target, prop, receiver) {\n if (prop === \"press\") {\n return async function interceptedPress(keyStr: string): Promise<T> {\n const { key, ...modifiers } = parseHotkey(keyStr)\n const ctx = getKeyContext()\n\n // Try to resolve to a command\n const commandId = resolveKeybinding(\n key,\n modifiers,\n bindings as ExtendedKeybindingDef[],\n ctx,\n )\n\n if (commandId) {\n const cmd = target.cmd[commandId]\n if (cmd) {\n await cmd()\n return receiver as T\n }\n }\n\n // Pass through to original press handler (for useInput)\n await originalPress(keyStr)\n return receiver as T\n }\n }\n return Reflect.get(target, prop, receiver)\n },\n })\n}\n","/**\n * Buffer comparison utility for differential rendering tests.\n *\n * Compares two terminal buffers cell-by-cell, returning the first\n * mismatch found (or null if buffers are identical).\n *\n * ## Documented-distinct from the termless comparator family\n *\n * The terminal-flow vertical slice (§7 \"Comparator family\") converges the\n * ecosystem's differs — termless `diffBuffers`, `terminalStateDigest` /\n * `diffTerminalStates`, and the restore-equivalence oracle's state digest — into\n * ONE compare family in termless. `compareBuffers` here is deliberately **NOT** a\n * member of that family, and NOT a duplicate of `diffBuffers`. It is a\n * **render-level** check, kept distinct by design:\n *\n * - **Different input.** The termless family compares terminal *state* read\n * through the `TerminalReadable` contract (\"are these two terminals in the same\n * observable state?\"). `compareBuffers` compares two silvery `TerminalBuffer`s —\n * silvery's internal packed render target, which is NOT a `TerminalReadable` and\n * cannot be one: it carries render-only semantics (the `SELECTABLE_FLAG`, the\n * `DEFAULT_BG` sentinel, inheritedBg-resolved cells, `continuation` occupancy)\n * that exist only inside the render pipeline, before any emulator sees output.\n * - **Different question.** Every caller (`with-diagnostics`'s `SILVERY_STRICT`\n * `incremental` check, the incremental-rendering fuzz, `render-plan-parity`)\n * asks \"did silvery's incremental / replayed render path produce the identical\n * render buffer as a fresh / recorded render?\" — the incremental≡fresh\n * invariant. That has no analog in the termless state-equivalence family.\n * - **Non-temporal A/B framing.** `cellA`/`cellB` are two renders of the SAME\n * frame (incremental vs fresh, replayed vs recorded) — deliberately NOT the\n * `oldCell`/`newCell` temporal framing of `diffBuffers`, and it returns the\n * FIRST mismatch (fail-fast for an assertion) rather than every changed cell\n * (a differential corpus).\n *\n * Structural correspondence, for readers crossing between the two: this file's\n * `{ x, y }` is `diffBuffers`'s `{ col, row }` (x=col, y=row); `cellA`/`cellB`\n * carry silvery buffer `Cell`s (fg/bg = `number | { r, g, b, index? } | null`),\n * whose color shape is index-preserving and thus structurally compatible with the\n * termless `Cell`'s `Color = { r, g, b, index? }` — but the two types are never\n * imported across the silvery↔termless boundary (compatible shapes, not shared\n * imports; slice §9 rule 7).\n */\n\nimport { type Cell, type TerminalBuffer, cellEquals } from \"@silvery/ag-term/buffer\"\n\n/**\n * A single cell mismatch between two buffers.\n */\nexport interface BufferMismatch {\n /** Column of the mismatched cell */\n x: number\n /** Row of the mismatched cell */\n y: number\n /** Cell from buffer A (e.g., incremental render) */\n cellA: Cell\n /** Cell from buffer B (e.g., fresh render) */\n cellB: Cell\n}\n\n/**\n * Compare two terminal buffers cell-by-cell.\n *\n * @returns The first mismatch found, or null if buffers are identical.\n */\nexport function compareBuffers(a: TerminalBuffer, b: TerminalBuffer): BufferMismatch | null {\n const width = Math.max(a.width, b.width)\n const height = Math.max(a.height, b.height)\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const cellA = a.inBounds(x, y)\n ? a.getCell(x, y)\n : {\n char: \" \",\n fg: null,\n bg: null,\n underlineColor: null,\n attrs: {},\n wide: false,\n continuation: false,\n }\n const cellB = b.inBounds(x, y)\n ? b.getCell(x, y)\n : {\n char: \" \",\n fg: null,\n bg: null,\n underlineColor: null,\n attrs: {},\n wide: false,\n continuation: false,\n }\n\n if (!cellEquals(cellA, cellB)) {\n return { x, y, cellA, cellB }\n }\n }\n }\n\n return null\n}\n\n/**\n * Format a buffer mismatch for human-readable error output.\n */\nexport function formatMismatch(\n mismatch: BufferMismatch,\n context?: {\n incrementalText?: string\n freshText?: string\n seed?: number\n iteration?: number\n key?: string\n },\n): string {\n const { x, y, cellA, cellB } = mismatch\n const lines: string[] = [\n `Buffer mismatch at (${x}, ${y})`,\n ` incremental: char=${JSON.stringify(cellA.char)} fg=${JSON.stringify(cellA.fg)} bg=${JSON.stringify(cellA.bg)} attrs=${JSON.stringify(cellA.attrs)}`,\n ` fresh: char=${JSON.stringify(cellB.char)} fg=${JSON.stringify(cellB.fg)} bg=${JSON.stringify(cellB.bg)} attrs=${JSON.stringify(cellB.attrs)}`,\n ]\n\n if (context?.seed !== undefined) lines.push(` seed: ${context.seed}`)\n if (context?.iteration !== undefined) {\n lines.push(` iteration: ${context.iteration}`)\n }\n if (context?.key) lines.push(` key: ${JSON.stringify(context.key)}`)\n\n if (context?.incrementalText) {\n lines.push(\"\", \"--- incremental ---\", context.incrementalText)\n }\n if (context?.freshText) {\n lines.push(\"\", \"--- fresh ---\", context.freshText)\n }\n\n return lines.join(\"\\n\")\n}\n","/**\n * withDiagnostics - Plugin for buffer and rendering diagnostic checks\n *\n * Wraps the `cmd` object to check invariants after command execution:\n * - All commands: Check incremental vs fresh render\n * - Cursor moves: Also check buffer content stability\n * - Optional: ANSI replay verification (characters AND SGR styles)\n *\n * ## Design Note: Why wrap `cmd` instead of `sendInput`?\n *\n * The two approaches are complementary:\n *\n * 1. **`sendInput()` level** (in renderer.ts) — Already has `SILVERY_STRICT`\n * which catches ALL inputs regardless of how they arrive (raw key presses,\n * type(), press(), etc.). This is the right place for incremental render checks.\n *\n * 2. **`cmd` level** (this plugin) — Command-aware, can selectively check stability\n * for cursor moves only. Raw sendInput doesn't know which inputs are cursor\n * commands that should preserve content. Another option would be `withInput()`\n * which could wrap sendInput with awareness of what input was sent.\n *\n * This plugin focuses on the command-aware checks. For comprehensive incremental\n * render checking, use `SILVERY_STRICT=1` environment variable which enables all checks.\n *\n * @example\n * ```typescript\n * import { withDiagnostics } from '@silvery/ag-term/toolbelt';\n *\n * // All checks enabled by default when you call withDiagnostics()\n * const driver = withDiagnostics(createBoardDriver(repo, rootId));\n *\n * // Or disable specific checks\n * const driver = withDiagnostics(createBoardDriver(repo, rootId), {\n * checkReplay: false // skip ANSI replay check\n * });\n *\n * // Commands now run invariant checks automatically\n * await driver.cmd.down(); // Checks incremental + stability + replay\n * await driver.cmd.search(); // Checks incremental + replay\n * ```\n */\n\nimport { mkdir } from \"node:fs/promises\"\nimport { join } from \"node:path\"\nimport { type Color, type TerminalBuffer, colorEquals } from \"../buffer\"\nimport { outputPhase } from \"../pipeline\"\nimport { compareBuffers, formatMismatch } from \"@silvery/test/compare-buffers\"\nimport type { BoxProps, AgNode } from \"@silvery/create/types\"\nimport type { App } from \"../app\"\nimport type { AppWithCommands, Cmd, Command } from \"@silvery/commands/with-commands\"\n\n/** App with both full App capabilities and command system */\ntype FullAppWithCommands = AppWithCommands<App>\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface DiagnosticOptions {\n /** Check incremental vs fresh render (default: true when plugin is used) */\n checkIncremental?: boolean\n /** Check buffer stability for cursor commands (default: true when plugin is used) */\n checkStability?: boolean\n /** Check ANSI replay produces correct result (default: true when plugin is used) */\n checkReplay?: boolean\n /** Check layout tree integrity after each command (default: true when plugin is used) */\n checkLayout?: boolean\n /** Lines to skip for stability check (e.g., [0, -1] for breadcrumb/statusbar) */\n skipLines?: number[]\n /** Capture screenshot on failure (default: false) */\n captureOnFailure?: boolean\n /** Directory for failure screenshots (default: /tmp/silvery-diagnostics) */\n screenshotDir?: string\n}\n\n/**\n * Text mismatch between before and after states\n */\ninterface TextMismatch {\n line: number\n before: string\n after: string\n}\n\n/**\n * ANSI replay mismatch (character content)\n */\ninterface ReplayMismatch {\n x: number\n y: number\n expected: string\n actual: string\n}\n\n/**\n * SGR style attributes tracked per cell in the virtual terminal.\n */\ninterface VTermStyle {\n fg: number | { r: number; g: number; b: number } | null\n bg: number | { r: number; g: number; b: number } | null\n bold: boolean\n dim: boolean\n italic: boolean\n underline: boolean\n blink: boolean\n inverse: boolean\n hidden: boolean\n strikethrough: boolean\n}\n\n/**\n * ANSI replay style mismatch\n */\ninterface StyleMismatch {\n x: number\n y: number\n char: string\n diffs: string[]\n}\n\n// =============================================================================\n// VirtualTerminal - ANSI Replay Simulator\n// =============================================================================\n\n/**\n * Virtual terminal simulator for testing ANSI replay equivalence.\n *\n * Parses ANSI sequences and applies them to a 2D grid tracking both\n * character content and SGR style attributes (fg, bg, bold, italic, etc.).\n * Used to verify the Replay Equivalence invariant: applying the ANSI\n * diff to the previous buffer state should produce the target buffer.\n *\n * Handles:\n * - Cursor positioning (H, G, A, B, C, D)\n * - Line clear (K)\n * - Wide characters (emojis, CJK)\n * - CR/LF\n * - SGR style sequences (m) — fg/bg colors, text attributes\n */\nexport class VirtualTerminal {\n private grid: string[][]\n private wideMarker: boolean[][]\n private styles: VTermStyle[][]\n private sgr: VTermStyle\n private cursorX = 0\n private cursorY = 0\n\n constructor(\n public readonly width: number,\n public readonly height: number,\n ) {\n this.grid = Array.from({ length: height }, () => Array(width).fill(\" \"))\n this.wideMarker = Array.from({ length: height }, () => Array(width).fill(false))\n this.styles = Array.from({ length: height }, () =>\n Array.from({ length: width }, () => createDefaultVTermStyle()),\n )\n this.sgr = createDefaultVTermStyle()\n }\n\n /**\n * Initialize grid from a TerminalBuffer (for incremental replay).\n * Loads both character content and style attributes.\n */\n loadFromBuffer(buffer: TerminalBuffer): void {\n for (let y = 0; y < Math.min(this.height, buffer.height); y++) {\n for (let x = 0; x < Math.min(this.width, buffer.width); x++) {\n if (buffer.isCellContinuation(x, y)) {\n this.wideMarker[y]![x] = true\n this.grid[y]![x] = \"\"\n // Continuation cells have default style\n this.styles[y]![x] = createDefaultVTermStyle()\n } else {\n this.grid[y]![x] = buffer.getCellChar(x, y)\n this.wideMarker[y]![x] = false\n // Load style from buffer cell\n const cell = buffer.getCell(x, y)\n this.styles[y]![x] = {\n fg: cell.fg,\n bg: cell.bg,\n bold: !!cell.attrs.bold,\n dim: !!cell.attrs.dim,\n italic: !!cell.attrs.italic,\n underline: !!cell.attrs.underline || !!cell.attrs.underlineStyle,\n blink: !!cell.attrs.blink,\n inverse: !!cell.attrs.inverse,\n hidden: !!cell.attrs.hidden,\n strikethrough: !!cell.attrs.strikethrough,\n }\n }\n }\n }\n }\n\n /**\n * Apply ANSI escape sequence string to the virtual terminal.\n */\n applyAnsi(ansi: string): void {\n let i = 0\n while (i < ansi.length) {\n if (ansi[i] === \"\\x1b\" && ansi[i + 1] === \"[\") {\n const match = ansi.slice(i).match(/^\\x1b\\[([0-9;:?]*)([A-Za-z])/)\n if (match) {\n this.handleCsi(match[1] || \"\", match[2]!)\n i += match[0].length\n continue\n }\n }\n\n // OSC sequences: \\x1b] ... (ST or BEL)\n // e.g. OSC 8 hyperlinks: \\x1b]8;;url\\x1b\\\\ or \\x1b]8;;url\\x07\n if (ansi[i] === \"\\x1b\" && ansi[i + 1] === \"]\") {\n // Find terminator: ST (\\x1b\\\\) or BEL (\\x07)\n let j = i + 2\n while (j < ansi.length) {\n if (ansi[j] === \"\\x07\") {\n j++\n break\n }\n if (ansi[j] === \"\\x1b\" && ansi[j + 1] === \"\\\\\") {\n j += 2\n break\n }\n j++\n }\n i = j\n continue\n }\n\n if (ansi[i] === \"\\r\") {\n this.cursorX = 0\n i++\n continue\n }\n\n if (ansi[i] === \"\\n\") {\n this.cursorY = Math.min(this.cursorY + 1, this.height - 1)\n i++\n continue\n }\n\n // Handle multi-byte Unicode characters\n const char = this.extractChar(ansi, i)\n if (this.cursorX < this.width && this.cursorY < this.height) {\n this.grid[this.cursorY]![this.cursorX] = char\n this.wideMarker[this.cursorY]![this.cursorX] = false\n // Apply current SGR state to the cell\n this.styles[this.cursorY]![this.cursorX] = { ...this.sgr }\n this.cursorX++\n\n // Wide characters take 2 columns\n if (this.isWideChar(char) && this.cursorX < this.width) {\n this.grid[this.cursorY]![this.cursorX] = \"\"\n this.wideMarker[this.cursorY]![this.cursorX] = true\n // Continuation cell gets default style\n this.styles[this.cursorY]![this.cursorX] = createDefaultVTermStyle()\n this.cursorX++\n }\n }\n i += char.length\n }\n }\n\n /**\n * Check if a character is wide (emoji, CJK, etc).\n */\n private isWideChar(char: string): boolean {\n if (char.length === 0) return false\n\n // Characters with VS16 (U+FE0F) are emoji presentation = 2 columns\n if (char.includes(\"\\uFE0F\")) return true\n\n const code = char.codePointAt(0) || 0\n\n // Emoji ranges\n if (code >= 0x1f300 && code <= 0x1f9ff) return true\n if (code >= 0x2600 && code <= 0x26ff) return true\n if (code >= 0x2700 && code <= 0x27bf) return true\n\n // CJK ranges\n if (code >= 0x4e00 && code <= 0x9fff) return true\n if (code >= 0x3000 && code <= 0x303f) return true\n if (code >= 0xff00 && code <= 0xffef) return true\n\n return false\n }\n\n /**\n * Extract a single Unicode character (which may be multiple bytes).\n * Includes VS16 (U+FE0F) if it follows, since VS16 is a presentation selector\n * that modifies the preceding character's rendering width.\n */\n private extractChar(str: string, start: number): string {\n const code = str.codePointAt(start)\n if (code === undefined) return str[start] || \"\"\n let char: string\n if (code > 0xffff) {\n char = String.fromCodePoint(code)\n } else {\n char = str[start] || \"\"\n }\n // Absorb VS16 (U+FE0F) if it follows — it's a presentation modifier\n const nextIdx = start + char.length\n if (nextIdx < str.length && str.codePointAt(nextIdx) === 0xfe0f) {\n char += \"\\uFE0F\"\n }\n return char\n }\n\n private handleCsi(params: string, cmd: string): void {\n switch (cmd) {\n case \"H\": {\n const parts = params.split(\";\")\n this.cursorY = Math.max(0, (Number.parseInt(parts[0] || \"1\", 10) || 1) - 1)\n this.cursorX = Math.max(0, (Number.parseInt(parts[1] || \"1\", 10) || 1) - 1)\n break\n }\n case \"G\": {\n this.cursorX = Math.max(0, (Number.parseInt(params || \"1\", 10) || 1) - 1)\n break\n }\n case \"A\": {\n const n = Number.parseInt(params || \"1\", 10) || 1\n this.cursorY = Math.max(0, this.cursorY - n)\n break\n }\n case \"B\": {\n const n = Number.parseInt(params || \"1\", 10) || 1\n this.cursorY = Math.min(this.height - 1, this.cursorY + n)\n break\n }\n case \"C\": {\n const n = Number.parseInt(params || \"1\", 10) || 1\n this.cursorX = Math.min(this.width - 1, this.cursorX + n)\n break\n }\n case \"D\": {\n const n = Number.parseInt(params || \"1\", 10) || 1\n this.cursorX = Math.max(0, this.cursorX - n)\n break\n }\n case \"K\": {\n const mode = Number.parseInt(params || \"0\", 10)\n // Erase in Line: fills cleared cells with current bg (per ECMA-48)\n if (mode === 0) {\n for (let x = this.cursorX; x < this.width; x++) {\n this.grid[this.cursorY]![x] = \" \"\n this.wideMarker[this.cursorY]![x] = false\n this.styles[this.cursorY]![x] = {\n ...createDefaultVTermStyle(),\n bg: this.sgr.bg,\n }\n }\n } else if (mode === 1) {\n for (let x = 0; x <= this.cursorX; x++) {\n this.grid[this.cursorY]![x] = \" \"\n this.wideMarker[this.cursorY]![x] = false\n this.styles[this.cursorY]![x] = {\n ...createDefaultVTermStyle(),\n bg: this.sgr.bg,\n }\n }\n } else if (mode === 2) {\n for (let x = 0; x < this.width; x++) {\n this.grid[this.cursorY]![x] = \" \"\n this.wideMarker[this.cursorY]![x] = false\n this.styles[this.cursorY]![x] = {\n ...createDefaultVTermStyle(),\n bg: this.sgr.bg,\n }\n }\n }\n break\n }\n case \"m\":\n // SGR (Select Graphic Rendition) — apply style changes\n this.applySgr(params)\n break\n case \"l\":\n case \"h\":\n // Private modes — ignore for replay\n break\n }\n }\n\n /**\n * Apply SGR parameters to the current style state.\n * Parses the semicolon-separated parameter string and updates this.sgr.\n */\n private applySgr(params: string): void {\n if (params === \"\" || params === \"0\") {\n // Reset all attributes\n Object.assign(this.sgr, createDefaultVTermStyle())\n return\n }\n\n const parts = params.split(\";\")\n let i = 0\n while (i < parts.length) {\n const code = parts[i]!\n // Handle subparameters (e.g., \"4:3\" for curly underline)\n const colonIdx = code.indexOf(\":\")\n if (colonIdx >= 0) {\n const mainCode = Number.parseInt(code.substring(0, colonIdx), 10)\n if (mainCode === 4) {\n const sub = Number.parseInt(code.substring(colonIdx + 1), 10)\n this.sgr.underline = sub > 0\n }\n i++\n continue\n }\n\n const n = Number.parseInt(code, 10)\n if (n === 0) {\n Object.assign(this.sgr, createDefaultVTermStyle())\n } else if (n === 1) {\n this.sgr.bold = true\n } else if (n === 2) {\n this.sgr.dim = true\n } else if (n === 3) {\n this.sgr.italic = true\n } else if (n === 4) {\n this.sgr.underline = true\n } else if (n === 5 || n === 6) {\n this.sgr.blink = true\n } else if (n === 7) {\n this.sgr.inverse = true\n } else if (n === 8) {\n this.sgr.hidden = true\n } else if (n === 9) {\n this.sgr.strikethrough = true\n } else if (n === 22) {\n this.sgr.bold = false\n this.sgr.dim = false\n } else if (n === 23) {\n this.sgr.italic = false\n } else if (n === 24) {\n this.sgr.underline = false\n } else if (n === 25) {\n this.sgr.blink = false\n } else if (n === 27) {\n this.sgr.inverse = false\n } else if (n === 28) {\n this.sgr.hidden = false\n } else if (n === 29) {\n this.sgr.strikethrough = false\n } else if (n >= 30 && n <= 37) {\n // Standard foreground colors (0-7)\n this.sgr.fg = n - 30\n } else if (n === 38) {\n // Extended foreground color\n if (i + 1 < parts.length && parts[i + 1] === \"5\" && i + 2 < parts.length) {\n // 256-color: \\x1b[38;5;Nm\n this.sgr.fg = Number.parseInt(parts[i + 2]!, 10)\n i += 2\n } else if (i + 1 < parts.length && parts[i + 1] === \"2\" && i + 4 < parts.length) {\n // True color: \\x1b[38;2;R;G;Bm\n this.sgr.fg = {\n r: Number.parseInt(parts[i + 2]!, 10),\n g: Number.parseInt(parts[i + 3]!, 10),\n b: Number.parseInt(parts[i + 4]!, 10),\n }\n i += 4\n }\n } else if (n === 39) {\n this.sgr.fg = null\n } else if (n >= 40 && n <= 47) {\n // Standard background colors (0-7)\n this.sgr.bg = n - 40\n } else if (n === 48) {\n // Extended background color\n if (i + 1 < parts.length && parts[i + 1] === \"5\" && i + 2 < parts.length) {\n // 256-color: \\x1b[48;5;Nm\n this.sgr.bg = Number.parseInt(parts[i + 2]!, 10)\n i += 2\n } else if (i + 1 < parts.length && parts[i + 1] === \"2\" && i + 4 < parts.length) {\n // True color: \\x1b[48;2;R;G;Bm\n this.sgr.bg = {\n r: Number.parseInt(parts[i + 2]!, 10),\n g: Number.parseInt(parts[i + 3]!, 10),\n b: Number.parseInt(parts[i + 4]!, 10),\n }\n i += 4\n }\n } else if (n === 49) {\n this.sgr.bg = null\n } else if (n >= 90 && n <= 97) {\n // Bright foreground colors (8-15)\n this.sgr.fg = n - 90 + 8\n } else if (n >= 100 && n <= 107) {\n // Bright background colors (8-15)\n this.sgr.bg = n - 100 + 8\n }\n // 58/59 (underline color) not tracked in diagnostic comparison\n i++\n }\n }\n\n /**\n * Get the character at a position.\n */\n getChar(x: number, y: number): string {\n if (this.wideMarker[y]?.[x]) return \"\"\n return this.grid[y]?.[x] ?? \" \"\n }\n\n /**\n * Get the style at a position.\n */\n getStyle(x: number, y: number): VTermStyle {\n return this.styles[y]?.[x] ?? createDefaultVTermStyle()\n }\n\n /**\n * Compare with a TerminalBuffer and return character mismatches.\n */\n compareToBuffer(buffer: TerminalBuffer): ReplayMismatch[] {\n const mismatches: ReplayMismatch[] = []\n for (let y = 0; y < Math.min(this.height, buffer.height); y++) {\n for (let x = 0; x < Math.min(this.width, buffer.width); x++) {\n if (buffer.isCellContinuation(x, y)) continue\n\n const expected = buffer.getCellChar(x, y)\n const actual = this.getChar(x, y)\n if (expected !== actual) {\n mismatches.push({ x, y, expected, actual })\n }\n }\n }\n return mismatches\n }\n\n /**\n * Compare styles with a TerminalBuffer and return style mismatches.\n * Checks fg, bg, bold, dim, italic, underline, blink, inverse, hidden, strikethrough.\n */\n compareStylesToBuffer(buffer: TerminalBuffer): StyleMismatch[] {\n const mismatches: StyleMismatch[] = []\n for (let y = 0; y < Math.min(this.height, buffer.height); y++) {\n for (let x = 0; x < Math.min(this.width, buffer.width); x++) {\n if (buffer.isCellContinuation(x, y)) continue\n\n const cell = buffer.getCell(x, y)\n const actual = this.getStyle(x, y)\n const diffs: string[] = []\n\n // Compare foreground color\n if (!colorEquals(actual.fg as Color, cell.fg)) {\n diffs.push(`fg: ${formatVTermColor(actual.fg)} vs ${formatVTermColor(cell.fg)}`)\n }\n // Compare background color\n if (!colorEquals(actual.bg as Color, cell.bg)) {\n diffs.push(`bg: ${formatVTermColor(actual.bg)} vs ${formatVTermColor(cell.bg)}`)\n }\n // Compare text attributes\n if (actual.bold !== !!cell.attrs.bold) {\n diffs.push(`bold: ${actual.bold} vs ${!!cell.attrs.bold}`)\n }\n if (actual.dim !== !!cell.attrs.dim) {\n diffs.push(`dim: ${actual.dim} vs ${!!cell.attrs.dim}`)\n }\n if (actual.italic !== !!cell.attrs.italic) {\n diffs.push(`italic: ${actual.italic} vs ${!!cell.attrs.italic}`)\n }\n // Underline: buffer can have underline or underlineStyle\n const expectedUnderline = !!cell.attrs.underline || !!cell.attrs.underlineStyle\n if (actual.underline !== expectedUnderline) {\n diffs.push(`underline: ${actual.underline} vs ${expectedUnderline}`)\n }\n if (actual.blink !== !!cell.attrs.blink) {\n diffs.push(`blink: ${actual.blink} vs ${!!cell.attrs.blink}`)\n }\n if (actual.inverse !== !!cell.attrs.inverse) {\n diffs.push(`inverse: ${actual.inverse} vs ${!!cell.attrs.inverse}`)\n }\n if (actual.hidden !== !!cell.attrs.hidden) {\n diffs.push(`hidden: ${actual.hidden} vs ${!!cell.attrs.hidden}`)\n }\n if (actual.strikethrough !== !!cell.attrs.strikethrough) {\n diffs.push(`strikethrough: ${actual.strikethrough} vs ${!!cell.attrs.strikethrough}`)\n }\n\n if (diffs.length > 0) {\n mismatches.push({ x, y, char: cell.char, diffs })\n }\n }\n }\n return mismatches\n }\n}\n\n/**\n * Create a default VTermStyle with all attributes reset to defaults.\n */\nfunction createDefaultVTermStyle(): VTermStyle {\n return {\n fg: null,\n bg: null,\n bold: false,\n dim: false,\n italic: false,\n underline: false,\n blink: false,\n inverse: false,\n hidden: false,\n strikethrough: false,\n }\n}\n\n/**\n * Format a color value for diagnostic display.\n */\nfunction formatVTermColor(c: number | { r: number; g: number; b: number } | null): string {\n if (c === null) return \"default\"\n if (typeof c === \"number\") return `${c}`\n return `rgb(${c.r},${c.g},${c.b})`\n}\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/** Commands that should preserve buffer content (only cursor position changes) */\nconst CURSOR_COMMANDS = new Set([\n // Full names\n \"cursor_up\",\n \"cursor_down\",\n \"cursor_left\",\n \"cursor_right\",\n // Short names\n \"up\",\n \"down\",\n \"left\",\n \"right\",\n])\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Parse SILVERY_STABILITY_SKIP_LINES environment variable.\n * Format: comma-separated integers, e.g., \"0,-1\"\n */\nfunction parseSkipLines(env?: string): number[] {\n if (!env) return []\n return env\n .split(\",\")\n .map((s) => Number.parseInt(s.trim(), 10))\n .filter((n) => !isNaN(n))\n}\n\n/**\n * Compare text content before and after a command.\n * Returns the first mismatch found, or null if content matches.\n *\n * @param before - Text before command execution\n * @param after - Text after command execution\n * @param skipLines - Line indices to skip (supports negative indices from end)\n */\nfunction compareText(before: string, after: string, skipLines: number[]): TextMismatch | null {\n const beforeLines = before.split(\"\\n\")\n const afterLines = after.split(\"\\n\")\n const maxLines = Math.max(beforeLines.length, afterLines.length)\n\n // Build set of lines to skip, resolving negative indices\n const skipSet = new Set<number>()\n for (const line of skipLines) {\n if (line >= 0) {\n skipSet.add(line)\n } else {\n // Negative index: -1 = last line, -2 = second to last, etc.\n skipSet.add(maxLines + line)\n }\n }\n\n for (let i = 0; i < maxLines; i++) {\n if (skipSet.has(i)) continue\n const b = beforeLines[i] ?? \"\"\n const a = afterLines[i] ?? \"\"\n if (b !== a) {\n return { line: i, before: b, after: a }\n }\n }\n return null\n}\n\n// =============================================================================\n// Layout Invariant Checks\n// =============================================================================\n\n/**\n * Check layout tree integrity. Returns violation messages, or empty array if valid.\n *\n * Checks:\n * - All rect dimensions are finite and non-negative (width >= 0, height >= 0)\n * - All positions are finite (x, y are valid numbers)\n * - No NaN values in computed layout\n * - Children don't overflow parent bounds (1px tolerance for rounding)\n * - Skips overflow check for nodes with overflow:hidden/scroll (they intentionally clip)\n */\nexport function checkLayoutInvariants(node: AgNode): string[] {\n const violations: string[] = []\n walkLayout(node, null, violations)\n return violations\n}\n\nfunction walkLayout(\n node: AgNode,\n parentRect: {\n x: number\n y: number\n width: number\n height: number\n clipped: boolean\n } | null,\n violations: string[],\n): void {\n const rect = node.boxRect\n if (!rect) return // No layout computed yet — skip\n\n const id = (node.props as BoxProps).id ?? node.type\n\n // Check finite and non-negative dimensions\n if (!Number.isFinite(rect.width) || rect.width < 0) {\n violations.push(`${id}: invalid width ${rect.width}`)\n }\n if (!Number.isFinite(rect.height) || rect.height < 0) {\n violations.push(`${id}: invalid height ${rect.height}`)\n }\n if (!Number.isFinite(rect.x)) {\n violations.push(`${id}: invalid x ${rect.x}`)\n }\n if (!Number.isFinite(rect.y)) {\n violations.push(`${id}: invalid y ${rect.y}`)\n }\n\n // Check children don't overflow parent (with 1px tolerance)\n if (parentRect && !parentRect.clipped) {\n const TOLERANCE = 1\n if (rect.x + rect.width > parentRect.x + parentRect.width + TOLERANCE) {\n violations.push(\n `${id}: overflows parent right (${rect.x + rect.width} > ${parentRect.x + parentRect.width})`,\n )\n }\n if (rect.y + rect.height > parentRect.y + parentRect.height + TOLERANCE) {\n violations.push(\n `${id}: overflows parent bottom (${rect.y + rect.height} > ${parentRect.y + parentRect.height})`,\n )\n }\n if (rect.x < parentRect.x - TOLERANCE) {\n violations.push(`${id}: overflows parent left (${rect.x} < ${parentRect.x})`)\n }\n if (rect.y < parentRect.y - TOLERANCE) {\n violations.push(`${id}: overflows parent top (${rect.y} < ${parentRect.y})`)\n }\n }\n\n // Determine if this node clips its children\n const overflow = (node.props as BoxProps).overflow\n const clipped = overflow === \"hidden\" || overflow === \"scroll\"\n\n const childParentRect = {\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height,\n clipped,\n }\n\n for (const child of node.children) {\n walkLayout(child, childParentRect, violations)\n }\n}\n\n// =============================================================================\n// Plugin Implementation\n// =============================================================================\n\n/**\n * Add diagnostic checking to an app with commands.\n *\n * Wraps the `cmd` proxy to intercept all command executions and run checks:\n * - **All commands**: Check that incremental render matches fresh render\n * - **Cursor commands**: Also check that buffer content didn't change\n *\n * **All checks are enabled by default** when you call this function.\n * The principle: if you wrapped with withDiagnostics(), you want diagnostics.\n *\n * @param app - App with command system (from withCommands)\n * @param options - Diagnostic check configuration (all enabled by default)\n * @returns App with wrapped cmd that runs diagnostic checks\n */\nexport function withDiagnostics<T extends FullAppWithCommands>(\n app: T,\n options: DiagnosticOptions = {},\n): T {\n // All checks enabled by default when plugin is used\n const {\n checkIncremental = true,\n checkStability = true,\n checkReplay = true,\n checkLayout = true,\n skipLines = parseSkipLines(process.env.SILVERY_STABILITY_SKIP_LINES),\n captureOnFailure = false,\n screenshotDir = \"/tmp/silvery-diagnostics\",\n } = options\n\n // If all checks are explicitly disabled, return app unchanged\n if (!checkIncremental && !checkStability && !checkReplay && !checkLayout) return app\n\n /** Capture screenshot on diagnostic failure (best-effort, never masks original error) */\n async function captureFailureScreenshot(\n commandId: string,\n checkType: string,\n ): Promise<string | null> {\n if (!captureOnFailure) return null\n try {\n await mkdir(screenshotDir, { recursive: true })\n const filename = `fail-${commandId}-${checkType}.png`\n const filepath = join(screenshotDir, filename)\n await app.screenshot(filepath)\n return filepath\n } catch {\n return null\n }\n }\n\n // Wrap the cmd proxy\n const wrappedCmd = new Proxy(app.cmd, {\n get(target, prop: string | symbol): unknown {\n // Handle symbol access (for JS internals)\n if (typeof prop === \"symbol\") return Reflect.get(target, prop)\n\n const original = Reflect.get(target, prop)\n\n // Pass through non-function properties and special methods\n if (typeof original !== \"function\") return original\n if (prop === \"all\" || prop === \"describe\") return original\n\n // Wrap command execution\n const command = original as Command\n const wrapped = async () => {\n // Capture state before command\n const beforeText = app.text\n const beforeBuffer = checkReplay ? app.lastBuffer() : null\n\n // Execute the original command\n await command()\n\n // Check 1: Incremental vs fresh render\n if (checkIncremental) {\n const incremental = app.lastBuffer()\n // freshRender() may throw if not available (non-test renderer)\n try {\n const fresh = app.freshRender()\n if (incremental && fresh) {\n const mismatch = compareBuffers(incremental, fresh)\n if (mismatch) {\n // Include full buffer text for debugging\n const incrementalText = app.text\n const freshText = fresh\n ? Array.from({ length: fresh.height }, (_, y) =>\n Array.from({ length: fresh.width }, (_, x) => fresh.getCellChar(x, y)).join(\n \"\",\n ),\n ).join(\"\\n\")\n : \"(no fresh buffer)\"\n const screenshotPath = await captureFailureScreenshot(command.id, \"incremental\")\n throw new Error(\n `SILVERY_DIAGNOSTIC: Incremental/fresh mismatch after ${command.id}\\n` +\n formatMismatch(mismatch, {\n key: command.id,\n incrementalText,\n freshText,\n }) +\n (screenshotPath ? `\\n Screenshot saved: ${screenshotPath}` : \"\"),\n )\n }\n }\n } catch (e) {\n // If freshRender isn't available, skip the check\n if (!(e instanceof Error) || !e.message.includes(\"only available in test renderer\")) {\n throw e\n }\n }\n }\n\n // Check 2: Content stability for cursor commands\n if (checkStability && CURSOR_COMMANDS.has(command.id)) {\n const afterText = app.text\n const mismatch = compareText(beforeText, afterText, skipLines)\n if (mismatch) {\n const screenshotPath = await captureFailureScreenshot(command.id, \"stability\")\n throw new Error(\n `SILVERY_DIAGNOSTIC: Content changed after cursor move ${command.id}\\n` +\n ` Line ${mismatch.line}: \"${mismatch.before}\" → \"${mismatch.after}\"` +\n (screenshotPath ? `\\n Screenshot saved: ${screenshotPath}` : \"\"),\n )\n }\n }\n\n // Check 3: ANSI replay produces correct result\n if (checkReplay && beforeBuffer) {\n const afterBuffer = app.lastBuffer()\n if (afterBuffer) {\n // Get the ANSI diff that would be sent to terminal\n const ansiDiff = outputPhase(beforeBuffer, afterBuffer)\n\n // Create virtual terminal initialized with previous state\n const vterm = new VirtualTerminal(afterBuffer.width, afterBuffer.height)\n vterm.loadFromBuffer(beforeBuffer)\n\n // Apply the ANSI diff\n vterm.applyAnsi(ansiDiff)\n\n // Compare character content\n const mismatches = vterm.compareToBuffer(afterBuffer)\n if (mismatches.length > 0) {\n const first5 = mismatches.slice(0, 5)\n const details = first5\n .map((m) => ` (${m.x},${m.y}): expected=\"${m.expected}\" actual=\"${m.actual}\"`)\n .join(\"\\n\")\n const screenshotPath = await captureFailureScreenshot(command.id, \"replay\")\n throw new Error(\n `SILVERY_DIAGNOSTIC: ANSI replay mismatch after ${command.id}\\n` +\n ` ${mismatches.length} cells differ:\\n${details}` +\n (mismatches.length > 5 ? `\\n ... and ${mismatches.length - 5} more` : \"\") +\n (screenshotPath ? `\\n Screenshot saved: ${screenshotPath}` : \"\"),\n )\n }\n\n // Compare SGR styles (fg, bg, bold, italic, underline, etc.)\n const styleMismatches = vterm.compareStylesToBuffer(afterBuffer)\n if (styleMismatches.length > 0) {\n const first5 = styleMismatches.slice(0, 5)\n const details = first5\n .map((m) => ` (${m.x},${m.y}) char=\"${m.char}\": ${m.diffs.join(\", \")}`)\n .join(\"\\n\")\n const screenshotPath = await captureFailureScreenshot(command.id, \"replay-style\")\n throw new Error(\n `SILVERY_DIAGNOSTIC: ANSI replay style mismatch after ${command.id}\\n` +\n ` ${styleMismatches.length} cells have style differences:\\n${details}` +\n (styleMismatches.length > 5\n ? `\\n ... and ${styleMismatches.length - 5} more`\n : \"\") +\n (screenshotPath ? `\\n Screenshot saved: ${screenshotPath}` : \"\"),\n )\n }\n }\n }\n\n // Check 4: Layout tree integrity\n if (checkLayout) {\n const root = app.getContainer()\n const violations = checkLayoutInvariants(root)\n if (violations.length > 0) {\n const details = violations\n .slice(0, 10)\n .map((v) => ` ${v}`)\n .join(\"\\n\")\n const screenshotPath = await captureFailureScreenshot(command.id, \"layout\")\n throw new Error(\n `SILVERY_DIAGNOSTIC: Layout invariant violation after ${command.id}\\n` +\n ` ${violations.length} violation(s):\\n${details}` +\n (violations.length > 10 ? `\\n ... and ${violations.length - 10} more` : \"\") +\n (screenshotPath ? `\\n Screenshot saved: ${screenshotPath}` : \"\"),\n )\n }\n }\n }\n\n // Copy metadata from original command\n Object.defineProperties(wrapped, {\n id: { value: command.id, enumerable: true },\n name: { value: command.name, enumerable: true },\n help: { value: command.help, enumerable: true },\n keys: { value: command.keys, enumerable: true },\n })\n\n return wrapped\n },\n\n has(target, prop): boolean {\n return Reflect.has(target, prop)\n },\n\n ownKeys(target): (string | symbol)[] {\n return Reflect.ownKeys(target)\n },\n\n getOwnPropertyDescriptor(target, prop): PropertyDescriptor | undefined {\n return Reflect.getOwnPropertyDescriptor(target, prop)\n },\n })\n\n // IMPORTANT: Use Object.assign instead of spread to preserve getters.\n // Spread `{ ...app }` snapshots getters (text, ansi, lastBuffer) as static\n // values from the initial render. Object.assign mutates the original object,\n // preserving its getters so they return current buffer state.\n return Object.assign(app, { cmd: wrappedCmd as Cmd }) as T\n}\n","/**\n * withRender(term) -- extends a Term with render pipeline capabilities.\n *\n * Creates term-scoped pipeline config (width measurer + output phase) from caps,\n * then returns an extended term with render() and renderStatic() methods.\n *\n * Usage:\n * const term = withRender(createTerm())\n * const { output, buffer } = term.render(root, 80, 24, null, { mode: \"fullscreen\" })\n * const html = await term.renderStatic(<Report />)\n */\n\nimport type { Term } from \"../ansi\"\nimport type { ReactElement } from \"react\"\nimport type { TerminalBuffer } from \"../buffer\"\nimport { createPipeline, type MeasuredTerm } from \"../measurer\"\nimport type { PipelineConfig, ExecuteRenderOptions } from \"../pipeline\"\nimport { outputPhase } from \"../pipeline/output-phase\"\nimport { createAg } from \"../ag\"\nimport { runWithMeasurer } from \"../unicode\"\nimport type { AgNode } from \"@silvery/create/types\"\n\n/**\n * Extended Term with render pipeline capabilities.\n *\n * Extends MeasuredTerm (Term + Measurer methods) with render/renderStatic.\n */\nexport interface RenderTerm extends MeasuredTerm {\n /** Pipeline configuration (measurer + output phase) */\n readonly pipelineConfig: PipelineConfig\n /**\n * Run the full render pipeline.\n */\n render(\n root: AgNode,\n width: number,\n height: number,\n prevBuffer: TerminalBuffer | null,\n options?: ExecuteRenderOptions | \"fullscreen\" | \"inline\",\n ): { output: string; buffer: TerminalBuffer }\n /**\n * Render a React element to a string using this terminal's caps.\n * Uses the term's width measurer for correct text measurement.\n */\n renderStatic(\n element: ReactElement,\n options?: { width?: number; height?: number; plain?: boolean },\n ): Promise<string>\n}\n\n/**\n * Extend a Term with render pipeline capabilities.\n *\n * Creates a pipeline config (width measurer + output phase) from the term's caps,\n * and adds render() and renderStatic() methods plus measurer methods.\n *\n * @param term - A Term instance (from createTerm)\n * @returns Extended term with render and measurement capabilities\n */\nexport function withRender(term: Term): RenderTerm {\n const pipelineConfig = createPipeline({ caps: term.caps })\n const { measurer } = pipelineConfig\n\n function renderPipeline(\n root: AgNode,\n width: number,\n height: number,\n prevBuffer: TerminalBuffer | null,\n options?: ExecuteRenderOptions | \"fullscreen\" | \"inline\",\n ): { output: string; buffer: TerminalBuffer } {\n const opts: ExecuteRenderOptions =\n typeof options === \"string\" ? { mode: options } : (options ?? {})\n const {\n mode = \"fullscreen\",\n skipLayoutNotifications = false,\n skipScrollStateUpdates = false,\n scrollbackOffset = 0,\n termRows,\n cursorPos,\n } = opts\n\n const doRender = () => {\n const ag = createAg(root, { measurer })\n ag.layout({ cols: width, rows: height }, { skipLayoutNotifications, skipScrollStateUpdates })\n const { buffer, overlay } = ag.render({ prevBuffer })\n\n const outputFn = pipelineConfig.outputPhaseFn ?? outputPhase\n let output = outputFn(prevBuffer, buffer, mode, scrollbackOffset, termRows, cursorPos)\n // Append Kitty emoji-scrim overlay when backdrop-fade produced one.\n if (overlay) output += overlay\n\n return { output, buffer }\n }\n\n return measurer ? runWithMeasurer(measurer, doRender) : doRender()\n }\n\n async function renderStaticFn(\n element: ReactElement,\n options?: { width?: number; height?: number; plain?: boolean },\n ): Promise<string> {\n const { renderString } = await import(\"@silvery/ag-react/render-string\")\n return renderString(element, { ...options, pipelineConfig })\n }\n\n // Return a proxy that extends the original term with measurer methods and render capabilities\n return Object.create(term, {\n // Measurer methods (from pipeline config)\n maybeWideEmojis: { get: () => measurer.maybeWideEmojis, enumerable: true },\n textSizing: { get: () => measurer.textSizing, enumerable: true },\n displayWidth: { value: measurer.displayWidth.bind(measurer), enumerable: true },\n displayWidthAnsi: { value: measurer.displayWidthAnsi.bind(measurer), enumerable: true },\n graphemeWidth: { value: measurer.graphemeWidth.bind(measurer), enumerable: true },\n wrapText: { value: measurer.wrapText.bind(measurer), enumerable: true },\n sliceByWidth: { value: measurer.sliceByWidth.bind(measurer), enumerable: true },\n sliceByWidthFromEnd: { value: measurer.sliceByWidthFromEnd.bind(measurer), enumerable: true },\n // Pipeline config and render methods\n pipelineConfig: { value: pipelineConfig, enumerable: true },\n render: { value: renderPipeline, enumerable: true },\n renderStatic: { value: renderStaticFn, enumerable: true },\n }) as RenderTerm\n}\n","/**\n * BaseApp — the apply-chain substrate for silvery's runtime.\n *\n * This is the contract from the v1r prototype, shipped as production code.\n * Plugins wrap `apply()`; `dispatch()` drives the chain and drains the\n * resulting effect queue.\n *\n * ## Semantics\n *\n * `apply(op)` is synchronous and returns an {@link ApplyResult}:\n * - `false` — \"I did not handle this op; pass to the next plugin (or ignore)\"\n * - `Effect[]` — \"I handled this op; here are the follow-up effects to run\"\n *\n * `dispatch(op)` runs `apply(op)` inside a reentry guard, pushes any\n * emitted effects onto a shared queue, then drains the queue. The only\n * built-in effect understood by the base is `{type: \"dispatch\", op: Op}` —\n * which re-dispatches the nested op through the apply chain. All other\n * effects (`render`, `exit`, `suspend`, `render-barrier`) are interpreted\n * by the runner that owns this app (typically `create-app.tsx`'s\n * processEventBatch).\n *\n * ## Why a queue + drain loop?\n *\n * Plugins should be free to emit multiple effects and to chain them —\n * `dispatch` effects from one plugin can produce more effects from\n * another. The queue + drain guarantees:\n * 1. No reentrant `dispatch()` — a plugin that dispatches from inside\n * its own apply() throws. Follow-up dispatches must be Effects.\n * 2. Bounded iteration — each drain step consumes the whole batch\n * before accepting new effects, so we can detect runaways.\n *\n * ## Why a named sentinel effect queue?\n *\n * When the runner reads the queue (via {@link BaseApp.drainEffects}), it\n * gets an immutable snapshot so it can choose how to interpret the\n * effects. The base never implements `render` or `exit` — those belong\n * to whichever runner is driving.\n *\n * @see {@link Op}, {@link Effect}, {@link ApplyResult}\n */\n\nimport type { ApplyResult, Effect, Op } from \"../types\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * An app's `apply` function: takes an op, returns either `false` (not\n * handled) or an array of effects.\n */\nexport type Apply = (op: Op) => ApplyResult\n\n/**\n * The minimal contract every silvery runtime exposes.\n *\n * Plugins extend this by capturing `app.apply` and replacing it with a\n * new function that delegates to the captured one for ops it doesn't\n * handle. Stores/state slices ride as extra properties on the concrete\n * plugin-enhanced type.\n */\nexport interface BaseApp {\n /**\n * Entry point for an op. Runs the apply chain, captures emitted\n * effects, then drains. Throws on reentrant dispatch.\n */\n dispatch(op: Op): void\n\n /**\n * The apply chain. The base returns `false` (nothing handles\n * anything). Plugins wrap this — last plugin applied = outermost\n * wrapper = runs first.\n */\n apply: Apply\n\n /**\n * Pull (and clear) effects the runner should interpret. Returns any\n * effects that were left *after* built-in `dispatch` drain completed\n * and that the runner is expected to act on (render/exit/etc).\n *\n * Most runners call this after `dispatch()` returns to flush the\n * render/exit queue.\n */\n drainEffects(): Effect[]\n}\n\n// ---------------------------------------------------------------------------\n// create() — base of the chain\n// ---------------------------------------------------------------------------\n\n/**\n * Create a fresh {@link BaseApp}. The base handles nothing — all\n * behavior comes from plugins.\n *\n * Call order inside `dispatch(op)`:\n * 1. `apply(op)` (runs the plugin chain, inside reentry guard)\n * 2. Queue the returned effects (if handled)\n * 3. Drain loop — dispatch-type effects re-enter, others bubble up to\n * the runner via {@link BaseApp.drainEffects}\n */\nexport function createBaseApp(): BaseApp {\n let dispatching = false\n let draining = false\n /** Effects currently queued from the active dispatch() + its drain. */\n const effectQueue: Effect[] = []\n /** Effects the runner should interpret (everything not consumed by\n * the internal dispatch-drain, e.g. render/exit). */\n const pendingRunnerEffects: Effect[] = []\n\n const app: BaseApp = {\n dispatch(op) {\n if (dispatching) {\n throw new Error(`Reentrant dispatch: ${op.type}`)\n }\n dispatching = true\n try {\n const result = app.apply(op)\n if (result !== false) effectQueue.push(...result)\n } finally {\n dispatching = false\n }\n if (draining) return\n draining = true\n try {\n while (effectQueue.length > 0) {\n const batch = effectQueue.splice(0)\n for (const eff of batch) {\n if (eff.type === \"dispatch\") {\n // Re-dispatch is the only effect the base knows. The\n // nested op travels under `op` to avoid spread collisions\n // with the `type: \"dispatch\"` discriminator.\n const nested = (eff as { op?: Op }).op\n if (!nested || typeof nested.type !== \"string\") continue\n dispatching = true\n try {\n const nestedResult = app.apply(nested)\n if (nestedResult !== false) effectQueue.push(...nestedResult)\n } finally {\n dispatching = false\n }\n } else {\n pendingRunnerEffects.push(eff)\n }\n }\n }\n } finally {\n draining = false\n }\n },\n apply() {\n return false\n },\n drainEffects() {\n if (pendingRunnerEffects.length === 0) return []\n return pendingRunnerEffects.splice(0)\n },\n }\n return app\n}\n\n// ---------------------------------------------------------------------------\n// Plugin pattern (no helper — just the idiom)\n// ---------------------------------------------------------------------------\n\n/**\n * Every `with*` plugin follows the same three-line idiom:\n *\n * ```ts\n * export function withEcho<A extends BaseApp>(app: A): A {\n * const prev = app.apply\n * app.apply = (op) => {\n * if (op.type === \"echo\") {\n * // handle\n * return []\n * }\n * return prev(op) // delegate to downstream chain\n * }\n * return app\n * }\n * ```\n *\n * The last plugin installed is the OUTERMOST wrapper and runs first.\n * Always delegate via `prev(op)` for ops you don't handle.\n */\n","/**\n * withInkCursor() — Thin adapter bridging Ink's useCursor to silvery's CursorStore.\n *\n * Canonical home for InkCursorStoreCtx. Consumed by ink.ts (useCursor hook).\n *\n * @packageDocumentation\n */\nimport React, { createContext, Fragment } from \"react\"\nimport {\n createCursorStore,\n CursorProvider,\n type CursorStore,\n} from \"@silvery/ag-react/hooks/useCursor\"\nimport type { RunnableApp } from \"./with-ink\"\n\n// =============================================================================\n// Ink Cursor Store Context\n// =============================================================================\n\n/**\n * Context for passing cursor store to Ink compat useCursor hook.\n * This lets useCursor write directly to the store without going through\n * silvery's useCursor hook (which requires NodeContext for layout).\n */\nexport const InkCursorStoreCtx = createContext<CursorStore | null>(null)\n\n// =============================================================================\n// withInkCursor — App-level plugin for pipe() composition\n// =============================================================================\n\nexport interface WithInkCursorOptions {\n cursorStore?: CursorStore\n}\n\nexport interface AppWithInkCursor {\n readonly Root: React.ComponentType<{ children: React.ReactNode }>\n}\n\nexport function withInkCursor<T extends RunnableApp>(\n options: WithInkCursorOptions = {},\n): (app: T) => T & AppWithInkCursor {\n const cursorStore = options.cursorStore ?? createCursorStore()\n\n return (app: T): T & AppWithInkCursor => {\n const PrevRoot = app.Root ?? Fragment\n const InkCursorRoot = ({ children }: { children: React.ReactNode }) =>\n React.createElement(\n CursorProvider,\n { store: cursorStore },\n React.createElement(\n InkCursorStoreCtx.Provider,\n { value: cursorStore },\n React.createElement(PrevRoot, null, children),\n ),\n )\n\n return Object.assign(Object.create(app), {\n Root: InkCursorRoot,\n }) as T & AppWithInkCursor\n }\n}\n","/**\n * withInkFocus() — Plugin providing Ink's focus system.\n *\n * Canonical home for InkFocusContext, InkFocusProvider, and the Focusable type.\n * Consumed by ink.ts (useFocus/useFocusManager hooks).\n *\n * @packageDocumentation\n */\nimport React, { createContext, Fragment, useCallback, useEffect, useMemo, useState } from \"react\"\nimport type { EventEmitter } from \"node:events\"\nimport type { RunnableApp } from \"./with-ink\"\n\n// =============================================================================\n// Ink Focus Context & Provider\n// =============================================================================\n\n/** A focusable entry in the Ink focus system. */\ntype Focusable = { id: string; isActive: boolean }\n\nexport type InkFocusContextValue = {\n activeId: string | undefined\n isFocusEnabled: boolean\n add: (id: string, options: { autoFocus: boolean }) => void\n remove: (id: string) => void\n activate: (id: string) => void\n deactivate: (id: string) => void\n enableFocus: () => void\n disableFocus: () => void\n focusNext: () => void\n focusPrevious: () => void\n focus: (id: string) => void\n blur: () => void\n}\n\nexport const InkFocusContext = createContext<InkFocusContextValue>({\n activeId: undefined,\n isFocusEnabled: true,\n add() {},\n remove() {},\n activate() {},\n deactivate() {},\n enableFocus() {},\n disableFocus() {},\n focusNext() {},\n focusPrevious() {},\n focus() {},\n blur() {},\n})\n\n/**\n * Ink-compatible FocusProvider component.\n * Manages focus state: list of focusables, active focus ID, tab navigation.\n */\nexport function InkFocusProvider({\n children,\n inputEmitter,\n}: {\n children?: React.ReactNode\n inputEmitter?: EventEmitter\n}) {\n const [isFocusEnabled, setIsFocusEnabled] = useState(true)\n const [activeFocusId, setActiveFocusId] = useState<string | undefined>(undefined)\n const [, setFocusables] = useState<Focusable[]>([])\n const focusablesCountRef = React.useRef(0)\n\n const findNextFocusable = useCallback(\n (\n currentFocusables: Focusable[],\n currentActiveFocusId: string | undefined,\n ): string | undefined => {\n const activeIndex = currentFocusables.findIndex((f) => f.id === currentActiveFocusId)\n for (let i = activeIndex + 1; i < currentFocusables.length; i++) {\n if (currentFocusables[i]?.isActive) return currentFocusables[i]!.id\n }\n return undefined\n },\n [],\n )\n\n const findPreviousFocusable = useCallback(\n (\n currentFocusables: Focusable[],\n currentActiveFocusId: string | undefined,\n ): string | undefined => {\n const activeIndex = currentFocusables.findIndex((f) => f.id === currentActiveFocusId)\n for (let i = activeIndex - 1; i >= 0; i--) {\n if (currentFocusables[i]?.isActive) return currentFocusables[i]!.id\n }\n return undefined\n },\n [],\n )\n\n const focusNext = useCallback((): void => {\n setFocusables((currentFocusables) => {\n setActiveFocusId((currentActiveFocusId) => {\n const firstFocusableId = currentFocusables.find((f) => f.isActive)?.id\n const nextFocusableId = findNextFocusable(currentFocusables, currentActiveFocusId)\n return nextFocusableId ?? firstFocusableId\n })\n return currentFocusables\n })\n }, [findNextFocusable])\n\n const focusPrevious = useCallback((): void => {\n setFocusables((currentFocusables) => {\n setActiveFocusId((currentActiveFocusId) => {\n const lastFocusableId = currentFocusables.findLast((f) => f.isActive)?.id\n const previousFocusableId = findPreviousFocusable(currentFocusables, currentActiveFocusId)\n return previousFocusableId ?? lastFocusableId\n })\n return currentFocusables\n })\n }, [findPreviousFocusable])\n\n const enableFocus = useCallback((): void => {\n setIsFocusEnabled(true)\n }, [])\n const disableFocus = useCallback((): void => {\n setIsFocusEnabled(false)\n }, [])\n\n const focus = useCallback((id: string): void => {\n setFocusables((currentFocusables) => {\n if (currentFocusables.some((f) => f.id === id)) {\n setActiveFocusId(id)\n }\n return currentFocusables\n })\n }, [])\n\n const blur = useCallback((): void => {\n setActiveFocusId(undefined)\n }, [])\n\n const addFocusable = useCallback((id: string, { autoFocus }: { autoFocus: boolean }): void => {\n setFocusables((currentFocusables) => {\n focusablesCountRef.current = currentFocusables.length + 1\n return [...currentFocusables, { id, isActive: true }]\n })\n if (autoFocus) {\n setActiveFocusId((currentActiveFocusId) => {\n if (!currentActiveFocusId) return id\n return currentActiveFocusId\n })\n }\n }, [])\n\n const removeFocusable = useCallback((id: string): void => {\n setActiveFocusId((currentActiveFocusId) => {\n if (currentActiveFocusId === id) return undefined\n return currentActiveFocusId\n })\n setFocusables((currentFocusables) => {\n const filtered = currentFocusables.filter((f) => f.id !== id)\n focusablesCountRef.current = filtered.length\n return filtered\n })\n }, [])\n\n const activateFocusable = useCallback((id: string): void => {\n setFocusables((currentFocusables) =>\n currentFocusables.map((f) => (f.id === id ? { ...f, isActive: true } : f)),\n )\n }, [])\n\n const deactivateFocusable = useCallback((id: string): void => {\n setActiveFocusId((currentActiveFocusId) => {\n if (currentActiveFocusId === id) return undefined\n return currentActiveFocusId\n })\n setFocusables((currentFocusables) =>\n currentFocusables.map((f) => (f.id === id ? { ...f, isActive: false } : f)),\n )\n }, [])\n\n // Tab/Shift+Tab/Esc focus navigation via inputEmitter (raw escape sequences)\n useEffect(() => {\n if (!inputEmitter) return\n const tab = \"\\t\"\n const shiftTab = \"\\x1b[Z\"\n const escape = \"\\x1b\"\n const handleInput = (data: string | Buffer) => {\n const input = typeof data === \"string\" ? data : data.toString()\n if (!isFocusEnabled || focusablesCountRef.current === 0) return\n if (input === tab) focusNext()\n else if (input === shiftTab) focusPrevious()\n else if (input === escape) setActiveFocusId(undefined)\n }\n inputEmitter.on(\"input\", handleInput)\n return () => {\n inputEmitter.removeListener(\"input\", handleInput)\n }\n }, [isFocusEnabled, focusNext, focusPrevious, inputEmitter])\n\n const contextValue = useMemo(\n () => ({\n activeId: activeFocusId,\n isFocusEnabled,\n add: addFocusable,\n remove: removeFocusable,\n activate: activateFocusable,\n deactivate: deactivateFocusable,\n enableFocus,\n disableFocus,\n focusNext,\n focusPrevious,\n focus,\n blur,\n }),\n [\n activeFocusId,\n isFocusEnabled,\n addFocusable,\n removeFocusable,\n activateFocusable,\n deactivateFocusable,\n enableFocus,\n disableFocus,\n focusNext,\n focusPrevious,\n focus,\n blur,\n ],\n )\n\n return React.createElement(InkFocusContext.Provider, { value: contextValue }, children)\n}\n\n// =============================================================================\n// withInkFocus — App-level plugin for pipe() composition\n// =============================================================================\n\nexport interface WithInkFocusOptions {\n inputEmitter?: EventEmitter\n}\n\nexport interface AppWithInkFocus {\n readonly Root: React.ComponentType<{ children: React.ReactNode }>\n}\n\nexport function withInkFocus<T extends RunnableApp>(\n options: WithInkFocusOptions = {},\n): (app: T) => T & AppWithInkFocus {\n return (app: T): T & AppWithInkFocus => {\n const PrevRoot = app.Root ?? Fragment\n const InkFocusRoot = ({ children }: { children: React.ReactNode }) =>\n React.createElement(\n InkFocusProvider,\n { inputEmitter: options.inputEmitter },\n React.createElement(PrevRoot, null, children),\n )\n\n return Object.assign(Object.create(app), {\n Root: InkFocusRoot,\n }) as T & AppWithInkFocus\n }\n}\n","/**\n * Render-output throughput monitor — backs the `SILVERY_STRICT=bytes_out`\n * slug. Watches the bytes silvery's render pipeline writes to stdout and\n * trips at fixed thresholds.\n *\n * Why this exists: silvery emits ANSI bytes to stdout; those bytes leave the\n * process and land in the pty parent's buffer (terminal multiplexer\n * scrollback — cmux, tmux, etc.). Internal heap profilers\n * (`process.memoryUsage`, `Bun.heapStats`, `--inspect`) cannot see this\n * firehose because the bytes are already gone. The 2026-05-13 cmux/silvercode\n * incident attributed multi-GB of memory to cmux, not silvercode; silvercode's\n * own heap looked healthy throughout. See\n * `docs/lessons/cmux-pty-buffer-firehose.md` (in km root) for the full\n * incident write-up.\n *\n * Behavior (fixed thresholds — no env knobs):\n * - WARN at sustained ≥1 MB/s over a 10s window. Emits a `silvery:bytes_out`\n * log + writes frame summaries to `/tmp/silvery-bytes-out-warn-<pid>-<ts>.txt`.\n * Cooldown 10s before re-trip.\n * - PANIC at burst ≥100 MB/s over a 2s window. Emits frame summaries AND a\n * v8 heap snapshot. Cooldown 60s before re-trip.\n *\n * Activation: tier 1 default (inherited from `SILVERY_STRICT=1`). Opt out per\n * test/session with `SILVERY_STRICT=1,!bytes_out`. The options below are\n * TEST overrides only — production reads no env knobs (per the user's\n * \"diagnostics that require remembering ad-hoc env vars don't get used\" rule).\n */\nimport { createLogger, type ConditionalLogger } from \"loggily\"\n\n/**\n * Minimal logger surface the monitor depends on. Lets tests pass a small\n * mock without satisfying the full ConditionalLogger interface.\n */\nexport interface BytesOutLogger {\n warn?: (msg: string | (() => string), data?: unknown) => void\n error?: (msg: string | (() => string), data?: unknown) => void\n}\n\nconst NS = \"silvery:bytes_out\"\n\nconst FRAME_HISTORY = 100\nconst WARN_WINDOW_MS = 10_000\nconst PANIC_WINDOW_MS = 2_000\nconst WARN_COOLDOWN_MS = 10_000\nconst PANIC_COOLDOWN_MS = 60_000\nconst EVICT_OLDER_THAN_MS = 60_000\n\nconst DEFAULT_WARN_MB_PER_SEC = 1\nconst DEFAULT_PANIC_MB_PER_SEC = 100\n\ninterface FrameEntry {\n frameNum: number\n ts: number\n bytes: number\n diagnostics?: BytesOutFrameDiagnostics\n}\n\nexport interface BytesOutFrameDiagnostics {\n source?: \"terminal-artifact\" | \"post-paint-write\" | \"terminal-protocol\"\n owner?: string\n phase?: \"setup\" | \"runtime\" | \"cleanup\" | \"pre-paint\" | \"post-paint\"\n artifactKind?: string\n reason?: string\n mode?: \"fullscreen\" | \"inline\"\n width?: number\n height?: number\n prevWidth?: number\n prevHeight?: number\n changedCells?: number\n rawChangedCells?: number\n dirtyRows?: number\n outputChars?: number\n cursorChars?: number\n syncWrapped?: boolean\n}\n\n/**\n * Test-only overrides for the monitor. Production code passes nothing — the\n * scheduler instantiates the monitor with all defaults so the activation\n * surface stays at `SILVERY_STRICT` slugs only.\n */\nexport interface BytesOutMonitorOptions {\n /** TEST: override WARN threshold (MB/s). Production is fixed at 1 MB/s. */\n warnMbPerSec?: number\n /** TEST: override PANIC threshold (MB/s). Production is fixed at 100 MB/s. */\n panicMbPerSec?: number\n /** TEST: skip the v8 heap snapshot at PANIC. Production always writes one. */\n skipHeapSnapshot?: boolean\n /** TEST: snapshot output directory. Production is `/tmp`. */\n snapshotDir?: string\n /** TEST: logger override (minimal warn/error surface; full ConditionalLogger also accepted). */\n logger?: BytesOutLogger | ConditionalLogger\n /** TEST: clock override. */\n now?: () => number\n /** TEST: heap-snapshot writer override. */\n writeHeapSnapshot?: (path: string) => string | void\n /** TEST: frame-summary writer override. */\n writeFile?: (path: string, contents: string) => void\n /** TEST: process id used in dump paths. */\n pid?: number\n}\n\nexport interface BytesOutTrip {\n kind: \"warn\" | \"panic\"\n ts: number\n /** MB/s observed over the window. */\n mbPerSec: number\n /** Path to the frame-summaries dump. */\n summaryPath: string\n /** Path to the heap snapshot — present at PANIC unless skipped. */\n snapshotPath?: string\n}\n\nexport interface BytesOutMonitor {\n recordWrite(frameNum: number, bytes: number, diagnostics?: BytesOutFrameDiagnostics): void\n /** Most recent trip event (across both warn + panic) — primarily for tests. */\n readonly lastTrip: BytesOutTrip | null\n /** Last 100 frame entries — primarily for tests. */\n snapshotFrames(): readonly FrameEntry[]\n dispose(): void\n}\n\nexport function createBytesOutMonitor(options: BytesOutMonitorOptions = {}): BytesOutMonitor {\n const warnMbPerSec = options.warnMbPerSec ?? DEFAULT_WARN_MB_PER_SEC\n const panicMbPerSec = options.panicMbPerSec ?? DEFAULT_PANIC_MB_PER_SEC\n const skipHeapSnapshot = options.skipHeapSnapshot ?? false\n const snapshotDir = options.snapshotDir ?? \"/tmp\"\n const log = options.logger ?? createLogger(NS)\n const now = options.now ?? Date.now\n const writeHeapSnapshot = options.writeHeapSnapshot ?? defaultWriteHeapSnapshot\n const writeFile = options.writeFile ?? defaultWriteFile\n const pid = options.pid ?? process.pid\n\n // Convert thresholds: MB/s × window-seconds → bytes-in-window.\n const warnBytesInWindow = warnMbPerSec * 1_000_000 * (WARN_WINDOW_MS / 1000)\n const panicBytesInWindow = panicMbPerSec * 1_000_000 * (PANIC_WINDOW_MS / 1000)\n\n const frames: FrameEntry[] = []\n let lastWarnAt = 0\n let lastPanicAt = 0\n let lastTrip: BytesOutTrip | null = null\n let disposed = false\n\n function evictOld(cutoff: number): void {\n while (frames.length > 0 && frames[0]!.ts < cutoff) frames.shift()\n }\n\n function sumBytesInWindow(windowMs: number, t: number): number {\n const horizon = t - windowMs\n let sum = 0\n for (let i = frames.length - 1; i >= 0; i--) {\n const f = frames[i]!\n if (f.ts < horizon) break\n sum += f.bytes\n }\n return sum\n }\n\n function dumpFrameSummaries(filePath: string): boolean {\n try {\n const tail = frames.slice(-FRAME_HISTORY)\n const header =\n \"# frameNum\\tts\\tbytes\\tsource\\towner\\tphase\\tartifactKind\\treason\\tmode\\twidth\\theight\\tprevWidth\\tprevHeight\\tchangedCells\\trawChangedCells\\tdirtyRows\\toutputChars\\tcursorChars\\tsyncWrapped\\n\"\n const body = tail\n .map((f) => {\n const d = f.diagnostics\n return [\n f.frameNum,\n new Date(f.ts).toISOString(),\n f.bytes,\n d?.source,\n d?.owner,\n d?.phase,\n d?.artifactKind,\n d?.reason,\n d?.mode,\n d?.width,\n d?.height,\n d?.prevWidth,\n d?.prevHeight,\n d?.changedCells,\n d?.rawChangedCells,\n d?.dirtyRows,\n d?.outputChars,\n d?.cursorChars,\n d?.syncWrapped,\n ]\n .map(formatSummaryField)\n .join(\"\\t\")\n })\n .join(\"\\n\")\n writeFile(filePath, header + body + \"\\n\")\n return true\n } catch (e) {\n log.error?.(`failed to write frame summary: ${(e as Error).message}`)\n return false\n }\n }\n\n function maybePanic(t: number): boolean {\n if (t - lastPanicAt < PANIC_COOLDOWN_MS) return false\n const burstBytes = sumBytesInWindow(PANIC_WINDOW_MS, t)\n if (burstBytes < panicBytesInWindow) return false\n\n const summaryPath = `${snapshotDir}/silvery-bytes-out-panic-${pid}-${t}.summary.txt`\n dumpFrameSummaries(summaryPath)\n\n let snapshotPath: string | undefined\n if (!skipHeapSnapshot) {\n const candidate = `${snapshotDir}/silvery-firehose-${pid}-${t}.heapsnapshot`\n try {\n const written = writeHeapSnapshot(candidate)\n snapshotPath = typeof written === \"string\" ? written : candidate\n } catch (e) {\n log.error?.(`failed to write heap snapshot: ${(e as Error).message}`)\n }\n }\n\n const mbPerSec = burstBytes / 1_000_000 / (PANIC_WINDOW_MS / 1000)\n log.error?.(\n `PANIC render-out burst ${mbPerSec.toFixed(1)} MB/s exceeds ${panicMbPerSec} MB/s for ${PANIC_WINDOW_MS}ms`,\n { summary: summaryPath, heapSnapshot: snapshotPath },\n )\n\n lastPanicAt = t\n lastTrip = { kind: \"panic\", ts: t, mbPerSec, summaryPath, snapshotPath }\n return true\n }\n\n function maybeWarn(t: number): boolean {\n if (t - lastWarnAt < WARN_COOLDOWN_MS) return false\n const sustainedBytes = sumBytesInWindow(WARN_WINDOW_MS, t)\n if (sustainedBytes < warnBytesInWindow) return false\n\n const summaryPath = `${snapshotDir}/silvery-bytes-out-warn-${pid}-${t}.summary.txt`\n dumpFrameSummaries(summaryPath)\n\n const mbPerSec = sustainedBytes / 1_000_000 / (WARN_WINDOW_MS / 1000)\n log.warn?.(\n `WARN render-out sustained ${mbPerSec.toFixed(1)} MB/s exceeds ${warnMbPerSec} MB/s over ${WARN_WINDOW_MS}ms`,\n { summary: summaryPath },\n )\n\n lastWarnAt = t\n lastTrip = { kind: \"warn\", ts: t, mbPerSec, summaryPath }\n return true\n }\n\n function recordWrite(\n frameNum: number,\n bytes: number,\n diagnostics?: BytesOutFrameDiagnostics,\n ): void {\n if (disposed) return\n const t = now()\n evictOld(t - EVICT_OLDER_THAN_MS)\n frames.push({ frameNum, ts: t, bytes, diagnostics })\n // PANIC supersedes WARN in the same record — they have separate cooldowns.\n if (maybePanic(t)) return\n maybeWarn(t)\n }\n\n function snapshotFrames(): readonly FrameEntry[] {\n return frames.slice()\n }\n\n function dispose(): void {\n disposed = true\n frames.length = 0\n }\n\n return {\n recordWrite,\n get lastTrip() {\n return lastTrip\n },\n snapshotFrames,\n dispose,\n }\n}\n\nfunction formatSummaryField(value: unknown): string {\n if (value === undefined || value === null) return \"\"\n return String(value)\n}\n\nfunction defaultWriteHeapSnapshot(path: string): string | void {\n const v8 = loadV8()\n return v8.writeHeapSnapshot(path)\n}\n\nfunction defaultWriteFile(path: string, contents: string): void {\n // sync-node-builtin: multi-target lazy guard via getBuiltinModule (ESM-only wave-3).\n const fs = process.getBuiltinModule(\"node:fs\")\n fs.writeFileSync(path, contents)\n}\n\nfunction loadV8(): typeof import(\"node:v8\") {\n // sync-node-builtin: multi-target lazy guard via getBuiltinModule (ESM-only wave-3).\n return process.getBuiltinModule(\"node:v8\")\n}\n","/**\n * Repaint-storm (flicker) monitor — backs the `SILVERY_STRICT=flicker` slug\n * (alias intent: \"cls_storm\"). Watches the ANSI byte stream silvery's render\n * pipeline writes to the terminal and PANICs when destructive full-screen\n * clears / scroll-region resets repeat fast enough to constitute visible\n * flicker.\n *\n * Why this exists: flicker is the single hardest render bug to catch — it is\n * invisible to text-frame assertions (the FINAL frame is correct; the damage\n * is the transient blank between frames) and historically took eyeball +\n * `#undead` rounds to pin down. The `@ag/code/20297-pane-flicker-on-resize`\n * incident was a bare `\\x1b[2J` emitted OUTSIDE the DEC-2026 sync region on\n * every resize: each `2J` blanked the pane for one composited frame before the\n * repaint landed. Repeated rapidly (resize-drag, a layout limit-cycle, or a\n * stale process re-emitting frames) that is a strobing blank-flash. This\n * monitor makes that class fail LOUD + NAMED at the source.\n *\n * What counts as a storm signal (per frame written to the terminal):\n * - `\\x1b[2J` / `\\x1b[3J` — erase-in-display (the 20297 culprit). `\\x1b[J` and\n * `\\x1b[0J` (erase-to-end) and `\\x1b[1J` (erase-to-start) are NOT counted:\n * they are partial, non-blanking, and used legitimately by inline/static\n * output. Only the full-screen erasers blank the viewport.\n * - `\\x1b[r` / `\\x1b[<top>;<bottom>r` — DECSTBM scroll-region reset/set. A\n * scroll-region thrash (set/reset churn) is the other shape of repaint\n * storm (a \"scroll storm\"); a single frame carrying one is benign,\n * repetition in the window is not.\n *\n * What does NOT count (CRITICAL — the 20297 *fix* must never false-trip):\n * - A homed full-viewport repaint (`\\x1b[H` followed by a full `bufferToAnsi`\n * rewrite of every cell) is the LEGITIMATE post-20297 resize/focus frame. It\n * carries NO `2J`/`3J` at all, so it registers ZERO storm signals — it can\n * never trip this monitor regardless of size. The monitor targets\n * destructive-clear REPETITION within a window, not full repaints, and not a\n * single isolated clear.\n *\n * Behavior (fixed thresholds — no env knobs):\n * - PANIC when ≥ `PANIC_CLEARS` destructive clears land within `WINDOW_MS`\n * with no intervening settle (a settle = a frame in the window that carried\n * zero storm signals, i.e. the storm actually paused). The diagnostic NAMES\n * the offending sequence + the count + the window, plus a culprit hint\n * (bare `2J` outside the sync region — the @ag/code/20297 signature).\n * Cooldown `PANIC_COOLDOWN_MS` before a re-trip so a genuine sustained storm\n * logs once, not every frame.\n *\n * Activation: tier 1 default (inherited from `SILVERY_STRICT=1`, the same tier\n * as `bytes_out`/`mem`). Opt out per test/session with\n * `SILVERY_STRICT=1,!flicker`. The options below are TEST overrides only —\n * production reads no env knobs (per the user's \"diagnostics that require\n * remembering ad-hoc env vars don't get used\" rule).\n */\nimport { createLogger, type ConditionalLogger } from \"loggily\"\n\n/**\n * Minimal logger surface the monitor depends on. Lets tests pass a small\n * mock without satisfying the full ConditionalLogger interface.\n */\nexport interface FlickerLogger {\n warn?: (msg: string | (() => string), data?: unknown) => void\n error?: (msg: string | (() => string), data?: unknown) => void\n}\n\nconst NS = \"silvery:flicker\"\n\n/** Rolling window over which clears are counted. */\nconst WINDOW_MS = 1_000\n/**\n * Number of destructive full-screen clears within the window (with no\n * intervening settle frame) that constitutes a repaint storm.\n *\n * Three is deliberately above any LEGITIMATE clear cadence:\n * - A normal session emits at most ONE full clear per discrete event (startup\n * alt-screen entry, an explicit `invalidate({ clearScreen })` on focus-in).\n * Post-20297 a resize/focus repaint emits ZERO `2J` (the homed full repaint\n * IS the clear), so even rapid resize-drag produces no clears at all.\n * - The 20297 BUG emitted a `2J` on EVERY resize frame; a resize-drag fires\n * tens of frames per second → ≫3 clears in a 1 s window → trips.\n */\nconst PANIC_CLEARS = 3\n/** Cooldown before a sustained storm is allowed to re-log. */\nconst PANIC_COOLDOWN_MS = 5_000\n/** Drop frame records older than this to keep memory bounded. */\nconst EVICT_OLDER_THAN_MS = 10_000\n/** Last-N frames retained for the diagnostic dump. */\nconst FRAME_HISTORY = 50\n\n/**\n * Destructive full-screen erase-in-display: `\\x1b[2J` (whole viewport) and\n * `\\x1b[3J` (viewport + scrollback). Deliberately does NOT match `\\x1b[J`,\n * `\\x1b[0J`, or `\\x1b[1J` — those are partial, non-blanking erases.\n */\nconst FULL_CLEAR_RE = /\\x1b\\[[23]J/g\n/**\n * DECSTBM scroll-region reset/set: `\\x1b[r` (reset to full screen) or\n * `\\x1b[<top>;<bottom>r`. A private-mode variant (`\\x1b[?...r`) is matched too\n * so DEC private scroll-region churn is caught.\n */\nconst SCROLL_REGION_RE = /\\x1b\\[\\??(?:\\d+(?:;\\d+)*)?r/g\n\n/** Per-frame storm-signal tally. */\ninterface FrameSignals {\n fullClears: number\n scrollRegionResets: number\n}\n\ninterface FrameEntry {\n frameNum: number\n ts: number\n bytes: number\n signals: FrameSignals\n}\n\n/**\n * Test-only overrides for the monitor. Production code passes nothing — the\n * scheduler instantiates the monitor with all defaults so the activation\n * surface stays at `SILVERY_STRICT` slugs only.\n */\nexport interface FlickerMonitorOptions {\n /** TEST: override the clears-in-window PANIC threshold. Production is fixed. */\n panicClears?: number\n /** TEST: override the rolling window (ms). Production is fixed at 1000ms. */\n windowMs?: number\n /**\n * TEST: when true the monitor records + diagnoses but does NOT throw, so a\n * test can assert the trip via `lastTrip` without unwinding the stack.\n * Production always THROWS on PANIC (fail loud). Default false.\n */\n noThrow?: boolean\n /** TEST: logger override (minimal warn/error surface; full ConditionalLogger also accepted). */\n logger?: FlickerLogger | ConditionalLogger\n /** TEST: clock override. */\n now?: () => number\n /** TEST: frame-summary writer override. */\n writeFile?: (path: string, contents: string) => void\n /** TEST: summary output directory. Production is `/tmp`. */\n snapshotDir?: string\n /** TEST: process id used in dump paths. */\n pid?: number\n}\n\nexport interface FlickerTrip {\n kind: \"panic\"\n ts: number\n /** Number of destructive clears counted in the window. */\n clears: number\n /** Number of scroll-region resets counted in the window. */\n scrollRegionResets: number\n /** The window (ms) the count was measured over. */\n windowMs: number\n /** The named offending sequence (human-readable). */\n sequence: string\n /** The full panic message (also passed to log.error + the thrown Error). */\n message: string\n /** Path to the frame-summaries dump. */\n summaryPath: string\n}\n\nexport interface FlickerMonitor {\n /**\n * Record one frame's worth of bytes actually written to the terminal. The\n * monitor scans `output` for destructive-clear / scroll-region sequences and\n * trips when their rate within the rolling window crosses the threshold.\n */\n recordFrame(frameNum: number, output: string): void\n /** Most recent trip event — primarily for tests. */\n readonly lastTrip: FlickerTrip | null\n /** Last frame entries — primarily for tests. */\n snapshotFrames(): readonly FrameEntry[]\n dispose(): void\n}\n\nexport function createFlickerMonitor(options: FlickerMonitorOptions = {}): FlickerMonitor {\n const panicClears = options.panicClears ?? PANIC_CLEARS\n const windowMs = options.windowMs ?? WINDOW_MS\n const noThrow = options.noThrow ?? false\n const log = options.logger ?? createLogger(NS)\n const now = options.now ?? Date.now\n const writeFile = options.writeFile ?? defaultWriteFile\n const snapshotDir = options.snapshotDir ?? \"/tmp\"\n const pid = options.pid ?? process.pid\n\n const frames: FrameEntry[] = []\n let lastPanicAt = 0\n let lastTrip: FlickerTrip | null = null\n let disposed = false\n\n function evictOld(cutoff: number): void {\n while (frames.length > 0 && frames[0]!.ts < cutoff) frames.shift()\n }\n\n /**\n * Count storm signals across the in-window tail. Returns the totals AND\n * whether the storm settled (a window frame carried zero signals) — a settle\n * resets the \"fast repetition\" judgment so a slow trickle of clears spread\n * across calm frames never trips.\n */\n function tallyWindow(t: number): {\n fullClears: number\n scrollRegionResets: number\n settled: boolean\n } {\n const horizon = t - windowMs\n let fullClears = 0\n let scrollRegionResets = 0\n let settled = false\n for (let i = frames.length - 1; i >= 0; i--) {\n const f = frames[i]!\n if (f.ts < horizon) break\n const s = f.signals\n const frameSignals = s.fullClears + s.scrollRegionResets\n if (frameSignals === 0) {\n // A settle frame in the window means the storm paused — the clears on\n // either side of it are not a single uninterrupted burst.\n settled = true\n break\n }\n fullClears += s.fullClears\n scrollRegionResets += s.scrollRegionResets\n }\n return { fullClears, scrollRegionResets, settled }\n }\n\n function dumpFrameSummaries(filePath: string): void {\n try {\n const tail = frames.slice(-FRAME_HISTORY)\n const header = \"# frameNum\\tts\\tbytes\\tfullClears\\tscrollRegionResets\\n\"\n const body = tail\n .map((f) =>\n [\n f.frameNum,\n new Date(f.ts).toISOString(),\n f.bytes,\n f.signals.fullClears,\n f.signals.scrollRegionResets,\n ].join(\"\\t\"),\n )\n .join(\"\\n\")\n writeFile(filePath, header + body + \"\\n\")\n } catch (e) {\n log.error?.(`failed to write flicker frame summary: ${(e as Error).message}`)\n }\n }\n\n function maybePanic(t: number): void {\n if (t - lastPanicAt < PANIC_COOLDOWN_MS) return\n const { fullClears, scrollRegionResets, settled } = tallyWindow(t)\n // The storm must be UNINTERRUPTED within the window — a settle frame means\n // the clears were not a single fast burst (so an occasional legit clear,\n // even a few across a calm session, never accumulates into a trip).\n if (settled) return\n if (fullClears < panicClears) return\n\n const summaryPath = `${snapshotDir}/silvery-flicker-panic-${pid}-${t}.summary.txt`\n dumpFrameSummaries(summaryPath)\n\n const clearSeq = \"\\\\x1b[2J\"\n const sequence =\n scrollRegionResets > 0\n ? \"\\\\x1b[2J/\\\\x1b[3J full-screen clear + \\\\x1b[r scroll-region reset\"\n : \"\\\\x1b[2J/\\\\x1b[3J full-screen clear\"\n const message =\n `PANIC repaint storm: ${fullClears} full-screen clears` +\n (scrollRegionResets > 0 ? ` + ${scrollRegionResets} scroll-region resets` : \"\") +\n ` in ${windowMs}ms (threshold ${panicClears}). ` +\n `Offending sequence: ${sequence}. ` +\n `Likely culprit: a destructive ${clearSeq} emitted per-frame outside the ` +\n `DEC-2026 sync region (the @ag/code/20297-pane-flicker-on-resize signature) — ` +\n `a full repaint should home (\\\\x1b[H) and overwrite cells instead. ` +\n `Frame summary: ${summaryPath}`\n\n log.error?.(message, { clears: fullClears, scrollRegionResets, windowMs, summary: summaryPath })\n\n lastPanicAt = t\n lastTrip = {\n kind: \"panic\",\n ts: t,\n clears: fullClears,\n scrollRegionResets,\n windowMs,\n sequence,\n message,\n summaryPath,\n }\n\n if (!noThrow) throw new Error(message)\n }\n\n function recordFrame(frameNum: number, output: string): void {\n if (disposed) return\n const t = now()\n evictOld(t - EVICT_OLDER_THAN_MS)\n const signals: FrameSignals = {\n fullClears: countMatches(output, FULL_CLEAR_RE),\n scrollRegionResets: countMatches(output, SCROLL_REGION_RE),\n }\n frames.push({ frameNum, ts: t, bytes: output.length, signals })\n maybePanic(t)\n }\n\n function snapshotFrames(): readonly FrameEntry[] {\n return frames.slice()\n }\n\n function dispose(): void {\n disposed = true\n frames.length = 0\n }\n\n return {\n recordFrame,\n get lastTrip() {\n return lastTrip\n },\n snapshotFrames,\n dispose,\n }\n}\n\nfunction countMatches(haystack: string, re: RegExp): number {\n // re carries the global flag; reset lastIndex defensively (these are\n // module-level singletons reused across calls).\n re.lastIndex = 0\n let count = 0\n while (re.exec(haystack) !== null) count++\n re.lastIndex = 0\n return count\n}\n\nfunction defaultWriteFile(path: string, contents: string): void {\n // sync-node-builtin: multi-target lazy guard via getBuiltinModule (ESM-only wave-3).\n const fs = process.getBuiltinModule(\"node:fs\")\n fs.writeFileSync(path, contents)\n}\n","/**\n * In-process heap poll monitor — backs the `SILVERY_STRICT=mem` slug\n * (Layer 2 of the memory-observability stack).\n *\n * Samples `process.memoryUsage()` every 30 s on a fixed interval, logs the\n * full breakdown via the `silvery:mem` loggily namespace, and trips a\n * one-shot WARN line if RSS or `external + arrayBuffers` doubles within a\n * 60 s window.\n *\n * Where this fits: L1 (`bytes_out`) watches data leaving the process via the\n * render pipeline. L2 (`mem`, this file) watches data still inside the\n * process. L4 (`bun memwatch <pid>`) is the external view. Together they\n * cover the bytes-out / bytes-in / RSS-outside triad for a pty-attached\n * TUI. See `docs/lessons/cmux-pty-buffer-firehose.md` (in km root) for the\n * motivating incident.\n *\n * Convention: tier-1 default, opt-out via `SILVERY_STRICT=1,!mem`. No env\n * knobs — fixed sample cadence + threshold in code. Per the user-direction\n * documented in `vendor/silvery/docs/guide/debugging.md`:\n * \"diagnostics that require remembering ad-hoc env vars don't get used.\"\n */\nimport { createLogger, type ConditionalLogger } from \"loggily\"\n\n/** Minimal logger surface — tests can pass a partial mock. */\nexport interface MemMonitorLogger {\n info?: (msg: string | (() => string), data?: unknown) => void\n warn?: (msg: string | (() => string), data?: unknown) => void\n error?: (msg: string | (() => string), data?: unknown) => void\n}\n\nconst NS = \"silvery:mem\"\n\nconst SAMPLE_INTERVAL_MS = 30_000\nconst DOUBLE_WINDOW_MS = 60_000\nconst SAMPLE_HISTORY = 8 // 8 × 30s = 240s of history (covers the 60s window comfortably)\n\ninterface MemSample {\n ts: number\n rss: number\n heapUsed: number\n heapTotal: number\n external: number\n arrayBuffers: number\n}\n\nexport interface MemMonitorOptions {\n /** TEST: logger override (minimal info/warn/error surface). */\n logger?: MemMonitorLogger | ConditionalLogger\n /** TEST: clock override. */\n now?: () => number\n /** TEST: memoryUsage override. */\n memoryUsage?: () => NodeJS.MemoryUsage\n /** TEST: drive ticks manually instead of via setInterval (use `tick()`). */\n manual?: boolean\n}\n\nexport interface MemMonitor {\n /** Force one sample now (also fired by the internal interval). */\n tick(): void\n /** Most recent sample (test introspection). */\n readonly lastSample: MemSample | null\n /** Last WARN trip (test introspection). */\n readonly lastWarn: { ts: number; reason: string } | null\n dispose(): void\n}\n\nexport function createMemMonitor(options: MemMonitorOptions = {}): MemMonitor {\n const log = options.logger ?? createLogger(NS)\n const now = options.now ?? Date.now\n const memoryUsage = options.memoryUsage ?? process.memoryUsage\n const manual = options.manual ?? false\n\n const samples: MemSample[] = []\n let lastWarn: { ts: number; reason: string } | null = null\n let lastWarnReason: string | null = null\n let disposed = false\n let interval: ReturnType<typeof setInterval> | null = null\n\n function evictOld(cutoff: number): void {\n while (samples.length > 0 && samples[0]!.ts < cutoff) samples.shift()\n while (samples.length > SAMPLE_HISTORY) samples.shift()\n }\n\n function snapshotMin(field: keyof Omit<MemSample, \"ts\">, windowMs: number, t: number): number {\n const horizon = t - windowMs\n let min = Number.POSITIVE_INFINITY\n for (const s of samples) {\n if (s.ts < horizon) continue\n const v = s[field]\n if (v < min) min = v\n }\n return min\n }\n\n function tick(): void {\n if (disposed) return\n const t = now()\n const m = memoryUsage()\n const sample: MemSample = {\n ts: t,\n rss: m.rss,\n heapUsed: m.heapUsed,\n heapTotal: m.heapTotal,\n external: m.external,\n arrayBuffers: m.arrayBuffers,\n }\n samples.push(sample)\n evictOld(t - DOUBLE_WINDOW_MS * 2) // keep at least one full window of history\n\n log.info?.(\"mem sample\", {\n rss_mb: Math.round(sample.rss / 1_000_000),\n heap_used_mb: Math.round(sample.heapUsed / 1_000_000),\n heap_total_mb: Math.round(sample.heapTotal / 1_000_000),\n external_mb: Math.round(sample.external / 1_000_000),\n array_buffers_mb: Math.round(sample.arrayBuffers / 1_000_000),\n })\n\n // Doubling check — RSS or external+arrayBuffers vs the floor within window.\n const rssMin = snapshotMin(\"rss\", DOUBLE_WINDOW_MS, t)\n if (Number.isFinite(rssMin) && rssMin > 0 && sample.rss >= rssMin * 2) {\n const reason = `RSS doubled in ${DOUBLE_WINDOW_MS / 1000}s: ${Math.round(rssMin / 1_000_000)} → ${Math.round(sample.rss / 1_000_000)} MB`\n if (reason !== lastWarnReason) {\n log.warn?.(`WARN ${reason}`)\n lastWarn = { ts: t, reason }\n lastWarnReason = reason\n }\n return\n }\n const offHeapNow = sample.external + sample.arrayBuffers\n // Compute synthetic min for off-heap = min(external + arrayBuffers) within window.\n const horizon = t - DOUBLE_WINDOW_MS\n let offHeapMin = Number.POSITIVE_INFINITY\n for (const s of samples) {\n if (s.ts < horizon) continue\n const sum = s.external + s.arrayBuffers\n if (sum < offHeapMin) offHeapMin = sum\n }\n if (Number.isFinite(offHeapMin) && offHeapMin > 0 && offHeapNow >= offHeapMin * 2) {\n const reason = `external+arrayBuffers doubled in ${DOUBLE_WINDOW_MS / 1000}s: ${Math.round(offHeapMin / 1_000_000)} → ${Math.round(offHeapNow / 1_000_000)} MB`\n if (reason !== lastWarnReason) {\n log.warn?.(`WARN ${reason}`)\n lastWarn = { ts: t, reason }\n lastWarnReason = reason\n }\n }\n }\n\n function dispose(): void {\n if (disposed) return\n disposed = true\n if (interval !== null) {\n clearInterval(interval)\n interval = null\n }\n samples.length = 0\n }\n\n if (!manual) {\n interval = setInterval(tick, SAMPLE_INTERVAL_MS)\n // Don't keep the process alive just for memory sampling.\n const maybeUnref = interval as unknown as { unref?: () => unknown }\n if (typeof maybeUnref.unref === \"function\") {\n maybeUnref.unref()\n }\n }\n\n return {\n tick,\n get lastSample() {\n return samples.length > 0 ? samples[samples.length - 1]! : null\n },\n get lastWarn() {\n return lastWarn\n },\n dispose,\n }\n}\n","/**\n * Debug utilities for incremental render mismatch diagnostics.\n *\n * When SILVERY_STRICT detects a mismatch between incremental and fresh renders,\n * these utilities help identify the root cause by providing:\n * - Node attribution (which node owns the mismatched cell)\n * - Dirty flag state (what flags were set before render)\n * - Layout changes (prevLayout vs boxRect)\n * - Scroll context (offset changes, hidden items)\n */\n\nimport type { Cell } from \"@silvery/ag-term/buffer\"\nimport type { BoxProps, AgNode, Rect, TextProps } from \"@silvery/ag/types\"\nimport {\n isDirty,\n isAnyDirty,\n CONTENT_BIT,\n STYLE_PROPS_BIT,\n SUBTREE_BIT,\n CHILDREN_BIT,\n} from \"@silvery/ag/epoch\"\nimport type { RenderPhaseStats } from \"@silvery/ag-term/pipeline/types\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Debug info about a node at a screen position */\nexport interface NodeDebugInfo {\n /** Node ID (if set via props.id) */\n id: string | undefined\n /** Node type (silvery-box, silvery-text, silvery-root) */\n type: string\n /** Path from root to this node (IDs or indices) */\n path: string\n /** Index within parent's children array */\n childIndex: number | null\n /** Dirty flags at time of mismatch */\n dirtyFlags: {\n contentDirty: boolean\n stylePropsDirty: boolean\n subtreeDirty: boolean\n childrenDirty: boolean\n }\n /** Layout info */\n layout: {\n prevLayout: Rect | null\n boxRect: Rect | null\n scrollRect: Rect | null\n layoutChanged: boolean\n }\n /** Scroll context (if this is a scroll container or inside one) */\n scroll?: {\n offset: number\n prevOffset: number\n offsetChanged: boolean\n contentHeight: number\n viewportHeight: number\n hiddenAbove: number\n hiddenBelow: number\n firstVisibleChild: number\n lastVisibleChild: number\n }\n /** Background color from props */\n backgroundColor: string | undefined\n /** Number of children */\n childCount: number\n /** Whether node is hidden (Suspense) */\n hidden: boolean\n}\n\n/** Full mismatch debug context */\nexport interface MismatchDebugContext {\n /** Screen position of the mismatch */\n position: { x: number; y: number }\n /** Cell values */\n cells: {\n incremental: Cell\n fresh: Cell\n }\n /** Render number */\n renderNum: number\n /** Node that owns this screen position (innermost) */\n node: NodeDebugInfo | null\n /** Scroll container ancestry (if any) */\n scrollAncestors: NodeDebugInfo[]\n /** All nodes whose scrollRect contains this position */\n containingNodes: NodeDebugInfo[]\n /** Fast-path analysis - why the node was likely skipped */\n fastPathAnalysis: string[]\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Find the innermost node at a screen position.\n */\nexport function findNodeAtPosition(root: AgNode, x: number, y: number): AgNode | null {\n let result: AgNode | null = null\n\n function visit(node: AgNode): void {\n const rect = node.scrollRect\n if (!rect) return\n\n // Check if position is within this node's scrollRect\n if (x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height) {\n result = node // This node contains the position\n\n // Check children (later children render on top of earlier ones)\n for (const child of node.children) {\n visit(child)\n }\n }\n }\n\n visit(root)\n return result\n}\n\n/**\n * Find all nodes whose scrollRect contains the given position.\n * Returns nodes from root to innermost (outermost first).\n */\nexport function findAllContainingNodes(root: AgNode, x: number, y: number): AgNode[] {\n const result: AgNode[] = []\n\n function visit(node: AgNode): void {\n const rect = node.scrollRect\n if (!rect) return\n\n if (x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height) {\n result.push(node)\n for (const child of node.children) {\n visit(child)\n }\n }\n }\n\n visit(root)\n return result\n}\n\n/**\n * Get the path from root to a node (for identification).\n */\nfunction getNodePath(node: AgNode): string {\n const parts: string[] = []\n let current: AgNode | null = node\n\n while (current) {\n const props = current.props as BoxProps & TextProps\n if (props.id) {\n parts.unshift(`#${props.id}`)\n } else if (current.parent) {\n const idx = current.parent.children.indexOf(current)\n parts.unshift(`[${idx}]`)\n } else {\n parts.unshift(\"root\")\n }\n current = current.parent\n }\n\n return parts.join(\" > \")\n}\n\n/**\n * Check if a rect changed (position or size).\n */\nfunction rectChanged(a: Rect | null, b: Rect | null): boolean {\n if (a === b) return false\n if (!a || !b) return true\n return a.x !== b.x || a.y !== b.y || a.width !== b.width || a.height !== b.height\n}\n\n/**\n * Extract debug info from a node.\n */\nexport function getNodeDebugInfo(node: AgNode): NodeDebugInfo {\n const props = node.props as BoxProps & TextProps\n\n // Get child index within parent\n let childIndex: number | null = null\n if (node.parent) {\n childIndex = node.parent.children.indexOf(node)\n }\n\n return {\n id: props.id,\n type: node.type,\n path: getNodePath(node),\n childIndex,\n dirtyFlags: {\n contentDirty: isDirty(node.dirtyBits, node.dirtyEpoch, CONTENT_BIT),\n stylePropsDirty: isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT),\n subtreeDirty: isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT),\n childrenDirty: isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT),\n },\n layout: {\n prevLayout: node.prevLayout,\n boxRect: node.boxRect,\n scrollRect: node.scrollRect,\n layoutChanged: rectChanged(node.prevLayout, node.boxRect),\n },\n scroll: node.scrollState\n ? {\n offset: node.scrollState.offset,\n prevOffset: node.scrollState.prevOffset,\n offsetChanged: node.scrollState.offset !== node.scrollState.prevOffset,\n contentHeight: node.scrollState.contentHeight,\n viewportHeight: node.scrollState.viewportHeight,\n hiddenAbove: node.scrollState.hiddenAbove,\n hiddenBelow: node.scrollState.hiddenBelow,\n firstVisibleChild: node.scrollState.firstVisibleChild,\n lastVisibleChild: node.scrollState.lastVisibleChild,\n }\n : undefined,\n backgroundColor: props.backgroundColor,\n childCount: node.children.length,\n hidden: node.hidden ?? false,\n }\n}\n\n/**\n * Find scroll container ancestors for a node.\n */\nfunction findScrollAncestors(node: AgNode): AgNode[] {\n const result: AgNode[] = []\n let current = node.parent\n\n while (current) {\n if (current.scrollState) {\n result.push(current)\n }\n current = current.parent\n }\n\n return result\n}\n\n/**\n * Analyze why a node might have been incorrectly skipped by fast-path.\n */\nfunction analyzeFastPath(node: AgNode | null, scrollAncestors: AgNode[]): string[] {\n const analysis: string[] = []\n\n if (!node) {\n analysis.push(\"⚠ No node found at mismatch position - possible virtualization issue\")\n return analysis\n }\n\n const flags = node\n const allClean = !isAnyDirty(flags.dirtyBits, flags.dirtyEpoch)\n\n if (allClean) {\n analysis.push(\"⚠ ALL DIRTY FLAGS FALSE - fast-path likely skipped this node\")\n }\n\n // Check if node is in a scroll container\n const scrollParent = scrollAncestors[0]\n if (scrollParent?.scrollState) {\n const ss = scrollParent.scrollState\n const childIndex = node.parent ? node.parent.children.indexOf(node) : -1\n\n // Check if this node SHOULD be in visible range\n const inVisibleRange = childIndex >= ss.firstVisibleChild && childIndex <= ss.lastVisibleChild\n if (!inVisibleRange && childIndex >= 0) {\n analysis.push(\n `⚠ Node index ${childIndex} is OUTSIDE visible range [${ss.firstVisibleChild}..${ss.lastVisibleChild}]`,\n )\n analysis.push(\" → Node should have been skipped, but mismatch suggests it should render\")\n } else if (inVisibleRange) {\n analysis.push(\n `✓ Node index ${childIndex} is in visible range [${ss.firstVisibleChild}..${ss.lastVisibleChild}]`,\n )\n }\n\n // Check scroll offset\n if (ss.offset === ss.prevOffset) {\n analysis.push(\"✓ Scroll offset unchanged (fast-path enabled for children)\")\n } else {\n analysis.push(`⚠ Scroll offset CHANGED: ${ss.prevOffset} → ${ss.offset}`)\n }\n\n // Check if visible range might have changed\n if (ss.firstVisibleChild !== 0 || ss.lastVisibleChild !== scrollParent.children.length - 1) {\n analysis.push(\n ` Visible range is partial: [${ss.firstVisibleChild}..${ss.lastVisibleChild}] of ${scrollParent.children.length} children`,\n )\n analysis.push(\" → If visible range changed, newly visible children need rendering\")\n }\n }\n\n // Check prevLayout\n const layoutChanged = rectChanged(node.prevLayout, node.boxRect)\n if (!layoutChanged && node.prevLayout) {\n analysis.push(\"✓ Layout unchanged (prevLayout matches boxRect)\")\n } else if (!node.prevLayout) {\n analysis.push(\"⚠ prevLayout is NULL - node may never have been rendered before\")\n } else {\n analysis.push(\"⚠ Layout CHANGED but node still skipped - dirty flag not set?\")\n }\n\n // Check for sibling child position changes\n if (node.parent && node.parent.children.length > 1) {\n let siblingMoved = false\n for (const sibling of node.parent.children) {\n if (sibling !== node && sibling.boxRect && sibling.prevLayout) {\n if (\n sibling.boxRect.x !== sibling.prevLayout.x ||\n sibling.boxRect.y !== sibling.prevLayout.y\n ) {\n siblingMoved = true\n break\n }\n }\n }\n if (siblingMoved) {\n analysis.push(\"⚠ SIBLING POSITION CHANGED - parent should have detected this\")\n }\n }\n\n // Check hidden state\n if (node.hidden) {\n analysis.push(\"⚠ Node is HIDDEN (Suspense) - should not be rendered\")\n }\n\n return analysis\n}\n\n/**\n * Build full mismatch debug context.\n */\nexport function buildMismatchContext(\n root: AgNode,\n x: number,\n y: number,\n incrementalCell: Cell,\n freshCell: Cell,\n renderNum: number,\n): MismatchDebugContext {\n const innermost = findNodeAtPosition(root, x, y)\n const containing = findAllContainingNodes(root, x, y)\n const scrollAncestorNodes = innermost ? findScrollAncestors(innermost) : []\n\n return {\n position: { x, y },\n cells: {\n incremental: incrementalCell,\n fresh: freshCell,\n },\n renderNum,\n node: innermost ? getNodeDebugInfo(innermost) : null,\n scrollAncestors: scrollAncestorNodes.map(getNodeDebugInfo),\n containingNodes: containing.map(getNodeDebugInfo),\n fastPathAnalysis: analyzeFastPath(innermost, scrollAncestorNodes),\n }\n}\n\n/**\n * Format mismatch context as a human-readable string.\n *\n * @param ctx - The mismatch debug context (node attribution, dirty flags, scroll, fast-path)\n * @param renderPhaseStats - Optional render-phase instrumentation snapshot (auto-included by SILVERY_STRICT)\n */\nexport function formatMismatchContext(\n ctx: MismatchDebugContext,\n renderPhaseStats?: RenderPhaseStats,\n): string {\n const lines: string[] = []\n\n // Header\n lines.push(\n `SILVERY_STRICT: MISMATCH at (${ctx.position.x}, ${ctx.position.y}) on render #${ctx.renderNum}`,\n )\n lines.push(\"\")\n\n // Cell values\n const { incremental, fresh } = ctx.cells\n lines.push(\"CELL VALUES:\")\n lines.push(\n ` incremental: char=${JSON.stringify(incremental.char)} fg=${JSON.stringify(incremental.fg)} bg=${JSON.stringify(incremental.bg)} attrs=${JSON.stringify(incremental.attrs)}`,\n )\n lines.push(\n ` fresh: char=${JSON.stringify(fresh.char)} fg=${JSON.stringify(fresh.fg)} bg=${JSON.stringify(fresh.bg)} attrs=${JSON.stringify(fresh.attrs)}`,\n )\n lines.push(\"\")\n\n // Node attribution\n if (ctx.node) {\n lines.push(\"INNERMOST NODE:\")\n lines.push(` path: ${ctx.node.path}`)\n lines.push(` type: ${ctx.node.type}`)\n if (ctx.node.backgroundColor) {\n lines.push(` backgroundColor: ${ctx.node.backgroundColor}`)\n }\n lines.push(\"\")\n\n // Dirty flags\n const flags = ctx.node.dirtyFlags\n const activeFlags = Object.entries(flags)\n .filter(([, v]) => v)\n .map(([k]) => k)\n lines.push(\"DIRTY FLAGS:\")\n if (activeFlags.length > 0) {\n lines.push(` active: ${activeFlags.join(\", \")}`)\n } else {\n lines.push(\" active: (none - node was clean)\")\n }\n lines.push(\n ` all: contentDirty=${flags.contentDirty} stylePropsDirty=${flags.stylePropsDirty} subtreeDirty=${flags.subtreeDirty} childrenDirty=${flags.childrenDirty}`,\n )\n lines.push(\"\")\n\n // Layout info\n const { layout } = ctx.node\n lines.push(\"LAYOUT:\")\n if (layout.layoutChanged) {\n lines.push(\" ⚠ LAYOUT CHANGED:\")\n lines.push(` prevLayout: ${formatRect(layout.prevLayout)}`)\n lines.push(` boxRect: ${formatRect(layout.boxRect)}`)\n } else {\n lines.push(` boxRect: ${formatRect(layout.boxRect)}`)\n }\n lines.push(` scrollRect: ${formatRect(layout.scrollRect)}`)\n lines.push(\"\")\n\n // Scroll context\n if (ctx.node.scroll) {\n lines.push(\"SCROLL STATE (this node):\")\n formatScrollState(lines, ctx.node.scroll)\n lines.push(\"\")\n }\n } else {\n lines.push(\"INNERMOST NODE: (none found at this position)\")\n lines.push(\"\")\n }\n\n // Scroll ancestors\n if (ctx.scrollAncestors.length > 0) {\n lines.push(\"SCROLL ANCESTORS:\")\n for (const ancestor of ctx.scrollAncestors) {\n lines.push(` ${ancestor.path}:`)\n if (ancestor.scroll) {\n formatScrollState(lines, ancestor.scroll, \" \")\n }\n }\n lines.push(\"\")\n }\n\n // Containing nodes (for debugging layering issues)\n if (ctx.containingNodes.length > 1) {\n lines.push(\"ALL CONTAINING NODES (outermost to innermost):\")\n for (const node of ctx.containingNodes) {\n const flags = Object.entries(node.dirtyFlags)\n .filter(([, v]) => v)\n .map(([k]) => k.replace(\"Dirty\", \"\"))\n .join(\",\")\n const flagStr = flags ? ` [${flags}]` : \" [clean]\"\n const bgStr = node.backgroundColor ? ` bg=${node.backgroundColor}` : \"\"\n const childStr = node.childIndex !== null ? ` child[${node.childIndex}]` : \"\"\n lines.push(` ${node.path}${flagStr}${bgStr}${childStr}`)\n }\n lines.push(\"\")\n }\n\n // Fast-path analysis\n if (ctx.fastPathAnalysis.length > 0) {\n lines.push(\"FAST-PATH ANALYSIS:\")\n for (const line of ctx.fastPathAnalysis) {\n lines.push(` ${line}`)\n }\n lines.push(\"\")\n }\n\n // Render-phase instrumentation stats\n if (renderPhaseStats) {\n const s = renderPhaseStats\n lines.push(\"RENDER PHASE STATS:\")\n lines.push(\n ` nodesVisited: ${s.nodesVisited} nodesRendered: ${s.nodesRendered} nodesSkipped: ${s.nodesSkipped}`,\n )\n lines.push(` textNodes: ${s.textNodes} boxNodes: ${s.boxNodes} clearOps: ${s.clearOps}`)\n // Per-flag breakdown (why nodes weren't skipped)\n const flagLines: string[] = []\n if (s.noPrevBuffer) flagLines.push(`noPrevBuffer=${s.noPrevBuffer}`)\n if (s.flagContentDirty) flagLines.push(`contentDirty=${s.flagContentDirty}`)\n if (s.flagStylePropsDirty) flagLines.push(`stylePropsDirty=${s.flagStylePropsDirty}`)\n if (s.flagLayoutChanged) flagLines.push(`layoutChanged=${s.flagLayoutChanged}`)\n if (s.flagSubtreeDirty) flagLines.push(`subtreeDirty=${s.flagSubtreeDirty}`)\n if (s.flagChildrenDirty) flagLines.push(`childrenDirty=${s.flagChildrenDirty}`)\n if (s.flagChildPositionChanged)\n flagLines.push(`childPositionChanged=${s.flagChildPositionChanged}`)\n if (flagLines.length > 0) {\n lines.push(` render reasons: ${flagLines.join(\", \")}`)\n }\n // Scroll container diagnostics\n if (s.scrollContainerCount > 0) {\n lines.push(\n ` scrollContainers: ${s.scrollContainerCount} viewportCleared: ${s.scrollViewportCleared}`,\n )\n if (s.scrollClearReason) lines.push(` scrollClearReason: ${s.scrollClearReason}`)\n }\n // Normal container diagnostics\n if (s.normalChildrenRepaint > 0) {\n lines.push(` normalChildrenRepaint: ${s.normalChildrenRepaint}`)\n if (s.normalRepaintReason) lines.push(` normalRepaintReason: ${s.normalRepaintReason}`)\n }\n // Cascade diagnostics\n if (s.cascadeMinDepth < 999) {\n lines.push(` cascadeMinDepth: ${s.cascadeMinDepth}`)\n if (s.cascadeNodes) lines.push(` cascadeNodes: ${s.cascadeNodes}`)\n }\n lines.push(\"\")\n }\n\n return lines.join(\"\\n\")\n}\n\nfunction formatRect(rect: Rect | null): string {\n if (!rect) return \"(null)\"\n return `{x:${rect.x}, y:${rect.y}, w:${rect.width}, h:${rect.height}}`\n}\n\nfunction formatScrollState(\n lines: string[],\n scroll: NonNullable<NodeDebugInfo[\"scroll\"]>,\n indent = \" \",\n): void {\n if (scroll.offsetChanged) {\n lines.push(`${indent}⚠ SCROLL CHANGED: offset ${scroll.prevOffset} → ${scroll.offset}`)\n } else {\n lines.push(`${indent}offset: ${scroll.offset}`)\n }\n lines.push(\n `${indent}viewport: ${scroll.viewportHeight}/${scroll.contentHeight} (hidden: ▲${scroll.hiddenAbove} ▼${scroll.hiddenBelow})`,\n )\n lines.push(`${indent}visibleRange: [${scroll.firstVisibleChild}..${scroll.lastVisibleChild}]`)\n}\n","import type { TerminalBuffer } from \"./buffer\"\nimport {\n type CursorRect,\n computeContentRect,\n findActiveCursorRectWithProvenance,\n findActiveParkRect,\n} from \"@silvery/ag/layout-signals\"\nimport { findActiveCursorNode } from \"./caret-style\"\nimport { ANSI } from \"./output\"\nimport type {\n OutputCompositorCaret,\n OutputCursorBounds,\n OutputCursorTarget,\n} from \"./cursor-diagnostics\"\nimport { isStrictEnabled } from \"./strict-mode\"\nimport { IncrementalRenderMismatchError } from \"./errors\"\nimport type { AgNode, Rect } from \"@silvery/ag/types\"\n\n/**\n * Composited-caret shapes painted into the presentation buffer:\n * - `\"block\"` — filled inverse block. The focused caret (and the default).\n * - `\"underline\"` — bottom rule only (a declared `cursorOffset.shape:\n * \"underline\"`). Distinct from the focus state — an underline-shaped caret\n * stays underline whenever a caret is shown at all.\n *\n * An UNFOCUSED window composites NO caret. The product contract\n * (@km/code/v0.2/19702, reframed 2026-06-18) is hide-the-cursor-COMPLETELY, not\n * the earlier 20082 hollow box — a freshly-spawned, unfocused agent pane must\n * show nothing. So there is no `\"hollow\"` shape: an unfocused pane returns a\n * null `compositorCaret`. The hardware cursor is parked-and-hidden regardless of\n * focus, so an unfocused pane shows nothing at all.\n */\nexport interface CompositedCaret {\n x: number\n y: number\n visible: boolean\n style: \"block\" | \"underline\"\n}\n\nexport interface ManagedCaretFrame {\n buffer: TerminalBuffer\n compositorCaret: CompositedCaret | null\n}\n\nexport interface CursorOwnerBounds {\n promptBounds: Rect | null\n composerBounds: Rect | null\n}\n\n/**\n * Resolve the composited caret SHAPE from the cursor's declared shape. Only ever\n * called when a caret is shown at all — i.e. the window is FOCUSED (an unfocused\n * window composites no caret; see `composeManagedCaret`).\n *\n * - A declared `shape: \"underline\"` wins — an underline-shaped caret is\n * semantically distinct and stays underline.\n * - Otherwise the default filled `\"block\"`.\n */\nfunction caretStyle(cursor: CursorRect): CompositedCaret[\"style\"] {\n return cursor.shape === \"underline\" ? \"underline\" : \"block\"\n}\n\n/** Map a composited-caret style to the cell attributes that render it. */\nfunction caretCellAttrs(style: CompositedCaret[\"style\"]): {\n inverse?: boolean\n underline?: boolean\n} {\n switch (style) {\n case \"underline\":\n return { underline: true }\n case \"block\":\n default:\n return { inverse: true }\n }\n}\n\n/**\n * Paint a semantic caret into a presentation buffer for managed terminal frames.\n *\n * The source buffer stays untouched. Runtime diff state stores the presentation\n * buffer so moving/hiding the caret clears the previous frame's overlay like any\n * other cell update.\n *\n * @param windowFocused - whether the terminal WINDOW is focused. An UNFOCUSED\n * window composites NO caret (@km/code/v0.2/19702, reframed 2026-06-18: hide\n * the cursor COMPLETELY, replacing 20082's hollow box). Defaults to `true` —\n * a fail-safe so a single user with a focused terminal (or any caller that\n * hasn't wired standard `?1004` focus reporting) still sees the filled block,\n * never a vanished caret on unknown focus. For a composited caret the hardware\n * cursor is parked-and-hidden regardless of focus (see `managedCursorSuffix`),\n * so an unfocused pane shows nothing at all — and a dropped `?25l` cannot\n * strand a hardware cursor. The one exception is a `cursorActive` island\n * host-caret, shown as the real hardware cursor when the window is focused\n * (see `computeManagedFrame` / @hab 20398).\n */\nexport function composeManagedCaret(\n buffer: TerminalBuffer,\n cursor: CursorRect | null,\n windowFocused = true,\n): ManagedCaretFrame {\n // @km/code/v0.2/19702: an UNFOCUSED window hides the caret completely — no\n // composited overlay. (Default-true keeps a plain/unknown-focus terminal\n // visible; only a confirmed-unfocused window suppresses.)\n if (!windowFocused || !cursor?.visible || !buffer.inBounds(cursor.x, cursor.y)) {\n return { buffer, compositorCaret: null }\n }\n\n const next = buffer.clone()\n // Cloning intentionally drops dirty-row metadata. Mark all rows so the diff\n // compares this presentation buffer against the previous presentation buffer\n // and cannot skip unrelated app content that changed in the same frame.\n next.markAllRowsDirty()\n const style = caretStyle(cursor)\n next.mergeAttrsInRect(cursor.x, cursor.y, 1, 1, caretCellAttrs(style))\n return {\n buffer: next,\n compositorCaret: {\n x: cursor.x,\n y: cursor.y,\n visible: true,\n style,\n },\n }\n}\n\n/** SILVERY_STRICT slug for the managed-caret overlay-residue check. */\nexport const CARET_OVERLAY_STRICT_SLUG = \"cursor\"\nexport const CARET_OVERLAY_STRICT_MIN_TIER = 2\n\n/** Just the cell coordinates — all the overlay-clear logic needs. */\ntype CaretCell = { x: number; y: number } | null\n\nfunction sameCaretCell(a: CaretCell, b: CaretCell): boolean {\n if (a === null || b === null) return a === b\n return a.x === b.x && a.y === b.y\n}\n\n/**\n * Clear a prior frame's composited-caret overlay from a STATIC row.\n *\n * `composeManagedCaret`'s two paths are asymmetric about dirty rows: the PAINT\n * path calls `markAllRowsDirty()` (so a freshly-painted caret is always\n * diffed), but the NO-CARET early-return hands back the source buffer untouched.\n * When a caret that was painted at cell C in frame N is suppressed or moves to a\n * different cell in frame N+1, and C's ROW is otherwise static (clean in the\n * incremental buffer — the reconciler only marks rows whose content changed),\n * `diffBuffers` skips that row (`if (!next.isRowDirty(y)) continue`). The frame-N\n * `inverse` cell is then never compared against the cleared frame-N+1 buffer, so\n * the reverse-video block strands on screen with NO `?25h`/CUP — the\n * @km/code/v0.2/19702 cursor-above-composer signature.\n *\n * Fix: mark EXACTLY the prior caret's row dirty in the presentation buffer the\n * caller will diff+store, so `diffBuffers` re-scans it and emits the cleared\n * cell. We never call `markAllRowsDirty` (that would re-diff every static\n * transcript row each frame — the incremental-perf regression the dirty-row gate\n * exists to prevent). We touch one row, only when the caret actually left it.\n *\n * Buffer ownership: when the current frame composited NO caret (or composited\n * one on a DIFFERENT buffer), `presentationBuffer` may be the caller's untouched\n * source buffer (the documented `composeManagedCaret` no-clone contract). To\n * honour \"the source buffer stays untouched\", we clone before marking in that\n * case and return the clone. When the PAINT path already cloned, we mutate that\n * clone in place (it is already the caller's diff baseline).\n */\nfunction clearPriorCaretOverlay(\n presentationBuffer: TerminalBuffer,\n sourceBuffer: TerminalBuffer,\n currentCaret: CaretCell,\n prevCaret: CaretCell,\n): TerminalBuffer {\n // Nothing to clear: no prior caret, or the caret stayed on the same cell\n // (the PAINT path's markAllRowsDirty already covers an in-place repaint).\n if (prevCaret === null) return presentationBuffer\n if (sameCaretCell(prevCaret, currentCaret)) return presentationBuffer\n if (prevCaret.y < 0 || prevCaret.y >= presentationBuffer.height) return presentationBuffer\n\n // If the prior caret's row is already dirty (e.g. the PAINT path marked all\n // rows, or the app re-rendered that row), the diff will clear it for free.\n if (presentationBuffer.isRowDirty(prevCaret.y)) return presentationBuffer\n\n // The row is static (clean) — diffBuffers would skip it and strand the prior\n // inverse cell. Make the row dirty so the diff re-scans and clears it.\n const buf = presentationBuffer === sourceBuffer ? presentationBuffer.clone() : presentationBuffer\n buf.markRowDirty(prevCaret.y)\n return buf\n}\n\n/**\n * STRICT (tier 2, slug `cursor`) — managed-caret overlay-residue invariant.\n *\n * After a frame whose composited caret left cell C (suppressed or moved), C's\n * row in the presentation buffer the caller diffs MUST be dirty — otherwise\n * `diffBuffers` skips it and the prior `inverse` overlay strands (the\n * @km/code/v0.2/19702 signature). This is the overlay-layer analogue of\n * `strict-residue.ts` (which runs BELOW the caret overlay, in the render phase,\n * and so cannot see this). Fires only under `SILVERY_STRICT=cursor` / tier ≥ 2.\n */\nfunction verifyNoCaretOverlayResidue(\n presentationBuffer: TerminalBuffer,\n currentCaret: CaretCell,\n prevCaret: CaretCell,\n): void {\n if (!isStrictEnabled(CARET_OVERLAY_STRICT_SLUG, CARET_OVERLAY_STRICT_MIN_TIER)) return\n if (prevCaret === null) return\n if (sameCaretCell(prevCaret, currentCaret)) return\n if (prevCaret.y < 0 || prevCaret.y >= presentationBuffer.height) return\n if (presentationBuffer.isRowDirty(prevCaret.y)) return\n\n throw new IncrementalRenderMismatchError(\n `STRICT caret-overlay residue: prior composited caret at (${prevCaret.x},${prevCaret.y}) ` +\n `left the row, but row ${prevCaret.y} is CLEAN in the presentation buffer.\\n` +\n ` diffBuffers skips clean rows, so the prior frame's reverse-video (inverse)\\n` +\n ` overlay cell at (${prevCaret.x},${prevCaret.y}) will NOT be cleared — it strands\\n` +\n ` on screen with no ?25h/CUP. This is the @km/code/v0.2/19702\\n` +\n ` cursor-above-composer mechanism.\\n` +\n ` current caret: ${currentCaret ? `(${currentCaret.x},${currentCaret.y})` : \"none (suppressed)\"}\\n` +\n ` Fix: clearPriorCaretOverlay must mark the prior caret's row dirty.\\n` +\n ` Slug: SILVERY_STRICT=${CARET_OVERLAY_STRICT_SLUG} (tier ${CARET_OVERLAY_STRICT_MIN_TIER}+).`,\n )\n}\n\n/**\n * STRICT (tier 2, slug `cursor`) — no-fallback park invariant.\n *\n * When an editable declares a `parkOffset` (resolved to `parkRect`) and no\n * visible caret owns the frame, the managed hardware cursor MUST park on that\n * declared cell — never `home(0,0)` or a box-origin fallback. This pins that the\n * @km/code/v0.2/19702 fix stays wired: a regression that re-introduces a\n * fallback overriding the declared park target throws HERE instead of silently\n * stranding the cursor above the prompt (the recurring #undead signature, where\n * a dropped `?25l` surfaces the mis-parked cursor). The undeclared-editable case\n * (an editable that declares NO park) has no generic runtime signal — it is\n * pinned by the composer park-target contract + the parkOffset mutation test.\n * Fires only under `SILVERY_STRICT=cursor` / tier ≥ 2.\n *\n * Exported for direct mutation-proof unit testing (the fault path can't arise\n * from the correct `managedCursorSuffix`, so the test simulates a fallback park).\n */\nexport function verifyParkHonorsDeclaredTarget(\n cursor: CursorRect | null,\n parkRect: Rect | null,\n parkTarget: { x: number; y: number } | null,\n): void {\n if (!isStrictEnabled(CARET_OVERLAY_STRICT_SLUG, CARET_OVERLAY_STRICT_MIN_TIER)) return\n // Only meaningful when no caret owns the frame and a park target was declared.\n if (cursor !== null || parkRect === null) return\n if (parkTarget !== null && parkTarget.x === parkRect.x && parkTarget.y === parkRect.y) return\n\n throw new IncrementalRenderMismatchError(\n `STRICT no-fallback park: parkOffset declared at (${parkRect.x},${parkRect.y}) and no caret ` +\n `owns the frame, but the hardware cursor parked at ` +\n `${parkTarget ? `(${parkTarget.x},${parkTarget.y})` : \"none\"} instead.\\n` +\n ` A managed frame MUST park on the editable's declared input cell — never a\\n` +\n ` box-origin / home(0,0) fallback. A dropped ?25l would then strand the cursor off\\n` +\n ` the prompt — the @km/code/v0.2/19702 cursor-above-composer mechanism.\\n` +\n ` Fix: managedCursorSuffix must honor findActiveParkRect's result.\\n` +\n ` Slug: SILVERY_STRICT=${CARET_OVERLAY_STRICT_SLUG} (tier ${CARET_OVERLAY_STRICT_MIN_TIER}+).`,\n )\n}\n\nexport function cursorOwnerBounds(\n activeNode: AgNode | null,\n cursor: CursorRect | null,\n): CursorOwnerBounds {\n return {\n // Silvery core does not know an app's prompt string. The semantic cursor\n // cell is the targetable prompt/caret locus available at this layer.\n promptBounds: cursor ? { x: cursor.x, y: cursor.y, width: 1, height: 1 } : null,\n composerBounds: activeNode ? computeContentRect(activeNode) : null,\n }\n}\n\nexport interface ManagedCursorControls {\n /** ANSI suffix: park the hardware cursor at a safe cell, then hide it. */\n suffix: string\n /** Where the hardware cursor was parked (0-indexed), or null when no park. */\n parkTarget: { x: number; y: number } | null\n}\n\n/**\n * Compute the trailing cursor-control bytes for a managed fullscreen frame.\n *\n * The visible caret is painted into the presentation buffer by\n * `composeManagedCaret`, so the hardware cursor's only job is to stay OUT of\n * the way: every managed frame moves it to a deterministic safe cell and hides\n * it. Parking-before-hide is load-bearing because a multiplexer/terminal can\n * drop or override `?25l` (cmux re-shows the focused pane's cursor; an\n * unfocused pane renders a hollow cursor). If we hide WITHOUT moving, the\n * hardware cursor stays wherever the diff's last content write landed — the\n * bottom-most painted row — and a dropped hide leaves a visible cursor\n * stranded in transcript/activity/chrome content (the recurring\n * @km/code/v0.2/19702 signature, e.g. a block on the `Codex Done …` status\n * row). Parking pins that worst case to a benign, predictable cell instead.\n *\n * Park priority:\n * 1. The active caret cell — when a caret exists, the hardware cursor and the\n * composited caret coincide, so a dropped hide just overlaps the caret.\n * 2. The editable's DECLARED input-cell park target (`parkOffset`, resolved by\n * `findActiveParkRect`) — present even when the editable is unfocused/idle,\n * so a dropped hide surfaces the cursor ON the prompt input cell.\n * 3. Home (0,0) — last resort, ONLY for a frame with no editable/park declarer\n * at all (no input locus to be \"above\"). Never a dynamic transcript/chrome\n * row.\n *\n * The earlier composer-ORIGIN fallback (the editable's box top/border row — one\n * cell ABOVE the `>` prompt) is DELETED: it was the @km/code/v0.2/19702\n * bug-masking fallback (a multiplexer dropping `?25l` surfaced the parked cursor\n * one row above the prompt). Per Fail-Loud / no-defensive-fallbacks, a present\n * editable now declares its input cell via `parkOffset` instead of relying on a\n * silent box-origin degrade.\n */\nexport function managedCursorSuffix(\n cursor: CursorRect | null,\n parkRect: Rect | null,\n opts?: { visible?: boolean },\n): ManagedCursorControls {\n const park =\n cursor !== null\n ? { x: cursor.x, y: cursor.y }\n : parkRect !== null\n ? { x: parkRect.x, y: parkRect.y }\n : { x: 0, y: 0 }\n // Default: park-then-HIDE (@km/code/v0.2/19702 anti-stranding). A `cursorActive`\n // island host-caret (@km/silvery/19426) instead parks-then-SHOWS — the focused\n // guest terminal's real cursor is the one hardware cursor we deliberately leave\n // visible. Parking first means a shown cursor lands exactly on the guest cell.\n const tail = opts?.visible === true ? ANSI.CURSOR_SHOW : ANSI.CURSOR_HIDE\n return {\n suffix: ANSI.moveCursor(park.x, park.y) + tail,\n parkTarget: park,\n }\n}\n\n/**\n * Transport-safe managed cursor delivery.\n *\n * The semantic suffix is still \"park then hide\" (`managedCursorSuffix` above).\n * Output paths append this idempotent SGR reset after it so the managed cursor\n * controls are not the literal trailing run of cursor-control bytes. That keeps\n * multiplexers that drop a trailing cursor-control tail from peeling the park\n * move away from the content frame that required it.\n */\nexport function protectManagedCursorSuffix(cursorSuffix: string): string {\n return cursorSuffix.length === 0 ? \"\" : cursorSuffix + ANSI.RESET\n}\n\n/**\n * The single source of truth for managed-frame cursor handling.\n *\n * Three render entry points (`scheduler.ts`, `runtime/create-runtime.ts`,\n * `runtime/create-app.tsx`) previously hand-duplicated the same four-step\n * derivation (find active cursor rect → find active node → owner bounds →\n * composite caret + park/hide suffix). The copies drifted: the\n * @km/code/v0.2/19702 fixes patched one copy at a time, so the live path always\n * lagged a fix. `computeManagedFrame` collapses all four steps into ONE\n * function — changing the policy is now a single edit, and a regression cannot\n * hide in an uncovered copy (`docs/lessons/no-parallel-derivation.md`).\n *\n * **Policy (the @km/code/v0.2/19702 fix):** paint the visible inverse caret into\n * the presentation buffer ONLY when a FOCUSED editable owns the active cursor.\n * During an active turn nothing is focused, so a non-focused fallback declarer\n * (the unfocused composer, or a transcript editable still reporting\n * `cursorOffset.visible`) gets NO composited caret — eliminating the stranded\n * reverse-video block several rows above the prompt. The hardware cursor is\n * still parked at the editable's bounds (then hidden), so a dropped/overridden\n * `?25l` cannot strand a hollow hardware cursor in transcript/chrome either.\n *\n * @param sourceBuffer - the freshly-rendered content buffer (left untouched).\n * @param root - the AgNode tree (the `buffer.nodes` / `this.root` the caller holds).\n * @param mode - only `\"fullscreen\"` paints a managed caret; `\"inline\"` positions\n * the cursor inside the diff output and returns an empty managed frame.\n * @param opts.legacyCursor - the resolved legacy/store cursor\n * (`useCursor()` / Ink-compat `setCursorPosition()`), used ONLY when no node\n * in the tree declares a layout-output `cursorOffset`. This imperative cursor\n * is an explicit \"show a caret here\" request with no focus node, so it is\n * composited unconditionally (it is NOT focus-gated — that gate exists to\n * suppress passive declarative fallbacks, not explicit imperative cursors).\n * The scheduler passes this; `createRuntime`/`createApp` do not (they have no\n * store-cursor path).\n * @param opts.prevCaret - the `compositorCaret` THIS function returned for the\n * PREVIOUS frame (the caller stores it across frames). When the caret leaves a\n * cell whose row is otherwise static, the prior cell's row is made dirty in the\n * returned `presentationBuffer` so `diffBuffers` clears the stale `inverse`\n * overlay (the @km/code/v0.2/19702 fix — see `clearPriorCaretOverlay`). All\n * three render entry points pass this; omitting it disables the overlay-clear\n * (correct only for single-frame callers with no prev buffer).\n * @param opts.windowFocused - whether the terminal WINDOW is focused\n * (@km/code/v0.2/19702, reframed). `true`/omitted → filled inverse block;\n * `false` → NO caret composited (hidden completely — the pane shows nothing).\n * The hardware cursor is parked-and-hidden in BOTH states. All three render\n * entry points read their shared window-focus signal (standard DEC `?1004`\n * focus reporting) and pass it here; omitting it is the fail-safe focused\n * default (a single user with a focused terminal that never emits focusIn must\n * still see the caret).\n * @returns everything every call site needs: the presentation buffer to diff +\n * store, the cursor-control suffix, and the full diagnostic payload. The\n * caller stores `compositorCaret` and passes it back as `prevCaret` next frame.\n */\nexport interface ManagedFrame {\n /**\n * The buffer the terminal actually shows this frame. Equals `sourceBuffer`\n * when no caret is composited (same reference — callers can `===`-compare to\n * detect the no-op case). Callers MUST store THIS as their diff baseline so\n * the next frame clears a moved/removed caret.\n */\n presentationBuffer: TerminalBuffer\n /** Trailing cursor-control bytes: park the hardware cursor, then hide it. */\n cursorSuffix: string\n /** The composited caret painted into `presentationBuffer`, or null. */\n compositorCaret: OutputCompositorCaret | null\n /** Semantic cursor target (where a hardware cursor WOULD go), or null. */\n cursorTarget: OutputCursorTarget | null\n /** What the terminal cursor should be after the frame (always hidden), or null. */\n expectedTerminal: OutputCursorTarget | null\n /** The prompt/caret single-cell locus, for diagnostics. */\n promptBounds: OutputCursorBounds | null\n /** The editable content rect that owns the frame, for diagnostics. */\n composerBounds: OutputCursorBounds | null\n}\n\nexport function computeManagedFrame(\n sourceBuffer: TerminalBuffer,\n root: AgNode,\n mode: \"fullscreen\" | \"inline\",\n opts?: {\n legacyCursor?: CursorRect | null\n prevCaret?: OutputCompositorCaret | null\n windowFocused?: boolean\n },\n): ManagedFrame {\n // Inline mode never paints a managed caret — the diff output positions the\n // cursor itself (see output-phase.ts inlineCursorSuffix).\n if (mode !== \"fullscreen\") {\n return {\n presentationBuffer: sourceBuffer,\n cursorSuffix: \"\",\n compositorCaret: null,\n cursorTarget: null,\n expectedTerminal: null,\n promptBounds: null,\n composerBounds: null,\n }\n }\n\n // ONE tree walk resolves the active cursor rect AND its provenance. The\n // suppression decision below reads that provenance — it does NOT re-derive\n // focus from a second, divergent walk. (The earlier focus-gate used\n // `findActiveCursorNode`, which matches only `props.cursorOffset` and is blind\n // to `cursorActive` islands + the clip stack, so it suppressed island\n // host-carets — an inner no-parallel-derivation bug, @km/silvery/19426.)\n const active = findActiveCursorRectWithProvenance(root)\n const treeCursor = active?.rect ?? null\n\n // Cursor resolution priority (mirrors the scheduler's resolveActiveCursor):\n // 1. Layout-output cursor declared in the tree (Box cursorOffset OR island).\n // 2. Legacy/store cursor (useCursor() / Ink-compat) — only when the tree\n // declares none.\n const legacyCursor = treeCursor ? null : (opts?.legacyCursor ?? null)\n const cursor = treeCursor ?? legacyCursor\n\n // Park bounds still come from the node walk — `cursorOwnerBounds` needs the\n // editable's content rect. This is a DIAGNOSTIC/PARK input only; it never\n // gates the visible caret (that decision is provenance-driven below), so its\n // island-blindness is harmless here.\n const activeNode = findActiveCursorNode(root)\n const bounds = cursorOwnerBounds(activeNode, cursor)\n\n // The @km/code/v0.2/19702 fix, now provenance-aware: composite the VISIBLE\n // caret for everything EXCEPT a non-focused declarative fallback. Suppressing\n // that one source removes the stranded inverse block several rows above the\n // prompt (an active-turn composer / passive transcript editable still\n // reporting `cursorOffset.visible`). Focused-declarative carets, `cursorActive`\n // island host-carets (@km/silvery/19426), and explicit legacy/imperative\n // cursors (useCursor / Ink-compat — no focus node) all still composite. The\n // hardware cursor is parked at the editable's bounds below regardless, so a\n // dropped/overridden hide lands on a benign cell, never a dynamic\n // transcript/chrome row.\n const suppressDeclarativeFallback = active?.provenance === \"declarative-fallback\"\n // @km/code/v0.2/20082: the window-focus state selects the caret SHAPE (filled\n // block when focused, hidden when unfocused). Default focused — the fail-safe so\n // unknown focus never yields a hidden caret.\n const windowFocused = opts?.windowFocused ?? true\n\n // @km/silvery/19426 / @hab/.../20398 — island host-caret. A `cursorActive`\n // island is a REAL guest terminal (a shell PTY, an embedded app) whose guest\n // cursor must render as the host HARDWARE caret: a real, VISIBLE cursor at the\n // guest cell (like the focused pane of a terminal multiplexer). The 19702\n // managed model — composite an inverse block + park-and-HIDE the hardware cursor\n // (`?25l`) — is correct for silvery's OWN declarative editables, whose \"caret\" is\n // painted content with no real hardware cursor to show. Lumping islands into it\n // published the focused shell's guest cursor as a HIDDEN hardware cursor — the\n // \"focused shell has no cursor\" recurrence (20398). So an island host-caret is\n // shown as the hardware cursor instead, gated on window focus (an unfocused\n // window shows nothing). The island block in `findActiveCursorRect` already\n // gates on the guest's own `cursorVisible`, so an island provenance here means\n // the guest wants its cursor shown.\n const islandHardwareCursor =\n active?.provenance === \"island\" && windowFocused && treeCursor?.visible === true\n\n // Composite the VISIBLE inverse caret for everything EXCEPT (a) a non-focused\n // declarative fallback (@km/code/v0.2/19702) and (b) an island host-caret, which\n // is shown as a real hardware cursor below — compositing an inverse block on top\n // of it would double the caret.\n const compositeCursor =\n legacyCursor !== null\n ? legacyCursor\n : islandHardwareCursor || suppressDeclarativeFallback\n ? null\n : treeCursor\n const managed = composeManagedCaret(sourceBuffer, compositeCursor, windowFocused)\n\n // Clear a prior frame's composited-caret overlay from a static row. When the\n // caret left cell C (suppressed or moved) and C's row is clean in this\n // incremental buffer, diffBuffers would skip it and strand the frame-N inverse\n // cell — the @km/code/v0.2/19702 mechanism. Mark exactly C's row dirty (never\n // all rows — that's the incremental-perf regression the dirty-row gate\n // prevents). ONE place; all three render entry points thread `prevCaret`.\n const prevCaret = opts?.prevCaret ?? null\n const presentationBuffer = clearPriorCaretOverlay(\n managed.buffer,\n sourceBuffer,\n managed.compositorCaret,\n prevCaret,\n )\n // STRICT (tier 2, slug `cursor`): the overlay-residue invariant. Verify on the\n // FINAL presentation buffer — after the clear — so a green check proves the\n // stale overlay will actually be diffed away.\n verifyNoCaretOverlayResidue(presentationBuffer, managed.compositorCaret, prevCaret)\n\n // Hardware-cursor park target: caret cell when a caret exists, else the\n // editable's DECLARED input-cell park (`parkOffset`, present even unfocused/\n // idle), else home for an editable-less frame. Parking is a SAFETY net for a\n // dropped `?25l`, so it runs even when the visible caret is suppressed. The\n // park is resolved NON-focus-gated so an unfocused/idle composer still parks\n // on its prompt cell (@km/code/v0.2/19702 — replaces the box-origin fallback\n // that stranded the cursor one row above the prompt).\n const parkRect = findActiveParkRect(root)\n // Island host-caret → park at the guest cell and leave the hardware cursor\n // VISIBLE (`?25h`): the focused guest terminal's real cursor is the one hardware\n // cursor we deliberately show. Every other managed frame parks-then-HIDES\n // (`?25l`) — the 19702 anti-stranding safety net.\n const managedCursor = managedCursorSuffix(cursor, parkRect, { visible: islandHardwareCursor })\n // STRICT (tier 2, slug `cursor`): the no-fallback park invariant — a declared\n // parkOffset MUST be honored, never overridden by a box-origin/home fallback.\n verifyParkHonorsDeclaredTarget(cursor, parkRect, managedCursor.parkTarget)\n\n let cursorTarget: OutputCursorTarget | null = null\n let expectedTerminal: OutputCursorTarget | null = null\n if (cursor) {\n cursorTarget = { x: cursor.x, y: cursor.y, visible: cursor.visible, shape: cursor.shape }\n // The island host-caret is the one managed frame that leaves the hardware\n // cursor VISIBLE; every other managed frame hides it.\n expectedTerminal = { ...cursorTarget, visible: islandHardwareCursor }\n } else if (managedCursor.parkTarget) {\n expectedTerminal = { ...managedCursor.parkTarget, visible: false }\n }\n\n return {\n presentationBuffer,\n cursorSuffix: managedCursor.suffix,\n compositorCaret: managed.compositorCaret,\n cursorTarget,\n expectedTerminal,\n promptBounds: bounds.promptBounds,\n composerBounds: bounds.composerBounds,\n }\n}\n","/**\n * render-trace.ts — Render-boundary instrumentation for the Visual Eyes epic.\n *\n * Phase 4 of the Visual Eyes epic. Each silvery render-pass becomes an\n * observable `RENDER_DISPATCHED` event annotated with WHY it rendered:\n *\n * { ts, reason, dirtyRegions, signalDelta, fiberHash }\n *\n * A frame-trace consumer (e.g. termless's `createFrameTracer`) joins these\n * events onto its numbered frames by timestamp, so a trace frame's metadata\n * answers \"what made this render happen\" instead of just \"the buffer changed\".\n *\n * ## Design — additive instrumentation only\n *\n * This module emits an event AFTER the existing reconciler-flush + render\n * pipeline boundary in `renderer.ts`'s `doRender()`. It changes NO rendering\n * behavior, NO dirty-flag logic, NO pipeline ordering. It only reads state\n * the renderer already computed and writes it to an opt-in sink.\n *\n * ## Zero-cost when disabled\n *\n * The whole module is gated by the `SILVERY_TRACE_FRAMES` env var. When\n * unset, `isRenderTraceEnabled()` is `false`, `emitRenderDispatched()`\n * returns immediately, and the renderer's call site is a single boolean\n * check — production rendering pays nothing.\n *\n * ## Channel choice — sidecar JSONL + in-process bus\n *\n * `SILVERY_TRACE_FRAMES` names a directory (the same dir a termless\n * frame-trace writes its `index.jsonl` into). silvery appends render\n * events to `<dir>/render-events.jsonl`, one JSON object per line. termless\n * reads + joins on `ts`. This is the simplest robust channel for\n * `bun km view <vault>` — silvery and termless run in one process but are\n * separate packages with no shared module; a filesystem sidecar needs no\n * cross-package wiring and survives process boundaries too. Terminal-output\n * events are written to `<dir>/render-output-events.jsonl` with bytes,\n * changed-cell counts, dirty-row counts, and output-phase reason.\n *\n * A process-global in-memory ring buffer (`globalThis.__silvery_render_events`)\n * is also maintained so a same-process consumer can read events without\n * touching the filesystem.\n */\n\nimport { appendFileSync, existsSync, mkdirSync } from \"node:fs\"\nimport { join } from \"node:path\"\nimport { createLogger } from \"loggily\"\nimport type { OutputPhaseDiagnostics } from \"../pipeline/output-phase\"\n\nconst renderLog = createLogger(\"silvery:render\")\nconst outputLog = createLogger(\"silvery:render:output\")\n\n/**\n * A render-boundary event. Emitted once per `doRender()` flush when render\n * tracing is enabled.\n */\nexport interface RenderDispatchedEvent {\n /** Event type discriminator. */\n type: \"RENDER_DISPATCHED\"\n /** Wall-clock ms epoch — the join key for frame-trace consumers. */\n ts: number\n /** Monotonic render counter from the renderer (1-based). */\n renderCount: number\n /**\n * Why this render ran. Human-readable, derived from the dirty bits that\n * were set on the root node + dimension changes. E.g.\n * `\"content,subtree\"`, `\"dims-changed\"`, `\"initial\"`.\n */\n reason: string\n /**\n * Dirty regions of the produced buffer — contiguous row ranges that the\n * render pipeline marked dirty. `[]` when nothing was dirty (skip render).\n */\n dirtyRegions: { row: number; height: number }[]\n /**\n * What the render pipeline did this pass — a compact \"signal delta\":\n * how many nodes were visited / rendered / skipped. Sourced from the\n * render-phase stats the pipeline already computes. This is the\n * cheapest faithful proxy for \"what changed since the last flush\" —\n * the render phase only re-renders nodes whose layout/content signals\n * actually changed.\n */\n signalDelta: {\n nodesVisited: number\n nodesRendered: number\n nodesSkipped: number\n incremental: boolean\n }\n /**\n * A cheap structural hash of the fiber tree as committed this pass:\n * `<rootNodeCount>:<rootDirtyEpoch>`. Two renders with the same fiberHash\n * committed the same tree shape at the same epoch. Not a cryptographic\n * hash — a fast change-detector.\n */\n fiberHash: string\n}\n\n/**\n * Output-phase diagnostics attached to an emitted terminal frame. This is\n * deliberately a compact clone of the bytes_out monitor's frame summary:\n * render traces need the same changed-cell explanation without waiting for\n * bytes_out thresholds to trip.\n */\nexport type RenderOutputFrameDiagnostics = Partial<OutputPhaseDiagnostics> & {\n /**\n * Where these bytes came from. Omitted for the ordinary render-frame path\n * to preserve existing trace shape; terminal side-channel writes set this.\n */\n source?: \"terminal-artifact\" | \"post-paint-write\" | \"terminal-protocol\"\n /** Protocol/component owner for terminal artifacts, e.g. image:kitty:transmit. */\n owner?: string\n /** Flush phase for terminal artifacts. */\n phase?: \"setup\" | \"runtime\" | \"cleanup\" | \"pre-paint\" | \"post-paint\"\n /** Artifact discriminator from the runtime queue. */\n artifactKind?: string\n /** UTF-16 string length written before terminal-output wrapping. */\n outputChars?: number\n /** Extra hardware-cursor suffix length appended outside outputPhase(). */\n cursorChars?: number\n /** True when the frame was wrapped in DEC synchronized-output markers. */\n syncWrapped?: boolean\n}\n\n/**\n * Output-frame event. Emitted once per terminal write when render tracing is\n * enabled. Stored in a dedicated sidecar so existing `render-events.jsonl`\n * consumers keep seeing only RENDER_DISPATCHED events.\n */\nexport interface RenderOutputFrameEvent {\n /** Event type discriminator. */\n type: \"RENDER_OUTPUT\"\n /** Wall-clock ms epoch — the join key for frame-trace consumers. */\n ts: number\n /**\n * Render counter from the current renderer/scheduler at write time. In\n * createApp this counter is local to the current event batch; use\n * outputFrame for monotonic ordering.\n */\n renderCount: number\n /** Monotonic output-write counter, 1-based for this process/app handle. */\n outputFrame: number\n /** Bytes actually written for this frame. */\n bytes: number\n /** Output-phase reason, dimensions, changed-cell counts, and write shape. */\n diagnostics: RenderOutputFrameDiagnostics\n}\n\n/**\n * Source values the renderer hands to `emitRenderDispatched`. The renderer\n * already has all of these in scope at the flush boundary; this module just\n * shapes them into the event.\n */\nexport interface RenderTraceInput {\n renderCount: number\n /** Dirty-bit names captured BEFORE the pipeline consumed the flags. */\n dirtyReasons: string[]\n /** True when this render ran because terminal dims changed. */\n dimsChanged: boolean\n /** Height of the produced buffer (rows). */\n bufferHeight: number\n /** Per-row dirty predicate over the produced buffer. */\n isRowDirty: (y: number) => boolean\n /** Render-phase signal delta (visited/rendered/skipped). */\n signalDelta: RenderDispatchedEvent[\"signalDelta\"]\n /** Root node count for the fiber hash. */\n rootNodeCount: number\n /** Root dirty epoch for the fiber hash. */\n rootDirtyEpoch: number\n}\n\n// ── Env gate ────────────────────────────────────────────────────────────────\n//\n// `SILVERY_TRACE_FRAMES` names the trace directory. `DEBUG=silvery:render*`\n// enables the same in-process/loggily trace without a sidecar. Both gates\n// are read lazily so the trace surface is testable and opt-in.\n\nlet sidecarFile: string | null = null\nlet sidecarReady = false\nlet outputSidecarFile: string | null = null\nlet outputSidecarReady = false\n\n/** The configured trace directory, or `null` when `SILVERY_TRACE_FRAMES` is unset. */\nexport function renderTraceDir(): string | null {\n return process.env.SILVERY_TRACE_FRAMES?.trim() || null\n}\n\n/** True when sidecar or loggily render tracing is active. */\nexport function isRenderTraceEnabled(): boolean {\n return renderTraceDir() !== null || renderLog.debug !== undefined || outputLog.debug !== undefined\n}\n\n// ── In-process event bus ────────────────────────────────────────────────────\n//\n// A bounded ring of recent events on globalThis so a same-process consumer\n// (km-tui wiring both silvery + termless) can read events without filesystem\n// round-trips. Bounded so a long-running app can't leak.\n\nconst MAX_BUFFERED_EVENTS = 4096\n\ninterface RenderEventBus {\n events: RenderDispatchedEvent[]\n outputEvents: RenderOutputFrameEvent[]\n}\n\nfunction bus(): RenderEventBus {\n const g = globalThis as { __silvery_render_events?: RenderEventBus }\n if (!g.__silvery_render_events) {\n g.__silvery_render_events = { events: [], outputEvents: [] }\n } else if (!g.__silvery_render_events.outputEvents) {\n g.__silvery_render_events.outputEvents = []\n }\n return g.__silvery_render_events\n}\n\n/**\n * Recent render events from the in-process bus. Consumers running in the\n * same process as silvery (e.g. a km-tui-wired termless tracer) can poll\n * this instead of reading the sidecar file.\n */\nexport function recentRenderEvents(): readonly RenderDispatchedEvent[] {\n return bus().events\n}\n\n/**\n * Recent output-frame events from the in-process bus. Same-process consumers\n * can join these with `recentRenderEvents()` by timestamp and frame order.\n */\nexport function recentRenderOutputEvents(): readonly RenderOutputFrameEvent[] {\n return bus().outputEvents\n}\n\n// ── Emit ────────────────────────────────────────────────────────────────────\n\n/** Convert the buffer's per-row dirty predicate into contiguous ranges. */\nfunction dirtyRegionsFromBuffer(\n height: number,\n isRowDirty: (y: number) => boolean,\n): { row: number; height: number }[] {\n const regions: { row: number; height: number }[] = []\n let runStart = -1\n for (let y = 0; y < height; y++) {\n if (isRowDirty(y)) {\n if (runStart === -1) runStart = y\n } else if (runStart !== -1) {\n regions.push({ row: runStart, height: y - runStart })\n runStart = -1\n }\n }\n if (runStart !== -1) {\n regions.push({ row: runStart, height: height - runStart })\n }\n return regions\n}\n\n/**\n * Emit a `RENDER_DISPATCHED` event for the just-completed render pass.\n *\n * No-op when `SILVERY_TRACE_FRAMES` is unset. Called from `renderer.ts`'s\n * `doRender()` at the flush boundary — AFTER reconciliation + the render\n * pipeline, just before the buffer is returned. Purely additive: reads\n * already-computed state, writes to the opt-in sink, never throws into the\n * render path.\n */\nexport function emitRenderDispatched(input: RenderTraceInput): void {\n const traceDir = renderTraceDir()\n const logEnabled = renderLog.debug !== undefined\n if (traceDir === null && !logEnabled) return\n try {\n const reasonParts: string[] = [...input.dirtyReasons]\n if (input.dimsChanged) reasonParts.push(\"dims-changed\")\n const event: RenderDispatchedEvent = {\n type: \"RENDER_DISPATCHED\",\n ts: Date.now(),\n renderCount: input.renderCount,\n reason: reasonParts.length > 0 ? reasonParts.join(\",\") : \"initial\",\n dirtyRegions: dirtyRegionsFromBuffer(input.bufferHeight, input.isRowDirty),\n signalDelta: input.signalDelta,\n fiberHash: `${input.rootNodeCount}:${input.rootDirtyEpoch}`,\n }\n\n // In-process bus (bounded ring).\n const b = bus()\n b.events.push(event)\n if (b.events.length > MAX_BUFFERED_EVENTS) {\n b.events.splice(0, b.events.length - MAX_BUFFERED_EVENTS)\n }\n\n renderLog.debug?.(\"render dispatched\", { ...event })\n\n // Sidecar JSONL — lazily created on first emit so a disabled trace dir\n // never gets touched.\n if (traceDir !== null && !sidecarReady) {\n if (!existsSync(traceDir)) mkdirSync(traceDir, { recursive: true })\n sidecarFile = join(traceDir, \"render-events.jsonl\")\n // Truncate any prior sidecar for this dir.\n appendFileSync(sidecarFile, \"\", { flag: \"w\" })\n sidecarReady = true\n }\n if (traceDir !== null && sidecarFile) {\n appendFileSync(sidecarFile, JSON.stringify(event) + \"\\n\")\n }\n } catch {\n // Tracing must never destabilise rendering. Swallow any sink error\n // (full disk, permission, race) — the in-process bus still has the\n // event if the push succeeded before the throw.\n }\n}\n\nexport interface RenderOutputFrameInput {\n renderCount: number\n outputFrame: number\n bytes: number\n diagnostics?: RenderOutputFrameDiagnostics | null\n}\n\n/**\n * Emit a `RENDER_OUTPUT` event for bytes actually written to the terminal.\n *\n * No-op when `SILVERY_TRACE_FRAMES` is unset. This is intentionally separate\n * from `emitRenderDispatched`: the render pipeline produces dirty regions,\n * while the output phase later decides which cells/bytes were emitted.\n */\nexport function emitRenderOutputFrame(input: RenderOutputFrameInput): void {\n const traceDir = renderTraceDir()\n const logEnabled = renderLog.debug !== undefined || outputLog.debug !== undefined\n if (traceDir === null && !logEnabled) return\n try {\n const event: RenderOutputFrameEvent = {\n type: \"RENDER_OUTPUT\",\n ts: Date.now(),\n renderCount: input.renderCount,\n outputFrame: input.outputFrame,\n bytes: input.bytes,\n diagnostics: input.diagnostics ? { ...input.diagnostics } : {},\n }\n\n const b = bus()\n b.outputEvents.push(event)\n if (b.outputEvents.length > MAX_BUFFERED_EVENTS) {\n b.outputEvents.splice(0, b.outputEvents.length - MAX_BUFFERED_EVENTS)\n }\n\n outputLog.debug?.(\"render output\", { ...event })\n\n if (traceDir !== null && !outputSidecarReady) {\n if (!existsSync(traceDir)) mkdirSync(traceDir, { recursive: true })\n outputSidecarFile = join(traceDir, \"render-output-events.jsonl\")\n appendFileSync(outputSidecarFile, \"\", { flag: \"w\" })\n outputSidecarReady = true\n }\n if (traceDir !== null && outputSidecarFile) {\n appendFileSync(outputSidecarFile, JSON.stringify(event) + \"\\n\")\n }\n } catch {\n // Tracing must never destabilise rendering. Swallow sink errors.\n }\n}\n\n/**\n * Test-only: reset module state (in-process bus + sidecar latch). Lets a\n * test exercise the enabled and disabled paths without a fresh process.\n */\nexport function __resetRenderTraceForTests(): void {\n bus().events.length = 0\n bus().outputEvents.length = 0\n sidecarReady = false\n sidecarFile = null\n outputSidecarReady = false\n outputSidecarFile = null\n}\n","/**\n * Silvery Render Scheduler\n *\n * Batches rapid state updates to prevent flicker and improve performance.\n * Uses queueMicrotask for coalescing multiple synchronous state changes\n * into a single render pass.\n *\n * Features:\n * - Microtask-based batching (coalesces synchronous updates)\n * - Frame batching to prevent flicker\n * - Resize handling with debounce\n * - Clean shutdown\n */\n\nimport { appendFileSync } from \"node:fs\"\nimport { type Logger, createLogger } from \"loggily\"\nimport { type BytesOutMonitor, createBytesOutMonitor } from \"./bytes-out-monitor\"\nimport { type FlickerMonitor, createFlickerMonitor } from \"./flicker-monitor\"\nimport { type MemMonitor, createMemMonitor } from \"./mem-monitor\"\nimport { isStrictEnabled } from \"./strict-mode\"\nimport { type TerminalBuffer, bufferToText, cellEquals } from \"./buffer\"\nimport { buildMismatchContext, formatMismatchContext } from \"@silvery/test/debug-mismatch\"\nimport {\n type ResolvedNonTTYMode as ResolvedMode,\n countLines,\n createOutputTransformer,\n resolveNonTTYMode,\n stripAnsi,\n} from \"./non-tty\"\nimport type { CursorAccessors } from \"@silvery/ag-react/hooks/useCursor\"\nimport {\n commitLayoutSnapshot,\n findActiveCursorRect,\n type CursorRect,\n} from \"@silvery/ag/layout-signals\"\nimport { createOsc52Backend } from \"./clipboard\"\nimport {\n recordOutputCursorDiagnostics,\n type OutputCursorBounds,\n type OutputCursorTarget,\n type PrevPresentation,\n} from \"./cursor-diagnostics\"\nimport { ANSI } from \"./output\"\nimport { computeManagedFrame, protectManagedCursorSuffix } from \"./managed-caret\"\nimport type { PipelineConfig } from \"./pipeline\"\nimport {\n markLayoutTreeDirty,\n FRESH_LAYOUT_STRICT_SLUG,\n FRESH_LAYOUT_STRICT_MIN_TIER,\n} from \"./pipeline\"\nimport {\n clearLastOutputPhaseDiagnostics,\n getLastOutputPhaseDiagnostics,\n outputPhase,\n} from \"./pipeline/output-phase\"\nimport { emitRenderOutputFrame, isRenderTraceEnabled } from \"./runtime/render-trace\"\nimport { createAg, createRenderPostState, type RenderPostState } from \"./ag\"\nimport { runWithMeasurer } from \"./unicode\"\nimport type { RenderPhaseStats } from \"./pipeline/types\"\nimport type { AgNode } from \"@silvery/ag/types\"\n\nconst log = createLogger(\"silvery:scheduler\")\n\n/**\n * Whether synchronized update mode (DEC 2026) is enabled.\n *\n * Disabled by default due to a Ghostty rendering bug where incremental\n * cursor-positioned updates inside a sync region cause progressive visual\n * corruption. Works correctly in Kitty. Full renders (bufferToAnsi) work\n * fine with sync — only incremental diff output (changesToAnsi) triggers it.\n *\n * Set SILVERY_SYNC_UPDATE=1 to force-enable (e.g., for testing in Kitty).\n * TODO: Re-enable by default once the Ghostty bug is fixed.\n * See: https://github.com/ghostty-org/ghostty/discussions/11002\n */\nconst SYNC_UPDATE_ENABLED =\n process.env.SILVERY_SYNC_UPDATE === \"1\" || process.env.SILVERY_SYNC_UPDATE === \"true\"\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n// Re-export from errors.ts (kept separate for React-free barrel imports)\nexport { IncrementalRenderMismatchError } from \"./errors\"\nimport { IncrementalRenderMismatchError } from \"./errors\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Non-TTY mode for rendering in non-interactive environments.\n */\nexport type NonTTYMode = \"auto\" | \"tty\" | \"line-by-line\" | \"static\" | \"plain\"\n\n/**\n * Resolved non-TTY mode after auto-detection.\n */\nexport type ResolvedNonTTYMode = Exclude<NonTTYMode, \"auto\">\n\nexport interface SchedulerOptions {\n /** stdout stream for writing output */\n stdout: NodeJS.WriteStream\n /**\n * Term size owner — single source of truth for cols/rows (and the only\n * reader that sees the coalesced resize post-window). When omitted the\n * scheduler falls back to reading `stdout.columns` / `stdout.rows`\n * directly; this keeps legacy embedders working but forfeits the coalesce\n * window.\n */\n size?: import(\"./runtime/devices/size\").Size\n /** Root Silvery node */\n root: AgNode\n /** Debug mode - logs render timing */\n debug?: boolean\n /** Minimum time between frames in ms (default: 16 for ~60fps) */\n minFrameTime?: number\n /** Render mode: fullscreen (absolute positioning) or inline (relative positioning) */\n mode?: \"fullscreen\" | \"inline\"\n /**\n * Non-TTY mode for non-interactive environments (default: 'auto')\n *\n * - 'auto': Detect based on environment\n * - 'tty': Force TTY mode\n * - 'line-by-line': Simple line output\n * - 'static': Only output final frame\n * - 'plain': Strip all ANSI codes\n */\n nonTTYMode?: NonTTYMode\n /** Slow frame warning threshold in ms (default: 50). Set to 0 to disable. */\n slowFrameThreshold?: number\n /** Pipeline configuration (caps-scoped measurer + output phase) */\n pipelineConfig?: PipelineConfig\n /**\n * Per-instance cursor accessors. When omitted, `getCursorState` falls\n * back to a `() => null` no-op (no hardware cursor) and no\n * subscribeCursor subscription is wired up. Callers that want cursor\n * visibility must construct accessors via `createCursorStore()` and\n * pass them here. (The legacy module-level global fallback was removed\n * in `km-silvery.delete-cursor-globals`.)\n */\n cursorAccessors?: CursorAccessors\n /**\n * Custom output writer. When provided, all render output is routed through\n * this function instead of stdout.write(). Used by the output guard to\n * ensure only silvery's render pipeline writes to stdout in alt screen mode.\n */\n writeOutput?: (data: string) => boolean\n /**\n * Read the terminal WINDOW focus state (@km/code/v0.2/19702). Called once per\n * fullscreen frame and threaded into `computeManagedFrame`: focused → filled\n * inverse block; unfocused → NO caret (hidden completely). Omitted → focused\n * (the fail-safe default).\n */\n windowFocused?: () => boolean\n}\n\nexport interface RenderStats {\n /** Number of renders executed */\n renderCount: number\n /** Number of renders skipped (batched) */\n skippedCount: number\n /** Last render duration in ms */\n lastRenderTime: number\n /** Average render time in ms */\n avgRenderTime: number\n}\n\n// ============================================================================\n// RenderScheduler Class\n// ============================================================================\n\n/**\n * Schedules and batches render operations.\n *\n * Usage:\n * ```ts\n * const scheduler = new RenderScheduler({\n * stdout: process.stdout,\n * root: rootNode,\n * });\n *\n * // Schedule renders (automatically batched)\n * scheduler.scheduleRender();\n * scheduler.scheduleRender(); // This won't cause duplicate render\n *\n * // Force immediate render\n * scheduler.forceRender();\n *\n * // Clean shutdown\n * scheduler.dispose();\n * ```\n */\nexport class RenderScheduler {\n private stdout: NodeJS.WriteStream\n private size?: import(\"./runtime/devices/size\").Size\n private root: AgNode\n private debugMode: boolean\n private minFrameTime: number\n private slowFrameThreshold: number\n private mode: \"fullscreen\" | \"inline\"\n private pipelineConfig?: PipelineConfig\n private getCursorState: () => import(\"@silvery/ag-react/hooks/useCursor\").CursorState | null\n /**\n * Read the terminal WINDOW focus state (@km/code/v0.2/19702). Threaded into\n * `computeManagedFrame` each frame: focused → filled block, unfocused → NO\n * caret (hidden). Defaults to `() => true` (the fail-safe focused default)\n * when no reader is supplied.\n */\n private getWindowFocused: () => boolean\n private nonTTYMode: ResolvedMode\n private outputTransformer: (content: string, prevLineCount: number) => string\n private writeOutput: (data: string) => boolean\n /**\n * Render-output throughput monitor — backs `SILVERY_STRICT=bytes_out`.\n * Constructed lazily, only when the slug is active, so the no-op path\n * has zero overhead. See `bytes-out-monitor.ts` +\n * `docs/lessons/cmux-pty-buffer-firehose.md`.\n */\n private bytesOutMonitor: BytesOutMonitor | null = null\n /**\n * Repaint-storm monitor — backs `SILVERY_STRICT=flicker`. Sibling of\n * `bytesOutMonitor`; scans the SAME terminal-output bytes for destructive\n * full-screen clears / scroll-region resets and PANICs when they repeat fast\n * enough to constitute flicker (the `@ag/code/20297-pane-flicker-on-resize`\n * class). Lazily constructed only when the slug is active. See\n * `flicker-monitor.ts`.\n */\n private flickerMonitor: FlickerMonitor | null = null\n /**\n * In-process heap-poll monitor — backs `SILVERY_STRICT=mem`. Sibling of\n * `bytesOutMonitor`; samples `process.memoryUsage()` on its own 30s\n * interval. See `mem-monitor.ts`.\n */\n private memMonitor: MemMonitor | null = null\n private log: Logger\n\n /** Previous buffer for diffing */\n private prevBuffer: TerminalBuffer | null = null\n\n /**\n * Previous presentation buffer for terminal output diffing, bundled with the\n * composited caret painted into it.\n *\n * The caret is threaded back into `computeManagedFrame` so a caret that moves\n * off / is suppressed on a static row gets that row marked dirty and the stale\n * `inverse` overlay cleared — the @km/code/v0.2/19702 overlay-residue fix.\n * Buffer and caret live in one struct so they cannot desync: every write sets\n * both in a single assignment, so the buffer baseline can never advance\n * without the caret it was composited with (see `PrevPresentation`).\n */\n private prevPresentation: PrevPresentation<TerminalBuffer> | null = null\n\n /**\n * Cross-frame post-state carrier (outline snapshots, etc.).\n *\n * Held at the scheduler level — not on `Ag` — because each `doRender()`\n * creates a fresh `Ag` per frame, so an Ag-internal `_postState` would be\n * empty every time and `clearPreviousOutlines` would never find prior\n * snapshots to restore. Mirrors the `prevBuffer` lifecycle: invalidated\n * together on resize / pause / clear so the snapshot coordinates always\n * match the buffer they reference.\n */\n private postState: RenderPostState = createRenderPostState()\n\n /** Line count of previous render (for non-TTY modes) */\n private prevLineCount = 0\n\n /** Monotonic terminal-output frame counter for render traces. */\n private outputFrameCount = 0\n\n /** Cached trace gate so disabled output frames do not read env per render. */\n private renderTraceEnabled = false\n\n /** Accumulated output for static mode */\n private staticOutput = \"\"\n\n /** Is a render currently scheduled? */\n private renderScheduled = false\n\n /** Last render timestamp */\n private lastRenderTime = 0\n\n /** Pending frame timeout (for frame rate limiting) */\n private frameTimeout: ReturnType<typeof setTimeout> | null = null\n\n /** Resize listener cleanup */\n private resizeCleanup: (() => void) | null = null\n\n /**\n * Cursor-state listener cleanup.\n *\n * The scheduler subscribes to cursor-state changes so a re-render is\n * triggered whenever `useCursor` (mounts / unmounts / moves) changes the\n * store. Without this subscription, `setCursorState` calls that happen\n * AFTER a render completes (e.g. on the second commit of a parent Box,\n * when `NodeContext` finally propagates a non-null AgNode) leave the\n * scheduler holding a stale (`null`) cursor state — the previous\n * render emitted `CURSOR_HIDE` and no further render is triggered to\n * emit `moveCursor + CURSOR_SHOW`. Result: the hardware cursor stays\n * parked at the prior frame's last buffer write. See bead\n * `km-silvercode.cursor-startup-position`.\n */\n private cursorCleanup: (() => void) | null = null\n\n /** Render statistics */\n private stats: RenderStats = {\n renderCount: 0,\n skippedCount: 0,\n lastRenderTime: 0,\n avgRenderTime: 0,\n }\n\n /** Is the scheduler disposed? */\n private disposed = false\n\n /** Is the scheduler paused? When paused, renders are deferred until resume. */\n private paused = false\n\n /** Was a render requested while paused? */\n private pendingWhilePaused = false\n\n /**\n * Lines written to stdout between renders (inline mode only).\n * When useScrollback or other code writes to stdout, those lines\n * displace the terminal cursor. This offset is consumed on the next render.\n */\n private scrollbackOffset = 0\n\n constructor(options: SchedulerOptions) {\n this.stdout = options.stdout\n this.size = options.size\n this.root = options.root\n this.debugMode = options.debug ?? false\n this.minFrameTime = options.minFrameTime ?? 16\n this.slowFrameThreshold = options.slowFrameThreshold ?? 50\n this.mode = options.mode ?? \"fullscreen\"\n this.pipelineConfig = options.pipelineConfig\n // No-op fallback for callers that don't pass `cursorAccessors`. The\n // legacy `globalGetCursorState` shim was deleted in\n // km-silvery.delete-cursor-globals; callers wanting cursor visibility\n // must wire `cursorAccessors` explicitly via createCursorStore().\n this.getCursorState = options.cursorAccessors?.getCursorState ?? (() => null)\n // @km/code/v0.2/20082 window-focus reader — default focused (fail-safe).\n this.getWindowFocused = options.windowFocused ?? (() => true)\n // Subscribe to cursor-state changes so the scheduler re-renders when\n // useCursor's store updates land between render frames. The\n // `subscribeCursor` accessor was previously exported but never wired up\n // — that gap is the root of km-silvercode.cursor-startup-position.\n if (options.cursorAccessors) {\n this.cursorCleanup = options.cursorAccessors.subscribeCursor(() => {\n // Schedule (not force) — coalesces with concurrent React commits.\n this.scheduleRender()\n })\n }\n this.writeOutput = options.writeOutput ?? ((data: string) => options.stdout.write(data))\n // bytes_out is a tier-1 strict slug (auto under SILVERY_STRICT=1).\n // Opt out per session with SILVERY_STRICT=1,!bytes_out.\n if (isStrictEnabled(\"bytes_out\", 1)) {\n this.bytesOutMonitor = createBytesOutMonitor()\n }\n // flicker is a tier-1 strict slug — repaint-storm sibling of bytes_out.\n // Opt out per session with SILVERY_STRICT=1,!flicker.\n if (isStrictEnabled(\"flicker\", 1)) {\n this.flickerMonitor = createFlickerMonitor()\n }\n // mem is a tier-1 strict slug — in-process heap-poll sibling of\n // bytes_out. Opt out per session with SILVERY_STRICT=1,!mem.\n if (isStrictEnabled(\"mem\", 1)) {\n this.memMonitor = createMemMonitor()\n }\n this.renderTraceEnabled = isRenderTraceEnabled()\n this.log = createLogger(\"silvery:scheduler\") as unknown as Logger\n\n // Resolve non-TTY mode based on environment\n this.nonTTYMode = resolveNonTTYMode({\n mode: options.nonTTYMode,\n stdout: this.stdout,\n })\n this.outputTransformer = createOutputTransformer(this.nonTTYMode)\n\n log.debug?.(`non-TTY mode resolved to: ${this.nonTTYMode}`)\n\n // Listen for terminal resize (only in TTY mode)\n if (this.nonTTYMode === \"tty\") {\n this.setupResizeListener()\n }\n }\n\n /**\n * Get the resolved non-TTY mode.\n */\n getNonTTYMode(): ResolvedMode {\n return this.nonTTYMode\n }\n\n // ==========================================================================\n // Public API\n // ==========================================================================\n\n /**\n * Schedule a render on the next microtask.\n *\n * Multiple calls within the same synchronous execution will be\n * coalesced into a single render.\n */\n scheduleRender(): void {\n if (this.disposed) return\n\n if (this.paused) {\n this.pendingWhilePaused = true\n return\n }\n\n if (this.renderScheduled) {\n this.stats.skippedCount++\n log.debug?.(`render skipped (batched), total: ${this.stats.skippedCount}`)\n return\n }\n\n this.renderScheduled = true\n log.debug?.(\"render scheduled\")\n\n // Use queueMicrotask for batching synchronous updates\n queueMicrotask(() => {\n this.renderScheduled = false\n\n if (this.disposed) return\n\n // Check frame rate limiting\n const now = Date.now()\n const timeSinceLastRender = now - this.lastRenderTime\n\n if (timeSinceLastRender < this.minFrameTime) {\n // Schedule for next frame\n log.debug?.(`frame limited, delay: ${this.minFrameTime - timeSinceLastRender}ms`)\n this.scheduleNextFrame(this.minFrameTime - timeSinceLastRender)\n } else {\n this.doRender()\n }\n })\n }\n\n /**\n * Force an immediate render, bypassing batching.\n */\n forceRender(): void {\n if (this.disposed) return\n\n if (this.paused) {\n this.pendingWhilePaused = true\n return\n }\n\n // Cancel any pending scheduled render\n this.renderScheduled = false\n if (this.frameTimeout) {\n clearTimeout(this.frameTimeout)\n this.frameTimeout = null\n }\n\n this.doRender()\n }\n\n /**\n * Get render statistics.\n */\n getStats(): RenderStats {\n return { ...this.stats }\n }\n\n /**\n * Report lines written to stdout between renders (inline mode only).\n * This adjusts cursor position tracking so the next render accounts\n * for the extra lines. Used by useScrollback to notify the scheduler\n * when it writes frozen items to stdout.\n */\n addScrollbackLines(lines: number): void {\n if (this.mode !== \"inline\" || lines <= 0) return\n this.scrollbackOffset += lines\n }\n\n /**\n * Copy text to the system clipboard via OSC 52.\n * Works across SSH sessions in terminals that support it.\n */\n copyToClipboard(text: string): void {\n if (this.disposed) return\n // Route through writeOutput so the output guard allows it through.\n // Uses createOsc52Backend (the canonical ClipboardBackend factory).\n const writable = { write: (data: string) => this.writeOutput(data) }\n createOsc52Backend(writable).write({ text })\n }\n\n /**\n * Pause rendering. While paused, scheduled and forced renders are deferred.\n * Input handling continues normally. Call resume() to unpause and force a\n * full redraw. Used for screen-switching (alt screen ↔ normal screen).\n */\n pause(): void {\n if (this.disposed || this.paused) return\n this.paused = true\n this.pendingWhilePaused = false\n log.debug?.(\"scheduler paused\")\n }\n\n /**\n * Resume rendering after pause. Resets the previous buffer so the next\n * render outputs everything (full redraw), then forces an immediate render.\n */\n resume(): void {\n if (this.disposed || !this.paused) return\n this.paused = false\n log.debug?.(\"scheduler resumed\")\n\n // Reset buffer for full redraw (alt screen was switched)\n this.prevBuffer = null\n this.prevPresentation = null // no prev buffer ⇒ no stale overlay to clear\n // Outline snapshots reference cells in the discarded buffer — reset together.\n this.postState = createRenderPostState()\n\n // If anything was deferred, render now\n if (this.pendingWhilePaused) {\n this.pendingWhilePaused = false\n this.doRender()\n }\n }\n\n /**\n * Whether the scheduler is currently paused.\n */\n isPaused(): boolean {\n return this.paused\n }\n\n /**\n * Clear the terminal and reset buffer.\n */\n clear(): void {\n if (this.disposed) return\n\n // Clear screen and keep cursor hidden\n this.writeOutput(\"\\x1b[2J\\x1b[H\\x1b[?25l\")\n\n // Reset buffer so next render outputs everything\n this.prevBuffer = null\n this.prevPresentation = null // no prev buffer ⇒ no stale overlay to clear\n // Outline snapshots reference cells in the discarded buffer — reset together.\n this.postState = createRenderPostState()\n }\n\n /**\n * Dispose the scheduler and clean up resources.\n */\n [Symbol.dispose](): void {\n this.dispose()\n }\n\n dispose(): void {\n if (this.disposed) return\n\n log.debug?.(\n `dispose: renders=${this.stats.renderCount}, skipped=${this.stats.skippedCount}, avg=${Math.round(this.stats.avgRenderTime)}ms`,\n )\n this.disposed = true\n\n // Cancel pending renders\n this.renderScheduled = false\n if (this.frameTimeout) {\n clearTimeout(this.frameTimeout)\n this.frameTimeout = null\n }\n\n // Remove resize listener\n if (this.resizeCleanup) {\n this.resizeCleanup()\n this.resizeCleanup = null\n }\n\n // Unsubscribe from cursor-state changes\n if (this.cursorCleanup) {\n this.cursorCleanup()\n this.cursorCleanup = null\n }\n\n // Stop the bytes_out throughput monitor (if active).\n if (this.bytesOutMonitor) {\n this.bytesOutMonitor.dispose()\n this.bytesOutMonitor = null\n }\n\n // Stop the flicker repaint-storm monitor (if active).\n if (this.flickerMonitor) {\n this.flickerMonitor.dispose()\n this.flickerMonitor = null\n }\n\n // Stop the mem heap-poll monitor (if active).\n if (this.memMonitor) {\n this.memMonitor.dispose()\n this.memMonitor = null\n }\n\n // In static mode, output the final frame on dispose\n if (this.nonTTYMode === \"static\" && this.staticOutput) {\n this.writeOutput(this.staticOutput)\n this.writeOutput(\"\\n\")\n }\n }\n\n /**\n * Get the last rendered output (for static mode).\n * Returns the plain text output that would be written on dispose.\n */\n getStaticOutput(): string {\n return this.staticOutput\n }\n\n // ==========================================================================\n // Private Methods\n // ==========================================================================\n\n /**\n * Schedule render for next frame (frame rate limiting).\n */\n private scheduleNextFrame(delay: number): void {\n if (this.frameTimeout) return\n\n this.frameTimeout = setTimeout(() => {\n this.frameTimeout = null\n if (!this.disposed) {\n this.doRender()\n }\n }, delay)\n }\n\n /**\n * Resolve the active cursor for this frame.\n *\n * Priority:\n * 1. Layout-output cursor (BoxProps.cursorOffset → LayoutSignals.cursorRect).\n * Set synchronously by the layout phase, so available on the very first\n * frame after mount — no React-effect-chain stale read.\n * 2. Cursor store (legacy useCursor() / Ink-compat setCursorPosition()).\n * Falls back here when no node declares a layout-output cursor.\n *\n * Returns null when no active cursor in either path.\n *\n * The shape is identical between paths so callers (cursor suffix emission,\n * inline-mode pipeline cursor) treat them uniformly.\n */\n private resolveActiveCursor(): CursorRect | null {\n const layoutCursor = findActiveCursorRect(this.root)\n if (layoutCursor) return layoutCursor\n return this.getStoreCursor()\n }\n\n /**\n * The legacy/store cursor (useCursor() / Ink-compat setCursorPosition()),\n * projected into CursorRect shape, or null. This is the cursor source that\n * has NO owning AgNode — `computeManagedFrame` composites it unconditionally\n * (it is an explicit imperative request, not a focus-gated declarative\n * fallback). Layout-output cursors are resolved by `findActiveCursorRect`\n * inside `computeManagedFrame`, so this helper intentionally does NOT consult\n * the tree.\n */\n private getStoreCursor(): CursorRect | null {\n const storeCursor = this.getCursorState()\n if (!storeCursor) return null\n // Project store CursorState into CursorRect shape (visible defaults to\n // true on the store side; we keep it explicit here for clarity).\n return {\n x: storeCursor.x,\n y: storeCursor.y,\n visible: storeCursor.visible !== false,\n shape: storeCursor.shape,\n }\n }\n\n /**\n * Execute the actual render.\n */\n private doRender(): void {\n using render = this.log.span?.(\"render\")\n const startTime = Date.now()\n\n try {\n // Get terminal dimensions. Prefer the Term's Size owner (coalesced\n // resize, alien-signals-backed) when provided. Falls back to direct\n // stdout reads for legacy embedders that haven't wired a Size yet.\n const width = this.size?.cols() ?? this.stdout.columns ?? 80\n // Inline mode: use NaN height so layout engine auto-sizes to content.\n // Fullscreen mode: use terminal rows as the constraint.\n const height = this.mode === \"inline\" ? NaN : (this.size?.rows() ?? this.stdout.rows ?? 24)\n\n log.debug?.(\n `render #${this.stats.renderCount + 1}: ${width}x${height}, nonTTYMode=${this.nonTTYMode}`,\n )\n\n // Run render pipeline\n const scrollbackOffset = this.scrollbackOffset\n this.scrollbackOffset = 0 // Consume the offset\n const measurer = this.pipelineConfig?.measurer\n // Inline cursor is resolved AFTER layout below — the layout-output path\n // (BoxProps.cursorOffset → cursorRect signal) requires layout to have\n // run. The legacy store fallback used to be resolved pre-layout, but\n // that path doesn't depend on layout state either, so deferring is safe.\n let inlineCursor:\n | { x: number; y: number; visible: boolean; shape?: CursorRect[\"shape\"] }\n | null\n | undefined\n const doRender = () => {\n const ag = createAg(this.root, { measurer })\n ag.layout({ cols: width, rows: height })\n if (this.mode === \"inline\") {\n const c = this.resolveActiveCursor()\n // Output phase expects the legacy CursorState shape. CursorRect is a\n // structural superset (same fields), so a direct assign is safe.\n inlineCursor = c\n }\n // Pass the scheduler-owned post-state carrier so outline snapshots\n // survive the per-frame `createAg(this.root)` recycle. Without this,\n // every frame's `clearPreviousOutlines` runs against an empty list\n // and stale outline pixels from `prevBuffer.clone()` leak through —\n // the same shape as km-yej6 / km-silvery.outline-incremental-clear.\n const { buffer, overlay } = ag.render({\n prevBuffer: this.prevBuffer,\n postState: this.postState,\n })\n // Single source of truth for managed-frame cursor handling\n // (managed-caret.ts). The scheduler's legacy/store cursor is threaded as\n // `legacyCursor` so the Ink-compat `useCursor()` path still composites,\n // while a declarative tree cursor is focus-gated (the\n // @km/code/v0.2/19702 fix). The full ManagedFrame is reused by the\n // cursor-suffix block below — no re-derivation.\n const managed = computeManagedFrame(buffer, this.root, this.mode, {\n legacyCursor: this.mode === \"fullscreen\" ? this.getStoreCursor() : null,\n prevCaret: this.prevPresentation?.caret ?? null,\n windowFocused: this.getWindowFocused(),\n })\n const fullscreenCursor = managed.cursorTarget\n const outputBuffer = managed.presentationBuffer\n\n const start = performance.now()\n const outputFn = this.pipelineConfig?.outputPhaseFn ?? outputPhase\n let ansiOutput: string\n try {\n clearLastOutputPhaseDiagnostics()\n ansiOutput = outputFn(\n this.prevPresentation?.buffer ?? null,\n outputBuffer,\n this.mode,\n scrollbackOffset,\n this.mode === \"inline\" ? (this.size?.rows() ?? this.stdout.rows ?? 24) : undefined,\n inlineCursor ?? undefined,\n )\n } catch (e) {\n if (e instanceof Error) {\n ;(e as any).__silvery_buffer = buffer\n }\n throw e\n }\n // Append Kitty emoji-scrim overlay from the backdrop-fade pass.\n // No-op when backdrop inactive or cap disabled — zero-length string.\n if (overlay) ansiOutput += overlay\n const tOutput = performance.now() - start\n\n // Bench instrumentation: accumulate output-phase timing\n const acc = (globalThis as any).__silvery_bench_phases\n if (acc) {\n acc.output += tOutput\n acc.pipelineCalls += 1\n }\n\n return {\n output: ansiOutput,\n buffer,\n outputBuffer,\n overlay,\n cursor: fullscreenCursor,\n compositorCaret: managed.compositorCaret,\n managed,\n }\n }\n const {\n output,\n buffer,\n outputBuffer,\n overlay: incrementalOverlay,\n compositorCaret,\n managed: managedFrame,\n } = measurer ? runWithMeasurer(measurer, doRender) : doRender()\n\n // Transform output based on non-TTY mode\n let transformedOutput: string\n if (this.nonTTYMode === \"tty\") {\n // Pass through unchanged\n transformedOutput = output\n } else if (this.nonTTYMode === \"static\") {\n // Store for final output, don't write yet\n this.staticOutput = stripAnsi(output)\n transformedOutput = \"\"\n } else {\n // Apply line-by-line or plain transformation\n transformedOutput = this.outputTransformer(output, this.prevLineCount)\n this.prevLineCount = countLines(output)\n }\n\n // Managed-frame cursor controls come from the single computeManagedFrame\n // call inside doRender (managed-caret.ts). Fullscreen TTY frames paint the\n // visible caret into the presentation buffer ONLY for a focused editable\n // (the @km/code/v0.2/19702 fix), then always park + hide the hardware\n // cursor at the editable's bounds so late/hollow terminal cursors cannot\n // appear in transcript or chrome rows. The suffix is consumed only on a\n // real TTY — `managedFrame` was computed for mode === \"fullscreen\", which\n // is exactly when `nonTTYMode === \"tty\"`.\n let cursorSuffix = \"\"\n let cursorTarget: OutputCursorTarget | null = null\n let expectedTerminal: OutputCursorTarget | null = null\n let promptBounds: OutputCursorBounds | null = null\n let composerBounds: OutputCursorBounds | null = null\n if (this.nonTTYMode === \"tty\") {\n cursorSuffix = managedFrame.cursorSuffix\n cursorTarget = managedFrame.cursorTarget\n expectedTerminal = managedFrame.expectedTerminal\n promptBounds = managedFrame.promptBounds\n composerBounds = managedFrame.composerBounds\n }\n\n // Write output wrapped with synchronized update (DEC 2026) for TTY mode.\n // This tells the terminal to batch the output and paint atomically,\n // preventing tearing during rapid screen updates.\n const cursorDelivery = protectManagedCursorSuffix(cursorSuffix)\n if (transformedOutput.length > 0 || cursorDelivery.length > 0) {\n const cursorDeliveryNeedsSync =\n this.nonTTYMode === \"tty\" &&\n cursorSuffix.length > 0 &&\n expectedTerminal !== null &&\n (expectedTerminal.x !== 0 || expectedTerminal.y !== 0)\n const fullOutput =\n this.nonTTYMode === \"tty\" && (SYNC_UPDATE_ENABLED || cursorDeliveryNeedsSync)\n ? `${ANSI.SYNC_BEGIN}${transformedOutput}${cursorDelivery}${ANSI.SYNC_END}`\n : transformedOutput + cursorDelivery\n\n // Debug: log output sizes to detect potential pipe buffer splits\n if (log.debug) {\n const bytes = Buffer.byteLength(fullOutput)\n log.debug?.(\n `stdout.write: ${bytes} bytes (${transformedOutput.length} chars output + ${cursorSuffix.length} chars cursor)`,\n )\n if (bytes > 16384) {\n log.debug?.(\n `large output: ${bytes} bytes may exceed pipe buffer (16KB on macOS), risk of mid-sequence split`,\n )\n }\n }\n\n // Capture raw ANSI output to file for debugging garbled rendering\n const captureFile = process.env.SILVERY_CAPTURE_OUTPUT\n if (captureFile) {\n // sync-node-builtin: multi-target lazy guard via getBuiltinModule (ESM-only wave-3).\n const fs = process.getBuiltinModule(\"node:fs\")\n fs.appendFileSync(\n captureFile,\n `--- FRAME ${this.stats.renderCount + 1} (${Buffer.byteLength(fullOutput)} bytes) ---\\n`,\n )\n fs.appendFileSync(captureFile, fullOutput)\n fs.appendFileSync(captureFile, \"\\n\")\n }\n\n recordOutputCursorDiagnostics({\n reason: \"scheduler-render\",\n mode: \"fullscreen\",\n width,\n height,\n output: fullOutput,\n target: cursorTarget,\n expectedTerminal,\n compositorCaret,\n promptBounds,\n composerBounds,\n })\n this.writeOutput(fullOutput)\n // flicker instrumentation — scan the SAME post-write bytes for\n // destructive-clear / scroll-region repaint storms. Records the raw\n // ANSI string (not just a byte count) so it can detect the bare-`2J`\n // @ag/code/20297 signature. PANICs (throws) at the fixed\n // clears-per-window threshold in `flicker-monitor.ts`.\n if (this.flickerMonitor) {\n const renderNumber = this.stats.renderCount + 1\n this.flickerMonitor.recordFrame(renderNumber, fullOutput)\n }\n // bytes_out instrumentation — record AFTER write so the monitor\n // accounts for what actually left the process. Fixed thresholds\n // (1 MB/s WARN × 10s, 100 MB/s PANIC × 2s) in `bytes-out-monitor.ts`.\n const traceOutputEnabled = this.renderTraceEnabled || isRenderTraceEnabled()\n if (this.bytesOutMonitor || traceOutputEnabled) {\n const outputDiagnostics = getLastOutputPhaseDiagnostics()\n const bytes = Buffer.byteLength(fullOutput)\n const diagnostics = {\n ...outputDiagnostics,\n outputChars: transformedOutput.length,\n cursorChars: cursorDelivery.length,\n syncWrapped:\n this.nonTTYMode === \"tty\" && (SYNC_UPDATE_ENABLED || cursorDeliveryNeedsSync),\n }\n const renderNumber = this.stats.renderCount + 1\n this.bytesOutMonitor?.recordWrite(renderNumber, bytes, diagnostics)\n if (traceOutputEnabled) {\n this.outputFrameCount += 1\n emitRenderOutputFrame({\n renderCount: renderNumber,\n outputFrame: this.outputFrameCount,\n bytes,\n diagnostics,\n })\n }\n }\n }\n\n // Save buffer for next diff\n this.prevBuffer = buffer\n // The presentation buffer and the caret composited into it are stored as\n // one unit so the next frame's overlay-clear always sees the caret that\n // matches the baseline it diffs against. @km/code/v0.2/19702.\n this.prevPresentation = { buffer: outputBuffer, caret: compositorCaret }\n\n // Commit boundary — promote in-flight rect signals (just written by\n // ag.layout above) to their committed peers. Reactive\n // useBoxRect/useScrollRect/useScreenRect consumers subscribe to\n // committed and will fire `forceUpdate` here if a rect changed,\n // causing React to schedule a re-render that the scheduler picks up\n // via the next store.subscribe → scheduleRender cycle. Within this\n // single doRender pass the committed value is invariant, so a\n // render that branches on useBoxRect cannot ping-pong.\n // See bead `@km/silvery/use-deferred-box-rect-and-post-commit-observers`.\n commitLayoutSnapshot(this.root)\n\n // SILVERY_STRICT: compare incremental render against fresh render.\n // The `fresh-layout` slug (tier 2) also activates the comparison on its own\n // so it composes when isolated (SILVERY_STRICT=fresh-layout).\n const freshLayout = isStrictEnabled(FRESH_LAYOUT_STRICT_SLUG, FRESH_LAYOUT_STRICT_MIN_TIER)\n if ((isStrictEnabled(\"incremental\", 1) || freshLayout) && this.stats.renderCount > 0) {\n const renderNum = this.stats.renderCount + 1\n const doFreshRender = () => {\n const freshAg = createAg(this.root, { measurer })\n // fresh-layout STRICT slug: force an INDEPENDENT from-scratch layout so\n // a shared stale rect (a layout-affecting change that failed to\n // markDirty flexily) surfaces as a buffer mismatch instead of being\n // masked by the incremental path's cleaned flexily tree. Deterministic\n // on clean code (flexily expectRelayoutMatchesFresh fuzz). See\n // markLayoutTreeDirty + AgLayoutOptions.forceFullPropagate.\n if (freshLayout) markLayoutTreeDirty(this.root)\n freshAg.layout(\n { cols: width, rows: height },\n { skipLayoutNotifications: true, forceFullPropagate: freshLayout },\n )\n // Fresh render must NOT consume or mutate `this.postState` (which\n // tracks the incremental path's snapshots). Pass `fresh: true` so\n // the Ag uses a throw-away empty carrier for this comparison.\n return freshAg.render({ fresh: true })\n }\n const { buffer: freshBuffer, overlay: freshOverlay } = measurer\n ? runWithMeasurer(measurer, doFreshRender)\n : doFreshRender()\n\n // STRICT overlay-plan comparison.\n //\n // Invariant: `applyBackdrop` is a pure function of (tree markers,\n // buffer cells, options) → overlay is deterministic. Incremental\n // and fresh paths MUST emit byte-identical overlay strings.\n //\n // A drift here signals non-determinism in marker collection order,\n // the emoji walk, or placement ID derivation — any of which would\n // cause scrim flicker or orphaned placements across frames.\n if (incrementalOverlay !== freshOverlay) {\n const msg = formatOverlayMismatch(incrementalOverlay, freshOverlay, renderNum)\n if (process.env.DEBUG_LOG) {\n appendFileSync(process.env.DEBUG_LOG, msg + \"\\n\")\n }\n log.error?.(msg)\n throw new IncrementalRenderMismatchError(msg)\n }\n\n let found = false\n for (let y = 0; y < buffer.height && !found; y++) {\n for (let x = 0; x < buffer.width && !found; x++) {\n const a = buffer.getCell(x, y)\n const b = freshBuffer.getCell(x, y)\n if (!cellEquals(a, b)) {\n found = true\n\n // Build rich debug context\n const ctx = buildMismatchContext(this.root, x, y, a, b, renderNum)\n\n // Capture render-phase instrumentation snapshot\n const renderPhaseStats: RenderPhaseStats | undefined = (globalThis as any)\n .__silvery_content_detail\n ? structuredClone((globalThis as any).__silvery_content_detail)\n : undefined\n\n const debugInfo = formatMismatchContext(ctx, renderPhaseStats)\n\n // Include text output for full picture\n const incText = bufferToText(buffer)\n const freshText = bufferToText(freshBuffer)\n const msg = debugInfo + `--- incremental ---\\n${incText}\\n--- fresh ---\\n${freshText}`\n\n if (process.env.DEBUG_LOG) {\n appendFileSync(process.env.DEBUG_LOG, msg + \"\\n\")\n }\n log.error?.(msg)\n // Throw special error that won't be caught by general error handler\n throw new IncrementalRenderMismatchError(msg, {\n renderPhaseStats,\n mismatchContext: ctx,\n })\n }\n }\n }\n if (!found && process.env.DEBUG_LOG) {\n appendFileSync(process.env.DEBUG_LOG, `SILVERY_STRICT: render #${renderNum} OK\\n`)\n }\n }\n\n // Update stats\n const renderTime = Date.now() - startTime\n this.stats.renderCount++\n this.stats.lastRenderTime = renderTime\n this.stats.avgRenderTime =\n (this.stats.avgRenderTime * (this.stats.renderCount - 1) + renderTime) /\n this.stats.renderCount\n this.lastRenderTime = Date.now()\n\n // Record span data\n if (render) {\n render.spanData.renderCount = this.stats.renderCount\n render.spanData.renderTime = renderTime\n render.spanData.bytes = transformedOutput.length\n }\n\n log.debug?.(\n `render #${this.stats.renderCount} complete: ${renderTime}ms, output: ${transformedOutput.length} bytes`,\n )\n\n // First render is always slow (initialization); use 5x threshold for it\n const threshold =\n this.stats.renderCount <= 1 ? this.slowFrameThreshold * 5 : this.slowFrameThreshold\n if (threshold > 0 && renderTime > threshold) {\n log.debug?.(\n `slow frame: render #${this.stats.renderCount} took ${renderTime}ms (threshold: ${this.slowFrameThreshold}ms, bytes: ${transformedOutput.length})`,\n )\n }\n\n if (this.debugMode) {\n this.logDebug(`Render #${this.stats.renderCount} took ${renderTime}ms`)\n }\n } catch (error) {\n // Log and re-throw all render errors - the app should handle cleanup\n log.error?.(`render error: ${error}`)\n this.logError(\"Render error:\", error)\n throw error\n }\n }\n\n /**\n * Set up terminal resize listener.\n */\n private setupResizeListener(): void {\n let resizeTimeout: ReturnType<typeof setTimeout> | null = null\n\n const handleResize = () => {\n // Debounce resize events\n if (resizeTimeout) {\n clearTimeout(resizeTimeout)\n }\n\n resizeTimeout = setTimeout(() => {\n resizeTimeout = null\n\n // Reset buffer to force full redraw\n this.prevBuffer = null\n this.prevPresentation = null // no prev buffer ⇒ no stale overlay to clear\n // Outline snapshots are buffer-coordinate-bound; reset together so\n // post-resize clearPreviousOutlines doesn't write at out-of-bounds\n // coordinates from the old dimensions.\n this.postState = createRenderPostState()\n\n // Schedule render\n this.scheduleRender()\n }, 50) // 50ms debounce\n }\n\n this.stdout.on(\"resize\", handleResize)\n\n this.resizeCleanup = () => {\n this.stdout.off(\"resize\", handleResize)\n if (resizeTimeout) {\n clearTimeout(resizeTimeout)\n }\n }\n }\n\n /**\n * Log debug message (via loggily, not stderr — stderr corrupts alt screen).\n */\n private logDebug(message: string): void {\n log.debug?.(message)\n }\n\n /**\n * Log error message (via loggily, not stderr — stderr corrupts alt screen).\n */\n private logError(message: string, error: unknown): void {\n if (error instanceof Error) {\n log.error?.(`${message} ${error.stack ?? error.message}`)\n } else {\n log.error?.(`${message} ${String(error)}`)\n }\n }\n}\n\n// ============================================================================\n// STRICT Diagnostics Helpers\n// ============================================================================\n\n/**\n * Format a human-readable diff when the incremental and fresh Kitty overlays\n * disagree. The overlays are Kitty graphics protocol escape sequences that\n * place translucent \"scrim\" images over emoji cells inside faded regions\n * (see `pipeline/backdrop/`). They MUST be byte-identical across fresh and\n * incremental paths — any drift is a determinism bug in:\n *\n * - `collectBackdropMarkers` (tree walk order)\n * - `collectEmojiCellsInFadeRegion` (rect iteration)\n * - `backdropPlacementId` (per-cell ID derivation)\n * - `assertSingleAmount` (amount selection when markers disagree)\n *\n * The diff surfaces the placement IDs that differ — the Kitty protocol uses\n * `p=<id>` in each placement command, so grep-friendly IDs are the\n * quickest path from \"test failed\" to \"which cell drifted\".\n */\nfunction formatOverlayMismatch(incremental: string, fresh: string, renderNum: number): string {\n const incPlacements = extractPlacementIds(incremental)\n const freshPlacements = extractPlacementIds(fresh)\n\n const incSet = new Set(incPlacements)\n const freshSet = new Set(freshPlacements)\n\n const onlyIncremental = incPlacements.filter((id) => !freshSet.has(id))\n const onlyFresh = freshPlacements.filter((id) => !incSet.has(id))\n\n const lines: string[] = [\n `[SILVERY_STRICT] Kitty overlay mismatch at render #${renderNum}`,\n ` incremental length: ${incremental.length} bytes, placements: ${incPlacements.length}`,\n ` fresh length: ${fresh.length} bytes, placements: ${freshPlacements.length}`,\n ]\n if (onlyIncremental.length > 0) {\n lines.push(` only in incremental (moved/appeared): ${onlyIncremental.join(\", \")}`)\n }\n if (onlyFresh.length > 0) {\n lines.push(` only in fresh (missing/disappeared): ${onlyFresh.join(\", \")}`)\n }\n if (onlyIncremental.length === 0 && onlyFresh.length === 0) {\n lines.push(` placements match — drift is in scrim-image payload or ordering`)\n }\n\n // Truncated raw bytes for inspection. Full strings can be many KB (each\n // scrim image payload is base64-encoded) — cap to avoid log-flood.\n const cap = 400\n lines.push(\n ` incremental[0..${cap}]: ${JSON.stringify(incremental.slice(0, cap))}`,\n ` fresh [0..${cap}]: ${JSON.stringify(fresh.slice(0, cap))}`,\n )\n return lines.join(\"\\n\")\n}\n\n/**\n * Extract the list of `p=<id>` placement IDs from a Kitty overlay string, in\n * emission order. Used by the STRICT diagnostic to show which placements\n * moved/appeared/disappeared.\n */\nfunction extractPlacementIds(overlay: string): string[] {\n const ids: string[] = []\n const re = /p=(\\d+)/g\n let m: RegExpExecArray | null\n while ((m = re.exec(overlay)) !== null) {\n ids.push(m[1]!)\n }\n return ids\n}\n\n// ============================================================================\n// Factory Function\n// ============================================================================\n\n/**\n * Create a render scheduler.\n *\n * @param options Scheduler options\n * @returns A new RenderScheduler instance\n */\nexport function createScheduler(options: SchedulerOptions): RenderScheduler {\n return new RenderScheduler(options)\n}\n\n// ============================================================================\n// Utility: Simple Render (for testing/debugging)\n// ============================================================================\n\n/**\n * Render once to a string (for testing).\n *\n * Does not batch or diff - just runs the pipeline and returns ANSI output.\n */\nexport function renderToString(root: AgNode, width: number, height: number): string {\n const ag = createAg(root)\n ag.layout({ cols: width, rows: height })\n const { buffer } = ag.render()\n return outputPhase(null, buffer, \"fullscreen\")\n}\n","/**\n * withPasteChain — apply-chain plugin for bracketed-paste routing.\n *\n * Paste routing has priority:\n *\n * 1. Focused `onPaste` — if a focused element subscribes directly\n * (via a focus event listener) it should consume the paste first.\n * The chain expresses this via a pluggable `routeToFocused`\n * callback — in production this dispatches a `paste` DOM event\n * through the focus tree (see `create-app.tsx`); in tests it can\n * be stubbed.\n *\n * 2. Global `usePaste` handlers — registered by React components via\n * the `usePaste()` / `usePasteCallback()` / `usePasteEvents()`\n * hooks. Invoked in registration order, same contract as\n * `withInputChain`'s fallback store.\n *\n * A paste never produces an \"exit\" result; we return `[]` whenever at\n * least one handler (or the focused route) consumes the paste, so the\n * outer runner knows to render.\n */\n\nimport type { ApplyResult, Effect, Op } from \"../types\"\nimport type { BaseApp } from \"./base-app\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type PasteHandler = (text: string) => void\n\n/** Paste handler with richer metadata (mirrors `usePasteEvents`). */\nexport interface PasteEvent {\n /** The raw text that was pasted (as delivered by the terminal). */\n readonly text: string\n /** Whether the focus route consumed this event. */\n readonly focusedConsumed: boolean\n}\n\n/** Store slice installed by {@link withPasteChain}. */\nexport interface PasteStore {\n readonly handlers: ReadonlyArray<PasteHandler>\n register(handler: PasteHandler): () => void\n unregister(handler: PasteHandler): void\n}\n\n/** Options for {@link withPasteChain}. */\nexport interface WithPasteChainOptions {\n /**\n * If provided, called with the pasted text before any fallback\n * handlers run. Return `true` if the paste was consumed by a focused\n * element (fallback handlers will be skipped).\n *\n * Production wires this to `dispatchPasteEvent` through the focus\n * tree; tests typically leave it undefined.\n */\n routeToFocused?: (text: string) => boolean\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\n/**\n * Install the paste router plugin.\n *\n * On `term:paste` ops:\n * - Call `routeToFocused(text)` first (if provided). If it returns\n * true, we short-circuit: handled by focus tree, no fallback.\n * - Otherwise invoke every registered global handler in order.\n * - Always emit `[{type:\"render\"}]` when handled, so the runner\n * knows to paint the post-paste state.\n */\nexport function withPasteChain(\n options: WithPasteChainOptions = {},\n): <A extends BaseApp>(app: A) => A & { paste: PasteStore } {\n return <A extends BaseApp>(app: A): A & { paste: PasteStore } => {\n const handlers: PasteHandler[] = []\n const store: PasteStore = {\n handlers,\n register(handler) {\n handlers.push(handler)\n return () => {\n const i = handlers.indexOf(handler)\n if (i >= 0) handlers.splice(i, 1)\n }\n },\n unregister(handler) {\n for (let i = handlers.length - 1; i >= 0; i--) {\n if (handlers[i] === handler) handlers.splice(i, 1)\n }\n },\n }\n const prev = app.apply\n app.apply = (op: Op): ApplyResult => {\n if (op.type !== \"term:paste\") return prev(op)\n const text = (op as { text?: string }).text ?? \"\"\n let consumed = false\n if (options.routeToFocused) {\n try {\n consumed = !!options.routeToFocused(text)\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error(\"[withPasteChain] routeToFocused threw\", err)\n }\n }\n if (!consumed && handlers.length > 0) {\n for (const handler of handlers) {\n try {\n handler(text)\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error(\"[withPasteChain] handler threw\", err)\n }\n }\n consumed = true\n }\n if (!consumed) return prev(op)\n const effects: Effect[] = [{ type: \"render\" }]\n return effects\n }\n return Object.assign(app, { paste: store })\n }\n}\n","/**\n * withInputChain — apply-chain plugin for the fallback `useInput` store.\n *\n * Fallback = this plugin runs AFTER the focused dispatch plugin\n * (`withFocusChain`) in the apply chain. When focus consumes an event,\n * useInput never sees it — focused components have priority.\n *\n * ## Contract\n *\n * - Plugin owns a list of handlers registered by React components via\n * the `useInput()` hook. Handlers are invoked in registration order\n * (stable; matches the pre-refactor RuntimeContext behaviour).\n *\n * - On `input:key` ops:\n * - release / modifier-only events are observed (some useInput\n * callers still want to know about Ctrl up/down) but do not\n * mark the event as consumed.\n * - press / repeat events invoke every active handler. If any\n * handler returns \"exit\", we short-circuit with\n * `[{type:\"exit\"}]`. Otherwise we signal \"handled\" by returning\n * `[]` when at least one active handler exists.\n *\n * - If no active handlers exist, the plugin returns `false` (pass\n * through) so downstream plugins or the app handler can see the\n * event unchanged.\n *\n * ## Why \"active\" handlers?\n *\n * Components can `useInput(fn, { isActive: false })` to temporarily\n * suppress their handler without unmounting. This is used e.g. by\n * picker dialogs where the parent's global keys should be disabled\n * while the picker is open but the component remains mounted.\n */\n\nimport type { ApplyResult, Effect, Op } from \"../types\"\nimport type { BaseApp } from \"./base-app\"\nimport type { KeyShape } from \"./with-terminal-chain\"\n\n// ---------------------------------------------------------------------------\n// Handler + store types\n// ---------------------------------------------------------------------------\n\n/** Signature matches the classic `useInput(handler)` form. */\nexport type InputHandler = (input: string, key: KeyShape) => void | \"exit\"\n\ninterface InputEntry {\n handler: InputHandler\n active: boolean\n}\n\n/** Store slice installed by {@link withInputChain}. */\nexport interface InputStore {\n /** Internal array of registered entries (exposed for diagnostics). */\n readonly handlers: ReadonlyArray<InputEntry>\n /**\n * Register a new input handler. Returns an unregister function.\n * @param handler the callback\n * @param active whether the handler is currently active (default: true)\n */\n register(handler: InputHandler, active?: boolean): () => void\n /** Update the active flag for a previously-registered handler. */\n setActive(handler: InputHandler, active: boolean): void\n /** Remove a handler (primarily for tests; `register` returns an unregister fn). */\n unregister(handler: InputHandler): void\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction isModifierOnly(_input: string, key: KeyShape | undefined): boolean {\n // Authoritative flag — set by @silvery/ag/keys `parseKey()` when the\n // key name is one of the dedicated Kitty modifier codepoints\n // (leftshift, leftctrl, …). Deriving from `input === \"\"` plus a\n // modifier flag is WRONG: Shift+Tab, Shift+Enter, Shift+Arrow,\n // Ctrl+Tab, etc. all have empty `input` but carry a real payload via\n // the `tab` / `return` / `upArrow` flags. See\n // `MODIFIER_ONLY_NAMES` in @silvery/ag/keys.\n return key?.isModifierOnly === true\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\n/**\n * Install the fallback useInput store plugin.\n *\n * Pipe order note: place this plugin so it runs AFTER withFocusChain.\n * Because the last plugin in `pipe()` wraps the outermost layer, the\n * chain reads left-to-right with the base on the left:\n *\n * pipe(create(), withTerminalChain(), withInputChain(), withFocusChain())\n *\n * => apply order: focusChain -> inputChain -> terminalChain -> base\n *\n * => focused components consume before useInput, which is what we want.\n */\nexport function withInputChain<A extends BaseApp>(app: A): A & { input: InputStore } {\n const entries: InputEntry[] = []\n const store: InputStore = {\n handlers: entries,\n register(handler, active = true) {\n const entry: InputEntry = { handler, active }\n entries.push(entry)\n return () => {\n const i = entries.indexOf(entry)\n if (i >= 0) entries.splice(i, 1)\n }\n },\n setActive(handler, active) {\n for (const entry of entries) {\n if (entry.handler === handler) entry.active = active\n }\n },\n unregister(handler) {\n for (let i = entries.length - 1; i >= 0; i--) {\n if (entries[i]!.handler === handler) entries.splice(i, 1)\n }\n },\n }\n const prev = app.apply\n app.apply = (op: Op): ApplyResult => {\n if (op.type !== \"input:key\") return prev(op)\n const input = (op as { input?: string }).input ?? \"\"\n const key = (op as { key?: KeyShape }).key\n const isRelease = key?.eventType === \"release\"\n const modOnly = isModifierOnly(input, key)\n // Always let release / modifier-only events bubble down the chain —\n // e.g. `useModifierKeys` (a future plugin) wants to hear them — but\n // do NOT invoke useInput handlers and do NOT mark the event handled\n // at this layer.\n if (isRelease || modOnly) return prev(op)\n\n let hasActive = false\n const effects: Effect[] = []\n for (const entry of entries) {\n if (!entry.active) continue\n hasActive = true\n try {\n const result = entry.handler(input, key ?? ({} as KeyShape))\n if (result === \"exit\") {\n effects.push({ type: \"exit\" })\n return effects\n }\n } catch (err) {\n // Surface, but don't kill the event loop for a single bad handler.\n // eslint-disable-next-line no-console\n console.error(\"[withInputChain] handler threw\", err)\n }\n }\n if (hasActive) return effects\n return prev(op)\n }\n return Object.assign(app, { input: store })\n}\n","/**\n * withCustomEvents — apply-chain plugin for app-defined custom events.\n *\n * Replaces the legacy `RuntimeContextValue.emit(\"foo\", …)` /\n * `RuntimeContextValue.on(\"foo\", …)` pair for anything that isn't one of\n * the built-in input / paste / focus events. Provides a typed bus where\n * components emit named events and handlers subscribe by channel name.\n *\n * ## Contract\n *\n * - Channel names are arbitrary strings chosen by the app. Payloads are\n * untyped `unknown[]` at the plugin boundary — callers usually narrow\n * via a branded wrapper (see `km-tui`'s `useLinkOpen`).\n *\n * - Handlers are invoked in registration order; a thrown handler is\n * surfaced to the console but does not short-circuit the bus.\n *\n * - The plugin never intercepts ops — it exposes its store via\n * `app.events` and returns `false` for every op so downstream plugins\n * still see them.\n *\n * ## Why a chain plugin?\n *\n * Before this existed, custom events rode on `RuntimeContextValue.on /\n * emit`. That surface is being trimmed to just `{exit: () => void}` as\n * part of the TEA Phase 2 wiring. Routing custom events through a\n * dedicated plugin keeps the chain the single authority for view ↔\n * runtime messaging, matching how input / paste / focus already work.\n *\n * @example\n * ```tsx\n * // Install\n * const app = pipe(\n * createBaseApp(),\n * withTerminalChain(),\n * withPasteChain(),\n * withInputChain,\n * withFocusChain(...),\n * withCustomEvents,\n * )\n *\n * // Emit from a component\n * app.events.emit(\"link:open\", \"https://example.com\")\n *\n * // Subscribe from a hook\n * useEffect(() => chain.events.on(\"link:open\", (href) => openExternal(href as string)), [chain])\n * ```\n */\n\nimport type { ApplyResult, Op } from \"../types\"\nimport type { BaseApp } from \"./base-app\"\n\n// ---------------------------------------------------------------------------\n// Store shape\n// ---------------------------------------------------------------------------\n\n/** Generic payload handler — custom events carry `unknown[]` payloads. */\nexport type CustomEventHandler = (...args: unknown[]) => void\n\n/** Store slice installed by {@link withCustomEvents}. */\nexport interface CustomEventStore {\n /** Subscribe to `channel`. Returns an unsubscribe function. */\n on(channel: string, handler: CustomEventHandler): () => void\n /** Emit `channel` with payload — invokes every registered handler in order. */\n emit(channel: string, ...args: unknown[]): void\n /**\n * Remove a specific handler. Prefer the cleanup function returned by\n * `on()`; this is primarily for tests and rare teardown paths.\n */\n off(channel: string, handler: CustomEventHandler): void\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\n/**\n * Install the custom-events plugin.\n *\n * Exposes the event bus at `app.events`. The plugin is observer-only —\n * it does not touch the op stream; every op passes straight through to\n * downstream plugins.\n */\nexport function withCustomEvents<A extends BaseApp>(app: A): A & { events: CustomEventStore } {\n const channels = new Map<string, CustomEventHandler[]>()\n const store: CustomEventStore = {\n on(channel, handler) {\n let handlers = channels.get(channel)\n if (!handlers) {\n handlers = []\n channels.set(channel, handlers)\n }\n handlers.push(handler)\n return () => {\n const list = channels.get(channel)\n if (!list) return\n const i = list.indexOf(handler)\n if (i >= 0) list.splice(i, 1)\n }\n },\n emit(channel, ...args) {\n const handlers = channels.get(channel)\n if (!handlers || handlers.length === 0) return\n // Snapshot — allow handlers to unsubscribe without mutating the\n // iteration set.\n for (const handler of handlers.slice()) {\n try {\n handler(...args)\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error(`[withCustomEvents] handler for \"${channel}\" threw`, err)\n }\n }\n },\n off(channel, handler) {\n const handlers = channels.get(channel)\n if (!handlers) return\n for (let i = handlers.length - 1; i >= 0; i--) {\n if (handlers[i] === handler) handlers.splice(i, 1)\n }\n },\n }\n const prev = app.apply\n app.apply = (op: Op): ApplyResult => prev(op)\n return Object.assign(app, { events: store })\n}\n","/**\n * Wrap-measurer registration for `@silvery/ag-term`.\n *\n * `@silvery/ag` exposes a process-level wrap-measurer registry\n * (`setWrapMeasurer` / `getWrapMeasurer`) so geometry helpers like\n * `computeSelectionFragments` can ask the active terminal runtime for\n * grapheme-correct soft-wrap slices. Without registration, the geometry\n * layer falls back to `\\n`-only line splitting — fine for pure-framework\n * unit tests, wrong for real apps where a 60-char paragraph at width 20\n * needs to emit 3 fragments.\n *\n * **Registration shape (Option B per `hub/silvery/design/overlay-anchor-system.md`\n * § 8)**: terminal-side `wrapTextWithOffsets` adapts to the registry's\n * `wrapText(text, maxWidth) → readonly WrapSlice[]` contract. The slice\n * shape is structurally identical (`text` + `startOffset` + `endOffset`),\n * so the adapter is a passthrough — no allocation, no reformatting.\n *\n * **Lifecycle**: registration happens at module load — see the side-effect\n * call below. The adapter reads the active scoped measurer (terminal caps,\n * wide-emoji heuristics, text-sizing) at call time, so caps changes during\n * a session (e.g., post-DA1 width-detection toggling `maybeWideEmojis`)\n * are picked up automatically.\n *\n * **Test isolation**: tests that need the `\\n`-only fallback (pure-`ag`\n * pathway) call `setWrapMeasurer(null)` in `beforeEach`/`afterEach` and\n * `restoreDefaultWrapMeasurer()` in `afterAll`. Tests that exercise the\n * registered path do nothing — the import-time side effect already wired\n * it up.\n *\n * **Multi-Term**: v1 is module-level singleton — see `@silvery/ag/wrap-measurer.ts`\n * file header for the upgrade path. Single Term-per-process is the\n * production reality; multi-Term scenarios are research / future work.\n *\n * Tracking: bead `km-silvery.softwrap-selection-fragments` (closes Phase\n * 4b deferred wrap-spanning).\n */\n\nimport {\n getWrapMeasurer,\n setWrapMeasurer,\n type WrapMeasurer,\n type WrapSlice,\n} from \"@silvery/ag/wrap-measurer\"\nimport { wrapTextWithOffsets } from \"../unicode\"\n\n/**\n * The terminal-runtime adapter — a thin passthrough that converts\n * `wrapTextWithOffsets`'s return type to `readonly WrapSlice[]`. The\n * shapes are identical so this is a re-tag, but going through a typed\n * conversion lets us tighten or widen either side without breaking the\n * other.\n */\nconst terminalWrapMeasurer: WrapMeasurer = {\n wrapText(text, maxWidth): readonly WrapSlice[] {\n return wrapTextWithOffsets(text, maxWidth)\n },\n}\n\n/**\n * Install the terminal-runtime wrap measurer into `@silvery/ag`'s registry.\n *\n * Reads the live registry (`getWrapMeasurer()`) — not a local boolean — to\n * decide whether to install. This makes the function safe to call after a\n * `setWrapMeasurer(null)` teardown that bypassed `uninstallTerminalWrapMeasurer`\n * (e.g. tests that drop the registration through `@silvery/ag`'s own API).\n *\n * Idempotent: when our adapter is already the registered measurer, this\n * is a no-op. When a foreign measurer is registered (rare — only seen if\n * a test or alternate runtime registered its own), this overwrites it\n * with our adapter, matching v1's \"single Term-per-process\" assumption.\n *\n * Called automatically at module load (see side-effect below); exported\n * for tests that want to re-arm after a `setWrapMeasurer(null)` teardown.\n */\nexport function installTerminalWrapMeasurer(): void {\n if (getWrapMeasurer() === terminalWrapMeasurer) return\n setWrapMeasurer(terminalWrapMeasurer)\n}\n\n/**\n * Reset registration when our adapter is the active measurer. After\n * calling this, the `@silvery/ag` registry is empty (`getWrapMeasurer()`\n * returns null) and `computeSelectionFragments` falls back to `\\n`-only\n * splitting. Tests use this to exercise the fallback path; runtime\n * teardown uses it on Term dispose.\n *\n * No-op when a foreign measurer is registered — clearing it would surprise\n * whoever installed it. Call `setWrapMeasurer(null)` directly if the goal\n * is to clear unconditionally.\n */\nexport function uninstallTerminalWrapMeasurer(): void {\n if (getWrapMeasurer() !== terminalWrapMeasurer) return\n setWrapMeasurer(null)\n}\n\n/**\n * Test helper: restore the registry to its post-import-time default\n * state. Equivalent to `installTerminalWrapMeasurer()` but reads more\n * naturally in `afterEach` / `afterAll` blocks.\n */\nexport function restoreDefaultWrapMeasurer(): void {\n installTerminalWrapMeasurer()\n}\n\n/**\n * Read whether the terminal measurer is currently the active registration.\n * Sources truth from the live registry so a concurrent test that called\n * `setWrapMeasurer(null)` is observed correctly.\n */\nexport function isTerminalWrapMeasurerInstalled(): boolean {\n return getWrapMeasurer() === terminalWrapMeasurer\n}\n\n// =============================================================================\n// Module-load side effect\n// =============================================================================\n//\n// Importing `@silvery/ag-term` (anywhere — directly, transitively via\n// `silvery/runtime`, via `@silvery/test`'s `createRenderer`) is the signal\n// that the process wants terminal-grade wrap geometry. We register\n// immediately so the very first `computeSelectionFragments` call sees the\n// adapter — there's no race window where pre-runtime selections produce\n// stale `\\n`-only fragments and post-runtime selections produce\n// soft-wrapped ones.\n//\n// This mirrors the convention already in place for the layout engine\n// (`ensureDefaultLayoutEngine`) and `term.input` (single ownership): set\n// the right thing up the moment the module loads, let teardown opt out.\n\ninstallTerminalWrapMeasurer()\n","/**\n * ScopeContext — plumbs `Scope` down the React tree.\n *\n * Two contexts intentionally:\n *\n * - `ScopeContext` carries the *current* enclosing scope. Nested\n * `<ScopeProvider scope={...}>` elements shadow it. `useScope()` reads this.\n *\n * - `AppScopeContext` carries the *root app scope*. It is set exactly once,\n * at the app root, by `withScope()` (wired into `createApp()` in a later\n * phase) or by `<ScopeProvider scope={root} appScope={root}>` in tests.\n * `useAppScope()` reads this so whole-app shutdown paths always reach the\n * root even from deeply nested components.\n *\n * Separating them matters: nesting a `<ScopeProvider>` must not change what\n * `useAppScope()` returns — only `useScope()`.\n */\n\nimport { createContext } from \"react\"\nimport type { Scope } from \"@silvery/scope\"\n\n/** Current enclosing scope. `null` when no provider is above the component. */\nexport const ScopeContext = createContext<Scope | null>(null)\n\n/** Root app scope. Set once by `withScope()` (or in tests). */\nexport const AppScopeContext = createContext<Scope | null>(null)\n","/**\n * ScopeProvider — make a `Scope` available to descendants.\n *\n * Three modes:\n *\n * <ScopeProvider scope={s}> // set current scope only\n * <ScopeProvider appScope={root}> // set app-root scope only\n * <ScopeProvider scope={s} appScope={root}> // both\n *\n * `withScope()` will, in a later phase, emit the equivalent of the third\n * form at the app root: the same `Scope` value flows into both contexts so\n * `useScope()` and `useAppScope()` both resolve to it. Tests use the\n * explicit form to exercise nested-scope behavior.\n */\n\nimport type { ReactNode } from \"react\"\nimport type { Scope } from \"@silvery/scope\"\nimport { AppScopeContext, ScopeContext } from \"./scope-context\"\n\nexport interface ScopeProviderProps {\n /** Replace the current enclosing scope for descendants. */\n scope?: Scope\n /** Set the app-root scope (only meaningful at the app root). */\n appScope?: Scope\n children?: ReactNode\n}\n\nexport function ScopeProvider({\n scope,\n appScope,\n children,\n}: ScopeProviderProps): React.JSX.Element {\n // Chain providers conditionally so we don't shadow the parent's values\n // with `null` when a given prop wasn't passed. A missing prop means\n // \"inherit from parent\".\n let tree: React.JSX.Element = <>{children}</>\n if (scope !== undefined) {\n tree = <ScopeContext.Provider value={scope}>{tree}</ScopeContext.Provider>\n }\n if (appScope !== undefined) {\n tree = <AppScopeContext.Provider value={appScope}>{tree}</AppScopeContext.Provider>\n }\n return tree\n}\n","/**\n * CLS — Cumulative Layout Shift instrumentation for silvery.\n *\n * Analogous to Core Web Vitals CLS: a layout shift is the same block\n * rendering at a different rect in successive frames during a single\n * user-action window. Score = sum(area × distance) over all shifts.\n *\n * This module is pure: it defines the data shape + diff math. The runtime\n * ClsMonitor (ag-term/runtime/cls-monitor) calls these helpers per render\n * commit; the strict-mode check (ag-term/strict-cls) consumes the CLSReport.\n * The termless capture API (ag-term/app) drives begin/end of the capture\n * window via ClsMonitor.\n *\n * Bead: km-silvery.cls-instrumentation-primitive\n */\n\nimport { rectEqual, type Rect } from \"./types\"\n\n/**\n * Why a layout shift happened. Used to filter \"expected\" shifts (the user\n * scrolled, content arrived in a streaming view) from \"unexpected\" shifts\n * (a code fence resized mid-stream, a status line bounced) — only the\n * latter fail CLS strict checks.\n */\nexport type ReflowReason =\n | \"user-action\" // user did something — typed, scrolled, resized\n | \"unexpected\" // no triggering event — the bad kind, CLS we want to catch\n | \"animation\" // ongoing animation that's expected to move stuff\n | \"content-arrival\" // new content streamed in — expected for chat / log views\n\n/**\n * Classifier function — given a block + rect transition, returns the\n * reflow reason. The capture API passes a classifier that inspects layout\n * context (user-action vs streamed content vs unsolicited reflow). Default\n * classifier returns \"unexpected\" — most pessimistic, captures the bug\n * class CLS is designed to detect.\n */\nexport type ReasonClassifier = (\n blockId: string,\n fromRect: Rect,\n toRect: Rect,\n frameTimestamp: number,\n) => ReflowReason\n\n/**\n * Default reflow-reason classifier — every shift is \"unexpected\". Tests\n * + close-gates use this to surface every shift; apps with legitimate\n * reflow events (streaming chat, animation) pass a custom classifier.\n */\nexport const defaultClassifier: ReasonClassifier = () => \"unexpected\"\n\nexport interface LayoutShift {\n /** Stable identifier for the shifted block. */\n blockId: string\n /** Position + size in the previous frame. */\n fromRect: Rect\n /** Position + size in the current frame. */\n toRect: Rect\n /** Wall-clock timestamp at frame end (ms since epoch). */\n frameTimestamp: number\n /** Why the shift happened — see ReflowReason for taxonomy. */\n reflowReason: ReflowReason\n}\n\nexport interface CLSReport {\n /** All shifts observed in the capture window. */\n shifts: readonly LayoutShift[]\n /** Sum of (area × distance) over all shifts (CLS metric proper). */\n cumulativeScore: number\n /** Subset of shifts with reflowReason=\"unexpected\" — the actionable ones. */\n unexpectedShifts: readonly LayoutShift[]\n}\n\n/**\n * Compute the \"impact\" of a single shift: max(prev-area, curr-area) ×\n * euclidean-distance-moved between top-left corners. Returns 0 when the\n * rect is unchanged.\n *\n * Area is in cells (cols × rows); distance is in cells. Web CLS uses\n * \"impact fraction\" relative to viewport — we use raw area × distance\n * because terminal viewports are small and per-block context matters more\n * than viewport-relative weighting. Score is comparable across runs in the\n * same terminal-size + content envelope; not directly comparable across\n * different sizes.\n */\nexport function computeShiftScore(from: Rect, to: Rect): number {\n if (rectEqual(from, to)) return 0\n const dx = to.x - from.x\n const dy = to.y - from.y\n const distance = Math.sqrt(dx * dx + dy * dy)\n const fromArea = from.width * from.height\n const toArea = to.width * to.height\n return Math.max(fromArea, toArea) * distance\n}\n\n/**\n * Build a LayoutShift record from a rect transition. Returns null when the\n * rects are equal (no shift — caller can skip without branching).\n */\nexport function makeShift(\n blockId: string,\n fromRect: Rect,\n toRect: Rect,\n frameTimestamp: number,\n reflowReason: ReflowReason,\n): LayoutShift | null {\n if (rectEqual(fromRect, toRect)) return null\n return { blockId, fromRect, toRect, frameTimestamp, reflowReason }\n}\n\n/**\n * Aggregate a list of shifts into a CLSReport. Pure: callers can stash\n * the shifts array however they like (per-frame, per-capture, per-test)\n * and call this once when building the report.\n *\n * cumulativeScore sums every shift, regardless of reflowReason — the\n * intent is \"how much did the layout move?\" — not \"how much surprise?\".\n * For surprise, callers check unexpectedShifts.length or\n * aggregateUnexpectedScore().\n */\nexport function aggregateReport(shifts: readonly LayoutShift[]): CLSReport {\n let cumulativeScore = 0\n const unexpectedShifts: LayoutShift[] = []\n for (const s of shifts) {\n cumulativeScore += computeShiftScore(s.fromRect, s.toRect)\n if (s.reflowReason === \"unexpected\") unexpectedShifts.push(s)\n }\n return { shifts, cumulativeScore, unexpectedShifts }\n}\n\n/**\n * Sum of impact scores for the unexpected subset only. Strict-mode checks\n * use this; \"all shifts including content-arrival\" is too noisy for an\n * assertion threshold.\n */\nexport function aggregateUnexpectedScore(shifts: readonly LayoutShift[]): number {\n let score = 0\n for (const s of shifts) {\n if (s.reflowReason !== \"unexpected\") continue\n score += computeShiftScore(s.fromRect, s.toRect)\n }\n return score\n}\n\n/**\n * Empty report shape — useful as the initial value for accumulators and\n * the return value when capture finds nothing.\n */\nexport function emptyReport(): CLSReport {\n return { shifts: [], cumulativeScore: 0, unexpectedShifts: [] }\n}\n","/**\n * STRICT cls — assert no unexpected layout shifts in a CLS capture window.\n *\n * Bead: km-silvery.cls-instrumentation-primitive (Phase 4/7, 2026-05-13).\n *\n * The CLS instrumentation primitive (packages/ag/src/cls.ts) measures\n * layout shifts per frame, classifies each as user-action / animation /\n * content-arrival / unexpected, and aggregates into a CLSReport. Most\n * apps want shifts of the first three reasons to be allowed (they\n * correspond to legitimate reflow events). The \"unexpected\" reason is\n * the actionable subset — it's the bug class CLS is designed to catch\n * (a code fence resizing mid-stream, a status line bouncing, a card\n * flashing flush-left before snapping to its real position).\n *\n * SILVERY_STRICT=cls flips this from \"consumers decide\" to \"assert that\n * the active capture observed no unexpected shifts.\" Termless tests can\n * then ship a green close-gate via the umbrella env var rather than\n * hand-rolled assertions in every test.\n *\n * Tier 2 (paranoid). `SILVERY_STRICT=2` includes it; `SILVERY_STRICT=1`\n * does NOT — the default fast pass leaves CLS to consumer-side checks\n * so that pre-existing unfixed shift offenders don't fail every test\n * run before they're fixed.\n *\n * Why ag-term/src/ and not pipeline/strict-cls.ts: this check fires at\n * capture-end (termless `endCLSCapture()`), not per-frame in the pipeline.\n * It has no coupling to render-phase / layout-phase internals — it\n * consumes a CLSReport and decides via the SILVERY_STRICT contract. The\n * placement parallels `strict-bordered-rect.ts` (another pure assertion\n * helper that sits outside `pipeline/`).\n */\n\nimport { aggregateUnexpectedScore, type CLSReport, type LayoutShift } from \"@silvery/ag/cls\"\nimport { isStrictEnabled } from \"./strict-mode\"\n\n/** SILVERY_STRICT slug for the CLS check. Tier 2 by design. */\nexport const CLS_STRICT_SLUG = \"cls\"\nexport const CLS_STRICT_MIN_TIER = 2\n\n/** Returns true when SILVERY_STRICT=cls (or =2 / =3 / etc.) is enabled. */\nexport function isClsStrictEnabled(): boolean {\n return isStrictEnabled(CLS_STRICT_SLUG, CLS_STRICT_MIN_TIER)\n}\n\n/**\n * Thrown when SILVERY_STRICT=cls is enabled and a capture window saw at\n * least one shift with reflowReason=\"unexpected\".\n *\n * Carries the offending shifts AND the cumulative unexpected-only score\n * so failing tests can point at the worst offenders without rerunning.\n * `.message` is human-readable; `.shifts` + `.score` are for programmatic\n * inspection.\n */\nexport class UnexpectedLayoutShiftError extends Error {\n readonly shifts: readonly LayoutShift[]\n readonly score: number\n\n constructor(shifts: readonly LayoutShift[], score: number) {\n const summary = shifts\n .slice(0, 5)\n .map(\n (s) =>\n ` ${s.blockId}: (${s.fromRect.x},${s.fromRect.y} ${s.fromRect.width}×${s.fromRect.height}) → (${s.toRect.x},${s.toRect.y} ${s.toRect.width}×${s.toRect.height})`,\n )\n .join(\"\\n\")\n const tail = shifts.length > 5 ? `\\n ... and ${shifts.length - 5} more` : \"\"\n super(\n `[SILVERY_STRICT=cls] CLSReport contains ${shifts.length} unexpected layout shift(s), cumulative score ${score.toFixed(2)}:\\n${summary}${tail}`,\n )\n this.name = \"UnexpectedLayoutShiftError\"\n this.shifts = shifts\n this.score = score\n }\n}\n\n/**\n * Throws `UnexpectedLayoutShiftError` if the report contains any\n * `reflowReason=\"unexpected\"` shifts AND `SILVERY_STRICT=cls` (or a\n * stricter tier) is enabled. Otherwise a no-op.\n *\n * Callers (termless `endCLSCapture`, test harnesses, app-level\n * close-gate hooks) should invoke this immediately after producing a\n * CLSReport — the error names the offending blocks so the failure\n * message points at the bug.\n */\nexport function assertNoUnexpectedShifts(report: CLSReport): void {\n if (!isClsStrictEnabled()) return\n if (report.unexpectedShifts.length === 0) return\n const score = aggregateUnexpectedScore(report.shifts)\n throw new UnexpectedLayoutShiftError(report.unexpectedShifts, score)\n}\n","/**\n * Layout-shift (CLS) monitor — post-commit instrumentation.\n *\n * Detects two classes of layout instability:\n *\n * 1. **Layout shifts** — an AgNode's `screenRect` changes between\n * consecutive render commits with no scroll/resize cause. Logged as\n * per-commit counts under `DEBUG=silvery:cls`; quick-reflow storms (the\n * same path shifting many times in a short window) escalate to a\n * `warn` summary. Full per-node shift logs live under the narrower\n * `DEBUG=silvery:cls-detail` namespace so instrumentation does not create\n * the scroll jank it is meant to diagnose.\n *\n * 2. **Unreasonable sizes** — degenerate or oversized rects detected\n * during the same walk: `width=0` / `height=0` with visible content\n * underneath, negative coords, rect exceeding the terminal bounds.\n * Logged at `warn` regardless of `DEBUG`.\n *\n * Inspired by Web Vitals' Cumulative Layout Shift. The DOM equivalent\n * fires on visible content moving; in terminals the same shape produces\n * the \"first centered, then shifts left\" symptom the user reported\n * 2026-05-11 12:42 + 13:05.\n *\n * Bead: @km/silvery/layout-shift-instrumentation-cls.\n *\n * ## Activation\n *\n * The walk runs every commit but does nothing when no logger method is\n * armed. Activation:\n *\n * - `DEBUG=silvery:cls` — summary instrumentation, per-commit shift\n * counts + storm `warn` logs + size-violation `warn` logs.\n * - `DEBUG=silvery:cls-detail` — full per-node shift `debug` logs.\n * - `LOG_LEVEL=warn` only — storm + size-violation `warn` logs (no\n * per-shift spam). Useful in CI / smoke tests.\n *\n * In both modes, the per-commit walk is O(visible-tree-nodes); the\n * dominant cost is the tree traversal, not the comparison. When\n * disabled, only the `if (!log.debug && !log.warn) return` guard runs.\n */\n\nimport { createLogger } from \"loggily\"\nimport {\n aggregateReport,\n defaultClassifier,\n makeShift,\n type CLSReport,\n type LayoutShift,\n type ReasonClassifier,\n} from \"@silvery/ag/cls\"\nimport type { AgNode, Rect } from \"@silvery/ag/types\"\nimport { assertNoUnexpectedShifts } from \"../strict-cls\"\n\nconst log = createLogger(\"silvery:cls\")\nconst shiftLog = createLogger(\"silvery:cls-detail\")\n\n/**\n * Opt-in gate. CLS instrumentation runs ONLY when one of these\n * env vars is set:\n *\n * - `DEBUG=silvery:cls` / `DEBUG=silvery:cls-detail` (or `DEBUG=silvery:*` / `DEBUG=*`)\n * - `SILVERY_INSTRUMENT=cls`\n *\n * Without the gate, even `warn`-level output stays silent — the\n * instrumentation produces too much noise in synthetic test\n * harnesses (createRenderer fixtures often have transient\n * zero-rect frames during initial measurement) to be safely\n * always-on. Phase 2 of the bead will tier specific checks back\n * to always-on once their false-positive rate is characterised.\n *\n * Bead: @km/silvery/layout-shift-instrumentation-cls.\n */\nfunction clsEnabled(): boolean {\n if (process.env.SILVERY_INSTRUMENT === \"cls\") return true\n const debug = process.env.DEBUG ?? \"\"\n if (!debug) return false\n return /(^|,)\\s*(silvery:cls|silvery:cls-detail|silvery:\\*|\\*)\\s*(,|$)/.test(debug)\n}\n\nconst STORM_PATH_WINDOW_MS = 500\nconst STORM_PATH_THRESHOLD = 3\nconst STORM_COMMIT_THRESHOLD = 5\n\n/** Data-* attributes treated as identity tokens for CLS path resolution.\n * Any component carrying one of these in its props is named in the\n * layout-shift log even without an explicit `id` / `data-testid`. The\n * list is intentionally broad so anonymous app-level boxes (cards,\n * chat blocks, code containers, table grids) show up by role instead\n * of collapsing to a generic \"box\" entry. */\nconst IDENTITY_DATA_KEYS = [\n \"data-view\",\n \"data-block\",\n \"data-component\",\n \"data-card-id\",\n \"data-pane\",\n \"data-role\",\n] as const\n\ninterface ShiftRecord {\n path: string\n prev: Rect | null\n next: Rect | null\n /** Milliseconds since the previous shift for this path; null for the\n * first observed shift. Included in `silvery:cls` debug logs so quick\n * reflows can be diagnosed without reconstructing timing offline. */\n dtMs: number | null\n}\n\ninterface PathHistory {\n /** Wall-clock timestamps of recent shifts for this path. Trimmed to the\n * most recent `STORM_PATH_WINDOW_MS` on every probe. */\n ts: number[]\n /** Set of (kind, key) sentinels already fired — once-per-pair to avoid\n * spamming. Reset when the path settles (no shift for > 2s). */\n firedKinds: Set<string>\n /** Most recent shift timestamp for this path, used for per-shift dt logs. */\n lastShiftAt: number | null\n /** True while the current timestamp window is already above the storm threshold. */\n inStorm: boolean\n}\n\nexport interface ClsMonitor {\n /**\n * Run the post-commit walk. Call once per `paintFrame` boundary in\n * the runtime, after layout has settled.\n *\n * @param root The reconciler container's root AgNode.\n * @param cols Terminal columns (for overflow checks).\n * @param rows Terminal rows (for overflow checks).\n * @param scrollOrResize When true, suppresses shift logs from this\n * commit — scroll/resize-driven motion isn't a\n * layout-stability bug. Size sentinels still\n * run.\n */\n onCommit(root: AgNode | null, cols: number, rows: number, scrollOrResize: boolean): void\n\n /** Drop accumulated path history. Tests and dimension changes use\n * this to start with a clean storm-detector state. */\n reset(): void\n\n /**\n * Start a CLS capture window. Subsequent `onCommit` calls accumulate\n * layout shifts (post-scroll `screenRect` transitions) into a session\n * buffer. The classifier labels each shift with a ReflowReason; default\n * classifier marks every shift \"unexpected\" (most pessimistic — surfaces\n * every shift). Throws on double-begin. While capturing, the monitor's\n * walk runs even without `DEBUG=silvery:cls` / `SILVERY_INSTRUMENT=cls`.\n *\n * Bead: @km/silvery/cls-instrumentation-primitive (Phase 8 — Option C consolidation).\n */\n beginCapture(classifier?: ReasonClassifier): void\n\n /**\n * End the active capture window, return aggregated `CLSReport`, reset\n * session state. Throws when not capturing. Under `SILVERY_STRICT=cls`\n * (tier 2 by default), throws `UnexpectedLayoutShiftError` when any\n * shift in the window was labeled \"unexpected\".\n */\n endCapture(): CLSReport\n\n /**\n * Cancel the active capture without producing a report. Idempotent —\n * safe to call when no capture is active (cleanup paths, error\n * recovery).\n */\n cancelCapture(): void\n}\n\nexport function createClsMonitor(): ClsMonitor {\n const envEnabled = clsEnabled()\n const pathHistory = new Map<string, PathHistory>()\n let prevCols = 0\n let prevRows = 0\n\n // Capture-window state. Independent of the env-gate so test-time\n // capture works without DEBUG=silvery:cls. When `capturing === true`\n // the walk runs even when `envEnabled === false`, and per-commit\n // shifts are pushed into `sessionShifts` for `endCapture()` to\n // aggregate into a `CLSReport`.\n let capturing = false\n let activeClassifier: ReasonClassifier = defaultClassifier\n let sessionShifts: LayoutShift[] = []\n\n function ensureHistory(path: string): PathHistory {\n let h = pathHistory.get(path)\n if (h === undefined) {\n h = { ts: [], firedKinds: new Set(), lastShiftAt: null, inStorm: false }\n pathHistory.set(path, h)\n }\n return h\n }\n\n function rectEqual(a: Rect | null, b: Rect | null): boolean {\n if (a === b) return true\n if (a === null || b === null) return false\n return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height\n }\n\n function nodePath(node: AgNode): string {\n // CSS-selector-style identity chain. Walk ancestors, keep ONLY\n // nodes carrying a recognized identity prop (rendered as `#name`\n // or `[data-*=value]`); the unidentified intermediate\n // `silvery-box` chains drop out. The leaf always renders even\n // if untagged (as its bare type minus the `silvery-` prefix).\n // Joins with ` > ` like a CSS descendant selector. Bead:\n // @km/silvery/layout-shift-instrumentation-cls user feedback\n // 2026-05-11: paths were \"silvery-root/silvery-box/silvery-box\n // #main/...\" which is the full-chain — switch to \"#main >\n // #bottom-bar > ... > text#watcher-status\".\n //\n // Identity hierarchy (first non-empty wins):\n // 1. `id` → `#name`\n // 2. `data-testid` → `#name` (test identity)\n // 3. `data-view` → `[data-view=name]` (km-tui board roles)\n // 4. `data-block` / `-component` / `-card-id` → `[data-...=name]`\n //\n // When none present, the node is treated as a transparent\n // wrapper and skipped (unless it's the leaf).\n const parts: string[] = []\n let n: AgNode | null = node\n let depth = 0\n let isLeaf = true\n while (n && depth < 20) {\n const tag = identityTag(n)\n if (tag !== null) {\n const typeTag = n.type === \"silvery-box\" ? \"\" : `${typeShort(n.type)}`\n parts.push(`${typeTag}${tag}`)\n } else if (isLeaf) {\n // Leaf without id — emit the bare type so the log still\n // names what shifted.\n parts.push(typeShort(n.type))\n }\n // Else: untagged intermediate — skip.\n isLeaf = false\n n = n.parent\n depth++\n }\n return parts.reverse().join(\" > \")\n }\n\n /** Resolve a node's identity tag for CLS paths. Returns null when no\n * recognised identity prop is set — the node is then treated as a\n * transparent wrapper. */\n function identityTag(node: AgNode): string | null {\n const props = node.props as Record<string, unknown>\n const id = props.id\n if (typeof id === \"string\" && id.length > 0) return `#${id}`\n const testid = props[\"data-testid\"]\n if (typeof testid === \"string\" && testid.length > 0) return `#${testid}`\n // Common data-* identity props from km/silvercode/silvery apps. Anything\n // that names the component's role qualifies — we treat them all as\n // equivalent identity sources so anonymous boxes wearing a `data-block`\n // (or similar) get a real path token. The selector form ([data-x=y])\n // matches CSS for the read-back-to-code experience.\n for (const key of IDENTITY_DATA_KEYS) {\n const v = props[key]\n if (typeof v === \"string\" && v.length > 0) return `[${key}=${v}]`\n }\n return null\n }\n\n function typeShort(type: string): string {\n // Drop the `silvery-` prefix when present: `silvery-text` → `text`,\n // `silvery-box` → `box`, `silvery-root` → `root`.\n return type.startsWith(\"silvery-\") ? type.slice(\"silvery-\".length) : type\n }\n\n function recordShift(rec: ShiftRecord, now: number): boolean {\n const h = ensureHistory(rec.path)\n rec.dtMs = h.lastShiftAt === null ? null : now - h.lastShiftAt\n h.lastShiftAt = now\n h.ts.push(now)\n // Trim to window.\n while (h.ts.length > 0 && h.ts[0]! < now - STORM_PATH_WINDOW_MS) h.ts.shift()\n const inStorm = h.ts.length >= STORM_PATH_THRESHOLD\n const justEnteredStorm = inStorm && !h.inStorm\n h.inStorm = inStorm\n return justEnteredStorm\n }\n\n function fireOnce(path: string, key: string): boolean {\n const h = ensureHistory(path)\n if (h.firedKinds.has(key)) return false\n h.firedKinds.add(key)\n return true\n }\n\n function checkSizeInvariants(node: AgNode, cols: number, rows: number): void {\n const rect = node.screenRect\n if (!rect) return\n // silvery-text nodes inherit layout from their parent silvery-box.\n // Virtual inline text children (Text-in-Text) legitimately have\n // {0,0,0,0} rects — they don't own layout. Skip text nodes; their\n // owning container will fire if it's actually degenerate.\n if (node.type === \"silvery-text\") return\n const path = nodePath(node)\n if (rect.x < 0 || rect.y < 0 || rect.width < 0 || rect.height < 0) {\n if (fireOnce(path, \"negative\")) {\n log.warn?.(\"negative-rect\", { path, rect })\n }\n }\n if (rect.width > cols || rect.height > rows) {\n if (fireOnce(path, \"overflow\")) {\n log.warn?.(\"rect-overflows-terminal\", { path, rect, cols, rows })\n }\n }\n // Zero-size with visible content: only flag when the node clearly\n // intends to render (has children with text, or has an explicit bg).\n if ((rect.width === 0 || rect.height === 0) && hasVisibleContent(node)) {\n if (fireOnce(path, \"zero-area-with-content\")) {\n log.warn?.(\"zero-area-with-content\", { path, rect })\n }\n }\n }\n\n function hasVisibleContent(node: AgNode): boolean {\n if (node.type === \"silvery-text\") return true\n const props = node.props as { backgroundColor?: unknown; borderStyle?: unknown }\n if (props.backgroundColor !== undefined || props.borderStyle !== undefined) return true\n for (const child of node.children) {\n if (hasVisibleContent(child)) return true\n }\n return false\n }\n\n function walk(\n node: AgNode,\n cols: number,\n rows: number,\n suppressShift: boolean,\n commitShifts: ShiftRecord[],\n ): void {\n if (!node.screenRect) {\n for (const child of node.children) walk(child, cols, rows, suppressShift, commitShifts)\n return\n }\n // Size-invariant WARN logging is a production-diagnostic side effect\n // gated by env (DEBUG=silvery:cls / SILVERY_INSTRUMENT=cls). Test-time\n // capture must walk silently — surfacing `silvery:cls` WARNs during a\n // capture window would fail consumers that treat any console output\n // as a test error (e.g. silvercode's vitest setup). Bead:\n // @km/silvery/cls-instrumentation-primitive — first-consumer broke\n // post-Option-C until this gate landed.\n if (envEnabled) checkSizeInvariants(node, cols, rows)\n if (!suppressShift) {\n const prev = node.prevScreenRect\n const next = node.screenRect\n if (prev !== null && !rectEqual(prev, next)) {\n // First-time-measurement transitions don't count as shifts —\n // those are the deferred-only useBoxRect contract working as\n // designed (0,0 seed → real rect on next commit).\n const isFirstMeasure =\n prev.width === 0 && prev.height === 0 && next.width > 0 && next.height > 0\n // Virtual inline text children always have {0,0,0,0} or\n // size-zero rects (they don't own layout — they inherit\n // position from their parent silvery-box). Their \"shifts\"\n // are just propagation noise from the real parent shift.\n const isInlineTextNoise =\n node.type === \"silvery-text\" &&\n (prev.width === 0 || prev.height === 0) &&\n (next.width === 0 || next.height === 0)\n if (!isFirstMeasure && !isInlineTextNoise) {\n commitShifts.push({ path: nodePath(node), prev, next, dtMs: null })\n }\n }\n }\n for (const child of node.children) walk(child, cols, rows, suppressShift, commitShifts)\n }\n\n function onCommit(\n root: AgNode | null,\n cols: number,\n rows: number,\n scrollOrResize: boolean,\n ): void {\n // Dimension tracking is metadata, not gated. Tracking prevCols/rows on\n // every commit (regardless of envEnabled/capturing) means a capture\n // started mid-session can use the established baseline instead of\n // treating its first commit as a first-paint resize. Two field\n // assignments per commit when nobody's listening — negligible.\n const resized = cols !== prevCols || rows !== prevRows\n const suppressShift = scrollOrResize || resized || prevCols === 0 || prevRows === 0\n prevCols = cols\n prevRows = rows\n\n // Cheap gate: opt-in via DEBUG=silvery:cls or SILVERY_INSTRUMENT=cls,\n // OR an active test-time capture window. Outside both, this is a\n // single boolean compare per commit — zero overhead.\n if (!envEnabled && !capturing) return\n if (!root) return\n\n const commitShifts: ShiftRecord[] = []\n walk(root, cols, rows, suppressShift, commitShifts)\n\n // Capture path: stash shifts into the active session for endCapture()\n // to aggregate. Skipped when suppressShift is true — scroll/resize\n // motion is user-action, not an unexpected layout shift. Classifier\n // still sees every non-suppressed shift and may override the default\n // \"unexpected\" label per-shift (e.g. label streamed chat reflow as\n // content-arrival).\n if (capturing && !suppressShift && commitShifts.length > 0) {\n const ts = Date.now()\n for (const rec of commitShifts) {\n if (rec.prev === null || rec.next === null) continue\n const reason = activeClassifier(rec.path, rec.prev, rec.next, ts)\n const shift = makeShift(rec.path, rec.prev, rec.next, ts, reason)\n if (shift !== null) sessionShifts.push(shift)\n }\n }\n\n // Production path: storm-detection + silvery:cls logging. Only runs\n // when env-gated — test-time captures don't pollute the storm history\n // (independent buffers, independent thresholds).\n if (!envEnabled) return\n if (commitShifts.length === 0) return\n\n const now = performance.now()\n let stormPaths = 0\n for (const rec of commitShifts) {\n const stormForPath = recordShift(rec, now)\n if (stormForPath) stormPaths++\n shiftLog.debug?.(\"shift\", { path: rec.path, prev: rec.prev, next: rec.next, dtMs: rec.dtMs })\n }\n log.debug?.(\"shift-count\", {\n shifts: commitShifts.length,\n firstPaths: commitShifts.slice(0, 5).map((r) => r.path),\n })\n if (stormPaths > 0) {\n log.warn?.(\"reflow-storm-per-path\", {\n stormPaths,\n windowMs: STORM_PATH_WINDOW_MS,\n threshold: STORM_PATH_THRESHOLD,\n })\n }\n if (commitShifts.length >= STORM_COMMIT_THRESHOLD) {\n log.warn?.(\"reflow-storm-per-commit\", {\n shifts: commitShifts.length,\n threshold: STORM_COMMIT_THRESHOLD,\n firstPaths: commitShifts.slice(0, 5).map((r) => r.path),\n })\n }\n }\n\n function reset(): void {\n pathHistory.clear()\n prevCols = 0\n prevRows = 0\n }\n\n function beginCapture(classifier?: ReasonClassifier): void {\n if (capturing) {\n throw new Error(\n \"ClsMonitor.beginCapture: already capturing. Call endCapture() or cancelCapture() before starting a new window.\",\n )\n }\n capturing = true\n activeClassifier = classifier ?? defaultClassifier\n sessionShifts = []\n }\n\n function endCapture(): CLSReport {\n if (!capturing) {\n throw new Error(\"ClsMonitor.endCapture: not capturing. Call beginCapture() first.\")\n }\n const report = aggregateReport(sessionShifts)\n capturing = false\n activeClassifier = defaultClassifier\n sessionShifts = []\n // Honors SILVERY_STRICT=cls — throws UnexpectedLayoutShiftError when\n // tier 2 strict is active and the window saw any \"unexpected\" shifts.\n // No-op when strict isn't engaged.\n assertNoUnexpectedShifts(report)\n return report\n }\n\n function cancelCapture(): void {\n capturing = false\n activeClassifier = defaultClassifier\n sessionShifts = []\n }\n\n return { onCommit, reset, beginCapture, endCapture, cancelCapture }\n}\n","/**\n * chain-bridge — utilities for installing an apply-chain on a non-root\n * runtime (render.tsx's Silvery instance and InputBoundary's isolated\n * scope).\n *\n * createApp()'s `create-app.tsx` owns the canonical chain plumbing. The\n * helpers here let callers that don't go through createApp still expose\n * a {@link ChainAppContextValue} to their children so the ag-react\n * hooks (useInput / useModifierKeys / useTerminalFocused / usePaste*)\n * can subscribe through a single unified surface.\n *\n * The child BaseApp installs the same four plugins as create-app.tsx:\n *\n * pipe(createBaseApp(),\n * withTerminalChain,\n * withPasteChain,\n * withInputChain,\n * withFocusChain)\n *\n * withFocusChain is wired with a no-op dispatcher — the isolated scope\n * doesn't own a focus manager, so focused dispatch simply returns false\n * and keys flow straight to the input store.\n */\n\nimport type { Key } from \"@silvery/ag/keys\"\nimport {\n createBaseApp,\n withCustomEvents,\n withFocusChain,\n withInputChain,\n withPasteChain,\n withTerminalChain,\n type BaseApp,\n type CustomEventStore,\n type InputStore,\n type PasteStore,\n type TerminalStore,\n} from \"@silvery/create/plugins\"\nimport type {\n ChainAppContextValue,\n ChainFocusEvents,\n ChainFocusHandler,\n ChainRawKeyHandler,\n ChainRawKeyObserver,\n} from \"./context\"\n\n/**\n * BaseApp enhanced with the four chain plugins plus the raw-key observer\n * and focus-event slices that complete the {@link ChainAppContextValue}\n * surface.\n */\nexport interface ChildApp extends BaseApp {\n readonly input: InputStore\n readonly paste: PasteStore\n readonly terminal: TerminalStore\n readonly events: CustomEventStore\n readonly rawKeys: ChainRawKeyObserver & {\n /** Fire all registered raw-key observers. */\n notify(input: string, key: Key): void\n }\n readonly focusEvents: ChainFocusEvents & {\n /** Fire all registered focus-event observers. */\n notify(focused: boolean): void\n }\n}\n\n/**\n * Build a non-root BaseApp with the canonical plugin chain.\n *\n * The returned app is self-contained: ops dispatched through it don't\n * reach the root runtime. Callers own the lifecycle — there's no exit /\n * render effect handler wired up; effects are drained and discarded.\n */\nexport function createChildApp(): ChildApp {\n const base = createBaseApp()\n const terminal = withTerminalChain()(base)\n const paste = withPasteChain()(terminal)\n const input = withInputChain(paste)\n const focus = withFocusChain({\n dispatchKey: () => false,\n hasActiveFocus: () => false,\n })(input)\n const events = withCustomEvents(focus)\n\n // Raw-key observer slice — unfiltered access to every dispatched key\n // (used by useModifierKeys which needs release + modifier-only events).\n const rawKeyListeners: Array<(input: string, key: Key) => void> = []\n const rawKeys = {\n register(handler: ChainRawKeyHandler): () => void {\n rawKeyListeners.push(handler as (input: string, key: Key) => void)\n return () => {\n const i = rawKeyListeners.indexOf(handler as (input: string, key: Key) => void)\n if (i >= 0) rawKeyListeners.splice(i, 1)\n }\n },\n notify(input: string, key: Key): void {\n for (const h of rawKeyListeners) h(input, key)\n },\n }\n\n // Focus-events slice — isolated scopes typically don't emit focus\n // events, but useTerminalFocused still expects a register() that\n // returns a clean unsubscribe.\n const focusListeners: Array<ChainFocusHandler> = []\n const focusEvents = {\n register(handler: ChainFocusHandler): () => void {\n focusListeners.push(handler)\n return () => {\n const i = focusListeners.indexOf(handler)\n if (i >= 0) focusListeners.splice(i, 1)\n }\n },\n notify(focused: boolean): void {\n for (const h of focusListeners) h(focused)\n },\n }\n\n return Object.assign(events, { rawKeys, focusEvents }) as ChildApp\n}\n\n/**\n * Project a {@link ChildApp} to the surface exposed on\n * {@link ChainAppContextValue}.\n */\nexport function toChainAppContextValue(app: ChildApp): ChainAppContextValue {\n return {\n input: app.input,\n paste: app.paste,\n focusEvents: app.focusEvents,\n rawKeys: app.rawKeys,\n events: app.events,\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chain-bridge-B7LNJwVF.mjs","names":["isModifierOnly","NS","FRAME_HISTORY","PANIC_COOLDOWN_MS","EVICT_OLDER_THAN_MS","defaultWriteFile","NS","log"],"sources":["../packages/ag/src/focus-events.ts","../packages/create/src/pipe.ts","../packages/create/src/internal/capability-registry.ts","../packages/create/src/internal/capabilities.ts","../packages/ag-term/src/features/selection.ts","../packages/create/src/runtime/with-terminal-chain.ts","../packages/create/src/runtime/with-focus-chain.ts","../packages/commands/src/with-commands.ts","../packages/commands/src/with-keybindings.ts","../packages/test/src/compare-buffers.ts","../packages/ag-term/src/plugins/with-diagnostics.ts","../packages/ag-term/src/plugins/with-render.ts","../packages/create/src/runtime/base-app.ts","../packages/ink/src/with-ink-cursor.ts","../packages/ink/src/with-ink-focus.ts","../packages/ag-term/src/bytes-out-monitor.ts","../packages/ag-term/src/flicker-monitor.ts","../packages/ag-term/src/mem-monitor.ts","../packages/test/src/debug-mismatch.ts","../packages/ag-term/src/managed-caret.ts","../packages/ag-term/src/runtime/render-trace.ts","../packages/ag-term/src/scheduler.ts","../packages/create/src/runtime/with-paste-chain.ts","../packages/create/src/runtime/with-input-chain.ts","../packages/create/src/runtime/with-custom-events.ts","../packages/ag-term/src/runtime/wrap-measurer-registration.ts","../packages/ag-react/src/scope-context.ts","../packages/ag-react/src/ScopeProvider.tsx","../packages/ag/src/cls.ts","../packages/ag-term/src/strict-cls.ts","../packages/ag-term/src/runtime/cls-monitor.ts","../packages/ag-react/src/chain-bridge.ts"],"sourcesContent":["/**\n * DOM-level Focus and Keyboard Events for silvery\n *\n * Provides React DOM-compatible focus/keyboard event infrastructure:\n * - SilveryKeyEvent / SilveryFocusEvent synthetic event objects\n * - Event dispatch with capture/target/bubble phases (key events)\n * - Event dispatch with target + bubble (focus events)\n *\n * Follows the same patterns as mouse-events.ts for consistency.\n */\n\nimport type { Key } from \"./keys\"\nimport { getAncestorPath } from \"./tree-utils.js\"\nimport type { AgNode } from \"./types\"\n\n// ============================================================================\n// Event Types\n// ============================================================================\n\n/**\n * Synthetic keyboard event, mirroring React.KeyboardEvent / DOM KeyboardEvent.\n */\nexport interface SilveryKeyEvent {\n /** The printable character, or \"\" for non-printable keys */\n key: string\n /** Raw terminal input string */\n input: string\n /** Modifier keys */\n ctrl: boolean\n meta: boolean\n shift: boolean\n super: boolean\n hyper: boolean\n /** Kitty event type */\n eventType?: \"press\" | \"repeat\" | \"release\"\n /** Deepest focusable node that received this event */\n target: AgNode\n /** Node whose handler is currently firing (changes during capture/bubble) */\n currentTarget: AgNode\n /** Stop event from propagating further */\n stopPropagation(): void\n /** Prevent default behavior */\n preventDefault(): void\n /** Whether stopPropagation() was called */\n readonly propagationStopped: boolean\n /** Whether preventDefault() was called */\n readonly defaultPrevented: boolean\n /** Raw parsed key data */\n nativeEvent: { input: string; key: Key }\n}\n\n/**\n * Synthetic focus event, mirroring React.FocusEvent / DOM FocusEvent.\n */\nexport interface SilveryFocusEvent {\n /** The node gaining or losing focus */\n target: AgNode\n /** The other node involved (losing focus on 'focus', gaining on 'blur') */\n relatedTarget: AgNode | null\n /** Event type */\n type: \"focus\" | \"blur\"\n /** Node whose handler is currently firing (changes during bubble) */\n currentTarget: AgNode\n /** Stop event from bubbling to parent nodes */\n stopPropagation(): void\n /** Whether stopPropagation() was called */\n readonly propagationStopped: boolean\n}\n\n// ============================================================================\n// Focus Event Handler Props (added to BoxProps)\n// ============================================================================\n\nexport interface FocusEventProps {\n /** Whether this node can receive focus */\n focusable?: boolean\n /** Whether this node should receive focus on mount */\n autoFocus?: boolean\n /** Whether this node creates a focus scope (focus trapping boundary) */\n focusScope?: boolean\n /** ID of the node to focus when pressing Up from this node */\n nextFocusUp?: string\n /** ID of the node to focus when pressing Down from this node */\n nextFocusDown?: string\n /** ID of the node to focus when pressing Left from this node */\n nextFocusLeft?: string\n /** ID of the node to focus when pressing Right from this node */\n nextFocusRight?: string\n /** Called when this node gains focus */\n onFocus?: (event: SilveryFocusEvent) => void\n /** Called when this node loses focus */\n onBlur?: (event: SilveryFocusEvent) => void\n /** Called on key down (bubble phase) */\n onKeyDown?: (event: SilveryKeyEvent, dispatch?: (msg: unknown) => void) => void\n /** Called on key up (bubble phase) */\n onKeyUp?: (event: SilveryKeyEvent, dispatch?: (msg: unknown) => void) => void\n /** Called on key down (capture phase — fires before target) */\n onKeyDownCapture?: (event: SilveryKeyEvent) => void\n}\n\n// ============================================================================\n// Event Factories\n// ============================================================================\n\n/**\n * Create a synthetic keyboard event.\n */\nexport function createKeyEvent(input: string, key: Key, target: AgNode): SilveryKeyEvent {\n let propagationStopped = false\n let defaultPrevented = false\n\n return {\n key: input,\n input,\n ctrl: key.ctrl,\n meta: key.meta,\n shift: key.shift,\n super: key.super,\n hyper: key.hyper,\n eventType: key.eventType,\n target,\n currentTarget: target,\n nativeEvent: { input, key },\n get propagationStopped() {\n return propagationStopped\n },\n get defaultPrevented() {\n return defaultPrevented\n },\n stopPropagation() {\n propagationStopped = true\n },\n preventDefault() {\n defaultPrevented = true\n },\n }\n}\n\n/**\n * Create a synthetic focus event.\n */\nexport function createFocusEvent(\n type: \"focus\" | \"blur\",\n target: AgNode,\n relatedTarget: AgNode | null,\n): SilveryFocusEvent {\n let propagationStopped = false\n\n return {\n type,\n target,\n relatedTarget,\n currentTarget: target,\n get propagationStopped() {\n return propagationStopped\n },\n stopPropagation() {\n propagationStopped = true\n },\n }\n}\n\n// ============================================================================\n// Tree Walking\n// ============================================================================\n\n// ============================================================================\n// Event Dispatch\n// ============================================================================\n\n/**\n * Dispatch a keyboard event through the render tree with DOM-style\n * capture/target/bubble phases.\n *\n * For press/repeat events:\n * 1. Capture phase: root → target (onKeyDownCapture props)\n * 2. Target phase: target's onKeyDown\n * 3. Bubble phase: target parent → root (onKeyDown props)\n *\n * For release events:\n * 1. Target phase: target's onKeyUp\n * 2. Bubble phase: target parent → root (onKeyUp props)\n * (No capture phase for keyUp — deliberate simplification; React DOM has onKeyUpCapture)\n *\n * stopPropagation() halts traversal at any phase.\n */\nexport function dispatchKeyEvent(event: SilveryKeyEvent, dispatch?: (msg: unknown) => void): void {\n const path = getAncestorPath(event.target)\n const mutableEvent = event as { currentTarget: AgNode }\n\n // Release events → onKeyUp (no capture phase — deliberate simplification; React DOM has onKeyUpCapture)\n const isRelease = event.eventType === \"release\"\n const handlerProp = isRelease ? \"onKeyUp\" : \"onKeyDown\"\n\n // Capture phase: root → target (onKeyDownCapture — press/repeat only)\n if (!isRelease) {\n for (let i = path.length - 1; i > 0; i--) {\n if (event.propagationStopped) return\n const node = path[i]!\n const handler = (node.props as Record<string, unknown>).onKeyDownCapture as\n | ((e: SilveryKeyEvent) => void)\n | undefined\n if (handler) {\n mutableEvent.currentTarget = node\n handler(event)\n }\n }\n }\n\n // Target phase\n if (!event.propagationStopped) {\n const target = path[0]!\n mutableEvent.currentTarget = target\n const handler = (target.props as Record<string, unknown>)[handlerProp] as\n | ((e: SilveryKeyEvent, d?: (msg: unknown) => void) => void)\n | undefined\n if (handler) {\n handler(event, dispatch)\n }\n }\n\n // Bubble phase: target parent → root\n for (let i = 1; i < path.length; i++) {\n if (event.propagationStopped) return\n const node = path[i]!\n const handler = (node.props as Record<string, unknown>)[handlerProp] as\n | ((e: SilveryKeyEvent, d?: (msg: unknown) => void) => void)\n | undefined\n if (handler) {\n mutableEvent.currentTarget = node\n handler(event, dispatch)\n }\n }\n}\n\n/**\n * Dispatch a focus event through the render tree.\n *\n * Fires onFocus/onBlur on the target, then bubbles to ancestors.\n */\nexport function dispatchFocusEvent(event: SilveryFocusEvent): void {\n const handlerProp = event.type === \"focus\" ? \"onFocus\" : \"onBlur\"\n const path = getAncestorPath(event.target)\n const mutableEvent = event as { currentTarget: AgNode }\n\n for (const node of path) {\n if (event.propagationStopped) break\n\n const handler = (node.props as Record<string, unknown>)[handlerProp] as\n | ((e: SilveryFocusEvent) => void)\n | undefined\n if (handler) {\n mutableEvent.currentTarget = node\n handler(event)\n }\n }\n}\n","/**\n * pipe() — Compose app plugins left-to-right.\n *\n * The foundational composition function for silvery's plugin system.\n * Each plugin is a function `(app) => enhancedApp` that takes an app object\n * and returns an enhanced version with additional capabilities.\n *\n * Plugins compose left-to-right: `pipe(base, p1, p2)` = `p2(p1(base))`\n *\n * @example\n * ```tsx\n * import { pipe, createApp, withReact, withTerminal, withFocus, withDomEvents } from '@silvery/create'\n *\n * const app = pipe(\n * createApp(store),\n * withReact(<Board />),\n * withTerminal(process),\n * withFocus(),\n * withDomEvents(),\n * )\n * await app.run()\n * ```\n *\n * @example Typed plugin\n * ```tsx\n * type MyPlugin = (app: App) => App & { custom: () => void }\n *\n * const withCustom: MyPlugin = (app) => ({\n * ...app,\n * custom: () => console.log('hello'),\n * })\n *\n * const enhanced = pipe(baseApp, withCustom)\n * enhanced.custom() // typed!\n * ```\n */\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * A plugin function that enhances an app.\n *\n * Takes an app of type A and returns an enhanced app of type B.\n * The plugin can add new methods, override existing ones, or\n * wrap behavior via closures.\n */\nexport type AppPlugin<A, B> = (app: A) => B\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/**\n * Compose app plugins left-to-right.\n *\n * `pipe(base, p1, p2, p3)` is equivalent to `p3(p2(p1(base)))`.\n *\n * Each plugin receives the result of the previous plugin, allowing\n * progressive enhancement of the app object.\n *\n * Type inference works through the chain: if p1 adds `.cmd` and p2\n * requires it, TypeScript catches the error at the call site.\n */\nexport function pipe<A>(base: A): A\nexport function pipe<A, B>(base: A, p1: AppPlugin<A, B>): B\nexport function pipe<A, B, C>(base: A, p1: AppPlugin<A, B>, p2: AppPlugin<B, C>): C\nexport function pipe<A, B, C, D>(\n base: A,\n p1: AppPlugin<A, B>,\n p2: AppPlugin<B, C>,\n p3: AppPlugin<C, D>,\n): D\nexport function pipe<A, B, C, D, E>(\n base: A,\n p1: AppPlugin<A, B>,\n p2: AppPlugin<B, C>,\n p3: AppPlugin<C, D>,\n p4: AppPlugin<D, E>,\n): E\nexport function pipe<A, B, C, D, E, F>(\n base: A,\n p1: AppPlugin<A, B>,\n p2: AppPlugin<B, C>,\n p3: AppPlugin<C, D>,\n p4: AppPlugin<D, E>,\n p5: AppPlugin<E, F>,\n): F\nexport function pipe<A, B, C, D, E, F, G>(\n base: A,\n p1: AppPlugin<A, B>,\n p2: AppPlugin<B, C>,\n p3: AppPlugin<C, D>,\n p4: AppPlugin<D, E>,\n p5: AppPlugin<E, F>,\n p6: AppPlugin<F, G>,\n): G\nexport function pipe<A, B, C, D, E, F, G, H>(\n base: A,\n p1: AppPlugin<A, B>,\n p2: AppPlugin<B, C>,\n p3: AppPlugin<C, D>,\n p4: AppPlugin<D, E>,\n p5: AppPlugin<E, F>,\n p6: AppPlugin<F, G>,\n p7: AppPlugin<G, H>,\n): H\nexport function pipe(base: unknown, ...plugins: AppPlugin<unknown, unknown>[]): unknown {\n let result = base\n for (const plugin of plugins) {\n result = plugin(result)\n }\n return result\n}\n","/**\n * Capability Registry — symbol-keyed service registry for cross-feature communication.\n *\n * Each createApp instance gets its own registry. Interaction features use it to\n * discover each other (e.g., selection looks up clipboard capability).\n *\n * @internal Not exported from the public barrel.\n */\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/** Symbol-keyed service registry for cross-feature communication. */\nexport interface CapabilityRegistry {\n /** Register a capability. Overwrites any existing registration for the same key. */\n register<T>(key: symbol, capability: T): void\n\n /** Look up a capability. Returns undefined if not registered. */\n get<T>(key: symbol): T | undefined\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/** Create a new capability registry (one per app instance). */\nexport function createCapabilityRegistry(): CapabilityRegistry {\n const capabilities = new Map<symbol, unknown>()\n\n return {\n register<T>(key: symbol, capability: T): void {\n capabilities.set(key, capability)\n },\n\n get<T>(key: symbol): T | undefined {\n return capabilities.get(key) as T | undefined\n },\n }\n}\n","/**\n * Capability symbols — well-known keys for the CapabilityRegistry.\n *\n * Features register themselves under these symbols so other parts\n * of the composition chain can discover and interact with them.\n *\n * @internal Not exported from the public barrel.\n */\n\n/** Selection feature: text selection state + mouse handling. */\nexport const SELECTION_CAPABILITY = Symbol.for(\"silvery.selection\")\n\n/** Clipboard feature: copy/paste via OSC 52 or other backends. */\nexport const CLIPBOARD_CAPABILITY = Symbol.for(\"silvery.clipboard\")\n\n/** Copy-mode feature: keyboard-driven selection (Esc+v). */\nexport const COPY_MODE_CAPABILITY = Symbol.for(\"silvery.copy-mode\")\n\n/** Find feature: text search (Ctrl+F). */\nexport const FIND_CAPABILITY = Symbol.for(\"silvery.find\")\n\n/** Drag feature: drag-and-drop state + mouse handling. */\nexport const DRAG_CAPABILITY = Symbol.for(\"silvery.drag\")\n\n/** Input router: priority-based event dispatch for interaction features. */\nexport const INPUT_ROUTER = Symbol.for(\"silvery.input-router\")\n\n/** Color scheme detector: Mode 2031 dark/light reactive detection. */\nexport const COLOR_SCHEME_CAPABILITY = Symbol.for(\"silvery.color-scheme\")\n","/**\n * SelectionFeature — service wrapping the headless selection machine.\n *\n * Connects the pure `terminalSelectionUpdate` state machine to ag-term's\n * buffer for text extraction, clipboard for copy-on-select, and the\n * runtime's invalidate callback for render triggering.\n *\n * Mouse event handling:\n * - mousedown → start selection (character granularity)\n * - mousemove while selecting → extend selection range\n * - mouseup → finish selection, copy to clipboard if available\n *\n * run() exposes its inline selection owner through a bridge registered in the\n * CapabilityRegistry under SELECTION_CAPABILITY. Explicit/headless composition\n * can still create a standalone feature for copy mode.\n */\n\nimport {\n terminalSelectionUpdate,\n createTerminalSelectionState,\n extractText,\n type TerminalSelectionState,\n type SelectionRange,\n type SelectionEffect,\n} from \"@silvery/headless/selection\"\nimport type { TerminalBuffer } from \"../buffer\"\nimport type { ClipboardCapability } from \"./clipboard-capability\"\nimport { extractHtml } from \"../extract-html\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Observable selection state + mouse handlers. */\nexport interface SelectionFeature {\n /** Current selection state (getter). */\n readonly state: TerminalSelectionState\n\n /** Subscribe to state changes. Returns an unsubscribe function. */\n subscribe(listener: () => void): () => void\n\n /** Handle mouse down — start selection. */\n handleMouseDown(col: number, row: number, altKey: boolean): void\n\n /** Handle mouse move — extend selection while dragging. */\n handleMouseMove(col: number, row: number): void\n\n /** Handle mouse up — finish selection, trigger copy. */\n handleMouseUp(col: number, row: number): void\n\n /** Programmatically set a selection range (or null to clear). */\n setRange(range: SelectionRange | null): void\n\n /** Clear the current selection. */\n clear(): void\n\n /**\n * Copy the current selection to the clipboard, using the same extraction +\n * write path as mouse/drag copy. Drives keyboard copy-mode `yank`. No-op when\n * there is no selection, buffer, or clipboard.\n */\n copySelection(): void\n\n /** Clean up resources. */\n dispose(): void\n}\n\n/**\n * Options for creating a bridge SelectionFeature that delegates to\n * an external state owner (e.g., create-app's inline selection state).\n */\nexport interface SelectionBridgeOptions {\n /** Get the current selection state. */\n getState: () => TerminalSelectionState\n /** Subscribe to state changes. Returns an unsubscribe function. */\n subscribe: (listener: () => void) => () => void\n /** Set a selection range programmatically (used by copy-mode). */\n setRange: (range: SelectionRange | null) => void\n /** Clear the selection. */\n clear: () => void\n /**\n * Copy the current selection to the clipboard (used by copy-mode `yank`).\n * create-app supplies this to run its inline extract + OSC 52 write — the\n * same path mouse drag-copy uses. Optional: a bridge without it makes\n * `copySelection` a no-op (headless / test usage).\n */\n copy?: () => void\n}\n\n/** Options for creating a SelectionFeature. */\nexport interface SelectionFeatureOptions {\n /** Terminal buffer to extract text from (required for mouse selection / copy). */\n buffer?: TerminalBuffer\n /** Optional clipboard capability for copy-on-select. */\n clipboard?: ClipboardCapability\n /** Callback to trigger a render pass after state changes. */\n invalidate: () => void\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Create a SelectionFeature that wraps the headless selection machine.\n *\n * The feature manages subscriptions, processes effects from the machine,\n * and coordinates with clipboard and render invalidation.\n */\nexport function createSelectionFeature(options: SelectionFeatureOptions): SelectionFeature {\n const { buffer, clipboard, invalidate } = options\n\n let selectionState = createTerminalSelectionState()\n const listeners = new Set<() => void>()\n\n function notifyListeners(): void {\n for (const listener of listeners) {\n listener()\n }\n }\n\n function processEffects(effects: SelectionEffect[], richRange?: SelectionRange | null): void {\n for (const effect of effects) {\n if (effect.type === \"render\") {\n invalidate()\n } else if (effect.type === \"copy\" && clipboard) {\n if (clipboard.copyRich && richRange && buffer) {\n const html = extractHtml(buffer, richRange)\n clipboard.copyRich(effect.text, html)\n } else {\n clipboard.copy(effect.text)\n }\n }\n }\n }\n\n function updateState(\n newState: TerminalSelectionState,\n effects: SelectionEffect[],\n richRange?: SelectionRange | null,\n ): void {\n selectionState = newState\n notifyListeners()\n processEffects(effects, richRange)\n }\n\n return {\n get state(): TerminalSelectionState {\n return selectionState\n },\n\n subscribe(listener: () => void): () => void {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n },\n\n handleMouseDown(col: number, row: number, _altKey: boolean): void {\n const [newState, effects] = terminalSelectionUpdate(\n { type: \"start\", col, row, source: \"mouse\" },\n selectionState,\n )\n updateState(newState, effects)\n },\n\n handleMouseMove(col: number, row: number): void {\n if (!selectionState.selecting) return\n const [newState, effects] = terminalSelectionUpdate(\n { type: \"extend\", col, row, buffer: buffer! },\n selectionState,\n )\n updateState(newState, effects)\n },\n\n handleMouseUp(_col: number, _row: number): void {\n if (!selectionState.selecting) return\n const [newState, effects] = terminalSelectionUpdate({ type: \"finish\" }, selectionState)\n\n // Extract text and copy to clipboard on mouse up\n const copyEffects = [...effects]\n if (newState.range && clipboard && buffer) {\n // Semantic copy is the default (skips non-selectable margins/gutters/\n // padding, matching the highlight). This fallback path has no Shift+drag\n // raw-buffer mode, so it never opts out. rowMetadata joins soft-wrapped\n // rows into their logical line + precise trailing-space trimming.\n const text = extractText(buffer, newState.range, {\n rowMetadata: buffer.getRowMetadataArray(),\n })\n if (text.length > 0) {\n copyEffects.push({ type: \"copy\", text })\n }\n }\n\n updateState(newState, copyEffects, newState.range)\n },\n\n setRange(range: SelectionRange | null): void {\n if (range === null) {\n const [newState, effects] = terminalSelectionUpdate({ type: \"clear\" }, selectionState)\n updateState(newState, effects)\n } else {\n // Set range by starting at anchor and extending to head\n const [startState, startEffects] = terminalSelectionUpdate(\n { type: \"start\", col: range.anchor.col, row: range.anchor.row, source: \"keyboard\" },\n selectionState,\n )\n const [extendState, extendEffects] = terminalSelectionUpdate(\n { type: \"extend\", col: range.head.col, row: range.head.row },\n startState,\n )\n const [finishState, finishEffects] = terminalSelectionUpdate(\n { type: \"finish\" },\n extendState,\n )\n updateState(finishState, [...startEffects, ...extendEffects, ...finishEffects])\n }\n },\n\n clear(): void {\n const [newState, effects] = terminalSelectionUpdate({ type: \"clear\" }, selectionState)\n updateState(newState, effects)\n },\n\n copySelection(): void {\n const range = selectionState.range\n if (!range || !clipboard || !buffer) return\n // Same extraction the mouse-up copy path uses: semantic by default,\n // soft-wrap rejoin + trailing trim via row metadata. Route through the\n // shared processEffects \"copy\" handler so rich (extractHtml) vs plain\n // copy is decided identically to drag-copy.\n const text = extractText(buffer, range, {\n rowMetadata: buffer.getRowMetadataArray(),\n })\n if (text.length > 0) {\n processEffects([{ type: \"copy\", text }], range)\n }\n },\n\n dispose(): void {\n selectionState = createTerminalSelectionState()\n listeners.clear()\n },\n }\n}\n\n// ============================================================================\n// Bridge Implementation\n// ============================================================================\n\n/**\n * Create a bridge SelectionFeature that delegates to an external state owner.\n *\n * Used by create-app to expose its inline selection state to React hooks\n * (useSelection) and copy-mode (which calls setRange/clear) without\n * duplicating the state machine. Mouse handlers are no-ops — the external\n * owner (create-app's event loop) handles mouse events directly.\n */\nexport function createSelectionBridge(options: SelectionBridgeOptions): SelectionFeature {\n return {\n get state(): TerminalSelectionState {\n return options.getState()\n },\n\n subscribe(listener: () => void): () => void {\n return options.subscribe(listener)\n },\n\n // Mouse handlers are no-ops — create-app handles mouse events directly.\n handleMouseDown(_col: number, _row: number, _altKey: boolean): void {},\n handleMouseMove(_col: number, _row: number): void {},\n handleMouseUp(_col: number, _row: number): void {},\n\n setRange(range: SelectionRange | null): void {\n options.setRange(range)\n },\n\n clear(): void {\n options.clear()\n },\n\n copySelection(): void {\n options.copy?.()\n },\n\n dispose(): void {},\n }\n}\n","/**\n * withTerminalChain — apply-chain plugin for terminal observer + lifecycle.\n *\n * This is the substrate counterpart to `@silvery/ag-term/plugins/with-terminal`\n * (which wraps the test-harness `App`). Where that plugin cares about the\n * test-facing handle, this one plugs into the runtime apply chain that\n * `create-app.tsx`'s processEventBatch drives.\n *\n * ## Responsibilities\n *\n * - Observer lane: always update modifier state on `input:key` ops\n * (never consumes; always passes through to the next plugin).\n * - Resize handling: `term:resize` updates the stored dimensions\n * and schedules a `render` effect.\n * - Focus lifecycle: `term:focus` updates `focused` and clears\n * sticky modifiers on blur (a very common source of \"Ctrl stuck\n * down\" bugs after Alt-Tab).\n *\n * ## What this plugin does NOT do\n *\n * - It does NOT intercept key events (observer only).\n * - It does NOT implement raw-mode / alternate-screen / mouse /\n * kitty-keyboard setup — those belong to the terminal provider\n * (`createTermProvider`) that feeds ops *into* the chain.\n * - It does NOT handle Ctrl+C / Ctrl+Z — see\n * `runtime/lifecycle-effects.ts` for those.\n *\n * The separation matters: lifecycle-effects deals with keys that\n * *terminate* the app, while this plugin is a steady-state observer.\n */\n\nimport type { ApplyResult, Effect, Op } from \"../types\"\nimport type { BaseApp } from \"./base-app\"\n\n// ---------------------------------------------------------------------------\n// Store shape\n// ---------------------------------------------------------------------------\n\n/** Snapshot of the modifier state the terminal thinks is currently held. */\nexport interface ModifierState {\n readonly ctrl: boolean\n readonly shift: boolean\n readonly alt: boolean\n readonly meta: boolean\n readonly super: boolean\n readonly hyper: boolean\n}\n\n/** Store slice installed by {@link withTerminalChain}. */\nexport interface TerminalStore {\n /** Columns (from last resize or initial). */\n cols: number\n /** Rows. */\n rows: number\n /** Is the terminal focused right now (OSC 1004 focus reporting)? */\n focused: boolean\n /** Current modifier key state. */\n modifiers: ModifierState\n}\n\n// ---------------------------------------------------------------------------\n// Op payload types (shared with the runner and with other plugins)\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal key shape that withTerminalChain inspects on `input:key` ops.\n *\n * Deliberately structural (not imported from ag-term's `Key`) so this\n * package stays free of terminal imports. Any compatible shape works.\n *\n * `isModifierOnly` is the authoritative modifier-only flag — set by\n * `parseKey()` in @silvery/ag/keys when the key name is one of\n * `leftshift`, `leftctrl`, … (the dedicated modifier-key codepoints in\n * the Kitty keyboard protocol). Heuristics like \"input is empty and\n * some modifier flag is set\" are incorrect — they misclassify\n * Shift+Tab, Shift+Enter, Shift+Arrow, Ctrl+Tab, etc. (input is empty\n * but the event carries a real payload via the `tab` / `return` /\n * `upArrow` / ... flags, not via `input`).\n */\nexport interface KeyShape {\n ctrl?: boolean\n shift?: boolean\n meta?: boolean\n super?: boolean\n hyper?: boolean\n alt?: boolean\n eventType?: \"press\" | \"repeat\" | \"release\" | undefined\n /**\n * True iff the key itself is a modifier key (Shift, Ctrl, …) pressed\n * in isolation. Authoritative — do NOT derive from `input === \"\"` +\n * modifier flags; those are simultaneously true for e.g. Shift+Tab.\n */\n isModifierOnly?: boolean\n}\n\n/** Options accepted by {@link withTerminalChain}. */\nexport interface WithTerminalChainOptions {\n /** Initial columns. Default: 80. */\n cols?: number\n /** Initial rows. Default: 24. */\n rows?: number\n /** Initial focused state. Default: true. */\n focused?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\n/**\n * Install the terminal observer + lifecycle plugin.\n *\n * The returned app exposes the terminal slice at `app.terminal`.\n * Plugins upstream (last in `pipe()`) see ops first; this plugin\n * should go near the BASE of the chain so focused/useInput plugins\n * handle actual key content before the terminal observer peeks at\n * modifier state.\n */\nexport function withTerminalChain(\n options: WithTerminalChainOptions = {},\n): <A extends BaseApp>(app: A) => A & { terminal: TerminalStore } {\n return <A extends BaseApp>(app: A): A & { terminal: TerminalStore } => {\n const store: TerminalStore = {\n cols: options.cols ?? 80,\n rows: options.rows ?? 24,\n focused: options.focused ?? true,\n modifiers: {\n ctrl: false,\n shift: false,\n alt: false,\n meta: false,\n super: false,\n hyper: false,\n },\n }\n const prev = app.apply\n app.apply = (op: Op): ApplyResult => {\n if (op.type === \"input:key\") {\n // Observer lane: peek the modifiers, never consume.\n const key = (op as { key?: KeyShape }).key\n if (key) {\n store.modifiers = {\n ctrl: !!key.ctrl,\n shift: !!key.shift,\n alt: !!key.alt || !!key.meta,\n meta: !!key.meta,\n super: !!key.super,\n hyper: !!key.hyper,\n }\n }\n return prev(op)\n }\n if (op.type === \"term:resize\") {\n const cols = (op as { cols?: number }).cols\n const rows = (op as { rows?: number }).rows\n if (typeof cols === \"number\") store.cols = cols\n if (typeof rows === \"number\") store.rows = rows\n const effects: Effect[] = [{ type: \"render\" }]\n // Chain downstream so anyone else (e.g. layout-aware plugins)\n // can also react.\n const downstream = prev(op)\n if (downstream !== false) effects.push(...downstream)\n return effects\n }\n if (op.type === \"term:focus\") {\n const focused = !!(op as { focused?: boolean }).focused\n store.focused = focused\n if (!focused) {\n // Clear sticky modifiers on blur so a stuck Ctrl from a\n // previous window doesn't bleed into the next session.\n store.modifiers = {\n ctrl: false,\n shift: false,\n alt: false,\n meta: false,\n super: false,\n hyper: false,\n }\n }\n return []\n }\n return prev(op)\n }\n return Object.assign(app, { terminal: store })\n }\n}\n","/**\n * withFocusChain — apply-chain plugin for focused-element key dispatch.\n *\n * This is the production form of the v1r prototype's `withFocus` plugin:\n * it owns the \"focused lane\" of the input pipeline. When the app has an\n * active focus target, keys go to it FIRST. If the target handles the\n * key (stopPropagation / preventDefault / handler returned `true`), we\n * signal \"handled\" to the chain and downstream plugins (`useInput`) skip\n * the event.\n *\n * ## Relation to `@silvery/ag-term/plugins/with-focus`\n *\n * The existing `withFocus` in ag-term wraps the *test harness* `App`\n * (`app.press()`). It's a higher-level construct that drives the same\n * runtime bits via a proxy around `press`.\n *\n * `withFocusChain` is the lower-level substrate: it plugs into the\n * runtime's apply chain so `processEventBatch` can replace its\n * ad-hoc `handleFocusNavigation + runtimeInputListeners` loop with a\n * single `app.dispatch({type:\"input:key\", ...})`.\n *\n * The two layer and stack:\n *\n * - ag-term/plugins/with-focus (test/harness) ──────────────┐\n * │\n * - runtime/with-focus-chain (apply chain) ◀──────────────┘\n *\n * ## Options\n *\n * The production runtime already has a rich `createFocusManager`. Rather\n * than re-implement it here, withFocusChain accepts a pluggable\n * `dispatchKey` function — typically `dispatchKeyEvent(createKeyEvent(...),\n * focusManager.activeElement)` or equivalent. It MUST return a boolean:\n *\n * - `true` — the focused tree consumed the key (stopPropagation)\n * - `false` — the focused tree didn't handle it; fall through\n *\n * This dependency-injection style keeps @silvery/create free of\n * terminal-specific imports while still letting create-app.tsx wire\n * in the real focus manager.\n */\n\nimport type { ApplyResult, Op } from \"../types\"\nimport type { BaseApp } from \"./base-app\"\nimport type { KeyShape } from \"./with-terminal-chain\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Caller-supplied: decide whether the focused tree consumed the key.\n *\n * Must return `true` iff the focused node's `onKeyDown` handler\n * invoked `stopPropagation()` / `preventDefault()` — identical to the\n * current `handleFocusNavigation` return.\n */\nexport type FocusKeyDispatch = (input: string, key: KeyShape) => boolean\n\n/**\n * Caller-supplied: is there an active focus target right now?\n *\n * Checked up-front so we skip the dispatch entirely when nothing is\n * focused (matches create-app's `if (focusManager.activeElement)`\n * short-circuit).\n */\nexport type HasActiveFocus = () => boolean\n\n/** Options for {@link withFocusChain}. */\nexport interface WithFocusChainOptions {\n dispatchKey: FocusKeyDispatch\n hasActiveFocus: HasActiveFocus\n /**\n * Optional: when true, releasing keys and modifier-only events are\n * forwarded to `dispatchKey` too. Default: false (matches\n * create-app.tsx's pre-refactor behaviour where such events skip\n * focused dispatch).\n */\n dispatchReleaseAndModifierOnly?: boolean\n}\n\n/** Store slice installed by {@link withFocusChain}. */\nexport interface FocusChainStore {\n /** Most recent result from `dispatchKey` (for diagnostics / tests). */\n lastConsumed: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction isModifierOnly(_input: string, key: KeyShape | undefined): boolean {\n // Authoritative flag — set by @silvery/ag/keys `parseKey()` for\n // dedicated modifier-only codepoints. See the longer note in\n // with-input-chain.ts; same bug, same fix.\n return key?.isModifierOnly === true\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\n/**\n * Install the focus-dispatch plugin.\n *\n * Place this plugin OUTERMOST in the input-handling chain so focused\n * targets see the key before the `useInput` fallback store.\n */\nexport function withFocusChain(\n options: WithFocusChainOptions,\n): <A extends BaseApp>(app: A) => A & { focusChain: FocusChainStore } {\n return <A extends BaseApp>(app: A): A & { focusChain: FocusChainStore } => {\n const store: FocusChainStore = { lastConsumed: false }\n const prev = app.apply\n app.apply = (op: Op): ApplyResult => {\n if (op.type !== \"input:key\") return prev(op)\n const input = (op as { input?: string }).input ?? \"\"\n const key = (op as { key?: KeyShape }).key\n const isRelease = key?.eventType === \"release\"\n const modOnly = isModifierOnly(input, key)\n if ((isRelease || modOnly) && !options.dispatchReleaseAndModifierOnly) {\n return prev(op)\n }\n // `dispatchKey` is responsible for both the focused-element event\n // dispatch (which is gated on `hasActiveFocus` inside the caller,\n // typically `handleFocusNavigation`) AND focus-navigation fallbacks\n // like \"Tab focuses the first focusable when nothing is active\".\n // Previously this plugin short-circuited via `!hasActiveFocus()`,\n // which wrongly suppressed the Tab-seeds-first-focus behaviour.\n // `hasActiveFocus` is retained on the options type for callers who\n // still inspect it, but we always invoke `dispatchKey` — the caller\n // decides what to consume.\n let consumed = false\n try {\n consumed = !!options.dispatchKey(input, key ?? ({} as KeyShape))\n } catch (err) {\n // A bad focused handler shouldn't break the event loop.\n // eslint-disable-next-line no-console\n console.error(\"[withFocusChain] dispatchKey threw\", err)\n }\n store.lastConsumed = consumed\n if (consumed) {\n // The focused tree consumed the key. Signal \"handled\" with a\n // render request so the runner repaints. Downstream plugins\n // (useInput fallback) are short-circuited.\n return [{ type: \"render\" }]\n }\n return prev(op)\n }\n return Object.assign(app, { focusChain: store })\n }\n}\n","/**\n * withCommands - SlateJS-style plugin for command system\n *\n * Adds a `cmd` object to the App for direct command invocation with metadata.\n *\n * @example\n * ```tsx\n * const app = withCommands(render(<Board />), {\n * registry: commandRegistry,\n * getContext: () => buildCommandContext(state),\n * handleAction: (action) => dispatch(action),\n * getKeybindings: () => keybindings,\n * })\n *\n * // Direct command invocation\n * await app.cmd.down()\n * await app.cmd['cursor_down']()\n *\n * // Command metadata\n * app.cmd.down.id // 'cursor_down'\n * app.cmd.down.name // 'Move Down'\n * app.cmd.down.help // 'Move cursor down'\n * app.cmd.down.keys // ['j', 'ArrowDown']\n *\n * // Introspection\n * app.cmd.all() // All commands with metadata\n * app.getState() // { screen, commands, focus } for AI\n * ```\n *\n * See docs/future/silvery-command-api-research.md for design rationale.\n */\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Minimal app interface required by the command system.\n * The full App type (from @silvery/ag-term/app) satisfies this.\n * Using a minimal interface avoids a layer violation (commands → ag-term).\n */\nexport interface CommandableApp {\n /** Full rendered text (no ANSI codes) — used by getState() for AI introspection */\n readonly text: string\n /**\n * Send a key press — used by withKeybindings to intercept and route to\n * commands. Returns a `PromiseLike` (covers both `Promise<unknown>` and\n * `ChainableApp` from @silvery/ag-term/app post-fluent-chain). Bead:\n * @km/silvery/fluent-chain-actions.\n */\n press(key: string): PromiseLike<unknown>\n}\n\n/**\n * Generic command definition interface.\n * Compatible with @km/commands CommandDef but doesn't require the dependency.\n */\nexport interface CommandDef<TContext = unknown, TAction = unknown> {\n id: string\n name: string\n description: string\n shortcuts?: string[]\n execute: (ctx: TContext) => TAction | TAction[] | null\n}\n\n/**\n * Generic keybinding interface.\n * Compatible with @km/commands Keybinding.\n */\nexport interface KeybindingDef {\n key: string\n commandId: string\n ctrl?: boolean\n alt?: boolean\n opt?: boolean\n shift?: boolean\n cmd?: boolean\n}\n\n/**\n * Generic command registry interface.\n */\nexport interface CommandRegistryLike<TContext = unknown, TAction = unknown> {\n get(id: string): CommandDef<TContext, TAction> | undefined\n getAll(): CommandDef<TContext, TAction>[]\n}\n\n/**\n * Command metadata exposed on the cmd object.\n */\nexport interface CommandInfo {\n id: string\n name: string\n description: string\n keys: readonly string[]\n}\n\n/**\n * A callable command with metadata.\n */\nexport interface Command {\n (): Promise<void>\n readonly id: string\n readonly name: string\n readonly help: string\n readonly keys: readonly string[]\n}\n\n/**\n * The cmd object added to the app.\n *\n * Provides both method-style (`cmd.down()`) and index-style (`cmd['cursor_down']()`)\n * access to commands. Uses Proxy for dynamic lookup.\n */\nexport interface Cmd {\n [key: string]: Command | (() => CommandInfo[]) | (() => string) | undefined\n /** Get all commands with metadata */\n all(): CommandInfo[]\n /** Get human/AI readable description of all commands */\n describe(): string\n}\n\n/**\n * Options for withCommands.\n *\n * @typeParam TContext - The context type passed to command execute()\n * @typeParam TAction - The action type returned by command execute()\n */\nexport interface WithCommandsOptions<TContext, TAction> {\n /** Command registry with get() and getAll() */\n registry: CommandRegistryLike<TContext, TAction>\n /** Build context for command execution */\n getContext: () => TContext\n /** Handle actions returned by command execution */\n handleAction: (action: TAction) => void\n /** Get keybindings for command metadata (optional) */\n getKeybindings?: () => KeybindingDef[]\n}\n\n/**\n * App state for AI introspection.\n */\nexport interface AppState {\n screen: string\n commands: CommandInfo[]\n focus?: { id: string; text: string }\n}\n\n/**\n * App with command system.\n * Generic over the app type — preserves the full type when used with App.\n */\nexport type AppWithCommands<TApp extends CommandableApp = CommandableApp> = TApp & {\n cmd: Cmd\n getState(): AppState\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/**\n * Find command by short name or full id.\n *\n * Supports both:\n * - Exact id match: `cmd['cursor_down']`\n * - Short name match: `cmd.down` (matches 'cursor_down', 'navigation_down', etc.)\n */\nfunction findCommand<TContext, TAction>(\n registry: CommandRegistryLike<TContext, TAction>,\n key: string,\n): CommandDef<TContext, TAction> | undefined {\n // Try exact id match first\n const byId = registry.get(key)\n if (byId) return byId\n\n // Try short name (last segment after underscore or dot)\n const all = registry.getAll()\n return all.find((c) => {\n const shortName = c.id.split(/[._]/).pop()\n return shortName === key\n })\n}\n\n/**\n * Get keys bound to a command.\n */\nfunction getKeysForCommand(commandId: string, keybindings?: KeybindingDef[]): readonly string[] {\n if (!keybindings) return []\n return keybindings\n .filter((kb) => kb.commandId === commandId)\n .map((kb) => {\n const parts: string[] = []\n if (kb.cmd) parts.push(\"Cmd\")\n if (kb.ctrl) parts.push(\"Ctrl\")\n if (kb.alt || kb.opt) parts.push(\"Alt\")\n if (kb.shift) parts.push(\"Shift\")\n parts.push(kb.key)\n return parts.join(\"+\")\n })\n}\n\n/**\n * Format help text for all commands.\n */\nfunction formatHelp<TContext, TAction>(\n registry: CommandRegistryLike<TContext, TAction>,\n keybindings?: KeybindingDef[],\n): string {\n const commands = registry.getAll()\n const lines = commands.map((cmd) => {\n const keys = getKeysForCommand(cmd.id, keybindings)\n const keyStr = keys.length > 0 ? ` [${keys.join(\", \")}]` : \"\"\n return `${cmd.id}${keyStr}: ${cmd.description}`\n })\n return lines.join(\"\\n\")\n}\n\n/**\n * Add command system to an App.\n *\n * Supports two calling styles:\n * - Direct: `withCommands(app, options)` — returns enhanced app immediately\n * - Curried: `withCommands(options)` — returns a plugin for pipe() composition\n *\n * @example Direct\n * ```tsx\n * const app = withCommands(render(<Board />), {\n * registry: commandRegistry,\n * getContext: () => buildContext(state),\n * handleAction: (action) => dispatch(action),\n * })\n * ```\n *\n * @example Curried (pipe)\n * ```tsx\n * const app = pipe(\n * baseApp,\n * withCommands({\n * registry: commandRegistry,\n * getContext: () => buildContext(state),\n * handleAction: (action) => dispatch(action),\n * }),\n * )\n * ```\n */\n// Curried form: withCommands(options) => plugin\nexport function withCommands<TContext, TAction>(\n options: WithCommandsOptions<TContext, TAction>,\n): <TApp extends CommandableApp>(app: TApp) => AppWithCommands<TApp>\n// Direct form: withCommands(app, options) => enhancedApp\nexport function withCommands<TApp extends CommandableApp, TContext, TAction>(\n app: TApp,\n options: WithCommandsOptions<TContext, TAction>,\n): AppWithCommands<TApp>\nexport function withCommands<TContext, TAction>(\n appOrOptions: CommandableApp | WithCommandsOptions<TContext, TAction>,\n maybeOptions?: WithCommandsOptions<TContext, TAction>,\n): AppWithCommands | ((app: CommandableApp) => AppWithCommands) {\n // Curried form: first arg is options (no press/text/ansi = not an App)\n if (maybeOptions === undefined) {\n const options = appOrOptions as WithCommandsOptions<TContext, TAction>\n return (app: CommandableApp) => applyCommands(app, options)\n }\n // Direct form: first arg is app\n return applyCommands(appOrOptions as CommandableApp, maybeOptions)\n}\n\nfunction applyCommands<TContext, TAction>(\n app: CommandableApp,\n options: WithCommandsOptions<TContext, TAction>,\n): AppWithCommands {\n const { registry, getContext, handleAction, getKeybindings } = options\n\n const cmd = new Proxy({} as Cmd, {\n get(_, prop: string | symbol): unknown {\n // Handle symbol access (for JS internals)\n if (typeof prop === \"symbol\") return undefined\n\n // Introspection methods\n if (prop === \"all\") {\n return () => {\n const commands = registry.getAll()\n const keybindings = getKeybindings?.()\n return commands.map((c) => ({\n id: c.id,\n name: c.name,\n description: c.description,\n keys: getKeysForCommand(c.id, keybindings),\n }))\n }\n }\n\n if (prop === \"describe\") {\n return () => formatHelp(registry, getKeybindings?.())\n }\n\n // Look up command by short name or full id\n const def = findCommand(registry, prop)\n if (!def) return undefined\n\n // Build callable with metadata\n const fn = async () => {\n const ctx = getContext()\n const result = def.execute(ctx)\n if (result) {\n const actions = Array.isArray(result) ? result : [result]\n for (const action of actions) {\n handleAction(action)\n }\n }\n // Allow microtask to flush for test synchronization\n await Promise.resolve()\n }\n\n // Attach metadata\n const keybindings = getKeybindings?.()\n Object.defineProperties(fn, {\n id: { value: def.id, enumerable: true },\n name: { value: def.name, enumerable: true },\n help: { value: def.description, enumerable: true },\n keys: {\n value: getKeysForCommand(def.id, keybindings),\n enumerable: true,\n },\n })\n\n return fn as Command\n },\n\n has(_, prop): boolean {\n if (typeof prop === \"symbol\") return false\n if (prop === \"all\" || prop === \"describe\") return true\n return !!findCommand(registry, prop)\n },\n })\n\n // Build getState for AI introspection\n const getState = (): AppState => {\n const commands = registry.getAll()\n const keybindings = getKeybindings?.()\n return {\n screen: app.text,\n commands: commands.map((c) => ({\n id: c.id,\n name: c.name,\n description: c.description,\n keys: getKeysForCommand(c.id, keybindings),\n })),\n // Focus info would require DOM query - leave undefined for now\n focus: undefined,\n }\n }\n\n return Object.assign(app, { cmd, getState }) as AppWithCommands\n}\n","/**\n * withKeybindings - SlateJS-style plugin for keybinding wiring\n *\n * Intercepts `press()` calls and routes them to commands via keybinding resolution.\n * Commands not in the registry fall through to component useInput handlers.\n *\n * @example\n * ```tsx\n * const app = withKeybindings(withCommands(render(<Board />), cmdOpts), {\n * bindings: defaultKeybindings,\n * getKeyContext: () => ({ mode: 'normal', hasSelection: false, ... }),\n * })\n *\n * // Press 'j' → resolves to cursor_down → calls app.cmd.down()\n * await app.press('j')\n *\n * // Press 'x' (no binding) → passes through to useInput handlers\n * await app.press('x')\n * ```\n *\n * See docs/future/silvery-command-api-research.md for design rationale.\n */\n\nimport type { AppWithCommands, KeybindingDef } from \"./with-commands\"\nimport { parseHotkey } from \"@silvery/ag/keys\"\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Context for keybinding resolution.\n * Used to match mode-specific bindings and conditional bindings.\n */\nexport interface KeybindingContext {\n mode: string\n hasSelection: boolean\n [key: string]: unknown\n}\n\n/**\n * Options for withKeybindings.\n */\nexport interface WithKeybindingsOptions {\n /** Keybindings to resolve */\n bindings: KeybindingDef[]\n /** Build context for keybinding resolution */\n getKeyContext: () => KeybindingContext\n}\n\n/**\n * Extended keybinding with mode support.\n */\nexport interface ExtendedKeybindingDef extends KeybindingDef {\n modes?: string[]\n when?: (ctx: KeybindingContext) => boolean\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n// parseKey replaced by parseHotkey from ./keys.js\n\n/**\n * Resolve a key press to a command ID using keybinding lookup.\n */\nfunction resolveKeybinding(\n key: string,\n modifiers: { ctrl: boolean; meta: boolean; shift: boolean; super: boolean },\n bindings: ExtendedKeybindingDef[],\n ctx: KeybindingContext,\n): string | null {\n for (const binding of bindings) {\n // Check key match\n if (binding.key !== key) continue\n\n // Check modifiers\n if (!!binding.ctrl !== !!modifiers.ctrl) continue\n if (!!binding.opt !== !!modifiers.meta) continue\n if (!!binding.cmd !== !!modifiers.super) continue\n\n // For single uppercase letters (A-Z), the shift key is implicit\n const isUppercaseLetter = key.length === 1 && key >= \"A\" && key <= \"Z\" && !binding.shift\n if (!isUppercaseLetter && !!binding.shift !== !!modifiers.shift) continue\n\n // alt removed — macOS uses opt (⌥), matched against modifiers.meta above\n\n // Check mode\n if (binding.modes && binding.modes.length > 0) {\n if (!binding.modes.includes(ctx.mode)) continue\n }\n\n // Check conditional\n if (binding.when && !binding.when(ctx)) continue\n\n return binding.commandId\n }\n return null\n}\n\n/**\n * Wire keybindings to command invocation.\n *\n * Intercepts `press()` and routes matching keys to commands.\n * Non-matching keys pass through to the original press handler.\n *\n * Supports two calling styles:\n * - Direct: `withKeybindings(app, options)` — returns enhanced app immediately\n * - Curried: `withKeybindings(options)` — returns a plugin for pipe() composition\n *\n * @example Direct\n * ```tsx\n * const app = withKeybindings(appWithCmd, {\n * bindings: defaultKeybindings,\n * getKeyContext: () => buildKeybindingContext(state),\n * })\n * ```\n *\n * @example Curried (pipe)\n * ```tsx\n * const app = pipe(\n * baseApp,\n * withCommands(cmdOpts),\n * withKeybindings({\n * bindings: defaultKeybindings,\n * getKeyContext: () => buildKeybindingContext(state),\n * }),\n * )\n * ```\n */\n// Curried form: withKeybindings(options) => plugin\nexport function withKeybindings(\n options: WithKeybindingsOptions,\n): <T extends AppWithCommands>(app: T) => T\n// Direct form: withKeybindings(app, options) => enhancedApp\nexport function withKeybindings<T extends AppWithCommands>(\n app: T,\n options: WithKeybindingsOptions,\n): T\nexport function withKeybindings<T extends AppWithCommands>(\n appOrOptions: T | WithKeybindingsOptions,\n maybeOptions?: WithKeybindingsOptions,\n): T | (<U extends AppWithCommands>(app: U) => U) {\n // Curried form: first arg is options (no press/text/ansi = not an App)\n if (maybeOptions === undefined) {\n const options = appOrOptions as WithKeybindingsOptions\n return <U extends AppWithCommands>(app: U) => applyKeybindings(app, options)\n }\n // Direct form: first arg is app\n return applyKeybindings(appOrOptions as T, maybeOptions)\n}\n\nfunction applyKeybindings<T extends AppWithCommands>(app: T, options: WithKeybindingsOptions): T {\n const { bindings, getKeyContext } = options\n const originalPress = app.press.bind(app)\n\n // Create a proxy to intercept press() while preserving all other properties\n return new Proxy(app, {\n get(target, prop, receiver) {\n if (prop === \"press\") {\n return async function interceptedPress(keyStr: string): Promise<T> {\n const { key, ...modifiers } = parseHotkey(keyStr)\n const ctx = getKeyContext()\n\n // Try to resolve to a command\n const commandId = resolveKeybinding(\n key,\n modifiers,\n bindings as ExtendedKeybindingDef[],\n ctx,\n )\n\n if (commandId) {\n const cmd = target.cmd[commandId]\n if (cmd) {\n await cmd()\n return receiver as T\n }\n }\n\n // Pass through to original press handler (for useInput)\n await originalPress(keyStr)\n return receiver as T\n }\n }\n return Reflect.get(target, prop, receiver)\n },\n })\n}\n","/**\n * Buffer comparison utility for differential rendering tests.\n *\n * Compares two terminal buffers cell-by-cell, returning the first\n * mismatch found (or null if buffers are identical).\n *\n * ## Documented-distinct from the termless comparator family\n *\n * The terminal-flow vertical slice (§7 \"Comparator family\") converges the\n * ecosystem's differs — termless `diffBuffers`, `terminalStateDigest` /\n * `diffTerminalStates`, and the restore-equivalence oracle's state digest — into\n * ONE compare family in termless. `compareBuffers` here is deliberately **NOT** a\n * member of that family, and NOT a duplicate of `diffBuffers`. It is a\n * **render-level** check, kept distinct by design:\n *\n * - **Different input.** The termless family compares terminal *state* read\n * through the `TerminalReadable` contract (\"are these two terminals in the same\n * observable state?\"). `compareBuffers` compares two silvery `TerminalBuffer`s —\n * silvery's internal packed render target, which is NOT a `TerminalReadable` and\n * cannot be one: it carries render-only semantics (the `SELECTABLE_FLAG`, the\n * `DEFAULT_BG` sentinel, inheritedBg-resolved cells, `continuation` occupancy)\n * that exist only inside the render pipeline, before any emulator sees output.\n * - **Different question.** Every caller (`with-diagnostics`'s `SILVERY_STRICT`\n * `incremental` check, the incremental-rendering fuzz, `render-plan-parity`)\n * asks \"did silvery's incremental / replayed render path produce the identical\n * render buffer as a fresh / recorded render?\" — the incremental≡fresh\n * invariant. That has no analog in the termless state-equivalence family.\n * - **Non-temporal A/B framing.** `cellA`/`cellB` are two renders of the SAME\n * frame (incremental vs fresh, replayed vs recorded) — deliberately NOT the\n * `oldCell`/`newCell` temporal framing of `diffBuffers`, and it returns the\n * FIRST mismatch (fail-fast for an assertion) rather than every changed cell\n * (a differential corpus).\n *\n * Structural correspondence, for readers crossing between the two: this file's\n * `{ x, y }` is `diffBuffers`'s `{ col, row }` (x=col, y=row); `cellA`/`cellB`\n * carry silvery buffer `Cell`s (fg/bg = `number | { r, g, b, index? } | null`),\n * whose color shape is index-preserving and thus structurally compatible with the\n * termless `Cell`'s `Color = { r, g, b, index? }` — but the two types are never\n * imported across the silvery↔termless boundary (compatible shapes, not shared\n * imports; slice §9 rule 7).\n */\n\nimport { type Cell, type TerminalBuffer, cellEquals } from \"@silvery/ag-term/buffer\"\n\n/**\n * A single cell mismatch between two buffers.\n */\nexport interface BufferMismatch {\n /** Column of the mismatched cell */\n x: number\n /** Row of the mismatched cell */\n y: number\n /** Cell from buffer A (e.g., incremental render) */\n cellA: Cell\n /** Cell from buffer B (e.g., fresh render) */\n cellB: Cell\n}\n\n/**\n * Compare two terminal buffers cell-by-cell.\n *\n * @returns The first mismatch found, or null if buffers are identical.\n */\nexport function compareBuffers(a: TerminalBuffer, b: TerminalBuffer): BufferMismatch | null {\n const width = Math.max(a.width, b.width)\n const height = Math.max(a.height, b.height)\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const cellA = a.inBounds(x, y)\n ? a.getCell(x, y)\n : {\n char: \" \",\n fg: null,\n bg: null,\n underlineColor: null,\n attrs: {},\n wide: false,\n continuation: false,\n }\n const cellB = b.inBounds(x, y)\n ? b.getCell(x, y)\n : {\n char: \" \",\n fg: null,\n bg: null,\n underlineColor: null,\n attrs: {},\n wide: false,\n continuation: false,\n }\n\n if (!cellEquals(cellA, cellB)) {\n return { x, y, cellA, cellB }\n }\n }\n }\n\n return null\n}\n\n/**\n * Format a buffer mismatch for human-readable error output.\n */\nexport function formatMismatch(\n mismatch: BufferMismatch,\n context?: {\n incrementalText?: string\n freshText?: string\n seed?: number\n iteration?: number\n key?: string\n },\n): string {\n const { x, y, cellA, cellB } = mismatch\n const lines: string[] = [\n `Buffer mismatch at (${x}, ${y})`,\n ` incremental: char=${JSON.stringify(cellA.char)} fg=${JSON.stringify(cellA.fg)} bg=${JSON.stringify(cellA.bg)} attrs=${JSON.stringify(cellA.attrs)}`,\n ` fresh: char=${JSON.stringify(cellB.char)} fg=${JSON.stringify(cellB.fg)} bg=${JSON.stringify(cellB.bg)} attrs=${JSON.stringify(cellB.attrs)}`,\n ]\n\n if (context?.seed !== undefined) lines.push(` seed: ${context.seed}`)\n if (context?.iteration !== undefined) {\n lines.push(` iteration: ${context.iteration}`)\n }\n if (context?.key) lines.push(` key: ${JSON.stringify(context.key)}`)\n\n if (context?.incrementalText) {\n lines.push(\"\", \"--- incremental ---\", context.incrementalText)\n }\n if (context?.freshText) {\n lines.push(\"\", \"--- fresh ---\", context.freshText)\n }\n\n return lines.join(\"\\n\")\n}\n","/**\n * withDiagnostics - Plugin for buffer and rendering diagnostic checks\n *\n * Wraps the `cmd` object to check invariants after command execution:\n * - All commands: Check incremental vs fresh render\n * - Cursor moves: Also check buffer content stability\n * - Optional: ANSI replay verification (characters AND SGR styles)\n *\n * ## Design Note: Why wrap `cmd` instead of `sendInput`?\n *\n * The two approaches are complementary:\n *\n * 1. **`sendInput()` level** (in renderer.ts) — Already has `SILVERY_STRICT`\n * which catches ALL inputs regardless of how they arrive (raw key presses,\n * type(), press(), etc.). This is the right place for incremental render checks.\n *\n * 2. **`cmd` level** (this plugin) — Command-aware, can selectively check stability\n * for cursor moves only. Raw sendInput doesn't know which inputs are cursor\n * commands that should preserve content. Another option would be `withInput()`\n * which could wrap sendInput with awareness of what input was sent.\n *\n * This plugin focuses on the command-aware checks. For comprehensive incremental\n * render checking, use `SILVERY_STRICT=1` environment variable which enables all checks.\n *\n * @example\n * ```typescript\n * import { withDiagnostics } from '@silvery/ag-term/toolbelt';\n *\n * // All checks enabled by default when you call withDiagnostics()\n * const driver = withDiagnostics(createBoardDriver(repo, rootId));\n *\n * // Or disable specific checks\n * const driver = withDiagnostics(createBoardDriver(repo, rootId), {\n * checkReplay: false // skip ANSI replay check\n * });\n *\n * // Commands now run invariant checks automatically\n * await driver.cmd.down(); // Checks incremental + stability + replay\n * await driver.cmd.search(); // Checks incremental + replay\n * ```\n */\n\nimport { mkdir } from \"node:fs/promises\"\nimport { join } from \"node:path\"\nimport { type Color, type TerminalBuffer, colorEquals } from \"../buffer\"\nimport { outputPhase } from \"../pipeline\"\nimport { compareBuffers, formatMismatch } from \"@silvery/test/compare-buffers\"\nimport type { BoxProps, AgNode } from \"@silvery/create/types\"\nimport type { App } from \"../app\"\nimport type { AppWithCommands, Cmd, Command } from \"@silvery/commands/with-commands\"\n\n/** App with both full App capabilities and command system */\ntype FullAppWithCommands = AppWithCommands<App>\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface DiagnosticOptions {\n /** Check incremental vs fresh render (default: true when plugin is used) */\n checkIncremental?: boolean\n /** Check buffer stability for cursor commands (default: true when plugin is used) */\n checkStability?: boolean\n /** Check ANSI replay produces correct result (default: true when plugin is used) */\n checkReplay?: boolean\n /** Check layout tree integrity after each command (default: true when plugin is used) */\n checkLayout?: boolean\n /** Lines to skip for stability check (e.g., [0, -1] for breadcrumb/statusbar) */\n skipLines?: number[]\n /** Capture screenshot on failure (default: false) */\n captureOnFailure?: boolean\n /** Directory for failure screenshots (default: /tmp/silvery-diagnostics) */\n screenshotDir?: string\n}\n\n/**\n * Text mismatch between before and after states\n */\ninterface TextMismatch {\n line: number\n before: string\n after: string\n}\n\n/**\n * ANSI replay mismatch (character content)\n */\ninterface ReplayMismatch {\n x: number\n y: number\n expected: string\n actual: string\n}\n\n/**\n * SGR style attributes tracked per cell in the virtual terminal.\n */\ninterface VTermStyle {\n fg: number | { r: number; g: number; b: number } | null\n bg: number | { r: number; g: number; b: number } | null\n bold: boolean\n dim: boolean\n italic: boolean\n underline: boolean\n blink: boolean\n inverse: boolean\n hidden: boolean\n strikethrough: boolean\n}\n\n/**\n * ANSI replay style mismatch\n */\ninterface StyleMismatch {\n x: number\n y: number\n char: string\n diffs: string[]\n}\n\n// =============================================================================\n// VirtualTerminal - ANSI Replay Simulator\n// =============================================================================\n\n/**\n * Virtual terminal simulator for testing ANSI replay equivalence.\n *\n * Parses ANSI sequences and applies them to a 2D grid tracking both\n * character content and SGR style attributes (fg, bg, bold, italic, etc.).\n * Used to verify the Replay Equivalence invariant: applying the ANSI\n * diff to the previous buffer state should produce the target buffer.\n *\n * Handles:\n * - Cursor positioning (H, G, A, B, C, D)\n * - Line clear (K)\n * - Wide characters (emojis, CJK)\n * - CR/LF\n * - SGR style sequences (m) — fg/bg colors, text attributes\n */\nexport class VirtualTerminal {\n private grid: string[][]\n private wideMarker: boolean[][]\n private styles: VTermStyle[][]\n private sgr: VTermStyle\n private cursorX = 0\n private cursorY = 0\n\n constructor(\n public readonly width: number,\n public readonly height: number,\n ) {\n this.grid = Array.from({ length: height }, () => Array(width).fill(\" \"))\n this.wideMarker = Array.from({ length: height }, () => Array(width).fill(false))\n this.styles = Array.from({ length: height }, () =>\n Array.from({ length: width }, () => createDefaultVTermStyle()),\n )\n this.sgr = createDefaultVTermStyle()\n }\n\n /**\n * Initialize grid from a TerminalBuffer (for incremental replay).\n * Loads both character content and style attributes.\n */\n loadFromBuffer(buffer: TerminalBuffer): void {\n for (let y = 0; y < Math.min(this.height, buffer.height); y++) {\n for (let x = 0; x < Math.min(this.width, buffer.width); x++) {\n if (buffer.isCellContinuation(x, y)) {\n this.wideMarker[y]![x] = true\n this.grid[y]![x] = \"\"\n // Continuation cells have default style\n this.styles[y]![x] = createDefaultVTermStyle()\n } else {\n this.grid[y]![x] = buffer.getCellChar(x, y)\n this.wideMarker[y]![x] = false\n // Load style from buffer cell\n const cell = buffer.getCell(x, y)\n this.styles[y]![x] = {\n fg: cell.fg,\n bg: cell.bg,\n bold: !!cell.attrs.bold,\n dim: !!cell.attrs.dim,\n italic: !!cell.attrs.italic,\n underline: !!cell.attrs.underline || !!cell.attrs.underlineStyle,\n blink: !!cell.attrs.blink,\n inverse: !!cell.attrs.inverse,\n hidden: !!cell.attrs.hidden,\n strikethrough: !!cell.attrs.strikethrough,\n }\n }\n }\n }\n }\n\n /**\n * Apply ANSI escape sequence string to the virtual terminal.\n */\n applyAnsi(ansi: string): void {\n let i = 0\n while (i < ansi.length) {\n if (ansi[i] === \"\\x1b\" && ansi[i + 1] === \"[\") {\n const match = ansi.slice(i).match(/^\\x1b\\[([0-9;:?]*)([A-Za-z])/)\n if (match) {\n this.handleCsi(match[1] || \"\", match[2]!)\n i += match[0].length\n continue\n }\n }\n\n // OSC sequences: \\x1b] ... (ST or BEL)\n // e.g. OSC 8 hyperlinks: \\x1b]8;;url\\x1b\\\\ or \\x1b]8;;url\\x07\n if (ansi[i] === \"\\x1b\" && ansi[i + 1] === \"]\") {\n // Find terminator: ST (\\x1b\\\\) or BEL (\\x07)\n let j = i + 2\n while (j < ansi.length) {\n if (ansi[j] === \"\\x07\") {\n j++\n break\n }\n if (ansi[j] === \"\\x1b\" && ansi[j + 1] === \"\\\\\") {\n j += 2\n break\n }\n j++\n }\n i = j\n continue\n }\n\n if (ansi[i] === \"\\r\") {\n this.cursorX = 0\n i++\n continue\n }\n\n if (ansi[i] === \"\\n\") {\n this.cursorY = Math.min(this.cursorY + 1, this.height - 1)\n i++\n continue\n }\n\n // Handle multi-byte Unicode characters\n const char = this.extractChar(ansi, i)\n if (this.cursorX < this.width && this.cursorY < this.height) {\n this.grid[this.cursorY]![this.cursorX] = char\n this.wideMarker[this.cursorY]![this.cursorX] = false\n // Apply current SGR state to the cell\n this.styles[this.cursorY]![this.cursorX] = { ...this.sgr }\n this.cursorX++\n\n // Wide characters take 2 columns\n if (this.isWideChar(char) && this.cursorX < this.width) {\n this.grid[this.cursorY]![this.cursorX] = \"\"\n this.wideMarker[this.cursorY]![this.cursorX] = true\n // Continuation cell gets default style\n this.styles[this.cursorY]![this.cursorX] = createDefaultVTermStyle()\n this.cursorX++\n }\n }\n i += char.length\n }\n }\n\n /**\n * Check if a character is wide (emoji, CJK, etc).\n */\n private isWideChar(char: string): boolean {\n if (char.length === 0) return false\n\n // Characters with VS16 (U+FE0F) are emoji presentation = 2 columns\n if (char.includes(\"\\uFE0F\")) return true\n\n const code = char.codePointAt(0) || 0\n\n // Emoji ranges\n if (code >= 0x1f300 && code <= 0x1f9ff) return true\n if (code >= 0x2600 && code <= 0x26ff) return true\n if (code >= 0x2700 && code <= 0x27bf) return true\n\n // CJK ranges\n if (code >= 0x4e00 && code <= 0x9fff) return true\n if (code >= 0x3000 && code <= 0x303f) return true\n if (code >= 0xff00 && code <= 0xffef) return true\n\n return false\n }\n\n /**\n * Extract a single Unicode character (which may be multiple bytes).\n * Includes VS16 (U+FE0F) if it follows, since VS16 is a presentation selector\n * that modifies the preceding character's rendering width.\n */\n private extractChar(str: string, start: number): string {\n const code = str.codePointAt(start)\n if (code === undefined) return str[start] || \"\"\n let char: string\n if (code > 0xffff) {\n char = String.fromCodePoint(code)\n } else {\n char = str[start] || \"\"\n }\n // Absorb VS16 (U+FE0F) if it follows — it's a presentation modifier\n const nextIdx = start + char.length\n if (nextIdx < str.length && str.codePointAt(nextIdx) === 0xfe0f) {\n char += \"\\uFE0F\"\n }\n return char\n }\n\n private handleCsi(params: string, cmd: string): void {\n switch (cmd) {\n case \"H\": {\n const parts = params.split(\";\")\n this.cursorY = Math.max(0, (Number.parseInt(parts[0] || \"1\", 10) || 1) - 1)\n this.cursorX = Math.max(0, (Number.parseInt(parts[1] || \"1\", 10) || 1) - 1)\n break\n }\n case \"G\": {\n this.cursorX = Math.max(0, (Number.parseInt(params || \"1\", 10) || 1) - 1)\n break\n }\n case \"A\": {\n const n = Number.parseInt(params || \"1\", 10) || 1\n this.cursorY = Math.max(0, this.cursorY - n)\n break\n }\n case \"B\": {\n const n = Number.parseInt(params || \"1\", 10) || 1\n this.cursorY = Math.min(this.height - 1, this.cursorY + n)\n break\n }\n case \"C\": {\n const n = Number.parseInt(params || \"1\", 10) || 1\n this.cursorX = Math.min(this.width - 1, this.cursorX + n)\n break\n }\n case \"D\": {\n const n = Number.parseInt(params || \"1\", 10) || 1\n this.cursorX = Math.max(0, this.cursorX - n)\n break\n }\n case \"K\": {\n const mode = Number.parseInt(params || \"0\", 10)\n // Erase in Line: fills cleared cells with current bg (per ECMA-48)\n if (mode === 0) {\n for (let x = this.cursorX; x < this.width; x++) {\n this.grid[this.cursorY]![x] = \" \"\n this.wideMarker[this.cursorY]![x] = false\n this.styles[this.cursorY]![x] = {\n ...createDefaultVTermStyle(),\n bg: this.sgr.bg,\n }\n }\n } else if (mode === 1) {\n for (let x = 0; x <= this.cursorX; x++) {\n this.grid[this.cursorY]![x] = \" \"\n this.wideMarker[this.cursorY]![x] = false\n this.styles[this.cursorY]![x] = {\n ...createDefaultVTermStyle(),\n bg: this.sgr.bg,\n }\n }\n } else if (mode === 2) {\n for (let x = 0; x < this.width; x++) {\n this.grid[this.cursorY]![x] = \" \"\n this.wideMarker[this.cursorY]![x] = false\n this.styles[this.cursorY]![x] = {\n ...createDefaultVTermStyle(),\n bg: this.sgr.bg,\n }\n }\n }\n break\n }\n case \"m\":\n // SGR (Select Graphic Rendition) — apply style changes\n this.applySgr(params)\n break\n case \"l\":\n case \"h\":\n // Private modes — ignore for replay\n break\n }\n }\n\n /**\n * Apply SGR parameters to the current style state.\n * Parses the semicolon-separated parameter string and updates this.sgr.\n */\n private applySgr(params: string): void {\n if (params === \"\" || params === \"0\") {\n // Reset all attributes\n Object.assign(this.sgr, createDefaultVTermStyle())\n return\n }\n\n const parts = params.split(\";\")\n let i = 0\n while (i < parts.length) {\n const code = parts[i]!\n // Handle subparameters (e.g., \"4:3\" for curly underline)\n const colonIdx = code.indexOf(\":\")\n if (colonIdx >= 0) {\n const mainCode = Number.parseInt(code.substring(0, colonIdx), 10)\n if (mainCode === 4) {\n const sub = Number.parseInt(code.substring(colonIdx + 1), 10)\n this.sgr.underline = sub > 0\n }\n i++\n continue\n }\n\n const n = Number.parseInt(code, 10)\n if (n === 0) {\n Object.assign(this.sgr, createDefaultVTermStyle())\n } else if (n === 1) {\n this.sgr.bold = true\n } else if (n === 2) {\n this.sgr.dim = true\n } else if (n === 3) {\n this.sgr.italic = true\n } else if (n === 4) {\n this.sgr.underline = true\n } else if (n === 5 || n === 6) {\n this.sgr.blink = true\n } else if (n === 7) {\n this.sgr.inverse = true\n } else if (n === 8) {\n this.sgr.hidden = true\n } else if (n === 9) {\n this.sgr.strikethrough = true\n } else if (n === 22) {\n this.sgr.bold = false\n this.sgr.dim = false\n } else if (n === 23) {\n this.sgr.italic = false\n } else if (n === 24) {\n this.sgr.underline = false\n } else if (n === 25) {\n this.sgr.blink = false\n } else if (n === 27) {\n this.sgr.inverse = false\n } else if (n === 28) {\n this.sgr.hidden = false\n } else if (n === 29) {\n this.sgr.strikethrough = false\n } else if (n >= 30 && n <= 37) {\n // Standard foreground colors (0-7)\n this.sgr.fg = n - 30\n } else if (n === 38) {\n // Extended foreground color\n if (i + 1 < parts.length && parts[i + 1] === \"5\" && i + 2 < parts.length) {\n // 256-color: \\x1b[38;5;Nm\n this.sgr.fg = Number.parseInt(parts[i + 2]!, 10)\n i += 2\n } else if (i + 1 < parts.length && parts[i + 1] === \"2\" && i + 4 < parts.length) {\n // True color: \\x1b[38;2;R;G;Bm\n this.sgr.fg = {\n r: Number.parseInt(parts[i + 2]!, 10),\n g: Number.parseInt(parts[i + 3]!, 10),\n b: Number.parseInt(parts[i + 4]!, 10),\n }\n i += 4\n }\n } else if (n === 39) {\n this.sgr.fg = null\n } else if (n >= 40 && n <= 47) {\n // Standard background colors (0-7)\n this.sgr.bg = n - 40\n } else if (n === 48) {\n // Extended background color\n if (i + 1 < parts.length && parts[i + 1] === \"5\" && i + 2 < parts.length) {\n // 256-color: \\x1b[48;5;Nm\n this.sgr.bg = Number.parseInt(parts[i + 2]!, 10)\n i += 2\n } else if (i + 1 < parts.length && parts[i + 1] === \"2\" && i + 4 < parts.length) {\n // True color: \\x1b[48;2;R;G;Bm\n this.sgr.bg = {\n r: Number.parseInt(parts[i + 2]!, 10),\n g: Number.parseInt(parts[i + 3]!, 10),\n b: Number.parseInt(parts[i + 4]!, 10),\n }\n i += 4\n }\n } else if (n === 49) {\n this.sgr.bg = null\n } else if (n >= 90 && n <= 97) {\n // Bright foreground colors (8-15)\n this.sgr.fg = n - 90 + 8\n } else if (n >= 100 && n <= 107) {\n // Bright background colors (8-15)\n this.sgr.bg = n - 100 + 8\n }\n // 58/59 (underline color) not tracked in diagnostic comparison\n i++\n }\n }\n\n /**\n * Get the character at a position.\n */\n getChar(x: number, y: number): string {\n if (this.wideMarker[y]?.[x]) return \"\"\n return this.grid[y]?.[x] ?? \" \"\n }\n\n /**\n * Get the style at a position.\n */\n getStyle(x: number, y: number): VTermStyle {\n return this.styles[y]?.[x] ?? createDefaultVTermStyle()\n }\n\n /**\n * Compare with a TerminalBuffer and return character mismatches.\n */\n compareToBuffer(buffer: TerminalBuffer): ReplayMismatch[] {\n const mismatches: ReplayMismatch[] = []\n for (let y = 0; y < Math.min(this.height, buffer.height); y++) {\n for (let x = 0; x < Math.min(this.width, buffer.width); x++) {\n if (buffer.isCellContinuation(x, y)) continue\n\n const expected = buffer.getCellChar(x, y)\n const actual = this.getChar(x, y)\n if (expected !== actual) {\n mismatches.push({ x, y, expected, actual })\n }\n }\n }\n return mismatches\n }\n\n /**\n * Compare styles with a TerminalBuffer and return style mismatches.\n * Checks fg, bg, bold, dim, italic, underline, blink, inverse, hidden, strikethrough.\n */\n compareStylesToBuffer(buffer: TerminalBuffer): StyleMismatch[] {\n const mismatches: StyleMismatch[] = []\n for (let y = 0; y < Math.min(this.height, buffer.height); y++) {\n for (let x = 0; x < Math.min(this.width, buffer.width); x++) {\n if (buffer.isCellContinuation(x, y)) continue\n\n const cell = buffer.getCell(x, y)\n const actual = this.getStyle(x, y)\n const diffs: string[] = []\n\n // Compare foreground color\n if (!colorEquals(actual.fg as Color, cell.fg)) {\n diffs.push(`fg: ${formatVTermColor(actual.fg)} vs ${formatVTermColor(cell.fg)}`)\n }\n // Compare background color\n if (!colorEquals(actual.bg as Color, cell.bg)) {\n diffs.push(`bg: ${formatVTermColor(actual.bg)} vs ${formatVTermColor(cell.bg)}`)\n }\n // Compare text attributes\n if (actual.bold !== !!cell.attrs.bold) {\n diffs.push(`bold: ${actual.bold} vs ${!!cell.attrs.bold}`)\n }\n if (actual.dim !== !!cell.attrs.dim) {\n diffs.push(`dim: ${actual.dim} vs ${!!cell.attrs.dim}`)\n }\n if (actual.italic !== !!cell.attrs.italic) {\n diffs.push(`italic: ${actual.italic} vs ${!!cell.attrs.italic}`)\n }\n // Underline: buffer can have underline or underlineStyle\n const expectedUnderline = !!cell.attrs.underline || !!cell.attrs.underlineStyle\n if (actual.underline !== expectedUnderline) {\n diffs.push(`underline: ${actual.underline} vs ${expectedUnderline}`)\n }\n if (actual.blink !== !!cell.attrs.blink) {\n diffs.push(`blink: ${actual.blink} vs ${!!cell.attrs.blink}`)\n }\n if (actual.inverse !== !!cell.attrs.inverse) {\n diffs.push(`inverse: ${actual.inverse} vs ${!!cell.attrs.inverse}`)\n }\n if (actual.hidden !== !!cell.attrs.hidden) {\n diffs.push(`hidden: ${actual.hidden} vs ${!!cell.attrs.hidden}`)\n }\n if (actual.strikethrough !== !!cell.attrs.strikethrough) {\n diffs.push(`strikethrough: ${actual.strikethrough} vs ${!!cell.attrs.strikethrough}`)\n }\n\n if (diffs.length > 0) {\n mismatches.push({ x, y, char: cell.char, diffs })\n }\n }\n }\n return mismatches\n }\n}\n\n/**\n * Create a default VTermStyle with all attributes reset to defaults.\n */\nfunction createDefaultVTermStyle(): VTermStyle {\n return {\n fg: null,\n bg: null,\n bold: false,\n dim: false,\n italic: false,\n underline: false,\n blink: false,\n inverse: false,\n hidden: false,\n strikethrough: false,\n }\n}\n\n/**\n * Format a color value for diagnostic display.\n */\nfunction formatVTermColor(c: number | { r: number; g: number; b: number } | null): string {\n if (c === null) return \"default\"\n if (typeof c === \"number\") return `${c}`\n return `rgb(${c.r},${c.g},${c.b})`\n}\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/** Commands that should preserve buffer content (only cursor position changes) */\nconst CURSOR_COMMANDS = new Set([\n // Full names\n \"cursor_up\",\n \"cursor_down\",\n \"cursor_left\",\n \"cursor_right\",\n // Short names\n \"up\",\n \"down\",\n \"left\",\n \"right\",\n])\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Parse SILVERY_STABILITY_SKIP_LINES environment variable.\n * Format: comma-separated integers, e.g., \"0,-1\"\n */\nfunction parseSkipLines(env?: string): number[] {\n if (!env) return []\n return env\n .split(\",\")\n .map((s) => Number.parseInt(s.trim(), 10))\n .filter((n) => !isNaN(n))\n}\n\n/**\n * Compare text content before and after a command.\n * Returns the first mismatch found, or null if content matches.\n *\n * @param before - Text before command execution\n * @param after - Text after command execution\n * @param skipLines - Line indices to skip (supports negative indices from end)\n */\nfunction compareText(before: string, after: string, skipLines: number[]): TextMismatch | null {\n const beforeLines = before.split(\"\\n\")\n const afterLines = after.split(\"\\n\")\n const maxLines = Math.max(beforeLines.length, afterLines.length)\n\n // Build set of lines to skip, resolving negative indices\n const skipSet = new Set<number>()\n for (const line of skipLines) {\n if (line >= 0) {\n skipSet.add(line)\n } else {\n // Negative index: -1 = last line, -2 = second to last, etc.\n skipSet.add(maxLines + line)\n }\n }\n\n for (let i = 0; i < maxLines; i++) {\n if (skipSet.has(i)) continue\n const b = beforeLines[i] ?? \"\"\n const a = afterLines[i] ?? \"\"\n if (b !== a) {\n return { line: i, before: b, after: a }\n }\n }\n return null\n}\n\n// =============================================================================\n// Layout Invariant Checks\n// =============================================================================\n\n/**\n * Check layout tree integrity. Returns violation messages, or empty array if valid.\n *\n * Checks:\n * - All rect dimensions are finite and non-negative (width >= 0, height >= 0)\n * - All positions are finite (x, y are valid numbers)\n * - No NaN values in computed layout\n * - Children don't overflow parent bounds (1px tolerance for rounding)\n * - Skips overflow check for nodes with overflow:hidden/scroll (they intentionally clip)\n */\nexport function checkLayoutInvariants(node: AgNode): string[] {\n const violations: string[] = []\n walkLayout(node, null, violations)\n return violations\n}\n\nfunction walkLayout(\n node: AgNode,\n parentRect: {\n x: number\n y: number\n width: number\n height: number\n clipped: boolean\n } | null,\n violations: string[],\n): void {\n const rect = node.boxRect\n if (!rect) return // No layout computed yet — skip\n\n const id = (node.props as BoxProps).id ?? node.type\n\n // Check finite and non-negative dimensions\n if (!Number.isFinite(rect.width) || rect.width < 0) {\n violations.push(`${id}: invalid width ${rect.width}`)\n }\n if (!Number.isFinite(rect.height) || rect.height < 0) {\n violations.push(`${id}: invalid height ${rect.height}`)\n }\n if (!Number.isFinite(rect.x)) {\n violations.push(`${id}: invalid x ${rect.x}`)\n }\n if (!Number.isFinite(rect.y)) {\n violations.push(`${id}: invalid y ${rect.y}`)\n }\n\n // Check children don't overflow parent (with 1px tolerance)\n if (parentRect && !parentRect.clipped) {\n const TOLERANCE = 1\n if (rect.x + rect.width > parentRect.x + parentRect.width + TOLERANCE) {\n violations.push(\n `${id}: overflows parent right (${rect.x + rect.width} > ${parentRect.x + parentRect.width})`,\n )\n }\n if (rect.y + rect.height > parentRect.y + parentRect.height + TOLERANCE) {\n violations.push(\n `${id}: overflows parent bottom (${rect.y + rect.height} > ${parentRect.y + parentRect.height})`,\n )\n }\n if (rect.x < parentRect.x - TOLERANCE) {\n violations.push(`${id}: overflows parent left (${rect.x} < ${parentRect.x})`)\n }\n if (rect.y < parentRect.y - TOLERANCE) {\n violations.push(`${id}: overflows parent top (${rect.y} < ${parentRect.y})`)\n }\n }\n\n // Determine if this node clips its children\n const overflow = (node.props as BoxProps).overflow\n const clipped = overflow === \"hidden\" || overflow === \"scroll\"\n\n const childParentRect = {\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height,\n clipped,\n }\n\n for (const child of node.children) {\n walkLayout(child, childParentRect, violations)\n }\n}\n\n// =============================================================================\n// Plugin Implementation\n// =============================================================================\n\n/**\n * Add diagnostic checking to an app with commands.\n *\n * Wraps the `cmd` proxy to intercept all command executions and run checks:\n * - **All commands**: Check that incremental render matches fresh render\n * - **Cursor commands**: Also check that buffer content didn't change\n *\n * **All checks are enabled by default** when you call this function.\n * The principle: if you wrapped with withDiagnostics(), you want diagnostics.\n *\n * @param app - App with command system (from withCommands)\n * @param options - Diagnostic check configuration (all enabled by default)\n * @returns App with wrapped cmd that runs diagnostic checks\n */\nexport function withDiagnostics<T extends FullAppWithCommands>(\n app: T,\n options: DiagnosticOptions = {},\n): T {\n // All checks enabled by default when plugin is used\n const {\n checkIncremental = true,\n checkStability = true,\n checkReplay = true,\n checkLayout = true,\n skipLines = parseSkipLines(process.env.SILVERY_STABILITY_SKIP_LINES),\n captureOnFailure = false,\n screenshotDir = \"/tmp/silvery-diagnostics\",\n } = options\n\n // If all checks are explicitly disabled, return app unchanged\n if (!checkIncremental && !checkStability && !checkReplay && !checkLayout) return app\n\n /** Capture screenshot on diagnostic failure (best-effort, never masks original error) */\n async function captureFailureScreenshot(\n commandId: string,\n checkType: string,\n ): Promise<string | null> {\n if (!captureOnFailure) return null\n try {\n await mkdir(screenshotDir, { recursive: true })\n const filename = `fail-${commandId}-${checkType}.png`\n const filepath = join(screenshotDir, filename)\n await app.screenshot(filepath)\n return filepath\n } catch {\n return null\n }\n }\n\n // Wrap the cmd proxy\n const wrappedCmd = new Proxy(app.cmd, {\n get(target, prop: string | symbol): unknown {\n // Handle symbol access (for JS internals)\n if (typeof prop === \"symbol\") return Reflect.get(target, prop)\n\n const original = Reflect.get(target, prop)\n\n // Pass through non-function properties and special methods\n if (typeof original !== \"function\") return original\n if (prop === \"all\" || prop === \"describe\") return original\n\n // Wrap command execution\n const command = original as Command\n const wrapped = async () => {\n // Capture state before command\n const beforeText = app.text\n const beforeBuffer = checkReplay ? app.lastBuffer() : null\n\n // Execute the original command\n await command()\n\n // Check 1: Incremental vs fresh render\n if (checkIncremental) {\n const incremental = app.lastBuffer()\n // freshRender() may throw if not available (non-test renderer)\n try {\n const fresh = app.freshRender()\n if (incremental && fresh) {\n const mismatch = compareBuffers(incremental, fresh)\n if (mismatch) {\n // Include full buffer text for debugging\n const incrementalText = app.text\n const freshText = fresh\n ? Array.from({ length: fresh.height }, (_, y) =>\n Array.from({ length: fresh.width }, (_, x) => fresh.getCellChar(x, y)).join(\n \"\",\n ),\n ).join(\"\\n\")\n : \"(no fresh buffer)\"\n const screenshotPath = await captureFailureScreenshot(command.id, \"incremental\")\n throw new Error(\n `SILVERY_DIAGNOSTIC: Incremental/fresh mismatch after ${command.id}\\n` +\n formatMismatch(mismatch, {\n key: command.id,\n incrementalText,\n freshText,\n }) +\n (screenshotPath ? `\\n Screenshot saved: ${screenshotPath}` : \"\"),\n )\n }\n }\n } catch (e) {\n // If freshRender isn't available, skip the check\n if (!(e instanceof Error) || !e.message.includes(\"only available in test renderer\")) {\n throw e\n }\n }\n }\n\n // Check 2: Content stability for cursor commands\n if (checkStability && CURSOR_COMMANDS.has(command.id)) {\n const afterText = app.text\n const mismatch = compareText(beforeText, afterText, skipLines)\n if (mismatch) {\n const screenshotPath = await captureFailureScreenshot(command.id, \"stability\")\n throw new Error(\n `SILVERY_DIAGNOSTIC: Content changed after cursor move ${command.id}\\n` +\n ` Line ${mismatch.line}: \"${mismatch.before}\" → \"${mismatch.after}\"` +\n (screenshotPath ? `\\n Screenshot saved: ${screenshotPath}` : \"\"),\n )\n }\n }\n\n // Check 3: ANSI replay produces correct result\n if (checkReplay && beforeBuffer) {\n const afterBuffer = app.lastBuffer()\n if (afterBuffer) {\n // Get the ANSI diff that would be sent to terminal\n const ansiDiff = outputPhase(beforeBuffer, afterBuffer)\n\n // Create virtual terminal initialized with previous state\n const vterm = new VirtualTerminal(afterBuffer.width, afterBuffer.height)\n vterm.loadFromBuffer(beforeBuffer)\n\n // Apply the ANSI diff\n vterm.applyAnsi(ansiDiff)\n\n // Compare character content\n const mismatches = vterm.compareToBuffer(afterBuffer)\n if (mismatches.length > 0) {\n const first5 = mismatches.slice(0, 5)\n const details = first5\n .map((m) => ` (${m.x},${m.y}): expected=\"${m.expected}\" actual=\"${m.actual}\"`)\n .join(\"\\n\")\n const screenshotPath = await captureFailureScreenshot(command.id, \"replay\")\n throw new Error(\n `SILVERY_DIAGNOSTIC: ANSI replay mismatch after ${command.id}\\n` +\n ` ${mismatches.length} cells differ:\\n${details}` +\n (mismatches.length > 5 ? `\\n ... and ${mismatches.length - 5} more` : \"\") +\n (screenshotPath ? `\\n Screenshot saved: ${screenshotPath}` : \"\"),\n )\n }\n\n // Compare SGR styles (fg, bg, bold, italic, underline, etc.)\n const styleMismatches = vterm.compareStylesToBuffer(afterBuffer)\n if (styleMismatches.length > 0) {\n const first5 = styleMismatches.slice(0, 5)\n const details = first5\n .map((m) => ` (${m.x},${m.y}) char=\"${m.char}\": ${m.diffs.join(\", \")}`)\n .join(\"\\n\")\n const screenshotPath = await captureFailureScreenshot(command.id, \"replay-style\")\n throw new Error(\n `SILVERY_DIAGNOSTIC: ANSI replay style mismatch after ${command.id}\\n` +\n ` ${styleMismatches.length} cells have style differences:\\n${details}` +\n (styleMismatches.length > 5\n ? `\\n ... and ${styleMismatches.length - 5} more`\n : \"\") +\n (screenshotPath ? `\\n Screenshot saved: ${screenshotPath}` : \"\"),\n )\n }\n }\n }\n\n // Check 4: Layout tree integrity\n if (checkLayout) {\n const root = app.getContainer()\n const violations = checkLayoutInvariants(root)\n if (violations.length > 0) {\n const details = violations\n .slice(0, 10)\n .map((v) => ` ${v}`)\n .join(\"\\n\")\n const screenshotPath = await captureFailureScreenshot(command.id, \"layout\")\n throw new Error(\n `SILVERY_DIAGNOSTIC: Layout invariant violation after ${command.id}\\n` +\n ` ${violations.length} violation(s):\\n${details}` +\n (violations.length > 10 ? `\\n ... and ${violations.length - 10} more` : \"\") +\n (screenshotPath ? `\\n Screenshot saved: ${screenshotPath}` : \"\"),\n )\n }\n }\n }\n\n // Copy metadata from original command\n Object.defineProperties(wrapped, {\n id: { value: command.id, enumerable: true },\n name: { value: command.name, enumerable: true },\n help: { value: command.help, enumerable: true },\n keys: { value: command.keys, enumerable: true },\n })\n\n return wrapped\n },\n\n has(target, prop): boolean {\n return Reflect.has(target, prop)\n },\n\n ownKeys(target): (string | symbol)[] {\n return Reflect.ownKeys(target)\n },\n\n getOwnPropertyDescriptor(target, prop): PropertyDescriptor | undefined {\n return Reflect.getOwnPropertyDescriptor(target, prop)\n },\n })\n\n // IMPORTANT: Use Object.assign instead of spread to preserve getters.\n // Spread `{ ...app }` snapshots getters (text, ansi, lastBuffer) as static\n // values from the initial render. Object.assign mutates the original object,\n // preserving its getters so they return current buffer state.\n return Object.assign(app, { cmd: wrappedCmd as Cmd }) as T\n}\n","/**\n * withRender(term) -- extends a Term with render pipeline capabilities.\n *\n * Creates term-scoped pipeline config (width measurer + output phase) from caps,\n * then returns an extended term with render() and renderStatic() methods.\n *\n * Usage:\n * const term = withRender(createTerm())\n * const { output, buffer } = term.render(root, 80, 24, null, { mode: \"fullscreen\" })\n * const html = await term.renderStatic(<Report />)\n */\n\nimport type { Term } from \"../ansi\"\nimport type { ReactElement } from \"react\"\nimport type { TerminalBuffer } from \"../buffer\"\nimport { createPipeline, type MeasuredTerm } from \"../measurer\"\nimport type { PipelineConfig, ExecuteRenderOptions } from \"../pipeline\"\nimport { outputPhase } from \"../pipeline/output-phase\"\nimport { createAg } from \"../ag\"\nimport { runWithMeasurer } from \"../unicode\"\nimport type { AgNode } from \"@silvery/create/types\"\n\n/**\n * Extended Term with render pipeline capabilities.\n *\n * Extends MeasuredTerm (Term + Measurer methods) with render/renderStatic.\n */\nexport interface RenderTerm extends MeasuredTerm {\n /** Pipeline configuration (measurer + output phase) */\n readonly pipelineConfig: PipelineConfig\n /**\n * Run the full render pipeline.\n */\n render(\n root: AgNode,\n width: number,\n height: number,\n prevBuffer: TerminalBuffer | null,\n options?: ExecuteRenderOptions | \"fullscreen\" | \"inline\",\n ): { output: string; buffer: TerminalBuffer }\n /**\n * Render a React element to a string using this terminal's caps.\n * Uses the term's width measurer for correct text measurement.\n */\n renderStatic(\n element: ReactElement,\n options?: { width?: number; height?: number; plain?: boolean },\n ): Promise<string>\n}\n\n/**\n * Extend a Term with render pipeline capabilities.\n *\n * Creates a pipeline config (width measurer + output phase) from the term's caps,\n * and adds render() and renderStatic() methods plus measurer methods.\n *\n * @param term - A Term instance (from createTerm)\n * @returns Extended term with render and measurement capabilities\n */\nexport function withRender(term: Term): RenderTerm {\n const pipelineConfig = createPipeline({ caps: term.caps })\n const { measurer } = pipelineConfig\n\n function renderPipeline(\n root: AgNode,\n width: number,\n height: number,\n prevBuffer: TerminalBuffer | null,\n options?: ExecuteRenderOptions | \"fullscreen\" | \"inline\",\n ): { output: string; buffer: TerminalBuffer } {\n const opts: ExecuteRenderOptions =\n typeof options === \"string\" ? { mode: options } : (options ?? {})\n const {\n mode = \"fullscreen\",\n skipLayoutNotifications = false,\n skipScrollStateUpdates = false,\n scrollbackOffset = 0,\n termRows,\n cursorPos,\n } = opts\n\n const doRender = () => {\n const ag = createAg(root, { measurer })\n ag.layout({ cols: width, rows: height }, { skipLayoutNotifications, skipScrollStateUpdates })\n const { buffer, overlay } = ag.render({ prevBuffer })\n\n const outputFn = pipelineConfig.outputPhaseFn ?? outputPhase\n let output = outputFn(prevBuffer, buffer, mode, scrollbackOffset, termRows, cursorPos)\n // Append Kitty emoji-scrim overlay when backdrop-fade produced one.\n if (overlay) output += overlay\n\n return { output, buffer }\n }\n\n return measurer ? runWithMeasurer(measurer, doRender) : doRender()\n }\n\n async function renderStaticFn(\n element: ReactElement,\n options?: { width?: number; height?: number; plain?: boolean },\n ): Promise<string> {\n const { renderString } = await import(\"@silvery/ag-react/render-string\")\n return renderString(element, { ...options, pipelineConfig })\n }\n\n // Return a proxy that extends the original term with measurer methods and render capabilities\n return Object.create(term, {\n // Measurer methods (from pipeline config)\n maybeWideEmojis: { get: () => measurer.maybeWideEmojis, enumerable: true },\n textSizing: { get: () => measurer.textSizing, enumerable: true },\n displayWidth: { value: measurer.displayWidth.bind(measurer), enumerable: true },\n displayWidthAnsi: { value: measurer.displayWidthAnsi.bind(measurer), enumerable: true },\n graphemeWidth: { value: measurer.graphemeWidth.bind(measurer), enumerable: true },\n wrapText: { value: measurer.wrapText.bind(measurer), enumerable: true },\n sliceByWidth: { value: measurer.sliceByWidth.bind(measurer), enumerable: true },\n sliceByWidthFromEnd: { value: measurer.sliceByWidthFromEnd.bind(measurer), enumerable: true },\n // Pipeline config and render methods\n pipelineConfig: { value: pipelineConfig, enumerable: true },\n render: { value: renderPipeline, enumerable: true },\n renderStatic: { value: renderStaticFn, enumerable: true },\n }) as RenderTerm\n}\n","/**\n * BaseApp — the apply-chain substrate for silvery's runtime.\n *\n * This is the contract from the v1r prototype, shipped as production code.\n * Plugins wrap `apply()`; `dispatch()` drives the chain and drains the\n * resulting effect queue.\n *\n * ## Semantics\n *\n * `apply(op)` is synchronous and returns an {@link ApplyResult}:\n * - `false` — \"I did not handle this op; pass to the next plugin (or ignore)\"\n * - `Effect[]` — \"I handled this op; here are the follow-up effects to run\"\n *\n * `dispatch(op)` runs `apply(op)` inside a reentry guard, pushes any\n * emitted effects onto a shared queue, then drains the queue. The only\n * built-in effect understood by the base is `{type: \"dispatch\", op: Op}` —\n * which re-dispatches the nested op through the apply chain. All other\n * effects (`render`, `exit`, `suspend`, `render-barrier`) are interpreted\n * by the runner that owns this app (typically `create-app.tsx`'s\n * processEventBatch).\n *\n * ## Why a queue + drain loop?\n *\n * Plugins should be free to emit multiple effects and to chain them —\n * `dispatch` effects from one plugin can produce more effects from\n * another. The queue + drain guarantees:\n * 1. No reentrant `dispatch()` — a plugin that dispatches from inside\n * its own apply() throws. Follow-up dispatches must be Effects.\n * 2. Bounded iteration — each drain step consumes the whole batch\n * before accepting new effects, so we can detect runaways.\n *\n * ## Why a named sentinel effect queue?\n *\n * When the runner reads the queue (via {@link BaseApp.drainEffects}), it\n * gets an immutable snapshot so it can choose how to interpret the\n * effects. The base never implements `render` or `exit` — those belong\n * to whichever runner is driving.\n *\n * @see {@link Op}, {@link Effect}, {@link ApplyResult}\n */\n\nimport type { ApplyResult, Effect, Op } from \"../types\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * An app's `apply` function: takes an op, returns either `false` (not\n * handled) or an array of effects.\n */\nexport type Apply = (op: Op) => ApplyResult\n\n/**\n * The minimal contract every silvery runtime exposes.\n *\n * Plugins extend this by capturing `app.apply` and replacing it with a\n * new function that delegates to the captured one for ops it doesn't\n * handle. Stores/state slices ride as extra properties on the concrete\n * plugin-enhanced type.\n */\nexport interface BaseApp {\n /**\n * Entry point for an op. Runs the apply chain, captures emitted\n * effects, then drains. Throws on reentrant dispatch.\n */\n dispatch(op: Op): void\n\n /**\n * The apply chain. The base returns `false` (nothing handles\n * anything). Plugins wrap this — last plugin applied = outermost\n * wrapper = runs first.\n */\n apply: Apply\n\n /**\n * Pull (and clear) effects the runner should interpret. Returns any\n * effects that were left *after* built-in `dispatch` drain completed\n * and that the runner is expected to act on (render/exit/etc).\n *\n * Most runners call this after `dispatch()` returns to flush the\n * render/exit queue.\n */\n drainEffects(): Effect[]\n}\n\n// ---------------------------------------------------------------------------\n// create() — base of the chain\n// ---------------------------------------------------------------------------\n\n/**\n * Create a fresh {@link BaseApp}. The base handles nothing — all\n * behavior comes from plugins.\n *\n * Call order inside `dispatch(op)`:\n * 1. `apply(op)` (runs the plugin chain, inside reentry guard)\n * 2. Queue the returned effects (if handled)\n * 3. Drain loop — dispatch-type effects re-enter, others bubble up to\n * the runner via {@link BaseApp.drainEffects}\n */\nexport function createBaseApp(): BaseApp {\n let dispatching = false\n let draining = false\n /** Effects currently queued from the active dispatch() + its drain. */\n const effectQueue: Effect[] = []\n /** Effects the runner should interpret (everything not consumed by\n * the internal dispatch-drain, e.g. render/exit). */\n const pendingRunnerEffects: Effect[] = []\n\n const app: BaseApp = {\n dispatch(op) {\n if (dispatching) {\n throw new Error(`Reentrant dispatch: ${op.type}`)\n }\n dispatching = true\n try {\n const result = app.apply(op)\n if (result !== false) effectQueue.push(...result)\n } finally {\n dispatching = false\n }\n if (draining) return\n draining = true\n try {\n while (effectQueue.length > 0) {\n const batch = effectQueue.splice(0)\n for (const eff of batch) {\n if (eff.type === \"dispatch\") {\n // Re-dispatch is the only effect the base knows. The\n // nested op travels under `op` to avoid spread collisions\n // with the `type: \"dispatch\"` discriminator.\n const nested = (eff as { op?: Op }).op\n if (!nested || typeof nested.type !== \"string\") continue\n dispatching = true\n try {\n const nestedResult = app.apply(nested)\n if (nestedResult !== false) effectQueue.push(...nestedResult)\n } finally {\n dispatching = false\n }\n } else {\n pendingRunnerEffects.push(eff)\n }\n }\n }\n } finally {\n draining = false\n }\n },\n apply() {\n return false\n },\n drainEffects() {\n if (pendingRunnerEffects.length === 0) return []\n return pendingRunnerEffects.splice(0)\n },\n }\n return app\n}\n\n// ---------------------------------------------------------------------------\n// Plugin pattern (no helper — just the idiom)\n// ---------------------------------------------------------------------------\n\n/**\n * Every `with*` plugin follows the same three-line idiom:\n *\n * ```ts\n * export function withEcho<A extends BaseApp>(app: A): A {\n * const prev = app.apply\n * app.apply = (op) => {\n * if (op.type === \"echo\") {\n * // handle\n * return []\n * }\n * return prev(op) // delegate to downstream chain\n * }\n * return app\n * }\n * ```\n *\n * The last plugin installed is the OUTERMOST wrapper and runs first.\n * Always delegate via `prev(op)` for ops you don't handle.\n */\n","/**\n * withInkCursor() — Thin adapter bridging Ink's useCursor to silvery's CursorStore.\n *\n * Canonical home for InkCursorStoreCtx. Consumed by ink.ts (useCursor hook).\n *\n * @packageDocumentation\n */\nimport React, { createContext, Fragment } from \"react\"\nimport {\n createCursorStore,\n CursorProvider,\n type CursorStore,\n} from \"@silvery/ag-react/hooks/useCursor\"\nimport type { RunnableApp } from \"./with-ink\"\n\n// =============================================================================\n// Ink Cursor Store Context\n// =============================================================================\n\n/**\n * Context for passing cursor store to Ink compat useCursor hook.\n * This lets useCursor write directly to the store without going through\n * silvery's useCursor hook (which requires NodeContext for layout).\n */\nexport const InkCursorStoreCtx = createContext<CursorStore | null>(null)\n\n// =============================================================================\n// withInkCursor — App-level plugin for pipe() composition\n// =============================================================================\n\nexport interface WithInkCursorOptions {\n cursorStore?: CursorStore\n}\n\nexport interface AppWithInkCursor {\n readonly Root: React.ComponentType<{ children: React.ReactNode }>\n}\n\nexport function withInkCursor<T extends RunnableApp>(\n options: WithInkCursorOptions = {},\n): (app: T) => T & AppWithInkCursor {\n const cursorStore = options.cursorStore ?? createCursorStore()\n\n return (app: T): T & AppWithInkCursor => {\n const PrevRoot = app.Root ?? Fragment\n const InkCursorRoot = ({ children }: { children: React.ReactNode }) =>\n React.createElement(\n CursorProvider,\n { store: cursorStore },\n React.createElement(\n InkCursorStoreCtx.Provider,\n { value: cursorStore },\n React.createElement(PrevRoot, null, children),\n ),\n )\n\n return Object.assign(Object.create(app), {\n Root: InkCursorRoot,\n }) as T & AppWithInkCursor\n }\n}\n","/**\n * withInkFocus() — Plugin providing Ink's focus system.\n *\n * Canonical home for InkFocusContext, InkFocusProvider, and the Focusable type.\n * Consumed by ink.ts (useFocus/useFocusManager hooks).\n *\n * @packageDocumentation\n */\nimport React, { createContext, Fragment, useCallback, useEffect, useMemo, useState } from \"react\"\nimport type { EventEmitter } from \"node:events\"\nimport type { RunnableApp } from \"./with-ink\"\n\n// =============================================================================\n// Ink Focus Context & Provider\n// =============================================================================\n\n/** A focusable entry in the Ink focus system. */\ntype Focusable = { id: string; isActive: boolean }\n\nexport type InkFocusContextValue = {\n activeId: string | undefined\n isFocusEnabled: boolean\n add: (id: string, options: { autoFocus: boolean }) => void\n remove: (id: string) => void\n activate: (id: string) => void\n deactivate: (id: string) => void\n enableFocus: () => void\n disableFocus: () => void\n focusNext: () => void\n focusPrevious: () => void\n focus: (id: string) => void\n blur: () => void\n}\n\nexport const InkFocusContext = createContext<InkFocusContextValue>({\n activeId: undefined,\n isFocusEnabled: true,\n add() {},\n remove() {},\n activate() {},\n deactivate() {},\n enableFocus() {},\n disableFocus() {},\n focusNext() {},\n focusPrevious() {},\n focus() {},\n blur() {},\n})\n\n/**\n * Ink-compatible FocusProvider component.\n * Manages focus state: list of focusables, active focus ID, tab navigation.\n */\nexport function InkFocusProvider({\n children,\n inputEmitter,\n}: {\n children?: React.ReactNode\n inputEmitter?: EventEmitter\n}) {\n const [isFocusEnabled, setIsFocusEnabled] = useState(true)\n const [activeFocusId, setActiveFocusId] = useState<string | undefined>(undefined)\n const [, setFocusables] = useState<Focusable[]>([])\n const focusablesCountRef = React.useRef(0)\n\n const findNextFocusable = useCallback(\n (\n currentFocusables: Focusable[],\n currentActiveFocusId: string | undefined,\n ): string | undefined => {\n const activeIndex = currentFocusables.findIndex((f) => f.id === currentActiveFocusId)\n for (let i = activeIndex + 1; i < currentFocusables.length; i++) {\n if (currentFocusables[i]?.isActive) return currentFocusables[i]!.id\n }\n return undefined\n },\n [],\n )\n\n const findPreviousFocusable = useCallback(\n (\n currentFocusables: Focusable[],\n currentActiveFocusId: string | undefined,\n ): string | undefined => {\n const activeIndex = currentFocusables.findIndex((f) => f.id === currentActiveFocusId)\n for (let i = activeIndex - 1; i >= 0; i--) {\n if (currentFocusables[i]?.isActive) return currentFocusables[i]!.id\n }\n return undefined\n },\n [],\n )\n\n const focusNext = useCallback((): void => {\n setFocusables((currentFocusables) => {\n setActiveFocusId((currentActiveFocusId) => {\n const firstFocusableId = currentFocusables.find((f) => f.isActive)?.id\n const nextFocusableId = findNextFocusable(currentFocusables, currentActiveFocusId)\n return nextFocusableId ?? firstFocusableId\n })\n return currentFocusables\n })\n }, [findNextFocusable])\n\n const focusPrevious = useCallback((): void => {\n setFocusables((currentFocusables) => {\n setActiveFocusId((currentActiveFocusId) => {\n const lastFocusableId = currentFocusables.findLast((f) => f.isActive)?.id\n const previousFocusableId = findPreviousFocusable(currentFocusables, currentActiveFocusId)\n return previousFocusableId ?? lastFocusableId\n })\n return currentFocusables\n })\n }, [findPreviousFocusable])\n\n const enableFocus = useCallback((): void => {\n setIsFocusEnabled(true)\n }, [])\n const disableFocus = useCallback((): void => {\n setIsFocusEnabled(false)\n }, [])\n\n const focus = useCallback((id: string): void => {\n setFocusables((currentFocusables) => {\n if (currentFocusables.some((f) => f.id === id)) {\n setActiveFocusId(id)\n }\n return currentFocusables\n })\n }, [])\n\n const blur = useCallback((): void => {\n setActiveFocusId(undefined)\n }, [])\n\n const addFocusable = useCallback((id: string, { autoFocus }: { autoFocus: boolean }): void => {\n setFocusables((currentFocusables) => {\n focusablesCountRef.current = currentFocusables.length + 1\n return [...currentFocusables, { id, isActive: true }]\n })\n if (autoFocus) {\n setActiveFocusId((currentActiveFocusId) => {\n if (!currentActiveFocusId) return id\n return currentActiveFocusId\n })\n }\n }, [])\n\n const removeFocusable = useCallback((id: string): void => {\n setActiveFocusId((currentActiveFocusId) => {\n if (currentActiveFocusId === id) return undefined\n return currentActiveFocusId\n })\n setFocusables((currentFocusables) => {\n const filtered = currentFocusables.filter((f) => f.id !== id)\n focusablesCountRef.current = filtered.length\n return filtered\n })\n }, [])\n\n const activateFocusable = useCallback((id: string): void => {\n setFocusables((currentFocusables) =>\n currentFocusables.map((f) => (f.id === id ? { ...f, isActive: true } : f)),\n )\n }, [])\n\n const deactivateFocusable = useCallback((id: string): void => {\n setActiveFocusId((currentActiveFocusId) => {\n if (currentActiveFocusId === id) return undefined\n return currentActiveFocusId\n })\n setFocusables((currentFocusables) =>\n currentFocusables.map((f) => (f.id === id ? { ...f, isActive: false } : f)),\n )\n }, [])\n\n // Tab/Shift+Tab/Esc focus navigation via inputEmitter (raw escape sequences)\n useEffect(() => {\n if (!inputEmitter) return\n const tab = \"\\t\"\n const shiftTab = \"\\x1b[Z\"\n const escape = \"\\x1b\"\n const handleInput = (data: string | Buffer) => {\n const input = typeof data === \"string\" ? data : data.toString()\n if (!isFocusEnabled || focusablesCountRef.current === 0) return\n if (input === tab) focusNext()\n else if (input === shiftTab) focusPrevious()\n else if (input === escape) setActiveFocusId(undefined)\n }\n inputEmitter.on(\"input\", handleInput)\n return () => {\n inputEmitter.removeListener(\"input\", handleInput)\n }\n }, [isFocusEnabled, focusNext, focusPrevious, inputEmitter])\n\n const contextValue = useMemo(\n () => ({\n activeId: activeFocusId,\n isFocusEnabled,\n add: addFocusable,\n remove: removeFocusable,\n activate: activateFocusable,\n deactivate: deactivateFocusable,\n enableFocus,\n disableFocus,\n focusNext,\n focusPrevious,\n focus,\n blur,\n }),\n [\n activeFocusId,\n isFocusEnabled,\n addFocusable,\n removeFocusable,\n activateFocusable,\n deactivateFocusable,\n enableFocus,\n disableFocus,\n focusNext,\n focusPrevious,\n focus,\n blur,\n ],\n )\n\n return React.createElement(InkFocusContext.Provider, { value: contextValue }, children)\n}\n\n// =============================================================================\n// withInkFocus — App-level plugin for pipe() composition\n// =============================================================================\n\nexport interface WithInkFocusOptions {\n inputEmitter?: EventEmitter\n}\n\nexport interface AppWithInkFocus {\n readonly Root: React.ComponentType<{ children: React.ReactNode }>\n}\n\nexport function withInkFocus<T extends RunnableApp>(\n options: WithInkFocusOptions = {},\n): (app: T) => T & AppWithInkFocus {\n return (app: T): T & AppWithInkFocus => {\n const PrevRoot = app.Root ?? Fragment\n const InkFocusRoot = ({ children }: { children: React.ReactNode }) =>\n React.createElement(\n InkFocusProvider,\n { inputEmitter: options.inputEmitter },\n React.createElement(PrevRoot, null, children),\n )\n\n return Object.assign(Object.create(app), {\n Root: InkFocusRoot,\n }) as T & AppWithInkFocus\n }\n}\n","/**\n * Render-output throughput monitor — backs the `SILVERY_STRICT=bytes_out`\n * slug. Watches the bytes silvery's render pipeline writes to stdout and\n * trips at fixed thresholds.\n *\n * Why this exists: silvery emits ANSI bytes to stdout; those bytes leave the\n * process and land in the pty parent's buffer (terminal multiplexer\n * scrollback — cmux, tmux, etc.). Internal heap profilers\n * (`process.memoryUsage`, `Bun.heapStats`, `--inspect`) cannot see this\n * firehose because the bytes are already gone. The 2026-05-13 cmux/silvercode\n * incident attributed multi-GB of memory to cmux, not silvercode; silvercode's\n * own heap looked healthy throughout. See\n * `docs/lessons/cmux-pty-buffer-firehose.md` (in km root) for the full\n * incident write-up.\n *\n * Behavior (fixed thresholds — no env knobs):\n * - WARN at sustained ≥1 MB/s over a 10s window. Emits a `silvery:bytes_out`\n * log + writes frame summaries to `/tmp/silvery-bytes-out-warn-<pid>-<ts>.txt`.\n * Cooldown 10s before re-trip.\n * - PANIC at burst ≥100 MB/s over a 2s window. Emits frame summaries AND a\n * v8 heap snapshot. Cooldown 60s before re-trip.\n *\n * Activation: tier 1 default (inherited from `SILVERY_STRICT=1`). Opt out per\n * test/session with `SILVERY_STRICT=1,!bytes_out`. The options below are\n * TEST overrides only — production reads no env knobs (per the user's\n * \"diagnostics that require remembering ad-hoc env vars don't get used\" rule).\n */\nimport { createLogger, type ConditionalLogger } from \"loggily\"\n\n/**\n * Minimal logger surface the monitor depends on. Lets tests pass a small\n * mock without satisfying the full ConditionalLogger interface.\n */\nexport interface BytesOutLogger {\n warn?: (msg: string | (() => string), data?: unknown) => void\n error?: (msg: string | (() => string), data?: unknown) => void\n}\n\nconst NS = \"silvery:bytes_out\"\n\nconst FRAME_HISTORY = 100\nconst WARN_WINDOW_MS = 10_000\nconst PANIC_WINDOW_MS = 2_000\nconst WARN_COOLDOWN_MS = 10_000\nconst PANIC_COOLDOWN_MS = 60_000\nconst EVICT_OLDER_THAN_MS = 60_000\n\nconst DEFAULT_WARN_MB_PER_SEC = 1\nconst DEFAULT_PANIC_MB_PER_SEC = 100\n\ninterface FrameEntry {\n frameNum: number\n ts: number\n bytes: number\n diagnostics?: BytesOutFrameDiagnostics\n}\n\nexport interface BytesOutFrameDiagnostics {\n source?: \"terminal-artifact\" | \"post-paint-write\" | \"terminal-protocol\"\n owner?: string\n phase?: \"setup\" | \"runtime\" | \"cleanup\" | \"pre-paint\" | \"post-paint\"\n artifactKind?: string\n reason?: string\n mode?: \"fullscreen\" | \"inline\"\n width?: number\n height?: number\n prevWidth?: number\n prevHeight?: number\n changedCells?: number\n rawChangedCells?: number\n dirtyRows?: number\n outputChars?: number\n cursorChars?: number\n syncWrapped?: boolean\n}\n\n/**\n * Test-only overrides for the monitor. Production code passes nothing — the\n * scheduler instantiates the monitor with all defaults so the activation\n * surface stays at `SILVERY_STRICT` slugs only.\n */\nexport interface BytesOutMonitorOptions {\n /** TEST: override WARN threshold (MB/s). Production is fixed at 1 MB/s. */\n warnMbPerSec?: number\n /** TEST: override PANIC threshold (MB/s). Production is fixed at 100 MB/s. */\n panicMbPerSec?: number\n /** TEST: skip the v8 heap snapshot at PANIC. Production always writes one. */\n skipHeapSnapshot?: boolean\n /** TEST: snapshot output directory. Production is `/tmp`. */\n snapshotDir?: string\n /** TEST: logger override (minimal warn/error surface; full ConditionalLogger also accepted). */\n logger?: BytesOutLogger | ConditionalLogger\n /** TEST: clock override. */\n now?: () => number\n /** TEST: heap-snapshot writer override. */\n writeHeapSnapshot?: (path: string) => string | void\n /** TEST: frame-summary writer override. */\n writeFile?: (path: string, contents: string) => void\n /** TEST: process id used in dump paths. */\n pid?: number\n}\n\nexport interface BytesOutTrip {\n kind: \"warn\" | \"panic\"\n ts: number\n /** MB/s observed over the window. */\n mbPerSec: number\n /** Path to the frame-summaries dump. */\n summaryPath: string\n /** Path to the heap snapshot — present at PANIC unless skipped. */\n snapshotPath?: string\n}\n\nexport interface BytesOutMonitor {\n recordWrite(frameNum: number, bytes: number, diagnostics?: BytesOutFrameDiagnostics): void\n /** Most recent trip event (across both warn + panic) — primarily for tests. */\n readonly lastTrip: BytesOutTrip | null\n /** Last 100 frame entries — primarily for tests. */\n snapshotFrames(): readonly FrameEntry[]\n dispose(): void\n}\n\nexport function createBytesOutMonitor(options: BytesOutMonitorOptions = {}): BytesOutMonitor {\n const warnMbPerSec = options.warnMbPerSec ?? DEFAULT_WARN_MB_PER_SEC\n const panicMbPerSec = options.panicMbPerSec ?? DEFAULT_PANIC_MB_PER_SEC\n const skipHeapSnapshot = options.skipHeapSnapshot ?? false\n const snapshotDir = options.snapshotDir ?? \"/tmp\"\n const log = options.logger ?? createLogger(NS)\n const now = options.now ?? Date.now\n const writeHeapSnapshot = options.writeHeapSnapshot ?? defaultWriteHeapSnapshot\n const writeFile = options.writeFile ?? defaultWriteFile\n const pid = options.pid ?? process.pid\n\n // Convert thresholds: MB/s × window-seconds → bytes-in-window.\n const warnBytesInWindow = warnMbPerSec * 1_000_000 * (WARN_WINDOW_MS / 1000)\n const panicBytesInWindow = panicMbPerSec * 1_000_000 * (PANIC_WINDOW_MS / 1000)\n\n const frames: FrameEntry[] = []\n let lastWarnAt = 0\n let lastPanicAt = 0\n let lastTrip: BytesOutTrip | null = null\n let disposed = false\n\n function evictOld(cutoff: number): void {\n while (frames.length > 0 && frames[0]!.ts < cutoff) frames.shift()\n }\n\n function sumBytesInWindow(windowMs: number, t: number): number {\n const horizon = t - windowMs\n let sum = 0\n for (let i = frames.length - 1; i >= 0; i--) {\n const f = frames[i]!\n if (f.ts < horizon) break\n sum += f.bytes\n }\n return sum\n }\n\n function dumpFrameSummaries(filePath: string): boolean {\n try {\n const tail = frames.slice(-FRAME_HISTORY)\n const header =\n \"# frameNum\\tts\\tbytes\\tsource\\towner\\tphase\\tartifactKind\\treason\\tmode\\twidth\\theight\\tprevWidth\\tprevHeight\\tchangedCells\\trawChangedCells\\tdirtyRows\\toutputChars\\tcursorChars\\tsyncWrapped\\n\"\n const body = tail\n .map((f) => {\n const d = f.diagnostics\n return [\n f.frameNum,\n new Date(f.ts).toISOString(),\n f.bytes,\n d?.source,\n d?.owner,\n d?.phase,\n d?.artifactKind,\n d?.reason,\n d?.mode,\n d?.width,\n d?.height,\n d?.prevWidth,\n d?.prevHeight,\n d?.changedCells,\n d?.rawChangedCells,\n d?.dirtyRows,\n d?.outputChars,\n d?.cursorChars,\n d?.syncWrapped,\n ]\n .map(formatSummaryField)\n .join(\"\\t\")\n })\n .join(\"\\n\")\n writeFile(filePath, header + body + \"\\n\")\n return true\n } catch (e) {\n log.error?.(`failed to write frame summary: ${(e as Error).message}`)\n return false\n }\n }\n\n function maybePanic(t: number): boolean {\n if (t - lastPanicAt < PANIC_COOLDOWN_MS) return false\n const burstBytes = sumBytesInWindow(PANIC_WINDOW_MS, t)\n if (burstBytes < panicBytesInWindow) return false\n\n const summaryPath = `${snapshotDir}/silvery-bytes-out-panic-${pid}-${t}.summary.txt`\n dumpFrameSummaries(summaryPath)\n\n let snapshotPath: string | undefined\n if (!skipHeapSnapshot) {\n const candidate = `${snapshotDir}/silvery-firehose-${pid}-${t}.heapsnapshot`\n try {\n const written = writeHeapSnapshot(candidate)\n snapshotPath = typeof written === \"string\" ? written : candidate\n } catch (e) {\n log.error?.(`failed to write heap snapshot: ${(e as Error).message}`)\n }\n }\n\n const mbPerSec = burstBytes / 1_000_000 / (PANIC_WINDOW_MS / 1000)\n log.error?.(\n `PANIC render-out burst ${mbPerSec.toFixed(1)} MB/s exceeds ${panicMbPerSec} MB/s for ${PANIC_WINDOW_MS}ms`,\n { summary: summaryPath, heapSnapshot: snapshotPath },\n )\n\n lastPanicAt = t\n lastTrip = { kind: \"panic\", ts: t, mbPerSec, summaryPath, snapshotPath }\n return true\n }\n\n function maybeWarn(t: number): boolean {\n if (t - lastWarnAt < WARN_COOLDOWN_MS) return false\n const sustainedBytes = sumBytesInWindow(WARN_WINDOW_MS, t)\n if (sustainedBytes < warnBytesInWindow) return false\n\n const summaryPath = `${snapshotDir}/silvery-bytes-out-warn-${pid}-${t}.summary.txt`\n dumpFrameSummaries(summaryPath)\n\n const mbPerSec = sustainedBytes / 1_000_000 / (WARN_WINDOW_MS / 1000)\n log.warn?.(\n `WARN render-out sustained ${mbPerSec.toFixed(1)} MB/s exceeds ${warnMbPerSec} MB/s over ${WARN_WINDOW_MS}ms`,\n { summary: summaryPath },\n )\n\n lastWarnAt = t\n lastTrip = { kind: \"warn\", ts: t, mbPerSec, summaryPath }\n return true\n }\n\n function recordWrite(\n frameNum: number,\n bytes: number,\n diagnostics?: BytesOutFrameDiagnostics,\n ): void {\n if (disposed) return\n const t = now()\n evictOld(t - EVICT_OLDER_THAN_MS)\n frames.push({ frameNum, ts: t, bytes, diagnostics })\n // PANIC supersedes WARN in the same record — they have separate cooldowns.\n if (maybePanic(t)) return\n maybeWarn(t)\n }\n\n function snapshotFrames(): readonly FrameEntry[] {\n return frames.slice()\n }\n\n function dispose(): void {\n disposed = true\n frames.length = 0\n }\n\n return {\n recordWrite,\n get lastTrip() {\n return lastTrip\n },\n snapshotFrames,\n dispose,\n }\n}\n\nfunction formatSummaryField(value: unknown): string {\n if (value === undefined || value === null) return \"\"\n return String(value)\n}\n\nfunction defaultWriteHeapSnapshot(path: string): string | void {\n const v8 = loadV8()\n return v8.writeHeapSnapshot(path)\n}\n\nfunction defaultWriteFile(path: string, contents: string): void {\n // sync-node-builtin: multi-target lazy guard via getBuiltinModule (ESM-only wave-3).\n const fs = process.getBuiltinModule(\"node:fs\")\n fs.writeFileSync(path, contents)\n}\n\nfunction loadV8(): typeof import(\"node:v8\") {\n // sync-node-builtin: multi-target lazy guard via getBuiltinModule (ESM-only wave-3).\n return process.getBuiltinModule(\"node:v8\")\n}\n","/**\n * Repaint-storm (flicker) monitor — backs the `SILVERY_STRICT=flicker` slug\n * (alias intent: \"cls_storm\"). Watches the ANSI byte stream silvery's render\n * pipeline writes to the terminal and PANICs when destructive full-screen\n * clears / scroll-region resets repeat fast enough to constitute visible\n * flicker.\n *\n * Why this exists: flicker is the single hardest render bug to catch — it is\n * invisible to text-frame assertions (the FINAL frame is correct; the damage\n * is the transient blank between frames) and historically took eyeball +\n * `#undead` rounds to pin down. The `@ag/code/20297-pane-flicker-on-resize`\n * incident was a bare `\\x1b[2J` emitted OUTSIDE the DEC-2026 sync region on\n * every resize: each `2J` blanked the pane for one composited frame before the\n * repaint landed. Repeated rapidly (resize-drag, a layout limit-cycle, or a\n * stale process re-emitting frames) that is a strobing blank-flash. This\n * monitor makes that class fail LOUD + NAMED at the source.\n *\n * What counts as a storm signal (per frame written to the terminal):\n * - `\\x1b[2J` / `\\x1b[3J` — erase-in-display (the 20297 culprit). `\\x1b[J` and\n * `\\x1b[0J` (erase-to-end) and `\\x1b[1J` (erase-to-start) are NOT counted:\n * they are partial, non-blanking, and used legitimately by inline/static\n * output. Only the full-screen erasers blank the viewport.\n * - `\\x1b[r` / `\\x1b[<top>;<bottom>r` — DECSTBM scroll-region reset/set. A\n * scroll-region thrash (set/reset churn) is the other shape of repaint\n * storm (a \"scroll storm\"); a single frame carrying one is benign,\n * repetition in the window is not.\n *\n * What does NOT count (CRITICAL — the 20297 *fix* must never false-trip):\n * - A homed full-viewport repaint (`\\x1b[H` followed by a full `bufferToAnsi`\n * rewrite of every cell) is the LEGITIMATE post-20297 resize/focus frame. It\n * carries NO `2J`/`3J` at all, so it registers ZERO storm signals — it can\n * never trip this monitor regardless of size. The monitor targets\n * destructive-clear REPETITION within a window, not full repaints, and not a\n * single isolated clear.\n *\n * Behavior (fixed thresholds — no env knobs):\n * - PANIC when ≥ `PANIC_CLEARS` destructive clears land within `WINDOW_MS`\n * with no intervening settle (a settle = a frame in the window that carried\n * zero storm signals, i.e. the storm actually paused). The diagnostic NAMES\n * the offending sequence + the count + the window, plus a culprit hint\n * (bare `2J` outside the sync region — the @ag/code/20297 signature).\n * Cooldown `PANIC_COOLDOWN_MS` before a re-trip so a genuine sustained storm\n * logs once, not every frame.\n *\n * Activation: tier 1 default (inherited from `SILVERY_STRICT=1`, the same tier\n * as `bytes_out`/`mem`). Opt out per test/session with\n * `SILVERY_STRICT=1,!flicker`. The options below are TEST overrides only —\n * production reads no env knobs (per the user's \"diagnostics that require\n * remembering ad-hoc env vars don't get used\" rule).\n */\nimport { createLogger, type ConditionalLogger } from \"loggily\"\n\n/**\n * Minimal logger surface the monitor depends on. Lets tests pass a small\n * mock without satisfying the full ConditionalLogger interface.\n */\nexport interface FlickerLogger {\n warn?: (msg: string | (() => string), data?: unknown) => void\n error?: (msg: string | (() => string), data?: unknown) => void\n}\n\nconst NS = \"silvery:flicker\"\n\n/** Rolling window over which clears are counted. */\nconst WINDOW_MS = 1_000\n/**\n * Number of destructive full-screen clears within the window (with no\n * intervening settle frame) that constitutes a repaint storm.\n *\n * Three is deliberately above any LEGITIMATE clear cadence:\n * - A normal session emits at most ONE full clear per discrete event (startup\n * alt-screen entry, an explicit `invalidate({ clearScreen })` on focus-in).\n * Post-20297 a resize/focus repaint emits ZERO `2J` (the homed full repaint\n * IS the clear), so even rapid resize-drag produces no clears at all.\n * - The 20297 BUG emitted a `2J` on EVERY resize frame; a resize-drag fires\n * tens of frames per second → ≫3 clears in a 1 s window → trips.\n */\nconst PANIC_CLEARS = 3\n/** Cooldown before a sustained storm is allowed to re-log. */\nconst PANIC_COOLDOWN_MS = 5_000\n/** Drop frame records older than this to keep memory bounded. */\nconst EVICT_OLDER_THAN_MS = 10_000\n/** Last-N frames retained for the diagnostic dump. */\nconst FRAME_HISTORY = 50\n\n/**\n * Destructive full-screen erase-in-display: `\\x1b[2J` (whole viewport) and\n * `\\x1b[3J` (viewport + scrollback). Deliberately does NOT match `\\x1b[J`,\n * `\\x1b[0J`, or `\\x1b[1J` — those are partial, non-blanking erases.\n */\nconst FULL_CLEAR_RE = /\\x1b\\[[23]J/g\n/**\n * DECSTBM scroll-region reset/set: `\\x1b[r` (reset to full screen) or\n * `\\x1b[<top>;<bottom>r`. A private-mode variant (`\\x1b[?...r`) is matched too\n * so DEC private scroll-region churn is caught.\n */\nconst SCROLL_REGION_RE = /\\x1b\\[\\??(?:\\d+(?:;\\d+)*)?r/g\n\n/** Per-frame storm-signal tally. */\ninterface FrameSignals {\n fullClears: number\n scrollRegionResets: number\n}\n\ninterface FrameEntry {\n frameNum: number\n ts: number\n bytes: number\n signals: FrameSignals\n}\n\n/**\n * Test-only overrides for the monitor. Production code passes nothing — the\n * scheduler instantiates the monitor with all defaults so the activation\n * surface stays at `SILVERY_STRICT` slugs only.\n */\nexport interface FlickerMonitorOptions {\n /** TEST: override the clears-in-window PANIC threshold. Production is fixed. */\n panicClears?: number\n /** TEST: override the rolling window (ms). Production is fixed at 1000ms. */\n windowMs?: number\n /**\n * TEST: when true the monitor records + diagnoses but does NOT throw, so a\n * test can assert the trip via `lastTrip` without unwinding the stack.\n * Production always THROWS on PANIC (fail loud). Default false.\n */\n noThrow?: boolean\n /** TEST: logger override (minimal warn/error surface; full ConditionalLogger also accepted). */\n logger?: FlickerLogger | ConditionalLogger\n /** TEST: clock override. */\n now?: () => number\n /** TEST: frame-summary writer override. */\n writeFile?: (path: string, contents: string) => void\n /** TEST: summary output directory. Production is `/tmp`. */\n snapshotDir?: string\n /** TEST: process id used in dump paths. */\n pid?: number\n}\n\nexport interface FlickerTrip {\n kind: \"panic\"\n ts: number\n /** Number of destructive clears counted in the window. */\n clears: number\n /** Number of scroll-region resets counted in the window. */\n scrollRegionResets: number\n /** The window (ms) the count was measured over. */\n windowMs: number\n /** The named offending sequence (human-readable). */\n sequence: string\n /** The full panic message (also passed to log.error + the thrown Error). */\n message: string\n /** Path to the frame-summaries dump. */\n summaryPath: string\n}\n\nexport interface FlickerMonitor {\n /**\n * Record one frame's worth of bytes actually written to the terminal. The\n * monitor scans `output` for destructive-clear / scroll-region sequences and\n * trips when their rate within the rolling window crosses the threshold.\n */\n recordFrame(frameNum: number, output: string): void\n /** Most recent trip event — primarily for tests. */\n readonly lastTrip: FlickerTrip | null\n /** Last frame entries — primarily for tests. */\n snapshotFrames(): readonly FrameEntry[]\n dispose(): void\n}\n\nexport function createFlickerMonitor(options: FlickerMonitorOptions = {}): FlickerMonitor {\n const panicClears = options.panicClears ?? PANIC_CLEARS\n const windowMs = options.windowMs ?? WINDOW_MS\n const noThrow = options.noThrow ?? false\n const log = options.logger ?? createLogger(NS)\n const now = options.now ?? Date.now\n const writeFile = options.writeFile ?? defaultWriteFile\n const snapshotDir = options.snapshotDir ?? \"/tmp\"\n const pid = options.pid ?? process.pid\n\n const frames: FrameEntry[] = []\n let lastPanicAt = 0\n let lastTrip: FlickerTrip | null = null\n let disposed = false\n\n function evictOld(cutoff: number): void {\n while (frames.length > 0 && frames[0]!.ts < cutoff) frames.shift()\n }\n\n /**\n * Count storm signals across the in-window tail. Returns the totals AND\n * whether the storm settled (a window frame carried zero signals) — a settle\n * resets the \"fast repetition\" judgment so a slow trickle of clears spread\n * across calm frames never trips.\n */\n function tallyWindow(t: number): {\n fullClears: number\n scrollRegionResets: number\n settled: boolean\n } {\n const horizon = t - windowMs\n let fullClears = 0\n let scrollRegionResets = 0\n let settled = false\n for (let i = frames.length - 1; i >= 0; i--) {\n const f = frames[i]!\n if (f.ts < horizon) break\n const s = f.signals\n const frameSignals = s.fullClears + s.scrollRegionResets\n if (frameSignals === 0) {\n // A settle frame in the window means the storm paused — the clears on\n // either side of it are not a single uninterrupted burst.\n settled = true\n break\n }\n fullClears += s.fullClears\n scrollRegionResets += s.scrollRegionResets\n }\n return { fullClears, scrollRegionResets, settled }\n }\n\n function dumpFrameSummaries(filePath: string): void {\n try {\n const tail = frames.slice(-FRAME_HISTORY)\n const header = \"# frameNum\\tts\\tbytes\\tfullClears\\tscrollRegionResets\\n\"\n const body = tail\n .map((f) =>\n [\n f.frameNum,\n new Date(f.ts).toISOString(),\n f.bytes,\n f.signals.fullClears,\n f.signals.scrollRegionResets,\n ].join(\"\\t\"),\n )\n .join(\"\\n\")\n writeFile(filePath, header + body + \"\\n\")\n } catch (e) {\n log.error?.(`failed to write flicker frame summary: ${(e as Error).message}`)\n }\n }\n\n function maybePanic(t: number): void {\n if (t - lastPanicAt < PANIC_COOLDOWN_MS) return\n const { fullClears, scrollRegionResets, settled } = tallyWindow(t)\n // The storm must be UNINTERRUPTED within the window — a settle frame means\n // the clears were not a single fast burst (so an occasional legit clear,\n // even a few across a calm session, never accumulates into a trip).\n if (settled) return\n if (fullClears < panicClears) return\n\n const summaryPath = `${snapshotDir}/silvery-flicker-panic-${pid}-${t}.summary.txt`\n dumpFrameSummaries(summaryPath)\n\n const clearSeq = \"\\\\x1b[2J\"\n const sequence =\n scrollRegionResets > 0\n ? \"\\\\x1b[2J/\\\\x1b[3J full-screen clear + \\\\x1b[r scroll-region reset\"\n : \"\\\\x1b[2J/\\\\x1b[3J full-screen clear\"\n const message =\n `PANIC repaint storm: ${fullClears} full-screen clears` +\n (scrollRegionResets > 0 ? ` + ${scrollRegionResets} scroll-region resets` : \"\") +\n ` in ${windowMs}ms (threshold ${panicClears}). ` +\n `Offending sequence: ${sequence}. ` +\n `Likely culprit: a destructive ${clearSeq} emitted per-frame outside the ` +\n `DEC-2026 sync region (the @ag/code/20297-pane-flicker-on-resize signature) — ` +\n `a full repaint should home (\\\\x1b[H) and overwrite cells instead. ` +\n `Frame summary: ${summaryPath}`\n\n log.error?.(message, { clears: fullClears, scrollRegionResets, windowMs, summary: summaryPath })\n\n lastPanicAt = t\n lastTrip = {\n kind: \"panic\",\n ts: t,\n clears: fullClears,\n scrollRegionResets,\n windowMs,\n sequence,\n message,\n summaryPath,\n }\n\n if (!noThrow) throw new Error(message)\n }\n\n function recordFrame(frameNum: number, output: string): void {\n if (disposed) return\n const t = now()\n evictOld(t - EVICT_OLDER_THAN_MS)\n const signals: FrameSignals = {\n fullClears: countMatches(output, FULL_CLEAR_RE),\n scrollRegionResets: countMatches(output, SCROLL_REGION_RE),\n }\n frames.push({ frameNum, ts: t, bytes: output.length, signals })\n maybePanic(t)\n }\n\n function snapshotFrames(): readonly FrameEntry[] {\n return frames.slice()\n }\n\n function dispose(): void {\n disposed = true\n frames.length = 0\n }\n\n return {\n recordFrame,\n get lastTrip() {\n return lastTrip\n },\n snapshotFrames,\n dispose,\n }\n}\n\nfunction countMatches(haystack: string, re: RegExp): number {\n // re carries the global flag; reset lastIndex defensively (these are\n // module-level singletons reused across calls).\n re.lastIndex = 0\n let count = 0\n while (re.exec(haystack) !== null) count++\n re.lastIndex = 0\n return count\n}\n\nfunction defaultWriteFile(path: string, contents: string): void {\n // sync-node-builtin: multi-target lazy guard via getBuiltinModule (ESM-only wave-3).\n const fs = process.getBuiltinModule(\"node:fs\")\n fs.writeFileSync(path, contents)\n}\n","/**\n * In-process heap poll monitor — backs the `SILVERY_STRICT=mem` slug\n * (Layer 2 of the memory-observability stack).\n *\n * Samples `process.memoryUsage()` every 30 s on a fixed interval, logs the\n * full breakdown via the `silvery:mem` loggily namespace, and trips a\n * one-shot WARN line if RSS or `external + arrayBuffers` doubles within a\n * 60 s window.\n *\n * Where this fits: L1 (`bytes_out`) watches data leaving the process via the\n * render pipeline. L2 (`mem`, this file) watches data still inside the\n * process. L4 (`bun memwatch <pid>`) is the external view. Together they\n * cover the bytes-out / bytes-in / RSS-outside triad for a pty-attached\n * TUI. See `docs/lessons/cmux-pty-buffer-firehose.md` (in km root) for the\n * motivating incident.\n *\n * Convention: tier-1 default, opt-out via `SILVERY_STRICT=1,!mem`. No env\n * knobs — fixed sample cadence + threshold in code. Per the user-direction\n * documented in `vendor/silvery/docs/guide/debugging.md`:\n * \"diagnostics that require remembering ad-hoc env vars don't get used.\"\n */\nimport { createLogger, type ConditionalLogger } from \"loggily\"\n\n/** Minimal logger surface — tests can pass a partial mock. */\nexport interface MemMonitorLogger {\n info?: (msg: string | (() => string), data?: unknown) => void\n warn?: (msg: string | (() => string), data?: unknown) => void\n error?: (msg: string | (() => string), data?: unknown) => void\n}\n\nconst NS = \"silvery:mem\"\n\nconst SAMPLE_INTERVAL_MS = 30_000\nconst DOUBLE_WINDOW_MS = 60_000\nconst SAMPLE_HISTORY = 8 // 8 × 30s = 240s of history (covers the 60s window comfortably)\n\ninterface MemSample {\n ts: number\n rss: number\n heapUsed: number\n heapTotal: number\n external: number\n arrayBuffers: number\n}\n\nexport interface MemMonitorOptions {\n /** TEST: logger override (minimal info/warn/error surface). */\n logger?: MemMonitorLogger | ConditionalLogger\n /** TEST: clock override. */\n now?: () => number\n /** TEST: memoryUsage override. */\n memoryUsage?: () => NodeJS.MemoryUsage\n /** TEST: drive ticks manually instead of via setInterval (use `tick()`). */\n manual?: boolean\n}\n\nexport interface MemMonitor {\n /** Force one sample now (also fired by the internal interval). */\n tick(): void\n /** Most recent sample (test introspection). */\n readonly lastSample: MemSample | null\n /** Last WARN trip (test introspection). */\n readonly lastWarn: { ts: number; reason: string } | null\n dispose(): void\n}\n\nexport function createMemMonitor(options: MemMonitorOptions = {}): MemMonitor {\n const log = options.logger ?? createLogger(NS)\n const now = options.now ?? Date.now\n const memoryUsage = options.memoryUsage ?? process.memoryUsage\n const manual = options.manual ?? false\n\n const samples: MemSample[] = []\n let lastWarn: { ts: number; reason: string } | null = null\n let lastWarnReason: string | null = null\n let disposed = false\n let interval: ReturnType<typeof setInterval> | null = null\n\n function evictOld(cutoff: number): void {\n while (samples.length > 0 && samples[0]!.ts < cutoff) samples.shift()\n while (samples.length > SAMPLE_HISTORY) samples.shift()\n }\n\n function snapshotMin(field: keyof Omit<MemSample, \"ts\">, windowMs: number, t: number): number {\n const horizon = t - windowMs\n let min = Number.POSITIVE_INFINITY\n for (const s of samples) {\n if (s.ts < horizon) continue\n const v = s[field]\n if (v < min) min = v\n }\n return min\n }\n\n function tick(): void {\n if (disposed) return\n const t = now()\n const m = memoryUsage()\n const sample: MemSample = {\n ts: t,\n rss: m.rss,\n heapUsed: m.heapUsed,\n heapTotal: m.heapTotal,\n external: m.external,\n arrayBuffers: m.arrayBuffers,\n }\n samples.push(sample)\n evictOld(t - DOUBLE_WINDOW_MS * 2) // keep at least one full window of history\n\n log.info?.(\"mem sample\", {\n rss_mb: Math.round(sample.rss / 1_000_000),\n heap_used_mb: Math.round(sample.heapUsed / 1_000_000),\n heap_total_mb: Math.round(sample.heapTotal / 1_000_000),\n external_mb: Math.round(sample.external / 1_000_000),\n array_buffers_mb: Math.round(sample.arrayBuffers / 1_000_000),\n })\n\n // Doubling check — RSS or external+arrayBuffers vs the floor within window.\n const rssMin = snapshotMin(\"rss\", DOUBLE_WINDOW_MS, t)\n if (Number.isFinite(rssMin) && rssMin > 0 && sample.rss >= rssMin * 2) {\n const reason = `RSS doubled in ${DOUBLE_WINDOW_MS / 1000}s: ${Math.round(rssMin / 1_000_000)} → ${Math.round(sample.rss / 1_000_000)} MB`\n if (reason !== lastWarnReason) {\n log.warn?.(`WARN ${reason}`)\n lastWarn = { ts: t, reason }\n lastWarnReason = reason\n }\n return\n }\n const offHeapNow = sample.external + sample.arrayBuffers\n // Compute synthetic min for off-heap = min(external + arrayBuffers) within window.\n const horizon = t - DOUBLE_WINDOW_MS\n let offHeapMin = Number.POSITIVE_INFINITY\n for (const s of samples) {\n if (s.ts < horizon) continue\n const sum = s.external + s.arrayBuffers\n if (sum < offHeapMin) offHeapMin = sum\n }\n if (Number.isFinite(offHeapMin) && offHeapMin > 0 && offHeapNow >= offHeapMin * 2) {\n const reason = `external+arrayBuffers doubled in ${DOUBLE_WINDOW_MS / 1000}s: ${Math.round(offHeapMin / 1_000_000)} → ${Math.round(offHeapNow / 1_000_000)} MB`\n if (reason !== lastWarnReason) {\n log.warn?.(`WARN ${reason}`)\n lastWarn = { ts: t, reason }\n lastWarnReason = reason\n }\n }\n }\n\n function dispose(): void {\n if (disposed) return\n disposed = true\n if (interval !== null) {\n clearInterval(interval)\n interval = null\n }\n samples.length = 0\n }\n\n if (!manual) {\n interval = setInterval(tick, SAMPLE_INTERVAL_MS)\n // Don't keep the process alive just for memory sampling.\n const maybeUnref = interval as unknown as { unref?: () => unknown }\n if (typeof maybeUnref.unref === \"function\") {\n maybeUnref.unref()\n }\n }\n\n return {\n tick,\n get lastSample() {\n return samples.length > 0 ? samples[samples.length - 1]! : null\n },\n get lastWarn() {\n return lastWarn\n },\n dispose,\n }\n}\n","/**\n * Debug utilities for incremental render mismatch diagnostics.\n *\n * When SILVERY_STRICT detects a mismatch between incremental and fresh renders,\n * these utilities help identify the root cause by providing:\n * - Node attribution (which node owns the mismatched cell)\n * - Dirty flag state (what flags were set before render)\n * - Layout changes (prevLayout vs boxRect)\n * - Scroll context (offset changes, hidden items)\n */\n\nimport type { Cell } from \"@silvery/ag-term/buffer\"\nimport type { BoxProps, AgNode, Rect, TextProps } from \"@silvery/ag/types\"\nimport {\n isDirty,\n isAnyDirty,\n CONTENT_BIT,\n STYLE_PROPS_BIT,\n SUBTREE_BIT,\n CHILDREN_BIT,\n} from \"@silvery/ag/epoch\"\nimport type { RenderPhaseStats } from \"@silvery/ag-term/pipeline/types\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Debug info about a node at a screen position */\nexport interface NodeDebugInfo {\n /** Node ID (if set via props.id) */\n id: string | undefined\n /** Node type (silvery-box, silvery-text, silvery-root) */\n type: string\n /** Path from root to this node (IDs or indices) */\n path: string\n /** Index within parent's children array */\n childIndex: number | null\n /** Dirty flags at time of mismatch */\n dirtyFlags: {\n contentDirty: boolean\n stylePropsDirty: boolean\n subtreeDirty: boolean\n childrenDirty: boolean\n }\n /** Layout info */\n layout: {\n prevLayout: Rect | null\n boxRect: Rect | null\n scrollRect: Rect | null\n layoutChanged: boolean\n }\n /** Scroll context (if this is a scroll container or inside one) */\n scroll?: {\n offset: number\n prevOffset: number\n offsetChanged: boolean\n contentHeight: number\n viewportHeight: number\n hiddenAbove: number\n hiddenBelow: number\n firstVisibleChild: number\n lastVisibleChild: number\n }\n /** Background color from props */\n backgroundColor: string | undefined\n /** Number of children */\n childCount: number\n /** Whether node is hidden (Suspense) */\n hidden: boolean\n}\n\n/** Full mismatch debug context */\nexport interface MismatchDebugContext {\n /** Screen position of the mismatch */\n position: { x: number; y: number }\n /** Cell values */\n cells: {\n incremental: Cell\n fresh: Cell\n }\n /** Render number */\n renderNum: number\n /** Node that owns this screen position (innermost) */\n node: NodeDebugInfo | null\n /** Scroll container ancestry (if any) */\n scrollAncestors: NodeDebugInfo[]\n /** All nodes whose scrollRect contains this position */\n containingNodes: NodeDebugInfo[]\n /** Fast-path analysis - why the node was likely skipped */\n fastPathAnalysis: string[]\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Find the innermost node at a screen position.\n */\nexport function findNodeAtPosition(root: AgNode, x: number, y: number): AgNode | null {\n let result: AgNode | null = null\n\n function visit(node: AgNode): void {\n const rect = node.scrollRect\n if (!rect) return\n\n // Check if position is within this node's scrollRect\n if (x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height) {\n result = node // This node contains the position\n\n // Check children (later children render on top of earlier ones)\n for (const child of node.children) {\n visit(child)\n }\n }\n }\n\n visit(root)\n return result\n}\n\n/**\n * Find all nodes whose scrollRect contains the given position.\n * Returns nodes from root to innermost (outermost first).\n */\nexport function findAllContainingNodes(root: AgNode, x: number, y: number): AgNode[] {\n const result: AgNode[] = []\n\n function visit(node: AgNode): void {\n const rect = node.scrollRect\n if (!rect) return\n\n if (x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height) {\n result.push(node)\n for (const child of node.children) {\n visit(child)\n }\n }\n }\n\n visit(root)\n return result\n}\n\n/**\n * Get the path from root to a node (for identification).\n */\nfunction getNodePath(node: AgNode): string {\n const parts: string[] = []\n let current: AgNode | null = node\n\n while (current) {\n const props = current.props as BoxProps & TextProps\n if (props.id) {\n parts.unshift(`#${props.id}`)\n } else if (current.parent) {\n const idx = current.parent.children.indexOf(current)\n parts.unshift(`[${idx}]`)\n } else {\n parts.unshift(\"root\")\n }\n current = current.parent\n }\n\n return parts.join(\" > \")\n}\n\n/**\n * Check if a rect changed (position or size).\n */\nfunction rectChanged(a: Rect | null, b: Rect | null): boolean {\n if (a === b) return false\n if (!a || !b) return true\n return a.x !== b.x || a.y !== b.y || a.width !== b.width || a.height !== b.height\n}\n\n/**\n * Extract debug info from a node.\n */\nexport function getNodeDebugInfo(node: AgNode): NodeDebugInfo {\n const props = node.props as BoxProps & TextProps\n\n // Get child index within parent\n let childIndex: number | null = null\n if (node.parent) {\n childIndex = node.parent.children.indexOf(node)\n }\n\n return {\n id: props.id,\n type: node.type,\n path: getNodePath(node),\n childIndex,\n dirtyFlags: {\n contentDirty: isDirty(node.dirtyBits, node.dirtyEpoch, CONTENT_BIT),\n stylePropsDirty: isDirty(node.dirtyBits, node.dirtyEpoch, STYLE_PROPS_BIT),\n subtreeDirty: isDirty(node.dirtyBits, node.dirtyEpoch, SUBTREE_BIT),\n childrenDirty: isDirty(node.dirtyBits, node.dirtyEpoch, CHILDREN_BIT),\n },\n layout: {\n prevLayout: node.prevLayout,\n boxRect: node.boxRect,\n scrollRect: node.scrollRect,\n layoutChanged: rectChanged(node.prevLayout, node.boxRect),\n },\n scroll: node.scrollState\n ? {\n offset: node.scrollState.offset,\n prevOffset: node.scrollState.prevOffset,\n offsetChanged: node.scrollState.offset !== node.scrollState.prevOffset,\n contentHeight: node.scrollState.contentHeight,\n viewportHeight: node.scrollState.viewportHeight,\n hiddenAbove: node.scrollState.hiddenAbove,\n hiddenBelow: node.scrollState.hiddenBelow,\n firstVisibleChild: node.scrollState.firstVisibleChild,\n lastVisibleChild: node.scrollState.lastVisibleChild,\n }\n : undefined,\n backgroundColor: props.backgroundColor,\n childCount: node.children.length,\n hidden: node.hidden ?? false,\n }\n}\n\n/**\n * Find scroll container ancestors for a node.\n */\nfunction findScrollAncestors(node: AgNode): AgNode[] {\n const result: AgNode[] = []\n let current = node.parent\n\n while (current) {\n if (current.scrollState) {\n result.push(current)\n }\n current = current.parent\n }\n\n return result\n}\n\n/**\n * Analyze why a node might have been incorrectly skipped by fast-path.\n */\nfunction analyzeFastPath(node: AgNode | null, scrollAncestors: AgNode[]): string[] {\n const analysis: string[] = []\n\n if (!node) {\n analysis.push(\"⚠ No node found at mismatch position - possible virtualization issue\")\n return analysis\n }\n\n const flags = node\n const allClean = !isAnyDirty(flags.dirtyBits, flags.dirtyEpoch)\n\n if (allClean) {\n analysis.push(\"⚠ ALL DIRTY FLAGS FALSE - fast-path likely skipped this node\")\n }\n\n // Check if node is in a scroll container\n const scrollParent = scrollAncestors[0]\n if (scrollParent?.scrollState) {\n const ss = scrollParent.scrollState\n const childIndex = node.parent ? node.parent.children.indexOf(node) : -1\n\n // Check if this node SHOULD be in visible range\n const inVisibleRange = childIndex >= ss.firstVisibleChild && childIndex <= ss.lastVisibleChild\n if (!inVisibleRange && childIndex >= 0) {\n analysis.push(\n `⚠ Node index ${childIndex} is OUTSIDE visible range [${ss.firstVisibleChild}..${ss.lastVisibleChild}]`,\n )\n analysis.push(\" → Node should have been skipped, but mismatch suggests it should render\")\n } else if (inVisibleRange) {\n analysis.push(\n `✓ Node index ${childIndex} is in visible range [${ss.firstVisibleChild}..${ss.lastVisibleChild}]`,\n )\n }\n\n // Check scroll offset\n if (ss.offset === ss.prevOffset) {\n analysis.push(\"✓ Scroll offset unchanged (fast-path enabled for children)\")\n } else {\n analysis.push(`⚠ Scroll offset CHANGED: ${ss.prevOffset} → ${ss.offset}`)\n }\n\n // Check if visible range might have changed\n if (ss.firstVisibleChild !== 0 || ss.lastVisibleChild !== scrollParent.children.length - 1) {\n analysis.push(\n ` Visible range is partial: [${ss.firstVisibleChild}..${ss.lastVisibleChild}] of ${scrollParent.children.length} children`,\n )\n analysis.push(\" → If visible range changed, newly visible children need rendering\")\n }\n }\n\n // Check prevLayout\n const layoutChanged = rectChanged(node.prevLayout, node.boxRect)\n if (!layoutChanged && node.prevLayout) {\n analysis.push(\"✓ Layout unchanged (prevLayout matches boxRect)\")\n } else if (!node.prevLayout) {\n analysis.push(\"⚠ prevLayout is NULL - node may never have been rendered before\")\n } else {\n analysis.push(\"⚠ Layout CHANGED but node still skipped - dirty flag not set?\")\n }\n\n // Check for sibling child position changes\n if (node.parent && node.parent.children.length > 1) {\n let siblingMoved = false\n for (const sibling of node.parent.children) {\n if (sibling !== node && sibling.boxRect && sibling.prevLayout) {\n if (\n sibling.boxRect.x !== sibling.prevLayout.x ||\n sibling.boxRect.y !== sibling.prevLayout.y\n ) {\n siblingMoved = true\n break\n }\n }\n }\n if (siblingMoved) {\n analysis.push(\"⚠ SIBLING POSITION CHANGED - parent should have detected this\")\n }\n }\n\n // Check hidden state\n if (node.hidden) {\n analysis.push(\"⚠ Node is HIDDEN (Suspense) - should not be rendered\")\n }\n\n return analysis\n}\n\n/**\n * Build full mismatch debug context.\n */\nexport function buildMismatchContext(\n root: AgNode,\n x: number,\n y: number,\n incrementalCell: Cell,\n freshCell: Cell,\n renderNum: number,\n): MismatchDebugContext {\n const innermost = findNodeAtPosition(root, x, y)\n const containing = findAllContainingNodes(root, x, y)\n const scrollAncestorNodes = innermost ? findScrollAncestors(innermost) : []\n\n return {\n position: { x, y },\n cells: {\n incremental: incrementalCell,\n fresh: freshCell,\n },\n renderNum,\n node: innermost ? getNodeDebugInfo(innermost) : null,\n scrollAncestors: scrollAncestorNodes.map(getNodeDebugInfo),\n containingNodes: containing.map(getNodeDebugInfo),\n fastPathAnalysis: analyzeFastPath(innermost, scrollAncestorNodes),\n }\n}\n\n/**\n * Format mismatch context as a human-readable string.\n *\n * @param ctx - The mismatch debug context (node attribution, dirty flags, scroll, fast-path)\n * @param renderPhaseStats - Optional render-phase instrumentation snapshot (auto-included by SILVERY_STRICT)\n */\nexport function formatMismatchContext(\n ctx: MismatchDebugContext,\n renderPhaseStats?: RenderPhaseStats,\n): string {\n const lines: string[] = []\n\n // Header\n lines.push(\n `SILVERY_STRICT: MISMATCH at (${ctx.position.x}, ${ctx.position.y}) on render #${ctx.renderNum}`,\n )\n lines.push(\"\")\n\n // Cell values\n const { incremental, fresh } = ctx.cells\n lines.push(\"CELL VALUES:\")\n lines.push(\n ` incremental: char=${JSON.stringify(incremental.char)} fg=${JSON.stringify(incremental.fg)} bg=${JSON.stringify(incremental.bg)} attrs=${JSON.stringify(incremental.attrs)}`,\n )\n lines.push(\n ` fresh: char=${JSON.stringify(fresh.char)} fg=${JSON.stringify(fresh.fg)} bg=${JSON.stringify(fresh.bg)} attrs=${JSON.stringify(fresh.attrs)}`,\n )\n lines.push(\"\")\n\n // Node attribution\n if (ctx.node) {\n lines.push(\"INNERMOST NODE:\")\n lines.push(` path: ${ctx.node.path}`)\n lines.push(` type: ${ctx.node.type}`)\n if (ctx.node.backgroundColor) {\n lines.push(` backgroundColor: ${ctx.node.backgroundColor}`)\n }\n lines.push(\"\")\n\n // Dirty flags\n const flags = ctx.node.dirtyFlags\n const activeFlags = Object.entries(flags)\n .filter(([, v]) => v)\n .map(([k]) => k)\n lines.push(\"DIRTY FLAGS:\")\n if (activeFlags.length > 0) {\n lines.push(` active: ${activeFlags.join(\", \")}`)\n } else {\n lines.push(\" active: (none - node was clean)\")\n }\n lines.push(\n ` all: contentDirty=${flags.contentDirty} stylePropsDirty=${flags.stylePropsDirty} subtreeDirty=${flags.subtreeDirty} childrenDirty=${flags.childrenDirty}`,\n )\n lines.push(\"\")\n\n // Layout info\n const { layout } = ctx.node\n lines.push(\"LAYOUT:\")\n if (layout.layoutChanged) {\n lines.push(\" ⚠ LAYOUT CHANGED:\")\n lines.push(` prevLayout: ${formatRect(layout.prevLayout)}`)\n lines.push(` boxRect: ${formatRect(layout.boxRect)}`)\n } else {\n lines.push(` boxRect: ${formatRect(layout.boxRect)}`)\n }\n lines.push(` scrollRect: ${formatRect(layout.scrollRect)}`)\n lines.push(\"\")\n\n // Scroll context\n if (ctx.node.scroll) {\n lines.push(\"SCROLL STATE (this node):\")\n formatScrollState(lines, ctx.node.scroll)\n lines.push(\"\")\n }\n } else {\n lines.push(\"INNERMOST NODE: (none found at this position)\")\n lines.push(\"\")\n }\n\n // Scroll ancestors\n if (ctx.scrollAncestors.length > 0) {\n lines.push(\"SCROLL ANCESTORS:\")\n for (const ancestor of ctx.scrollAncestors) {\n lines.push(` ${ancestor.path}:`)\n if (ancestor.scroll) {\n formatScrollState(lines, ancestor.scroll, \" \")\n }\n }\n lines.push(\"\")\n }\n\n // Containing nodes (for debugging layering issues)\n if (ctx.containingNodes.length > 1) {\n lines.push(\"ALL CONTAINING NODES (outermost to innermost):\")\n for (const node of ctx.containingNodes) {\n const flags = Object.entries(node.dirtyFlags)\n .filter(([, v]) => v)\n .map(([k]) => k.replace(\"Dirty\", \"\"))\n .join(\",\")\n const flagStr = flags ? ` [${flags}]` : \" [clean]\"\n const bgStr = node.backgroundColor ? ` bg=${node.backgroundColor}` : \"\"\n const childStr = node.childIndex !== null ? ` child[${node.childIndex}]` : \"\"\n lines.push(` ${node.path}${flagStr}${bgStr}${childStr}`)\n }\n lines.push(\"\")\n }\n\n // Fast-path analysis\n if (ctx.fastPathAnalysis.length > 0) {\n lines.push(\"FAST-PATH ANALYSIS:\")\n for (const line of ctx.fastPathAnalysis) {\n lines.push(` ${line}`)\n }\n lines.push(\"\")\n }\n\n // Render-phase instrumentation stats\n if (renderPhaseStats) {\n const s = renderPhaseStats\n lines.push(\"RENDER PHASE STATS:\")\n lines.push(\n ` nodesVisited: ${s.nodesVisited} nodesRendered: ${s.nodesRendered} nodesSkipped: ${s.nodesSkipped}`,\n )\n lines.push(` textNodes: ${s.textNodes} boxNodes: ${s.boxNodes} clearOps: ${s.clearOps}`)\n // Per-flag breakdown (why nodes weren't skipped)\n const flagLines: string[] = []\n if (s.noPrevBuffer) flagLines.push(`noPrevBuffer=${s.noPrevBuffer}`)\n if (s.flagContentDirty) flagLines.push(`contentDirty=${s.flagContentDirty}`)\n if (s.flagStylePropsDirty) flagLines.push(`stylePropsDirty=${s.flagStylePropsDirty}`)\n if (s.flagLayoutChanged) flagLines.push(`layoutChanged=${s.flagLayoutChanged}`)\n if (s.flagSubtreeDirty) flagLines.push(`subtreeDirty=${s.flagSubtreeDirty}`)\n if (s.flagChildrenDirty) flagLines.push(`childrenDirty=${s.flagChildrenDirty}`)\n if (s.flagChildPositionChanged)\n flagLines.push(`childPositionChanged=${s.flagChildPositionChanged}`)\n if (flagLines.length > 0) {\n lines.push(` render reasons: ${flagLines.join(\", \")}`)\n }\n // Scroll container diagnostics\n if (s.scrollContainerCount > 0) {\n lines.push(\n ` scrollContainers: ${s.scrollContainerCount} viewportCleared: ${s.scrollViewportCleared}`,\n )\n if (s.scrollClearReason) lines.push(` scrollClearReason: ${s.scrollClearReason}`)\n }\n // Normal container diagnostics\n if (s.normalChildrenRepaint > 0) {\n lines.push(` normalChildrenRepaint: ${s.normalChildrenRepaint}`)\n if (s.normalRepaintReason) lines.push(` normalRepaintReason: ${s.normalRepaintReason}`)\n }\n // Cascade diagnostics\n if (s.cascadeMinDepth < 999) {\n lines.push(` cascadeMinDepth: ${s.cascadeMinDepth}`)\n if (s.cascadeNodes) lines.push(` cascadeNodes: ${s.cascadeNodes}`)\n }\n lines.push(\"\")\n }\n\n return lines.join(\"\\n\")\n}\n\nfunction formatRect(rect: Rect | null): string {\n if (!rect) return \"(null)\"\n return `{x:${rect.x}, y:${rect.y}, w:${rect.width}, h:${rect.height}}`\n}\n\nfunction formatScrollState(\n lines: string[],\n scroll: NonNullable<NodeDebugInfo[\"scroll\"]>,\n indent = \" \",\n): void {\n if (scroll.offsetChanged) {\n lines.push(`${indent}⚠ SCROLL CHANGED: offset ${scroll.prevOffset} → ${scroll.offset}`)\n } else {\n lines.push(`${indent}offset: ${scroll.offset}`)\n }\n lines.push(\n `${indent}viewport: ${scroll.viewportHeight}/${scroll.contentHeight} (hidden: ▲${scroll.hiddenAbove} ▼${scroll.hiddenBelow})`,\n )\n lines.push(`${indent}visibleRange: [${scroll.firstVisibleChild}..${scroll.lastVisibleChild}]`)\n}\n","import type { TerminalBuffer } from \"./buffer\"\nimport {\n type CursorRect,\n computeContentRect,\n findActiveCursorRectWithProvenance,\n findActiveParkRect,\n} from \"@silvery/ag/layout-signals\"\nimport { findActiveCursorNode } from \"./caret-style\"\nimport { ANSI } from \"./output\"\nimport type {\n OutputCompositorCaret,\n OutputCursorBounds,\n OutputCursorTarget,\n} from \"./cursor-diagnostics\"\nimport { isStrictEnabled } from \"./strict-mode\"\nimport { IncrementalRenderMismatchError } from \"./errors\"\nimport type { AgNode, Rect } from \"@silvery/ag/types\"\n\n/**\n * Composited-caret shapes painted into the presentation buffer:\n * - `\"block\"` — filled inverse block. The focused caret (and the default).\n * - `\"underline\"` — bottom rule only (a declared `cursorOffset.shape:\n * \"underline\"`). Distinct from the focus state — an underline-shaped caret\n * stays underline whenever a caret is shown at all.\n *\n * An UNFOCUSED window composites NO caret. The product contract\n * (@km/code/v0.2/19702, reframed 2026-06-18) is hide-the-cursor-COMPLETELY, not\n * the earlier 20082 hollow box — a freshly-spawned, unfocused agent pane must\n * show nothing. So there is no `\"hollow\"` shape: an unfocused pane returns a\n * null `compositorCaret`. The hardware cursor is parked-and-hidden regardless of\n * focus, so an unfocused pane shows nothing at all.\n */\nexport interface CompositedCaret {\n x: number\n y: number\n visible: boolean\n style: \"block\" | \"underline\"\n}\n\nexport interface ManagedCaretFrame {\n buffer: TerminalBuffer\n compositorCaret: CompositedCaret | null\n}\n\nexport interface CursorOwnerBounds {\n promptBounds: Rect | null\n composerBounds: Rect | null\n}\n\n/**\n * Resolve the composited caret SHAPE from the cursor's declared shape. Only ever\n * called when a caret is shown at all — i.e. the window is FOCUSED (an unfocused\n * window composites no caret; see `composeManagedCaret`).\n *\n * - A declared `shape: \"underline\"` wins — an underline-shaped caret is\n * semantically distinct and stays underline.\n * - Otherwise the default filled `\"block\"`.\n */\nfunction caretStyle(cursor: CursorRect): CompositedCaret[\"style\"] {\n return cursor.shape === \"underline\" ? \"underline\" : \"block\"\n}\n\n/** Map a composited-caret style to the cell attributes that render it. */\nfunction caretCellAttrs(style: CompositedCaret[\"style\"]): {\n inverse?: boolean\n underline?: boolean\n} {\n switch (style) {\n case \"underline\":\n return { underline: true }\n case \"block\":\n default:\n return { inverse: true }\n }\n}\n\n/**\n * Paint a semantic caret into a presentation buffer for managed terminal frames.\n *\n * The source buffer stays untouched. Runtime diff state stores the presentation\n * buffer so moving/hiding the caret clears the previous frame's overlay like any\n * other cell update.\n *\n * @param windowFocused - whether the terminal WINDOW is focused. An UNFOCUSED\n * window composites NO caret (@km/code/v0.2/19702, reframed 2026-06-18: hide\n * the cursor COMPLETELY, replacing 20082's hollow box). Defaults to `true` —\n * a fail-safe so a single user with a focused terminal (or any caller that\n * hasn't wired standard `?1004` focus reporting) still sees the filled block,\n * never a vanished caret on unknown focus. For a composited caret the hardware\n * cursor is parked-and-hidden regardless of focus (see `managedCursorSuffix`),\n * so an unfocused pane shows nothing at all — and a dropped `?25l` cannot\n * strand a hardware cursor. The one exception is a `cursorActive` island\n * host-caret, shown as the real hardware cursor when the window is focused\n * (see `computeManagedFrame` / @hab 20398).\n */\nexport function composeManagedCaret(\n buffer: TerminalBuffer,\n cursor: CursorRect | null,\n windowFocused = true,\n): ManagedCaretFrame {\n // @km/code/v0.2/19702: an UNFOCUSED window hides the caret completely — no\n // composited overlay. (Default-true keeps a plain/unknown-focus terminal\n // visible; only a confirmed-unfocused window suppresses.)\n if (!windowFocused || !cursor?.visible || !buffer.inBounds(cursor.x, cursor.y)) {\n return { buffer, compositorCaret: null }\n }\n\n const next = buffer.clone()\n // Cloning intentionally drops dirty-row metadata. Mark all rows so the diff\n // compares this presentation buffer against the previous presentation buffer\n // and cannot skip unrelated app content that changed in the same frame.\n next.markAllRowsDirty()\n const style = caretStyle(cursor)\n next.mergeAttrsInRect(cursor.x, cursor.y, 1, 1, caretCellAttrs(style))\n return {\n buffer: next,\n compositorCaret: {\n x: cursor.x,\n y: cursor.y,\n visible: true,\n style,\n },\n }\n}\n\n/** SILVERY_STRICT slug for the managed-caret overlay-residue check. */\nexport const CARET_OVERLAY_STRICT_SLUG = \"cursor\"\nexport const CARET_OVERLAY_STRICT_MIN_TIER = 2\n\n/** Just the cell coordinates — all the overlay-clear logic needs. */\ntype CaretCell = { x: number; y: number } | null\n\nfunction sameCaretCell(a: CaretCell, b: CaretCell): boolean {\n if (a === null || b === null) return a === b\n return a.x === b.x && a.y === b.y\n}\n\n/**\n * Clear a prior frame's composited-caret overlay from a STATIC row.\n *\n * `composeManagedCaret`'s two paths are asymmetric about dirty rows: the PAINT\n * path calls `markAllRowsDirty()` (so a freshly-painted caret is always\n * diffed), but the NO-CARET early-return hands back the source buffer untouched.\n * When a caret that was painted at cell C in frame N is suppressed or moves to a\n * different cell in frame N+1, and C's ROW is otherwise static (clean in the\n * incremental buffer — the reconciler only marks rows whose content changed),\n * `diffBuffers` skips that row (`if (!next.isRowDirty(y)) continue`). The frame-N\n * `inverse` cell is then never compared against the cleared frame-N+1 buffer, so\n * the reverse-video block strands on screen with NO `?25h`/CUP — the\n * @km/code/v0.2/19702 cursor-above-composer signature.\n *\n * Fix: mark EXACTLY the prior caret's row dirty in the presentation buffer the\n * caller will diff+store, so `diffBuffers` re-scans it and emits the cleared\n * cell. We never call `markAllRowsDirty` (that would re-diff every static\n * transcript row each frame — the incremental-perf regression the dirty-row gate\n * exists to prevent). We touch one row, only when the caret actually left it.\n *\n * Buffer ownership: when the current frame composited NO caret (or composited\n * one on a DIFFERENT buffer), `presentationBuffer` may be the caller's untouched\n * source buffer (the documented `composeManagedCaret` no-clone contract). To\n * honour \"the source buffer stays untouched\", we clone before marking in that\n * case and return the clone. When the PAINT path already cloned, we mutate that\n * clone in place (it is already the caller's diff baseline).\n */\nfunction clearPriorCaretOverlay(\n presentationBuffer: TerminalBuffer,\n sourceBuffer: TerminalBuffer,\n currentCaret: CaretCell,\n prevCaret: CaretCell,\n): TerminalBuffer {\n // Nothing to clear: no prior caret, or the caret stayed on the same cell\n // (the PAINT path's markAllRowsDirty already covers an in-place repaint).\n if (prevCaret === null) return presentationBuffer\n if (sameCaretCell(prevCaret, currentCaret)) return presentationBuffer\n if (prevCaret.y < 0 || prevCaret.y >= presentationBuffer.height) return presentationBuffer\n\n // If the prior caret's row is already dirty (e.g. the PAINT path marked all\n // rows, or the app re-rendered that row), the diff will clear it for free.\n if (presentationBuffer.isRowDirty(prevCaret.y)) return presentationBuffer\n\n // The row is static (clean) — diffBuffers would skip it and strand the prior\n // inverse cell. Make the row dirty so the diff re-scans and clears it.\n const buf = presentationBuffer === sourceBuffer ? presentationBuffer.clone() : presentationBuffer\n buf.markRowDirty(prevCaret.y)\n return buf\n}\n\n/**\n * STRICT (tier 2, slug `cursor`) — managed-caret overlay-residue invariant.\n *\n * After a frame whose composited caret left cell C (suppressed or moved), C's\n * row in the presentation buffer the caller diffs MUST be dirty — otherwise\n * `diffBuffers` skips it and the prior `inverse` overlay strands (the\n * @km/code/v0.2/19702 signature). This is the overlay-layer analogue of\n * `strict-residue.ts` (which runs BELOW the caret overlay, in the render phase,\n * and so cannot see this). Fires only under `SILVERY_STRICT=cursor` / tier ≥ 2.\n */\nfunction verifyNoCaretOverlayResidue(\n presentationBuffer: TerminalBuffer,\n currentCaret: CaretCell,\n prevCaret: CaretCell,\n): void {\n if (!isStrictEnabled(CARET_OVERLAY_STRICT_SLUG, CARET_OVERLAY_STRICT_MIN_TIER)) return\n if (prevCaret === null) return\n if (sameCaretCell(prevCaret, currentCaret)) return\n if (prevCaret.y < 0 || prevCaret.y >= presentationBuffer.height) return\n if (presentationBuffer.isRowDirty(prevCaret.y)) return\n\n throw new IncrementalRenderMismatchError(\n `STRICT caret-overlay residue: prior composited caret at (${prevCaret.x},${prevCaret.y}) ` +\n `left the row, but row ${prevCaret.y} is CLEAN in the presentation buffer.\\n` +\n ` diffBuffers skips clean rows, so the prior frame's reverse-video (inverse)\\n` +\n ` overlay cell at (${prevCaret.x},${prevCaret.y}) will NOT be cleared — it strands\\n` +\n ` on screen with no ?25h/CUP. This is the @km/code/v0.2/19702\\n` +\n ` cursor-above-composer mechanism.\\n` +\n ` current caret: ${currentCaret ? `(${currentCaret.x},${currentCaret.y})` : \"none (suppressed)\"}\\n` +\n ` Fix: clearPriorCaretOverlay must mark the prior caret's row dirty.\\n` +\n ` Slug: SILVERY_STRICT=${CARET_OVERLAY_STRICT_SLUG} (tier ${CARET_OVERLAY_STRICT_MIN_TIER}+).`,\n )\n}\n\n/**\n * STRICT (tier 2, slug `cursor`) — no-fallback park invariant.\n *\n * When an editable declares a `parkOffset` (resolved to `parkRect`) and no\n * visible caret owns the frame, the managed hardware cursor MUST park on that\n * declared cell — never `home(0,0)` or a box-origin fallback. This pins that the\n * @km/code/v0.2/19702 fix stays wired: a regression that re-introduces a\n * fallback overriding the declared park target throws HERE instead of silently\n * stranding the cursor above the prompt (the recurring #undead signature, where\n * a dropped `?25l` surfaces the mis-parked cursor). The undeclared-editable case\n * (an editable that declares NO park) has no generic runtime signal — it is\n * pinned by the composer park-target contract + the parkOffset mutation test.\n * Fires only under `SILVERY_STRICT=cursor` / tier ≥ 2.\n *\n * Exported for direct mutation-proof unit testing (the fault path can't arise\n * from the correct `managedCursorSuffix`, so the test simulates a fallback park).\n */\nexport function verifyParkHonorsDeclaredTarget(\n cursor: CursorRect | null,\n parkRect: Rect | null,\n parkTarget: { x: number; y: number } | null,\n): void {\n if (!isStrictEnabled(CARET_OVERLAY_STRICT_SLUG, CARET_OVERLAY_STRICT_MIN_TIER)) return\n // Only meaningful when no caret owns the frame and a park target was declared.\n if (cursor !== null || parkRect === null) return\n if (parkTarget !== null && parkTarget.x === parkRect.x && parkTarget.y === parkRect.y) return\n\n throw new IncrementalRenderMismatchError(\n `STRICT no-fallback park: parkOffset declared at (${parkRect.x},${parkRect.y}) and no caret ` +\n `owns the frame, but the hardware cursor parked at ` +\n `${parkTarget ? `(${parkTarget.x},${parkTarget.y})` : \"none\"} instead.\\n` +\n ` A managed frame MUST park on the editable's declared input cell — never a\\n` +\n ` box-origin / home(0,0) fallback. A dropped ?25l would then strand the cursor off\\n` +\n ` the prompt — the @km/code/v0.2/19702 cursor-above-composer mechanism.\\n` +\n ` Fix: managedCursorSuffix must honor findActiveParkRect's result.\\n` +\n ` Slug: SILVERY_STRICT=${CARET_OVERLAY_STRICT_SLUG} (tier ${CARET_OVERLAY_STRICT_MIN_TIER}+).`,\n )\n}\n\nexport function cursorOwnerBounds(\n activeNode: AgNode | null,\n cursor: CursorRect | null,\n): CursorOwnerBounds {\n return {\n // Silvery core does not know an app's prompt string. The semantic cursor\n // cell is the targetable prompt/caret locus available at this layer.\n promptBounds: cursor ? { x: cursor.x, y: cursor.y, width: 1, height: 1 } : null,\n composerBounds: activeNode ? computeContentRect(activeNode) : null,\n }\n}\n\nexport interface ManagedCursorControls {\n /** ANSI suffix: park the hardware cursor at a safe cell, then hide it. */\n suffix: string\n /** Where the hardware cursor was parked (0-indexed), or null when no park. */\n parkTarget: { x: number; y: number } | null\n}\n\n/**\n * Compute the trailing cursor-control bytes for a managed fullscreen frame.\n *\n * The visible caret is painted into the presentation buffer by\n * `composeManagedCaret`, so the hardware cursor's only job is to stay OUT of\n * the way: every managed frame moves it to a deterministic safe cell and hides\n * it. Parking-before-hide is load-bearing because a multiplexer/terminal can\n * drop or override `?25l` (cmux re-shows the focused pane's cursor; an\n * unfocused pane renders a hollow cursor). If we hide WITHOUT moving, the\n * hardware cursor stays wherever the diff's last content write landed — the\n * bottom-most painted row — and a dropped hide leaves a visible cursor\n * stranded in transcript/activity/chrome content (the recurring\n * @km/code/v0.2/19702 signature, e.g. a block on the `Codex Done …` status\n * row). Parking pins that worst case to a benign, predictable cell instead.\n *\n * Park priority:\n * 1. The active caret cell — when a caret exists, the hardware cursor and the\n * composited caret coincide, so a dropped hide just overlaps the caret.\n * 2. The editable's DECLARED input-cell park target (`parkOffset`, resolved by\n * `findActiveParkRect`) — present even when the editable is unfocused/idle,\n * so a dropped hide surfaces the cursor ON the prompt input cell.\n * 3. Home (0,0) — last resort, ONLY for a frame with no editable/park declarer\n * at all (no input locus to be \"above\"). Never a dynamic transcript/chrome\n * row.\n *\n * The earlier composer-ORIGIN fallback (the editable's box top/border row — one\n * cell ABOVE the `>` prompt) is DELETED: it was the @km/code/v0.2/19702\n * bug-masking fallback (a multiplexer dropping `?25l` surfaced the parked cursor\n * one row above the prompt). Per Fail-Loud / no-defensive-fallbacks, a present\n * editable now declares its input cell via `parkOffset` instead of relying on a\n * silent box-origin degrade.\n */\nexport function managedCursorSuffix(\n cursor: CursorRect | null,\n parkRect: Rect | null,\n opts?: { visible?: boolean },\n): ManagedCursorControls {\n const park =\n cursor !== null\n ? { x: cursor.x, y: cursor.y }\n : parkRect !== null\n ? { x: parkRect.x, y: parkRect.y }\n : { x: 0, y: 0 }\n // Default: park-then-HIDE (@km/code/v0.2/19702 anti-stranding). A `cursorActive`\n // island host-caret (@km/silvery/19426) instead parks-then-SHOWS — the focused\n // guest terminal's real cursor is the one hardware cursor we deliberately leave\n // visible. Parking first means a shown cursor lands exactly on the guest cell.\n const tail = opts?.visible === true ? ANSI.CURSOR_SHOW : ANSI.CURSOR_HIDE\n return {\n suffix: ANSI.moveCursor(park.x, park.y) + tail,\n parkTarget: park,\n }\n}\n\n/**\n * Transport-safe managed cursor delivery.\n *\n * The semantic suffix is still \"park then hide\" (`managedCursorSuffix` above).\n * Output paths append this idempotent SGR reset after it so the managed cursor\n * controls are not the literal trailing run of cursor-control bytes. That keeps\n * multiplexers that drop a trailing cursor-control tail from peeling the park\n * move away from the content frame that required it.\n */\nexport function protectManagedCursorSuffix(cursorSuffix: string): string {\n return cursorSuffix.length === 0 ? \"\" : cursorSuffix + ANSI.RESET\n}\n\n/**\n * The single source of truth for managed-frame cursor handling.\n *\n * Three render entry points (`scheduler.ts`, `runtime/create-runtime.ts`,\n * `runtime/create-app.tsx`) previously hand-duplicated the same four-step\n * derivation (find active cursor rect → find active node → owner bounds →\n * composite caret + park/hide suffix). The copies drifted: the\n * @km/code/v0.2/19702 fixes patched one copy at a time, so the live path always\n * lagged a fix. `computeManagedFrame` collapses all four steps into ONE\n * function — changing the policy is now a single edit, and a regression cannot\n * hide in an uncovered copy (`docs/lessons/no-parallel-derivation.md`).\n *\n * **Policy (the @km/code/v0.2/19702 fix):** paint the visible inverse caret into\n * the presentation buffer ONLY when a FOCUSED editable owns the active cursor.\n * During an active turn nothing is focused, so a non-focused fallback declarer\n * (the unfocused composer, or a transcript editable still reporting\n * `cursorOffset.visible`) gets NO composited caret — eliminating the stranded\n * reverse-video block several rows above the prompt. The hardware cursor is\n * still parked at the editable's bounds (then hidden), so a dropped/overridden\n * `?25l` cannot strand a hollow hardware cursor in transcript/chrome either.\n *\n * @param sourceBuffer - the freshly-rendered content buffer (left untouched).\n * @param root - the AgNode tree (the `buffer.nodes` / `this.root` the caller holds).\n * @param mode - only `\"fullscreen\"` paints a managed caret; `\"inline\"` positions\n * the cursor inside the diff output and returns an empty managed frame.\n * @param opts.legacyCursor - the resolved legacy/store cursor\n * (`useCursor()` / Ink-compat `setCursorPosition()`), used ONLY when no node\n * in the tree declares a layout-output `cursorOffset`. This imperative cursor\n * is an explicit \"show a caret here\" request with no focus node, so it is\n * composited unconditionally (it is NOT focus-gated — that gate exists to\n * suppress passive declarative fallbacks, not explicit imperative cursors).\n * The scheduler passes this; `createRuntime`/`createApp` do not (they have no\n * store-cursor path).\n * @param opts.prevCaret - the `compositorCaret` THIS function returned for the\n * PREVIOUS frame (the caller stores it across frames). When the caret leaves a\n * cell whose row is otherwise static, the prior cell's row is made dirty in the\n * returned `presentationBuffer` so `diffBuffers` clears the stale `inverse`\n * overlay (the @km/code/v0.2/19702 fix — see `clearPriorCaretOverlay`). All\n * three render entry points pass this; omitting it disables the overlay-clear\n * (correct only for single-frame callers with no prev buffer).\n * @param opts.windowFocused - whether the terminal WINDOW is focused\n * (@km/code/v0.2/19702, reframed). `true`/omitted → filled inverse block;\n * `false` → NO caret composited (hidden completely — the pane shows nothing).\n * The hardware cursor is parked-and-hidden in BOTH states. All three render\n * entry points read their shared window-focus signal (standard DEC `?1004`\n * focus reporting) and pass it here; omitting it is the fail-safe focused\n * default (a single user with a focused terminal that never emits focusIn must\n * still see the caret).\n * @returns everything every call site needs: the presentation buffer to diff +\n * store, the cursor-control suffix, and the full diagnostic payload. The\n * caller stores `compositorCaret` and passes it back as `prevCaret` next frame.\n */\nexport interface ManagedFrame {\n /**\n * The buffer the terminal actually shows this frame. Equals `sourceBuffer`\n * when no caret is composited (same reference — callers can `===`-compare to\n * detect the no-op case). Callers MUST store THIS as their diff baseline so\n * the next frame clears a moved/removed caret.\n */\n presentationBuffer: TerminalBuffer\n /** Trailing cursor-control bytes: park the hardware cursor, then hide it. */\n cursorSuffix: string\n /** The composited caret painted into `presentationBuffer`, or null. */\n compositorCaret: OutputCompositorCaret | null\n /** Semantic cursor target (where a hardware cursor WOULD go), or null. */\n cursorTarget: OutputCursorTarget | null\n /** What the terminal cursor should be after the frame (always hidden), or null. */\n expectedTerminal: OutputCursorTarget | null\n /** The prompt/caret single-cell locus, for diagnostics. */\n promptBounds: OutputCursorBounds | null\n /** The editable content rect that owns the frame, for diagnostics. */\n composerBounds: OutputCursorBounds | null\n}\n\nexport function computeManagedFrame(\n sourceBuffer: TerminalBuffer,\n root: AgNode,\n mode: \"fullscreen\" | \"inline\",\n opts?: {\n legacyCursor?: CursorRect | null\n prevCaret?: OutputCompositorCaret | null\n windowFocused?: boolean\n },\n): ManagedFrame {\n // Inline mode never paints a managed caret — the diff output positions the\n // cursor itself (see output-phase.ts inlineCursorSuffix).\n if (mode !== \"fullscreen\") {\n return {\n presentationBuffer: sourceBuffer,\n cursorSuffix: \"\",\n compositorCaret: null,\n cursorTarget: null,\n expectedTerminal: null,\n promptBounds: null,\n composerBounds: null,\n }\n }\n\n // ONE tree walk resolves the active cursor rect AND its provenance. The\n // suppression decision below reads that provenance — it does NOT re-derive\n // focus from a second, divergent walk. (The earlier focus-gate used\n // `findActiveCursorNode`, which matches only `props.cursorOffset` and is blind\n // to `cursorActive` islands + the clip stack, so it suppressed island\n // host-carets — an inner no-parallel-derivation bug, @km/silvery/19426.)\n const active = findActiveCursorRectWithProvenance(root)\n const treeCursor = active?.rect ?? null\n\n // Cursor resolution priority (mirrors the scheduler's resolveActiveCursor):\n // 1. Layout-output cursor declared in the tree (Box cursorOffset OR island).\n // 2. Legacy/store cursor (useCursor() / Ink-compat) — only when the tree\n // declares none.\n const legacyCursor = treeCursor ? null : (opts?.legacyCursor ?? null)\n const cursor = treeCursor ?? legacyCursor\n\n // Park bounds still come from the node walk — `cursorOwnerBounds` needs the\n // editable's content rect. This is a DIAGNOSTIC/PARK input only; it never\n // gates the visible caret (that decision is provenance-driven below), so its\n // island-blindness is harmless here.\n const activeNode = findActiveCursorNode(root)\n const bounds = cursorOwnerBounds(activeNode, cursor)\n\n // The @km/code/v0.2/19702 fix, now provenance-aware: composite the VISIBLE\n // caret for everything EXCEPT a non-focused declarative fallback. Suppressing\n // that one source removes the stranded inverse block several rows above the\n // prompt (an active-turn composer / passive transcript editable still\n // reporting `cursorOffset.visible`). Focused-declarative carets, `cursorActive`\n // island host-carets (@km/silvery/19426), and explicit legacy/imperative\n // cursors (useCursor / Ink-compat — no focus node) all still composite. The\n // hardware cursor is parked at the editable's bounds below regardless, so a\n // dropped/overridden hide lands on a benign cell, never a dynamic\n // transcript/chrome row.\n const suppressDeclarativeFallback = active?.provenance === \"declarative-fallback\"\n // @km/code/v0.2/20082: the window-focus state selects the caret SHAPE (filled\n // block when focused, hidden when unfocused). Default focused — the fail-safe so\n // unknown focus never yields a hidden caret.\n const windowFocused = opts?.windowFocused ?? true\n\n // @km/silvery/19426 / @hab/.../20398 — island host-caret. A `cursorActive`\n // island is a REAL guest terminal (a shell PTY, an embedded app) whose guest\n // cursor must render as the host HARDWARE caret: a real, VISIBLE cursor at the\n // guest cell (like the focused pane of a terminal multiplexer). The 19702\n // managed model — composite an inverse block + park-and-HIDE the hardware cursor\n // (`?25l`) — is correct for silvery's OWN declarative editables, whose \"caret\" is\n // painted content with no real hardware cursor to show. Lumping islands into it\n // published the focused shell's guest cursor as a HIDDEN hardware cursor — the\n // \"focused shell has no cursor\" recurrence (20398). So an island host-caret is\n // shown as the hardware cursor instead, gated on window focus (an unfocused\n // window shows nothing). The island block in `findActiveCursorRect` already\n // gates on the guest's own `cursorVisible`, so an island provenance here means\n // the guest wants its cursor shown.\n const islandHardwareCursor =\n active?.provenance === \"island\" && windowFocused && treeCursor?.visible === true\n\n // Composite the VISIBLE inverse caret for everything EXCEPT (a) a non-focused\n // declarative fallback (@km/code/v0.2/19702) and (b) an island host-caret, which\n // is shown as a real hardware cursor below — compositing an inverse block on top\n // of it would double the caret.\n const compositeCursor =\n legacyCursor !== null\n ? legacyCursor\n : islandHardwareCursor || suppressDeclarativeFallback\n ? null\n : treeCursor\n const managed = composeManagedCaret(sourceBuffer, compositeCursor, windowFocused)\n\n // Clear a prior frame's composited-caret overlay from a static row. When the\n // caret left cell C (suppressed or moved) and C's row is clean in this\n // incremental buffer, diffBuffers would skip it and strand the frame-N inverse\n // cell — the @km/code/v0.2/19702 mechanism. Mark exactly C's row dirty (never\n // all rows — that's the incremental-perf regression the dirty-row gate\n // prevents). ONE place; all three render entry points thread `prevCaret`.\n const prevCaret = opts?.prevCaret ?? null\n const presentationBuffer = clearPriorCaretOverlay(\n managed.buffer,\n sourceBuffer,\n managed.compositorCaret,\n prevCaret,\n )\n // STRICT (tier 2, slug `cursor`): the overlay-residue invariant. Verify on the\n // FINAL presentation buffer — after the clear — so a green check proves the\n // stale overlay will actually be diffed away.\n verifyNoCaretOverlayResidue(presentationBuffer, managed.compositorCaret, prevCaret)\n\n // Hardware-cursor park target: caret cell when a caret exists, else the\n // editable's DECLARED input-cell park (`parkOffset`, present even unfocused/\n // idle), else home for an editable-less frame. Parking is a SAFETY net for a\n // dropped `?25l`, so it runs even when the visible caret is suppressed. The\n // park is resolved NON-focus-gated so an unfocused/idle composer still parks\n // on its prompt cell (@km/code/v0.2/19702 — replaces the box-origin fallback\n // that stranded the cursor one row above the prompt).\n const parkRect = findActiveParkRect(root)\n // Island host-caret → park at the guest cell and leave the hardware cursor\n // VISIBLE (`?25h`): the focused guest terminal's real cursor is the one hardware\n // cursor we deliberately show. Every other managed frame parks-then-HIDES\n // (`?25l`) — the 19702 anti-stranding safety net.\n const managedCursor = managedCursorSuffix(cursor, parkRect, { visible: islandHardwareCursor })\n // STRICT (tier 2, slug `cursor`): the no-fallback park invariant — a declared\n // parkOffset MUST be honored, never overridden by a box-origin/home fallback.\n verifyParkHonorsDeclaredTarget(cursor, parkRect, managedCursor.parkTarget)\n\n let cursorTarget: OutputCursorTarget | null = null\n let expectedTerminal: OutputCursorTarget | null = null\n if (cursor) {\n cursorTarget = { x: cursor.x, y: cursor.y, visible: cursor.visible, shape: cursor.shape }\n // The island host-caret is the one managed frame that leaves the hardware\n // cursor VISIBLE; every other managed frame hides it.\n expectedTerminal = { ...cursorTarget, visible: islandHardwareCursor }\n } else if (managedCursor.parkTarget) {\n expectedTerminal = { ...managedCursor.parkTarget, visible: false }\n }\n\n return {\n presentationBuffer,\n cursorSuffix: managedCursor.suffix,\n compositorCaret: managed.compositorCaret,\n cursorTarget,\n expectedTerminal,\n promptBounds: bounds.promptBounds,\n composerBounds: bounds.composerBounds,\n }\n}\n","/**\n * render-trace.ts — Render-boundary instrumentation for the Visual Eyes epic.\n *\n * Phase 4 of the Visual Eyes epic. Each silvery render-pass becomes an\n * observable `RENDER_DISPATCHED` event annotated with WHY it rendered:\n *\n * { ts, reason, dirtyRegions, signalDelta, fiberHash }\n *\n * A frame-trace consumer (e.g. termless's `createFrameTracer`) joins these\n * events onto its numbered frames by timestamp, so a trace frame's metadata\n * answers \"what made this render happen\" instead of just \"the buffer changed\".\n *\n * ## Design — additive instrumentation only\n *\n * This module emits an event AFTER the existing reconciler-flush + render\n * pipeline boundary in `renderer.ts`'s `doRender()`. It changes NO rendering\n * behavior, NO dirty-flag logic, NO pipeline ordering. It only reads state\n * the renderer already computed and writes it to an opt-in sink.\n *\n * ## Zero-cost when disabled\n *\n * The whole module is gated by the `SILVERY_TRACE_FRAMES` env var. When\n * unset, `isRenderTraceEnabled()` is `false`, `emitRenderDispatched()`\n * returns immediately, and the renderer's call site is a single boolean\n * check — production rendering pays nothing.\n *\n * ## Channel choice — sidecar JSONL + in-process bus\n *\n * `SILVERY_TRACE_FRAMES` names a directory (the same dir a termless\n * frame-trace writes its `index.jsonl` into). silvery appends render\n * events to `<dir>/render-events.jsonl`, one JSON object per line. termless\n * reads + joins on `ts`. This is the simplest robust channel for\n * `bun km view <vault>` — silvery and termless run in one process but are\n * separate packages with no shared module; a filesystem sidecar needs no\n * cross-package wiring and survives process boundaries too. Terminal-output\n * events are written to `<dir>/render-output-events.jsonl` with bytes,\n * changed-cell counts, dirty-row counts, and output-phase reason.\n *\n * A process-global in-memory ring buffer (`globalThis.__silvery_render_events`)\n * is also maintained so a same-process consumer can read events without\n * touching the filesystem.\n */\n\nimport { appendFileSync, existsSync, mkdirSync } from \"node:fs\"\nimport { join } from \"node:path\"\nimport { createLogger } from \"loggily\"\nimport type { OutputPhaseDiagnostics } from \"../pipeline/output-phase\"\n\nconst renderLog = createLogger(\"silvery:render\")\nconst outputLog = createLogger(\"silvery:render:output\")\n\n/**\n * A render-boundary event. Emitted once per `doRender()` flush when render\n * tracing is enabled.\n */\nexport interface RenderDispatchedEvent {\n /** Event type discriminator. */\n type: \"RENDER_DISPATCHED\"\n /** Wall-clock ms epoch — the join key for frame-trace consumers. */\n ts: number\n /** Monotonic render counter from the renderer (1-based). */\n renderCount: number\n /**\n * Why this render ran. Human-readable, derived from the dirty bits that\n * were set on the root node + dimension changes. E.g.\n * `\"content,subtree\"`, `\"dims-changed\"`, `\"initial\"`.\n */\n reason: string\n /**\n * Dirty regions of the produced buffer — contiguous row ranges that the\n * render pipeline marked dirty. `[]` when nothing was dirty (skip render).\n */\n dirtyRegions: { row: number; height: number }[]\n /**\n * What the render pipeline did this pass — a compact \"signal delta\":\n * how many nodes were visited / rendered / skipped. Sourced from the\n * render-phase stats the pipeline already computes. This is the\n * cheapest faithful proxy for \"what changed since the last flush\" —\n * the render phase only re-renders nodes whose layout/content signals\n * actually changed.\n */\n signalDelta: {\n nodesVisited: number\n nodesRendered: number\n nodesSkipped: number\n incremental: boolean\n }\n /**\n * A cheap structural hash of the fiber tree as committed this pass:\n * `<rootNodeCount>:<rootDirtyEpoch>`. Two renders with the same fiberHash\n * committed the same tree shape at the same epoch. Not a cryptographic\n * hash — a fast change-detector.\n */\n fiberHash: string\n}\n\n/**\n * Output-phase diagnostics attached to an emitted terminal frame. This is\n * deliberately a compact clone of the bytes_out monitor's frame summary:\n * render traces need the same changed-cell explanation without waiting for\n * bytes_out thresholds to trip.\n */\nexport type RenderOutputFrameDiagnostics = Partial<OutputPhaseDiagnostics> & {\n /**\n * Where these bytes came from. Omitted for the ordinary render-frame path\n * to preserve existing trace shape; terminal side-channel writes set this.\n */\n source?: \"terminal-artifact\" | \"post-paint-write\" | \"terminal-protocol\"\n /** Protocol/component owner for terminal artifacts, e.g. image:kitty:transmit. */\n owner?: string\n /** Flush phase for terminal artifacts. */\n phase?: \"setup\" | \"runtime\" | \"cleanup\" | \"pre-paint\" | \"post-paint\"\n /** Artifact discriminator from the runtime queue. */\n artifactKind?: string\n /** UTF-16 string length written before terminal-output wrapping. */\n outputChars?: number\n /** Extra hardware-cursor suffix length appended outside outputPhase(). */\n cursorChars?: number\n /** True when the frame was wrapped in DEC synchronized-output markers. */\n syncWrapped?: boolean\n}\n\n/**\n * Output-frame event. Emitted once per terminal write when render tracing is\n * enabled. Stored in a dedicated sidecar so existing `render-events.jsonl`\n * consumers keep seeing only RENDER_DISPATCHED events.\n */\nexport interface RenderOutputFrameEvent {\n /** Event type discriminator. */\n type: \"RENDER_OUTPUT\"\n /** Wall-clock ms epoch — the join key for frame-trace consumers. */\n ts: number\n /**\n * Render counter from the current renderer/scheduler at write time. In\n * createApp this counter is local to the current event batch; use\n * outputFrame for monotonic ordering.\n */\n renderCount: number\n /** Monotonic output-write counter, 1-based for this process/app handle. */\n outputFrame: number\n /** Bytes actually written for this frame. */\n bytes: number\n /** Output-phase reason, dimensions, changed-cell counts, and write shape. */\n diagnostics: RenderOutputFrameDiagnostics\n}\n\n/**\n * Source values the renderer hands to `emitRenderDispatched`. The renderer\n * already has all of these in scope at the flush boundary; this module just\n * shapes them into the event.\n */\nexport interface RenderTraceInput {\n renderCount: number\n /** Dirty-bit names captured BEFORE the pipeline consumed the flags. */\n dirtyReasons: string[]\n /** True when this render ran because terminal dims changed. */\n dimsChanged: boolean\n /** Height of the produced buffer (rows). */\n bufferHeight: number\n /** Per-row dirty predicate over the produced buffer. */\n isRowDirty: (y: number) => boolean\n /** Render-phase signal delta (visited/rendered/skipped). */\n signalDelta: RenderDispatchedEvent[\"signalDelta\"]\n /** Root node count for the fiber hash. */\n rootNodeCount: number\n /** Root dirty epoch for the fiber hash. */\n rootDirtyEpoch: number\n}\n\n// ── Env gate ────────────────────────────────────────────────────────────────\n//\n// `SILVERY_TRACE_FRAMES` names the trace directory. `DEBUG=silvery:render*`\n// enables the same in-process/loggily trace without a sidecar. Both gates\n// are read lazily so the trace surface is testable and opt-in.\n\nlet sidecarFile: string | null = null\nlet sidecarReady = false\nlet outputSidecarFile: string | null = null\nlet outputSidecarReady = false\n\n/** The configured trace directory, or `null` when `SILVERY_TRACE_FRAMES` is unset. */\nexport function renderTraceDir(): string | null {\n return process.env.SILVERY_TRACE_FRAMES?.trim() || null\n}\n\n/** True when sidecar or loggily render tracing is active. */\nexport function isRenderTraceEnabled(): boolean {\n return renderTraceDir() !== null || renderLog.debug !== undefined || outputLog.debug !== undefined\n}\n\n// ── In-process event bus ────────────────────────────────────────────────────\n//\n// A bounded ring of recent events on globalThis so a same-process consumer\n// (km-tui wiring both silvery + termless) can read events without filesystem\n// round-trips. Bounded so a long-running app can't leak.\n\nconst MAX_BUFFERED_EVENTS = 4096\n\ninterface RenderEventBus {\n events: RenderDispatchedEvent[]\n outputEvents: RenderOutputFrameEvent[]\n}\n\nfunction bus(): RenderEventBus {\n const g = globalThis as { __silvery_render_events?: RenderEventBus }\n if (!g.__silvery_render_events) {\n g.__silvery_render_events = { events: [], outputEvents: [] }\n } else if (!g.__silvery_render_events.outputEvents) {\n g.__silvery_render_events.outputEvents = []\n }\n return g.__silvery_render_events\n}\n\n/**\n * Recent render events from the in-process bus. Consumers running in the\n * same process as silvery (e.g. a km-tui-wired termless tracer) can poll\n * this instead of reading the sidecar file.\n */\nexport function recentRenderEvents(): readonly RenderDispatchedEvent[] {\n return bus().events\n}\n\n/**\n * Recent output-frame events from the in-process bus. Same-process consumers\n * can join these with `recentRenderEvents()` by timestamp and frame order.\n */\nexport function recentRenderOutputEvents(): readonly RenderOutputFrameEvent[] {\n return bus().outputEvents\n}\n\n// ── Emit ────────────────────────────────────────────────────────────────────\n\n/** Convert the buffer's per-row dirty predicate into contiguous ranges. */\nfunction dirtyRegionsFromBuffer(\n height: number,\n isRowDirty: (y: number) => boolean,\n): { row: number; height: number }[] {\n const regions: { row: number; height: number }[] = []\n let runStart = -1\n for (let y = 0; y < height; y++) {\n if (isRowDirty(y)) {\n if (runStart === -1) runStart = y\n } else if (runStart !== -1) {\n regions.push({ row: runStart, height: y - runStart })\n runStart = -1\n }\n }\n if (runStart !== -1) {\n regions.push({ row: runStart, height: height - runStart })\n }\n return regions\n}\n\n/**\n * Emit a `RENDER_DISPATCHED` event for the just-completed render pass.\n *\n * No-op when `SILVERY_TRACE_FRAMES` is unset. Called from `renderer.ts`'s\n * `doRender()` at the flush boundary — AFTER reconciliation + the render\n * pipeline, just before the buffer is returned. Purely additive: reads\n * already-computed state, writes to the opt-in sink, never throws into the\n * render path.\n */\nexport function emitRenderDispatched(input: RenderTraceInput): void {\n const traceDir = renderTraceDir()\n const logEnabled = renderLog.debug !== undefined\n if (traceDir === null && !logEnabled) return\n try {\n const reasonParts: string[] = [...input.dirtyReasons]\n if (input.dimsChanged) reasonParts.push(\"dims-changed\")\n const event: RenderDispatchedEvent = {\n type: \"RENDER_DISPATCHED\",\n ts: Date.now(),\n renderCount: input.renderCount,\n reason: reasonParts.length > 0 ? reasonParts.join(\",\") : \"initial\",\n dirtyRegions: dirtyRegionsFromBuffer(input.bufferHeight, input.isRowDirty),\n signalDelta: input.signalDelta,\n fiberHash: `${input.rootNodeCount}:${input.rootDirtyEpoch}`,\n }\n\n // In-process bus (bounded ring).\n const b = bus()\n b.events.push(event)\n if (b.events.length > MAX_BUFFERED_EVENTS) {\n b.events.splice(0, b.events.length - MAX_BUFFERED_EVENTS)\n }\n\n renderLog.debug?.(\"render dispatched\", { ...event })\n\n // Sidecar JSONL — lazily created on first emit so a disabled trace dir\n // never gets touched.\n if (traceDir !== null && !sidecarReady) {\n if (!existsSync(traceDir)) mkdirSync(traceDir, { recursive: true })\n sidecarFile = join(traceDir, \"render-events.jsonl\")\n // Truncate any prior sidecar for this dir.\n appendFileSync(sidecarFile, \"\", { flag: \"w\" })\n sidecarReady = true\n }\n if (traceDir !== null && sidecarFile) {\n appendFileSync(sidecarFile, JSON.stringify(event) + \"\\n\")\n }\n } catch {\n // Tracing must never destabilise rendering. Swallow any sink error\n // (full disk, permission, race) — the in-process bus still has the\n // event if the push succeeded before the throw.\n }\n}\n\nexport interface RenderOutputFrameInput {\n renderCount: number\n outputFrame: number\n bytes: number\n diagnostics?: RenderOutputFrameDiagnostics | null\n}\n\n/**\n * Emit a `RENDER_OUTPUT` event for bytes actually written to the terminal.\n *\n * No-op when `SILVERY_TRACE_FRAMES` is unset. This is intentionally separate\n * from `emitRenderDispatched`: the render pipeline produces dirty regions,\n * while the output phase later decides which cells/bytes were emitted.\n */\nexport function emitRenderOutputFrame(input: RenderOutputFrameInput): void {\n const traceDir = renderTraceDir()\n const logEnabled = renderLog.debug !== undefined || outputLog.debug !== undefined\n if (traceDir === null && !logEnabled) return\n try {\n const event: RenderOutputFrameEvent = {\n type: \"RENDER_OUTPUT\",\n ts: Date.now(),\n renderCount: input.renderCount,\n outputFrame: input.outputFrame,\n bytes: input.bytes,\n diagnostics: input.diagnostics ? { ...input.diagnostics } : {},\n }\n\n const b = bus()\n b.outputEvents.push(event)\n if (b.outputEvents.length > MAX_BUFFERED_EVENTS) {\n b.outputEvents.splice(0, b.outputEvents.length - MAX_BUFFERED_EVENTS)\n }\n\n outputLog.debug?.(\"render output\", { ...event })\n\n if (traceDir !== null && !outputSidecarReady) {\n if (!existsSync(traceDir)) mkdirSync(traceDir, { recursive: true })\n outputSidecarFile = join(traceDir, \"render-output-events.jsonl\")\n appendFileSync(outputSidecarFile, \"\", { flag: \"w\" })\n outputSidecarReady = true\n }\n if (traceDir !== null && outputSidecarFile) {\n appendFileSync(outputSidecarFile, JSON.stringify(event) + \"\\n\")\n }\n } catch {\n // Tracing must never destabilise rendering. Swallow sink errors.\n }\n}\n\n/**\n * Test-only: reset module state (in-process bus + sidecar latch). Lets a\n * test exercise the enabled and disabled paths without a fresh process.\n */\nexport function __resetRenderTraceForTests(): void {\n bus().events.length = 0\n bus().outputEvents.length = 0\n sidecarReady = false\n sidecarFile = null\n outputSidecarReady = false\n outputSidecarFile = null\n}\n","/**\n * Silvery Render Scheduler\n *\n * Batches rapid state updates to prevent flicker and improve performance.\n * Uses queueMicrotask for coalescing multiple synchronous state changes\n * into a single render pass.\n *\n * Features:\n * - Microtask-based batching (coalesces synchronous updates)\n * - Frame batching to prevent flicker\n * - Resize handling with debounce\n * - Clean shutdown\n */\n\nimport { appendFileSync } from \"node:fs\"\nimport { type Logger, createLogger } from \"loggily\"\nimport { type BytesOutMonitor, createBytesOutMonitor } from \"./bytes-out-monitor\"\nimport { type FlickerMonitor, createFlickerMonitor } from \"./flicker-monitor\"\nimport { type MemMonitor, createMemMonitor } from \"./mem-monitor\"\nimport { isStrictEnabled } from \"./strict-mode\"\nimport { type TerminalBuffer, bufferToText, cellEquals } from \"./buffer\"\nimport { buildMismatchContext, formatMismatchContext } from \"@silvery/test/debug-mismatch\"\nimport {\n type ResolvedNonTTYMode as ResolvedMode,\n countLines,\n createOutputTransformer,\n resolveNonTTYMode,\n stripAnsi,\n} from \"./non-tty\"\nimport type { CursorAccessors } from \"@silvery/ag-react/hooks/useCursor\"\nimport {\n commitLayoutSnapshot,\n findActiveCursorRect,\n type CursorRect,\n} from \"@silvery/ag/layout-signals\"\nimport { createOsc52Backend } from \"./clipboard\"\nimport {\n recordOutputCursorDiagnostics,\n type OutputCursorBounds,\n type OutputCursorTarget,\n type PrevPresentation,\n} from \"./cursor-diagnostics\"\nimport { ANSI } from \"./output\"\nimport { computeManagedFrame, protectManagedCursorSuffix } from \"./managed-caret\"\nimport type { PipelineConfig } from \"./pipeline\"\nimport {\n markLayoutTreeDirty,\n FRESH_LAYOUT_STRICT_SLUG,\n FRESH_LAYOUT_STRICT_MIN_TIER,\n} from \"./pipeline\"\nimport {\n clearLastOutputPhaseDiagnostics,\n getLastOutputPhaseDiagnostics,\n outputPhase,\n} from \"./pipeline/output-phase\"\nimport { emitRenderOutputFrame, isRenderTraceEnabled } from \"./runtime/render-trace\"\nimport { createAg, createRenderPostState, type RenderPostState } from \"./ag\"\nimport { runWithMeasurer } from \"./unicode\"\nimport type { RenderPhaseStats } from \"./pipeline/types\"\nimport type { AgNode } from \"@silvery/ag/types\"\n\nconst log = createLogger(\"silvery:scheduler\")\n\n/**\n * Whether synchronized update mode (DEC 2026) is enabled.\n *\n * Disabled by default due to a Ghostty rendering bug where incremental\n * cursor-positioned updates inside a sync region cause progressive visual\n * corruption. Works correctly in Kitty. Full renders (bufferToAnsi) work\n * fine with sync — only incremental diff output (changesToAnsi) triggers it.\n *\n * Set SILVERY_SYNC_UPDATE=1 to force-enable (e.g., for testing in Kitty).\n * TODO: Re-enable by default once the Ghostty bug is fixed.\n * See: https://github.com/ghostty-org/ghostty/discussions/11002\n */\nconst SYNC_UPDATE_ENABLED =\n process.env.SILVERY_SYNC_UPDATE === \"1\" || process.env.SILVERY_SYNC_UPDATE === \"true\"\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n// Re-export from errors.ts (kept separate for React-free barrel imports)\nexport { IncrementalRenderMismatchError } from \"./errors\"\nimport { IncrementalRenderMismatchError } from \"./errors\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Non-TTY mode for rendering in non-interactive environments.\n */\nexport type NonTTYMode = \"auto\" | \"tty\" | \"line-by-line\" | \"static\" | \"plain\"\n\n/**\n * Resolved non-TTY mode after auto-detection.\n */\nexport type ResolvedNonTTYMode = Exclude<NonTTYMode, \"auto\">\n\nexport interface SchedulerOptions {\n /** stdout stream for writing output */\n stdout: NodeJS.WriteStream\n /**\n * Term size owner — single source of truth for cols/rows (and the only\n * reader that sees the coalesced resize post-window). When omitted the\n * scheduler falls back to reading `stdout.columns` / `stdout.rows`\n * directly; this keeps legacy embedders working but forfeits the coalesce\n * window.\n */\n size?: import(\"./runtime/devices/size\").Size\n /** Root Silvery node */\n root: AgNode\n /** Debug mode - logs render timing */\n debug?: boolean\n /** Minimum time between frames in ms (default: 16 for ~60fps) */\n minFrameTime?: number\n /** Render mode: fullscreen (absolute positioning) or inline (relative positioning) */\n mode?: \"fullscreen\" | \"inline\"\n /**\n * Non-TTY mode for non-interactive environments (default: 'auto')\n *\n * - 'auto': Detect based on environment\n * - 'tty': Force TTY mode\n * - 'line-by-line': Simple line output\n * - 'static': Only output final frame\n * - 'plain': Strip all ANSI codes\n */\n nonTTYMode?: NonTTYMode\n /** Slow frame warning threshold in ms (default: 50). Set to 0 to disable. */\n slowFrameThreshold?: number\n /** Pipeline configuration (caps-scoped measurer + output phase) */\n pipelineConfig?: PipelineConfig\n /**\n * Per-instance cursor accessors. When omitted, `getCursorState` falls\n * back to a `() => null` no-op (no hardware cursor) and no\n * subscribeCursor subscription is wired up. Callers that want cursor\n * visibility must construct accessors via `createCursorStore()` and\n * pass them here. (The legacy module-level global fallback was removed\n * in `km-silvery.delete-cursor-globals`.)\n */\n cursorAccessors?: CursorAccessors\n /**\n * Custom output writer. When provided, all render output is routed through\n * this function instead of stdout.write(). Used by the output guard to\n * ensure only silvery's render pipeline writes to stdout in alt screen mode.\n */\n writeOutput?: (data: string) => boolean\n /**\n * Read the terminal WINDOW focus state (@km/code/v0.2/19702). Called once per\n * fullscreen frame and threaded into `computeManagedFrame`: focused → filled\n * inverse block; unfocused → NO caret (hidden completely). Omitted → focused\n * (the fail-safe default).\n */\n windowFocused?: () => boolean\n}\n\nexport interface RenderStats {\n /** Number of renders executed */\n renderCount: number\n /** Number of renders skipped (batched) */\n skippedCount: number\n /** Last render duration in ms */\n lastRenderTime: number\n /** Average render time in ms */\n avgRenderTime: number\n}\n\n// ============================================================================\n// RenderScheduler Class\n// ============================================================================\n\n/**\n * Schedules and batches render operations.\n *\n * Usage:\n * ```ts\n * const scheduler = new RenderScheduler({\n * stdout: process.stdout,\n * root: rootNode,\n * });\n *\n * // Schedule renders (automatically batched)\n * scheduler.scheduleRender();\n * scheduler.scheduleRender(); // This won't cause duplicate render\n *\n * // Force immediate render\n * scheduler.forceRender();\n *\n * // Clean shutdown\n * scheduler.dispose();\n * ```\n */\nexport class RenderScheduler {\n private stdout: NodeJS.WriteStream\n private size?: import(\"./runtime/devices/size\").Size\n private root: AgNode\n private debugMode: boolean\n private minFrameTime: number\n private slowFrameThreshold: number\n private mode: \"fullscreen\" | \"inline\"\n private pipelineConfig?: PipelineConfig\n private getCursorState: () => import(\"@silvery/ag-react/hooks/useCursor\").CursorState | null\n /**\n * Read the terminal WINDOW focus state (@km/code/v0.2/19702). Threaded into\n * `computeManagedFrame` each frame: focused → filled block, unfocused → NO\n * caret (hidden). Defaults to `() => true` (the fail-safe focused default)\n * when no reader is supplied.\n */\n private getWindowFocused: () => boolean\n private nonTTYMode: ResolvedMode\n private outputTransformer: (content: string, prevLineCount: number) => string\n private writeOutput: (data: string) => boolean\n /**\n * Render-output throughput monitor — backs `SILVERY_STRICT=bytes_out`.\n * Constructed lazily, only when the slug is active, so the no-op path\n * has zero overhead. See `bytes-out-monitor.ts` +\n * `docs/lessons/cmux-pty-buffer-firehose.md`.\n */\n private bytesOutMonitor: BytesOutMonitor | null = null\n /**\n * Repaint-storm monitor — backs `SILVERY_STRICT=flicker`. Sibling of\n * `bytesOutMonitor`; scans the SAME terminal-output bytes for destructive\n * full-screen clears / scroll-region resets and PANICs when they repeat fast\n * enough to constitute flicker (the `@ag/code/20297-pane-flicker-on-resize`\n * class). Lazily constructed only when the slug is active. See\n * `flicker-monitor.ts`.\n */\n private flickerMonitor: FlickerMonitor | null = null\n /**\n * In-process heap-poll monitor — backs `SILVERY_STRICT=mem`. Sibling of\n * `bytesOutMonitor`; samples `process.memoryUsage()` on its own 30s\n * interval. See `mem-monitor.ts`.\n */\n private memMonitor: MemMonitor | null = null\n private log: Logger\n\n /** Previous buffer for diffing */\n private prevBuffer: TerminalBuffer | null = null\n\n /**\n * Previous presentation buffer for terminal output diffing, bundled with the\n * composited caret painted into it.\n *\n * The caret is threaded back into `computeManagedFrame` so a caret that moves\n * off / is suppressed on a static row gets that row marked dirty and the stale\n * `inverse` overlay cleared — the @km/code/v0.2/19702 overlay-residue fix.\n * Buffer and caret live in one struct so they cannot desync: every write sets\n * both in a single assignment, so the buffer baseline can never advance\n * without the caret it was composited with (see `PrevPresentation`).\n */\n private prevPresentation: PrevPresentation<TerminalBuffer> | null = null\n\n /**\n * Cross-frame post-state carrier (outline snapshots, etc.).\n *\n * Held at the scheduler level — not on `Ag` — because each `doRender()`\n * creates a fresh `Ag` per frame, so an Ag-internal `_postState` would be\n * empty every time and `clearPreviousOutlines` would never find prior\n * snapshots to restore. Mirrors the `prevBuffer` lifecycle: invalidated\n * together on resize / pause / clear so the snapshot coordinates always\n * match the buffer they reference.\n */\n private postState: RenderPostState = createRenderPostState()\n\n /** Line count of previous render (for non-TTY modes) */\n private prevLineCount = 0\n\n /** Monotonic terminal-output frame counter for render traces. */\n private outputFrameCount = 0\n\n /** Cached trace gate so disabled output frames do not read env per render. */\n private renderTraceEnabled = false\n\n /** Accumulated output for static mode */\n private staticOutput = \"\"\n\n /** Is a render currently scheduled? */\n private renderScheduled = false\n\n /** Last render timestamp */\n private lastRenderTime = 0\n\n /** Pending frame timeout (for frame rate limiting) */\n private frameTimeout: ReturnType<typeof setTimeout> | null = null\n\n /** Resize listener cleanup */\n private resizeCleanup: (() => void) | null = null\n\n /**\n * Cursor-state listener cleanup.\n *\n * The scheduler subscribes to cursor-state changes so a re-render is\n * triggered whenever `useCursor` (mounts / unmounts / moves) changes the\n * store. Without this subscription, `setCursorState` calls that happen\n * AFTER a render completes (e.g. on the second commit of a parent Box,\n * when `NodeContext` finally propagates a non-null AgNode) leave the\n * scheduler holding a stale (`null`) cursor state — the previous\n * render emitted `CURSOR_HIDE` and no further render is triggered to\n * emit `moveCursor + CURSOR_SHOW`. Result: the hardware cursor stays\n * parked at the prior frame's last buffer write. See bead\n * `km-silvercode.cursor-startup-position`.\n */\n private cursorCleanup: (() => void) | null = null\n\n /** Render statistics */\n private stats: RenderStats = {\n renderCount: 0,\n skippedCount: 0,\n lastRenderTime: 0,\n avgRenderTime: 0,\n }\n\n /** Is the scheduler disposed? */\n private disposed = false\n\n /** Is the scheduler paused? When paused, renders are deferred until resume. */\n private paused = false\n\n /** Was a render requested while paused? */\n private pendingWhilePaused = false\n\n /**\n * Lines written to stdout between renders (inline mode only).\n * When useScrollback or other code writes to stdout, those lines\n * displace the terminal cursor. This offset is consumed on the next render.\n */\n private scrollbackOffset = 0\n\n constructor(options: SchedulerOptions) {\n this.stdout = options.stdout\n this.size = options.size\n this.root = options.root\n this.debugMode = options.debug ?? false\n this.minFrameTime = options.minFrameTime ?? 16\n this.slowFrameThreshold = options.slowFrameThreshold ?? 50\n this.mode = options.mode ?? \"fullscreen\"\n this.pipelineConfig = options.pipelineConfig\n // No-op fallback for callers that don't pass `cursorAccessors`. The\n // legacy `globalGetCursorState` shim was deleted in\n // km-silvery.delete-cursor-globals; callers wanting cursor visibility\n // must wire `cursorAccessors` explicitly via createCursorStore().\n this.getCursorState = options.cursorAccessors?.getCursorState ?? (() => null)\n // @km/code/v0.2/20082 window-focus reader — default focused (fail-safe).\n this.getWindowFocused = options.windowFocused ?? (() => true)\n // Subscribe to cursor-state changes so the scheduler re-renders when\n // useCursor's store updates land between render frames. The\n // `subscribeCursor` accessor was previously exported but never wired up\n // — that gap is the root of km-silvercode.cursor-startup-position.\n if (options.cursorAccessors) {\n this.cursorCleanup = options.cursorAccessors.subscribeCursor(() => {\n // Schedule (not force) — coalesces with concurrent React commits.\n this.scheduleRender()\n })\n }\n this.writeOutput = options.writeOutput ?? ((data: string) => options.stdout.write(data))\n // bytes_out is a tier-1 strict slug (auto under SILVERY_STRICT=1).\n // Opt out per session with SILVERY_STRICT=1,!bytes_out.\n if (isStrictEnabled(\"bytes_out\", 1)) {\n this.bytesOutMonitor = createBytesOutMonitor()\n }\n // flicker is a tier-1 strict slug — repaint-storm sibling of bytes_out.\n // Opt out per session with SILVERY_STRICT=1,!flicker.\n if (isStrictEnabled(\"flicker\", 1)) {\n this.flickerMonitor = createFlickerMonitor()\n }\n // mem is a tier-1 strict slug — in-process heap-poll sibling of\n // bytes_out. Opt out per session with SILVERY_STRICT=1,!mem.\n if (isStrictEnabled(\"mem\", 1)) {\n this.memMonitor = createMemMonitor()\n }\n this.renderTraceEnabled = isRenderTraceEnabled()\n this.log = createLogger(\"silvery:scheduler\") as unknown as Logger\n\n // Resolve non-TTY mode based on environment\n this.nonTTYMode = resolveNonTTYMode({\n mode: options.nonTTYMode,\n stdout: this.stdout,\n })\n this.outputTransformer = createOutputTransformer(this.nonTTYMode)\n\n log.debug?.(`non-TTY mode resolved to: ${this.nonTTYMode}`)\n\n // Listen for terminal resize (only in TTY mode)\n if (this.nonTTYMode === \"tty\") {\n this.setupResizeListener()\n }\n }\n\n /**\n * Get the resolved non-TTY mode.\n */\n getNonTTYMode(): ResolvedMode {\n return this.nonTTYMode\n }\n\n // ==========================================================================\n // Public API\n // ==========================================================================\n\n /**\n * Schedule a render on the next microtask.\n *\n * Multiple calls within the same synchronous execution will be\n * coalesced into a single render.\n */\n scheduleRender(): void {\n if (this.disposed) return\n\n if (this.paused) {\n this.pendingWhilePaused = true\n return\n }\n\n if (this.renderScheduled) {\n this.stats.skippedCount++\n log.debug?.(`render skipped (batched), total: ${this.stats.skippedCount}`)\n return\n }\n\n this.renderScheduled = true\n log.debug?.(\"render scheduled\")\n\n // Use queueMicrotask for batching synchronous updates\n queueMicrotask(() => {\n this.renderScheduled = false\n\n if (this.disposed) return\n\n // Check frame rate limiting\n const now = Date.now()\n const timeSinceLastRender = now - this.lastRenderTime\n\n if (timeSinceLastRender < this.minFrameTime) {\n // Schedule for next frame\n log.debug?.(`frame limited, delay: ${this.minFrameTime - timeSinceLastRender}ms`)\n this.scheduleNextFrame(this.minFrameTime - timeSinceLastRender)\n } else {\n this.doRender()\n }\n })\n }\n\n /**\n * Force an immediate render, bypassing batching.\n */\n forceRender(): void {\n if (this.disposed) return\n\n if (this.paused) {\n this.pendingWhilePaused = true\n return\n }\n\n // Cancel any pending scheduled render\n this.renderScheduled = false\n if (this.frameTimeout) {\n clearTimeout(this.frameTimeout)\n this.frameTimeout = null\n }\n\n this.doRender()\n }\n\n /**\n * Get render statistics.\n */\n getStats(): RenderStats {\n return { ...this.stats }\n }\n\n /**\n * Report lines written to stdout between renders (inline mode only).\n * This adjusts cursor position tracking so the next render accounts\n * for the extra lines. Used by useScrollback to notify the scheduler\n * when it writes frozen items to stdout.\n */\n addScrollbackLines(lines: number): void {\n if (this.mode !== \"inline\" || lines <= 0) return\n this.scrollbackOffset += lines\n }\n\n /**\n * Copy text to the system clipboard via OSC 52.\n * Works across SSH sessions in terminals that support it.\n */\n copyToClipboard(text: string): void {\n if (this.disposed) return\n // Route through writeOutput so the output guard allows it through.\n // Uses createOsc52Backend (the canonical ClipboardBackend factory).\n const writable = { write: (data: string) => this.writeOutput(data) }\n createOsc52Backend(writable).write({ text })\n }\n\n /**\n * Pause rendering. While paused, scheduled and forced renders are deferred.\n * Input handling continues normally. Call resume() to unpause and force a\n * full redraw. Used for screen-switching (alt screen ↔ normal screen).\n */\n pause(): void {\n if (this.disposed || this.paused) return\n this.paused = true\n this.pendingWhilePaused = false\n log.debug?.(\"scheduler paused\")\n }\n\n /**\n * Resume rendering after pause. Resets the previous buffer so the next\n * render outputs everything (full redraw), then forces an immediate render.\n */\n resume(): void {\n if (this.disposed || !this.paused) return\n this.paused = false\n log.debug?.(\"scheduler resumed\")\n\n // Reset buffer for full redraw (alt screen was switched)\n this.prevBuffer = null\n this.prevPresentation = null // no prev buffer ⇒ no stale overlay to clear\n // Outline snapshots reference cells in the discarded buffer — reset together.\n this.postState = createRenderPostState()\n\n // If anything was deferred, render now\n if (this.pendingWhilePaused) {\n this.pendingWhilePaused = false\n this.doRender()\n }\n }\n\n /**\n * Whether the scheduler is currently paused.\n */\n isPaused(): boolean {\n return this.paused\n }\n\n /**\n * Clear the terminal and reset buffer.\n */\n clear(): void {\n if (this.disposed) return\n\n // Clear screen and keep cursor hidden\n this.writeOutput(\"\\x1b[2J\\x1b[H\\x1b[?25l\")\n\n // Reset buffer so next render outputs everything\n this.prevBuffer = null\n this.prevPresentation = null // no prev buffer ⇒ no stale overlay to clear\n // Outline snapshots reference cells in the discarded buffer — reset together.\n this.postState = createRenderPostState()\n }\n\n /**\n * Dispose the scheduler and clean up resources.\n */\n [Symbol.dispose](): void {\n this.dispose()\n }\n\n dispose(): void {\n if (this.disposed) return\n\n log.debug?.(\n `dispose: renders=${this.stats.renderCount}, skipped=${this.stats.skippedCount}, avg=${Math.round(this.stats.avgRenderTime)}ms`,\n )\n this.disposed = true\n\n // Cancel pending renders\n this.renderScheduled = false\n if (this.frameTimeout) {\n clearTimeout(this.frameTimeout)\n this.frameTimeout = null\n }\n\n // Remove resize listener\n if (this.resizeCleanup) {\n this.resizeCleanup()\n this.resizeCleanup = null\n }\n\n // Unsubscribe from cursor-state changes\n if (this.cursorCleanup) {\n this.cursorCleanup()\n this.cursorCleanup = null\n }\n\n // Stop the bytes_out throughput monitor (if active).\n if (this.bytesOutMonitor) {\n this.bytesOutMonitor.dispose()\n this.bytesOutMonitor = null\n }\n\n // Stop the flicker repaint-storm monitor (if active).\n if (this.flickerMonitor) {\n this.flickerMonitor.dispose()\n this.flickerMonitor = null\n }\n\n // Stop the mem heap-poll monitor (if active).\n if (this.memMonitor) {\n this.memMonitor.dispose()\n this.memMonitor = null\n }\n\n // In static mode, output the final frame on dispose\n if (this.nonTTYMode === \"static\" && this.staticOutput) {\n this.writeOutput(this.staticOutput)\n this.writeOutput(\"\\n\")\n }\n }\n\n /**\n * Get the last rendered output (for static mode).\n * Returns the plain text output that would be written on dispose.\n */\n getStaticOutput(): string {\n return this.staticOutput\n }\n\n // ==========================================================================\n // Private Methods\n // ==========================================================================\n\n /**\n * Schedule render for next frame (frame rate limiting).\n */\n private scheduleNextFrame(delay: number): void {\n if (this.frameTimeout) return\n\n this.frameTimeout = setTimeout(() => {\n this.frameTimeout = null\n if (!this.disposed) {\n this.doRender()\n }\n }, delay)\n }\n\n /**\n * Resolve the active cursor for this frame.\n *\n * Priority:\n * 1. Layout-output cursor (BoxProps.cursorOffset → LayoutSignals.cursorRect).\n * Set synchronously by the layout phase, so available on the very first\n * frame after mount — no React-effect-chain stale read.\n * 2. Cursor store (legacy useCursor() / Ink-compat setCursorPosition()).\n * Falls back here when no node declares a layout-output cursor.\n *\n * Returns null when no active cursor in either path.\n *\n * The shape is identical between paths so callers (cursor suffix emission,\n * inline-mode pipeline cursor) treat them uniformly.\n */\n private resolveActiveCursor(): CursorRect | null {\n const layoutCursor = findActiveCursorRect(this.root)\n if (layoutCursor) return layoutCursor\n return this.getStoreCursor()\n }\n\n /**\n * The legacy/store cursor (useCursor() / Ink-compat setCursorPosition()),\n * projected into CursorRect shape, or null. This is the cursor source that\n * has NO owning AgNode — `computeManagedFrame` composites it unconditionally\n * (it is an explicit imperative request, not a focus-gated declarative\n * fallback). Layout-output cursors are resolved by `findActiveCursorRect`\n * inside `computeManagedFrame`, so this helper intentionally does NOT consult\n * the tree.\n */\n private getStoreCursor(): CursorRect | null {\n const storeCursor = this.getCursorState()\n if (!storeCursor) return null\n // Project store CursorState into CursorRect shape (visible defaults to\n // true on the store side; we keep it explicit here for clarity).\n return {\n x: storeCursor.x,\n y: storeCursor.y,\n visible: storeCursor.visible !== false,\n shape: storeCursor.shape,\n }\n }\n\n /**\n * Execute the actual render.\n */\n private doRender(): void {\n using render = this.log.span?.(\"render\")\n const startTime = Date.now()\n\n try {\n // Get terminal dimensions. Prefer the Term's Size owner (coalesced\n // resize, alien-signals-backed) when provided. Falls back to direct\n // stdout reads for legacy embedders that haven't wired a Size yet.\n const width = this.size?.cols() ?? this.stdout.columns ?? 80\n // Inline mode: use NaN height so layout engine auto-sizes to content.\n // Fullscreen mode: use terminal rows as the constraint.\n const height = this.mode === \"inline\" ? NaN : (this.size?.rows() ?? this.stdout.rows ?? 24)\n\n log.debug?.(\n `render #${this.stats.renderCount + 1}: ${width}x${height}, nonTTYMode=${this.nonTTYMode}`,\n )\n\n // Run render pipeline\n const scrollbackOffset = this.scrollbackOffset\n this.scrollbackOffset = 0 // Consume the offset\n const measurer = this.pipelineConfig?.measurer\n // Inline cursor is resolved AFTER layout below — the layout-output path\n // (BoxProps.cursorOffset → cursorRect signal) requires layout to have\n // run. The legacy store fallback used to be resolved pre-layout, but\n // that path doesn't depend on layout state either, so deferring is safe.\n let inlineCursor:\n | { x: number; y: number; visible: boolean; shape?: CursorRect[\"shape\"] }\n | null\n | undefined\n const doRender = () => {\n const ag = createAg(this.root, { measurer })\n ag.layout({ cols: width, rows: height })\n if (this.mode === \"inline\") {\n const c = this.resolveActiveCursor()\n // Output phase expects the legacy CursorState shape. CursorRect is a\n // structural superset (same fields), so a direct assign is safe.\n inlineCursor = c\n }\n // Pass the scheduler-owned post-state carrier so outline snapshots\n // survive the per-frame `createAg(this.root)` recycle. Without this,\n // every frame's `clearPreviousOutlines` runs against an empty list\n // and stale outline pixels from `prevBuffer.clone()` leak through —\n // the same shape as km-yej6 / km-silvery.outline-incremental-clear.\n const { buffer, overlay } = ag.render({\n prevBuffer: this.prevBuffer,\n postState: this.postState,\n })\n // Single source of truth for managed-frame cursor handling\n // (managed-caret.ts). The scheduler's legacy/store cursor is threaded as\n // `legacyCursor` so the Ink-compat `useCursor()` path still composites,\n // while a declarative tree cursor is focus-gated (the\n // @km/code/v0.2/19702 fix). The full ManagedFrame is reused by the\n // cursor-suffix block below — no re-derivation.\n const managed = computeManagedFrame(buffer, this.root, this.mode, {\n legacyCursor: this.mode === \"fullscreen\" ? this.getStoreCursor() : null,\n prevCaret: this.prevPresentation?.caret ?? null,\n windowFocused: this.getWindowFocused(),\n })\n const fullscreenCursor = managed.cursorTarget\n const outputBuffer = managed.presentationBuffer\n\n const start = performance.now()\n const outputFn = this.pipelineConfig?.outputPhaseFn ?? outputPhase\n let ansiOutput: string\n try {\n clearLastOutputPhaseDiagnostics()\n ansiOutput = outputFn(\n this.prevPresentation?.buffer ?? null,\n outputBuffer,\n this.mode,\n scrollbackOffset,\n this.mode === \"inline\" ? (this.size?.rows() ?? this.stdout.rows ?? 24) : undefined,\n inlineCursor ?? undefined,\n )\n } catch (e) {\n if (e instanceof Error) {\n ;(e as any).__silvery_buffer = buffer\n }\n throw e\n }\n // Append Kitty emoji-scrim overlay from the backdrop-fade pass.\n // No-op when backdrop inactive or cap disabled — zero-length string.\n if (overlay) ansiOutput += overlay\n const tOutput = performance.now() - start\n\n // Bench instrumentation: accumulate output-phase timing\n const acc = (globalThis as any).__silvery_bench_phases\n if (acc) {\n acc.output += tOutput\n acc.pipelineCalls += 1\n }\n\n return {\n output: ansiOutput,\n buffer,\n outputBuffer,\n overlay,\n cursor: fullscreenCursor,\n compositorCaret: managed.compositorCaret,\n managed,\n }\n }\n const {\n output,\n buffer,\n outputBuffer,\n overlay: incrementalOverlay,\n compositorCaret,\n managed: managedFrame,\n } = measurer ? runWithMeasurer(measurer, doRender) : doRender()\n\n // Transform output based on non-TTY mode\n let transformedOutput: string\n if (this.nonTTYMode === \"tty\") {\n // Pass through unchanged\n transformedOutput = output\n } else if (this.nonTTYMode === \"static\") {\n // Store for final output, don't write yet\n this.staticOutput = stripAnsi(output)\n transformedOutput = \"\"\n } else {\n // Apply line-by-line or plain transformation\n transformedOutput = this.outputTransformer(output, this.prevLineCount)\n this.prevLineCount = countLines(output)\n }\n\n // Managed-frame cursor controls come from the single computeManagedFrame\n // call inside doRender (managed-caret.ts). Fullscreen TTY frames paint the\n // visible caret into the presentation buffer ONLY for a focused editable\n // (the @km/code/v0.2/19702 fix), then always park + hide the hardware\n // cursor at the editable's bounds so late/hollow terminal cursors cannot\n // appear in transcript or chrome rows. The suffix is consumed only on a\n // real TTY — `managedFrame` was computed for mode === \"fullscreen\", which\n // is exactly when `nonTTYMode === \"tty\"`.\n let cursorSuffix = \"\"\n let cursorTarget: OutputCursorTarget | null = null\n let expectedTerminal: OutputCursorTarget | null = null\n let promptBounds: OutputCursorBounds | null = null\n let composerBounds: OutputCursorBounds | null = null\n if (this.nonTTYMode === \"tty\") {\n cursorSuffix = managedFrame.cursorSuffix\n cursorTarget = managedFrame.cursorTarget\n expectedTerminal = managedFrame.expectedTerminal\n promptBounds = managedFrame.promptBounds\n composerBounds = managedFrame.composerBounds\n }\n\n // Write output wrapped with synchronized update (DEC 2026) for TTY mode.\n // This tells the terminal to batch the output and paint atomically,\n // preventing tearing during rapid screen updates.\n const cursorDelivery = protectManagedCursorSuffix(cursorSuffix)\n if (transformedOutput.length > 0 || cursorDelivery.length > 0) {\n const cursorDeliveryNeedsSync =\n this.nonTTYMode === \"tty\" &&\n cursorSuffix.length > 0 &&\n expectedTerminal !== null &&\n (expectedTerminal.x !== 0 || expectedTerminal.y !== 0)\n const fullOutput =\n this.nonTTYMode === \"tty\" && (SYNC_UPDATE_ENABLED || cursorDeliveryNeedsSync)\n ? `${ANSI.SYNC_BEGIN}${transformedOutput}${cursorDelivery}${ANSI.SYNC_END}`\n : transformedOutput + cursorDelivery\n\n // Debug: log output sizes to detect potential pipe buffer splits\n if (log.debug) {\n const bytes = Buffer.byteLength(fullOutput)\n log.debug?.(\n `stdout.write: ${bytes} bytes (${transformedOutput.length} chars output + ${cursorSuffix.length} chars cursor)`,\n )\n if (bytes > 16384) {\n log.debug?.(\n `large output: ${bytes} bytes may exceed pipe buffer (16KB on macOS), risk of mid-sequence split`,\n )\n }\n }\n\n // Capture raw ANSI output to file for debugging garbled rendering\n const captureFile = process.env.SILVERY_CAPTURE_OUTPUT\n if (captureFile) {\n // sync-node-builtin: multi-target lazy guard via getBuiltinModule (ESM-only wave-3).\n const fs = process.getBuiltinModule(\"node:fs\")\n fs.appendFileSync(\n captureFile,\n `--- FRAME ${this.stats.renderCount + 1} (${Buffer.byteLength(fullOutput)} bytes) ---\\n`,\n )\n fs.appendFileSync(captureFile, fullOutput)\n fs.appendFileSync(captureFile, \"\\n\")\n }\n\n recordOutputCursorDiagnostics({\n reason: \"scheduler-render\",\n mode: \"fullscreen\",\n width,\n height,\n output: fullOutput,\n target: cursorTarget,\n expectedTerminal,\n compositorCaret,\n promptBounds,\n composerBounds,\n })\n this.writeOutput(fullOutput)\n // flicker instrumentation — scan the SAME post-write bytes for\n // destructive-clear / scroll-region repaint storms. Records the raw\n // ANSI string (not just a byte count) so it can detect the bare-`2J`\n // @ag/code/20297 signature. PANICs (throws) at the fixed\n // clears-per-window threshold in `flicker-monitor.ts`.\n if (this.flickerMonitor) {\n const renderNumber = this.stats.renderCount + 1\n this.flickerMonitor.recordFrame(renderNumber, fullOutput)\n }\n // bytes_out instrumentation — record AFTER write so the monitor\n // accounts for what actually left the process. Fixed thresholds\n // (1 MB/s WARN × 10s, 100 MB/s PANIC × 2s) in `bytes-out-monitor.ts`.\n const traceOutputEnabled = this.renderTraceEnabled || isRenderTraceEnabled()\n if (this.bytesOutMonitor || traceOutputEnabled) {\n const outputDiagnostics = getLastOutputPhaseDiagnostics()\n const bytes = Buffer.byteLength(fullOutput)\n const diagnostics = {\n ...outputDiagnostics,\n outputChars: transformedOutput.length,\n cursorChars: cursorDelivery.length,\n syncWrapped:\n this.nonTTYMode === \"tty\" && (SYNC_UPDATE_ENABLED || cursorDeliveryNeedsSync),\n }\n const renderNumber = this.stats.renderCount + 1\n this.bytesOutMonitor?.recordWrite(renderNumber, bytes, diagnostics)\n if (traceOutputEnabled) {\n this.outputFrameCount += 1\n emitRenderOutputFrame({\n renderCount: renderNumber,\n outputFrame: this.outputFrameCount,\n bytes,\n diagnostics,\n })\n }\n }\n }\n\n // Save buffer for next diff\n this.prevBuffer = buffer\n // The presentation buffer and the caret composited into it are stored as\n // one unit so the next frame's overlay-clear always sees the caret that\n // matches the baseline it diffs against. @km/code/v0.2/19702.\n this.prevPresentation = { buffer: outputBuffer, caret: compositorCaret }\n\n // Commit boundary — promote in-flight rect signals (just written by\n // ag.layout above) to their committed peers. Reactive\n // useBoxRect/useScrollRect/useScreenRect consumers subscribe to\n // committed and will fire `forceUpdate` here if a rect changed,\n // causing React to schedule a re-render that the scheduler picks up\n // via the next store.subscribe → scheduleRender cycle. Within this\n // single doRender pass the committed value is invariant, so a\n // render that branches on useBoxRect cannot ping-pong.\n // See bead `@km/silvery/use-deferred-box-rect-and-post-commit-observers`.\n commitLayoutSnapshot(this.root)\n\n // SILVERY_STRICT: compare incremental render against fresh render.\n // The `fresh-layout` slug (tier 2) also activates the comparison on its own\n // so it composes when isolated (SILVERY_STRICT=fresh-layout).\n const freshLayout = isStrictEnabled(FRESH_LAYOUT_STRICT_SLUG, FRESH_LAYOUT_STRICT_MIN_TIER)\n if ((isStrictEnabled(\"incremental\", 1) || freshLayout) && this.stats.renderCount > 0) {\n const renderNum = this.stats.renderCount + 1\n const doFreshRender = () => {\n const freshAg = createAg(this.root, { measurer })\n // fresh-layout STRICT slug: force an INDEPENDENT from-scratch layout so\n // a shared stale rect (a layout-affecting change that failed to\n // markDirty flexily) surfaces as a buffer mismatch instead of being\n // masked by the incremental path's cleaned flexily tree. Deterministic\n // on clean code (flexily expectRelayoutMatchesFresh fuzz). See\n // markLayoutTreeDirty + AgLayoutOptions.forceFullPropagate.\n if (freshLayout) markLayoutTreeDirty(this.root)\n freshAg.layout(\n { cols: width, rows: height },\n { skipLayoutNotifications: true, forceFullPropagate: freshLayout },\n )\n // Fresh render must NOT consume or mutate `this.postState` (which\n // tracks the incremental path's snapshots). Pass `fresh: true` so\n // the Ag uses a throw-away empty carrier for this comparison.\n return freshAg.render({ fresh: true })\n }\n const { buffer: freshBuffer, overlay: freshOverlay } = measurer\n ? runWithMeasurer(measurer, doFreshRender)\n : doFreshRender()\n\n // STRICT overlay-plan comparison.\n //\n // Invariant: `applyBackdrop` is a pure function of (tree markers,\n // buffer cells, options) → overlay is deterministic. Incremental\n // and fresh paths MUST emit byte-identical overlay strings.\n //\n // A drift here signals non-determinism in marker collection order,\n // the emoji walk, or placement ID derivation — any of which would\n // cause scrim flicker or orphaned placements across frames.\n if (incrementalOverlay !== freshOverlay) {\n const msg = formatOverlayMismatch(incrementalOverlay, freshOverlay, renderNum)\n if (process.env.DEBUG_LOG) {\n appendFileSync(process.env.DEBUG_LOG, msg + \"\\n\")\n }\n log.error?.(msg)\n throw new IncrementalRenderMismatchError(msg)\n }\n\n let found = false\n for (let y = 0; y < buffer.height && !found; y++) {\n for (let x = 0; x < buffer.width && !found; x++) {\n const a = buffer.getCell(x, y)\n const b = freshBuffer.getCell(x, y)\n if (!cellEquals(a, b)) {\n found = true\n\n // Build rich debug context\n const ctx = buildMismatchContext(this.root, x, y, a, b, renderNum)\n\n // Capture render-phase instrumentation snapshot\n const renderPhaseStats: RenderPhaseStats | undefined = (globalThis as any)\n .__silvery_content_detail\n ? structuredClone((globalThis as any).__silvery_content_detail)\n : undefined\n\n const debugInfo = formatMismatchContext(ctx, renderPhaseStats)\n\n // Include text output for full picture\n const incText = bufferToText(buffer)\n const freshText = bufferToText(freshBuffer)\n const msg = debugInfo + `--- incremental ---\\n${incText}\\n--- fresh ---\\n${freshText}`\n\n if (process.env.DEBUG_LOG) {\n appendFileSync(process.env.DEBUG_LOG, msg + \"\\n\")\n }\n log.error?.(msg)\n // Throw special error that won't be caught by general error handler\n throw new IncrementalRenderMismatchError(msg, {\n renderPhaseStats,\n mismatchContext: ctx,\n })\n }\n }\n }\n if (!found && process.env.DEBUG_LOG) {\n appendFileSync(process.env.DEBUG_LOG, `SILVERY_STRICT: render #${renderNum} OK\\n`)\n }\n }\n\n // Update stats\n const renderTime = Date.now() - startTime\n this.stats.renderCount++\n this.stats.lastRenderTime = renderTime\n this.stats.avgRenderTime =\n (this.stats.avgRenderTime * (this.stats.renderCount - 1) + renderTime) /\n this.stats.renderCount\n this.lastRenderTime = Date.now()\n\n // Record span data\n if (render) {\n render.spanData.renderCount = this.stats.renderCount\n render.spanData.renderTime = renderTime\n render.spanData.bytes = transformedOutput.length\n }\n\n log.debug?.(\n `render #${this.stats.renderCount} complete: ${renderTime}ms, output: ${transformedOutput.length} bytes`,\n )\n\n // First render is always slow (initialization); use 5x threshold for it\n const threshold =\n this.stats.renderCount <= 1 ? this.slowFrameThreshold * 5 : this.slowFrameThreshold\n if (threshold > 0 && renderTime > threshold) {\n log.debug?.(\n `slow frame: render #${this.stats.renderCount} took ${renderTime}ms (threshold: ${this.slowFrameThreshold}ms, bytes: ${transformedOutput.length})`,\n )\n }\n\n if (this.debugMode) {\n this.logDebug(`Render #${this.stats.renderCount} took ${renderTime}ms`)\n }\n } catch (error) {\n // Log and re-throw all render errors - the app should handle cleanup\n log.error?.(`render error: ${error}`)\n this.logError(\"Render error:\", error)\n throw error\n }\n }\n\n /**\n * Set up terminal resize listener.\n */\n private setupResizeListener(): void {\n let resizeTimeout: ReturnType<typeof setTimeout> | null = null\n\n const handleResize = () => {\n // Debounce resize events\n if (resizeTimeout) {\n clearTimeout(resizeTimeout)\n }\n\n resizeTimeout = setTimeout(() => {\n resizeTimeout = null\n\n // Reset buffer to force full redraw\n this.prevBuffer = null\n this.prevPresentation = null // no prev buffer ⇒ no stale overlay to clear\n // Outline snapshots are buffer-coordinate-bound; reset together so\n // post-resize clearPreviousOutlines doesn't write at out-of-bounds\n // coordinates from the old dimensions.\n this.postState = createRenderPostState()\n\n // Schedule render\n this.scheduleRender()\n }, 50) // 50ms debounce\n }\n\n this.stdout.on(\"resize\", handleResize)\n\n this.resizeCleanup = () => {\n this.stdout.off(\"resize\", handleResize)\n if (resizeTimeout) {\n clearTimeout(resizeTimeout)\n }\n }\n }\n\n /**\n * Log debug message (via loggily, not stderr — stderr corrupts alt screen).\n */\n private logDebug(message: string): void {\n log.debug?.(message)\n }\n\n /**\n * Log error message (via loggily, not stderr — stderr corrupts alt screen).\n */\n private logError(message: string, error: unknown): void {\n if (error instanceof Error) {\n log.error?.(`${message} ${error.stack ?? error.message}`)\n } else {\n log.error?.(`${message} ${String(error)}`)\n }\n }\n}\n\n// ============================================================================\n// STRICT Diagnostics Helpers\n// ============================================================================\n\n/**\n * Format a human-readable diff when the incremental and fresh Kitty overlays\n * disagree. The overlays are Kitty graphics protocol escape sequences that\n * place translucent \"scrim\" images over emoji cells inside faded regions\n * (see `pipeline/backdrop/`). They MUST be byte-identical across fresh and\n * incremental paths — any drift is a determinism bug in:\n *\n * - `collectBackdropMarkers` (tree walk order)\n * - `collectEmojiCellsInFadeRegion` (rect iteration)\n * - `backdropPlacementId` (per-cell ID derivation)\n * - `assertSingleAmount` (amount selection when markers disagree)\n *\n * The diff surfaces the placement IDs that differ — the Kitty protocol uses\n * `p=<id>` in each placement command, so grep-friendly IDs are the\n * quickest path from \"test failed\" to \"which cell drifted\".\n */\nfunction formatOverlayMismatch(incremental: string, fresh: string, renderNum: number): string {\n const incPlacements = extractPlacementIds(incremental)\n const freshPlacements = extractPlacementIds(fresh)\n\n const incSet = new Set(incPlacements)\n const freshSet = new Set(freshPlacements)\n\n const onlyIncremental = incPlacements.filter((id) => !freshSet.has(id))\n const onlyFresh = freshPlacements.filter((id) => !incSet.has(id))\n\n const lines: string[] = [\n `[SILVERY_STRICT] Kitty overlay mismatch at render #${renderNum}`,\n ` incremental length: ${incremental.length} bytes, placements: ${incPlacements.length}`,\n ` fresh length: ${fresh.length} bytes, placements: ${freshPlacements.length}`,\n ]\n if (onlyIncremental.length > 0) {\n lines.push(` only in incremental (moved/appeared): ${onlyIncremental.join(\", \")}`)\n }\n if (onlyFresh.length > 0) {\n lines.push(` only in fresh (missing/disappeared): ${onlyFresh.join(\", \")}`)\n }\n if (onlyIncremental.length === 0 && onlyFresh.length === 0) {\n lines.push(` placements match — drift is in scrim-image payload or ordering`)\n }\n\n // Truncated raw bytes for inspection. Full strings can be many KB (each\n // scrim image payload is base64-encoded) — cap to avoid log-flood.\n const cap = 400\n lines.push(\n ` incremental[0..${cap}]: ${JSON.stringify(incremental.slice(0, cap))}`,\n ` fresh [0..${cap}]: ${JSON.stringify(fresh.slice(0, cap))}`,\n )\n return lines.join(\"\\n\")\n}\n\n/**\n * Extract the list of `p=<id>` placement IDs from a Kitty overlay string, in\n * emission order. Used by the STRICT diagnostic to show which placements\n * moved/appeared/disappeared.\n */\nfunction extractPlacementIds(overlay: string): string[] {\n const ids: string[] = []\n const re = /p=(\\d+)/g\n let m: RegExpExecArray | null\n while ((m = re.exec(overlay)) !== null) {\n ids.push(m[1]!)\n }\n return ids\n}\n\n// ============================================================================\n// Factory Function\n// ============================================================================\n\n/**\n * Create a render scheduler.\n *\n * @param options Scheduler options\n * @returns A new RenderScheduler instance\n */\nexport function createScheduler(options: SchedulerOptions): RenderScheduler {\n return new RenderScheduler(options)\n}\n\n// ============================================================================\n// Utility: Simple Render (for testing/debugging)\n// ============================================================================\n\n/**\n * Render once to a string (for testing).\n *\n * Does not batch or diff - just runs the pipeline and returns ANSI output.\n */\nexport function renderToString(root: AgNode, width: number, height: number): string {\n const ag = createAg(root)\n ag.layout({ cols: width, rows: height })\n const { buffer } = ag.render()\n return outputPhase(null, buffer, \"fullscreen\")\n}\n","/**\n * withPasteChain — apply-chain plugin for bracketed-paste routing.\n *\n * Paste routing has priority:\n *\n * 1. Focused `onPaste` — if a focused element subscribes directly\n * (via a focus event listener) it should consume the paste first.\n * The chain expresses this via a pluggable `routeToFocused`\n * callback — in production this dispatches a `paste` DOM event\n * through the focus tree (see `create-app.tsx`); in tests it can\n * be stubbed.\n *\n * 2. Global `usePaste` handlers — registered by React components via\n * the `usePaste()` / `usePasteCallback()` / `usePasteEvents()`\n * hooks. Invoked in registration order, same contract as\n * `withInputChain`'s fallback store.\n *\n * A paste never produces an \"exit\" result; we return `[]` whenever at\n * least one handler (or the focused route) consumes the paste, so the\n * outer runner knows to render.\n */\n\nimport type { ApplyResult, Effect, Op } from \"../types\"\nimport type { BaseApp } from \"./base-app\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type PasteHandler = (text: string) => void\n\n/** Paste handler with richer metadata (mirrors `usePasteEvents`). */\nexport interface PasteEvent {\n /** The raw text that was pasted (as delivered by the terminal). */\n readonly text: string\n /** Whether the focus route consumed this event. */\n readonly focusedConsumed: boolean\n}\n\n/** Store slice installed by {@link withPasteChain}. */\nexport interface PasteStore {\n readonly handlers: ReadonlyArray<PasteHandler>\n register(handler: PasteHandler): () => void\n unregister(handler: PasteHandler): void\n}\n\n/** Options for {@link withPasteChain}. */\nexport interface WithPasteChainOptions {\n /**\n * If provided, called with the pasted text before any fallback\n * handlers run. Return `true` if the paste was consumed by a focused\n * element (fallback handlers will be skipped).\n *\n * Production wires this to `dispatchPasteEvent` through the focus\n * tree; tests typically leave it undefined.\n */\n routeToFocused?: (text: string) => boolean\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\n/**\n * Install the paste router plugin.\n *\n * On `term:paste` ops:\n * - Call `routeToFocused(text)` first (if provided). If it returns\n * true, we short-circuit: handled by focus tree, no fallback.\n * - Otherwise invoke every registered global handler in order.\n * - Always emit `[{type:\"render\"}]` when handled, so the runner\n * knows to paint the post-paste state.\n */\nexport function withPasteChain(\n options: WithPasteChainOptions = {},\n): <A extends BaseApp>(app: A) => A & { paste: PasteStore } {\n return <A extends BaseApp>(app: A): A & { paste: PasteStore } => {\n const handlers: PasteHandler[] = []\n const store: PasteStore = {\n handlers,\n register(handler) {\n handlers.push(handler)\n return () => {\n const i = handlers.indexOf(handler)\n if (i >= 0) handlers.splice(i, 1)\n }\n },\n unregister(handler) {\n for (let i = handlers.length - 1; i >= 0; i--) {\n if (handlers[i] === handler) handlers.splice(i, 1)\n }\n },\n }\n const prev = app.apply\n app.apply = (op: Op): ApplyResult => {\n if (op.type !== \"term:paste\") return prev(op)\n const text = (op as { text?: string }).text ?? \"\"\n let consumed = false\n if (options.routeToFocused) {\n try {\n consumed = !!options.routeToFocused(text)\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error(\"[withPasteChain] routeToFocused threw\", err)\n }\n }\n if (!consumed && handlers.length > 0) {\n for (const handler of handlers) {\n try {\n handler(text)\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error(\"[withPasteChain] handler threw\", err)\n }\n }\n consumed = true\n }\n if (!consumed) return prev(op)\n const effects: Effect[] = [{ type: \"render\" }]\n return effects\n }\n return Object.assign(app, { paste: store })\n }\n}\n","/**\n * withInputChain — apply-chain plugin for the fallback `useInput` store.\n *\n * Fallback = this plugin runs AFTER the focused dispatch plugin\n * (`withFocusChain`) in the apply chain. When focus consumes an event,\n * useInput never sees it — focused components have priority.\n *\n * ## Contract\n *\n * - Plugin owns a list of handlers registered by React components via\n * the `useInput()` hook. Handlers are invoked in registration order\n * (stable; matches the pre-refactor RuntimeContext behaviour).\n *\n * - On `input:key` ops:\n * - release / modifier-only events are observed (some useInput\n * callers still want to know about Ctrl up/down) but do not\n * mark the event as consumed.\n * - press / repeat events invoke every active handler. If any\n * handler returns \"exit\", we short-circuit with\n * `[{type:\"exit\"}]`. Otherwise we signal \"handled\" by returning\n * `[]` when at least one active handler exists.\n *\n * - If no active handlers exist, the plugin returns `false` (pass\n * through) so downstream plugins or the app handler can see the\n * event unchanged.\n *\n * ## Why \"active\" handlers?\n *\n * Components can `useInput(fn, { isActive: false })` to temporarily\n * suppress their handler without unmounting. This is used e.g. by\n * picker dialogs where the parent's global keys should be disabled\n * while the picker is open but the component remains mounted.\n */\n\nimport type { ApplyResult, Effect, Op } from \"../types\"\nimport type { BaseApp } from \"./base-app\"\nimport type { KeyShape } from \"./with-terminal-chain\"\n\n// ---------------------------------------------------------------------------\n// Handler + store types\n// ---------------------------------------------------------------------------\n\n/** Signature matches the classic `useInput(handler)` form. */\nexport type InputHandler = (input: string, key: KeyShape) => void | \"exit\"\n\ninterface InputEntry {\n handler: InputHandler\n active: boolean\n}\n\n/** Store slice installed by {@link withInputChain}. */\nexport interface InputStore {\n /** Internal array of registered entries (exposed for diagnostics). */\n readonly handlers: ReadonlyArray<InputEntry>\n /**\n * Register a new input handler. Returns an unregister function.\n * @param handler the callback\n * @param active whether the handler is currently active (default: true)\n */\n register(handler: InputHandler, active?: boolean): () => void\n /** Update the active flag for a previously-registered handler. */\n setActive(handler: InputHandler, active: boolean): void\n /** Remove a handler (primarily for tests; `register` returns an unregister fn). */\n unregister(handler: InputHandler): void\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction isModifierOnly(_input: string, key: KeyShape | undefined): boolean {\n // Authoritative flag — set by @silvery/ag/keys `parseKey()` when the\n // key name is one of the dedicated Kitty modifier codepoints\n // (leftshift, leftctrl, …). Deriving from `input === \"\"` plus a\n // modifier flag is WRONG: Shift+Tab, Shift+Enter, Shift+Arrow,\n // Ctrl+Tab, etc. all have empty `input` but carry a real payload via\n // the `tab` / `return` / `upArrow` flags. See\n // `MODIFIER_ONLY_NAMES` in @silvery/ag/keys.\n return key?.isModifierOnly === true\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\n/**\n * Install the fallback useInput store plugin.\n *\n * Pipe order note: place this plugin so it runs AFTER withFocusChain.\n * Because the last plugin in `pipe()` wraps the outermost layer, the\n * chain reads left-to-right with the base on the left:\n *\n * pipe(create(), withTerminalChain(), withInputChain(), withFocusChain())\n *\n * => apply order: focusChain -> inputChain -> terminalChain -> base\n *\n * => focused components consume before useInput, which is what we want.\n */\nexport function withInputChain<A extends BaseApp>(app: A): A & { input: InputStore } {\n const entries: InputEntry[] = []\n const store: InputStore = {\n handlers: entries,\n register(handler, active = true) {\n const entry: InputEntry = { handler, active }\n entries.push(entry)\n return () => {\n const i = entries.indexOf(entry)\n if (i >= 0) entries.splice(i, 1)\n }\n },\n setActive(handler, active) {\n for (const entry of entries) {\n if (entry.handler === handler) entry.active = active\n }\n },\n unregister(handler) {\n for (let i = entries.length - 1; i >= 0; i--) {\n if (entries[i]!.handler === handler) entries.splice(i, 1)\n }\n },\n }\n const prev = app.apply\n app.apply = (op: Op): ApplyResult => {\n if (op.type !== \"input:key\") return prev(op)\n const input = (op as { input?: string }).input ?? \"\"\n const key = (op as { key?: KeyShape }).key\n const isRelease = key?.eventType === \"release\"\n const modOnly = isModifierOnly(input, key)\n // Always let release / modifier-only events bubble down the chain —\n // e.g. `useModifierKeys` (a future plugin) wants to hear them — but\n // do NOT invoke useInput handlers and do NOT mark the event handled\n // at this layer.\n if (isRelease || modOnly) return prev(op)\n\n let hasActive = false\n const effects: Effect[] = []\n for (const entry of entries) {\n if (!entry.active) continue\n hasActive = true\n try {\n const result = entry.handler(input, key ?? ({} as KeyShape))\n if (result === \"exit\") {\n effects.push({ type: \"exit\" })\n return effects\n }\n } catch (err) {\n // Surface, but don't kill the event loop for a single bad handler.\n // eslint-disable-next-line no-console\n console.error(\"[withInputChain] handler threw\", err)\n }\n }\n if (hasActive) return effects\n return prev(op)\n }\n return Object.assign(app, { input: store })\n}\n","/**\n * withCustomEvents — apply-chain plugin for app-defined custom events.\n *\n * Replaces the legacy `RuntimeContextValue.emit(\"foo\", …)` /\n * `RuntimeContextValue.on(\"foo\", …)` pair for anything that isn't one of\n * the built-in input / paste / focus events. Provides a typed bus where\n * components emit named events and handlers subscribe by channel name.\n *\n * ## Contract\n *\n * - Channel names are arbitrary strings chosen by the app. Payloads are\n * untyped `unknown[]` at the plugin boundary — callers usually narrow\n * via a branded wrapper (see `km-tui`'s `useLinkOpen`).\n *\n * - Handlers are invoked in registration order; a thrown handler is\n * surfaced to the console but does not short-circuit the bus.\n *\n * - The plugin never intercepts ops — it exposes its store via\n * `app.events` and returns `false` for every op so downstream plugins\n * still see them.\n *\n * ## Why a chain plugin?\n *\n * Before this existed, custom events rode on `RuntimeContextValue.on /\n * emit`. That surface is being trimmed to just `{exit: () => void}` as\n * part of the TEA Phase 2 wiring. Routing custom events through a\n * dedicated plugin keeps the chain the single authority for view ↔\n * runtime messaging, matching how input / paste / focus already work.\n *\n * @example\n * ```tsx\n * // Install\n * const app = pipe(\n * createBaseApp(),\n * withTerminalChain(),\n * withPasteChain(),\n * withInputChain,\n * withFocusChain(...),\n * withCustomEvents,\n * )\n *\n * // Emit from a component\n * app.events.emit(\"link:open\", \"https://example.com\")\n *\n * // Subscribe from a hook\n * useEffect(() => chain.events.on(\"link:open\", (href) => openExternal(href as string)), [chain])\n * ```\n */\n\nimport type { ApplyResult, Op } from \"../types\"\nimport type { BaseApp } from \"./base-app\"\n\n// ---------------------------------------------------------------------------\n// Store shape\n// ---------------------------------------------------------------------------\n\n/** Generic payload handler — custom events carry `unknown[]` payloads. */\nexport type CustomEventHandler = (...args: unknown[]) => void\n\n/** Store slice installed by {@link withCustomEvents}. */\nexport interface CustomEventStore {\n /** Subscribe to `channel`. Returns an unsubscribe function. */\n on(channel: string, handler: CustomEventHandler): () => void\n /** Emit `channel` with payload — invokes every registered handler in order. */\n emit(channel: string, ...args: unknown[]): void\n /**\n * Remove a specific handler. Prefer the cleanup function returned by\n * `on()`; this is primarily for tests and rare teardown paths.\n */\n off(channel: string, handler: CustomEventHandler): void\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\n/**\n * Install the custom-events plugin.\n *\n * Exposes the event bus at `app.events`. The plugin is observer-only —\n * it does not touch the op stream; every op passes straight through to\n * downstream plugins.\n */\nexport function withCustomEvents<A extends BaseApp>(app: A): A & { events: CustomEventStore } {\n const channels = new Map<string, CustomEventHandler[]>()\n const store: CustomEventStore = {\n on(channel, handler) {\n let handlers = channels.get(channel)\n if (!handlers) {\n handlers = []\n channels.set(channel, handlers)\n }\n handlers.push(handler)\n return () => {\n const list = channels.get(channel)\n if (!list) return\n const i = list.indexOf(handler)\n if (i >= 0) list.splice(i, 1)\n }\n },\n emit(channel, ...args) {\n const handlers = channels.get(channel)\n if (!handlers || handlers.length === 0) return\n // Snapshot — allow handlers to unsubscribe without mutating the\n // iteration set.\n for (const handler of handlers.slice()) {\n try {\n handler(...args)\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error(`[withCustomEvents] handler for \"${channel}\" threw`, err)\n }\n }\n },\n off(channel, handler) {\n const handlers = channels.get(channel)\n if (!handlers) return\n for (let i = handlers.length - 1; i >= 0; i--) {\n if (handlers[i] === handler) handlers.splice(i, 1)\n }\n },\n }\n const prev = app.apply\n app.apply = (op: Op): ApplyResult => prev(op)\n return Object.assign(app, { events: store })\n}\n","/**\n * Wrap-measurer registration for `@silvery/ag-term`.\n *\n * `@silvery/ag` exposes a process-level wrap-measurer registry\n * (`setWrapMeasurer` / `getWrapMeasurer`) so geometry helpers like\n * `computeSelectionFragments` can ask the active terminal runtime for\n * grapheme-correct soft-wrap slices. Without registration, the geometry\n * layer falls back to `\\n`-only line splitting — fine for pure-framework\n * unit tests, wrong for real apps where a 60-char paragraph at width 20\n * needs to emit 3 fragments.\n *\n * **Registration shape (Option B per `hub/silvery/design/overlay-anchor-system.md`\n * § 8)**: terminal-side `wrapTextWithOffsets` adapts to the registry's\n * `wrapText(text, maxWidth) → readonly WrapSlice[]` contract. The slice\n * shape is structurally identical (`text` + `startOffset` + `endOffset`),\n * so the adapter is a passthrough — no allocation, no reformatting.\n *\n * **Lifecycle**: registration happens at module load — see the side-effect\n * call below. The adapter reads the active scoped measurer (terminal caps,\n * wide-emoji heuristics, text-sizing) at call time, so caps changes during\n * a session (e.g., post-DA1 width-detection toggling `maybeWideEmojis`)\n * are picked up automatically.\n *\n * **Test isolation**: tests that need the `\\n`-only fallback (pure-`ag`\n * pathway) call `setWrapMeasurer(null)` in `beforeEach`/`afterEach` and\n * `restoreDefaultWrapMeasurer()` in `afterAll`. Tests that exercise the\n * registered path do nothing — the import-time side effect already wired\n * it up.\n *\n * **Multi-Term**: v1 is module-level singleton — see `@silvery/ag/wrap-measurer.ts`\n * file header for the upgrade path. Single Term-per-process is the\n * production reality; multi-Term scenarios are research / future work.\n *\n * Tracking: bead `km-silvery.softwrap-selection-fragments` (closes Phase\n * 4b deferred wrap-spanning).\n */\n\nimport {\n getWrapMeasurer,\n setWrapMeasurer,\n type WrapMeasurer,\n type WrapSlice,\n} from \"@silvery/ag/wrap-measurer\"\nimport { wrapTextWithOffsets } from \"../unicode\"\n\n/**\n * The terminal-runtime adapter — a thin passthrough that converts\n * `wrapTextWithOffsets`'s return type to `readonly WrapSlice[]`. The\n * shapes are identical so this is a re-tag, but going through a typed\n * conversion lets us tighten or widen either side without breaking the\n * other.\n */\nconst terminalWrapMeasurer: WrapMeasurer = {\n wrapText(text, maxWidth): readonly WrapSlice[] {\n return wrapTextWithOffsets(text, maxWidth)\n },\n}\n\n/**\n * Install the terminal-runtime wrap measurer into `@silvery/ag`'s registry.\n *\n * Reads the live registry (`getWrapMeasurer()`) — not a local boolean — to\n * decide whether to install. This makes the function safe to call after a\n * `setWrapMeasurer(null)` teardown that bypassed `uninstallTerminalWrapMeasurer`\n * (e.g. tests that drop the registration through `@silvery/ag`'s own API).\n *\n * Idempotent: when our adapter is already the registered measurer, this\n * is a no-op. When a foreign measurer is registered (rare — only seen if\n * a test or alternate runtime registered its own), this overwrites it\n * with our adapter, matching v1's \"single Term-per-process\" assumption.\n *\n * Called automatically at module load (see side-effect below); exported\n * for tests that want to re-arm after a `setWrapMeasurer(null)` teardown.\n */\nexport function installTerminalWrapMeasurer(): void {\n if (getWrapMeasurer() === terminalWrapMeasurer) return\n setWrapMeasurer(terminalWrapMeasurer)\n}\n\n/**\n * Reset registration when our adapter is the active measurer. After\n * calling this, the `@silvery/ag` registry is empty (`getWrapMeasurer()`\n * returns null) and `computeSelectionFragments` falls back to `\\n`-only\n * splitting. Tests use this to exercise the fallback path; runtime\n * teardown uses it on Term dispose.\n *\n * No-op when a foreign measurer is registered — clearing it would surprise\n * whoever installed it. Call `setWrapMeasurer(null)` directly if the goal\n * is to clear unconditionally.\n */\nexport function uninstallTerminalWrapMeasurer(): void {\n if (getWrapMeasurer() !== terminalWrapMeasurer) return\n setWrapMeasurer(null)\n}\n\n/**\n * Test helper: restore the registry to its post-import-time default\n * state. Equivalent to `installTerminalWrapMeasurer()` but reads more\n * naturally in `afterEach` / `afterAll` blocks.\n */\nexport function restoreDefaultWrapMeasurer(): void {\n installTerminalWrapMeasurer()\n}\n\n/**\n * Read whether the terminal measurer is currently the active registration.\n * Sources truth from the live registry so a concurrent test that called\n * `setWrapMeasurer(null)` is observed correctly.\n */\nexport function isTerminalWrapMeasurerInstalled(): boolean {\n return getWrapMeasurer() === terminalWrapMeasurer\n}\n\n// =============================================================================\n// Module-load side effect\n// =============================================================================\n//\n// Importing `@silvery/ag-term` (anywhere — directly, transitively via\n// `silvery/runtime`, via `@silvery/test`'s `createRenderer`) is the signal\n// that the process wants terminal-grade wrap geometry. We register\n// immediately so the very first `computeSelectionFragments` call sees the\n// adapter — there's no race window where pre-runtime selections produce\n// stale `\\n`-only fragments and post-runtime selections produce\n// soft-wrapped ones.\n//\n// This mirrors the convention already in place for the layout engine\n// (`ensureDefaultLayoutEngine`) and `term.input` (single ownership): set\n// the right thing up the moment the module loads, let teardown opt out.\n\ninstallTerminalWrapMeasurer()\n","/**\n * ScopeContext — plumbs `Scope` down the React tree.\n *\n * Two contexts intentionally:\n *\n * - `ScopeContext` carries the *current* enclosing scope. Nested\n * `<ScopeProvider scope={...}>` elements shadow it. `useScope()` reads this.\n *\n * - `AppScopeContext` carries the *root app scope*. It is set exactly once,\n * at the app root, by `withScope()` (wired into `createApp()` in a later\n * phase) or by `<ScopeProvider scope={root} appScope={root}>` in tests.\n * `useAppScope()` reads this so whole-app shutdown paths always reach the\n * root even from deeply nested components.\n *\n * Separating them matters: nesting a `<ScopeProvider>` must not change what\n * `useAppScope()` returns — only `useScope()`.\n */\n\nimport { createContext } from \"react\"\nimport type { Scope } from \"@silvery/scope\"\n\n/** Current enclosing scope. `null` when no provider is above the component. */\nexport const ScopeContext = createContext<Scope | null>(null)\n\n/** Root app scope. Set once by `withScope()` (or in tests). */\nexport const AppScopeContext = createContext<Scope | null>(null)\n","/**\n * ScopeProvider — make a `Scope` available to descendants.\n *\n * Three modes:\n *\n * <ScopeProvider scope={s}> // set current scope only\n * <ScopeProvider appScope={root}> // set app-root scope only\n * <ScopeProvider scope={s} appScope={root}> // both\n *\n * `withScope()` will, in a later phase, emit the equivalent of the third\n * form at the app root: the same `Scope` value flows into both contexts so\n * `useScope()` and `useAppScope()` both resolve to it. Tests use the\n * explicit form to exercise nested-scope behavior.\n */\n\nimport type { ReactNode } from \"react\"\nimport type { Scope } from \"@silvery/scope\"\nimport { AppScopeContext, ScopeContext } from \"./scope-context\"\n\nexport interface ScopeProviderProps {\n /** Replace the current enclosing scope for descendants. */\n scope?: Scope\n /** Set the app-root scope (only meaningful at the app root). */\n appScope?: Scope\n children?: ReactNode\n}\n\nexport function ScopeProvider({\n scope,\n appScope,\n children,\n}: ScopeProviderProps): React.JSX.Element {\n // Chain providers conditionally so we don't shadow the parent's values\n // with `null` when a given prop wasn't passed. A missing prop means\n // \"inherit from parent\".\n let tree: React.JSX.Element = <>{children}</>\n if (scope !== undefined) {\n tree = <ScopeContext.Provider value={scope}>{tree}</ScopeContext.Provider>\n }\n if (appScope !== undefined) {\n tree = <AppScopeContext.Provider value={appScope}>{tree}</AppScopeContext.Provider>\n }\n return tree\n}\n","/**\n * CLS — Cumulative Layout Shift instrumentation for silvery.\n *\n * Analogous to Core Web Vitals CLS: a layout shift is the same block\n * rendering at a different rect in successive frames during a single\n * user-action window. Score = sum(area × distance) over all shifts.\n *\n * This module is pure: it defines the data shape + diff math. The runtime\n * ClsMonitor (ag-term/runtime/cls-monitor) calls these helpers per render\n * commit; the strict-mode check (ag-term/strict-cls) consumes the CLSReport.\n * The termless capture API (ag-term/app) drives begin/end of the capture\n * window via ClsMonitor.\n *\n * Bead: km-silvery.cls-instrumentation-primitive\n */\n\nimport { rectEqual, type Rect } from \"./types\"\n\n/**\n * Why a layout shift happened. Used to filter \"expected\" shifts (the user\n * scrolled, content arrived in a streaming view) from \"unexpected\" shifts\n * (a code fence resized mid-stream, a status line bounced) — only the\n * latter fail CLS strict checks.\n */\nexport type ReflowReason =\n | \"user-action\" // user did something — typed, scrolled, resized\n | \"unexpected\" // no triggering event — the bad kind, CLS we want to catch\n | \"animation\" // ongoing animation that's expected to move stuff\n | \"content-arrival\" // new content streamed in — expected for chat / log views\n\n/**\n * Classifier function — given a block + rect transition, returns the\n * reflow reason. The capture API passes a classifier that inspects layout\n * context (user-action vs streamed content vs unsolicited reflow). Default\n * classifier returns \"unexpected\" — most pessimistic, captures the bug\n * class CLS is designed to detect.\n */\nexport type ReasonClassifier = (\n blockId: string,\n fromRect: Rect,\n toRect: Rect,\n frameTimestamp: number,\n) => ReflowReason\n\n/**\n * Default reflow-reason classifier — every shift is \"unexpected\". Tests\n * + close-gates use this to surface every shift; apps with legitimate\n * reflow events (streaming chat, animation) pass a custom classifier.\n */\nexport const defaultClassifier: ReasonClassifier = () => \"unexpected\"\n\nexport interface LayoutShift {\n /** Stable identifier for the shifted block. */\n blockId: string\n /** Position + size in the previous frame. */\n fromRect: Rect\n /** Position + size in the current frame. */\n toRect: Rect\n /** Wall-clock timestamp at frame end (ms since epoch). */\n frameTimestamp: number\n /** Why the shift happened — see ReflowReason for taxonomy. */\n reflowReason: ReflowReason\n}\n\nexport interface CLSReport {\n /** All shifts observed in the capture window. */\n shifts: readonly LayoutShift[]\n /** Sum of (area × distance) over all shifts (CLS metric proper). */\n cumulativeScore: number\n /** Subset of shifts with reflowReason=\"unexpected\" — the actionable ones. */\n unexpectedShifts: readonly LayoutShift[]\n}\n\n/**\n * Compute the \"impact\" of a single shift: max(prev-area, curr-area) ×\n * euclidean-distance-moved between top-left corners. Returns 0 when the\n * rect is unchanged.\n *\n * Area is in cells (cols × rows); distance is in cells. Web CLS uses\n * \"impact fraction\" relative to viewport — we use raw area × distance\n * because terminal viewports are small and per-block context matters more\n * than viewport-relative weighting. Score is comparable across runs in the\n * same terminal-size + content envelope; not directly comparable across\n * different sizes.\n */\nexport function computeShiftScore(from: Rect, to: Rect): number {\n if (rectEqual(from, to)) return 0\n const dx = to.x - from.x\n const dy = to.y - from.y\n const distance = Math.sqrt(dx * dx + dy * dy)\n const fromArea = from.width * from.height\n const toArea = to.width * to.height\n return Math.max(fromArea, toArea) * distance\n}\n\n/**\n * Build a LayoutShift record from a rect transition. Returns null when the\n * rects are equal (no shift — caller can skip without branching).\n */\nexport function makeShift(\n blockId: string,\n fromRect: Rect,\n toRect: Rect,\n frameTimestamp: number,\n reflowReason: ReflowReason,\n): LayoutShift | null {\n if (rectEqual(fromRect, toRect)) return null\n return { blockId, fromRect, toRect, frameTimestamp, reflowReason }\n}\n\n/**\n * Aggregate a list of shifts into a CLSReport. Pure: callers can stash\n * the shifts array however they like (per-frame, per-capture, per-test)\n * and call this once when building the report.\n *\n * cumulativeScore sums every shift, regardless of reflowReason — the\n * intent is \"how much did the layout move?\" — not \"how much surprise?\".\n * For surprise, callers check unexpectedShifts.length or\n * aggregateUnexpectedScore().\n */\nexport function aggregateReport(shifts: readonly LayoutShift[]): CLSReport {\n let cumulativeScore = 0\n const unexpectedShifts: LayoutShift[] = []\n for (const s of shifts) {\n cumulativeScore += computeShiftScore(s.fromRect, s.toRect)\n if (s.reflowReason === \"unexpected\") unexpectedShifts.push(s)\n }\n return { shifts, cumulativeScore, unexpectedShifts }\n}\n\n/**\n * Sum of impact scores for the unexpected subset only. Strict-mode checks\n * use this; \"all shifts including content-arrival\" is too noisy for an\n * assertion threshold.\n */\nexport function aggregateUnexpectedScore(shifts: readonly LayoutShift[]): number {\n let score = 0\n for (const s of shifts) {\n if (s.reflowReason !== \"unexpected\") continue\n score += computeShiftScore(s.fromRect, s.toRect)\n }\n return score\n}\n\n/**\n * Empty report shape — useful as the initial value for accumulators and\n * the return value when capture finds nothing.\n */\nexport function emptyReport(): CLSReport {\n return { shifts: [], cumulativeScore: 0, unexpectedShifts: [] }\n}\n","/**\n * STRICT cls — assert no unexpected layout shifts in a CLS capture window.\n *\n * Bead: km-silvery.cls-instrumentation-primitive (Phase 4/7, 2026-05-13).\n *\n * The CLS instrumentation primitive (packages/ag/src/cls.ts) measures\n * layout shifts per frame, classifies each as user-action / animation /\n * content-arrival / unexpected, and aggregates into a CLSReport. Most\n * apps want shifts of the first three reasons to be allowed (they\n * correspond to legitimate reflow events). The \"unexpected\" reason is\n * the actionable subset — it's the bug class CLS is designed to catch\n * (a code fence resizing mid-stream, a status line bouncing, a card\n * flashing flush-left before snapping to its real position).\n *\n * SILVERY_STRICT=cls flips this from \"consumers decide\" to \"assert that\n * the active capture observed no unexpected shifts.\" Termless tests can\n * then ship a green close-gate via the umbrella env var rather than\n * hand-rolled assertions in every test.\n *\n * Tier 2 (paranoid). `SILVERY_STRICT=2` includes it; `SILVERY_STRICT=1`\n * does NOT — the default fast pass leaves CLS to consumer-side checks\n * so that pre-existing unfixed shift offenders don't fail every test\n * run before they're fixed.\n *\n * Why ag-term/src/ and not pipeline/strict-cls.ts: this check fires at\n * capture-end (termless `endCLSCapture()`), not per-frame in the pipeline.\n * It has no coupling to render-phase / layout-phase internals — it\n * consumes a CLSReport and decides via the SILVERY_STRICT contract. The\n * placement parallels `strict-bordered-rect.ts` (another pure assertion\n * helper that sits outside `pipeline/`).\n */\n\nimport { aggregateUnexpectedScore, type CLSReport, type LayoutShift } from \"@silvery/ag/cls\"\nimport { isStrictEnabled } from \"./strict-mode\"\n\n/** SILVERY_STRICT slug for the CLS check. Tier 2 by design. */\nexport const CLS_STRICT_SLUG = \"cls\"\nexport const CLS_STRICT_MIN_TIER = 2\n\n/** Returns true when SILVERY_STRICT=cls (or =2 / =3 / etc.) is enabled. */\nexport function isClsStrictEnabled(): boolean {\n return isStrictEnabled(CLS_STRICT_SLUG, CLS_STRICT_MIN_TIER)\n}\n\n/**\n * Thrown when SILVERY_STRICT=cls is enabled and a capture window saw at\n * least one shift with reflowReason=\"unexpected\".\n *\n * Carries the offending shifts AND the cumulative unexpected-only score\n * so failing tests can point at the worst offenders without rerunning.\n * `.message` is human-readable; `.shifts` + `.score` are for programmatic\n * inspection.\n */\nexport class UnexpectedLayoutShiftError extends Error {\n readonly shifts: readonly LayoutShift[]\n readonly score: number\n\n constructor(shifts: readonly LayoutShift[], score: number) {\n const summary = shifts\n .slice(0, 5)\n .map(\n (s) =>\n ` ${s.blockId}: (${s.fromRect.x},${s.fromRect.y} ${s.fromRect.width}×${s.fromRect.height}) → (${s.toRect.x},${s.toRect.y} ${s.toRect.width}×${s.toRect.height})`,\n )\n .join(\"\\n\")\n const tail = shifts.length > 5 ? `\\n ... and ${shifts.length - 5} more` : \"\"\n super(\n `[SILVERY_STRICT=cls] CLSReport contains ${shifts.length} unexpected layout shift(s), cumulative score ${score.toFixed(2)}:\\n${summary}${tail}`,\n )\n this.name = \"UnexpectedLayoutShiftError\"\n this.shifts = shifts\n this.score = score\n }\n}\n\n/**\n * Throws `UnexpectedLayoutShiftError` if the report contains any\n * `reflowReason=\"unexpected\"` shifts AND `SILVERY_STRICT=cls` (or a\n * stricter tier) is enabled. Otherwise a no-op.\n *\n * Callers (termless `endCLSCapture`, test harnesses, app-level\n * close-gate hooks) should invoke this immediately after producing a\n * CLSReport — the error names the offending blocks so the failure\n * message points at the bug.\n */\nexport function assertNoUnexpectedShifts(report: CLSReport): void {\n if (!isClsStrictEnabled()) return\n if (report.unexpectedShifts.length === 0) return\n const score = aggregateUnexpectedScore(report.shifts)\n throw new UnexpectedLayoutShiftError(report.unexpectedShifts, score)\n}\n","/**\n * Layout-shift (CLS) monitor — post-commit instrumentation.\n *\n * Detects two classes of layout instability:\n *\n * 1. **Layout shifts** — an AgNode's `screenRect` changes between\n * consecutive render commits with no scroll/resize cause. Logged as\n * per-commit counts under `DEBUG=silvery:cls`; quick-reflow storms (the\n * same path shifting many times in a short window) escalate to a\n * `warn` summary. Full per-node shift logs live under the narrower\n * `DEBUG=silvery:cls-detail` namespace so instrumentation does not create\n * the scroll jank it is meant to diagnose.\n *\n * 2. **Unreasonable sizes** — degenerate or oversized rects detected\n * during the same walk: `width=0` / `height=0` with visible content\n * underneath, negative coords, rect exceeding the terminal bounds.\n * Logged at `warn` regardless of `DEBUG`.\n *\n * Inspired by Web Vitals' Cumulative Layout Shift. The DOM equivalent\n * fires on visible content moving; in terminals the same shape produces\n * the \"first centered, then shifts left\" symptom the user reported\n * 2026-05-11 12:42 + 13:05.\n *\n * Bead: @km/silvery/layout-shift-instrumentation-cls.\n *\n * ## Activation\n *\n * The walk runs every commit but does nothing when no logger method is\n * armed. Activation:\n *\n * - `DEBUG=silvery:cls` — summary instrumentation, per-commit shift\n * counts + storm `warn` logs + size-violation `warn` logs.\n * - `DEBUG=silvery:cls-detail` — full per-node shift `debug` logs.\n * - `LOG_LEVEL=warn` only — storm + size-violation `warn` logs (no\n * per-shift spam). Useful in CI / smoke tests.\n *\n * In both modes, the per-commit walk is O(visible-tree-nodes); the\n * dominant cost is the tree traversal, not the comparison. When\n * disabled, only the `if (!log.debug && !log.warn) return` guard runs.\n */\n\nimport { createLogger } from \"loggily\"\nimport {\n aggregateReport,\n defaultClassifier,\n makeShift,\n type CLSReport,\n type LayoutShift,\n type ReasonClassifier,\n} from \"@silvery/ag/cls\"\nimport type { AgNode, Rect } from \"@silvery/ag/types\"\nimport { assertNoUnexpectedShifts } from \"../strict-cls\"\n\nconst log = createLogger(\"silvery:cls\")\nconst shiftLog = createLogger(\"silvery:cls-detail\")\n\n/**\n * Opt-in gate. CLS instrumentation runs ONLY when one of these\n * env vars is set:\n *\n * - `DEBUG=silvery:cls` / `DEBUG=silvery:cls-detail` (or `DEBUG=silvery:*` / `DEBUG=*`)\n * - `SILVERY_INSTRUMENT=cls`\n *\n * Without the gate, even `warn`-level output stays silent — the\n * instrumentation produces too much noise in synthetic test\n * harnesses (createRenderer fixtures often have transient\n * zero-rect frames during initial measurement) to be safely\n * always-on. Phase 2 of the bead will tier specific checks back\n * to always-on once their false-positive rate is characterised.\n *\n * Bead: @km/silvery/layout-shift-instrumentation-cls.\n */\nfunction clsEnabled(): boolean {\n if (process.env.SILVERY_INSTRUMENT === \"cls\") return true\n const debug = process.env.DEBUG ?? \"\"\n if (!debug) return false\n return /(^|,)\\s*(silvery:cls|silvery:cls-detail|silvery:\\*|\\*)\\s*(,|$)/.test(debug)\n}\n\nconst STORM_PATH_WINDOW_MS = 500\nconst STORM_PATH_THRESHOLD = 3\nconst STORM_COMMIT_THRESHOLD = 5\n\n/** Data-* attributes treated as identity tokens for CLS path resolution.\n * Any component carrying one of these in its props is named in the\n * layout-shift log even without an explicit `id` / `data-testid`. The\n * list is intentionally broad so anonymous app-level boxes (cards,\n * chat blocks, code containers, table grids) show up by role instead\n * of collapsing to a generic \"box\" entry. */\nconst IDENTITY_DATA_KEYS = [\n \"data-view\",\n \"data-block\",\n \"data-component\",\n \"data-card-id\",\n \"data-pane\",\n \"data-role\",\n] as const\n\ninterface ShiftRecord {\n path: string\n prev: Rect | null\n next: Rect | null\n /** Milliseconds since the previous shift for this path; null for the\n * first observed shift. Included in `silvery:cls` debug logs so quick\n * reflows can be diagnosed without reconstructing timing offline. */\n dtMs: number | null\n}\n\ninterface PathHistory {\n /** Wall-clock timestamps of recent shifts for this path. Trimmed to the\n * most recent `STORM_PATH_WINDOW_MS` on every probe. */\n ts: number[]\n /** Set of (kind, key) sentinels already fired — once-per-pair to avoid\n * spamming. Reset when the path settles (no shift for > 2s). */\n firedKinds: Set<string>\n /** Most recent shift timestamp for this path, used for per-shift dt logs. */\n lastShiftAt: number | null\n /** True while the current timestamp window is already above the storm threshold. */\n inStorm: boolean\n}\n\nexport interface ClsMonitor {\n /**\n * Run the post-commit walk. Call once per `paintFrame` boundary in\n * the runtime, after layout has settled.\n *\n * @param root The reconciler container's root AgNode.\n * @param cols Terminal columns (for overflow checks).\n * @param rows Terminal rows (for overflow checks).\n * @param scrollOrResize When true, suppresses shift logs from this\n * commit — scroll/resize-driven motion isn't a\n * layout-stability bug. Size sentinels still\n * run.\n */\n onCommit(root: AgNode | null, cols: number, rows: number, scrollOrResize: boolean): void\n\n /** Drop accumulated path history. Tests and dimension changes use\n * this to start with a clean storm-detector state. */\n reset(): void\n\n /**\n * Start a CLS capture window. Subsequent `onCommit` calls accumulate\n * layout shifts (post-scroll `screenRect` transitions) into a session\n * buffer. The classifier labels each shift with a ReflowReason; default\n * classifier marks every shift \"unexpected\" (most pessimistic — surfaces\n * every shift). Throws on double-begin. While capturing, the monitor's\n * walk runs even without `DEBUG=silvery:cls` / `SILVERY_INSTRUMENT=cls`.\n *\n * Bead: @km/silvery/cls-instrumentation-primitive (Phase 8 — Option C consolidation).\n */\n beginCapture(classifier?: ReasonClassifier): void\n\n /**\n * End the active capture window, return aggregated `CLSReport`, reset\n * session state. Throws when not capturing. Under `SILVERY_STRICT=cls`\n * (tier 2 by default), throws `UnexpectedLayoutShiftError` when any\n * shift in the window was labeled \"unexpected\".\n */\n endCapture(): CLSReport\n\n /**\n * Cancel the active capture without producing a report. Idempotent —\n * safe to call when no capture is active (cleanup paths, error\n * recovery).\n */\n cancelCapture(): void\n}\n\nexport function createClsMonitor(): ClsMonitor {\n const envEnabled = clsEnabled()\n const pathHistory = new Map<string, PathHistory>()\n let prevCols = 0\n let prevRows = 0\n\n // Capture-window state. Independent of the env-gate so test-time\n // capture works without DEBUG=silvery:cls. When `capturing === true`\n // the walk runs even when `envEnabled === false`, and per-commit\n // shifts are pushed into `sessionShifts` for `endCapture()` to\n // aggregate into a `CLSReport`.\n let capturing = false\n let activeClassifier: ReasonClassifier = defaultClassifier\n let sessionShifts: LayoutShift[] = []\n\n function ensureHistory(path: string): PathHistory {\n let h = pathHistory.get(path)\n if (h === undefined) {\n h = { ts: [], firedKinds: new Set(), lastShiftAt: null, inStorm: false }\n pathHistory.set(path, h)\n }\n return h\n }\n\n function rectEqual(a: Rect | null, b: Rect | null): boolean {\n if (a === b) return true\n if (a === null || b === null) return false\n return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height\n }\n\n function nodePath(node: AgNode): string {\n // CSS-selector-style identity chain. Walk ancestors, keep ONLY\n // nodes carrying a recognized identity prop (rendered as `#name`\n // or `[data-*=value]`); the unidentified intermediate\n // `silvery-box` chains drop out. The leaf always renders even\n // if untagged (as its bare type minus the `silvery-` prefix).\n // Joins with ` > ` like a CSS descendant selector. Bead:\n // @km/silvery/layout-shift-instrumentation-cls user feedback\n // 2026-05-11: paths were \"silvery-root/silvery-box/silvery-box\n // #main/...\" which is the full-chain — switch to \"#main >\n // #bottom-bar > ... > text#watcher-status\".\n //\n // Identity hierarchy (first non-empty wins):\n // 1. `id` → `#name`\n // 2. `data-testid` → `#name` (test identity)\n // 3. `data-view` → `[data-view=name]` (km-tui board roles)\n // 4. `data-block` / `-component` / `-card-id` → `[data-...=name]`\n //\n // When none present, the node is treated as a transparent\n // wrapper and skipped (unless it's the leaf).\n const parts: string[] = []\n let n: AgNode | null = node\n let depth = 0\n let isLeaf = true\n while (n && depth < 20) {\n const tag = identityTag(n)\n if (tag !== null) {\n const typeTag = n.type === \"silvery-box\" ? \"\" : `${typeShort(n.type)}`\n parts.push(`${typeTag}${tag}`)\n } else if (isLeaf) {\n // Leaf without id — emit the bare type so the log still\n // names what shifted.\n parts.push(typeShort(n.type))\n }\n // Else: untagged intermediate — skip.\n isLeaf = false\n n = n.parent\n depth++\n }\n return parts.reverse().join(\" > \")\n }\n\n /** Resolve a node's identity tag for CLS paths. Returns null when no\n * recognised identity prop is set — the node is then treated as a\n * transparent wrapper. */\n function identityTag(node: AgNode): string | null {\n const props = node.props as Record<string, unknown>\n const id = props.id\n if (typeof id === \"string\" && id.length > 0) return `#${id}`\n const testid = props[\"data-testid\"]\n if (typeof testid === \"string\" && testid.length > 0) return `#${testid}`\n // Common data-* identity props from km/silvercode/silvery apps. Anything\n // that names the component's role qualifies — we treat them all as\n // equivalent identity sources so anonymous boxes wearing a `data-block`\n // (or similar) get a real path token. The selector form ([data-x=y])\n // matches CSS for the read-back-to-code experience.\n for (const key of IDENTITY_DATA_KEYS) {\n const v = props[key]\n if (typeof v === \"string\" && v.length > 0) return `[${key}=${v}]`\n }\n return null\n }\n\n function typeShort(type: string): string {\n // Drop the `silvery-` prefix when present: `silvery-text` → `text`,\n // `silvery-box` → `box`, `silvery-root` → `root`.\n return type.startsWith(\"silvery-\") ? type.slice(\"silvery-\".length) : type\n }\n\n function recordShift(rec: ShiftRecord, now: number): boolean {\n const h = ensureHistory(rec.path)\n rec.dtMs = h.lastShiftAt === null ? null : now - h.lastShiftAt\n h.lastShiftAt = now\n h.ts.push(now)\n // Trim to window.\n while (h.ts.length > 0 && h.ts[0]! < now - STORM_PATH_WINDOW_MS) h.ts.shift()\n const inStorm = h.ts.length >= STORM_PATH_THRESHOLD\n const justEnteredStorm = inStorm && !h.inStorm\n h.inStorm = inStorm\n return justEnteredStorm\n }\n\n function fireOnce(path: string, key: string): boolean {\n const h = ensureHistory(path)\n if (h.firedKinds.has(key)) return false\n h.firedKinds.add(key)\n return true\n }\n\n function checkSizeInvariants(node: AgNode, cols: number, rows: number): void {\n const rect = node.screenRect\n if (!rect) return\n // silvery-text nodes inherit layout from their parent silvery-box.\n // Virtual inline text children (Text-in-Text) legitimately have\n // {0,0,0,0} rects — they don't own layout. Skip text nodes; their\n // owning container will fire if it's actually degenerate.\n if (node.type === \"silvery-text\") return\n const path = nodePath(node)\n if (rect.x < 0 || rect.y < 0 || rect.width < 0 || rect.height < 0) {\n if (fireOnce(path, \"negative\")) {\n log.warn?.(\"negative-rect\", { path, rect })\n }\n }\n if (rect.width > cols || rect.height > rows) {\n if (fireOnce(path, \"overflow\")) {\n log.warn?.(\"rect-overflows-terminal\", { path, rect, cols, rows })\n }\n }\n // Zero-size with visible content: only flag when the node clearly\n // intends to render (has children with text, or has an explicit bg).\n if ((rect.width === 0 || rect.height === 0) && hasVisibleContent(node)) {\n if (fireOnce(path, \"zero-area-with-content\")) {\n log.warn?.(\"zero-area-with-content\", { path, rect })\n }\n }\n }\n\n function hasVisibleContent(node: AgNode): boolean {\n if (node.type === \"silvery-text\") return true\n const props = node.props as { backgroundColor?: unknown; borderStyle?: unknown }\n if (props.backgroundColor !== undefined || props.borderStyle !== undefined) return true\n for (const child of node.children) {\n if (hasVisibleContent(child)) return true\n }\n return false\n }\n\n function walk(\n node: AgNode,\n cols: number,\n rows: number,\n suppressShift: boolean,\n commitShifts: ShiftRecord[],\n ): void {\n if (!node.screenRect) {\n for (const child of node.children) walk(child, cols, rows, suppressShift, commitShifts)\n return\n }\n // Size-invariant WARN logging is a production-diagnostic side effect\n // gated by env (DEBUG=silvery:cls / SILVERY_INSTRUMENT=cls). Test-time\n // capture must walk silently — surfacing `silvery:cls` WARNs during a\n // capture window would fail consumers that treat any console output\n // as a test error (e.g. silvercode's vitest setup). Bead:\n // @km/silvery/cls-instrumentation-primitive — first-consumer broke\n // post-Option-C until this gate landed.\n if (envEnabled) checkSizeInvariants(node, cols, rows)\n if (!suppressShift) {\n const prev = node.prevScreenRect\n const next = node.screenRect\n if (prev !== null && !rectEqual(prev, next)) {\n // First-time-measurement transitions don't count as shifts —\n // those are the deferred-only useBoxRect contract working as\n // designed (0,0 seed → real rect on next commit).\n const isFirstMeasure =\n prev.width === 0 && prev.height === 0 && next.width > 0 && next.height > 0\n // Virtual inline text children always have {0,0,0,0} or\n // size-zero rects (they don't own layout — they inherit\n // position from their parent silvery-box). Their \"shifts\"\n // are just propagation noise from the real parent shift.\n const isInlineTextNoise =\n node.type === \"silvery-text\" &&\n (prev.width === 0 || prev.height === 0) &&\n (next.width === 0 || next.height === 0)\n if (!isFirstMeasure && !isInlineTextNoise) {\n commitShifts.push({ path: nodePath(node), prev, next, dtMs: null })\n }\n }\n }\n for (const child of node.children) walk(child, cols, rows, suppressShift, commitShifts)\n }\n\n function onCommit(\n root: AgNode | null,\n cols: number,\n rows: number,\n scrollOrResize: boolean,\n ): void {\n // Dimension tracking is metadata, not gated. Tracking prevCols/rows on\n // every commit (regardless of envEnabled/capturing) means a capture\n // started mid-session can use the established baseline instead of\n // treating its first commit as a first-paint resize. Two field\n // assignments per commit when nobody's listening — negligible.\n const resized = cols !== prevCols || rows !== prevRows\n const suppressShift = scrollOrResize || resized || prevCols === 0 || prevRows === 0\n prevCols = cols\n prevRows = rows\n\n // Cheap gate: opt-in via DEBUG=silvery:cls or SILVERY_INSTRUMENT=cls,\n // OR an active test-time capture window. Outside both, this is a\n // single boolean compare per commit — zero overhead.\n if (!envEnabled && !capturing) return\n if (!root) return\n\n const commitShifts: ShiftRecord[] = []\n walk(root, cols, rows, suppressShift, commitShifts)\n\n // Capture path: stash shifts into the active session for endCapture()\n // to aggregate. Skipped when suppressShift is true — scroll/resize\n // motion is user-action, not an unexpected layout shift. Classifier\n // still sees every non-suppressed shift and may override the default\n // \"unexpected\" label per-shift (e.g. label streamed chat reflow as\n // content-arrival).\n if (capturing && !suppressShift && commitShifts.length > 0) {\n const ts = Date.now()\n for (const rec of commitShifts) {\n if (rec.prev === null || rec.next === null) continue\n const reason = activeClassifier(rec.path, rec.prev, rec.next, ts)\n const shift = makeShift(rec.path, rec.prev, rec.next, ts, reason)\n if (shift !== null) sessionShifts.push(shift)\n }\n }\n\n // Production path: storm-detection + silvery:cls logging. Only runs\n // when env-gated — test-time captures don't pollute the storm history\n // (independent buffers, independent thresholds).\n if (!envEnabled) return\n if (commitShifts.length === 0) return\n\n const now = performance.now()\n let stormPaths = 0\n for (const rec of commitShifts) {\n const stormForPath = recordShift(rec, now)\n if (stormForPath) stormPaths++\n shiftLog.debug?.(\"shift\", { path: rec.path, prev: rec.prev, next: rec.next, dtMs: rec.dtMs })\n }\n log.debug?.(\"shift-count\", {\n shifts: commitShifts.length,\n firstPaths: commitShifts.slice(0, 5).map((r) => r.path),\n })\n if (stormPaths > 0) {\n log.warn?.(\"reflow-storm-per-path\", {\n stormPaths,\n windowMs: STORM_PATH_WINDOW_MS,\n threshold: STORM_PATH_THRESHOLD,\n })\n }\n if (commitShifts.length >= STORM_COMMIT_THRESHOLD) {\n log.warn?.(\"reflow-storm-per-commit\", {\n shifts: commitShifts.length,\n threshold: STORM_COMMIT_THRESHOLD,\n firstPaths: commitShifts.slice(0, 5).map((r) => r.path),\n })\n }\n }\n\n function reset(): void {\n pathHistory.clear()\n prevCols = 0\n prevRows = 0\n }\n\n function beginCapture(classifier?: ReasonClassifier): void {\n if (capturing) {\n throw new Error(\n \"ClsMonitor.beginCapture: already capturing. Call endCapture() or cancelCapture() before starting a new window.\",\n )\n }\n capturing = true\n activeClassifier = classifier ?? defaultClassifier\n sessionShifts = []\n }\n\n function endCapture(): CLSReport {\n if (!capturing) {\n throw new Error(\"ClsMonitor.endCapture: not capturing. Call beginCapture() first.\")\n }\n const report = aggregateReport(sessionShifts)\n capturing = false\n activeClassifier = defaultClassifier\n sessionShifts = []\n // Honors SILVERY_STRICT=cls — throws UnexpectedLayoutShiftError when\n // tier 2 strict is active and the window saw any \"unexpected\" shifts.\n // No-op when strict isn't engaged.\n assertNoUnexpectedShifts(report)\n return report\n }\n\n function cancelCapture(): void {\n capturing = false\n activeClassifier = defaultClassifier\n sessionShifts = []\n }\n\n return { onCommit, reset, beginCapture, endCapture, cancelCapture }\n}\n","/**\n * chain-bridge — utilities for installing an apply-chain on a non-root\n * runtime (render.tsx's Silvery instance and InputBoundary's isolated\n * scope).\n *\n * createApp()'s `create-app.tsx` owns the canonical chain plumbing. The\n * helpers here let callers that don't go through createApp still expose\n * a {@link ChainAppContextValue} to their children so the ag-react\n * hooks (useInput / useModifierKeys / useTerminalFocused / usePaste*)\n * can subscribe through a single unified surface.\n *\n * The child BaseApp installs the same four plugins as create-app.tsx:\n *\n * pipe(createBaseApp(),\n * withTerminalChain,\n * withPasteChain,\n * withInputChain,\n * withFocusChain)\n *\n * withFocusChain is wired with a no-op dispatcher — the isolated scope\n * doesn't own a focus manager, so focused dispatch simply returns false\n * and keys flow straight to the input store.\n */\n\nimport type { Key } from \"@silvery/ag/keys\"\nimport {\n createBaseApp,\n withCustomEvents,\n withFocusChain,\n withInputChain,\n withPasteChain,\n withTerminalChain,\n type BaseApp,\n type CustomEventStore,\n type InputStore,\n type PasteStore,\n type TerminalStore,\n} from \"@silvery/create/plugins\"\nimport type {\n ChainAppContextValue,\n ChainFocusEvents,\n ChainFocusHandler,\n ChainRawKeyHandler,\n ChainRawKeyObserver,\n} from \"./context\"\n\n/**\n * BaseApp enhanced with the four chain plugins plus the raw-key observer\n * and focus-event slices that complete the {@link ChainAppContextValue}\n * surface.\n */\nexport interface ChildApp extends BaseApp {\n readonly input: InputStore\n readonly paste: PasteStore\n readonly terminal: TerminalStore\n readonly events: CustomEventStore\n readonly rawKeys: ChainRawKeyObserver & {\n /** Fire all registered raw-key observers. */\n notify(input: string, key: Key): void\n }\n readonly focusEvents: ChainFocusEvents & {\n /** Fire all registered focus-event observers. */\n notify(focused: boolean): void\n }\n}\n\n/**\n * Build a non-root BaseApp with the canonical plugin chain.\n *\n * The returned app is self-contained: ops dispatched through it don't\n * reach the root runtime. Callers own the lifecycle — there's no exit /\n * render effect handler wired up; effects are drained and discarded.\n */\nexport function createChildApp(): ChildApp {\n const base = createBaseApp()\n const terminal = withTerminalChain()(base)\n const paste = withPasteChain()(terminal)\n const input = withInputChain(paste)\n const focus = withFocusChain({\n dispatchKey: () => false,\n hasActiveFocus: () => false,\n })(input)\n const events = withCustomEvents(focus)\n\n // Raw-key observer slice — unfiltered access to every dispatched key\n // (used by useModifierKeys which needs release + modifier-only events).\n const rawKeyListeners: Array<(input: string, key: Key) => void> = []\n const rawKeys = {\n register(handler: ChainRawKeyHandler): () => void {\n rawKeyListeners.push(handler as (input: string, key: Key) => void)\n return () => {\n const i = rawKeyListeners.indexOf(handler as (input: string, key: Key) => void)\n if (i >= 0) rawKeyListeners.splice(i, 1)\n }\n },\n notify(input: string, key: Key): void {\n for (const h of rawKeyListeners) h(input, key)\n },\n }\n\n // Focus-events slice — isolated scopes typically don't emit focus\n // events, but useTerminalFocused still expects a register() that\n // returns a clean unsubscribe.\n const focusListeners: Array<ChainFocusHandler> = []\n const focusEvents = {\n register(handler: ChainFocusHandler): () => void {\n focusListeners.push(handler)\n return () => {\n const i = focusListeners.indexOf(handler)\n if (i >= 0) focusListeners.splice(i, 1)\n }\n },\n notify(focused: boolean): void {\n for (const h of focusListeners) h(focused)\n },\n }\n\n return Object.assign(events, { rawKeys, focusEvents }) as ChildApp\n}\n\n/**\n * Project a {@link ChildApp} to the surface exposed on\n * {@link ChainAppContextValue}.\n */\nexport function toChainAppContextValue(app: ChildApp): ChainAppContextValue {\n return {\n input: app.input,\n paste: app.paste,\n focusEvents: app.focusEvents,\n rawKeys: app.rawKeys,\n events: app.events,\n }\n}\n"],"mappings":"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"}
|