@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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +53 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/design-connect.ts +419 -19
- package/corpus/core/src/client/AgentPanel.tsx +33 -1
- package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
- package/corpus/core/src/client/chat/index.ts +1 -0
- package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
- package/corpus/core/src/client/chat-view-transition.ts +33 -2
- package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
- package/corpus/core/src/client/index.ts +1 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
- package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
- package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
- package/corpus/core/src/db/client.ts +29 -2
- package/corpus/core/src/provider-api/index.ts +29 -0
- package/corpus/core/src/server/core-routes-plugin.ts +58 -20
- package/corpus/core/src/styles/agent-native.css +54 -0
- package/corpus/core/src/usage/store.ts +2 -2
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
- package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
- package/corpus/templates/analytics/app/global.css +8 -0
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
- package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
- package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
- package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
- package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
- package/corpus/templates/assets/app/global.css +15 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
- package/corpus/templates/assets/app/routes/library.tsx +90 -16
- package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
- package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
- package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
- package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
- package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
- package/corpus/templates/design/AGENTS.md +27 -0
- package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
- package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +63 -20
- package/corpus/templates/design/actions/apply-motion-edit.ts +67 -69
- package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
- package/corpus/templates/design/actions/capture-design-state.ts +27 -4
- package/corpus/templates/design/actions/connect-localhost.ts +59 -22
- package/corpus/templates/design/actions/export-zip.ts +23 -8
- package/corpus/templates/design/actions/generate-design.ts +53 -1
- package/corpus/templates/design/actions/get-component-details.ts +5 -19
- package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
- package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
- package/corpus/templates/design/actions/index-design-tokens.ts +10 -1
- package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
- package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
- package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
- package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
- package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
- package/corpus/templates/design/actions/navigate.ts +18 -3
- package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
- package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
- package/corpus/templates/design/actions/provider-api-request.ts +3 -3
- package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
- package/corpus/templates/design/actions/run-design-audit.ts +53 -4
- package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
- package/corpus/templates/design/actions/view-screen.ts +1 -1
- package/corpus/templates/design/actions/write-local-file.ts +213 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +230 -3308
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +776 -287
- package/corpus/templates/design/app/components/design/EditPanel.tsx +212 -208
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +14 -2
- package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +235 -266
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +490 -55
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +43 -62
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +34 -61
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +251 -0
- package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
- package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
- package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
- package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
- package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
- package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
- package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
- package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/design/app/global.css +7 -0
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +21 -0
- package/corpus/templates/design/app/i18n-data.ts +358 -0
- package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +2402 -499
- package/corpus/templates/design/app/pages/Index.tsx +5 -1
- package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +168 -65
- package/corpus/templates/design/package.json +3 -1
- package/corpus/templates/design/server/db/schema.ts +23 -0
- package/corpus/templates/design/server/handlers/uploads.ts +22 -1
- package/corpus/templates/design/server/lib/provider-api.ts +7 -4
- package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
- package/corpus/templates/design/server/plugins/db.ts +39 -0
- package/corpus/templates/design/server/register-secrets.ts +38 -0
- package/corpus/templates/design/shared/board-file.ts +228 -0
- package/corpus/templates/design/shared/board-objects.ts +288 -0
- package/corpus/templates/design/shared/code-layer.ts +52 -0
- package/corpus/templates/design/shared/motion-compiler.ts +58 -7
- package/corpus/templates/design/shared/motion-timeline.ts +2 -2
- package/corpus/templates/design/tsconfig.json +2 -1
- package/corpus/templates/slides/AGENTS.md +9 -0
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
- package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
- package/corpus/templates/slides/app/vite-env.d.ts +18 -0
- package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
- package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
- package/corpus/templates/slides/package.json +1 -0
- package/corpus/templates/slides/vite.config.ts +1 -0
- package/dist/cli/design-connect.d.ts +25 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +357 -18
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +18 -3
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
- package/dist/client/ConnectBuilderCard.js +1 -0
- package/dist/client/ConnectBuilderCard.js.map +1 -1
- package/dist/client/chat/index.d.ts +1 -1
- package/dist/client/chat/index.d.ts.map +1 -1
- package/dist/client/chat/index.js +1 -1
- package/dist/client/chat/index.js.map +1 -1
- package/dist/client/chat/run-recovery.d.ts.map +1 -1
- package/dist/client/chat/run-recovery.js +5 -5
- package/dist/client/chat/run-recovery.js.map +1 -1
- package/dist/client/chat-view-transition.d.ts +6 -0
- package/dist/client/chat-view-transition.d.ts.map +1 -1
- package/dist/client/chat-view-transition.js +23 -2
- package/dist/client/chat-view-transition.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +3 -2
- package/dist/client/composer/PromptComposer.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/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +11 -4
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/sharing/ShareButton.js +3 -3
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
- package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.js +20 -6
- package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +1 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +28 -2
- package/dist/db/client.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +8 -8
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/index.d.ts +5 -4
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +28 -0
- package/dist/provider-api/index.js.map +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/core-routes-plugin.d.ts +25 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +43 -18
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +54 -0
- package/dist/usage/store.js +2 -2
- package/dist/usage/store.js.map +1 -1
- 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:
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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
|
-
<
|
|
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 =
|
|
339
|
-
|
|
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(
|
|
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
|
-
|
|
442
|
-
|
|
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="
|
|
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"
|
|
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
|
-
{
|
|
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
|
|
527
|
-
if (!ms || !Number.isFinite(ms) || ms <= 0) return "
|
|
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
|
-
|
|
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`;
|
|
@@ -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
|
-
() =>
|
|
49
|
+
() =>
|
|
50
|
+
sendDashboardReportSubscription(sub, { requireScreenshot: true }),
|
|
50
51
|
);
|
|
51
|
-
|
|
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);
|