@agent-native/core 0.98.1 → 0.98.3

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 (165) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +18 -0
  3. package/corpus/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +25 -1
  4. package/corpus/core/docs/content/locales/ar-SA/template-plan.mdx +1 -1
  5. package/corpus/core/docs/content/locales/de-DE/pr-visual-recap.mdx +25 -1
  6. package/corpus/core/docs/content/locales/de-DE/template-plan.mdx +1 -1
  7. package/corpus/core/docs/content/locales/es-ES/pr-visual-recap.mdx +25 -1
  8. package/corpus/core/docs/content/locales/es-ES/template-plan.mdx +1 -1
  9. package/corpus/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +25 -1
  10. package/corpus/core/docs/content/locales/fr-FR/template-plan.mdx +1 -1
  11. package/corpus/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +25 -1
  12. package/corpus/core/docs/content/locales/hi-IN/template-plan.mdx +1 -1
  13. package/corpus/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +25 -1
  14. package/corpus/core/docs/content/locales/ja-JP/template-plan.mdx +1 -1
  15. package/corpus/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +25 -1
  16. package/corpus/core/docs/content/locales/ko-KR/template-plan.mdx +1 -1
  17. package/corpus/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +25 -1
  18. package/corpus/core/docs/content/locales/pt-BR/template-plan.mdx +1 -1
  19. package/corpus/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +25 -1
  20. package/corpus/core/docs/content/locales/zh-CN/template-plan.mdx +1 -1
  21. package/corpus/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +25 -1
  22. package/corpus/core/docs/content/locales/zh-TW/template-plan.mdx +1 -1
  23. package/corpus/core/docs/content/pr-visual-recap.mdx +50 -1
  24. package/corpus/core/docs/content/template-plan.mdx +1 -1
  25. package/corpus/core/package.json +1 -1
  26. package/corpus/core/src/agent/production-agent.ts +2 -2
  27. package/corpus/core/src/cli/plan-local.ts +43 -11
  28. package/corpus/core/src/cli/pr-visual-recap-workflow.ts +2 -9
  29. package/corpus/core/src/cli/recap.ts +356 -4
  30. package/corpus/core/src/cli/skills-content/local-files.ts +17 -12
  31. package/corpus/core/src/client/analytics.ts +96 -14
  32. package/corpus/core/src/client/index.ts +7 -0
  33. package/corpus/core/src/client/session-replay.ts +10 -0
  34. package/corpus/core/src/client/settings/DemoModeSection.tsx +10 -34
  35. package/corpus/core/src/client/settings/SettingsPanel.tsx +31 -211
  36. package/corpus/core/src/client/settings/agent-settings-search.ts +219 -0
  37. package/corpus/core/src/client/settings/index.ts +4 -0
  38. package/corpus/core/src/client/url-scrub.ts +13 -0
  39. package/corpus/core/src/client/use-demo-mode-status.ts +44 -0
  40. package/corpus/core/src/demo/actions/toggle-demo-mode.ts +2 -2
  41. package/corpus/core/src/demo/config.ts +6 -6
  42. package/corpus/core/src/demo/redact.ts +18 -76
  43. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +21 -4
  44. package/corpus/templates/analytics/AGENTS.md +8 -0
  45. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +18 -4
  46. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +429 -244
  47. package/corpus/templates/analytics/app/components/layout/command-palette-search.ts +93 -0
  48. package/corpus/templates/analytics/app/hooks/use-replay-storage-status.ts +2 -1
  49. package/corpus/templates/analytics/app/lib/demo-chart-trend.ts +169 -0
  50. package/corpus/templates/analytics/app/pages/Settings.tsx +8 -45
  51. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +205 -70
  52. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +32 -16
  53. package/corpus/templates/analytics/app/pages/settings/settings-search.ts +126 -0
  54. package/corpus/templates/analytics/changelog/2026-07-12-command-menu-loading-placeholders-now-stay-below-available-r.md +6 -0
  55. package/corpus/templates/analytics/changelog/2026-07-12-command-menu-search-now-prioritizes-the-best-match.md +6 -0
  56. package/corpus/templates/analytics/changelog/2026-07-12-daily-dashboard-emails-reliably-include-the-complete-dashboa.md +6 -0
  57. package/corpus/templates/analytics/changelog/2026-07-12-demo-charts-preserve-source-movement.md +6 -0
  58. package/corpus/templates/analytics/changelog/2026-07-12-demo-mode-charts-trend-upward.md +6 -0
  59. package/corpus/templates/analytics/changelog/2026-07-12-replay-storage-settings-stay-readable-at-narrow-widths.md +6 -0
  60. package/corpus/templates/analytics/changelog/2026-07-12-session-replays-preserve-recorded-styling-and-keep-malformed.md +6 -0
  61. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +2 -0
  62. package/corpus/templates/analytics/server/lib/dashboard-report.ts +17 -33
  63. package/corpus/templates/clips/app/components/player/video-player.tsx +5 -1
  64. package/corpus/templates/clips/changelog/2026-07-11-clips-no-longer-stay-stuck-on-preparing-clip-when-they-are-r.md +6 -0
  65. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +2 -2
  66. package/corpus/templates/content/changelog/2026-07-12-the-left-sidebar-stays-fixed-horizontally-while-scrolling-on.md +6 -0
  67. package/corpus/templates/plan/.agents/skills/visual-plan/references/local-files.md +17 -12
  68. package/corpus/templates/plan/.agents/skills/visual-recap/references/local-files.md +17 -12
  69. package/corpus/templates/plan/actions/validate-local-plan-source.ts +4 -4
  70. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +2 -1
  71. package/corpus/templates/plan/app/lib/plan-local-bridge.ts +41 -0
  72. package/corpus/templates/plan/app/lib/plan-tracking.ts +3 -0
  73. package/corpus/templates/plan/app/pages/PlansPage.tsx +9 -9
  74. package/corpus/templates/plan/app/root.tsx +13 -3
  75. package/corpus/templates/plan/changelog/2026-07-12-local-plan-source-now-stays-on-device.md +6 -0
  76. package/dist/agent/production-agent.js +2 -2
  77. package/dist/agent/production-agent.js.map +1 -1
  78. package/dist/cli/plan-local.d.ts +6 -6
  79. package/dist/cli/plan-local.d.ts.map +1 -1
  80. package/dist/cli/plan-local.js +36 -7
  81. package/dist/cli/plan-local.js.map +1 -1
  82. package/dist/cli/pr-visual-recap-workflow.d.ts +2 -8
  83. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  84. package/dist/cli/pr-visual-recap-workflow.js +2 -8
  85. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  86. package/dist/cli/recap.d.ts +22 -1
  87. package/dist/cli/recap.d.ts.map +1 -1
  88. package/dist/cli/recap.js +286 -2
  89. package/dist/cli/recap.js.map +1 -1
  90. package/dist/cli/skills-content/local-files.d.ts +1 -1
  91. package/dist/cli/skills-content/local-files.d.ts.map +1 -1
  92. package/dist/cli/skills-content/local-files.js +17 -12
  93. package/dist/cli/skills-content/local-files.js.map +1 -1
  94. package/dist/client/analytics.d.ts +8 -0
  95. package/dist/client/analytics.d.ts.map +1 -1
  96. package/dist/client/analytics.js +77 -13
  97. package/dist/client/analytics.js.map +1 -1
  98. package/dist/client/index.d.ts +3 -2
  99. package/dist/client/index.d.ts.map +1 -1
  100. package/dist/client/index.js +3 -2
  101. package/dist/client/index.js.map +1 -1
  102. package/dist/client/session-replay.d.ts +2 -0
  103. package/dist/client/session-replay.d.ts.map +1 -1
  104. package/dist/client/session-replay.js +7 -0
  105. package/dist/client/session-replay.js.map +1 -1
  106. package/dist/client/settings/DemoModeSection.d.ts +3 -3
  107. package/dist/client/settings/DemoModeSection.d.ts.map +1 -1
  108. package/dist/client/settings/DemoModeSection.js +8 -27
  109. package/dist/client/settings/DemoModeSection.js.map +1 -1
  110. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  111. package/dist/client/settings/SettingsPanel.js +41 -191
  112. package/dist/client/settings/SettingsPanel.js.map +1 -1
  113. package/dist/client/settings/agent-settings-search.d.ts +16 -0
  114. package/dist/client/settings/agent-settings-search.d.ts.map +1 -0
  115. package/dist/client/settings/agent-settings-search.js +178 -0
  116. package/dist/client/settings/agent-settings-search.js.map +1 -0
  117. package/dist/client/settings/index.d.ts +1 -0
  118. package/dist/client/settings/index.d.ts.map +1 -1
  119. package/dist/client/settings/index.js +1 -0
  120. package/dist/client/settings/index.js.map +1 -1
  121. package/dist/client/url-scrub.d.ts.map +1 -1
  122. package/dist/client/url-scrub.js +14 -0
  123. package/dist/client/url-scrub.js.map +1 -1
  124. package/dist/client/use-demo-mode-status.d.ts +12 -0
  125. package/dist/client/use-demo-mode-status.d.ts.map +1 -0
  126. package/dist/client/use-demo-mode-status.js +30 -0
  127. package/dist/client/use-demo-mode-status.js.map +1 -0
  128. package/dist/collab/routes.d.ts +2 -2
  129. package/dist/demo/actions/toggle-demo-mode.js +2 -2
  130. package/dist/demo/actions/toggle-demo-mode.js.map +1 -1
  131. package/dist/demo/config.js +6 -6
  132. package/dist/demo/config.js.map +1 -1
  133. package/dist/demo/redact.d.ts +7 -5
  134. package/dist/demo/redact.d.ts.map +1 -1
  135. package/dist/demo/redact.js +19 -54
  136. package/dist/demo/redact.js.map +1 -1
  137. package/dist/notifications/routes.d.ts +2 -2
  138. package/dist/observability/routes.d.ts +5 -5
  139. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  140. package/dist/secrets/routes.d.ts +9 -9
  141. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  142. package/dist/server/transcribe-voice.d.ts +1 -1
  143. package/docs/content/locales/ar-SA/pr-visual-recap.mdx +25 -1
  144. package/docs/content/locales/ar-SA/template-plan.mdx +1 -1
  145. package/docs/content/locales/de-DE/pr-visual-recap.mdx +25 -1
  146. package/docs/content/locales/de-DE/template-plan.mdx +1 -1
  147. package/docs/content/locales/es-ES/pr-visual-recap.mdx +25 -1
  148. package/docs/content/locales/es-ES/template-plan.mdx +1 -1
  149. package/docs/content/locales/fr-FR/pr-visual-recap.mdx +25 -1
  150. package/docs/content/locales/fr-FR/template-plan.mdx +1 -1
  151. package/docs/content/locales/hi-IN/pr-visual-recap.mdx +25 -1
  152. package/docs/content/locales/hi-IN/template-plan.mdx +1 -1
  153. package/docs/content/locales/ja-JP/pr-visual-recap.mdx +25 -1
  154. package/docs/content/locales/ja-JP/template-plan.mdx +1 -1
  155. package/docs/content/locales/ko-KR/pr-visual-recap.mdx +25 -1
  156. package/docs/content/locales/ko-KR/template-plan.mdx +1 -1
  157. package/docs/content/locales/pt-BR/pr-visual-recap.mdx +25 -1
  158. package/docs/content/locales/pt-BR/template-plan.mdx +1 -1
  159. package/docs/content/locales/zh-CN/pr-visual-recap.mdx +25 -1
  160. package/docs/content/locales/zh-CN/template-plan.mdx +1 -1
  161. package/docs/content/locales/zh-TW/pr-visual-recap.mdx +25 -1
  162. package/docs/content/locales/zh-TW/template-plan.mdx +1 -1
  163. package/docs/content/pr-visual-recap.mdx +50 -1
  164. package/docs/content/template-plan.mdx +1 -1
  165. package/package.json +2 -2
@@ -160,8 +160,17 @@ type ReplayViewportDimensions = {
160
160
  height: number;
161
161
  };
162
162
 
163
+ type ReplayViewportChange = ReplayViewportDimensions & {
164
+ offsetMs: number;
165
+ };
166
+
163
167
  const DEFAULT_PLAYER_WIDTH = 1024;
164
168
  const DEFAULT_PLAYER_HEIGHT = 640;
169
+ const MALFORMED_REPLAY_MIN_WIDTH = 4000;
170
+ const MALFORMED_REPLAY_MIN_ASPECT_RATIO = 4;
171
+ const MIN_REPLAY_DISPLAY_DIMENSION = 240;
172
+ const RECOVERED_REPLAY_DISPLAY_ASPECT_RATIO =
173
+ DEFAULT_PLAYER_WIDTH / DEFAULT_PLAYER_HEIGHT;
165
174
  const DEFAULT_SPEED = 2;
166
175
  const SPEED_OPTIONS = [0.5, 1, 2, 4, 8];
167
176
  const SKIP_STEP_MS = 5000;
@@ -487,8 +496,17 @@ function ReplayPlayer({
487
496
  height: number;
488
497
  } | null>(null);
489
498
  const [fitScale, setFitScale] = useState(1);
490
- const initialDims = useMemo(() => replayViewportDimensions(events), [events]);
499
+ const initialDims = useMemo(
500
+ () => replayInitialDisplayDimensions(events),
501
+ [events],
502
+ );
503
+ const viewportTimeline = useMemo(
504
+ () => buildReplayViewportTimeline(events),
505
+ [events],
506
+ );
491
507
  const eventsRef = useLiveRef(events);
508
+ const viewportTimelineRef = useLiveRef(viewportTimeline);
509
+ const streamedDimsRef = useLiveRef(streamedDims);
492
510
  // Stable identity for the loaded event set so progressive chunk publishes
493
511
  // that only grow the array do not tear down a healthy Replayer mid-playback.
494
512
  const eventsIdentity = useMemo(
@@ -501,13 +519,16 @@ function ReplayPlayer({
501
519
  const scrubbingRef = useRef(false);
502
520
  const scrubResumePlayingRef = useRef(false);
503
521
 
504
- // Keep the stage in rrweb's coordinate system. Clamping only this outer
505
- // wrapper while rrweb keeps its raw iframe dimensions clips and stretches
506
- // wide recordings because the two layers no longer share a viewport.
507
- const playerWidth =
508
- streamedDims?.width ?? initialDims?.width ?? DEFAULT_PLAYER_WIDTH;
509
- const playerHeight =
510
- streamedDims?.height ?? initialDims?.height ?? DEFAULT_PLAYER_HEIGHT;
522
+ // IMPORTANT DO NOT REMOVE THIS LEGACY VIEWPORT CORRECTION.
523
+ // Some stored sessions contain impossible 4,000–7,500px-wide Meta/resize
524
+ // values even though the browser was a normal desktop viewport. Rendering
525
+ // those raw values collapses the replay into a tiny horizontal ribbon. Keep
526
+ // normal recordings exact—including 32:9 and mobile portrait. Recover only
527
+ // widths >=4,000px with aspect >4:1 to the standard 16:10 viewport. The same
528
+ // dimensions are applied to rrweb's iframe so CSS breakpoints/camera agree.
529
+ const displayDims = clampReplayDisplayDimensions(streamedDims ?? initialDims);
530
+ const playerWidth = displayDims?.width ?? DEFAULT_PLAYER_WIDTH;
531
+ const playerHeight = displayDims?.height ?? DEFAULT_PLAYER_HEIGHT;
511
532
  const skipRanges = useMemo(() => buildIdleSkipRanges(events), [events]);
512
533
  const skipRangesRef = useLiveRef(skipRanges);
513
534
  const skipInactiveRef = useLiveRef(skipInactive);
@@ -626,9 +647,35 @@ function ReplayPlayer({
626
647
  console.warn("[session-replay] seek failed", seekError);
627
648
  return;
628
649
  }
650
+ const seekDims = replayViewportDimensionsAtTime(
651
+ viewportTimelineRef.current,
652
+ clamped,
653
+ );
654
+ if (seekDims) {
655
+ // rrweb 2.1 does not reliably re-emit Resize when seeking backwards.
656
+ // IMPORTANT: correct both layers together. Correcting only the outer
657
+ // stage or only rrweb's iframe recreates the ultra-wide/clipped bug.
658
+ const correctedSeekDims = clampReplayDisplayDimensions(seekDims);
659
+ const currentDims = streamedDimsRef.current;
660
+ if (
661
+ correctedSeekDims &&
662
+ (currentDims?.width !== correctedSeekDims.width ||
663
+ currentDims?.height !== correctedSeekDims.height)
664
+ ) {
665
+ replayer.handleResize?.(correctedSeekDims);
666
+ setStreamedDims(correctedSeekDims);
667
+ }
668
+ }
629
669
  updateTime(clamped);
630
670
  },
631
- [playingRef, status, totalTime, updateTime],
671
+ [
672
+ playingRef,
673
+ status,
674
+ streamedDimsRef,
675
+ totalTime,
676
+ updateTime,
677
+ viewportTimelineRef,
678
+ ],
632
679
  );
633
680
 
634
681
  const beginScrub = useCallback(
@@ -693,11 +740,13 @@ function ReplayPlayer({
693
740
 
694
741
  stageRootRef.current.innerHTML = "";
695
742
  // Match builder-internal: pass events through untouched and let rrweb own
696
- // iframe sizing via Meta / ViewportResize. Only use dimensions for CSS
697
- // fit-to-stage scaling of the outer wrapper never rewrite Meta or force
698
- // iframe width/height (that mismatches the FullSnapshot DOM and blanks
699
- // the stage).
700
- setStreamedDims(replayViewportDimensions(replayEvents));
743
+ // normal iframe sizing via Meta / ViewportResize. Never rewrite Meta or
744
+ // force arbitrary iframe dimensions (that mismatches the FullSnapshot DOM
745
+ // and blanks the stage). The one exception below mirrors the narrowly
746
+ // detected legacy malformed viewport onto both the iframe and outer stage.
747
+ const rawInitialDims = replayInitialViewportDimensions(replayEvents);
748
+ const correctedInitialDims = replayInitialDisplayDimensions(replayEvents);
749
+ setStreamedDims(correctedInitialDims);
701
750
  localReplayer = new Replayer(replayEvents as any[], {
702
751
  root: stageRootRef.current,
703
752
  speed: speedRef.current,
@@ -708,6 +757,21 @@ function ReplayPlayer({
708
757
  mouseTail: false,
709
758
  insertStyleRules: SUPPRESS_OVERLAYS_CSS,
710
759
  });
760
+ // IMPORTANT: rrweb constructs its iframe from the raw initial Meta event.
761
+ // When that legacy Meta width is malformed, correcting only streamedDims
762
+ // fixes the outer stage but leaves CSS breakpoints and clicks ultra-wide.
763
+ // Apply the same correction to rrweb immediately, before first playback.
764
+ if (
765
+ correctedInitialDims &&
766
+ (rawInitialDims?.width !== correctedInitialDims.width ||
767
+ rawInitialDims?.height !== correctedInitialDims.height)
768
+ ) {
769
+ localReplayer.handleResize?.(correctedInitialDims);
770
+ }
771
+ // rrweb already sandboxes the replay document without script execution.
772
+ // Do not mutate recorded URLs/CSS; suppress viewer-page referrer leakage
773
+ // at the iframe boundary while retaining historical visual resources.
774
+ localReplayer.iframe?.setAttribute?.("referrerpolicy", "no-referrer");
711
775
  replayerRef.current = localReplayer;
712
776
  const meta = localReplayer.getMetaData?.();
713
777
  const total = Number(meta?.totalTime ?? replayDuration(replayEvents));
@@ -732,10 +796,29 @@ function ReplayPlayer({
732
796
  dims.width > 0 &&
733
797
  dims.height > 0
734
798
  ) {
735
- setStreamedDims({
799
+ const rawDims = {
736
800
  width: Math.round(dims.width),
737
801
  height: Math.round(dims.height),
738
- });
802
+ };
803
+ const correctedDims = clampReplayDisplayDimensions(rawDims);
804
+ if (!correctedDims) return;
805
+ // rrweb handles the raw resize before notifying us. Override only
806
+ // impossible legacy geometry so its iframe and our stage stay equal.
807
+ // DO NOT clamp just one layer; that breaks responsive CSS and clicks.
808
+ const currentDims = streamedDimsRef.current;
809
+ if (
810
+ currentDims?.width === correctedDims.width &&
811
+ currentDims?.height === correctedDims.height
812
+ ) {
813
+ return;
814
+ }
815
+ if (
816
+ correctedDims.width !== rawDims.width ||
817
+ correctedDims.height !== rawDims.height
818
+ ) {
819
+ localReplayer.handleResize?.(correctedDims);
820
+ }
821
+ setStreamedDims(correctedDims);
739
822
  }
740
823
  });
741
824
  updateTime(startAt);
@@ -1855,65 +1938,19 @@ function useReplayEvents(
1855
1938
  );
1856
1939
  }
1857
1940
 
1858
- const REPLAY_NETWORK_ATTRIBUTES = new Set([
1859
- "src",
1860
- "srcset",
1861
- "poster",
1862
- "background",
1863
- "action",
1864
- "formaction",
1865
- "data",
1866
- "ping",
1867
- "cite",
1868
- "xlink:href",
1869
- ]);
1870
-
1871
- function stripReplayCssUrls(value: string): string {
1872
- return value
1873
- .replace(/@import\s+(?:url\()?[^;)]+(?:\))?\s*;?/gi, "")
1874
- .replace(/url\(\s*[^)]*\)/gi, "none");
1875
- }
1876
-
1877
- function sanitizeReplayValue(value: unknown, key?: string): unknown {
1878
- if (Array.isArray(value)) {
1879
- return value.map((item) => sanitizeReplayValue(item));
1880
- }
1881
- if (!isRecord(value)) {
1882
- if (typeof value !== "string") return value;
1883
- const normalizedKey = key?.toLowerCase();
1884
- if (
1885
- normalizedKey === "href" ||
1886
- REPLAY_NETWORK_ATTRIBUTES.has(normalizedKey ?? "")
1887
- ) {
1888
- return "about:blank";
1889
- }
1890
- if (
1891
- normalizedKey === "style" ||
1892
- normalizedKey === "_csstext" ||
1893
- normalizedKey === "csstext"
1894
- ) {
1895
- return stripReplayCssUrls(value);
1896
- }
1897
- return value;
1898
- }
1899
-
1900
- return Object.fromEntries(
1901
- Object.entries(value).map(([entryKey, entryValue]) => [
1902
- entryKey,
1903
- sanitizeReplayValue(entryValue, entryKey),
1904
- ]),
1905
- );
1906
- }
1907
-
1908
1941
  /**
1909
- * rrweb's iframe sandbox prevents scripts but permits passive resource loads.
1910
- * Preserve the captured DOM structure and inline styling while neutralizing
1911
- * URLs that could contact a recorded page or third-party origin.
1942
+ * Stock rrweb consumes the recorded event objects directly. Playback-time URL
1943
+ * or CSS rewriting breaks snapshots, responsive rules, fonts, and navigation
1944
+ * metadata, so filtering invalid container entries is the only normalization.
1945
+ *
1946
+ * IMPORTANT — DO NOT add URL/CSS sanitization here. A previous security review
1947
+ * changed href/src/_cssText to about:blank and immediately regressed every
1948
+ * historical recording that depended on captured styles. Network/privacy
1949
+ * controls belong at capture or the sandbox boundary, never in rrweb events.
1912
1950
  */
1913
1951
  export function normalizeReplayEvents(events: unknown[]): AnyReplayEvent[] {
1914
1952
  return events
1915
1953
  .filter((event): event is AnyReplayEvent => isRecord(event))
1916
- .map((event) => sanitizeReplayValue(event) as AnyReplayEvent)
1917
1954
  .sort((a, b) => Number(a.timestamp ?? 0) - Number(b.timestamp ?? 0));
1918
1955
  }
1919
1956
 
@@ -2236,6 +2273,81 @@ export function replayViewportDimensions(
2236
2273
  return best;
2237
2274
  }
2238
2275
 
2276
+ export function replayInitialViewportDimensions(
2277
+ events: AnyReplayEvent[],
2278
+ ): ReplayViewportDimensions | null {
2279
+ // rrweb itself initializes from the first Meta event. A resize can appear
2280
+ // earlier in chunk order after reconnect/flush boundaries, but it must not
2281
+ // replace the snapshot's native starting viewport.
2282
+ for (const event of events) {
2283
+ if (event.type !== RRWEB_EVENT_TYPE.Meta) continue;
2284
+ const dims = dimensionsFromReplayEvent(event);
2285
+ if (dims) return dims;
2286
+ }
2287
+ for (const event of events) {
2288
+ const dims = dimensionsFromReplayEvent(event);
2289
+ if (dims) return dims;
2290
+ }
2291
+ return null;
2292
+ }
2293
+
2294
+ export function replayInitialDisplayDimensions(
2295
+ events: AnyReplayEvent[],
2296
+ ): ReplayViewportDimensions | null {
2297
+ // Keep the initial outer stage and rrweb iframe on one corrected camera.
2298
+ // DO NOT use the raw initial Meta dimensions for only one of those layers.
2299
+ return clampReplayDisplayDimensions(replayInitialViewportDimensions(events));
2300
+ }
2301
+
2302
+ export function buildReplayViewportTimeline(
2303
+ events: AnyReplayEvent[],
2304
+ ): ReplayViewportChange[] {
2305
+ const initial = replayInitialViewportDimensions(events);
2306
+ if (!initial) return [];
2307
+ const startedAt = replayStartedAt(events);
2308
+ const firstMetaTimestamp = events.reduce((best, event) => {
2309
+ if (event.type !== RRWEB_EVENT_TYPE.Meta) return best;
2310
+ const timestamp = Number(event.timestamp ?? 0);
2311
+ return Number.isFinite(timestamp) && timestamp > 0
2312
+ ? Math.min(best, timestamp)
2313
+ : best;
2314
+ }, Number.POSITIVE_INFINITY);
2315
+ const changes: ReplayViewportChange[] = [{ ...initial, offsetMs: 0 }];
2316
+ for (const event of events) {
2317
+ const timestamp = Number(event.timestamp ?? 0);
2318
+ if (!Number.isFinite(timestamp) || timestamp <= firstMetaTimestamp)
2319
+ continue;
2320
+ const dims = dimensionsFromReplayEvent(event);
2321
+ if (!dims) continue;
2322
+ const previous = changes[changes.length - 1];
2323
+ if (previous?.width === dims.width && previous.height === dims.height) {
2324
+ continue;
2325
+ }
2326
+ changes.push({
2327
+ ...dims,
2328
+ offsetMs: Math.max(0, timestamp - startedAt),
2329
+ });
2330
+ }
2331
+ return changes;
2332
+ }
2333
+
2334
+ export function replayViewportDimensionsAtTime(
2335
+ changes: ReplayViewportChange[],
2336
+ elapsedMs: number,
2337
+ ): ReplayViewportDimensions | null {
2338
+ if (changes.length === 0) return null;
2339
+ const target = Math.max(0, elapsedMs);
2340
+ let low = 0;
2341
+ let high = changes.length - 1;
2342
+ while (low < high) {
2343
+ const middle = Math.ceil((low + high) / 2);
2344
+ if ((changes[middle]?.offsetMs ?? 0) <= target) low = middle;
2345
+ else high = middle - 1;
2346
+ }
2347
+ const match = changes[low];
2348
+ return match ? { width: match.width, height: match.height } : null;
2349
+ }
2350
+
2239
2351
  function dimensionsFromReplayEvent(
2240
2352
  event: AnyReplayEvent,
2241
2353
  ): ReplayViewportDimensions | null {
@@ -2272,6 +2384,29 @@ export function normalizeReplayDimensions(
2272
2384
  };
2273
2385
  }
2274
2386
 
2387
+ export function clampReplayDisplayDimensions(
2388
+ dims: ReplayViewportDimensions | null,
2389
+ ): ReplayViewportDimensions | null {
2390
+ if (!dims) return null;
2391
+ if (
2392
+ dims.width < MIN_REPLAY_DISPLAY_DIMENSION ||
2393
+ dims.height < MIN_REPLAY_DISPLAY_DIMENSION
2394
+ ) {
2395
+ return { width: DEFAULT_PLAYER_WIDTH, height: DEFAULT_PLAYER_HEIGHT };
2396
+ }
2397
+ const aspect = dims.width / dims.height;
2398
+ if (
2399
+ dims.width >= MALFORMED_REPLAY_MIN_WIDTH &&
2400
+ aspect > MALFORMED_REPLAY_MIN_ASPECT_RATIO
2401
+ ) {
2402
+ return {
2403
+ width: Math.round(dims.height * RECOVERED_REPLAY_DISPLAY_ASPECT_RATIO),
2404
+ height: dims.height,
2405
+ };
2406
+ }
2407
+ return dims;
2408
+ }
2409
+
2275
2410
  export function filterReplayMarkers(
2276
2411
  markers: ReplayMarker[],
2277
2412
  query: string,
@@ -464,7 +464,11 @@ function EmptySessionsState() {
464
464
  );
465
465
  }
466
466
 
467
- export function ReplayStorageHint() {
467
+ export function ReplayStorageHint({
468
+ embedded = false,
469
+ }: {
470
+ embedded?: boolean;
471
+ }) {
468
472
  const t = useT();
469
473
  const storageStatus = useReplayStorageStatus();
470
474
  const builderStatus = useBuilderStatus();
@@ -523,22 +527,29 @@ export function ReplayStorageHint() {
523
527
 
524
528
  return (
525
529
  <Collapsible open={s3Expanded} onOpenChange={setS3Expanded}>
526
- <div className="mb-6 rounded-md border border-primary/30 bg-primary/5 p-4">
527
- <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
528
- <div className="flex min-w-0 items-start gap-3">
529
- <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-background text-primary">
530
- <IconCloud className="h-5 w-5" />
531
- </div>
532
- <div className="min-w-0">
533
- <div className="text-sm font-medium">
534
- {t("sessions.storageSetupTitle")}
530
+ <div
531
+ className={cn(
532
+ !embedded &&
533
+ "mb-6 rounded-md border border-primary/30 bg-primary/5 p-4",
534
+ )}
535
+ >
536
+ <div className="flex flex-wrap items-center gap-4">
537
+ {!embedded ? (
538
+ <div className="flex min-w-[min(100%,24rem)] flex-1 items-start gap-3">
539
+ <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-background text-primary">
540
+ <IconCloud className="h-5 w-5" />
541
+ </div>
542
+ <div className="min-w-0">
543
+ <div className="text-sm font-medium">
544
+ {t("sessions.storageSetupTitle")}
545
+ </div>
546
+ <p className="mt-0.5 text-xs text-muted-foreground">
547
+ {t("sessions.storageSetupDescription")}
548
+ </p>
535
549
  </div>
536
- <p className="mt-0.5 text-xs text-muted-foreground">
537
- {t("sessions.storageSetupDescription")}
538
- </p>
539
550
  </div>
540
- </div>
541
- <div className="flex shrink-0 flex-wrap items-center gap-3">
551
+ ) : null}
552
+ <div className="flex max-w-full flex-wrap items-center gap-3">
542
553
  <Button
543
554
  type="button"
544
555
  size="sm"
@@ -584,7 +595,12 @@ export function ReplayStorageHint() {
584
595
  </div>
585
596
  </div>
586
597
  <CollapsibleContent>
587
- <div className="mt-4 border-t border-primary/20 pt-4">
598
+ <div
599
+ className={cn(
600
+ "mt-4 border-t pt-4",
601
+ embedded ? "border-border" : "border-primary/20",
602
+ )}
603
+ >
588
604
  <p className="mb-4 text-xs text-muted-foreground">
589
605
  {t("settings.s3OwnBucketDescription")}
590
606
  </p>
@@ -0,0 +1,126 @@
1
+ import {
2
+ getAgentSettingsSearchTabs,
3
+ type SettingsSearchEntry,
4
+ } from "@agent-native/core/client";
5
+
6
+ interface SettingsCommandItem {
7
+ id: string;
8
+ label: string;
9
+ keywords: string;
10
+ href: string;
11
+ }
12
+
13
+ type Translate = (key: string) => string;
14
+
15
+ export function buildAnalyticsGeneralSettingsSearchEntries(
16
+ t: Translate,
17
+ replayStorageConfigured: boolean,
18
+ ): SettingsSearchEntry[] {
19
+ return [
20
+ {
21
+ id: "analytics-account",
22
+ label: t("settings.account"),
23
+ keywords: "profile email signed in identity",
24
+ hash: "account",
25
+ },
26
+ {
27
+ id: "analytics-credentials",
28
+ label: t("settings.credentials"),
29
+ keywords: "data sources api keys manage credentials",
30
+ hash: "credentials",
31
+ },
32
+ {
33
+ id: "analytics-dashboard-templates",
34
+ label: t("settings.dashboardTemplates"),
35
+ keywords: "templates catalog dashboards",
36
+ hash: "dashboard-templates",
37
+ },
38
+ ...(replayStorageConfigured
39
+ ? [
40
+ {
41
+ id: "analytics-replay-storage",
42
+ label: t("sessions.storageSetupTitle"),
43
+ keywords: "session replay recording storage s3 bucket builder",
44
+ hash: "replay-storage",
45
+ },
46
+ ]
47
+ : []),
48
+ {
49
+ id: "analytics-language",
50
+ label: t("settings.languageTitle"),
51
+ keywords: "language locale translation i18n",
52
+ hash: "language",
53
+ },
54
+ {
55
+ id: "analytics-about",
56
+ label: t("settings.about"),
57
+ keywords: "about version info usage",
58
+ hash: "about",
59
+ },
60
+ ];
61
+ }
62
+
63
+ function normalizeLabel(label: string): string {
64
+ return label.trim().toLocaleLowerCase();
65
+ }
66
+
67
+ export function buildAnalyticsSettingsCommandItems(
68
+ t: Translate,
69
+ generalEntries: SettingsSearchEntry[],
70
+ ): SettingsCommandItem[] {
71
+ const tabs = [
72
+ {
73
+ id: "general",
74
+ label: "General",
75
+ keywords: "settings preferences configuration",
76
+ searchEntries: generalEntries.filter(
77
+ (entry) => entry.id !== "analytics-language",
78
+ ),
79
+ },
80
+ {
81
+ id: "alerts",
82
+ label: t("settings.alertsTitle"),
83
+ keywords: "alerts rules notifications thresholds triggers monitoring",
84
+ },
85
+ ...getAgentSettingsSearchTabs(),
86
+ ];
87
+ const commandIndexByDestination = new Map<string, number>();
88
+ const commands: SettingsCommandItem[] = [];
89
+
90
+ const add = (command: SettingsCommandItem) => {
91
+ const destinationKey = `${normalizeLabel(command.label)}\0${command.href}`;
92
+ const existingIndex = commandIndexByDestination.get(destinationKey);
93
+ if (existingIndex !== undefined) {
94
+ const existing = commands[existingIndex];
95
+ commands[existingIndex] = {
96
+ ...existing,
97
+ // Duplicate destinations can come from the app and shared settings
98
+ // catalogs. Preserve both sources' search phrases and tab context.
99
+ keywords: `${existing.keywords} ${command.keywords}`,
100
+ };
101
+ return;
102
+ }
103
+
104
+ commandIndexByDestination.set(destinationKey, commands.length);
105
+ commands.push(command);
106
+ };
107
+
108
+ for (const tab of tabs) {
109
+ add({
110
+ id: `tab:${tab.id}`,
111
+ label: tab.label,
112
+ keywords: `${tab.keywords} settings`,
113
+ href: `/settings#${tab.id}`,
114
+ });
115
+ for (const entry of tab.searchEntries ?? []) {
116
+ add({
117
+ id: entry.id,
118
+ label: entry.label,
119
+ keywords: `${entry.keywords ?? ""} ${entry.description ?? ""} ${tab.label} settings`,
120
+ href: `/settings#${entry.hash ?? entry.tabId ?? tab.id}`,
121
+ });
122
+ }
123
+ }
124
+
125
+ return commands;
126
+ }
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Command menu loading placeholders now stay below available results without flashing.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Command menu search now finds settings, prioritizes the best match, and keeps keyboard selection aligned with each query.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Daily dashboard emails reliably include the complete dashboard image.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Demo mode trend charts now preserve the source series' real spikes, dips, and smooth stretches.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Demo mode line and area charts now show unique upward trends while preserving each series' original range and volatility.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Replay storage settings stay readable at narrow widths.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Session replays preserve recorded styling and keep malformed ultra-wide captures readable.
@@ -56,6 +56,8 @@ export async function runDashboardReportsOnce(): Promise<{
56
56
  `[dashboard-report] Subscription ${sub.id} sent without a screenshot:`,
57
57
  message,
58
58
  );
59
+ await markDashboardReportResult(sub, "success", message);
60
+ continue;
59
61
  }
60
62
  await markDashboardReportResult(sub, "success");
61
63
  } catch (err: any) {