@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.
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/replay/ReplayDevtools.d.ts +6 -3
- package/dist/replay/ReplayDevtools.d.ts.map +1 -1
- package/dist/replay/ReplayDevtools.js +38 -16
- package/dist/replay/ReplayDevtools.js.map +1 -1
- package/dist/replay/ReplayFile.d.ts +7 -1
- package/dist/replay/ReplayFile.d.ts.map +1 -1
- package/dist/replay/ReplayFile.js +17 -3
- package/dist/replay/ReplayFile.js.map +1 -1
- package/dist/replay/ReplayPlayer.d.ts +18 -4
- package/dist/replay/ReplayPlayer.d.ts.map +1 -1
- package/dist/replay/ReplayPlayer.js +130 -20
- package/dist/replay/ReplayPlayer.js.map +1 -1
- package/dist/replay/devtools.d.ts +5 -0
- package/dist/replay/devtools.d.ts.map +1 -1
- package/dist/replay/devtools.js +3 -2
- package/dist/replay/devtools.js.map +1 -1
- package/dist/replay/index.d.ts +3 -1
- package/dist/replay/index.d.ts.map +1 -1
- package/dist/replay/index.js +2 -1
- package/dist/replay/index.js.map +1 -1
- package/dist/replay/inspections.d.ts +36 -0
- package/dist/replay/inspections.d.ts.map +1 -0
- package/dist/replay/inspections.js +64 -0
- package/dist/replay/inspections.js.map +1 -0
- package/dist/styles.css +34 -8
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/replay/ReplayDevtools.tsx +42 -19
- package/src/replay/ReplayFile.tsx +16 -3
- package/src/replay/ReplayPlayer.tsx +152 -28
- package/src/replay/devtools.ts +8 -2
- package/src/replay/index.ts +3 -1
- package/src/replay/inspections.ts +105 -0
- package/src/styles.css +34 -8
package/src/replay/index.ts
CHANGED
|
@@ -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
|
-
|
|
4605
|
-
|
|
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);
|