@effect-tui/react 0.15.2 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/README.md +2 -2
  2. package/dist/src/components/ListView.d.ts +4 -4
  3. package/dist/src/components/ListView.d.ts.map +1 -1
  4. package/dist/src/components/ListView.js +16 -17
  5. package/dist/src/components/ListView.js.map +1 -1
  6. package/dist/src/console/ConsolePopover.d.ts +7 -1
  7. package/dist/src/console/ConsolePopover.d.ts.map +1 -1
  8. package/dist/src/console/ConsolePopover.js +55 -74
  9. package/dist/src/console/ConsolePopover.js.map +1 -1
  10. package/dist/src/debug/DebugOverlay.d.ts.map +1 -1
  11. package/dist/src/debug/DebugOverlay.js +3 -57
  12. package/dist/src/debug/DebugOverlay.js.map +1 -1
  13. package/dist/src/debug/DiagnosticsPanel.js +1 -1
  14. package/dist/src/debug/DiagnosticsPanel.js.map +1 -1
  15. package/dist/src/dev.d.ts +5 -117
  16. package/dist/src/dev.d.ts.map +1 -1
  17. package/dist/src/dev.js +3 -333
  18. package/dist/src/dev.js.map +1 -1
  19. package/dist/src/hooks/use-scroll.d.ts +31 -35
  20. package/dist/src/hooks/use-scroll.d.ts.map +1 -1
  21. package/dist/src/hooks/use-scroll.js +51 -90
  22. package/dist/src/hooks/use-scroll.js.map +1 -1
  23. package/dist/src/hosts/canvas.d.ts +2 -2
  24. package/dist/src/hosts/canvas.d.ts.map +1 -1
  25. package/dist/src/hosts/canvas.js +8 -10
  26. package/dist/src/hosts/canvas.js.map +1 -1
  27. package/dist/src/hosts/codeblock.d.ts +2 -2
  28. package/dist/src/hosts/codeblock.js +2 -2
  29. package/dist/src/hosts/flex-container.d.ts +1 -1
  30. package/dist/src/hosts/flex-container.d.ts.map +1 -1
  31. package/dist/src/hosts/flex-container.js +3 -3
  32. package/dist/src/hosts/flex-container.js.map +1 -1
  33. package/dist/src/hosts/index.d.ts +2 -1
  34. package/dist/src/hosts/index.d.ts.map +1 -1
  35. package/dist/src/hosts/index.js +2 -1
  36. package/dist/src/hosts/index.js.map +1 -1
  37. package/dist/src/hosts/layout-helpers.d.ts +10 -0
  38. package/dist/src/hosts/layout-helpers.d.ts.map +1 -0
  39. package/dist/src/hosts/layout-helpers.js +10 -0
  40. package/dist/src/hosts/layout-helpers.js.map +1 -0
  41. package/dist/src/hosts/leaf.d.ts +14 -0
  42. package/dist/src/hosts/leaf.d.ts.map +1 -0
  43. package/dist/src/hosts/leaf.js +31 -0
  44. package/dist/src/hosts/leaf.js.map +1 -0
  45. package/dist/src/hosts/overlay.d.ts.map +1 -1
  46. package/dist/src/hosts/overlay.js +4 -7
  47. package/dist/src/hosts/overlay.js.map +1 -1
  48. package/dist/src/hosts/scroll.d.ts +47 -24
  49. package/dist/src/hosts/scroll.d.ts.map +1 -1
  50. package/dist/src/hosts/scroll.js +68 -51
  51. package/dist/src/hosts/scroll.js.map +1 -1
  52. package/dist/src/hosts/spacer.d.ts +2 -2
  53. package/dist/src/hosts/spacer.js +2 -2
  54. package/dist/src/hosts/text.d.ts +2 -3
  55. package/dist/src/hosts/text.d.ts.map +1 -1
  56. package/dist/src/hosts/text.js +5 -61
  57. package/dist/src/hosts/text.js.map +1 -1
  58. package/dist/src/hosts/vstack.js +1 -1
  59. package/dist/src/hosts/vstack.js.map +1 -1
  60. package/dist/src/hosts/zstack.d.ts +1 -1
  61. package/dist/src/hosts/zstack.d.ts.map +1 -1
  62. package/dist/src/hosts/zstack.js +6 -6
  63. package/dist/src/hosts/zstack.js.map +1 -1
  64. package/dist/src/index.d.ts +1 -1
  65. package/dist/src/index.d.ts.map +1 -1
  66. package/dist/src/internal/dev/hmr.d.ts +20 -0
  67. package/dist/src/internal/dev/hmr.d.ts.map +1 -0
  68. package/dist/src/internal/dev/hmr.js +93 -0
  69. package/dist/src/internal/dev/hmr.js.map +1 -0
  70. package/dist/src/internal/dev/runtime.d.ts +24 -0
  71. package/dist/src/internal/dev/runtime.d.ts.map +1 -0
  72. package/dist/src/internal/dev/runtime.js +135 -0
  73. package/dist/src/internal/dev/runtime.js.map +1 -0
  74. package/dist/src/internal/dev/ui.d.ts +13 -0
  75. package/dist/src/internal/dev/ui.d.ts.map +1 -0
  76. package/dist/src/internal/dev/ui.js +51 -0
  77. package/dist/src/internal/dev/ui.js.map +1 -0
  78. package/dist/src/internal/renderer/context.d.ts +9 -0
  79. package/dist/src/internal/renderer/context.d.ts.map +1 -0
  80. package/dist/src/internal/renderer/context.js +22 -0
  81. package/dist/src/internal/renderer/context.js.map +1 -0
  82. package/dist/src/internal/renderer/core/FrameBuilder.d.ts +18 -0
  83. package/dist/src/internal/renderer/core/FrameBuilder.d.ts.map +1 -0
  84. package/dist/src/internal/renderer/core/FrameBuilder.js +40 -0
  85. package/dist/src/internal/renderer/core/FrameBuilder.js.map +1 -0
  86. package/dist/src/internal/renderer/core/RendererState.d.ts +41 -0
  87. package/dist/src/internal/renderer/core/RendererState.d.ts.map +1 -0
  88. package/dist/src/internal/renderer/core/RendererState.js +70 -0
  89. package/dist/src/internal/renderer/core/RendererState.js.map +1 -0
  90. package/dist/src/internal/renderer/core/index.d.ts +3 -0
  91. package/dist/src/internal/renderer/core/index.d.ts.map +1 -0
  92. package/dist/src/internal/renderer/core/index.js +3 -0
  93. package/dist/src/internal/renderer/core/index.js.map +1 -0
  94. package/dist/src/internal/renderer/index.d.ts +40 -0
  95. package/dist/src/internal/renderer/index.d.ts.map +1 -0
  96. package/dist/src/internal/renderer/index.js +518 -0
  97. package/dist/src/internal/renderer/index.js.map +1 -0
  98. package/dist/src/internal/renderer/input/InputProcessor.d.ts +30 -0
  99. package/dist/src/internal/renderer/input/InputProcessor.d.ts.map +1 -0
  100. package/dist/src/internal/renderer/input/InputProcessor.js +122 -0
  101. package/dist/src/internal/renderer/input/InputProcessor.js.map +1 -0
  102. package/dist/src/internal/renderer/input/index.d.ts +2 -0
  103. package/dist/src/internal/renderer/input/index.d.ts.map +1 -0
  104. package/dist/src/internal/renderer/input/index.js +2 -0
  105. package/dist/src/internal/renderer/input/index.js.map +1 -0
  106. package/dist/src/internal/renderer/lifecycle/EventBus.d.ts +42 -0
  107. package/dist/src/internal/renderer/lifecycle/EventBus.d.ts.map +1 -0
  108. package/dist/src/internal/renderer/lifecycle/EventBus.js +97 -0
  109. package/dist/src/internal/renderer/lifecycle/EventBus.js.map +1 -0
  110. package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.d.ts +13 -0
  111. package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.d.ts.map +1 -0
  112. package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.js +111 -0
  113. package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.js.map +1 -0
  114. package/dist/src/internal/renderer/lifecycle/RenderCache.d.ts +3 -0
  115. package/dist/src/internal/renderer/lifecycle/RenderCache.d.ts.map +1 -0
  116. package/dist/src/internal/renderer/lifecycle/RenderCache.js +9 -0
  117. package/dist/src/internal/renderer/lifecycle/RenderCache.js.map +1 -0
  118. package/dist/src/internal/renderer/lifecycle/index.d.ts +4 -0
  119. package/dist/src/internal/renderer/lifecycle/index.d.ts.map +1 -0
  120. package/dist/src/internal/renderer/lifecycle/index.js +4 -0
  121. package/dist/src/internal/renderer/lifecycle/index.js.map +1 -0
  122. package/dist/src/internal/renderer/modes/FullscreenRenderer.d.ts +12 -0
  123. package/dist/src/internal/renderer/modes/FullscreenRenderer.d.ts.map +1 -0
  124. package/dist/src/internal/renderer/modes/FullscreenRenderer.js +54 -0
  125. package/dist/src/internal/renderer/modes/FullscreenRenderer.js.map +1 -0
  126. package/dist/src/internal/renderer/modes/InlineRenderer.d.ts +25 -0
  127. package/dist/src/internal/renderer/modes/InlineRenderer.d.ts.map +1 -0
  128. package/dist/src/internal/renderer/modes/InlineRenderer.js +166 -0
  129. package/dist/src/internal/renderer/modes/InlineRenderer.js.map +1 -0
  130. package/dist/src/internal/renderer/modes/RendererMode.d.ts +42 -0
  131. package/dist/src/internal/renderer/modes/RendererMode.d.ts.map +1 -0
  132. package/dist/src/internal/renderer/modes/RendererMode.js +2 -0
  133. package/dist/src/internal/renderer/modes/RendererMode.js.map +1 -0
  134. package/dist/src/internal/renderer/modes/StaticContentRenderer.d.ts +25 -0
  135. package/dist/src/internal/renderer/modes/StaticContentRenderer.d.ts.map +1 -0
  136. package/dist/src/internal/renderer/modes/StaticContentRenderer.js +49 -0
  137. package/dist/src/internal/renderer/modes/StaticContentRenderer.js.map +1 -0
  138. package/dist/src/internal/renderer/modes/index.d.ts +5 -0
  139. package/dist/src/internal/renderer/modes/index.d.ts.map +1 -0
  140. package/dist/src/internal/renderer/modes/index.js +4 -0
  141. package/dist/src/internal/renderer/modes/index.js.map +1 -0
  142. package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.d.ts +13 -0
  143. package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.d.ts.map +1 -0
  144. package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.js +75 -0
  145. package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.js.map +1 -0
  146. package/dist/src/internal/renderer/terminal/TerminalSetup.d.ts +29 -0
  147. package/dist/src/internal/renderer/terminal/TerminalSetup.d.ts.map +1 -0
  148. package/dist/src/internal/renderer/terminal/TerminalSetup.js +82 -0
  149. package/dist/src/internal/renderer/terminal/TerminalSetup.js.map +1 -0
  150. package/dist/src/internal/renderer/terminal/index.d.ts +3 -0
  151. package/dist/src/internal/renderer/terminal/index.d.ts.map +1 -0
  152. package/dist/src/internal/renderer/terminal/index.js +3 -0
  153. package/dist/src/internal/renderer/terminal/index.js.map +1 -0
  154. package/dist/src/internal/renderer/types.d.ts +118 -0
  155. package/dist/src/internal/renderer/types.d.ts.map +1 -0
  156. package/dist/src/internal/renderer/types.js +2 -0
  157. package/dist/src/internal/renderer/types.js.map +1 -0
  158. package/dist/src/renderer-context.d.ts +1 -8
  159. package/dist/src/renderer-context.d.ts.map +1 -1
  160. package/dist/src/renderer-context.js +1 -21
  161. package/dist/src/renderer-context.js.map +1 -1
  162. package/dist/src/renderer-types.d.ts +1 -115
  163. package/dist/src/renderer-types.d.ts.map +1 -1
  164. package/dist/src/renderer.d.ts +1 -31
  165. package/dist/src/renderer.d.ts.map +1 -1
  166. package/dist/src/renderer.js +1 -495
  167. package/dist/src/renderer.js.map +1 -1
  168. package/dist/src/test/render-tui.d.ts +3 -3
  169. package/dist/src/test/render-tui.d.ts.map +1 -1
  170. package/dist/src/test/render-tui.js +16 -9
  171. package/dist/src/test/render-tui.js.map +1 -1
  172. package/dist/src/utils/alignment.d.ts +1 -1
  173. package/dist/src/utils/alignment.d.ts.map +1 -1
  174. package/dist/src/utils/alignment.js +0 -2
  175. package/dist/src/utils/alignment.js.map +1 -1
  176. package/dist/src/utils/console-helpers.d.ts +19 -0
  177. package/dist/src/utils/console-helpers.d.ts.map +1 -0
  178. package/dist/src/utils/console-helpers.js +61 -0
  179. package/dist/src/utils/console-helpers.js.map +1 -0
  180. package/dist/src/utils/index.d.ts +1 -1
  181. package/dist/src/utils/index.d.ts.map +1 -1
  182. package/dist/src/utils/index.js +1 -1
  183. package/dist/src/utils/index.js.map +1 -1
  184. package/dist/src/utils/styles.d.ts +8 -1
  185. package/dist/src/utils/styles.d.ts.map +1 -1
  186. package/dist/src/utils/styles.js +10 -8
  187. package/dist/src/utils/styles.js.map +1 -1
  188. package/dist/src/utils/text-wrap.d.ts +5 -0
  189. package/dist/src/utils/text-wrap.d.ts.map +1 -1
  190. package/dist/src/utils/text-wrap.js +110 -48
  191. package/dist/src/utils/text-wrap.js.map +1 -1
  192. package/dist/src/visualize/index.js +1 -1
  193. package/dist/src/visualize/index.js.map +1 -1
  194. package/dist/tsconfig.tsbuildinfo +1 -1
  195. package/package.json +2 -2
  196. package/src/components/ListView.tsx +21 -23
  197. package/src/console/ConsolePopover.tsx +124 -107
  198. package/src/debug/DebugOverlay.ts +15 -74
  199. package/src/debug/DiagnosticsPanel.tsx +1 -1
  200. package/src/dev.tsx +5 -458
  201. package/src/hooks/use-scroll.ts +85 -145
  202. package/src/hosts/canvas.ts +8 -11
  203. package/src/hosts/codeblock.ts +2 -2
  204. package/src/hosts/flex-container.ts +4 -4
  205. package/src/hosts/index.ts +10 -1
  206. package/src/hosts/layout-helpers.ts +20 -0
  207. package/src/hosts/leaf.ts +36 -0
  208. package/src/hosts/overlay.ts +11 -9
  209. package/src/hosts/scroll.ts +94 -69
  210. package/src/hosts/spacer.ts +2 -2
  211. package/src/hosts/text.ts +5 -58
  212. package/src/hosts/vstack.ts +1 -1
  213. package/src/hosts/zstack.ts +7 -7
  214. package/src/index.ts +1 -1
  215. package/src/internal/dev/hmr.ts +101 -0
  216. package/src/internal/dev/runtime.ts +170 -0
  217. package/src/internal/dev/ui.tsx +87 -0
  218. package/src/internal/renderer/context.ts +27 -0
  219. package/src/{renderer → internal/renderer}/core/FrameBuilder.ts +2 -2
  220. package/src/internal/renderer/index.ts +656 -0
  221. package/src/{renderer → internal/renderer}/input/InputProcessor.ts +10 -1
  222. package/src/{renderer → internal/renderer}/lifecycle/EventBus.ts +9 -1
  223. package/src/internal/renderer/lifecycle/ProcessLifecycle.ts +125 -0
  224. package/src/internal/renderer/lifecycle/index.ts +3 -0
  225. package/src/{renderer → internal/renderer}/modes/InlineRenderer.ts +5 -2
  226. package/src/{renderer → internal/renderer}/modes/RendererMode.ts +1 -1
  227. package/src/{renderer → internal/renderer}/modes/StaticContentRenderer.ts +5 -2
  228. package/src/internal/renderer/terminal/KeyboardCapabilityProbe.ts +91 -0
  229. package/src/{renderer/lifecycle → internal/renderer/terminal}/TerminalSetup.ts +4 -22
  230. package/src/internal/renderer/terminal/index.ts +2 -0
  231. package/src/internal/renderer/types.ts +125 -0
  232. package/src/renderer-context.ts +1 -27
  233. package/src/renderer-types.ts +10 -123
  234. package/src/renderer.ts +1 -619
  235. package/src/test/render-tui.ts +16 -10
  236. package/src/utils/alignment.ts +1 -3
  237. package/src/utils/console-helpers.ts +86 -0
  238. package/src/utils/index.ts +1 -1
  239. package/src/utils/styles.ts +16 -4
  240. package/src/utils/text-wrap.ts +139 -48
  241. package/src/visualize/index.tsx +1 -1
  242. package/src/renderer/lifecycle/ResizeManager.ts +0 -65
  243. package/src/renderer/lifecycle/index.ts +0 -4
  244. /package/src/{renderer → internal/renderer}/core/RendererState.ts +0 -0
  245. /package/src/{renderer → internal/renderer}/core/index.ts +0 -0
  246. /package/src/{renderer → internal/renderer}/input/index.ts +0 -0
  247. /package/src/{renderer → internal/renderer}/lifecycle/RenderCache.ts +0 -0
  248. /package/src/{renderer → internal/renderer}/modes/FullscreenRenderer.ts +0 -0
  249. /package/src/{renderer → internal/renderer}/modes/index.ts +0 -0
@@ -0,0 +1,170 @@
1
+ import { stat } from "node:fs/promises"
2
+ import { dirname } from "node:path"
3
+ import * as watcher from "@parcel/watcher"
4
+ import React from "react"
5
+ import { enableRemote } from "../../remote/index.js"
6
+ import { DevWrapper } from "./ui.js"
7
+ import type { RendererOptions, TuiRenderer } from "../renderer/types.js"
8
+
9
+ export interface DevOptions {
10
+ /** Called before each hot reload */
11
+ onReload?: () => void
12
+ /** Called on errors */
13
+ onError?: (error: Error) => void
14
+ }
15
+
16
+ export interface DevRuntime {
17
+ /** Promise that resolves after the first render + watcher setup. */
18
+ ready: Promise<void>
19
+ /** Manually trigger a reload. */
20
+ reload: () => Promise<void>
21
+ /** Stop watching and cleanup. */
22
+ stop: () => Promise<void>
23
+ }
24
+
25
+ type DevRoot = {
26
+ render(element: React.ReactNode, sync?: boolean): void
27
+ }
28
+
29
+ /**
30
+ * Clear require.cache for project files to ensure fresh imports.
31
+ * Bun uses require.cache internally even for ESM modules.
32
+ * This is necessary because query-string cache-busting only affects
33
+ * the entry point, not transitive dependencies.
34
+ */
35
+ function clearProjectCache(projectRoot: string): void {
36
+ for (const file of Object.keys(require.cache)) {
37
+ if (file.startsWith(projectRoot) && !file.includes("node_modules")) {
38
+ delete require.cache[file]
39
+ }
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Wait for a file to stabilize (no size changes) before proceeding.
45
+ * This avoids importing partially-written files.
46
+ */
47
+ async function awaitWriteFinish(path: string, stabilityMs: number): Promise<void> {
48
+ let lastSize = -1
49
+ let stableCount = 0
50
+ const checkInterval = 10
51
+
52
+ while (stableCount < stabilityMs / checkInterval) {
53
+ try {
54
+ const stats = await stat(path)
55
+ if (stats.size === lastSize) {
56
+ stableCount++
57
+ } else {
58
+ lastSize = stats.size
59
+ stableCount = 0
60
+ }
61
+ } catch {
62
+ // File might be temporarily unavailable during write
63
+ stableCount = 0
64
+ }
65
+ await new Promise((r) => setTimeout(r, checkInterval))
66
+ }
67
+ }
68
+
69
+ export function startDevRuntime(
70
+ entryPath: string,
71
+ renderer: TuiRenderer,
72
+ root: DevRoot,
73
+ options?: DevOptions & { mode?: RendererOptions["mode"] },
74
+ ): DevRuntime {
75
+ const watchExtensions = [".ts", ".tsx"]
76
+ const debounceMs = 150
77
+ const stabilityMs = 50
78
+
79
+ // Dev mode always enables remote control with entry path for identification
80
+ const stopRemote = enableRemote(renderer, { entryPath })
81
+
82
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null
83
+ let version = 0
84
+ let subscription: watcher.AsyncSubscription | null = null
85
+
86
+ // Determine project root for cache clearing
87
+ const projectRoot = dirname(entryPath)
88
+
89
+ const reload = async () => {
90
+ const thisVersion = ++version
91
+
92
+ try {
93
+ // Clear module cache for project files before re-importing
94
+ clearProjectCache(projectRoot)
95
+
96
+ // Cache-bust by adding query string with version
97
+ const mod = await import(`${entryPath}?v=${thisVersion}`)
98
+
99
+ // Skip if a newer render was triggered
100
+ if (thisVersion !== version) return
101
+
102
+ const App = mod.default
103
+
104
+ if (!App) {
105
+ const err = new Error(`No default export found in ${entryPath}`)
106
+ options?.onError?.(err)
107
+ console.error("[effect-tui] Dev render failed:", err.message)
108
+ return
109
+ }
110
+
111
+ // Render the component wrapped in DevWrapper for console + stats
112
+ const appElement = typeof App === "function" ? React.createElement(App) : App
113
+ const wrapped = React.createElement(
114
+ DevWrapper,
115
+ {
116
+ mode: options?.mode,
117
+ },
118
+ appElement,
119
+ )
120
+ root.render(wrapped)
121
+ } catch (err) {
122
+ const error = err instanceof Error ? err : new Error(String(err))
123
+ options?.onError?.(error)
124
+ console.error("[effect-tui] Dev render error:", error.message)
125
+ }
126
+ }
127
+
128
+ const ready = (async () => {
129
+ await reload()
130
+
131
+ try {
132
+ subscription = await watcher.subscribe(projectRoot, async (err, events) => {
133
+ if (err) {
134
+ options?.onError?.(err)
135
+ console.error("[effect-tui] Dev watcher error:", err)
136
+ return
137
+ }
138
+
139
+ // Filter to relevant file extensions
140
+ const relevantEvents = events.filter((event) => watchExtensions.some((ext) => event.path.endsWith(ext)))
141
+
142
+ if (relevantEvents.length === 0) return
143
+
144
+ // Debounce rapid changes
145
+ if (debounceTimer) clearTimeout(debounceTimer)
146
+ debounceTimer = setTimeout(async () => {
147
+ // Wait for file writes to stabilize
148
+ const changedPath = relevantEvents[0].path
149
+ await awaitWriteFinish(changedPath, stabilityMs)
150
+
151
+ options?.onReload?.()
152
+ await reload()
153
+ }, debounceMs)
154
+ })
155
+ } catch (err) {
156
+ console.error(`[effect-tui] Failed to watch ${projectRoot}:`, err)
157
+ }
158
+ })()
159
+
160
+ const stop = async () => {
161
+ if (debounceTimer) clearTimeout(debounceTimer)
162
+ if (subscription) {
163
+ await subscription.unsubscribe()
164
+ }
165
+ stopRemote()
166
+ renderer.stop()
167
+ }
168
+
169
+ return { ready, reload, stop }
170
+ }
@@ -0,0 +1,87 @@
1
+ import React from "react"
2
+ import { Overlay } from "../../components/Overlay.js"
3
+ import { ConsolePopover } from "../../console/ConsolePopover.js"
4
+ import { copyToClipboard, copyToClipboardSync } from "../../console/clipboard.js"
5
+ import { useConsole } from "../../console/useConsole.js"
6
+ import { ToastContainer, ToastProvider, useToast } from "../../dev/Toast.js"
7
+ import { useKeyboard } from "../../hooks/use-keyboard.js"
8
+ import { useRenderer } from "../renderer/context.js"
9
+
10
+ /**
11
+ * Internal component that handles screenshot with toast notification
12
+ */
13
+ function ScreenshotHandler() {
14
+ const renderer = useRenderer()
15
+ const { show } = useToast()
16
+
17
+ useKeyboard((key) => {
18
+ // ~ (tilde) - screenshot
19
+ if (key.name === "char" && key.text === "~") {
20
+ const ansiOutput = renderer.getScreenshot()
21
+ if (!ansiOutput) return
22
+
23
+ // Copy actual content to clipboard
24
+ const copied = copyToClipboardSync(ansiOutput)
25
+ if (!copied) {
26
+ copyToClipboard(ansiOutput)
27
+ }
28
+
29
+ // Also save to file as backup
30
+ const tmpPath = `/tmp/tui-screenshot-${Date.now()}.txt`
31
+ Bun.write(tmpPath, ansiOutput)
32
+
33
+ show("Screenshot copied!", "screenshot", 2500)
34
+ key.preventDefault?.()
35
+ }
36
+ })
37
+
38
+ return null
39
+ }
40
+
41
+ export interface DevWrapperProps {
42
+ children?: React.ReactNode
43
+ mode?: "fullscreen" | "inline"
44
+ }
45
+
46
+ /**
47
+ * Wrapper component that provides dev mode features:
48
+ * - Debug console panel (` backtick to toggle)
49
+ * - Screenshot support (~ tilde) with toast notification
50
+ * - Auto-show console on errors
51
+ */
52
+ export function DevWrapper({ children, mode }: DevWrapperProps) {
53
+ const { visible } = useConsole({ autoShowOnError: true, initiallyVisible: false })
54
+
55
+ // Inline mode: content flows naturally in vstack
56
+ if (mode === "inline") {
57
+ return (
58
+ <ToastProvider>
59
+ <vstack>
60
+ <ScreenshotHandler />
61
+ <ToastContainer />
62
+ {children}
63
+ {visible && <ConsolePopover fixedHeight={5} mode="inline" showStatsTab />}
64
+ </vstack>
65
+ </ToastProvider>
66
+ )
67
+ }
68
+
69
+ // Fullscreen mode: Overlay with toast/console positioned by alignment
70
+ return (
71
+ <ToastProvider>
72
+ <Overlay>
73
+ <ScreenshotHandler />
74
+ {children}
75
+
76
+ {visible && (
77
+ <Overlay.Item alignment={{ v: "bottom" }}>
78
+ <ConsolePopover fixedHeight={12} showStatsTab />
79
+ </Overlay.Item>
80
+ )}
81
+ <Overlay.Item alignment={{ v: "top" }}>
82
+ <ToastContainer />
83
+ </Overlay.Item>
84
+ </Overlay>
85
+ </ToastProvider>
86
+ )
87
+ }
@@ -0,0 +1,27 @@
1
+ import { createContext, useContext, useEffect, useState } from "react"
2
+ import type { TuiRenderer } from "./types.js"
3
+
4
+ // Context for accessing renderer in components
5
+ export const RendererContext = createContext<TuiRenderer | null>(null)
6
+
7
+ export function useRenderer(): TuiRenderer {
8
+ const renderer = useContext(RendererContext)
9
+ if (!renderer) {
10
+ throw new Error("useRenderer must be used within a TUI renderer")
11
+ }
12
+ return renderer
13
+ }
14
+
15
+ /** Hook that returns terminal size and re-renders on resize */
16
+ export function useTerminalSize(): { width: number; height: number } {
17
+ const renderer = useRenderer()
18
+ const [size, setSize] = useState({ width: renderer.width, height: renderer.height })
19
+
20
+ useEffect(() => {
21
+ return renderer.onResize((width, height) => {
22
+ setSize({ width, height })
23
+ })
24
+ }, [renderer])
25
+
26
+ return size
27
+ }
@@ -1,7 +1,7 @@
1
1
  import { performance } from "node:perf_hooks"
2
2
  import type { CellBuffer, Palette } from "@effect-tui/core"
3
- import * as Prof from "../../profiler.js"
4
- import type { HostInstance } from "../../reconciler/types.js"
3
+ import * as Prof from "../../../profiler.js"
4
+ import type { HostInstance } from "../../../reconciler/types.js"
5
5
 
6
6
  export interface FrameTimings {
7
7
  clear: number