@three-flatland/devtools 0.1.0-alpha.3 → 1.0.0-alpha.6

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 (172) hide show
  1. package/README.md +1 -1
  2. package/dist/buffers-modal.d.ts +16 -52
  3. package/dist/buffers-modal.d.ts.map +1 -0
  4. package/dist/buffers-modal.js +459 -464
  5. package/dist/buffers-modal.js.map +1 -1
  6. package/dist/buffers-view.d.ts +22 -34
  7. package/dist/buffers-view.d.ts.map +1 -0
  8. package/dist/buffers-view.js +252 -240
  9. package/dist/buffers-view.js.map +1 -1
  10. package/dist/create-pane.d.ts +38 -37
  11. package/dist/create-pane.d.ts.map +1 -0
  12. package/dist/create-pane.js +197 -166
  13. package/dist/create-pane.js.map +1 -1
  14. package/dist/dashboard/app.tsx +2 -0
  15. package/dist/dashboard/export.ts +1 -3
  16. package/dist/dashboard/frame-cursor.ts +81 -0
  17. package/dist/dashboard/hooks.ts +10 -2
  18. package/dist/dashboard/index.html +61 -0
  19. package/dist/dashboard/panels/batches.tsx +5 -18
  20. package/dist/dashboard/panels/buffers.tsx +52 -26
  21. package/dist/dashboard/panels/env.tsx +5 -6
  22. package/dist/dashboard/panels/protocol-log.tsx +146 -58
  23. package/dist/dashboard/panels/registry.tsx +55 -69
  24. package/dist/dashboard/panels/scrubber.tsx +100 -0
  25. package/dist/dashboard/panels/stats.tsx +60 -32
  26. package/dist/dashboard/protocol-store.ts +25 -8
  27. package/dist/dashboard/vendor/hooks.module.d.ts +146 -0
  28. package/dist/dashboard/vendor/jsx-runtime.d.ts +55 -0
  29. package/dist/dashboard/vendor/jsx.d.ts +3123 -0
  30. package/dist/dashboard/vendor/preact.module.d.ts +397 -0
  31. package/dist/devtools-client.d.ts +312 -300
  32. package/dist/devtools-client.d.ts.map +1 -0
  33. package/dist/devtools-client.js +866 -838
  34. package/dist/devtools-client.js.map +1 -1
  35. package/dist/index.d.ts +6 -8
  36. package/dist/index.js +5 -12
  37. package/dist/perf-trace.d.ts +6 -4
  38. package/dist/perf-trace.d.ts.map +1 -0
  39. package/dist/perf-trace.js +80 -66
  40. package/dist/perf-trace.js.map +1 -1
  41. package/dist/plugins.d.ts +6 -6
  42. package/dist/plugins.d.ts.map +1 -0
  43. package/dist/plugins.js +5 -5
  44. package/dist/plugins.js.map +1 -1
  45. package/dist/provider-switcher.d.ts +8 -15
  46. package/dist/provider-switcher.d.ts.map +1 -0
  47. package/dist/provider-switcher.js +68 -68
  48. package/dist/provider-switcher.js.map +1 -1
  49. package/dist/react/devtools-provider.d.ts +11 -10
  50. package/dist/react/devtools-provider.d.ts.map +1 -0
  51. package/dist/react/devtools-provider.js +60 -32
  52. package/dist/react/devtools-provider.js.map +1 -1
  53. package/dist/react/use-fps-graph.d.ts +7 -7
  54. package/dist/react/use-fps-graph.d.ts.map +1 -0
  55. package/dist/react/use-fps-graph.js +27 -21
  56. package/dist/react/use-fps-graph.js.map +1 -1
  57. package/dist/react/use-pane-button.d.ts +4 -4
  58. package/dist/react/use-pane-button.d.ts.map +1 -0
  59. package/dist/react/use-pane-button.js +35 -30
  60. package/dist/react/use-pane-button.js.map +1 -1
  61. package/dist/react/use-pane-folder.d.ts +5 -4
  62. package/dist/react/use-pane-folder.d.ts.map +1 -0
  63. package/dist/react/use-pane-folder.js +33 -20
  64. package/dist/react/use-pane-folder.js.map +1 -1
  65. package/dist/react/use-pane-input.d.ts +31 -30
  66. package/dist/react/use-pane-input.d.ts.map +1 -0
  67. package/dist/react/use-pane-input.js +46 -42
  68. package/dist/react/use-pane-input.js.map +1 -1
  69. package/dist/react/use-pane-radio-grid.d.ts +20 -20
  70. package/dist/react/use-pane-radio-grid.d.ts.map +1 -0
  71. package/dist/react/use-pane-radio-grid.js +72 -50
  72. package/dist/react/use-pane-radio-grid.js.map +1 -1
  73. package/dist/react/use-pane.d.ts +4 -4
  74. package/dist/react/use-pane.d.ts.map +1 -0
  75. package/dist/react/use-pane.js +52 -17
  76. package/dist/react/use-pane.js.map +1 -1
  77. package/dist/react.d.ts +10 -12
  78. package/dist/react.js +2 -12
  79. package/dist/registry-view.d.ts +8 -21
  80. package/dist/registry-view.d.ts.map +1 -0
  81. package/dist/registry-view.js +357 -338
  82. package/dist/registry-view.js.map +1 -1
  83. package/dist/relay.d.ts +16 -0
  84. package/dist/relay.d.ts.map +1 -0
  85. package/dist/relay.js +189 -0
  86. package/dist/relay.js.map +1 -0
  87. package/dist/remote.d.ts +10 -0
  88. package/dist/remote.d.ts.map +1 -0
  89. package/dist/remote.js +28 -0
  90. package/dist/remote.js.map +1 -0
  91. package/dist/stats-graph.d.ts +23 -31
  92. package/dist/stats-graph.d.ts.map +1 -0
  93. package/dist/stats-graph.js +323 -264
  94. package/dist/stats-graph.js.map +1 -1
  95. package/dist/stats-row.d.ts +8 -21
  96. package/dist/stats-row.d.ts.map +1 -0
  97. package/dist/stats-row.js +99 -101
  98. package/dist/stats-row.js.map +1 -1
  99. package/dist/theme.d.ts +3 -1
  100. package/dist/theme.d.ts.map +1 -0
  101. package/dist/theme.js +43 -54
  102. package/dist/theme.js.map +1 -1
  103. package/dist/vite-plugin.d.ts +7 -6
  104. package/dist/vite-plugin.d.ts.map +1 -0
  105. package/dist/vite-plugin.js +87 -78
  106. package/dist/vite-plugin.js.map +1 -1
  107. package/package.json +33 -29
  108. package/dist/buffers-modal.cjs +0 -491
  109. package/dist/buffers-modal.cjs.map +0 -1
  110. package/dist/buffers-modal.d.cts +0 -62
  111. package/dist/buffers-view.cjs +0 -269
  112. package/dist/buffers-view.cjs.map +0 -1
  113. package/dist/buffers-view.d.cts +0 -39
  114. package/dist/create-pane.cjs +0 -203
  115. package/dist/create-pane.cjs.map +0 -1
  116. package/dist/create-pane.d.cts +0 -66
  117. package/dist/devtools-client.cjs +0 -870
  118. package/dist/devtools-client.cjs.map +0 -1
  119. package/dist/devtools-client.d.cts +0 -362
  120. package/dist/index.cjs +0 -42
  121. package/dist/index.cjs.map +0 -1
  122. package/dist/index.d.cts +0 -8
  123. package/dist/index.js.map +0 -1
  124. package/dist/perf-trace.cjs +0 -103
  125. package/dist/perf-trace.cjs.map +0 -1
  126. package/dist/perf-trace.d.cts +0 -27
  127. package/dist/plugins.cjs +0 -44
  128. package/dist/plugins.cjs.map +0 -1
  129. package/dist/plugins.d.cts +0 -7
  130. package/dist/provider-switcher.cjs +0 -95
  131. package/dist/provider-switcher.cjs.map +0 -1
  132. package/dist/provider-switcher.d.cts +0 -18
  133. package/dist/react/devtools-provider.cjs +0 -59
  134. package/dist/react/devtools-provider.cjs.map +0 -1
  135. package/dist/react/devtools-provider.d.cts +0 -39
  136. package/dist/react/use-fps-graph.cjs +0 -49
  137. package/dist/react/use-fps-graph.cjs.map +0 -1
  138. package/dist/react/use-fps-graph.d.cts +0 -15
  139. package/dist/react/use-pane-button.cjs +0 -58
  140. package/dist/react/use-pane-button.cjs.map +0 -1
  141. package/dist/react/use-pane-button.d.cts +0 -10
  142. package/dist/react/use-pane-folder.cjs +0 -48
  143. package/dist/react/use-pane-folder.cjs.map +0 -1
  144. package/dist/react/use-pane-folder.d.cts +0 -15
  145. package/dist/react/use-pane-input.cjs +0 -70
  146. package/dist/react/use-pane-input.cjs.map +0 -1
  147. package/dist/react/use-pane-input.d.cts +0 -44
  148. package/dist/react/use-pane-radio-grid.cjs +0 -78
  149. package/dist/react/use-pane-radio-grid.cjs.map +0 -1
  150. package/dist/react/use-pane-radio-grid.d.cts +0 -35
  151. package/dist/react/use-pane.cjs +0 -45
  152. package/dist/react/use-pane.cjs.map +0 -1
  153. package/dist/react/use-pane.d.cts +0 -40
  154. package/dist/react.cjs +0 -50
  155. package/dist/react.cjs.map +0 -1
  156. package/dist/react.d.cts +0 -12
  157. package/dist/react.js.map +0 -1
  158. package/dist/registry-view.cjs +0 -371
  159. package/dist/registry-view.cjs.map +0 -1
  160. package/dist/registry-view.d.cts +0 -24
  161. package/dist/stats-graph.cjs +0 -297
  162. package/dist/stats-graph.cjs.map +0 -1
  163. package/dist/stats-graph.d.cts +0 -40
  164. package/dist/stats-row.cjs +0 -153
  165. package/dist/stats-row.cjs.map +0 -1
  166. package/dist/stats-row.d.cts +0 -24
  167. package/dist/theme.cjs +0 -260
  168. package/dist/theme.cjs.map +0 -1
  169. package/dist/theme.d.cts +0 -10
  170. package/dist/vite-plugin.cjs +0 -113
  171. package/dist/vite-plugin.cjs.map +0 -1
  172. package/dist/vite-plugin.d.cts +0 -9
@@ -0,0 +1,100 @@
1
+ /** @jsxImportSource preact */
2
+ /**
3
+ * Time-travel scrubber (#29 Phase A) — sits under the stats strip:
4
+ *
5
+ * [◀] ──●── [▶] [N/M] [● LIVE]
6
+ *
7
+ * Entry points: drag the slider, step ◀/▶, or click a protocol-log
8
+ * row. Return to live: the LIVE button, double-click the slider, or
9
+ * Esc. The scrubbable range is the frames covered by the stats series
10
+ * ring (~17s at 60 fps); deeper history is the Phase C flight recorder.
11
+ */
12
+ import { useEffect, useLayoutEffect } from 'preact/hooks'
13
+ import { useDevtoolsState, useFrameTick } from '../hooks.js'
14
+ import { addFrameCursorListener, getFrameCursor, goLive, setCursorProvider, setFrameCursor } from '../frame-cursor.js'
15
+ import { useState } from 'preact/hooks'
16
+
17
+ /** Oldest/newest engine frame currently held by the stats ring. */
18
+ function frameRange(state: ReturnType<typeof useDevtoolsState>): {
19
+ min: number
20
+ max: number
21
+ } | null {
22
+ const ring = state.series.frames
23
+ if (ring.length === 0) return null
24
+ const size = ring.data.length
25
+ const newest = ring.data[(ring.write - 1 + size) % size]!
26
+ const oldest = ring.data[(ring.write - ring.length + size) % size]!
27
+ return { min: oldest, max: newest }
28
+ }
29
+
30
+ export function Scrubber() {
31
+ const state = useDevtoolsState()
32
+ useFrameTick()
33
+ const [, setTick] = useState(0)
34
+
35
+ useEffect(() => {
36
+ return addFrameCursorListener(() => setTick((n) => (n + 1) & 0xffff))
37
+ }, [])
38
+
39
+ // Per-provider cursor memory follows the producer switcher. Layout
40
+ // effect, not plain effect: the sync must land before paint so panels
41
+ // reading getFrameCursor() during render never show one frame with the
42
+ // previous provider's cursor. Still post-render, so the listener →
43
+ // setState it triggers isn't mid-render.
44
+ useLayoutEffect(() => {
45
+ setCursorProvider(state.selectedProviderId)
46
+ }, [state.selectedProviderId])
47
+
48
+ // Esc returns to live (no modal handling needed here — modals stop
49
+ // propagation of their own keys).
50
+ useEffect(() => {
51
+ const onKey = (ev: KeyboardEvent): void => {
52
+ // A handler that consumed Esc (detail pane close, modal) wins.
53
+ if (ev.defaultPrevented) return
54
+ if (ev.key === 'Escape' && getFrameCursor() !== null) goLive()
55
+ }
56
+ window.addEventListener('keydown', onKey)
57
+ return () => window.removeEventListener('keydown', onKey)
58
+ }, [])
59
+
60
+ const cursor = getFrameCursor()
61
+ const range = frameRange(state)
62
+ if (range === null) return null
63
+
64
+ const parked = cursor !== null
65
+ const shown = parked ? Math.min(Math.max(cursor, range.min), range.max) : range.max
66
+
67
+ const step = (delta: number): void => {
68
+ const base = parked ? shown : range.max
69
+ const next = Math.min(range.max, Math.max(range.min, base + delta))
70
+ setFrameCursor(next)
71
+ }
72
+
73
+ return (
74
+ <div class={`frame-scrubber${parked ? ' parked' : ''}`}>
75
+ <button class="scrub-step" title="Back one frame" onClick={() => step(-1)}>
76
+
77
+ </button>
78
+ <input
79
+ class="scrub-slider"
80
+ type="range"
81
+ min={range.min}
82
+ max={range.max}
83
+ value={shown}
84
+ onInput={(ev) => setFrameCursor(Number((ev.target as HTMLInputElement).value))}
85
+ onDblClick={() => goLive()}
86
+ />
87
+ <button class="scrub-step" title="Forward one frame" onClick={() => step(1)}>
88
+
89
+ </button>
90
+ <span class="scrub-frame">{parked ? `${shown}/${range.max}` : `${range.max}`}</span>
91
+ <button
92
+ class={`scrub-live${parked ? '' : ' active'}`}
93
+ title={parked ? 'Return to live' : 'Live'}
94
+ onClick={() => goLive()}
95
+ >
96
+ ● LIVE
97
+ </button>
98
+ </div>
99
+ )
100
+ }
@@ -13,8 +13,9 @@
13
13
  * jittering zero.
14
14
  */
15
15
  import { useEffect, useRef, useState } from 'preact/hooks'
16
- import type { DevtoolsSeries, DevtoolsState } from '../../devtools-client.js'
16
+ import type { DevtoolsSeries, DevtoolsState, DevtoolsFrameSeries } from '../../devtools-client.js'
17
17
  import { useDevtoolsState } from '../hooks.js'
18
+ import { getFrameCursor } from '../frame-cursor.js'
18
19
 
19
20
  interface StatDef {
20
21
  label: string
@@ -35,15 +36,15 @@ function fmtMB(v: number | undefined): string {
35
36
  }
36
37
 
37
38
  const STATS: StatDef[] = [
38
- { label: 'fps', pick: (s) => s.fps, series: (s) => s.series.fps, format: fmt0, color: '#47cca9' },
39
- { label: 'cpu', pick: (s) => s.cpuMs, series: (s) => s.series.cpuMs, format: fmtMs, color: '#47cc6a' },
40
- { label: 'gpu', pick: (s) => s.gpuMs, series: (s) => s.series.gpuMs, format: fmtMs, color: '#ffa347' },
41
- { label: 'draws', pick: (s) => s.drawCalls, series: (s) => s.series.drawCalls, format: fmt0, color: '#5eb0ff' },
42
- { label: 'tris', pick: (s) => s.triangles, series: (s) => s.series.triangles, format: fmt0, color: '#c792ea' },
43
- { label: 'prims', pick: (s) => s.primitives, series: (s) => s.series.primitives, format: fmt0, color: '#ff8fa3' },
44
- { label: 'geos', pick: (s) => s.geometries, series: (s) => s.series.geometries, format: fmt0, color: '#f78c6c' },
45
- { label: 'tex', pick: (s) => s.textures, series: (s) => s.series.textures, format: fmt0, color: '#ffc371' },
46
- { label: 'heap', pick: (s) => s.heapUsedMB, series: (s) => s.series.heapUsedMB, format: fmtMB, color: '#d94c87' },
39
+ { label: 'fps', pick: (s) => s.fps, series: (s) => s.series.fps, format: fmt0, color: '#47cca9' },
40
+ { label: 'cpu', pick: (s) => s.cpuMs, series: (s) => s.series.cpuMs, format: fmtMs, color: '#47cc6a' },
41
+ { label: 'gpu', pick: (s) => s.gpuMs, series: (s) => s.series.gpuMs, format: fmtMs, color: '#ffa347' },
42
+ { label: 'draws', pick: (s) => s.drawCalls, series: (s) => s.series.drawCalls, format: fmt0, color: '#5eb0ff' },
43
+ { label: 'tris', pick: (s) => s.triangles, series: (s) => s.series.triangles, format: fmt0, color: '#c792ea' },
44
+ { label: 'prims', pick: (s) => s.primitives, series: (s) => s.series.primitives, format: fmt0, color: '#ff8fa3' },
45
+ { label: 'geos', pick: (s) => s.geometries, series: (s) => s.series.geometries, format: fmt0, color: '#f78c6c' },
46
+ { label: 'tex', pick: (s) => s.textures, series: (s) => s.series.textures, format: fmt0, color: '#ffc371' },
47
+ { label: 'heap', pick: (s) => s.heapUsedMB, series: (s) => s.series.heapUsedMB, format: fmtMB, color: '#d94c87' },
47
48
  ]
48
49
 
49
50
  export function StatsStrip() {
@@ -75,13 +76,7 @@ export function StatsStrip() {
75
76
  />
76
77
  ))}
77
78
  </div>
78
- {expandedDef !== null && (
79
- <StatDetail
80
- def={expandedDef}
81
- state={s}
82
- onClose={() => setExpandedKey(null)}
83
- />
84
- )}
79
+ {expandedDef !== null && <StatDetail def={expandedDef} state={s} onClose={() => setExpandedKey(null)} />}
85
80
  </section>
86
81
  )
87
82
  }
@@ -104,8 +99,12 @@ function StatCard({
104
99
  // instantly to match new peaks, decays slowly afterward (see
105
100
  // `drawSeries`'s RANGE_DECAY).
106
101
  const rangeRef = useRef<{ min: number; max: number } | null>(null)
107
- const value = def.pick(state)
108
102
  const series = def.series(state)
103
+ // Parked cursor: show the ring sample whose engine frame is the
104
+ // closest ≤ the cursor instead of the live smoothed value.
105
+ const cursor = getFrameCursor()
106
+ const parkedValue = cursor !== null ? seriesValueAtFrame(series, state.series.frames, cursor) : undefined
107
+ const value = cursor !== null ? parkedValue : def.pick(state)
109
108
 
110
109
  useEffect(() => {
111
110
  const canvas = canvasRef.current
@@ -133,16 +132,33 @@ function StatCard({
133
132
  >
134
133
  <canvas class="stat-canvas" ref={canvasRef} />
135
134
  <span class="stat-label">{def.label}</span>
136
- <span class="stat-value">{def.format(value)}</span>
135
+ <span class={`stat-value${cursor !== null ? ' stat-parked' : ''}`}>{def.format(value)}</span>
137
136
  </button>
138
137
  )
139
138
  }
140
139
 
141
- function StatDetail({ def, state, onClose }: {
142
- def: StatDef
143
- state: DevtoolsState
144
- onClose: () => void
145
- }) {
140
+ /**
141
+ * Ring sample for the closest engine frame ≤ `frame` (time-travel
142
+ * cursor). Rings ingest in lockstep, so an index into the frames ring
143
+ * is valid for every series ring.
144
+ */
145
+ function seriesValueAtFrame(series: DevtoolsSeries, frames: DevtoolsFrameSeries, frame: number): number | undefined {
146
+ const len = Math.min(series.length, frames.length)
147
+ if (len === 0) return undefined
148
+ const size = frames.data.length
149
+ // Walk newest → oldest; rings are small (1024) and this only runs
150
+ // while parked.
151
+ for (let i = 0; i < len; i++) {
152
+ const idx = (frames.write - 1 - i + size) % size
153
+ if (frames.data[idx]! <= frame) {
154
+ return series.data[(series.write - 1 - i + size) % size]
155
+ }
156
+ }
157
+ // Cursor is older than everything we hold — show the oldest sample.
158
+ return series.data[(series.write - len + size) % size]
159
+ }
160
+
161
+ function StatDetail({ def, state, onClose }: { def: StatDef; state: DevtoolsState; onClose: () => void }) {
146
162
  const series = def.series(state)
147
163
  const stats = computeStats(series)
148
164
  const histoRef = useRef<HTMLCanvasElement | null>(null)
@@ -166,7 +182,9 @@ function StatDetail({ def, state, onClose }: {
166
182
  <div class="stat-detail-header">
167
183
  <span class="stat-detail-label">{def.label}</span>
168
184
  <span class="stat-detail-samples">{stats.count} samples</span>
169
- <button type="button" class="stat-detail-close" onClick={onClose} aria-label="Close">×</button>
185
+ <button type="button" class="stat-detail-close" onClick={onClose} aria-label="Close">
186
+ ×
187
+ </button>
170
188
  </div>
171
189
  <div class="stat-detail-metrics">
172
190
  <Metric label="min" value={def.format(stats.min)} />
@@ -202,7 +220,8 @@ interface SeriesStats {
202
220
 
203
221
  function computeStats(series: DevtoolsSeries): SeriesStats {
204
222
  const len = series.length
205
- if (len === 0) return { count: 0, min: undefined, max: undefined, mean: undefined, p50: undefined, p95: undefined, p99: undefined }
223
+ if (len === 0)
224
+ return { count: 0, min: undefined, max: undefined, mean: undefined, p50: undefined, p95: undefined, p99: undefined }
206
225
  const size = series.data.length
207
226
  const start = (series.write - len + size) % size
208
227
  // Copy into a dense array for sorting — len is typically ~256 so
@@ -236,7 +255,7 @@ function drawHistogram(
236
255
  w: number,
237
256
  h: number,
238
257
  series: DevtoolsSeries,
239
- color: string,
258
+ color: string
240
259
  ): void {
241
260
  ctx.clearRect(0, 0, w, h)
242
261
  const len = series.length
@@ -333,7 +352,7 @@ function drawSeries(
333
352
  h: number,
334
353
  series: DevtoolsSeries,
335
354
  color: string,
336
- rangeRef: { current: { min: number; max: number } | null },
355
+ rangeRef: { current: { min: number; max: number } | null }
337
356
  ): void {
338
357
  ctx.clearRect(0, 0, w, h)
339
358
  const len = series.length
@@ -349,12 +368,21 @@ function drawSeries(
349
368
  // compares per sample, no allocation). Top1 is the max; topK is
350
369
  // the K-th-largest = our trimmed observedMax. When fewer than K
351
370
  // samples exist (window still warming up), fall back to top1.
352
- let top1 = -Infinity, top2 = -Infinity, top3 = -Infinity
371
+ let top1 = -Infinity,
372
+ top2 = -Infinity,
373
+ top3 = -Infinity
353
374
  for (let i = 0; i < len; i++) {
354
375
  const v = series.data[(start + i) % size]!
355
- if (v > top1) { top3 = top2; top2 = top1; top1 = v }
356
- else if (v > top2) { top3 = top2; top2 = v }
357
- else if (v > top3) { top3 = v }
376
+ if (v > top1) {
377
+ top3 = top2
378
+ top2 = top1
379
+ top1 = v
380
+ } else if (v > top2) {
381
+ top3 = top2
382
+ top2 = v
383
+ } else if (v > top3) {
384
+ top3 = v
385
+ }
358
386
  }
359
387
  const observedMax = len < AXIS_TRIM_K ? top1 : top3
360
388
  if (!Number.isFinite(observedMax) || observedMax < 0) return
@@ -102,12 +102,18 @@ export class ProtocolStore {
102
102
 
103
103
  addListener(cb: Listener): () => void {
104
104
  this._listeners.add(cb)
105
- return () => { this._listeners.delete(cb) }
105
+ return () => {
106
+ this._listeners.delete(cb)
107
+ }
106
108
  }
107
109
 
108
110
  private _fire(): void {
109
111
  for (const cb of this._listeners) {
110
- try { cb() } catch { /* ignore */ }
112
+ try {
113
+ cb()
114
+ } catch {
115
+ /* ignore */
116
+ }
111
117
  }
112
118
  }
113
119
 
@@ -149,7 +155,7 @@ export class ProtocolStore {
149
155
  if (this._flushTimer === null) {
150
156
  this._flushTimer = (globalThis.setTimeout as unknown as (cb: () => void, ms: number) => number)(
151
157
  () => this._flush(),
152
- WRITE_FLUSH_MS,
158
+ WRITE_FLUSH_MS
153
159
  )
154
160
  }
155
161
  this._evictIfNeeded(providerId)
@@ -170,7 +176,9 @@ export class ProtocolStore {
170
176
  try {
171
177
  const tx = this._db.transaction(STORE, 'readwrite')
172
178
  tx.objectStore(STORE).clear()
173
- } catch { /* db closing */ }
179
+ } catch {
180
+ /* db closing */
181
+ }
174
182
  }
175
183
  this._fire()
176
184
  }
@@ -189,7 +197,10 @@ export class ProtocolStore {
189
197
  // Quick hit check.
190
198
  let allHit = true
191
199
  for (let id = startId; id <= endId; id++) {
192
- if (!this._cache.has(cacheKey(providerId, id))) { allHit = false; break }
200
+ if (!this._cache.has(cacheKey(providerId, id))) {
201
+ allHit = false
202
+ break
203
+ }
193
204
  }
194
205
  if (allHit) return
195
206
  const promise = this._readRange(providerId, startId, endId)
@@ -214,7 +225,7 @@ export class ProtocolStore {
214
225
  async queryFiltered(
215
226
  providerId: string,
216
227
  predicate: (entry: LogEntry) => boolean,
217
- signal?: { aborted: boolean },
228
+ signal?: { aborted: boolean }
218
229
  ): Promise<number[]> {
219
230
  const db = await this._dbReady
220
231
  const out: number[] = []
@@ -224,9 +235,15 @@ export class ProtocolStore {
224
235
  const index = store.index(INDEX)
225
236
  const req = index.openCursor(IDBKeyRange.only(providerId))
226
237
  req.onsuccess = () => {
227
- if (signal?.aborted === true) { resolve(); return }
238
+ if (signal?.aborted === true) {
239
+ resolve()
240
+ return
241
+ }
228
242
  const cursor = req.result
229
- if (cursor === null) { resolve(); return }
243
+ if (cursor === null) {
244
+ resolve()
245
+ return
246
+ }
230
247
  const entry = cursor.value as LogEntry
231
248
  if (predicate(entry)) {
232
249
  out.push(entry.id)
@@ -0,0 +1,146 @@
1
+ import { ErrorInfo, PreactContext, Ref as PreactRef } from './preact.module';
2
+
3
+ type Inputs = ReadonlyArray<unknown>;
4
+
5
+ export type Dispatch<A> = (value: A) => void;
6
+ export type StateUpdater<S> = S | ((prevState: S) => S);
7
+
8
+ /**
9
+ * Returns a stateful value, and a function to update it.
10
+ * @param initialState The initial value (or a function that returns the initial value)
11
+ */
12
+ export function useState<S>(
13
+ initialState: S | (() => S)
14
+ ): [S, Dispatch<StateUpdater<S>>];
15
+
16
+ export function useState<S = undefined>(): [
17
+ S | undefined,
18
+ Dispatch<StateUpdater<S | undefined>>
19
+ ];
20
+
21
+ export type Reducer<S, A> = (prevState: S, action: A) => S;
22
+
23
+ /**
24
+ * An alternative to `useState`.
25
+ *
26
+ * `useReducer` is usually preferable to `useState` when you have complex state logic that involves
27
+ * multiple sub-values. It also lets you optimize performance for components that trigger deep
28
+ * updates because you can pass `dispatch` down instead of callbacks.
29
+ * @param reducer Given the current state and an action, returns the new state
30
+ * @param initialState The initial value to store as state
31
+ */
32
+ export function useReducer<S, A>(
33
+ reducer: Reducer<S, A>,
34
+ initialState: S
35
+ ): [S, Dispatch<A>];
36
+
37
+ /**
38
+ * An alternative to `useState`.
39
+ *
40
+ * `useReducer` is usually preferable to `useState` when you have complex state logic that involves
41
+ * multiple sub-values. It also lets you optimize performance for components that trigger deep
42
+ * updates because you can pass `dispatch` down instead of callbacks.
43
+ * @param reducer Given the current state and an action, returns the new state
44
+ * @param initialArg The initial argument to pass to the `init` function
45
+ * @param init A function that, given the `initialArg`, returns the initial value to store as state
46
+ */
47
+ export function useReducer<S, A, I>(
48
+ reducer: Reducer<S, A>,
49
+ initialArg: I,
50
+ init: (arg: I) => S
51
+ ): [S, Dispatch<A>];
52
+
53
+ /** @deprecated Use the `Ref` type instead. */
54
+ type PropRef<T> = MutableRef<T>;
55
+ interface Ref<T> {
56
+ readonly current: T | null;
57
+ }
58
+
59
+ interface MutableRef<T> {
60
+ current: T;
61
+ }
62
+
63
+ /**
64
+ * `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
65
+ * (`initialValue`). The returned object will persist for the full lifetime of the component.
66
+ *
67
+ * Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
68
+ * value around similar to how you’d use instance fields in classes.
69
+ *
70
+ * @param initialValue the initial value to store in the ref object
71
+ */
72
+ export function useRef<T>(initialValue: T): MutableRef<T>;
73
+ export function useRef<T>(initialValue: T | null): Ref<T>;
74
+ export function useRef<T = undefined>(): MutableRef<T | undefined>;
75
+
76
+ type EffectCallback = () => void | (() => void);
77
+ /**
78
+ * Accepts a function that contains imperative, possibly effectful code.
79
+ * The effects run after browser paint, without blocking it.
80
+ *
81
+ * @param effect Imperative function that can return a cleanup function
82
+ * @param inputs If present, effect will only activate if the values in the list change (using ===).
83
+ */
84
+ export function useEffect(effect: EffectCallback, inputs?: Inputs): void;
85
+
86
+ type CreateHandle = () => object;
87
+
88
+ /**
89
+ * @param ref The ref that will be mutated
90
+ * @param create The function that will be executed to get the value that will be attached to
91
+ * ref.current
92
+ * @param inputs If present, effect will only activate if the values in the list change (using ===).
93
+ */
94
+ export function useImperativeHandle<T, R extends T>(
95
+ ref: PreactRef<T>,
96
+ create: () => R,
97
+ inputs?: Inputs
98
+ ): void;
99
+
100
+ /**
101
+ * Accepts a function that contains imperative, possibly effectful code.
102
+ * Use this to read layout from the DOM and synchronously re-render.
103
+ * Updates scheduled inside `useLayoutEffect` will be flushed synchronously, after all DOM mutations but before the browser has a chance to paint.
104
+ * Prefer the standard `useEffect` hook when possible to avoid blocking visual updates.
105
+ *
106
+ * @param effect Imperative function that can return a cleanup function
107
+ * @param inputs If present, effect will only activate if the values in the list change (using ===).
108
+ */
109
+ export function useLayoutEffect(effect: EffectCallback, inputs?: Inputs): void;
110
+
111
+ /**
112
+ * Returns a memoized version of the callback that only changes if one of the `inputs`
113
+ * has changed (using ===).
114
+ */
115
+ export function useCallback<T extends Function>(callback: T, inputs: Inputs): T;
116
+
117
+ /**
118
+ * Pass a factory function and an array of inputs.
119
+ * useMemo will only recompute the memoized value when one of the inputs has changed.
120
+ * This optimization helps to avoid expensive calculations on every render.
121
+ * If no array is provided, a new value will be computed whenever a new function instance is passed as the first argument.
122
+ */
123
+ // for `inputs`, allow undefined, but don't make it optional as that is very likely a mistake
124
+ export function useMemo<T>(factory: () => T, inputs: Inputs | undefined): T;
125
+
126
+ /**
127
+ * Returns the current context value, as given by the nearest context provider for the given context.
128
+ * When the provider updates, this Hook will trigger a rerender with the latest context value.
129
+ *
130
+ * @param context The context you want to use
131
+ */
132
+ export function useContext<T>(context: PreactContext<T>): T;
133
+
134
+ /**
135
+ * Customize the displayed value in the devtools panel.
136
+ *
137
+ * @param value Custom hook name or object that is passed to formatter
138
+ * @param formatter Formatter to modify value before sending it to the devtools
139
+ */
140
+ export function useDebugValue<T>(value: T, formatter?: (value: T) => any): void;
141
+
142
+ export function useErrorBoundary(
143
+ callback?: (error: any, errorInfo: ErrorInfo) => Promise<void> | void
144
+ ): [any, () => void];
145
+
146
+ export function useId(): string;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * JSX-runtime types for the vendored Preact copy. Mirrors
3
+ * `preact/jsx-runtime/src/index.d.ts` verbatim (structure-wise) but imports
4
+ * from the sibling vendored file instead of the npm-published `preact`.
5
+ */
6
+ import {
7
+ Attributes,
8
+ ComponentChild,
9
+ ComponentChildren,
10
+ ComponentType,
11
+ VNode,
12
+ } from './preact.module'
13
+ import { JSXInternal } from './jsx'
14
+
15
+ export { Fragment } from './preact.module'
16
+ export { JSXInternal as JSX }
17
+
18
+ export function jsx(
19
+ type: string,
20
+ props: JSXInternal.HTMLAttributes &
21
+ JSXInternal.SVGAttributes &
22
+ Record<string, unknown> & { children?: ComponentChild },
23
+ key?: string,
24
+ ): VNode<unknown>
25
+ export function jsx<P>(
26
+ type: ComponentType<P>,
27
+ props: Attributes & P & { children?: ComponentChild },
28
+ key?: string,
29
+ ): VNode<unknown>
30
+
31
+ export function jsxs(
32
+ type: string,
33
+ props: JSXInternal.HTMLAttributes &
34
+ JSXInternal.SVGAttributes &
35
+ Record<string, unknown> & { children?: ComponentChild[] },
36
+ key?: string,
37
+ ): VNode<unknown>
38
+ export function jsxs<P>(
39
+ type: ComponentType<P>,
40
+ props: Attributes & P & { children?: ComponentChild[] },
41
+ key?: string,
42
+ ): VNode<unknown>
43
+
44
+ export function jsxDEV(
45
+ type: string,
46
+ props: JSXInternal.HTMLAttributes &
47
+ JSXInternal.SVGAttributes &
48
+ Record<string, unknown> & { children?: ComponentChildren },
49
+ key?: string,
50
+ ): VNode<unknown>
51
+ export function jsxDEV<P>(
52
+ type: ComponentType<P>,
53
+ props: Attributes & P & { children?: ComponentChildren },
54
+ key?: string,
55
+ ): VNode<unknown>