@agent-native/core 0.120.2 → 0.120.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +3 -3
- package/corpus/core/CHANGELOG.md +28 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/context-xray-local.ts +1 -1
- package/corpus/core/src/cli/index.ts +63 -18
- package/corpus/core/src/cli/skills.ts +5 -0
- package/corpus/core/src/cli/telemetry.ts +118 -1
- package/corpus/core/src/client/AgentPanel.tsx +10 -0
- package/corpus/core/src/client/CommandMenu.tsx +13 -1
- package/corpus/core/src/client/agent-page/AgentTabsPage.tsx +39 -4
- package/corpus/core/src/client/analytics.ts +13 -0
- package/corpus/core/src/client/error-capture.ts +6 -1
- package/corpus/core/src/client/extensions/ExtensionsListPage.tsx +12 -5
- package/corpus/core/src/client/extensions/agent-native-extension-runtime.ts +1 -1
- package/corpus/core/src/client/navigation/index.ts +2 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +14 -32
- package/corpus/core/src/client/settings/ExtensionsSettingsContent.tsx +1 -1
- package/corpus/core/src/extensions/theme.ts +1 -1
- package/corpus/core/src/localization/default-messages.ts +2 -0
- package/corpus/core/src/server/core-routes-plugin.ts +10 -1
- package/corpus/core/src/server/framework-request-handler.ts +17 -26
- package/corpus/core/src/styles/agent-native.css +13 -0
- package/corpus/core/src/templates/chat/app/components/layout/Layout.tsx +3 -0
- package/corpus/core/src/templates/chat/app/components/layout/Sidebar.tsx +78 -45
- package/corpus/core/src/templates/chat/app/global.css +1 -1
- package/corpus/core/src/templates/default/app/global.css +1 -1
- package/corpus/core/src/tracking/error-capture.ts +151 -0
- package/corpus/core/src/tracking/index.ts +5 -0
- package/corpus/templates/analytics/app/components/dashboard/CumulativeNetChart.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +1 -4
- package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/DataTable.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/DateRangePicker.tsx +2 -2
- package/corpus/templates/analytics/app/components/dashboard/MetricCard.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/RecentActivity.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/RevenueChart.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/RevenueComparisonChart.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +41 -9
- package/corpus/templates/analytics/app/components/dashboard/StatsCard.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/TimeSeriesChart.tsx +1 -1
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +2 -0
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +28 -14
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +152 -124
- package/corpus/templates/analytics/app/global.css +5 -5
- package/corpus/templates/analytics/app/lib/dashboard-report-capture.ts +42 -0
- package/corpus/templates/analytics/app/pages/adhoc/_shared/KpiChart.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/ChartTypePicker.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/DateRangePicker.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/EventRow.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/SqlPreview.tsx +2 -2
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +3 -3
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +2 -2
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +162 -51
- package/corpus/templates/analytics/app/root.tsx +28 -0
- package/corpus/templates/analytics/changelog/2026-07-24-analytics-charts-now-explain-slow-loads-and-offer-a-direct-r.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-24-daily-dashboard-emails-now-load-signed-reports-more-reliably.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-24-manage-agent-is-clearly-marked-as-a-link-to-its-dedicated-pa.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +1 -1
- package/corpus/templates/analytics/server/db/schema.ts +5 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +21 -0
- package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +11 -0
- package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +54 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +235 -26
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +4 -1
- package/corpus/templates/analytics/server/plugins/db.ts +9 -0
- package/corpus/templates/analytics/shared/dashboard-report-timeouts.ts +1 -1
- package/corpus/templates/assets/app/components/asset/AssetPreviewDialog.tsx +376 -0
- package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +14 -4
- package/corpus/templates/assets/app/components/layout/Layout.tsx +5 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +118 -74
- package/corpus/templates/assets/app/global.css +1 -1
- package/corpus/templates/assets/app/i18n-data.ts +10 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +99 -10
- package/corpus/templates/assets/app/routes/library.tsx +21 -198
- package/corpus/templates/assets/changelog/2026-07-21-unified-asset-preview-click-any-asset-to-open-one-preview-wi.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/brain/app/components/layout/Layout.tsx +3 -0
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +76 -43
- package/corpus/templates/brain/app/global.css +1 -1
- package/corpus/templates/brain/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/brain/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +67 -52
- package/corpus/templates/calendar/app/global.css +1 -1
- package/corpus/templates/calendar/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/calendar/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/chat/app/components/layout/Layout.tsx +3 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +78 -45
- package/corpus/templates/chat/app/global.css +1 -1
- package/corpus/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +65 -31
- package/corpus/templates/clips/app/components/library/organization-switcher.tsx +2 -2
- package/corpus/templates/clips/app/components/player/video-player.tsx +16 -1
- package/corpus/templates/clips/app/global.css +1 -1
- package/corpus/templates/clips/app/hooks/use-view-tracking.ts +55 -12
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +6 -5
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +7 -7
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -5
- package/corpus/templates/clips/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/clips/server/plugins/db.ts +113 -68
- package/corpus/templates/content/actions/update-document.ts +145 -91
- package/corpus/templates/content/app/components/editor/CommentComposer.tsx +4 -1
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +366 -164
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +51 -28
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +19 -8
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +66 -42
- package/corpus/templates/content/app/global.css +1 -1
- package/corpus/templates/content/app/i18n-data.ts +15 -0
- package/corpus/templates/content/changelog/2026-07-23-shared-pages-now-keep-durable-content-visible-and-comment-th.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-comment-drafts-now-stay-open-when-saving-fails-and-long-live.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/design/actions/update-file.ts +85 -18
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +65 -30
- package/corpus/templates/design/app/entry.client.tsx +14 -1
- package/corpus/templates/design/app/global.css +1 -1
- package/corpus/templates/design/app/lib/design-save-outbox.ts +39 -0
- package/corpus/templates/design/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/design/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/dispatch/app/global.css +1 -1
- package/corpus/templates/dispatch/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/forms/app/components/layout/Layout.tsx +3 -0
- package/corpus/templates/forms/app/components/layout/Sidebar.tsx +72 -37
- package/corpus/templates/forms/app/global.css +1 -1
- package/corpus/templates/forms/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-24-forms-sidebar-actions-now-keep-search-language-feedback-and-.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/macros/app/components/DailyProgress.tsx +5 -5
- package/corpus/templates/macros/app/components/ExerciseCard.tsx +2 -2
- package/corpus/templates/macros/app/components/MealCard.tsx +2 -2
- package/corpus/templates/macros/app/components/WeightCard.tsx +2 -2
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +56 -29
- package/corpus/templates/macros/app/components/ui/button.tsx +1 -2
- package/corpus/templates/macros/app/components/ui/card.tsx +1 -1
- package/corpus/templates/macros/app/global.css +1 -1
- package/corpus/templates/macros/app/routes/_index.tsx +1 -1
- package/corpus/templates/macros/app/routes/analytics.tsx +4 -7
- package/corpus/templates/macros/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/macros/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +125 -37
- package/corpus/templates/mail/app/global.css +1 -1
- package/corpus/templates/mail/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/mail/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/plan/app/components/layout/Layout.tsx +3 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +90 -60
- package/corpus/templates/plan/app/global.css +1 -1
- package/corpus/templates/plan/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/slides/app/components/layout/Sidebar.tsx +69 -32
- package/corpus/templates/slides/app/global.css +1 -1
- package/corpus/templates/slides/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/tasks/app/global.css +1 -1
- package/corpus/templates/tasks/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/toolkit/CHANGELOG.md +14 -0
- package/corpus/toolkit/README.md +8 -1
- package/corpus/toolkit/agent-native.eject.json +1 -1
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/app-shell/index.ts +1 -0
- package/corpus/toolkit/src/app-shell/sidebar-footer-actions.tsx +48 -0
- package/corpus/toolkit/src/chat-history.css +52 -1
- package/corpus/toolkit/src/design-system/default-adapter.tsx +1 -1
- package/corpus/toolkit/src/editor/useCollabReconcile.ts +83 -15
- package/corpus/toolkit/src/ui/button.tsx +1 -2
- package/corpus/toolkit/src/ui/card.tsx +1 -1
- package/corpus/toolkit/src/ui/table.tsx +4 -11
- package/dist/cli/context-xray-local.js +1 -1
- package/dist/cli/context-xray-local.js.map +1 -1
- package/dist/cli/index.js +56 -17
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +5 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/cli/telemetry.d.ts +7 -0
- package/dist/cli/telemetry.d.ts.map +1 -1
- package/dist/cli/telemetry.js +91 -1
- package/dist/cli/telemetry.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +7 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +2 -2
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/CommandMenu.d.ts +2 -0
- package/dist/client/CommandMenu.d.ts.map +1 -1
- package/dist/client/CommandMenu.js +12 -1
- package/dist/client/CommandMenu.js.map +1 -1
- package/dist/client/agent-page/AgentTabsPage.d.ts +4 -0
- package/dist/client/agent-page/AgentTabsPage.d.ts.map +1 -1
- package/dist/client/agent-page/AgentTabsPage.js +8 -4
- package/dist/client/agent-page/AgentTabsPage.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +7 -0
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/error-capture.d.ts.map +1 -1
- package/dist/client/error-capture.js +8 -1
- package/dist/client/error-capture.js.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.js +2 -2
- package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
- package/dist/client/extensions/agent-native-extension-runtime.js +1 -1
- package/dist/client/extensions/agent-native-extension-runtime.js.map +1 -1
- package/dist/client/navigation/index.d.ts +1 -1
- package/dist/client/navigation/index.d.ts.map +1 -1
- package/dist/client/navigation/index.js +1 -1
- package/dist/client/navigation/index.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +9 -13
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/settings/ExtensionsSettingsContent.js +1 -1
- package/dist/client/settings/ExtensionsSettingsContent.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/extensions/theme.js +1 -1
- package/dist/extensions/theme.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/default-messages.d.ts +2 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +2 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +8 -1
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/framework-request-handler.d.ts.map +1 -1
- package/dist/server/framework-request-handler.js +17 -27
- package/dist/server/framework-request-handler.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/styles/agent-native.css +13 -0
- package/dist/templates/chat/app/components/layout/Layout.tsx +3 -0
- package/dist/templates/chat/app/components/layout/Sidebar.tsx +78 -45
- package/dist/templates/chat/app/global.css +1 -1
- package/dist/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/dist/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/dist/templates/default/app/global.css +1 -1
- package/dist/tracking/error-capture.d.ts +14 -0
- package/dist/tracking/error-capture.d.ts.map +1 -0
- package/dist/tracking/error-capture.js +113 -0
- package/dist/tracking/error-capture.js.map +1 -0
- package/dist/tracking/index.d.ts +1 -0
- package/dist/tracking/index.d.ts.map +1 -1
- package/dist/tracking/index.js +1 -0
- package/dist/tracking/index.js.map +1 -1
- package/package.json +2 -2
- package/src/cli/context-xray-local.ts +1 -1
- package/src/cli/index.ts +63 -18
- package/src/cli/skills.ts +5 -0
- package/src/cli/telemetry.ts +118 -1
- package/src/client/AgentPanel.tsx +10 -0
- package/src/client/CommandMenu.tsx +13 -1
- package/src/client/agent-page/AgentTabsPage.tsx +39 -4
- package/src/client/analytics.ts +13 -0
- package/src/client/error-capture.ts +6 -1
- package/src/client/extensions/ExtensionsListPage.tsx +12 -5
- package/src/client/extensions/agent-native-extension-runtime.ts +1 -1
- package/src/client/navigation/index.ts +2 -0
- package/src/client/org/OrgSwitcher.tsx +14 -32
- package/src/client/settings/ExtensionsSettingsContent.tsx +1 -1
- package/src/extensions/theme.ts +1 -1
- package/src/localization/default-messages.ts +2 -0
- package/src/server/core-routes-plugin.ts +10 -1
- package/src/server/framework-request-handler.ts +17 -26
- package/src/styles/agent-native.css +13 -0
- package/src/templates/chat/app/components/layout/Layout.tsx +3 -0
- package/src/templates/chat/app/components/layout/Sidebar.tsx +78 -45
- package/src/templates/chat/app/global.css +1 -1
- package/src/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/src/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/src/templates/default/app/global.css +1 -1
- package/src/tracking/error-capture.ts +151 -0
- package/src/tracking/index.ts +5 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AgentSidebar,
|
|
3
3
|
focusAgentChat,
|
|
4
|
+
isAgentChatHomeHandoffActive,
|
|
4
5
|
navigateWithAgentChatViewTransition,
|
|
5
6
|
useAgentChatHomeHandoff,
|
|
6
7
|
useAgentChatHomeHandoffLinks,
|
|
@@ -57,6 +58,7 @@ export function Layout({ children }: LayoutProps) {
|
|
|
57
58
|
activePath: location.pathname,
|
|
58
59
|
enabled: !isChatRoute,
|
|
59
60
|
});
|
|
61
|
+
const chatHomeHandoffPending = isAgentChatHomeHandoffActive("chat");
|
|
60
62
|
useAgentChatHomeHandoffLinks({
|
|
61
63
|
storageKey: "chat",
|
|
62
64
|
isChatPath: (pathname) => pathname === "/" || pathname.startsWith("/chat/"),
|
|
@@ -164,6 +166,7 @@ export function Layout({ children }: LayoutProps) {
|
|
|
164
166
|
<AgentSidebar
|
|
165
167
|
position="right"
|
|
166
168
|
chatViewTransition
|
|
169
|
+
chatViewTransitionHandoff={chatHomeHandoffPending}
|
|
167
170
|
storageKey="chat"
|
|
168
171
|
browserTabId={TAB_ID}
|
|
169
172
|
openOnChatRunning={chatHomeHandoffActive}
|
|
@@ -4,9 +4,11 @@ import {
|
|
|
4
4
|
type ChatThreadSummary,
|
|
5
5
|
} from "@agent-native/core/client/agent-chat";
|
|
6
6
|
import { appPath } from "@agent-native/core/client/api-path";
|
|
7
|
-
import { useT } from "@agent-native/core/client/i18n";
|
|
7
|
+
import { LanguagePicker, useT } from "@agent-native/core/client/i18n";
|
|
8
|
+
import { openCommandMenu } from "@agent-native/core/client/navigation";
|
|
8
9
|
import { OrgSwitcher } from "@agent-native/core/client/org";
|
|
9
10
|
import { FeedbackButton } from "@agent-native/core/client/ui";
|
|
11
|
+
import { SidebarFooterActions } from "@agent-native/toolkit/app-shell";
|
|
10
12
|
import {
|
|
11
13
|
ChatHistoryRail,
|
|
12
14
|
type ChatHistoryItem,
|
|
@@ -16,6 +18,7 @@ import {
|
|
|
16
18
|
IconLayoutSidebarLeftCollapse,
|
|
17
19
|
IconLayoutSidebarLeftExpand,
|
|
18
20
|
IconMessageCircle,
|
|
21
|
+
IconSearch,
|
|
19
22
|
IconSettings,
|
|
20
23
|
} from "@tabler/icons-react";
|
|
21
24
|
import { useEffect, useMemo } from "react";
|
|
@@ -126,7 +129,7 @@ function chatThreadPath(threadId: string) {
|
|
|
126
129
|
return `/chat/${encodeURIComponent(threadId)}`;
|
|
127
130
|
}
|
|
128
131
|
|
|
129
|
-
function ChatThreadsSection() {
|
|
132
|
+
function ChatThreadsSection({ open }: { open: boolean }) {
|
|
130
133
|
const navigate = useNavigate();
|
|
131
134
|
const location = useLocation();
|
|
132
135
|
const t = useT();
|
|
@@ -230,36 +233,42 @@ function ChatThreadsSection() {
|
|
|
230
233
|
}
|
|
231
234
|
|
|
232
235
|
return (
|
|
233
|
-
<div
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
236
|
+
<div
|
|
237
|
+
className="an-chat-history-rail__collapse"
|
|
238
|
+
data-state={open ? "open" : "closed"}
|
|
239
|
+
aria-hidden={!open}
|
|
240
|
+
>
|
|
241
|
+
<div className="ms-4">
|
|
242
|
+
<ChatHistoryRail
|
|
243
|
+
items={chatItems}
|
|
244
|
+
activeId={displayedActiveThreadId}
|
|
245
|
+
onSelect={(threadId) => openThread(threadId)}
|
|
246
|
+
onNewChat={() => void handleNewChat()}
|
|
247
|
+
railLabels={{
|
|
248
|
+
newChat: t("chat.newChat"),
|
|
249
|
+
showMore: t("chat.chats"),
|
|
250
|
+
showLess: t("chat.chats"),
|
|
251
|
+
}}
|
|
252
|
+
renameMaxLength={160}
|
|
253
|
+
onTogglePin={(threadId) => {
|
|
254
|
+
const thread = visibleThreads.find((item) => item.id === threadId);
|
|
255
|
+
if (thread) void pinThread(threadId, !thread.pinnedAt);
|
|
256
|
+
}}
|
|
257
|
+
onRename={handleRenameThread}
|
|
258
|
+
onDelete={(threadId) => void handleArchiveThread(threadId)}
|
|
259
|
+
labels={{
|
|
260
|
+
options: (item) =>
|
|
261
|
+
t("chat.optionsFor", { title: item.titleText ?? "" }),
|
|
262
|
+
renameInput: (item) =>
|
|
263
|
+
t("chat.renameThread", { title: item.titleText ?? "" }),
|
|
264
|
+
rename: t("chat.renameChat"),
|
|
265
|
+
pin: t("chat.pinChat"),
|
|
266
|
+
unpin: t("chat.unpinChat"),
|
|
267
|
+
delete: t("chat.archiveChat"),
|
|
268
|
+
}}
|
|
269
|
+
className="min-w-0"
|
|
270
|
+
/>
|
|
271
|
+
</div>
|
|
263
272
|
</div>
|
|
264
273
|
);
|
|
265
274
|
}
|
|
@@ -281,14 +290,14 @@ export function Sidebar({
|
|
|
281
290
|
cn(
|
|
282
291
|
"flex items-center text-sm transition-colors",
|
|
283
292
|
collapsed
|
|
284
|
-
? "relative h-10 w-full justify-center rounded-none
|
|
293
|
+
? "relative h-10 w-full justify-center rounded-none px-0"
|
|
285
294
|
: "h-9 rounded-md gap-3 px-3",
|
|
286
295
|
isActive
|
|
287
296
|
? collapsed
|
|
288
|
-
? "
|
|
297
|
+
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
|
289
298
|
: "bg-sidebar-accent text-sidebar-accent-foreground"
|
|
290
299
|
: collapsed
|
|
291
|
-
? "
|
|
300
|
+
? "text-sidebar-foreground/70 hover:bg-sidebar-accent/55 hover:text-sidebar-accent-foreground"
|
|
292
301
|
: "text-sidebar-foreground hover:bg-sidebar-accent/65 hover:text-sidebar-accent-foreground",
|
|
293
302
|
);
|
|
294
303
|
const collapseButton = collapsible ? (
|
|
@@ -317,6 +326,31 @@ export function Sidebar({
|
|
|
317
326
|
</TooltipContent>
|
|
318
327
|
</Tooltip>
|
|
319
328
|
) : null;
|
|
329
|
+
const searchButton = (
|
|
330
|
+
<Tooltip>
|
|
331
|
+
<TooltipTrigger asChild>
|
|
332
|
+
<button
|
|
333
|
+
type="button"
|
|
334
|
+
onClick={openCommandMenu}
|
|
335
|
+
aria-label={t("root.commandSearch")}
|
|
336
|
+
className="flex size-8 items-center justify-center rounded-md text-sidebar-foreground/65 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
337
|
+
>
|
|
338
|
+
<IconSearch className="size-4" />
|
|
339
|
+
</button>
|
|
340
|
+
</TooltipTrigger>
|
|
341
|
+
<TooltipContent side="right">{t("root.commandSearch")}</TooltipContent>
|
|
342
|
+
</Tooltip>
|
|
343
|
+
);
|
|
344
|
+
const translateButton = (
|
|
345
|
+
<LanguagePicker variant="ghost-icon" label={t("settings.languageLabel")} />
|
|
346
|
+
);
|
|
347
|
+
const feedbackButton = (
|
|
348
|
+
<FeedbackButton
|
|
349
|
+
variant={collapsed ? "icon" : "sidebar"}
|
|
350
|
+
side="right"
|
|
351
|
+
className={collapsed ? "h-8 w-8" : "min-w-0"}
|
|
352
|
+
/>
|
|
353
|
+
);
|
|
320
354
|
|
|
321
355
|
return (
|
|
322
356
|
<aside
|
|
@@ -411,8 +445,8 @@ export function Sidebar({
|
|
|
411
445
|
) : (
|
|
412
446
|
link
|
|
413
447
|
)}
|
|
414
|
-
{!collapsed && item.view === "chat"
|
|
415
|
-
<ChatThreadsSection />
|
|
448
|
+
{!collapsed && item.view === "chat" ? (
|
|
449
|
+
<ChatThreadsSection open={isChatRoute} />
|
|
416
450
|
) : null}
|
|
417
451
|
</div>
|
|
418
452
|
);
|
|
@@ -465,14 +499,13 @@ export function Sidebar({
|
|
|
465
499
|
/>
|
|
466
500
|
</div>
|
|
467
501
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
) : null}
|
|
502
|
+
<SidebarFooterActions
|
|
503
|
+
collapsed={collapsed}
|
|
504
|
+
feedback={feedbackButton}
|
|
505
|
+
translate={translateButton}
|
|
506
|
+
search={searchButton}
|
|
507
|
+
collapse={collapseButton}
|
|
508
|
+
/>
|
|
476
509
|
</div>
|
|
477
510
|
</aside>
|
|
478
511
|
);
|
|
@@ -5,13 +5,15 @@ import {
|
|
|
5
5
|
import { appPath } from "@agent-native/core/client/api-path";
|
|
6
6
|
import { DevDatabaseLink } from "@agent-native/core/client/db-admin";
|
|
7
7
|
import { getBrowserTabId } from "@agent-native/core/client/hooks";
|
|
8
|
-
import { useT } from "@agent-native/core/client/i18n";
|
|
8
|
+
import { LanguagePicker, useT } from "@agent-native/core/client/i18n";
|
|
9
|
+
import { openCommandMenu } from "@agent-native/core/client/navigation";
|
|
9
10
|
import {
|
|
10
11
|
InvitationBanner,
|
|
11
12
|
OrgSwitcher,
|
|
12
13
|
useOrgRole,
|
|
13
14
|
} from "@agent-native/core/client/org";
|
|
14
15
|
import { FeedbackButton } from "@agent-native/core/client/ui";
|
|
16
|
+
import { SidebarFooterActions } from "@agent-native/toolkit/app-shell";
|
|
15
17
|
import {
|
|
16
18
|
IconInbox,
|
|
17
19
|
IconArchive,
|
|
@@ -30,6 +32,7 @@ import {
|
|
|
30
32
|
IconShare,
|
|
31
33
|
IconHierarchy2,
|
|
32
34
|
IconSettings,
|
|
35
|
+
IconSearch,
|
|
33
36
|
} from "@tabler/icons-react";
|
|
34
37
|
import { ReactNode, useEffect, useMemo, useState } from "react";
|
|
35
38
|
import { NavLink, useLocation, useParams } from "react-router";
|
|
@@ -154,6 +157,59 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
|
|
|
154
157
|
);
|
|
155
158
|
const [headerSlot, setHeaderSlot] = useState<HTMLElement | null>(null);
|
|
156
159
|
const showCollapsedSidebar = sidebarCollapsed && !isMobile;
|
|
160
|
+
|
|
161
|
+
const collapseButton = !isMobile ? (
|
|
162
|
+
<Tooltip>
|
|
163
|
+
<TooltipTrigger asChild>
|
|
164
|
+
<button
|
|
165
|
+
type="button"
|
|
166
|
+
aria-label={
|
|
167
|
+
showCollapsedSidebar
|
|
168
|
+
? t("navigation.expandSidebar")
|
|
169
|
+
: t("navigation.collapseSidebar")
|
|
170
|
+
}
|
|
171
|
+
className="flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent/60 hover:text-foreground"
|
|
172
|
+
onClick={() => setSidebarCollapsed((value) => !value)}
|
|
173
|
+
>
|
|
174
|
+
{showCollapsedSidebar ? (
|
|
175
|
+
<IconLayoutSidebarLeftExpand className="h-4 w-4" />
|
|
176
|
+
) : (
|
|
177
|
+
<IconLayoutSidebarLeftCollapse className="h-4 w-4" />
|
|
178
|
+
)}
|
|
179
|
+
</button>
|
|
180
|
+
</TooltipTrigger>
|
|
181
|
+
<TooltipContent side="right">
|
|
182
|
+
{showCollapsedSidebar
|
|
183
|
+
? t("navigation.expandSidebar")
|
|
184
|
+
: t("navigation.collapseSidebar")}
|
|
185
|
+
</TooltipContent>
|
|
186
|
+
</Tooltip>
|
|
187
|
+
) : null;
|
|
188
|
+
const searchButton = (
|
|
189
|
+
<Tooltip>
|
|
190
|
+
<TooltipTrigger asChild>
|
|
191
|
+
<button
|
|
192
|
+
type="button"
|
|
193
|
+
aria-label={t("root.commandSearch")}
|
|
194
|
+
className="flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent/60 hover:text-foreground"
|
|
195
|
+
onClick={openCommandMenu}
|
|
196
|
+
>
|
|
197
|
+
<IconSearch className="h-4 w-4" />
|
|
198
|
+
</button>
|
|
199
|
+
</TooltipTrigger>
|
|
200
|
+
<TooltipContent side="right">{t("root.commandSearch")}</TooltipContent>
|
|
201
|
+
</Tooltip>
|
|
202
|
+
);
|
|
203
|
+
const translateButton = (
|
|
204
|
+
<LanguagePicker variant="ghost-icon" label={t("settings.languageLabel")} />
|
|
205
|
+
);
|
|
206
|
+
const feedbackButton = (
|
|
207
|
+
<FeedbackButton
|
|
208
|
+
variant={showCollapsedSidebar ? "icon" : "sidebar"}
|
|
209
|
+
side="right"
|
|
210
|
+
className={showCollapsedSidebar ? "size-8" : "min-w-0"}
|
|
211
|
+
/>
|
|
212
|
+
);
|
|
157
213
|
const sidebarHasNewRecordingAction = isMobile
|
|
158
214
|
? sidebarOpen
|
|
159
215
|
: !sidebarCollapsed;
|
|
@@ -314,35 +370,6 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
|
|
|
314
370
|
</>
|
|
315
371
|
)}
|
|
316
372
|
</div>
|
|
317
|
-
|
|
318
|
-
<Tooltip>
|
|
319
|
-
<TooltipTrigger asChild>
|
|
320
|
-
<Button
|
|
321
|
-
type="button"
|
|
322
|
-
variant="ghost"
|
|
323
|
-
size="icon"
|
|
324
|
-
className="hidden h-7 w-7 shrink-0 text-muted-foreground hover:text-foreground md:inline-flex"
|
|
325
|
-
aria-label={
|
|
326
|
-
showCollapsedSidebar
|
|
327
|
-
? t("navigation.expandSidebar")
|
|
328
|
-
: t("navigation.collapseSidebar")
|
|
329
|
-
}
|
|
330
|
-
aria-expanded={!showCollapsedSidebar}
|
|
331
|
-
onClick={() => setSidebarCollapsed((value) => !value)}
|
|
332
|
-
>
|
|
333
|
-
{showCollapsedSidebar ? (
|
|
334
|
-
<IconLayoutSidebarLeftExpand className="h-4 w-4" />
|
|
335
|
-
) : (
|
|
336
|
-
<IconLayoutSidebarLeftCollapse className="h-4 w-4" />
|
|
337
|
-
)}
|
|
338
|
-
</Button>
|
|
339
|
-
</TooltipTrigger>
|
|
340
|
-
<TooltipContent side="right">
|
|
341
|
-
{showCollapsedSidebar
|
|
342
|
-
? t("navigation.expandSidebar")
|
|
343
|
-
: t("navigation.collapseSidebar")}
|
|
344
|
-
</TooltipContent>
|
|
345
|
-
</Tooltip>
|
|
346
373
|
</div>
|
|
347
374
|
<div className="min-h-0 flex-1 overflow-y-auto">
|
|
348
375
|
{showCollapsedSidebar ? (
|
|
@@ -592,10 +619,17 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
|
|
|
592
619
|
<div className="shrink-0 space-y-2 px-3 py-2">
|
|
593
620
|
<OrgSwitcher settingsPath="/settings/organization" />
|
|
594
621
|
<DevDatabaseLink />
|
|
595
|
-
<FeedbackButton />
|
|
596
622
|
</div>
|
|
597
623
|
</>
|
|
598
624
|
)}
|
|
625
|
+
<SidebarFooterActions
|
|
626
|
+
collapsed={showCollapsedSidebar}
|
|
627
|
+
feedback={feedbackButton}
|
|
628
|
+
translate={translateButton}
|
|
629
|
+
search={searchButton}
|
|
630
|
+
collapse={collapseButton}
|
|
631
|
+
className={showCollapsedSidebar ? undefined : "px-0 py-0"}
|
|
632
|
+
/>
|
|
599
633
|
</aside>
|
|
600
634
|
|
|
601
635
|
<AgentSidebar
|
|
@@ -59,8 +59,8 @@ export function OrganizationSwitcher({ className }: OrganizationSwitcherProps) {
|
|
|
59
59
|
<button
|
|
60
60
|
type="button"
|
|
61
61
|
className={cn(
|
|
62
|
-
"flex w-full items-center gap-2 rounded-md border
|
|
63
|
-
"hover:bg-accent",
|
|
62
|
+
"flex w-full items-center gap-2 rounded-md border-0 bg-accent/50 px-2 py-1.5 text-start",
|
|
63
|
+
"hover:bg-accent/70",
|
|
64
64
|
className,
|
|
65
65
|
)}
|
|
66
66
|
>
|
|
@@ -209,6 +209,13 @@ export interface VideoPlayerProps {
|
|
|
209
209
|
* a visible frame for missing or blank auto-generated library thumbnails.
|
|
210
210
|
*/
|
|
211
211
|
role?: "owner" | "admin" | "editor" | "viewer";
|
|
212
|
+
/**
|
|
213
|
+
* Called with the live `<video>` DOM node whenever it is created or
|
|
214
|
+
* destroyed (e.g. swapping to/from the Loom iframe or unsupported-format
|
|
215
|
+
* placeholder). Lets a caller key an effect off the actual element
|
|
216
|
+
* lifecycle instead of polling an imperative-handle getter.
|
|
217
|
+
*/
|
|
218
|
+
onVideoElementChange?: (video: HTMLVideoElement | null) => void;
|
|
212
219
|
}
|
|
213
220
|
|
|
214
221
|
export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
@@ -245,6 +252,7 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
245
252
|
cover,
|
|
246
253
|
recordingId,
|
|
247
254
|
role,
|
|
255
|
+
onVideoElementChange,
|
|
248
256
|
} = props;
|
|
249
257
|
|
|
250
258
|
const resolvedVideoSrc = useMemo(
|
|
@@ -252,6 +260,13 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
252
260
|
[videoUrl],
|
|
253
261
|
);
|
|
254
262
|
const videoRef = useRef<HTMLVideoElement | null>(null);
|
|
263
|
+
const setVideoNode = useCallback(
|
|
264
|
+
(el: HTMLVideoElement | null) => {
|
|
265
|
+
videoRef.current = el;
|
|
266
|
+
onVideoElementChange?.(el);
|
|
267
|
+
},
|
|
268
|
+
[onVideoElementChange],
|
|
269
|
+
);
|
|
255
270
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
256
271
|
const idleTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
257
272
|
const touchTapCandidateRef = useRef<{
|
|
@@ -1262,7 +1277,7 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
1262
1277
|
</div>
|
|
1263
1278
|
) : activeVideoSrc ? (
|
|
1264
1279
|
<video
|
|
1265
|
-
ref={
|
|
1280
|
+
ref={setVideoNode}
|
|
1266
1281
|
src={domVideoSrc}
|
|
1267
1282
|
poster={resolveLocalUrl(thumbnailUrl)}
|
|
1268
1283
|
// `crossOrigin` is only needed so the owner's canvas thumbnail
|
|
@@ -34,7 +34,14 @@ function createViewSessionId(recordingId: string): string {
|
|
|
34
34
|
|
|
35
35
|
export interface UseViewTrackingOpts {
|
|
36
36
|
recordingId: string;
|
|
37
|
-
|
|
37
|
+
/**
|
|
38
|
+
* The live `<video>` DOM node, or `null` when there is none (e.g. a Loom
|
|
39
|
+
* iframe embed). Pass the actual element — not a ref wrapper — so this
|
|
40
|
+
* hook's effect can depend on it directly and React's own dependency
|
|
41
|
+
* comparison decides when to reattach, instead of hand-rolled identity
|
|
42
|
+
* bookkeeping.
|
|
43
|
+
*/
|
|
44
|
+
videoEl: HTMLVideoElement | null;
|
|
38
45
|
durationMs: number;
|
|
39
46
|
/** Disable tracking entirely (e.g. for the recording's owner viewing their own clip). */
|
|
40
47
|
disabled?: boolean;
|
|
@@ -46,10 +53,24 @@ export interface UseViewTrackingOpts {
|
|
|
46
53
|
* Wires up the view-event tracker for a player instance. Fires a "view-start"
|
|
47
54
|
* on mount, then throttled "watch-progress" every 5s while playing, plus
|
|
48
55
|
* seek/pause/resume events and a final flush on unmount.
|
|
56
|
+
*
|
|
57
|
+
* The effect depends on `[recordingId, videoEl, trackOpenWithoutVideo,
|
|
58
|
+
* disabled]`, so React naturally creates a fresh closure — and runs the
|
|
59
|
+
* previous one's cleanup — exactly when any of those actually change (a
|
|
60
|
+
* different video element, a different recording, or the no-video/embed
|
|
61
|
+
* mode flipping). Each closure captures its own `recordingId` and `videoEl`,
|
|
62
|
+
* so a cleanup's final flush always describes the session it belonged to,
|
|
63
|
+
* never a session that has since replaced it.
|
|
64
|
+
*
|
|
65
|
+
* `durationMs` is intentionally excluded from the dependency array — it can
|
|
66
|
+
* load asynchronously after the video/recording is already attached, and
|
|
67
|
+
* reattaching just for that would be wasted work. It's kept in a ref that's
|
|
68
|
+
* synced every render and read fresh inside `post()`.
|
|
49
69
|
*/
|
|
50
70
|
export function useViewTracking(opts: UseViewTrackingOpts) {
|
|
51
|
-
const { recordingId,
|
|
71
|
+
const { recordingId, videoEl, durationMs, disabled, trackOpenWithoutVideo } =
|
|
52
72
|
opts;
|
|
73
|
+
|
|
53
74
|
const watchMsRef = useRef(0);
|
|
54
75
|
const lastTickRef = useRef<number | null>(null);
|
|
55
76
|
const startedRef = useRef(false);
|
|
@@ -57,11 +78,25 @@ export function useViewTracking(opts: UseViewTrackingOpts) {
|
|
|
57
78
|
const lastSentProgressRef = useRef(0);
|
|
58
79
|
const maxPctRef = useRef(0);
|
|
59
80
|
const viewSessionRef = useRef<string | null>(null);
|
|
81
|
+
const durationMsRef = useRef(durationMs);
|
|
82
|
+
const recordingIdRef = useRef(recordingId);
|
|
83
|
+
|
|
84
|
+
durationMsRef.current = durationMs;
|
|
85
|
+
recordingIdRef.current = recordingId;
|
|
60
86
|
|
|
61
87
|
useEffect(() => {
|
|
62
88
|
if (disabled) return;
|
|
63
|
-
|
|
64
|
-
|
|
89
|
+
|
|
90
|
+
// Reset per-session counters — this effect only reruns when the video
|
|
91
|
+
// element, recording, or embed mode actually change.
|
|
92
|
+
watchMsRef.current = 0;
|
|
93
|
+
lastTickRef.current = null;
|
|
94
|
+
startedRef.current = false;
|
|
95
|
+
lastSentProgressRef.current = 0;
|
|
96
|
+
maxPctRef.current = 0;
|
|
97
|
+
viewSessionRef.current = null;
|
|
98
|
+
|
|
99
|
+
if (!videoEl) {
|
|
65
100
|
if (
|
|
66
101
|
!trackOpenWithoutVideo ||
|
|
67
102
|
!recordingId ||
|
|
@@ -69,6 +104,10 @@ export function useViewTracking(opts: UseViewTrackingOpts) {
|
|
|
69
104
|
) {
|
|
70
105
|
return;
|
|
71
106
|
}
|
|
107
|
+
// Persists for the hook's lifetime (never reset on cleanup): this is
|
|
108
|
+
// what stops a React StrictMode dev mount->cleanup->remount cycle
|
|
109
|
+
// from double-posting the same iframe-open view-start, since — unlike
|
|
110
|
+
// the with-video path below — there's no native DOM event gating it.
|
|
72
111
|
openTrackedRecordingRef.current = recordingId;
|
|
73
112
|
viewSessionRef.current = createViewSessionId(recordingId);
|
|
74
113
|
fetch(`${appBasePath()}/api/view-event`, {
|
|
@@ -90,6 +129,7 @@ export function useViewTracking(opts: UseViewTrackingOpts) {
|
|
|
90
129
|
return;
|
|
91
130
|
}
|
|
92
131
|
|
|
132
|
+
const video = videoEl;
|
|
93
133
|
const sessionId = getSessionId();
|
|
94
134
|
viewSessionRef.current = createViewSessionId(recordingId);
|
|
95
135
|
let progressTimer: ReturnType<typeof setInterval> | null = null;
|
|
@@ -105,8 +145,7 @@ export function useViewTracking(opts: UseViewTrackingOpts) {
|
|
|
105
145
|
| "reaction",
|
|
106
146
|
extra?: Record<string, unknown>,
|
|
107
147
|
) {
|
|
108
|
-
const
|
|
109
|
-
if (!v) return;
|
|
148
|
+
const durationMs = durationMsRef.current;
|
|
110
149
|
const completedPct =
|
|
111
150
|
durationMs > 0 ? (watchMsRef.current / durationMs) * 100 : 0;
|
|
112
151
|
maxPctRef.current = Math.max(
|
|
@@ -120,12 +159,13 @@ export function useViewTracking(opts: UseViewTrackingOpts) {
|
|
|
120
159
|
body: JSON.stringify({
|
|
121
160
|
recordingId,
|
|
122
161
|
kind,
|
|
123
|
-
timestampMs: Math.floor(
|
|
162
|
+
timestampMs: Math.floor(video.currentTime * 1000),
|
|
124
163
|
sessionId,
|
|
125
164
|
viewSessionId: viewSessionRef.current,
|
|
126
165
|
totalWatchMs: Math.floor(watchMsRef.current),
|
|
127
166
|
completedPct: Math.floor(maxPctRef.current),
|
|
128
|
-
scrubbedToEnd:
|
|
167
|
+
scrubbedToEnd:
|
|
168
|
+
video.duration > 0 && video.currentTime >= video.duration - 0.5,
|
|
129
169
|
payload: extra,
|
|
130
170
|
}),
|
|
131
171
|
}).catch(() => {});
|
|
@@ -186,10 +226,13 @@ export function useViewTracking(opts: UseViewTrackingOpts) {
|
|
|
186
226
|
video.removeEventListener("seeked", onSeek);
|
|
187
227
|
video.removeEventListener("ended", onEnded);
|
|
188
228
|
if (progressTimer) clearInterval(progressTimer);
|
|
189
|
-
// Flush final progress
|
|
229
|
+
// Flush final progress, still scoped to this closure's own video and
|
|
230
|
+
// recordingId — never one a later render has since moved on to.
|
|
190
231
|
if (startedRef.current) post("watch-progress");
|
|
191
232
|
};
|
|
192
|
-
|
|
233
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- durationMs is
|
|
234
|
+
// deliberately excluded; it's read live from durationMsRef inside post().
|
|
235
|
+
}, [recordingId, videoEl, trackOpenWithoutVideo, disabled]);
|
|
193
236
|
|
|
194
237
|
return {
|
|
195
238
|
reportCtaClick: () => {
|
|
@@ -198,7 +241,7 @@ export function useViewTracking(opts: UseViewTrackingOpts) {
|
|
|
198
241
|
keepalive: true,
|
|
199
242
|
headers: { "Content-Type": "application/json" },
|
|
200
243
|
body: JSON.stringify({
|
|
201
|
-
recordingId,
|
|
244
|
+
recordingId: recordingIdRef.current,
|
|
202
245
|
kind: "cta-click",
|
|
203
246
|
sessionId: getSessionId(),
|
|
204
247
|
}),
|
|
@@ -210,7 +253,7 @@ export function useViewTracking(opts: UseViewTrackingOpts) {
|
|
|
210
253
|
keepalive: true,
|
|
211
254
|
headers: { "Content-Type": "application/json" },
|
|
212
255
|
body: JSON.stringify({
|
|
213
|
-
recordingId,
|
|
256
|
+
recordingId: recordingIdRef.current,
|
|
214
257
|
kind: "reaction",
|
|
215
258
|
sessionId: getSessionId(),
|
|
216
259
|
payload: { emoji },
|
|
@@ -131,13 +131,13 @@ export default function EmbedRoute() {
|
|
|
131
131
|
const firstCta = ctas[0] ?? null;
|
|
132
132
|
const isLoomEmbedBacked = isLoomEmbedBackedRecording(recording);
|
|
133
133
|
|
|
134
|
+
const [trackedVideoEl, setTrackedVideoEl] = useState<HTMLVideoElement | null>(
|
|
135
|
+
null,
|
|
136
|
+
);
|
|
137
|
+
|
|
134
138
|
const tracking = useViewTracking({
|
|
135
139
|
recordingId: shareId ?? "",
|
|
136
|
-
|
|
137
|
-
get current() {
|
|
138
|
-
return playerRef.current?.video ?? null;
|
|
139
|
-
},
|
|
140
|
-
} as any,
|
|
140
|
+
videoEl: trackedVideoEl,
|
|
141
141
|
durationMs: recording?.durationMs ?? 0,
|
|
142
142
|
trackOpenWithoutVideo: isLoomEmbedBacked,
|
|
143
143
|
});
|
|
@@ -193,6 +193,7 @@ export default function EmbedRoute() {
|
|
|
193
193
|
<div className="fixed inset-0 h-dvh w-dvw overflow-hidden bg-black">
|
|
194
194
|
<VideoPlayer
|
|
195
195
|
ref={playerRef}
|
|
196
|
+
onVideoElementChange={setTrackedVideoEl}
|
|
196
197
|
recordingId={recording.id}
|
|
197
198
|
videoUrl={recording.videoUrl}
|
|
198
199
|
videoFormat={recording.videoFormat}
|
|
@@ -800,16 +800,15 @@ export default function RecordingPage() {
|
|
|
800
800
|
|
|
801
801
|
usePlayerShortcuts({ playerRef, chapters });
|
|
802
802
|
|
|
803
|
+
const [trackedVideoEl, setTrackedVideoEl] = useState<HTMLVideoElement | null>(
|
|
804
|
+
null,
|
|
805
|
+
);
|
|
806
|
+
|
|
803
807
|
const tracking = useViewTracking({
|
|
804
808
|
recordingId: recordingId ?? "",
|
|
805
|
-
|
|
806
|
-
get current() {
|
|
807
|
-
return playerRef.current?.video ?? null;
|
|
808
|
-
},
|
|
809
|
-
} as any,
|
|
809
|
+
videoEl: trackedVideoEl,
|
|
810
810
|
durationMs: recording?.durationMs ?? 0,
|
|
811
|
-
// Skip tracking for the owner
|
|
812
|
-
disabled: role === "owner",
|
|
811
|
+
disabled: role === "owner", // Skip tracking for the owner: they shouldn't inflate their own views.
|
|
813
812
|
});
|
|
814
813
|
|
|
815
814
|
if (!recordingId) return null;
|
|
@@ -1545,6 +1544,7 @@ export default function RecordingPage() {
|
|
|
1545
1544
|
<div className="relative aspect-video w-full xl:min-h-0 xl:flex-1 xl:aspect-auto">
|
|
1546
1545
|
<VideoPlayer
|
|
1547
1546
|
ref={playerRef}
|
|
1547
|
+
onVideoElementChange={setTrackedVideoEl}
|
|
1548
1548
|
recordingId={recording.id}
|
|
1549
1549
|
videoUrl={recording.videoUrl}
|
|
1550
1550
|
videoFormat={recording.videoFormat}
|