@three-flatland/devtools 0.1.0-alpha.3
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/LICENSE +21 -0
- package/README.md +82 -0
- package/dist/buffers-modal.cjs +491 -0
- package/dist/buffers-modal.cjs.map +1 -0
- package/dist/buffers-modal.d.cts +62 -0
- package/dist/buffers-modal.d.ts +62 -0
- package/dist/buffers-modal.js +467 -0
- package/dist/buffers-modal.js.map +1 -0
- package/dist/buffers-view.cjs +269 -0
- package/dist/buffers-view.cjs.map +1 -0
- package/dist/buffers-view.d.cts +39 -0
- package/dist/buffers-view.d.ts +39 -0
- package/dist/buffers-view.js +245 -0
- package/dist/buffers-view.js.map +1 -0
- package/dist/create-pane.cjs +203 -0
- package/dist/create-pane.cjs.map +1 -0
- package/dist/create-pane.d.cts +66 -0
- package/dist/create-pane.d.ts +66 -0
- package/dist/create-pane.js +178 -0
- package/dist/create-pane.js.map +1 -0
- package/dist/dashboard/app.tsx +42 -0
- package/dist/dashboard/client.ts +21 -0
- package/dist/dashboard/export.ts +84 -0
- package/dist/dashboard/hooks.ts +75 -0
- package/dist/dashboard/index.html +1149 -0
- package/dist/dashboard/index.tsx +18 -0
- package/dist/dashboard/panels/batches.tsx +282 -0
- package/dist/dashboard/panels/buffers.tsx +425 -0
- package/dist/dashboard/panels/env.tsx +85 -0
- package/dist/dashboard/panels/header-stats.tsx +30 -0
- package/dist/dashboard/panels/producer-select.tsx +81 -0
- package/dist/dashboard/panels/protocol-log.tsx +743 -0
- package/dist/dashboard/panels/registry.tsx +642 -0
- package/dist/dashboard/panels/stats.tsx +431 -0
- package/dist/dashboard/protocol-store.ts +312 -0
- package/dist/dashboard/tsconfig.json +19 -0
- package/dist/dashboard/vendor/hooks.module.js +1 -0
- package/dist/dashboard/vendor/jsx-runtime.js +22 -0
- package/dist/dashboard/vendor/preact.module.js +12 -0
- package/dist/devtools-client.cjs +870 -0
- package/dist/devtools-client.cjs.map +1 -0
- package/dist/devtools-client.d.cts +362 -0
- package/dist/devtools-client.d.ts +362 -0
- package/dist/devtools-client.js +852 -0
- package/dist/devtools-client.js.map +1 -0
- package/dist/index.cjs +42 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/perf-trace.cjs +103 -0
- package/dist/perf-trace.cjs.map +1 -0
- package/dist/perf-trace.d.cts +27 -0
- package/dist/perf-trace.d.ts +27 -0
- package/dist/perf-trace.js +79 -0
- package/dist/perf-trace.js.map +1 -0
- package/dist/plugins.cjs +44 -0
- package/dist/plugins.cjs.map +1 -0
- package/dist/plugins.d.cts +7 -0
- package/dist/plugins.d.ts +7 -0
- package/dist/plugins.js +9 -0
- package/dist/plugins.js.map +1 -0
- package/dist/provider-switcher.cjs +95 -0
- package/dist/provider-switcher.cjs.map +1 -0
- package/dist/provider-switcher.d.cts +18 -0
- package/dist/provider-switcher.d.ts +18 -0
- package/dist/provider-switcher.js +71 -0
- package/dist/provider-switcher.js.map +1 -0
- package/dist/react/devtools-provider.cjs +59 -0
- package/dist/react/devtools-provider.cjs.map +1 -0
- package/dist/react/devtools-provider.d.cts +39 -0
- package/dist/react/devtools-provider.d.ts +39 -0
- package/dist/react/devtools-provider.js +38 -0
- package/dist/react/devtools-provider.js.map +1 -0
- package/dist/react/use-fps-graph.cjs +49 -0
- package/dist/react/use-fps-graph.cjs.map +1 -0
- package/dist/react/use-fps-graph.d.cts +15 -0
- package/dist/react/use-fps-graph.d.ts +15 -0
- package/dist/react/use-fps-graph.js +25 -0
- package/dist/react/use-fps-graph.js.map +1 -0
- package/dist/react/use-pane-button.cjs +58 -0
- package/dist/react/use-pane-button.cjs.map +1 -0
- package/dist/react/use-pane-button.d.cts +10 -0
- package/dist/react/use-pane-button.d.ts +10 -0
- package/dist/react/use-pane-button.js +34 -0
- package/dist/react/use-pane-button.js.map +1 -0
- package/dist/react/use-pane-folder.cjs +48 -0
- package/dist/react/use-pane-folder.cjs.map +1 -0
- package/dist/react/use-pane-folder.d.cts +15 -0
- package/dist/react/use-pane-folder.d.ts +15 -0
- package/dist/react/use-pane-folder.js +24 -0
- package/dist/react/use-pane-folder.js.map +1 -0
- package/dist/react/use-pane-input.cjs +70 -0
- package/dist/react/use-pane-input.cjs.map +1 -0
- package/dist/react/use-pane-input.d.cts +44 -0
- package/dist/react/use-pane-input.d.ts +44 -0
- package/dist/react/use-pane-input.js +46 -0
- package/dist/react/use-pane-input.js.map +1 -0
- package/dist/react/use-pane-radio-grid.cjs +78 -0
- package/dist/react/use-pane-radio-grid.cjs.map +1 -0
- package/dist/react/use-pane-radio-grid.d.cts +35 -0
- package/dist/react/use-pane-radio-grid.d.ts +35 -0
- package/dist/react/use-pane-radio-grid.js +54 -0
- package/dist/react/use-pane-radio-grid.js.map +1 -0
- package/dist/react/use-pane.cjs +45 -0
- package/dist/react/use-pane.cjs.map +1 -0
- package/dist/react/use-pane.d.cts +40 -0
- package/dist/react/use-pane.d.ts +40 -0
- package/dist/react/use-pane.js +21 -0
- package/dist/react/use-pane.js.map +1 -0
- package/dist/react.cjs +50 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +12 -0
- package/dist/react.d.ts +12 -0
- package/dist/react.js +19 -0
- package/dist/react.js.map +1 -0
- package/dist/registry-view.cjs +371 -0
- package/dist/registry-view.cjs.map +1 -0
- package/dist/registry-view.d.cts +24 -0
- package/dist/registry-view.d.ts +24 -0
- package/dist/registry-view.js +347 -0
- package/dist/registry-view.js.map +1 -0
- package/dist/stats-graph.cjs +297 -0
- package/dist/stats-graph.cjs.map +1 -0
- package/dist/stats-graph.d.cts +40 -0
- package/dist/stats-graph.d.ts +40 -0
- package/dist/stats-graph.js +273 -0
- package/dist/stats-graph.js.map +1 -0
- package/dist/stats-row.cjs +153 -0
- package/dist/stats-row.cjs.map +1 -0
- package/dist/stats-row.d.cts +24 -0
- package/dist/stats-row.d.ts +24 -0
- package/dist/stats-row.js +129 -0
- package/dist/stats-row.js.map +1 -0
- package/dist/theme.cjs +260 -0
- package/dist/theme.cjs.map +1 -0
- package/dist/theme.d.cts +10 -0
- package/dist/theme.d.ts +10 -0
- package/dist/theme.js +235 -0
- package/dist/theme.js.map +1 -0
- package/dist/vite-plugin.cjs +113 -0
- package/dist/vite-plugin.cjs.map +1 -0
- package/dist/vite-plugin.d.cts +9 -0
- package/dist/vite-plugin.d.ts +9 -0
- package/dist/vite-plugin.js +88 -0
- package/dist/vite-plugin.js.map +1 -0
- package/package.json +124 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** @jsxImportSource preact */
|
|
2
|
+
/**
|
|
3
|
+
* Dashboard root layout. Producer switcher lives in the header to
|
|
4
|
+
* reclaim left-rail real estate — the rail is gone, the content area
|
|
5
|
+
* fills the full viewport.
|
|
6
|
+
*/
|
|
7
|
+
import { BatchesPanel } from './panels/batches.js'
|
|
8
|
+
import { BuffersPanel } from './panels/buffers.js'
|
|
9
|
+
import { EnvPopover } from './panels/env.js'
|
|
10
|
+
import { HeaderStats } from './panels/header-stats.js'
|
|
11
|
+
import { ProducerSelect } from './panels/producer-select.js'
|
|
12
|
+
import { ProtocolLog } from './panels/protocol-log.js'
|
|
13
|
+
import { RegistryPanel } from './panels/registry.js'
|
|
14
|
+
import { StatsStrip } from './panels/stats.js'
|
|
15
|
+
|
|
16
|
+
export function App() {
|
|
17
|
+
return (
|
|
18
|
+
<div class="dashboard-root">
|
|
19
|
+
<header class="dashboard-header">
|
|
20
|
+
<span class="brand">three-flatland devtools</span>
|
|
21
|
+
<ProducerSelect />
|
|
22
|
+
<EnvPopover />
|
|
23
|
+
<HeaderStats />
|
|
24
|
+
</header>
|
|
25
|
+
<main class="dashboard-main">
|
|
26
|
+
<section class="dashboard-content">
|
|
27
|
+
<StatsStrip />
|
|
28
|
+
<div class="dashboard-split">
|
|
29
|
+
<div class="dashboard-split-col">
|
|
30
|
+
<BuffersPanel />
|
|
31
|
+
<BatchesPanel />
|
|
32
|
+
<RegistryPanel />
|
|
33
|
+
</div>
|
|
34
|
+
<div class="dashboard-split-col">
|
|
35
|
+
<ProtocolLog />
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</section>
|
|
39
|
+
</main>
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Singleton `DevtoolsClient` for the dashboard page.
|
|
3
|
+
*
|
|
4
|
+
* The dashboard runs on the same origin as the app (via the microfrontend
|
|
5
|
+
* proxy) so a regular `new DevtoolsClient()` here reaches every producer
|
|
6
|
+
* the app creates via `BroadcastChannel`. One client covers the whole
|
|
7
|
+
* page — all panels share it and subscribe to the same state.
|
|
8
|
+
*/
|
|
9
|
+
import { DevtoolsClient } from '../devtools-client.js'
|
|
10
|
+
|
|
11
|
+
const ALL_FEATURES = ['stats', 'env', 'registry', 'buffers', 'batches'] as const
|
|
12
|
+
|
|
13
|
+
let instance: DevtoolsClient | null = null
|
|
14
|
+
|
|
15
|
+
export function getClient(): DevtoolsClient {
|
|
16
|
+
if (instance === null) {
|
|
17
|
+
instance = new DevtoolsClient({ features: [...ALL_FEATURES] })
|
|
18
|
+
instance.start()
|
|
19
|
+
}
|
|
20
|
+
return instance
|
|
21
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Export helpers — drop the session's protocol log to a file the user
|
|
3
|
+
* can attach to a bug report or inspect offline. Walks the IDB store
|
|
4
|
+
* directly so even entries evicted from the in-memory cache make it
|
|
5
|
+
* into the dump.
|
|
6
|
+
*
|
|
7
|
+
* Format: one JSON document with header + entries array. ArrayBuffer /
|
|
8
|
+
* TypedArray payloads are serialised as `{__buf: base64, byteLength}`
|
|
9
|
+
* so the dump survives a JSON round-trip without losing binary.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const DB_NAME = 'tf-devtools-protocol'
|
|
13
|
+
const STORE = 'messages'
|
|
14
|
+
|
|
15
|
+
interface RawEntry {
|
|
16
|
+
id: number
|
|
17
|
+
providerId: string
|
|
18
|
+
at: number
|
|
19
|
+
direction: 'in' | 'out'
|
|
20
|
+
type: string
|
|
21
|
+
tag?: string
|
|
22
|
+
frame?: number
|
|
23
|
+
bytes: number
|
|
24
|
+
msg: unknown
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function readAll(): Promise<RawEntry[]> {
|
|
28
|
+
const db = await new Promise<IDBDatabase>((resolve, reject) => {
|
|
29
|
+
const req = indexedDB.open(DB_NAME)
|
|
30
|
+
req.onsuccess = () => resolve(req.result)
|
|
31
|
+
req.onerror = () => reject(req.error ?? new Error('IndexedDB open failed'))
|
|
32
|
+
})
|
|
33
|
+
const entries = await new Promise<RawEntry[]>((resolve, reject) => {
|
|
34
|
+
const tx = db.transaction(STORE, 'readonly')
|
|
35
|
+
const req = tx.objectStore(STORE).getAll()
|
|
36
|
+
req.onsuccess = () => resolve(req.result as RawEntry[])
|
|
37
|
+
req.onerror = () => reject(req.error ?? new Error('IndexedDB read failed'))
|
|
38
|
+
})
|
|
39
|
+
db.close()
|
|
40
|
+
return entries
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function replacer(_k: string, v: unknown): unknown {
|
|
44
|
+
if (v instanceof ArrayBuffer) return tagBuffer(new Uint8Array(v), v.byteLength)
|
|
45
|
+
if (ArrayBuffer.isView(v)) {
|
|
46
|
+
const u = new Uint8Array(v.buffer, v.byteOffset, v.byteLength)
|
|
47
|
+
return { ...tagBuffer(u, v.byteLength), ctor: v.constructor.name }
|
|
48
|
+
}
|
|
49
|
+
return v
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function tagBuffer(u: Uint8Array, byteLength: number): { __buf: string; byteLength: number } {
|
|
53
|
+
let binary = ''
|
|
54
|
+
for (let i = 0; i < u.length; i++) binary += String.fromCharCode(u[i]!)
|
|
55
|
+
return { __buf: btoa(binary), byteLength }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Read the full session log and prompt the user to save a JSON dump.
|
|
60
|
+
* Optional `providerId` narrows to a single producer.
|
|
61
|
+
*/
|
|
62
|
+
export async function exportSession(providerId: string | null = null): Promise<void> {
|
|
63
|
+
const entries = await readAll()
|
|
64
|
+
const filtered = providerId === null ? entries : entries.filter((e) => e.providerId === providerId)
|
|
65
|
+
const payload = {
|
|
66
|
+
exportedAt: new Date().toISOString(),
|
|
67
|
+
providerId,
|
|
68
|
+
count: filtered.length,
|
|
69
|
+
entries: filtered,
|
|
70
|
+
}
|
|
71
|
+
const json = JSON.stringify(payload, replacer, 2)
|
|
72
|
+
const blob = new Blob([json], { type: 'application/json' })
|
|
73
|
+
const url = URL.createObjectURL(blob)
|
|
74
|
+
const a = document.createElement('a')
|
|
75
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-')
|
|
76
|
+
a.href = url
|
|
77
|
+
a.download = providerId !== null
|
|
78
|
+
? `tf-devtools-${providerId}-${stamp}.json`
|
|
79
|
+
: `tf-devtools-session-${stamp}.json`
|
|
80
|
+
document.body.appendChild(a)
|
|
81
|
+
a.click()
|
|
82
|
+
document.body.removeChild(a)
|
|
83
|
+
URL.revokeObjectURL(url)
|
|
84
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preact hooks over the dashboard's singleton `DevtoolsClient`.
|
|
3
|
+
*
|
|
4
|
+
* State updates fan out through a shared rAF coalescer so a single
|
|
5
|
+
* `provider → client.addListener` event notifies every subscribed hook
|
|
6
|
+
* on the same frame. Without this each `use*` hook would schedule its
|
|
7
|
+
* own rAF, multiplying the update cost by the number of panels.
|
|
8
|
+
*/
|
|
9
|
+
import { useEffect, useState } from 'preact/hooks'
|
|
10
|
+
import type { DevtoolsState } from '../devtools-client.js'
|
|
11
|
+
import { getClient } from './client.js'
|
|
12
|
+
import { getProtocolStore } from './protocol-store.js'
|
|
13
|
+
|
|
14
|
+
type Subscriber = () => void
|
|
15
|
+
|
|
16
|
+
// One shared rAF per update, ticked by any underlying source that
|
|
17
|
+
// subscribes. Listeners register with `subscribeFrame`; all fire
|
|
18
|
+
// together when the rAF lands.
|
|
19
|
+
const _frameListeners = new Set<Subscriber>()
|
|
20
|
+
let _frameScheduled = false
|
|
21
|
+
|
|
22
|
+
function scheduleFrame(): void {
|
|
23
|
+
if (_frameScheduled) return
|
|
24
|
+
_frameScheduled = true
|
|
25
|
+
requestAnimationFrame(() => {
|
|
26
|
+
_frameScheduled = false
|
|
27
|
+
for (const cb of _frameListeners) {
|
|
28
|
+
try { cb() } catch { /* listener errors shouldn't break the bus */ }
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function subscribeFrame(cb: Subscriber): () => void {
|
|
34
|
+
_frameListeners.add(cb)
|
|
35
|
+
return () => { _frameListeners.delete(cb) }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Wire the underlying data sources to the shared rAF once. Any event on
|
|
39
|
+
// the client or store ticks the shared loop.
|
|
40
|
+
let _sourcesWired = false
|
|
41
|
+
function ensureSourcesWired(): void {
|
|
42
|
+
if (_sourcesWired) return
|
|
43
|
+
_sourcesWired = true
|
|
44
|
+
getClient().addListener(scheduleFrame)
|
|
45
|
+
getProtocolStore().addListener(scheduleFrame)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Subscribe to the client's state. Returns the live state object; the
|
|
50
|
+
* client mutates it in place, so we bump a tick to force a re-render.
|
|
51
|
+
* All subscribers share one rAF per frame.
|
|
52
|
+
*/
|
|
53
|
+
export function useDevtoolsState(): DevtoolsState {
|
|
54
|
+
ensureSourcesWired()
|
|
55
|
+
const client = getClient()
|
|
56
|
+
const [, setTick] = useState(0)
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
return subscribeFrame(() => setTick((n) => (n + 1) & 0xffff))
|
|
59
|
+
}, [])
|
|
60
|
+
return client.state
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Subscribe to the shared frame tick without reading any specific
|
|
65
|
+
* source. Use this for components (protocol log, header stats, stats
|
|
66
|
+
* strip) that derive their render from a source other than the client
|
|
67
|
+
* state but still want to coalesce onto the same rAF.
|
|
68
|
+
*/
|
|
69
|
+
export function useFrameTick(): void {
|
|
70
|
+
ensureSourcesWired()
|
|
71
|
+
const [, setTick] = useState(0)
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
return subscribeFrame(() => setTick((n) => (n + 1) & 0xffff))
|
|
74
|
+
}, [])
|
|
75
|
+
}
|