silvery 0.21.1 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/dist/{Text-BRf-59j2.mjs → Text-Ci6UCSTL.mjs} +46 -46
  2. package/dist/Text-Ci6UCSTL.mjs.map +1 -0
  3. package/dist/{ag-BeFC4S2N.mjs → ag-q9lRlKlt.mjs} +42 -19
  4. package/dist/ag-q9lRlKlt.mjs.map +1 -0
  5. package/dist/{animation-N8MFybTk.mjs → animation-CZ4x2R7C.mjs} +1 -1
  6. package/dist/{animation-N8MFybTk.mjs.map → animation-CZ4x2R7C.mjs.map} +1 -1
  7. package/dist/{ansi-C6Qs1Wn2.mjs → ansi-CLmIMnop.mjs} +1 -1
  8. package/dist/{ansi-C6Qs1Wn2.mjs.map → ansi-CLmIMnop.mjs.map} +1 -1
  9. package/dist/{ansi-CBkam1ty.d.mts → ansi-zmNzgkPB.d.mts} +1 -1
  10. package/dist/{ansi-CBkam1ty.d.mts.map → ansi-zmNzgkPB.d.mts.map} +1 -1
  11. package/dist/backend-DSiSIc-h.mjs +13396 -0
  12. package/dist/backend-DSiSIc-h.mjs.map +1 -0
  13. package/dist/chain-bridge-CkYcMxlq.mjs +4149 -0
  14. package/dist/chain-bridge-CkYcMxlq.mjs.map +1 -0
  15. package/dist/{chunk-hT5z_Zn9.mjs → chunk-BEJ448es.mjs} +1 -1
  16. package/dist/cli-VNqUKlXw.mjs +4 -0
  17. package/dist/{context-BU5LkkIy.mjs → context-CEhuD1R_.mjs} +1 -1
  18. package/dist/{context-BU5LkkIy.mjs.map → context-CEhuD1R_.mjs.map} +1 -1
  19. package/dist/devtools-BF4IMumr.mjs +2 -0
  20. package/dist/{devtools-9zfhpFyG.mjs → devtools-td2AjFwc.mjs} +5 -4
  21. package/dist/{devtools-9zfhpFyG.mjs.map → devtools-td2AjFwc.mjs.map} +1 -1
  22. package/dist/{easing-B0oZKDki.d.mts → easing-C5MrdO7V.d.mts} +1 -1
  23. package/dist/{easing-B0oZKDki.d.mts.map → easing-C5MrdO7V.d.mts.map} +1 -1
  24. package/dist/{eta-DGOuC8yU.mjs → eta-BwYWEjfS.mjs} +1 -5
  25. package/dist/{eta-DGOuC8yU.mjs.map → eta-BwYWEjfS.mjs.map} +1 -1
  26. package/dist/{flexily-zero-adapter-D6hcFgrH.mjs → flexily-zero-adapter-DktX3HsX.mjs} +1 -1
  27. package/dist/{flexily-zero-adapter-CEJOcbNp.mjs → flexily-zero-adapter-VoBGHG14.mjs} +1 -1
  28. package/dist/{flexily-zero-adapter-CEJOcbNp.mjs.map → flexily-zero-adapter-VoBGHG14.mjs.map} +1 -1
  29. package/dist/hit-registry-D_nxOivS.d.mts +71 -0
  30. package/dist/hit-registry-D_nxOivS.d.mts.map +1 -0
  31. package/dist/{image-1nRyKa60.mjs → image-CIF75xMr.mjs} +24 -24
  32. package/dist/image-CIF75xMr.mjs.map +1 -0
  33. package/dist/{index-2E1jYgak.d.mts → index-377zdPLP.d.mts} +35 -12
  34. package/dist/index-377zdPLP.d.mts.map +1 -0
  35. package/dist/{index-DnuadDNL.d.mts → index-BcSr0RAQ.d.mts} +8590 -11173
  36. package/dist/index-BcSr0RAQ.d.mts.map +1 -0
  37. package/dist/{index-Dg1YaeJb.d.mts → index-DLnpxDAm.d.mts} +2 -2
  38. package/dist/{index-Dg1YaeJb.d.mts.map → index-DLnpxDAm.d.mts.map} +1 -1
  39. package/dist/{index-Bi4Jdz5g.d.mts → index-DlNmckTq.d.mts} +2 -2
  40. package/dist/{index-Bi4Jdz5g.d.mts.map → index-DlNmckTq.d.mts.map} +1 -1
  41. package/dist/index.d.mts +12 -8
  42. package/dist/index.d.mts.map +1 -1
  43. package/dist/index.mjs +18 -17
  44. package/dist/index.mjs.map +1 -1
  45. package/dist/{layout-engine-Ca_nbtfL.mjs → layout-engine-BgR58vL0.mjs} +3 -3
  46. package/dist/{layout-engine-Ca_nbtfL.mjs.map → layout-engine-BgR58vL0.mjs.map} +1 -1
  47. package/dist/{layout-engine-CgsoBRIn.mjs → layout-engine-DNjTha8f.mjs} +1 -1
  48. package/dist/{layout-signals-Dch2EiCy.mjs → layout-signals-CGYbHmxy.mjs} +2 -2
  49. package/dist/layout-signals-CGYbHmxy.mjs.map +1 -0
  50. package/dist/{bound-term-BumfuXXW.d.mts → mouse-events-B2z5tbsL.d.mts} +540 -401
  51. package/dist/mouse-events-B2z5tbsL.d.mts.map +1 -0
  52. package/dist/{mouse-events-hnbJZRwK.mjs → mouse-events-WQ2h-uqG.mjs} +14 -6
  53. package/dist/mouse-events-WQ2h-uqG.mjs.map +1 -0
  54. package/dist/{multi-progress-Bg4ngK80.d.mts → multi-progress-BaEFfHKP.d.mts} +2 -2
  55. package/dist/{multi-progress-Bg4ngK80.d.mts.map → multi-progress-BaEFfHKP.d.mts.map} +1 -1
  56. package/dist/{multi-progress-CaXTuL9G.mjs → multi-progress-zCWHEm9P.mjs} +3 -3
  57. package/dist/{multi-progress-CaXTuL9G.mjs.map → multi-progress-zCWHEm9P.mjs.map} +1 -1
  58. package/dist/{node-BiFu8I9Y.mjs → node-CJ3J_rW-.mjs} +4 -4
  59. package/dist/node-CJ3J_rW-.mjs.map +1 -0
  60. package/dist/{progress-B_UPy6zk.mjs → progress-6vJ7nxUg.mjs} +4 -4
  61. package/dist/{progress-B_UPy6zk.mjs.map → progress-6vJ7nxUg.mjs.map} +1 -1
  62. package/dist/{progress-bar-DmIMPdL0.mjs → progress-bar-BlFNsZL8.mjs} +4 -4
  63. package/dist/{progress-bar-DmIMPdL0.mjs.map → progress-bar-BlFNsZL8.mjs.map} +1 -1
  64. package/dist/{reconciler-NBDSEm8k.mjs → reconciler--_-biN23.mjs} +111 -150
  65. package/dist/reconciler--_-biN23.mjs.map +1 -0
  66. package/dist/{render-string-BntLj7Xq.mjs → render-string-DaghHpLN.mjs} +1 -1
  67. package/dist/{render-string-B4h4SmK7.mjs → render-string-DtHZP0cU.mjs} +15 -11
  68. package/dist/render-string-DtHZP0cU.mjs.map +1 -0
  69. package/dist/runtime.d.mts +3 -2
  70. package/dist/runtime.mjs +3 -2
  71. package/dist/{schemes-DYt2ushj.mjs → schemes-DWQPxXAi.mjs} +2 -2
  72. package/dist/{schemes-DYt2ushj.mjs.map → schemes-DWQPxXAi.mjs.map} +1 -1
  73. package/dist/{spinner-Py8_-hn9.mjs → spinner-B50hILqx.mjs} +3 -3
  74. package/dist/{spinner-Py8_-hn9.mjs.map → spinner-B50hILqx.mjs.map} +1 -1
  75. package/dist/{spinner-CLgzJ_QF.d.mts → spinner-DN10UfF0.d.mts} +2 -2
  76. package/dist/{spinner-CLgzJ_QF.d.mts.map → spinner-DN10UfF0.d.mts.map} +1 -1
  77. package/dist/{src-Oe6x5PrS.mjs → src-B8lf1kiR.mjs} +190 -83
  78. package/dist/src-B8lf1kiR.mjs.map +1 -0
  79. package/dist/{src-Dvq-s8iD.mjs → src-BOLdi3WG.mjs} +3 -3
  80. package/dist/{src-Dvq-s8iD.mjs.map → src-BOLdi3WG.mjs.map} +1 -1
  81. package/dist/{src-B9S_woYc.mjs → src-BXeTHHlL.mjs} +20289 -23262
  82. package/dist/src-BXeTHHlL.mjs.map +1 -0
  83. package/dist/{src-WeA_J4BV.mjs → src-BqeBFB3X.mjs} +90 -47
  84. package/dist/src-BqeBFB3X.mjs.map +1 -0
  85. package/dist/{src-CbhWmUnF.mjs → src-DbqcSrhy.mjs} +1400 -1427
  86. package/dist/src-DbqcSrhy.mjs.map +1 -0
  87. package/dist/{steps-DYrzCUCK.d.mts → steps-B2kBmFpb.d.mts} +1 -1
  88. package/dist/{steps-DYrzCUCK.d.mts.map → steps-B2kBmFpb.d.mts.map} +1 -1
  89. package/dist/{svg-DhxQkz-O.mjs → svg-NmV9dgB9.mjs} +1 -1
  90. package/dist/{svg-DhxQkz-O.mjs.map → svg-NmV9dgB9.mjs.map} +1 -1
  91. package/dist/{svg-Hk7lIl4F.d.mts → svg-VUr78l5d.d.mts} +1 -1
  92. package/dist/{svg-Hk7lIl4F.d.mts.map → svg-VUr78l5d.d.mts.map} +1 -1
  93. package/dist/term.d.mts +2 -2
  94. package/dist/term.mjs +3 -3
  95. package/dist/test.d.mts +673 -0
  96. package/dist/test.d.mts.map +1 -0
  97. package/dist/test.mjs +2829 -0
  98. package/dist/test.mjs.map +1 -0
  99. package/dist/theme.d.mts +2 -2
  100. package/dist/theme.d.mts.map +1 -1
  101. package/dist/theme.mjs +3 -3
  102. package/dist/{types-Bx-XZNbE.d.mts → types-D_E0FbqA.d.mts} +1 -1
  103. package/dist/{types-Bx-XZNbE.d.mts.map → types-D_E0FbqA.d.mts.map} +1 -1
  104. package/dist/ui/animation.d.mts +2 -2
  105. package/dist/ui/animation.mjs +1 -1
  106. package/dist/ui/ansi.d.mts +1 -1
  107. package/dist/ui/ansi.mjs +1 -1
  108. package/dist/ui/cli.d.mts +3 -3
  109. package/dist/ui/cli.mjs +5 -5
  110. package/dist/ui/image.d.mts +1 -1
  111. package/dist/ui/image.mjs +1 -1
  112. package/dist/ui/progress.d.mts +4 -4
  113. package/dist/ui/progress.mjs +4 -4
  114. package/dist/ui/react.d.mts +1 -1
  115. package/dist/ui/react.mjs +2 -2
  116. package/dist/ui/react.mjs.map +1 -1
  117. package/dist/ui/recording-chrome-react.d.mts +1 -1
  118. package/dist/ui/recording-chrome-react.mjs +2 -2
  119. package/dist/ui/recording-chrome.d.mts +1 -1
  120. package/dist/ui/recording-chrome.mjs +1 -1
  121. package/dist/ui/utils.mjs +1 -1
  122. package/dist/ui/wrappers.d.mts +2 -2
  123. package/dist/ui/wrappers.mjs +1 -1
  124. package/dist/ui.d.mts +6 -6
  125. package/dist/ui.mjs +7 -7
  126. package/dist/{ansi-OYRLxxZQ.mjs → unicode-CxyxVGmB.mjs} +7317 -6909
  127. package/dist/unicode-CxyxVGmB.mjs.map +1 -0
  128. package/dist/{useLatest-DC8i7guK.d.mts → useLatest-UOvWy0ZW.d.mts} +2 -2
  129. package/dist/{useLatest-DC8i7guK.d.mts.map → useLatest-UOvWy0ZW.d.mts.map} +1 -1
  130. package/dist/{useLayout-BKsQl2Or.mjs → useLayout-D5cE8OCI.mjs} +4 -4
  131. package/dist/{useLayout-BKsQl2Or.mjs.map → useLayout-D5cE8OCI.mjs.map} +1 -1
  132. package/dist/{with-text-input-DG4f7JII.d.mts → with-text-input-DLoSCBrm.d.mts} +3 -3
  133. package/dist/{with-text-input-DG4f7JII.d.mts.map → with-text-input-DLoSCBrm.d.mts.map} +1 -1
  134. package/dist/wrap-measurer-registration-IV2HtcCd.d.mts +3106 -0
  135. package/dist/wrap-measurer-registration-IV2HtcCd.d.mts.map +1 -0
  136. package/dist/{wrapper-D7gNSsgf.mjs → wrapper-CD29afOs.mjs} +17 -37
  137. package/dist/wrapper-CD29afOs.mjs.map +1 -0
  138. package/dist/{wrappers-CypAzrMO.mjs → wrappers-CInBJm44.mjs} +6 -6
  139. package/dist/{wrappers-CypAzrMO.mjs.map → wrappers-CInBJm44.mjs.map} +1 -1
  140. package/dist/{yoga-adapter-SsEIqMc1.mjs → yoga-adapter-9uab5Qn-.mjs} +2 -2
  141. package/dist/{yoga-adapter-SsEIqMc1.mjs.map → yoga-adapter-9uab5Qn-.mjs.map} +1 -1
  142. package/dist/yoga-adapter-yqTl1aXK.mjs +2 -0
  143. package/package.json +20 -15
  144. package/dist/Text-BRf-59j2.mjs.map +0 -1
  145. package/dist/ag-BeFC4S2N.mjs.map +0 -1
  146. package/dist/ansi-OYRLxxZQ.mjs.map +0 -1
  147. package/dist/bound-term-BumfuXXW.d.mts.map +0 -1
  148. package/dist/cli-DG6zsfsS.mjs +0 -4
  149. package/dist/devtools-nmPUmYU_.mjs +0 -2
  150. package/dist/image-1nRyKa60.mjs.map +0 -1
  151. package/dist/index-2E1jYgak.d.mts.map +0 -1
  152. package/dist/index-DnuadDNL.d.mts.map +0 -1
  153. package/dist/layout-signals-Dch2EiCy.mjs.map +0 -1
  154. package/dist/mouse-events-hnbJZRwK.mjs.map +0 -1
  155. package/dist/node-BiFu8I9Y.mjs.map +0 -1
  156. package/dist/reconciler-NBDSEm8k.mjs.map +0 -1
  157. package/dist/render-string-B4h4SmK7.mjs.map +0 -1
  158. package/dist/src-B9S_woYc.mjs.map +0 -1
  159. package/dist/src-CbhWmUnF.mjs.map +0 -1
  160. package/dist/src-Oe6x5PrS.mjs.map +0 -1
  161. package/dist/src-WeA_J4BV.mjs.map +0 -1
  162. package/dist/ui/display.d.mts +0 -35
  163. package/dist/ui/display.d.mts.map +0 -1
  164. package/dist/ui/display.mjs +0 -123
  165. package/dist/ui/display.mjs.map +0 -1
  166. package/dist/ui/input.d.mts +0 -184
  167. package/dist/ui/input.d.mts.map +0 -1
  168. package/dist/ui/input.mjs +0 -283
  169. package/dist/ui/input.mjs.map +0 -1
  170. package/dist/wrapper-D7gNSsgf.mjs.map +0 -1
  171. package/dist/yoga-adapter-1ex8r0ws.mjs +0 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.mjs","names":["cssIs","_kittyDeleteAllScrimPlacements"],"sources":["../packages/test/src/auto-locator.ts","../packages/test/src/debug.ts","../packages/test/src/diagnostic-theme.ts","../packages/test/src/cell-inspector.ts","../packages/test/src/frame-recorder.ts","../packages/ag-term/src/bound-term.ts","../packages/ag-term/src/screenshot.ts","../packages/ag-react/src/debug/render-path.ts","../packages/ag-term/src/app.ts","../packages/ag-term/src/pipeline/strict-residue.ts","../packages/ag-term/src/renderer.ts","../packages/test/src/index.tsx"],"sourcesContent":["/**\n * AutoLocator - Self-refreshing Playwright-style locator (canonical implementation)\n *\n * This is the primary locator API. Prefer `App.locator()` / `App.getByTestId()` /\n * `App.getByText()` which use AutoLocator internally.\n *\n * AutoLocator re-evaluates queries against the current tree on each access.\n * This eliminates the stale locator problem in tests (a static locator\n * captures node identity at construction and cannot survive re-renders).\n *\n * @example\n * ```tsx\n * const app = render(<Board />)\n * const cursor = app.locator('[data-cursor]')\n *\n * // Same locator, fresh result after state change\n * expect(cursor.textContent()).toBe('item1')\n * await app.press('j')\n * expect(cursor.textContent()).toBe('item2') // Auto-refreshed!\n * ```\n */\n\nimport type { AgNode, Rect } from \"@silvery/ag/types\"\nimport { is as cssIs, type Options } from \"css-select\"\n\n/**\n * Filter options for locator narrowing\n */\nexport interface FilterOptions {\n /** Match nodes containing this text */\n hasText?: string | RegExp\n /** Match nodes with this testID */\n hasTestId?: string\n /** Match nodes with this attribute value */\n has?: { attr: string; value?: string }\n}\n\n/**\n * AutoLocator interface - lazy, self-refreshing reference to nodes\n */\nexport interface AutoLocator {\n // Core queries (return new AutoLocators)\n getByText(text: string | RegExp): AutoLocator\n getByTestId(id: string): AutoLocator\n locator(selector: string): AutoLocator\n\n // Filtering\n filter(options: FilterOptions): AutoLocator\n filter(predicate: (node: AgNode) => boolean): AutoLocator\n\n // Narrowing\n first(): AutoLocator\n last(): AutoLocator\n nth(index: number): AutoLocator\n\n // Resolution (actually finds nodes - re-evaluates on each call)\n resolve(): AgNode | null\n resolveAll(): AgNode[]\n count(): number\n\n // Utilities (resolve then read)\n textContent(): string\n getAttribute(name: string): string | undefined\n boundingBox(): Rect | null\n isVisible(): boolean\n}\n\n// Query predicate type\ntype NodePredicate = (node: AgNode) => boolean\n\n/**\n * Create an AutoLocator from a container getter function.\n * The getter is called fresh on each resolution.\n */\nexport function createAutoLocator(\n getContainer: () => AgNode,\n opts?: { strict?: boolean },\n): AutoLocator {\n return new AutoLocatorImpl(getContainer, [], undefined, opts?.strict ?? false)\n}\n\n/**\n * AutoLocator implementation\n */\nclass AutoLocatorImpl implements AutoLocator {\n constructor(\n private getContainer: () => AgNode,\n private predicates: NodePredicate[],\n private indexSelector?: { type: \"first\" | \"last\" | \"nth\"; index?: number },\n private strict: boolean = false,\n ) {}\n\n getByText(text: string | RegExp): AutoLocator {\n const predicate: NodePredicate = (node) => {\n const content = getNodeTextContent(node)\n if (!content) return false\n\n // Only match silvery-text nodes (not containers)\n if (node.type !== \"silvery-text\") {\n return false\n }\n\n // Skip raw text nodes if their parent also matches\n if (node.isRawText && node.parent?.type === \"silvery-text\") {\n return false\n }\n\n if (typeof text === \"string\") {\n return content.includes(text)\n }\n return text.test(content)\n }\n return new AutoLocatorImpl(\n this.getContainer,\n [...this.predicates, predicate],\n undefined,\n this.strict,\n )\n }\n\n getByTestId(id: string): AutoLocator {\n const predicate: NodePredicate = (node) => {\n return getNodeProp(node, \"testID\") === id\n }\n return new AutoLocatorImpl(\n this.getContainer,\n [...this.predicates, predicate],\n undefined,\n this.strict,\n )\n }\n\n locator(selector: string): AutoLocator {\n const predicate: NodePredicate = (node) => {\n try {\n return cssIs(node, selector, cssSelectOptions)\n } catch {\n return false // Invalid selector → match nothing\n }\n }\n return new AutoLocatorImpl(\n this.getContainer,\n [...this.predicates, predicate],\n undefined,\n this.strict,\n )\n }\n\n filter(optionsOrPredicate: FilterOptions | ((node: AgNode) => boolean)): AutoLocator {\n let predicate: NodePredicate\n\n if (typeof optionsOrPredicate === \"function\") {\n predicate = optionsOrPredicate\n } else {\n const opts = optionsOrPredicate\n predicate = (node: AgNode) => {\n if (opts.hasText !== undefined) {\n const content = getNodeTextContent(node)\n if (typeof opts.hasText === \"string\") {\n if (!content.includes(opts.hasText)) return false\n } else {\n if (!opts.hasText.test(content)) return false\n }\n }\n if (opts.hasTestId !== undefined) {\n if (getNodeProp(node, \"testID\") !== opts.hasTestId) return false\n }\n if (opts.has !== undefined) {\n const value = getNodeProp(node, opts.has.attr)\n if (opts.has.value !== undefined) {\n if (value !== opts.has.value) return false\n } else {\n if (value === undefined) return false\n }\n }\n return true\n }\n }\n\n return new AutoLocatorImpl(\n this.getContainer,\n [...this.predicates, predicate],\n undefined,\n this.strict,\n )\n }\n\n first(): AutoLocator {\n return new AutoLocatorImpl(\n this.getContainer,\n this.predicates,\n {\n type: \"first\",\n },\n this.strict,\n )\n }\n\n last(): AutoLocator {\n return new AutoLocatorImpl(\n this.getContainer,\n this.predicates,\n {\n type: \"last\",\n },\n this.strict,\n )\n }\n\n nth(index: number): AutoLocator {\n return new AutoLocatorImpl(\n this.getContainer,\n this.predicates,\n {\n type: \"nth\",\n index,\n },\n this.strict,\n )\n }\n\n resolve(): AgNode | null {\n const nodes = this.resolveAll()\n if (this.indexSelector) {\n switch (this.indexSelector.type) {\n case \"first\":\n return nodes[0] ?? null\n case \"last\":\n return nodes[nodes.length - 1] ?? null\n case \"nth\":\n return nodes[this.indexSelector.index ?? 0] ?? null\n }\n }\n if (this.strict && nodes.length > 1) {\n throw new Error(\n `Locator resolved to ${nodes.length} elements. Use .first(), .nth(n), or a more specific selector. ` +\n `Matched types: ${nodes\n .slice(0, 3)\n .map((n) => n.type)\n .join(\", \")}${nodes.length > 3 ? \"...\" : \"\"}`,\n )\n }\n return nodes[0] ?? null\n }\n\n resolveAll(): AgNode[] {\n // Get fresh container on each resolution\n const container = this.getContainer()\n\n if (this.predicates.length === 0) {\n return [container]\n }\n\n const matches: AgNode[] = []\n walkTree(container, (node) => {\n if (this.predicates.every((p) => p(node))) {\n matches.push(node)\n }\n })\n return matches\n }\n\n count(): number {\n return this.resolveAll().length\n }\n\n textContent(): string {\n const node = this.resolve()\n if (!node) return \"\"\n return getNodeTextContent(node)\n }\n\n getAttribute(name: string): string | undefined {\n const node = this.resolve()\n if (!node) return undefined\n return getNodeProp(node, name)\n }\n\n boundingBox(): Rect | null {\n const node = this.resolve()\n if (!node) return null\n return node.scrollRect ?? null\n }\n\n isVisible(): boolean {\n const box = this.boundingBox()\n if (!box) return false\n return box.width > 0 && box.height > 0\n }\n}\n\n// ============================================================================\n// Tree Walking Helpers\n// ============================================================================\n\n/**\n * Walk tree depth-first, calling visitor for each node\n */\nfunction walkTree(node: AgNode, visitor: (node: AgNode) => void): void {\n visitor(node)\n for (const child of node.children) {\n walkTree(child, visitor)\n }\n}\n\n/**\n * Get text content of a node (concatenated from all text descendants)\n */\nfunction getNodeTextContent(node: AgNode): string {\n if (node.textContent !== undefined) {\n return node.textContent\n }\n return node.children.map(getNodeTextContent).join(\"\")\n}\n\n/**\n * Get a prop value from node\n */\nfunction getNodeProp(node: AgNode, name: string): string | undefined {\n const props = node.props as Record<string, unknown>\n const value = props[name]\n if (value === undefined || value === null) return undefined\n return String(value)\n}\n\n// ============================================================================\n// css-select Adapter for AgNode\n// ============================================================================\n\n/**\n * Adapter that maps AgNode tree operations to css-select's DOM interface.\n * This gives us full CSS3 selector support: combinators (>, +, ~, space),\n * pseudo-classes (:first-child, :last-child, :nth-child, :not, :has, :empty),\n * attribute selectors ([attr], [attr=val], [attr^=val], [attr$=val], [attr*=val]),\n * and more — all handled by the battle-tested css-select engine.\n */\nconst agNodeAdapter = {\n isTag: (node: AgNode): node is AgNode => !node.isRawText,\n\n getAttributeValue: (element: AgNode, name: string): string | undefined =>\n getNodeProp(element, name),\n\n getChildren: (node: AgNode): AgNode[] => [...node.children],\n\n getName: (element: AgNode): string => element.type ?? \"unknown\",\n\n getParent: (node: AgNode): AgNode | null => node.parent,\n\n getSiblings: (node: AgNode): AgNode[] => (node.parent ? [...node.parent.children] : [node]),\n\n getText: (node: AgNode): string => getNodeTextContent(node),\n\n hasAttrib: (element: AgNode, name: string): boolean => getNodeProp(element, name) !== undefined,\n\n removeSubsets: (nodes: AgNode[]): AgNode[] => {\n return nodes.filter((node, i) => {\n for (let j = 0; j < nodes.length; j++) {\n if (i !== j) {\n let ancestor = node.parent\n while (ancestor) {\n if (ancestor === nodes[j]) return false\n ancestor = ancestor.parent\n }\n }\n }\n return true\n })\n },\n\n prevElementSibling: (node: AgNode): AgNode | null => {\n if (!node.parent) return null\n const siblings = node.parent.children\n const index = siblings.indexOf(node)\n for (let i = index - 1; i >= 0; i--) {\n if (!siblings[i]!.isRawText) return siblings[i]!\n }\n return null\n },\n}\n\n/** Shared css-select options — case-sensitive tags, no caching (tree changes between queries) */\nconst cssSelectOptions: Options<AgNode, AgNode> = {\n adapter: agNodeAdapter,\n xmlMode: true,\n cacheResults: false,\n}\n","/**\n * Debug Tree Inspection\n *\n * Pretty-prints SilveryNode trees for debugging TUI tests.\n * Similar to React DevTools component tree or browser DOM inspection.\n *\n * @example\n * ```tsx\n * import { debugTree } from '@silvery/test'\n *\n * const { getContainer } = render(<MyComponent />)\n * console.log(debugTree(getContainer()))\n * // Output:\n * // <silvery-root [0,0 80×24]>\n * // <silvery-box testID=\"main\" [0,0 80×24]>\n * // <silvery-text \"Hello World\" [0,0 11×1]>\n * ```\n */\n\nimport type { AgNode } from \"@silvery/ag/types\"\n\nexport interface DebugTreeOptions {\n /** Maximum depth to traverse (default: unlimited) */\n depth?: number\n /** Include layout rectangles (default: true) */\n showRects?: boolean\n /** Include text content (default: true) */\n showText?: boolean\n}\n\n/**\n * Pretty-print SilveryNode tree for debugging.\n *\n * @param node - Root node to inspect\n * @param options - Display options\n * @returns Formatted tree string\n */\nexport function debugTree(node: AgNode, options: DebugTreeOptions = {}): string {\n const { depth = Number.POSITIVE_INFINITY, showRects = true, showText = true } = options\n const lines: string[] = []\n\n // Safe JSON.stringify that handles cyclic references\n function safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value)\n } catch {\n // Handle cyclic structures or other stringify errors\n if (typeof value === \"object\" && value !== null) {\n return \"[object]\"\n }\n return String(value)\n }\n }\n\n function walk(n: AgNode, indent: number, currentDepth: number): void {\n if (currentDepth > depth) return\n\n // Build props string (exclude children and internal props)\n const props = Object.entries(n.props ?? {})\n .filter(([k]) => ![\"children\"].includes(k))\n .filter(([, v]) => v !== undefined && v !== null && v !== false)\n .map(([k, v]) => {\n if (typeof v === \"string\") return `${k}=\"${v}\"`\n if (typeof v === \"boolean\") return k\n return `${k}=${safeStringify(v)}`\n })\n .join(\" \")\n\n // Build rect string\n let rect = \"\"\n if (showRects && n.scrollRect) {\n const { x, y, width, height } = n.scrollRect\n rect = ` [${x},${y} ${width}×${height}]`\n }\n\n // Build text content string\n let text = \"\"\n if (showText && n.textContent) {\n // Truncate long text\n const content = n.textContent.length > 40 ? n.textContent.slice(0, 37) + \"...\" : n.textContent\n text = ` \"${content}\"`\n }\n\n // Format line\n const propsStr = props ? \" \" + props : \"\"\n lines.push(\" \".repeat(indent) + `<${n.type}${propsStr}${text}${rect}>`)\n\n // Recurse into children\n for (const child of n.children) {\n walk(child, indent + 1, currentDepth + 1)\n }\n }\n\n walk(node, 0, 0)\n return lines.join(\"\\n\")\n}\n","/**\n * Diagnostic theme for pipeline regression tests.\n *\n * Built so that **every distinct flat token resolves to a distinct, visible\n * color** — exposing pipeline bugs that would be invisible under themes where\n * tokens collapse to the canvas background.\n *\n * ## Why this exists\n *\n * The default `ansi16DarkTheme` uses Nord-derived hexes. In Nord:\n * - canvas `bg` = `#2e3440` = rgb(46, 52, 64)\n * - legacy `mutedbg` = blend(bg, fg, 0.04) ≈ rgb(52, 58, 70)\n *\n * When tests render at ansi16, the underlying RGBs from `mutedbg` and `bg`\n * differ by only a few units — and crucially, they look \"the same\" to\n * naive bg-vs-canvas detectors that compare against a fixed canvas color.\n *\n * One concrete consequence: the cyan-strip cold-start bug\n * (`@km/silvery/render-light-blue-bg-strip-residue`) emitted bg=$mutedbg paint\n * at cells past a clip boundary, but every test passed because the bg color\n * was indistinguishable from canvas in the test theme. Real users running at\n * Nord saw a visible strip; tests saw nothing.\n *\n * The diagnostic theme uses pure black canvas + saturated primaries so blends\n * preserve their step magnitude in every channel. Every Sterling/legacy token\n * resolves to a distinct RGB tuple. A test that asserts \"no non-canvas bg in\n * region X\" catches phantom bg paints regardless of which token emitted them.\n *\n * ## Usage\n *\n * ```ts\n * import { diagnosticTheme } from \"@silvery/test\"\n * import { createRenderer } from \"@silvery/test\"\n *\n * const r = createRenderer({ cols: 80, rows: 24, theme: diagnosticTheme })\n * ```\n *\n * Or via `testBoard` in km-tui:\n *\n * ```ts\n * const result = await testBoard(VAULT, { columns: 82, rows: 75, theme: diagnosticTheme })\n * // assert no cells past col 40 have non-canvas bg\n * for (let r = 0; r < 75; r++) {\n * for (let c = 40; c < 82; c++) {\n * const cell = result._result.cell(c, r)\n * expect(cell.bg).toBeNull() // canvas = null bg\n * }\n * }\n * ```\n *\n * ## Distinctness invariant\n *\n * Pure black canvas (rgb 0,0,0) + pure white fg (rgb 255,255,255) means every\n * blend(bg, fg, t) for distinct `t` produces distinct RGBs. Sterling/legacy\n * derive uses these `t` values:\n *\n * - `bg-surface-subtle` = blend(0.03) ≈ rgb(8, 8, 8)\n * - `bg-surface-hover` = blend(0.10) ≈ rgb(26, 26, 26)\n * - `bg-surface-raised` = blend(0.10) ≈ rgb(26, 26, 26)\n * - `bg-surface-overlay` = blend(0.12) ≈ rgb(31, 31, 31)\n * - `bg-muted` (Sterling)= blend(0.08) ≈ rgb(20, 20, 20)\n * - `mutedbg` (legacy) = blend(0.04) ≈ rgb(10, 10, 10)\n *\n * All distinct from canvas (rgb 0,0,0) and from each other. The named ANSI\n * slots are saturated primaries so accentBg, redBg, etc. paint in vivid\n * unmistakable colors.\n */\n\nimport { deriveTheme } from \"../../ansi/src/theme/derive.ts\"\nimport type { ColorScheme, Theme } from \"../../ansi/src/theme/types.ts\"\n\n/**\n * Maximally-distinct ColorScheme for diagnostic rendering tests.\n *\n * Pure black canvas, pure white fg, saturated ANSI primaries. Every derived\n * token gets a unique visible RGB.\n */\nexport const diagnosticScheme: ColorScheme = {\n name: \"diagnostic\",\n dark: true,\n // Mid-luminance dark navy — sits clear of OKLCH gamut floor so even small\n // blends toward fg produce visibly distinct RGBs (pure-black canvas would\n // round small blends back to #000000 due to OKLCH lightness compression).\n background: \"#101828\",\n // Light cyan-pink mix — high chroma + high luminance so blends inherit\n // visible chromatic shift (not just luminance step) at every blend factor.\n foreground: \"#f0f8ff\",\n primary: \"#ff00ff\", // magenta — accent slots become vivid magenta\n // Saturated ANSI primaries so each named slot is unmistakable\n black: \"#202830\", // distinct from background — used for *bg slots in ansi16 path\n red: \"#ff0000\",\n green: \"#00ff00\",\n yellow: \"#ffff00\",\n blue: \"#4060ff\",\n magenta: \"#ff00ff\",\n cyan: \"#00ffff\",\n white: \"#f0f8ff\",\n // Bright variants — tinted away from base so distinguishable\n brightBlack: \"#606870\",\n brightRed: \"#ff8080\",\n brightGreen: \"#80ff80\",\n brightYellow: \"#ffff80\",\n brightBlue: \"#8080ff\",\n brightMagenta: \"#ff80ff\",\n brightCyan: \"#80ffff\",\n brightWhite: \"#ffffff\",\n // Cursor + selection in distinct vivid hues\n cursorColor: \"#ffaa00\", // orange\n cursorText: \"#101828\",\n selectionBackground: \"#aa00ff\", // purple\n selectionForeground: \"#ffffff\",\n}\n\n/**\n * Pre-derived diagnostic theme. Pass to `createRenderer({ theme })` or\n * `testBoard({ theme })` for pipeline regression coverage.\n */\nexport const diagnosticTheme: Theme = deriveTheme(diagnosticScheme)\n","import type { Cell, TerminalBuffer } from \"@silvery/ag-term/buffer\"\n\nexport interface TestCellSnapshot extends Cell {\n readonly x: number\n readonly y: number\n readonly selectable: boolean\n}\n\nexport function readCellSnapshot(buffer: TerminalBuffer, x: number, y: number): TestCellSnapshot {\n return {\n x,\n y,\n ...buffer.getCell(x, y),\n selectable: buffer.isCellSelectable(x, y),\n }\n}\n\nexport function readCellRow(buffer: TerminalBuffer, y: number): TestCellSnapshot[] {\n const cells: TestCellSnapshot[] = []\n for (let x = 0; x < buffer.width; x++) cells.push(readCellSnapshot(buffer, x, y))\n return cells\n}\n\nexport function formatSelectableCells(buffer: TerminalBuffer): string {\n const lines: string[] = []\n for (let y = 0; y < buffer.height; y++) {\n const chars = readCellRow(buffer, y)\n .map((cell) => {\n if (cell.char === \"\") return cell.selectable ? \"^\" : \",\"\n if (cell.char === \" \") return cell.selectable ? \"_\" : \".\"\n return cell.selectable ? cell.char : cell.char.toLowerCase()\n })\n .join(\"\")\n lines.push(chars)\n }\n return lines.join(\"\\n\")\n}\n","/**\n * Frame recorder — frame-by-frame cell-level testing for silvery apps.\n *\n * Captures an immutable TextFrame snapshot after each render pass, letting\n * tests iterate render history, diff adjacent frames, and detect transient\n * visual artifacts (e.g., a selection overlay that appears for exactly one\n * frame and then reverts).\n *\n * Works with any Term that writes output through a stdout-like sink. For\n * emulator-backed terms (e.g., `createTermless()`), snapshots read cells\n * directly from the emulator after each write, so colors, attributes, and\n * wide-char layout reflect what the real terminal would display.\n *\n * @example\n * ```tsx\n * import { createTermless, recordFrames } from \"@silvery/test\"\n * import { run } from \"@silvery/ag-term/runtime\"\n *\n * test(\"no flicker on cursor move\", async () => {\n * using term = createTermless({ cols: 80, rows: 24 })\n * const handle = await run(<App />, term)\n * const recording = recordFrames(handle)\n *\n * await handle.press(\"j\")\n *\n * expect(recording.frames.length).toBeGreaterThan(0)\n * // Ensure no cell at (10, 5) is painted twice in a single transient way:\n * expect(\n * recording.hasTransientChange(\n * (a, b, c) =>\n * a.cell(10, 5).bg !== b.cell(10, 5).bg &&\n * b.cell(10, 5).bg !== c.cell(10, 5).bg,\n * ),\n * ).toBe(false)\n * })\n * ```\n */\n\nimport type { FrameCell, RGB, TextFrame } from \"@silvery/ag/text-frame\"\nimport type { UnderlineStyle } from \"@silvery/ag/types\"\nimport type { Term } from \"@silvery/ag-term\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * A single cell change between two frames at the same (col, row).\n *\n * `from` and `to` are the cell states in the \"before\" and \"after\" frames.\n * Both are always present — to detect appearance/disappearance, compare\n * `char`, `fg`, or `bg` against the blank-cell defaults.\n */\nexport interface CellChange {\n readonly col: number\n readonly row: number\n readonly from: FrameCell\n readonly to: FrameCell\n}\n\n/**\n * A predicate over three consecutive frames. Returns true when the cell at\n * some coordinate changes in frame B and reverts in frame C — the signature\n * of a 1-frame transient flicker.\n */\nexport type TransientPredicate = (a: TextFrame, b: TextFrame, c: TextFrame) => boolean\n\n/**\n * A live recording of rendered frames.\n *\n * `frames` is an append-only array of immutable TextFrame snapshots. Each\n * entry captures the terminal state after one render pass. Subsequent renders\n * push new entries without mutating existing ones, so tests can safely iterate\n * and diff across history.\n */\nexport interface FrameRecording {\n /** All captured frames, in render order. */\n readonly frames: readonly TextFrame[]\n /** Convenience: `frames.length`. */\n readonly count: number\n /**\n * Cells that differ between frames `i` and `j`. Returns an empty array when\n * the frames are identical. Size mismatches (frames captured at different\n * terminal dimensions) are diffed over the intersection.\n */\n diff(i: number, j: number): CellChange[]\n /**\n * Scan every 3-frame window for transient changes. Returns true when the\n * predicate fires on any `(frames[i], frames[i+1], frames[i+2])` triple.\n *\n * A transient change is a cell that changes in one frame and reverts in the\n * next — the visual signature of a flicker, stale overlay, or one-frame\n * paint bug.\n */\n hasTransientChange(predicate: TransientPredicate): boolean\n /** Clear all recorded frames. Useful between test phases. */\n clear(): void\n /** Stop recording. Frames already captured remain accessible. */\n stop(): void\n}\n\n/**\n * A handle-like object that exposes a Term. Accepted by `recordFrames()` in\n * addition to a bare Term.\n */\nexport interface FrameRecordable {\n readonly term: Term\n}\n\n// ============================================================================\n// Blank cell — returned for out-of-bounds queries\n// ============================================================================\n\nconst BLANK_CELL: FrameCell = Object.freeze({\n char: \" \",\n fg: null,\n bg: null,\n bold: false,\n dim: false,\n italic: false,\n underline: false as UnderlineStyle,\n underlineColor: null,\n overline: false,\n strikethrough: false,\n inverse: false,\n blink: false,\n hidden: false,\n wide: false,\n continuation: false,\n hyperlink: null,\n})\n\n// ============================================================================\n// Termless → TextFrame snapshot\n// ============================================================================\n\n/**\n * Termless emulator shape used by the snapshot helper. Matches the\n * `TermEmulator` duck type from @silvery/ag-term with additional cell access\n * from the underlying termless Terminal.\n */\ninterface TermlessLike {\n readonly cols: number\n readonly rows: number\n feed(data: Uint8Array | string): void\n cell(row: number, col: number): TermlessCellView\n screen: {\n getText(): string\n getLines(): string[]\n }\n}\n\ninterface TermlessCellView {\n readonly char: string\n readonly fg: RGB | null\n readonly bg: RGB | null\n readonly bold: boolean\n readonly dim: boolean\n readonly italic: boolean\n readonly underline: UnderlineStyle\n readonly underlineColor: RGB | null\n readonly overline?: boolean\n readonly strikethrough: boolean\n readonly inverse: boolean\n readonly blink: boolean\n readonly hidden: boolean\n readonly wide: boolean\n readonly continuation: boolean\n readonly hyperlink: string | null\n}\n\n/**\n * Build an immutable TextFrame snapshot from a termless emulator. Cell values\n * must be copied eagerly: the emulator is mutable, so a lazy `cell()` read\n * would make every historical frame report the terminal's latest style.\n */\nfunction snapshotEmulator(emu: TermlessLike): TextFrame {\n const width = emu.cols\n const height = emu.rows\n const lines = emu.screen.getLines().slice(0, height)\n // Pad to height in case the emulator reports fewer lines than rows.\n while (lines.length < height) lines.push(\"\")\n const text = lines.join(\"\\n\")\n\n const cells: FrameCell[] = []\n for (let row = 0; row < height; row++) {\n for (let col = 0; col < width; col++) {\n const view = emu.cell(row, col)\n cells.push(\n Object.freeze({\n char: view.char,\n fg: view.fg,\n bg: view.bg,\n bold: view.bold,\n dim: view.dim,\n italic: view.italic,\n underline: view.underline,\n underlineColor: view.underlineColor,\n overline: view.overline ?? false,\n strikethrough: view.strikethrough,\n inverse: view.inverse,\n blink: view.blink,\n hidden: view.hidden,\n wide: view.wide,\n continuation: view.continuation,\n hyperlink: view.hyperlink,\n }),\n )\n }\n }\n\n const frame: TextFrame = {\n get text() {\n return text\n },\n get ansi() {\n // The emulator has already applied ANSI — we don't preserve the raw\n // patch string here. Return plain text; callers needing ANSI should\n // inspect the term's backend directly.\n return text\n },\n get lines() {\n return lines\n },\n width,\n height,\n cell(col: number, row: number): FrameCell {\n if (col < 0 || col >= width || row < 0 || row >= height) return BLANK_CELL\n return cells[row * width + col]!\n },\n containsText(needle: string) {\n return text.includes(needle)\n },\n }\n return frame\n}\n\n// ============================================================================\n// Diffing\n// ============================================================================\n\nfunction cellsEqual(a: FrameCell, b: FrameCell): boolean {\n if (a.char !== b.char) return false\n if (!rgbEqual(a.fg, b.fg)) return false\n if (!rgbEqual(a.bg, b.bg)) return false\n if (a.bold !== b.bold) return false\n if (a.dim !== b.dim) return false\n if (a.italic !== b.italic) return false\n if (a.underline !== b.underline) return false\n if (!rgbEqual(a.underlineColor, b.underlineColor)) return false\n if (a.overline !== b.overline) return false\n if (a.strikethrough !== b.strikethrough) return false\n if (a.inverse !== b.inverse) return false\n if (a.blink !== b.blink) return false\n if (a.hidden !== b.hidden) return false\n if (a.wide !== b.wide) return false\n if (a.continuation !== b.continuation) return false\n if (a.hyperlink !== b.hyperlink) return false\n return true\n}\n\nfunction rgbEqual(a: RGB | null, b: RGB | null): boolean {\n if (a === b) return true\n if (!a || !b) return false\n return a.r === b.r && a.g === b.g && a.b === b.b\n}\n\nfunction diffFrames(a: TextFrame, b: TextFrame): CellChange[] {\n const width = Math.min(a.width, b.width)\n const height = Math.min(a.height, b.height)\n const changes: CellChange[] = []\n for (let row = 0; row < height; row++) {\n for (let col = 0; col < width; col++) {\n const from = a.cell(col, row)\n const to = b.cell(col, row)\n if (!cellsEqual(from, to)) {\n changes.push({ col, row, from, to })\n }\n }\n }\n return changes\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Start recording frames from a Term (or any object exposing `.term`).\n *\n * The recorder observes the emulator's ANSI feed so that every render patch\n * triggers a fresh TextFrame snapshot. Only calls made AFTER `recordFrames()`\n * returns are captured — the initial render is not retroactively recorded.\n * Call `recording.stop()` to end recording early.\n *\n * For emulator-backed terms created via `createTermless()`, snapshots reflect\n * the real terminal state including resolved colors and wide characters. For\n * other Term types, this function throws — cell-level recording requires an\n * emulator.\n */\nexport function recordFrames(target: Term | FrameRecordable): FrameRecording {\n const term = isFrameRecordable(target) ? target.term : target\n const emulator = getEmulator(term)\n if (!emulator) {\n throw new Error(\n \"recordFrames(): term has no emulator backend. \" +\n \"Use createTermless() to create an emulator-backed Term for frame recording.\",\n )\n }\n\n const frames: TextFrame[] = []\n let stopped = false\n\n // run() writes ANSI patches straight to the emulator; it does not call the\n // legacy Term.paint() helper. Observe that production output boundary so a\n // recorder attached either before or after run() sees every live repaint.\n // createTermless already wraps feed() for clipboard capture, so preserve and\n // call the current wrapper rather than reaching through to a raw stream.\n const originalFeed = emulator.feed\n const recordingFeed = function (this: TermlessLike, data: Uint8Array | string): void {\n originalFeed.call(this, data)\n // The emulator has just been fed (synchronously) — snapshot its state.\n // Snapshot errors must fail the test instead of silently dropping proof.\n if (!stopped) frames.push(snapshotEmulator(emulator))\n }\n emulator.feed = recordingFeed\n\n const recording: FrameRecording = {\n get frames() {\n return frames\n },\n get count() {\n return frames.length\n },\n diff(i: number, j: number): CellChange[] {\n const a = frames[i]\n const b = frames[j]\n if (!a || !b) return []\n return diffFrames(a, b)\n },\n hasTransientChange(predicate: TransientPredicate): boolean {\n for (let i = 0; i + 2 < frames.length; i++) {\n if (predicate(frames[i]!, frames[i + 1]!, frames[i + 2]!)) return true\n }\n return false\n },\n clear() {\n frames.length = 0\n },\n stop() {\n if (stopped) return\n stopped = true\n if (emulator.feed === recordingFeed) emulator.feed = originalFeed\n },\n }\n\n return recording\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction isFrameRecordable(value: unknown): value is FrameRecordable {\n if (value === null || typeof value !== \"object\") return false\n const maybe = value as { term?: unknown }\n // Term is a Proxy wrapping a function (chalk-style), so typeof term is\n // \"function\", not \"object\". Accept both.\n if (maybe.term == null) return false\n const t = typeof maybe.term\n return t === \"object\" || t === \"function\"\n}\n\nfunction getEmulator(term: Term): TermlessLike | null {\n const withEmu = term as unknown as { _emulator?: unknown }\n const emu = withEmu._emulator\n // Emulator is a plain factory object — typeof should be \"object\".\n if (emu == null) return null\n const t = typeof emu\n if (t !== \"object\" && t !== \"function\") return null\n const candidate = emu as Record<string, unknown>\n if (typeof candidate.cols !== \"number\") return null\n if (typeof candidate.rows !== \"number\") return null\n if (typeof candidate.cell !== \"function\") return null\n if (candidate.screen == null) return null\n return emu as TermlessLike\n}\n","/**\n * BoundTerm - Terminal buffer with node awareness\n *\n * Bridges the terminal buffer (screen space) with the SilveryNode tree.\n * Provides screen-coordinate queries that return nodes.\n *\n * @example\n * ```tsx\n * const app = render(<Board />)\n *\n * // Screen-space access\n * const cell = app.term.cell(10, 5)\n * const node = app.term.nodeAt(10, 5)\n * console.log(app.term.text)\n * ```\n */\n\nimport type { Cell, TerminalBuffer } from \"./buffer\"\nimport type { AgNode } from \"@silvery/ag/types\"\n\n/**\n * BoundTerm interface - terminal with node awareness\n */\nexport interface BoundTerm {\n /** Get cell at screen coordinates */\n cell(x: number, y: number): Cell\n\n /** Get node at screen coordinates */\n nodeAt(x: number, y: number): AgNode | null\n\n /** Get visible text (plain, no ANSI) */\n readonly text: string\n\n /** Terminal dimensions */\n readonly columns: number\n readonly rows: number\n\n /** Access underlying buffer */\n readonly buffer: TerminalBuffer\n}\n\n/**\n * Create a BoundTerm from a buffer and root node getter\n */\nexport function createBoundTerm(\n buffer: TerminalBuffer,\n getRoot: () => AgNode,\n getText: () => string,\n): BoundTerm {\n return {\n cell(x: number, y: number): Cell {\n return buffer.getCell(x, y)\n },\n\n nodeAt(x: number, y: number): AgNode | null {\n const root = getRoot()\n return findNodeAtScreenPosition(root, x, y)\n },\n\n get text(): string {\n return getText()\n },\n\n get columns(): number {\n return buffer.width\n },\n\n get rows(): number {\n return buffer.height\n },\n\n get buffer(): TerminalBuffer {\n return buffer\n },\n }\n}\n\n/**\n * Find the deepest node at the given screen coordinates\n */\nfunction findNodeAtScreenPosition(node: AgNode, x: number, y: number): AgNode | null {\n const rect = node.scrollRect\n if (!rect) return null\n\n // Check if point is within this node's bounds\n if (x < rect.x || x >= rect.x + rect.width || y < rect.y || y >= rect.y + rect.height) {\n return null\n }\n\n // Check children (deepest match wins)\n for (const child of node.children) {\n const found = findNodeAtScreenPosition(child, x, y)\n if (found) return found\n }\n\n // Check virtual text children with inlineRects (nested Text inside Text).\n // These don't have scrollRect/layoutNode, so standard DFS misses them.\n if (node.type === \"silvery-text\") {\n for (let i = node.children.length - 1; i >= 0; i--) {\n const child = node.children[i]!\n if (child.inlineRects) {\n for (const inlineRect of child.inlineRects) {\n if (\n x >= inlineRect.x &&\n x < inlineRect.x + inlineRect.width &&\n y >= inlineRect.y &&\n y < inlineRect.y + inlineRect.height\n ) {\n return child\n }\n }\n }\n }\n }\n\n // No child matched, this node is the deepest match\n return node\n}\n","import { writeFile } from \"node:fs/promises\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface Screenshotter {\n /** Render HTML to PNG. First call starts Playwright (~3-5s), subsequent calls ~200ms */\n capture(html: string, outputPath?: string): Promise<Buffer>\n /** Close browser */\n close(): Promise<void>\n [Symbol.asyncDispose](): Promise<void>\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\nexport function createScreenshotter(): Screenshotter {\n let browser: import(\"playwright\").Browser | null = null\n let page: import(\"playwright\").Page | null = null\n\n async function ensureBrowser() {\n if (browser && page) return page\n\n const { chromium } = await import(\"playwright\")\n browser = await chromium.launch()\n const context = await browser.newContext()\n page = await context.newPage()\n return page\n }\n\n async function capture(html: string, outputPath?: string): Promise<Buffer> {\n const p = await ensureBrowser()\n await p.setContent(html, { waitUntil: \"load\" })\n await p.waitForTimeout(50)\n const buffer = (await p.screenshot({ fullPage: true })) as Buffer\n if (outputPath) {\n await writeFile(outputPath, buffer)\n }\n return buffer\n }\n\n async function close() {\n if (browser) {\n await browser.close()\n browser = null\n page = null\n }\n }\n\n return {\n capture,\n close,\n [Symbol.asyncDispose]: close,\n }\n}\n","/**\n * Render-path debug primitive — query the live AgNode tree to answer\n * \"what is the parent chain of component X?\" without grepping source.\n *\n * Use case: visual / first-paint / layout bugs where the bead reporter\n * thinks component X renders inside lane A, but the actual mounted tree\n * has it inside lane B. Static-trace this BEFORE /pro to catch\n * misframed beads early. See bead @km/silvery/14348-render-path-trace.\n *\n * No instrumentation required — reads directly from the committed\n * AgNode tree. Pairs with `silvery:mount` lifecycle logs (which fire on\n * every mount/update/unmount) for time-series instrumentation.\n *\n * Sibling primitive to:\n * - `silvery:cls` (ClsMonitor) — detects layout SHIFTS\n * - `silvery:mount` (logNodeLifecycle) — logs mount/update/unmount events\n *\n * Render-path answers a different question: \"where does X actually live\n * in the tree right now?\"\n */\n\nimport type { AgNode, AgNodeType } from \"@silvery/ag/types\"\n\n/**\n * A summary of one node in a render-path or mount-tree dump.\n * The shape is JSON-serializable for snapshot tests.\n */\nexport interface RenderPathNode {\n /** Component name (data-component prop, or `${hostType}#${testID|id}`, or hostType) */\n name: string\n /** AgNode host type (silvery-box, silvery-text, silvery-root) */\n type: AgNodeType\n /** Optional content-relative position from layout phase */\n boxRect?: { x: number; y: number; width: number; height: number }\n}\n\n/** Recursive mount tree — name + children for snapshot-friendly dumps. */\nexport interface MountTree extends RenderPathNode {\n children: MountTree[]\n}\n\nfunction hostTypeLabel(type: AgNodeType): string {\n if (type === \"silvery-box\") return \"Box\"\n if (type === \"silvery-text\") return \"Text\"\n return type\n}\n\n/**\n * Resolve a node to its display name.\n * Mirrors `getDebugComponentName` in reconciler/host-config.ts (kept in\n * sync; if you change one, update the other).\n */\nexport function getComponentName(node: AgNode): string {\n const props = node.props as Record<string, unknown>\n const explicit = props[\"data-component\"]\n if (typeof explicit === \"string\" && explicit.length > 0) return explicit\n const base = hostTypeLabel(node.type)\n const testID = props.testID\n if (typeof testID === \"string\" && testID.length > 0) return `${base}#${testID}`\n const id = props.id\n if (typeof id === \"string\" && id.length > 0) return `${base}#${id}`\n return base\n}\n\nfunction summarize(node: AgNode): RenderPathNode {\n const out: RenderPathNode = { name: getComponentName(node), type: node.type }\n if (node.boxRect) {\n out.boxRect = {\n x: node.boxRect.x,\n y: node.boxRect.y,\n width: node.boxRect.width,\n height: node.boxRect.height,\n }\n }\n return out\n}\n\n/**\n * Find every AgNode whose component name matches `componentName`.\n * Match is exact string equality on `getComponentName(node)`.\n *\n * Walks the tree rooted at `root`. Returns nodes in tree order (DFS pre-order).\n */\nexport function findNodesByComponentName(root: AgNode, componentName: string): AgNode[] {\n const out: AgNode[] = []\n function walk(node: AgNode): void {\n if (getComponentName(node) === componentName) out.push(node)\n for (const child of node.children) walk(child)\n }\n walk(root)\n return out\n}\n\n/**\n * Return the parent chain from `root` down to the first node matching\n * `componentName`. Each element is a `RenderPathNode` summary.\n *\n * Returns `[]` if no match is found.\n *\n * If multiple nodes match, returns the path to the FIRST in tree order.\n * Use `findNodesByComponentName` to inspect all matches.\n */\nexport function getRenderPath(root: AgNode, componentName: string): RenderPathNode[] {\n function walk(node: AgNode, chain: AgNode[]): AgNode[] | null {\n const next = [...chain, node]\n if (getComponentName(node) === componentName) return next\n for (const child of node.children) {\n const hit = walk(child, next)\n if (hit) return hit\n }\n return null\n }\n const hit = walk(root, [])\n return hit ? hit.map(summarize) : []\n}\n\n/**\n * Recursive JSON dump of the entire mount tree rooted at `root`.\n *\n * Useful for snapshot tests asserting on structural invariants —\n * e.g., \"ToolBlock is always wrapped in a Content.Body[width=full]\".\n */\nexport function getMountTree(root: AgNode): MountTree {\n return {\n ...summarize(root),\n children: root.children.map(getMountTree),\n }\n}\n\n/**\n * Format a render-path chain as a human-readable string. One-liner\n * suitable for console.log or debug-log output.\n *\n * Example: `Root > Content > MeasuredLayoutProbe > ChatBlockList > ToolBlock`\n */\nexport function formatRenderPath(path: RenderPathNode[]): string {\n return path.map((n) => n.name).join(\" > \")\n}\n\n/**\n * Convenience: log the render path of `componentName` to console.\n * Equivalent to `console.log(formatRenderPath(getRenderPath(root, componentName)))`.\n */\nexport function printRenderPath(root: AgNode, componentName: string): void {\n const path = getRenderPath(root, componentName)\n if (path.length === 0) {\n console.log(`[render-path] ${componentName}: NOT FOUND`)\n return\n }\n console.log(`[render-path] ${formatRenderPath(path)}`)\n}\n","/**\n * App - Unified render API for silvery\n *\n * Both production and testing return an App instance with the same interface.\n * Key improvements over the old API:\n * - Auto-refreshing locators (no stale locator problem)\n * - Playwright-style API (app.press(), app.getByTestId())\n * - Bound terminal (app.term) with node awareness\n *\n * @example\n * ```tsx\n * // Both production and testing\n * const app = await render(<App />, term)\n *\n * // Query and interact\n * app.text // rendered text (no ANSI)\n * app.getByTestId('modal') // auto-refreshing locator\n * await app.press('ArrowUp') // send key\n * await app.waitUntilExit() // wait until exit\n *\n * // Terminal access\n * app.term.cell(x, y) // { char, fg, bg, attrs }\n * app.term.nodeAt(x, y) // node at screen coords\n * ```\n */\n\nimport type { ReactNode } from \"react\"\nimport { type AutoLocator, createAutoLocator } from \"@silvery/test/auto-locator\"\nimport { type BoundTerm, createBoundTerm } from \"./bound-term\"\nimport type { TerminalBuffer } from \"./buffer\"\nimport {\n bufferToHTML,\n bufferToStyledText,\n bufferToText,\n cellToFrameCell,\n EMPTY_FRAME_CELL,\n} from \"./buffer\"\nimport { type Screenshotter, createScreenshotter } from \"./screenshot\"\nimport { keyToAnsi, keyToKittyAnsi, parseHotkey } from \"@silvery/ag/keys\"\nimport { findActiveCursorRect } from \"@silvery/ag/layout-signals\"\nimport { updateKeyboardModifiers } from \"./mouse-events\"\nimport type { ParsedMouse } from \"./mouse\"\nimport { createMouseEventProcessor, processMouseEvent } from \"./mouse-events\"\nimport type { FocusManager } from \"@silvery/ag/focus-manager\"\nimport { pointInRect } from \"@silvery/ag/tree-utils\"\nimport type { AgNode } from \"@silvery/ag/types\"\nimport type { FrameCell } from \"@silvery/ag/text-frame\"\nimport type { CLSReport, ReasonClassifier } from \"@silvery/ag/cls\"\nimport type { ClsMonitor } from \"./runtime/cls-monitor\"\nimport { getRenderPath, getMountTree } from \"@silvery/ag-react/debug/render-path\"\n\n/**\n * App interface - unified return type from render()\n */\n/**\n * Fluent chain of App actions. Each action method returns `ChainableApp`\n * — a `PromiseLike<App>` that exposes the same action methods so calls\n * can be composed without explicit `await` between every step.\n *\n * ```ts\n * await app.keyDown(\"Super\").hover(x, y).keyUp(\"Super\")\n * ```\n *\n * The returned chain `.then`-s into the App, so existing `await\n * app.action(...)` calls continue to receive the App instance — the\n * change is purely additive at the call site.\n *\n * Bead: @km/silvery/fluent-chain-actions.\n */\nexport interface ChainableApp extends PromiseLike<App> {\n press(key: string): ChainableApp\n keyDown(key: string): ChainableApp\n keyUp(key: string): ChainableApp\n pressSequence(...keys: string[]): ChainableApp\n type(text: string): ChainableApp\n click(\n x: number,\n y: number,\n options?: { button?: number; shift?: boolean; meta?: boolean; ctrl?: boolean; cmd?: boolean },\n ): ChainableApp\n doubleClick(\n x: number,\n y: number,\n options?: { button?: number; shift?: boolean; meta?: boolean; ctrl?: boolean; cmd?: boolean },\n ): ChainableApp\n hover(\n x: number,\n y: number,\n options?: { shift?: boolean; meta?: boolean; ctrl?: boolean; cmd?: boolean },\n ): ChainableApp\n wheel(\n x: number,\n y: number,\n delta: number,\n options?: { shift?: boolean; meta?: boolean; ctrl?: boolean; cmd?: boolean },\n ): ChainableApp\n}\n\nexport interface App {\n // === Content/Document Perspective ===\n\n /** Full rendered text (no ANSI codes) */\n readonly text: string\n\n /** Full rendered text with ANSI styling */\n readonly ansi: string\n\n /** Per-line plain text array (no ANSI codes) */\n readonly lines: string[]\n\n /** Frame width in terminal columns */\n readonly width: number\n\n /** Frame height in terminal rows */\n readonly height: number\n\n /** Get the cell at the given column and row (resolved styling) */\n cell(col: number, row: number): FrameCell\n\n /** Check whether the plain text contains the given substring */\n containsText(text: string): boolean\n\n /** Get node at content coordinates */\n nodeAt(x: number, y: number): AgNode | null\n\n /** Get locator by testID attribute */\n getByTestId(id: string): AutoLocator\n\n /** Get locator by text content */\n getByText(text: string | RegExp): AutoLocator\n\n /** Get locator by CSS-style selector */\n locator(selector: string): AutoLocator\n\n // === Actions (return ChainableApp for fluent composition) ===\n\n /**\n * Send a single key press. Emits the bare Kitty CSI u shape\n * (no `:eventType` byte) — the parser defaults to \"press\" without\n * one. Equivalent to a full down+up keystroke for tests asserting\n * \"user typed key X\". For held-state scenarios (Cmd-hover popovers,\n * multi-key chords) use `keyDown` / `keyUp` which emit explicit\n * `:1` (press) and `:3` (release) event-type bytes.\n *\n * Both bare-press and explicit-`:1` shapes parse to\n * `{ eventType: \"press\" | undefined }` respectively; consumers that\n * test `eventType !== \"release\"` see them identically. The shape\n * difference is intentional: bare = transient single event, typed =\n * paired down/up lifecycle. Bead:\n * @km/silvery/keydown-keyup-test-primitives.\n */\n press(key: string): ChainableApp\n\n /**\n * Send a key DOWN event without auto-release. Useful for held-modifier\n * scenarios — e.g., `app.keyDown(\"Super\")` then `app.hover(x, y)` then\n * `app.keyUp(\"Super\")` to drive a Cmd-hover popover that opens after a\n * dwell timer reads the (still-held) modifier from the input store.\n *\n * Requires Kitty keyboard protocol (`kittyMode: true`) for modifier keys —\n * legacy ANSI cannot represent Super (Cmd) alone.\n *\n * For a single press+release event (the common case) use `press()`. Bead:\n * @km/silvery/keydown-keyup-test-primitives.\n */\n keyDown(key: string): ChainableApp\n\n /**\n * Send a key UP event. Pairs with `keyDown(key)`. Drops the implicit\n * modifier state (mouseState.keyboardModifiers + the input-store\n * modifier tracker) so subsequent events run without the modifier\n * asserted. Bead: @km/silvery/keydown-keyup-test-primitives.\n */\n keyUp(key: string): ChainableApp\n\n /** Send multiple key presses */\n pressSequence(...keys: string[]): ChainableApp\n\n /** Type text input */\n type(text: string): ChainableApp\n\n /** Simulate a mouse click at (x, y) terminal coordinates */\n click(\n x: number,\n y: number,\n options?: { button?: number; shift?: boolean; meta?: boolean; ctrl?: boolean; cmd?: boolean },\n ): ChainableApp\n\n /** Simulate a double-click at (x, y) terminal coordinates */\n doubleClick(\n x: number,\n y: number,\n options?: { button?: number; shift?: boolean; meta?: boolean; ctrl?: boolean; cmd?: boolean },\n ): ChainableApp\n\n /** Simulate a mouse move/hover at (x, y) terminal coordinates */\n hover(\n x: number,\n y: number,\n options?: { shift?: boolean; meta?: boolean; ctrl?: boolean; cmd?: boolean },\n ): ChainableApp\n\n /** Simulate a mouse wheel event at (x, y) with delta (-1=up, +1=down) */\n wheel(\n x: number,\n y: number,\n delta: number,\n options?: { shift?: boolean; meta?: boolean; ctrl?: boolean; cmd?: boolean },\n ): ChainableApp\n\n /** Resize the virtual terminal and re-render. Only available in test renderer. */\n resize(cols: number, rows: number): void\n\n /** Wait until app exits */\n run(): Promise<void>\n\n // === Terminal Binding ===\n\n /** Bound terminal for screen-space access */\n readonly term: BoundTerm\n\n // === Lifecycle (Instance compatibility) ===\n\n /** Re-render with a new element */\n rerender(element: ReactNode): void\n\n /** Unmount the component and clean up */\n unmount(): void\n\n /** Dispose (alias for unmount) — enables `using` */\n [Symbol.dispose](): void\n\n /** Promise that resolves when the app exits (alias for run()) */\n waitUntilExit(): Promise<void>\n\n /**\n * Drain additional commit / layout cycles until layout reports stable\n * (no node dirty, no pending React commit) OR a budget cap is reached\n * (default 20 passes / 50ms wall clock).\n *\n * The default test harness exposes the post-`MAX_CONVERGENCE_PASSES`\n * frame — matching what production silvery commits on first paint.\n * Most tests don't need to wait further: keyboard / mouse input each\n * runs its own bounded-convergence loop, so an assertion after `press()`\n * already sees post-convergence state for that batch.\n *\n * This method is for assertions on layout state IMMEDIATELY after mount\n * that require chains needing more than `MAX_CONVERGENCE_PASSES` passes\n * to settle (rare with the layout-signals primitive, common with older\n * useState+onLayout chains). Returns once stable so tests can assert\n * post-convergence text/layout.\n *\n * Resolves without throwing even when the cap is hit — an infinitely\n * non-converging app is a structural bug surfaced by SILVERY_STRICT's\n * `assertBoundedConvergence`, not a test-author concern here.\n *\n * Bead: `@km/silvery/test-harness-convergence-cap-parity`.\n */\n waitForLayoutStable(opts?: { timeoutMs?: number; maxPasses?: number }): Promise<void>\n\n // === CLS (Cumulative Layout Shift) capture ===\n\n /**\n * Begin a CLS capture window. Layout shifts that occur between this call\n * and the next `endCLSCapture()` are recorded with the supplied\n * `ReasonClassifier` (default: every shift labeled \"unexpected\"). Use\n * `waitForLayoutStable()` to drain pending layout passes before reading\n * the report.\n *\n * Throws if a capture is already active — call `endCLSCapture()` or\n * `cancelCLSCapture()` first. The capture is process-wide (single active\n * recorder per process); two parallel captures on different App\n * instances is not supported by design.\n *\n * Bead: km-silvery.cls-instrumentation-primitive\n */\n beginCLSCapture(classifier?: ReasonClassifier): void\n\n /**\n * End the active CLS capture and return the aggregated report. When\n * `SILVERY_STRICT=cls` (or tier 2+) is enabled, throws\n * `UnexpectedLayoutShiftError` if any shift has reflowReason=\"unexpected\"\n * — failing-fast under STRICT lets close-gate tests skip explicit\n * assertions and rely on the umbrella env var.\n *\n * Throws if no capture is active.\n *\n * Bead: km-silvery.cls-instrumentation-primitive\n */\n endCLSCapture(): import(\"@silvery/ag/cls\").CLSReport\n\n /**\n * Cancel the active CLS capture without producing a report. Idempotent\n * — safe to call when no capture is active. Used by test-cleanup paths\n * that need to bail without asserting.\n *\n * Bead: km-silvery.cls-instrumentation-primitive\n */\n cancelCLSCapture(): void\n\n /** Clear the terminal output */\n clear(): void\n\n // === Screenshot ===\n\n /** Render current buffer to PNG. Requires Playwright (lazy-loaded on first call). */\n screenshot(outputPath?: string): Promise<Buffer>\n\n // === Debug ===\n\n /** Print component tree to console */\n debug(): void\n\n // === Testing extras ===\n\n /** Render the current tree from scratch (no incremental buffer reuse).\n * Returns the fresh buffer without updating incremental state.\n * Only available in test renderer - throws otherwise. */\n freshRender(): TerminalBuffer\n\n /** Check if exit() was called */\n exitCalled(): boolean\n\n /** Get error passed to exit() */\n exitError(): Error | undefined\n\n /** Send raw stdin input (for sync test helpers; prefer app.press() for new code) */\n readonly stdin: { write: (data: string) => void }\n\n // === Internal/Legacy (kept for silvery test compatibility, not for external use) ===\n\n /** All rendered frames (internal) */\n readonly frames: string[]\n\n /** Get last frame with ANSI codes (internal - use app.ansi instead) */\n lastFrame(): string | undefined\n\n /** Get last buffer (internal - use app.term.buffer instead) */\n lastBuffer(): TerminalBuffer | undefined\n\n /** Get last frame as plain text (internal - use app.text instead) */\n lastFrameText(): string | undefined\n\n /** Get container root node (internal - use app.locator() instead) */\n getContainer(): AgNode\n\n // === Focus System ===\n\n /** Focus a node by testID */\n focus(testID: string): void\n\n /** Get the focus path from focused node to root (testID[]) */\n getFocusPath(): string[]\n\n /** Direct access to the FocusManager instance */\n readonly focusManager: FocusManager\n\n // === Cursor State ===\n\n /** Get the current cursor state for this silvery instance (per-instance, not global). */\n getCursorState(): import(\"@silvery/ag-react/hooks/useCursor\").CursorState | null\n\n // === Debug (render-path / mount-tree) ===\n\n /**\n * Return the parent chain from the container root down to the first\n * AgNode whose component name matches `componentName`. Empty array if\n * not found. Useful for asserting structural invariants in tests and\n * for bead-investigation static traces (\"does ToolBlock actually\n * render inside Content.Body[width=full]?\").\n *\n * See `@silvery/ag-react/debug/render-path` for the underlying API.\n */\n renderPath(componentName: string): import(\"@silvery/ag-react/debug/render-path\").RenderPathNode[]\n\n /**\n * Recursive JSON dump of the entire mount tree. Useful for snapshot\n * tests asserting on structural invariants.\n */\n mountTree(): import(\"@silvery/ag-react/debug/render-path\").MountTree\n}\n\n/**\n * Options for creating an App instance\n */\nexport interface AppOptions {\n /** Function to get current container root */\n getContainer: () => AgNode\n\n /** Function to get current buffer */\n getBuffer: () => TerminalBuffer | null\n\n /** Function to send input */\n sendInput: (data: string) => void\n\n /** Function to rerender */\n rerender: (element: ReactNode) => void\n\n /** Function to unmount */\n unmount: () => void\n\n /** Function to wait for exit */\n waitUntilExit: () => Promise<void>\n\n /** Function to clear output */\n clear: () => void\n\n /** Function to check if exit was called */\n exitCalled?: () => boolean\n\n /** Function to get exit error */\n exitError?: () => Error | undefined\n\n /** Fresh render function (test renderer only) */\n freshRender?: () => TerminalBuffer\n\n /** Debug print function */\n debugFn?: () => void\n\n /** Captured frames array (internal) */\n frames?: string[]\n\n /**\n * Optional ClsMonitor instance. When provided, the App's\n * `beginCLSCapture` / `endCLSCapture` / `cancelCLSCapture` methods\n * delegate to it. The host harness is responsible for instantiating\n * the monitor AND calling `clsMonitor.onCommit(...)` on every render\n * commit so the monitor's session-shift buffer stays in sync with\n * the rendered tree (Option C consolidation: cls-monitor is the\n * single source of truth for layout-shift detection).\n *\n * Bead: @km/silvery/cls-instrumentation-primitive (Phase 9/11).\n */\n clsMonitor?: ClsMonitor\n\n /** Terminal dimensions */\n columns: number\n rows: number\n\n /** Use Kitty keyboard protocol encoding for press(). When true, press() uses keyToKittyAnsi. */\n kittyMode?: boolean\n\n /** Wrap a callback in act() + doRender() for the test renderer. Ensures React state updates from mouse handlers are flushed. */\n actAndRender?: (fn: () => void) => void\n\n /** Resize the virtual terminal (test renderer only). */\n resize?: (cols: number, rows: number) => void\n\n /** Focus manager instance for focus system */\n focusManager?: FocusManager\n\n /** Per-instance cursor state accessor */\n getCursorState?: () => import(\"@silvery/ag-react/hooks/useCursor\").CursorState | null\n\n /**\n * Drain commit / layout cycles until stable (test renderer only).\n * See {@link App.waitForLayoutStable} for the contract.\n */\n waitForLayoutStable?: (opts?: { timeoutMs?: number; maxPasses?: number }) => Promise<void>\n}\n\n/**\n * Create an App instance\n */\nexport function buildApp(options: AppOptions): App {\n const {\n getContainer,\n getBuffer,\n sendInput,\n rerender,\n unmount,\n waitUntilExit,\n clear,\n exitCalled = () => false,\n exitError = () => undefined,\n freshRender: freshRenderFn,\n debugFn,\n frames = [],\n columns,\n rows,\n kittyMode = false,\n actAndRender,\n resize: resizeFn,\n focusManager: fm,\n waitForLayoutStable: waitForLayoutStableFn,\n clsMonitor,\n } = options\n\n // Create auto-refreshing locator factory\n const createLocator = () => createAutoLocator(getContainer)\n\n // Create bound terminal\n const getText = () => {\n const buffer = getBuffer()\n return buffer ? bufferToText(buffer) : \"\"\n }\n\n // Note: BoundTerm is created lazily since buffer may not exist initially\n let boundTerm: BoundTerm | null = null\n\n // Mouse event processor for click/doubleClick/wheel\n const mouseState = createMouseEventProcessor()\n\n // Screenshotter is created lazily on first screenshot() call\n let screenshotter: Screenshotter | null = null\n\n const app: App = {\n // === Content/Document Perspective ===\n\n get text(): string {\n return getText()\n },\n\n get ansi(): string {\n const buffer = getBuffer()\n return buffer ? bufferToStyledText(buffer) : \"\"\n },\n\n get lines(): string[] {\n return getText().split(\"\\n\")\n },\n\n get width(): number {\n return getBuffer()?.width ?? columns\n },\n\n get height(): number {\n return getBuffer()?.height ?? rows\n },\n\n cell(col: number, row: number): FrameCell {\n const buffer = getBuffer()\n if (!buffer) return EMPTY_FRAME_CELL as FrameCell\n return cellToFrameCell(buffer.getCell(col, row))\n },\n\n containsText(text: string): boolean {\n return getText().includes(text)\n },\n\n nodeAt(x: number, y: number): AgNode | null {\n const root = getContainer()\n return findNodeAtContentPosition(root, x, y)\n },\n\n getByTestId(id: string): AutoLocator {\n return createLocator().getByTestId(id)\n },\n\n getByText(text: string | RegExp): AutoLocator {\n return createLocator().getByText(text)\n },\n\n locator(selector: string): AutoLocator {\n return createLocator().locator(selector)\n },\n\n // === Actions ===\n\n press(key: string): ChainableApp {\n return makeChain(\n (async (): Promise<App> => {\n const hotkey = parseHotkey(key)\n updateKeyboardModifiers(mouseState, {\n super: hotkey.super,\n hyper: hotkey.hyper,\n eventType: \"press\",\n })\n const sequence = kittyMode ? keyToKittyAnsi(key) : keyToAnsi(key)\n sendInput(sequence)\n await Promise.resolve()\n return app\n })(),\n )\n },\n\n keyDown(key: string): ChainableApp {\n return makeChain(\n (async (): Promise<App> => {\n const hotkey = parseHotkey(key)\n updateKeyboardModifiers(mouseState, {\n super: hotkey.super || keyIsModifier(key, \"Super\"),\n hyper: hotkey.hyper || keyIsModifier(key, \"Hyper\"),\n eventType: \"press\",\n })\n const baseSeq = kittyMode ? keyToKittyAnsi(key) : keyToAnsi(key)\n sendInput(injectKittyEventType(baseSeq, 1))\n await Promise.resolve()\n return app\n })(),\n )\n },\n\n keyUp(key: string): ChainableApp {\n return makeChain(\n (async (): Promise<App> => {\n const hotkey = parseHotkey(key)\n updateKeyboardModifiers(mouseState, {\n super: false,\n hyper: false,\n eventType: \"release\",\n })\n const baseSeq = kittyMode ? keyToKittyAnsi(key) : keyToAnsi(key)\n sendInput(injectKittyEventType(baseSeq, 3))\n void hotkey\n await Promise.resolve()\n return app\n })(),\n )\n },\n\n pressSequence(...keys: string[]): ChainableApp {\n return makeChain(\n (async (): Promise<App> => {\n for (const key of keys) {\n await app.press(key)\n }\n return app\n })(),\n )\n },\n\n type(text: string): ChainableApp {\n return makeChain(\n (async (): Promise<App> => {\n for (const char of text) {\n sendInput(char)\n }\n await Promise.resolve()\n return app\n })(),\n )\n },\n\n click(\n x: number,\n y: number,\n options?: { button?: number; shift?: boolean; meta?: boolean; ctrl?: boolean; cmd?: boolean },\n ): ChainableApp {\n return makeChain(\n (async (): Promise<App> => {\n const button = options?.button ?? 0\n // cmd is an alias for setting keyboard-tracked Super (Cmd on macOS)\n if (options?.cmd) mouseState.keyboardModifiers.super = true\n const doClick = () => {\n const parsed: ParsedMouse = {\n button,\n x,\n y,\n coordinateMode: \"cell\",\n action: \"down\",\n shift: options?.shift ?? false,\n meta: options?.meta ?? false,\n ctrl: options?.ctrl ?? false,\n }\n processMouseEvent(mouseState, parsed, getContainer())\n const upParsed: ParsedMouse = { ...parsed, action: \"up\" }\n processMouseEvent(mouseState, upParsed, getContainer())\n }\n if (actAndRender) {\n actAndRender(doClick)\n } else {\n doClick()\n }\n if (options?.cmd) mouseState.keyboardModifiers.super = false\n await Promise.resolve()\n return app\n })(),\n )\n },\n\n doubleClick(\n x: number,\n y: number,\n options?: { button?: number; shift?: boolean; meta?: boolean; ctrl?: boolean; cmd?: boolean },\n ): ChainableApp {\n return makeChain(\n (async (): Promise<App> => {\n const button = options?.button ?? 0\n if (options?.cmd) mouseState.keyboardModifiers.super = true\n const doDblClick = () => {\n const baseParsed: ParsedMouse = {\n button,\n x,\n y,\n coordinateMode: \"cell\",\n action: \"down\",\n shift: options?.shift ?? false,\n meta: options?.meta ?? false,\n ctrl: options?.ctrl ?? false,\n }\n processMouseEvent(mouseState, baseParsed, getContainer())\n processMouseEvent(mouseState, { ...baseParsed, action: \"up\" }, getContainer())\n processMouseEvent(mouseState, baseParsed, getContainer())\n processMouseEvent(mouseState, { ...baseParsed, action: \"up\" }, getContainer())\n }\n if (actAndRender) {\n actAndRender(doDblClick)\n } else {\n doDblClick()\n }\n if (options?.cmd) mouseState.keyboardModifiers.super = false\n await Promise.resolve()\n return app\n })(),\n )\n },\n\n hover(\n x: number,\n y: number,\n options?: { shift?: boolean; meta?: boolean; ctrl?: boolean; cmd?: boolean },\n ): ChainableApp {\n return makeChain(\n (async (): Promise<App> => {\n // cmd is an alias for setting keyboard-tracked Super (Cmd on macOS)\n // for the duration of the hover — same hack as click/doubleClick.\n // Bead: @km/silvery/hover-wheel-modifier-options-parity.\n if (options?.cmd) mouseState.keyboardModifiers.super = true\n const doHover = () => {\n const parsed: ParsedMouse = {\n button: 0,\n x,\n y,\n coordinateMode: \"cell\",\n action: \"move\",\n shift: options?.shift ?? false,\n meta: options?.meta ?? false,\n ctrl: options?.ctrl ?? false,\n }\n processMouseEvent(mouseState, parsed, getContainer())\n }\n if (actAndRender) {\n actAndRender(doHover)\n } else {\n doHover()\n }\n if (options?.cmd) mouseState.keyboardModifiers.super = false\n await Promise.resolve()\n return app\n })(),\n )\n },\n\n wheel(\n x: number,\n y: number,\n delta: number,\n options?: { shift?: boolean; meta?: boolean; ctrl?: boolean; cmd?: boolean },\n ): ChainableApp {\n return makeChain(\n (async (): Promise<App> => {\n // Bead: @km/silvery/hover-wheel-modifier-options-parity.\n if (options?.cmd) mouseState.keyboardModifiers.super = true\n const doWheel = () => {\n const parsed: ParsedMouse = {\n button: 0,\n x,\n y,\n coordinateMode: \"cell\",\n action: \"wheel\",\n delta,\n shift: options?.shift ?? false,\n meta: options?.meta ?? false,\n ctrl: options?.ctrl ?? false,\n }\n processMouseEvent(mouseState, parsed, getContainer())\n }\n if (actAndRender) {\n actAndRender(doWheel)\n } else {\n doWheel()\n }\n if (options?.cmd) mouseState.keyboardModifiers.super = false\n await Promise.resolve()\n return app\n })(),\n )\n },\n\n resize(cols: number, rows: number): void {\n if (!resizeFn) {\n throw new Error(\"resize() is only available in test renderer\")\n }\n resizeFn(cols, rows)\n },\n\n async run(): Promise<void> {\n return waitUntilExit()\n },\n\n // === Terminal Binding ===\n\n get term(): BoundTerm {\n const buffer = getBuffer()\n if (!buffer) {\n // Return a dummy bound term if no buffer yet\n const dummyBuffer = {\n width: columns,\n height: rows,\n getCell: () => ({\n char: \" \",\n fg: null,\n bg: null,\n attrs: {},\n wide: false,\n continuation: false,\n }),\n setCell: () => {},\n clear: () => {},\n inBounds: () => false,\n } as unknown as TerminalBuffer\n return createBoundTerm(dummyBuffer, getContainer, getText)\n }\n if (!boundTerm || boundTerm.buffer !== buffer) {\n boundTerm = createBoundTerm(buffer, getContainer, getText)\n }\n return boundTerm\n },\n\n // === Screenshot ===\n\n async screenshot(outputPath?: string): Promise<Buffer> {\n const buffer = getBuffer()\n if (!buffer) {\n throw new Error(\"No buffer available for screenshot\")\n }\n const html = bufferToHTML(buffer)\n if (!screenshotter) {\n screenshotter = createScreenshotter()\n }\n return screenshotter.capture(html, outputPath)\n },\n\n // === Lifecycle ===\n\n rerender,\n unmount() {\n // Close screenshotter if it was created\n if (screenshotter) {\n screenshotter.close().catch(() => {})\n screenshotter = null\n }\n unmount()\n },\n [Symbol.dispose]() {\n app.unmount()\n },\n waitUntilExit,\n waitForLayoutStable(opts?: { timeoutMs?: number; maxPasses?: number }): Promise<void> {\n if (!waitForLayoutStableFn) {\n return Promise.reject(\n new Error(\"waitForLayoutStable() is only available in the test renderer\"),\n )\n }\n return waitForLayoutStableFn(opts)\n },\n\n // CLS capture delegates to the harness-provided ClsMonitor (Option C\n // consolidation 2026-05-13). The host harness (renderer.ts /\n // create-app.tsx) owns the monitor's lifecycle AND calls\n // `clsMonitor.onCommit(...)` on every render commit so its session-\n // shift buffer stays in sync with the rendered tree. Reading\n // `screenRect` (post-scroll, sticky-aware) — the only domain that\n // catches user-visible flicker (scroll-induced + sticky shifts).\n //\n // Bead: @km/silvery/cls-instrumentation-primitive.\n beginCLSCapture(classifier?: ReasonClassifier): void {\n if (!clsMonitor) {\n throw new Error(\n \"beginCLSCapture: no ClsMonitor was provided to buildApp(). The render harness must instantiate `createClsMonitor()` and pass it via AppOptions.clsMonitor for CLS capture to work.\",\n )\n }\n clsMonitor.beginCapture(classifier)\n },\n endCLSCapture(): CLSReport {\n if (!clsMonitor) {\n throw new Error(\"endCLSCapture: no ClsMonitor was provided to buildApp().\")\n }\n // endCapture() applies SILVERY_STRICT=cls internally — throws\n // UnexpectedLayoutShiftError when tier 2 strict is active and the\n // window saw any \"unexpected\" shifts.\n return clsMonitor.endCapture()\n },\n cancelCLSCapture(): void {\n // Idempotent — safe to call even when no monitor is provided\n // (cleanup paths shouldn't fail loud when the App was built\n // without a monitor).\n clsMonitor?.cancelCapture()\n },\n\n clear,\n\n // === Debug ===\n\n debug(): void {\n if (debugFn) {\n debugFn()\n } else {\n console.log(app.text)\n }\n },\n\n // === Testing extras ===\n\n freshRender(): TerminalBuffer {\n if (!freshRenderFn) {\n throw new Error(\"freshRender() is only available in test renderer\")\n }\n return freshRenderFn()\n },\n\n exitCalled,\n exitError,\n\n stdin: {\n write: sendInput,\n },\n\n // Internal/Legacy (kept for silvery test compatibility)\n frames,\n\n lastFrame(): string | undefined {\n return frames[frames.length - 1]\n },\n\n lastBuffer(): TerminalBuffer | undefined {\n return getBuffer() ?? undefined\n },\n\n lastFrameText(): string | undefined {\n const buffer = getBuffer()\n return buffer ? bufferToText(buffer) : undefined\n },\n\n getContainer(): AgNode {\n return getContainer()\n },\n\n // === Focus System ===\n\n focus(testID: string): void {\n if (fm) {\n const root = getContainer()\n fm.focusById(testID, root, \"programmatic\")\n }\n },\n\n getFocusPath(): string[] {\n if (fm) {\n const root = getContainer()\n return fm.getFocusPath(root)\n }\n return []\n },\n\n get focusManager(): FocusManager {\n if (!fm) {\n throw new Error(\"FocusManager not available — pass focusManager to buildApp()\")\n }\n return fm\n },\n\n getCursorState() {\n // Layout-output cursor takes priority over the legacy store. After\n // Phase 2 of `km-silvery.view-as-layout-output`, TextArea / TextInput\n // declare cursor position as a Box prop (`cursorOffset`) which the\n // layout phase resolves into `LayoutSignals.cursorRect`. The store\n // remains as a fallback for Ink-compat consumers + the deprecated\n // `useCursor` hook.\n const root = getContainer()\n const layoutCursor = findActiveCursorRect(root)\n if (layoutCursor) {\n return {\n x: layoutCursor.x,\n y: layoutCursor.y,\n visible: layoutCursor.visible,\n shape: layoutCursor.shape,\n }\n }\n return options.getCursorState?.() ?? null\n },\n\n renderPath(componentName) {\n return getRenderPath(getContainer(), componentName)\n },\n\n mountTree() {\n return getMountTree(getContainer())\n },\n }\n\n return app\n}\n\n/**\n * Wrap a `Promise<App>` in a `ChainableApp` — a thenable that exposes\n * the action methods so consumers can compose without explicit `await`\n * between every step:\n *\n * await app.keyDown(\"Super\").hover(x, y).keyUp(\"Super\")\n *\n * Each chained method returns a new `ChainableApp` whose backing promise\n * `.then`s into the previous step + the new action. The terminal `await`\n * resolves the entire chain to the underlying `App`.\n *\n * Bead: @km/silvery/fluent-chain-actions.\n */\nfunction makeChain(p: Promise<App>): ChainableApp {\n return {\n press: (key) => makeChain(p.then((a) => a.press(key))),\n keyDown: (key) => makeChain(p.then((a) => a.keyDown(key))),\n keyUp: (key) => makeChain(p.then((a) => a.keyUp(key))),\n pressSequence: (...keys) => makeChain(p.then((a) => a.pressSequence(...keys))),\n type: (text) => makeChain(p.then((a) => a.type(text))),\n click: (x, y, options) => makeChain(p.then((a) => a.click(x, y, options))),\n doubleClick: (x, y, options) => makeChain(p.then((a) => a.doubleClick(x, y, options))),\n hover: (x, y, options) => makeChain(p.then((a) => a.hover(x, y, options))),\n wheel: (x, y, delta, options) => makeChain(p.then((a) => a.wheel(x, y, delta, options))),\n then: (onFulfilled, onRejected) => p.then(onFulfilled, onRejected),\n }\n}\n\n/**\n * Inject a Kitty CSI u event-type byte (`:1` press, `:2` repeat, `:3`\n * release) before the trailing terminator (`u`, `~`, or a CSI letter)\n * of a Kitty key sequence. Used by `keyDown`/`keyUp` to express held\n * vs. transient state — `keyToKittyAnsi` returns the bare-press shape\n * (no event-type byte), which the parser interprets as \"press\" by\n * default; explicit `:1`/`:3` lets tests drive paired down/up events.\n *\n * Examples:\n *\n * `\\x1b[57444;9u` → `\\x1b[57444;9:1u` (left-super press)\n * `\\x1b[57444;9u` → `\\x1b[57444;9:3u` (left-super release)\n * `\\x1b[1;5A` → `\\x1b[1;5:1A` (Ctrl+ArrowUp press)\n * `\\x1b[3;5~` → `\\x1b[3;5:1~` (Ctrl+Delete press)\n *\n * Sequences without a modifier byte (`\\x1b[CPu`) get a `;1:eventType`\n * insert: `\\x1b[57444u` → `\\x1b[57444;1:1u`. The base modifier is `1`\n * (no modifiers + 1 base) per the Kitty spec.\n *\n * Bead: @km/silvery/keydown-keyup-test-primitives.\n */\nfunction injectKittyEventType(seq: string, eventType: 1 | 2 | 3): string {\n // Match the trailing CSI terminator: `u`, `~`, or a single letter A-Z.\n // The byte before the terminator may be a digit (modifier) or the\n // codepoint itself when no modifier is present.\n // Case 1: trailing `;MOD{terminator}` → insert `:eventType` before terminator.\n const withMod = seq.replace(/(;[0-9]+)([A-Za-z~])$/, `$1:${eventType}$2`)\n if (withMod !== seq) return withMod\n // Case 2: trailing `[CP{terminator}` (no modifier byte) → insert `;1:eventType`.\n return seq.replace(/^(\\x1b\\[[0-9]+)([A-Za-z~])$/, `$1;1:${eventType}$2`)\n}\n\n/**\n * Detect whether a key string represents a specific bare modifier\n * (e.g., `keyIsModifier(\"Super\", \"Super\")` → true,\n * `keyIsModifier(\"leftsuper\", \"Super\")` → true,\n * `keyIsModifier(\"a\", \"Super\")` → false). Used by `keyDown`/`keyUp`\n * to update `mouseState.keyboardModifiers` even when the caller passes\n * a bare modifier name like \"Super\" rather than a chord like\n * \"Super+a\". `parseHotkey` only sets `.super` when the key is a chord\n * with Super as a modifier; it doesn't set it for the bare modifier\n * name itself.\n *\n * Bead: @km/silvery/keydown-keyup-test-primitives.\n */\nfunction keyIsModifier(key: string, target: \"Super\" | \"Hyper\"): boolean {\n // Strip any +-prefixed modifiers; we only look at the trailing key.\n const parts = key.split(\"+\")\n const last = parts[parts.length - 1]?.toLowerCase() ?? \"\"\n if (target === \"Super\") {\n return (\n last === \"super\" ||\n last === \"cmd\" ||\n last === \"command\" ||\n last === \"leftsuper\" ||\n last === \"rightsuper\"\n )\n }\n return last === \"hyper\" || last === \"lefthyper\" || last === \"righthyper\"\n}\n\n/**\n * Find node at content coordinates (not screen coordinates)\n */\nfunction findNodeAtContentPosition(node: AgNode, x: number, y: number): AgNode | null {\n const rect = node.boxRect\n if (!rect) return null\n\n if (!pointInRect(x, y, rect)) {\n return null\n }\n\n for (const child of node.children) {\n const found = findNodeAtContentPosition(child, x, y)\n if (found) return found\n }\n\n return node\n}\n","/**\n * STRICT residue invariant — sentinel-compare verification.\n *\n * Bead: @km/silvery/render-no-stale-residue-invariant (P1, 2026-05-05).\n *\n * The canonical incremental≡fresh comparison (run on every frame at\n * SILVERY_STRICT=1) catches divergences in CHANGED cells. It does NOT catch\n * the \"stale carry-over\" class of bug where a cell painted in frame N-1 is\n * left untouched in frame N because the incremental cascade thinks it's\n * clean — but the prev pixel happens to coincide with what fresh would\n * paint, so cell-equality holds. The cyan-strip residue saw exactly this\n * shape: a row of $bg-selected cells from a previous cursor position\n * survived a downstream re-render because the parent's cascade stopped\n * cascading clears.\n *\n * The sentinel-compare check exposes this class. The semantic in detail:\n *\n * 1. Snapshot the REAL prev buffer P.\n * 2. Clone P and POISON every cell with a color/char no theme produces:\n * rgb(254, 0, 254), char \"þ\" — call this P'.\n * 3. Run the regular incremental render against P'. Cells the cascade\n * skips retain the sentinel (they're cloned from P' and not\n * repainted). Cells the cascade repaints have fresh content. Call\n * this buffer I'.\n * 4. Run a fresh render from a zeroed buffer with all pipeline state\n * (postState, accumulators) reset → buffer F.\n * 5. INVARIANT: at every (x,y),\n * I'[x,y] is sentinel ⇒ P[x,y] == F[x,y]\n * (cascade skipped this cell ⇒ prev pixel matched what fresh\n * paints; the skip was correct).\n * Equivalently: at any cell where P[x,y] ≠ F[x,y], the cascade\n * MUST repaint and I'[x,y] MUST NOT be the sentinel.\n * 6. Pipeline-state contamination shows up as a non-sentinel cell in\n * I' that disagrees with F — both passes painted but disagreed\n * because cross-pass state leaked.\n *\n * Cost: O(width × height) extra memory for the poisoned clone + one\n * additional render-phase run per frame. Tier 2 (paranoid). Default\n * SILVERY_STRICT=1 does NOT enable this.\n *\n * Sentinel choice: rgb(254, 0, 254) \"þ\". Verified against all 84\n * shipped color schemes (`grep -rE '#[0-9A-Fa-f]{6}'` in\n * packages/theme/src/schemes/, 992 unique colors, no hit on `#fe00fe`).\n * `#FF00FF` would NOT be safe — `xtermColors` ships a brightMagenta of\n * `#FF00FF`. The \"þ\" character (LATIN SMALL LETTER THORN) is similarly\n * unlikely to appear in normal UI text.\n *\n * Why \"compare to F (fresh-from-zero)\" and NOT \"compare to incremental\n * with REAL prev\": at cells the cascade correctly skips, both renders\n * agree by definition (the comparison would be a no-op). The whole\n * point of comparing to F is to expose the asymmetry between \"cascade\n * skipped because prev was already right\" (I' = sentinel, F = correct)\n * and \"cascade skipped but prev was WRONG\" (I' = sentinel, F = correct,\n * but P ≠ F — the bug). Sentinel-poison is the discriminator.\n */\n\nimport type { TerminalBuffer } from \"../buffer.js\"\nimport { cellEquals } from \"../buffer.js\"\nimport { IncrementalRenderMismatchError } from \"../errors.js\"\nimport { isStrictEnabled } from \"../strict-mode.js\"\n\n/** Sentinel cell value used to poison the prev buffer.\n *\n * Every theme-shipped color was checked: `#FE00FE` (rgb 254, 0, 254) does\n * not appear in any of the 84 schemes in packages/theme/src/schemes/. The\n * neighbouring `#FF00FF` IS used by xtermColors (brightMagenta), so we\n * deliberately avoid it.\n *\n * The character \"þ\" (LATIN SMALL LETTER THORN, \"þ\") is non-ASCII and\n * does not appear in silvery built-in components or default theme text.\n */\nexport const RESIDUE_SENTINEL_CHAR = \"þ\"\nexport const RESIDUE_SENTINEL_RGB = { r: 254, g: 0, b: 254 } as const\n\n/** SILVERY_STRICT slug for the residue check. Tier 2 by design. */\nexport const RESIDUE_STRICT_SLUG = \"residue\"\nexport const RESIDUE_STRICT_MIN_TIER = 2\n\n/** Returns true when the residue check should fire. */\nexport function isResidueStrictEnabled(): boolean {\n return isStrictEnabled(RESIDUE_STRICT_SLUG, RESIDUE_STRICT_MIN_TIER)\n}\n\n/**\n * Fill every cell of `buffer` with the residue sentinel — a magenta-ish\n * RGB no theme uses + the thorn character \"þ\". This is a destructive\n * operation; callers must operate on a clone of the live prev buffer.\n */\nexport function poisonBufferWithSentinel(buffer: TerminalBuffer): void {\n buffer.fill(0, 0, buffer.width, buffer.height, {\n char: RESIDUE_SENTINEL_CHAR,\n fg: RESIDUE_SENTINEL_RGB,\n bg: RESIDUE_SENTINEL_RGB,\n wide: false,\n continuation: false,\n })\n}\n\n/** Result of the cell-by-cell sentinel comparison. */\nexport interface ResidueDivergence {\n x: number\n y: number\n /** The sentinel-cloned incremental render's cell. */\n incrChar: string\n incrFg: unknown\n incrBg: unknown\n /** The fresh-from-zero render's cell. */\n freshChar: string\n freshFg: unknown\n freshBg: unknown\n /** True when the incremental cell still carries the sentinel itself\n * (strongest signal — definitely a stale carry-over). */\n isSentinelLeak: boolean\n}\n\n/**\n * Compare a sentinel-poison-prev incremental render to a fresh render,\n * accounting for the legitimate \"cascade skipped this cell because prev\n * was already correct\" pattern.\n *\n * The check semantic in detail:\n *\n * 1. Real prev buffer P captures the pre-frame state (the buffer the\n * production incremental render correctly clones from).\n * 2. Poisoned prev P' = P with every cell replaced by sentinel.\n * 3. Incremental-with-poison buffer I' is what runPipeline produced\n * against P'. Cells the cascade skipped retain sentinel; cells it\n * repainted have fresh content.\n * 4. Fresh-from-zero buffer F is the all-paths-recomputed reference.\n * 5. INVARIANT: at every (x,y),\n * I'[x,y] is sentinel ⇒ P[x,y] === F[x,y]\n * (cascade skipped this cell ⇒ prev pixel was already what fresh\n * paints; no stale carry-over).\n * 6. Equivalently: at any cell where P[x,y] ≠ F[x,y], the incremental\n * cascade MUST repaint, and I'[x,y] ≠ sentinel.\n *\n * A violation = \"the cascade decided this cell is clean, but the real\n * prev had different content from what fresh paints\". That's exactly\n * the cyan-strip-residue class: prev had cyan bg at row 5; fresh paints\n * default bg; cascade skipped row 5; user sees cyan on a row that\n * should be default. Sentinel-poison reveals it because I' has the\n * sentinel where the cascade skipped, and F[x,y] ≠ P[x,y] at that cell.\n *\n * Pipeline-state contamination is detected as a side effect: if any\n * cross-pass state (postState / accumulators) leaks, I' diverges from\n * F at cells the sentinel doesn't cover, and `cellEquals` catches it.\n *\n * Throws on first divergence with a diagnostic identifying the verdict\n * (sentinel leak vs pipeline contamination), the cell coordinates, and\n * the value triple (P, I', F) for the offending cell.\n */\nexport function verifyNoResidueLeak(\n realPrevBuffer: TerminalBuffer,\n incrPoisonedBuffer: TerminalBuffer,\n freshBuffer: TerminalBuffer,\n frameCount: number,\n): void {\n const w = Math.min(realPrevBuffer.width, incrPoisonedBuffer.width, freshBuffer.width)\n const h = Math.min(realPrevBuffer.height, incrPoisonedBuffer.height, freshBuffer.height)\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const incr = incrPoisonedBuffer.getCell(x, y)\n const fresh = freshBuffer.getCell(x, y)\n\n // Identical incr vs fresh — no possible divergence to investigate\n // regardless of what prev held.\n if (cellEquals(incr, fresh)) continue\n\n const isSentinelLeak = isSentinelCell(incr)\n\n if (isSentinelLeak) {\n // Cascade skipped this cell. Look up real-prev content; if it\n // matches fresh, this is the legitimate \"skip because prev was\n // already correct\" case — NOT a residue bug. Skip silently.\n const prev = realPrevBuffer.getCell(x, y)\n if (cellEquals(prev, fresh)) continue\n // Otherwise: the cascade trusted a prev pixel that DOES NOT\n // match fresh. Real residue bug.\n const msg =\n `STRICT residue check (frame ${frameCount}): SENTINEL LEAK at (${x},${y})\\n` +\n ` real prev: char='${prev.char}' fg=${formatColor(prev.fg)} bg=${formatColor(prev.bg)}\\n` +\n ` incremental: char='${incr.char}' fg=${formatColor(incr.fg)} bg=${formatColor(incr.bg)} (sentinel — cascade skipped)\\n` +\n ` fresh: char='${fresh.char}' fg=${formatColor(fresh.fg)} bg=${formatColor(fresh.bg)}\\n` +\n ` Real prev ≠ fresh at this cell, but the incremental cascade decided\\n` +\n ` not to repaint it. The user sees a stale prev pixel where fresh\\n` +\n ` would have painted differently — the cyan-strip-residue class.\\n` +\n ` Likely root cause: missing dirty flag propagation, a skipBgFill\\n` +\n ` that should have been bgRefillNeeded, or a transparent-Box\\n` +\n ` ancestorCleared cascade gap.\\n` +\n ` Slug: SILVERY_STRICT=${RESIDUE_STRICT_SLUG} (tier ${RESIDUE_STRICT_MIN_TIER}+).\\n` +\n ` Per-test opt-out: SILVERY_STRICT=2,!${RESIDUE_STRICT_SLUG}.`\n throw new IncrementalRenderMismatchError(msg)\n }\n\n // Non-sentinel divergence: the cascade DID repaint this cell, but\n // the result differs from fresh. This is pipeline-state\n // contamination — the cross-pass postState / scroll-offset /\n // outlineSnapshots leaked between the incremental run and the\n // fresh-render baseline. Always a real bug.\n const msg =\n `STRICT residue check (frame ${frameCount}): pipeline-state contamination at (${x},${y})\\n` +\n ` incremental: char='${incr.char}' fg=${formatColor(incr.fg)} bg=${formatColor(incr.bg)} (repainted)\\n` +\n ` fresh: char='${fresh.char}' fg=${formatColor(fresh.fg)} bg=${formatColor(fresh.bg)}\\n` +\n ` Both passes painted this cell but disagreed. Cross-pass state\\n` +\n ` (postState / scrollOffset / outlineSnapshots) is leaking between\\n` +\n ` the sentinel-poisoned incremental render and the fresh baseline.\\n` +\n ` Slug: SILVERY_STRICT=${RESIDUE_STRICT_SLUG} (tier ${RESIDUE_STRICT_MIN_TIER}+).\\n` +\n ` Per-test opt-out: SILVERY_STRICT=2,!${RESIDUE_STRICT_SLUG}.`\n throw new IncrementalRenderMismatchError(msg)\n }\n }\n}\n\n/** True when a cell still carries the residue sentinel. */\nfunction isSentinelCell(c: { char: string; fg: unknown; bg: unknown }): boolean {\n if (c.char !== RESIDUE_SENTINEL_CHAR) return false\n const bg = c.bg\n if (typeof bg !== \"object\" || bg === null) return false\n const o = bg as { r: number; g: number; b: number }\n return (\n o.r === RESIDUE_SENTINEL_RGB.r &&\n o.g === RESIDUE_SENTINEL_RGB.g &&\n o.b === RESIDUE_SENTINEL_RGB.b\n )\n}\n\nfunction formatColor(c: unknown): string {\n if (c === null || c === undefined) return \"default\"\n if (typeof c === \"number\") return `${c}`\n if (typeof c === \"object\") {\n const o = c as { r: number; g: number; b: number }\n return `rgb(${o.r},${o.g},${o.b})`\n }\n return String(c)\n}\n","/**\n * Unified Render API for silvery\n *\n * Composable primitives:\n * - render(element, opts | store) — always sync, returns full App\n * - createStore(providers) — flattens providers into { cols, rows, events() }\n * - run(app, events?) — event loop driver (sync or async)\n * - createApp(element, providers) — sugar: render + createStore + run\n * - createRenderer(opts | store) — factory with auto-cleanup\n */\n\nimport { EventEmitter } from \"node:events\"\nimport React, { type ReactElement, type ReactNode } from \"react\"\n// React's `act` exists only in dev/test builds — production React omits the\n// export, and a static named import fails to LINK in a NODE_ENV=production\n// host (km 19841). The renderer's act-wrapping matters only on test paths\n// (where dev React is loaded and provides the real act); under production\n// React, fall back to direct invocation — updateContainerSync /\n// flushSyncWork still run synchronously, and passive-effect flushing is\n// handled by the renderer's own settle/convergence passes.\ntype ActCallback = (cb: () => void) => void\nconst act: ActCallback = (React as unknown as { act?: ActCallback }).act ?? ((cb) => cb())\nimport { type App, buildApp } from \"./app.js\"\nimport { createClsMonitor } from \"./runtime/cls-monitor.js\"\nimport { type TerminalBuffer, cellEquals } from \"./buffer.js\"\nimport {\n ChainAppContext,\n FocusManagerContext,\n RuntimeContext,\n type RuntimeContextValue,\n StdoutContext,\n TermContext,\n} from \"@silvery/ag-react/context\"\nimport { createChildApp, toChainAppContextValue } from \"@silvery/ag-react/chain-bridge\"\nimport { createFocusManager } from \"@silvery/ag/focus-manager\"\nimport {\n type LayoutEngine,\n ensureDefaultLayoutEngine,\n isLayoutEngineInitialized,\n setLayoutEngine,\n} from \"./layout-engine.js\"\nimport { createAg, createRenderPostState, type RenderPostState } from \"./ag.js\"\nimport { commitLayoutSnapshot } from \"@silvery/ag/layout-signals\"\nimport { isStrictEnabled } from \"./strict-mode.js\"\nimport {\n isResidueStrictEnabled,\n poisonBufferWithSentinel,\n verifyNoResidueLeak,\n} from \"./pipeline/strict-residue.js\"\nimport { outputPhase } from \"./pipeline/output-phase.js\"\nimport {\n markLayoutTreeDirty,\n FRESH_LAYOUT_STRICT_SLUG,\n FRESH_LAYOUT_STRICT_MIN_TIER,\n} from \"./pipeline/layout-phase.js\"\nimport {\n CURSOR_SAVE as _CURSOR_SAVE,\n CURSOR_RESTORE as _CURSOR_RESTORE,\n kittyDeleteAllScrimPlacements as _kittyDeleteAllScrimPlacements,\n} from \"@silvery/ansi\"\nimport {\n createContainer,\n createFiberRoot,\n getContainerRoot,\n reconciler,\n releaseContainer,\n setContainerNodeLifecycle,\n} from \"@silvery/ag-react/reconciler\"\nimport { ScopeProvider } from \"@silvery/ag-react/ScopeProvider\"\nimport { createScope, reportDisposeError } from \"@silvery/scope\"\n\nimport { isProtocolError } from \"@silvery/ansi\"\nimport { createTerm } from \"./ansi/index\"\nimport { bufferToText } from \"./buffer.js\"\nimport { buildMismatchContext, formatMismatchContext } from \"@silvery/test/debug-mismatch\"\nimport { createCursorStore, CursorProvider } from \"@silvery/ag-react/hooks/useCursor\"\nimport { keyToAnsi, parseKey, splitRawInput } from \"@silvery/ag/keys\"\nimport { parseBracketedPaste } from \"./bracketed-paste\"\nimport { IncrementalRenderMismatchError } from \"./scheduler.js\"\nimport type { RenderPhaseStats } from \"./pipeline/types\"\nimport { debugTree } from \"@silvery/test/debug\"\nimport { createLogger } from \"loggily\"\nimport {\n beginConvergenceLoop,\n beginPass,\n notePassCommit,\n logPass,\n recordPassRing,\n assertBoundedConvergence,\n MAX_CONVERGENCE_PASSES,\n INSTRUMENT,\n} from \"./runtime/pass-cause\"\nimport { ALL_RECONCILER_BITS, getRenderEpoch, isAnyDirty } from \"@silvery/ag/epoch\"\n// Side-effect import: arms `@silvery/ag`'s wrap-measurer registry with the\n// terminal grapheme-aware adapter. Importing renderer.ts (via createRenderer\n// in tests, or run() in production) now means soft-wrap-aware\n// computeSelectionFragments without further wiring. See bead\n// km-silvery.softwrap-selection-fragments.\nimport \"./runtime/wrap-measurer-registration\"\n\nconst log = createLogger(\"silvery:render\")\n\nfunction invalidateRootAfterLayoutFeedback(root: ReturnType<typeof getContainerRoot>): void {\n root.dirtyBits = ALL_RECONCILER_BITS\n root.dirtyEpoch = getRenderEpoch()\n}\n\n// ============================================================================\n// Defensive Guards\n// ============================================================================\n\n/**\n * Track all active (mounted) render instances to detect leaks.\n * Uses a Set of WeakRefs so GC can clean up unreferenced apps.\n */\nconst activeRenders = new Set<WeakRef<{ unmount: () => void; id: number }>>()\nlet renderIdCounter = 0\n\n/**\n * Maximum number of active render instances before throwing.\n * Set high to allow large test files (each test may create a render without unmount),\n * but catch genuine leaks like infinite loops creating renders.\n */\nconst ACTIVE_RENDER_LEAK_THRESHOLD = 1000\n\n/**\n * Prune GC'd entries from activeRenders and return live count.\n */\nfunction pruneAndCountActiveRenders(): number {\n let count = 0\n for (const ref of activeRenders) {\n if (ref.deref() === undefined) {\n activeRenders.delete(ref)\n } else {\n count++\n }\n }\n return count\n}\n\n/**\n * Assert that the layout engine is initialized before rendering.\n * This catches the common mistake of calling render() without await ensureEngine().\n */\nfunction assertLayoutEngine(): void {\n if (!isLayoutEngineInitialized()) {\n throw new Error(\n \"silvery: Layout engine not initialized. \" +\n \"Call `await ensureEngine()` before render(), or use the testing module \" +\n \"which initializes it automatically via top-level await.\",\n )\n }\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Options for headless render (no terminal).\n */\nexport interface RenderOptions {\n /** Terminal width for layout. Default: 80 */\n cols?: number\n /** Terminal height for layout. Default: 24 */\n rows?: number\n /** Layout engine to use. Default: current global engine */\n layoutEngine?: LayoutEngine\n /** Enable debug output. Default: false */\n debug?: boolean\n /** Enable incremental rendering. Default: true */\n incremental?: boolean\n /** Use Kitty keyboard protocol encoding for press(). When true, press() uses keyToKittyAnsi. */\n kittyMode?: boolean\n /**\n * Handle Tab / Shift+Tab as focus-cycling keystrokes in `sendInput`/`press`.\n * When `false`, Tab and Shift+Tab reach `useInput` instead of being consumed\n * by the focus manager — matching the production `run()` option of the same\n * name (Claude-Code-style \"Shift+Tab cycles permission mode\" apps).\n *\n * Default: true.\n */\n handleTabCycling?: boolean\n /**\n * Maximum layout-pass iterations per render. Each pass: runPipeline +\n * flushSyncWork. Loop exits when no React commit happens (converged) or\n * when the cap is reached.\n *\n * Default: `MAX_CONVERGENCE_PASSES` (2 — production-derived structural\n * bound: 1 initial + 1 settle for measurement feedback).\n *\n * Tests that depend on multi-iteration stabilization (e.g. legacy\n * scrollable-list virtualization that previously relied on the classic\n * 5-iteration loop) can opt into a higher cap via `maxLayoutPasses: 5`.\n *\n * See pass-cause.ts:MAX_CONVERGENCE_PASSES for the structural derivation\n * and bead `@km/silvery/renderer-convergence-by-design` for the convergence\n * theorem.\n */\n maxLayoutPasses?: number\n /**\n * Auto-render on async React commits (e.g., setTimeout → setState).\n *\n * When true, the renderer schedules a microtask re-render whenever React\n * commits new work outside of explicit render/sendInput/rerender calls.\n * This enables test components with async state updates to produce new\n * frames automatically.\n *\n * Default: false (renderer only renders on explicit triggers).\n */\n autoRender?: boolean\n /**\n * Callback fired after each frame render.\n *\n * Called with the frame output string and the underlying TerminalBuffer.\n * Fires after initial render, sendInput, rerender, and (if autoRender)\n * async state changes.\n */\n onFrame?: (frame: string, buffer: TerminalBuffer, contentHeight?: number) => void\n /**\n * Callback fired after each pipeline execution, before React effects flush.\n *\n * Called inside act() after runPipeline produces the buffer but before\n * useLayoutEffect/useEffect callbacks run. Use this to make pipeline output\n * available to effects (e.g., Ink compat debug mode where useStdout().write()\n * needs to replay the latest frame).\n */\n onBufferReady?: (frame: string, buffer: TerminalBuffer, contentHeight?: number) => void\n /**\n * Wrap the root element with additional providers.\n * Called with the element after silvery's internal contexts are applied.\n * Use this to inject additional context providers (e.g., Ink compatibility wrappers).\n * The wrapper is applied INSIDE silvery's contexts, so wrapped providers\n * can access silvery's Term, Stdout, FocusManager, and Runtime contexts.\n */\n wrapRoot?: (element: React.ReactElement) => React.ReactElement\n /**\n * External stdin stream to bridge to the renderer's input.\n * When provided, readable data from this stream is forwarded to the renderer's\n * input handler (equivalent to calling app.stdin.write()).\n */\n stdin?: NodeJS.ReadStream\n}\n\n/**\n * Store — the TermDef-like environment for render().\n * Provides cols, rows, and optionally an event stream for interactive mode.\n */\nexport interface Store {\n /** Terminal columns */\n readonly cols: number\n /** Terminal rows */\n readonly rows: number\n /** Async event stream (if present, enables interactive mode) */\n events?(): AsyncIterable<StoreEvent>\n}\n\n/**\n * Event from a store's event stream.\n */\nexport interface StoreEvent {\n type: string\n data: unknown\n}\n\n/**\n * Provider options for createStore().\n */\nexport interface StoreOptions {\n /** Terminal columns. Default: 80 */\n cols?: number\n /** Terminal rows. Default: 24 */\n rows?: number\n /** Event source for interactive mode */\n events?: AsyncIterable<StoreEvent>\n}\n\n// ============================================================================\n// Module Initialization\n// ============================================================================\n\n// Layout engine initialization promise (lazy)\nlet engineReady: Promise<void> | null = null\n\n/**\n * Ensure layout engine is initialized (async, cached).\n */\nexport async function ensureEngine(): Promise<void> {\n if (isLayoutEngineInitialized()) return\n if (!engineReady) {\n engineReady = ensureDefaultLayoutEngine()\n }\n await engineReady\n}\n\n// ============================================================================\n// render() — sync, returns full App\n// ============================================================================\n\n/**\n * Internal state for a render instance.\n */\ninterface RenderInstance {\n frames: string[]\n container: ReturnType<typeof createContainer>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- React reconciler internal type\n fiberRoot: any\n prevBuffer: TerminalBuffer | null\n /**\n * The buffer that was painted on the previous frame (post-fade). Used by\n * the output-phase diff as the \"from\" state so only actually-painted cells\n * are compared. `prevBuffer` separately carries the PRE-fade state forward\n * for the next frame's renderPhase incremental clone. When no backdrop\n * markers exist, the two are identical.\n */\n prevPaintedBuffer: TerminalBuffer | null\n /**\n * Cross-frame post-state carrier (outline snapshots, etc.). Held at the\n * instance level — not on `Ag` — because this driver creates a fresh\n * `Ag` per `runPipeline()` call, so an Ag-internal carrier would be\n * empty every frame and `clearPreviousOutlines` would never find prior\n * snapshots to restore. Mirrors the `prevBuffer` pattern: invalidated on\n * resize / explicit reset alongside it.\n */\n postState: RenderPostState\n mounted: boolean\n /** True while inside act() or doRender() — detects re-entrant calls */\n rendering: boolean\n columns: number\n rows: number\n inputEmitter: EventEmitter\n debug: boolean\n incremental: boolean\n /** Render count for SILVERY_STRICT checking (skip first render) */\n renderCount: number\n /** Max layout-pass iterations per render. See RenderOptions.maxLayoutPasses. */\n maxLayoutPasses: number\n /**\n * True when the PREVIOUS frame emitted Kitty scrim placements. Drives the\n * one-shot `a=d` delete-all on the first frame where the backdrop goes away.\n * Lives at the instance level (not on `Ag`) because this test driver\n * creates a fresh `Ag` per `runPipeline()` call, so `Ag`-scoped tracking\n * doesn't persist across frames here. Production runtimes with a\n * long-lived `Ag` also track this internally and produce the correct\n * deactivation emission — both paths converge on identical behavior.\n */\n kittyActive: boolean\n}\n\nfunction isStore(arg: unknown): arg is Store {\n // Store has cols and rows as required (not optional) properties.\n // RenderOptions has them as optional. Disambiguate by checking for\n // RenderOptions-only traits.\n if (arg === null || typeof arg !== \"object\") return false\n const obj = arg as Record<string, unknown>\n return (\n typeof obj.cols === \"number\" &&\n typeof obj.rows === \"number\" &&\n !(\"layoutEngine\" in obj) &&\n !(\"debug\" in obj) &&\n !(\"incremental\" in obj) &&\n !(\"maxLayoutPasses\" in obj) &&\n !(\"autoRender\" in obj) &&\n !(\"onFrame\" in obj) &&\n !(\"kittyMode\" in obj) &&\n !(\"wrapRoot\" in obj) &&\n !(\"stdin\" in obj)\n )\n}\n\n/**\n * Render a React element synchronously. Returns a full App with locators,\n * press(), text, ansi, etc.\n *\n * Layout engine must be initialized before calling (use ensureEngine() or\n * the top-level await in testing module).\n *\n * Overloads:\n * - render(element, { cols, rows }) — headless with dimensions\n * - render(element, store) — with a Store from createStore()\n *\n * @example\n * ```tsx\n * const app = render(<Counter />, { cols: 80, rows: 24 })\n * expect(app.text).toContain('Count: 0')\n * await app.press('j')\n * expect(app.text).toContain('Count: 1')\n * ```\n */\nexport function render(element: ReactElement, optsOrStore: RenderOptions | Store = {}): App {\n // Guard: layout engine must be initialized\n assertLayoutEngine()\n\n const storeMode = isStore(optsOrStore)\n const cols = storeMode ? optsOrStore.cols : (optsOrStore.cols ?? 80)\n const rows = storeMode ? optsOrStore.rows : (optsOrStore.rows ?? 24)\n const debug = storeMode ? false : (optsOrStore.debug ?? false)\n // Incremental rendering is enabled by default for all renders\n // Store mode also supports incremental - the RenderInstance tracks prevBuffer\n const incremental = storeMode ? true : (optsOrStore.incremental ?? true)\n const maxLayoutPasses = storeMode\n ? MAX_CONVERGENCE_PASSES\n : (optsOrStore.maxLayoutPasses ?? MAX_CONVERGENCE_PASSES)\n const kittyMode = storeMode ? false : (optsOrStore.kittyMode ?? false)\n const autoRender = storeMode ? false : (optsOrStore.autoRender ?? false)\n const handleTabCycling = storeMode ? true : (optsOrStore.handleTabCycling ?? true)\n const onFrame = storeMode ? undefined : optsOrStore.onFrame\n const onBufferReady = storeMode ? undefined : optsOrStore.onBufferReady\n const wrapRoot = storeMode ? undefined : optsOrStore.wrapRoot\n const stdinStream = storeMode ? undefined : optsOrStore.stdin\n\n // Guard: detect render leaks (absurd number of active instances)\n const liveCount = pruneAndCountActiveRenders()\n if (liveCount >= ACTIVE_RENDER_LEAK_THRESHOLD) {\n throw new Error(\n `silvery: ${liveCount} active render instances without unmount(). ` +\n \"This is a render leak. Use createRenderer() for auto-cleanup, \" +\n \"or call unmount() when done with each render.\",\n )\n }\n\n // Set layout engine if provided\n if (!storeMode && optsOrStore.layoutEngine) {\n setLayoutEngine(optsOrStore.layoutEngine)\n }\n\n // Unique ID for this render instance (for tracking/debugging)\n const renderId = ++renderIdCounter\n\n const instance: RenderInstance = {\n frames: [],\n container: null as unknown as ReturnType<typeof createContainer>,\n fiberRoot: null,\n prevBuffer: null,\n prevPaintedBuffer: null,\n postState: createRenderPostState(),\n mounted: true,\n rendering: false,\n columns: cols,\n rows: rows,\n inputEmitter: new EventEmitter(),\n debug,\n incremental,\n renderCount: 0,\n maxLayoutPasses,\n kittyActive: false,\n }\n\n // Track whether React committed new work (from layout notifications etc.)\n let hadReactCommit = false\n let autoRenderScheduled = false\n let inRenderCycle = false // true during doRender() and explicit operations\n let currentElement: ReactElement = element\n\n // Per-App ClsMonitor instance. The monitor walks the tree on every commit\n // (reading post-scroll, sticky-aware `screenRect`) and feeds shifts to\n // any active capture window. Always instantiated; production logging\n // remains env-gated (DEBUG=silvery:cls / SILVERY_INSTRUMENT=cls) while\n // test-time `beginCLSCapture()` engages the per-window collector.\n // Bead: @km/silvery/cls-instrumentation-primitive (Phase 9/11).\n const clsMonitor = createClsMonitor()\n instance.container = createContainer(() => {\n hadReactCommit = true\n // Auto-render: schedule a microtask re-render on async React commits\n // (e.g., setTimeout → setState). Skipped during explicit render operations\n // (rendering=true or inRenderCycle=true) since those call doRender() themselves.\n if (\n autoRender &&\n !instance.rendering &&\n !inRenderCycle &&\n !autoRenderScheduled &&\n instance.mounted\n ) {\n autoRenderScheduled = true\n queueMicrotask(() => {\n autoRenderScheduled = false\n if (!instance.mounted || instance.rendering || inRenderCycle) return\n inRenderCycle = true\n try {\n // settleAfterCommit isn't in scope at module init order — fall\n // back to inline commit + one extra pass. Same shape as the\n // closure version below.\n let newFrame = doRender()\n const root = getContainerRoot(instance.container)\n hadReactCommit = false\n withActEnvironment(() => {\n act(() => {\n commitLayoutSnapshot(root)\n })\n if (hadReactCommit) {\n act(() => {\n reconciler.flushSyncWork()\n })\n }\n })\n if (hadReactCommit) {\n newFrame = doRender()\n }\n instance.frames.push(newFrame)\n onFrame?.(newFrame, instance.prevBuffer!, getRootContentHeight())\n } finally {\n inRenderCycle = false\n }\n })\n }\n })\n\n instance.fiberRoot = createFiberRoot(instance.container)\n\n /**\n * Get the content extent of the root node's children (for buffer padding trimming).\n * Returns the max outer bottom edge of the root's direct children, including\n * margins. The root itself stretches to fill the terminal, but its children\n * have specific layout-computed heights plus margins that extend beyond.\n * Returns 0 if no children have layout (empty tree).\n * Returns undefined if root has no layout at all.\n */\n const getRootContentHeight = (): number | undefined => {\n try {\n const root = getContainerRoot(instance.container)\n if (!root?.boxRect) return undefined\n let maxBottom = 0\n let hasChildren = false\n for (const child of root.children) {\n if (child.boxRect) {\n hasChildren = true\n // boxRect includes marginTop in the y position but NOT marginBottom\n // in the height. Read marginBottom from props to get the full outer extent.\n const props = child.props as Record<string, unknown>\n const mb =\n (props.marginBottom as number) ??\n (props.marginY as number) ??\n (props.margin as number) ??\n 0\n const childBottom = child.boxRect.y + child.boxRect.height + mb\n if (childBottom > maxBottom) maxBottom = childBottom\n }\n }\n return hasChildren ? maxBottom : 0\n } catch {\n return undefined\n }\n }\n\n // Track exit state\n let exitCalledFlag = false\n let exitErrorValue: Error | undefined\n\n const handleExit = (error?: Error) => {\n exitCalledFlag = true\n exitErrorValue = error\n if (debug) {\n console.log(\"[silvery] exit() called\", error ? `with error: ${error.message}` : \"\")\n }\n }\n\n // Create mock stdout with mutable dimensions and event support (for resize)\n const stdoutEmitter = new EventEmitter()\n const mockStdout = {\n columns: instance.columns,\n rows: instance.rows,\n write: () => true,\n isTTY: true,\n on: (event: string, listener: (...args: unknown[]) => void) => {\n stdoutEmitter.on(event, listener)\n return mockStdout\n },\n off: (event: string, listener: (...args: unknown[]) => void) => {\n stdoutEmitter.off(event, listener)\n return mockStdout\n },\n once: (event: string, listener: (...args: unknown[]) => void) => {\n stdoutEmitter.once(event, listener)\n return mockStdout\n },\n removeListener: (event: string, listener: (...args: unknown[]) => void) => {\n stdoutEmitter.removeListener(event, listener)\n return mockStdout\n },\n addListener: (event: string, listener: (...args: unknown[]) => void) => {\n stdoutEmitter.addListener(event, listener)\n return mockStdout\n },\n } as unknown as NodeJS.WriteStream\n\n // Create a fixed-dimension mock term so useWindowSize reads the renderer's\n // viewport synchronously. Using a node-style stdout-backed Term here would\n // trail resize events through Size's production SIGWINCH coalescer, causing\n // tests to paint once at the new outer buffer size with stale term.size,\n // then repaint ~200ms later with the final width. Production's app event\n // loop receives resize events from the coalesced Term size owner, so the\n // test renderer should update both dimensions in one explicit resize step.\n const mockTerm = createTerm({\n cols: instance.columns,\n rows: instance.rows,\n caps: { colorLevel: \"truecolor\" },\n })\n const updateMockTermSize = (\n mockTerm.size as typeof mockTerm.size & { update?: (cols: number, rows: number) => void }\n ).update\n\n // Focus manager (tree-based focus system)\n const focusManager = createFocusManager()\n\n // Wire up focus cleanup for this render root.\n setContainerNodeLifecycle(instance.container, {\n onNodeRemoved: (removedNode) => focusManager.handleSubtreeRemoved(removedNode),\n onNodeUpdated: (updatedNode) => focusManager.handleNodeUpdated(updatedNode),\n onSubtreeAttached: (attachedRoot) => focusManager.handleSubtreeAttached(attachedRoot),\n })\n\n // Per-instance cursor state (replaces module-level globals)\n const cursorStore = createCursorStore()\n\n // Per-render app-root scope — faithful to production.\n //\n // `create-app.tsx` creates `const appScope = createScope(\"app\")` and wraps\n // the React tree in `<ScopeProvider scope={appScope} appScope={appScope}>`,\n // disposing it on app exit. The lower-level test renderer must mirror this:\n // components migrated to `useScopeEffect` call `useScope()`, which throws\n // without a `<ScopeProvider>` ancestor OR an app-root scope (see\n // `@silvery/ag-react/hooks/useScope`). Rendering an isolated component\n // (`render(<CommandBox />)`) through this path therefore needs the same\n // app-root scope production provides.\n //\n // The scope is created ONCE per render instance and lives for the\n // instance's lifetime — it survives `rerender()` (which reuses the same\n // fiberRoot) just as production's `appScope` survives across event batches.\n // It is disposed in `unmountFn` (after React unmount, before\n // `releaseContainer`), mirroring create-app's\n // `appScope[Symbol.asyncDispose]()` on-exit ordering.\n //\n // The same value flows into both `ScopeContext` and `AppScopeContext` so\n // `useScope()` and `useAppScope()` resolve to it when no inner provider is\n // present. This is additive: components that don't use scope are\n // unaffected, and a test that nests its own `<ScopeProvider>` (e.g. via\n // createTestApp → BoardApp → KmTuiScopeProvider) still resolves the NEAREST\n // `ScopeContext` — inner wins, this is only the fallback. Bead:\n // @km/silvery/test-render-app-root-scope.\n const renderScope = createScope(\"test-render\")\n\n // Child apply-chain BaseApp — the ChainAppContext surface for hooks\n // rendered via the test renderer. Mirrors the plumbing in\n // `create-app.tsx` (TEA Phase 2). Inputs routed here via the\n // inputEmitter bridge below.\n const childApp = createChildApp()\n const chainAppContextValue = toChainAppContextValue(childApp)\n instance.inputEmitter.on(\"input\", (data: string | Buffer) => {\n const [input, key] = parseKey(data)\n childApp.rawKeys.notify(input, key)\n childApp.dispatch({ type: \"input:key\", input, key })\n childApp.drainEffects()\n })\n instance.inputEmitter.on(\"paste\", (text: string) => {\n childApp.dispatch({ type: \"term:paste\", text })\n childApp.drainEffects()\n })\n\n // RuntimeContext — trimmed to `exit()` only. First-party hooks\n // subscribe via ChainAppContext above; app-defined events ride on\n // `chain.events`.\n const runtimeValue: RuntimeContextValue = {\n exit: handleExit,\n panic: (reason) => handleExit(reason instanceof Error ? reason : new Error(String(reason))),\n }\n\n // Wrap element with contexts\n function wrapWithContexts(el: ReactElement): ReactElement {\n const inner = wrapRoot ? wrapRoot(el) : el\n // ScopeProvider is the outermost wrapper (mirrors create-app.tsx, where it\n // sits just under SilveryErrorBoundary) so `useScope()` / `useAppScope()`\n // resolve to the per-render app-root scope for any component that doesn't\n // nest its own provider. The same `renderScope` value flows into both\n // ScopeContext and AppScopeContext.\n return React.createElement(\n ScopeProvider,\n { scope: renderScope, appScope: renderScope },\n React.createElement(\n CursorProvider,\n { store: cursorStore },\n React.createElement(\n TermContext.Provider,\n { value: mockTerm },\n React.createElement(\n StdoutContext.Provider,\n { value: { stdout: mockStdout, write: () => {} } },\n React.createElement(\n FocusManagerContext.Provider,\n { value: focusManager },\n React.createElement(\n RuntimeContext.Provider,\n { value: runtimeValue },\n React.createElement(\n ChainAppContext.Provider,\n { value: chainAppContextValue },\n inner,\n ),\n ),\n ),\n ),\n ),\n ),\n )\n }\n\n // Check SILVERY_STRICT for automatic incremental checking (like scheduler does)\n // Note: \"0\" and \"false\" are treated as disabled\n const strictEnv = process.env.SILVERY_STRICT\n const strictMode = incremental && strictEnv && strictEnv !== \"0\" && strictEnv !== \"false\"\n\n // Render function that executes the pipeline.\n //\n // Two modes:\n // 1. Multi-pass (default): Layout stabilization loop (up to 5 iterations).\n // After runPipeline fires notifyLayoutSubscribers (Phase 2.7), hooks\n // like useBoxRect call forceUpdate(). These React updates are flushed\n // and the pipeline re-run until stable.\n //\n // 2. Single-pass (singlePassLayout=true): Matches production create-app.tsx.\n // Single runPipeline call per doRender(), with a separate effect flush\n // loop afterward (like production's processEventBatch). This ensures tests\n // exercise the same rendering pipeline as production.\n //\n // Key insight: runPipeline must run inside act() so that forceUpdate/setState\n // calls from layout notifications are properly captured by React's scheduler.\n // With IS_REACT_ACT_ENVIRONMENT=true (set by silvery/testing), state updates\n // outside act() boundaries may be dropped.\n // Convergence bound — see `pass-cause.ts:MAX_CONVERGENCE_PASSES`. Replaces\n // four prior magic constants (single-pass=15, classic=5, effect-flush=5,\n // production-flush=5) with a single value derived from PASS_CAUSE_BOUNDS.\n //\n // The C3a v3 corpus (105 termless app teardowns / 11 538 pass-cause\n // records) showed the observed envelope was pass 0 → 1; the structural\n // ceiling derived in `pass-cause.ts:PASS_CAUSE_BOUNDS` matches at\n // MAX = 1 (initial) + 1 (settle) = 2.\n //\n // When a loop exceeds the bound, `assertBoundedConvergence` surfaces a\n // per-PassCause breakdown in STRICT mode — a regression in pipeline\n // determinism (a new feedback edge that breaks the per-cause invariants)\n // fails loudly with attribution rather than silently consuming a 15-pass\n // safety margin.\n //\n // See hub/silvery/design/convergence-bounds.md for the convergence theorem.\n\n /** Run the full pipeline: layout + render + output phase. */\n function runPipeline(\n root: ReturnType<typeof getContainerRoot>,\n cols: number,\n rows: number,\n prevBuffer: TerminalBuffer | null,\n opts?: {\n skipLayoutNotifications?: boolean\n skipScrollStateUpdates?: boolean\n /**\n * When true, render against a throw-away empty post-state carrier\n * instead of `instance.postState`. Used by `doFreshRenderFull` so\n * STRICT's fresh-render comparison neither consumes the\n * carried-forward outline snapshots nor stomps them with the fresh\n * pass's own snapshots — the next incremental render still sees the\n * snapshot set captured by the previous incremental render.\n */\n fresh?: boolean\n /** Disable propagateLayout's incremental skip — see AgLayoutOptions. */\n forceFullPropagate?: boolean\n },\n ): {\n output: string\n buffer: TerminalBuffer\n carryForwardBuffer: TerminalBuffer\n overlay: string\n } {\n const ag = createAg(root)\n ag.layout({ cols, rows }, opts)\n // `buffer` is post-fade (what we paint); `carryForwardBuffer` is pre-fade\n // (what the NEXT frame's incremental render must clone). See ag.ts\n // `AgRenderResult.carryForwardBuffer` for the invariant rationale.\n //\n // `postState` is the instance-level outline-snapshot carrier (see\n // `RenderInstance.postState`). The Ag mutates it in place during the\n // decoration phase: `clearPreviousOutlines` reads the previous frame's\n // snapshots, `renderDecorationPass` writes this frame's. Because the\n // same reference is passed every call, the data survives the\n // per-frame `createAg(root)` recycle.\n //\n // For fresh renders (STRICT comparison only), pass a throw-away carrier\n // so the fresh pass doesn't observe or mutate the cross-frame snapshots\n // — see the `fresh` option doc above.\n const { buffer, carryForwardBuffer, overlay } = ag.render({\n prevBuffer,\n postState: opts?.fresh ? createRenderPostState() : instance.postState,\n })\n // Output-phase diff uses the previously painted (post-fade) buffer.\n // `prevPaintedBuffer` is set by the caller (see singlePass/classic loops)\n // and stored as `instance.prevPaintedBuffer`.\n const prevForDiff = instance.prevPaintedBuffer ?? prevBuffer\n let output = outputPhase(prevForDiff, buffer, \"fullscreen\")\n // Backdrop emoji-scrim overlay (Kitty graphics). Since we create a fresh\n // `Ag` per render, the ag-internal `_kittyActive` tracker is always fresh\n // and can't emit the deactivation `a=d`. Track it at the instance level\n // and synthesize the delete-all when the overlay disappears.\n const overlayEmitted = overlay.length > 0\n if (overlayEmitted) {\n output += overlay\n instance.kittyActive = true\n } else if (instance.kittyActive) {\n // Transition active → inactive (modal closed). Emit delete-all once\n // so leftover scrim rectangles don't linger in the terminal.\n output += _CURSOR_SAVE + _kittyDeleteAllScrimPlacements() + _CURSOR_RESTORE\n instance.kittyActive = false\n }\n return { output, buffer, carryForwardBuffer, overlay }\n }\n\n function doRender(): string {\n let output: string\n let buffer!: TerminalBuffer\n // Kitty backdrop-fade overlay from the incremental render. Captured\n // here so the STRICT block below can compare it against the fresh\n // render's overlay (determinism invariant — see pipeline/backdrop/).\n let incrementalOverlay = \"\"\n\n // True when the multi-pass loop drained all React work (hadReactCommit\n // was false at the exit iteration). When false, React state is still\n // pending — the buffer reflects the pre-commit tree but the React tree\n // has uncommitted layout-subscriber updates. STRICT comparison against\n // a fresh render would compare apples to oranges (fresh re-runs\n // calculateLayout against the post-commit React tree). The outer\n // drain loop in sendInput / resizeFn handles this by calling doRender\n // again after flushing — that subsequent doRender's STRICT check runs\n // on a stable state. (km-yej6 column-resize-incremental-mismatch.)\n let multiPassConverged = false\n\n // Unified bounded layout-pass loop. Each pass: runPipeline +\n // flushSyncWork. Loop exits when no React commit happened (converged)\n // or when the cap is reached. Default cap = MAX_CONVERGENCE_PASSES (2,\n // production-derived structural bound). Tests can opt into higher caps\n // via RenderOptions.maxLayoutPasses for legacy multi-iteration\n // stabilization needs.\n //\n // IMPORTANT: do NOT flush sync work here as part of the same act block.\n // runPipeline fires notifyLayoutSubscribers (Phase 2.7) which may call\n // forceUpdate(). If we flushed inside the same act, the pipeline\n // output would still reflect the pre-forceUpdate state. Flush in a\n // separate act after each pass, then loop if hadReactCommit.\n let passCount = 0\n const cap = instance.maxLayoutPasses\n if (INSTRUMENT) beginConvergenceLoop()\n for (let pass = 0; pass < cap; pass++) {\n hadReactCommit = false\n passCount++\n if (INSTRUMENT) beginPass(pass)\n let renderError: Error | null = null\n let carryForwardBuffer: TerminalBuffer | undefined\n withActEnvironment(() => {\n act(() => {\n const root = getContainerRoot(instance.container)\n try {\n const result = runPipeline(\n root,\n instance.columns,\n instance.rows,\n incremental ? instance.prevBuffer : null,\n )\n output = result.output\n buffer = result.buffer\n carryForwardBuffer = result.carryForwardBuffer\n incrementalOverlay = result.overlay\n } catch (e) {\n // STRICT output verification may throw from the output phase.\n // The render phase buffer is still valid and attached to the\n // error — extract it so lastBuffer() returns the correct frame.\n renderError = e as Error\n const attachedBuffer = (e as any)?.__silvery_buffer\n if (attachedBuffer) {\n buffer = attachedBuffer\n carryForwardBuffer = attachedBuffer\n }\n }\n // Always update prevBuffer when a new buffer was produced, even\n // if output phase threw. Carry forward PRE-fade buffer for\n // renderPhase incremental clone; track POST-fade buffer for\n // output-phase diff.\n if (buffer) {\n instance.prevBuffer = carryForwardBuffer ?? buffer\n instance.prevPaintedBuffer = buffer\n }\n instance.renderCount++\n onBufferReady?.(output, buffer, getRootContentHeight())\n })\n // Flush any React work scheduled during runPipeline (e.g. from\n // useSyncExternalStore updates triggered by Phase 2.7 callbacks).\n if (!hadReactCommit) {\n act(() => {\n reconciler.flushSyncWork()\n })\n }\n })\n // Re-throw non-diagnostic errors. IncrementalRenderMismatchError\n // from STRICT output verification is diagnostic.\n if (renderError) {\n if (!((renderError as Error) instanceof IncrementalRenderMismatchError)) {\n throw renderError\n }\n }\n if (!hadReactCommit) {\n multiPassConverged = true\n break\n }\n invalidateRootAfterLayoutFeedback(getContainerRoot(instance.container))\n // Always-on exhaustion marker → guarantees the violation ring is never\n // empty when this loop runs to cap, so the breakdown is never silent.\n if (pass === cap - 1) recordPassRing(\"unknown\", \"layout-pass-exhaustion\")\n if (INSTRUMENT) {\n notePassCommit(pass)\n if (pass === cap - 1) {\n logPass({ cause: \"unknown\", detail: \"layout-pass-exhaustion\" })\n }\n }\n }\n\n if (hadReactCommit && passCount >= cap) {\n assertBoundedConvergence(passCount, \"layout-pass\", cap)\n }\n\n // When multiple passes ran, the final buffer's dirty rows only cover\n // the LAST pass's render phase writes. Mark all rows dirty so the\n // output phase does a full diff scan.\n if (incremental && buffer && passCount > 1) {\n buffer.markAllRowsDirty()\n }\n\n // Observe the authoritative render before verification-only fresh passes\n // mutate layout history. In particular, a tier-2 fresh-layout comparison\n // advances prevScreenRect and would otherwise erase the shift CLS is meant\n // to record.\n clsMonitor.onCommit(\n getContainerRoot(instance.container),\n instance.columns,\n instance.rows,\n /*scrollOrResize*/ false,\n )\n\n // SILVERY_STRICT: Compare incremental vs fresh on every render (like scheduler)\n //\n // Skip when:\n // - First render (nothing to compare against)\n // - Multi-pass loop exited with React work pending (multiPassConverged\n // === false). The buffer reflects pre-commit React tree state, but\n // fresh would re-run calculateLayout against the post-commit tree.\n // The OUTER drain (sendInput / resizeFn) calls doRender again after\n // flushing — that subsequent doRender's STRICT check runs on a\n // stable tree. (km-yej6 column-resize-incremental-mismatch.)\n if (strictMode && instance.renderCount > 1 && multiPassConverged) {\n const root = getContainerRoot(instance.container)\n const { buffer: freshBuffer, overlay: freshOverlay } = doFreshRenderFull()\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. Any drift\n // is a determinism bug in marker collection, the emoji walk, or\n // placement ID derivation — see `formatOverlayMismatch` in\n // scheduler.ts for the diagnostic path.\n if (incrementalOverlay !== freshOverlay) {\n const msg = formatRendererOverlayMismatch(\n incrementalOverlay,\n freshOverlay,\n instance.renderCount,\n )\n throw new IncrementalRenderMismatchError(msg)\n }\n\n for (let y = 0; y < buffer!.height; y++) {\n for (let x = 0; x < buffer!.width; x++) {\n const a = buffer!.getCell(x, y)\n const b = freshBuffer.getCell(x, y)\n if (!cellEquals(a, b)) {\n // Re-run fresh render with write trap to capture what writes here\n let trapInfo = \"\"\n const trap = { x, y, log: [] as string[] }\n ;(globalThis as any).__silvery_write_trap = trap\n try {\n doFreshRender()\n } catch {\n // ignore\n }\n ;(globalThis as any).__silvery_write_trap = null\n if (trap.log.length > 0) {\n trapInfo = `\\nWRITE TRAP (${trap.log.length} writes to (${x},${y})):\\n${trap.log.join(\"\\n\")}\\n`\n } else {\n trapInfo = `\\nWRITE TRAP: NO WRITES to (${x},${y})\\n`\n }\n\n // Build rich debug context\n const ctx = buildMismatchContext(root, x, y, a, b, instance.renderCount)\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 =\n debugInfo + trapInfo + `--- incremental ---\\n${incText}\\n--- fresh ---\\n${freshText}`\n throw new IncrementalRenderMismatchError(msg, {\n renderPhaseStats,\n mismatchContext: ctx,\n })\n }\n }\n }\n\n // SILVERY_STRICT residue check (slug \"residue\", tier 2+).\n //\n // The incremental≡fresh comparison above catches divergences in cells\n // whose VALUES differ, but it cannot catch the \"stale carry-over\"\n // class — where a cell from frame N-1 happens to coincide with what\n // fresh would paint, so cell-equality holds even though no paint op\n // covered it under incremental. The cyan-strip residue (P1, bead\n // @km/silvery/render-no-stale-residue-invariant) was exactly this\n // shape.\n //\n // The sentinel-compare check exposes it: clone the prev buffer,\n // poison every cell with a color/char no theme produces, run the\n // pipeline against the poisoned clone, then compare against a\n // fresh-from-zeroed-buffer render. Any cell that still carries the\n // sentinel after incremental ran reveals stale carry-over.\n //\n // Cost: one extra render-phase pass + a cloned buffer per frame.\n // Default SILVERY_STRICT=1 does NOT enable this — it is tier 2.\n // See packages/ag-term/src/pipeline/strict-residue.ts for the\n // sentinel choice and full rationale.\n if (\n instance.prevBuffer &&\n instance.renderCount > 1 &&\n multiPassConverged &&\n isResidueStrictEnabled()\n ) {\n const root2 = getContainerRoot(instance.container)\n // Snapshot the REAL prev before poisoning the clone — required\n // for verifyNoResidueLeak to distinguish \"cascade skipped because\n // prev was already correct\" (no bug) from \"cascade skipped but\n // prev was wrong\" (cyan-strip class).\n const realPrev = instance.prevBuffer.clone()\n const poisonedPrev = instance.prevBuffer.clone()\n poisonBufferWithSentinel(poisonedPrev)\n // Render against the sentinel-poisoned prev. `fresh: true` swaps\n // the postState carrier so the production cross-frame snapshots\n // are not consumed or stomped. We do NOT update instance.prevBuffer\n // / prevPaintedBuffer — this is a verification-only pass.\n const sentinelResult = runPipeline(root2, instance.columns, instance.rows, poisonedPrev, {\n skipLayoutNotifications: true,\n skipScrollStateUpdates: true,\n fresh: true,\n })\n // Fresh-from-zero baseline. doFreshRenderFull already passes\n // prevBuffer=null and a throwaway postState — this is the\n // \"all pipeline state reset\" reference.\n const { buffer: freshFromZero } = doFreshRenderFull()\n verifyNoResidueLeak(realPrev, sentinelResult.buffer, freshFromZero, instance.renderCount)\n }\n }\n\n return output!\n }\n\n // Fresh render: renders from scratch without updating incremental state.\n // Returns buffer + overlay so the STRICT block can compare the\n // backdrop-fade overlay byte-for-byte against the incremental render.\n function doFreshRenderFull(): { buffer: TerminalBuffer; overlay: string } {\n const root = getContainerRoot(instance.container)\n // `fresh-layout` STRICT slug (tier 2): force an INDEPENDENT from-scratch\n // layout for the comparison baseline. Without this the fresh render shares\n // the incremental path's just-cleaned flexily tree — the ag.ts layout-on-\n // demand gate skips calculateLayout, so a stale rect (a layout-affecting\n // change that failed to markDirty flexily) is shared by both paths and the\n // `incremental` check stays green while the layout is wrong. Deterministic\n // on clean code (flexily expectRelayoutMatchesFresh fuzz) → no false\n // positive, no shared-tree drift. See markLayoutTreeDirty.\n const freshLayout = isStrictEnabled(FRESH_LAYOUT_STRICT_SLUG, FRESH_LAYOUT_STRICT_MIN_TIER)\n if (freshLayout) {\n markLayoutTreeDirty(root)\n }\n const { buffer, overlay } = runPipeline(root, instance.columns, instance.rows, null, {\n skipLayoutNotifications: true,\n skipScrollStateUpdates: true,\n // Throw-away post-state — fresh renders are STRICT-only; they must not\n // observe or mutate `instance.postState` (which is owned by the\n // incremental path).\n fresh: true,\n // fresh-layout baseline: write every recomputed rect to boxRect (the\n // parent-match skip would otherwise drop a stale child under an unchanged\n // ancestor and hide the divergence).\n forceFullPropagate: freshLayout,\n })\n return { buffer, overlay }\n }\n // Public shim: keeps the `freshRender(): TerminalBuffer` API stable for\n // external callers (with-diagnostics, app.freshRender).\n function doFreshRender(): TerminalBuffer {\n return doFreshRenderFull().buffer\n }\n\n // Synchronously update React tree within act()\n instance.rendering = true\n try {\n withActEnvironment(() => {\n act(() => {\n reconciler.updateContainerSync(\n wrapWithContexts(currentElement),\n instance.fiberRoot,\n null,\n null,\n )\n reconciler.flushSyncWork()\n })\n })\n } finally {\n instance.rendering = false\n }\n\n /**\n * Promote in-flight rect signals to their committed peers, then drain any\n * resulting React work with one more `doRender()` if needed.\n *\n * Reactive `useBoxRect()` / `useScrollRect()` / `useScreenRect()` hooks\n * read the COMMITTED rect signals (stable across all convergence passes\n * within one batch). Calling `commitLayoutSnapshot` advances them by one\n * frame, which may fire `effect()` callbacks that trigger React\n * `forceUpdate`s. Those forceUpdates need to land before the test\n * driver's caller observes the frame — otherwise the first frame would\n * show the empty-rect fallback for any reactive layout consumer.\n *\n * The settle pass is bounded to ONE doRender. Any in-flight rect changes\n * produced by it are intentionally NOT committed in this batch — they\n * belong to the next event boundary (one-frame-late contract).\n *\n * See bead `@km/silvery/use-deferred-box-rect-and-post-commit-observers`.\n */\n function settleAfterCommit(prevOutput: string): string {\n const root = getContainerRoot(instance.container)\n let scheduled = false\n hadReactCommit = false\n withActEnvironment(() => {\n act(() => {\n commitLayoutSnapshot(root)\n })\n // act() flushes scheduled work; if commit fired effect → forceUpdate,\n // hadReactCommit was set during the inner act(). If anything still\n // pending, flush once more.\n if (hadReactCommit) {\n act(() => {\n reconciler.flushSyncWork()\n })\n scheduled = true\n }\n })\n if (!scheduled) return prevOutput\n // One settle pass — re-render with the new committed values. Do NOT\n // re-commit; further in-flight changes defer to the next batch.\n return doRender()\n }\n\n /**\n * Drain additional commit / layout cycles until the React tree is stable\n * (no pending commit, no dirty layout nodes) OR a budget is reached.\n * Resolves once stable so tests can assert post-convergence state.\n *\n * The default is production timing — `render()` exposes a frame after the\n * production-cap `MAX_CONVERGENCE_PASSES`. Tests asserting layout that\n * requires more passes to settle (multi-layer measurement chains,\n * Suspense-driven first-paint cascades, etc.) call this explicitly:\n *\n * ```ts\n * const app = render(<MyApp />)\n * await app.waitForLayoutStable()\n * expect(app.text).toMatchSnapshot()\n * ```\n *\n * Stability detection: a pass is run, then `flushSyncWork` runs in a\n * separate `act()`. If `hadReactCommit` flips during the flush, layout\n * is still in-flight; loop again (run another commit boundary + flush).\n * Same predicate the renderer uses internally — does not invent a new\n * readiness probe.\n *\n * Bounded by `maxPasses` (default 20) and `timeoutMs` (default 50ms).\n * If the cap is reached, resolves WITHOUT throwing — an infinitely\n * non-converging app is a bug, not a test fault. The contract is \"best\n * effort within the budget.\"\n *\n * Bead: `@km/silvery/test-harness-convergence-cap-parity`.\n */\n async function waitForLayoutStable(opts?: {\n timeoutMs?: number\n maxPasses?: number\n }): Promise<void> {\n if (!instance.mounted) return\n const timeoutMs = opts?.timeoutMs ?? 50\n const maxPasses = opts?.maxPasses ?? 20\n const start = performance.now()\n let lastFrame = instance.frames[instance.frames.length - 1] ?? \"\"\n for (let pass = 0; pass < maxPasses; pass++) {\n if (performance.now() - start >= timeoutMs) return\n if (!instance.mounted) return\n\n // Drain any pending async React work in an act() boundary. Async\n // microtasks (useEffect → queueMicrotask → setState) need to fire\n // INSIDE act() so React's scheduler commits the resulting state\n // updates synchronously rather than warning and deferring.\n //\n // `act(async () => { await Promise.resolve() })` is the canonical\n // React-testing pattern: drains microtasks + flushes passive\n // effects + commits scheduled state updates, all under the act\n // boundary. Without the async act wrapper, setState calls inside\n // microtasks are warned and dropped under IS_REACT_ACT_ENVIRONMENT.\n // (IS_REACT_ACT_ENVIRONMENT is set globally by `@silvery/test`'s\n // top-level await, so no withActEnvironment wrapper is required.)\n hadReactCommit = false\n instance.rendering = true\n try {\n await act(async () => {\n await Promise.resolve()\n })\n } finally {\n instance.rendering = false\n }\n const reactCommittedThisPass = hadReactCommit\n\n // If React committed, re-render to materialize the new tree into\n // the buffer. Then run the commit-boundary settle for any reactive\n // useBoxRect / useScrollRect / useScreenRect cascades.\n let next = lastFrame\n if (reactCommittedThisPass) {\n next = doRender()\n }\n // commitLayoutSnapshot + flush — promotes in-flight rect signals\n // to committed and drains any resulting forceUpdate cascade. Same\n // pattern sendInput / resizeFn use after their bounded loops.\n next = settleAfterCommit(next)\n if (!reactCommittedThisPass && next === lastFrame && !hadReactCommit) {\n // Stable: nothing committed during microtask drain, nothing fired\n // in the commit-boundary settle. Verify no node remains\n // epoch-dirty as a final correctness check.\n try {\n const root = getContainerRoot(instance.container)\n if (root && !isAnyDirty(root.dirtyBits, root.dirtyEpoch)) return\n } catch {\n return\n }\n return\n }\n if (next !== lastFrame) {\n lastFrame = next\n instance.frames.push(next)\n onFrame?.(next, instance.prevBuffer!, getRootContentHeight())\n }\n }\n // Budget exhausted — the app didn't converge in the cap. Resolve\n // anyway (per the contract — best effort within budget). The\n // SILVERY_STRICT bound assertion already catches structural\n // non-convergence in the inner loops; this method is for test-author\n // ergonomics, not a determinism check.\n }\n\n // Execute the render pipeline.\n //\n // Production parity (bead `@km/silvery/test-harness-convergence-cap-parity`):\n // the initial render uses the same `MAX_CONVERGENCE_PASSES` cap as\n // production's `processEventBatch` and `app.run()` initial-render flush\n // loops (see `create-app.tsx` ~line 2435). Production NEVER runs an extra-\n // wide cap on first paint, so the test harness must not either — otherwise\n // tests see post-convergence layout that real users never get on first\n // paint, and bugs that fire during the first 1-2 passes are invisible.\n //\n // Tests that need post-convergence state for their assertions call\n // `await app.waitForLayoutStable()` explicitly — see that method's docs.\n // The default is production timing; opt-in is the explicit primitive.\n //\n // Historical note: this used to run a wider 5-pass initial cap to\n // accommodate multi-layer measurement chains (useBoxRect → forceUpdate\n // → re-render → useBoxRect again). With the layout-signals refactor\n // (`@km/silvery/listview-layout-signals-from-getlayoutsignals`), the\n // canonical primitive `boxRectCommitted` reads synchronously during the\n // SAME render that consumes it, so multi-layer chains converge in 1-2\n // passes via signal idempotence rather than needing 3-5 React commits.\n let output = doRender()\n // Commit boundary for the initial render. Reactive useBoxRect/useScrollRect/\n // useScreenRect hooks subscribed during this render see the seeded committed\n // signal (often null/zero) on first read; promoting in-flight → committed\n // here fires their effects so the next render reads real rects. See bead\n // `@km/silvery/use-deferred-box-rect-and-post-commit-observers`.\n output = settleAfterCommit(output)\n\n instance.frames.push(output)\n onFrame?.(output, instance.prevBuffer!, getRootContentHeight())\n\n if (debug) {\n console.log(\"[silvery] Initial render:\", output)\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Degenerate-frame canary (SILVERY_STRICT slug: \"canary\")\n // ─────────────────────────────────────────────────────────────────────────\n //\n // After the initial render settles, measure the painted-cell ratio. A\n // healthy full-app render at any non-trivial geometry paints thousands\n // of cells. A misconfigured root that omits a `<Screen>` /\n // `<Box width height>` wrapper collapses the entire tree to ~one row\n // (the title bar) — at 360×120 that's ~360 of 43,200 cells, < 1%.\n //\n // The canary only fires when the buffer is large enough that a real-app\n // frame is expected (cols × rows >= CANARY_MIN_BUFFER_CELLS). Smaller\n // buffers are unit-test fixtures where low paint ratio is meaningful.\n //\n // Strictness: gated by the canonical SILVERY_STRICT contract — fires\n // whenever `SILVERY_STRICT` includes tier ≥ 1 OR the explicit slug\n // \"canary\". Per-test opt-out: `SILVERY_STRICT=1,!canary`.\n // No new SILVERY_* env vars. See packages/ag-term/src/strict-mode.ts.\n //\n // Default (SILVERY_STRICT unset / \"0\"): emit a `silvery:render` debug\n // log line. `console.warn` is deliberately NOT used because km's vitest\n // setup treats any console.warn as a hard test failure.\n //\n // Bead: @km/silvery/render-degenerate-frame-canary.\n if (instance.prevBuffer) {\n const bufW = instance.prevBuffer.width\n const bufH = instance.prevBuffer.height\n const totalCells = bufW * bufH\n const CANARY_MIN_BUFFER_CELLS = 4000 // ≈ 80×50 — anything smaller is a unit-test fixture\n if (totalCells >= CANARY_MIN_BUFFER_CELLS) {\n const painted = instance.prevBuffer.countPaintedCells()\n const ratio = painted / totalCells\n if (ratio < 0.05) {\n const msg =\n `silvery: degenerate frame after first render — only ${painted} of ${totalCells} cells ` +\n `painted (${(ratio * 100).toFixed(2)}%) at ${bufW}x${bufH}. ` +\n `Likely cause: the root component does not pin width/height (no <Screen> ` +\n `wrapper). createRenderer({cols, rows}) passes dimensions as the AVAILABLE ` +\n `size to layout, but does NOT set root.style.width/height — wrap the tree ` +\n `in <Box width={cols} height={rows}> or <Screen>. ` +\n `Per-test opt-out: SILVERY_STRICT=1,!canary. ` +\n `See @km/silvery/render-degenerate-frame-canary.`\n // Tier 2 — surfaces a punch list of harness defects without\n // blocking tier-1 (back-compat). Promotes to tier 1 once the\n // existing test suite is clean. Explicit slug \"canary\" or\n // tier ≥ 2 fires the throw; tier 1 alone emits debug-log.\n if (isStrictEnabled(\"canary\", 2)) {\n throw new Error(msg)\n } else {\n log.debug?.(msg)\n }\n }\n }\n }\n\n // Set up stdin bridge: forward external stdin data to the renderer's input\n let stdinOnReadable: (() => void) | undefined\n if (stdinStream) {\n stdinOnReadable = () => {\n let chunk: string | null\n while ((chunk = (stdinStream as any).read?.()) !== null && chunk !== undefined) {\n instance.inputEmitter.emit(\"input\", chunk)\n }\n }\n stdinStream.on(\"readable\", stdinOnReadable)\n }\n\n // Helper functions for App\n const getContainer = () => getContainerRoot(instance.container)\n // Expose the POST-fade buffer so app.cell() / app.text / createTextFrame\n // read what was actually painted. `instance.prevBuffer` is the pre-fade\n // buffer used for the next frame's renderPhase incremental clone.\n const getBuffer = () => instance.prevPaintedBuffer ?? instance.prevBuffer\n\n const sendInput = (data: string) => {\n if (!instance.mounted) {\n throw new Error(\"Cannot write to stdin after unmount\")\n }\n if (instance.rendering) {\n throw new Error(\n \"silvery: Re-entrant render detected. \" +\n \"Cannot call press()/stdin.write() from inside a React render or effect. \" +\n \"Use setTimeout or an event handler instead.\",\n )\n }\n const t0 = performance.now()\n instance.rendering = true\n try {\n // Check for bracketed paste before splitting into individual keys.\n // Paste content is delivered as a single \"paste\" event, not individual keystrokes.\n // This mirrors the production path in term-provider.ts.\n //\n // parseBracketedPaste may throw ProtocolError when PASTE_START is found\n // but PASTE_END is missing in the test input (typically a fixture\n // mistake or intentional malformed-input test). The test renderer\n // mirrors the runtime input-owner contract: log + fall through.\n // Bead: @km/silvery/15127-custom-protocol-implementation/protocol-loud-errors.\n let pasteResult: ReturnType<typeof parseBracketedPaste> = null\n try {\n pasteResult = parseBracketedPaste(data)\n } catch (err) {\n if (isProtocolError(err)) {\n log.debug?.(\n `bracketed paste parser flagged malformed input in press(): ${err.reason} (len=${err.inputLength})`,\n )\n } else {\n throw err\n }\n }\n if (pasteResult) {\n withActEnvironment(() => {\n act(() => {\n instance.inputEmitter.emit(\"paste\", pasteResult.content)\n })\n })\n } else {\n // Split multi-character data into individual keypresses.\n // This mirrors the production path (render.tsx handleReadable)\n // where stdin.read() can return buffered characters.\n withActEnvironment(() => {\n for (const keypress of splitRawInput(data)) {\n // Default Tab/Shift+Tab focus cycling and Escape blur.\n // Matches production behavior in run.tsx and render.tsx.\n // Tab events are consumed (not passed to useInput handlers).\n // Each focus change runs in its own act() boundary so React\n // commits the re-render before the next keypress or doRender().\n const [, key] = parseKey(keypress)\n if (handleTabCycling && key.tab && !key.shift) {\n act(() => {\n const root = getContainerRoot(instance.container)\n focusManager.focusNext(root)\n })\n continue\n }\n if (handleTabCycling && key.tab && key.shift) {\n act(() => {\n const root = getContainerRoot(instance.container)\n focusManager.focusPrev(root)\n })\n continue\n }\n if (key.escape && focusManager.activeElement) {\n act(() => {\n focusManager.blur()\n })\n continue\n }\n act(() => {\n instance.inputEmitter.emit(\"input\", keypress)\n })\n }\n })\n } // end else (non-paste input)\n } finally {\n instance.rendering = false\n }\n\n const t1 = performance.now()\n // doRender() handles SILVERY_STRICT checking internally\n let newFrame = doRender()\n\n // Effect-flush after doRender() — matching production's\n // processEventBatch pattern (create-app.tsx). Production does:\n // doRender → await Promise.resolve() → check pendingRerender → repeat.\n // In tests we use act(flushSyncWork) as the synchronous equivalent.\n //\n // Only runs when the caller opted into a tight cap (cap <=\n // MAX_CONVERGENCE_PASSES). With wider caps (legacy multi-iteration\n // stabilization), doRender's internal loop already absorbs the\n // feedback in its own iterations — running the outer flush in\n // addition would consume microtasks the test fixture timeline\n // depends on (e.g. controller event ordering in chat-stability).\n let doRenderCount = 1\n if (instance.maxLayoutPasses <= MAX_CONVERGENCE_PASSES) {\n let flushCount = 0\n if (INSTRUMENT) beginConvergenceLoop()\n for (let flush = 0; flush < MAX_CONVERGENCE_PASSES; flush++) {\n hadReactCommit = false\n flushCount = flush + 1\n if (INSTRUMENT) beginPass(flush)\n withActEnvironment(() => {\n act(() => {\n reconciler.flushSyncWork()\n })\n })\n if (!hadReactCommit) break\n // Always-on exhaustion marker → never-empty violation ring.\n if (flush === MAX_CONVERGENCE_PASSES - 1)\n recordPassRing(\"unknown\", \"effect-flush-exhaustion\")\n if (INSTRUMENT) {\n notePassCommit(flush)\n if (flush === MAX_CONVERGENCE_PASSES - 1) {\n logPass({ cause: \"unknown\", detail: \"effect-flush-exhaustion\" })\n }\n }\n newFrame = doRender()\n doRenderCount++\n }\n if (flushCount >= MAX_CONVERGENCE_PASSES && hadReactCommit) {\n assertBoundedConvergence(flushCount, \"effect-flush\", MAX_CONVERGENCE_PASSES)\n }\n }\n\n // Commit boundary — promote in-flight rect signals to committed peers,\n // then settle any forceUpdate-driven re-renders with one more pass.\n // Reactive useBoxRect/useScrollRect/useScreenRect consumers see one\n // stable value across the whole sendInput cycle. See bead\n // `@km/silvery/use-deferred-box-rect-and-post-commit-observers`.\n const settled = settleAfterCommit(newFrame)\n if (settled !== newFrame) {\n newFrame = settled\n doRenderCount++\n }\n\n // When multiple doRender() calls ran (layout feedback, effects), the final\n // buffer's dirty rows only cover the LAST call's writes. Rows changed in\n // earlier doRender calls are invisible to callers using outputPhase to diff\n // against an older prevBuffer. Mark all rows dirty for correctness.\n if (incremental && doRenderCount > 1 && instance.prevBuffer) {\n instance.prevBuffer!.markAllRowsDirty()\n }\n\n const t2 = performance.now()\n instance.frames.push(newFrame)\n onFrame?.(newFrame, instance.prevBuffer!, getRootContentHeight())\n if (debug) {\n console.log(\"[silvery] stdin.write:\", newFrame)\n }\n // Expose timing on global for benchmarking\n ;(globalThis as any).__silvery_last_timing = {\n actMs: t1 - t0,\n renderMs: t2 - t1,\n }\n }\n\n const rerenderFn = (newElement: ReactNode) => {\n if (!instance.mounted) {\n throw new Error(\"Cannot rerender after unmount\")\n }\n if (instance.rendering) {\n throw new Error(\n \"silvery: Re-entrant render detected. \" +\n \"Cannot call rerender() from inside a React render or effect.\",\n )\n }\n instance.rendering = true\n try {\n withActEnvironment(() => {\n act(() => {\n currentElement = newElement as ReactElement\n reconciler.updateContainerSync(\n wrapWithContexts(currentElement),\n instance.fiberRoot,\n null,\n null,\n )\n reconciler.flushSyncWork()\n })\n })\n } finally {\n instance.rendering = false\n }\n const newFrame = settleAfterCommit(doRender())\n instance.frames.push(newFrame)\n onFrame?.(newFrame, instance.prevBuffer!, getRootContentHeight())\n if (debug) {\n console.log(\"[silvery] Rerender:\", newFrame)\n }\n }\n\n // Track this render for leak detection\n const renderTracker = { unmount: () => {}, id: renderId }\n const renderRef = new WeakRef(renderTracker)\n activeRenders.add(renderRef)\n\n const unmountFn = () => {\n if (!instance.mounted) {\n throw new Error(\"Already unmounted\")\n }\n // The root is created as ConcurrentRoot (see createFiberRoot). Mount and\n // rerender both use updateContainerSync + flushSyncWork; unmount must do\n // the same so React layout-effect cleanups (e.g. useBoxRect's\n // signalEffect disposers) actually run synchronously. The async\n // updateContainer(null, …) path on a ConcurrentRoot leaves cleanups\n // pending past unmount, which kept signal subscriptions + the whole\n // RenderInstance graph alive across mount/unmount cycles.\n const fiberRoot = instance.fiberRoot\n withActEnvironment(() => {\n act(() => {\n reconciler.updateContainerSync(null, fiberRoot, null, null)\n reconciler.flushSyncWork()\n })\n })\n\n // Dispose the per-render app-root scope AFTER React unmount (so\n // useScopeEffect child scopes and host-attached scopes have already\n // disposed via fiber teardown) but BEFORE releaseContainer. Mirrors\n // create-app.tsx's `appScope[Symbol.asyncDispose]()` on-exit step.\n // Fire-and-forget: unmountFn is sync, so any async-disposer rejection\n // routes through reportDisposeError (phase \"app-exit\") instead of\n // throwing into the caller.\n void renderScope[Symbol.asyncDispose]().catch((error) =>\n reportDisposeError(error, { phase: \"app-exit\", scope: renderScope }),\n )\n\n instance.mounted = false\n instance.rendering = false\n autoRenderScheduled = false\n inRenderCycle = false\n\n instance.inputEmitter.removeAllListeners()\n stdoutEmitter.removeAllListeners()\n\n // Clean up stdin bridge\n if (stdinStream && stdinOnReadable) {\n stdinStream.removeListener(\"readable\", stdinOnReadable)\n stdinOnReadable = undefined\n }\n\n // Untrack this render\n activeRenders.delete(renderRef)\n\n // Drop heavy retained state and break the FiberRoot → Container.onRender\n // → RenderInstance retention chain. Without releaseContainer(), even a\n // synchronously-flushed unmount keeps the full instance reachable\n // through the container that the FiberRoot still references.\n clearFn()\n instance.kittyActive = false\n releaseContainer(instance.container)\n instance.fiberRoot = null\n\n if (debug) {\n console.log(\"[silvery] Unmounted\")\n }\n }\n renderTracker.unmount = unmountFn\n\n const clearFn = () => {\n instance.frames.length = 0\n instance.prevBuffer = null\n instance.prevPaintedBuffer = null\n // Snapshots reference cells in the discarded prev buffer; reset alongside.\n instance.postState = createRenderPostState()\n }\n\n const debugFn = () => {\n console.log(debugTree(getContainerRoot(instance.container)))\n }\n\n // actAndRender: wrap a callback in act() so React state updates are flushed,\n // then doRender() to update the buffer. Used by click/wheel/doubleClick.\n const actAndRenderFn = (fn: () => void) => {\n if (!instance.mounted) return\n withActEnvironment(() => {\n act(() => {\n fn()\n reconciler.updateContainerSync(\n wrapWithContexts(currentElement),\n instance.fiberRoot,\n null,\n null,\n )\n reconciler.flushSyncWork()\n })\n })\n const newFrame = settleAfterCommit(doRender())\n instance.frames.push(newFrame)\n onFrame?.(newFrame, instance.prevBuffer!, getRootContentHeight())\n }\n\n // Resize: update dimensions, clear prevBuffer, re-render (matches scheduler resize behavior)\n const resizeFn = (newCols: number, newRows: number) => {\n if (!instance.mounted) {\n throw new Error(\"Cannot resize after unmount\")\n }\n instance.columns = newCols\n instance.rows = newRows\n mockStdout.columns = newCols\n mockStdout.rows = newRows\n updateMockTermSize?.(newCols, newRows)\n // Emit resize event so component-level listeners (e.g., ScrollbackView's\n // width tracking) fire before the render, matching real terminal behavior.\n stdoutEmitter.emit(\"resize\")\n // Clear prevBuffer to force full redraw (matches scheduler.setupResizeListener)\n instance.prevBuffer = null\n instance.prevPaintedBuffer = null\n // Outline snapshots reference cells in the OLD-dimensions buffer; if we\n // kept them, the next frame's clearPreviousOutlines would write at\n // potentially out-of-bounds coordinates. Reset alongside prevBuffer.\n instance.postState = createRenderPostState()\n // Re-render at new dimensions\n let newFrame = doRender()\n\n // Drain any remaining React effects after resize. Resize is a worst\n // case for layout-subscriber feedback (the multi-pass cascade in\n // doRender's internal loop may exit with React still dirty from a\n // late subscriber notify). Same gate as sendInput's effect-flush\n // (only runs at tight caps; wider caps already absorb in doRender).\n let doRenderCount = 1\n if (instance.maxLayoutPasses <= MAX_CONVERGENCE_PASSES) {\n for (let flush = 0; flush < MAX_CONVERGENCE_PASSES; flush++) {\n hadReactCommit = false\n withActEnvironment(() => {\n act(() => {\n reconciler.flushSyncWork()\n })\n })\n if (!hadReactCommit) break\n newFrame = doRender()\n doRenderCount++\n }\n }\n\n // Commit boundary — see `settleAfterCommit` JSDoc.\n const settled = settleAfterCommit(newFrame)\n if (settled !== newFrame) {\n newFrame = settled\n doRenderCount++\n }\n\n const prevBufferForDirtying = instance.prevBuffer as TerminalBuffer | null\n if (incremental && doRenderCount > 1 && prevBufferForDirtying) {\n prevBufferForDirtying.markAllRowsDirty()\n }\n\n instance.frames.push(newFrame)\n onFrame?.(newFrame, instance.prevBuffer!, getRootContentHeight())\n if (debug) {\n console.log(\"[silvery] Resize:\", newCols, \"x\", newRows)\n }\n }\n\n // Build unified App instance\n return buildApp({\n getContainer,\n getBuffer,\n sendInput,\n rerender: rerenderFn,\n unmount: unmountFn,\n waitUntilExit: () => Promise.resolve(),\n clear: clearFn,\n exitCalled: () => exitCalledFlag,\n exitError: () => exitErrorValue,\n freshRender: doFreshRender,\n debugFn,\n frames: instance.frames,\n columns: cols,\n rows: rows,\n kittyMode,\n actAndRender: actAndRenderFn,\n resize: resizeFn,\n focusManager,\n getCursorState: cursorStore.accessors.getCursorState,\n waitForLayoutStable,\n clsMonitor,\n })\n}\n\n// ============================================================================\n// createStore() — flatten providers into { cols, rows, events() }\n// ============================================================================\n\n/**\n * Create a Store from provider options.\n *\n * A Store is the TermDef-like environment: cols, rows, and optionally events.\n *\n * @example\n * ```tsx\n * const store = createStore({ cols: 80, rows: 24 })\n * const app = render(<App />, store)\n * ```\n */\nexport function createStore(options: StoreOptions = {}): Store {\n const { cols = 80, rows = 24, events: eventsSource } = options\n\n const store: Store = {\n cols,\n rows,\n }\n\n if (eventsSource) {\n store.events = () => eventsSource\n }\n\n return store\n}\n\n// ============================================================================\n// createRenderer() — factory with auto-cleanup\n// ============================================================================\n\n/**\n * Per-render overrides for createRenderer's returned function.\n */\nexport interface PerRenderOptions {\n /** Enable incremental rendering for this render. */\n incremental?: boolean\n /** Max layout-pass iterations per render. See RenderOptions.maxLayoutPasses. */\n maxLayoutPasses?: number\n /** Use Kitty keyboard protocol encoding for press(). */\n kittyMode?: boolean\n}\n\n/**\n * Create a render function that auto-cleans previous renders.\n *\n * By default, incremental rendering is ENABLED for test renders.\n * This matches production behavior (live scheduler uses incremental)\n * and enables withDiagnostics to catch incremental vs fresh mismatches.\n *\n * **Instance reuse:** When the same renderer is called repeatedly with a\n * compatible element (no overrides that would require fresh state), the\n * returned function calls `current.rerender(element)` instead of\n * unmount+remount. This avoids paying the full React reconciler init +\n * fiberRoot creation cost on each iteration — which otherwise dominates\n * cold-render microbenchmarks. See bead `km-silvery.renderer-reuse`.\n *\n * Reuse is skipped (forcing fresh mount) when:\n * - The previous instance has been unmounted (rerender would throw)\n * - Per-render overrides are supplied that conflict with baseOpts\n * (e.g., flipping `incremental`, `maxLayoutPasses`, or `kittyMode`)\n * - The base options are a Store (store-mode renders manage their own\n * lifecycle and we conservatively force a fresh mount)\n *\n * @example\n * ```tsx\n * const render = createRenderer({ cols: 80, rows: 24 })\n * const app1 = render(<Foo />) // fresh mount\n * const app2 = render(<Bar />) // reuses app1 via rerender()\n * // app1 === app2 when reuse kicks in\n *\n * // Explicitly disable incremental if needed\n * const render2 = createRenderer({ cols: 80, rows: 24, incremental: false })\n * ```\n */\nexport function createRenderer(\n optsOrStore: RenderOptions | Store = {},\n): (el: ReactElement, overrides?: PerRenderOptions) => App {\n let current: App | null = null\n // Tracks whether `current` is reusable via rerender(). Set to false when\n // the instance is unmounted (either via an exception path below or if the\n // caller called unmount() directly — in which case rerender() will throw\n // and we fall back to a fresh mount).\n let currentReusable = false\n\n // Default to incremental: true for test renders (matches production behavior)\n // User can explicitly pass incremental: false to disable\n // Note: When passed a Store-like object (cols/rows only), convert to RenderOptions with incremental.\n // The converted baseOpts always has `incremental`, so isStore(baseOpts) === false.\n const baseOpts = isStore(optsOrStore)\n ? { incremental: true, cols: optsOrStore.cols, rows: optsOrStore.rows }\n : { incremental: true, ...optsOrStore }\n\n return (element: ReactElement, overrides?: PerRenderOptions): App => {\n // Fast path: reuse the existing instance via rerender() when safe.\n // This skips unmount + full React reconciler re-init on every call,\n // which dominates cold-render microbenchmarks against Ink.\n //\n // We reuse when:\n // 1. We have a live previous instance.\n // 2. No overrides that conflict with the base config (dimensions are\n // always identical since they come from the fixed baseOpts).\n //\n // On any rerender() exception (e.g., the caller unmounted `current`\n // manually), we fall through to the legacy unmount+render path.\n if (current && currentReusable && canReuseInstance(overrides, baseOpts)) {\n try {\n current.rerender(element)\n return current\n } catch {\n // Fall through to unmount+remount path. The instance may have been\n // unmounted by the caller or torn down by an error.\n currentReusable = false\n }\n }\n\n if (current) {\n try {\n current.unmount()\n } catch {\n // Already unmounted\n }\n }\n let opts = baseOpts\n if (overrides && !isStore(opts)) {\n opts = { ...opts, ...overrides }\n }\n current = render(element, opts)\n currentReusable = true\n return current\n }\n}\n\n/**\n * Can we reuse the current App instance for this call? Overrides that would\n * change reconciler/layout semantics force a fresh mount.\n */\nfunction canReuseInstance(\n overrides: PerRenderOptions | undefined,\n baseOpts: { incremental?: boolean; maxLayoutPasses?: number; kittyMode?: boolean },\n): boolean {\n if (!overrides) return true\n if (\n overrides.incremental !== undefined &&\n overrides.incremental !== (baseOpts.incremental ?? true)\n ) {\n return false\n }\n if (\n overrides.maxLayoutPasses !== undefined &&\n overrides.maxLayoutPasses !== baseOpts.maxLayoutPasses\n ) {\n return false\n }\n if (overrides.kittyMode !== undefined && overrides.kittyMode !== (baseOpts.kittyMode ?? false)) {\n return false\n }\n return true\n}\n\n// ============================================================================\n// run() — event loop driver\n// ============================================================================\n\n/**\n * Result of run() with sync events — iterable over events.\n */\nexport interface SyncRunResult extends Iterable<string> {\n /** Current rendered text */\n readonly text: string\n /** The app being driven */\n readonly app: App\n}\n\n/**\n * Result of run() with async events — async iterable and awaitable.\n */\nexport interface AsyncRunResult extends AsyncIterable<string>, PromiseLike<void> {\n /** Current rendered text */\n readonly text: string\n /** The app being driven */\n readonly app: App\n /** Unmount and stop the event loop */\n unmount(): void\n}\n\n/**\n * Drive an App with events.\n *\n * - `run(app, ['j', 'k', 'Enter'])` — sync, explicit key events\n * - `run(app, syncIterable)` — sync iteration over events\n * - `await run(app)` — async, reads events from store (if rendered with one)\n * - `for await (const text of run(app, asyncEvents))` — async iteration\n *\n * @example\n * ```tsx\n * const app = render(<Counter />, { cols: 80, rows: 24 })\n * run(app, ['j', 'k', 'Enter'])\n * expect(app.text).toContain('Count: 2')\n * ```\n */\nexport function run(app: App, events: string[]): SyncRunResult\nexport function run(app: App, events: Iterable<string>): SyncRunResult\nexport function run(app: App, events?: AsyncIterable<string>): AsyncRunResult\nexport function run(\n app: App,\n events?: string[] | Iterable<string> | AsyncIterable<string>,\n): SyncRunResult | AsyncRunResult {\n // Sync path: array or sync iterable\n if (events !== undefined && !isAsyncIterable(events)) {\n const iter = Array.isArray(events) ? events : events\n const processedEvents: string[] = []\n\n for (const key of iter) {\n app.stdin.write(keyToAnsi(key))\n processedEvents.push(key)\n }\n\n return {\n get text() {\n return app.text\n },\n app,\n [Symbol.iterator]() {\n return processedEvents[Symbol.iterator]()\n },\n }\n }\n\n // Async path\n let stopped = false\n const unmount = () => {\n stopped = true\n app.unmount()\n }\n\n const asyncResult: AsyncRunResult = {\n get text() {\n return app.text\n },\n app,\n unmount,\n\n // PromiseLike — `await run(app)` or `await run(app, asyncEvents)`\n then<TResult1 = void, TResult2 = never>(\n onfulfilled?: ((value: void) => TResult1 | PromiseLike<TResult1>) | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,\n ): Promise<TResult1 | TResult2> {\n const promise = (async () => {\n if (events) {\n for await (const key of events) {\n if (stopped) break\n await app.press(key)\n }\n } else {\n // No events — wait until exit\n await app.run()\n }\n })()\n return promise.then(onfulfilled, onrejected)\n },\n\n // AsyncIterable — `for await (const text of run(app, asyncEvents))`\n [Symbol.asyncIterator](): AsyncIterator<string> {\n if (!events) {\n // No events source — yield current text, then done\n let yielded = false\n return {\n async next(): Promise<IteratorResult<string>> {\n if (yielded || stopped) {\n return { done: true, value: undefined as unknown as string }\n }\n yielded = true\n return { done: false, value: app.text }\n },\n }\n }\n\n const iter = (events as AsyncIterable<string>)[Symbol.asyncIterator]()\n return {\n async next(): Promise<IteratorResult<string>> {\n if (stopped) {\n return { done: true, value: undefined as unknown as string }\n }\n const result = await iter.next()\n if (result.done) {\n return { done: true, value: undefined as unknown as string }\n }\n await app.press(result.value)\n return { done: false, value: app.text }\n },\n async return(): Promise<IteratorResult<string>> {\n unmount()\n return { done: true, value: undefined as unknown as string }\n },\n }\n },\n }\n\n return asyncResult\n}\n\nfunction isAsyncIterable(value: unknown): value is AsyncIterable<unknown> {\n return value !== null && typeof value === \"object\" && Symbol.asyncIterator in value\n}\n\n/**\n * Run a function with IS_REACT_ACT_ENVIRONMENT temporarily set to true.\n * This ensures act() works correctly without polluting the global scope.\n */\nfunction withActEnvironment(fn: () => void): void {\n const prev = (globalThis as any).IS_REACT_ACT_ENVIRONMENT\n ;(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true\n try {\n fn()\n } finally {\n ;(globalThis as any).IS_REACT_ACT_ENVIRONMENT = prev\n }\n}\n\n// ============================================================================\n// Test Utilities\n// ============================================================================\n\n/**\n * Get the number of currently active (mounted) render instances.\n * Useful for tests to verify cleanup.\n */\nexport function getActiveRenderCount(): number {\n return pruneAndCountActiveRenders()\n}\n\n// ============================================================================\n// STRICT Diagnostics — Backdrop Overlay Mismatch\n// ============================================================================\n\n/**\n * Format a human-readable diff when the incremental and fresh Kitty overlays\n * disagree. The overlay is a Kitty graphics protocol escape sequence emitted\n * by `realizeToKitty` (see `pipeline/backdrop/`) — it\n * places translucent \"scrim\" images over emoji cells inside faded regions.\n * The invariant is that the fresh and incremental paths emit byte-identical\n * strings for the same tree; any drift is a determinism bug.\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 quickest\n * path from \"test failed\" to \"which cell drifted\".\n */\nfunction formatRendererOverlayMismatch(\n incremental: string,\n fresh: string,\n renderNum: number,\n): string {\n const incPlacements = extractOverlayPlacementIds(incremental)\n const freshPlacements = extractOverlayPlacementIds(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 extractOverlayPlacementIds(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// Re-exports\n// ============================================================================\n\nexport { keyToAnsi } from \"@silvery/ag/keys\"\nexport type { App } from \"./app.js\"\n","/**\n * Silvery Testing Library\n *\n * Unified App-based API for testing Silvery components.\n * Uses the actual silvery render pipeline for accurate ANSI output.\n *\n * ## Import Syntax\n *\n * ```tsx\n * import { createRenderer, bufferToText, stripAnsi } from '@silvery/test';\n * ```\n *\n * ## Auto-cleanup\n *\n * Each render() call from createRenderer automatically unmounts the previous render,\n * so you don't need explicit cleanup.\n *\n * ## Basic Testing\n *\n * @example\n * ```tsx\n * import { createRenderer } from '@silvery/test';\n * import { Text, Box } from '@silvery/ag-react';\n *\n * const render = createRenderer({ cols: 80, rows: 24 });\n *\n * test('renders text', () => {\n * const app = render(<Text>Hello</Text>);\n *\n * // Plain text (no ANSI)\n * expect(app.text).toContain('Hello');\n *\n * // Auto-refreshing locators\n * expect(app.getByText('Hello').count()).toBe(1);\n * });\n * ```\n *\n * ## Keyboard Input Testing\n *\n * @example\n * ```tsx\n * test('handles keyboard', () => {\n * const app = render(<MyComponent />);\n *\n * await app.press('j'); // Letter key\n * await app.press('ArrowUp'); // Arrow keys\n * await app.press('Escape'); // Special keys\n * await app.press('Enter'); // Enter\n *\n * expect(app.text).toContain('expected result');\n * });\n * ```\n *\n * ## Auto-refreshing Locators\n *\n * @example\n * ```tsx\n * test('locators auto-refresh', () => {\n * const app = render(<Board />);\n * const cursor = app.locator('[data-cursor]');\n *\n * expect(cursor.textContent()).toBe('item1');\n * await app.press('j');\n * expect(cursor.textContent()).toBe('item2'); // Same locator, fresh result!\n * });\n * ```\n *\n * ## Querying by ID\n *\n * Two equivalent approaches for identifying components:\n *\n * @example\n * ```tsx\n * // Option 1: id prop with #id selector (CSS-style, preferred)\n * const app = render(<Box id=\"sidebar\">Content</Box>);\n * expect(app.locator('#sidebar').textContent()).toBe('Content');\n *\n * // Option 2: testID prop with getByTestId (React Testing Library style)\n * const app = render(<Box testID=\"sidebar\">Content</Box>);\n * expect(app.getByTestId('sidebar').textContent()).toBe('Content');\n * ```\n */\n\nimport { ensureDefaultLayoutEngine } from \"@silvery/ag-term/layout-engine\"\n\n// Re-export App for type usage\nexport type { App } from \"@silvery/ag-term/app\"\nexport { createAutoLocator, type AutoLocator, type FilterOptions } from \"./auto-locator\"\nexport type { BoundTerm } from \"@silvery/ag-term/bound-term\"\n\n// Re-export buffer utilities for testing convenience\nexport { bufferToText, bufferToStyledText, bufferToHTML } from \"@silvery/ag-term/buffer\"\nexport type { TerminalBuffer } from \"@silvery/ag-term/buffer\"\n\nexport type { Rect } from \"@silvery/ag/types\"\n\n// CLS (Cumulative Layout Shift) types — re-exported so tests can import\n// from @silvery/test rather than reaching across @silvery/ag and\n// @silvery/ag-term. Bead: km-silvery.cls-instrumentation-primitive\nexport type { CLSReport, LayoutShift, ReflowReason } from \"@silvery/ag/cls\"\nexport type { ReasonClassifier } from \"@silvery/ag/cls\"\nexport {\n CLS_STRICT_MIN_TIER,\n CLS_STRICT_SLUG,\n UnexpectedLayoutShiftError,\n isClsStrictEnabled,\n} from \"@silvery/ag-term/strict-cls\"\n\n// Re-export keyboard utilities\nexport { keyToAnsi, keyToKittyAnsi, CODE_TO_KEY } from \"@silvery/ag/keys\"\n\n// Re-export debug utilities\nexport { debugTree, type DebugTreeOptions } from \"./debug\"\n\n// Diagnostic theme for pipeline regression tests — every distinct token\n// resolves to a distinct visible color. Use to catch phantom bg paints\n// that would be invisible under themes where tokens collapse to canvas.\nexport { diagnosticTheme, diagnosticScheme } from \"./diagnostic-theme\"\n\n// Re-export buffer comparison utilities\nexport { compareBuffers, formatMismatch, type BufferMismatch } from \"./compare-buffers\"\nexport {\n readCellSnapshot,\n readCellRow,\n formatSelectableCells,\n type TestCellSnapshot,\n} from \"./cell-inspector\"\n\n// Re-export frame-recorder API (per-paint TextFrame snapshots over time).\nexport {\n recordFrames,\n type FrameRecording,\n type FrameRecordable,\n type CellChange,\n type TransientPredicate,\n} from \"./frame-recorder\"\n\n// Re-export render API\nexport {\n render,\n createRenderer,\n createStore,\n run,\n ensureEngine,\n getActiveRenderCount,\n type RenderOptions,\n type PerRenderOptions,\n type Store,\n type StoreOptions,\n} from \"@silvery/ag-term/renderer\"\n\n// ============================================================================\n// Module Initialization\n// ============================================================================\n\n// Configure React to recognize this as a testing environment for act() support\n// This suppresses the \"testing environment not configured\" warning\n;(\n globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }\n).IS_REACT_ACT_ENVIRONMENT = true\n\n// Initialize default layout engine via top-level await.\n// This ensures render()/createRenderer() work immediately after import.\nawait ensureDefaultLayoutEngine()\n\n// ============================================================================\n// Termless — in-process terminal emulation for full ANSI testing\n// ============================================================================\n\nimport {\n createTerm,\n type Term,\n type TerminalCaps,\n type TermEmulatorBackend,\n} from \"@silvery/ag-term\"\nimport { preloadStrictTerminalBackends } from \"@silvery/ag-term/strict-terminal-backends\"\nimport { warnOnce } from \"@silvery/ansi\"\nimport { createGhosttyBackend } from \"@termless/ghostty\"\nimport { createGhosttyNativeBackend } from \"@termless/ghostty-native\"\nimport { createXtermBackend } from \"@termless/xtermjs\"\n\n// Preload the @termless emulator backends into ag-term's strict-verify cache so\n// the SYNCHRONOUS SILVERY_STRICT_TERMINAL / cursor verifiers (output-verify.ts,\n// cursor-diagnostics.ts) resolve them mid-frame WITHOUT createRequire — the\n// 2026-07-02 Ghostty-WASM singleton-split fix. The backends are already statically\n// imported just above, so this is a free ESM-graph re-resolve; ghostty WASM init\n// stays deferred to each ghostty test's own `await initGhostty()` (initGhosttyWasm:\n// false) so non-ghostty suites don't pay the WASM load.\nawait preloadStrictTerminalBackends({ ghostty: true, initGhosttyWasm: false })\n\n/**\n * Live-termless tracker. Each `createTermless()` registers a WeakRef to the\n * returned Term; when the Term is disposed (Symbol.dispose → emulator.close\n * → backend.destroy → xterm Terminal.dispose), the GC eventually clears the\n * ref. A large number of live un-disposed terminals typically means a test\n * forgot to use `using term = createTermless(...)` — the xterm Terminal\n * (1000-line scrollback buffer) stays resident until the worker exits.\n *\n * See bead km-silvery.termless-memleak.\n */\nconst liveTermlessInstances = new Set<WeakRef<Term>>()\n\n/** Tunable guard threshold — print a warning when we exceed this many live Terms. */\nconst TERMLESS_LEAK_WARN_THRESHOLD = 128\n\n/** Warning ID for the shared warnOnce latch. */\nconst TERMLESS_LEAK_WARNING_ID = \"silvery/test:termless-leak\"\n\n/**\n * Prune GC'd entries from the tracker and return the count of still-live\n * instances. Cheap O(n) scan; n is small in practice.\n */\nfunction pruneLiveTermless(): number {\n let live = 0\n for (const ref of liveTermlessInstances) {\n if (ref.deref() === undefined) {\n liveTermlessInstances.delete(ref)\n } else {\n live++\n }\n }\n return live\n}\n\n/**\n * Return the current count of live (un-GC'd) termless Term instances.\n * Exported for tests that want to assert cleanup happened.\n */\nexport function getActiveTermlessCount(): number {\n return pruneLiveTermless()\n}\n\n// ============================================================================\n// Mouse + clipboard ergonomic surface\n//\n// `createTermless()` returns a Term augmented with `.mouse` and `.clipboard`.\n// These remove the need for hand-rolled SGR byte strings and `(term as any)`\n// casts in tests — the first-class ergonomic API for mouse interactions.\n//\n// Tracking bead: km-silvery.expose-termless-mouse.\n// ============================================================================\n\n/** Modifier keys for synthetic mouse events (matches termless MouseModifiers). */\nexport interface TermlessMouseModifiers {\n ctrl?: boolean\n shift?: boolean\n alt?: boolean\n /** SGR mouse uses the same modifier bit for Meta and Alt. */\n meta?: boolean\n}\n\n/** Options for a mouse event (button + modifiers). */\nexport interface TermlessMouseOptions extends TermlessMouseModifiers {\n /** 0=left (default), 1=middle, 2=right */\n button?: 0 | 1 | 2\n}\n\n/** Pixel-coordinate pair, 0-indexed. */\nexport type TermlessPoint = [x: number, y: number]\n\nexport type TermlessMouseCoordinateMode = \"auto\" | \"cell\" | \"pixel\"\nexport type TermlessWheelDirection = \"up\" | \"down\"\n\nexport interface TermlessMouseCellSize {\n width: number\n height: number\n}\n\nexport interface TermlessTrackpadFlickPacket {\n /** Relative delivery time from gesture start. Packets with the same value are one stdin chunk. */\n atMs: number\n /** Number of identical wheel reports in this packet. Preferred spelling for new tests. */\n count?: number\n /** Compatibility alias for older gesture fixtures. */\n events?: number\n /** Wheel delta sign; negative = up, positive = down. Also supplies count when count/events are omitted. */\n delta?: number\n /** Exact SGR wheel button byte for terminal captures: 64 up, 65 down, 66 left, 67 right. */\n button?: number\n /** Direction override for this packet. */\n direction?: TermlessWheelDirection\n /** Cell coordinate override for this packet. */\n x?: number\n /** Cell coordinate override for this packet. */\n y?: number\n}\n\nexport interface TermlessTrackpadFlickProfile {\n /** Cell coordinate under the pointer. Pixel mode converts this through cellSize. */\n x: number\n /** Cell coordinate under the pointer. Pixel mode converts this through cellSize. */\n y: number\n direction?: TermlessWheelDirection\n /**\n * Coordinate mode for encoded SGR reports.\n * - auto: mirror the app's negotiated SGR/SGR-Pixels mode\n * - cell: force SGR cell coordinates\n * - pixel: force SGR-Pixels coordinates using cellSize\n */\n coordinateMode?: TermlessMouseCoordinateMode\n cellSize?: TermlessMouseCellSize\n settleMs?: number\n packets: readonly TermlessTrackpadFlickPacket[]\n}\n\nexport interface TermlessWheelOptions extends TermlessMouseModifiers {\n /** Horizontal wheel delta. Negative = left, positive = right. */\n deltaX?: number\n /** Vertical wheel delta. Negative = up, positive = down. */\n deltaY?: number\n /** Compatibility alias for vertical wheel delta. */\n lines?: number\n /** Delay between repeated reports. Default: 8ms, matching ~120Hz trackpad cadence. */\n cadenceMs?: number\n /** Coordinate mode for encoded SGR reports. Defaults to auto-detect. */\n coordinateMode?: TermlessMouseCoordinateMode\n cellSize?: TermlessMouseCellSize\n}\n\nexport interface TermlessTrackpadFlickGroup {\n index: number\n atMs: number\n packetCount: number\n eventCount: number\n deltaSum: number\n}\n\nexport interface TermlessTrackpadFlickResult {\n eventCount: number\n durationMs: number\n groups: readonly TermlessTrackpadFlickGroup[]\n}\n\nexport interface TermlessTrackpadFlickDebugLogOptions {\n /** Default: \"pixel\" when SGR-Pixels cell size can be inferred, otherwise \"cell\". */\n coordinateMode?: TermlessMouseCoordinateMode\n /** Override inferred cell size for SGR-Pixels logs. */\n cellSize?: TermlessMouseCellSize\n /** Override profile x. Defaults to first parsed wheel event x. */\n x?: number\n /** Override profile y. Defaults to first parsed wheel event y. */\n y?: number\n}\n\ninterface ParsedDebugWheelEvent {\n time: number\n x: number\n y: number\n delta: number\n button?: number\n sgrX?: number\n sgrY?: number\n}\n\nexport function parseTermlessTrackpadFlickProfileFromDebugLog(\n logText: string,\n options: TermlessTrackpadFlickDebugLogOptions = {},\n): TermlessTrackpadFlickProfile {\n const events: ParsedDebugWheelEvent[] = []\n for (const line of logText.split(/\\r?\\n/)) {\n if (!line.startsWith(\"{\")) continue\n let record: unknown\n try {\n record = JSON.parse(line)\n } catch {\n continue\n }\n if (typeof record !== \"object\" || record === null) continue\n const obj = record as { time?: unknown; name?: unknown; msg?: unknown }\n if (obj.name !== \"silvery:input-owner\" || typeof obj.msg !== \"string\") continue\n if (!obj.msg.startsWith(\"parsed mouse: action=wheel\")) continue\n const parsed = parseDebugWheelMessage(obj.msg, obj.time)\n if (parsed !== null) events.push(parsed)\n }\n\n if (events.length === 0) {\n throw new Error(\"No silvery input-owner wheel events found in debug log\")\n }\n\n const first = events[0]!\n const cellSize = options.cellSize ?? inferDebugWheelCellSize(first)\n const coordinateMode = options.coordinateMode ?? (cellSize === undefined ? \"cell\" : \"pixel\")\n return {\n x: options.x ?? first.x,\n y: options.y ?? first.y,\n direction: first.delta < 0 ? \"up\" : \"down\",\n coordinateMode,\n ...(cellSize === undefined ? {} : { cellSize }),\n packets: eventsToTrackpadPackets(events),\n }\n}\n\nfunction parseDebugWheelMessage(msg: string, time: unknown): ParsedDebugWheelEvent | null {\n if (typeof time !== \"string\") return null\n const timestamp = Date.parse(time)\n if (!Number.isFinite(timestamp)) return null\n\n const coords = msg.match(/\\bx=([-0-9.]+)\\s+y=([-0-9.]+)\\s+delta=([-0-9.]+)/)\n if (coords === null) return null\n const x = Number(coords[1])\n const y = Number(coords[2])\n const delta = Number(coords[3])\n if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(delta)) return null\n\n const escapedSgr = msg.match(/\\\\u001b\\[<(\\d+);(\\d+);(\\d+)[Mm]/)\n const sgr =\n escapedSgr ?? msg.match(new RegExp(`${String.fromCharCode(27)}\\\\[<(\\\\d+);(\\\\d+);(\\\\d+)[Mm]`))\n return {\n time: timestamp,\n x,\n y,\n delta,\n ...(sgr === null\n ? {}\n : {\n button: Number(sgr[1]),\n sgrX: Number(sgr[2]),\n sgrY: Number(sgr[3]),\n }),\n }\n}\n\nfunction inferDebugWheelCellSize(event: ParsedDebugWheelEvent): TermlessMouseCellSize | undefined {\n if (event.sgrX === undefined || event.sgrY === undefined || event.x <= 0 || event.y <= 0) {\n return undefined\n }\n const width = (event.sgrX - 1) / event.x\n const height = (event.sgrY - 1) / event.y\n if (!Number.isFinite(width) || !Number.isFinite(height)) return undefined\n if (width <= 1 || height <= 1) return undefined\n return {\n width: Math.round(width * 1000) / 1000,\n height: Math.round(height * 1000) / 1000,\n }\n}\n\nfunction eventsToTrackpadPackets(\n events: readonly ParsedDebugWheelEvent[],\n): TermlessTrackpadFlickPacket[] {\n const firstTime = events[0]!.time\n const packets: TermlessTrackpadFlickPacket[] = []\n for (const event of events) {\n const atMs = Math.max(0, Math.round(event.time - firstTime))\n const last = packets[packets.length - 1]\n if (\n last !== undefined &&\n last.atMs === atMs &&\n last.delta === event.delta &&\n last.button === event.button &&\n last.x === undefined &&\n last.y === undefined\n ) {\n last.count = (last.count ?? 1) + 1\n continue\n }\n packets.push({\n atMs,\n count: 1,\n delta: event.delta,\n ...(event.button === undefined ? {} : { button: event.button }),\n })\n }\n return packets\n}\n\nexport interface TermlessTrackpadFlickOptions {\n /** Called after each timed stdin chunk is delivered and the event loop yields once. */\n afterGroup?: (group: TermlessTrackpadFlickGroup) => void | Promise<void>\n}\n\nexport interface TermlessMouse {\n /** Fire a mousedown (press only — no release). */\n down(x: number, y: number, options?: TermlessMouseOptions): Promise<void>\n /** Fire a mouseup (release only — no press). */\n up(x: number, y: number, options?: TermlessMouseOptions): Promise<void>\n /** Fire a mouse move to (x,y). Stateless — no press implied. */\n move(x: number, y: number, options?: TermlessMouseOptions): Promise<void>\n /** Down then up at the same coordinate — a plain click, no drag. */\n click(x: number, y: number, options?: TermlessMouseOptions): Promise<void>\n /** Two consecutive clicks at the same coordinate. */\n dblclick(x: number, y: number, options?: TermlessMouseOptions): Promise<void>\n /**\n * Drag from one cell to another with N intermediate moves. Dispatches\n * `down(from)` → `move(...via)` → `move(to)` → `up(to)`. Waits briefly\n * between steps so the event loop can process each before the next.\n *\n * @example drag(from: [5, 2], to: [20, 2]) // simple drag\n * @example drag(from: [10, 0], to: [5, 0], via: [[20, 0]]) // forward then shrink back\n */\n drag(opts: {\n from: TermlessPoint\n to: TermlessPoint\n via?: TermlessPoint[]\n options?: TermlessMouseOptions\n /** ms between dispatch steps. Default: 20 */\n stepDelay?: number\n }): Promise<void>\n /** Fire mouse wheel events at (x,y). Positive vertical delta = scroll down. */\n wheel(\n x: number,\n y: number,\n deltaOrOptions: number | TermlessWheelOptions,\n options?: TermlessWheelOptions,\n ): Promise<void>\n /**\n * Replay a real trackpad-style wheel gesture: timestamped packet groups,\n * optional SGR-Pixels coordinates, and many wheel reports in one stdin chunk.\n */\n trackpadFlick(\n profile: TermlessTrackpadFlickProfile,\n options?: TermlessTrackpadFlickOptions,\n ): Promise<TermlessTrackpadFlickResult>\n}\n\n/** OSC 52 clipboard capture — every clipboard write during the Term's life. */\nexport interface TermlessClipboard {\n /** Last OSC 52 payload captured, or null if none. */\n readonly last: string | null\n /** All OSC 52 payloads captured, in order. */\n readonly all: readonly string[]\n /** Drop all captured payloads (useful between test phases). */\n clear(): void\n}\n\nexport interface TermlessOutput {\n getText(): string\n getChunks(): readonly string[]\n containsOutput(text: string): boolean\n clear(): void\n}\n\nexport interface TermlessReflowResidue {\n /** Visible marker injected by the residue simulator when an app repaints without ED2. */\n readonly marker: string\n /**\n * Arm the residue simulator for the next terminal write. Resizes arm this\n * automatically; call this to model cmux workspace/alt-screen restoration\n * that reflows the terminal without changing dimensions.\n */\n arm(): void\n /** Disarm the simulator without requiring a clear. */\n clear(): void\n /** Whether the next non-clearing write will inject the marker. */\n readonly armed: boolean\n}\n\n/**\n * Term augmented with mouse + clipboard test helpers.\n *\n * `createTermless()` always returns an emulator-backed term, so `screen`,\n * `scrollback`, and `cell()` are guaranteed defined here (the `Term`\n * interface marks them optional because non-emulator-backed Terms — Node\n * stdout, headless — don't have them). Narrowing them to required removes\n * the need for `!` non-null assertions at every test callsite.\n */\nexport interface TermlessTerm extends Term {\n readonly mouse: TermlessMouse\n readonly clipboard: TermlessClipboard\n readonly out: TermlessOutput\n readonly reflowResidue?: TermlessReflowResidue\n readonly screen: NonNullable<Term[\"screen\"]>\n readonly scrollback: NonNullable<Term[\"scrollback\"]>\n cell(\n row: number,\n col: number,\n ): { readonly fg: unknown; readonly bg: unknown; readonly char: string }\n}\n\ntype TermlessBackendChoice = \"xterm\" | \"ghostty\" | \"ghostty-native\"\n\n/** Default sleep between steps in `drag()`. Short enough to be fast, long\n * enough for async event dispatch to flush. */\nconst DEFAULT_DRAG_STEP_DELAY_MS = 20\nconst DEFAULT_WHEEL_CADENCE_MS = 8\n\nconst sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms))\n\nconst DEFAULT_REFLOW_RESIDUE_MARKER = \"CMUX-REFLOW-RESIDUE\"\n\nfunction strictTerminalRequestsGhostty(value: string | undefined): boolean {\n const normalized = value?.toLowerCase().trim()\n if (!normalized) return false\n if (normalized === \"all\" || normalized === \"both\") return true\n return normalized\n .split(\",\")\n .map((part) => part.trim())\n .includes(\"ghostty\")\n}\n\nfunction inferBackendFromStrictTerminal(): TermlessBackendChoice | undefined {\n if (typeof process === \"undefined\") return undefined\n return strictTerminalRequestsGhostty(process.env.SILVERY_STRICT_TERMINAL) ? \"ghostty\" : undefined\n}\n\nfunction withReflowResidue(\n backend: TermEmulatorBackend,\n marker = DEFAULT_REFLOW_RESIDUE_MARKER,\n): { backend: TermEmulatorBackend; residue: TermlessReflowResidue } {\n let armed = false\n const encoder = new TextEncoder()\n const decoder = new TextDecoder()\n\n const residue: TermlessReflowResidue = {\n marker,\n arm() {\n armed = true\n },\n clear() {\n armed = false\n },\n get armed() {\n return armed\n },\n }\n\n const wrapped = Object.create(backend) as TermEmulatorBackend\n Object.defineProperties(wrapped, {\n name: {\n get() {\n return `${backend.name}+reflow-residue`\n },\n },\n init: {\n value(opts: { cols: number; rows: number; scrollbackLimit?: number }) {\n backend.init(opts)\n },\n },\n destroy: {\n value() {\n backend.destroy()\n },\n },\n feed: {\n value(data: Uint8Array) {\n const text = decoder.decode(data)\n // Reflow residue (multiplexer main-screen dump) is defeated by a\n // full-viewport repaint — a frame that homes the cursor and rewrites\n // every cell. Historically silvery did that with a destructive\n // `\\x1b[2J` clear prefix; @ag/code/20297-pane-flicker-on-resize dropped\n // the `2J` (it blank-flashed on every resize) in favour of relying on\n // the homed full `bufferToAnsi` repaint, which overwrites every cell —\n // including residue — by itself. Both shapes start by homing the cursor\n // (`\\x1b[H`); the bare-home sequence is the unambiguous full-render\n // signature (incremental diffs use parameterized CUP `\\x1b[row;colH`,\n // never bare `\\x1b[H`). So a frame that contains a destructive clear OR\n // a homed full repaint defeats the residue.\n const defeatsResidue = text.includes(\"\\x1b[2J\") || text.includes(\"\\x1b[H\")\n if (defeatsResidue) armed = false\n backend.feed(data)\n if (armed && !defeatsResidue) {\n backend.feed(encoder.encode(`\\x1b[H${marker}`))\n }\n },\n },\n resize: {\n value(cols: number, rows: number) {\n backend.resize(cols, rows)\n armed = true\n },\n },\n })\n\n return { backend: wrapped, residue }\n}\n\nfunction applyMouseModifierBits(btn: number, options?: TermlessMouseModifiers): number {\n if (options?.shift) btn += 4\n if (options?.alt || options?.meta) btn += 8\n if (options?.ctrl) btn += 16\n return btn\n}\n\nfunction sgrButtonByte(options?: TermlessMouseOptions): number {\n return applyMouseModifierBits(options?.button ?? 0, options)\n}\n\n/**\n * Create a Term backed by a termless xterm.js emulator for full ANSI testing.\n *\n * Convenience wrapper around `createTerm(createXtermBackend(), dims)` that\n * handles the xterm.js backend import. Use with `run()` to render components\n * into a real terminal emulator in-process — no PTY, no timing issues.\n *\n * The returned Term exposes two testing conveniences:\n * - `term.mouse.*` — ergonomic mouse API (click, drag, down/up/move, wheel)\n * - `term.clipboard` — OSC 52 capture (`term.clipboard.last`, `.all`)\n *\n * **Always dispose with `using` or an explicit `term[Symbol.dispose]()`.**\n * Without disposal, each call leaks the xterm.js Terminal (~1 MB scrollback\n * per instance). This accumulated to 18-28 GB per vitest worker in CI before\n * `km-silvery.termless-memleak` was fixed.\n *\n * @example Mouse drag + clipboard capture\n * ```tsx\n * using term = createTermless({ cols: 40, rows: 10 })\n * const handle = await run(<App />, term, { selection: true, mouse: true })\n *\n * await term.mouse.drag({ from: [5, 2], to: [20, 2] })\n * expect(term.clipboard.last).toContain(\"selected text\")\n * ```\n *\n * @example Plain click\n * ```tsx\n * await term.mouse.click(10, 5)\n * await term.mouse.click(10, 5, { shift: true })\n * ```\n *\n * @example Override capabilities (defaults-contract tests)\n * ```tsx\n * using term = createTermless({ cols: 80, rows: 24, caps: { colorLevel: '256' } })\n * ```\n *\n * Without a `caps` override, `term.caps` is populated from `defaultCaps()`\n * (truecolor / unicode / mouse). Callers that want a specific terminal\n * profile for a test — Apple_Terminal's lack of `maybeWideEmojis`, Kitty's\n * `kittyKeyboard`, etc. — can pass a `Partial<TerminalCaps>` here.\n */\nexport function createTermless(\n dims: {\n cols: number\n rows: number\n caps?: Partial<TerminalCaps>\n backend?: TermlessBackendChoice\n reflowResidue?: boolean | { marker?: string }\n } = {\n cols: 80,\n rows: 24,\n },\n): TermlessTerm {\n // Backend selection (env-overridable):\n // xterm — xterm.js (default; fast, well-tested)\n // ghostty — Ghostty WASM (closest to real Ghostty.app rendering)\n // ghostty-native — Ghostty via Zig native binding (truest fidelity)\n //\n // TERMLESS_BACKEND env var globally overrides; per-call `backend` arg\n // overrides the env. Unknown values throw immediately — no silent fallback\n // to xterm. `initGhostty()` MUST have been awaited before any ghostty-\n // backed `createTermless` call (preferred from a vitest setup).\n // SILVERY_STRICT_TERMINAL=ghostty/all is a lower-precedence inference:\n // strict terminal verification should exercise the same Ghostty backend\n // end-to-end unless the caller or suite explicitly chose otherwise.\n //\n // Note: this is a thin wrapper over `createTerm(backend, dims)`, which\n // already accepts any TermEmulatorBackend. Pass a custom backend by\n // calling `createTerm(myBackend, dims)` directly — the named choices\n // here are just the bundled backends.\n const envBackend = typeof process !== \"undefined\" ? process.env.TERMLESS_BACKEND : undefined\n const choice = dims.backend ?? envBackend ?? inferBackendFromStrictTerminal() ?? \"xterm\"\n\n let backend: import(\"@silvery/ag-term\").TermEmulatorBackend\n if (choice === \"ghostty\") {\n backend = createGhosttyBackend()\n } else if (choice === \"ghostty-native\") {\n backend = createGhosttyNativeBackend()\n } else if (choice === \"xterm\") {\n backend = createXtermBackend()\n } else {\n throw new Error(\n `[silvery/test] createTermless: unknown backend \"${choice}\". ` +\n `Valid choices: \"xterm\", \"ghostty\", \"ghostty-native\". ` +\n `For a custom backend, call createTerm(backend, dims) directly.`,\n )\n }\n\n let reflowResidue: TermlessReflowResidue | undefined\n if (dims.reflowResidue) {\n const marker = typeof dims.reflowResidue === \"object\" ? dims.reflowResidue.marker : undefined\n const wrapped = withReflowResidue(backend, marker)\n backend = wrapped.backend\n reflowResidue = wrapped.residue\n }\n\n const term = createTerm(backend, dims)\n\n // Track this instance for leak detection\n liveTermlessInstances.add(new WeakRef(term))\n const live = pruneLiveTermless()\n if (live >= TERMLESS_LEAK_WARN_THRESHOLD) {\n warnOnce(TERMLESS_LEAK_WARNING_ID, () => {\n // eslint-disable-next-line no-console\n console.warn(\n `[silvery/test] ${live} live termless Term instances detected — likely a test forgot ` +\n \"to use `using term = createTermless(...)`. Each un-disposed Term retains an \" +\n \"xterm.js Terminal with ~1 MB scrollback. See bead km-silvery.termless-memleak.\",\n )\n })\n }\n\n // --- Mouse surface ---\n // Synthetic SGR mouse injection, delegated to `sendInput` on the\n // underlying Term. Test authors call `mouse.click(x, y)` etc. with CELL\n // coordinates (0-indexed, matching Silvery's internal convention); the\n // byte format is 1-indexed at the wire.\n //\n // Coordinate-mode dispatch (post @km/silvery/run-emulator-probe-mouse-mode-parity):\n //\n // silvery's emulator-branch now probes CSI 14t / 18t at boot and upgrades\n // to SGR-Pixels (1016) when the backend answers. The xterm backend\n // synthesizes a 14t response of `rows × CELL_H_PX × cols × CELL_W_PX`\n // (CELL_W_PX = 8, CELL_H_PX = 17 — typical Iosevka/JetBrains Mono cell at\n // 12pt 96 DPI). When silvery is in pixel mode the parser divides incoming\n // SGR coordinates by `cellSize` to recover cell coords. So mouse.click\n // must encode cell `(cx, cy)` as pixel `(cx * W, cy * H)` — the top-left\n // of the cell — so the parser's `rawX / cellW` lands cleanly on cell `cx`.\n //\n // Mode is detected by scanning `term.out.getText()` for the SGR-Pixels\n // enable sequence (`\\x1b[?1016h`). The result is cached after the first\n // mouse call once silvery has emitted its first frame.\n const sendInput = (data: string) => {\n ;(term as unknown as { sendInput: (s: string) => void }).sendInput(data)\n }\n\n // Match the xterm-js backend's synthesized 14t metrics. Kept in sync with\n // vendor/termless/packages/xtermjs/src/backend.ts (CELL_W_PX / CELL_H_PX).\n // Both backends ghostty / ghostty-native use the same defaults for their\n // synthesized 14t. If those ever diverge, expose the metrics via\n // `term.caps` and read them here instead.\n const PIXEL_CELL_W = 8\n const PIXEL_CELL_H = 17\n\n const SGR_PIXELS_ENABLE = \"\\x1b[?1016h\"\n let cachedPixelMode: boolean | null = null\n const out = (term as unknown as { out?: { getText(): string } }).out\n function isPixelMode(): boolean {\n if (cachedPixelMode !== null) return cachedPixelMode\n if (!out) return false\n const written = out.getText()\n // Latches once observed — silvery enables the mode at boot and doesn't\n // toggle it during a test session.\n if (written.includes(SGR_PIXELS_ENABLE)) {\n cachedPixelMode = true\n return true\n }\n return false\n }\n\n function encodeCoord(\n cellX: number,\n cellY: number,\n options?: {\n coordinateMode?: TermlessMouseCoordinateMode\n cellSize?: TermlessMouseCellSize\n },\n ): { sgrX: number; sgrY: number } {\n const coordinateMode = options?.coordinateMode ?? \"auto\"\n const pixelMode = coordinateMode === \"pixel\" || (coordinateMode === \"auto\" && isPixelMode())\n if (pixelMode) {\n // Top-left of cell — silvery's parser computes cellX = rawX / cellW\n // as a float; downstream hit-testing floors to land on a cell. Using\n // (cellX * W, cellY * H) maps cleanly to exactly cell (cellX, cellY)\n // without fractional ambiguity at the boundary.\n const cellSize = options?.cellSize ?? { width: PIXEL_CELL_W, height: PIXEL_CELL_H }\n const pixelX = cellX * cellSize.width\n const pixelY = cellY * cellSize.height\n // SGR coordinates are 1-indexed at the wire.\n return { sgrX: Math.round(pixelX) + 1, sgrY: Math.round(pixelY) + 1 }\n }\n return { sgrX: cellX + 1, sgrY: cellY + 1 }\n }\n\n function packetEventCount(packet: TermlessTrackpadFlickPacket): number {\n const count = packet.count ?? packet.events ?? Math.abs(packet.delta ?? 1)\n return Math.max(0, Math.floor(count))\n }\n\n function packetDelta(\n packet: TermlessTrackpadFlickPacket,\n profile: TermlessTrackpadFlickProfile,\n ): number {\n if (packet.delta !== undefined) return packet.delta\n const direction = packet.direction ?? profile.direction ?? \"up\"\n return direction === \"up\" ? -1 : 1\n }\n\n function packetButton(\n packet: TermlessTrackpadFlickPacket,\n profile: TermlessTrackpadFlickProfile,\n ): number {\n if (packet.button !== undefined) return packet.button\n return packetDelta(packet, profile) < 0 ? 64 : 65\n }\n\n function groupTrackpadPackets(\n packets: readonly TermlessTrackpadFlickPacket[],\n ): { atMs: number; packets: TermlessTrackpadFlickPacket[] }[] {\n const sorted = packets\n .map((packet, order) => ({ packet, order }))\n .sort((a, b) => a.packet.atMs - b.packet.atMs || a.order - b.order)\n const groups: { atMs: number; packets: TermlessTrackpadFlickPacket[] }[] = []\n for (const { packet } of sorted) {\n const atMs = Math.max(0, Math.round(packet.atMs))\n const last = groups[groups.length - 1]\n if (last && last.atMs === atMs) {\n last.packets.push(packet)\n } else {\n groups.push({ atMs, packets: [packet] })\n }\n }\n return groups\n }\n\n const mouse: TermlessMouse = {\n async down(x, y, options) {\n const btn = sgrButtonByte(options)\n const { sgrX, sgrY } = encodeCoord(x, y)\n sendInput(`\\x1b[<${btn};${sgrX};${sgrY}M`)\n await Promise.resolve()\n },\n async up(x, y, options) {\n const btn = sgrButtonByte(options)\n const { sgrX, sgrY } = encodeCoord(x, y)\n sendInput(`\\x1b[<${btn};${sgrX};${sgrY}m`)\n await Promise.resolve()\n },\n async move(x, y, options) {\n const btn = 32 + sgrButtonByte(options)\n const { sgrX, sgrY } = encodeCoord(x, y)\n sendInput(`\\x1b[<${btn};${sgrX};${sgrY}M`)\n await Promise.resolve()\n },\n async click(x, y, options) {\n await mouse.down(x, y, options)\n await mouse.up(x, y, options)\n },\n async dblclick(x, y, options) {\n await mouse.click(x, y, options)\n await mouse.click(x, y, options)\n },\n async drag({ from, to, via, options, stepDelay = DEFAULT_DRAG_STEP_DELAY_MS }) {\n await mouse.down(from[0], from[1], options)\n await sleep(stepDelay)\n if (via) {\n for (const [x, y] of via) {\n await mouse.move(x, y, options)\n await sleep(stepDelay)\n }\n }\n await mouse.move(to[0], to[1], options)\n await sleep(stepDelay)\n await mouse.up(to[0], to[1], options)\n },\n async wheel(x, y, deltaOrOptions, maybeOptions) {\n const options = typeof deltaOrOptions === \"number\" ? maybeOptions : deltaOrOptions\n const deltaX =\n typeof deltaOrOptions === \"number\" ? (options?.deltaX ?? 0) : (deltaOrOptions.deltaX ?? 0)\n const deltaY =\n typeof deltaOrOptions === \"number\"\n ? deltaOrOptions\n : (deltaOrOptions.deltaY ?? deltaOrOptions.lines ?? 0)\n const { sgrX, sgrY } = encodeCoord(x, y, {\n coordinateMode: options?.coordinateMode,\n cellSize: options?.cellSize,\n })\n const reports: string[] = []\n const appendReports = (baseButton: number, count: number) => {\n const button = applyMouseModifierBits(baseButton, options)\n for (let i = 0; i < count; i++) reports.push(`\\x1b[<${button};${sgrX};${sgrY}M`)\n }\n\n const horizontalCount = Math.max(0, Math.floor(Math.abs(deltaX)))\n if (horizontalCount > 0) appendReports(deltaX < 0 ? 66 : 67, horizontalCount)\n\n const verticalCount = Math.max(0, Math.floor(Math.abs(deltaY)))\n if (verticalCount > 0) appendReports(deltaY < 0 ? 64 : 65, verticalCount)\n\n const cadenceMs = options?.cadenceMs ?? DEFAULT_WHEEL_CADENCE_MS\n for (let index = 0; index < reports.length; index++) {\n sendInput(reports[index]!)\n await Promise.resolve()\n if (cadenceMs > 0 && index < reports.length - 1) await sleep(cadenceMs)\n }\n },\n async trackpadFlick(profile, options) {\n const groups = groupTrackpadPackets(profile.packets)\n const deliveredGroups: TermlessTrackpadFlickGroup[] = []\n let lastAtMs = 0\n let eventCount = 0\n\n for (let index = 0; index < groups.length; index++) {\n const group = groups[index]!\n const waitMs = Math.max(0, group.atMs - lastAtMs)\n if (waitMs > 0) await sleep(waitMs)\n lastAtMs = group.atMs\n\n let chunk = \"\"\n let deltaSum = 0\n let groupEventCount = 0\n for (const packet of group.packets) {\n const count = packetEventCount(packet)\n if (count === 0) continue\n const delta = packetDelta(packet, profile)\n const button = packetButton(packet, profile)\n const { sgrX, sgrY } = encodeCoord(packet.x ?? profile.x, packet.y ?? profile.y, {\n coordinateMode: profile.coordinateMode,\n cellSize: profile.cellSize,\n })\n chunk += Array.from({ length: count }, () => `\\x1b[<${button};${sgrX};${sgrY}M`).join(\"\")\n deltaSum += delta * count\n groupEventCount += count\n }\n if (chunk.length === 0) continue\n\n sendInput(chunk)\n eventCount += groupEventCount\n await Promise.resolve()\n\n const deliveredGroup: TermlessTrackpadFlickGroup = {\n index,\n atMs: group.atMs,\n packetCount: group.packets.length,\n eventCount,\n deltaSum,\n }\n deliveredGroups.push(deliveredGroup)\n await options?.afterGroup?.(deliveredGroup)\n }\n\n if (profile.settleMs !== undefined && profile.settleMs > 0) {\n await sleep(profile.settleMs)\n }\n\n return {\n eventCount,\n durationMs: lastAtMs,\n groups: deliveredGroups,\n }\n },\n }\n\n // --- Clipboard capture (OSC 52) ---\n // Every OSC 52 write eventually reaches the emulator via emulator.feed\n // (termless runs inside the host process — the mock stdout forwards writes\n // to the emulator). We wrap feed() to extract the payloads.\n const clipboardPayloads: string[] = []\n const emulator = (term as unknown as { _emulator?: { feed: (s: string) => void } })._emulator\n if (emulator) {\n const origFeed = emulator.feed.bind(emulator)\n // eslint-disable-next-line no-control-regex -- OSC 52 is framed by ESC and BEL bytes.\n const OSC52_RE = new RegExp(\"\\\\x1b\\\\]52;c;([A-Za-z0-9+/=]*)\\\\x07\", \"g\")\n emulator.feed = (data: string) => {\n OSC52_RE.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = OSC52_RE.exec(data)) !== null) {\n try {\n const decoded = globalThis.Buffer.from(match[1]!, \"base64\").toString(\"utf-8\")\n clipboardPayloads.push(decoded)\n } catch {\n // ignore malformed base64 — shouldn't happen in practice\n }\n }\n origFeed(data)\n }\n }\n\n const clipboard: TermlessClipboard = {\n get last() {\n return clipboardPayloads.length > 0 ? clipboardPayloads[clipboardPayloads.length - 1]! : null\n },\n get all() {\n return clipboardPayloads\n },\n clear() {\n clipboardPayloads.length = 0\n },\n }\n\n Object.defineProperty(term, \"mouse\", { value: mouse, enumerable: true })\n Object.defineProperty(term, \"clipboard\", { value: clipboard, enumerable: true })\n if (reflowResidue) {\n Object.defineProperty(term, \"reflowResidue\", { value: reflowResidue, enumerable: true })\n }\n\n return term as TermlessTerm\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n// Re-export stripAnsi from unicode.ts (canonical implementation)\nimport { stripAnsi } from \"@silvery/ag-term/unicode\"\nexport { stripAnsi } from \"@silvery/ag-term/unicode\"\n\n/**\n * Normalize frame output for comparison.\n * - Strips ANSI codes\n * - Trims trailing whitespace from lines\n * - Removes empty trailing lines\n */\nexport function normalizeFrame(frame: string): string {\n return stripAnsi(frame)\n .split(\"\\n\")\n .map((line) => line.trimEnd())\n .join(\"\\n\")\n .trimEnd()\n}\n\n/**\n * Wait for a condition to be true, polling at intervals.\n * Useful for waiting for async state updates.\n */\nexport async function waitFor(\n condition: () => boolean,\n { timeout = 1000, interval = 10 } = {},\n): Promise<void> {\n const start = Date.now()\n while (!condition()) {\n if (Date.now() - start > timeout) {\n throw new Error(`waitFor timed out after ${timeout}ms`)\n }\n await new Promise<void>((resolve) => {\n setTimeout(resolve, interval)\n })\n }\n}\n"],"mappings":"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"}