@agent-native/core 0.120.2 → 0.120.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/corpus/README.md +3 -3
  2. package/corpus/core/CHANGELOG.md +20 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/context-xray-local.ts +1 -1
  5. package/corpus/core/src/cli/index.ts +63 -18
  6. package/corpus/core/src/cli/skills.ts +5 -0
  7. package/corpus/core/src/cli/telemetry.ts +118 -1
  8. package/corpus/core/src/client/AgentPanel.tsx +10 -0
  9. package/corpus/core/src/client/CommandMenu.tsx +13 -1
  10. package/corpus/core/src/client/agent-page/AgentTabsPage.tsx +39 -4
  11. package/corpus/core/src/client/analytics.ts +13 -0
  12. package/corpus/core/src/client/error-capture.ts +6 -1
  13. package/corpus/core/src/client/extensions/ExtensionsListPage.tsx +12 -5
  14. package/corpus/core/src/client/extensions/agent-native-extension-runtime.ts +1 -1
  15. package/corpus/core/src/client/navigation/index.ts +2 -0
  16. package/corpus/core/src/client/org/OrgSwitcher.tsx +14 -32
  17. package/corpus/core/src/client/settings/ExtensionsSettingsContent.tsx +1 -1
  18. package/corpus/core/src/extensions/theme.ts +1 -1
  19. package/corpus/core/src/localization/default-messages.ts +2 -0
  20. package/corpus/core/src/server/core-routes-plugin.ts +10 -1
  21. package/corpus/core/src/server/framework-request-handler.ts +17 -26
  22. package/corpus/core/src/styles/agent-native.css +13 -0
  23. package/corpus/core/src/templates/chat/app/components/layout/Layout.tsx +3 -0
  24. package/corpus/core/src/templates/chat/app/components/layout/Sidebar.tsx +78 -45
  25. package/corpus/core/src/templates/chat/app/global.css +1 -1
  26. package/corpus/core/src/templates/default/app/global.css +1 -1
  27. package/corpus/core/src/tracking/error-capture.ts +151 -0
  28. package/corpus/core/src/tracking/index.ts +5 -0
  29. package/corpus/templates/analytics/app/components/dashboard/CumulativeNetChart.tsx +1 -1
  30. package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +1 -4
  31. package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +1 -1
  32. package/corpus/templates/analytics/app/components/dashboard/DataTable.tsx +1 -1
  33. package/corpus/templates/analytics/app/components/dashboard/DateRangePicker.tsx +2 -2
  34. package/corpus/templates/analytics/app/components/dashboard/MetricCard.tsx +1 -1
  35. package/corpus/templates/analytics/app/components/dashboard/RecentActivity.tsx +1 -1
  36. package/corpus/templates/analytics/app/components/dashboard/RevenueChart.tsx +1 -1
  37. package/corpus/templates/analytics/app/components/dashboard/RevenueComparisonChart.tsx +1 -1
  38. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +41 -9
  39. package/corpus/templates/analytics/app/components/dashboard/StatsCard.tsx +1 -1
  40. package/corpus/templates/analytics/app/components/dashboard/TimeSeriesChart.tsx +1 -1
  41. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +2 -0
  42. package/corpus/templates/analytics/app/components/layout/Layout.tsx +28 -14
  43. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +152 -124
  44. package/corpus/templates/analytics/app/global.css +5 -5
  45. package/corpus/templates/analytics/app/lib/dashboard-report-capture.ts +42 -0
  46. package/corpus/templates/analytics/app/pages/adhoc/_shared/KpiChart.tsx +1 -1
  47. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/ChartTypePicker.tsx +1 -1
  48. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/DateRangePicker.tsx +1 -1
  49. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/EventRow.tsx +1 -1
  50. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/SqlPreview.tsx +2 -2
  51. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +3 -3
  52. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +2 -2
  53. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
  54. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +1 -1
  55. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +162 -51
  56. package/corpus/templates/analytics/app/root.tsx +28 -0
  57. package/corpus/templates/analytics/changelog/2026-07-24-analytics-charts-now-explain-slow-loads-and-offer-a-direct-r.md +6 -0
  58. package/corpus/templates/analytics/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  59. package/corpus/templates/analytics/changelog/2026-07-24-daily-dashboard-emails-now-load-signed-reports-more-reliably.md +6 -0
  60. package/corpus/templates/analytics/changelog/2026-07-24-manage-agent-is-clearly-marked-as-a-link-to-its-dedicated-pa.md +6 -0
  61. package/corpus/templates/analytics/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  62. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +1 -1
  63. package/corpus/templates/analytics/server/db/schema.ts +5 -0
  64. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +21 -0
  65. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +11 -0
  66. package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +54 -0
  67. package/corpus/templates/analytics/server/lib/dashboard-report.ts +235 -26
  68. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +4 -1
  69. package/corpus/templates/analytics/server/plugins/db.ts +9 -0
  70. package/corpus/templates/analytics/shared/dashboard-report-timeouts.ts +1 -1
  71. package/corpus/templates/assets/app/components/asset/AssetPreviewDialog.tsx +376 -0
  72. package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +14 -4
  73. package/corpus/templates/assets/app/components/layout/Layout.tsx +5 -0
  74. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +118 -74
  75. package/corpus/templates/assets/app/global.css +1 -1
  76. package/corpus/templates/assets/app/i18n-data.ts +10 -0
  77. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +99 -10
  78. package/corpus/templates/assets/app/routes/library.tsx +21 -198
  79. package/corpus/templates/assets/changelog/2026-07-21-unified-asset-preview-click-any-asset-to-open-one-preview-wi.md +6 -0
  80. package/corpus/templates/assets/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  81. package/corpus/templates/assets/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  82. package/corpus/templates/brain/app/components/layout/Layout.tsx +3 -0
  83. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +76 -43
  84. package/corpus/templates/brain/app/global.css +1 -1
  85. package/corpus/templates/brain/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  86. package/corpus/templates/brain/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  87. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +67 -52
  88. package/corpus/templates/calendar/app/global.css +1 -1
  89. package/corpus/templates/calendar/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  90. package/corpus/templates/calendar/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  91. package/corpus/templates/chat/app/components/layout/Layout.tsx +3 -0
  92. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +78 -45
  93. package/corpus/templates/chat/app/global.css +1 -1
  94. package/corpus/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  95. package/corpus/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  96. package/corpus/templates/clips/app/components/library/library-layout.tsx +65 -31
  97. package/corpus/templates/clips/app/components/library/organization-switcher.tsx +2 -2
  98. package/corpus/templates/clips/app/components/player/video-player.tsx +16 -1
  99. package/corpus/templates/clips/app/global.css +1 -1
  100. package/corpus/templates/clips/app/hooks/use-view-tracking.ts +55 -12
  101. package/corpus/templates/clips/app/routes/embed.$shareId.tsx +6 -5
  102. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +7 -7
  103. package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -5
  104. package/corpus/templates/clips/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  105. package/corpus/templates/clips/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  106. package/corpus/templates/clips/server/plugins/db.ts +113 -68
  107. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +66 -42
  108. package/corpus/templates/content/app/global.css +1 -1
  109. package/corpus/templates/content/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  110. package/corpus/templates/content/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  111. package/corpus/templates/design/app/components/layout/Sidebar.tsx +65 -30
  112. package/corpus/templates/design/app/entry.client.tsx +14 -1
  113. package/corpus/templates/design/app/global.css +1 -1
  114. package/corpus/templates/design/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  115. package/corpus/templates/design/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  116. package/corpus/templates/dispatch/app/global.css +1 -1
  117. package/corpus/templates/dispatch/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  118. package/corpus/templates/dispatch/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  119. package/corpus/templates/forms/app/components/layout/Layout.tsx +3 -0
  120. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +72 -37
  121. package/corpus/templates/forms/app/global.css +1 -1
  122. package/corpus/templates/forms/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  123. package/corpus/templates/forms/changelog/2026-07-24-forms-sidebar-actions-now-keep-search-language-feedback-and-.md +6 -0
  124. package/corpus/templates/forms/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  125. package/corpus/templates/macros/app/components/DailyProgress.tsx +5 -5
  126. package/corpus/templates/macros/app/components/ExerciseCard.tsx +2 -2
  127. package/corpus/templates/macros/app/components/MealCard.tsx +2 -2
  128. package/corpus/templates/macros/app/components/WeightCard.tsx +2 -2
  129. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +56 -29
  130. package/corpus/templates/macros/app/components/ui/button.tsx +1 -2
  131. package/corpus/templates/macros/app/components/ui/card.tsx +1 -1
  132. package/corpus/templates/macros/app/global.css +1 -1
  133. package/corpus/templates/macros/app/routes/_index.tsx +1 -1
  134. package/corpus/templates/macros/app/routes/analytics.tsx +4 -7
  135. package/corpus/templates/macros/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  136. package/corpus/templates/macros/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  137. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +125 -37
  138. package/corpus/templates/mail/app/global.css +1 -1
  139. package/corpus/templates/mail/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  140. package/corpus/templates/mail/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  141. package/corpus/templates/plan/app/components/layout/Layout.tsx +3 -0
  142. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +90 -60
  143. package/corpus/templates/plan/app/global.css +1 -1
  144. package/corpus/templates/plan/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  145. package/corpus/templates/plan/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  146. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +69 -32
  147. package/corpus/templates/slides/app/global.css +1 -1
  148. package/corpus/templates/slides/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  149. package/corpus/templates/slides/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  150. package/corpus/templates/tasks/app/global.css +1 -1
  151. package/corpus/templates/tasks/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  152. package/corpus/toolkit/CHANGELOG.md +8 -0
  153. package/corpus/toolkit/README.md +8 -1
  154. package/corpus/toolkit/agent-native.eject.json +1 -1
  155. package/corpus/toolkit/package.json +1 -1
  156. package/corpus/toolkit/src/app-shell/index.ts +1 -0
  157. package/corpus/toolkit/src/app-shell/sidebar-footer-actions.tsx +48 -0
  158. package/corpus/toolkit/src/chat-history.css +52 -1
  159. package/corpus/toolkit/src/design-system/default-adapter.tsx +1 -1
  160. package/corpus/toolkit/src/ui/button.tsx +1 -2
  161. package/corpus/toolkit/src/ui/card.tsx +1 -1
  162. package/corpus/toolkit/src/ui/table.tsx +4 -11
  163. package/dist/cli/context-xray-local.js +1 -1
  164. package/dist/cli/context-xray-local.js.map +1 -1
  165. package/dist/cli/index.js +56 -17
  166. package/dist/cli/index.js.map +1 -1
  167. package/dist/cli/skills.d.ts.map +1 -1
  168. package/dist/cli/skills.js +5 -0
  169. package/dist/cli/skills.js.map +1 -1
  170. package/dist/cli/telemetry.d.ts +7 -0
  171. package/dist/cli/telemetry.d.ts.map +1 -1
  172. package/dist/cli/telemetry.js +91 -1
  173. package/dist/cli/telemetry.js.map +1 -1
  174. package/dist/client/AgentPanel.d.ts +7 -1
  175. package/dist/client/AgentPanel.d.ts.map +1 -1
  176. package/dist/client/AgentPanel.js +2 -2
  177. package/dist/client/AgentPanel.js.map +1 -1
  178. package/dist/client/CommandMenu.d.ts +2 -0
  179. package/dist/client/CommandMenu.d.ts.map +1 -1
  180. package/dist/client/CommandMenu.js +12 -1
  181. package/dist/client/CommandMenu.js.map +1 -1
  182. package/dist/client/agent-page/AgentTabsPage.d.ts +4 -0
  183. package/dist/client/agent-page/AgentTabsPage.d.ts.map +1 -1
  184. package/dist/client/agent-page/AgentTabsPage.js +8 -4
  185. package/dist/client/agent-page/AgentTabsPage.js.map +1 -1
  186. package/dist/client/analytics.d.ts.map +1 -1
  187. package/dist/client/analytics.js +7 -0
  188. package/dist/client/analytics.js.map +1 -1
  189. package/dist/client/error-capture.d.ts.map +1 -1
  190. package/dist/client/error-capture.js +8 -1
  191. package/dist/client/error-capture.js.map +1 -1
  192. package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
  193. package/dist/client/extensions/ExtensionsListPage.js +2 -2
  194. package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
  195. package/dist/client/extensions/agent-native-extension-runtime.js +1 -1
  196. package/dist/client/extensions/agent-native-extension-runtime.js.map +1 -1
  197. package/dist/client/navigation/index.d.ts +1 -1
  198. package/dist/client/navigation/index.d.ts.map +1 -1
  199. package/dist/client/navigation/index.js +1 -1
  200. package/dist/client/navigation/index.js.map +1 -1
  201. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  202. package/dist/client/org/OrgSwitcher.js +9 -13
  203. package/dist/client/org/OrgSwitcher.js.map +1 -1
  204. package/dist/client/settings/ExtensionsSettingsContent.js +1 -1
  205. package/dist/client/settings/ExtensionsSettingsContent.js.map +1 -1
  206. package/dist/collab/awareness.d.ts +2 -2
  207. package/dist/collab/awareness.d.ts.map +1 -1
  208. package/dist/extensions/theme.js +1 -1
  209. package/dist/extensions/theme.js.map +1 -1
  210. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  211. package/dist/localization/default-messages.d.ts +2 -0
  212. package/dist/localization/default-messages.d.ts.map +1 -1
  213. package/dist/localization/default-messages.js +2 -0
  214. package/dist/localization/default-messages.js.map +1 -1
  215. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  216. package/dist/resources/handlers.d.ts +1 -1
  217. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  218. package/dist/server/core-routes-plugin.js +8 -1
  219. package/dist/server/core-routes-plugin.js.map +1 -1
  220. package/dist/server/framework-request-handler.d.ts.map +1 -1
  221. package/dist/server/framework-request-handler.js +17 -27
  222. package/dist/server/framework-request-handler.js.map +1 -1
  223. package/dist/server/realtime-token.d.ts +1 -1
  224. package/dist/styles/agent-native.css +13 -0
  225. package/dist/templates/chat/app/components/layout/Layout.tsx +3 -0
  226. package/dist/templates/chat/app/components/layout/Sidebar.tsx +78 -45
  227. package/dist/templates/chat/app/global.css +1 -1
  228. package/dist/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  229. package/dist/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  230. package/dist/templates/default/app/global.css +1 -1
  231. package/dist/tracking/error-capture.d.ts +14 -0
  232. package/dist/tracking/error-capture.d.ts.map +1 -0
  233. package/dist/tracking/error-capture.js +113 -0
  234. package/dist/tracking/error-capture.js.map +1 -0
  235. package/dist/tracking/index.d.ts +1 -0
  236. package/dist/tracking/index.d.ts.map +1 -1
  237. package/dist/tracking/index.js +1 -0
  238. package/dist/tracking/index.js.map +1 -1
  239. package/package.json +2 -2
  240. package/src/cli/context-xray-local.ts +1 -1
  241. package/src/cli/index.ts +63 -18
  242. package/src/cli/skills.ts +5 -0
  243. package/src/cli/telemetry.ts +118 -1
  244. package/src/client/AgentPanel.tsx +10 -0
  245. package/src/client/CommandMenu.tsx +13 -1
  246. package/src/client/agent-page/AgentTabsPage.tsx +39 -4
  247. package/src/client/analytics.ts +13 -0
  248. package/src/client/error-capture.ts +6 -1
  249. package/src/client/extensions/ExtensionsListPage.tsx +12 -5
  250. package/src/client/extensions/agent-native-extension-runtime.ts +1 -1
  251. package/src/client/navigation/index.ts +2 -0
  252. package/src/client/org/OrgSwitcher.tsx +14 -32
  253. package/src/client/settings/ExtensionsSettingsContent.tsx +1 -1
  254. package/src/extensions/theme.ts +1 -1
  255. package/src/localization/default-messages.ts +2 -0
  256. package/src/server/core-routes-plugin.ts +10 -1
  257. package/src/server/framework-request-handler.ts +17 -26
  258. package/src/styles/agent-native.css +13 -0
  259. package/src/templates/chat/app/components/layout/Layout.tsx +3 -0
  260. package/src/templates/chat/app/components/layout/Sidebar.tsx +78 -45
  261. package/src/templates/chat/app/global.css +1 -1
  262. package/src/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  263. package/src/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  264. package/src/templates/default/app/global.css +1 -1
  265. package/src/tracking/error-capture.ts +151 -0
  266. package/src/tracking/index.ts +5 -0
@@ -0,0 +1,376 @@
1
+ import { useActionMutation } from "@agent-native/core/client/hooks";
2
+ import { useT } from "@agent-native/core/client/i18n";
3
+ import {
4
+ IconChevronLeft,
5
+ IconChevronRight,
6
+ IconDownload,
7
+ IconInfoCircle,
8
+ IconX,
9
+ } from "@tabler/icons-react";
10
+ import { useEffect, useState, type ReactNode } from "react";
11
+ import { toast } from "sonner";
12
+
13
+ import { Badge } from "@/components/ui/badge";
14
+ import { Button } from "@/components/ui/button";
15
+ import {
16
+ Dialog,
17
+ DialogClose,
18
+ DialogContent,
19
+ DialogDescription,
20
+ DialogTitle,
21
+ } from "@/components/ui/dialog";
22
+ import { assetPreviewSources } from "@/lib/asset-preview-sources";
23
+ import { assetMediaUrl } from "@/lib/asset-urls";
24
+
25
+ export type PreviewAsset = {
26
+ id: string;
27
+ libraryId?: string | null;
28
+ role?: string | null;
29
+ status?: string | null;
30
+ title?: string | null;
31
+ description?: string | null;
32
+ altText?: string | null;
33
+ prompt?: string | null;
34
+ mediaType?: string | null;
35
+ mimeType?: string | null;
36
+ width?: number | null;
37
+ height?: number | null;
38
+ url?: string | null;
39
+ previewUrl?: string | null;
40
+ thumbnailUrl?: string | null;
41
+ downloadUrl?: string | null;
42
+ folderId?: string | null;
43
+ category?: string | null;
44
+ model?: string | null;
45
+ aspectRatio?: string | null;
46
+ durationSeconds?: number | null;
47
+ metadata?: Record<string, unknown> | null;
48
+ lineage?: { label?: string | null } | null;
49
+ };
50
+
51
+ /**
52
+ * The single side-panel asset preview used everywhere an asset is opened:
53
+ * large media on the left, details on the right, a top toolbar (download,
54
+ * details toggle, close), and previous/next navigation across `assets`.
55
+ */
56
+ export function AssetPreviewDialog({
57
+ asset,
58
+ assets,
59
+ onAssetChange,
60
+ renderImage,
61
+ }: {
62
+ asset: PreviewAsset | null;
63
+ assets: PreviewAsset[];
64
+ onAssetChange: (asset: PreviewAsset | null) => void;
65
+ /** Optional media renderer (e.g. an embed/COEP-aware image loader). */
66
+ renderImage?: (asset: PreviewAsset) => ReactNode;
67
+ }) {
68
+ const t = useT();
69
+ const exportAsset = useActionMutation("export-asset");
70
+ const [showDetails, setShowDetails] = useState(true);
71
+ return (
72
+ <Dialog
73
+ open={Boolean(asset)}
74
+ onOpenChange={(open) => {
75
+ if (!open) onAssetChange(null);
76
+ }}
77
+ >
78
+ {asset &&
79
+ (() => {
80
+ const previewIndex = assets.findIndex(
81
+ (candidate) => candidate.id === asset.id,
82
+ );
83
+ const hasPrev = previewIndex > 0;
84
+ const hasNext = previewIndex >= 0 && previewIndex < assets.length - 1;
85
+ const showPreviousAsset = () => {
86
+ if (hasPrev) onAssetChange(assets[previewIndex - 1]);
87
+ };
88
+ const showNextAsset = () => {
89
+ if (hasNext) onAssetChange(assets[previewIndex + 1]);
90
+ };
91
+ const isVideo =
92
+ asset.mediaType === "video" ||
93
+ Boolean(asset.mimeType?.startsWith("video/"));
94
+ const videoSrc = assetPreviewSources(asset)[0];
95
+ const downloadAsset = () => {
96
+ // Synthetic starter-preset assets aren't database rows, so
97
+ // export-asset can't resolve them; download the source directly.
98
+ if (isStarterPreviewAsset(asset)) {
99
+ const directUrl = assetPreviewSources(asset)[0];
100
+ if (directUrl) window.location.href = directUrl;
101
+ else toast.error(t("assetDetail.downloadFailed"));
102
+ return;
103
+ }
104
+ exportAsset.mutate(
105
+ { assetId: asset.id },
106
+ {
107
+ onSuccess: (result: any) => {
108
+ const url =
109
+ assetMediaUrl(result?.downloadUrl) ?? result?.downloadUrl;
110
+ if (url) window.location.href = url;
111
+ else toast.error(t("assetDetail.downloadFailed"));
112
+ },
113
+ onError: () => toast.error(t("assetDetail.downloadFailed")),
114
+ },
115
+ );
116
+ };
117
+ return (
118
+ <DialogContent
119
+ hideClose
120
+ onKeyDown={(event) => {
121
+ if (event.key === "ArrowLeft") showPreviousAsset();
122
+ if (event.key === "ArrowRight") showNextAsset();
123
+ }}
124
+ className="assets-preview-dialog flex max-h-[92vh] w-[94vw] max-w-6xl flex-col gap-0 overflow-hidden p-0"
125
+ >
126
+ <DialogTitle className="sr-only">
127
+ {assetPreviewTitle(asset)}
128
+ </DialogTitle>
129
+ <DialogDescription className="sr-only">
130
+ {t("library.fullSizePreview", {
131
+ title: assetPreviewTitle(asset),
132
+ })}
133
+ </DialogDescription>
134
+
135
+ <div className="flex items-center justify-end gap-1 border-b border-border px-3 py-2">
136
+ <Button
137
+ variant="ghost"
138
+ size="sm"
139
+ className="gap-2"
140
+ disabled={exportAsset.isPending}
141
+ onClick={downloadAsset}
142
+ >
143
+ <IconDownload className="h-4 w-4" />
144
+ {t("assetDetail.download")}
145
+ </Button>
146
+ <Button
147
+ variant="ghost"
148
+ size="sm"
149
+ className="gap-2"
150
+ aria-pressed={showDetails}
151
+ onClick={() => setShowDetails((value) => !value)}
152
+ >
153
+ <IconInfoCircle className="h-4 w-4" />
154
+ {t("library.viewDetails")}
155
+ </Button>
156
+ <DialogClose
157
+ aria-label={t("library.closePreview")}
158
+ className="inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
159
+ >
160
+ <IconX className="h-5 w-5" />
161
+ </DialogClose>
162
+ </div>
163
+
164
+ <div className="flex min-h-0 flex-1 flex-col md:flex-row">
165
+ <div className="relative flex min-h-0 flex-1 items-center justify-center bg-muted/30 p-4">
166
+ {isVideo ? (
167
+ <video
168
+ src={videoSrc}
169
+ poster={asset.thumbnailUrl ?? undefined}
170
+ controls
171
+ autoPlay
172
+ playsInline
173
+ className="max-h-[72vh] max-w-full rounded-lg bg-black object-contain"
174
+ />
175
+ ) : renderImage ? (
176
+ renderImage(asset)
177
+ ) : (
178
+ <AssetPreviewImage asset={asset} />
179
+ )}
180
+ </div>
181
+ {showDetails && (
182
+ <aside className="w-full shrink-0 overflow-y-auto border-t border-border p-5 md:w-80 md:border-l md:border-t-0">
183
+ <AssetPreviewDetails asset={asset} isVideo={isVideo} />
184
+ </aside>
185
+ )}
186
+ </div>
187
+
188
+ {(hasPrev || hasNext) && (
189
+ <div className="flex justify-center gap-2 border-t border-border py-3">
190
+ <button
191
+ type="button"
192
+ aria-label={t("library.previousImage")}
193
+ onClick={showPreviousAsset}
194
+ disabled={!hasPrev}
195
+ className="inline-flex h-9 w-9 items-center justify-center rounded-full border border-border bg-background text-foreground transition hover:bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-40"
196
+ >
197
+ <IconChevronLeft className="h-5 w-5" />
198
+ </button>
199
+ <button
200
+ type="button"
201
+ aria-label={t("library.nextImage")}
202
+ onClick={showNextAsset}
203
+ disabled={!hasNext}
204
+ className="inline-flex h-9 w-9 items-center justify-center rounded-full border border-border bg-background text-foreground transition hover:bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-40"
205
+ >
206
+ <IconChevronRight className="h-5 w-5" />
207
+ </button>
208
+ </div>
209
+ )}
210
+ </DialogContent>
211
+ );
212
+ })()}
213
+ </Dialog>
214
+ );
215
+ }
216
+
217
+ function AssetPreviewImage({ asset }: { asset: PreviewAsset }) {
218
+ const t = useT();
219
+ const sources = assetPreviewSources(asset);
220
+ const [sourceIndex, setSourceIndex] = useState(0);
221
+ const [unavailable, setUnavailable] = useState(false);
222
+ const sourcesKey = sources.join("\n");
223
+
224
+ useEffect(() => {
225
+ setSourceIndex(0);
226
+ setUnavailable(false);
227
+ }, [sourcesKey]);
228
+
229
+ const src = sources[sourceIndex];
230
+ if (!src || unavailable) {
231
+ return (
232
+ <div className="flex aspect-square w-full max-w-sm items-center justify-center rounded-lg border border-dashed border-border bg-background px-6 text-sm font-medium text-muted-foreground">
233
+ {t("assetDetail.previewUnavailable")}
234
+ </div>
235
+ );
236
+ }
237
+ return (
238
+ <img
239
+ src={src}
240
+ alt={asset.altText ?? asset.title ?? ""}
241
+ className="max-h-[72vh] max-w-full rounded-lg object-contain"
242
+ onError={() => {
243
+ const nextIndex = sourceIndex + 1;
244
+ if (nextIndex < sources.length) setSourceIndex(nextIndex);
245
+ else setUnavailable(true);
246
+ }}
247
+ />
248
+ );
249
+ }
250
+
251
+ function AssetPreviewDetails({
252
+ asset,
253
+ isVideo,
254
+ }: {
255
+ asset: PreviewAsset;
256
+ isVideo: boolean;
257
+ }) {
258
+ const t = useT();
259
+ const category = assetPreviewCategoryLabel(asset, t);
260
+ return (
261
+ <div className="space-y-5">
262
+ <div>
263
+ <h2 className="text-base font-semibold leading-tight tracking-tight">
264
+ {assetPreviewTitle(asset)}
265
+ </h2>
266
+ <div className="mt-2 flex flex-wrap gap-1.5">
267
+ {asset.status ? (
268
+ <Badge variant="secondary">{asset.status}</Badge>
269
+ ) : null}
270
+ {asset.role ? <Badge variant="outline">{asset.role}</Badge> : null}
271
+ <Badge variant="outline">{isVideo ? "video" : "image"}</Badge>
272
+ {category ? <Badge variant="outline">{category}</Badge> : null}
273
+ </div>
274
+ </div>
275
+ <div className="space-y-4 text-sm">
276
+ {isVideo ? (
277
+ <PreviewField
278
+ label={t("assetDetail.video")}
279
+ value={`${asset.durationSeconds || "?"}s · ${asset.aspectRatio || "n/a"} · ${asset.model || "n/a"}`}
280
+ />
281
+ ) : (
282
+ <PreviewField
283
+ label={t("assetDetail.dimensions")}
284
+ value={formatPreviewDimensions(asset.width, asset.height)}
285
+ />
286
+ )}
287
+ <PreviewField label="MIME" value={asset.mimeType || "n/a"} />
288
+ <PreviewField
289
+ label={t("assetDetail.folder")}
290
+ value={asset.folderId || t("assetDetail.unfiled")}
291
+ />
292
+ <PreviewField
293
+ label={t("assetDetail.description")}
294
+ value={
295
+ asset.description || asset.altText || t("assetDetail.noDescription")
296
+ }
297
+ multiline
298
+ />
299
+ <PreviewField
300
+ label={t("assetDetail.prompt")}
301
+ value={asset.prompt || t("assetDetail.noPrompt")}
302
+ multiline
303
+ />
304
+ </div>
305
+ </div>
306
+ );
307
+ }
308
+
309
+ function PreviewField({
310
+ label,
311
+ value,
312
+ multiline,
313
+ }: {
314
+ label: string;
315
+ value: ReactNode;
316
+ multiline?: boolean;
317
+ }) {
318
+ return (
319
+ <div>
320
+ <div className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
321
+ {label}
322
+ </div>
323
+ <div className={multiline ? "mt-1 whitespace-pre-wrap" : "mt-1 truncate"}>
324
+ {value}
325
+ </div>
326
+ </div>
327
+ );
328
+ }
329
+
330
+ function assetPreviewTitle(asset: PreviewAsset): string {
331
+ return (
332
+ asset.lineage?.label || asset.title || asset.prompt || "Untitled asset"
333
+ );
334
+ }
335
+
336
+ function assetPreviewCategoryLabel(
337
+ asset: PreviewAsset,
338
+ t: (key: string, options?: Record<string, unknown>) => string,
339
+ ): string | null {
340
+ const metadata = (asset.metadata ?? {}) as Record<string, unknown>;
341
+ if (metadata.intent === "subject" || asset.role === "subject_reference") {
342
+ return t("assetDetail.contentOnly");
343
+ }
344
+ const category = (metadata.category as string | undefined) ?? asset.category;
345
+ if (typeof category !== "string") return null;
346
+ if (category === "style-only") return t("assetDetail.styleReference");
347
+ if (category === "skeleton") return t("assetDetail.skeletonPlate");
348
+ return category.replace(/-/g, " ");
349
+ }
350
+
351
+ function formatPreviewDimensions(
352
+ width?: number | null,
353
+ height?: number | null,
354
+ ) {
355
+ const dimensions = `${width || "?"} x ${height || "?"}`;
356
+ if (!width || !height) return dimensions;
357
+ const divisor = previewGcd(width, height);
358
+ return (
359
+ <span className="flex items-center gap-2">
360
+ {dimensions}
361
+ <span className="h-4 w-px bg-border" />
362
+ {`${width / divisor}:${height / divisor}`}
363
+ </span>
364
+ );
365
+ }
366
+
367
+ function previewGcd(a: number, b: number): number {
368
+ return b === 0 ? a : previewGcd(b, a % b);
369
+ }
370
+
371
+ function isStarterPreviewAsset(asset: PreviewAsset): boolean {
372
+ return (
373
+ asset.id.startsWith("starter-") ||
374
+ Boolean(asset.libraryId?.startsWith("starter:"))
375
+ );
376
+ }
@@ -5,8 +5,10 @@ import {
5
5
  IconArrowUpRight,
6
6
  IconPhoto,
7
7
  } from "@tabler/icons-react";
8
+ import { useState } from "react";
8
9
  import { Link } from "react-router";
9
10
 
11
+ import { AssetPreviewDialog } from "@/components/asset/AssetPreviewDialog";
10
12
  import { Button } from "@/components/ui/button";
11
13
  import { Skeleton } from "@/components/ui/skeleton";
12
14
 
@@ -32,6 +34,7 @@ export function RecentDraftsSection() {
32
34
  },
33
35
  );
34
36
  const drafts = ((data as any)?.assets ?? []) as DraftAsset[];
37
+ const [previewAsset, setPreviewAsset] = useState<DraftAsset | null>(null);
35
38
 
36
39
  if (isError) {
37
40
  return (
@@ -74,18 +77,25 @@ export function RecentDraftsSection() {
74
77
  <Skeleton key={index} className="aspect-square rounded-lg" />
75
78
  ))
76
79
  : drafts.map((draft) => (
77
- <Link
80
+ <button
78
81
  key={draft.id}
79
- to={`/asset/${encodeURIComponent(draft.id)}`}
82
+ type="button"
83
+ onClick={() => setPreviewAsset(draft)}
80
84
  title={draft.title || draft.prompt || t("library.draftAsset")}
81
- className="group block overflow-hidden rounded-lg border border-border bg-card shadow-sm transition hover:border-primary/60 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
85
+ className="group block overflow-hidden rounded-lg border border-border bg-card text-left shadow-sm transition hover:border-primary/60 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
82
86
  >
83
87
  <div className="aspect-square bg-muted">
84
88
  <DraftThumbnail draft={draft} />
85
89
  </div>
86
- </Link>
90
+ </button>
87
91
  ))}
88
92
  </div>
93
+
94
+ <AssetPreviewDialog
95
+ asset={previewAsset}
96
+ assets={drafts}
97
+ onAssetChange={(next) => setPreviewAsset(next as DraftAsset | null)}
98
+ />
89
99
  </section>
90
100
  );
91
101
  }
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AgentSidebar,
3
3
  focusAgentChat,
4
+ isAgentChatHomeHandoffActive,
4
5
  navigateWithAgentChatViewTransition,
5
6
  useAgentChatHomeHandoff,
6
7
  useAgentChatHomeHandoffLinks,
@@ -65,6 +66,9 @@ export function Layout({ children }: LayoutProps) {
65
66
  activePath: location.pathname,
66
67
  enabled: !isCreateRoute,
67
68
  });
69
+ const chatHomeHandoffPending = isAgentChatHomeHandoffActive(
70
+ ASSETS_CHAT_STORAGE_KEY,
71
+ );
68
72
  useAgentChatHomeHandoffLinks({
69
73
  storageKey: ASSETS_CHAT_STORAGE_KEY,
70
74
  isChatPath: (pathname) => pathname === "/" || pathname.startsWith("/chat/"),
@@ -155,6 +159,7 @@ export function Layout({ children }: LayoutProps) {
155
159
  <AgentSidebar
156
160
  position="right"
157
161
  chatViewTransition
162
+ chatViewTransitionHandoff={chatHomeHandoffPending}
158
163
  storageKey={ASSETS_CHAT_STORAGE_KEY}
159
164
  browserTabId={getBrowserTabId()}
160
165
  openOnChatRunning={chatHomeHandoffActive}