@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.
- 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 +55 -0
- package/dist/src/components/TextInput.d.ts.map +1 -0
- package/dist/src/components/TextInput.js +277 -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 +30 -23
- package/dist/src/hosts/box.js.map +1 -1
- package/dist/src/hosts/canvas.d.ts +16 -8
- package/dist/src/hosts/canvas.d.ts.map +1 -1
- package/dist/src/hosts/canvas.js +27 -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 +25 -0
- package/dist/src/renderer/modes/InlineRenderer.d.ts.map +1 -0
- package/dist/src/renderer/modes/InlineRenderer.js +161 -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 +214 -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 +356 -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 -94
- package/src/hosts/canvas.ts +170 -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 +186 -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 +392 -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
|
@@ -3,36 +3,36 @@ import { useFrameStats } from "../hooks/useFrameStats.js"
|
|
|
3
3
|
import "../jsx.js"
|
|
4
4
|
|
|
5
5
|
export interface DiagnosticsPanelProps {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
sampleMs?: number
|
|
7
|
+
title?: string
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export function DiagnosticsPanel({ sampleMs = 200, title = "Diagnostics" }: DiagnosticsPanelProps) {
|
|
11
|
-
|
|
11
|
+
const stats = useFrameStats(sampleMs)
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
const fps = stats ? (stats.frameMs > 0 ? (1000 / stats.frameMs).toFixed(1) : "∞") : "—"
|
|
14
|
+
const bytes = stats ? stats.bytes : 0
|
|
15
|
+
const h = stats ? stats.contentHeight : 0
|
|
16
|
+
const phases = stats?.phases
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
18
|
+
return (
|
|
19
|
+
<vstack spacing={1}>
|
|
20
|
+
<text fg={Colors.cyan} bold>
|
|
21
|
+
{title} ({stats?.mode ?? "?"})
|
|
22
|
+
</text>
|
|
23
|
+
{stats ? (
|
|
24
|
+
<>
|
|
25
|
+
<text fg={Colors.gray(12)}>
|
|
26
|
+
size {stats.width}×{stats.height} content {h} rows — {bytes} bytes/frame — {fps} fps
|
|
27
|
+
</text>
|
|
28
|
+
<text fg={Colors.gray(12)}>
|
|
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
|
|
31
|
+
</text>
|
|
32
|
+
</>
|
|
33
|
+
) : (
|
|
34
|
+
<text fg={Colors.gray(10)}>Waiting for first frame…</text>
|
|
35
|
+
)}
|
|
36
|
+
</vstack>
|
|
37
|
+
)
|
|
38
38
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Toast notification system for dev mode
|
|
2
|
+
// Beautiful, minimal notifications that appear at the top of the screen
|
|
3
|
+
|
|
4
|
+
import { useState, useCallback, createContext, useContext, type ReactNode } from "react"
|
|
5
|
+
import { Colors } from "@effect-tui/core"
|
|
6
|
+
import { useTerminalSize } from "../renderer-context.js"
|
|
7
|
+
|
|
8
|
+
// ─────────────────────────────────────────────────────────────
|
|
9
|
+
// Types
|
|
10
|
+
// ─────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
export type ToastType = "success" | "info" | "warning" | "error"
|
|
13
|
+
|
|
14
|
+
export interface Toast {
|
|
15
|
+
id: number
|
|
16
|
+
message: string
|
|
17
|
+
type: ToastType
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ToastContextValue {
|
|
21
|
+
toasts: Toast[]
|
|
22
|
+
show: (message: string, type?: ToastType, durationMs?: number) => void
|
|
23
|
+
dismiss: (id: number) => void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─────────────────────────────────────────────────────────────
|
|
27
|
+
// Context
|
|
28
|
+
// ─────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
const ToastContext = createContext<ToastContextValue | null>(null)
|
|
31
|
+
|
|
32
|
+
export function useToast(): ToastContextValue {
|
|
33
|
+
const ctx = useContext(ToastContext)
|
|
34
|
+
if (!ctx) {
|
|
35
|
+
throw new Error("useToast must be used within a ToastProvider")
|
|
36
|
+
}
|
|
37
|
+
return ctx
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─────────────────────────────────────────────────────────────
|
|
41
|
+
// Styling
|
|
42
|
+
// ─────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
const TOAST_STYLES: Record<ToastType, { bg: ReturnType<typeof Colors.rgb>; fg: ReturnType<typeof Colors.rgb>; icon: string }> = {
|
|
45
|
+
success: { bg: Colors.rgb(30, 70, 40), fg: Colors.rgb(140, 230, 140), icon: "✓" },
|
|
46
|
+
info: { bg: Colors.rgb(30, 50, 80), fg: Colors.rgb(140, 180, 230), icon: "ℹ" },
|
|
47
|
+
warning: { bg: Colors.rgb(80, 60, 20), fg: Colors.rgb(230, 200, 100), icon: "⚠" },
|
|
48
|
+
error: { bg: Colors.rgb(80, 30, 30), fg: Colors.rgb(230, 140, 140), icon: "✗" },
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ─────────────────────────────────────────────────────────────
|
|
52
|
+
// Provider
|
|
53
|
+
// ─────────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
let toastId = 0
|
|
56
|
+
|
|
57
|
+
export function ToastProvider({ children }: { children: ReactNode }) {
|
|
58
|
+
const [toasts, setToasts] = useState<Toast[]>([])
|
|
59
|
+
|
|
60
|
+
const show = useCallback((message: string, type: ToastType = "info", durationMs = 2000) => {
|
|
61
|
+
const id = ++toastId
|
|
62
|
+
setToasts((prev) => [...prev, { id, message, type }])
|
|
63
|
+
|
|
64
|
+
setTimeout(() => {
|
|
65
|
+
setToasts((prev) => prev.filter((t) => t.id !== id))
|
|
66
|
+
}, durationMs)
|
|
67
|
+
}, [])
|
|
68
|
+
|
|
69
|
+
const dismiss = useCallback((id: number) => {
|
|
70
|
+
setToasts((prev) => prev.filter((t) => t.id !== id))
|
|
71
|
+
}, [])
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<ToastContext.Provider value={{ toasts, show, dismiss }}>
|
|
75
|
+
{children}
|
|
76
|
+
</ToastContext.Provider>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─────────────────────────────────────────────────────────────
|
|
81
|
+
// Toast Display Component
|
|
82
|
+
// ─────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
export function ToastContainer() {
|
|
85
|
+
const { toasts } = useToast()
|
|
86
|
+
const { width } = useTerminalSize()
|
|
87
|
+
|
|
88
|
+
if (toasts.length === 0) return null
|
|
89
|
+
|
|
90
|
+
// Show only the most recent toast
|
|
91
|
+
const toast = toasts[toasts.length - 1]
|
|
92
|
+
const style = TOAST_STYLES[toast.type]
|
|
93
|
+
|
|
94
|
+
// Center the message with padding
|
|
95
|
+
const content = ` ${style.icon} ${toast.message} `
|
|
96
|
+
const padding = Math.max(0, Math.floor((width - content.length) / 2))
|
|
97
|
+
const paddedContent = " ".repeat(padding) + content + " ".repeat(width - padding - content.length)
|
|
98
|
+
|
|
99
|
+
// Just render the toast line - parent zstack handles positioning
|
|
100
|
+
return (
|
|
101
|
+
<text bg={style.bg} fg={style.fg}>
|
|
102
|
+
{paddedContent}
|
|
103
|
+
</text>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ─────────────────────────────────────────────────────────────
|
|
108
|
+
// Convenience hook for screenshot toast
|
|
109
|
+
// ─────────────────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
export function useScreenshotToast() {
|
|
112
|
+
const { show } = useToast()
|
|
113
|
+
|
|
114
|
+
return useCallback((path: string) => {
|
|
115
|
+
show(`Screenshot saved: ${path}`, "success", 2500)
|
|
116
|
+
}, [show])
|
|
117
|
+
}
|
package/src/dev/index.ts
ADDED
package/src/dev.tsx
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Development utilities for hot module replacement (HMR).
|
|
3
|
+
*
|
|
4
|
+
* Enables hot reload during development:
|
|
5
|
+
* - File changes trigger re-render without process restart
|
|
6
|
+
* - Terminal stays stable (no screen flash)
|
|
7
|
+
* - State preserved when using Effect's globalValue with effect-atom
|
|
8
|
+
* - Debug console panel (` to toggle, ~ for screenshot)
|
|
9
|
+
* - Remote control when EFFECT_TUI_REMOTE=1
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { globalValue } from "effect/GlobalValue"
|
|
13
|
+
import * as watcher from "@parcel/watcher"
|
|
14
|
+
import { dirname } from "path"
|
|
15
|
+
import { stat } from "fs/promises"
|
|
16
|
+
import { readFileSync } from "fs"
|
|
17
|
+
import React from "react"
|
|
18
|
+
import { Colors } from "@effect-tui/core"
|
|
19
|
+
import { createRenderer, createRoot, type Root } from "./renderer.js"
|
|
20
|
+
import type { RendererOptions } from "./renderer-types.js"
|
|
21
|
+
import type { TuiRenderer } from "./renderer-types.js"
|
|
22
|
+
import { ConsolePopover } from "./console/ConsolePopover.js"
|
|
23
|
+
import { useConsole } from "./console/useConsole.js"
|
|
24
|
+
import { enableRemote } from "./remote/index.js"
|
|
25
|
+
import { ToastProvider, ToastContainer, useToast } from "./dev/Toast.js"
|
|
26
|
+
import { useKeyboard } from "./hooks/use-keyboard.js"
|
|
27
|
+
import { useRenderer, useTerminalSize } from "./renderer-context.js"
|
|
28
|
+
import { copyToClipboardSync, copyToClipboard } from "./console/clipboard.js"
|
|
29
|
+
import { DiagnosticsPanel } from "./debug/DiagnosticsPanel.js"
|
|
30
|
+
import { Overlay } from "./components/Overlay.js"
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Pipeable combinator that makes an atom persist across hot reloads.
|
|
34
|
+
*
|
|
35
|
+
* This is the cleanest way to define HMR-persistent atoms - just pipe it!
|
|
36
|
+
* Combines globalValue (for persistence) + keepAlive (prevents registry cleanup).
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* import { Atom } from "@effect-atom/atom-react"
|
|
41
|
+
* import { hmr, devRender } from "@effect-tui/react"
|
|
42
|
+
*
|
|
43
|
+
* // Just pipe hmr() - that's it!
|
|
44
|
+
* const countAtom = Atom.make(0).pipe(hmr("count"))
|
|
45
|
+
* const userAtom = Atom.make<User | null>(null).pipe(hmr("user"))
|
|
46
|
+
*
|
|
47
|
+
* export default function App() {
|
|
48
|
+
* const count = useAtomValue(countAtom)
|
|
49
|
+
* // Edit this file - count persists!
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function hmr<A>(key: string): (self: A) => A {
|
|
54
|
+
return (self: A): A => {
|
|
55
|
+
return globalValue(Symbol.for(`hmr/${key}`), () => {
|
|
56
|
+
// Apply keepAlive if it's an atom (just sets keepAlive: true)
|
|
57
|
+
if (typeof self === "object" && self !== null && "keepAlive" in self) {
|
|
58
|
+
return Object.assign(Object.create(Object.getPrototypeOf(self)), {
|
|
59
|
+
...self,
|
|
60
|
+
keepAlive: true,
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
return self
|
|
64
|
+
}) as A
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Wrap any value creation in HMR persistence.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```tsx
|
|
73
|
+
* const countAtom = hmrState("count", () => Atom.make(0).pipe(Atom.keepAlive))
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export function hmrState<T>(key: string, create: () => T): T {
|
|
77
|
+
return globalValue(Symbol.for(`hmr/${key}`), create)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Simple self-running dev mode. Just add this at the bottom of your file:
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```tsx
|
|
85
|
+
* export default function App() { ... }
|
|
86
|
+
*
|
|
87
|
+
* devMain(import.meta)
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* That's it! Handles:
|
|
91
|
+
* - Only runs when file is the entry point (import.meta.main)
|
|
92
|
+
* - Only initializes once across HMR reloads
|
|
93
|
+
* - Automatically finds the file path
|
|
94
|
+
*/
|
|
95
|
+
export function devMain(importMeta: { main?: boolean; url: string }, options?: Omit<DevRenderOptions, never>): void {
|
|
96
|
+
// Skip if not main entry point
|
|
97
|
+
if (!importMeta.main) return
|
|
98
|
+
|
|
99
|
+
// Skip if this is a HMR re-import (has cache-busting query string)
|
|
100
|
+
if (importMeta.url.includes("?")) return
|
|
101
|
+
|
|
102
|
+
const key = `devMain/${importMeta.url}`
|
|
103
|
+
const initialized = hmrState(key, () => ({ done: false }))
|
|
104
|
+
|
|
105
|
+
if (!initialized.done) {
|
|
106
|
+
initialized.done = true
|
|
107
|
+
const filePath = new URL(importMeta.url).pathname
|
|
108
|
+
devRender(filePath, options)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Cache for runtime key extraction
|
|
113
|
+
const keyCache = new Map<string, string>()
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Parse stack trace to get file path and line number.
|
|
117
|
+
* Returns null if parsing fails.
|
|
118
|
+
*/
|
|
119
|
+
function parseStack(stack: string): { file: string; line: number; col: number } | null {
|
|
120
|
+
// Bun stack format: " at functionName (file:line:col)" or " at file:line:col"
|
|
121
|
+
const lines = stack.split("\n")
|
|
122
|
+
// Skip first line (Error message) and find caller (skip autoHmr itself)
|
|
123
|
+
for (let i = 2; i < lines.length; i++) {
|
|
124
|
+
const match = lines[i].match(/\((.+?):(\d+):(\d+)\)/) || lines[i].match(/at\s+(.+?):(\d+):(\d+)/)
|
|
125
|
+
if (match) {
|
|
126
|
+
return { file: match[1], line: parseInt(match[2], 10), col: parseInt(match[3], 10) }
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Extract variable name from source line using regex.
|
|
134
|
+
* Returns null if extraction fails.
|
|
135
|
+
*/
|
|
136
|
+
function extractVarName(source: string, line: number): string | null {
|
|
137
|
+
const lines = source.split("\n")
|
|
138
|
+
if (line < 1 || line > lines.length) return null
|
|
139
|
+
|
|
140
|
+
const sourceLine = lines[line - 1]
|
|
141
|
+
const match = sourceLine.match(/(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=/)
|
|
142
|
+
return match?.[1] ?? null
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Auto-keyed HMR persistence for atoms.
|
|
147
|
+
*
|
|
148
|
+
* When used with the HMR plugin (recommended), keys are injected at load time
|
|
149
|
+
* based on the variable name. Without the plugin, falls back to runtime
|
|
150
|
+
* stack trace parsing.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```tsx
|
|
154
|
+
* import { Atom } from "@effect-atom/atom-react"
|
|
155
|
+
* import { autoHmr, devRender } from "@effect-tui/react"
|
|
156
|
+
*
|
|
157
|
+
* // No manual key needed - derived from variable name!
|
|
158
|
+
* const countAtom = Atom.make(0).pipe(autoHmr)
|
|
159
|
+
* const userAtom = Atom.make<User | null>(null).pipe(autoHmr)
|
|
160
|
+
*
|
|
161
|
+
* // To enable compile-time transform (faster, more reliable):
|
|
162
|
+
* // bun --preload @effect-tui/react/hmr-plugin run src/app.tsx
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
export function autoHmr<A>(self: A): A {
|
|
166
|
+
// Get stack trace for caller location
|
|
167
|
+
const stack = new Error().stack ?? ""
|
|
168
|
+
const location = parseStack(stack)
|
|
169
|
+
|
|
170
|
+
if (!location) {
|
|
171
|
+
// Fallback: use a hash of the stack trace
|
|
172
|
+
const fallbackKey = `unknown:${stack.slice(0, 100)}`
|
|
173
|
+
return hmr<A>(fallbackKey)(self)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const cacheKey = `${location.file}:${location.line}:${location.col}`
|
|
177
|
+
|
|
178
|
+
// Check cache first
|
|
179
|
+
let key = keyCache.get(cacheKey)
|
|
180
|
+
if (!key) {
|
|
181
|
+
try {
|
|
182
|
+
// Read source file and extract variable name
|
|
183
|
+
const source = readFileSync(location.file, "utf-8")
|
|
184
|
+
const varName = extractVarName(source, location.line)
|
|
185
|
+
key = `${location.file}:${varName ?? `line${location.line}`}`
|
|
186
|
+
} catch {
|
|
187
|
+
// File read failed, use line-based key
|
|
188
|
+
key = `${location.file}:line${location.line}`
|
|
189
|
+
}
|
|
190
|
+
keyCache.set(cacheKey, key)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return hmr<A>(key)(self)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface DevRenderOptions extends RendererOptions {
|
|
197
|
+
/** Directories to watch (defaults to entry file's directory) */
|
|
198
|
+
watchDirs?: string[]
|
|
199
|
+
/** File extensions to watch (defaults to [".ts", ".tsx"]) */
|
|
200
|
+
watchExtensions?: string[]
|
|
201
|
+
/** Debounce delay in ms (defaults to 150) */
|
|
202
|
+
debounce?: number
|
|
203
|
+
/** Wait for file writes to stabilize (defaults to 50ms) */
|
|
204
|
+
awaitWriteFinish?: number
|
|
205
|
+
/** Called before each hot reload */
|
|
206
|
+
onReload?: () => void
|
|
207
|
+
/** Called on errors */
|
|
208
|
+
onError?: (error: Error) => void
|
|
209
|
+
/** Show renderer diagnostics overlay. */
|
|
210
|
+
showStats?: boolean
|
|
211
|
+
/** Sampling window for renderer stats (ms). */
|
|
212
|
+
statsSampleMs?: number
|
|
213
|
+
/** Override diagnostics panel title. */
|
|
214
|
+
statsTitle?: string
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface DevRenderResult {
|
|
218
|
+
renderer: TuiRenderer
|
|
219
|
+
root: Root
|
|
220
|
+
/** Manually trigger a reload */
|
|
221
|
+
rerender: () => Promise<void>
|
|
222
|
+
/** Stop watching and cleanup */
|
|
223
|
+
stop: () => Promise<void>
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Wait for a file to stabilize (no size changes) before proceeding.
|
|
228
|
+
* This avoids importing partially-written files.
|
|
229
|
+
*/
|
|
230
|
+
async function awaitWriteFinish(path: string, stabilityMs: number): Promise<void> {
|
|
231
|
+
let lastSize = -1
|
|
232
|
+
let stableCount = 0
|
|
233
|
+
const checkInterval = 10
|
|
234
|
+
|
|
235
|
+
while (stableCount < stabilityMs / checkInterval) {
|
|
236
|
+
try {
|
|
237
|
+
const stats = await stat(path)
|
|
238
|
+
if (stats.size === lastSize) {
|
|
239
|
+
stableCount++
|
|
240
|
+
} else {
|
|
241
|
+
lastSize = stats.size
|
|
242
|
+
stableCount = 0
|
|
243
|
+
}
|
|
244
|
+
} catch {
|
|
245
|
+
// File might be temporarily unavailable during write
|
|
246
|
+
stableCount = 0
|
|
247
|
+
}
|
|
248
|
+
await new Promise((r) => setTimeout(r, checkInterval))
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Render a TUI app with hot module replacement support.
|
|
254
|
+
*
|
|
255
|
+
* Uses @parcel/watcher for reliable, cross-platform file watching.
|
|
256
|
+
* When files change, the module is re-imported and the app re-renders
|
|
257
|
+
* without restarting the process or tearing down the terminal.
|
|
258
|
+
*
|
|
259
|
+
* For state preservation, use effect-atom with the hmr() combinator:
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* ```tsx
|
|
263
|
+
* import { Atom, useAtomValue } from "@effect-atom/atom-react"
|
|
264
|
+
* import { hmr, devRender } from "@effect-tui/react"
|
|
265
|
+
*
|
|
266
|
+
* const countAtom = Atom.make(0).pipe(hmr("count"))
|
|
267
|
+
*
|
|
268
|
+
* export default function App() {
|
|
269
|
+
* const count = useAtomValue(countAtom)
|
|
270
|
+
* return <text>Count: {count}</text>
|
|
271
|
+
* }
|
|
272
|
+
*
|
|
273
|
+
* devRender(import.meta.path, { mode: "inline" })
|
|
274
|
+
* ```
|
|
275
|
+
*/
|
|
276
|
+
/**
|
|
277
|
+
* Internal component that handles screenshot with toast notification
|
|
278
|
+
*/
|
|
279
|
+
function ScreenshotHandler() {
|
|
280
|
+
const renderer = useRenderer()
|
|
281
|
+
const { show } = useToast()
|
|
282
|
+
|
|
283
|
+
useKeyboard((key) => {
|
|
284
|
+
// ~ (tilde) - screenshot
|
|
285
|
+
if (key.name === "char" && key.text === "~") {
|
|
286
|
+
const ansiOutput = renderer.getScreenshot()
|
|
287
|
+
if (!ansiOutput) return
|
|
288
|
+
|
|
289
|
+
// Copy actual content to clipboard
|
|
290
|
+
const copied = copyToClipboardSync(ansiOutput)
|
|
291
|
+
if (!copied) {
|
|
292
|
+
copyToClipboard(ansiOutput)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Also save to file as backup
|
|
296
|
+
const tmpPath = `/tmp/tui-screenshot-${Date.now()}.txt`
|
|
297
|
+
Bun.write(tmpPath, ansiOutput)
|
|
298
|
+
|
|
299
|
+
show("Screenshot copied!", "success", 1500)
|
|
300
|
+
key.preventDefault?.()
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
return null
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Overlay for renderer diagnostics stats.
|
|
309
|
+
*/
|
|
310
|
+
function StatsOverlay({ sampleMs, title }: { sampleMs?: number; title?: string }) {
|
|
311
|
+
const { width, height } = useTerminalSize()
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<vstack width={width} height={height}>
|
|
315
|
+
<hstack width={width}>
|
|
316
|
+
<spacer />
|
|
317
|
+
<box padding={1} border="rounded" borderColor={Colors.gray(8)} bg={Colors.gray(2)}>
|
|
318
|
+
<DiagnosticsPanel sampleMs={sampleMs} title={title ?? "Renderer Stats"} />
|
|
319
|
+
</box>
|
|
320
|
+
</hstack>
|
|
321
|
+
<spacer />
|
|
322
|
+
</vstack>
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Internal wrapper component that provides dev mode features:
|
|
328
|
+
* - Debug console panel (` backtick to toggle)
|
|
329
|
+
* - Screenshot support (~ tilde) with toast notification
|
|
330
|
+
* - Auto-show console on errors
|
|
331
|
+
* - Optional renderer stats overlay (showStats)
|
|
332
|
+
*/
|
|
333
|
+
function DevWrapper({
|
|
334
|
+
children,
|
|
335
|
+
showStats,
|
|
336
|
+
statsSampleMs,
|
|
337
|
+
statsTitle,
|
|
338
|
+
mode,
|
|
339
|
+
}: {
|
|
340
|
+
children?: React.ReactNode
|
|
341
|
+
showStats?: boolean
|
|
342
|
+
statsSampleMs?: number
|
|
343
|
+
statsTitle?: string
|
|
344
|
+
mode?: "fullscreen" | "inline"
|
|
345
|
+
}) {
|
|
346
|
+
const { visible } = useConsole({ autoShowOnError: true, initiallyVisible: false })
|
|
347
|
+
|
|
348
|
+
// Inline mode: content flows naturally in vstack
|
|
349
|
+
if (mode === "inline") {
|
|
350
|
+
return (
|
|
351
|
+
<ToastProvider>
|
|
352
|
+
<vstack>
|
|
353
|
+
<ScreenshotHandler />
|
|
354
|
+
<ToastContainer />
|
|
355
|
+
{children}
|
|
356
|
+
{visible && <ConsolePopover fixedHeight={5} mode="inline" />}
|
|
357
|
+
</vstack>
|
|
358
|
+
</ToastProvider>
|
|
359
|
+
)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Fullscreen mode: Overlay with toast/console positioned by alignment
|
|
363
|
+
return (
|
|
364
|
+
<ToastProvider>
|
|
365
|
+
<Overlay>
|
|
366
|
+
{/* Base - determines container size */}
|
|
367
|
+
<>
|
|
368
|
+
<ScreenshotHandler />
|
|
369
|
+
{children}
|
|
370
|
+
</>
|
|
371
|
+
{/* Overlays */}
|
|
372
|
+
{visible && (
|
|
373
|
+
<Overlay.Item alignment={{ v: "bottom" }}>
|
|
374
|
+
<ConsolePopover fixedHeight={12} />
|
|
375
|
+
</Overlay.Item>
|
|
376
|
+
)}
|
|
377
|
+
{showStats && (
|
|
378
|
+
<Overlay.Item alignment={{ v: "top", h: "right" }}>
|
|
379
|
+
<StatsOverlay sampleMs={statsSampleMs} title={statsTitle} />
|
|
380
|
+
</Overlay.Item>
|
|
381
|
+
)}
|
|
382
|
+
<Overlay.Item alignment={{ v: "top" }}>
|
|
383
|
+
<ToastContainer />
|
|
384
|
+
</Overlay.Item>
|
|
385
|
+
</Overlay>
|
|
386
|
+
</ToastProvider>
|
|
387
|
+
)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export async function devRender(entryPath: string, options?: DevRenderOptions): Promise<DevRenderResult> {
|
|
391
|
+
const watchExtensions = options?.watchExtensions ?? [".ts", ".tsx"]
|
|
392
|
+
const debounceMs = options?.debounce ?? 150
|
|
393
|
+
const stabilityMs = options?.awaitWriteFinish ?? 50
|
|
394
|
+
|
|
395
|
+
const renderer = createRenderer(options)
|
|
396
|
+
const root = createRoot(renderer)
|
|
397
|
+
|
|
398
|
+
// Dev mode always enables remote control
|
|
399
|
+
const stopRemote = enableRemote(renderer)
|
|
400
|
+
|
|
401
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
|
402
|
+
let version = 0
|
|
403
|
+
let subscription: watcher.AsyncSubscription | null = null
|
|
404
|
+
|
|
405
|
+
// Import and render the module
|
|
406
|
+
const render = async () => {
|
|
407
|
+
const thisVersion = ++version
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
// Cache-bust by adding query string with version
|
|
411
|
+
const mod = await import(`${entryPath}?v=${thisVersion}`)
|
|
412
|
+
|
|
413
|
+
// Skip if a newer render was triggered
|
|
414
|
+
if (thisVersion !== version) return
|
|
415
|
+
|
|
416
|
+
const App = mod.default
|
|
417
|
+
|
|
418
|
+
if (!App) {
|
|
419
|
+
const err = new Error(`No default export found in ${entryPath}`)
|
|
420
|
+
options?.onError?.(err)
|
|
421
|
+
console.error("[devRender]", err.message)
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Render the component wrapped in DevWrapper for console panel
|
|
426
|
+
const appElement = typeof App === "function" ? React.createElement(App) : App
|
|
427
|
+
const wrapped = React.createElement(
|
|
428
|
+
DevWrapper,
|
|
429
|
+
{
|
|
430
|
+
showStats: options?.showStats,
|
|
431
|
+
statsSampleMs: options?.statsSampleMs,
|
|
432
|
+
statsTitle: options?.statsTitle,
|
|
433
|
+
mode: options?.mode,
|
|
434
|
+
},
|
|
435
|
+
appElement,
|
|
436
|
+
)
|
|
437
|
+
root.render(wrapped)
|
|
438
|
+
} catch (err) {
|
|
439
|
+
const error = err instanceof Error ? err : new Error(String(err))
|
|
440
|
+
options?.onError?.(error)
|
|
441
|
+
console.error("[devRender] Error loading module:", error.message)
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Initial render
|
|
446
|
+
await render()
|
|
447
|
+
|
|
448
|
+
// Watch for changes using @parcel/watcher
|
|
449
|
+
const watchDir = options?.watchDirs?.[0] ?? dirname(entryPath)
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
subscription = await watcher.subscribe(watchDir, async (err, events) => {
|
|
453
|
+
if (err) {
|
|
454
|
+
options?.onError?.(err)
|
|
455
|
+
console.error("[devRender] Watcher error:", err)
|
|
456
|
+
return
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Filter to relevant file extensions
|
|
460
|
+
const relevantEvents = events.filter((event) => watchExtensions.some((ext) => event.path.endsWith(ext)))
|
|
461
|
+
|
|
462
|
+
if (relevantEvents.length === 0) return
|
|
463
|
+
|
|
464
|
+
// Debounce rapid changes
|
|
465
|
+
if (debounceTimer) clearTimeout(debounceTimer)
|
|
466
|
+
debounceTimer = setTimeout(async () => {
|
|
467
|
+
// Wait for file writes to stabilize
|
|
468
|
+
const changedPath = relevantEvents[0].path
|
|
469
|
+
await awaitWriteFinish(changedPath, stabilityMs)
|
|
470
|
+
|
|
471
|
+
options?.onReload?.()
|
|
472
|
+
await render()
|
|
473
|
+
}, debounceMs)
|
|
474
|
+
})
|
|
475
|
+
} catch (err) {
|
|
476
|
+
console.error(`[devRender] Failed to watch ${watchDir}:`, err)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const stop = async () => {
|
|
480
|
+
if (debounceTimer) clearTimeout(debounceTimer)
|
|
481
|
+
if (subscription) {
|
|
482
|
+
await subscription.unsubscribe()
|
|
483
|
+
}
|
|
484
|
+
stopRemote()
|
|
485
|
+
renderer.stop()
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return { renderer, root, rerender: render, stop }
|
|
489
|
+
}
|