@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.
Files changed (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -0
  3. package/dist/buffers-modal.cjs +491 -0
  4. package/dist/buffers-modal.cjs.map +1 -0
  5. package/dist/buffers-modal.d.cts +62 -0
  6. package/dist/buffers-modal.d.ts +62 -0
  7. package/dist/buffers-modal.js +467 -0
  8. package/dist/buffers-modal.js.map +1 -0
  9. package/dist/buffers-view.cjs +269 -0
  10. package/dist/buffers-view.cjs.map +1 -0
  11. package/dist/buffers-view.d.cts +39 -0
  12. package/dist/buffers-view.d.ts +39 -0
  13. package/dist/buffers-view.js +245 -0
  14. package/dist/buffers-view.js.map +1 -0
  15. package/dist/create-pane.cjs +203 -0
  16. package/dist/create-pane.cjs.map +1 -0
  17. package/dist/create-pane.d.cts +66 -0
  18. package/dist/create-pane.d.ts +66 -0
  19. package/dist/create-pane.js +178 -0
  20. package/dist/create-pane.js.map +1 -0
  21. package/dist/dashboard/app.tsx +42 -0
  22. package/dist/dashboard/client.ts +21 -0
  23. package/dist/dashboard/export.ts +84 -0
  24. package/dist/dashboard/hooks.ts +75 -0
  25. package/dist/dashboard/index.html +1149 -0
  26. package/dist/dashboard/index.tsx +18 -0
  27. package/dist/dashboard/panels/batches.tsx +282 -0
  28. package/dist/dashboard/panels/buffers.tsx +425 -0
  29. package/dist/dashboard/panels/env.tsx +85 -0
  30. package/dist/dashboard/panels/header-stats.tsx +30 -0
  31. package/dist/dashboard/panels/producer-select.tsx +81 -0
  32. package/dist/dashboard/panels/protocol-log.tsx +743 -0
  33. package/dist/dashboard/panels/registry.tsx +642 -0
  34. package/dist/dashboard/panels/stats.tsx +431 -0
  35. package/dist/dashboard/protocol-store.ts +312 -0
  36. package/dist/dashboard/tsconfig.json +19 -0
  37. package/dist/dashboard/vendor/hooks.module.js +1 -0
  38. package/dist/dashboard/vendor/jsx-runtime.js +22 -0
  39. package/dist/dashboard/vendor/preact.module.js +12 -0
  40. package/dist/devtools-client.cjs +870 -0
  41. package/dist/devtools-client.cjs.map +1 -0
  42. package/dist/devtools-client.d.cts +362 -0
  43. package/dist/devtools-client.d.ts +362 -0
  44. package/dist/devtools-client.js +852 -0
  45. package/dist/devtools-client.js.map +1 -0
  46. package/dist/index.cjs +42 -0
  47. package/dist/index.cjs.map +1 -0
  48. package/dist/index.d.cts +8 -0
  49. package/dist/index.d.ts +8 -0
  50. package/dist/index.js +13 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/perf-trace.cjs +103 -0
  53. package/dist/perf-trace.cjs.map +1 -0
  54. package/dist/perf-trace.d.cts +27 -0
  55. package/dist/perf-trace.d.ts +27 -0
  56. package/dist/perf-trace.js +79 -0
  57. package/dist/perf-trace.js.map +1 -0
  58. package/dist/plugins.cjs +44 -0
  59. package/dist/plugins.cjs.map +1 -0
  60. package/dist/plugins.d.cts +7 -0
  61. package/dist/plugins.d.ts +7 -0
  62. package/dist/plugins.js +9 -0
  63. package/dist/plugins.js.map +1 -0
  64. package/dist/provider-switcher.cjs +95 -0
  65. package/dist/provider-switcher.cjs.map +1 -0
  66. package/dist/provider-switcher.d.cts +18 -0
  67. package/dist/provider-switcher.d.ts +18 -0
  68. package/dist/provider-switcher.js +71 -0
  69. package/dist/provider-switcher.js.map +1 -0
  70. package/dist/react/devtools-provider.cjs +59 -0
  71. package/dist/react/devtools-provider.cjs.map +1 -0
  72. package/dist/react/devtools-provider.d.cts +39 -0
  73. package/dist/react/devtools-provider.d.ts +39 -0
  74. package/dist/react/devtools-provider.js +38 -0
  75. package/dist/react/devtools-provider.js.map +1 -0
  76. package/dist/react/use-fps-graph.cjs +49 -0
  77. package/dist/react/use-fps-graph.cjs.map +1 -0
  78. package/dist/react/use-fps-graph.d.cts +15 -0
  79. package/dist/react/use-fps-graph.d.ts +15 -0
  80. package/dist/react/use-fps-graph.js +25 -0
  81. package/dist/react/use-fps-graph.js.map +1 -0
  82. package/dist/react/use-pane-button.cjs +58 -0
  83. package/dist/react/use-pane-button.cjs.map +1 -0
  84. package/dist/react/use-pane-button.d.cts +10 -0
  85. package/dist/react/use-pane-button.d.ts +10 -0
  86. package/dist/react/use-pane-button.js +34 -0
  87. package/dist/react/use-pane-button.js.map +1 -0
  88. package/dist/react/use-pane-folder.cjs +48 -0
  89. package/dist/react/use-pane-folder.cjs.map +1 -0
  90. package/dist/react/use-pane-folder.d.cts +15 -0
  91. package/dist/react/use-pane-folder.d.ts +15 -0
  92. package/dist/react/use-pane-folder.js +24 -0
  93. package/dist/react/use-pane-folder.js.map +1 -0
  94. package/dist/react/use-pane-input.cjs +70 -0
  95. package/dist/react/use-pane-input.cjs.map +1 -0
  96. package/dist/react/use-pane-input.d.cts +44 -0
  97. package/dist/react/use-pane-input.d.ts +44 -0
  98. package/dist/react/use-pane-input.js +46 -0
  99. package/dist/react/use-pane-input.js.map +1 -0
  100. package/dist/react/use-pane-radio-grid.cjs +78 -0
  101. package/dist/react/use-pane-radio-grid.cjs.map +1 -0
  102. package/dist/react/use-pane-radio-grid.d.cts +35 -0
  103. package/dist/react/use-pane-radio-grid.d.ts +35 -0
  104. package/dist/react/use-pane-radio-grid.js +54 -0
  105. package/dist/react/use-pane-radio-grid.js.map +1 -0
  106. package/dist/react/use-pane.cjs +45 -0
  107. package/dist/react/use-pane.cjs.map +1 -0
  108. package/dist/react/use-pane.d.cts +40 -0
  109. package/dist/react/use-pane.d.ts +40 -0
  110. package/dist/react/use-pane.js +21 -0
  111. package/dist/react/use-pane.js.map +1 -0
  112. package/dist/react.cjs +50 -0
  113. package/dist/react.cjs.map +1 -0
  114. package/dist/react.d.cts +12 -0
  115. package/dist/react.d.ts +12 -0
  116. package/dist/react.js +19 -0
  117. package/dist/react.js.map +1 -0
  118. package/dist/registry-view.cjs +371 -0
  119. package/dist/registry-view.cjs.map +1 -0
  120. package/dist/registry-view.d.cts +24 -0
  121. package/dist/registry-view.d.ts +24 -0
  122. package/dist/registry-view.js +347 -0
  123. package/dist/registry-view.js.map +1 -0
  124. package/dist/stats-graph.cjs +297 -0
  125. package/dist/stats-graph.cjs.map +1 -0
  126. package/dist/stats-graph.d.cts +40 -0
  127. package/dist/stats-graph.d.ts +40 -0
  128. package/dist/stats-graph.js +273 -0
  129. package/dist/stats-graph.js.map +1 -0
  130. package/dist/stats-row.cjs +153 -0
  131. package/dist/stats-row.cjs.map +1 -0
  132. package/dist/stats-row.d.cts +24 -0
  133. package/dist/stats-row.d.ts +24 -0
  134. package/dist/stats-row.js +129 -0
  135. package/dist/stats-row.js.map +1 -0
  136. package/dist/theme.cjs +260 -0
  137. package/dist/theme.cjs.map +1 -0
  138. package/dist/theme.d.cts +10 -0
  139. package/dist/theme.d.ts +10 -0
  140. package/dist/theme.js +235 -0
  141. package/dist/theme.js.map +1 -0
  142. package/dist/vite-plugin.cjs +113 -0
  143. package/dist/vite-plugin.cjs.map +1 -0
  144. package/dist/vite-plugin.d.cts +9 -0
  145. package/dist/vite-plugin.d.ts +9 -0
  146. package/dist/vite-plugin.js +88 -0
  147. package/dist/vite-plugin.js.map +1 -0
  148. 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
+ }