@effect-tui/react 0.1.1 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jsx-runtime.d.ts +13 -0
- package/dist/jsx-runtime.d.ts.map +1 -1
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/src/codeblock.d.ts.map +1 -1
- package/dist/src/codeblock.js.map +1 -1
- package/dist/src/components/Divider.d.ts +18 -0
- package/dist/src/components/Divider.d.ts.map +1 -0
- package/dist/src/components/Divider.js +17 -0
- package/dist/src/components/Divider.js.map +1 -0
- package/dist/src/components/Markdown.d.ts +66 -0
- package/dist/src/components/Markdown.d.ts.map +1 -0
- package/dist/src/components/Markdown.js +226 -0
- package/dist/src/components/Markdown.js.map +1 -0
- package/dist/src/components/MultilineTextInput.d.ts +65 -0
- package/dist/src/components/MultilineTextInput.d.ts.map +1 -0
- package/dist/src/components/MultilineTextInput.js +607 -0
- package/dist/src/components/MultilineTextInput.js.map +1 -0
- package/dist/src/components/Overlay.d.ts +46 -0
- package/dist/src/components/Overlay.d.ts.map +1 -0
- package/dist/src/components/Overlay.js +11 -0
- package/dist/src/components/Overlay.js.map +1 -0
- package/dist/src/components/Static.d.ts +44 -0
- package/dist/src/components/Static.d.ts.map +1 -0
- package/dist/src/components/Static.js +53 -0
- package/dist/src/components/Static.js.map +1 -0
- package/dist/src/components/TextInput.d.ts +53 -0
- package/dist/src/components/TextInput.d.ts.map +1 -0
- package/dist/src/components/TextInput.js +210 -0
- package/dist/src/components/TextInput.js.map +1 -0
- package/dist/src/components/index.d.ts +7 -0
- package/dist/src/components/index.d.ts.map +1 -0
- package/dist/src/components/index.js +7 -0
- package/dist/src/components/index.js.map +1 -0
- package/dist/src/components/text-editing.d.ts +62 -0
- package/dist/src/components/text-editing.d.ts.map +1 -0
- package/dist/src/components/text-editing.js +385 -0
- package/dist/src/components/text-editing.js.map +1 -0
- package/dist/src/console/ConsoleCapture.d.ts +36 -0
- package/dist/src/console/ConsoleCapture.d.ts.map +1 -0
- package/dist/src/console/ConsoleCapture.js +210 -0
- package/dist/src/console/ConsoleCapture.js.map +1 -0
- package/dist/src/console/ConsolePopover.d.ts +18 -0
- package/dist/src/console/ConsolePopover.d.ts.map +1 -0
- package/dist/src/console/ConsolePopover.js +324 -0
- package/dist/src/console/ConsolePopover.js.map +1 -0
- package/dist/src/console/clipboard.d.ts +10 -0
- package/dist/src/console/clipboard.d.ts.map +1 -0
- package/dist/src/console/clipboard.js +74 -0
- package/dist/src/console/clipboard.js.map +1 -0
- package/dist/src/console/index.d.ts +5 -0
- package/dist/src/console/index.d.ts.map +1 -0
- package/dist/src/console/index.js +33 -0
- package/dist/src/console/index.js.map +1 -0
- package/dist/src/console/useConsole.d.ts +44 -0
- package/dist/src/console/useConsole.d.ts.map +1 -0
- package/dist/src/console/useConsole.js +91 -0
- package/dist/src/console/useConsole.js.map +1 -0
- package/dist/src/debug/DebugOverlay.d.ts +49 -0
- package/dist/src/debug/DebugOverlay.d.ts.map +1 -0
- package/dist/src/debug/DebugOverlay.js +438 -0
- package/dist/src/debug/DebugOverlay.js.map +1 -0
- package/dist/src/debug/DiagnosticsPanel.d.ts.map +1 -1
- package/dist/src/debug/DiagnosticsPanel.js.map +1 -1
- package/dist/src/dev/Toast.d.ts +19 -0
- package/dist/src/dev/Toast.d.ts.map +1 -0
- package/dist/src/dev/Toast.js +72 -0
- package/dist/src/dev/Toast.js.map +1 -0
- package/dist/src/dev/index.d.ts +2 -0
- package/dist/src/dev/index.d.ts.map +1 -0
- package/dist/src/dev/index.js +3 -0
- package/dist/src/dev/index.js.map +1 -0
- package/dist/src/dev.d.ts +114 -0
- package/dist/src/dev.d.ts.map +1 -0
- package/dist/src/dev.js +373 -0
- package/dist/src/dev.js.map +1 -0
- package/dist/src/highlight.d.ts +3 -3
- package/dist/src/highlight.d.ts.map +1 -1
- package/dist/src/highlight.js.map +1 -1
- package/dist/src/hmr-plugin.d.ts +2 -0
- package/dist/src/hmr-plugin.d.ts.map +1 -0
- package/dist/src/hmr-plugin.js +53 -0
- package/dist/src/hmr-plugin.js.map +1 -0
- package/dist/src/hooks/index.d.ts +4 -0
- package/dist/src/hooks/index.d.ts.map +1 -1
- package/dist/src/hooks/index.js +2 -0
- package/dist/src/hooks/index.js.map +1 -1
- package/dist/src/hooks/use-keyboard.d.ts +11 -0
- package/dist/src/hooks/use-keyboard.d.ts.map +1 -1
- package/dist/src/hooks/use-keyboard.js +22 -4
- package/dist/src/hooks/use-keyboard.js.map +1 -1
- package/dist/src/hooks/use-mouse.d.ts +24 -0
- package/dist/src/hooks/use-mouse.d.ts.map +1 -0
- package/dist/src/hooks/use-mouse.js +41 -0
- package/dist/src/hooks/use-mouse.js.map +1 -0
- package/dist/src/hooks/use-paste.d.ts +11 -0
- package/dist/src/hooks/use-paste.d.ts.map +1 -1
- package/dist/src/hooks/use-paste.js +17 -3
- package/dist/src/hooks/use-paste.js.map +1 -1
- package/dist/src/hooks/use-scroll.d.ts +79 -0
- package/dist/src/hooks/use-scroll.d.ts.map +1 -0
- package/dist/src/hooks/use-scroll.js +239 -0
- package/dist/src/hooks/use-scroll.js.map +1 -0
- package/dist/src/hooks/useFrameStats.js.map +1 -1
- package/dist/src/hosts/base.d.ts +62 -1
- package/dist/src/hosts/base.d.ts.map +1 -1
- package/dist/src/hosts/base.js +118 -5
- package/dist/src/hosts/base.js.map +1 -1
- package/dist/src/hosts/box.d.ts +7 -7
- package/dist/src/hosts/box.d.ts.map +1 -1
- package/dist/src/hosts/box.js +31 -26
- package/dist/src/hosts/box.js.map +1 -1
- package/dist/src/hosts/canvas.d.ts +8 -8
- package/dist/src/hosts/canvas.d.ts.map +1 -1
- package/dist/src/hosts/canvas.js +13 -22
- package/dist/src/hosts/canvas.js.map +1 -1
- package/dist/src/hosts/codeblock.d.ts +7 -7
- package/dist/src/hosts/codeblock.d.ts.map +1 -1
- package/dist/src/hosts/codeblock.js +11 -20
- package/dist/src/hosts/codeblock.js.map +1 -1
- package/dist/src/hosts/flex-container.d.ts +45 -0
- package/dist/src/hosts/flex-container.d.ts.map +1 -0
- package/dist/src/hosts/flex-container.js +90 -0
- package/dist/src/hosts/flex-container.js.map +1 -0
- package/dist/src/hosts/hstack.d.ts +6 -11
- package/dist/src/hosts/hstack.d.ts.map +1 -1
- package/dist/src/hosts/hstack.js +6 -41
- package/dist/src/hosts/hstack.js.map +1 -1
- package/dist/src/hosts/index.d.ts +4 -0
- package/dist/src/hosts/index.d.ts.map +1 -1
- package/dist/src/hosts/index.js +10 -0
- package/dist/src/hosts/index.js.map +1 -1
- package/dist/src/hosts/overlay-item.d.ts +32 -0
- package/dist/src/hosts/overlay-item.d.ts.map +1 -0
- package/dist/src/hosts/overlay-item.js +54 -0
- package/dist/src/hosts/overlay-item.js.map +1 -0
- package/dist/src/hosts/overlay.d.ts +30 -0
- package/dist/src/hosts/overlay.d.ts.map +1 -0
- package/dist/src/hosts/overlay.js +105 -0
- package/dist/src/hosts/overlay.js.map +1 -0
- package/dist/src/hosts/scroll.d.ts +56 -0
- package/dist/src/hosts/scroll.d.ts.map +1 -0
- package/dist/src/hosts/scroll.js +204 -0
- package/dist/src/hosts/scroll.js.map +1 -0
- package/dist/src/hosts/single-child.d.ts +16 -0
- package/dist/src/hosts/single-child.d.ts.map +1 -0
- package/dist/src/hosts/single-child.js +45 -0
- package/dist/src/hosts/single-child.js.map +1 -0
- package/dist/src/hosts/spacer.d.ts.map +1 -1
- package/dist/src/hosts/spacer.js +7 -3
- package/dist/src/hosts/spacer.js.map +1 -1
- package/dist/src/hosts/text.d.ts +9 -6
- package/dist/src/hosts/text.d.ts.map +1 -1
- package/dist/src/hosts/text.js +49 -22
- package/dist/src/hosts/text.js.map +1 -1
- package/dist/src/hosts/vstack.d.ts +6 -11
- package/dist/src/hosts/vstack.d.ts.map +1 -1
- package/dist/src/hosts/vstack.js +6 -41
- package/dist/src/hosts/vstack.js.map +1 -1
- package/dist/src/hosts/zstack.d.ts.map +1 -1
- package/dist/src/hosts/zstack.js +16 -5
- package/dist/src/hosts/zstack.js.map +1 -1
- package/dist/src/index.d.ts +9 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +10 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/inline/index.d.ts.map +1 -1
- package/dist/src/inline/index.js.map +1 -1
- package/dist/src/motion/color-motion-value.d.ts.map +1 -1
- package/dist/src/motion/color-motion-value.js.map +1 -1
- package/dist/src/motion/color.d.ts +1 -29
- package/dist/src/motion/color.d.ts.map +1 -1
- package/dist/src/motion/color.js +2 -170
- package/dist/src/motion/color.js.map +1 -1
- package/dist/src/motion/color.test.js.map +1 -1
- package/dist/src/motion/event-emitter.d.ts.map +1 -1
- package/dist/src/motion/event-emitter.js.map +1 -1
- package/dist/src/motion/frame.js.map +1 -1
- package/dist/src/motion/hooks.d.ts.map +1 -1
- package/dist/src/motion/hooks.js +8 -3
- package/dist/src/motion/hooks.js.map +1 -1
- package/dist/src/motion/index.d.ts.map +1 -1
- package/dist/src/motion/index.js.map +1 -1
- package/dist/src/motion/motion-value.d.ts.map +1 -1
- package/dist/src/motion/motion-value.js.map +1 -1
- package/dist/src/motion/motion-value.test.js.map +1 -1
- package/dist/src/motion/spring-math.d.ts +6 -1
- package/dist/src/motion/spring-math.d.ts.map +1 -1
- package/dist/src/motion/spring-math.js +6 -1
- package/dist/src/motion/spring-math.js.map +1 -1
- package/dist/src/motion/types.d.ts.map +1 -1
- package/dist/src/motion/types.js.map +1 -1
- package/dist/src/profiler.js.map +1 -1
- package/dist/src/reconciler/host-config.d.ts +5 -5
- package/dist/src/reconciler/host-config.d.ts.map +1 -1
- package/dist/src/reconciler/host-config.js +43 -51
- package/dist/src/reconciler/host-config.js.map +1 -1
- package/dist/src/reconciler/noop-methods.d.ts +29 -0
- package/dist/src/reconciler/noop-methods.d.ts.map +1 -0
- package/dist/src/reconciler/noop-methods.js +43 -0
- package/dist/src/reconciler/noop-methods.js.map +1 -0
- package/dist/src/reconciler/types.d.ts +68 -14
- package/dist/src/reconciler/types.d.ts.map +1 -1
- package/dist/src/remote/Procedures.d.ts +22 -0
- package/dist/src/remote/Procedures.d.ts.map +1 -0
- package/dist/src/remote/Procedures.js +42 -0
- package/dist/src/remote/Procedures.js.map +1 -0
- package/dist/src/remote/Router.d.ts +20 -0
- package/dist/src/remote/Router.d.ts.map +1 -0
- package/dist/src/remote/Router.js +26 -0
- package/dist/src/remote/Router.js.map +1 -0
- package/dist/src/remote/Server.d.ts +6 -0
- package/dist/src/remote/Server.d.ts.map +1 -0
- package/dist/src/remote/Server.js +53 -0
- package/dist/src/remote/Server.js.map +1 -0
- package/dist/src/remote/index.d.ts +18 -0
- package/dist/src/remote/index.d.ts.map +1 -0
- package/dist/src/remote/index.js +74 -0
- package/dist/src/remote/index.js.map +1 -0
- package/dist/src/renderer/core/FrameBuilder.d.ts +18 -0
- package/dist/src/renderer/core/FrameBuilder.d.ts.map +1 -0
- package/dist/src/renderer/core/FrameBuilder.js +38 -0
- package/dist/src/renderer/core/FrameBuilder.js.map +1 -0
- package/dist/src/renderer/core/RendererState.d.ts +41 -0
- package/dist/src/renderer/core/RendererState.d.ts.map +1 -0
- package/dist/src/renderer/core/RendererState.js +70 -0
- package/dist/src/renderer/core/RendererState.js.map +1 -0
- package/dist/src/renderer/core/index.d.ts +3 -0
- package/dist/src/renderer/core/index.d.ts.map +1 -0
- package/dist/src/renderer/core/index.js +3 -0
- package/dist/src/renderer/core/index.js.map +1 -0
- package/dist/src/renderer/input/InputProcessor.d.ts +25 -0
- package/dist/src/renderer/input/InputProcessor.d.ts.map +1 -0
- package/dist/src/renderer/input/InputProcessor.js +81 -0
- package/dist/src/renderer/input/InputProcessor.js.map +1 -0
- package/dist/src/renderer/input/index.d.ts +2 -0
- package/dist/src/renderer/input/index.d.ts.map +1 -0
- package/dist/src/renderer/input/index.js +2 -0
- package/dist/src/renderer/input/index.js.map +1 -0
- package/dist/src/renderer/lifecycle/EventBus.d.ts +41 -0
- package/dist/src/renderer/lifecycle/EventBus.d.ts.map +1 -0
- package/dist/src/renderer/lifecycle/EventBus.js +78 -0
- package/dist/src/renderer/lifecycle/EventBus.js.map +1 -0
- package/dist/src/renderer/lifecycle/ResizeManager.d.ts +34 -0
- package/dist/src/renderer/lifecycle/ResizeManager.d.ts.map +1 -0
- package/dist/src/renderer/lifecycle/ResizeManager.js +47 -0
- package/dist/src/renderer/lifecycle/ResizeManager.js.map +1 -0
- package/dist/src/renderer/lifecycle/TerminalSetup.d.ts +36 -0
- package/dist/src/renderer/lifecycle/TerminalSetup.d.ts.map +1 -0
- package/dist/src/renderer/lifecycle/TerminalSetup.js +82 -0
- package/dist/src/renderer/lifecycle/TerminalSetup.js.map +1 -0
- package/dist/src/renderer/lifecycle/index.d.ts +4 -0
- package/dist/src/renderer/lifecycle/index.d.ts.map +1 -0
- package/dist/src/renderer/lifecycle/index.js +4 -0
- package/dist/src/renderer/lifecycle/index.js.map +1 -0
- package/dist/src/renderer/modes/FullscreenRenderer.d.ts +12 -0
- package/dist/src/renderer/modes/FullscreenRenderer.d.ts.map +1 -0
- package/dist/src/renderer/modes/FullscreenRenderer.js +52 -0
- package/dist/src/renderer/modes/FullscreenRenderer.js.map +1 -0
- package/dist/src/renderer/modes/InlineRenderer.d.ts +22 -0
- package/dist/src/renderer/modes/InlineRenderer.d.ts.map +1 -0
- package/dist/src/renderer/modes/InlineRenderer.js +154 -0
- package/dist/src/renderer/modes/InlineRenderer.js.map +1 -0
- package/dist/src/renderer/modes/RendererMode.d.ts +42 -0
- package/dist/src/renderer/modes/RendererMode.d.ts.map +1 -0
- package/dist/src/renderer/modes/RendererMode.js +2 -0
- package/dist/src/renderer/modes/RendererMode.js.map +1 -0
- package/dist/src/renderer/modes/StaticContentRenderer.d.ts +25 -0
- package/dist/src/renderer/modes/StaticContentRenderer.d.ts.map +1 -0
- package/dist/src/renderer/modes/StaticContentRenderer.js +47 -0
- package/dist/src/renderer/modes/StaticContentRenderer.js.map +1 -0
- package/dist/src/renderer/modes/index.d.ts +5 -0
- package/dist/src/renderer/modes/index.d.ts.map +1 -0
- package/dist/src/renderer/modes/index.js +4 -0
- package/dist/src/renderer/modes/index.js.map +1 -0
- package/dist/src/renderer-context.d.ts +9 -0
- package/dist/src/renderer-context.d.ts.map +1 -0
- package/dist/src/renderer-context.js +22 -0
- package/dist/src/renderer-context.js.map +1 -0
- package/dist/src/renderer-types.d.ts +103 -0
- package/dist/src/renderer-types.d.ts.map +1 -0
- package/dist/src/renderer-types.js +2 -0
- package/dist/src/renderer-types.js.map +1 -0
- package/dist/src/renderer.d.ts +4 -86
- package/dist/src/renderer.d.ts.map +1 -1
- package/dist/src/renderer.js +213 -384
- package/dist/src/renderer.js.map +1 -1
- package/dist/src/test/index.d.ts.map +1 -1
- package/dist/src/test/index.js.map +1 -1
- package/dist/src/test/mock-streams.d.ts.map +1 -1
- package/dist/src/test/mock-streams.js.map +1 -1
- package/dist/src/test/render-tui.d.ts.map +1 -1
- package/dist/src/test/render-tui.js +2 -5
- package/dist/src/test/render-tui.js.map +1 -1
- package/dist/src/trace/SpanTree.d.ts.map +1 -1
- package/dist/src/trace/SpanTree.js +21 -11
- package/dist/src/trace/SpanTree.js.map +1 -1
- package/dist/src/trace/format-value.d.ts +15 -0
- package/dist/src/trace/format-value.d.ts.map +1 -0
- package/dist/src/trace/format-value.js +77 -0
- package/dist/src/trace/format-value.js.map +1 -0
- package/dist/src/trace/index.d.ts.map +1 -1
- package/dist/src/trace/index.js.map +1 -1
- package/dist/src/trace/location.js +1 -1
- package/dist/src/trace/location.js.map +1 -1
- package/dist/src/trace/span-processor.d.ts.map +1 -1
- package/dist/src/trace/span-processor.js.map +1 -1
- package/dist/src/trace/span-state.d.ts +19 -2
- package/dist/src/trace/span-state.d.ts.map +1 -1
- package/dist/src/trace/span-state.js +62 -31
- package/dist/src/trace/span-state.js.map +1 -1
- package/dist/src/trace/tui-logger.js.map +1 -1
- package/dist/src/utils/border.d.ts +1 -1
- package/dist/src/utils/border.d.ts.map +1 -1
- package/dist/src/utils/border.js +6 -0
- package/dist/src/utils/border.js.map +1 -1
- package/dist/src/utils/flex-layout.d.ts +2 -1
- package/dist/src/utils/flex-layout.d.ts.map +1 -1
- package/dist/src/utils/flex-layout.js +22 -33
- package/dist/src/utils/flex-layout.js.map +1 -1
- package/dist/src/utils/index.d.ts +1 -1
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/index.js +1 -1
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/utils/padding.d.ts.map +1 -1
- package/dist/src/utils/padding.js.map +1 -1
- package/dist/src/utils/styles.d.ts +20 -1
- package/dist/src/utils/styles.d.ts.map +1 -1
- package/dist/src/utils/styles.js +36 -1
- package/dist/src/utils/styles.js.map +1 -1
- package/dist/src/visualize/index.d.ts +8 -19
- package/dist/src/visualize/index.d.ts.map +1 -1
- package/dist/src/visualize/index.js +11 -25
- package/dist/src/visualize/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/jsx-dev-runtime.ts +5 -0
- package/jsx-runtime.ts +54 -0
- package/package.json +124 -92
- package/src/codeblock.tsx +34 -34
- package/src/components/Divider.tsx +23 -0
- package/src/components/Markdown.tsx +380 -0
- package/src/components/MultilineTextInput.tsx +749 -0
- package/src/components/Overlay.tsx +56 -0
- package/src/components/Static.tsx +68 -0
- package/src/components/TextInput.tsx +285 -0
- package/src/components/index.ts +6 -0
- package/src/components/text-editing.ts +464 -0
- package/src/console/ConsoleCapture.ts +272 -0
- package/src/console/ConsolePopover.tsx +487 -0
- package/src/console/clipboard.ts +81 -0
- package/src/console/index.ts +42 -0
- package/src/console/useConsole.ts +129 -0
- package/src/debug/DebugOverlay.ts +557 -0
- package/src/debug/DiagnosticsPanel.tsx +27 -27
- package/src/dev/Toast.tsx +117 -0
- package/src/dev/index.ts +2 -0
- package/src/dev.tsx +489 -0
- package/src/highlight.ts +46 -46
- package/src/hmr-plugin.ts +61 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/use-keyboard.ts +44 -24
- package/src/hooks/use-mouse.ts +51 -0
- package/src/hooks/use-paste.ts +21 -6
- package/src/hooks/use-scroll.ts +386 -0
- package/src/hooks/useFrameStats.ts +17 -17
- package/src/hosts/base.ts +180 -59
- package/src/hosts/box.ts +117 -96
- package/src/hosts/canvas.ts +137 -141
- package/src/hosts/codeblock.ts +117 -133
- package/src/hosts/flex-container.ts +124 -0
- package/src/hosts/hstack.ts +11 -59
- package/src/hosts/index.ts +24 -14
- package/src/hosts/overlay-item.ts +72 -0
- package/src/hosts/overlay.ts +125 -0
- package/src/hosts/scroll.ts +255 -0
- package/src/hosts/single-child.ts +52 -0
- package/src/hosts/spacer.ts +30 -26
- package/src/hosts/text.ts +198 -164
- package/src/hosts/vstack.ts +11 -59
- package/src/hosts/zstack.ts +79 -67
- package/src/index.ts +44 -19
- package/src/inline/index.tsx +123 -123
- package/src/motion/color-motion-value.ts +67 -67
- package/src/motion/color.test.ts +107 -107
- package/src/motion/color.ts +9 -190
- package/src/motion/event-emitter.ts +20 -20
- package/src/motion/frame.ts +35 -35
- package/src/motion/hooks.ts +144 -139
- package/src/motion/index.ts +10 -10
- package/src/motion/motion-value.test.ts +207 -207
- package/src/motion/motion-value.ts +112 -112
- package/src/motion/spring-math.ts +88 -83
- package/src/motion/types.ts +25 -25
- package/src/profiler.ts +50 -50
- package/src/reconciler/host-config.ts +152 -174
- package/src/reconciler/noop-methods.ts +55 -0
- package/src/reconciler/types.ts +112 -46
- package/src/remote/Procedures.ts +52 -0
- package/src/remote/Router.ts +58 -0
- package/src/remote/Server.ts +76 -0
- package/src/remote/index.ts +90 -0
- package/src/renderer/core/FrameBuilder.ts +49 -0
- package/src/renderer/core/RendererState.ts +80 -0
- package/src/renderer/core/index.ts +2 -0
- package/src/renderer/input/InputProcessor.ts +94 -0
- package/src/renderer/input/index.ts +1 -0
- package/src/renderer/lifecycle/EventBus.ts +90 -0
- package/src/renderer/lifecycle/ResizeManager.ts +65 -0
- package/src/renderer/lifecycle/TerminalSetup.ts +105 -0
- package/src/renderer/lifecycle/index.ts +3 -0
- package/src/renderer/modes/FullscreenRenderer.ts +53 -0
- package/src/renderer/modes/InlineRenderer.ts +178 -0
- package/src/renderer/modes/RendererMode.ts +46 -0
- package/src/renderer/modes/StaticContentRenderer.ts +56 -0
- package/src/renderer/modes/index.ts +4 -0
- package/src/renderer-context.ts +27 -0
- package/src/renderer-types.ts +109 -0
- package/src/renderer.ts +391 -642
- package/src/test/index.ts +5 -5
- package/src/test/mock-streams.ts +115 -115
- package/src/test/render-tui.ts +84 -87
- package/src/utils/border.ts +79 -73
- package/src/utils/flex-layout.ts +80 -93
- package/src/utils/index.ts +1 -1
- package/src/utils/padding.ts +27 -27
- package/src/utils/styles.ts +50 -7
- package/src/visualize/index.tsx +225 -240
- package/dist/src/output.d.ts +0 -47
- package/dist/src/output.d.ts.map +0 -1
- package/dist/src/output.js +0 -125
- package/dist/src/output.js.map +0 -1
- package/dist/src/terminal.d.ts +0 -37
- package/dist/src/terminal.d.ts.map +0 -1
- package/dist/src/terminal.js +0 -65
- package/dist/src/terminal.js.map +0 -1
- package/src/output.ts +0 -156
- package/src/terminal.ts +0 -67
- package/src/trace/SpanTree.tsx +0 -195
- package/src/trace/index.tsx +0 -205
- package/src/trace/location.ts +0 -90
- package/src/trace/span-processor.ts +0 -65
- package/src/trace/span-state.ts +0 -286
- package/src/trace/tui-logger.ts +0 -72
package/src/trace/index.tsx
DELETED
|
@@ -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>>
|
package/src/trace/location.ts
DELETED
|
@@ -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
|
-
}
|
package/src/trace/span-state.ts
DELETED
|
@@ -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
|
-
}
|