@agent-native/core 0.79.20 → 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 (141) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/docs/content/deployment.md +133 -0
  4. package/corpus/core/docs/content/faq.md +1 -1
  5. package/corpus/core/docs/content/multi-app-workspace.md +2 -0
  6. package/corpus/core/docs/content/tracking.md +3 -3
  7. package/corpus/core/package.json +1 -1
  8. package/corpus/core/src/agent/engine/openai-compatible-endpoint.ts +25 -0
  9. package/corpus/core/src/agent/engine/provider-env-vars.ts +2 -0
  10. package/corpus/core/src/agent/engine/registry.ts +59 -11
  11. package/corpus/core/src/client/FeedbackButton.tsx +11 -1
  12. package/corpus/core/src/client/agent-engine-key.ts +57 -10
  13. package/corpus/core/src/client/analytics.ts +78 -27
  14. package/corpus/core/src/client/blocks/library/HighlightedCode.tsx +1 -1
  15. package/corpus/core/src/client/index.ts +2 -0
  16. package/corpus/core/src/client/session-replay.ts +159 -17
  17. package/corpus/core/src/client/settings/SettingsPanel.tsx +119 -4
  18. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +5 -0
  19. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +8 -3
  20. package/corpus/core/src/scripts/agent-engines/test-agent-engine.ts +54 -6
  21. package/corpus/core/src/server/agent-engine-api-key-route.ts +83 -12
  22. package/corpus/core/src/server/core-routes-plugin.ts +26 -2
  23. package/corpus/core/src/styles/blocks.css +23 -7
  24. package/corpus/templates/analytics/actions/list-session-recordings.ts +1 -1
  25. package/corpus/templates/analytics/app/components/layout/MobileNav.tsx +5 -10
  26. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +22 -4
  27. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +64 -50
  28. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +38 -0
  29. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-sync.ts +89 -0
  30. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +138 -59
  31. package/corpus/templates/analytics/changelog/2026-06-27-dashboard-charts-drag-smoothly-when-rearranging-panels.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-06-27-dashboard-email-reports-still-send-when-the-live-screenshot-.md +6 -0
  33. package/corpus/templates/analytics/changelog/2026-06-27-fixed-dashboard-edits-so-charts-no-longer-briefly-snap-back-.md +6 -0
  34. package/corpus/templates/analytics/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +6 -0
  35. package/corpus/templates/analytics/changelog/2026-06-27-session-replay-lists-now-show-only-signed-in-recordings-with.md +6 -0
  36. package/corpus/templates/analytics/changelog/2026-06-27-sidebar-loading-placeholders-are-visible-in-light-mode.md +6 -0
  37. package/corpus/templates/analytics/netlify.toml +4 -0
  38. package/corpus/templates/analytics/scripts/seed-session-replay.ts +1 -0
  39. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +1 -0
  40. package/corpus/templates/analytics/server/lib/dashboard-report.ts +120 -29
  41. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +1 -1
  42. package/corpus/templates/analytics/server/lib/session-replay.ts +158 -46
  43. package/corpus/templates/assets/app/components/layout/Layout.tsx +1 -1
  44. package/corpus/templates/assets/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +6 -0
  45. package/corpus/templates/clips/app/components/capture-install-options.tsx +0 -9
  46. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +20 -4
  47. package/corpus/templates/clips/app/hooks/use-desktop-promo.ts +6 -2
  48. package/corpus/templates/clips/changelog/2026-06-27-clips-no-longer-shows-desktop-app-prompts-on-mobile-screens.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-06-27-desktop-storage-setup-flow.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-06-27-recorder-download-options-now-show-the-chrome-extension-and-.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-06-27-recorder-setup-mobile-capture-options.md +6 -0
  52. package/corpus/templates/clips/chrome-extension/package.json +1 -1
  53. package/corpus/templates/clips/chrome-extension/src/sentry.ts +17 -1
  54. package/corpus/templates/clips/desktop/package.json +1 -0
  55. package/corpus/templates/clips/desktop/scripts/check-tauri-prereqs.mjs +45 -0
  56. package/corpus/templates/clips/desktop/src/app.tsx +32 -3
  57. package/corpus/templates/clips/desktop/src/lib/sentry.ts +71 -0
  58. package/corpus/templates/clips/desktop/src/main.tsx +2 -0
  59. package/corpus/templates/clips/desktop/src/styles.css +67 -0
  60. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +2 -1
  61. package/corpus/templates/clips/desktop/src-tauri/build.rs +29 -0
  62. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +3 -0
  63. package/corpus/templates/clips/desktop/src-tauri/src/logfile.rs +1 -0
  64. package/corpus/templates/clips/desktop/src-tauri/src/sentry_report.rs +142 -0
  65. package/corpus/templates/clips/desktop/vite.config.ts +66 -0
  66. package/corpus/templates/design/app/components/layout/Layout.tsx +1 -1
  67. package/corpus/templates/design/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +6 -0
  68. package/corpus/templates/forms/actions/export-responses.ts +3 -2
  69. package/corpus/templates/forms/actions/list-responses.ts +2 -1
  70. package/corpus/templates/forms/actions/response-insights.ts +13 -4
  71. package/corpus/templates/forms/changelog/2026-06-27-anonymous-feedback-no-longer-shows-internal-agent-native-ano.md +6 -0
  72. package/corpus/templates/forms/server/handlers/submissions.ts +5 -8
  73. package/corpus/templates/forms/server/lib/integrations.ts +9 -6
  74. package/corpus/templates/forms/shared/submitter-email.ts +20 -0
  75. package/corpus/templates/forms/shared/types.ts +1 -1
  76. package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +49 -10
  77. package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
  78. package/corpus/templates/slides/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +6 -0
  79. package/dist/agent/engine/openai-compatible-endpoint.d.ts +3 -0
  80. package/dist/agent/engine/openai-compatible-endpoint.d.ts.map +1 -0
  81. package/dist/agent/engine/openai-compatible-endpoint.js +23 -0
  82. package/dist/agent/engine/openai-compatible-endpoint.js.map +1 -0
  83. package/dist/agent/engine/provider-env-vars.d.ts +1 -0
  84. package/dist/agent/engine/provider-env-vars.d.ts.map +1 -1
  85. package/dist/agent/engine/provider-env-vars.js +1 -0
  86. package/dist/agent/engine/provider-env-vars.js.map +1 -1
  87. package/dist/agent/engine/registry.d.ts.map +1 -1
  88. package/dist/agent/engine/registry.js +39 -12
  89. package/dist/agent/engine/registry.js.map +1 -1
  90. package/dist/client/FeedbackButton.d.ts.map +1 -1
  91. package/dist/client/FeedbackButton.js +6 -1
  92. package/dist/client/FeedbackButton.js.map +1 -1
  93. package/dist/client/agent-engine-key.d.ts +14 -0
  94. package/dist/client/agent-engine-key.d.ts.map +1 -1
  95. package/dist/client/agent-engine-key.js +35 -10
  96. package/dist/client/agent-engine-key.js.map +1 -1
  97. package/dist/client/analytics.d.ts.map +1 -1
  98. package/dist/client/analytics.js +71 -24
  99. package/dist/client/analytics.js.map +1 -1
  100. package/dist/client/blocks/library/HighlightedCode.d.ts +1 -1
  101. package/dist/client/blocks/library/HighlightedCode.js +1 -1
  102. package/dist/client/blocks/library/HighlightedCode.js.map +1 -1
  103. package/dist/client/index.d.ts +1 -1
  104. package/dist/client/index.d.ts.map +1 -1
  105. package/dist/client/index.js +1 -1
  106. package/dist/client/index.js.map +1 -1
  107. package/dist/client/session-replay.d.ts +1 -1
  108. package/dist/client/session-replay.d.ts.map +1 -1
  109. package/dist/client/session-replay.js +115 -17
  110. package/dist/client/session-replay.js.map +1 -1
  111. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  112. package/dist/client/settings/SettingsPanel.js +42 -6
  113. package/dist/client/settings/SettingsPanel.js.map +1 -1
  114. package/dist/collab/routes.d.ts +2 -2
  115. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  116. package/dist/notifications/routes.d.ts +1 -1
  117. package/dist/observability/routes.d.ts +7 -7
  118. package/dist/progress/routes.d.ts +1 -1
  119. package/dist/resources/handlers.d.ts +1 -1
  120. package/dist/scripts/agent-engines/manage-agent-engine.d.ts.map +1 -1
  121. package/dist/scripts/agent-engines/manage-agent-engine.js +4 -0
  122. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  123. package/dist/scripts/agent-engines/set-agent-engine.d.ts.map +1 -1
  124. package/dist/scripts/agent-engines/set-agent-engine.js +5 -4
  125. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  126. package/dist/scripts/agent-engines/test-agent-engine.d.ts.map +1 -1
  127. package/dist/scripts/agent-engines/test-agent-engine.js +37 -5
  128. package/dist/scripts/agent-engines/test-agent-engine.js.map +1 -1
  129. package/dist/server/agent-engine-api-key-route.d.ts +5 -5
  130. package/dist/server/agent-engine-api-key-route.d.ts.map +1 -1
  131. package/dist/server/agent-engine-api-key-route.js +71 -11
  132. package/dist/server/agent-engine-api-key-route.js.map +1 -1
  133. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  134. package/dist/server/core-routes-plugin.js +20 -3
  135. package/dist/server/core-routes-plugin.js.map +1 -1
  136. package/dist/styles/blocks.css +23 -7
  137. package/docs/content/deployment.md +133 -0
  138. package/docs/content/faq.md +1 -1
  139. package/docs/content/multi-app-workspace.md +2 -0
  140. package/docs/content/tracking.md +3 -3
  141. package/package.json +1 -1
@@ -120,6 +120,8 @@ const DASHBOARD_SORT_MODE_KEY = "dashboard-sort-mode";
120
120
  const ANALYSIS_SORT_MODE_KEY = "analysis-sort-mode";
121
121
  const DASHBOARDS_OPEN_KEY = "analytics-sidebar-dashboards-open";
122
122
  const ANALYSES_OPEN_KEY = "analytics-sidebar-analyses-open";
123
+ const SIDEBAR_SKELETON_CLASS =
124
+ "bg-sidebar-foreground/12 dark:bg-sidebar-foreground/10";
123
125
 
124
126
  type SidebarSortMode = "most-used" | "alphabetical" | "manual";
125
127
 
@@ -2056,9 +2058,17 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
2056
2058
  key={`sql-skeleton-${i}`}
2057
2059
  className="flex items-center gap-2 px-3 py-1"
2058
2060
  >
2059
- <Skeleton className="h-3.5 w-3.5 shrink-0 rounded-sm" />
2060
2061
  <Skeleton
2061
- className="h-3 rounded"
2062
+ className={cn(
2063
+ "h-3.5 w-3.5 shrink-0 rounded-sm",
2064
+ SIDEBAR_SKELETON_CLASS,
2065
+ )}
2066
+ />
2067
+ <Skeleton
2068
+ className={cn(
2069
+ "h-3 rounded",
2070
+ SIDEBAR_SKELETON_CLASS,
2071
+ )}
2062
2072
  style={{ width: `${60 + ((i * 17) % 30)}%` }}
2063
2073
  />
2064
2074
  </div>
@@ -2184,9 +2194,17 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
2184
2194
  key={`analysis-skeleton-${i}`}
2185
2195
  className="flex items-center gap-2 px-3 py-1"
2186
2196
  >
2187
- <Skeleton className="h-3.5 w-3.5 shrink-0 rounded-sm" />
2188
2197
  <Skeleton
2189
- className="h-3 rounded"
2198
+ className={cn(
2199
+ "h-3.5 w-3.5 shrink-0 rounded-sm",
2200
+ SIDEBAR_SKELETON_CLASS,
2201
+ )}
2202
+ />
2203
+ <Skeleton
2204
+ className={cn(
2205
+ "h-3 rounded",
2206
+ SIDEBAR_SKELETON_CLASS,
2207
+ )}
2190
2208
  style={{ width: `${60 + ((i * 17) % 30)}%` }}
2191
2209
  />
2192
2210
  </div>
@@ -11,7 +11,7 @@ import {
11
11
  IconDownload,
12
12
  } from "@tabler/icons-react";
13
13
  import { useIsFetching, useQueryClient } from "@tanstack/react-query";
14
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
14
+ import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
15
15
 
16
16
  import { ChartFillHeight, SqlChart } from "@/components/dashboard/SqlChart";
17
17
  import {
@@ -59,8 +59,51 @@ interface SqlChartCardProps {
59
59
  onSaveSql?: (sql: string) => Promise<void>;
60
60
  editable?: boolean;
61
61
  eagerLoad?: boolean;
62
+ isDragSource?: boolean;
62
63
  }
63
64
 
65
+ const PanelDragHandle = memo(function PanelDragHandle({
66
+ panelId,
67
+ label,
68
+ className,
69
+ iconClassName,
70
+ }: {
71
+ panelId: string;
72
+ label: string;
73
+ className: string;
74
+ iconClassName: string;
75
+ }) {
76
+ const { attributes, listeners, setActivatorNodeRef, setNodeRef } =
77
+ useDraggable({
78
+ id: panelId,
79
+ });
80
+
81
+ const setHandleRef = useCallback(
82
+ (node: HTMLButtonElement | null) => {
83
+ setNodeRef(node);
84
+ setActivatorNodeRef(node);
85
+ },
86
+ [setActivatorNodeRef, setNodeRef],
87
+ );
88
+
89
+ return (
90
+ <Tooltip>
91
+ <TooltipTrigger asChild>
92
+ <button
93
+ ref={setHandleRef}
94
+ className={className}
95
+ aria-label={label}
96
+ {...attributes}
97
+ {...listeners}
98
+ >
99
+ <IconGripVertical className={iconClassName} />
100
+ </button>
101
+ </TooltipTrigger>
102
+ <TooltipContent>{label}</TooltipContent>
103
+ </Tooltip>
104
+ );
105
+ });
106
+
64
107
  export function SqlChartCard({
65
108
  panel,
66
109
  resolvedSql,
@@ -69,13 +112,10 @@ export function SqlChartCard({
69
112
  onSaveSql,
70
113
  editable = true,
71
114
  eagerLoad = false,
115
+ isDragSource = false,
72
116
  }: SqlChartCardProps) {
73
117
  const t = useT();
74
118
  const queryClient = useQueryClient();
75
- const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
76
- id: panel.id,
77
- disabled: !editable,
78
- });
79
119
 
80
120
  const [confirmOpen, setConfirmOpen] = useState(false);
81
121
  const [expanded, setExpanded] = useState(false);
@@ -99,13 +139,9 @@ export function SqlChartCard({
99
139
  queryClient.getQueryData(chartQueryKey) !== undefined;
100
140
  const isChartRefreshing = chartHasCachedData && chartFetchCount > 0;
101
141
 
102
- const setCardNodeRef = useCallback(
103
- (node: HTMLDivElement | null) => {
104
- setNodeRef(node);
105
- cardRef.current = node;
106
- },
107
- [setNodeRef],
108
- );
142
+ const setCardNodeRef = useCallback((node: HTMLDivElement | null) => {
143
+ cardRef.current = node;
144
+ }, []);
109
145
 
110
146
  const handleExportCsvChange = useCallback((handler: (() => void) | null) => {
111
147
  setExportCsv(handler ? () => handler : null);
@@ -155,10 +191,6 @@ export function SqlChartCard({
155
191
  setExportCsv(null);
156
192
  }, [panel.id]);
157
193
 
158
- const style = {
159
- zIndex: isDragging ? 50 : undefined,
160
- };
161
-
162
194
  // Section panels render as a flush header row (no card chrome, full width)
163
195
  // so they read as dividers between groups of panels rather than as another
164
196
  // tile in the grid.
@@ -166,8 +198,8 @@ export function SqlChartCard({
166
198
  return (
167
199
  <div
168
200
  ref={setCardNodeRef}
169
- style={style}
170
- data-dragging={isDragging ? "true" : undefined}
201
+ style={isDragSource ? { zIndex: 50 } : undefined}
202
+ data-dragging={isDragSource ? "true" : undefined}
171
203
  className="dashboard-section-card group relative mt-2 first:mt-0"
172
204
  >
173
205
  <div className="flex items-center gap-2 border-b border-border pb-2">
@@ -209,21 +241,12 @@ export function SqlChartCard({
209
241
  </DropdownMenuItem>
210
242
  </DropdownMenuContent>
211
243
  </DropdownMenu>
212
- <Tooltip>
213
- <TooltipTrigger asChild>
214
- <button
215
- className="p-1 rounded cursor-grab active:cursor-grabbing text-muted-foreground/50 hover:text-muted-foreground"
216
- aria-label={t("sqlDashboard.dragToReorder")}
217
- {...attributes}
218
- {...listeners}
219
- >
220
- <IconGripVertical className="h-3.5 w-3.5" />
221
- </button>
222
- </TooltipTrigger>
223
- <TooltipContent>
224
- {t("sqlDashboard.dragToReorder")}
225
- </TooltipContent>
226
- </Tooltip>
244
+ <PanelDragHandle
245
+ panelId={panel.id}
246
+ label={t("sqlDashboard.dragToReorder")}
247
+ className="p-1 rounded cursor-grab active:cursor-grabbing text-muted-foreground/50 hover:text-muted-foreground"
248
+ iconClassName="h-3.5 w-3.5"
249
+ />
227
250
  </div>
228
251
  ) : null}
229
252
  </div>
@@ -270,8 +293,8 @@ export function SqlChartCard({
270
293
  return (
271
294
  <div
272
295
  ref={setCardNodeRef}
273
- style={style}
274
- data-dragging={isDragging ? "true" : undefined}
296
+ style={isDragSource ? { zIndex: 50 } : undefined}
297
+ data-dragging={isDragSource ? "true" : undefined}
275
298
  className="dashboard-chart-card group relative h-full hover:z-20 focus-within:z-20"
276
299
  >
277
300
  <Card className="flex h-full flex-col overflow-visible">
@@ -375,21 +398,12 @@ export function SqlChartCard({
375
398
  </DropdownMenu>
376
399
  ) : null}
377
400
  {editable ? (
378
- <Tooltip>
379
- <TooltipTrigger asChild>
380
- <button
381
- className="p-1 rounded cursor-grab active:cursor-grabbing text-muted-foreground/50 hover:text-muted-foreground"
382
- aria-label={t("sqlDashboard.dragToReorder")}
383
- {...attributes}
384
- {...listeners}
385
- >
386
- <IconGripVertical className="h-3.5 w-3.5" />
387
- </button>
388
- </TooltipTrigger>
389
- <TooltipContent>
390
- {t("sqlDashboard.dragToReorder")}
391
- </TooltipContent>
392
- </Tooltip>
401
+ <PanelDragHandle
402
+ panelId={panel.id}
403
+ label={t("sqlDashboard.dragToReorder")}
404
+ className="p-1 rounded cursor-grab active:cursor-grabbing text-muted-foreground/50 hover:text-muted-foreground"
405
+ iconClassName="h-3.5 w-3.5"
406
+ />
393
407
  ) : null}
394
408
  </div>
395
409
  </CardHeader>
@@ -279,6 +279,44 @@ export function isDropSlotAvailable(
279
279
  return row.panels.length < group.columns;
280
280
  }
281
281
 
282
+ export function availableDropSlotIdsForPanel(
283
+ groups: DashboardPanelGroup[],
284
+ panelId: string,
285
+ ): Set<string> {
286
+ const ids = new Set<string>();
287
+ if (!panelIsInRenderedRow(groups, panelId)) return ids;
288
+
289
+ for (const group of groups) {
290
+ for (let rowIndex = 0; rowIndex <= group.rows.length; rowIndex++) {
291
+ ids.add(dropSlotId({ type: "row", groupKey: group.key, rowIndex }));
292
+ }
293
+
294
+ for (let rowIndex = 0; rowIndex < group.rows.length; rowIndex++) {
295
+ const row = group.rows[rowIndex];
296
+ const rowContainsPanel = row.panels.some((panel) => panel.id === panelId);
297
+ if (!rowContainsPanel && row.panels.length >= group.columns) continue;
298
+ if (rowContainsPanel && row.panels.length <= 1) continue;
299
+
300
+ for (
301
+ let columnIndex = 0;
302
+ columnIndex <= row.panels.length;
303
+ columnIndex++
304
+ ) {
305
+ ids.add(
306
+ dropSlotId({
307
+ type: "column",
308
+ groupKey: group.key,
309
+ rowIndex,
310
+ columnIndex,
311
+ }),
312
+ );
313
+ }
314
+ }
315
+ }
316
+
317
+ return ids;
318
+ }
319
+
282
320
  export function movePanelToDropSlot(
283
321
  panels: SqlPanel[],
284
322
  panelId: string,
@@ -0,0 +1,89 @@
1
+ export type DashboardAdoptionHold = {
2
+ dashboardId: string;
3
+ minUpdatedAtMs: number | null;
4
+ expiresAt: number;
5
+ };
6
+
7
+ type PrefetchSnapshotLike<T> = {
8
+ data: T;
9
+ syncVersion: number;
10
+ };
11
+
12
+ export function dashboardPrefetchInitialData<T>(
13
+ snapshot: PrefetchSnapshotLike<T> | undefined,
14
+ syncVersion: number,
15
+ ): T | undefined {
16
+ if (!snapshot || snapshot.syncVersion !== syncVersion) return undefined;
17
+ return snapshot.data;
18
+ }
19
+
20
+ export function dashboardUpdatedAtMs(value: string | null | undefined) {
21
+ if (!value) return null;
22
+ const ms = Date.parse(value);
23
+ return Number.isFinite(ms) ? ms : null;
24
+ }
25
+
26
+ export function createDashboardAdoptionHold({
27
+ dashboardId,
28
+ currentUpdatedAt,
29
+ now = Date.now(),
30
+ ttlMs = 10_000,
31
+ }: {
32
+ dashboardId: string;
33
+ currentUpdatedAt: string | null;
34
+ now?: number;
35
+ ttlMs?: number;
36
+ }): DashboardAdoptionHold {
37
+ const currentUpdatedAtMs = dashboardUpdatedAtMs(currentUpdatedAt);
38
+ return {
39
+ dashboardId,
40
+ minUpdatedAtMs: currentUpdatedAtMs ?? now,
41
+ expiresAt: now + ttlMs,
42
+ };
43
+ }
44
+
45
+ export function shouldAdoptDashboardQueryResult({
46
+ dashboardId,
47
+ loaded,
48
+ isPlaceholderData,
49
+ fetchedId,
50
+ fetchedUpdatedAt,
51
+ currentUpdatedAt,
52
+ hold,
53
+ now = Date.now(),
54
+ }: {
55
+ dashboardId: string | undefined;
56
+ loaded: boolean;
57
+ isPlaceholderData: boolean;
58
+ fetchedId: string | null | undefined;
59
+ fetchedUpdatedAt: string | null | undefined;
60
+ currentUpdatedAt: string | null;
61
+ hold: DashboardAdoptionHold | null;
62
+ now?: number;
63
+ }): { adopt: boolean; clearHold: boolean } {
64
+ if (!dashboardId) return { adopt: false, clearHold: false };
65
+ if (loaded && isPlaceholderData) {
66
+ return { adopt: false, clearHold: false };
67
+ }
68
+ if (fetchedId && fetchedId !== dashboardId) {
69
+ return { adopt: false, clearHold: false };
70
+ }
71
+
72
+ const fetchedMs = dashboardUpdatedAtMs(fetchedUpdatedAt);
73
+ const currentMs = dashboardUpdatedAtMs(currentUpdatedAt);
74
+ const activeHold = hold?.dashboardId === dashboardId ? hold : null;
75
+ const clearHold = !!activeHold && now >= activeHold.expiresAt;
76
+
77
+ if (activeHold && !clearHold && activeHold.minUpdatedAtMs !== null) {
78
+ if (fetchedMs === null || fetchedMs <= activeHold.minUpdatedAtMs) {
79
+ return { adopt: false, clearHold: false };
80
+ }
81
+ return { adopt: true, clearHold: true };
82
+ }
83
+
84
+ if (currentMs !== null && fetchedMs !== null && fetchedMs < currentMs) {
85
+ return { adopt: false, clearHold };
86
+ }
87
+
88
+ return { adopt: true, clearHold };
89
+ }