@brett_lamy/docstream 1.22.1 → 1.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/replay/ReplayDevtools.d.ts +6 -3
  6. package/dist/replay/ReplayDevtools.d.ts.map +1 -1
  7. package/dist/replay/ReplayDevtools.js +38 -16
  8. package/dist/replay/ReplayDevtools.js.map +1 -1
  9. package/dist/replay/ReplayFile.d.ts +7 -1
  10. package/dist/replay/ReplayFile.d.ts.map +1 -1
  11. package/dist/replay/ReplayFile.js +17 -3
  12. package/dist/replay/ReplayFile.js.map +1 -1
  13. package/dist/replay/ReplayPlayer.d.ts +18 -4
  14. package/dist/replay/ReplayPlayer.d.ts.map +1 -1
  15. package/dist/replay/ReplayPlayer.js +130 -20
  16. package/dist/replay/ReplayPlayer.js.map +1 -1
  17. package/dist/replay/devtools.d.ts +5 -0
  18. package/dist/replay/devtools.d.ts.map +1 -1
  19. package/dist/replay/devtools.js +3 -2
  20. package/dist/replay/devtools.js.map +1 -1
  21. package/dist/replay/index.d.ts +3 -1
  22. package/dist/replay/index.d.ts.map +1 -1
  23. package/dist/replay/index.js +2 -1
  24. package/dist/replay/index.js.map +1 -1
  25. package/dist/replay/inspections.d.ts +36 -0
  26. package/dist/replay/inspections.d.ts.map +1 -0
  27. package/dist/replay/inspections.js +64 -0
  28. package/dist/replay/inspections.js.map +1 -0
  29. package/dist/styles.css +34 -8
  30. package/package.json +1 -1
  31. package/src/index.ts +1 -1
  32. package/src/replay/ReplayDevtools.tsx +42 -19
  33. package/src/replay/ReplayFile.tsx +16 -3
  34. package/src/replay/ReplayPlayer.tsx +152 -28
  35. package/src/replay/devtools.ts +8 -2
  36. package/src/replay/index.ts +3 -1
  37. package/src/replay/inspections.ts +105 -0
  38. package/src/styles.css +34 -8
@@ -18,9 +18,11 @@ export type { ReplayMarker, ReplayMarkerKind, ReplayMeta, ReplayPointerPoint, Re
18
18
  export { getReplayDevtools, replayDevtoolsAt, requestName } from './devtools';
19
19
  export type { ReplayConsoleEntry, ReplayConsoleLevel, ReplayDevtools, ReplayDevtoolsState, ReplayRequest } from './devtools';
20
20
  export type { ReplayDevtoolsTab } from './ReplayDevtools';
21
+ export { getReplayInspections, replayInspectionAt, resolveReplayInspections } from './inspections';
22
+ export type { ReplayInspection, ReplayInspectionTab, ResolvedReplayInspection } from './inspections';
21
23
  export { ReplayEventType, ReplayIncrementalSource, ReplayMouseInteraction } from './types';
22
24
  export type { ReplayEvent } from './types';
23
25
  export { isReplayQaUrl, normalizeReplayEmbedUrl } from './url';
24
- export { ReplayFile, isRrwebUrl } from './ReplayFile';
26
+ export { ReplayFile, isRrwebUrl, splitReplayFragment } from './ReplayFile';
25
27
  export type { ReplayFileProps } from './ReplayFile';
26
28
  export { sanitizeReplayEvent } from './sanitize';
@@ -0,0 +1,105 @@
1
+ /* Inspections: moments in a recording where someone (a person, a debugging agent) looked at a request or a console
2
+ message — "at 0:35 we inspected GET /api/search?q=te". The player follows them by default: as the playhead
3
+ reaches one, DevTools opens on its tab with that request (or message) selected and the inspection's label above
4
+ it, and playback carries on.
5
+
6
+ They come from the `inspections` prop or from the recording itself, as custom events tagged `inspect`
7
+ (`record.addCustomEvent('inspect', { tab, request, message, label })`), so an .rrweb file can carry its own.
8
+ An inspection names its target by the recording's id for it (a request's `requestId` / `id`, a custom console
9
+ event's `id`) or, failing that, by URL (the request with that URL sent nearest the inspection) or by text (the
10
+ last message containing it). Pure readers, no rrweb import. */
11
+ import type { ReplayConsoleEntry, ReplayDevtools, ReplayRequest } from './devtools'
12
+ import { ReplayEventType, type ReplayEvent } from './types'
13
+
14
+ export type ReplayInspectionTab = 'network' | 'console'
15
+
16
+ export interface ReplayInspection {
17
+ /** Stable id; defaults to `inspect-<n>`. */
18
+ id?: string
19
+ /** Milliseconds from the start of the recording. */
20
+ time: number
21
+ /** The DevTools tab to open (default: `network` when `request` is set, else `console`). */
22
+ tab?: ReplayInspectionTab
23
+ /** The request to select: the recording's id for it (`key`), or its URL. */
24
+ request?: string
25
+ /** The console message to select: the recording's id for it (`key`), or text it contains. */
26
+ message?: string
27
+ /** What was found, shown above the panel while the inspection is current. */
28
+ label?: string
29
+ }
30
+
31
+ /** An inspection with its defaults filled in and its target resolved against the recording's DevTools. */
32
+ export interface ResolvedReplayInspection {
33
+ id: string
34
+ time: number
35
+ tab: ReplayInspectionTab
36
+ label?: string
37
+ /** The `ReplayRequest.id` to select, when the target was found. */
38
+ requestId?: string
39
+ /** The `ReplayConsoleEntry.id` to select, when the target was found. */
40
+ messageId?: string
41
+ }
42
+
43
+ export const INSPECT_TAG = 'inspect'
44
+
45
+ const str = (value: unknown): string | undefined => (typeof value === 'string' && value ? value : undefined)
46
+
47
+ /** The `inspect` custom events in a recording, as inspections (times in ms from its first event). */
48
+ export function getReplayInspections(events: readonly ReplayEvent[]): ReplayInspection[] {
49
+ const list = [...events].sort((a, b) => a.timestamp - b.timestamp)
50
+ const start = list[0]?.timestamp ?? 0
51
+ const out: ReplayInspection[] = []
52
+ for (const event of list) {
53
+ if (event.type !== ReplayEventType.Custom) continue
54
+ const data = (event.data ?? {}) as { tag?: unknown; payload?: Record<string, unknown> }
55
+ if (data.tag !== INSPECT_TAG) continue
56
+ const p = data.payload ?? {}
57
+ const tab = p.tab === 'network' || p.tab === 'console' ? p.tab : undefined
58
+ out.push({ id: str(p.id), time: event.timestamp - start, tab, request: str(p.request), message: str(p.message), label: str(p.label) })
59
+ }
60
+ return out
61
+ }
62
+
63
+ function findRequest(network: readonly ReplayRequest[], target: string, time: number): ReplayRequest | undefined {
64
+ const byKey = network.find((r) => r.key === target)
65
+ if (byKey) return byKey
66
+ // By URL (exact, else the address without its origin): the one sent nearest the inspection.
67
+ const matches = network.filter((r) => r.url === target || r.url.replace(/^\w+:\/\/[^/]+/, '') === target)
68
+ return matches.sort((a, b) => Math.abs(a.time - time) - Math.abs(b.time - time))[0]
69
+ }
70
+
71
+ function findMessage(messages: readonly ReplayConsoleEntry[], target: string, time: number): ReplayConsoleEntry | undefined {
72
+ const byKey = messages.find((m) => m.key === target)
73
+ if (byKey) return byKey
74
+ const matches = messages.filter((m) => m.message.includes(target))
75
+ // The last one logged by the inspection, else the first after it.
76
+ return matches.filter((m) => m.time <= time).at(-1) ?? matches[0]
77
+ }
78
+
79
+ /** Fills in each inspection's id and tab and finds its request or message; sorted by time. */
80
+ export function resolveReplayInspections(inspections: readonly ReplayInspection[], tools: ReplayDevtools): ResolvedReplayInspection[] {
81
+ return inspections
82
+ .map((inspection, i) => {
83
+ const request = inspection.request ? findRequest(tools.network, inspection.request, inspection.time) : undefined
84
+ const message = inspection.message ? findMessage(tools.console, inspection.message, inspection.time) : undefined
85
+ return {
86
+ id: inspection.id ?? `inspect-${i}`,
87
+ time: Math.max(0, inspection.time),
88
+ tab: inspection.tab ?? (inspection.request ? 'network' : 'console'),
89
+ label: inspection.label,
90
+ requestId: request?.id,
91
+ messageId: message?.id,
92
+ } satisfies ResolvedReplayInspection
93
+ })
94
+ .sort((a, b) => a.time - b.time)
95
+ }
96
+
97
+ /** The inspection current at `time`: the latest at or before it. */
98
+ export function replayInspectionAt(inspections: readonly ResolvedReplayInspection[], time: number): ResolvedReplayInspection | undefined {
99
+ let current: ResolvedReplayInspection | undefined
100
+ for (const inspection of inspections) {
101
+ if (inspection.time > time) break
102
+ current = inspection
103
+ }
104
+ return current
105
+ }
package/src/styles.css CHANGED
@@ -4260,6 +4260,24 @@
4260
4260
  transform: translateY(5px);
4261
4261
  }
4262
4262
 
4263
+ /* The time window: a band under the track, on the same geometry as the markers (4px padding, half a thumb). */
4264
+ :where([data-slot="replay-preview-window"]) {
4265
+ position: absolute;
4266
+ top: 50%;
4267
+ left: calc(10px + (100% - 20px) * var(--replay-window-start, 0));
4268
+ width: max(4px, calc((100% - 20px) * var(--replay-window-size, 1)));
4269
+ height: 10px;
4270
+ border-radius: 999px;
4271
+ background: color-mix(in srgb, var(--primary, #4f46e5) 34%, transparent);
4272
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--primary, #4f46e5) 60%, transparent);
4273
+ translate: 0 -50%;
4274
+ pointer-events: none;
4275
+ }
4276
+
4277
+ :where([data-slot="replay-preview-scrubber"][data-markers] > [data-slot="replay-preview-window"]) {
4278
+ translate: 0 calc(-50% + 5px);
4279
+ }
4280
+
4263
4281
  :where([data-slot="replay-preview-markers"] > button) {
4264
4282
  position: absolute;
4265
4283
  top: 50%;
@@ -4309,6 +4327,8 @@
4309
4327
  }
4310
4328
 
4311
4329
  :where([data-slot="replay-preview-scrubber"] > input[type="range"]) {
4330
+ /* Above the time-window band. */
4331
+ position: relative;
4312
4332
  --replay-track: 4px;
4313
4333
  --replay-thumb: 12px;
4314
4334
  appearance: none;
@@ -4331,8 +4351,11 @@
4331
4351
  [data-slot="replay-preview-scrubber"] > input[type="range"]::-webkit-slider-runnable-track {
4332
4352
  height: var(--replay-track);
4333
4353
  border-radius: 999px;
4354
+ /* Played: from the time window's start (0 without one) to the playhead. */
4334
4355
  background: linear-gradient(
4335
4356
  to right,
4357
+ color-mix(in srgb, var(--muted-foreground, #71717a) 26%, transparent) var(--replay-progress-start, 0%),
4358
+ var(--primary, #4f46e5) var(--replay-progress-start, 0%),
4336
4359
  var(--primary, #4f46e5) var(--replay-progress, 0%),
4337
4360
  color-mix(in srgb, var(--muted-foreground, #71717a) 26%, transparent) var(--replay-progress, 0%)
4338
4361
  );
@@ -4601,19 +4624,17 @@
4601
4624
  box-shadow: inset 0 0 0 2px var(--ring, #6366f1);
4602
4625
  }
4603
4626
 
4604
- :where([data-slot="replay-devtools-tabs"] [data-part="at"]) {
4605
- display: inline-flex;
4606
- align-items: center;
4607
- margin-inline-start: auto;
4608
- padding-inline: 8px;
4609
- color: var(--replay-devtools-dim);
4610
- font-variant-numeric: tabular-nums;
4611
- }
4627
+
4628
+
4612
4629
 
4613
4630
  :where([data-slot="replay-devtools-tabs"] > [data-slot="replay-devtools-close"], [data-slot="replay-devtools-detail-tabs"] > [data-slot="replay-devtools-close"]) {
4614
4631
  padding: 0 8px;
4615
4632
  }
4616
4633
 
4634
+ :where([data-slot="replay-devtools-tabs"] > [data-slot="replay-devtools-close"]) {
4635
+ margin-inline-start: auto;
4636
+ }
4637
+
4617
4638
  :where([data-slot="replay-devtools-pane"]) {
4618
4639
  display: flex;
4619
4640
  flex: 1 1 0%;
@@ -4882,6 +4903,11 @@
4882
4903
  :where([data-slot="replay-devtools-table"] tbody tr:focus-visible) {
4883
4904
  box-shadow: inset 0 0 0 2px var(--ring, #6366f1);
4884
4905
  }
4906
+ /* The inspected request or message: a bar down its leading edge (the selection tint shows it's open). */
4907
+ :where([data-slot="replay-devtools-table"] tbody tr[data-inspected], [data-slot="replay-devtools-message"][data-inspected]) {
4908
+ box-shadow: inset 3px 0 0 var(--primary, #2563eb);
4909
+ }
4910
+
4885
4911
 
4886
4912
  :where([data-slot="replay-devtools-table"] tr[data-failed]) {
4887
4913
  color: var(--replay-devtools-red);