@effect-tui/react 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/dist/jsx-runtime.d.ts +13 -0
  2. package/dist/jsx-runtime.d.ts.map +1 -1
  3. package/dist/jsx-runtime.js.map +1 -1
  4. package/dist/src/codeblock.d.ts.map +1 -1
  5. package/dist/src/codeblock.js.map +1 -1
  6. package/dist/src/components/Divider.d.ts +18 -0
  7. package/dist/src/components/Divider.d.ts.map +1 -0
  8. package/dist/src/components/Divider.js +17 -0
  9. package/dist/src/components/Divider.js.map +1 -0
  10. package/dist/src/components/Markdown.d.ts +66 -0
  11. package/dist/src/components/Markdown.d.ts.map +1 -0
  12. package/dist/src/components/Markdown.js +226 -0
  13. package/dist/src/components/Markdown.js.map +1 -0
  14. package/dist/src/components/MultilineTextInput.d.ts +65 -0
  15. package/dist/src/components/MultilineTextInput.d.ts.map +1 -0
  16. package/dist/src/components/MultilineTextInput.js +607 -0
  17. package/dist/src/components/MultilineTextInput.js.map +1 -0
  18. package/dist/src/components/Overlay.d.ts +46 -0
  19. package/dist/src/components/Overlay.d.ts.map +1 -0
  20. package/dist/src/components/Overlay.js +11 -0
  21. package/dist/src/components/Overlay.js.map +1 -0
  22. package/dist/src/components/Static.d.ts +44 -0
  23. package/dist/src/components/Static.d.ts.map +1 -0
  24. package/dist/src/components/Static.js +53 -0
  25. package/dist/src/components/Static.js.map +1 -0
  26. package/dist/src/components/TextInput.d.ts +55 -0
  27. package/dist/src/components/TextInput.d.ts.map +1 -0
  28. package/dist/src/components/TextInput.js +277 -0
  29. package/dist/src/components/TextInput.js.map +1 -0
  30. package/dist/src/components/index.d.ts +7 -0
  31. package/dist/src/components/index.d.ts.map +1 -0
  32. package/dist/src/components/index.js +7 -0
  33. package/dist/src/components/index.js.map +1 -0
  34. package/dist/src/components/text-editing.d.ts +62 -0
  35. package/dist/src/components/text-editing.d.ts.map +1 -0
  36. package/dist/src/components/text-editing.js +385 -0
  37. package/dist/src/components/text-editing.js.map +1 -0
  38. package/dist/src/console/ConsoleCapture.d.ts +36 -0
  39. package/dist/src/console/ConsoleCapture.d.ts.map +1 -0
  40. package/dist/src/console/ConsoleCapture.js +210 -0
  41. package/dist/src/console/ConsoleCapture.js.map +1 -0
  42. package/dist/src/console/ConsolePopover.d.ts +18 -0
  43. package/dist/src/console/ConsolePopover.d.ts.map +1 -0
  44. package/dist/src/console/ConsolePopover.js +324 -0
  45. package/dist/src/console/ConsolePopover.js.map +1 -0
  46. package/dist/src/console/clipboard.d.ts +10 -0
  47. package/dist/src/console/clipboard.d.ts.map +1 -0
  48. package/dist/src/console/clipboard.js +74 -0
  49. package/dist/src/console/clipboard.js.map +1 -0
  50. package/dist/src/console/index.d.ts +5 -0
  51. package/dist/src/console/index.d.ts.map +1 -0
  52. package/dist/src/console/index.js +33 -0
  53. package/dist/src/console/index.js.map +1 -0
  54. package/dist/src/console/useConsole.d.ts +44 -0
  55. package/dist/src/console/useConsole.d.ts.map +1 -0
  56. package/dist/src/console/useConsole.js +91 -0
  57. package/dist/src/console/useConsole.js.map +1 -0
  58. package/dist/src/debug/DebugOverlay.d.ts +49 -0
  59. package/dist/src/debug/DebugOverlay.d.ts.map +1 -0
  60. package/dist/src/debug/DebugOverlay.js +438 -0
  61. package/dist/src/debug/DebugOverlay.js.map +1 -0
  62. package/dist/src/debug/DiagnosticsPanel.d.ts.map +1 -1
  63. package/dist/src/debug/DiagnosticsPanel.js.map +1 -1
  64. package/dist/src/dev/Toast.d.ts +19 -0
  65. package/dist/src/dev/Toast.d.ts.map +1 -0
  66. package/dist/src/dev/Toast.js +72 -0
  67. package/dist/src/dev/Toast.js.map +1 -0
  68. package/dist/src/dev/index.d.ts +2 -0
  69. package/dist/src/dev/index.d.ts.map +1 -0
  70. package/dist/src/dev/index.js +3 -0
  71. package/dist/src/dev/index.js.map +1 -0
  72. package/dist/src/dev.d.ts +114 -0
  73. package/dist/src/dev.d.ts.map +1 -0
  74. package/dist/src/dev.js +373 -0
  75. package/dist/src/dev.js.map +1 -0
  76. package/dist/src/highlight.d.ts +3 -3
  77. package/dist/src/highlight.d.ts.map +1 -1
  78. package/dist/src/highlight.js.map +1 -1
  79. package/dist/src/hmr-plugin.d.ts +2 -0
  80. package/dist/src/hmr-plugin.d.ts.map +1 -0
  81. package/dist/src/hmr-plugin.js +53 -0
  82. package/dist/src/hmr-plugin.js.map +1 -0
  83. package/dist/src/hooks/index.d.ts +4 -0
  84. package/dist/src/hooks/index.d.ts.map +1 -1
  85. package/dist/src/hooks/index.js +2 -0
  86. package/dist/src/hooks/index.js.map +1 -1
  87. package/dist/src/hooks/use-keyboard.d.ts +11 -0
  88. package/dist/src/hooks/use-keyboard.d.ts.map +1 -1
  89. package/dist/src/hooks/use-keyboard.js +22 -4
  90. package/dist/src/hooks/use-keyboard.js.map +1 -1
  91. package/dist/src/hooks/use-mouse.d.ts +24 -0
  92. package/dist/src/hooks/use-mouse.d.ts.map +1 -0
  93. package/dist/src/hooks/use-mouse.js +41 -0
  94. package/dist/src/hooks/use-mouse.js.map +1 -0
  95. package/dist/src/hooks/use-paste.d.ts +11 -0
  96. package/dist/src/hooks/use-paste.d.ts.map +1 -1
  97. package/dist/src/hooks/use-paste.js +17 -3
  98. package/dist/src/hooks/use-paste.js.map +1 -1
  99. package/dist/src/hooks/use-scroll.d.ts +79 -0
  100. package/dist/src/hooks/use-scroll.d.ts.map +1 -0
  101. package/dist/src/hooks/use-scroll.js +239 -0
  102. package/dist/src/hooks/use-scroll.js.map +1 -0
  103. package/dist/src/hooks/useFrameStats.js.map +1 -1
  104. package/dist/src/hosts/base.d.ts +62 -1
  105. package/dist/src/hosts/base.d.ts.map +1 -1
  106. package/dist/src/hosts/base.js +118 -5
  107. package/dist/src/hosts/base.js.map +1 -1
  108. package/dist/src/hosts/box.d.ts +7 -7
  109. package/dist/src/hosts/box.d.ts.map +1 -1
  110. package/dist/src/hosts/box.js +30 -23
  111. package/dist/src/hosts/box.js.map +1 -1
  112. package/dist/src/hosts/canvas.d.ts +16 -8
  113. package/dist/src/hosts/canvas.d.ts.map +1 -1
  114. package/dist/src/hosts/canvas.js +27 -22
  115. package/dist/src/hosts/canvas.js.map +1 -1
  116. package/dist/src/hosts/codeblock.d.ts +7 -7
  117. package/dist/src/hosts/codeblock.d.ts.map +1 -1
  118. package/dist/src/hosts/codeblock.js +11 -20
  119. package/dist/src/hosts/codeblock.js.map +1 -1
  120. package/dist/src/hosts/flex-container.d.ts +45 -0
  121. package/dist/src/hosts/flex-container.d.ts.map +1 -0
  122. package/dist/src/hosts/flex-container.js +90 -0
  123. package/dist/src/hosts/flex-container.js.map +1 -0
  124. package/dist/src/hosts/hstack.d.ts +6 -11
  125. package/dist/src/hosts/hstack.d.ts.map +1 -1
  126. package/dist/src/hosts/hstack.js +6 -41
  127. package/dist/src/hosts/hstack.js.map +1 -1
  128. package/dist/src/hosts/index.d.ts +4 -0
  129. package/dist/src/hosts/index.d.ts.map +1 -1
  130. package/dist/src/hosts/index.js +10 -0
  131. package/dist/src/hosts/index.js.map +1 -1
  132. package/dist/src/hosts/overlay-item.d.ts +32 -0
  133. package/dist/src/hosts/overlay-item.d.ts.map +1 -0
  134. package/dist/src/hosts/overlay-item.js +54 -0
  135. package/dist/src/hosts/overlay-item.js.map +1 -0
  136. package/dist/src/hosts/overlay.d.ts +30 -0
  137. package/dist/src/hosts/overlay.d.ts.map +1 -0
  138. package/dist/src/hosts/overlay.js +105 -0
  139. package/dist/src/hosts/overlay.js.map +1 -0
  140. package/dist/src/hosts/scroll.d.ts +56 -0
  141. package/dist/src/hosts/scroll.d.ts.map +1 -0
  142. package/dist/src/hosts/scroll.js +204 -0
  143. package/dist/src/hosts/scroll.js.map +1 -0
  144. package/dist/src/hosts/single-child.d.ts +16 -0
  145. package/dist/src/hosts/single-child.d.ts.map +1 -0
  146. package/dist/src/hosts/single-child.js +45 -0
  147. package/dist/src/hosts/single-child.js.map +1 -0
  148. package/dist/src/hosts/spacer.d.ts.map +1 -1
  149. package/dist/src/hosts/spacer.js +7 -3
  150. package/dist/src/hosts/spacer.js.map +1 -1
  151. package/dist/src/hosts/text.d.ts +9 -6
  152. package/dist/src/hosts/text.d.ts.map +1 -1
  153. package/dist/src/hosts/text.js +49 -22
  154. package/dist/src/hosts/text.js.map +1 -1
  155. package/dist/src/hosts/vstack.d.ts +6 -11
  156. package/dist/src/hosts/vstack.d.ts.map +1 -1
  157. package/dist/src/hosts/vstack.js +6 -41
  158. package/dist/src/hosts/vstack.js.map +1 -1
  159. package/dist/src/hosts/zstack.d.ts.map +1 -1
  160. package/dist/src/hosts/zstack.js +16 -5
  161. package/dist/src/hosts/zstack.js.map +1 -1
  162. package/dist/src/index.d.ts +9 -2
  163. package/dist/src/index.d.ts.map +1 -1
  164. package/dist/src/index.js +10 -2
  165. package/dist/src/index.js.map +1 -1
  166. package/dist/src/inline/index.d.ts.map +1 -1
  167. package/dist/src/inline/index.js.map +1 -1
  168. package/dist/src/motion/color-motion-value.d.ts.map +1 -1
  169. package/dist/src/motion/color-motion-value.js.map +1 -1
  170. package/dist/src/motion/color.d.ts +1 -29
  171. package/dist/src/motion/color.d.ts.map +1 -1
  172. package/dist/src/motion/color.js +2 -170
  173. package/dist/src/motion/color.js.map +1 -1
  174. package/dist/src/motion/color.test.js.map +1 -1
  175. package/dist/src/motion/event-emitter.d.ts.map +1 -1
  176. package/dist/src/motion/event-emitter.js.map +1 -1
  177. package/dist/src/motion/frame.js.map +1 -1
  178. package/dist/src/motion/hooks.d.ts.map +1 -1
  179. package/dist/src/motion/hooks.js +8 -3
  180. package/dist/src/motion/hooks.js.map +1 -1
  181. package/dist/src/motion/index.d.ts.map +1 -1
  182. package/dist/src/motion/index.js.map +1 -1
  183. package/dist/src/motion/motion-value.d.ts.map +1 -1
  184. package/dist/src/motion/motion-value.js.map +1 -1
  185. package/dist/src/motion/motion-value.test.js.map +1 -1
  186. package/dist/src/motion/spring-math.d.ts +6 -1
  187. package/dist/src/motion/spring-math.d.ts.map +1 -1
  188. package/dist/src/motion/spring-math.js +6 -1
  189. package/dist/src/motion/spring-math.js.map +1 -1
  190. package/dist/src/motion/types.d.ts.map +1 -1
  191. package/dist/src/motion/types.js.map +1 -1
  192. package/dist/src/profiler.js.map +1 -1
  193. package/dist/src/reconciler/host-config.d.ts +5 -5
  194. package/dist/src/reconciler/host-config.d.ts.map +1 -1
  195. package/dist/src/reconciler/host-config.js +43 -51
  196. package/dist/src/reconciler/host-config.js.map +1 -1
  197. package/dist/src/reconciler/noop-methods.d.ts +29 -0
  198. package/dist/src/reconciler/noop-methods.d.ts.map +1 -0
  199. package/dist/src/reconciler/noop-methods.js +43 -0
  200. package/dist/src/reconciler/noop-methods.js.map +1 -0
  201. package/dist/src/reconciler/types.d.ts +68 -14
  202. package/dist/src/reconciler/types.d.ts.map +1 -1
  203. package/dist/src/remote/Procedures.d.ts +22 -0
  204. package/dist/src/remote/Procedures.d.ts.map +1 -0
  205. package/dist/src/remote/Procedures.js +42 -0
  206. package/dist/src/remote/Procedures.js.map +1 -0
  207. package/dist/src/remote/Router.d.ts +20 -0
  208. package/dist/src/remote/Router.d.ts.map +1 -0
  209. package/dist/src/remote/Router.js +26 -0
  210. package/dist/src/remote/Router.js.map +1 -0
  211. package/dist/src/remote/Server.d.ts +6 -0
  212. package/dist/src/remote/Server.d.ts.map +1 -0
  213. package/dist/src/remote/Server.js +53 -0
  214. package/dist/src/remote/Server.js.map +1 -0
  215. package/dist/src/remote/index.d.ts +18 -0
  216. package/dist/src/remote/index.d.ts.map +1 -0
  217. package/dist/src/remote/index.js +74 -0
  218. package/dist/src/remote/index.js.map +1 -0
  219. package/dist/src/renderer/core/FrameBuilder.d.ts +18 -0
  220. package/dist/src/renderer/core/FrameBuilder.d.ts.map +1 -0
  221. package/dist/src/renderer/core/FrameBuilder.js +38 -0
  222. package/dist/src/renderer/core/FrameBuilder.js.map +1 -0
  223. package/dist/src/renderer/core/RendererState.d.ts +41 -0
  224. package/dist/src/renderer/core/RendererState.d.ts.map +1 -0
  225. package/dist/src/renderer/core/RendererState.js +70 -0
  226. package/dist/src/renderer/core/RendererState.js.map +1 -0
  227. package/dist/src/renderer/core/index.d.ts +3 -0
  228. package/dist/src/renderer/core/index.d.ts.map +1 -0
  229. package/dist/src/renderer/core/index.js +3 -0
  230. package/dist/src/renderer/core/index.js.map +1 -0
  231. package/dist/src/renderer/input/InputProcessor.d.ts +25 -0
  232. package/dist/src/renderer/input/InputProcessor.d.ts.map +1 -0
  233. package/dist/src/renderer/input/InputProcessor.js +81 -0
  234. package/dist/src/renderer/input/InputProcessor.js.map +1 -0
  235. package/dist/src/renderer/input/index.d.ts +2 -0
  236. package/dist/src/renderer/input/index.d.ts.map +1 -0
  237. package/dist/src/renderer/input/index.js +2 -0
  238. package/dist/src/renderer/input/index.js.map +1 -0
  239. package/dist/src/renderer/lifecycle/EventBus.d.ts +41 -0
  240. package/dist/src/renderer/lifecycle/EventBus.d.ts.map +1 -0
  241. package/dist/src/renderer/lifecycle/EventBus.js +78 -0
  242. package/dist/src/renderer/lifecycle/EventBus.js.map +1 -0
  243. package/dist/src/renderer/lifecycle/ResizeManager.d.ts +34 -0
  244. package/dist/src/renderer/lifecycle/ResizeManager.d.ts.map +1 -0
  245. package/dist/src/renderer/lifecycle/ResizeManager.js +47 -0
  246. package/dist/src/renderer/lifecycle/ResizeManager.js.map +1 -0
  247. package/dist/src/renderer/lifecycle/TerminalSetup.d.ts +36 -0
  248. package/dist/src/renderer/lifecycle/TerminalSetup.d.ts.map +1 -0
  249. package/dist/src/renderer/lifecycle/TerminalSetup.js +82 -0
  250. package/dist/src/renderer/lifecycle/TerminalSetup.js.map +1 -0
  251. package/dist/src/renderer/lifecycle/index.d.ts +4 -0
  252. package/dist/src/renderer/lifecycle/index.d.ts.map +1 -0
  253. package/dist/src/renderer/lifecycle/index.js +4 -0
  254. package/dist/src/renderer/lifecycle/index.js.map +1 -0
  255. package/dist/src/renderer/modes/FullscreenRenderer.d.ts +12 -0
  256. package/dist/src/renderer/modes/FullscreenRenderer.d.ts.map +1 -0
  257. package/dist/src/renderer/modes/FullscreenRenderer.js +52 -0
  258. package/dist/src/renderer/modes/FullscreenRenderer.js.map +1 -0
  259. package/dist/src/renderer/modes/InlineRenderer.d.ts +25 -0
  260. package/dist/src/renderer/modes/InlineRenderer.d.ts.map +1 -0
  261. package/dist/src/renderer/modes/InlineRenderer.js +161 -0
  262. package/dist/src/renderer/modes/InlineRenderer.js.map +1 -0
  263. package/dist/src/renderer/modes/RendererMode.d.ts +42 -0
  264. package/dist/src/renderer/modes/RendererMode.d.ts.map +1 -0
  265. package/dist/src/renderer/modes/RendererMode.js +2 -0
  266. package/dist/src/renderer/modes/RendererMode.js.map +1 -0
  267. package/dist/src/renderer/modes/StaticContentRenderer.d.ts +25 -0
  268. package/dist/src/renderer/modes/StaticContentRenderer.d.ts.map +1 -0
  269. package/dist/src/renderer/modes/StaticContentRenderer.js +47 -0
  270. package/dist/src/renderer/modes/StaticContentRenderer.js.map +1 -0
  271. package/dist/src/renderer/modes/index.d.ts +5 -0
  272. package/dist/src/renderer/modes/index.d.ts.map +1 -0
  273. package/dist/src/renderer/modes/index.js +4 -0
  274. package/dist/src/renderer/modes/index.js.map +1 -0
  275. package/dist/src/renderer-context.d.ts +9 -0
  276. package/dist/src/renderer-context.d.ts.map +1 -0
  277. package/dist/src/renderer-context.js +22 -0
  278. package/dist/src/renderer-context.js.map +1 -0
  279. package/dist/src/renderer-types.d.ts +103 -0
  280. package/dist/src/renderer-types.d.ts.map +1 -0
  281. package/dist/src/renderer-types.js +2 -0
  282. package/dist/src/renderer-types.js.map +1 -0
  283. package/dist/src/renderer.d.ts +4 -86
  284. package/dist/src/renderer.d.ts.map +1 -1
  285. package/dist/src/renderer.js +214 -384
  286. package/dist/src/renderer.js.map +1 -1
  287. package/dist/src/test/index.d.ts.map +1 -1
  288. package/dist/src/test/index.js.map +1 -1
  289. package/dist/src/test/mock-streams.d.ts.map +1 -1
  290. package/dist/src/test/mock-streams.js.map +1 -1
  291. package/dist/src/test/render-tui.d.ts.map +1 -1
  292. package/dist/src/test/render-tui.js +2 -5
  293. package/dist/src/test/render-tui.js.map +1 -1
  294. package/dist/src/trace/SpanTree.d.ts.map +1 -1
  295. package/dist/src/trace/SpanTree.js +21 -11
  296. package/dist/src/trace/SpanTree.js.map +1 -1
  297. package/dist/src/trace/format-value.d.ts +15 -0
  298. package/dist/src/trace/format-value.d.ts.map +1 -0
  299. package/dist/src/trace/format-value.js +77 -0
  300. package/dist/src/trace/format-value.js.map +1 -0
  301. package/dist/src/trace/index.d.ts.map +1 -1
  302. package/dist/src/trace/index.js.map +1 -1
  303. package/dist/src/trace/location.js +1 -1
  304. package/dist/src/trace/location.js.map +1 -1
  305. package/dist/src/trace/span-processor.d.ts.map +1 -1
  306. package/dist/src/trace/span-processor.js.map +1 -1
  307. package/dist/src/trace/span-state.d.ts +19 -2
  308. package/dist/src/trace/span-state.d.ts.map +1 -1
  309. package/dist/src/trace/span-state.js +62 -31
  310. package/dist/src/trace/span-state.js.map +1 -1
  311. package/dist/src/trace/tui-logger.js.map +1 -1
  312. package/dist/src/utils/border.d.ts +1 -1
  313. package/dist/src/utils/border.d.ts.map +1 -1
  314. package/dist/src/utils/border.js +6 -0
  315. package/dist/src/utils/border.js.map +1 -1
  316. package/dist/src/utils/flex-layout.d.ts +2 -1
  317. package/dist/src/utils/flex-layout.d.ts.map +1 -1
  318. package/dist/src/utils/flex-layout.js +22 -33
  319. package/dist/src/utils/flex-layout.js.map +1 -1
  320. package/dist/src/utils/index.d.ts +1 -1
  321. package/dist/src/utils/index.d.ts.map +1 -1
  322. package/dist/src/utils/index.js +1 -1
  323. package/dist/src/utils/index.js.map +1 -1
  324. package/dist/src/utils/padding.d.ts.map +1 -1
  325. package/dist/src/utils/padding.js.map +1 -1
  326. package/dist/src/utils/styles.d.ts +20 -1
  327. package/dist/src/utils/styles.d.ts.map +1 -1
  328. package/dist/src/utils/styles.js +36 -1
  329. package/dist/src/utils/styles.js.map +1 -1
  330. package/dist/src/visualize/index.d.ts +8 -19
  331. package/dist/src/visualize/index.d.ts.map +1 -1
  332. package/dist/src/visualize/index.js +11 -25
  333. package/dist/src/visualize/index.js.map +1 -1
  334. package/dist/tsconfig.tsbuildinfo +1 -1
  335. package/jsx-dev-runtime.ts +5 -0
  336. package/jsx-runtime.ts +54 -0
  337. package/package.json +124 -92
  338. package/src/codeblock.tsx +34 -34
  339. package/src/components/Divider.tsx +23 -0
  340. package/src/components/Markdown.tsx +380 -0
  341. package/src/components/MultilineTextInput.tsx +749 -0
  342. package/src/components/Overlay.tsx +56 -0
  343. package/src/components/Static.tsx +68 -0
  344. package/src/components/TextInput.tsx +356 -0
  345. package/src/components/index.ts +6 -0
  346. package/src/components/text-editing.ts +464 -0
  347. package/src/console/ConsoleCapture.ts +272 -0
  348. package/src/console/ConsolePopover.tsx +487 -0
  349. package/src/console/clipboard.ts +81 -0
  350. package/src/console/index.ts +42 -0
  351. package/src/console/useConsole.ts +129 -0
  352. package/src/debug/DebugOverlay.ts +557 -0
  353. package/src/debug/DiagnosticsPanel.tsx +27 -27
  354. package/src/dev/Toast.tsx +117 -0
  355. package/src/dev/index.ts +2 -0
  356. package/src/dev.tsx +489 -0
  357. package/src/highlight.ts +46 -46
  358. package/src/hmr-plugin.ts +61 -0
  359. package/src/hooks/index.ts +4 -0
  360. package/src/hooks/use-keyboard.ts +44 -24
  361. package/src/hooks/use-mouse.ts +51 -0
  362. package/src/hooks/use-paste.ts +21 -6
  363. package/src/hooks/use-scroll.ts +386 -0
  364. package/src/hooks/useFrameStats.ts +17 -17
  365. package/src/hosts/base.ts +180 -59
  366. package/src/hosts/box.ts +117 -94
  367. package/src/hosts/canvas.ts +170 -141
  368. package/src/hosts/codeblock.ts +117 -133
  369. package/src/hosts/flex-container.ts +124 -0
  370. package/src/hosts/hstack.ts +11 -59
  371. package/src/hosts/index.ts +24 -14
  372. package/src/hosts/overlay-item.ts +72 -0
  373. package/src/hosts/overlay.ts +125 -0
  374. package/src/hosts/scroll.ts +255 -0
  375. package/src/hosts/single-child.ts +52 -0
  376. package/src/hosts/spacer.ts +30 -26
  377. package/src/hosts/text.ts +198 -164
  378. package/src/hosts/vstack.ts +11 -59
  379. package/src/hosts/zstack.ts +79 -67
  380. package/src/index.ts +44 -19
  381. package/src/inline/index.tsx +123 -123
  382. package/src/motion/color-motion-value.ts +67 -67
  383. package/src/motion/color.test.ts +107 -107
  384. package/src/motion/color.ts +9 -190
  385. package/src/motion/event-emitter.ts +20 -20
  386. package/src/motion/frame.ts +35 -35
  387. package/src/motion/hooks.ts +144 -139
  388. package/src/motion/index.ts +10 -10
  389. package/src/motion/motion-value.test.ts +207 -207
  390. package/src/motion/motion-value.ts +112 -112
  391. package/src/motion/spring-math.ts +88 -83
  392. package/src/motion/types.ts +25 -25
  393. package/src/profiler.ts +50 -50
  394. package/src/reconciler/host-config.ts +152 -174
  395. package/src/reconciler/noop-methods.ts +55 -0
  396. package/src/reconciler/types.ts +112 -46
  397. package/src/remote/Procedures.ts +52 -0
  398. package/src/remote/Router.ts +58 -0
  399. package/src/remote/Server.ts +76 -0
  400. package/src/remote/index.ts +90 -0
  401. package/src/renderer/core/FrameBuilder.ts +49 -0
  402. package/src/renderer/core/RendererState.ts +80 -0
  403. package/src/renderer/core/index.ts +2 -0
  404. package/src/renderer/input/InputProcessor.ts +94 -0
  405. package/src/renderer/input/index.ts +1 -0
  406. package/src/renderer/lifecycle/EventBus.ts +90 -0
  407. package/src/renderer/lifecycle/ResizeManager.ts +65 -0
  408. package/src/renderer/lifecycle/TerminalSetup.ts +105 -0
  409. package/src/renderer/lifecycle/index.ts +3 -0
  410. package/src/renderer/modes/FullscreenRenderer.ts +53 -0
  411. package/src/renderer/modes/InlineRenderer.ts +186 -0
  412. package/src/renderer/modes/RendererMode.ts +46 -0
  413. package/src/renderer/modes/StaticContentRenderer.ts +56 -0
  414. package/src/renderer/modes/index.ts +4 -0
  415. package/src/renderer-context.ts +27 -0
  416. package/src/renderer-types.ts +109 -0
  417. package/src/renderer.ts +392 -642
  418. package/src/test/index.ts +5 -5
  419. package/src/test/mock-streams.ts +115 -115
  420. package/src/test/render-tui.ts +84 -87
  421. package/src/utils/border.ts +79 -73
  422. package/src/utils/flex-layout.ts +80 -93
  423. package/src/utils/index.ts +1 -1
  424. package/src/utils/padding.ts +27 -27
  425. package/src/utils/styles.ts +50 -7
  426. package/src/visualize/index.tsx +225 -240
  427. package/dist/src/output.d.ts +0 -47
  428. package/dist/src/output.d.ts.map +0 -1
  429. package/dist/src/output.js +0 -125
  430. package/dist/src/output.js.map +0 -1
  431. package/dist/src/terminal.d.ts +0 -37
  432. package/dist/src/terminal.d.ts.map +0 -1
  433. package/dist/src/terminal.js +0 -65
  434. package/dist/src/terminal.js.map +0 -1
  435. package/src/output.ts +0 -156
  436. package/src/terminal.ts +0 -67
  437. package/src/trace/SpanTree.tsx +0 -195
  438. package/src/trace/index.tsx +0 -205
  439. package/src/trace/location.ts +0 -90
  440. package/src/trace/span-processor.ts +0 -65
  441. package/src/trace/span-state.ts +0 -286
  442. package/src/trace/tui-logger.ts +0 -72
@@ -1,205 +0,0 @@
1
- // OpenTelemetry trace visualization for Effect programs
2
-
3
- import React from "react"
4
- import { spawn } from "node:child_process"
5
- import { fileURLToPath } from "node:url"
6
- import { isAppPath } from "./location.js"
7
- import * as NodeSdk from "@effect/opentelemetry/NodeSdk"
8
- import type * as OtelResource from "@effect/opentelemetry/Resource"
9
- import { Effect, Layer, Logger } from "effect"
10
- import { createRenderer, createRoot } from "../renderer.js"
11
- import { useKeyboard } from "../hooks/index.js"
12
- import { SpanTreeState } from "./span-state.js"
13
- import { TuiSpanProcessor } from "./span-processor.js"
14
- import { SpanTree } from "./SpanTree.js"
15
- import { makeTuiLogger } from "./tui-logger.js"
16
- import { DiagnosticsPanel } from "../debug/DiagnosticsPanel.js"
17
-
18
- export { SpanTreeState } from "./span-state.js"
19
- export { TuiSpanProcessor } from "./span-processor.js"
20
- export { SpanTree } from "./SpanTree.js"
21
- export { makeTuiLogger } from "./tui-logger.js"
22
-
23
- const FRAME_INTERVAL_MS = 16 // ~60fps
24
- const COMPLETION_DELAY_MS = 500
25
-
26
- export interface TraceVisualizeOptions {
27
- /** Service name for OpenTelemetry resource (default: "effect-app") */
28
- serviceName?: string
29
- /** Show diagnostics panel inside the trace UI */
30
- debugPanel?: boolean
31
- }
32
-
33
- // Wrapper component that drives 60fps render loop
34
- function SpanTreeWrapper({
35
- state,
36
- renderer,
37
- serviceName,
38
- done,
39
- showDebug,
40
- }: {
41
- state: SpanTreeState
42
- renderer: ReturnType<typeof createRenderer>
43
- serviceName?: string
44
- done: boolean
45
- showDebug?: boolean
46
- }) {
47
- const [, forceUpdate] = React.useState(0)
48
-
49
- const openInEditor = React.useCallback((location: { file: string; line: number; column?: number }) => {
50
- const normalizedFile = location.file.startsWith("file://") ? fileURLToPath(location.file) : location.file
51
-
52
- if (!isAppPath(normalizedFile)) {
53
- if (process.env.EFFECT_TUI_DEBUG_STACKS === "1") {
54
- // eslint-disable-next-line no-console
55
- console.error("[effect-tui] refusing to open non-app path", normalizedFile)
56
- }
57
- return
58
- }
59
-
60
- const target =
61
- location.column !== undefined
62
- ? `${normalizedFile}:${location.line}:${location.column}`
63
- : `${normalizedFile}:${location.line}`
64
-
65
- const candidates = [
66
- process.env.EFFECT_TUI_EDITOR ? `${process.env.EFFECT_TUI_EDITOR} ${target}` : null,
67
- process.env.EDITOR ? `${process.env.EDITOR} ${target}` : null,
68
- `code -g ${target}`,
69
- `open ${target}`,
70
- `xdg-open ${target}`,
71
- ].filter(Boolean) as string[]
72
-
73
- for (const cmd of candidates) {
74
- try {
75
- const child = spawn(cmd, { shell: true, stdio: "ignore", detached: true })
76
- child.unref()
77
- return
78
- } catch {
79
- // keep trying next candidate
80
- }
81
- }
82
-
83
- // eslint-disable-next-line no-console
84
- console.error("Could not launch editor. Set EFFECT_TUI_EDITOR or EDITOR to your preferred command.")
85
- }, [])
86
-
87
- React.useEffect(() => {
88
- const interval = setInterval(() => {
89
- state.tick()
90
- forceUpdate((n) => n + 1)
91
- renderer.requestRender()
92
- }, FRAME_INTERVAL_MS)
93
- return () => clearInterval(interval)
94
- }, [renderer, state])
95
-
96
- // Handle keyboard
97
- useKeyboard(
98
- React.useCallback(
99
- (key) => {
100
- // q quits when done (Ctrl+C always works via renderer)
101
- if (done && key.text === "q") {
102
- process.exit(0)
103
- }
104
- // Navigate between spans with logs
105
- if (key.name === "up") {
106
- state.selectPrev()
107
- } else if (key.name === "down") {
108
- state.selectNext()
109
- } else if (key.name === "return" || key.name === "enter") {
110
- // Toggle expand selected span's logs
111
- state.toggleExpand()
112
- } else if (key.name === "char" && key.text?.toLowerCase() === "o") {
113
- const loc = state.getOpenTarget()
114
- if (loc) {
115
- openInEditor(loc)
116
- } else {
117
- state.toggleExpand()
118
- }
119
- }
120
- },
121
- [done, state, openInEditor],
122
- ),
123
- )
124
-
125
- return (
126
- <vstack spacing={1}>
127
- <SpanTree state={state} spinnerIndex={state.spinnerIndex} serviceName={serviceName} showExitPrompt={done} />
128
- {showDebug && <DiagnosticsPanel title="Trace Diagnostics" />}
129
- </vstack>
130
- )
131
- }
132
-
133
- /**
134
- * Run an Effect program with TUI visualization of OpenTelemetry spans.
135
- *
136
- * Shows a tree of spans as they execute, with spinners for in-progress
137
- * and checkmarks/X for completed spans.
138
- *
139
- * @example
140
- * ```ts
141
- * const program = Effect.gen(function* () {
142
- * yield* Effect.sleep(1000).pipe(Effect.withSpan("step-1"))
143
- * yield* Effect.sleep(500).pipe(Effect.withSpan("step-2"))
144
- * }).pipe(Effect.withSpan("main"))
145
- *
146
- * await traceVisualize(program)
147
- * ```
148
- */
149
- export const traceVisualize = <A, E, R>(
150
- effect: Effect.Effect<A, E, R>,
151
- options?: TraceVisualizeOptions,
152
- ): Effect.Effect<void, never, Exclude<R, OtelResource.Resource>> =>
153
- Effect.gen(function* () {
154
- // Create state and processor
155
- const state = new SpanTreeState()
156
-
157
- // Create renderer - handles terminal setup (raw mode, keyboard)
158
- const renderer = createRenderer({ mode: "inline" })
159
- const root = createRoot(renderer)
160
-
161
- // Connect processor to renderer
162
- const requestRender = () => renderer.requestRender()
163
- const processor = new TuiSpanProcessor(state, requestRender)
164
-
165
- const serviceName = options?.serviceName ?? "effect-app"
166
- const showDebug = options?.debugPanel ?? false
167
-
168
- // Create OpenTelemetry layer
169
- const TracerLive = NodeSdk.layer(() => ({
170
- resource: { serviceName },
171
- spanProcessor: processor,
172
- }))
173
-
174
- // Create TUI logger that feeds into state (replaces console logger)
175
- const tuiLogger = makeTuiLogger(state, requestRender)
176
- const TuiLoggerLive = Logger.replace(Logger.defaultLogger, tuiLogger)
177
-
178
- // Mount React component
179
- root.render(
180
- <SpanTreeWrapper
181
- state={state}
182
- renderer={renderer}
183
- serviceName={serviceName}
184
- done={false}
185
- showDebug={showDebug}
186
- />,
187
- )
188
-
189
- // Combine tracing layer with TUI logger
190
- const Live = Layer.merge(TracerLive, TuiLoggerLive)
191
-
192
- // Run the effect with tracing (ignore result, visualization shows success/failure)
193
- yield* effect.pipe(Effect.provide(Live), Effect.ignore)
194
-
195
- // Wait a bit for final render
196
- yield* Effect.sleep(COMPLETION_DELAY_MS)
197
-
198
- // Show exit prompt - component handles Enter keypress with process.exit(0)
199
- root.render(
200
- <SpanTreeWrapper state={state} renderer={renderer} serviceName={serviceName} done={true} showDebug={showDebug} />,
201
- )
202
-
203
- // Keep process alive until user presses Enter (component will call process.exit)
204
- return yield* Effect.never
205
- }) as Effect.Effect<void, never, Exclude<R, OtelResource.Resource>>
@@ -1,90 +0,0 @@
1
- import * as path from "node:path"
2
- import { fileURLToPath } from "node:url"
3
- import type { SourceLocation } from "./span-state.js"
4
-
5
- const cwd = process.cwd()
6
-
7
- export function normalizeFile(file: string): string {
8
- if (file.startsWith("file://")) return fileURLToPath(file)
9
- return file
10
- }
11
-
12
- export function isAppPath(file: string): boolean {
13
- const normalized = normalizeFile(file)
14
- if (normalized.includes(`${path.sep}node_modules${path.sep}`)) return false
15
- if (normalized.includes(`${path.sep}.bun${path.sep}`)) return false
16
- return normalized.startsWith(cwd)
17
- }
18
-
19
- export function isLibraryPath(file: string): boolean {
20
- const normalized = normalizeFile(file)
21
- return (
22
- normalized.includes(`${path.sep}node_modules${path.sep}`) ||
23
- normalized.includes("fiberRuntime") ||
24
- normalized.includes("native")
25
- )
26
- }
27
-
28
- export function pickBestLocation(candidates: SourceLocation[]): SourceLocation | undefined {
29
- if (candidates.length === 0) return undefined
30
- const app = candidates.find((c) => isAppPath(c.file))
31
- if (app) return app
32
- const nonLib = candidates.find((c) => !isLibraryPath(c.file))
33
- return nonLib ?? candidates[0]
34
- }
35
-
36
- export function parseStackLine(line: string): SourceLocation | undefined {
37
- const re = /at\s+(?:.+?\s+\()?((?:file:\/\/)?[^):]+):(\d+):(\d+)\)?$/
38
- const match = re.exec(line.trim())
39
- if (match) {
40
- const file = normalizeFile(match[1]!)
41
- return {
42
- file,
43
- line: Number(match[2]),
44
- column: Number(match[3]),
45
- }
46
- }
47
- }
48
-
49
- export function parseStackTrace(stack?: unknown): SourceLocation | undefined {
50
- if (typeof stack !== "string") return
51
- const lines = stack.split("\n")
52
- const parsed: SourceLocation[] = []
53
- for (const line of lines) {
54
- const loc = parseStackLine(line)
55
- if (loc) parsed.push(loc)
56
- }
57
- return pickBestLocation(parsed)
58
- }
59
-
60
- export function captureAppLocation(ignore: Array<RegExp | string> = []): SourceLocation | undefined {
61
- const prev = Error.prepareStackTrace
62
- try {
63
- Error.prepareStackTrace = (_err, structured) => structured
64
- const err = new Error()
65
- Error.captureStackTrace(err, captureAppLocation)
66
- const stack = err.stack as unknown as NodeJS.CallSite[] | undefined
67
- if (!stack) return
68
-
69
- const isIgnored = (file: string | null | undefined) => {
70
- if (!file) return false
71
- return ignore.some((p) => typeof p === "string" ? file.includes(p) : p.test(file))
72
- }
73
-
74
- const candidates: SourceLocation[] = []
75
- for (const frame of stack) {
76
- const file = frame.getFileName() || frame.getScriptNameOrSourceURL()
77
- if (!file || isIgnored(file)) continue
78
- const loc: SourceLocation = {
79
- file: normalizeFile(file),
80
- line: frame.getLineNumber() ?? 0,
81
- column: frame.getColumnNumber() ?? undefined,
82
- }
83
- candidates.push(loc)
84
- }
85
-
86
- return pickBestLocation(candidates)
87
- } finally {
88
- Error.prepareStackTrace = prev
89
- }
90
- }
@@ -1,65 +0,0 @@
1
- // Custom SpanProcessor that feeds into TUI visualization
2
-
3
- import type { Context, Span } from "@opentelemetry/api"
4
- import type { ReadableSpan, SpanProcessor } from "@opentelemetry/sdk-trace-base"
5
- import { SpanStatusCode } from "@opentelemetry/api"
6
- import type { SpanTreeState } from "./span-state.js"
7
- import { captureAppLocation, parseStackTrace } from "./location.js"
8
-
9
- /**
10
- * OpenTelemetry SpanProcessor that updates SpanTreeState for TUI visualization.
11
- */
12
- export class TuiSpanProcessor implements SpanProcessor {
13
- constructor(
14
- private state: SpanTreeState,
15
- private onUpdate: () => void,
16
- ) {}
17
-
18
- onStart(span: Span, parentContext: Context): void {
19
- const spanContext = span.spanContext()
20
- const parentSpanContext = parentContext.getValue(Symbol.for("OpenTelemetry Context Key SPAN"))
21
- const location = captureAppLocation([/span-processor/, /SpanProcessor/, /@effect-tui\/react/])
22
-
23
- // Extract parent span ID if available
24
- let parentId: string | null = null
25
- if (parentSpanContext && typeof parentSpanContext === "object" && "spanContext" in parentSpanContext) {
26
- const parentCtx = (parentSpanContext as Span).spanContext()
27
- parentId = parentCtx.spanId
28
- }
29
-
30
- // Get span name - the span object has a private _name, but we can get it from ReadableSpan later
31
- // For now use the spanId as placeholder, we'll update on end
32
- const name = (span as any).name || spanContext.spanId
33
-
34
- this.state.addSpan(spanContext.spanId, parentId, name, location)
35
- this.onUpdate()
36
- }
37
-
38
- onEnd(span: ReadableSpan): void {
39
- const spanContext = span.spanContext()
40
- const status = span.status
41
- let location = parseStackTrace((span.attributes as any)?.["code.stacktrace"])
42
- if (!location) {
43
- location = captureAppLocation([/span-processor/, /SpanProcessor/])
44
- if (!location && process.env.EFFECT_TUI_DEBUG_STACKS === "1") {
45
- // eslint-disable-next-line no-console
46
- console.error("[effect-tui] span end missing location; attributes stack:", (span.attributes as any)?.["code.stacktrace"])
47
- }
48
- }
49
-
50
- // Determine success/failure from span status
51
- const isSuccess = status.code !== SpanStatusCode.ERROR
52
- const error = status.code === SpanStatusCode.ERROR ? status.message : undefined
53
-
54
- this.state.endSpan(spanContext.spanId, isSuccess ? "success" : "failure", error, location)
55
- this.onUpdate()
56
- }
57
-
58
- forceFlush(): Promise<void> {
59
- return Promise.resolve()
60
- }
61
-
62
- shutdown(): Promise<void> {
63
- return Promise.resolve()
64
- }
65
- }
@@ -1,286 +0,0 @@
1
- import { isAppPath } from "./location.js"
2
-
3
- // Span tree state for TUI visualization
4
-
5
- export type SpanStatus = "running" | "success" | "failure"
6
-
7
- export type LogLevel = "INFO" | "WARN" | "ERROR" | "DEBUG" | "TRACE"
8
-
9
- export interface SourceLocation {
10
- file: string
11
- line: number
12
- column?: number
13
- }
14
-
15
- export interface LogEntry {
16
- level: LogLevel
17
- message: string
18
- timestamp: number
19
- location?: SourceLocation
20
- }
21
-
22
- export interface LogCounts {
23
- info: number
24
- warn: number
25
- error: number
26
- debug: number
27
- total: number
28
- }
29
-
30
- export interface SpanNode {
31
- id: string
32
- parentId: string | null
33
- name: string
34
- status: SpanStatus
35
- startTime: number // ms since epoch
36
- endTime?: number
37
- duration?: number // ms
38
- error?: string
39
- logEntries: LogEntry[]
40
- children: SpanNode[]
41
- completedAt?: number // when status changed to success/failure, for fade timing
42
- // Animated opacity (0-1, where 1 is fully opaque)
43
- opacity: number
44
- targetOpacity: number
45
- opacityVelocity: number
46
- location?: SourceLocation
47
- }
48
-
49
- const SPINNER_FRAMES_COUNT = 6
50
- const SPIN_INTERVAL_MS = 80
51
-
52
- /**
53
- * Manages span tree state for visualization.
54
- * Tracks hierarchy, status, and notifies listeners on changes.
55
- */
56
- export class SpanTreeState {
57
- private roots: SpanNode[] = []
58
- private nodesById = new Map<string, SpanNode>()
59
- private listeners = new Set<() => void>()
60
-
61
- // Selection state for log navigation
62
- selectedSpanId: string | null = null
63
- expandedSpanIds = new Set<string>()
64
-
65
- // Animation timing (managed here to decouple from React)
66
- spinnerIndex = 0
67
- private lastSpinnerTime = performance.now()
68
- private lastTickTime = performance.now()
69
-
70
- /** Add a new running span */
71
- addSpan(id: string, parentId: string | null, name: string, location?: SourceLocation): void {
72
- const node: SpanNode = {
73
- id,
74
- parentId,
75
- name,
76
- status: "running",
77
- startTime: Date.now(),
78
- logEntries: [],
79
- children: [],
80
- opacity: 1,
81
- targetOpacity: 1,
82
- opacityVelocity: 0,
83
- location,
84
- }
85
-
86
- this.nodesById.set(id, node)
87
-
88
- if (parentId) {
89
- const parent = this.nodesById.get(parentId)
90
- if (parent) {
91
- parent.children.push(node)
92
- } else {
93
- // Parent not found, add as root
94
- this.roots.push(node)
95
- }
96
- } else {
97
- this.roots.push(node)
98
- }
99
-
100
- this.notify()
101
- }
102
-
103
- /** Mark a span as completed */
104
- endSpan(id: string, status: "success" | "failure", error?: string, location?: SourceLocation): void {
105
- const node = this.nodesById.get(id)
106
- if (!node) return
107
-
108
- const endTime = Date.now()
109
- node.status = status
110
- node.endTime = endTime
111
- node.duration = endTime - node.startTime
112
- node.completedAt = performance.now() // Use perf time for fade animation timing
113
- // Keep opaque for now, fade will start after FADE_DELAY_MS in tickAnimations
114
- node.targetOpacity = 1
115
- if (error) node.error = error
116
- if (location) node.location = location
117
-
118
- this.notify()
119
- }
120
-
121
- /** Add a log entry to a span (called by custom logger) */
122
- addLog(spanId: string, level: LogLevel, message: string, location?: SourceLocation): void {
123
- const node = this.nodesById.get(spanId)
124
- if (!node) return
125
-
126
- node.logEntries.push({
127
- level,
128
- message,
129
- timestamp: Date.now(),
130
- location,
131
- })
132
-
133
- this.notify()
134
- }
135
-
136
- /** Get all spans that have logs or captured locations (for navigation) in tree order */
137
- getSpansWithLogs(): SpanNode[] {
138
- const result: SpanNode[] = []
139
- const visit = (node: SpanNode) => {
140
- if (node.logEntries.length > 0 || node.location) result.push(node)
141
- for (const child of node.children) visit(child)
142
- }
143
- for (const root of this.roots) visit(root)
144
- return result
145
- }
146
-
147
- /** Select next span with logs */
148
- selectNext(): void {
149
- const spans = this.getSpansWithLogs()
150
- if (spans.length === 0) return
151
-
152
- if (this.selectedSpanId === null) {
153
- this.selectedSpanId = spans[0]?.id
154
- } else {
155
- const idx = spans.findIndex((s) => s.id === this.selectedSpanId)
156
- if (idx < spans.length - 1) {
157
- this.selectedSpanId = spans[idx + 1]?.id
158
- }
159
- }
160
- this.notify()
161
- }
162
-
163
- /** Select previous span with logs */
164
- selectPrev(): void {
165
- const spans = this.getSpansWithLogs()
166
- if (spans.length === 0) return
167
-
168
- if (this.selectedSpanId === null) {
169
- this.selectedSpanId = spans[spans.length - 1]?.id
170
- } else {
171
- const idx = spans.findIndex((s) => s.id === this.selectedSpanId)
172
- if (idx > 0) {
173
- this.selectedSpanId = spans[idx - 1]?.id
174
- }
175
- }
176
- this.notify()
177
- }
178
-
179
- /** Toggle expansion of selected span */
180
- toggleExpand(): void {
181
- if (this.selectedSpanId === null) return
182
-
183
- if (this.expandedSpanIds.has(this.selectedSpanId)) {
184
- this.expandedSpanIds.delete(this.selectedSpanId)
185
- } else {
186
- this.expandedSpanIds.add(this.selectedSpanId)
187
- }
188
- this.notify()
189
- }
190
-
191
- /** Tick animations (spinner + fades). Call at 60fps for smooth rendering. */
192
- tick(): void {
193
- const now = performance.now()
194
- // Cap dt to prevent large jumps when frames are delayed (GC, etc.)
195
- const dt = Math.min((now - this.lastTickTime) / 1000, 0.05)
196
- this.lastTickTime = now
197
-
198
- // Advance spinner at its own rate
199
- if (now - this.lastSpinnerTime >= SPIN_INTERVAL_MS) {
200
- this.spinnerIndex = (this.spinnerIndex + 1) % SPINNER_FRAMES_COUNT
201
- this.lastSpinnerTime = now
202
- }
203
-
204
- // Fade animations
205
- const fadeDelayMs = 1000
206
- const halfLife = 0.3
207
- const decay = 0.5 ** (dt / halfLife)
208
- const epsilon = 0.01
209
-
210
- for (const node of this.nodesById.values()) {
211
- // Check if completed span should start fading
212
- if (node.completedAt && node.targetOpacity === 1) {
213
- if (now - node.completedAt >= fadeDelayMs) {
214
- node.targetOpacity = 0.4 // Start fading
215
- }
216
- }
217
-
218
- const diff = Math.abs(node.opacity - node.targetOpacity)
219
- if (diff > epsilon) {
220
- // Exponential decay toward target - unconditionally stable
221
- node.opacity = node.targetOpacity + (node.opacity - node.targetOpacity) * decay
222
- } else if (diff > 0) {
223
- // Snap to target when close enough
224
- node.opacity = node.targetOpacity
225
- }
226
- }
227
- }
228
-
229
- /** Get all root spans */
230
- getRoots(): readonly SpanNode[] {
231
- return this.roots
232
- }
233
-
234
- /** Get a span by ID */
235
- getSpan(id: string): SpanNode | undefined {
236
- return this.nodesById.get(id)
237
- }
238
-
239
- /** Best-effort source location for the currently selected span or its logs */
240
- getOpenTarget(): SourceLocation | null {
241
- if (!this.selectedSpanId) return null
242
- const node = this.nodesById.get(this.selectedSpanId)
243
- if (!node) return null
244
- let fallback: SourceLocation | undefined
245
-
246
- // Prefer app-path log entries (latest first)
247
- for (let i = node.logEntries.length - 1; i >= 0; i--) {
248
- const entry = node.logEntries[i]
249
- if (entry.location) {
250
- if (isAppPath(entry.location.file)) return entry.location
251
- if (!fallback) fallback = entry.location
252
- }
253
- }
254
-
255
- if (node.location) {
256
- if (isAppPath(node.location.file)) return node.location
257
- if (!fallback) fallback = node.location
258
- }
259
-
260
- if (!fallback) return null
261
- if (process.env.EFFECT_TUI_DEBUG_STACKS === "1") {
262
- // eslint-disable-next-line no-console
263
- console.error("[effect-tui] only non-app locations available", fallback)
264
- }
265
-
266
- return null
267
- }
268
-
269
- /** Check if any spans are still running */
270
- hasRunning(): boolean {
271
- for (const node of this.nodesById.values()) {
272
- if (node.status === "running") return true
273
- }
274
- return false
275
- }
276
-
277
- /** Subscribe to state changes */
278
- subscribe(listener: () => void): () => void {
279
- this.listeners.add(listener)
280
- return () => this.listeners.delete(listener)
281
- }
282
-
283
- private notify(): void {
284
- this.listeners.forEach((l) => l())
285
- }
286
- }