@agent-native/core 0.114.13 → 0.114.16

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 (163) hide show
  1. package/bin/agent-native.js +35 -14
  2. package/bin/launcher.js +34 -0
  3. package/corpus/README.md +2 -2
  4. package/corpus/core/CHANGELOG.md +21 -0
  5. package/corpus/core/bin/agent-native.js +35 -14
  6. package/corpus/core/bin/launcher.js +34 -0
  7. package/corpus/core/docs/content/deployment.mdx +1 -1
  8. package/corpus/core/docs/content/frames.mdx +1 -1
  9. package/corpus/core/docs/content/integrations.mdx +27 -27
  10. package/corpus/core/docs/content/pr-visual-recap.mdx +17 -13
  11. package/corpus/core/docs/content/workspace-management.mdx +1 -1
  12. package/corpus/core/package.json +2 -1
  13. package/corpus/core/src/agent/engine/builder-engine.ts +4 -0
  14. package/corpus/core/src/client/AgentPanel.tsx +10 -4
  15. package/corpus/core/src/client/ConnectBuilderCard.tsx +5 -1
  16. package/corpus/core/src/client/chat/markdown-renderer.tsx +3 -5
  17. package/corpus/core/src/client/error-format.ts +2 -1
  18. package/corpus/core/src/client/index.ts +1 -0
  19. package/corpus/core/src/client/integrations/IntegrationsPanel.tsx +2 -18
  20. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +43 -32
  21. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +4 -2
  22. package/corpus/core/src/client/settings/BackgroundAgentSection.tsx +5 -1
  23. package/corpus/core/src/client/settings/SettingsPanel.tsx +4 -4
  24. package/corpus/core/src/client/settings/useBuilderStatus.ts +3 -0
  25. package/corpus/core/src/localization/default-messages.ts +1 -7
  26. package/corpus/core/src/server/builder-browser.ts +4 -0
  27. package/corpus/core/src/server/builder-design-systems.ts +6 -1
  28. package/corpus/core/src/server/fusion-app.ts +5 -1
  29. package/corpus/core/src/shared/builder-link-tracking.ts +41 -0
  30. package/corpus/core/src/shared/index.ts +1 -0
  31. package/corpus/templates/analytics/AGENTS.md +10 -1
  32. package/corpus/templates/analytics/actions/save-dashboard-report-subscription.ts +10 -2
  33. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +5 -4
  34. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +20 -32
  35. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/report-panel-window.ts +62 -0
  36. package/corpus/templates/analytics/changelog/2026-07-21-chart-series-controls-stay-open-while-moving-from-the-legend.md +6 -0
  37. package/corpus/templates/analytics/changelog/2026-07-21-dashboard-email-captures-complete-large-dashboards-in-chunks.md +6 -0
  38. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +4 -4
  39. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +1 -8
  40. package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +39 -7
  41. package/corpus/templates/analytics/server/lib/dashboard-report.ts +341 -251
  42. package/corpus/templates/analytics/server/lib/first-party-dashboard-repair.ts +114 -0
  43. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +84 -6
  44. package/corpus/templates/analytics/server/plugins/db.ts +13 -0
  45. package/corpus/templates/clips/app/components/library/bulk-action-toolbar.tsx +25 -8
  46. package/corpus/templates/clips/app/components/library/library-grid.tsx +118 -9
  47. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  48. package/corpus/templates/clips/app/components/library/recording-card.tsx +10 -8
  49. package/corpus/templates/clips/app/components/player/video-player.tsx +10 -4
  50. package/corpus/templates/clips/app/i18n/en-US.ts +6 -0
  51. package/corpus/templates/clips/app/routes/_app.trash.tsx +16 -1
  52. package/corpus/templates/clips/changelog/2026-07-21-choose-which-whisper-model-to-use-in-settings.md +6 -0
  53. package/corpus/templates/clips/changelog/2026-07-21-fixed-organization-recording-visibility-default.md +6 -0
  54. package/corpus/templates/clips/changelog/2026-07-21-meeting-notes-stop-when-calls-end.md +6 -0
  55. package/corpus/templates/clips/desktop/src/app.tsx +90 -78
  56. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +2 -0
  57. package/corpus/templates/clips/desktop/src/hooks/useWhisperSettings.ts +172 -0
  58. package/corpus/templates/clips/desktop/src/lib/meeting-call-app.ts +41 -0
  59. package/corpus/templates/clips/desktop/src/lib/silence-events.ts +1 -0
  60. package/corpus/templates/clips/desktop/src/shared/config.ts +1 -0
  61. package/corpus/templates/clips/desktop/src/styles.css +80 -7
  62. package/corpus/templates/clips/desktop/src-tauri/src/config.rs +32 -0
  63. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +3 -0
  64. package/corpus/templates/clips/desktop/src-tauri/src/silence_detector.rs +268 -20
  65. package/corpus/templates/clips/desktop/src-tauri/src/whisper_model.rs +285 -164
  66. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +18 -7
  67. package/corpus/templates/clips/learnings.defaults.md +1 -1
  68. package/corpus/templates/clips/server/plugins/db.ts +11 -0
  69. package/corpus/templates/clips/shared/builder-credits.ts +1 -1
  70. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +20 -38
  71. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +40 -1
  72. package/corpus/templates/design/app/components/design/FusionAppBanner.tsx +9 -1
  73. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +5 -1
  74. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +9 -1
  75. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +9 -1
  76. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  77. package/dist/agent/engine/builder-engine.js +4 -0
  78. package/dist/agent/engine/builder-engine.js.map +1 -1
  79. package/dist/client/AgentPanel.d.ts.map +1 -1
  80. package/dist/client/AgentPanel.js +10 -3
  81. package/dist/client/AgentPanel.js.map +1 -1
  82. package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
  83. package/dist/client/ConnectBuilderCard.js +5 -1
  84. package/dist/client/ConnectBuilderCard.js.map +1 -1
  85. package/dist/client/chat/markdown-renderer.d.ts.map +1 -1
  86. package/dist/client/chat/markdown-renderer.js +3 -2
  87. package/dist/client/chat/markdown-renderer.js.map +1 -1
  88. package/dist/client/error-format.d.ts +1 -1
  89. package/dist/client/error-format.d.ts.map +1 -1
  90. package/dist/client/error-format.js +1 -1
  91. package/dist/client/error-format.js.map +1 -1
  92. package/dist/client/index.d.ts +1 -0
  93. package/dist/client/index.d.ts.map +1 -1
  94. package/dist/client/index.js +1 -0
  95. package/dist/client/index.js.map +1 -1
  96. package/dist/client/integrations/IntegrationsPanel.d.ts.map +1 -1
  97. package/dist/client/integrations/IntegrationsPanel.js +3 -4
  98. package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
  99. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  100. package/dist/client/resources/McpIntegrationDialog.js +21 -7
  101. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  102. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  103. package/dist/client/resources/mcp-integration-catalog.js +2 -2
  104. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  105. package/dist/client/settings/BackgroundAgentSection.d.ts.map +1 -1
  106. package/dist/client/settings/BackgroundAgentSection.js +5 -1
  107. package/dist/client/settings/BackgroundAgentSection.js.map +1 -1
  108. package/dist/client/settings/SettingsPanel.js +1 -1
  109. package/dist/client/settings/SettingsPanel.js.map +1 -1
  110. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  111. package/dist/client/settings/useBuilderStatus.js +2 -0
  112. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  113. package/dist/collab/struct-routes.d.ts +1 -1
  114. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  115. package/dist/localization/default-messages.d.ts +0 -6
  116. package/dist/localization/default-messages.d.ts.map +1 -1
  117. package/dist/localization/default-messages.js +1 -7
  118. package/dist/localization/default-messages.js.map +1 -1
  119. package/dist/observability/routes.d.ts +5 -5
  120. package/dist/resources/handlers.d.ts +1 -1
  121. package/dist/secrets/routes.d.ts +9 -9
  122. package/dist/server/builder-browser.d.ts.map +1 -1
  123. package/dist/server/builder-browser.js +4 -0
  124. package/dist/server/builder-browser.js.map +1 -1
  125. package/dist/server/builder-design-systems.d.ts.map +1 -1
  126. package/dist/server/builder-design-systems.js +6 -1
  127. package/dist/server/builder-design-systems.js.map +1 -1
  128. package/dist/server/fusion-app.d.ts.map +1 -1
  129. package/dist/server/fusion-app.js +2 -1
  130. package/dist/server/fusion-app.js.map +1 -1
  131. package/dist/server/transcribe-voice.d.ts +1 -1
  132. package/dist/shared/builder-link-tracking.d.ts +14 -0
  133. package/dist/shared/builder-link-tracking.d.ts.map +1 -0
  134. package/dist/shared/builder-link-tracking.js +26 -0
  135. package/dist/shared/builder-link-tracking.js.map +1 -0
  136. package/dist/shared/index.d.ts +1 -0
  137. package/dist/shared/index.d.ts.map +1 -1
  138. package/dist/shared/index.js +1 -0
  139. package/dist/shared/index.js.map +1 -1
  140. package/docs/content/deployment.mdx +1 -1
  141. package/docs/content/frames.mdx +1 -1
  142. package/docs/content/integrations.mdx +27 -27
  143. package/docs/content/pr-visual-recap.mdx +17 -13
  144. package/docs/content/workspace-management.mdx +1 -1
  145. package/package.json +3 -2
  146. package/src/agent/engine/builder-engine.ts +4 -0
  147. package/src/client/AgentPanel.tsx +10 -4
  148. package/src/client/ConnectBuilderCard.tsx +5 -1
  149. package/src/client/chat/markdown-renderer.tsx +3 -5
  150. package/src/client/error-format.ts +2 -1
  151. package/src/client/index.ts +1 -0
  152. package/src/client/integrations/IntegrationsPanel.tsx +2 -18
  153. package/src/client/resources/McpIntegrationDialog.tsx +43 -32
  154. package/src/client/resources/mcp-integration-catalog.ts +4 -2
  155. package/src/client/settings/BackgroundAgentSection.tsx +5 -1
  156. package/src/client/settings/SettingsPanel.tsx +4 -4
  157. package/src/client/settings/useBuilderStatus.ts +3 -0
  158. package/src/localization/default-messages.ts +1 -7
  159. package/src/server/builder-browser.ts +4 -0
  160. package/src/server/builder-design-systems.ts +6 -1
  161. package/src/server/fusion-app.ts +5 -1
  162. package/src/shared/builder-link-tracking.ts +41 -0
  163. package/src/shared/index.ts +1 -0
@@ -14,7 +14,6 @@ import {
14
14
  } from "@tabler/icons-react";
15
15
  import React, { useState, useCallback, useEffect } from "react";
16
16
 
17
- import { AgentAskPopover } from "../AgentAskPopover.js";
18
17
  import { agentNativePath } from "../api-path.js";
19
18
  import {
20
19
  Tooltip,
@@ -135,7 +134,8 @@ const PLATFORMS: PlatformInfo[] = [
135
134
  "Builder.io discovers each agent's skills via A2A",
136
135
  "Chat with one agent that can trigger actions across all your apps",
137
136
  ],
138
- docsUrl: "https://www.builder.io",
137
+ docsUrl:
138
+ "https://www.builder.io?utm_source=agent-native&utm_medium=product&utm_campaign=integrations&utm_content=integrations_panel",
139
139
  },
140
140
  ];
141
141
 
@@ -512,14 +512,6 @@ export function IntegrationsPanel() {
512
512
  <div className="text-[10px] font-medium text-muted-foreground mb-1.5">
513
513
  {t("integrations.addChatIntegration")}
514
514
  </div>
515
- <AgentAskPopover
516
- label={t("integrations.addSomething")}
517
- title={t("integrations.addSomethingTitle")}
518
- placeholder={t("integrations.addSomethingPlaceholder")}
519
- prompt=""
520
- context="The user wants to add a chat or workspace integration that is not in the current directory. Research the provider's official OAuth or MCP setup, explain any app registration or allowlist requirements, and add a reusable integration preset when it is supported. Never ask the user to paste credentials into a prompt."
521
- className="mb-2 h-7 w-full justify-center border-dashed px-2 text-[10px]"
522
- />
523
515
  <AddIntegrationPicker
524
516
  connectedIds={connectedIds}
525
517
  onSelect={(p) => {
@@ -554,14 +546,6 @@ export function IntegrationsPanel() {
554
546
  </TooltipTrigger>
555
547
  <TooltipContent>{t("integrations.addIntegration")}</TooltipContent>
556
548
  </Tooltip>
557
- <AgentAskPopover
558
- label={t("integrations.addSomething")}
559
- title={t("integrations.addSomethingTitle")}
560
- placeholder={t("integrations.addSomethingPlaceholder")}
561
- prompt=""
562
- context="The user wants to add a chat or workspace integration that is not in the current directory. Research the provider's official OAuth or MCP setup, explain any app registration or allowlist requirements, and add a reusable integration preset when it is supported. Never ask the user to paste credentials into a prompt."
563
- className="h-7 px-2 text-[10px]"
564
- />
565
549
  </div>
566
550
  </div>
567
551
 
@@ -7,7 +7,6 @@ import {
7
7
  } from "@tabler/icons-react";
8
8
  import { useEffect, useMemo, useRef, useState } from "react";
9
9
 
10
- import { AgentAskPopover } from "../AgentAskPopover.js";
11
10
  import { agentNativePath } from "../api-path.js";
12
11
  import { openAgentSettings } from "../CommandMenu.js";
13
12
  import {
@@ -62,6 +61,40 @@ interface TestResult {
62
61
  message: string;
63
62
  }
64
63
 
64
+ function IntegrationLogo({ name, logoUrl }: { name: string; logoUrl: string }) {
65
+ const [loaded, setLoaded] = useState(false);
66
+ const [loadFailed, setLoadFailed] = useState(false);
67
+
68
+ useEffect(() => {
69
+ setLoaded(false);
70
+ setLoadFailed(false);
71
+ }, [logoUrl]);
72
+
73
+ return (
74
+ <div className="relative flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-md border border-border bg-background text-[11px] font-semibold text-muted-foreground">
75
+ <span aria-hidden="true" hidden={loaded}>
76
+ {name.slice(0, 1)}
77
+ </span>
78
+ <img
79
+ src={logoUrl}
80
+ alt=""
81
+ loading="lazy"
82
+ decoding="async"
83
+ className="absolute inset-1 h-6 w-6 object-contain"
84
+ hidden={loadFailed}
85
+ onLoad={() => {
86
+ setLoadFailed(false);
87
+ setLoaded(true);
88
+ }}
89
+ onError={() => {
90
+ setLoadFailed(true);
91
+ setLoaded(false);
92
+ }}
93
+ />
94
+ </div>
95
+ );
96
+ }
97
+
65
98
  function parseHeaderLines(text: string): Record<string, string> | undefined {
66
99
  const out: Record<string, string> = {};
67
100
  for (const line of text.split(/\r?\n/)) {
@@ -412,7 +445,9 @@ export function McpIntegrationDialog({
412
445
  <DialogHeader className="shrink-0 px-7 pb-5 pe-14 pt-6">
413
446
  <DialogTitle>{t("mcpIntegrations.title")}</DialogTitle>
414
447
  <DialogDescription>
415
- {t("mcpIntegrations.description")}
448
+ {t("mcpIntegrations.description", {
449
+ count: defaultIntegrations.length,
450
+ })}
416
451
  </DialogDescription>
417
452
  </DialogHeader>
418
453
  <div className="flex shrink-0 flex-col gap-3 px-7 pb-5 sm:flex-row">
@@ -435,14 +470,6 @@ export function McpIntegrationDialog({
435
470
  >
436
471
  {t("mcpIntegrations.addYourOwn")}
437
472
  </button>
438
- <AgentAskPopover
439
- label={t("mcpIntegrations.addSomething")}
440
- title={t("mcpIntegrations.addSomethingTitle")}
441
- placeholder={t("mcpIntegrations.addSomethingPlaceholder")}
442
- prompt=""
443
- context="The user wants to add an MCP or provider integration that is not in the current directory. Research the provider's official remote MCP endpoint and OAuth, client-registration, or allowlist requirements. Prefer Streamable HTTP endpoints over legacy SSE, reuse an existing provider OAuth connector when appropriate, and never ask the user to paste credentials into a prompt. If this should become a reusable preset, update the integration catalog, official docs link, bundled logo, localization, and tests."
444
- className="h-9 whitespace-nowrap border-dashed px-3 text-[12px] font-medium"
445
- />
446
473
  </div>
447
474
  <div className="min-h-0 flex-1 overflow-y-auto px-7 pb-7">
448
475
  {error && (
@@ -467,27 +494,16 @@ export function McpIntegrationDialog({
467
494
  className="flex min-h-[128px] flex-col rounded-md border border-border bg-card p-4 transition-colors hover:border-border/80 hover:bg-accent/20"
468
495
  >
469
496
  <div className="flex items-center gap-3">
470
- <div className="relative flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-md border border-border bg-background text-[11px] font-semibold text-muted-foreground">
471
- <span aria-hidden="true">
472
- {integration.name.slice(0, 1)}
473
- </span>
474
- <img
475
- src={integration.logoUrl}
476
- alt=""
477
- loading="lazy"
478
- decoding="async"
479
- className="absolute inset-1 h-6 w-6 object-contain"
480
- onError={(event) => {
481
- event.currentTarget.hidden = true;
482
- }}
483
- />
484
- </div>
497
+ <IntegrationLogo
498
+ name={integration.name}
499
+ logoUrl={integration.logoUrl}
500
+ />
485
501
  <div className="min-w-0">
486
502
  <h3 className="truncate text-[13px] font-semibold text-foreground">
487
503
  {integration.name}
488
504
  </h3>
489
505
  {integration.availability !== "ready" && (
490
- <span className="mt-0.5 inline-flex rounded-full border border-amber-500/20 bg-amber-500/5 px-1.5 py-0.5 text-[9px] font-medium text-amber-700 dark:text-amber-300">
506
+ <span className="mt-0.5 inline-flex rounded-full border border-border/70 bg-muted/40 px-1.5 py-0.5 text-[9px] font-medium text-muted-foreground">
491
507
  {integration.availability === "beta"
492
508
  ? t("mcpIntegrations.status.beta")
493
509
  : integration.availability ===
@@ -508,11 +524,6 @@ export function McpIntegrationDialog({
508
524
  <p className="mt-1 line-clamp-2 flex-1 text-[12px] leading-relaxed text-muted-foreground">
509
525
  {t(integration.descriptionKey)}
510
526
  </p>
511
- {integration.setupNoteKey && (
512
- <p className="mt-2 line-clamp-3 text-[10px] leading-relaxed text-amber-700 dark:text-amber-300">
513
- {t(integration.setupNoteKey)}
514
- </p>
515
- )}
516
527
  <div className="mt-3 flex items-center gap-2">
517
528
  {connected ? (
518
529
  <button
@@ -637,7 +648,7 @@ export function McpIntegrationDialog({
637
648
  <div className="space-y-3">
638
649
  {renderScopeSelector()}
639
650
  {selected?.setupNoteKey && (
640
- <div className="rounded-md border border-amber-500/20 bg-amber-500/5 px-3 py-2 text-[11px] leading-relaxed text-amber-700 dark:text-amber-300">
651
+ <div className="rounded-md border border-border bg-muted/40 px-3 py-2 text-[11px] leading-relaxed text-muted-foreground">
641
652
  {t(selected.setupNoteKey)}
642
653
  </div>
643
654
  )}
@@ -189,7 +189,8 @@ export const DEFAULT_MCP_INTEGRATIONS: DefaultMcpIntegration[] = [
189
189
  availability: "ready",
190
190
  verification: "preflight-only",
191
191
  logoUrl: mcpIntegrationLogo("supabase"),
192
- docsUrl: "https://www.builder.io/c/docs/fusion-connect-to-supabase",
192
+ docsUrl:
193
+ "https://www.builder.io/c/docs/fusion-connect-to-supabase?utm_source=agent-native&utm_medium=product&utm_campaign=integrations&utm_content=fusion_connect_supabase",
193
194
  keywords: ["database", "auth", "postgres", "storage"],
194
195
  },
195
196
  {
@@ -206,7 +207,8 @@ export const DEFAULT_MCP_INTEGRATIONS: DefaultMcpIntegration[] = [
206
207
  availability: "ready",
207
208
  verification: "preflight-only",
208
209
  logoUrl: mcpIntegrationLogo("neon"),
209
- docsUrl: "https://www.builder.io/c/docs/fusion-connect-to-neon",
210
+ docsUrl:
211
+ "https://www.builder.io/c/docs/fusion-connect-to-neon?utm_source=agent-native&utm_medium=product&utm_campaign=integrations&utm_content=fusion_connect_neon",
210
212
  keywords: ["database", "postgres", "serverless", "backend"],
211
213
  },
212
214
  {
@@ -6,6 +6,7 @@ import {
6
6
  } from "@tabler/icons-react";
7
7
  import { useState } from "react";
8
8
 
9
+ import { withBuilderUtmTrackingParams } from "../../shared/builder-link-tracking.js";
9
10
  import { trackEvent } from "../analytics.js";
10
11
  import { agentNativePath } from "../api-path.js";
11
12
  import { SettingsSection } from "./SettingsSection.js";
@@ -170,7 +171,10 @@ export function BackgroundAgentSection() {
170
171
  Branch created
171
172
  </div>
172
173
  <a
173
- href={result.url}
174
+ href={withBuilderUtmTrackingParams(result.url, {
175
+ campaign: "product",
176
+ content: "background_agent_branch",
177
+ })}
174
178
  target="_blank"
175
179
  rel="noopener noreferrer"
176
180
  className="flex items-center gap-1 text-[10px] text-foreground hover:underline"
@@ -2857,7 +2857,7 @@ function SettingsPanelContent({
2857
2857
  />
2858
2858
  <ManualSetupCard
2859
2859
  hint="Deploy manually to Netlify, Vercel, Cloudflare, or any Nitro-supported target."
2860
- docsUrl="https://www.builder.io/c/docs/agent-native-deployment"
2860
+ docsUrl="https://www.builder.io/c/docs/agent-native-deployment?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=deployment_settings"
2861
2861
  dim={connected}
2862
2862
  />
2863
2863
  </div>
@@ -2888,7 +2888,7 @@ function SettingsPanelContent({
2888
2888
  />
2889
2889
  <ManualSetupCard
2890
2890
  hint="Set DATABASE_URL in your .env to connect Neon, Supabase, Turso, any Postgres/SQLite database, or local PGlite with pglite:./data/pglite."
2891
- docsUrl="https://www.builder.io/c/docs/agent-native-database"
2891
+ docsUrl="https://www.builder.io/c/docs/agent-native-database?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=database_settings"
2892
2892
  dim={connected}
2893
2893
  />
2894
2894
  </div>
@@ -2919,7 +2919,7 @@ function SettingsPanelContent({
2919
2919
  />
2920
2920
  <ManualSetupCard
2921
2921
  hint="Without a provider, files are stored as base64 in your database. Fine for dev, not recommended for production."
2922
- docsUrl="https://www.builder.io/c/docs/agent-native-file-uploads"
2922
+ docsUrl="https://www.builder.io/c/docs/agent-native-file-uploads?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=file_upload_settings"
2923
2923
  dim={connected}
2924
2924
  />
2925
2925
  </div>
@@ -2950,7 +2950,7 @@ function SettingsPanelContent({
2950
2950
  />
2951
2951
  <ManualSetupCard
2952
2952
  hint="Configure Better Auth with BETTER_AUTH_SECRET and optional Google/GitHub OAuth providers."
2953
- docsUrl="https://www.builder.io/c/docs/agent-native-authentication"
2953
+ docsUrl="https://www.builder.io/c/docs/agent-native-authentication?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=authentication_settings"
2954
2954
  dim={connected}
2955
2955
  />
2956
2956
  </div>
@@ -1,5 +1,6 @@
1
1
  import { useState, useEffect, useCallback, useRef } from "react";
2
2
 
3
+ import { applyBuilderUtmTrackingParams } from "../../shared/builder-link-tracking.js";
3
4
  import { trackEvent } from "../analytics.js";
4
5
  import { agentNativePath } from "../api-path.js";
5
6
  import { getCallbackOrigin } from "../frame.js";
@@ -343,6 +344,8 @@ export function withBuilderConnectTrackingParams(
343
344
  parsed.searchParams.set("redirect_url", parsedRedirect.toString());
344
345
  }
345
346
 
347
+ applyBuilderUtmTrackingParams(parsed.searchParams, { content: source });
348
+
346
349
  return parsed.toString();
347
350
  } catch {
348
351
  return url;
@@ -725,9 +725,6 @@ const messages = {
725
725
  chatIntegrations: "Chat Integrations",
726
726
  chatIntegrationsDescription: "Talk to this agent from other platforms",
727
727
  addIntegration: "Add integration",
728
- addSomething: "Add something not listed",
729
- addSomethingTitle: "Add another integration",
730
- addSomethingPlaceholder: "Which integration should we add?",
731
728
  dispatchEntrypoint:
732
729
  "For a central Slack or Telegram entrypoint that can route work across multiple apps, use the",
733
730
  sharedMessaging:
@@ -737,12 +734,9 @@ const messages = {
737
734
  menuLabel: "Integrations",
738
735
  menuDescription: "Connect MCP tools to the agent",
739
736
  title: "Connect integrations",
740
- description: "Choose a default MCP integration or add a custom server.",
737
+ description: "Browse {{count}} MCP integrations or add a custom server.",
741
738
  searchPlaceholder: "Search integrations",
742
739
  addYourOwn: "Add your own",
743
- addSomething: "Add something not listed",
744
- addSomethingTitle: "Add another integration",
745
- addSomethingPlaceholder: "Which MCP or provider should we add?",
746
740
  noMatches: "No integrations match that search.",
747
741
  connected: "Connected",
748
742
  configure: "Configure",
@@ -8,6 +8,7 @@ import {
8
8
  import type { H3Event } from "h3";
9
9
  import { getHeader } from "h3";
10
10
 
11
+ import { applyBuilderUtmTrackingParams } from "../shared/builder-link-tracking.js";
11
12
  import {
12
13
  getAuthSecret,
13
14
  resolveSignupTrackingIdentity,
@@ -920,6 +921,9 @@ export function buildBuilderCliAuthUrl(
920
921
  );
921
922
  url.searchParams.set("framework", "agent-native");
922
923
  applyBuilderConnectTrackingParams(url.searchParams, tracking);
924
+ applyBuilderUtmTrackingParams(url.searchParams, {
925
+ content: cleanTrackingParam(tracking.agentNativeConnectSource) ?? null,
926
+ });
923
927
  return url.toString();
924
928
  }
925
929
 
@@ -1,3 +1,4 @@
1
+ import { withBuilderUtmTrackingParams } from "../shared/builder-link-tracking.js";
1
2
  import { FeatureNotConfiguredError } from "./credential-provider.js";
2
3
  import {
3
4
  getBuilderProxyOrigin,
@@ -356,11 +357,15 @@ function nonEmptyFiles(
356
357
 
357
358
  export function builderDesignSystemUrl(designSystemId?: string | null): string {
358
359
  const host = trimTrailingSlash(getBuilderAppHost());
359
- return designSystemId
360
+ const url = designSystemId
360
361
  ? `${host}/app/design-system-intelligence/${encodeURIComponent(
361
362
  designSystemId,
362
363
  )}`
363
364
  : `${host}/app/design-system-intelligence`;
365
+ return withBuilderUtmTrackingParams(url, {
366
+ campaign: "product",
367
+ content: "design_system_intelligence",
368
+ });
364
369
  }
365
370
 
366
371
  export function localBuilderDesignSystemId(
@@ -24,6 +24,7 @@
24
24
  * respond with newline-delimited JSON over chunked HTTP.
25
25
  */
26
26
 
27
+ import { withBuilderUtmTrackingParams } from "../shared/builder-link-tracking.js";
27
28
  import { getBuilderApiHost, getBuilderAppHost } from "./builder-browser.js";
28
29
 
29
30
  export interface FusionBranchRef {
@@ -86,7 +87,10 @@ function fusionUrl(
86
87
  /** The Builder visual-editor URL for a fusion branch. */
87
88
  export function getFusionBranchEditorUrl(ref: FusionBranchRef): string {
88
89
  const host = getBuilderAppHost().replace(/\/+$/, "");
89
- return `${host}/app/projects/${encodeURIComponent(ref.projectId)}/${encodeURIComponent(ref.branchName)}`;
90
+ return withBuilderUtmTrackingParams(
91
+ `${host}/app/projects/${encodeURIComponent(ref.projectId)}/${encodeURIComponent(ref.branchName)}`,
92
+ { campaign: "product", content: "fusion_editor" },
93
+ );
90
94
  }
91
95
 
92
96
  /** Public URL for a reserved fusion hosting slug. */
@@ -0,0 +1,41 @@
1
+ export const BUILDER_UTM_SOURCE = "agent-native";
2
+ export const BUILDER_UTM_MEDIUM = "product";
3
+ export const BUILDER_UTM_CAMPAIGN = "onboarding";
4
+
5
+ export function applyBuilderUtmTrackingParams(
6
+ params: URLSearchParams,
7
+ options: {
8
+ medium?: string;
9
+ campaign?: string;
10
+ content?: string | null;
11
+ } = {},
12
+ ): void {
13
+ params.set("utm_source", BUILDER_UTM_SOURCE);
14
+ params.set("utm_medium", options.medium ?? BUILDER_UTM_MEDIUM);
15
+ params.set("utm_campaign", options.campaign ?? BUILDER_UTM_CAMPAIGN);
16
+ if (options.content) params.set("utm_content", options.content);
17
+ }
18
+
19
+ export function withBuilderUtmTrackingParams(
20
+ url: string,
21
+ options: {
22
+ medium?: string;
23
+ campaign?: string;
24
+ content?: string | null;
25
+ } = {},
26
+ ): string {
27
+ let parsed: URL;
28
+ try {
29
+ parsed = new URL(url);
30
+ } catch {
31
+ return url;
32
+ }
33
+ if (
34
+ parsed.hostname !== "builder.io" &&
35
+ parsed.hostname !== "www.builder.io"
36
+ ) {
37
+ return url;
38
+ }
39
+ applyBuilderUtmTrackingParams(parsed.searchParams, options);
40
+ return parsed.toString();
41
+ }
@@ -7,6 +7,7 @@ export {
7
7
  export { agentEnv, type EnvVar } from "./agent-env.js";
8
8
  export { extractOAuthStateAppId } from "./oauth-state.js";
9
9
  export { truncate } from "./truncate.js";
10
+ export { withBuilderUtmTrackingParams } from "./builder-link-tracking.js";
10
11
  export {
11
12
  llmConnectionTrackingProperties,
12
13
  normalizeLlmConnection,
@@ -230,7 +230,16 @@ membership id when its native update status reports `update-available`.
230
230
  filters; do not hand-wire custom email routes around that action surface.
231
231
  Report PNGs are Playwright captures of the real dashboard route in
232
232
  `reportScreenshot=1` mode, authenticated by a short-lived embed-session token
233
- and embedded inline in email with a CID image. Netlify builds emit a scheduled
233
+ and embedded inline in email as ordered CID images. Complete dashboards are
234
+ captured sequentially in bounded panel windows; every window must match the
235
+ panel ids snapshotted at the start, and a failed or mismatched window
236
+ invalidates the entire image set so the scheduler can retry instead of
237
+ sending a partial report. Capture is capped at 10 windows and 14 MiB of raw
238
+ PNG data, and subscriptions are capped at five distinct recipients; use a
239
+ mailing-list address for larger audiences. The serverless capture deadline
240
+ reserves 90 seconds of the 300-second worker budget for cleanup and delivery.
241
+ The ten-minute retry delay is an eligibility floor; the \*/15 sweep runs the
242
+ retry on its first tick after that floor. Netlify builds emit a scheduled
234
243
  trigger plus a background worker from
235
244
  `scripts/emit-netlify-dashboard-report-cron.ts`, using a per-deploy internal
236
245
  token and disabling the in-process interval scheduler on Netlify to avoid
@@ -5,7 +5,10 @@ import {
5
5
  } from "@agent-native/core/server";
6
6
  import { z } from "zod";
7
7
 
8
- import { saveDashboardReportSubscription } from "../server/lib/dashboard-report-subscriptions";
8
+ import {
9
+ MAX_DASHBOARD_REPORT_RECIPIENTS,
10
+ saveDashboardReportSubscription,
11
+ } from "../server/lib/dashboard-report-subscriptions";
9
12
 
10
13
  export default defineAction({
11
14
  description:
@@ -14,7 +17,12 @@ export default defineAction({
14
17
  id: z.string().optional().describe("Existing subscription ID to update"),
15
18
  dashboardId: z.string().describe("Dashboard ID to email"),
16
19
  name: z.string().optional().describe("Human-readable subscription name"),
17
- recipients: z.array(z.string().email()).min(1).describe("Email recipients"),
20
+ recipients: z
21
+ .array(z.string().email())
22
+ .min(1)
23
+ .describe(
24
+ `Email recipients (maximum ${MAX_DASHBOARD_REPORT_RECIPIENTS}; use a mailing-list address for larger audiences)`,
25
+ ),
18
26
  filters: z
19
27
  .record(z.string(), z.string())
20
28
  .optional()
@@ -87,7 +87,7 @@ const DEFAULT_COLORS = [
87
87
  ];
88
88
 
89
89
  const CHART_TOOLTIP_WRAPPER_STYLE: CSSProperties = {
90
- zIndex: 9999,
90
+ zIndex: 280,
91
91
  pointerEvents: "none",
92
92
  };
93
93
 
@@ -117,6 +117,7 @@ const CHART_LEGEND_PROPS = {
117
117
  } as const;
118
118
 
119
119
  const CHART_RESIZE_DEBOUNCE_MS = 50;
120
+ const LEGEND_ACTION_CLOSE_DELAY_MS = 600;
120
121
 
121
122
  type ChartSize = {
122
123
  width: number;
@@ -586,7 +587,7 @@ export function SeriesLegend({
586
587
  closeTimeoutRef.current = setTimeout(() => {
587
588
  setOpenKey(null);
588
589
  closeTimeoutRef.current = null;
589
- }, 200);
590
+ }, LEGEND_ACTION_CLOSE_DELAY_MS);
590
591
  }, [clearCloseTimeout]);
591
592
 
592
593
  useEffect(() => () => clearCloseTimeout(), [clearCloseTimeout]);
@@ -674,7 +675,7 @@ export function SeriesLegend({
674
675
  <PopoverContent
675
676
  side="top"
676
677
  align="center"
677
- sideOffset={8}
678
+ sideOffset={0}
678
679
  collisionPadding={12}
679
680
  className="w-auto max-w-[calc(100vw-1.5rem)] rounded-lg p-1 shadow-lg"
680
681
  onPointerEnter={clearCloseTimeout}
@@ -943,7 +944,7 @@ export function ChartTooltip({
943
944
  <div
944
945
  ref={boxRef}
945
946
  role="tooltip"
946
- className="fixed z-[9999] min-w-40 max-w-[280px] rounded-md border border-border bg-card px-3 py-2 text-xs text-foreground shadow-lg pointer-events-none"
947
+ className="fixed z-[280] min-w-40 max-w-[280px] rounded-md border border-border bg-card px-3 py-2 text-xs text-foreground shadow-lg pointer-events-none"
947
948
  >
948
949
  {labelText && (
949
950
  <div className="mb-1.5 truncate font-medium text-foreground">
@@ -148,6 +148,11 @@ import { dashboardExtensionSlotId } from "./extension-slot";
148
148
  import { interpolate } from "./interpolate";
149
149
  import { serializePanelSql } from "./panel-sql";
150
150
  import { AddPanelPopover, PanelEditorDialog } from "./PanelEditorDialog";
151
+ import {
152
+ listReportablePanelIds,
153
+ parseReportPanelWindow,
154
+ windowReportPanels,
155
+ } from "./report-panel-window";
151
156
  import { SqlChartCard } from "./SqlChartCard";
152
157
  import {
153
158
  clampDashboardColumns,
@@ -188,31 +193,6 @@ function groupDashboardTabs(tabs: string[]): {
188
193
  return { groups, hasNestedTabs };
189
194
  }
190
195
 
191
- function parseReportPanelLimit(raw: string | null): number | null {
192
- if (!raw) return null;
193
- const parsed = Number.parseInt(raw, 10);
194
- if (!Number.isFinite(parsed) || parsed <= 0) return null;
195
- return Math.min(50, parsed);
196
- }
197
-
198
- function limitReportPanels(panels: SqlPanel[], limit: number | null) {
199
- if (!limit) return panels;
200
- const limited: SqlPanel[] = [];
201
- let chartCount = 0;
202
-
203
- for (const panel of panels) {
204
- if (panel.chartType === "section") {
205
- if (chartCount < limit) limited.push(panel);
206
- continue;
207
- }
208
- if (chartCount >= limit) continue;
209
- chartCount++;
210
- limited.push(panel);
211
- }
212
-
213
- return limited;
214
- }
215
-
216
196
  function DashboardDropLine({
217
197
  slot,
218
198
  activeSlot,
@@ -504,8 +484,11 @@ export default function SqlDashboardPage() {
504
484
  const dashboardId = searchParams.get("id") || routeId;
505
485
  const reportScreenshot = searchParams.get("reportScreenshot") === "1";
506
486
  const reportSettingsRequested = searchParams.get("reportSettings") === "1";
507
- const reportPanelLimit = reportScreenshot
508
- ? parseReportPanelLimit(searchParams.get("reportPanelLimit"))
487
+ const reportPanelWindow = reportScreenshot
488
+ ? parseReportPanelWindow(
489
+ searchParams.get("reportPanelOffset"),
490
+ searchParams.get("reportPanelLimit"),
491
+ )
509
492
  : null;
510
493
 
511
494
  const [dashboard, setDashboard] = useState<SqlDashboardConfig | null>(null);
@@ -1180,9 +1163,9 @@ export default function SqlDashboardPage() {
1180
1163
  ? requestedTab
1181
1164
  : tabs[0]
1182
1165
  : null;
1183
- // Report captures need the complete dashboard in one image. The report
1184
- // URL intentionally has no `tab` parameter, so do not apply the normal
1185
- // first-tab selection while rendering the screenshot surface.
1166
+ // Report captures cover the complete dashboard across ordered windows. The
1167
+ // report URL intentionally has no `tab` parameter, so do not apply the
1168
+ // normal first-tab selection while rendering the screenshot surface.
1186
1169
  const activeTab = reportScreenshot ? null : selectedTab;
1187
1170
  const groupedTabs = useMemo(() => groupDashboardTabs(tabs), [tabs]);
1188
1171
  const activeTabGroup = activeTab
@@ -1221,8 +1204,8 @@ export default function SqlDashboardPage() {
1221
1204
  const tabPanels = activeTab
1222
1205
  ? dashboard.panels.filter((p) => !p.tab || p.tab === activeTab)
1223
1206
  : dashboard.panels;
1224
- return limitReportPanels(tabPanels, reportPanelLimit);
1225
- }, [dashboard, activeTab, reportPanelLimit]);
1207
+ return windowReportPanels(tabPanels, reportPanelWindow);
1208
+ }, [dashboard, activeTab, reportPanelWindow]);
1226
1209
 
1227
1210
  // Group panels into "section blocks": each section starts a new block whose
1228
1211
  // grid uses the section's `columns` (falling back to the dashboard default).
@@ -1713,6 +1696,11 @@ export default function SqlDashboardPage() {
1713
1696
  className="space-y-4"
1714
1697
  data-dashboard-report-capture
1715
1698
  data-dashboard-report-ready={loaded && dashboard ? "true" : "false"}
1699
+ data-dashboard-report-panel-ids={
1700
+ reportScreenshot
1701
+ ? JSON.stringify(listReportablePanelIds(visiblePanels))
1702
+ : undefined
1703
+ }
1716
1704
  >
1717
1705
  {hiddenAt ? (
1718
1706
  <div className="flex flex-wrap items-center gap-3 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-900 dark:border-amber-900/70 dark:bg-amber-950/40 dark:text-amber-200">
@@ -0,0 +1,62 @@
1
+ import type { SqlPanel } from "./types";
2
+
3
+ export const REPORT_PANEL_CHUNK_SIZE = 8;
4
+
5
+ export function listReportablePanelIds(panels: SqlPanel[]): string[] {
6
+ return panels
7
+ .filter((panel) => panel.chartType !== "section")
8
+ .map((panel) => panel.id);
9
+ }
10
+
11
+ export function parseReportPanelWindow(
12
+ offsetRaw: string | null,
13
+ limitRaw: string | null,
14
+ ): { offset: number; limit: number } | null {
15
+ if (!offsetRaw || !limitRaw) return null;
16
+ const offset = Number.parseInt(offsetRaw, 10);
17
+ const limit = Number.parseInt(limitRaw, 10);
18
+ if (!Number.isFinite(offset) || offset < 0) return null;
19
+ if (!Number.isFinite(limit) || limit <= 0) return null;
20
+ return { offset, limit: Math.min(REPORT_PANEL_CHUNK_SIZE, limit) };
21
+ }
22
+
23
+ export function countReportablePanels(panels: SqlPanel[]): number {
24
+ return listReportablePanelIds(panels).length;
25
+ }
26
+
27
+ /**
28
+ * A report chunk is indexed by chart panels, not section headers. When a
29
+ * chunk starts inside a section, repeat that section header so each image is
30
+ * readable on its own while preserving the dashboard's panel order.
31
+ */
32
+ export function windowReportPanels(
33
+ panels: SqlPanel[],
34
+ window: { offset: number; limit: number } | null,
35
+ ): SqlPanel[] {
36
+ if (!window) return panels;
37
+
38
+ const end = window.offset + window.limit;
39
+ let chartIndex = 0;
40
+ let activeSection: SqlPanel | null = null;
41
+ let includedFirstChart = false;
42
+ const selected: SqlPanel[] = [];
43
+
44
+ for (const panel of panels) {
45
+ if (panel.chartType === "section") {
46
+ activeSection = panel;
47
+ if (includedFirstChart && chartIndex < end) selected.push(panel);
48
+ continue;
49
+ }
50
+
51
+ const include = chartIndex >= window.offset && chartIndex < end;
52
+ if (include) {
53
+ if (!includedFirstChart && activeSection) selected.push(activeSection);
54
+ selected.push(panel);
55
+ includedFirstChart = true;
56
+ }
57
+ chartIndex++;
58
+ if (chartIndex >= end) break;
59
+ }
60
+
61
+ return selected;
62
+ }
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-21
4
+ ---
5
+
6
+ Chart series controls stay open while moving from the legend