@agent-native/core 0.124.2 → 0.124.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.
Files changed (267) hide show
  1. package/README.md +1 -1
  2. package/corpus/README.md +2 -2
  3. package/corpus/core/CHANGELOG.md +16 -0
  4. package/corpus/core/docs/content/cloneable-saas.mdx +9 -9
  5. package/corpus/core/docs/content/creating-templates.mdx +2 -2
  6. package/corpus/core/docs/content/deployment.mdx +23 -0
  7. package/corpus/core/docs/content/faq.mdx +4 -4
  8. package/corpus/core/docs/content/key-concepts.mdx +1 -1
  9. package/corpus/core/docs/content/multi-app-workspace.mdx +1 -1
  10. package/corpus/core/docs/content/syncing-template-changes.mdx +1 -1
  11. package/corpus/core/docs/content/template-analytics-developers.mdx +2 -2
  12. package/corpus/core/docs/content/template-analytics.mdx +2 -2
  13. package/corpus/core/docs/content/template-assets-developers.mdx +3 -3
  14. package/corpus/core/docs/content/template-assets.mdx +2 -2
  15. package/corpus/core/docs/content/template-brain-developers.mdx +2 -2
  16. package/corpus/core/docs/content/template-brain.mdx +1 -1
  17. package/corpus/core/docs/content/template-calendar-developers.mdx +1 -1
  18. package/corpus/core/docs/content/template-calendar.mdx +1 -1
  19. package/corpus/core/docs/content/template-chat.mdx +1 -1
  20. package/corpus/core/docs/content/template-clips-developers.mdx +4 -4
  21. package/corpus/core/docs/content/template-clips.mdx +2 -2
  22. package/corpus/core/docs/content/template-content-developers.mdx +2 -2
  23. package/corpus/core/docs/content/template-content.mdx +1 -1
  24. package/corpus/core/docs/content/template-design-brand-and-figma.mdx +1 -1
  25. package/corpus/core/docs/content/template-design-developers.mdx +2 -2
  26. package/corpus/core/docs/content/template-design.mdx +2 -2
  27. package/corpus/core/docs/content/template-dispatch-developers.mdx +5 -5
  28. package/corpus/core/docs/content/template-forms-developers.mdx +1 -1
  29. package/corpus/core/docs/content/template-forms.mdx +1 -1
  30. package/corpus/core/docs/content/template-mail-developers.mdx +2 -2
  31. package/corpus/core/docs/content/template-mail.mdx +2 -2
  32. package/corpus/core/docs/content/template-plan-developers.mdx +3 -3
  33. package/corpus/core/docs/content/template-plan.mdx +3 -3
  34. package/corpus/core/docs/content/template-slides-developers.mdx +4 -4
  35. package/corpus/core/docs/content/template-slides.mdx +1 -1
  36. package/corpus/core/docs/content/what-is-agent-native.mdx +5 -5
  37. package/corpus/core/package.json +1 -1
  38. package/corpus/core/src/a2a/auth-policy.ts +1 -1
  39. package/corpus/core/src/agent/production-agent.ts +14 -1
  40. package/corpus/core/src/agent/run-manager.ts +6 -13
  41. package/corpus/core/src/cli/index.ts +11 -1
  42. package/corpus/core/src/client/route-chunk-recovery.ts +18 -7
  43. package/corpus/core/src/db/client.ts +7 -6
  44. package/corpus/core/src/db/migrations.ts +2 -1
  45. package/corpus/core/src/deploy/build.ts +290 -4
  46. package/corpus/core/src/deploy/nitro-preset.ts +50 -0
  47. package/corpus/core/src/integrations/pending-tasks-retry-job.ts +2 -1
  48. package/corpus/core/src/scripts/call-agent.ts +4 -2
  49. package/corpus/core/src/server/agent-discovery.ts +1 -0
  50. package/corpus/core/src/server/agent-run-context.ts +13 -3
  51. package/corpus/core/src/server/request-context.ts +2 -0
  52. package/corpus/core/src/shared/cloudflare-globals.d.ts +2 -1
  53. package/corpus/core/src/shared/runtime.ts +2 -0
  54. package/corpus/core/src/shared/streaming-text-smoothing.ts +15 -14
  55. package/corpus/core/src/templates/default/.agents/skills/secrets/SKILL.md +29 -0
  56. package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +6 -6
  57. package/corpus/core/src/templates/default/app/i18n/de-DE.ts +6 -6
  58. package/corpus/core/src/templates/default/app/i18n/en-US.ts +6 -6
  59. package/corpus/core/src/templates/default/app/i18n/es-ES.ts +6 -6
  60. package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +6 -6
  61. package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +6 -6
  62. package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +6 -6
  63. package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +6 -6
  64. package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +6 -6
  65. package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +6 -6
  66. package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +6 -6
  67. package/corpus/core/src/templates/headless/.agents/skills/secrets/SKILL.md +29 -0
  68. package/corpus/core/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -0
  69. package/corpus/core/src/vite/client.ts +20 -1
  70. package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +9 -2
  71. package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +3 -0
  72. package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +1 -1
  73. package/corpus/templates/analytics/app/global.css +10 -9
  74. package/corpus/templates/analytics/app/hooks/use-chart-tooltip-portal.ts +59 -18
  75. package/corpus/templates/analytics/app/hooks/use-dashboard-revisions.ts +14 -2
  76. package/corpus/templates/analytics/app/i18n-data.ts +102 -0
  77. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +232 -2
  78. package/corpus/templates/analytics/changelog/2026-07-27-chart-tooltips-stay-beside-the-cursor-when-they-extend-beyon.md +6 -0
  79. package/corpus/templates/analytics/changelog/2026-07-27-chat-no-longer-claims-a-data-source-is-disconnected-when-it-.md +6 -0
  80. package/corpus/templates/analytics/changelog/2026-07-27-dashboard-changes-can-be-undone-with-cmd-z-and-actions-menu.md +6 -0
  81. package/corpus/templates/analytics/changelog/2026-07-27-dashboard-redo-with-cmd-shift-z-and-actions-menu.md +6 -0
  82. package/corpus/templates/analytics/changelog/2026-07-27-dashboard-skeletons-are-subtler-and-smoother.md +6 -0
  83. package/corpus/templates/analytics/server/lib/dashboards-store.ts +61 -20
  84. package/corpus/templates/analytics/server/lib/real-data-actions.ts +1 -0
  85. package/corpus/templates/analytics/server/plugins/agent-chat.ts +7 -3
  86. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +7 -32
  87. package/corpus/templates/calendar/changelog/2026-07-27-calendar-s-sidebar-now-uses-a-cleaner-layout-with-fewer-util.md +6 -0
  88. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +14 -6
  89. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +32 -37
  90. package/corpus/templates/content/changelog/2026-07-27-content-sidebar-page-lists-now-load-with-quiet-skeletons-and.md +6 -0
  91. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +34 -11
  92. package/corpus/templates/design/.agents/skills/responsive-breakpoints/SKILL.md +8 -0
  93. package/corpus/templates/design/actions/generate-design.ts +259 -36
  94. package/corpus/templates/design/actions/generate-screens.ts +51 -14
  95. package/corpus/templates/design/actions/present-design-variants.ts +6 -3
  96. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +8 -0
  97. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +158 -9
  98. package/corpus/templates/design/app/components/design/design-canvas/content-size-report.ts +124 -0
  99. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +61 -9
  100. package/corpus/templates/design/app/components/layout/Sidebar.tsx +16 -18
  101. package/corpus/templates/design/changelog/2026-07-24-ai-designs-now-default-to-a-full-size-desktop-mobile-pair-fr.md +6 -0
  102. package/corpus/templates/design/changelog/2026-07-24-design-frames-now-fill-the-viewport-height-and-grow-to-fit-t.md +6 -0
  103. package/corpus/templates/design/changelog/2026-07-24-generating-an-additional-screen-now-places-it-beside-the-exi.md +6 -0
  104. package/corpus/templates/design/changelog/2026-07-27-collapsed-sidebar-keeps-the-agent-native-logo.md +6 -0
  105. package/corpus/templates/dispatch/AGENTS.md +7 -2
  106. package/corpus/templates/dispatch/actions/view-screen.ts +3 -2
  107. package/corpus/templates/dispatch/changelog/2026-07-17-curated-template-remixes.md +1 -1
  108. package/corpus/templates/dispatch/changelog/2026-07-27-creating-an-app-from-a-curated-template-now-uses-clearer-mor.md +6 -0
  109. package/corpus/templates/dispatch/changelog/2026-07-27-thread-debug-accepts-request-run-ids.md +6 -0
  110. package/corpus/templates/slides/actions/create-design-system.ts +10 -2
  111. package/corpus/templates/slides/actions/import-file.ts +173 -1
  112. package/corpus/templates/slides/actions/list-slide-comments.ts +16 -1
  113. package/corpus/templates/slides/actions/update-design-system.ts +13 -2
  114. package/corpus/templates/slides/app/components/editor/AssetLibraryPanel.tsx +35 -12
  115. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +36 -0
  116. package/corpus/templates/slides/app/i18n/en-US.ts +3 -0
  117. package/corpus/templates/slides/app/lib/dynamic-import.ts +63 -0
  118. package/corpus/templates/slides/app/lib/export-pdf-client.ts +3 -2
  119. package/corpus/templates/slides/app/lib/export-pptx-client.ts +5 -2
  120. package/corpus/templates/slides/app/pages/DesignSystems.tsx +9 -1
  121. package/corpus/templates/slides/server/db/schema.ts +11 -0
  122. package/corpus/templates/slides/server/handlers/assets.ts +64 -19
  123. package/corpus/templates/slides/server/handlers/import/html-converter.ts +38 -0
  124. package/corpus/templates/slides/server/plugins/db.ts +20 -0
  125. package/corpus/templates/slides/shared/design-system-validation.ts +62 -0
  126. package/dist/a2a/auth-policy.js +1 -1
  127. package/dist/a2a/auth-policy.js.map +1 -1
  128. package/dist/agent/production-agent.d.ts +1 -0
  129. package/dist/agent/production-agent.d.ts.map +1 -1
  130. package/dist/agent/production-agent.js +12 -1
  131. package/dist/agent/production-agent.js.map +1 -1
  132. package/dist/agent/run-manager.d.ts +2 -0
  133. package/dist/agent/run-manager.d.ts.map +1 -1
  134. package/dist/agent/run-manager.js +4 -10
  135. package/dist/agent/run-manager.js.map +1 -1
  136. package/dist/cli/index.js +8 -1
  137. package/dist/cli/index.js.map +1 -1
  138. package/dist/client/route-chunk-recovery.d.ts.map +1 -1
  139. package/dist/client/route-chunk-recovery.js +16 -7
  140. package/dist/client/route-chunk-recovery.js.map +1 -1
  141. package/dist/db/client.d.ts.map +1 -1
  142. package/dist/db/client.js +4 -2
  143. package/dist/db/client.js.map +1 -1
  144. package/dist/db/migrations.d.ts.map +1 -1
  145. package/dist/db/migrations.js +2 -1
  146. package/dist/db/migrations.js.map +1 -1
  147. package/dist/deploy/build.d.ts +25 -0
  148. package/dist/deploy/build.d.ts.map +1 -1
  149. package/dist/deploy/build.js +227 -4
  150. package/dist/deploy/build.js.map +1 -1
  151. package/dist/deploy/nitro-preset.d.ts +8 -0
  152. package/dist/deploy/nitro-preset.d.ts.map +1 -0
  153. package/dist/deploy/nitro-preset.js +36 -0
  154. package/dist/deploy/nitro-preset.js.map +1 -0
  155. package/dist/integrations/pending-tasks-retry-job.d.ts.map +1 -1
  156. package/dist/integrations/pending-tasks-retry-job.js +2 -1
  157. package/dist/integrations/pending-tasks-retry-job.js.map +1 -1
  158. package/dist/notifications/routes.d.ts +3 -3
  159. package/dist/observability/routes.d.ts +3 -3
  160. package/dist/progress/routes.d.ts +1 -1
  161. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  162. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  163. package/dist/resources/handlers.d.ts +1 -1
  164. package/dist/scripts/call-agent.d.ts.map +1 -1
  165. package/dist/scripts/call-agent.js +4 -2
  166. package/dist/scripts/call-agent.js.map +1 -1
  167. package/dist/server/agent-discovery.d.ts.map +1 -1
  168. package/dist/server/agent-discovery.js +1 -0
  169. package/dist/server/agent-discovery.js.map +1 -1
  170. package/dist/server/agent-run-context.d.ts.map +1 -1
  171. package/dist/server/agent-run-context.js +10 -3
  172. package/dist/server/agent-run-context.js.map +1 -1
  173. package/dist/server/request-context.d.ts +2 -0
  174. package/dist/server/request-context.d.ts.map +1 -1
  175. package/dist/server/request-context.js.map +1 -1
  176. package/dist/server/transcribe-voice.d.ts +1 -1
  177. package/dist/shared/runtime.d.ts.map +1 -1
  178. package/dist/shared/runtime.js +2 -0
  179. package/dist/shared/runtime.js.map +1 -1
  180. package/dist/shared/streaming-text-smoothing.d.ts.map +1 -1
  181. package/dist/shared/streaming-text-smoothing.js +15 -8
  182. package/dist/shared/streaming-text-smoothing.js.map +1 -1
  183. package/dist/templates/default/.agents/skills/secrets/SKILL.md +29 -0
  184. package/dist/templates/default/app/i18n/ar-SA.ts +6 -6
  185. package/dist/templates/default/app/i18n/de-DE.ts +6 -6
  186. package/dist/templates/default/app/i18n/en-US.ts +6 -6
  187. package/dist/templates/default/app/i18n/es-ES.ts +6 -6
  188. package/dist/templates/default/app/i18n/fr-FR.ts +6 -6
  189. package/dist/templates/default/app/i18n/hi-IN.ts +6 -6
  190. package/dist/templates/default/app/i18n/ja-JP.ts +6 -6
  191. package/dist/templates/default/app/i18n/ko-KR.ts +6 -6
  192. package/dist/templates/default/app/i18n/pt-BR.ts +6 -6
  193. package/dist/templates/default/app/i18n/zh-CN.ts +6 -6
  194. package/dist/templates/default/app/i18n/zh-TW.ts +6 -6
  195. package/dist/templates/headless/.agents/skills/secrets/SKILL.md +29 -0
  196. package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -0
  197. package/dist/vite/client.d.ts +1 -1
  198. package/dist/vite/client.d.ts.map +1 -1
  199. package/dist/vite/client.js +16 -0
  200. package/dist/vite/client.js.map +1 -1
  201. package/docs/content/cloneable-saas.mdx +9 -9
  202. package/docs/content/creating-templates.mdx +2 -2
  203. package/docs/content/deployment.mdx +23 -0
  204. package/docs/content/faq.mdx +4 -4
  205. package/docs/content/key-concepts.mdx +1 -1
  206. package/docs/content/multi-app-workspace.mdx +1 -1
  207. package/docs/content/syncing-template-changes.mdx +1 -1
  208. package/docs/content/template-analytics-developers.mdx +2 -2
  209. package/docs/content/template-analytics.mdx +2 -2
  210. package/docs/content/template-assets-developers.mdx +3 -3
  211. package/docs/content/template-assets.mdx +2 -2
  212. package/docs/content/template-brain-developers.mdx +2 -2
  213. package/docs/content/template-brain.mdx +1 -1
  214. package/docs/content/template-calendar-developers.mdx +1 -1
  215. package/docs/content/template-calendar.mdx +1 -1
  216. package/docs/content/template-chat.mdx +1 -1
  217. package/docs/content/template-clips-developers.mdx +4 -4
  218. package/docs/content/template-clips.mdx +2 -2
  219. package/docs/content/template-content-developers.mdx +2 -2
  220. package/docs/content/template-content.mdx +1 -1
  221. package/docs/content/template-design-brand-and-figma.mdx +1 -1
  222. package/docs/content/template-design-developers.mdx +2 -2
  223. package/docs/content/template-design.mdx +2 -2
  224. package/docs/content/template-dispatch-developers.mdx +5 -5
  225. package/docs/content/template-forms-developers.mdx +1 -1
  226. package/docs/content/template-forms.mdx +1 -1
  227. package/docs/content/template-mail-developers.mdx +2 -2
  228. package/docs/content/template-mail.mdx +2 -2
  229. package/docs/content/template-plan-developers.mdx +3 -3
  230. package/docs/content/template-plan.mdx +3 -3
  231. package/docs/content/template-slides-developers.mdx +4 -4
  232. package/docs/content/template-slides.mdx +1 -1
  233. package/docs/content/what-is-agent-native.mdx +5 -5
  234. package/package.json +1 -1
  235. package/src/a2a/auth-policy.ts +1 -1
  236. package/src/agent/production-agent.ts +14 -1
  237. package/src/agent/run-manager.ts +6 -13
  238. package/src/cli/index.ts +11 -1
  239. package/src/client/route-chunk-recovery.ts +18 -7
  240. package/src/db/client.ts +7 -6
  241. package/src/db/migrations.ts +2 -1
  242. package/src/deploy/build.ts +290 -4
  243. package/src/deploy/nitro-preset.ts +50 -0
  244. package/src/integrations/pending-tasks-retry-job.ts +2 -1
  245. package/src/scripts/call-agent.ts +4 -2
  246. package/src/server/agent-discovery.ts +1 -0
  247. package/src/server/agent-run-context.ts +13 -3
  248. package/src/server/request-context.ts +2 -0
  249. package/src/shared/cloudflare-globals.d.ts +2 -1
  250. package/src/shared/runtime.ts +2 -0
  251. package/src/shared/streaming-text-smoothing.ts +15 -14
  252. package/src/templates/default/.agents/skills/secrets/SKILL.md +29 -0
  253. package/src/templates/default/app/i18n/ar-SA.ts +6 -6
  254. package/src/templates/default/app/i18n/de-DE.ts +6 -6
  255. package/src/templates/default/app/i18n/en-US.ts +6 -6
  256. package/src/templates/default/app/i18n/es-ES.ts +6 -6
  257. package/src/templates/default/app/i18n/fr-FR.ts +6 -6
  258. package/src/templates/default/app/i18n/hi-IN.ts +6 -6
  259. package/src/templates/default/app/i18n/ja-JP.ts +6 -6
  260. package/src/templates/default/app/i18n/ko-KR.ts +6 -6
  261. package/src/templates/default/app/i18n/pt-BR.ts +6 -6
  262. package/src/templates/default/app/i18n/zh-CN.ts +6 -6
  263. package/src/templates/default/app/i18n/zh-TW.ts +6 -6
  264. package/src/templates/headless/.agents/skills/secrets/SKILL.md +29 -0
  265. package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -0
  266. package/src/vite/client.ts +20 -1
  267. /package/corpus/templates/slides/server/routes/api/assets/{[filename].delete.ts → [id].delete.ts} +0 -0
@@ -34,6 +34,8 @@ import {
34
34
  } from "@dnd-kit/core";
35
35
  import {
36
36
  IconArchive,
37
+ IconArrowBackUp,
38
+ IconArrowForwardUp,
37
39
  IconDotsVertical,
38
40
  IconEye,
39
41
  IconEyeOff,
@@ -103,6 +105,10 @@ import {
103
105
  type DashboardPanelChatContextArgs,
104
106
  type SelectDashboardPanelOptions,
105
107
  } from "@/hooks/use-dashboard-chat-context";
108
+ import {
109
+ useDashboardRevisions,
110
+ useRestoreDashboardRevision,
111
+ } from "@/hooks/use-dashboard-revisions";
106
112
  import { useDashboardViews } from "@/hooks/use-dashboard-views";
107
113
  import { useUserPref } from "@/hooks/use-user-pref";
108
114
  import {
@@ -584,6 +590,8 @@ function SqlDashboardPageContent({
584
590
  const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
585
591
  const [emailReportOpen, setEmailReportOpen] = useState(false);
586
592
  const [historyOpen, setHistoryOpen] = useState(false);
593
+ const [undoRevisionId, setUndoRevisionId] = useState<string | null>(null);
594
+ const [redoRevisionIds, setRedoRevisionIds] = useState<string[]>([]);
587
595
  const [dashboardActionsOpen, setDashboardActionsOpen] = useState(false);
588
596
  const [contextSheetOpen, setContextSheetOpen] = useState(false);
589
597
  const [activeDropSlot, setActiveDropSlot] =
@@ -593,8 +601,13 @@ function SqlDashboardPageContent({
593
601
  );
594
602
  const viewedDashboardIdRef = useRef<string | null>(null);
595
603
  const pendingConfigRef = useRef<DashboardAdoptionHold | null>(null);
604
+ const revisionRestoreInFlightRef = useRef(false);
596
605
  const canEdit = !reportScreenshot && resourceCanEdit(resourceAccess);
597
606
  const canManage = !reportScreenshot && resourceCanManage(resourceAccess);
607
+ const resetRevisionNavigation = useCallback(() => {
608
+ setUndoRevisionId(null);
609
+ setRedoRevisionIds([]);
610
+ }, []);
598
611
  const dashboardColumns = clampDashboardColumns(
599
612
  dashboard?.columns ?? DEFAULT_DASHBOARD_COLUMNS,
600
613
  );
@@ -616,6 +629,25 @@ function SqlDashboardPageContent({
616
629
  );
617
630
  const { mutateAsync: archiveDashboardAction } =
618
631
  useActionMutation("archive-dashboard");
632
+ const { data: dashboardRevisions } = useDashboardRevisions(
633
+ !reportScreenshot && dashboardId ? dashboardId : null,
634
+ );
635
+ const restoreDashboardRevision = useRestoreDashboardRevision(
636
+ dashboardId ?? "",
637
+ );
638
+ const undoRevisionIndex = undoRevisionId
639
+ ? (dashboardRevisions?.findIndex(
640
+ (revision) => revision.id === undoRevisionId,
641
+ ) ?? -1)
642
+ : -1;
643
+ const canUndo =
644
+ canEdit &&
645
+ !!dashboardId &&
646
+ !!dashboardRevisions?.length &&
647
+ (undoRevisionId === null ||
648
+ (undoRevisionIndex >= 0 &&
649
+ undoRevisionIndex < dashboardRevisions.length - 1));
650
+ const canRedo = canEdit && !!dashboardId && redoRevisionIds.length > 0;
619
651
 
620
652
  // Refetch the dashboard whenever the `dashboards` source bumps OR any
621
653
  // agent action runs. We depend on both because:
@@ -757,6 +789,9 @@ function SqlDashboardPageContent({
757
789
  try {
758
790
  const parsed = JSON.parse(raw) as SqlDashboardConfig;
759
791
  if (parsed && Array.isArray(parsed.panels)) {
792
+ if (!revisionRestoreInFlightRef.current) {
793
+ resetRevisionNavigation();
794
+ }
760
795
  holdDashboardConfig();
761
796
  setDashboard(parsed);
762
797
  updateCachedDashboardConfig(parsed);
@@ -769,7 +804,13 @@ function SqlDashboardPageContent({
769
804
  return () => {
770
805
  ytext.unobserve(handler);
771
806
  };
772
- }, [ydoc, collabSynced, holdDashboardConfig, updateCachedDashboardConfig]);
807
+ }, [
808
+ ydoc,
809
+ collabSynced,
810
+ holdDashboardConfig,
811
+ resetRevisionNavigation,
812
+ updateCachedDashboardConfig,
813
+ ]);
773
814
 
774
815
  // Per-user saved filter state
775
816
  const filterPrefKey = dashboardId ? `dashboard-filters:${dashboardId}` : "";
@@ -798,8 +839,10 @@ function SqlDashboardPageContent({
798
839
  setDashboardUpdatedAt(null);
799
840
  setDashboardUpdatedBy(null);
800
841
  setResourceAccess(null);
842
+ resetRevisionNavigation();
843
+ revisionRestoreInFlightRef.current = false;
801
844
  if (!dashboardId) setLoaded(true);
802
- }, [dashboardId]);
845
+ }, [dashboardId, resetRevisionNavigation]);
803
846
 
804
847
  useEffect(() => {
805
848
  if (!dashboardId || !dashboardQuery.isSuccess) return;
@@ -817,6 +860,16 @@ function SqlDashboardPageContent({
817
860
  pendingConfigRef.current = null;
818
861
  }
819
862
  if (!adoption.adopt) return;
863
+ if (
864
+ loaded &&
865
+ dashboardUpdatedAt &&
866
+ fetched?.updatedAt &&
867
+ fetched.updatedAt !== dashboardUpdatedAt &&
868
+ !revisionRestoreInFlightRef.current &&
869
+ !pendingConfigRef.current
870
+ ) {
871
+ resetRevisionNavigation();
872
+ }
820
873
  const fetchedConfig = fetched?.config ?? null;
821
874
  const fetchedVisibility =
822
875
  fetched?.visibility === "private" ||
@@ -858,6 +911,7 @@ function SqlDashboardPageContent({
858
911
  dashboardUpdatedAt,
859
912
  loaded,
860
913
  reportScreenshot,
914
+ resetRevisionNavigation,
861
915
  ]);
862
916
 
863
917
  // Apply saved filters on initial load if no filter URL params are present
@@ -982,6 +1036,7 @@ function SqlDashboardPageContent({
982
1036
  toast.error(t("sqlDashboard.viewOnly"));
983
1037
  return;
984
1038
  }
1039
+ resetRevisionNavigation();
985
1040
  holdDashboardConfig();
986
1041
  setDashboard(updated);
987
1042
  updateCachedDashboardConfig(updated);
@@ -1017,6 +1072,7 @@ function SqlDashboardPageContent({
1017
1072
  holdDashboardConfig,
1018
1073
  queryClient,
1019
1074
  pushToCollab,
1075
+ resetRevisionNavigation,
1020
1076
  t,
1021
1077
  updateCachedDashboardConfig,
1022
1078
  ],
@@ -1032,6 +1088,7 @@ function SqlDashboardPageContent({
1032
1088
  if (!canEdit) {
1033
1089
  throw new Error(t("sqlDashboard.viewOnly"));
1034
1090
  }
1091
+ resetRevisionNavigation();
1035
1092
  holdDashboardConfig();
1036
1093
  await saveDashboard(dashboardId, updated);
1037
1094
  setDashboard(updated);
@@ -1052,11 +1109,148 @@ function SqlDashboardPageContent({
1052
1109
  holdDashboardConfig,
1053
1110
  queryClient,
1054
1111
  pushToCollab,
1112
+ resetRevisionNavigation,
1055
1113
  t,
1056
1114
  updateCachedDashboardConfig,
1057
1115
  ],
1058
1116
  );
1059
1117
 
1118
+ const handleUndo = useCallback(async () => {
1119
+ if (
1120
+ !dashboardId ||
1121
+ !canEdit ||
1122
+ !canUndo ||
1123
+ restoreDashboardRevision.isPending
1124
+ ) {
1125
+ return;
1126
+ }
1127
+
1128
+ const revisions = dashboardRevisions ?? [];
1129
+ const targetIndex =
1130
+ undoRevisionId === null ? 0 : Math.max(0, undoRevisionIndex + 1);
1131
+ const targetRevision = revisions[targetIndex];
1132
+ if (!targetRevision) return;
1133
+
1134
+ revisionRestoreInFlightRef.current = true;
1135
+ holdDashboardConfig();
1136
+ try {
1137
+ const restored = await restoreDashboardRevision.mutateAsync({
1138
+ dashboardId,
1139
+ revisionId: targetRevision.id,
1140
+ expectedUpdatedAt: dashboardUpdatedAt ?? undefined,
1141
+ });
1142
+ setUndoRevisionId(targetRevision.id);
1143
+ setRedoRevisionIds((current) => [
1144
+ ...current,
1145
+ restored.snapshotRevisionId,
1146
+ ]);
1147
+ if (restored?.updatedAt) setDashboardUpdatedAt(restored.updatedAt);
1148
+ toast.success(t("dashboard.undoSuccess"));
1149
+ } catch (error) {
1150
+ resetRevisionNavigation();
1151
+ toast.error(
1152
+ error instanceof Error
1153
+ ? t("dashboard.undoFailedWithMessage", { message: error.message })
1154
+ : t("dashboard.undoFailed"),
1155
+ );
1156
+ } finally {
1157
+ revisionRestoreInFlightRef.current = false;
1158
+ }
1159
+ }, [
1160
+ canEdit,
1161
+ canUndo,
1162
+ dashboardId,
1163
+ dashboardRevisions,
1164
+ dashboardUpdatedAt,
1165
+ holdDashboardConfig,
1166
+ restoreDashboardRevision,
1167
+ resetRevisionNavigation,
1168
+ t,
1169
+ undoRevisionId,
1170
+ undoRevisionIndex,
1171
+ ]);
1172
+
1173
+ const handleRedo = useCallback(async () => {
1174
+ if (
1175
+ !dashboardId ||
1176
+ !canEdit ||
1177
+ !canRedo ||
1178
+ restoreDashboardRevision.isPending
1179
+ ) {
1180
+ return;
1181
+ }
1182
+
1183
+ const targetRevisionId = redoRevisionIds[redoRevisionIds.length - 1];
1184
+ if (!targetRevisionId) return;
1185
+
1186
+ revisionRestoreInFlightRef.current = true;
1187
+ holdDashboardConfig();
1188
+ try {
1189
+ const restored = await restoreDashboardRevision.mutateAsync({
1190
+ dashboardId,
1191
+ revisionId: targetRevisionId,
1192
+ expectedUpdatedAt: dashboardUpdatedAt ?? undefined,
1193
+ });
1194
+ setRedoRevisionIds((current) => current.slice(0, -1));
1195
+ setUndoRevisionId(null);
1196
+ if (restored?.updatedAt) setDashboardUpdatedAt(restored.updatedAt);
1197
+ toast.success(t("dashboard.redoSuccess"));
1198
+ } catch (error) {
1199
+ resetRevisionNavigation();
1200
+ toast.error(
1201
+ error instanceof Error
1202
+ ? t("dashboard.redoFailedWithMessage", { message: error.message })
1203
+ : t("dashboard.redoFailed"),
1204
+ );
1205
+ } finally {
1206
+ revisionRestoreInFlightRef.current = false;
1207
+ }
1208
+ }, [
1209
+ canEdit,
1210
+ canRedo,
1211
+ dashboardId,
1212
+ dashboardUpdatedAt,
1213
+ holdDashboardConfig,
1214
+ redoRevisionIds,
1215
+ resetRevisionNavigation,
1216
+ restoreDashboardRevision,
1217
+ t,
1218
+ ]);
1219
+
1220
+ useEffect(() => {
1221
+ if (reportScreenshot) return;
1222
+
1223
+ const handleUndoShortcut = (event: KeyboardEvent) => {
1224
+ if (
1225
+ event.defaultPrevented ||
1226
+ !(event.metaKey || event.ctrlKey) ||
1227
+ event.key.toLowerCase() !== "z"
1228
+ ) {
1229
+ return;
1230
+ }
1231
+ const target = event.target as HTMLElement | null;
1232
+ if (
1233
+ target?.closest("input, textarea, select, [contenteditable='true']")
1234
+ ) {
1235
+ return;
1236
+ }
1237
+ const canHandle = event.shiftKey ? canRedo : canUndo;
1238
+ if (!canHandle || restoreDashboardRevision.isPending) return;
1239
+ event.preventDefault();
1240
+ void (event.shiftKey ? handleRedo() : handleUndo());
1241
+ };
1242
+
1243
+ window.addEventListener("keydown", handleUndoShortcut);
1244
+ return () => window.removeEventListener("keydown", handleUndoShortcut);
1245
+ }, [
1246
+ canUndo,
1247
+ canRedo,
1248
+ handleRedo,
1249
+ handleUndo,
1250
+ reportScreenshot,
1251
+ restoreDashboardRevision.isPending,
1252
+ ]);
1253
+
1060
1254
  const removePanel = useCallback(
1061
1255
  (panelId: string) => {
1062
1256
  if (!dashboard) return;
@@ -1594,6 +1788,41 @@ function SqlDashboardPageContent({
1594
1788
  {dashboardId ? (
1595
1789
  <>
1596
1790
  <DropdownMenuSeparator />
1791
+ {canEdit ? (
1792
+ <DropdownMenuItem
1793
+ disabled={!canUndo || restoreDashboardRevision.isPending}
1794
+ aria-keyshortcuts="Meta+Z"
1795
+ onSelect={(event) => {
1796
+ event.preventDefault();
1797
+ setDashboardActionsOpen(false);
1798
+ void handleUndo();
1799
+ }}
1800
+ >
1801
+ <IconArrowBackUp className="mr-2 h-3.5 w-3.5" />
1802
+ {t("dashboard.undo")}
1803
+ <span className="ml-auto text-[10px] text-muted-foreground">
1804
+ ⌘Z
1805
+ </span>
1806
+ </DropdownMenuItem>
1807
+ ) : null}
1808
+ {canEdit ? (
1809
+ <DropdownMenuItem
1810
+ disabled={!canRedo || restoreDashboardRevision.isPending}
1811
+ aria-keyshortcuts="Meta+Shift+Z"
1812
+ onSelect={(event) => {
1813
+ event.preventDefault();
1814
+ setDashboardActionsOpen(false);
1815
+ void handleRedo();
1816
+ }}
1817
+ >
1818
+ <IconArrowForwardUp className="mr-2 h-3.5 w-3.5" />
1819
+ {t("dashboard.redo")}
1820
+ <span className="ml-auto text-[10px] text-muted-foreground">
1821
+ {/* i18n-ignore keyboard shortcut hint */}
1822
+ ⌘⇧Z
1823
+ </span>
1824
+ </DropdownMenuItem>
1825
+ ) : null}
1597
1826
  <DropdownMenuItem
1598
1827
  onSelect={(event) => {
1599
1828
  event.preventDefault();
@@ -1696,6 +1925,7 @@ function SqlDashboardPageContent({
1696
1925
  open={historyOpen}
1697
1926
  onOpenChange={setHistoryOpen}
1698
1927
  canRestore={canEdit && !archivedAt}
1928
+ onRestored={resetRevisionNavigation}
1699
1929
  />
1700
1930
  ) : null}
1701
1931
  {dashboardId ? (
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-27
4
+ ---
5
+
6
+ Chart tooltips stay beside the cursor when they extend beyond a chart
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-27
4
+ ---
5
+
6
+ Chat no longer claims a data source is disconnected when it simply hadn't checked, which could make the agent refuse its own follow-up as a prompt injection.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-27
4
+ ---
5
+
6
+ Dashboard changes can be undone with Cmd+Z or from the dashboard actions menu, while full revision history remains available for restore.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-07-27
4
+ ---
5
+
6
+ Dashboard changes can be redone with Cmd+Shift+Z or from the dashboard actions menu.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-27
4
+ ---
5
+
6
+ Dashboard loading placeholders now use softer contrast and a smoother shimmer cycle.
@@ -635,9 +635,10 @@ async function snapshotDashboardRevision(
635
635
  db: any,
636
636
  dashboard: DashboardRecord,
637
637
  ctx: AccessCtx,
638
- ): Promise<void> {
638
+ ): Promise<string> {
639
+ const id = `dashrev-${Date.now()}-${nanoidFallback()}`;
639
640
  await db.insert(schema.dashboardRevisions).values({
640
- id: `dashrev-${Date.now()}-${nanoidFallback()}`,
641
+ id,
641
642
  dashboardId: dashboard.id,
642
643
  kind: dashboard.kind,
643
644
  title: dashboard.title,
@@ -648,6 +649,7 @@ async function snapshotDashboardRevision(
648
649
  orgId: dashboard.orgId,
649
650
  });
650
651
  await pruneDashboardRevisions(db, dashboard.id);
652
+ return id;
651
653
  }
652
654
 
653
655
  /**
@@ -844,13 +846,23 @@ export async function restoreDashboardRevision(
844
846
  dashboardId: string,
845
847
  revisionId: string,
846
848
  ctx: AccessCtx,
847
- ): Promise<DashboardRecord | null> {
849
+ expectedUpdatedAt?: string,
850
+ ): Promise<{
851
+ dashboard: DashboardRecord;
852
+ snapshotRevisionId: string;
853
+ } | null> {
848
854
  const existing = await getDashboard(dashboardId, ctx);
849
855
  if (!existing) return null;
850
856
  await assertAccess("dashboard", dashboardId, "editor", {
851
857
  userEmail: ctx.email,
852
858
  orgId: ctx.orgId ?? undefined,
853
859
  });
860
+ if (
861
+ expectedUpdatedAt !== undefined &&
862
+ existing.updatedAt !== expectedUpdatedAt
863
+ ) {
864
+ throw new DashboardConflictError(dashboardId);
865
+ }
854
866
  const db = getDb() as any;
855
867
  const [revisionRow] = await db
856
868
  .select()
@@ -864,22 +876,51 @@ export async function restoreDashboardRevision(
864
876
  .limit(1);
865
877
  if (!revisionRow) return null;
866
878
  const revision = rowToDashboardRevision(revisionRow);
867
- await snapshotDashboardRevision(db, existing, ctx);
868
- await db
869
- .update(schema.dashboards)
870
- .set({
871
- kind: revision.kind,
872
- title: revision.title,
873
- config: JSON.stringify(revision.config),
874
- updatedAt: nowIso(),
875
- updatedBy: ctx.email,
876
- })
877
- .where(eq(schema.dashboards.id, dashboardId));
878
- const [row] = await db
879
- .select()
880
- .from(schema.dashboards)
881
- .where(eq(schema.dashboards.id, dashboardId));
882
- const dashboard = rowToDashboard(row);
879
+ const updatedAt = nowIso();
880
+ const restored = await db.transaction(async (tx: any) => {
881
+ const updateResult = await tx
882
+ .update(schema.dashboards)
883
+ .set({
884
+ kind: revision.kind,
885
+ title: revision.title,
886
+ config: JSON.stringify(revision.config),
887
+ updatedAt,
888
+ updatedBy: ctx.email,
889
+ })
890
+ .where(
891
+ expectedUpdatedAt === undefined
892
+ ? eq(schema.dashboards.id, dashboardId)
893
+ : and(
894
+ eq(schema.dashboards.id, dashboardId),
895
+ eq(schema.dashboards.updatedAt, expectedUpdatedAt),
896
+ ),
897
+ );
898
+ if (expectedUpdatedAt !== undefined) {
899
+ const affected = affectedRowCount(updateResult);
900
+ if (affected === undefined) {
901
+ throw new Error(
902
+ "The database driver did not report an affected-row count for the fenced dashboard restore.",
903
+ );
904
+ }
905
+ if (affected === 0) throw new DashboardConflictError(dashboardId);
906
+ }
907
+
908
+ const snapshotRevisionId = await snapshotDashboardRevision(
909
+ tx,
910
+ existing,
911
+ ctx,
912
+ );
913
+ const [row] = await tx
914
+ .select()
915
+ .from(schema.dashboards)
916
+ .where(eq(schema.dashboards.id, dashboardId));
917
+ return {
918
+ dashboard: rowToDashboard(row),
919
+ snapshotRevisionId,
920
+ };
921
+ });
922
+ if (!restored) return null;
923
+ const { dashboard } = restored;
883
924
  recordScopedChange(
884
925
  "dashboards",
885
926
  "change",
@@ -888,7 +929,7 @@ export async function restoreDashboardRevision(
888
929
  dashboard.orgId,
889
930
  dashboard.visibility,
890
931
  );
891
- return dashboard;
932
+ return restored;
892
933
  }
893
934
 
894
935
  /**
@@ -9,6 +9,7 @@ const INJECTED_CONTEXT_BLOCKS = [
9
9
  "available-jobs",
10
10
  "plan-mode-note",
11
11
  "non-analytics-retry",
12
+ "response-guard",
12
13
  ];
13
14
 
14
15
  export const DATA_QUERY_ACTIONS = new Set([
@@ -479,10 +479,14 @@ export function realDataFinalGuard(
479
479
  );
480
480
  const firstPartySourceShouldBeTried =
481
481
  noConnectedExternalSources && !externalSourceRequest;
482
+ // Only a `data-source-status` result can show something is missing. A turn
483
+ // that never called it has empty label lists, which is "we did not look",
484
+ // not "nothing is connected" — treating those the same made the guard demand
485
+ // a Connect-data-sources link on turns whose sources were working fine.
482
486
  const needsDataSourceLink =
483
- !sourceStatus.checked ||
484
- (externalSourceRequest &&
485
- (noConnectedExternalSources || missingRequestedExternalSource));
487
+ sourceStatus.checked &&
488
+ externalSourceRequest &&
489
+ (noConnectedExternalSources || missingRequestedExternalSource);
486
490
  if (
487
491
  hasFailedCorpusWorkflowEvidence(context.toolResults) &&
488
492
  looksLikeCoverageSensitiveAnalyticsRequest(userText) &&
@@ -16,7 +16,6 @@ import {
16
16
  IconChevronRight,
17
17
  IconPlus,
18
18
  IconX,
19
- IconKeyboard,
20
19
  IconInfoCircle,
21
20
  IconCheck,
22
21
  IconEye,
@@ -46,7 +45,6 @@ import { useState, useEffect, useMemo } from "react";
46
45
  import { Link, useLocation, useNavigate } from "react-router";
47
46
  import { toast } from "sonner";
48
47
 
49
- import { ThemeToggle } from "@/components/ThemeToggle";
50
48
  import { Button } from "@/components/ui/button";
51
49
  import {
52
50
  Collapsible,
@@ -353,7 +351,7 @@ function GoogleConnectSidebarButton() {
353
351
  }
354
352
 
355
353
  return (
356
- <div className="border-t border-border p-3">
354
+ <div className="p-3">
357
355
  <div className="rounded-lg bg-primary/10 p-3">
358
356
  <p className="mb-1 text-xs font-semibold text-foreground">
359
357
  {t("settings.connectGoogleCalendar")}
@@ -502,7 +500,7 @@ function GoogleAccountsSection({
502
500
  }
503
501
 
504
502
  return (
505
- <div className="border-t border-border px-1.5 py-1.5">
503
+ <div className="px-1.5 py-1.5">
506
504
  <div className="mb-1 flex min-h-8 items-center justify-between px-3">
507
505
  <div className="flex items-center">
508
506
  <span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
@@ -769,14 +767,14 @@ export function Sidebar({
769
767
  data-open={open ? "true" : "false"}
770
768
  data-collapsed={collapsed ? "true" : "false"}
771
769
  className={cn(
772
- "agent-layout-left-drawer calendar-app-sidebar fixed start-0 top-0 z-50 flex h-full min-w-0 flex-col overflow-hidden border-e border-border bg-sidebar transition-[width,translate] duration-200 ease-out lg:static",
770
+ "agent-layout-left-drawer calendar-app-sidebar fixed start-0 top-0 z-50 flex h-full min-w-0 flex-col overflow-hidden bg-sidebar transition-[width,translate] duration-200 ease-out lg:static",
773
771
  collapsed ? "w-12" : "w-56",
774
772
  )}
775
773
  >
776
774
  {/* Logo */}
777
775
  <div
778
776
  className={cn(
779
- "flex h-12 shrink-0 items-center justify-between gap-2.5 border-b border-border",
777
+ "flex h-12 shrink-0 items-center justify-between gap-2.5",
780
778
  collapsed ? "px-1" : "px-4",
781
779
  )}
782
780
  >
@@ -846,7 +844,7 @@ export function Sidebar({
846
844
  />
847
845
 
848
846
  {/* Nav */}
849
- <nav className="space-y-0.5 border-t border-border p-2.5">
847
+ <nav className="space-y-0.5 p-2.5">
850
848
  {navItems.map((item) => {
851
849
  const isActive =
852
850
  item.path === "/"
@@ -886,7 +884,7 @@ export function Sidebar({
886
884
  )}
887
885
 
888
886
  {/* Other Calendars — people overlays + external ICS feeds combined */}
889
- <div className="border-t border-border px-1.5 py-1.5">
887
+ <div className="px-1.5 py-1.5">
890
888
  <div className="flex min-h-8 items-center justify-between px-3">
891
889
  <div className="flex items-center gap-1">
892
890
  <span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
@@ -1135,7 +1133,7 @@ export function Sidebar({
1135
1133
  "shrink-0",
1136
1134
  collapsed
1137
1135
  ? "flex flex-col items-center gap-1 px-1 py-2"
1138
- : "space-y-0.5 border-t border-border p-2.5",
1136
+ : "space-y-0.5 p-2.5",
1139
1137
  )}
1140
1138
  >
1141
1139
  {bottomNavItems.map((item) => {
@@ -1178,29 +1176,6 @@ export function Sidebar({
1178
1176
 
1179
1177
  <div className="flex items-center gap-1 px-1.5 py-1.5">
1180
1178
  <DevDatabaseLink />
1181
- <Tooltip>
1182
- <TooltipTrigger asChild>
1183
- <Button
1184
- variant="ghost"
1185
- size="icon"
1186
- className="h-8 w-8 text-muted-foreground"
1187
- onClick={() =>
1188
- window.dispatchEvent(new Event("calendar:open-shortcuts"))
1189
- }
1190
- >
1191
- <IconKeyboard className="h-4 w-4" />
1192
- </Button>
1193
- </TooltipTrigger>
1194
- <TooltipContent side="top">
1195
- <p>
1196
- {t("keyboardShortcuts.title")}{" "}
1197
- <kbd className="ms-1 rounded border border-border bg-muted px-1 font-mono text-[10px]">
1198
- ?
1199
- </kbd>
1200
- </p>
1201
- </TooltipContent>
1202
- </Tooltip>
1203
- <ThemeToggle className="h-8 w-8" />
1204
1179
  </div>
1205
1180
  </div>
1206
1181
  ) : null}
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-27
4
+ ---
5
+
6
+ Calendar's sidebar now uses a cleaner layout with fewer utility controls and no divider lines