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