@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/CHANGELOG.md +10 -0
- package/corpus/templates/calendar/CHANGELOG.md +39 -0
- package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +2 -0
- package/corpus/templates/clips/CHANGELOG.md +19 -0
- package/corpus/templates/clips/actions/save-browser-diagnostics.ts +43 -4
- package/corpus/templates/clips/app/components/player/browser-diagnostics-panel.tsx +114 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +7 -0
- package/corpus/templates/clips/app/lib/browser-diagnostics-capture.ts +108 -0
- package/corpus/templates/clips/app/routes/record.tsx +1 -0
- package/corpus/templates/clips/changelog/2026-09-15-recordings-include-a-structured-timeline-of-browser-interact.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +7 -0
- package/corpus/templates/clips/chrome-extension/src/background.ts +235 -9
- package/corpus/templates/clips/chrome-extension/src/content-history-bridge.ts +80 -0
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +166 -5
- package/corpus/templates/clips/chrome-extension/vite.config.ts +4 -0
- package/corpus/templates/clips/server/db/schema.ts +3 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +40 -2
- package/corpus/templates/clips/server/plugins/db.ts +5 -0
- package/corpus/templates/clips/shared/browser-diagnostics.ts +171 -0
- package/corpus/templates/content/.agents/skills/content/SKILL.md +4 -2
- package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +8 -1
- package/corpus/templates/content/AGENTS.md +5 -6
- package/corpus/templates/content/CHANGELOG.md +24 -0
- package/corpus/templates/content/actions/_document-edit-mutation.ts +74 -23
- package/corpus/templates/content/actions/edit-document.ts +48 -8
- package/corpus/templates/content/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/design/CHANGELOG.md +22 -0
- package/corpus/templates/factory/CHANGELOG.md +11 -0
- package/corpus/templates/forms/CHANGELOG.md +6 -0
- package/corpus/templates/mail/CHANGELOG.md +68 -0
- package/corpus/templates/slides/CHANGELOG.md +32 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/client/CommandMenu.js +14 -2
- package/dist/client/settings/AccountSettingsCard.js +3 -2
- package/dist/client/settings/SettingsTabsPage.js +9 -1
- package/dist/client/settings/agent-settings-search.js +5 -1
- package/dist/client/sign-out.d.ts +1 -0
- package/dist/client/sign-out.js +8 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/core-routes-plugin.d.ts +1 -1
- package/package.json +4 -4
package/corpus/README.md
CHANGED
|
@@ -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
|
|
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 =
|
|
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
|
|
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) {
|
|
@@ -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"],
|