@agent-native/core 0.79.21 → 0.79.22

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 (131) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +10 -0
  3. package/corpus/core/docs/content/deployment.md +82 -82
  4. package/corpus/core/docs/content/faq.md +1 -1
  5. package/corpus/core/docs/content/tracking.md +3 -3
  6. package/corpus/core/package.json +1 -1
  7. package/corpus/core/src/agent/engine/openai-compatible-endpoint.ts +25 -0
  8. package/corpus/core/src/agent/engine/provider-env-vars.ts +2 -0
  9. package/corpus/core/src/agent/engine/registry.ts +59 -11
  10. package/corpus/core/src/client/FeedbackButton.tsx +11 -1
  11. package/corpus/core/src/client/agent-engine-key.ts +57 -10
  12. package/corpus/core/src/client/analytics.ts +78 -27
  13. package/corpus/core/src/client/blocks/library/HighlightedCode.tsx +1 -1
  14. package/corpus/core/src/client/index.ts +2 -0
  15. package/corpus/core/src/client/session-replay.ts +159 -17
  16. package/corpus/core/src/client/settings/SettingsPanel.tsx +119 -4
  17. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +5 -0
  18. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +8 -3
  19. package/corpus/core/src/scripts/agent-engines/test-agent-engine.ts +54 -6
  20. package/corpus/core/src/server/agent-engine-api-key-route.ts +83 -12
  21. package/corpus/core/src/server/core-routes-plugin.ts +26 -2
  22. package/corpus/core/src/styles/blocks.css +23 -7
  23. package/corpus/templates/analytics/actions/list-session-recordings.ts +1 -1
  24. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +22 -4
  25. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +64 -50
  26. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +38 -0
  27. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-sync.ts +89 -0
  28. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +138 -59
  29. package/corpus/templates/analytics/changelog/2026-06-27-dashboard-charts-drag-smoothly-when-rearranging-panels.md +6 -0
  30. package/corpus/templates/analytics/changelog/2026-06-27-dashboard-email-reports-still-send-when-the-live-screenshot-.md +6 -0
  31. package/corpus/templates/analytics/changelog/2026-06-27-fixed-dashboard-edits-so-charts-no-longer-briefly-snap-back-.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +1 -0
  33. package/corpus/templates/analytics/changelog/2026-06-27-session-replay-lists-now-show-only-signed-in-recordings-with.md +6 -0
  34. package/corpus/templates/analytics/changelog/2026-06-27-sidebar-loading-placeholders-are-visible-in-light-mode.md +6 -0
  35. package/corpus/templates/analytics/netlify.toml +4 -0
  36. package/corpus/templates/analytics/scripts/seed-session-replay.ts +1 -0
  37. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +1 -0
  38. package/corpus/templates/analytics/server/lib/dashboard-report.ts +120 -29
  39. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +1 -1
  40. package/corpus/templates/analytics/server/lib/session-replay.ts +158 -46
  41. package/corpus/templates/assets/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +1 -0
  42. package/corpus/templates/clips/app/components/capture-install-options.tsx +0 -9
  43. package/corpus/templates/clips/changelog/2026-06-27-clips-no-longer-shows-desktop-app-prompts-on-mobile-screens.md +1 -0
  44. package/corpus/templates/clips/changelog/2026-06-27-recorder-download-options-now-show-the-chrome-extension-and-.md +6 -0
  45. package/corpus/templates/clips/changelog/2026-06-27-recorder-setup-mobile-capture-options.md +1 -0
  46. package/corpus/templates/clips/chrome-extension/package.json +1 -1
  47. package/corpus/templates/clips/chrome-extension/src/sentry.ts +17 -1
  48. package/corpus/templates/clips/desktop/package.json +1 -0
  49. package/corpus/templates/clips/desktop/scripts/check-tauri-prereqs.mjs +45 -0
  50. package/corpus/templates/clips/desktop/src/lib/sentry.ts +71 -0
  51. package/corpus/templates/clips/desktop/src/main.tsx +2 -0
  52. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +2 -1
  53. package/corpus/templates/clips/desktop/src-tauri/build.rs +29 -0
  54. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +3 -0
  55. package/corpus/templates/clips/desktop/src-tauri/src/logfile.rs +1 -0
  56. package/corpus/templates/clips/desktop/src-tauri/src/sentry_report.rs +142 -0
  57. package/corpus/templates/clips/desktop/vite.config.ts +66 -0
  58. package/corpus/templates/design/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +1 -0
  59. package/corpus/templates/forms/actions/export-responses.ts +3 -2
  60. package/corpus/templates/forms/actions/list-responses.ts +2 -1
  61. package/corpus/templates/forms/actions/response-insights.ts +13 -4
  62. package/corpus/templates/forms/changelog/2026-06-27-anonymous-feedback-no-longer-shows-internal-agent-native-ano.md +6 -0
  63. package/corpus/templates/forms/server/handlers/submissions.ts +5 -8
  64. package/corpus/templates/forms/server/lib/integrations.ts +9 -6
  65. package/corpus/templates/forms/shared/submitter-email.ts +20 -0
  66. package/corpus/templates/forms/shared/types.ts +1 -1
  67. package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +49 -10
  68. package/corpus/templates/slides/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +1 -0
  69. package/dist/agent/engine/openai-compatible-endpoint.d.ts +3 -0
  70. package/dist/agent/engine/openai-compatible-endpoint.d.ts.map +1 -0
  71. package/dist/agent/engine/openai-compatible-endpoint.js +23 -0
  72. package/dist/agent/engine/openai-compatible-endpoint.js.map +1 -0
  73. package/dist/agent/engine/provider-env-vars.d.ts +1 -0
  74. package/dist/agent/engine/provider-env-vars.d.ts.map +1 -1
  75. package/dist/agent/engine/provider-env-vars.js +1 -0
  76. package/dist/agent/engine/provider-env-vars.js.map +1 -1
  77. package/dist/agent/engine/registry.d.ts.map +1 -1
  78. package/dist/agent/engine/registry.js +39 -12
  79. package/dist/agent/engine/registry.js.map +1 -1
  80. package/dist/client/FeedbackButton.d.ts.map +1 -1
  81. package/dist/client/FeedbackButton.js +6 -1
  82. package/dist/client/FeedbackButton.js.map +1 -1
  83. package/dist/client/agent-engine-key.d.ts +14 -0
  84. package/dist/client/agent-engine-key.d.ts.map +1 -1
  85. package/dist/client/agent-engine-key.js +35 -10
  86. package/dist/client/agent-engine-key.js.map +1 -1
  87. package/dist/client/analytics.d.ts.map +1 -1
  88. package/dist/client/analytics.js +71 -24
  89. package/dist/client/analytics.js.map +1 -1
  90. package/dist/client/blocks/library/HighlightedCode.d.ts +1 -1
  91. package/dist/client/blocks/library/HighlightedCode.js +1 -1
  92. package/dist/client/blocks/library/HighlightedCode.js.map +1 -1
  93. package/dist/client/index.d.ts +1 -1
  94. package/dist/client/index.d.ts.map +1 -1
  95. package/dist/client/index.js +1 -1
  96. package/dist/client/index.js.map +1 -1
  97. package/dist/client/session-replay.d.ts +1 -1
  98. package/dist/client/session-replay.d.ts.map +1 -1
  99. package/dist/client/session-replay.js +115 -17
  100. package/dist/client/session-replay.js.map +1 -1
  101. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  102. package/dist/client/settings/SettingsPanel.js +42 -6
  103. package/dist/client/settings/SettingsPanel.js.map +1 -1
  104. package/dist/collab/awareness.d.ts +2 -2
  105. package/dist/collab/awareness.d.ts.map +1 -1
  106. package/dist/collab/routes.d.ts +1 -1
  107. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  108. package/dist/notifications/routes.d.ts +2 -2
  109. package/dist/observability/routes.d.ts +7 -7
  110. package/dist/progress/routes.d.ts +1 -1
  111. package/dist/scripts/agent-engines/manage-agent-engine.d.ts.map +1 -1
  112. package/dist/scripts/agent-engines/manage-agent-engine.js +4 -0
  113. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  114. package/dist/scripts/agent-engines/set-agent-engine.d.ts.map +1 -1
  115. package/dist/scripts/agent-engines/set-agent-engine.js +5 -4
  116. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  117. package/dist/scripts/agent-engines/test-agent-engine.d.ts.map +1 -1
  118. package/dist/scripts/agent-engines/test-agent-engine.js +37 -5
  119. package/dist/scripts/agent-engines/test-agent-engine.js.map +1 -1
  120. package/dist/server/agent-engine-api-key-route.d.ts +4 -4
  121. package/dist/server/agent-engine-api-key-route.d.ts.map +1 -1
  122. package/dist/server/agent-engine-api-key-route.js +71 -11
  123. package/dist/server/agent-engine-api-key-route.js.map +1 -1
  124. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  125. package/dist/server/core-routes-plugin.js +20 -3
  126. package/dist/server/core-routes-plugin.js.map +1 -1
  127. package/dist/styles/blocks.css +23 -7
  128. package/docs/content/deployment.md +82 -82
  129. package/docs/content/faq.md +1 -1
  130. package/docs/content/tracking.md +3 -3
  131. package/package.json +1 -1
@@ -25,7 +25,6 @@ import {
25
25
  type DragOverEvent,
26
26
  type DragStartEvent,
27
27
  type CollisionDetection,
28
- type Collision,
29
28
  } from "@dnd-kit/core";
30
29
  import {
31
30
  IconArchive,
@@ -105,16 +104,22 @@ import {
105
104
  import BlankDashboard from "../BlankDashboard";
106
105
  import { DashboardSkeleton } from "../DashboardSkeleton";
107
106
  import {
107
+ availableDropSlotIdsForPanel,
108
108
  buildDashboardPanelGroups,
109
109
  distanceFromPointerToRect,
110
110
  dropSlotId,
111
- isDropSlotAvailable,
112
111
  movePanelToDropSlot,
113
112
  readDropSlot,
114
113
  removePanelFromLayout,
115
114
  sameDropSlot,
116
115
  type DashboardDropSlot,
117
116
  } from "./dashboard-layout";
117
+ import {
118
+ createDashboardAdoptionHold,
119
+ dashboardPrefetchInitialData,
120
+ shouldAdoptDashboardQueryResult,
121
+ type DashboardAdoptionHold,
122
+ } from "./dashboard-sync";
118
123
  import {
119
124
  DashboardFilterBar,
120
125
  FILTER_PARAM_PREFIX,
@@ -165,6 +170,31 @@ function groupDashboardTabs(tabs: string[]): {
165
170
  return { groups, hasNestedTabs };
166
171
  }
167
172
 
173
+ function parseReportPanelLimit(raw: string | null): number | null {
174
+ if (!raw) return null;
175
+ const parsed = Number.parseInt(raw, 10);
176
+ if (!Number.isFinite(parsed) || parsed <= 0) return null;
177
+ return Math.min(50, parsed);
178
+ }
179
+
180
+ function limitReportPanels(panels: SqlPanel[], limit: number | null) {
181
+ if (!limit) return panels;
182
+ const limited: SqlPanel[] = [];
183
+ let chartCount = 0;
184
+
185
+ for (const panel of panels) {
186
+ if (panel.chartType === "section") {
187
+ if (chartCount < limit) limited.push(panel);
188
+ continue;
189
+ }
190
+ if (chartCount >= limit) continue;
191
+ chartCount++;
192
+ limited.push(panel);
193
+ }
194
+
195
+ return limited;
196
+ }
197
+
168
198
  function DashboardDropLine({
169
199
  slot,
170
200
  activeSlot,
@@ -220,6 +250,7 @@ const PanelCell = memo(function PanelCell({
220
250
  remoteEditor,
221
251
  editable,
222
252
  eagerLoad,
253
+ isDragSource,
223
254
  onRemovePanel,
224
255
  onEditPanel,
225
256
  onSavePanel,
@@ -229,6 +260,7 @@ const PanelCell = memo(function PanelCell({
229
260
  remoteEditor: { color: string; name: string } | undefined;
230
261
  editable: boolean;
231
262
  eagerLoad: boolean;
263
+ isDragSource: boolean;
232
264
  onRemovePanel: (panelId: string) => void;
233
265
  onEditPanel: (panel: SqlPanel) => void;
234
266
  onSavePanel: (panel: SqlPanel) => Promise<void>;
@@ -283,6 +315,7 @@ const PanelCell = memo(function PanelCell({
283
315
  onSaveSql={(sql) => onSavePanel({ ...panel, sql })}
284
316
  editable={editable}
285
317
  eagerLoad={eagerLoad}
318
+ isDragSource={isDragSource}
286
319
  />
287
320
  </div>
288
321
  );
@@ -394,11 +427,13 @@ export default function SqlDashboardPage() {
394
427
  const navigate = useNavigate();
395
428
  const dashboardId = searchParams.get("id") || routeId;
396
429
  const reportScreenshot = searchParams.get("reportScreenshot") === "1";
430
+ const reportPanelLimit = reportScreenshot
431
+ ? parseReportPanelLimit(searchParams.get("reportPanelLimit"))
432
+ : null;
397
433
 
398
434
  const [dashboard, setDashboard] = useState<SqlDashboardConfig | null>(null);
399
435
  const [archivedAt, setArchivedAt] = useState<string | null>(null);
400
436
  const [hiddenAt, setHiddenAt] = useState<string | null>(null);
401
- const [hiddenBy, setHiddenBy] = useState<string | null>(null);
402
437
  const [dashboardVisibility, setDashboardVisibility] = useState<
403
438
  "private" | "org" | "public" | null
404
439
  >(null);
@@ -422,6 +457,7 @@ export default function SqlDashboardPage() {
422
457
  null,
423
458
  );
424
459
  const viewedDashboardIdRef = useRef<string | null>(null);
460
+ const pendingConfigRef = useRef<DashboardAdoptionHold | null>(null);
425
461
  const canEdit = !reportScreenshot && resourceCanEdit(resourceAccess);
426
462
  const canManage = !reportScreenshot && resourceCanManage(resourceAccess);
427
463
  const dashboardColumns = clampDashboardColumns(
@@ -462,10 +498,7 @@ export default function SqlDashboardPage() {
462
498
  const snapshot = queryClient.getQueryData<
463
499
  PrefetchSnapshot<FetchedDashboard | null>
464
500
  >(sqlDashboardPrefetchKey(dashboardId));
465
- if (snapshot?.data === null && snapshot.syncVersion !== sync) {
466
- return undefined;
467
- }
468
- return snapshot?.data;
501
+ return dashboardPrefetchInitialData(snapshot, sync);
469
502
  },
470
503
  initialDataUpdatedAt: () => {
471
504
  if (!dashboardId) return undefined;
@@ -475,7 +508,7 @@ export default function SqlDashboardPage() {
475
508
  queryKey,
476
509
  );
477
510
  if (!snapshot) return undefined;
478
- if (snapshot.syncVersion !== sync) return 0;
511
+ if (snapshot.syncVersion !== sync) return undefined;
479
512
  return queryClient.getQueryState(queryKey)?.dataUpdatedAt;
480
513
  },
481
514
  });
@@ -510,6 +543,36 @@ export default function SqlDashboardPage() {
510
543
  user: currentUser,
511
544
  });
512
545
 
546
+ const updateCachedDashboardConfig = useCallback(
547
+ (updated: SqlDashboardConfig) => {
548
+ if (!dashboardId) return;
549
+ queryClient.setQueriesData<FetchedDashboard | null>(
550
+ { queryKey: ["data", "sql-dashboard", dashboardId] },
551
+ (prev) => (prev ? { ...prev, config: updated } : prev),
552
+ );
553
+ queryClient.setQueryData<PrefetchSnapshot<FetchedDashboard | null>>(
554
+ sqlDashboardPrefetchKey(dashboardId),
555
+ (prev) =>
556
+ prev?.data
557
+ ? { ...prev, data: { ...prev.data, config: updated } }
558
+ : prev,
559
+ );
560
+ },
561
+ [dashboardId, queryClient],
562
+ );
563
+
564
+ const holdDashboardConfig = useCallback(() => {
565
+ if (!dashboardId) return;
566
+ pendingConfigRef.current = createDashboardAdoptionHold({
567
+ dashboardId,
568
+ currentUpdatedAt: dashboardUpdatedAt,
569
+ });
570
+ void queryClient.cancelQueries(
571
+ { queryKey: ["data", "sql-dashboard", dashboardId] },
572
+ { revert: false },
573
+ );
574
+ }, [dashboardId, dashboardUpdatedAt, queryClient]);
575
+
513
576
  // Track which panels remote users are editing (from awareness)
514
577
  const [remoteEditingPanels, setRemoteEditingPanels] = useState<
515
578
  Map<string, { color: string; name: string }>
@@ -548,8 +611,10 @@ export default function SqlDashboardPage() {
548
611
  if (!raw) return;
549
612
  try {
550
613
  const parsed = JSON.parse(raw) as SqlDashboardConfig;
551
- if (parsed && parsed.panels) {
614
+ if (parsed && Array.isArray(parsed.panels)) {
615
+ holdDashboardConfig();
552
616
  setDashboard(parsed);
617
+ updateCachedDashboardConfig(parsed);
553
618
  }
554
619
  } catch {
555
620
  // JSON parse failed — ignore partial updates
@@ -559,7 +624,7 @@ export default function SqlDashboardPage() {
559
624
  return () => {
560
625
  ytext.unobserve(handler);
561
626
  };
562
- }, [ydoc, collabSynced]);
627
+ }, [ydoc, collabSynced, holdDashboardConfig, updateCachedDashboardConfig]);
563
628
 
564
629
  // Per-user saved filter state
565
630
  const filterPrefKey = dashboardId ? `dashboard-filters:${dashboardId}` : "";
@@ -581,7 +646,6 @@ export default function SqlDashboardPage() {
581
646
  setDashboard(null);
582
647
  setArchivedAt(null);
583
648
  setHiddenAt(null);
584
- setHiddenBy(null);
585
649
  setDashboardVisibility(null);
586
650
  setDashboardOwner(null);
587
651
  setDashboardUpdatedAt(null);
@@ -592,17 +656,29 @@ export default function SqlDashboardPage() {
592
656
  useEffect(() => {
593
657
  if (!dashboardId || !dashboardQuery.isSuccess) return;
594
658
  const fetched = dashboardQuery.data;
595
- if (fetched && fetched.id !== dashboardId) return;
659
+ const adoption = shouldAdoptDashboardQueryResult({
660
+ dashboardId,
661
+ loaded,
662
+ isPlaceholderData: dashboardQuery.isPlaceholderData,
663
+ fetchedId: fetched?.id,
664
+ fetchedUpdatedAt: fetched?.updatedAt,
665
+ currentUpdatedAt: dashboardUpdatedAt,
666
+ hold: pendingConfigRef.current,
667
+ });
668
+ if (adoption.clearHold) {
669
+ pendingConfigRef.current = null;
670
+ }
671
+ if (!adoption.adopt) return;
672
+ const fetchedConfig = fetched?.config ?? null;
596
673
  const fetchedVisibility =
597
674
  fetched?.visibility === "private" ||
598
675
  fetched?.visibility === "org" ||
599
676
  fetched?.visibility === "public"
600
677
  ? fetched.visibility
601
678
  : null;
602
- setDashboard(fetched?.config ?? null);
679
+ setDashboard(fetchedConfig);
603
680
  setArchivedAt(fetched?.archivedAt ?? null);
604
681
  setHiddenAt(fetched?.hiddenAt ?? null);
605
- setHiddenBy(fetched?.hiddenBy ?? null);
606
682
  setDashboardVisibility(fetchedVisibility);
607
683
  setDashboardOwner(fetched?.ownerEmail ?? null);
608
684
  setDashboardUpdatedAt(fetched?.updatedAt ?? null);
@@ -628,6 +704,9 @@ export default function SqlDashboardPage() {
628
704
  dashboardId,
629
705
  dashboardQuery.data,
630
706
  dashboardQuery.isSuccess,
707
+ dashboardQuery.isPlaceholderData,
708
+ dashboardUpdatedAt,
709
+ loaded,
631
710
  reportScreenshot,
632
711
  ]);
633
712
 
@@ -738,24 +817,6 @@ export default function SqlDashboardPage() {
738
817
  [collabDocId],
739
818
  );
740
819
 
741
- const updateCachedDashboardConfig = useCallback(
742
- (updated: SqlDashboardConfig) => {
743
- if (!dashboardId) return;
744
- queryClient.setQueriesData<FetchedDashboard | null>(
745
- { queryKey: ["data", "sql-dashboard", dashboardId] },
746
- (prev) => (prev ? { ...prev, config: updated } : prev),
747
- );
748
- queryClient.setQueryData<PrefetchSnapshot<FetchedDashboard | null>>(
749
- sqlDashboardPrefetchKey(dashboardId),
750
- (prev) =>
751
- prev?.data
752
- ? { ...prev, data: { ...prev.data, config: updated } }
753
- : prev,
754
- );
755
- },
756
- [dashboardId, queryClient],
757
- );
758
-
759
820
  /**
760
821
  * Persist without throwing — background save used for drag reorder, width
761
822
  * toggle, title/description edits, and panel delete. If the save fails
@@ -769,6 +830,7 @@ export default function SqlDashboardPage() {
769
830
  toast.error(t("sqlDashboard.viewOnly"));
770
831
  return;
771
832
  }
833
+ holdDashboardConfig();
772
834
  setDashboard(updated);
773
835
  updateCachedDashboardConfig(updated);
774
836
  pushToCollab(updated);
@@ -800,6 +862,7 @@ export default function SqlDashboardPage() {
800
862
  [
801
863
  dashboardId,
802
864
  canEdit,
865
+ holdDashboardConfig,
803
866
  queryClient,
804
867
  pushToCollab,
805
868
  t,
@@ -817,6 +880,7 @@ export default function SqlDashboardPage() {
817
880
  if (!canEdit) {
818
881
  throw new Error(t("sqlDashboard.viewOnly"));
819
882
  }
883
+ holdDashboardConfig();
820
884
  await saveDashboard(dashboardId, updated);
821
885
  setDashboard(updated);
822
886
  updateCachedDashboardConfig(updated);
@@ -833,6 +897,7 @@ export default function SqlDashboardPage() {
833
897
  [
834
898
  dashboardId,
835
899
  canEdit,
900
+ holdDashboardConfig,
836
901
  queryClient,
837
902
  pushToCollab,
838
903
  t,
@@ -1025,9 +1090,11 @@ export default function SqlDashboardPage() {
1025
1090
  // dashboard shows every panel as before.
1026
1091
  const visiblePanels = useMemo(() => {
1027
1092
  if (!dashboard) return [];
1028
- if (!activeTab) return dashboard.panels;
1029
- return dashboard.panels.filter((p) => !p.tab || p.tab === activeTab);
1030
- }, [dashboard, activeTab]);
1093
+ const tabPanels = activeTab
1094
+ ? dashboard.panels.filter((p) => !p.tab || p.tab === activeTab)
1095
+ : dashboard.panels;
1096
+ return limitReportPanels(tabPanels, reportPanelLimit);
1097
+ }, [dashboard, activeTab, reportPanelLimit]);
1031
1098
 
1032
1099
  // Group panels into "section blocks": each section starts a new block whose
1033
1100
  // grid uses the section's `columns` (falling back to the dashboard default).
@@ -1043,17 +1110,23 @@ export default function SqlDashboardPage() {
1043
1110
  : null,
1044
1111
  [activeDragPanelId, visiblePanels],
1045
1112
  );
1113
+ const activeDropSlotIds = useMemo(
1114
+ () =>
1115
+ activeDragPanelId
1116
+ ? availableDropSlotIdsForPanel(panelGroups, activeDragPanelId)
1117
+ : null,
1118
+ [activeDragPanelId, panelGroups],
1119
+ );
1046
1120
 
1047
1121
  const dashboardCollisionDetection = useCallback<CollisionDetection>(
1048
1122
  (args) => {
1049
1123
  const activePanelId = String(args.active.id);
1050
- const droppableContainers = args.droppableContainers.filter(
1051
- (container) => {
1052
- const slot = readDropSlot(container.data.current);
1053
- return (
1054
- !!slot && isDropSlotAvailable(panelGroups, activePanelId, slot)
1055
- );
1056
- },
1124
+ const availableSlotIds =
1125
+ activeDragPanelId === activePanelId && activeDropSlotIds
1126
+ ? activeDropSlotIds
1127
+ : availableDropSlotIdsForPanel(panelGroups, activePanelId);
1128
+ const droppableContainers = args.droppableContainers.filter((container) =>
1129
+ availableSlotIds.has(String(container.id)),
1057
1130
  );
1058
1131
 
1059
1132
  if (droppableContainers.length === 0) return [];
@@ -1066,29 +1139,34 @@ export default function SqlDashboardPage() {
1066
1139
 
1067
1140
  if (!args.pointerCoordinates) return [];
1068
1141
 
1069
- const collisions: Collision[] = [];
1142
+ let closestId: string | null = null;
1143
+ let closestContainer: (typeof droppableContainers)[number] | null = null;
1144
+ let closestValue = Number.MAX_VALUE;
1070
1145
  for (const droppableContainer of droppableContainers) {
1071
1146
  const rect = args.droppableRects.get(droppableContainer.id);
1072
1147
  if (!rect) continue;
1073
1148
 
1074
- collisions.push({
1075
- id: droppableContainer.id,
1076
- data: {
1077
- droppableContainer,
1078
- value: distanceFromPointerToRect(args.pointerCoordinates, rect),
1079
- },
1080
- });
1149
+ const value = distanceFromPointerToRect(args.pointerCoordinates, rect);
1150
+ if (value < closestValue) {
1151
+ closestId = String(droppableContainer.id);
1152
+ closestContainer = droppableContainer;
1153
+ closestValue = value;
1154
+ }
1081
1155
  }
1082
1156
 
1083
- return collisions.sort((a, b) => {
1084
- const aValue =
1085
- typeof a.data?.value === "number" ? a.data.value : Number.MAX_VALUE;
1086
- const bValue =
1087
- typeof b.data?.value === "number" ? b.data.value : Number.MAX_VALUE;
1088
- return aValue - bValue;
1089
- });
1157
+ return closestId && closestContainer
1158
+ ? [
1159
+ {
1160
+ id: closestId,
1161
+ data: {
1162
+ droppableContainer: closestContainer,
1163
+ value: closestValue,
1164
+ },
1165
+ },
1166
+ ]
1167
+ : [];
1090
1168
  },
1091
- [panelGroups],
1169
+ [activeDragPanelId, activeDropSlotIds, panelGroups],
1092
1170
  );
1093
1171
 
1094
1172
  const handleDelete = useCallback(async () => {
@@ -1155,7 +1233,6 @@ export default function SqlDashboardPage() {
1155
1233
  hidden: false,
1156
1234
  })) as { ownerEmail?: string | null } | undefined;
1157
1235
  setHiddenAt(null);
1158
- setHiddenBy(null);
1159
1236
  if (typeof result?.ownerEmail === "string") {
1160
1237
  setDashboardOwner(result.ownerEmail);
1161
1238
  }
@@ -1648,6 +1725,7 @@ export default function SqlDashboardPage() {
1648
1725
  onEdit={() => openEditPanel(section)}
1649
1726
  editable={canEdit}
1650
1727
  eagerLoad={reportScreenshot}
1728
+ isDragSource={activeDragPanelId === section.id}
1651
1729
  />
1652
1730
  </div>
1653
1731
  );
@@ -1699,6 +1777,7 @@ export default function SqlDashboardPage() {
1699
1777
  }
1700
1778
  editable={canEdit}
1701
1779
  eagerLoad={reportScreenshot}
1780
+ isDragSource={activeDragPanelId === panel.id}
1702
1781
  onRemovePanel={removePanel}
1703
1782
  onEditPanel={openEditPanel}
1704
1783
  onSavePanel={handleSavePanel}
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-27
4
+ ---
5
+
6
+ Dashboard charts drag smoothly when rearranging panels.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-27
4
+ ---
5
+
6
+ Dashboard email reports still send when the live screenshot renderer fails.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-27
4
+ ---
5
+
6
+ Fixed dashboard edits so charts no longer briefly snap back to an old layout after agent updates or drag-and-drop.
@@ -2,4 +2,5 @@
2
2
  type: improved
3
3
  date: 2026-06-27
4
4
  ---
5
+
5
6
  Improved mobile navigation chrome and sidebar drawer motion.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-27
4
+ ---
5
+
6
+ Session replay lists now show only signed-in recordings with real activity and reliable durations.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-27
4
+ ---
5
+
6
+ Sidebar loading placeholders are visible in light mode.
@@ -16,3 +16,7 @@ AGENT_PROD_CODE_EXECUTION = "sandboxed"
16
16
  # If AGENT_RUN_SOFT_TIMEOUT_MS is customized, keep it below this timeout.
17
17
  [functions."*"]
18
18
  timeout = 75
19
+
20
+ [functions.dashboard-report-sweep-background]
21
+ timeout = 300
22
+ memory = "2gb"
@@ -144,6 +144,7 @@ async function main() {
144
144
  publicKey,
145
145
  replayId: `seed-${seed.app}-${started}`,
146
146
  sessionId: `seed-session-${seed.app}-${started}`,
147
+ userId: `seed-user-${index + 1}@example.com`,
147
148
  anonymousId: `seed-anon-${index + 1}`,
148
149
  url: `http://localhost:8080${seed.path}`,
149
150
  app: seed.app,
@@ -11,6 +11,7 @@ const DEFAULT_MAX_REPORTS_PER_SWEEP = 5;
11
11
 
12
12
  function maxReportsPerSweep(): number {
13
13
  const raw = process.env.DASHBOARD_REPORT_SWEEP_LIMIT?.trim();
14
+ if (!raw && process.env.NETLIFY === "true") return 1;
14
15
  if (!raw) return DEFAULT_MAX_REPORTS_PER_SWEEP;
15
16
  const parsed = Number.parseInt(raw, 10);
16
17
  return Number.isFinite(parsed) && parsed > 0