@effect-tui/react 0.15.2 → 2.0.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 (313) hide show
  1. package/README.md +11 -2
  2. package/dist/src/codeblock.d.ts +1 -1
  3. package/dist/src/codeblock.d.ts.map +1 -1
  4. package/dist/src/codeblock.js +2 -2
  5. package/dist/src/codeblock.js.map +1 -1
  6. package/dist/src/components/ListView.d.ts +4 -4
  7. package/dist/src/components/ListView.d.ts.map +1 -1
  8. package/dist/src/components/ListView.js +16 -17
  9. package/dist/src/components/ListView.js.map +1 -1
  10. package/dist/src/components/Markdown.js +3 -3
  11. package/dist/src/components/Markdown.js.map +1 -1
  12. package/dist/src/components/MultilineTextInput.d.ts.map +1 -1
  13. package/dist/src/components/MultilineTextInput.js +133 -305
  14. package/dist/src/components/MultilineTextInput.js.map +1 -1
  15. package/dist/src/components/TextInput.d.ts.map +1 -1
  16. package/dist/src/components/TextInput.js +51 -98
  17. package/dist/src/components/TextInput.js.map +1 -1
  18. package/dist/src/components/text-editing.d.ts +61 -0
  19. package/dist/src/components/text-editing.d.ts.map +1 -1
  20. package/dist/src/components/text-editing.js +131 -0
  21. package/dist/src/components/text-editing.js.map +1 -1
  22. package/dist/src/console/ConsolePopover.d.ts +7 -1
  23. package/dist/src/console/ConsolePopover.d.ts.map +1 -1
  24. package/dist/src/console/ConsolePopover.js +55 -74
  25. package/dist/src/console/ConsolePopover.js.map +1 -1
  26. package/dist/src/debug/DebugOverlay.d.ts.map +1 -1
  27. package/dist/src/debug/DebugOverlay.js +3 -57
  28. package/dist/src/debug/DebugOverlay.js.map +1 -1
  29. package/dist/src/debug/DiagnosticsPanel.js +1 -1
  30. package/dist/src/debug/DiagnosticsPanel.js.map +1 -1
  31. package/dist/src/dev.d.ts +5 -117
  32. package/dist/src/dev.d.ts.map +1 -1
  33. package/dist/src/dev.js +3 -333
  34. package/dist/src/dev.js.map +1 -1
  35. package/dist/src/hooks/use-scroll.d.ts +31 -35
  36. package/dist/src/hooks/use-scroll.d.ts.map +1 -1
  37. package/dist/src/hooks/use-scroll.js +51 -90
  38. package/dist/src/hooks/use-scroll.js.map +1 -1
  39. package/dist/src/hosts/base.d.ts +13 -2
  40. package/dist/src/hosts/base.d.ts.map +1 -1
  41. package/dist/src/hosts/base.js +74 -2
  42. package/dist/src/hosts/base.js.map +1 -1
  43. package/dist/src/hosts/box.d.ts +2 -2
  44. package/dist/src/hosts/box.d.ts.map +1 -1
  45. package/dist/src/hosts/box.js +29 -2
  46. package/dist/src/hosts/box.js.map +1 -1
  47. package/dist/src/hosts/canvas.d.ts +24 -4
  48. package/dist/src/hosts/canvas.d.ts.map +1 -1
  49. package/dist/src/hosts/canvas.js +107 -41
  50. package/dist/src/hosts/canvas.js.map +1 -1
  51. package/dist/src/hosts/codeblock.d.ts +10 -12
  52. package/dist/src/hosts/codeblock.d.ts.map +1 -1
  53. package/dist/src/hosts/codeblock.js +38 -35
  54. package/dist/src/hosts/codeblock.js.map +1 -1
  55. package/dist/src/hosts/flex-container.d.ts +3 -3
  56. package/dist/src/hosts/flex-container.d.ts.map +1 -1
  57. package/dist/src/hosts/flex-container.js +20 -5
  58. package/dist/src/hosts/flex-container.js.map +1 -1
  59. package/dist/src/hosts/index.d.ts +3 -2
  60. package/dist/src/hosts/index.d.ts.map +1 -1
  61. package/dist/src/hosts/index.js +2 -1
  62. package/dist/src/hosts/index.js.map +1 -1
  63. package/dist/src/hosts/layout-helpers.d.ts +10 -0
  64. package/dist/src/hosts/layout-helpers.d.ts.map +1 -0
  65. package/dist/src/hosts/layout-helpers.js +10 -0
  66. package/dist/src/hosts/layout-helpers.js.map +1 -0
  67. package/dist/src/hosts/leaf.d.ts +14 -0
  68. package/dist/src/hosts/leaf.d.ts.map +1 -0
  69. package/dist/src/hosts/leaf.js +31 -0
  70. package/dist/src/hosts/leaf.js.map +1 -0
  71. package/dist/src/hosts/overlay-item.d.ts +2 -2
  72. package/dist/src/hosts/overlay-item.d.ts.map +1 -1
  73. package/dist/src/hosts/overlay-item.js +7 -2
  74. package/dist/src/hosts/overlay-item.js.map +1 -1
  75. package/dist/src/hosts/overlay.d.ts +2 -2
  76. package/dist/src/hosts/overlay.d.ts.map +1 -1
  77. package/dist/src/hosts/overlay.js +6 -9
  78. package/dist/src/hosts/overlay.js.map +1 -1
  79. package/dist/src/hosts/scroll.d.ts +54 -26
  80. package/dist/src/hosts/scroll.d.ts.map +1 -1
  81. package/dist/src/hosts/scroll.js +185 -87
  82. package/dist/src/hosts/scroll.js.map +1 -1
  83. package/dist/src/hosts/single-child.d.ts.map +1 -1
  84. package/dist/src/hosts/single-child.js +2 -0
  85. package/dist/src/hosts/single-child.js.map +1 -1
  86. package/dist/src/hosts/spacer.d.ts +3 -3
  87. package/dist/src/hosts/spacer.d.ts.map +1 -1
  88. package/dist/src/hosts/spacer.js +8 -3
  89. package/dist/src/hosts/spacer.js.map +1 -1
  90. package/dist/src/hosts/text.d.ts +22 -18
  91. package/dist/src/hosts/text.d.ts.map +1 -1
  92. package/dist/src/hosts/text.js +108 -131
  93. package/dist/src/hosts/text.js.map +1 -1
  94. package/dist/src/hosts/vstack.js +1 -1
  95. package/dist/src/hosts/vstack.js.map +1 -1
  96. package/dist/src/hosts/zstack.d.ts +3 -3
  97. package/dist/src/hosts/zstack.d.ts.map +1 -1
  98. package/dist/src/hosts/zstack.js +13 -8
  99. package/dist/src/hosts/zstack.js.map +1 -1
  100. package/dist/src/index.d.ts +2 -2
  101. package/dist/src/index.d.ts.map +1 -1
  102. package/dist/src/internal/dev/hmr.d.ts +20 -0
  103. package/dist/src/internal/dev/hmr.d.ts.map +1 -0
  104. package/dist/src/internal/dev/hmr.js +93 -0
  105. package/dist/src/internal/dev/hmr.js.map +1 -0
  106. package/dist/src/internal/dev/runtime.d.ts +24 -0
  107. package/dist/src/internal/dev/runtime.d.ts.map +1 -0
  108. package/dist/src/internal/dev/runtime.js +135 -0
  109. package/dist/src/internal/dev/runtime.js.map +1 -0
  110. package/dist/src/internal/dev/ui.d.ts +13 -0
  111. package/dist/src/internal/dev/ui.d.ts.map +1 -0
  112. package/dist/src/internal/dev/ui.js +51 -0
  113. package/dist/src/internal/dev/ui.js.map +1 -0
  114. package/dist/src/internal/renderer/context.d.ts +9 -0
  115. package/dist/src/internal/renderer/context.d.ts.map +1 -0
  116. package/dist/src/internal/renderer/context.js +22 -0
  117. package/dist/src/internal/renderer/context.js.map +1 -0
  118. package/dist/src/internal/renderer/core/FrameBuilder.d.ts +18 -0
  119. package/dist/src/internal/renderer/core/FrameBuilder.d.ts.map +1 -0
  120. package/dist/src/internal/renderer/core/FrameBuilder.js +40 -0
  121. package/dist/src/internal/renderer/core/FrameBuilder.js.map +1 -0
  122. package/dist/src/internal/renderer/core/RendererState.d.ts +41 -0
  123. package/dist/src/internal/renderer/core/RendererState.d.ts.map +1 -0
  124. package/dist/src/internal/renderer/core/RendererState.js +70 -0
  125. package/dist/src/internal/renderer/core/RendererState.js.map +1 -0
  126. package/dist/src/internal/renderer/core/index.d.ts +3 -0
  127. package/dist/src/internal/renderer/core/index.d.ts.map +1 -0
  128. package/dist/src/internal/renderer/core/index.js +3 -0
  129. package/dist/src/internal/renderer/core/index.js.map +1 -0
  130. package/dist/src/internal/renderer/index.d.ts +40 -0
  131. package/dist/src/internal/renderer/index.d.ts.map +1 -0
  132. package/dist/src/internal/renderer/index.js +543 -0
  133. package/dist/src/internal/renderer/index.js.map +1 -0
  134. package/dist/src/internal/renderer/input/InputProcessor.d.ts +30 -0
  135. package/dist/src/internal/renderer/input/InputProcessor.d.ts.map +1 -0
  136. package/dist/src/internal/renderer/input/InputProcessor.js +122 -0
  137. package/dist/src/internal/renderer/input/InputProcessor.js.map +1 -0
  138. package/dist/src/internal/renderer/input/index.d.ts +2 -0
  139. package/dist/src/internal/renderer/input/index.d.ts.map +1 -0
  140. package/dist/src/internal/renderer/input/index.js +2 -0
  141. package/dist/src/internal/renderer/input/index.js.map +1 -0
  142. package/dist/src/internal/renderer/lifecycle/EventBus.d.ts +42 -0
  143. package/dist/src/internal/renderer/lifecycle/EventBus.d.ts.map +1 -0
  144. package/dist/src/internal/renderer/lifecycle/EventBus.js +97 -0
  145. package/dist/src/internal/renderer/lifecycle/EventBus.js.map +1 -0
  146. package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.d.ts +13 -0
  147. package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.d.ts.map +1 -0
  148. package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.js +111 -0
  149. package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.js.map +1 -0
  150. package/dist/src/internal/renderer/lifecycle/RenderCache.d.ts +3 -0
  151. package/dist/src/internal/renderer/lifecycle/RenderCache.d.ts.map +1 -0
  152. package/dist/src/internal/renderer/lifecycle/RenderCache.js +9 -0
  153. package/dist/src/internal/renderer/lifecycle/RenderCache.js.map +1 -0
  154. package/dist/src/internal/renderer/lifecycle/index.d.ts +4 -0
  155. package/dist/src/internal/renderer/lifecycle/index.d.ts.map +1 -0
  156. package/dist/src/internal/renderer/lifecycle/index.js +4 -0
  157. package/dist/src/internal/renderer/lifecycle/index.js.map +1 -0
  158. package/dist/src/internal/renderer/modes/FullscreenRenderer.d.ts +12 -0
  159. package/dist/src/internal/renderer/modes/FullscreenRenderer.d.ts.map +1 -0
  160. package/dist/src/internal/renderer/modes/FullscreenRenderer.js +54 -0
  161. package/dist/src/internal/renderer/modes/FullscreenRenderer.js.map +1 -0
  162. package/dist/src/internal/renderer/modes/InlineRenderer.d.ts +25 -0
  163. package/dist/src/internal/renderer/modes/InlineRenderer.d.ts.map +1 -0
  164. package/dist/src/internal/renderer/modes/InlineRenderer.js +166 -0
  165. package/dist/src/internal/renderer/modes/InlineRenderer.js.map +1 -0
  166. package/dist/src/internal/renderer/modes/RendererMode.d.ts +42 -0
  167. package/dist/src/internal/renderer/modes/RendererMode.d.ts.map +1 -0
  168. package/dist/src/internal/renderer/modes/RendererMode.js +2 -0
  169. package/dist/src/internal/renderer/modes/RendererMode.js.map +1 -0
  170. package/dist/src/internal/renderer/modes/StaticContentRenderer.d.ts +25 -0
  171. package/dist/src/internal/renderer/modes/StaticContentRenderer.d.ts.map +1 -0
  172. package/dist/src/internal/renderer/modes/StaticContentRenderer.js +49 -0
  173. package/dist/src/internal/renderer/modes/StaticContentRenderer.js.map +1 -0
  174. package/dist/src/internal/renderer/modes/index.d.ts +5 -0
  175. package/dist/src/internal/renderer/modes/index.d.ts.map +1 -0
  176. package/dist/src/internal/renderer/modes/index.js +4 -0
  177. package/dist/src/internal/renderer/modes/index.js.map +1 -0
  178. package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.d.ts +13 -0
  179. package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.d.ts.map +1 -0
  180. package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.js +75 -0
  181. package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.js.map +1 -0
  182. package/dist/src/internal/renderer/terminal/TerminalSetup.d.ts +29 -0
  183. package/dist/src/internal/renderer/terminal/TerminalSetup.d.ts.map +1 -0
  184. package/dist/src/internal/renderer/terminal/TerminalSetup.js +82 -0
  185. package/dist/src/internal/renderer/terminal/TerminalSetup.js.map +1 -0
  186. package/dist/src/internal/renderer/terminal/index.d.ts +3 -0
  187. package/dist/src/internal/renderer/terminal/index.d.ts.map +1 -0
  188. package/dist/src/internal/renderer/terminal/index.js +3 -0
  189. package/dist/src/internal/renderer/terminal/index.js.map +1 -0
  190. package/dist/src/internal/renderer/types.d.ts +122 -0
  191. package/dist/src/internal/renderer/types.d.ts.map +1 -0
  192. package/dist/src/internal/renderer/types.js +2 -0
  193. package/dist/src/internal/renderer/types.js.map +1 -0
  194. package/dist/src/motion/hooks.d.ts +1 -1
  195. package/dist/src/motion/hooks.js +1 -1
  196. package/dist/src/reconciler/host-config.js +2 -2
  197. package/dist/src/reconciler/host-config.js.map +1 -1
  198. package/dist/src/reconciler/types.d.ts +5 -1
  199. package/dist/src/reconciler/types.d.ts.map +1 -1
  200. package/dist/src/renderer-context.d.ts +1 -8
  201. package/dist/src/renderer-context.d.ts.map +1 -1
  202. package/dist/src/renderer-context.js +1 -21
  203. package/dist/src/renderer-context.js.map +1 -1
  204. package/dist/src/renderer-types.d.ts +1 -115
  205. package/dist/src/renderer-types.d.ts.map +1 -1
  206. package/dist/src/renderer.d.ts +1 -31
  207. package/dist/src/renderer.d.ts.map +1 -1
  208. package/dist/src/renderer.js +1 -495
  209. package/dist/src/renderer.js.map +1 -1
  210. package/dist/src/test/render-tui.d.ts +3 -3
  211. package/dist/src/test/render-tui.d.ts.map +1 -1
  212. package/dist/src/test/render-tui.js +16 -9
  213. package/dist/src/test/render-tui.js.map +1 -1
  214. package/dist/src/utils/alignment.d.ts +1 -1
  215. package/dist/src/utils/alignment.d.ts.map +1 -1
  216. package/dist/src/utils/alignment.js +0 -2
  217. package/dist/src/utils/alignment.js.map +1 -1
  218. package/dist/src/utils/border.d.ts +1 -1
  219. package/dist/src/utils/border.d.ts.map +1 -1
  220. package/dist/src/utils/border.js +2 -0
  221. package/dist/src/utils/border.js.map +1 -1
  222. package/dist/src/utils/console-helpers.d.ts +19 -0
  223. package/dist/src/utils/console-helpers.d.ts.map +1 -0
  224. package/dist/src/utils/console-helpers.js +61 -0
  225. package/dist/src/utils/console-helpers.js.map +1 -0
  226. package/dist/src/utils/index.d.ts +2 -1
  227. package/dist/src/utils/index.d.ts.map +1 -1
  228. package/dist/src/utils/index.js +2 -1
  229. package/dist/src/utils/index.js.map +1 -1
  230. package/dist/src/utils/styles.d.ts +8 -1
  231. package/dist/src/utils/styles.d.ts.map +1 -1
  232. package/dist/src/utils/styles.js +10 -8
  233. package/dist/src/utils/styles.js.map +1 -1
  234. package/dist/src/utils/text-layout.d.ts +22 -0
  235. package/dist/src/utils/text-layout.d.ts.map +1 -0
  236. package/dist/src/utils/text-layout.js +37 -0
  237. package/dist/src/utils/text-layout.js.map +1 -0
  238. package/dist/src/utils/text-wrap.d.ts +31 -1
  239. package/dist/src/utils/text-wrap.d.ts.map +1 -1
  240. package/dist/src/utils/text-wrap.js +205 -48
  241. package/dist/src/utils/text-wrap.js.map +1 -1
  242. package/dist/src/visualize/index.js +1 -1
  243. package/dist/src/visualize/index.js.map +1 -1
  244. package/dist/tsconfig.tsbuildinfo +1 -1
  245. package/package.json +2 -2
  246. package/src/codeblock.tsx +2 -2
  247. package/src/components/ListView.tsx +21 -23
  248. package/src/components/Markdown.tsx +3 -3
  249. package/src/components/MultilineTextInput.tsx +138 -344
  250. package/src/components/TextInput.tsx +54 -99
  251. package/src/components/text-editing.ts +180 -0
  252. package/src/console/ConsolePopover.tsx +124 -107
  253. package/src/debug/DebugOverlay.ts +15 -74
  254. package/src/debug/DiagnosticsPanel.tsx +1 -1
  255. package/src/dev.tsx +5 -458
  256. package/src/hooks/use-scroll.ts +85 -145
  257. package/src/hosts/base.ts +86 -3
  258. package/src/hosts/box.ts +37 -2
  259. package/src/hosts/canvas.ts +128 -42
  260. package/src/hosts/codeblock.ts +48 -35
  261. package/src/hosts/flex-container.ts +25 -6
  262. package/src/hosts/index.ts +11 -2
  263. package/src/hosts/layout-helpers.ts +20 -0
  264. package/src/hosts/leaf.ts +36 -0
  265. package/src/hosts/overlay-item.ts +8 -2
  266. package/src/hosts/overlay.ts +13 -11
  267. package/src/hosts/scroll.ts +228 -106
  268. package/src/hosts/single-child.ts +2 -0
  269. package/src/hosts/spacer.ts +8 -3
  270. package/src/hosts/text.ts +126 -132
  271. package/src/hosts/vstack.ts +1 -1
  272. package/src/hosts/zstack.ts +14 -9
  273. package/src/index.ts +2 -2
  274. package/src/internal/dev/hmr.ts +101 -0
  275. package/src/internal/dev/runtime.ts +170 -0
  276. package/src/internal/dev/ui.tsx +87 -0
  277. package/src/internal/renderer/context.ts +27 -0
  278. package/src/{renderer → internal/renderer}/core/FrameBuilder.ts +2 -2
  279. package/src/internal/renderer/index.ts +689 -0
  280. package/src/{renderer → internal/renderer}/input/InputProcessor.ts +10 -1
  281. package/src/{renderer → internal/renderer}/lifecycle/EventBus.ts +9 -1
  282. package/src/internal/renderer/lifecycle/ProcessLifecycle.ts +125 -0
  283. package/src/internal/renderer/lifecycle/index.ts +3 -0
  284. package/src/{renderer → internal/renderer}/modes/InlineRenderer.ts +5 -2
  285. package/src/{renderer → internal/renderer}/modes/RendererMode.ts +1 -1
  286. package/src/{renderer → internal/renderer}/modes/StaticContentRenderer.ts +5 -2
  287. package/src/internal/renderer/terminal/KeyboardCapabilityProbe.ts +91 -0
  288. package/src/{renderer/lifecycle → internal/renderer/terminal}/TerminalSetup.ts +4 -22
  289. package/src/internal/renderer/terminal/index.ts +2 -0
  290. package/src/internal/renderer/types.ts +129 -0
  291. package/src/motion/hooks.ts +1 -1
  292. package/src/reconciler/host-config.ts +2 -2
  293. package/src/reconciler/types.ts +7 -1
  294. package/src/renderer-context.ts +1 -27
  295. package/src/renderer-types.ts +10 -123
  296. package/src/renderer.ts +1 -619
  297. package/src/test/render-tui.ts +16 -10
  298. package/src/utils/alignment.ts +1 -3
  299. package/src/utils/border.ts +11 -1
  300. package/src/utils/console-helpers.ts +86 -0
  301. package/src/utils/index.ts +15 -1
  302. package/src/utils/styles.ts +16 -4
  303. package/src/utils/text-layout.ts +65 -0
  304. package/src/utils/text-wrap.ts +261 -48
  305. package/src/visualize/index.tsx +1 -1
  306. package/src/renderer/lifecycle/ResizeManager.ts +0 -65
  307. package/src/renderer/lifecycle/index.ts +0 -4
  308. /package/src/{renderer → internal/renderer}/core/RendererState.ts +0 -0
  309. /package/src/{renderer → internal/renderer}/core/index.ts +0 -0
  310. /package/src/{renderer → internal/renderer}/input/index.ts +0 -0
  311. /package/src/{renderer → internal/renderer}/lifecycle/RenderCache.ts +0 -0
  312. /package/src/{renderer → internal/renderer}/modes/FullscreenRenderer.ts +0 -0
  313. /package/src/{renderer → internal/renderer}/modes/index.ts +0 -0
@@ -7,6 +7,16 @@ import { useKeyboard } from "../hooks/use-keyboard.js"
7
7
  import { useMouse } from "../hooks/use-mouse.js"
8
8
  import { useScroll } from "../hooks/use-scroll.js"
9
9
  import { useTerminalSize } from "../renderer.js"
10
+ import {
11
+ formatLocation,
12
+ formatTimestamp,
13
+ getLineSelection,
14
+ getSelectionText,
15
+ normalizeSelection,
16
+ type SelectionPoint,
17
+ wrapLine,
18
+ } from "../utils/console-helpers.js"
19
+ import { DiagnosticsPanel } from "../debug/DiagnosticsPanel.js"
10
20
  import { getConsoleCapture, type LogEntry, type LogLevel } from "./ConsoleCapture.js"
11
21
  import { copyToClipboard, copyToClipboardSync } from "./clipboard.js"
12
22
 
@@ -29,11 +39,12 @@ export interface ConsolePopoverProps {
29
39
  mode?: "overlay" | "inline"
30
40
  /** Fixed height in lines for inline mode */
31
41
  fixedHeight?: number
32
- }
33
-
34
- interface SelectionPoint {
35
- line: number
36
- col: number
42
+ /** Enable stats tab (toggled with Tab) */
43
+ showStatsTab?: boolean
44
+ /** Stats sampling window (ms) */
45
+ statsSampleMs?: number
46
+ /** Stats title override */
47
+ statsTitle?: string
37
48
  }
38
49
 
39
50
  // ─────────────────────────────────────────────────────────────
@@ -57,70 +68,6 @@ const SELECTION_BG = Colors.rgb(60, 90, 140)
57
68
  // Helpers
58
69
  // ─────────────────────────────────────────────────────────────
59
70
 
60
- function formatTimestamp(date: Date): string {
61
- const h = date.getHours().toString().padStart(2, "0")
62
- const m = date.getMinutes().toString().padStart(2, "0")
63
- const s = date.getSeconds().toString().padStart(2, "0")
64
- return `${h}:${m}:${s}`
65
- }
66
-
67
- function formatLocation(loc?: { file: string; line: number }): string {
68
- if (!loc) return ""
69
- const parts = loc.file.split("/")
70
- const filename = parts[parts.length - 1]
71
- return `${filename}:${loc.line}`
72
- }
73
-
74
- function wrapLine(text: string, maxWidth: number): string[] {
75
- if (text.length <= maxWidth) return [text]
76
-
77
- const lines: string[] = []
78
- let remaining = text
79
-
80
- while (remaining.length > 0) {
81
- if (remaining.length <= maxWidth) {
82
- lines.push(remaining)
83
- break
84
- }
85
-
86
- let breakAt = maxWidth
87
- const lastSpace = remaining.lastIndexOf(" ", maxWidth)
88
- if (lastSpace > maxWidth * 0.5) {
89
- breakAt = lastSpace
90
- }
91
-
92
- lines.push(remaining.slice(0, breakAt))
93
- remaining = remaining.slice(breakAt).trimStart()
94
- }
95
-
96
- return lines
97
- }
98
-
99
- /** Normalize selection so start is before end */
100
- function normalizeSelection(anchor: SelectionPoint, head: SelectionPoint): [SelectionPoint, SelectionPoint] {
101
- if (anchor.line < head.line || (anchor.line === head.line && anchor.col <= head.col)) {
102
- return [anchor, head]
103
- }
104
- return [head, anchor]
105
- }
106
-
107
- /** Get selection bounds for a specific line */
108
- function getLineSelection(
109
- lineIndex: number,
110
- lineLength: number,
111
- start: SelectionPoint,
112
- end: SelectionPoint,
113
- ): { startCol: number; endCol: number } | null {
114
- if (lineIndex < start.line || lineIndex > end.line) return null
115
-
116
- const startCol = lineIndex === start.line ? Math.min(start.col, lineLength) : 0
117
- const endCol = lineIndex === end.line ? Math.min(end.col, lineLength) : lineLength
118
-
119
- if (startCol >= endCol && lineIndex === start.line && lineIndex === end.line) return null
120
-
121
- return { startCol, endCol }
122
- }
123
-
124
71
  // ─────────────────────────────────────────────────────────────
125
72
  // Component
126
73
  // ─────────────────────────────────────────────────────────────
@@ -133,6 +80,9 @@ export function ConsolePopover({
133
80
  maxHeightPercent = 80,
134
81
  mode = "overlay",
135
82
  fixedHeight = 10,
83
+ showStatsTab = false,
84
+ statsSampleMs,
85
+ statsTitle,
136
86
  }: ConsolePopoverProps) {
137
87
  const { width: termWidth, height: termHeight } = useTerminalSize()
138
88
  const capture = useMemo(() => getConsoleCapture(), [])
@@ -143,6 +93,13 @@ export function ConsolePopover({
143
93
  // Size state (adjustable with +/-)
144
94
  const [sizePercent, setSizePercent] = useState(initialHeightPercent)
145
95
  const [inlineHeight, setInlineHeight] = useState(fixedHeight)
96
+ const [activeTab, setActiveTab] = useState<"console" | "stats">("console")
97
+
98
+ useEffect(() => {
99
+ if (!showStatsTab && activeTab === "stats") {
100
+ setActiveTab("console")
101
+ }
102
+ }, [showStatsTab, activeTab])
146
103
 
147
104
  // Selection state
148
105
  const [selectionAnchor, setSelectionAnchor] = useState<SelectionPoint | null>(null)
@@ -160,9 +117,9 @@ export function ConsolePopover({
160
117
  const {
161
118
  state: scrollState,
162
119
  scrollProps,
163
- scrollToEnd,
164
- scrollToStart,
165
- scrollBy,
120
+ scrollToEndY,
121
+ scrollToStartY,
122
+ scrollByY,
166
123
  } = useScroll({
167
124
  axis: "vertical",
168
125
  sticky: true,
@@ -232,16 +189,11 @@ export function ConsolePopover({
232
189
  if (!selectionAnchor || !selectionHead) return ""
233
190
 
234
191
  const [start, end] = normalizeSelection(selectionAnchor, selectionHead)
235
- const lines: string[] = []
236
-
237
- for (let i = start.line; i <= end.line && i < displayLines.length; i++) {
238
- const lineText = displayLines[i]?.text ?? ""
239
- const startCol = i === start.line ? Math.min(start.col, lineText.length) : 0
240
- const endCol = i === end.line ? Math.min(end.col, lineText.length) : lineText.length
241
- lines.push(lineText.slice(startCol, endCol))
242
- }
243
-
244
- return lines.join("\n")
192
+ return getSelectionText(
193
+ displayLines.map((line) => line.text),
194
+ start,
195
+ end,
196
+ )
245
197
  }, [selectionAnchor, selectionHead, displayLines])
246
198
 
247
199
  // Copy selection to clipboard
@@ -288,7 +240,7 @@ export function ConsolePopover({
288
240
  const mouseToSelectionPoint = useCallback(
289
241
  (mouseX: number, mouseY: number): SelectionPoint | null => {
290
242
  const relativeY = mouseY - contentStartY
291
- const lineIndex = relativeY + scrollState.offset
243
+ const lineIndex = relativeY + scrollState.offsetY
292
244
 
293
245
  if (lineIndex < 0 || lineIndex >= displayLines.length) return null
294
246
 
@@ -297,11 +249,38 @@ export function ConsolePopover({
297
249
 
298
250
  return { line: Math.floor(lineIndex), col }
299
251
  },
300
- [contentStartY, scrollState.offset, displayLines.length],
252
+ [contentStartY, scrollState.offsetY, displayLines.length],
301
253
  )
302
254
 
303
255
  // Keyboard handler - no useCallback needed, useKeyboard uses ref internally
304
256
  useKeyboard((key) => {
257
+ if (showStatsTab && key.name === "tab") {
258
+ setActiveTab((prev) => (prev === "console" ? "stats" : "console"))
259
+ key.preventDefault?.()
260
+ return
261
+ }
262
+
263
+ if (activeTab !== "console") {
264
+ // Allow size controls even when viewing stats
265
+ if (key.name === "char" && (key.text === "+" || key.text === "=")) {
266
+ if (mode === "inline") {
267
+ setInlineHeight((prev) => Math.min(30, prev + 2))
268
+ } else {
269
+ setSizePercent((prev) => Math.min(maxHeightPercent, prev + 5))
270
+ }
271
+ return
272
+ }
273
+ if (key.name === "char" && key.text === "-") {
274
+ if (mode === "inline") {
275
+ setInlineHeight((prev) => Math.max(4, prev - 2))
276
+ } else {
277
+ setSizePercent((prev) => Math.max(minHeightPercent, prev - 5))
278
+ }
279
+ return
280
+ }
281
+ return
282
+ }
283
+
305
284
  // Ctrl+Y or Ctrl+C - Copy selection
306
285
  if (key.ctrl && !key.shift && key.name === "char" && (key.text === "y" || key.text === "c")) {
307
286
  handleCopy()
@@ -334,31 +313,31 @@ export function ConsolePopover({
334
313
 
335
314
  // Shift+up/down - jump to top/bottom
336
315
  if (key.shift && key.name === "up") {
337
- scrollToStart()
316
+ scrollToStartY()
338
317
  return
339
318
  }
340
319
  if (key.shift && key.name === "down") {
341
- scrollToEnd()
320
+ scrollToEndY()
342
321
  return
343
322
  }
344
323
 
345
324
  // Regular up/down - scroll
346
325
  if (key.name === "up") {
347
- scrollBy(-1)
326
+ scrollByY(-1)
348
327
  return
349
328
  }
350
329
  if (key.name === "down") {
351
- scrollBy(1)
330
+ scrollByY(1)
352
331
  return
353
332
  }
354
333
 
355
334
  // Home/End
356
335
  if (key.name === "home") {
357
- scrollToStart()
336
+ scrollToStartY()
358
337
  return
359
338
  }
360
339
  if (key.name === "end") {
361
- scrollToEnd()
340
+ scrollToEndY()
362
341
  return
363
342
  }
364
343
  })
@@ -366,6 +345,7 @@ export function ConsolePopover({
366
345
  // Mouse handler for text selection - no useCallback needed, useMouse uses ref internally
367
346
  useMouse(
368
347
  (mouse) => {
348
+ if (activeTab !== "console") return
369
349
  // Only handle events in content area
370
350
  if (mouse.y < contentStartY || mouse.y >= termHeight) return
371
351
 
@@ -385,9 +365,9 @@ export function ConsolePopover({
385
365
  const relativeY = mouse.y - contentStartY
386
366
  const viewportHeight = popoverHeight - 1 // -1 for title
387
367
  if (relativeY <= 1) {
388
- scrollBy(-1)
368
+ scrollByY(-1)
389
369
  } else if (relativeY >= viewportHeight - 2) {
390
- scrollBy(1)
370
+ scrollByY(1)
391
371
  }
392
372
  } else if (mouse.action === "release") {
393
373
  isSelectingRef.current = false
@@ -398,7 +378,8 @@ export function ConsolePopover({
398
378
 
399
379
  // Title bar text
400
380
  const titleText = feedback ?? "Console"
401
- const hints = `[\` toggle] [~ screenshot] [+/- ${sizePercent}%]`
381
+ const tabHint = showStatsTab ? "[tab]" : ""
382
+ const hints = `[\` toggle] [~ screenshot] ${tabHint} [+/- ${sizePercent}%]`
402
383
 
403
384
  // Render a line with selection highlighting
404
385
  const renderLine = (line: (typeof displayLines)[0], index: number) => {
@@ -443,16 +424,43 @@ export function ConsolePopover({
443
424
  }
444
425
 
445
426
  // Inline mode title bar
446
- const inlineHints = `[\` toggle] [+/- size]`
427
+ const inlineHints = `[\` toggle] ${showStatsTab ? "[tab] " : ""}[+/- size]`
447
428
 
448
429
  const consoleContent = (
449
430
  <box width={termWidth} height={popoverHeight} bg={CONTENT_BG}>
450
431
  <vstack width={termWidth} height={popoverHeight}>
451
432
  {/* Title bar */}
452
433
  <hstack height={1} bg={TITLE_BG}>
453
- <text fg={feedback ? Colors.green(500) : TITLE_FG} bg={TITLE_BG}>
454
- {` ${titleText}`}
455
- </text>
434
+ {showStatsTab ? (
435
+ <>
436
+ <text
437
+ fg={activeTab === "console" ? TITLE_FG : Colors.ansi.gray(10)}
438
+ bg={TITLE_BG}
439
+ bold={activeTab === "console"}
440
+ >
441
+ {" Console "}
442
+ </text>
443
+ <text fg={Colors.ansi.gray(12)} bg={TITLE_BG}>
444
+ {"|"}
445
+ </text>
446
+ <text
447
+ fg={activeTab === "stats" ? TITLE_FG : Colors.ansi.gray(10)}
448
+ bg={TITLE_BG}
449
+ bold={activeTab === "stats"}
450
+ >
451
+ {" Stats"}
452
+ </text>
453
+ {feedback && (
454
+ <text fg={Colors.green(500)} bg={TITLE_BG}>
455
+ {` ${feedback}`}
456
+ </text>
457
+ )}
458
+ </>
459
+ ) : (
460
+ <text fg={feedback ? Colors.green(500) : TITLE_FG} bg={TITLE_BG}>
461
+ {` ${titleText}`}
462
+ </text>
463
+ )}
456
464
  <spacer />
457
465
  <text fg={Colors.ansi.gray(14)} bg={TITLE_BG}>
458
466
  {`${mode === "inline" ? inlineHints : hints} `}
@@ -460,17 +468,26 @@ export function ConsolePopover({
460
468
  </hstack>
461
469
 
462
470
  {/* Scrollable content */}
463
- <scroll {...scrollProps} sticky>
464
- <vstack>
465
- {displayLines.length === 0 ? (
466
- <text fg={Colors.ansi.gray(10)} bg={CONTENT_BG}>
467
- No console output yet
468
- </text>
469
- ) : (
470
- displayLines.map((line, i) => renderLine(line, i))
471
- )}
471
+ {activeTab === "console" ? (
472
+ <scroll {...scrollProps} sticky>
473
+ <vstack>
474
+ {displayLines.length === 0 ? (
475
+ <text fg={Colors.ansi.gray(10)} bg={CONTENT_BG}>
476
+ No console output yet
477
+ </text>
478
+ ) : (
479
+ displayLines.map((line, i) => renderLine(line, i))
480
+ )}
481
+ </vstack>
482
+ </scroll>
483
+ ) : (
484
+ <vstack width={termWidth} bg={CONTENT_BG}>
485
+ <text fg={Colors.ansi.gray(9)} bg={CONTENT_BG}>
486
+ {" "}
487
+ </text>
488
+ <DiagnosticsPanel sampleMs={statsSampleMs} title={statsTitle ?? "Renderer Stats"} />
472
489
  </vstack>
473
- </scroll>
490
+ )}
474
491
  </vstack>
475
492
  </box>
476
493
  )
@@ -4,6 +4,15 @@
4
4
  import type { CellBuffer, KeyMsg, MouseMsg, Palette } from "@effect-tui/core"
5
5
  import { getConsoleCapture, type LogLevel } from "../console/ConsoleCapture.js"
6
6
  import { copyToClipboard, copyToClipboardSync } from "../console/clipboard.js"
7
+ import {
8
+ formatLocation,
9
+ formatTimestamp,
10
+ getLineSelection,
11
+ getSelectionText,
12
+ normalizeSelection,
13
+ type SelectionPoint,
14
+ wrapLine,
15
+ } from "../utils/console-helpers.js"
7
16
 
8
17
  // ─────────────────────────────────────────────────────────────
9
18
  // Types
@@ -20,11 +29,6 @@ export interface DebugOverlayOptions {
20
29
  showLocations?: boolean
21
30
  }
22
31
 
23
- interface SelectionPoint {
24
- line: number
25
- col: number
26
- }
27
-
28
32
  // ─────────────────────────────────────────────────────────────
29
33
  // Color constants (8-bit palette indices for performance)
30
34
  // ─────────────────────────────────────────────────────────────
@@ -44,56 +48,6 @@ const LOG_COLORS: Record<LogLevel, number> = {
44
48
  DEBUG: 250, // gray(12)
45
49
  }
46
50
 
47
- // ─────────────────────────────────────────────────────────────
48
- // Helpers
49
- // ─────────────────────────────────────────────────────────────
50
-
51
- function formatTimestamp(date: Date): string {
52
- const h = date.getHours().toString().padStart(2, "0")
53
- const m = date.getMinutes().toString().padStart(2, "0")
54
- const s = date.getSeconds().toString().padStart(2, "0")
55
- return `${h}:${m}:${s}`
56
- }
57
-
58
- function formatLocation(loc?: { file: string; line: number }): string {
59
- if (!loc) return ""
60
- const parts = loc.file.split("/")
61
- const filename = parts[parts.length - 1]
62
- return `${filename}:${loc.line}`
63
- }
64
-
65
- function wrapLine(text: string, maxWidth: number): string[] {
66
- if (text.length <= maxWidth) return [text]
67
-
68
- const lines: string[] = []
69
- let remaining = text
70
-
71
- while (remaining.length > 0) {
72
- if (remaining.length <= maxWidth) {
73
- lines.push(remaining)
74
- break
75
- }
76
-
77
- let breakAt = maxWidth
78
- const lastSpace = remaining.lastIndexOf(" ", maxWidth)
79
- if (lastSpace > maxWidth * 0.5) {
80
- breakAt = lastSpace
81
- }
82
-
83
- lines.push(remaining.slice(0, breakAt))
84
- remaining = remaining.slice(breakAt).trimStart()
85
- }
86
-
87
- return lines
88
- }
89
-
90
- function normalizeSelection(anchor: SelectionPoint, head: SelectionPoint): [SelectionPoint, SelectionPoint] {
91
- if (anchor.line < head.line || (anchor.line === head.line && anchor.col <= head.col)) {
92
- return [anchor, head]
93
- }
94
- return [head, anchor]
95
- }
96
-
97
51
  // ─────────────────────────────────────────────────────────────
98
52
  // Debug Overlay Class
99
53
  // ─────────────────────────────────────────────────────────────
@@ -437,15 +391,7 @@ export class DebugOverlay {
437
391
  if (!this.selectionAnchor || !this.selectionHead) return null
438
392
 
439
393
  const [start, end] = normalizeSelection(this.selectionAnchor, this.selectionHead)
440
-
441
- if (lineIndex < start.line || lineIndex > end.line) return null
442
-
443
- const startCol = lineIndex === start.line ? Math.min(start.col, lineLength) : 0
444
- const endCol = lineIndex === end.line ? Math.min(end.col, lineLength) : lineLength
445
-
446
- if (startCol >= endCol && lineIndex === start.line && lineIndex === end.line) return null
447
-
448
- return { startCol, endCol }
394
+ return getLineSelection(lineIndex, lineLength, start, end)
449
395
  }
450
396
 
451
397
  private drawScrollbar(
@@ -476,16 +422,11 @@ export class DebugOverlay {
476
422
  if (!this.selectionAnchor || !this.selectionHead) return ""
477
423
 
478
424
  const [start, end] = normalizeSelection(this.selectionAnchor, this.selectionHead)
479
- const lines: string[] = []
480
-
481
- for (let i = start.line; i <= end.line && i < this.displayLines.length; i++) {
482
- const lineText = this.displayLines[i]?.text ?? ""
483
- const startCol = i === start.line ? Math.min(start.col, lineText.length) : 0
484
- const endCol = i === end.line ? Math.min(end.col, lineText.length) : lineText.length
485
- lines.push(lineText.slice(startCol, endCol))
486
- }
487
-
488
- return lines.join("\n")
425
+ return getSelectionText(
426
+ this.displayLines.map((line) => line.text),
427
+ start,
428
+ end,
429
+ )
489
430
  }
490
431
 
491
432
  private copySelection(): void {
@@ -27,7 +27,7 @@ export function DiagnosticsPanel({ sampleMs = 200, title = "Diagnostics" }: Diag
27
27
  </text>
28
28
  <text fg={Colors.ansi.gray(12)}>
29
29
  clear {phases?.clear.toFixed(2)}ms · layout {phases?.layout.toFixed(2)}ms · render{" "}
30
- {phases?.render.toFixed(2)}ms · diff {phases?.diffAnsi.toFixed(2)}ms · write {phases?.write.toFixed(2)}ms
30
+ {phases?.render.toFixed(2)}ms · diff {phases?.diffMs.toFixed(2)}ms · write {phases?.write.toFixed(2)}ms
31
31
  </text>
32
32
  </>
33
33
  ) : (