@agent-native/core 0.98.2 → 0.98.4
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 +2 -2
- package/corpus/core/CHANGELOG.md +23 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +2 -2
- package/corpus/core/src/client/analytics.ts +12 -0
- package/corpus/core/src/client/index.ts +2 -0
- package/corpus/core/src/client/session-replay.ts +612 -26
- package/corpus/core/src/client/settings/DemoModeSection.tsx +6 -6
- package/corpus/core/src/client/settings/SettingsPanel.tsx +31 -211
- package/corpus/core/src/client/settings/agent-settings-search.ts +219 -0
- package/corpus/core/src/client/settings/index.ts +4 -0
- package/corpus/core/src/demo/actions/toggle-demo-mode.ts +2 -2
- package/corpus/core/src/demo/config.ts +6 -6
- package/corpus/core/src/demo/fetch-interceptor.ts +25 -2
- package/corpus/core/src/demo/redact.ts +57 -66
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +65 -6
- package/corpus/templates/analytics/AGENTS.md +6 -6
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +429 -244
- package/corpus/templates/analytics/app/components/layout/command-palette-search.ts +93 -0
- package/corpus/templates/analytics/app/global.css +4 -62
- package/corpus/templates/analytics/app/hooks/use-replay-storage-status.ts +2 -1
- package/corpus/templates/analytics/app/lib/demo-chart-trend.ts +46 -33
- package/corpus/templates/analytics/app/pages/Settings.tsx +8 -45
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +413 -105
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +32 -16
- package/corpus/templates/analytics/app/pages/settings/settings-search.ts +126 -0
- package/corpus/templates/analytics/changelog/2026-07-12-command-menu-loading-placeholders-now-stay-below-available-r.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-command-menu-search-now-prioritizes-the-best-match.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-demo-charts-preserve-source-movement.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-fixed-session-replays-that-appeared-ultra-wide-hid-recorded-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-replay-storage-settings-stay-readable-at-narrow-widths.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-session-replays-load-faster-in-demo-mode-while-visitor-email.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-session-replays-preserve-recorded-styling-and-keep-malformed.md +6 -0
- package/corpus/templates/calendar/app/components/calendar/DeleteEventDialog.tsx +11 -2
- package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +14 -2
- package/corpus/templates/calendar/changelog/2026-07-12-calendar-responses-can-now-be-saved-with-command-enter-or-ct.md +6 -0
- package/dist/agent/production-agent.js +2 -2
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +11 -0
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/session-replay.d.ts +12 -3
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +488 -23
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/settings/DemoModeSection.d.ts +3 -3
- package/dist/client/settings/DemoModeSection.js +4 -4
- package/dist/client/settings/DemoModeSection.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +41 -191
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/agent-settings-search.d.ts +16 -0
- package/dist/client/settings/agent-settings-search.d.ts.map +1 -0
- package/dist/client/settings/agent-settings-search.js +178 -0
- package/dist/client/settings/agent-settings-search.js.map +1 -0
- package/dist/client/settings/index.d.ts +1 -0
- package/dist/client/settings/index.d.ts.map +1 -1
- package/dist/client/settings/index.js +1 -0
- package/dist/client/settings/index.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/demo/actions/toggle-demo-mode.js +2 -2
- package/dist/demo/actions/toggle-demo-mode.js.map +1 -1
- package/dist/demo/config.js +6 -6
- package/dist/demo/config.js.map +1 -1
- package/dist/demo/fetch-interceptor.d.ts +1 -0
- package/dist/demo/fetch-interceptor.d.ts.map +1 -1
- package/dist/demo/fetch-interceptor.js +19 -2
- package/dist/demo/fetch-interceptor.js.map +1 -1
- package/dist/demo/redact.d.ts +14 -5
- package/dist/demo/redact.d.ts.map +1 -1
- package/dist/demo/redact.js +30 -56
- package/dist/demo/redact.js.map +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/package.json +2 -2
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure, dependency-free, deterministic demo-mode redactor.
|
|
3
3
|
*
|
|
4
|
-
* Replaces sensitive values (
|
|
4
|
+
* Replaces sensitive values (emails and free numbers) with stable fake
|
|
5
5
|
* substitutes so a demo looks coherent — the same input always maps to the
|
|
6
6
|
* same fake. Crucially, it NEVER rewrites identifiers, structural tokens, or
|
|
7
|
-
* timestamps
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* timestamps. Names and other free text are deliberately left alone because
|
|
8
|
+
* guessing whether arbitrary text is a person's name is too inaccurate. The
|
|
9
|
+
* string redactor uses a protect-first strategy (mask IDs with opaque
|
|
10
|
+
* placeholders before any transform runs, restore them byte-identical
|
|
11
|
+
* afterwards), and the structure-aware walker additionally protects leaf
|
|
12
|
+
* values by key name.
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
export interface RedactOptions {
|
|
14
16
|
salt?: string;
|
|
17
|
+
/** Redact numeric values. Defaults to true for backward compatibility. */
|
|
18
|
+
redactNumbers?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Redact emails stored in otherwise protected structural fields such as
|
|
21
|
+
* `userId` and `userKey`. Intended for display-only frontend responses.
|
|
22
|
+
*/
|
|
23
|
+
redactProtectedEmails?: boolean;
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
/* ------------------------------------------------------------------ *
|
|
@@ -66,7 +75,7 @@ function pick<T>(rng: () => number, pool: readonly T[]): T {
|
|
|
66
75
|
* round-trips back through redaction (e.g. you edit a draft that's
|
|
67
76
|
* already showing fake text, it autosaves, then refetches) it is passed
|
|
68
77
|
* through UNCHANGED instead of being re-faked into something new. This
|
|
69
|
-
* is what stops
|
|
78
|
+
* is what stops emails drifting on every edit.
|
|
70
79
|
*
|
|
71
80
|
* Leak-free by construction: no timers, a hard size cap, and lazy TTL purge
|
|
72
81
|
* on write. Works the same per-tab in the browser and per-process on the
|
|
@@ -242,15 +251,6 @@ const LAST_NAMES: readonly string[] = [
|
|
|
242
251
|
* Fake value generators (deterministic in value + salt)
|
|
243
252
|
* ------------------------------------------------------------------ */
|
|
244
253
|
|
|
245
|
-
function fakeName(original: string, salt: string): string {
|
|
246
|
-
return memoFake("name", original, salt, () => {
|
|
247
|
-
const rng = seededRng(`name:${original}`, salt);
|
|
248
|
-
const first = pick(rng, FIRST_NAMES);
|
|
249
|
-
const last = pick(rng, LAST_NAMES);
|
|
250
|
-
return `${first} ${last}`;
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
|
|
254
254
|
// Realistic-looking but safe stand-in domains: real consumer providers (the
|
|
255
255
|
// local part is fake, so no real mailbox is implied) plus the canonical
|
|
256
256
|
// reserved fictional-company domains (Contoso/Fabrikam/Northwind), which read
|
|
@@ -484,10 +484,6 @@ function unprotect(text: string, restore: Map<string, string>): string {
|
|
|
484
484
|
|
|
485
485
|
const EMAIL_RE = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
486
486
|
|
|
487
|
-
// 2+ capitalized words (each starts uppercase, allows internal lowercase /
|
|
488
|
-
// apostrophes / hyphens). Single-letter middle initials allowed: `Sarah J Connor`.
|
|
489
|
-
const FULL_NAME_RE = /\b[A-Z][a-zA-Z'’-]*(?:\s+[A-Z](?:[a-zA-Z'’-]*)?){1,3}\b/g;
|
|
490
|
-
|
|
491
487
|
// A standalone numeric token: optional currency + sign, digits with optional
|
|
492
488
|
// comma grouping and a single optional decimal part. Bounded so it is NOT
|
|
493
489
|
// adjacent to a letter (placeholders already removed letter-mixed tokens, but
|
|
@@ -499,10 +495,6 @@ function transformEmails(text: string, salt: string): string {
|
|
|
499
495
|
return text.replace(EMAIL_RE, (match) => fakeEmail(match, salt));
|
|
500
496
|
}
|
|
501
497
|
|
|
502
|
-
function transformNames(text: string, salt: string): string {
|
|
503
|
-
return text.replace(FULL_NAME_RE, (match) => fakeName(match, salt));
|
|
504
|
-
}
|
|
505
|
-
|
|
506
498
|
function isProbablyYear(digits: string): boolean {
|
|
507
499
|
if (digits.length !== 4) return false;
|
|
508
500
|
const n = Number(digits);
|
|
@@ -550,20 +542,26 @@ function transformNumbers(text: string, salt: string): string {
|
|
|
550
542
|
function redactDemoStringInternal(
|
|
551
543
|
text: string,
|
|
552
544
|
salt: string,
|
|
553
|
-
|
|
545
|
+
redactNumbers: boolean,
|
|
554
546
|
): string {
|
|
555
547
|
if (typeof text !== "string" || text.length === 0) return text;
|
|
548
|
+
if (!text.includes("@") && (!redactNumbers || !/[\d$€£¥]/.test(text))) {
|
|
549
|
+
return text;
|
|
550
|
+
}
|
|
556
551
|
|
|
557
552
|
const { text: masked, restore } = protect(text);
|
|
558
553
|
let out = masked;
|
|
559
554
|
out = transformEmails(out, salt);
|
|
560
|
-
if (
|
|
561
|
-
out = transformNumbers(out, salt);
|
|
555
|
+
if (redactNumbers) out = transformNumbers(out, salt);
|
|
562
556
|
return unprotect(out, restore);
|
|
563
557
|
}
|
|
564
558
|
|
|
565
559
|
export function redactDemoString(text: string, opts?: RedactOptions): string {
|
|
566
|
-
return redactDemoStringInternal(
|
|
560
|
+
return redactDemoStringInternal(
|
|
561
|
+
text,
|
|
562
|
+
opts?.salt ?? "",
|
|
563
|
+
opts?.redactNumbers !== false,
|
|
564
|
+
);
|
|
567
565
|
}
|
|
568
566
|
|
|
569
567
|
/* ------------------------------------------------------------------ *
|
|
@@ -573,35 +571,14 @@ export function redactDemoString(text: string, opts?: RedactOptions): string {
|
|
|
573
571
|
// Keys whose leaf values must NEVER be transformed (still recurse into nested
|
|
574
572
|
// objects/arrays under them). Besides ids/urls/timestamps this also covers
|
|
575
573
|
// code/query-bearing keys (`sql`, `query`, `expression`, `formula`, `code`):
|
|
576
|
-
//
|
|
577
|
-
//
|
|
578
|
-
//
|
|
579
|
-
// RESULTS are what get redacted so the chart still shows fake values.
|
|
574
|
+
// redacting a SQL string mutates literals and can make the query semantically
|
|
575
|
+
// wrong or invalid. The query must run untouched; its RESULTS are what get
|
|
576
|
+
// redacted so the chart still shows fake values.
|
|
580
577
|
const PROTECTED_KEY_RE =
|
|
581
578
|
/^id$|(^|_)id$|Id$|Ids$|uuid|guid|slug|token|secret|password|passwd|apikey|api_key|hash|sha\d*|etag|cursor|nonce|sessionid|messageid|threadid|nodeid|(^|_)key$|keyid|(^|_)ref$|url$|uri$|href$|src$|path$|filename$|mimetype|mime|^sql$|sql$|query|expression|formula|^code$|createdat|updatedat|deletedat|expiresat|timestamp|.+at$|.+_at$/i;
|
|
582
579
|
|
|
583
|
-
// UI labels and chart/dashboard titles often look like person names ("Maya
|
|
584
|
-
// Davis (First-party)", "Clicks by Henry Moore") but are structural labels. Keep
|
|
585
|
-
// them stable while still redacting free-text and contact-style person fields.
|
|
586
|
-
const LABEL_TEXT_KEY_RE = /^(name|title|label)$/i;
|
|
587
|
-
|
|
588
580
|
const MAX_DEPTH = 64;
|
|
589
581
|
|
|
590
|
-
function isStandalonePersonNameLabel(text: string): boolean {
|
|
591
|
-
const trimmed = text.trim();
|
|
592
|
-
if (!trimmed) return false;
|
|
593
|
-
return /^[A-Z][a-zA-Z'’-]+(?:\s+[A-Z]\.?)?\s+[A-Z][a-zA-Z'’-]+$/.test(
|
|
594
|
-
trimmed,
|
|
595
|
-
);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
function shouldRedactNamesForChild(key: string, entry: unknown): boolean {
|
|
599
|
-
if (!LABEL_TEXT_KEY_RE.test(key)) return true;
|
|
600
|
-
// Preserve structural labels/titles, but do not let common contact-shaped
|
|
601
|
-
// fields like `{ name: "Olivia Parker" }` leak real person names.
|
|
602
|
-
return typeof entry === "string" ? isStandalonePersonNameLabel(entry) : true;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
582
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
606
583
|
if (value === null || typeof value !== "object") return false;
|
|
607
584
|
if (Array.isArray(value)) return false;
|
|
@@ -610,14 +587,6 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
|
610
587
|
return proto === Object.prototype || proto === null;
|
|
611
588
|
}
|
|
612
589
|
|
|
613
|
-
function redactDemoStringWithSalt(
|
|
614
|
-
text: string,
|
|
615
|
-
salt: string,
|
|
616
|
-
redactNames: boolean,
|
|
617
|
-
): string {
|
|
618
|
-
return redactDemoStringInternal(text, salt, redactNames);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
590
|
function redactNumberLeaf(value: number, salt: string): number {
|
|
622
591
|
if (!Number.isFinite(value)) return value;
|
|
623
592
|
const repr = String(value);
|
|
@@ -635,7 +604,8 @@ function walk(
|
|
|
635
604
|
salt: string,
|
|
636
605
|
depth: number,
|
|
637
606
|
seen: WeakSet<object>,
|
|
638
|
-
|
|
607
|
+
redactNumbers: boolean,
|
|
608
|
+
redactProtectedEmails: boolean,
|
|
639
609
|
): unknown {
|
|
640
610
|
if (depth > MAX_DEPTH) return value;
|
|
641
611
|
|
|
@@ -644,11 +614,11 @@ function walk(
|
|
|
644
614
|
const t = typeof value;
|
|
645
615
|
|
|
646
616
|
if (t === "string") {
|
|
647
|
-
return
|
|
617
|
+
return redactDemoStringInternal(value as string, salt, redactNumbers);
|
|
648
618
|
}
|
|
649
619
|
|
|
650
620
|
if (t === "number") {
|
|
651
|
-
return redactNumberLeaf(value as number, salt);
|
|
621
|
+
return redactNumbers ? redactNumberLeaf(value as number, salt) : value;
|
|
652
622
|
}
|
|
653
623
|
|
|
654
624
|
if (t === "boolean" || t === "bigint" || t === "function" || t === "symbol") {
|
|
@@ -661,7 +631,7 @@ function walk(
|
|
|
661
631
|
if (seen.has(value)) return value;
|
|
662
632
|
seen.add(value);
|
|
663
633
|
const out = value.map((item) =>
|
|
664
|
-
walk(item, salt, depth + 1, seen,
|
|
634
|
+
walk(item, salt, depth + 1, seen, redactNumbers, redactProtectedEmails),
|
|
665
635
|
);
|
|
666
636
|
seen.delete(value);
|
|
667
637
|
return out;
|
|
@@ -681,7 +651,20 @@ function walk(
|
|
|
681
651
|
) {
|
|
682
652
|
// Still recurse into nested structures, but the protected key does
|
|
683
653
|
// not transform its own leaf value.
|
|
684
|
-
out[key] = walk(
|
|
654
|
+
out[key] = walk(
|
|
655
|
+
entry,
|
|
656
|
+
salt,
|
|
657
|
+
depth + 1,
|
|
658
|
+
seen,
|
|
659
|
+
redactNumbers,
|
|
660
|
+
redactProtectedEmails,
|
|
661
|
+
);
|
|
662
|
+
} else if (
|
|
663
|
+
redactProtectedEmails &&
|
|
664
|
+
typeof entry === "string" &&
|
|
665
|
+
entry.includes("@")
|
|
666
|
+
) {
|
|
667
|
+
out[key] = redactDemoStringInternal(entry, salt, false);
|
|
685
668
|
} else {
|
|
686
669
|
// Leaf under a protected key: pass through completely untouched.
|
|
687
670
|
out[key] = entry;
|
|
@@ -693,7 +676,8 @@ function walk(
|
|
|
693
676
|
salt,
|
|
694
677
|
depth + 1,
|
|
695
678
|
seen,
|
|
696
|
-
|
|
679
|
+
redactNumbers,
|
|
680
|
+
redactProtectedEmails,
|
|
697
681
|
);
|
|
698
682
|
}
|
|
699
683
|
seen.delete(value);
|
|
@@ -706,7 +690,14 @@ function walk(
|
|
|
706
690
|
|
|
707
691
|
export function redactDemoData<T>(value: T, opts?: RedactOptions): T {
|
|
708
692
|
const salt = opts?.salt ?? "";
|
|
709
|
-
return walk(
|
|
693
|
+
return walk(
|
|
694
|
+
value,
|
|
695
|
+
salt,
|
|
696
|
+
0,
|
|
697
|
+
new WeakSet<object>(),
|
|
698
|
+
opts?.redactNumbers !== false,
|
|
699
|
+
opts?.redactProtectedEmails === true,
|
|
700
|
+
) as T;
|
|
710
701
|
}
|
|
711
702
|
|
|
712
703
|
/**
|
|
@@ -102,12 +102,60 @@ agent answers about browser recordings in the Analytics template.
|
|
|
102
102
|
- Wait for all replay chunks (`isComplete`) before constructing the rrweb
|
|
103
103
|
`Replayer`. Progressive chunk publishes should only update the loading bar;
|
|
104
104
|
rebuilding the player mid-load desyncs the scrubber and playhead.
|
|
105
|
-
- Pass events to `Replayer` untouched. rrweb rebuilds them in a sandboxed
|
|
106
|
-
pre-processing DOM, stylesheet, resource, or mutation payloads makes
|
|
107
|
-
diverge from the captured page.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
105
|
+
- Pass normal events to `Replayer` untouched. rrweb rebuilds them in a sandboxed
|
|
106
|
+
iframe; pre-processing DOM, stylesheet, resource, or mutation payloads makes
|
|
107
|
+
playback diverge from the captured page. In particular, never rewrite `href`, `src`,
|
|
108
|
+
`_cssText`, CSS `url()`, or Meta URLs to `about:blank`; that exact remediation
|
|
109
|
+
broke historical replay CSS in PR #2040. Handle request privacy at capture or
|
|
110
|
+
the sandbox boundary instead of mutating stored rrweb events. Historical
|
|
111
|
+
captures without inlined resources require live stylesheet/image/font
|
|
112
|
+
requests for accurate rendering; the viewer accepts that fidelity tradeoff,
|
|
113
|
+
uses rrweb's script-disabled sandbox plus `referrerpolicy="no-referrer"`, and
|
|
114
|
+
must never add credentials or proxy those URLs through a privileged server.
|
|
115
|
+
- Capture-time URL scrubbing must preserve load-bearing DOM resource attributes:
|
|
116
|
+
`src`, `srcset`, `poster`, `data`, and `href` only on resource links such as
|
|
117
|
+
stylesheets, preloads, and icons. Signed CDN query parameters are part of the
|
|
118
|
+
resource identity; redacting them produces missing CSS, fonts, images, and
|
|
119
|
+
oversized fallback icons. Keep scrubbing Meta/navigation URLs, anchor hrefs,
|
|
120
|
+
and console/network diagnostics. Captured `_cssText` and CSS `@import`/`url()`
|
|
121
|
+
values must remain byte-identical.
|
|
122
|
+
- rrweb rebuilds into an `about:srcdoc` iframe, which inherits the Analytics
|
|
123
|
+
document's CSP. Analytics currently sends no CSP header; if a future change
|
|
124
|
+
adds restrictive `style-src`, `font-src`, or `img-src` directives, verify
|
|
125
|
+
historical replays and resolve external imports/fonts at capture before
|
|
126
|
+
blocking the recorded resource origins. Do not diagnose current font loss as
|
|
127
|
+
CSP without checking the deployed response headers first.
|
|
128
|
+
- Let rrweb own normal iframe sizing via Meta / ViewportResize, and keep the
|
|
129
|
+
outer wrapper in that same coordinate system for fit-to-stage scaling. Some
|
|
130
|
+
legacy recordings contain demonstrably corrupt 4,000–7,500px-wide viewport
|
|
131
|
+
values from ordinary desktop sessions. Only widths of at least 4,000px with
|
|
132
|
+
aspect ratios above 4:1 are recovered to a 16:10 viewport; real 32:9 and
|
|
133
|
+
mobile portrait captures remain untouched. The resize handler applies the
|
|
134
|
+
recovery to **both** rrweb's iframe and the outer stage. This includes the
|
|
135
|
+
historical 3,000–3,999px-wide, sub-1,000px-high malformed shape; do not
|
|
136
|
+
narrow the guard back to only 4,000px+ recordings.
|
|
137
|
+
Never remove this recovery, clamp only one layer, or broadly rewrite event
|
|
138
|
+
geometry: those changes repeatedly recreated the ribbon/clipping regression.
|
|
139
|
+
- The malformed-viewport recovery's sole event-data exception is pointer
|
|
140
|
+
geometry: project MouseMove, TouchMove, Drag, and MouseInteraction x/y into
|
|
141
|
+
the corrected camera and clamp impossible coordinates. Correcting the iframe
|
|
142
|
+
without its paired pointer projection makes rrweb cast valid target ids
|
|
143
|
+
thousands of pixels off-stage. Normal-viewport pointer events must retain
|
|
144
|
+
their original object references and exact coordinates.
|
|
145
|
+
- Keep rrweb's stock cursor stylesheet and its hotspot transform. During
|
|
146
|
+
playback, hide the viewer's native pointer over Analytics' transparent
|
|
147
|
+
click-to-pause overlay so it cannot masquerade as a frozen recorded cursor.
|
|
148
|
+
- Keep rrweb's recorded focus handling enabled. Focus and focus-visible state
|
|
149
|
+
affect menus, forms, and keyboard UX; disabling `triggerFocus` makes a valid
|
|
150
|
+
snapshot diverge from the source page.
|
|
151
|
+
- `insertStyleRules` may suppress known toast/snackbar containers only. Never
|
|
152
|
+
hide generic framework primitives such as
|
|
153
|
+
`[data-radix-popper-content-wrapper]`: Radix dropdowns, selects, tooltips,
|
|
154
|
+
and other real recorded product UI all share that wrapper.
|
|
155
|
+
- Keep the realistic fidelity and malformed-viewport tests in
|
|
156
|
+
`SessionDetailPage.spec.ts`. Do not change their expectations merely to bless
|
|
157
|
+
a new sanitizer or raw ultra-wide sizing; validate the affected replay in a
|
|
158
|
+
browser first.
|
|
111
159
|
- The event timeline soft-highlights the active marker, auto-scrolls it into
|
|
112
160
|
view (pausing briefly after manual scroll), and supports search. It appears
|
|
113
161
|
beside the player from ~880px content width upward.
|
|
@@ -144,3 +192,14 @@ agent answers about browser recordings in the Analytics template.
|
|
|
144
192
|
`.an-mask` or `data-an-mask`.
|
|
145
193
|
- Use `.an-block`, `.an-ignore`, `data-an-block`, or `data-an-ignore` for
|
|
146
194
|
sensitive zones that should not be captured.
|
|
195
|
+
- A definitive upload `409` abandons only the conflicted replay identity and
|
|
196
|
+
immediately starts rrweb again under a fresh per-tab id, producing a new
|
|
197
|
+
Meta + FullSnapshot for long-lived SPA tabs. Recovery is limited to one
|
|
198
|
+
restart until an upload succeeds so a misconfigured endpoint cannot loop;
|
|
199
|
+
Analytics tracks the content-free `session replay upload rejected` lifecycle
|
|
200
|
+
event so conflicts and recovery success are measurable.
|
|
201
|
+
- Do not label an old recording "corrupt" from pointer coordinates, unknown
|
|
202
|
+
mutation node ids, or changing Meta geometry alone. Those shapes can be
|
|
203
|
+
legitimate with scrolling, iframes/shadow DOM, navigation, and resize. A
|
|
204
|
+
historical-artifact notice needs a durable capture/ingest marker or another
|
|
205
|
+
low-false-positive invariant; do not guess from playback heuristics.
|
|
@@ -41,12 +41,12 @@ details live in `.agents/skills/`.
|
|
|
41
41
|
unless the user explicitly asks to inspect the demo dashboard.
|
|
42
42
|
- Framework Demo mode is separate from the built-in `demo` source. While Demo
|
|
43
43
|
mode is enabled, Analytics line and area charts reshape each returned numeric
|
|
44
|
-
series into a deterministic
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
query results, tables, metrics,
|
|
48
|
-
unchanged, so do not cite the
|
|
49
|
-
retrieved source values.
|
|
44
|
+
series into a deterministic upward trend using that series' actual minimum,
|
|
45
|
+
maximum, and local step pattern. Source spikes and dips remain at the same
|
|
46
|
+
x-positions while the smallest necessary linear drift moves the series up and
|
|
47
|
+
to the right. This is presentation-only: query results, tables, metrics,
|
|
48
|
+
exports, and non-time-series charts remain unchanged, so do not cite the
|
|
49
|
+
displayed intermediate line/area points as retrieved source values.
|
|
50
50
|
- Every analytical answer should include enough audit context for the user to
|
|
51
51
|
trust it: source(s), time window, filters, sample size or row count,
|
|
52
52
|
join/match method when relevant, and caveats/gaps.
|