@agent-native/core 0.180.0-nightly-20260915193344 → 0.180.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 (49) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/CHANGELOG.md +10 -0
  3. package/corpus/templates/calendar/CHANGELOG.md +39 -0
  4. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +2 -0
  5. package/corpus/templates/clips/CHANGELOG.md +19 -0
  6. package/corpus/templates/clips/actions/save-browser-diagnostics.ts +43 -4
  7. package/corpus/templates/clips/app/components/player/browser-diagnostics-panel.tsx +114 -1
  8. package/corpus/templates/clips/app/i18n/en-US.ts +7 -0
  9. package/corpus/templates/clips/app/lib/browser-diagnostics-capture.ts +108 -0
  10. package/corpus/templates/clips/app/routes/record.tsx +1 -0
  11. package/corpus/templates/clips/changelog/2026-09-15-recordings-include-a-structured-timeline-of-browser-interact.md +6 -0
  12. package/corpus/templates/clips/chrome-extension/public/manifest.json +7 -0
  13. package/corpus/templates/clips/chrome-extension/src/background.ts +235 -9
  14. package/corpus/templates/clips/chrome-extension/src/content-history-bridge.ts +80 -0
  15. package/corpus/templates/clips/chrome-extension/src/content-script.ts +166 -5
  16. package/corpus/templates/clips/chrome-extension/vite.config.ts +4 -0
  17. package/corpus/templates/clips/server/db/schema.ts +3 -0
  18. package/corpus/templates/clips/server/lib/public-agent-context.ts +40 -2
  19. package/corpus/templates/clips/server/plugins/db.ts +5 -0
  20. package/corpus/templates/clips/shared/browser-diagnostics.ts +171 -0
  21. package/corpus/templates/content/.agents/skills/content/SKILL.md +4 -2
  22. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +8 -1
  23. package/corpus/templates/content/AGENTS.md +5 -6
  24. package/corpus/templates/content/CHANGELOG.md +24 -0
  25. package/corpus/templates/content/actions/_document-edit-mutation.ts +74 -23
  26. package/corpus/templates/content/actions/edit-document.ts +48 -8
  27. package/corpus/templates/content/server/plugins/agent-chat.ts +1 -1
  28. package/corpus/templates/design/CHANGELOG.md +22 -0
  29. package/corpus/templates/factory/CHANGELOG.md +11 -0
  30. package/corpus/templates/forms/CHANGELOG.md +6 -0
  31. package/corpus/templates/mail/CHANGELOG.md +68 -0
  32. package/corpus/templates/slides/CHANGELOG.md +32 -0
  33. package/dist/automation/index.d.ts +1 -1
  34. package/dist/client/CommandMenu.js +14 -2
  35. package/dist/client/settings/AccountSettingsCard.js +3 -2
  36. package/dist/client/settings/SettingsTabsPage.js +9 -1
  37. package/dist/client/settings/agent-settings-search.js +5 -1
  38. package/dist/client/sign-out.d.ts +1 -0
  39. package/dist/client/sign-out.js +8 -0
  40. package/dist/collab/awareness.d.ts +2 -2
  41. package/dist/collab/routes.d.ts +1 -1
  42. package/dist/collab/struct-routes.d.ts +1 -1
  43. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  44. package/dist/notifications/routes.d.ts +3 -3
  45. package/dist/observability/routes.d.ts +3 -3
  46. package/dist/progress/routes.d.ts +1 -1
  47. package/dist/secrets/routes.d.ts +6 -6
  48. package/dist/server/core-routes-plugin.d.ts +1 -1
  49. package/package.json +4 -4
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 9482
34
+ - template files: 9484
@@ -3,6 +3,16 @@
3
3
  All notable user-facing changes to Agent-Native Analytics are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-09-14
7
+
8
+ ### Improved
9
+
10
+ - Sidebar and session filter buttons now show when a filter is narrowing the list.
11
+
12
+ ### Changed
13
+
14
+ - Creative Context is now an opt-in Lab, off by default.
15
+
6
16
  ## 2026-09-12
7
17
 
8
18
  ### Improved
@@ -3,6 +3,45 @@
3
3
  All notable user-facing changes to Agent-Native Calendar are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-09-15
7
+
8
+ ### Improved
9
+
10
+ - Calendar mirrors Notion Calendar's customizable day ranges and view settings
11
+
12
+ ### Fixed
13
+
14
+ - Calendar shortcut help renders each command row without duplicate React keys
15
+ - Calendar treats already-deleted Google events as absent during cleanup.
16
+
17
+ ## 2026-09-14
18
+
19
+ ### Security
20
+
21
+ - Calendar requests only the permissions needed for Calendar and account identity
22
+
23
+ ## 2026-09-13
24
+
25
+ ### Improved
26
+
27
+ - Calendar sidebar overflow is easier to discover with visible scroll controls and edge cues.
28
+
29
+ ### Fixed
30
+
31
+ - Event mutations stay scoped to the selected calendar when provider IDs collide.
32
+ - Reset event detail drafts when switching between same-ID calendar sources
33
+
34
+ ## 2026-09-12
35
+
36
+ ### Fixed
37
+
38
+ - Deleting a recurring event no longer hides matching events from other calendars.
39
+ - Fixed calendar shortcuts and event interactions for more reliable location suggestions, drag confirmations, and recurring-event deletion.
40
+ - Prevent optimistic calendar updates and rollback from crossing accounts when provider event IDs collide.
41
+ - Primary Google recurring RSVP and deletion actions now update account-scoped cached occurrences.
42
+ - Selected events stay bound to the correct calendar when event IDs collide.
43
+ - Week navigation now opens on the configured first day of the destination week.
44
+
6
45
  ## 2026-09-10
7
46
 
8
47
  ### Fixed
@@ -228,6 +228,8 @@ temporary agent-link path:
228
228
  raw provider URLs.
229
229
 
230
230
  Public agent context also exposes the recording's redacted browser diagnostics:
231
+ the bounded relative event sequence as `browserDiagnostics.timeline`, including
232
+ navigation, click/input targets, console events, and request/response markers;
231
233
  the console stream (all levels) as `browserDiagnostics.consoleLogs` and the
232
234
  fetch/XHR stream as `browserDiagnostics.networkRequests` (method, sanitized URL
233
235
  with query values redacted, status, duration), plus `consoleIssues` and
@@ -3,6 +3,25 @@
3
3
  All notable user-facing changes to Clips are documented here. Open it any time
4
4
  from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-09-15
7
+
8
+ ### Improved
9
+
10
+ - Trash now supports paging through more than 100 deleted recordings
11
+
12
+ ### Fixed
13
+
14
+ - Fixed the volume slider knob alignment in shared recordings
15
+ - Shared Clips now focus the agent when asked, show clear viewer-safe transcript states, and keep workspace branding visible.
16
+
17
+ ## 2026-09-14
18
+
19
+ ### Fixed
20
+
21
+ - Comment edits and reactions stay consistent during rapid updates.
22
+ - Comment threads keep overlapping replies, reactions, and deletions consistent.
23
+ - Selected recording checkboxes remain visible while selecting multiple clips
24
+
6
25
  ## 2026-09-13
7
26
 
8
27
  ### Improved
@@ -2,8 +2,8 @@
2
2
  * Save redacted browser diagnostics captured during a recording session.
3
3
  *
4
4
  * Called by the recorder UI after stop/finalize. Diagnostics are intentionally
5
- * bounded and body/header-free: console text plus method/path/status/duration
6
- * for fetch/XHR requests.
5
+ * bounded and body/header-free: interaction markers, console text, plus
6
+ * method/path/status/duration for fetch/XHR requests.
7
7
  */
8
8
 
9
9
  import { defineAction } from "@agent-native/core/action";
@@ -16,15 +16,18 @@ import { getDb, schema } from "../server/db/index.js";
16
16
  import { nanoid } from "../server/lib/recordings.js";
17
17
  import {
18
18
  MAX_BROWSER_DIAGNOSTIC_CONSOLE_LOGS,
19
+ MAX_BROWSER_DIAGNOSTIC_INTERACTION_EVENTS,
19
20
  MAX_BROWSER_DIAGNOSTIC_MESSAGE_LENGTH,
20
21
  MAX_BROWSER_DIAGNOSTIC_NETWORK_REQUESTS,
22
+ MAX_BROWSER_DIAGNOSTIC_TARGET_LENGTH,
21
23
  MAX_BROWSER_DIAGNOSTIC_URL_LENGTH,
22
24
  redactBrowserDiagnosticString,
25
+ sanitizeBrowserDiagnosticNavigationUrl,
23
26
  summarizeBrowserDiagnostics,
24
27
  type BrowserDiagnosticConsoleLevel,
25
28
  } from "../shared/browser-diagnostics.js";
26
29
 
27
- const REDACTION_VERSION = 2;
30
+ const REDACTION_VERSION = 3;
28
31
 
29
32
  const consoleLevelSchema = z.enum(["debug", "log", "info", "warn", "error"]);
30
33
 
@@ -49,6 +52,14 @@ const networkRequestSchema = z.object({
49
52
  error: z.string().max(20_000).optional(),
50
53
  });
51
54
 
55
+ const interactionEventSchema = z.object({
56
+ timestampMs: z.number().finite().nonnegative(),
57
+ elapsedMs: z.number().finite().nonnegative(),
58
+ kind: z.enum(["navigation", "click", "input", "scroll"]),
59
+ target: z.string().max(MAX_BROWSER_DIAGNOSTIC_TARGET_LENGTH).optional(),
60
+ url: z.string().max(8_000).optional(),
61
+ });
62
+
52
63
  function truncate(value: string, maxLength: number): string {
53
64
  return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
54
65
  }
@@ -141,9 +152,29 @@ function sanitizeNetworkRequest(entry: z.infer<typeof networkRequestSchema>) {
141
152
  };
142
153
  }
143
154
 
155
+ function sanitizeInteractionEvent(
156
+ entry: z.infer<typeof interactionEventSchema>,
157
+ ) {
158
+ const target = entry.target
159
+ ? truncate(
160
+ redactString(entry.target, { redactQueryValues: true }),
161
+ MAX_BROWSER_DIAGNOSTIC_TARGET_LENGTH,
162
+ )
163
+ : "";
164
+ return {
165
+ timestampMs: Math.round(entry.timestampMs),
166
+ elapsedMs: Math.round(entry.elapsedMs),
167
+ kind: entry.kind,
168
+ ...(target ? { target } : {}),
169
+ ...(entry.url
170
+ ? { url: sanitizeBrowserDiagnosticNavigationUrl(entry.url) }
171
+ : {}),
172
+ };
173
+ }
174
+
144
175
  export default defineAction({
145
176
  description:
146
- "Save redacted console and network diagnostics captured during a Clips recording session. UI/internal use only.",
177
+ "Save redacted browser timeline, console, and network diagnostics captured during a Clips recording session. UI/internal use only.",
147
178
  agentTool: false,
148
179
  schema: z.object({
149
180
  recordingId: z.string().describe("Recording ID"),
@@ -164,6 +195,10 @@ export default defineAction({
164
195
  .array(networkRequestSchema)
165
196
  .max(MAX_BROWSER_DIAGNOSTIC_NETWORK_REQUESTS)
166
197
  .default([]),
198
+ interactionEvents: z
199
+ .array(interactionEventSchema)
200
+ .max(MAX_BROWSER_DIAGNOSTIC_INTERACTION_EVENTS)
201
+ .default([]),
167
202
  }),
168
203
  run: async (args) => {
169
204
  const access = await assertAccess("recording", args.recordingId, "editor");
@@ -178,6 +213,9 @@ export default defineAction({
178
213
  const networkRequests = args.networkRequests
179
214
  .slice(-MAX_BROWSER_DIAGNOSTIC_NETWORK_REQUESTS)
180
215
  .map(sanitizeNetworkRequest);
216
+ const interactionEvents = args.interactionEvents
217
+ .slice(-MAX_BROWSER_DIAGNOSTIC_INTERACTION_EVENTS)
218
+ .map(sanitizeInteractionEvent);
181
219
  const summary = summarizeBrowserDiagnostics({
182
220
  consoleLogs,
183
221
  networkRequests,
@@ -199,6 +237,7 @@ export default defineAction({
199
237
  endedAt,
200
238
  consoleLogsJson: JSON.stringify(consoleLogs),
201
239
  networkRequestsJson: JSON.stringify(networkRequests),
240
+ interactionEventsJson: JSON.stringify(interactionEvents),
202
241
  redactionVersion: REDACTION_VERSION,
203
242
  updatedAt: now,
204
243
  };
@@ -2,6 +2,7 @@ import { useT } from "@agent-native/core/client/i18n";
2
2
  import type {
3
3
  BrowserDiagnosticConsoleLog,
4
4
  BrowserDiagnosticNetworkRequest,
5
+ BrowserDiagnosticTimelineEvent,
5
6
  BrowserDiagnosticsData,
6
7
  } from "@shared/browser-diagnostics";
7
8
  import {
@@ -437,6 +438,106 @@ function NetworkGroupRow({
437
438
  );
438
439
  }
439
440
 
441
+ function timelineKindLabel(
442
+ t: ReturnType<typeof useT>,
443
+ event: BrowserDiagnosticTimelineEvent,
444
+ ): string {
445
+ if (event.kind === "console") return t("browserDiagnostics.consoleSource");
446
+ if (event.kind === "network") {
447
+ return event.phase === "request"
448
+ ? t("browserDiagnostics.requestStarted")
449
+ : t("browserDiagnostics.responseReceived");
450
+ }
451
+ if (event.kind === "navigation") return t("browserDiagnostics.navigation");
452
+ if (event.kind === "click") return t("browserDiagnostics.click");
453
+ if (event.kind === "input") return t("browserDiagnostics.input");
454
+ return t("browserDiagnostics.scroll");
455
+ }
456
+
457
+ function timelineDetail(event: BrowserDiagnosticTimelineEvent): string {
458
+ if (event.kind === "console") {
459
+ return `${event.level}: ${event.message.split("\n", 1)[0]}`;
460
+ }
461
+ if (event.kind === "network") {
462
+ const status = event.status ?? (event.error ? "ERR" : "");
463
+ return `${event.method} ${shortUrl(event.url)}${status ? ` · ${status}` : ""}`;
464
+ }
465
+ return event.url ?? event.target ?? event.kind;
466
+ }
467
+
468
+ function BrowserDiagnosticsTimelineRow({
469
+ event,
470
+ durationMs,
471
+ onSeek,
472
+ }: {
473
+ event: BrowserDiagnosticTimelineEvent;
474
+ durationMs: number;
475
+ onSeek: (ms: number) => void;
476
+ }) {
477
+ const t = useT();
478
+ return (
479
+ <div
480
+ className="flex min-h-9 items-center gap-2 border-b border-border/70 px-4 py-1.5 last:border-b-0"
481
+ data-browser-diagnostic-timeline-row
482
+ >
483
+ <span className="grid size-5 shrink-0 place-items-center rounded-md border border-border text-muted-foreground">
484
+ <IconInfoCircle className="size-3" aria-hidden="true" />
485
+ </span>
486
+ <span className="flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden">
487
+ <span className="shrink-0 text-[11px] font-medium text-muted-foreground">
488
+ {timelineKindLabel(t, event)}
489
+ </span>
490
+ <span className="min-w-0 truncate font-mono text-xs">
491
+ {timelineDetail(event)}
492
+ </span>
493
+ </span>
494
+ <DiagnosticTimeButton
495
+ elapsedMs={event.elapsedMs}
496
+ durationMs={durationMs}
497
+ onSeek={onSeek}
498
+ />
499
+ </div>
500
+ );
501
+ }
502
+
503
+ function BrowserDiagnosticsTimelineSection({
504
+ events,
505
+ durationMs,
506
+ onSeek,
507
+ }: {
508
+ events: BrowserDiagnosticTimelineEvent[];
509
+ durationMs: number;
510
+ onSeek: (ms: number) => void;
511
+ }) {
512
+ const t = useT();
513
+ return (
514
+ <AccordionItem
515
+ value="timeline"
516
+ className="border-border/70"
517
+ data-browser-diagnostics-section="timeline"
518
+ >
519
+ <AccordionTrigger className="min-h-10 px-4 py-2 text-xs hover:no-underline [&>svg]:size-3.5">
520
+ <span className="flex min-w-0 items-center gap-2">
521
+ <span className="truncate">{t("browserDiagnostics.timeline")}</span>
522
+ <span className="rounded-md border border-border px-1.5 py-px font-mono text-[10px] font-normal tabular-nums text-muted-foreground">
523
+ {events.length}
524
+ </span>
525
+ </span>
526
+ </AccordionTrigger>
527
+ <AccordionContent className="pb-0">
528
+ {events.map((event, index) => (
529
+ <BrowserDiagnosticsTimelineRow
530
+ key={`${event.elapsedMs}-${event.kind}-${index}`}
531
+ event={event}
532
+ durationMs={durationMs}
533
+ onSeek={onSeek}
534
+ />
535
+ ))}
536
+ </AccordionContent>
537
+ </AccordionItem>
538
+ );
539
+ }
540
+
440
541
  function BrowserDiagnosticsSection({
441
542
  view,
442
543
  label,
@@ -550,6 +651,7 @@ export function BrowserDiagnosticsPanel({
550
651
  }),
551
652
  [diagnostics],
552
653
  );
654
+ const timeline = diagnostics.timeline ?? [];
553
655
  const source = captureSource(diagnostics);
554
656
  const hasFailures =
555
657
  diagnostics.summary.consoleErrorCount > 0 ||
@@ -622,7 +724,18 @@ export function BrowserDiagnosticsPanel({
622
724
  </div>
623
725
 
624
726
  <div className="min-h-0 flex-1 overflow-y-auto">
625
- <Accordion type="single" defaultValue="issues" collapsible>
727
+ <Accordion
728
+ type="single"
729
+ defaultValue={timeline.length ? "timeline" : "issues"}
730
+ collapsible
731
+ >
732
+ {timeline.length ? (
733
+ <BrowserDiagnosticsTimelineSection
734
+ events={timeline}
735
+ durationMs={durationMs}
736
+ onSeek={onSeek}
737
+ />
738
+ ) : null}
626
739
  {sections.map((section) => (
627
740
  <BrowserDiagnosticsSection
628
741
  key={section.id}
@@ -1827,6 +1827,13 @@ const messages = {
1827
1827
  capturedFrom: "Captured from {{source}}",
1828
1828
  browserCapture: "Browser capture",
1829
1829
  views: "Diagnostic views",
1830
+ timeline: "Timeline",
1831
+ navigation: "Navigation",
1832
+ click: "Click",
1833
+ input: "Input",
1834
+ scroll: "Scroll",
1835
+ requestStarted: "Request started",
1836
+ responseReceived: "Response received",
1830
1837
  issues: "Issues",
1831
1838
  consoleSource: "Console",
1832
1839
  networkSource: "Network",
@@ -1,12 +1,18 @@
1
1
  import {
2
2
  MAX_BROWSER_DIAGNOSTIC_CONSOLE_LOGS,
3
+ MAX_BROWSER_DIAGNOSTIC_INTERACTION_EVENTS,
3
4
  MAX_BROWSER_DIAGNOSTIC_MESSAGE_LENGTH,
4
5
  MAX_BROWSER_DIAGNOSTIC_NETWORK_REQUESTS,
6
+ MAX_BROWSER_DIAGNOSTIC_TARGET_LENGTH,
5
7
  MAX_BROWSER_DIAGNOSTIC_URL_LENGTH,
8
+ buildBrowserDiagnosticTimeline,
6
9
  redactBrowserDiagnosticString,
10
+ sanitizeBrowserDiagnosticNavigationUrl,
7
11
  summarizeBrowserDiagnostics,
8
12
  type BrowserDiagnosticConsoleLevel,
9
13
  type BrowserDiagnosticConsoleLog,
14
+ type BrowserDiagnosticInteractionEvent,
15
+ type BrowserDiagnosticInteractionKind,
10
16
  type BrowserDiagnosticNetworkRequest,
11
17
  type BrowserDiagnosticsData,
12
18
  } from "@shared/browser-diagnostics";
@@ -83,12 +89,40 @@ function requestMethod(input: RequestInfo | URL, init?: RequestInit): string {
83
89
  ).toUpperCase();
84
90
  }
85
91
 
92
+ function safeTargetPart(value: string | null): string | null {
93
+ if (!value || value.length > MAX_BROWSER_DIAGNOSTIC_TARGET_LENGTH) {
94
+ return null;
95
+ }
96
+ return /^[A-Za-z0-9_.:-]+$/.test(value) ? value : null;
97
+ }
98
+
99
+ function describeTarget(target: EventTarget | null): string | null {
100
+ if (!(target instanceof Element)) return null;
101
+ const element = target.closest(
102
+ "button,a,input,textarea,select,[role=button]",
103
+ ) as Element | null;
104
+ const candidate = element ?? target;
105
+ const tag = candidate.tagName.toLowerCase();
106
+ const id = safeTargetPart(candidate.id);
107
+ const testId = safeTargetPart(candidate.getAttribute("data-testid"));
108
+ const name = safeTargetPart(candidate.getAttribute("name"));
109
+ const selector = id
110
+ ? `#${id}`
111
+ : testId
112
+ ? `[data-testid=${testId}]`
113
+ : name
114
+ ? `[name=${name}]`
115
+ : "";
116
+ return truncate(`${tag}${selector}`, MAX_BROWSER_DIAGNOSTIC_TARGET_LENGTH);
117
+ }
118
+
86
119
  export function createBrowserDiagnosticsCapture(): BrowserDiagnosticsCapture {
87
120
  const startedAt = new Date().toISOString();
88
121
  const startedAtMs = Date.now();
89
122
  const startedAtPerf = performance.now();
90
123
  const consoleLogs: BrowserDiagnosticConsoleLog[] = [];
91
124
  const networkRequests: BrowserDiagnosticNetworkRequest[] = [];
125
+ const interactionEvents: BrowserDiagnosticInteractionEvent[] = [];
92
126
  const originalConsole = {
93
127
  debug: console.debug,
94
128
  log: console.log,
@@ -115,6 +149,25 @@ export function createBrowserDiagnosticsCapture(): BrowserDiagnosticsCapture {
115
149
  Math.max(0, Math.round(performance.now() - startedAtPerf));
116
150
  const timestamp = () => startedAtMs + elapsed();
117
151
 
152
+ const pushInteraction = (
153
+ kind: BrowserDiagnosticInteractionKind,
154
+ target?: EventTarget | null,
155
+ url?: string,
156
+ ) => {
157
+ if (!active) return;
158
+ if (interactionEvents.length >= MAX_BROWSER_DIAGNOSTIC_INTERACTION_EVENTS) {
159
+ interactionEvents.shift();
160
+ }
161
+ const targetDescription = describeTarget(target ?? null);
162
+ interactionEvents.push({
163
+ timestampMs: timestamp(),
164
+ elapsedMs: elapsed(),
165
+ kind,
166
+ ...(targetDescription ? { target: targetDescription } : {}),
167
+ ...(url ? { url: sanitizeBrowserDiagnosticNavigationUrl(url) } : {}),
168
+ });
169
+ };
170
+
118
171
  const pushConsole = (
119
172
  level: BrowserDiagnosticConsoleLevel,
120
173
  args: unknown[],
@@ -305,6 +358,48 @@ export function createBrowserDiagnosticsCapture(): BrowserDiagnosticsCapture {
305
358
  window.addEventListener("error", onError);
306
359
  window.addEventListener("unhandledrejection", onUnhandledRejection);
307
360
 
361
+ const onClick = (event: MouseEvent) => pushInteraction("click", event.target);
362
+ const onInput = (event: Event) => pushInteraction("input", event.target);
363
+ let lastScrollElapsed = -Infinity;
364
+ const onScroll = (event: Event) => {
365
+ const currentElapsed = elapsed();
366
+ if (currentElapsed - lastScrollElapsed < 250) return;
367
+ lastScrollElapsed = currentElapsed;
368
+ pushInteraction("scroll", event.target);
369
+ };
370
+ const onNavigation = () =>
371
+ pushInteraction("navigation", null, window.location.href);
372
+ const originalPushState = history.pushState;
373
+ const originalReplaceState = history.replaceState;
374
+ const patchedPushState = function patchedPushState(
375
+ this: History,
376
+ state: unknown,
377
+ unused: string,
378
+ url?: string | URL | null,
379
+ ) {
380
+ const result = originalPushState.call(this, state, unused, url);
381
+ onNavigation();
382
+ return result;
383
+ };
384
+ const patchedReplaceState = function patchedReplaceState(
385
+ this: History,
386
+ state: unknown,
387
+ unused: string,
388
+ url?: string | URL | null,
389
+ ) {
390
+ const result = originalReplaceState.call(this, state, unused, url);
391
+ onNavigation();
392
+ return result;
393
+ };
394
+ history.pushState = patchedPushState;
395
+ history.replaceState = patchedReplaceState;
396
+ document.addEventListener("click", onClick, true);
397
+ document.addEventListener("input", onInput, true);
398
+ document.addEventListener("scroll", onScroll, true);
399
+ window.addEventListener("popstate", onNavigation);
400
+ window.addEventListener("hashchange", onNavigation);
401
+ pushInteraction("navigation", null, window.location.href);
402
+
308
403
  const restore = () => {
309
404
  window.fetch = originalFetch;
310
405
  for (const level of ["debug", "log", "info", "warn", "error"] as const) {
@@ -314,6 +409,17 @@ export function createBrowserDiagnosticsCapture(): BrowserDiagnosticsCapture {
314
409
  XMLHttpRequest.prototype.send = originalXhrSend;
315
410
  window.removeEventListener("error", onError);
316
411
  window.removeEventListener("unhandledrejection", onUnhandledRejection);
412
+ document.removeEventListener("click", onClick, true);
413
+ document.removeEventListener("input", onInput, true);
414
+ document.removeEventListener("scroll", onScroll, true);
415
+ window.removeEventListener("popstate", onNavigation);
416
+ window.removeEventListener("hashchange", onNavigation);
417
+ if (history.pushState === patchedPushState) {
418
+ history.pushState = originalPushState;
419
+ }
420
+ if (history.replaceState === patchedReplaceState) {
421
+ history.replaceState = originalReplaceState;
422
+ }
317
423
  };
318
424
 
319
425
  const stop = () => {
@@ -331,10 +437,12 @@ export function createBrowserDiagnosticsCapture(): BrowserDiagnosticsCapture {
331
437
  endedAt,
332
438
  consoleLogs: [...consoleLogs],
333
439
  networkRequests: [...networkRequests],
440
+ interactionEvents: [...interactionEvents],
334
441
  };
335
442
  stoppedSnapshot = {
336
443
  ...snapshot,
337
444
  summary: summarizeBrowserDiagnostics(snapshot),
445
+ timeline: buildBrowserDiagnosticTimeline(snapshot),
338
446
  };
339
447
  return stoppedSnapshot;
340
448
  };
@@ -2140,6 +2140,7 @@ export default function RecordRoute() {
2140
2140
  endedAt: snapshot.endedAt,
2141
2141
  consoleLogs: snapshot.consoleLogs,
2142
2142
  networkRequests: snapshot.networkRequests,
2143
+ interactionEvents: snapshot.interactionEvents,
2143
2144
  } as any,
2144
2145
  );
2145
2146
  } catch (err) {
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-09-15
4
+ ---
5
+
6
+ Recordings include a structured timeline of browser interactions and diagnostics
@@ -46,6 +46,13 @@
46
46
  "run_at": "document_idle",
47
47
  "all_frames": false
48
48
  },
49
+ {
50
+ "matches": ["<all_urls>"],
51
+ "js": ["assets/content-history-bridge.js"],
52
+ "run_at": "document_start",
53
+ "world": "MAIN",
54
+ "all_frames": false
55
+ },
49
56
  {
50
57
  "matches": ["<all_urls>"],
51
58
  "js": ["assets/content-script.js"],