@agent-native/core 0.80.0 → 0.80.1

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 (220) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +8 -0
  3. package/corpus/core/docs/content/locales/ar-SA/skills-guide.mdx +5 -0
  4. package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +14 -1
  5. package/corpus/core/docs/content/locales/de-DE/skills-guide.mdx +5 -0
  6. package/corpus/core/docs/content/locales/de-DE/template-design.mdx +15 -1
  7. package/corpus/core/docs/content/locales/es-ES/skills-guide.mdx +5 -0
  8. package/corpus/core/docs/content/locales/es-ES/template-design.mdx +15 -1
  9. package/corpus/core/docs/content/locales/fr-FR/skills-guide.mdx +5 -0
  10. package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +15 -1
  11. package/corpus/core/docs/content/locales/hi-IN/skills-guide.mdx +5 -0
  12. package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +15 -1
  13. package/corpus/core/docs/content/locales/ja-JP/skills-guide.mdx +5 -0
  14. package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +16 -1
  15. package/corpus/core/docs/content/locales/ko-KR/skills-guide.mdx +5 -0
  16. package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +14 -1
  17. package/corpus/core/docs/content/locales/pt-BR/skills-guide.mdx +5 -0
  18. package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +14 -1
  19. package/corpus/core/docs/content/locales/zh-CN/skills-guide.mdx +5 -0
  20. package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +14 -1
  21. package/corpus/core/docs/content/locales/zh-TW/skills-guide.mdx +5 -0
  22. package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +14 -1
  23. package/corpus/core/docs/content/skills-guide.mdx +5 -0
  24. package/corpus/core/docs/content/template-design.mdx +14 -1
  25. package/corpus/core/package.json +1 -1
  26. package/corpus/core/src/application-state/handlers.ts +1 -1
  27. package/corpus/core/src/brand-kit/index.ts +1 -1
  28. package/corpus/core/src/cli/skills.ts +18 -0
  29. package/corpus/core/src/client/AgentPanel.tsx +1 -1
  30. package/corpus/core/src/client/MultiTabAssistantChat.tsx +1 -1
  31. package/corpus/core/src/client/extensions/portable-extension.ts +1 -1
  32. package/corpus/core/src/code-agents/transcript-normalizer.ts +1 -1
  33. package/corpus/core/src/extensions/path.ts +1 -4
  34. package/corpus/core/src/integrations/a2a-continuation-processor.ts +1 -1
  35. package/corpus/core/src/mcp/build-server.ts +1 -1
  36. package/corpus/core/src/org/handlers.ts +5 -5
  37. package/corpus/core/src/org/plugin.ts +2 -2
  38. package/corpus/core/src/scripts/call-agent.ts +1 -1
  39. package/corpus/core/src/scripts/db/safety.ts +1 -1
  40. package/corpus/core/src/server/auth.ts +3 -0
  41. package/corpus/core/src/server/builder-design-systems.ts +308 -0
  42. package/corpus/core/src/server/core-routes-plugin.ts +29 -3
  43. package/corpus/core/src/server/index.ts +8 -0
  44. package/corpus/core/src/styles/agent-native.css +14 -7
  45. package/corpus/core/src/usage/store.ts +2 -2
  46. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +298 -15
  47. package/corpus/templates/analytics/actions/get-sql-dashboard.ts +9 -4
  48. package/corpus/templates/analytics/actions/mutate-dashboard.ts +18 -2
  49. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +12 -2
  50. package/corpus/templates/analytics/app/hooks/use-replay-storage-status.ts +66 -0
  51. package/corpus/templates/analytics/app/i18n/zh-TW.ts +37 -0
  52. package/corpus/templates/analytics/app/i18n-data.ts +404 -0
  53. package/corpus/templates/analytics/app/pages/Settings.tsx +333 -0
  54. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +2 -1
  55. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +121 -3
  56. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +49 -26
  57. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +137 -36
  58. package/corpus/templates/analytics/changelog/2026-06-29-analytics-agents-can-now-place-new-charts-into-requested-das.md +6 -0
  59. package/corpus/templates/analytics/changelog/2026-06-29-analytics-queries-now-report-failed-source-query-errors-inst.md +6 -0
  60. package/corpus/templates/analytics/changelog/2026-06-29-charts-can-now-be-dropped-between-full-dashboard-rows-to-cre.md +6 -0
  61. package/corpus/templates/analytics/changelog/2026-06-29-dashboard-email-reports-include-the-full-dashboard-image-wit.md +6 -0
  62. package/corpus/templates/analytics/changelog/2026-06-29-main-surface-border-reaches-the-top.md +6 -0
  63. package/corpus/templates/analytics/changelog/2026-06-29-session-replay-now-records-playable-sessions-in-production-a.md +6 -0
  64. package/corpus/templates/analytics/changelog/2026-06-29-set-up-session-replay-storage-from-settings-connect-builder-.md +6 -0
  65. package/corpus/templates/analytics/scripts/cleanup-empty-session-recordings.ts +102 -0
  66. package/corpus/templates/analytics/server/lib/dashboard-report.ts +49 -26
  67. package/corpus/templates/analytics/server/lib/real-data-actions.ts +81 -20
  68. package/corpus/templates/analytics/server/lib/replay-storage.ts +79 -0
  69. package/corpus/templates/analytics/server/lib/s3-upload-provider.ts +255 -0
  70. package/corpus/templates/analytics/server/lib/session-replay.ts +29 -8
  71. package/corpus/templates/analytics/server/plugins/agent-chat.ts +11 -2
  72. package/corpus/templates/analytics/server/plugins/onboarding.ts +97 -0
  73. package/corpus/templates/clips/desktop/src/lib/recorder.ts +24 -0
  74. package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +105 -5
  75. package/corpus/templates/content/actions/set-image-alt-text.ts +1 -1
  76. package/corpus/templates/content/shared/document-export.ts +1 -1
  77. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +8 -18
  78. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +17 -0
  79. package/corpus/templates/design/actions/add-localhost-screens.ts +32 -5
  80. package/corpus/templates/design/actions/index-design-system-with-builder.ts +259 -0
  81. package/corpus/templates/design/actions/update-file.ts +9 -2
  82. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +33 -75
  83. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +72 -9
  84. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +41 -39
  85. package/corpus/templates/design/app/components/design/EditPanel.tsx +1029 -249
  86. package/corpus/templates/design/app/components/design/LayersPanel.tsx +450 -101
  87. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +978 -154
  88. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +7 -9
  89. package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +133 -66
  90. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +1234 -344
  91. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +144 -94
  92. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +301 -72
  93. package/corpus/templates/design/app/components/design/inspector/FigmaColorPicker.tsx +876 -286
  94. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +601 -0
  95. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +242 -0
  96. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +30 -7
  97. package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +212 -155
  98. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +423 -0
  99. package/corpus/templates/design/app/components/design/inspector/figma-icons.tsx +487 -107
  100. package/corpus/templates/design/app/components/design/inspector/index.ts +27 -0
  101. package/corpus/templates/design/app/global.css +29 -3
  102. package/corpus/templates/design/app/i18n/zh-TW.ts +13 -0
  103. package/corpus/templates/design/app/i18n-data.ts +239 -1
  104. package/corpus/templates/design/app/pages/DesignEditor.tsx +1023 -353
  105. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +57 -221
  106. package/corpus/templates/design/changelog/2026-06-29-canvas-selection-no-longer-flashes-broken-prototype-html-whe.md +6 -0
  107. package/corpus/templates/design/changelog/2026-06-29-command-menu-selections-are-clearer-in-dark-mode.md +6 -0
  108. package/corpus/templates/design/changelog/2026-06-29-design-mode-top-chrome-now-groups-collaborators-preview-shar.md +6 -0
  109. package/corpus/templates/design/changelog/2026-06-29-element-selection-now-stays-on-the-intended-layer-after-sele.md +6 -0
  110. package/corpus/templates/design/changelog/2026-06-29-screen-overview-full-view-controls-now-sit-above-frames-inst.md +6 -0
  111. package/corpus/templates/design/changelog/2026-06-29-screen-overview-tools-can-draw-shapes-create-screens-and-kee.md +6 -0
  112. package/corpus/templates/design/changelog/2026-06-29-the-pen-tool-now-creates-anchor-based-bezier-vectors-with-ha.md +6 -0
  113. package/corpus/templates/design/server/handlers/import-figma-system.ts +38 -85
  114. package/corpus/templates/design/server/lib/design-snapshot.ts +9 -1
  115. package/corpus/templates/design/shared/code-layer.ts +209 -9
  116. package/corpus/templates/design/shared/html-content.ts +21 -0
  117. package/corpus/templates/design/shared/pen-path.ts +207 -0
  118. package/corpus/templates/mail/app/lib/utils.ts +2 -2
  119. package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +9 -10
  120. package/corpus/templates/slides/AGENTS.md +3 -2
  121. package/corpus/templates/slides/actions/import-file.ts +26 -17
  122. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +259 -0
  123. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +35 -167
  124. package/corpus/templates/slides/app/components/design-system/fig-import-response.ts +9 -13
  125. package/corpus/templates/slides/app/context/DeckContext.tsx +1 -1
  126. package/corpus/templates/slides/app/i18n/ar-SA.ts +9 -37
  127. package/corpus/templates/slides/app/i18n/de-DE.ts +10 -25
  128. package/corpus/templates/slides/app/i18n/en-US.ts +9 -26
  129. package/corpus/templates/slides/app/i18n/es-ES.ts +10 -28
  130. package/corpus/templates/slides/app/i18n/fr-FR.ts +10 -28
  131. package/corpus/templates/slides/app/i18n/hi-IN.ts +9 -26
  132. package/corpus/templates/slides/app/i18n/ja-JP.ts +10 -22
  133. package/corpus/templates/slides/app/i18n/ko-KR.ts +9 -23
  134. package/corpus/templates/slides/app/i18n/pt-BR.ts +10 -28
  135. package/corpus/templates/slides/app/i18n/zh-CN.ts +9 -22
  136. package/corpus/templates/slides/app/i18n/zh-TW.ts +9 -22
  137. package/corpus/templates/slides/server/handlers/import-figma-system.ts +44 -12
  138. package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
  139. package/corpus/templates/videos/app/components/NewCompositionPopover.tsx +1 -1
  140. package/dist/application-state/handlers.js +1 -1
  141. package/dist/application-state/handlers.js.map +1 -1
  142. package/dist/brand-kit/index.d.ts +1 -1
  143. package/dist/brand-kit/index.js +1 -1
  144. package/dist/brand-kit/index.js.map +1 -1
  145. package/dist/cli/skills.d.ts.map +1 -1
  146. package/dist/cli/skills.js +18 -0
  147. package/dist/cli/skills.js.map +1 -1
  148. package/dist/client/AgentPanel.js +1 -1
  149. package/dist/client/AgentPanel.js.map +1 -1
  150. package/dist/client/MultiTabAssistantChat.js +1 -1
  151. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  152. package/dist/client/extensions/portable-extension.d.ts +1 -1
  153. package/dist/client/extensions/portable-extension.d.ts.map +1 -1
  154. package/dist/client/extensions/portable-extension.js.map +1 -1
  155. package/dist/code-agents/transcript-normalizer.js +1 -1
  156. package/dist/code-agents/transcript-normalizer.js.map +1 -1
  157. package/dist/collab/awareness.d.ts +2 -2
  158. package/dist/collab/awareness.d.ts.map +1 -1
  159. package/dist/extensions/path.d.ts +1 -1
  160. package/dist/extensions/path.d.ts.map +1 -1
  161. package/dist/extensions/path.js.map +1 -1
  162. package/dist/integrations/a2a-continuation-processor.js +1 -1
  163. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  164. package/dist/mcp/build-server.d.ts +1 -1
  165. package/dist/mcp/build-server.d.ts.map +1 -1
  166. package/dist/mcp/build-server.js.map +1 -1
  167. package/dist/observability/routes.d.ts +3 -3
  168. package/dist/org/handlers.js +5 -5
  169. package/dist/org/handlers.js.map +1 -1
  170. package/dist/org/plugin.js +2 -2
  171. package/dist/org/plugin.js.map +1 -1
  172. package/dist/progress/routes.d.ts +1 -1
  173. package/dist/scripts/call-agent.js +1 -1
  174. package/dist/scripts/call-agent.js.map +1 -1
  175. package/dist/scripts/db/safety.js +1 -1
  176. package/dist/scripts/db/safety.js.map +1 -1
  177. package/dist/server/auth.d.ts.map +1 -1
  178. package/dist/server/auth.js +3 -0
  179. package/dist/server/auth.js.map +1 -1
  180. package/dist/server/builder-design-systems.d.ts +28 -0
  181. package/dist/server/builder-design-systems.d.ts.map +1 -0
  182. package/dist/server/builder-design-systems.js +217 -0
  183. package/dist/server/builder-design-systems.js.map +1 -0
  184. package/dist/server/core-routes-plugin.d.ts +1 -0
  185. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  186. package/dist/server/core-routes-plugin.js +20 -3
  187. package/dist/server/core-routes-plugin.js.map +1 -1
  188. package/dist/server/index.d.ts +1 -0
  189. package/dist/server/index.d.ts.map +1 -1
  190. package/dist/server/index.js +1 -0
  191. package/dist/server/index.js.map +1 -1
  192. package/dist/server/transcribe-voice.d.ts +1 -1
  193. package/dist/styles/agent-native.css +14 -7
  194. package/dist/usage/store.js +2 -2
  195. package/dist/usage/store.js.map +1 -1
  196. package/docs/content/locales/ar-SA/skills-guide.mdx +5 -0
  197. package/docs/content/locales/ar-SA/template-design.mdx +14 -1
  198. package/docs/content/locales/de-DE/skills-guide.mdx +5 -0
  199. package/docs/content/locales/de-DE/template-design.mdx +15 -1
  200. package/docs/content/locales/es-ES/skills-guide.mdx +5 -0
  201. package/docs/content/locales/es-ES/template-design.mdx +15 -1
  202. package/docs/content/locales/fr-FR/skills-guide.mdx +5 -0
  203. package/docs/content/locales/fr-FR/template-design.mdx +15 -1
  204. package/docs/content/locales/hi-IN/skills-guide.mdx +5 -0
  205. package/docs/content/locales/hi-IN/template-design.mdx +15 -1
  206. package/docs/content/locales/ja-JP/skills-guide.mdx +5 -0
  207. package/docs/content/locales/ja-JP/template-design.mdx +16 -1
  208. package/docs/content/locales/ko-KR/skills-guide.mdx +5 -0
  209. package/docs/content/locales/ko-KR/template-design.mdx +14 -1
  210. package/docs/content/locales/pt-BR/skills-guide.mdx +5 -0
  211. package/docs/content/locales/pt-BR/template-design.mdx +14 -1
  212. package/docs/content/locales/zh-CN/skills-guide.mdx +5 -0
  213. package/docs/content/locales/zh-CN/template-design.mdx +14 -1
  214. package/docs/content/locales/zh-TW/skills-guide.mdx +5 -0
  215. package/docs/content/locales/zh-TW/template-design.mdx +14 -1
  216. package/docs/content/skills-guide.mdx +5 -0
  217. package/docs/content/template-design.mdx +14 -1
  218. package/package.json +1 -1
  219. package/corpus/templates/design/actions/import-figma-file.ts +0 -166
  220. package/corpus/templates/slides/server/lib/fig-design-system.ts +0 -110
@@ -1,16 +1,25 @@
1
1
  import { CodeSurface } from "@agent-native/core/blocks";
2
- import { useActionQuery, useT } from "@agent-native/core/client";
2
+ import {
3
+ useActionQuery,
4
+ useBuilderConnectFlow,
5
+ useBuilderStatus,
6
+ useT,
7
+ } from "@agent-native/core/client";
3
8
  import {
4
9
  IconChevronDown,
10
+ IconCheck,
11
+ IconCloud,
5
12
  IconCode,
6
13
  IconExternalLink,
7
14
  IconFilter,
15
+ IconLoader2,
8
16
  IconPlayerPlay,
9
17
  IconRefresh,
10
18
  IconSearch,
19
+ IconServer,
11
20
  } from "@tabler/icons-react";
12
21
  import { useMemo, useState } from "react";
13
- import { useNavigate, useSearchParams } from "react-router";
22
+ import { Link, useNavigate, useSearchParams } from "react-router";
14
23
 
15
24
  import { Badge } from "@/components/ui/badge";
16
25
  import { Button } from "@/components/ui/button";
@@ -24,6 +33,7 @@ import {
24
33
  SelectValue,
25
34
  } from "@/components/ui/select";
26
35
  import { Skeleton } from "@/components/ui/skeleton";
36
+ import { useReplayStorageStatus } from "@/hooks/use-replay-storage-status";
27
37
  import { cn } from "@/lib/utils";
28
38
 
29
39
  type ReplayRange = "24h" | "7d" | "30d" | "90d" | "all";
@@ -294,46 +304,137 @@ export default function SessionsPage() {
294
304
 
295
305
  function EmptySessionsState() {
296
306
  const t = useT();
307
+ const storageStatus = useReplayStorageStatus();
308
+ const showStorageHint =
309
+ !storageStatus.isLoading && !storageStatus.data?.configured;
297
310
  return (
298
- <div className="grid min-h-[380px] gap-6 p-6 lg:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] lg:p-8">
299
- <div className="flex flex-col justify-center gap-3">
300
- <div className="flex h-10 w-10 items-center justify-center rounded-md border bg-muted/40">
301
- <IconPlayerPlay className="h-5 w-5 text-primary" />
311
+ <div className="p-6 lg:p-8">
312
+ {showStorageHint ? <ReplayStorageHint /> : null}
313
+ <div className="grid min-h-[380px] gap-6 lg:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)]">
314
+ <div className="flex flex-col justify-center gap-3">
315
+ <div className="flex h-10 w-10 items-center justify-center rounded-md border bg-muted/40">
316
+ <IconPlayerPlay className="h-5 w-5 text-primary" />
317
+ </div>
318
+ <div className="space-y-2">
319
+ <h2 className="text-lg font-semibold">
320
+ {t("sessions.noSessions")}
321
+ </h2>
322
+ <p className="max-w-xl text-sm text-muted-foreground">
323
+ {t("sessions.noSessionsDescription")}
324
+ </p>
325
+ </div>
302
326
  </div>
303
- <div className="space-y-2">
304
- <h2 className="text-lg font-semibold">{t("sessions.noSessions")}</h2>
305
- <p className="max-w-xl text-sm text-muted-foreground">
306
- {t("sessions.noSessionsDescription")}
307
- </p>
327
+ <div className="analytics-session-snippet overflow-hidden rounded-md border bg-muted/30">
328
+ <div className="flex items-center justify-between gap-3 border-b px-4 py-3">
329
+ <div className="flex min-w-0 items-center gap-2 text-sm font-medium">
330
+ <IconCode className="h-4 w-4 text-muted-foreground" />
331
+ <span className="truncate">
332
+ {t("sessions.installSnippetTitle")}
333
+ </span>
334
+ </div>
335
+ <Button
336
+ asChild
337
+ variant="ghost"
338
+ size="sm"
339
+ className="h-7 px-2 text-xs"
340
+ >
341
+ <a
342
+ href={SESSION_REPLAY_DOCS_URL}
343
+ target="_blank"
344
+ rel="noreferrer"
345
+ >
346
+ {t("common.docs")}
347
+ <IconExternalLink className="h-3.5 w-3.5" />
348
+ </a>
349
+ </Button>
350
+ </div>
351
+ <CodeSurface
352
+ code={SESSION_REPLAY_SNIPPET}
353
+ language="typescript"
354
+ maxLines={null}
355
+ showLanguageLabel={false}
356
+ className="mt-0"
357
+ />
308
358
  </div>
309
359
  </div>
310
- <div className="analytics-session-snippet overflow-hidden rounded-md border bg-muted/30">
311
- <div className="flex items-center justify-between gap-3 border-b px-4 py-3">
312
- <div className="flex min-w-0 items-center gap-2 text-sm font-medium">
313
- <IconCode className="h-4 w-4 text-muted-foreground" />
314
- <span className="truncate">
315
- {t("sessions.installSnippetTitle")}
316
- </span>
360
+ </div>
361
+ );
362
+ }
363
+
364
+ function ReplayStorageHint() {
365
+ const t = useT();
366
+ const storageStatus = useReplayStorageStatus();
367
+ const builderStatus = useBuilderStatus();
368
+ const builderConnect = useBuilderConnectFlow({
369
+ popupUrl:
370
+ builderStatus.status?.cliAuthUrl ?? builderStatus.status?.connectUrl,
371
+ trackingSource: "analytics_sessions_storage_hint",
372
+ trackingFlow: "replay_storage",
373
+ onConnected: async () => {
374
+ await Promise.all([storageStatus.refetch(), builderStatus.refetch()]);
375
+ },
376
+ });
377
+
378
+ const builderConnected = Boolean(
379
+ builderConnect.configured ||
380
+ builderStatus.status?.configured ||
381
+ storageStatus.data?.builderConfigured,
382
+ );
383
+ const builderStatusLoading =
384
+ storageStatus.isLoading ||
385
+ builderStatus.loading ||
386
+ !builderConnect.hasFetchedStatus;
387
+
388
+ return (
389
+ <div className="mb-6 flex flex-col gap-4 rounded-md border border-primary/30 bg-primary/5 p-4 sm:flex-row sm:items-center sm:justify-between">
390
+ <div className="flex min-w-0 items-start gap-3">
391
+ <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-background text-primary">
392
+ <IconCloud className="h-5 w-5" />
393
+ </div>
394
+ <div className="min-w-0">
395
+ <div className="text-sm font-medium">
396
+ {t("sessions.storageSetupTitle")}
317
397
  </div>
318
- <Button
319
- asChild
320
- variant="ghost"
321
- size="sm"
322
- className="h-7 px-2 text-xs"
323
- >
324
- <a href={SESSION_REPLAY_DOCS_URL} target="_blank" rel="noreferrer">
325
- {t("common.docs")}
326
- <IconExternalLink className="h-3.5 w-3.5" />
327
- </a>
328
- </Button>
398
+ <p className="mt-0.5 text-xs text-muted-foreground">
399
+ {t("sessions.storageSetupDescription")}
400
+ </p>
329
401
  </div>
330
- <CodeSurface
331
- code={SESSION_REPLAY_SNIPPET}
332
- language="typescript"
333
- maxLines={null}
334
- showLanguageLabel={false}
335
- className="mt-0"
336
- />
402
+ </div>
403
+ <div className="flex shrink-0 flex-wrap items-center gap-3">
404
+ <Button
405
+ type="button"
406
+ size="sm"
407
+ className="shrink-0"
408
+ onClick={() =>
409
+ builderConnect.start({
410
+ trackingSource: "analytics_sessions_storage_hint",
411
+ trackingFlow: "replay_storage",
412
+ })
413
+ }
414
+ disabled={
415
+ builderConnect.connecting ||
416
+ builderStatusLoading ||
417
+ builderConnected
418
+ }
419
+ >
420
+ {builderConnect.connecting ? (
421
+ <IconLoader2 className="h-4 w-4 animate-spin" />
422
+ ) : builderConnected ? (
423
+ <IconCheck className="h-4 w-4" />
424
+ ) : (
425
+ <IconExternalLink className="h-4 w-4" />
426
+ )}
427
+ {builderConnected
428
+ ? t("sessions.storageConnected")
429
+ : t("sessions.connectBuilder")}
430
+ </Button>
431
+ <Link
432
+ to="/settings#replay-storage"
433
+ className="inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground"
434
+ >
435
+ <IconServer className="h-3.5 w-3.5" />
436
+ {t("sessions.configureS3")}
437
+ </Link>
337
438
  </div>
338
439
  </div>
339
440
  );
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-29
4
+ ---
5
+
6
+ Analytics agents can now place new charts into requested dashboard rows.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-29
4
+ ---
5
+
6
+ Analytics queries now report failed source-query errors instead of falling back to a vague no-data message.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-29
4
+ ---
5
+
6
+ Charts can now be dropped between full dashboard rows to create wider rows.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-29
4
+ ---
5
+
6
+ Dashboard email reports include the full dashboard image without an extra image border.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-29
4
+ ---
5
+
6
+ The main dashboard surface border now reaches the top edge and keeps its rounded corners.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-29
4
+ ---
5
+
6
+ Session replay now records playable sessions in production and shows them to your whole workspace
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-06-29
4
+ ---
5
+
6
+ Set up session replay storage from settings: connect Builder.io or add S3-compatible storage
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env tsx
2
+
3
+ /**
4
+ * One-time maintenance for session replay rows created before the capture fix.
5
+ *
6
+ * Two jobs:
7
+ * 1. Delete empty "shell" recordings — rows that were created on ingest but
8
+ * never stored any replay data (chunk_count = 0, event_count = 0, and no
9
+ * session_replay_chunks rows). These exist because, before the ingest
10
+ * credential-scope fix, blob storage failed in production (no resolvable
11
+ * upload provider) and left unplayable husks behind. They are always
12
+ * filtered out of /sessions, so this is pure DB hygiene.
13
+ * 2. Backfill visibility "private" -> "org" for org-scoped recordings, so
14
+ * existing recordings follow the same org-visible model new ingests now use
15
+ * (teammates in the org can see them, not only the key owner).
16
+ *
17
+ * SAFE BY DEFAULT: dry-run unless you pass --apply. This connects to whatever
18
+ * DATABASE_URL the app is configured with, so double-check the target before
19
+ * using --apply against production.
20
+ *
21
+ * pnpm --filter analytics exec tsx scripts/cleanup-empty-session-recordings.ts # dry run
22
+ * pnpm --filter analytics exec tsx scripts/cleanup-empty-session-recordings.ts --apply # mutate
23
+ */
24
+
25
+ import path from "node:path";
26
+ import { pathToFileURL } from "node:url";
27
+
28
+ import { and, eq, isNotNull, sql } from "drizzle-orm";
29
+
30
+ import { getDb, schema } from "../server/db/index";
31
+ import migrations from "../server/plugins/db";
32
+
33
+ const apply = process.argv.includes("--apply");
34
+
35
+ const emptyShellCondition = and(
36
+ eq(schema.sessionRecordings.chunkCount, 0),
37
+ eq(schema.sessionRecordings.eventCount, 0),
38
+ sql`not exists (
39
+ select 1 from ${schema.sessionReplayChunks}
40
+ where ${schema.sessionReplayChunks.recordingId} = ${schema.sessionRecordings.id}
41
+ )`,
42
+ );
43
+
44
+ const orgPrivateCondition = and(
45
+ isNotNull(schema.sessionRecordings.orgId),
46
+ eq(schema.sessionRecordings.visibility, "private"),
47
+ );
48
+
49
+ async function countWhere(condition: unknown): Promise<number> {
50
+ const db = getDb() as any;
51
+ const rows = await db
52
+ .select({ n: sql<number>`count(*)::int` })
53
+ .from(schema.sessionRecordings)
54
+ .where(condition);
55
+ return Number(rows[0]?.n ?? 0);
56
+ }
57
+
58
+ async function main() {
59
+ await migrations({});
60
+ const db = getDb() as any;
61
+
62
+ const emptyShells = await countWhere(emptyShellCondition);
63
+ const orgPrivate = await countWhere(orgPrivateCondition);
64
+
65
+ console.log(`[cleanup] mode: ${apply ? "APPLY (mutating)" : "dry-run"}`);
66
+ console.log(`[cleanup] empty shell recordings to delete: ${emptyShells}`);
67
+ console.log(
68
+ `[cleanup] org-scoped recordings to make org-visible: ${orgPrivate}`,
69
+ );
70
+
71
+ if (!apply) {
72
+ console.log("[cleanup] dry-run only — re-run with --apply to mutate.");
73
+ return;
74
+ }
75
+
76
+ // Backfill visibility first so we don't widen rows we're about to delete.
77
+ await db
78
+ .update(schema.sessionRecordings)
79
+ .set({ visibility: "org" })
80
+ .where(orgPrivateCondition);
81
+ console.log(
82
+ `[cleanup] backfilled ${orgPrivate} recordings to visibility=org`,
83
+ );
84
+
85
+ await db.delete(schema.sessionRecordings).where(emptyShellCondition);
86
+ console.log(`[cleanup] deleted ${emptyShells} empty shell recordings`);
87
+ }
88
+
89
+ function isDirectRun(): boolean {
90
+ const entrypoint = process.argv[1];
91
+ return Boolean(
92
+ entrypoint &&
93
+ import.meta.url === pathToFileURL(path.resolve(entrypoint)).href,
94
+ );
95
+ }
96
+
97
+ if (isDirectRun()) {
98
+ main().catch((error) => {
99
+ console.error(error);
100
+ process.exit(1);
101
+ });
102
+ }
@@ -39,17 +39,17 @@ const DATE_FILTER_TYPES: ReadonlySet<FilterType> = new Set([
39
39
  const DEFAULT_SERVERLESS_CHROMIUM_PACK_URL =
40
40
  "https://github.com/Sparticuz/chromium/releases/download/v149.0.0/chromium-v149.0.0-pack.x64.tar";
41
41
  const DASHBOARD_REPORT_SCREENSHOT_PARAM = "reportScreenshot";
42
- const DASHBOARD_REPORT_PANEL_LIMIT_PARAM = "reportPanelLimit";
43
42
  const DASHBOARD_REPORT_SETTINGS_PARAM = "reportSettings";
44
43
  const DASHBOARD_REPORT_CID = "dashboard-report-snapshot";
45
- const COMPACT_REPORT_PANEL_LIMIT = 12;
46
44
  const LOCAL_SCREENSHOT_TIMEOUT_MS = 90_000;
47
45
  const SERVERLESS_SCREENSHOT_TIMEOUT_MS = 25_000;
48
46
  const SERVERLESS_SECOND_READY_TIMEOUT_MS = 10_000;
47
+ const MAX_SCREENSHOT_VIEWPORT_HEIGHT = 30_000;
48
+ const SCREENSHOT_VIEWPORT_PADDING = 64;
49
49
 
50
50
  type DashboardScreenshotAttempt = {
51
- label: "full" | "compact";
52
- panelLimit?: number;
51
+ label: "full" | "full-lightweight";
52
+ viewport: { width: number; height: number };
53
53
  };
54
54
 
55
55
  function daysAgo(n: number): string {
@@ -119,7 +119,6 @@ function buildDashboardPath(
119
119
  options?: {
120
120
  reportScreenshot?: boolean;
121
121
  reportSettings?: boolean;
122
- panelLimit?: number;
123
122
  },
124
123
  ): string {
125
124
  const url = new URL(
@@ -135,12 +134,6 @@ function buildDashboardPath(
135
134
  if (options?.reportSettings) {
136
135
  url.searchParams.set(DASHBOARD_REPORT_SETTINGS_PARAM, "1");
137
136
  }
138
- if (options?.panelLimit && options.panelLimit > 0) {
139
- url.searchParams.set(
140
- DASHBOARD_REPORT_PANEL_LIMIT_PARAM,
141
- String(Math.floor(options.panelLimit)),
142
- );
143
- }
144
137
  return `${url.pathname}${url.search}`;
145
138
  }
146
139
 
@@ -150,7 +143,6 @@ function buildDashboardUrl(
150
143
  options?: {
151
144
  reportScreenshot?: boolean;
152
145
  reportSettings?: boolean;
153
- panelLimit?: number;
154
146
  },
155
147
  ): string {
156
148
  const path = buildDashboardPath(dashboardId, filters, options);
@@ -319,6 +311,44 @@ async function scrollDashboardForLazyRendering(page: any): Promise<void> {
319
311
  })()`);
320
312
  }
321
313
 
314
+ async function fitViewportToDashboardCapture(
315
+ page: any,
316
+ capture: any,
317
+ minViewport: { width: number; height: number },
318
+ ): Promise<void> {
319
+ const box = await capture.boundingBox();
320
+ if (!box) return;
321
+
322
+ const size = {
323
+ width: Math.max(
324
+ minViewport.width,
325
+ Math.min(1800, Math.ceil(box.width + SCREENSHOT_VIEWPORT_PADDING)),
326
+ ),
327
+ height: Math.max(
328
+ minViewport.height,
329
+ Math.min(
330
+ MAX_SCREENSHOT_VIEWPORT_HEIGHT,
331
+ Math.ceil(box.height + SCREENSHOT_VIEWPORT_PADDING),
332
+ ),
333
+ ),
334
+ };
335
+ await page.setViewportSize(size);
336
+ await page.waitForTimeout(250);
337
+
338
+ const resizedBox = await capture.boundingBox();
339
+ if (!resizedBox) return;
340
+ const resizedHeight = Math.ceil(
341
+ resizedBox.height + SCREENSHOT_VIEWPORT_PADDING,
342
+ );
343
+ if (resizedHeight > size.height) {
344
+ await page.setViewportSize({
345
+ width: size.width,
346
+ height: Math.min(MAX_SCREENSHOT_VIEWPORT_HEIGHT, resizedHeight),
347
+ });
348
+ await page.waitForTimeout(250);
349
+ }
350
+ }
351
+
322
352
  async function captureDashboardPng(
323
353
  sub: DashboardReportSubscription,
324
354
  snapshot: ReportSnapshot,
@@ -329,7 +359,6 @@ async function captureDashboardPng(
329
359
  snapshot.filters,
330
360
  {
331
361
  reportScreenshot: true,
332
- panelLimit: attempt.panelLimit,
333
362
  },
334
363
  );
335
364
  const token = signEmbedSessionToken({
@@ -347,7 +376,7 @@ async function captureDashboardPng(
347
376
  try {
348
377
  const timeout = screenshotTimeoutMs();
349
378
  const page = await browser.newPage({
350
- viewport: { width: 1440, height: 1800 },
379
+ viewport: attempt.viewport,
351
380
  deviceScaleFactor: 1,
352
381
  });
353
382
  page.setDefaultTimeout(timeout);
@@ -371,13 +400,7 @@ async function captureDashboardPng(
371
400
  : timeout,
372
401
  );
373
402
 
374
- const box = await capture.boundingBox();
375
- if (box) {
376
- await page.setViewportSize({
377
- width: Math.max(1200, Math.min(1800, Math.ceil(box.width + 64))),
378
- height: Math.max(1000, Math.min(7000, Math.ceil(box.height + 64))),
379
- });
380
- }
403
+ await fitViewportToDashboardCapture(page, capture, attempt.viewport);
381
404
  await capture.scrollIntoViewIfNeeded();
382
405
  const image = await capture.screenshot({
383
406
  type: "png",
@@ -401,12 +424,12 @@ async function captureDashboardPngWithFallback(
401
424
  snapshot: ReportSnapshot,
402
425
  ): Promise<{
403
426
  png: Buffer | null;
404
- mode: "full" | "compact" | "none";
427
+ mode: "full" | "full-lightweight" | "none";
405
428
  error?: string;
406
429
  }> {
407
430
  const attempts: DashboardScreenshotAttempt[] = [
408
- { label: "full" },
409
- { label: "compact", panelLimit: COMPACT_REPORT_PANEL_LIMIT },
431
+ { label: "full", viewport: { width: 1440, height: 1800 } },
432
+ { label: "full-lightweight", viewport: { width: 1200, height: 1400 } },
410
433
  ];
411
434
  let lastError: string | undefined;
412
435
 
@@ -446,7 +469,7 @@ function renderReportEmailHtml(
446
469
  const date = escapeHtml(reportDate(snapshot));
447
470
  const screenshotBlock = options.screenshotAttached
448
471
  ? `<a href="${dashboardUrl}" style="display:block;text-decoration:none;">
449
- <img src="cid:${DASHBOARD_REPORT_CID}" alt="${title}" width="100%" style="display:block;width:100%;max-width:1280px;height:auto;border:1px solid #e5e7eb;border-radius:8px;" />
472
+ <img src="cid:${DASHBOARD_REPORT_CID}" alt="${title}" width="100%" style="display:block;width:100%;max-width:1280px;height:auto;border:0;outline:0;border-radius:0;" />
450
473
  </a>`
451
474
  : `<div style="margin:18px 0;padding:14px 16px;border:1px solid #e5e7eb;border-radius:8px;background:#f9fafb;color:#374151;font-size:14px;line-height:1.5;">
452
475
  The dashboard image was unavailable for this run. Open the live dashboard to view the latest report.
@@ -502,7 +525,7 @@ export async function sendDashboardReportSubscription(
502
525
  dashboardUrl: string;
503
526
  recipientCount: number;
504
527
  screenshotAttached: boolean;
505
- screenshotMode: "full" | "compact" | "none";
528
+ screenshotMode: "full" | "full-lightweight" | "none";
506
529
  screenshotError?: string;
507
530
  }> {
508
531
  const snapshot = await collectReportSnapshot(sub);