@agent-native/core 0.80.11 → 0.81.1

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 (230) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +53 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/design-connect.ts +419 -19
  5. package/corpus/core/src/client/AgentPanel.tsx +33 -1
  6. package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
  7. package/corpus/core/src/client/chat/index.ts +1 -0
  8. package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
  9. package/corpus/core/src/client/chat-view-transition.ts +33 -2
  10. package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
  11. package/corpus/core/src/client/index.ts +1 -0
  12. package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
  13. package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
  14. package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
  15. package/corpus/core/src/db/client.ts +29 -2
  16. package/corpus/core/src/provider-api/index.ts +29 -0
  17. package/corpus/core/src/server/core-routes-plugin.ts +58 -20
  18. package/corpus/core/src/styles/agent-native.css +54 -0
  19. package/corpus/core/src/usage/store.ts +2 -2
  20. package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
  21. package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
  22. package/corpus/templates/analytics/app/global.css +8 -0
  23. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
  24. package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
  25. package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
  26. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
  27. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
  28. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
  29. package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
  30. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
  31. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
  33. package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
  34. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
  35. package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
  36. package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
  37. package/corpus/templates/assets/app/global.css +15 -0
  38. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
  39. package/corpus/templates/assets/app/routes/library.tsx +90 -16
  40. package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
  41. package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
  42. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
  43. package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
  44. package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
  45. package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
  46. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  47. package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
  48. package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
  49. package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
  50. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
  51. package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
  52. package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
  53. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
  54. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
  55. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
  56. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
  57. package/corpus/templates/design/AGENTS.md +27 -0
  58. package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
  59. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
  60. package/corpus/templates/design/actions/apply-component-prop-edit.ts +63 -20
  61. package/corpus/templates/design/actions/apply-motion-edit.ts +67 -69
  62. package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
  63. package/corpus/templates/design/actions/capture-design-state.ts +27 -4
  64. package/corpus/templates/design/actions/connect-localhost.ts +59 -22
  65. package/corpus/templates/design/actions/export-zip.ts +23 -8
  66. package/corpus/templates/design/actions/generate-design.ts +53 -1
  67. package/corpus/templates/design/actions/get-component-details.ts +5 -19
  68. package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
  69. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
  70. package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
  71. package/corpus/templates/design/actions/index-design-tokens.ts +10 -1
  72. package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
  73. package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
  74. package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
  75. package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
  76. package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
  77. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
  78. package/corpus/templates/design/actions/navigate.ts +18 -3
  79. package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
  80. package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
  81. package/corpus/templates/design/actions/provider-api-request.ts +3 -3
  82. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
  83. package/corpus/templates/design/actions/run-design-audit.ts +53 -4
  84. package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
  85. package/corpus/templates/design/actions/view-screen.ts +1 -1
  86. package/corpus/templates/design/actions/write-local-file.ts +213 -0
  87. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +230 -3308
  88. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
  89. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +776 -287
  90. package/corpus/templates/design/app/components/design/EditPanel.tsx +212 -208
  91. package/corpus/templates/design/app/components/design/LayersPanel.tsx +14 -2
  92. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
  93. package/corpus/templates/design/app/components/design/MotionDock.tsx +235 -266
  94. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +490 -55
  95. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +43 -62
  96. package/corpus/templates/design/app/components/design/StatesPanel.tsx +34 -61
  97. package/corpus/templates/design/app/components/design/TokensPanel.tsx +251 -0
  98. package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
  99. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
  100. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
  101. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
  102. package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
  103. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
  104. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
  105. package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
  106. package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
  107. package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
  108. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
  109. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
  110. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
  111. package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
  112. package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
  113. package/corpus/templates/design/app/global.css +7 -0
  114. package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
  115. package/corpus/templates/design/app/i18n/zh-TW.ts +21 -0
  116. package/corpus/templates/design/app/i18n-data.ts +358 -0
  117. package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
  118. package/corpus/templates/design/app/pages/DesignEditor.tsx +2402 -499
  119. package/corpus/templates/design/app/pages/Index.tsx +5 -1
  120. package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
  121. package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
  122. package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
  123. package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
  124. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
  125. package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
  126. package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
  127. package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
  128. package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
  129. package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
  130. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
  131. package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
  132. package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
  133. package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
  134. package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
  135. package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
  136. package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
  137. package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
  138. package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
  139. package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
  140. package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
  141. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
  142. package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
  143. package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
  144. package/corpus/templates/design/e2e/helpers.ts +168 -65
  145. package/corpus/templates/design/package.json +3 -1
  146. package/corpus/templates/design/server/db/schema.ts +23 -0
  147. package/corpus/templates/design/server/handlers/uploads.ts +22 -1
  148. package/corpus/templates/design/server/lib/provider-api.ts +7 -4
  149. package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
  150. package/corpus/templates/design/server/plugins/db.ts +39 -0
  151. package/corpus/templates/design/server/register-secrets.ts +38 -0
  152. package/corpus/templates/design/shared/board-file.ts +228 -0
  153. package/corpus/templates/design/shared/board-objects.ts +288 -0
  154. package/corpus/templates/design/shared/code-layer.ts +52 -0
  155. package/corpus/templates/design/shared/motion-compiler.ts +58 -7
  156. package/corpus/templates/design/shared/motion-timeline.ts +2 -2
  157. package/corpus/templates/design/tsconfig.json +2 -1
  158. package/corpus/templates/slides/AGENTS.md +9 -0
  159. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
  160. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
  161. package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
  162. package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
  163. package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
  164. package/corpus/templates/slides/app/vite-env.d.ts +18 -0
  165. package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
  166. package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
  167. package/corpus/templates/slides/package.json +1 -0
  168. package/corpus/templates/slides/vite.config.ts +1 -0
  169. package/dist/cli/design-connect.d.ts +25 -0
  170. package/dist/cli/design-connect.d.ts.map +1 -1
  171. package/dist/cli/design-connect.js +357 -18
  172. package/dist/cli/design-connect.js.map +1 -1
  173. package/dist/client/AgentPanel.d.ts +1 -1
  174. package/dist/client/AgentPanel.d.ts.map +1 -1
  175. package/dist/client/AgentPanel.js +18 -3
  176. package/dist/client/AgentPanel.js.map +1 -1
  177. package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
  178. package/dist/client/ConnectBuilderCard.js +1 -0
  179. package/dist/client/ConnectBuilderCard.js.map +1 -1
  180. package/dist/client/chat/index.d.ts +1 -1
  181. package/dist/client/chat/index.d.ts.map +1 -1
  182. package/dist/client/chat/index.js +1 -1
  183. package/dist/client/chat/index.js.map +1 -1
  184. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  185. package/dist/client/chat/run-recovery.js +5 -5
  186. package/dist/client/chat/run-recovery.js.map +1 -1
  187. package/dist/client/chat-view-transition.d.ts +6 -0
  188. package/dist/client/chat-view-transition.d.ts.map +1 -1
  189. package/dist/client/chat-view-transition.js +23 -2
  190. package/dist/client/chat-view-transition.js.map +1 -1
  191. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  192. package/dist/client/composer/PromptComposer.js +3 -2
  193. package/dist/client/composer/PromptComposer.js.map +1 -1
  194. package/dist/client/index.d.ts +1 -1
  195. package/dist/client/index.d.ts.map +1 -1
  196. package/dist/client/index.js +1 -1
  197. package/dist/client/index.js.map +1 -1
  198. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  199. package/dist/client/org/OrgSwitcher.js +11 -4
  200. package/dist/client/org/OrgSwitcher.js.map +1 -1
  201. package/dist/client/sharing/ShareButton.js +3 -3
  202. package/dist/client/sharing/ShareButton.js.map +1 -1
  203. package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
  204. package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
  205. package/dist/client/use-agent-chat-home-handoff.js +20 -6
  206. package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
  207. package/dist/collab/routes.d.ts +1 -1
  208. package/dist/db/client.d.ts +1 -0
  209. package/dist/db/client.d.ts.map +1 -1
  210. package/dist/db/client.js +28 -2
  211. package/dist/db/client.js.map +1 -1
  212. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  213. package/dist/notifications/routes.d.ts +2 -2
  214. package/dist/observability/routes.d.ts +8 -8
  215. package/dist/progress/routes.d.ts +1 -1
  216. package/dist/provider-api/index.d.ts +5 -4
  217. package/dist/provider-api/index.d.ts.map +1 -1
  218. package/dist/provider-api/index.js +28 -0
  219. package/dist/provider-api/index.js.map +1 -1
  220. package/dist/resources/handlers.d.ts +3 -3
  221. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  222. package/dist/server/core-routes-plugin.d.ts +25 -0
  223. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  224. package/dist/server/core-routes-plugin.js +43 -18
  225. package/dist/server/core-routes-plugin.js.map +1 -1
  226. package/dist/server/transcribe-voice.d.ts +1 -1
  227. package/dist/styles/agent-native.css +54 -0
  228. package/dist/usage/store.js +2 -2
  229. package/dist/usage/store.js.map +1 -1
  230. package/package.json +1 -1
@@ -1,22 +1,32 @@
1
1
  import {
2
+ appBasePath,
2
3
  AgentSidebar,
3
4
  GuidedQuestionFlow,
4
5
  focusAgentChat,
6
+ markAgentChatHomeHandoff,
5
7
  navigateWithAgentChatViewTransition,
6
8
  useAgentChatHomeHandoff,
7
- useAgentChatHomeHandoffLinks,
8
9
  useGuidedQuestionFlow,
9
10
  useT,
10
11
  } from "@agent-native/core/client";
11
12
  import { InvitationBanner } from "@agent-native/core/client/org";
12
- import { useMemo } from "react";
13
+ import { useEffect, useMemo } from "react";
13
14
  import { useLocation, useNavigate } from "react-router";
14
15
 
15
16
  import { useNavigationState } from "@/hooks/use-navigation-state";
17
+ import {
18
+ ANALYTICS_CHAT_STORAGE_KEY,
19
+ hasRecentAnalyticsChat,
20
+ markAnalyticsChatActivity,
21
+ } from "@/lib/chat-handoff";
16
22
  import { TAB_ID } from "@/lib/tab-id";
17
23
 
18
24
  import { Header } from "./Header";
19
25
  import { HeaderActionsProvider } from "./HeaderActions";
26
+ import {
27
+ isAnalyticsSessionsRoute,
28
+ shouldDefaultOpenAnalyticsSidebar,
29
+ } from "./layout-route-policy";
20
30
  import { MobileNav } from "./MobileNav";
21
31
  import { Sidebar } from "./Sidebar";
22
32
 
@@ -26,6 +36,59 @@ interface LayoutProps {
26
36
 
27
37
  const BARE_ROUTES = new Set(["/chart"]);
28
38
 
39
+ function stripBasePath(path: string): string {
40
+ const basePath = appBasePath();
41
+ if (!basePath) return path;
42
+ if (path === basePath) return "/";
43
+ if (path.startsWith(`${basePath}/`)) return path.slice(basePath.length);
44
+ if (path.startsWith(`${basePath}?`) || path.startsWith(`${basePath}#`)) {
45
+ return `/${path.slice(basePath.length)}`;
46
+ }
47
+ return path;
48
+ }
49
+
50
+ function pathnameFromLocalPath(path: string): string {
51
+ return path.split(/[?#]/, 1)[0] || "/";
52
+ }
53
+
54
+ function isFrameworkOrApiPath(pathname: string): boolean {
55
+ return (
56
+ pathname === "/_agent-native" ||
57
+ pathname.startsWith("/_agent-native/") ||
58
+ pathname === "/api" ||
59
+ pathname.startsWith("/api/")
60
+ );
61
+ }
62
+
63
+ function isStaticAssetPath(pathname: string): boolean {
64
+ const lastSegment = pathname.split("/").pop() ?? "";
65
+ return /\.[A-Za-z0-9]{1,12}$/.test(lastSegment);
66
+ }
67
+
68
+ function localPathFromAnchor(anchor: HTMLAnchorElement): string | null {
69
+ if (!anchor.href) return null;
70
+ try {
71
+ const url = new URL(anchor.href);
72
+ if (url.origin !== window.location.origin) return null;
73
+ return stripBasePath(`${url.pathname}${url.search}${url.hash}`);
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+
79
+ function shouldHandleAnchorClick(
80
+ event: MouseEvent,
81
+ anchor: HTMLAnchorElement,
82
+ ): boolean {
83
+ if (event.defaultPrevented || event.button !== 0) return false;
84
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
85
+ return false;
86
+ }
87
+ if (anchor.target && anchor.target !== "_self") return false;
88
+ if (anchor.hasAttribute("download")) return false;
89
+ return true;
90
+ }
91
+
29
92
  export function Layout({ children }: LayoutProps) {
30
93
  useNavigationState();
31
94
  const location = useLocation();
@@ -82,18 +145,60 @@ export function Layout({ children }: LayoutProps) {
82
145
  const isExtensionsRoute =
83
146
  location.pathname === "/extensions" ||
84
147
  location.pathname.startsWith("/extensions/");
148
+ const isSessionsRoute = isAnalyticsSessionsRoute(location.pathname);
85
149
  const isAskRoute = location.pathname === "/ask";
86
150
  const chatHomeHandoffActive = useAgentChatHomeHandoff({
87
- storageKey: "analytics",
151
+ storageKey: ANALYTICS_CHAT_STORAGE_KEY,
88
152
  activePath: location.pathname,
89
153
  enabled: !isAskRoute && !reportScreenshot,
90
154
  });
91
- useAgentChatHomeHandoffLinks({
92
- storageKey: "analytics",
93
- chatPath: "/ask",
94
- });
95
155
  const sidebarScope = chatHomeHandoffActive ? null : analyticsScope;
96
156
 
157
+ useEffect(() => {
158
+ function handleChatRunning(event: Event) {
159
+ const detail = (event as CustomEvent).detail;
160
+ if (typeof detail?.isRunning === "boolean") {
161
+ markAnalyticsChatActivity();
162
+ }
163
+ }
164
+
165
+ window.addEventListener("agentNative.chatRunning", handleChatRunning);
166
+ return () =>
167
+ window.removeEventListener("agentNative.chatRunning", handleChatRunning);
168
+ }, []);
169
+
170
+ useEffect(() => {
171
+ if (!isAskRoute) return;
172
+
173
+ function handleClick(event: MouseEvent) {
174
+ const target = event.target;
175
+ if (!(target instanceof Element)) return;
176
+ const anchor = target.closest("a[href]");
177
+ if (!(anchor instanceof HTMLAnchorElement)) return;
178
+ if (!shouldHandleAnchorClick(event, anchor)) return;
179
+ if (anchor.closest(".agent-panel-root")) return;
180
+
181
+ const path = localPathFromAnchor(anchor);
182
+ const pathname = path ? pathnameFromLocalPath(path) : "";
183
+ if (
184
+ !path ||
185
+ pathname === "/ask" ||
186
+ isFrameworkOrApiPath(pathname) ||
187
+ isStaticAssetPath(pathname) ||
188
+ !hasRecentAnalyticsChat()
189
+ ) {
190
+ return;
191
+ }
192
+
193
+ event.preventDefault();
194
+ markAgentChatHomeHandoff(ANALYTICS_CHAT_STORAGE_KEY);
195
+ navigateWithAgentChatViewTransition(navigate, path);
196
+ }
197
+
198
+ document.addEventListener("click", handleClick, true);
199
+ return () => document.removeEventListener("click", handleClick, true);
200
+ }, [isAskRoute, navigate]);
201
+
97
202
  function openAskAgentFullscreen() {
98
203
  focusAgentChat();
99
204
  navigateWithAgentChatViewTransition(navigate, "/ask");
@@ -158,11 +263,14 @@ export function Layout({ children }: LayoutProps) {
158
263
  ) : (
159
264
  <AgentSidebar
160
265
  position="right"
161
- defaultOpen
266
+ defaultOpen={
267
+ chatHomeHandoffActive &&
268
+ shouldDefaultOpenAnalyticsSidebar(location.pathname)
269
+ }
162
270
  chatViewTransition
163
- storageKey="analytics"
271
+ storageKey={ANALYTICS_CHAT_STORAGE_KEY}
164
272
  browserTabId={TAB_ID}
165
- openOnChatRunning={chatHomeHandoffActive}
273
+ openOnChatRunning={chatHomeHandoffActive && !isSessionsRoute}
166
274
  onFullscreenRequest={openAskAgentFullscreen}
167
275
  emptyStateText={t("chat.emptyState")}
168
276
  suggestions={[
@@ -0,0 +1,7 @@
1
+ export function isAnalyticsSessionsRoute(pathname: string): boolean {
2
+ return pathname === "/sessions" || pathname.startsWith("/sessions/");
3
+ }
4
+
5
+ export function shouldDefaultOpenAnalyticsSidebar(pathname: string): boolean {
6
+ return !isAnalyticsSessionsRoute(pathname);
7
+ }
@@ -464,6 +464,10 @@
464
464
  container: analytics-session-detail / inline-size;
465
465
  }
466
466
 
467
+ .analytics-session-detail-timeline {
468
+ display: none;
469
+ }
470
+
467
471
  @container analytics-session-detail (min-width: 760px) {
468
472
  .analytics-session-detail-header {
469
473
  flex-direction: row;
@@ -476,6 +480,10 @@
476
480
  .analytics-session-detail-workbench {
477
481
  grid-template-columns: minmax(0, 1fr) 330px;
478
482
  }
483
+
484
+ .analytics-session-detail-timeline {
485
+ display: flex;
486
+ }
479
487
  }
480
488
 
481
489
  /* The dashboard grid responds to the available content width — which shrinks
@@ -4,6 +4,10 @@ import {
4
4
  } from "@agent-native/core/client";
5
5
  import { useLocation } from "react-router";
6
6
 
7
+ import {
8
+ ANALYTICS_CHAT_STORAGE_KEY,
9
+ hasRecentAnalyticsChat,
10
+ } from "@/lib/chat-handoff";
7
11
  import { rememberLastOpened } from "@/lib/last-opened";
8
12
  import { TAB_ID } from "@/lib/tab-id";
9
13
 
@@ -102,7 +106,9 @@ export function useNavigationState() {
102
106
  },
103
107
  onNavigate: (_command, path) => {
104
108
  if (location.pathname === "/ask" && pathnameFromPath(path) !== "/ask") {
105
- markAgentChatHomeHandoff("analytics");
109
+ if (hasRecentAnalyticsChat()) {
110
+ markAgentChatHomeHandoff(ANALYTICS_CHAT_STORAGE_KEY);
111
+ }
106
112
  }
107
113
  },
108
114
  });
@@ -0,0 +1,34 @@
1
+ export const ANALYTICS_CHAT_STORAGE_KEY = "analytics";
2
+
3
+ export const ANALYTICS_RECENT_CHAT_HANDOFF_TTL_MS = 5 * 60 * 1000;
4
+
5
+ const ANALYTICS_LAST_CHAT_ACTIVITY_KEY =
6
+ "agent-native.analytics.last-chat-activity-at";
7
+
8
+ function readAnalyticsLastChatActivityAt(): number {
9
+ if (typeof window === "undefined") return 0;
10
+ try {
11
+ const raw = window.sessionStorage.getItem(ANALYTICS_LAST_CHAT_ACTIVITY_KEY);
12
+ const parsed = raw ? Number.parseInt(raw, 10) : 0;
13
+ return Number.isFinite(parsed) ? parsed : 0;
14
+ } catch {
15
+ return 0;
16
+ }
17
+ }
18
+
19
+ export function markAnalyticsChatActivity(now = Date.now()): void {
20
+ if (typeof window === "undefined") return;
21
+ try {
22
+ window.sessionStorage.setItem(
23
+ ANALYTICS_LAST_CHAT_ACTIVITY_KEY,
24
+ String(now),
25
+ );
26
+ } catch {}
27
+ }
28
+
29
+ export function hasRecentAnalyticsChat(now = Date.now()): boolean {
30
+ const lastChatAt = readAnalyticsLastChatActivityAt();
31
+ return (
32
+ lastChatAt > 0 && now - lastChatAt <= ANALYTICS_RECENT_CHAT_HANDOFF_TTL_MS
33
+ );
34
+ }
@@ -1,25 +1,15 @@
1
- import {
2
- AgentChatSurface,
3
- markAgentChatHomeHandoff,
4
- useT,
5
- } from "@agent-native/core/client";
6
- import { useEffect } from "react";
1
+ import { AgentChatSurface, useT } from "@agent-native/core/client";
2
+ import { useState } from "react";
7
3
 
4
+ import {
5
+ ANALYTICS_CHAT_STORAGE_KEY,
6
+ hasRecentAnalyticsChat,
7
+ } from "@/lib/chat-handoff";
8
8
  import { TAB_ID } from "@/lib/tab-id";
9
9
 
10
10
  export default function AskPage() {
11
11
  const t = useT();
12
-
13
- useEffect(() => {
14
- function handleChatRunning(event: Event) {
15
- const detail = (event as CustomEvent).detail;
16
- if (detail?.isRunning === true) markAgentChatHomeHandoff("analytics");
17
- }
18
-
19
- window.addEventListener("agentNative.chatRunning", handleChatRunning);
20
- return () =>
21
- window.removeEventListener("agentNative.chatRunning", handleChatRunning);
22
- }, []);
12
+ const [restoreActiveThread] = useState(() => hasRecentAnalyticsChat());
23
13
 
24
14
  return (
25
15
  <div className="analytics-ask-page flex h-full min-h-0 flex-col bg-background">
@@ -28,7 +18,8 @@ export default function AskPage() {
28
18
  chatViewTransition
29
19
  className="analytics-chat-panel"
30
20
  defaultMode="chat"
31
- storageKey="analytics"
21
+ storageKey={ANALYTICS_CHAT_STORAGE_KEY}
22
+ restoreActiveThread={restoreActiveThread}
32
23
  browserTabId={TAB_ID}
33
24
  showHeader={false}
34
25
  showTabBar={false}
@@ -28,7 +28,7 @@ import {
28
28
  import {
29
29
  IconArchive,
30
30
  IconClock,
31
- IconDots,
31
+ IconDotsVertical,
32
32
  IconEye,
33
33
  IconEyeOff,
34
34
  IconGripVertical,
@@ -1391,7 +1391,7 @@ export default function SqlDashboardPage() {
1391
1391
  className="text-muted-foreground hover:text-foreground"
1392
1392
  aria-label={t("sqlDashboard.dashboardActions")}
1393
1393
  >
1394
- <IconDots className="h-4 w-4" />
1394
+ <IconDotsVertical className="h-4 w-4" />
1395
1395
  </Button>
1396
1396
  </DropdownMenuTrigger>
1397
1397
  </TooltipTrigger>
@@ -110,6 +110,11 @@ type SkipRange = {
110
110
  endMs: number;
111
111
  };
112
112
 
113
+ type ReplayViewportDimensions = {
114
+ width: number;
115
+ height: number;
116
+ };
117
+
113
118
  const DEFAULT_PLAYER_WIDTH = 1024;
114
119
  const DEFAULT_PLAYER_HEIGHT = 640;
115
120
  const DEFAULT_SPEED = 2;
@@ -334,9 +339,12 @@ function ReplayPlayer({
334
339
  height: number;
335
340
  } | null>(null);
336
341
  const [fitScale, setFitScale] = useState(1);
342
+ const initialDims = useMemo(() => replayViewportDimensions(events), [events]);
337
343
 
338
- const playerWidth = streamedDims?.width ?? DEFAULT_PLAYER_WIDTH;
339
- const playerHeight = streamedDims?.height ?? DEFAULT_PLAYER_HEIGHT;
344
+ const playerWidth =
345
+ streamedDims?.width ?? initialDims?.width ?? DEFAULT_PLAYER_WIDTH;
346
+ const playerHeight =
347
+ streamedDims?.height ?? initialDims?.height ?? DEFAULT_PLAYER_HEIGHT;
340
348
  const skipRanges = useMemo(() => buildIdleSkipRanges(events), [events]);
341
349
  const skipRangesRef = useLiveRef(skipRanges);
342
350
  const skipInactiveRef = useLiveRef(skipInactive);
@@ -414,7 +422,7 @@ function ReplayPlayer({
414
422
  if (cancelled || !stageRootRef.current) return;
415
423
 
416
424
  stageRootRef.current.innerHTML = "";
417
- setStreamedDims(null);
425
+ setStreamedDims(initialDims);
418
426
  localReplayer = new Replayer(events as any[], {
419
427
  root: stageRootRef.current,
420
428
  speed: DEFAULT_SPEED,
@@ -431,6 +439,7 @@ function ReplayPlayer({
431
439
  const meta = localReplayer.getMetaData?.();
432
440
  const total = Number(meta?.totalTime ?? replayDuration(events));
433
441
  setTotalTime(Number.isFinite(total) ? total : 0);
442
+ applyReplayFrameDimensions(localReplayer, initialDims);
434
443
  localReplayer.on?.("finish", () => {
435
444
  setPlaying(false);
436
445
  const finalTime = Number(localReplayer.getCurrentTime?.() ?? total);
@@ -438,10 +447,20 @@ function ReplayPlayer({
438
447
  });
439
448
  localReplayer.on?.("resize", (payload: unknown) => {
440
449
  const dims = payload as { width?: unknown; height?: unknown };
441
- if (typeof dims.width === "number" && typeof dims.height === "number") {
442
- setStreamedDims({ width: dims.width, height: dims.height });
450
+ const nextDims = normalizeReplayDimensions(dims.width, dims.height);
451
+ if (nextDims) {
452
+ applyReplayFrameDimensions(localReplayer, nextDims);
453
+ revealReplayFrame(localReplayer);
454
+ setStreamedDims(nextDims);
443
455
  }
444
456
  });
457
+ localReplayer.on?.("fullsnapshot-rebuilded", () => {
458
+ applyReplayFrameDimensions(
459
+ localReplayer,
460
+ replayViewportDimensions(events) ?? initialDims,
461
+ );
462
+ revealReplayFrame(localReplayer);
463
+ });
445
464
  updateTime(0);
446
465
  setStatus("ready");
447
466
  try {
@@ -456,6 +475,13 @@ function ReplayPlayer({
456
475
  }
457
476
  setPlaying(false);
458
477
  }
478
+ window.requestAnimationFrame(() => {
479
+ applyReplayFrameDimensions(
480
+ localReplayer,
481
+ replayViewportDimensions(events) ?? initialDims,
482
+ );
483
+ revealReplayFrame(localReplayer);
484
+ });
459
485
  }
460
486
 
461
487
  void loadReplay().catch((loadError: any) => {
@@ -479,7 +505,7 @@ function ReplayPlayer({
479
505
  replayerRef.current = null;
480
506
  if (stageRootRef.current) stageRootRef.current.innerHTML = "";
481
507
  };
482
- }, [events, t, updateTime]);
508
+ }, [events, initialDims, t, updateTime]);
483
509
 
484
510
  useEffect(() => {
485
511
  if (!playing || status !== "ready") {
@@ -842,7 +868,7 @@ function ReplayTimeline({
842
868
  const t = useT();
843
869
  const visibleMarkers = markers.slice(0, 120);
844
870
  return (
845
- <Card className="flex min-h-0 overflow-hidden">
871
+ <Card className="analytics-session-detail-timeline min-h-0 overflow-hidden">
846
872
  <CardContent className="flex min-h-0 flex-1 flex-col p-0">
847
873
  <div className="shrink-0 border-b px-3 py-2">
848
874
  <div className="flex items-center gap-2 text-sm font-semibold">
@@ -925,7 +951,7 @@ function DetailSkeleton() {
925
951
  return (
926
952
  <div className="analytics-session-detail-workbench grid min-h-0 flex-1 gap-3">
927
953
  <Skeleton className="h-full min-h-[420px] w-full" />
928
- <Skeleton className="h-full min-h-[420px] w-full" />
954
+ <Skeleton className="analytics-session-detail-timeline h-full min-h-[420px] w-full" />
929
955
  </div>
930
956
  );
931
957
  }
@@ -1008,13 +1034,13 @@ function sanitizeMutationData(data: AnyRecord): AnyRecord {
1008
1034
  typeof copy.value === "string" &&
1009
1035
  containsStylesheetNetworkLoad(copy.value)
1010
1036
  ) {
1011
- copy.value = "";
1037
+ copy.value = sanitizeCssText(copy.value);
1012
1038
  }
1013
1039
  if (
1014
1040
  typeof copy.textContent === "string" &&
1015
1041
  containsStylesheetNetworkLoad(copy.textContent)
1016
1042
  ) {
1017
- copy.textContent = "";
1043
+ copy.textContent = sanitizeCssText(copy.textContent);
1018
1044
  }
1019
1045
  return copy;
1020
1046
  });
@@ -1035,21 +1061,12 @@ function sanitizeSerializedNode(node: unknown): AnyRecord | null {
1035
1061
  childNodes: [],
1036
1062
  };
1037
1063
  }
1038
- if (next.type === 2 && tagName === "style") {
1039
- return {
1040
- ...next,
1041
- attributes: isRecord(next.attributes)
1042
- ? sanitizeAttributes(next.attributes)
1043
- : {},
1044
- childNodes: [],
1045
- };
1046
- }
1047
1064
  if (
1048
1065
  next.type === 3 &&
1049
1066
  typeof next.textContent === "string" &&
1050
1067
  containsStylesheetNetworkLoad(next.textContent)
1051
1068
  ) {
1052
- next.textContent = "";
1069
+ next.textContent = sanitizeCssText(next.textContent);
1053
1070
  }
1054
1071
  if (
1055
1072
  next.type === 2 &&
@@ -1073,6 +1090,13 @@ function containsStylesheetNetworkLoad(value: string): boolean {
1073
1090
  return /@import\b/i.test(value) || /\burl\s*\(/i.test(value);
1074
1091
  }
1075
1092
 
1093
+ function sanitizeCssText(value: string): string {
1094
+ if (!containsStylesheetNetworkLoad(value)) return value;
1095
+ return value
1096
+ .replace(/@import\s+(?:url\s*\()?[^;{}]+;?/gi, "")
1097
+ .replace(/\burl\s*\((?:\\.|[^\\)])*\)/gi, "none");
1098
+ }
1099
+
1076
1100
  function sanitizeAttributes(attributes: AnyRecord): AnyRecord {
1077
1101
  const next: AnyRecord = {};
1078
1102
  for (const [key, value] of Object.entries(attributes)) {
@@ -1080,7 +1104,11 @@ function sanitizeAttributes(attributes: AnyRecord): AnyRecord {
1080
1104
  if (normalized.startsWith("on")) continue;
1081
1105
  if (normalized === "srcdoc") continue;
1082
1106
  if (isReplayResourceAttribute(normalized)) continue;
1083
- if (normalized === "style" && /\burl\s*\(/i.test(String(value))) continue;
1107
+ if (normalized === "style") {
1108
+ const style = sanitizeCssText(String(value));
1109
+ if (style.trim()) next[key] = style;
1110
+ continue;
1111
+ }
1084
1112
  next[key] = value;
1085
1113
  }
1086
1114
  return next;
@@ -1240,6 +1268,66 @@ function currentUrlAt(events: AnyReplayEvent[], currentTime: number): string {
1240
1268
  return current;
1241
1269
  }
1242
1270
 
1271
+ export function replayViewportDimensions(
1272
+ events: AnyReplayEvent[],
1273
+ ): ReplayViewportDimensions | null {
1274
+ for (const event of events) {
1275
+ if (event.type !== RRWEB_EVENT_TYPE.Meta) continue;
1276
+ const dims = normalizeReplayDimensions(
1277
+ event.data?.width,
1278
+ event.data?.height,
1279
+ );
1280
+ if (dims) return dims;
1281
+ }
1282
+ return null;
1283
+ }
1284
+
1285
+ function normalizeReplayDimensions(
1286
+ width: unknown,
1287
+ height: unknown,
1288
+ ): ReplayViewportDimensions | null {
1289
+ if (
1290
+ typeof width !== "number" ||
1291
+ typeof height !== "number" ||
1292
+ !Number.isFinite(width) ||
1293
+ !Number.isFinite(height) ||
1294
+ width <= 0 ||
1295
+ height <= 0
1296
+ ) {
1297
+ return null;
1298
+ }
1299
+ return {
1300
+ width: Math.round(width),
1301
+ height: Math.round(height),
1302
+ };
1303
+ }
1304
+
1305
+ function applyReplayFrameDimensions(
1306
+ replayer: any,
1307
+ dims: ReplayViewportDimensions | null,
1308
+ ) {
1309
+ const width = dims?.width ?? DEFAULT_PLAYER_WIDTH;
1310
+ const height = dims?.height ?? DEFAULT_PLAYER_HEIGHT;
1311
+ const wrapper = replayer?.wrapper as HTMLElement | undefined;
1312
+ const iframe = replayer?.iframe as HTMLIFrameElement | undefined;
1313
+ const mouseTail = replayer?.mouseTail as HTMLCanvasElement | undefined;
1314
+ for (const element of [wrapper, iframe]) {
1315
+ if (!element) continue;
1316
+ element.style.width = `${width}px`;
1317
+ element.style.height = `${height}px`;
1318
+ }
1319
+ for (const element of [iframe, mouseTail]) {
1320
+ if (!element) continue;
1321
+ element.setAttribute("width", String(width));
1322
+ element.setAttribute("height", String(height));
1323
+ }
1324
+ }
1325
+
1326
+ function revealReplayFrame(replayer: any) {
1327
+ const iframe = replayer?.iframe as HTMLIFrameElement | undefined;
1328
+ if (iframe) iframe.style.display = "inherit";
1329
+ }
1330
+
1243
1331
  function replayStartedAt(events: AnyReplayEvent[]): number {
1244
1332
  const first = events.find((event) =>
1245
1333
  Number.isFinite(Number(event.timestamp)),
@@ -235,7 +235,7 @@ export default function SessionsPage() {
235
235
  >
236
236
  <span className="inline-flex h-10 w-[92px] items-center justify-center gap-2 rounded-md bg-primary/10 font-medium text-primary">
237
237
  <IconPlayerPlay className="h-4 w-4 fill-current" />
238
- {formatDuration(recording.durationMs)}
238
+ {formatSessionDuration(recording.durationMs)}
239
239
  </span>
240
240
  <span className="min-w-0">
241
241
  <span className="block truncate text-sm font-medium text-foreground">
@@ -523,13 +523,11 @@ function formatDateTime(value: string): string {
523
523
  }).format(date);
524
524
  }
525
525
 
526
- function formatDuration(ms: number | null): string {
527
- if (!ms || !Number.isFinite(ms) || ms <= 0) return "0s";
526
+ export function formatSessionDuration(ms: number | null): string {
527
+ if (!ms || !Number.isFinite(ms) || ms <= 0) return "0m";
528
528
  const seconds = Math.round(ms / 1000);
529
- if (seconds < 60) return `${seconds}s`;
530
529
  const minutes = Math.floor(seconds / 60);
531
- const remainingSeconds = seconds % 60;
532
- if (minutes < 60) return `${minutes}m ${remainingSeconds}s`;
530
+ if (minutes < 60) return `${minutes}m`;
533
531
  const hours = Math.floor(minutes / 60);
534
532
  const remainingMinutes = minutes % 60;
535
533
  return `${hours}h ${remainingMinutes}m`;
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-30
4
+ ---
5
+
6
+ Ask opens a fresh chat unless you were just chatting, and dashboard navigation keeps the sidebar closed from an empty Ask state.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-30
4
+ ---
5
+
6
+ Dashboard email reports are more reliable and use cleaner header copy.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-30
4
+ ---
5
+
6
+ Dashboard toolbar controls use a quieter share button and vertical actions menu.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-30
4
+ ---
5
+
6
+ Session replay rows show whole-minute durations in the playlist.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-30
4
+ ---
5
+
6
+ Session replays now render captured styling and keep the event timeline out of compact layouts.
@@ -41,14 +41,27 @@ export async function runDashboardReportsOnce(): Promise<{
41
41
  for (const sub of batch) {
42
42
  processed++;
43
43
  try {
44
- await runWithRequestContext(
44
+ const result = await runWithRequestContext(
45
45
  {
46
46
  userEmail: sub.ownerEmail,
47
47
  orgId: sub.orgId ?? undefined,
48
48
  },
49
- () => sendDashboardReportSubscription(sub),
49
+ () =>
50
+ sendDashboardReportSubscription(sub, { requireScreenshot: true }),
50
51
  );
51
- await markDashboardReportResult(sub, "success");
52
+ if (result.screenshotAttached) {
53
+ await markDashboardReportResult(sub, "success");
54
+ } else {
55
+ failed++;
56
+ const message = result.screenshotError
57
+ ? `Dashboard screenshot unavailable: ${result.screenshotError}`
58
+ : "Dashboard screenshot unavailable";
59
+ console.error(
60
+ `[dashboard-report] Subscription ${sub.id} sent without a screenshot:`,
61
+ message,
62
+ );
63
+ await markDashboardReportResult(sub, "error", message);
64
+ }
52
65
  } catch (err: any) {
53
66
  failed++;
54
67
  const message = err?.message ?? String(err);