@agent-native/core 0.72.3 → 0.73.0

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 (223) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +38 -0
  3. package/corpus/core/docs/content/skills-guide.md +14 -0
  4. package/corpus/core/docs/content/template-clips.md +7 -1
  5. package/corpus/core/package.json +6 -6
  6. package/corpus/core/src/a2a/client.ts +7 -3
  7. package/corpus/core/src/agent/durable-background.ts +25 -12
  8. package/corpus/core/src/cli/create.ts +1 -1
  9. package/corpus/core/src/cli/skills.ts +450 -19
  10. package/corpus/core/src/client/api-path.ts +28 -0
  11. package/corpus/core/src/client/embed-auth.ts +26 -0
  12. package/corpus/core/src/client/mcp-app-host.ts +6 -0
  13. package/corpus/core/src/client/mcp-apps/McpAppRenderer.tsx +227 -33
  14. package/corpus/core/src/client/resources/use-mcp-servers.ts +1 -0
  15. package/corpus/core/src/client/settings/DemoModeSection.tsx +3 -1
  16. package/corpus/core/src/demo/fetch-interceptor.ts +1 -1
  17. package/corpus/core/src/embedding/agent.ts +6 -1
  18. package/corpus/core/src/embedding/bridge.ts +34 -1
  19. package/corpus/core/src/mcp/build-server.ts +38 -10
  20. package/corpus/core/src/mcp/embed-app.ts +120 -45
  21. package/corpus/core/src/mcp/index.ts +5 -0
  22. package/corpus/core/src/mcp/org-directory.ts +70 -11
  23. package/corpus/core/src/mcp/server.ts +14 -0
  24. package/corpus/core/src/mcp-client/config.ts +12 -0
  25. package/corpus/core/src/mcp-client/index.ts +2 -0
  26. package/corpus/core/src/mcp-client/manager.ts +268 -9
  27. package/corpus/core/src/mcp-client/remote-store.ts +154 -0
  28. package/corpus/core/src/mcp-client/routes.ts +2 -0
  29. package/corpus/core/src/server/agent-chat-plugin.ts +8 -0
  30. package/corpus/core/src/server/auth.ts +7 -0
  31. package/corpus/core/src/server/index.ts +1 -0
  32. package/corpus/core/src/server/onboarding-html.ts +95 -9
  33. package/corpus/core/src/shared/mcp-embed-headers.ts +44 -3
  34. package/corpus/core/src/templates/default/AGENTS.md +4 -0
  35. package/corpus/core/src/templates/default/package.json +2 -1
  36. package/corpus/core/src/templates/headless/AGENTS.md +4 -0
  37. package/corpus/core/src/templates/headless/package.json +1 -0
  38. package/corpus/core/src/templates/workspace-root/AGENTS.md +4 -0
  39. package/corpus/core/src/templates/workspace-root/README.md +4 -0
  40. package/corpus/core/src/templates/workspace-root/package.json +1 -0
  41. package/corpus/core/src/vite/client.ts +53 -0
  42. package/corpus/templates/analytics/actions/update-dashboard.ts +3 -4
  43. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +144 -40
  44. package/corpus/templates/analytics/app/global.css +15 -13
  45. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +9 -32
  46. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +0 -26
  47. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +0 -23
  48. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/pivot.ts +65 -2
  49. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +3 -4
  50. package/corpus/templates/analytics/changelog/2026-06-24-dashboard-charts-now-fill-sparse-template-time-series-suppor.md +6 -0
  51. package/corpus/templates/analytics/changelog/2026-06-24-dashboard-rows-now-auto-fit-panels-so-one-two-or-three-chart.md +6 -0
  52. package/corpus/templates/analytics/changelog/2026-06-24-dashboard-traffic-tables-now-show-top-urls-and-shared-clips-.md +6 -0
  53. package/corpus/templates/analytics/changelog/2026-06-24-first-party-signup-and-pageview-panels-now-honor-dashboard-filters.md +5 -0
  54. package/corpus/templates/analytics/changelog/2026-06-24-first-party-template-dashboards-now-include-repeat-users-ret.md +6 -0
  55. package/corpus/templates/analytics/changelog/2026-06-24-retention-charts-now-break-out-1-day-and-7-day-return-rates-.md +6 -0
  56. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +256 -44
  57. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +7 -2
  58. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +35 -8
  59. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +287 -42
  60. package/corpus/templates/assets/.agents/skills/a2a-assets/SKILL.md +17 -10
  61. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +15 -6
  62. package/corpus/templates/assets/AGENTS.md +7 -5
  63. package/corpus/templates/assets/actions/generate-asset.ts +161 -0
  64. package/corpus/templates/assets/actions/list-assets.ts +4 -2
  65. package/corpus/templates/assets/actions/open-asset-picker.ts +84 -3
  66. package/corpus/templates/assets/app/routes/library.tsx +243 -40
  67. package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +1 -1
  68. package/corpus/templates/calendar/server/plugins/auth.ts +12 -0
  69. package/corpus/templates/clips/.agents/skills/recording/SKILL.md +7 -7
  70. package/corpus/templates/clips/AGENTS.md +6 -2
  71. package/corpus/templates/clips/app/components/capture-install-options.tsx +20 -4
  72. package/corpus/templates/clips/app/components/player/player-controls.tsx +62 -32
  73. package/corpus/templates/clips/app/lib/capture-install-options.ts +39 -5
  74. package/corpus/templates/clips/app/routes/download.tsx +2 -2
  75. package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -1
  76. package/corpus/templates/clips/changelog/2026-06-24-inline-slack-playback-controls-no-longer-shift-when-adjustin.md +6 -0
  77. package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-console-log-stream-all-le.md +6 -0
  78. package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-fetch-xhr-network-request.md +6 -0
  79. package/corpus/templates/clips/changelog/2026-06-24-the-chrome-extension-option-now-only-appears-on-supported-hosts.md +5 -0
  80. package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +13 -8
  81. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  82. package/corpus/templates/clips/chrome-extension/scripts/package.ts +15 -2
  83. package/corpus/templates/clips/chrome-extension/src/overlay.css +4 -25
  84. package/corpus/templates/clips/chrome-extension/src/overlay.ts +1 -3
  85. package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +9 -0
  86. package/corpus/templates/clips/server/lib/public-agent-context.ts +67 -24
  87. package/corpus/templates/design/AGENTS.md +10 -5
  88. package/corpus/templates/design/actions/connect-assets-mcp.ts +131 -0
  89. package/corpus/templates/design/actions/insert-asset.ts +219 -0
  90. package/corpus/templates/design/actions/view-screen.ts +3 -1
  91. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +1 -1
  92. package/corpus/templates/design/app/pages/DesignEditor.tsx +53 -0
  93. package/corpus/templates/design/server/plugins/agent-chat.ts +5 -1
  94. package/corpus/templates/design/server/plugins/assets-mcp.ts +60 -0
  95. package/corpus/templates/mail/server/plugins/auth.ts +4 -1
  96. package/corpus/templates/plan/app/pages/PlansPage.tsx +271 -197
  97. package/corpus/templates/plan/server/plan-content.ts +45 -2
  98. package/dist/a2a/client.d.ts +1 -0
  99. package/dist/a2a/client.d.ts.map +1 -1
  100. package/dist/a2a/client.js +5 -3
  101. package/dist/a2a/client.js.map +1 -1
  102. package/dist/agent/durable-background.d.ts +9 -4
  103. package/dist/agent/durable-background.d.ts.map +1 -1
  104. package/dist/agent/durable-background.js +24 -11
  105. package/dist/agent/durable-background.js.map +1 -1
  106. package/dist/cli/create.js +1 -1
  107. package/dist/cli/create.js.map +1 -1
  108. package/dist/cli/skills.d.ts.map +1 -1
  109. package/dist/cli/skills.js +361 -19
  110. package/dist/cli/skills.js.map +1 -1
  111. package/dist/client/api-path.d.ts.map +1 -1
  112. package/dist/client/api-path.js +27 -0
  113. package/dist/client/api-path.js.map +1 -1
  114. package/dist/client/embed-auth.d.ts.map +1 -1
  115. package/dist/client/embed-auth.js +27 -0
  116. package/dist/client/embed-auth.js.map +1 -1
  117. package/dist/client/mcp-app-host.d.ts.map +1 -1
  118. package/dist/client/mcp-app-host.js +5 -0
  119. package/dist/client/mcp-app-host.js.map +1 -1
  120. package/dist/client/mcp-apps/McpAppRenderer.d.ts +1 -0
  121. package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  122. package/dist/client/mcp-apps/McpAppRenderer.js +179 -33
  123. package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
  124. package/dist/client/resources/use-mcp-servers.d.ts +1 -0
  125. package/dist/client/resources/use-mcp-servers.d.ts.map +1 -1
  126. package/dist/client/resources/use-mcp-servers.js.map +1 -1
  127. package/dist/client/settings/DemoModeSection.d.ts.map +1 -1
  128. package/dist/client/settings/DemoModeSection.js +3 -1
  129. package/dist/client/settings/DemoModeSection.js.map +1 -1
  130. package/dist/demo/fetch-interceptor.js +1 -1
  131. package/dist/demo/fetch-interceptor.js.map +1 -1
  132. package/dist/embedding/agent.d.ts +1 -1
  133. package/dist/embedding/agent.d.ts.map +1 -1
  134. package/dist/embedding/agent.js +1 -1
  135. package/dist/embedding/agent.js.map +1 -1
  136. package/dist/embedding/bridge.d.ts.map +1 -1
  137. package/dist/embedding/bridge.js +27 -1
  138. package/dist/embedding/bridge.js.map +1 -1
  139. package/dist/extensions/schema.d.ts +2 -2
  140. package/dist/mcp/build-server.d.ts +2 -0
  141. package/dist/mcp/build-server.d.ts.map +1 -1
  142. package/dist/mcp/build-server.js +32 -9
  143. package/dist/mcp/build-server.js.map +1 -1
  144. package/dist/mcp/embed-app.d.ts.map +1 -1
  145. package/dist/mcp/embed-app.js +120 -45
  146. package/dist/mcp/embed-app.js.map +1 -1
  147. package/dist/mcp/index.d.ts +1 -0
  148. package/dist/mcp/index.d.ts.map +1 -1
  149. package/dist/mcp/index.js +1 -0
  150. package/dist/mcp/index.js.map +1 -1
  151. package/dist/mcp/org-directory.d.ts +1 -0
  152. package/dist/mcp/org-directory.d.ts.map +1 -1
  153. package/dist/mcp/org-directory.js +59 -11
  154. package/dist/mcp/org-directory.js.map +1 -1
  155. package/dist/mcp/server.d.ts.map +1 -1
  156. package/dist/mcp/server.js +9 -0
  157. package/dist/mcp/server.js.map +1 -1
  158. package/dist/mcp-client/config.d.ts +12 -0
  159. package/dist/mcp-client/config.d.ts.map +1 -1
  160. package/dist/mcp-client/config.js.map +1 -1
  161. package/dist/mcp-client/index.d.ts +1 -1
  162. package/dist/mcp-client/index.d.ts.map +1 -1
  163. package/dist/mcp-client/index.js +1 -1
  164. package/dist/mcp-client/index.js.map +1 -1
  165. package/dist/mcp-client/manager.d.ts +2 -0
  166. package/dist/mcp-client/manager.d.ts.map +1 -1
  167. package/dist/mcp-client/manager.js +201 -9
  168. package/dist/mcp-client/manager.js.map +1 -1
  169. package/dist/mcp-client/remote-store.d.ts +27 -0
  170. package/dist/mcp-client/remote-store.d.ts.map +1 -1
  171. package/dist/mcp-client/remote-store.js +105 -0
  172. package/dist/mcp-client/remote-store.js.map +1 -1
  173. package/dist/mcp-client/routes.d.ts +1 -0
  174. package/dist/mcp-client/routes.d.ts.map +1 -1
  175. package/dist/mcp-client/routes.js +1 -0
  176. package/dist/mcp-client/routes.js.map +1 -1
  177. package/dist/provider-api/actions/github-repo-files.d.ts +2 -2
  178. package/dist/provider-api/actions/query-staged-dataset.d.ts +3 -3
  179. package/dist/provider-api/corpus-jobs.d.ts +8 -8
  180. package/dist/server/agent-chat-plugin.d.ts +2 -0
  181. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  182. package/dist/server/agent-chat-plugin.js +8 -0
  183. package/dist/server/agent-chat-plugin.js.map +1 -1
  184. package/dist/server/auth.d.ts +7 -0
  185. package/dist/server/auth.d.ts.map +1 -1
  186. package/dist/server/auth.js +1 -0
  187. package/dist/server/auth.js.map +1 -1
  188. package/dist/server/index.d.ts +1 -1
  189. package/dist/server/index.d.ts.map +1 -1
  190. package/dist/server/index.js +1 -1
  191. package/dist/server/index.js.map +1 -1
  192. package/dist/server/onboarding-html.d.ts +15 -0
  193. package/dist/server/onboarding-html.d.ts.map +1 -1
  194. package/dist/server/onboarding-html.js +73 -9
  195. package/dist/server/onboarding-html.js.map +1 -1
  196. package/dist/shared/mcp-embed-headers.d.ts +3 -1
  197. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  198. package/dist/shared/mcp-embed-headers.js +40 -3
  199. package/dist/shared/mcp-embed-headers.js.map +1 -1
  200. package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
  201. package/dist/sharing/actions/set-resource-visibility.d.ts +2 -2
  202. package/dist/sharing/actions/share-resource.d.ts +1 -1
  203. package/dist/sharing/schema.d.ts +1 -1
  204. package/dist/templates/default/AGENTS.md +4 -0
  205. package/dist/templates/default/package.json +2 -1
  206. package/dist/templates/headless/AGENTS.md +4 -0
  207. package/dist/templates/headless/package.json +1 -0
  208. package/dist/templates/workspace-root/AGENTS.md +4 -0
  209. package/dist/templates/workspace-root/README.md +4 -0
  210. package/dist/templates/workspace-root/package.json +1 -0
  211. package/dist/vite/client.d.ts.map +1 -1
  212. package/dist/vite/client.js +44 -0
  213. package/dist/vite/client.js.map +1 -1
  214. package/docs/content/skills-guide.md +14 -0
  215. package/docs/content/template-clips.md +7 -1
  216. package/package.json +6 -6
  217. package/src/templates/default/AGENTS.md +4 -0
  218. package/src/templates/default/package.json +2 -1
  219. package/src/templates/headless/AGENTS.md +4 -0
  220. package/src/templates/headless/package.json +1 -0
  221. package/src/templates/workspace-root/AGENTS.md +4 -0
  222. package/src/templates/workspace-root/README.md +4 -0
  223. package/src/templates/workspace-root/package.json +1 -0
@@ -240,39 +240,114 @@ function shouldShowLegend(panel: SqlPanel, seriesCount: number): boolean {
240
240
  return panel.config?.legend !== false && seriesCount > 0;
241
241
  }
242
242
 
243
+ function numericTooltipValue(value: unknown): number {
244
+ const numeric = typeof value === "number" ? value : Number(value);
245
+ return Number.isFinite(numeric) ? numeric : Number.NEGATIVE_INFINITY;
246
+ }
247
+
248
+ export function sortTooltipPayloadItems<
249
+ T extends {
250
+ value?: unknown;
251
+ },
252
+ >(items: T[]): T[] {
253
+ return items
254
+ .map((item, index) => ({ item, index }))
255
+ .sort((a, b) => {
256
+ const diff =
257
+ numericTooltipValue(b.item.value) - numericTooltipValue(a.item.value);
258
+ return diff !== 0 ? diff : a.index - b.index;
259
+ })
260
+ .map(({ item }) => item);
261
+ }
262
+
263
+ function useSeriesVisibility(keys: string[]): {
264
+ hiddenKeys: Set<string>;
265
+ visibleKeys: string[];
266
+ toggleSeries: (key: string) => void;
267
+ } {
268
+ const [hiddenKeys, setHiddenKeys] = useState<Set<string>>(() => new Set());
269
+
270
+ useEffect(() => {
271
+ setHiddenKeys((prev) => {
272
+ const next = new Set(
273
+ Array.from(prev).filter((key) => keys.includes(key)),
274
+ );
275
+ return next.size === prev.size ? prev : next;
276
+ });
277
+ }, [keys]);
278
+
279
+ const visibleKeys = useMemo(
280
+ () => keys.filter((key) => !hiddenKeys.has(key)),
281
+ [hiddenKeys, keys],
282
+ );
283
+
284
+ const toggleSeries = useCallback(
285
+ (key: string) => {
286
+ setHiddenKeys((prev) => {
287
+ const next = new Set(prev);
288
+ if (next.has(key)) {
289
+ next.delete(key);
290
+ return next;
291
+ }
292
+ const visibleCount = keys.filter((k) => !next.has(k)).length;
293
+ if (visibleCount <= 1) return prev;
294
+ next.add(key);
295
+ return next;
296
+ });
297
+ },
298
+ [keys],
299
+ );
300
+
301
+ return { hiddenKeys, visibleKeys, toggleSeries };
302
+ }
303
+
243
304
  function SeriesLegend({
244
305
  keys,
245
306
  colors,
246
307
  panel,
308
+ hiddenKeys,
309
+ onToggleKey,
247
310
  }: {
248
311
  keys: string[];
249
312
  colors: string[];
250
313
  panel: SqlPanel;
314
+ hiddenKeys?: Set<string>;
315
+ onToggleKey?: (key: string) => void;
251
316
  }) {
252
- if (
253
- !usesPrometheusPresentation(panel) ||
254
- !shouldShowLegend(panel, keys.length)
255
- )
256
- return null;
317
+ if (!shouldShowLegend(panel, keys.length)) return null;
257
318
 
258
319
  return (
259
320
  <div className="mt-2 max-h-16 overflow-y-auto overflow-x-hidden pr-1 text-[11px] leading-4 text-muted-foreground">
260
321
  <div className="flex flex-wrap gap-x-3 gap-y-1">
261
- {keys.map((key, i) => (
262
- <span
263
- key={key}
264
- className="inline-flex max-w-[14rem] items-center gap-1.5"
265
- title={key}
266
- >
267
- <span
268
- className="h-2 w-2 shrink-0 rounded-full"
269
- style={{ backgroundColor: colors[i % colors.length] }}
270
- />
271
- <span className="truncate">
272
- {formatSeriesLabelForPanel(panel, key)}
273
- </span>
274
- </span>
275
- ))}
322
+ {keys.map((key, i) => {
323
+ const hidden = hiddenKeys?.has(key) ?? false;
324
+ const label = formatSeriesLabelForPanel(panel, key);
325
+ const color = colors[i % colors.length];
326
+ return (
327
+ <button
328
+ key={key}
329
+ type="button"
330
+ aria-pressed={!hidden}
331
+ className={`inline-flex max-w-[14rem] items-center gap-1.5 rounded-sm text-left transition-opacity hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring ${
332
+ hidden ? "opacity-35" : "opacity-100"
333
+ } ${onToggleKey ? "cursor-pointer" : "cursor-default"}`}
334
+ title={label}
335
+ onClick={() => onToggleKey?.(key)}
336
+ >
337
+ <span className="relative h-2.5 w-3 shrink-0">
338
+ <span
339
+ className="absolute left-0 right-0 top-1/2 h-px -translate-y-1/2"
340
+ style={{ backgroundColor: color }}
341
+ />
342
+ <span
343
+ className="absolute left-1/2 top-1/2 h-1.5 w-1.5 -translate-x-1/2 -translate-y-1/2 rounded-full"
344
+ style={{ backgroundColor: color }}
345
+ />
346
+ </span>
347
+ <span className="truncate">{label}</span>
348
+ </button>
349
+ );
350
+ })}
276
351
  </div>
277
352
  </div>
278
353
  );
@@ -296,17 +371,25 @@ function ChartFrame({
296
371
  panel,
297
372
  legendKeys,
298
373
  colors,
374
+ hiddenKeys,
375
+ onToggleLegendKey,
376
+ showCustomLegend = false,
299
377
  children,
300
378
  }: {
301
379
  panel: SqlPanel;
302
380
  legendKeys: string[];
303
381
  colors: string[];
382
+ hiddenKeys?: Set<string>;
383
+ onToggleLegendKey?: (key: string) => void;
384
+ showCustomLegend?: boolean;
304
385
  children: ReactNode;
305
386
  }) {
306
387
  const fill = useContext(ChartFillHeightContext);
307
388
  const chartHeight = fill ? "h-full min-h-[250px]" : "h-[250px]";
308
389
 
309
- if (!usesPrometheusPresentation(panel)) {
390
+ const renderLegend = showCustomLegend || usesPrometheusPresentation(panel);
391
+
392
+ if (!renderLegend) {
310
393
  return (
311
394
  <div className={`${chartHeight} w-full overflow-visible`}>{children}</div>
312
395
  );
@@ -321,7 +404,13 @@ function ChartFrame({
321
404
  >
322
405
  {children}
323
406
  </div>
324
- <SeriesLegend keys={legendKeys} colors={colors} panel={panel} />
407
+ <SeriesLegend
408
+ keys={legendKeys}
409
+ colors={colors}
410
+ panel={panel}
411
+ hiddenKeys={hiddenKeys}
412
+ onToggleKey={onToggleLegendKey}
413
+ />
325
414
  </div>
326
415
  );
327
416
  }
@@ -347,8 +436,9 @@ function ChartTooltip({
347
436
  valueFormatter?: (value: number) => string;
348
437
  }) {
349
438
  const tooltipRef = useChartTooltipFlip<HTMLDivElement>();
350
- const items =
351
- payload?.filter((item) => item.value != null && item.value !== "") ?? [];
439
+ const items = sortTooltipPayloadItems(
440
+ payload?.filter((item) => item.value != null && item.value !== "") ?? [],
441
+ );
352
442
  if (!active || items.length === 0) return null;
353
443
 
354
444
  const labelText =
@@ -1067,9 +1157,17 @@ function BarRenderer({
1067
1157
  formatXLabel(String(value ?? ""), panel);
1068
1158
  const seriesNameFormatter = (name: string) =>
1069
1159
  formatSeriesLabelForPanel(panel, name);
1160
+ const { hiddenKeys, toggleSeries } = useSeriesVisibility(yKeys);
1070
1161
 
1071
1162
  return (
1072
- <ChartFrame panel={panel} legendKeys={yKeys} colors={colors}>
1163
+ <ChartFrame
1164
+ panel={panel}
1165
+ legendKeys={yKeys}
1166
+ colors={colors}
1167
+ hiddenKeys={hiddenKeys}
1168
+ onToggleLegendKey={toggleSeries}
1169
+ showCustomLegend
1170
+ >
1073
1171
  <ResponsiveContainer width="100%" height="100%">
1074
1172
  <BarChart data={rows}>
1075
1173
  <XAxis
@@ -1104,10 +1202,6 @@ function BarRenderer({
1104
1202
  }
1105
1203
  itemSorter={(item) => -(Number(item.value) || 0)}
1106
1204
  />
1107
- {!usesPrometheusPresentation(panel) &&
1108
- shouldShowLegend(panel, yKeys.length) && (
1109
- <Legend {...CHART_LEGEND_PROPS} />
1110
- )}
1111
1205
  {yKeys.map((key, i) => (
1112
1206
  <Bar
1113
1207
  key={key}
@@ -1118,6 +1212,7 @@ function BarRenderer({
1118
1212
  stacked && i < yKeys.length - 1 ? [0, 0, 0, 0] : [4, 4, 0, 0]
1119
1213
  }
1120
1214
  stackId={stacked ? "stack" : undefined}
1215
+ hide={hiddenKeys.has(key)}
1121
1216
  />
1122
1217
  ))}
1123
1218
  </BarChart>
@@ -1149,10 +1244,18 @@ function TimeSeriesRenderer({
1149
1244
  formatXLabel(String(value ?? ""), panel);
1150
1245
  const seriesNameFormatter = (name: string) =>
1151
1246
  formatSeriesLabelForPanel(panel, name);
1247
+ const { hiddenKeys, visibleKeys, toggleSeries } = useSeriesVisibility(yKeys);
1152
1248
 
1153
1249
  if (chartType === "line") {
1154
1250
  return (
1155
- <ChartFrame panel={panel} legendKeys={yKeys} colors={colors}>
1251
+ <ChartFrame
1252
+ panel={panel}
1253
+ legendKeys={yKeys}
1254
+ colors={colors}
1255
+ hiddenKeys={hiddenKeys}
1256
+ onToggleLegendKey={toggleSeries}
1257
+ showCustomLegend
1258
+ >
1156
1259
  <ResponsiveContainer width="100%" height="100%">
1157
1260
  <LineChart data={rows}>
1158
1261
  <XAxis
@@ -1187,10 +1290,6 @@ function TimeSeriesRenderer({
1187
1290
  }
1188
1291
  itemSorter={(item) => -(Number(item.value) || 0)}
1189
1292
  />
1190
- {!usesPrometheusPresentation(panel) &&
1191
- shouldShowLegend(panel, yKeys.length) && (
1192
- <Legend {...CHART_LEGEND_PROPS} />
1193
- )}
1194
1293
  {yKeys.map((key, i) => (
1195
1294
  <Line
1196
1295
  key={key}
@@ -1200,6 +1299,7 @@ function TimeSeriesRenderer({
1200
1299
  stroke={colors[i % colors.length]}
1201
1300
  strokeWidth={2}
1202
1301
  dot={false}
1302
+ hide={hiddenKeys.has(key)}
1203
1303
  />
1204
1304
  ))}
1205
1305
  </LineChart>
@@ -1211,10 +1311,17 @@ function TimeSeriesRenderer({
1211
1311
  // With multiple series, filled areas stack and obscure lines behind them,
1212
1312
  // so only draw the gradient fill when there's a single series — unless
1213
1313
  // the caller asked for an explicit stacked area.
1214
- const showFill = yKeys.length === 1 || stacked;
1314
+ const showFill = visibleKeys.length === 1 || stacked;
1215
1315
 
1216
1316
  return (
1217
- <ChartFrame panel={panel} legendKeys={yKeys} colors={colors}>
1317
+ <ChartFrame
1318
+ panel={panel}
1319
+ legendKeys={yKeys}
1320
+ colors={colors}
1321
+ hiddenKeys={hiddenKeys}
1322
+ onToggleLegendKey={toggleSeries}
1323
+ showCustomLegend
1324
+ >
1218
1325
  <ResponsiveContainer width="100%" height="100%">
1219
1326
  <AreaChart data={rows}>
1220
1327
  {showFill && (
@@ -1274,10 +1381,6 @@ function TimeSeriesRenderer({
1274
1381
  }
1275
1382
  itemSorter={(item) => -(Number(item.value) || 0)}
1276
1383
  />
1277
- {!usesPrometheusPresentation(panel) &&
1278
- shouldShowLegend(panel, yKeys.length) && (
1279
- <Legend {...CHART_LEGEND_PROPS} />
1280
- )}
1281
1384
  {yKeys.map((key, i) => (
1282
1385
  <Area
1283
1386
  key={key}
@@ -1289,6 +1392,7 @@ function TimeSeriesRenderer({
1289
1392
  fillOpacity={showFill ? 1 : 0}
1290
1393
  fill={showFill ? `url(#sql-gradient-${key})` : "none"}
1291
1394
  stackId={stacked ? "stack" : undefined}
1395
+ hide={hiddenKeys.has(key)}
1292
1396
  />
1293
1397
  ))}
1294
1398
  </AreaChart>
@@ -314,31 +314,33 @@
314
314
  }
315
315
 
316
316
  /* The dashboard grid responds to the available content width — which shrinks
317
- when the agent sidebar is open — not the viewport. Below the threshold it
318
- stacks to one column (mobile layout); at/above it expands to the dashboard's
319
- configured column count. Mirrors templates/macros/app/global.css. */
317
+ when the agent sidebar is open — not the viewport. Rows auto-fit like
318
+ Amplitude: a partial row stretches its cards across the available width. */
320
319
  .dashboard-grid-container {
321
320
  container: dashboard-grid / inline-size;
322
321
  }
323
322
 
324
323
  .dashboard-grid {
325
- display: grid;
326
- grid-auto-rows: auto;
324
+ --dashboard-grid-gap: 1rem;
325
+ display: flex;
326
+ flex-wrap: wrap;
327
327
  align-items: stretch;
328
- gap: 1rem;
329
- grid-template-columns: minmax(0, 1fr);
328
+ gap: var(--dashboard-grid-gap);
330
329
  }
331
330
 
332
331
  .dashboard-grid > .dashboard-grid-cell {
333
- grid-column: auto;
332
+ flex: 1 1 100%;
333
+ min-width: 0;
334
334
  }
335
335
 
336
336
  @container dashboard-grid (min-width: 768px) {
337
- .dashboard-grid {
338
- grid-template-columns: repeat(var(--dash-cols), minmax(0, 1fr));
339
- }
340
-
341
337
  .dashboard-grid > .dashboard-grid-cell {
342
- grid-column: span var(--panel-span);
338
+ flex-basis: max(
339
+ 0px,
340
+ calc(
341
+ (100% - (var(--dash-cols) - 1) * var(--dashboard-grid-gap)) /
342
+ var(--dash-cols)
343
+ )
344
+ );
343
345
  }
344
346
  }
@@ -73,6 +73,8 @@ export interface PanelFormValues {
73
73
  title: string;
74
74
  chartType: ChartType;
75
75
  source: DataSourceType;
76
+ /** Legacy storage field retained for existing dashboards. Row widths are
77
+ * now inferred from how many panels share the row. */
76
78
  width: number;
77
79
  /** Section panels only: number of grid columns the panels following this
78
80
  * section should use. Ignored when `chartType` is not `"section"`. */
@@ -226,7 +228,8 @@ function PanelEditorContent({
226
228
  `If no source can answer, report the exact unavailable/error result instead of saving a panel with guessed schema or metrics. ` +
227
229
  `Use the \`update-dashboard\` action with ops=[{op:'insert', path:'/panels/-', value: <panel>}] ` +
228
230
  `to append, or an appropriate index to place the panel in the right spot. ` +
229
- `Panel shape: { id (unique slug), title, sql, source ('bigquery'|'ga4'|'amplitude'|'first-party'|'demo'|'prometheus'), chartType ('line'|'area'|'bar'|'metric'|'table'|'pie'|'section'), width (number of grid columns to span, 1..6), tab? (use 'Group / Tab' for grouped tabs), columns? (section panels only 1..6 grid columns for the panels following this section), config? }. ` +
231
+ `Panel shape: { id (unique slug), title, sql, source ('bigquery'|'ga4'|'amplitude'|'first-party'|'demo'|'prometheus'), chartType ('line'|'area'|'bar'|'metric'|'table'|'pie'|'section'), width (legacy integer 1..6; set to 1 unless editing existing data), tab? (use 'Group / Tab' for grouped tabs), columns? (section panels only - 1..6 max panels per row for panels following this section), config? }. ` +
232
+ `Visible layout auto-fits by row: one panel in a row spans the row, two split it, three split it into thirds, up to the section column limit. ` +
230
233
  `For amplitude panels, sql is a JSON descriptor: {"event":"event name","groupBy":"property","days":30}. ` +
231
234
  `For first-party panels, sql is read-only SQL over analytics_events only; use source 'first-party' and do not call db-query for this datasource. ` +
232
235
  `For demo panels, sql uses the same Prometheus JSON descriptor shape as source 'prometheus': {"promql":"rate(http_requests_total[5m])","mode":"range","range":"1h","step":"30s"}. ` +
@@ -310,11 +313,9 @@ function PanelEditorContent({
310
313
  </Select>
311
314
  </div>
312
315
 
313
- <div className="grid gap-1.5">
314
- <Label>
315
- {form.chartType === "section" ? "Section columns" : "Span"}
316
- </Label>
317
- {form.chartType === "section" ? (
316
+ {form.chartType === "section" ? (
317
+ <div className="grid gap-1.5">
318
+ <Label>Section columns</Label>
318
319
  <ToggleGroup
319
320
  type="single"
320
321
  value={String(form.columns)}
@@ -338,32 +339,8 @@ function PanelEditorContent({
338
339
  </ToggleGroupItem>
339
340
  ))}
340
341
  </ToggleGroup>
341
- ) : (
342
- <ToggleGroup
343
- type="single"
344
- value={String(form.width)}
345
- onValueChange={(v) => {
346
- if (!v) return;
347
- const next = clampPanelWidth(Number(v), MAX_DASHBOARD_COLUMNS);
348
- setForm((f) => ({ ...f, width: next }));
349
- }}
350
- className="justify-start h-9"
351
- >
352
- {Array.from(
353
- { length: MAX_DASHBOARD_COLUMNS },
354
- (_, i) => i + 1,
355
- ).map((n) => (
356
- <ToggleGroupItem
357
- key={n}
358
- value={String(n)}
359
- className="h-9 w-9 px-0 text-xs"
360
- >
361
- {n}
362
- </ToggleGroupItem>
363
- ))}
364
- </ToggleGroup>
365
- )}
366
- </div>
342
+ </div>
343
+ ) : null}
367
344
  </div>
368
345
 
369
346
  <div className="grid gap-1.5">
@@ -2,8 +2,6 @@ import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
3
3
  import {
4
4
  IconGripVertical,
5
- IconArrowsMaximize,
6
- IconArrowsMinimize,
7
5
  IconDotsVertical,
8
6
  IconMaximize,
9
7
  IconPencil,
@@ -49,13 +47,6 @@ interface SqlChartCardProps {
49
47
  panel: SqlPanel;
50
48
  resolvedSql?: string;
51
49
  onRemove: () => void;
52
- /** Toggle between "span 1 column" and "span all columns of the current
53
- * section". Optional because section panels never expose this control. */
54
- onToggleWidth?: () => void;
55
- /** Number of columns in the section this panel currently lives in. Used to
56
- * decide the toggle label / icon: when the panel already spans the full
57
- * row, we offer to shrink; otherwise we offer to expand. */
58
- gridColumns?: number;
59
50
  onEdit?: () => void;
60
51
  /** Persist a SQL-only edit from the inline View SQL popover. Should throw on
61
52
  * validation failure so the popover can stay open and surface the error. */
@@ -67,8 +58,6 @@ export function SqlChartCard({
67
58
  panel,
68
59
  resolvedSql,
69
60
  onRemove,
70
- onToggleWidth,
71
- gridColumns,
72
61
  onEdit,
73
62
  onSaveSql,
74
63
  editable = true,
@@ -300,21 +289,6 @@ export function SqlChartCard({
300
289
  </DropdownMenuItem>
301
290
  </ViewSqlPopover>
302
291
  ) : null}
303
- {editable && onToggleWidth && (gridColumns ?? 2) > 1 && (
304
- <DropdownMenuItem onSelect={onToggleWidth}>
305
- {panel.width >= (gridColumns ?? 2) ? (
306
- <>
307
- <IconArrowsMinimize className="h-4 w-4 mr-2" />
308
- Span 1 column
309
- </>
310
- ) : (
311
- <>
312
- <IconArrowsMaximize className="h-4 w-4 mr-2" />
313
- Span full row
314
- </>
315
- )}
316
- </DropdownMenuItem>
317
- )}
318
292
  {editable ? <DropdownMenuSeparator /> : null}
319
293
  {editable && onEdit && (
320
294
  <DropdownMenuItem onSelect={() => onEdit()}>
@@ -79,7 +79,6 @@ import { ViewsMenu } from "./ViewsMenu";
79
79
  import BlankDashboard from "../BlankDashboard";
80
80
  import {
81
81
  clampDashboardColumns,
82
- clampPanelWidth,
83
82
  DEFAULT_DASHBOARD_COLUMNS,
84
83
  type SqlDashboardConfig,
85
84
  type SqlPanel,
@@ -632,22 +631,6 @@ export default function SqlDashboardPage() {
632
631
  [dashboard, persist],
633
632
  );
634
633
 
635
- const toggleWidth = useCallback(
636
- (panelId: string, gridColumns: number) => {
637
- if (!dashboard) return;
638
- const max = clampDashboardColumns(gridColumns);
639
- persist({
640
- ...dashboard,
641
- panels: dashboard.panels.map((p) => {
642
- if (p.id !== panelId) return p;
643
- const current = clampPanelWidth(p.width, max);
644
- return { ...p, width: current >= max ? 1 : max };
645
- }),
646
- });
647
- },
648
- [dashboard, persist],
649
- );
650
-
651
634
  const handleSavePanel = useCallback(
652
635
  async (panel: SqlPanel) => {
653
636
  if (!dashboard) return;
@@ -1349,14 +1332,12 @@ export default function SqlDashboardPage() {
1349
1332
  }
1350
1333
  : panel;
1351
1334
  const remoteEditor = remoteEditingPanels.get(panel.id);
1352
- const span = clampPanelWidth(panel.width, group.columns);
1353
1335
  return (
1354
1336
  <div
1355
1337
  key={panel.id}
1356
1338
  className="dashboard-grid-cell relative h-full"
1357
1339
  style={
1358
1340
  {
1359
- "--panel-span": span,
1360
1341
  ...(remoteEditor
1361
1342
  ? {
1362
1343
  outline: `2px solid ${remoteEditor.color}`,
@@ -1384,11 +1365,7 @@ export default function SqlDashboardPage() {
1384
1365
  serializePanelSql(panel.sql),
1385
1366
  vars,
1386
1367
  )}
1387
- gridColumns={group.columns}
1388
1368
  onRemove={() => removePanel(panel.id)}
1389
- onToggleWidth={() =>
1390
- toggleWidth(panel.id, group.columns)
1391
- }
1392
1369
  onEdit={() => openEditPanel(panel)}
1393
1370
  onSaveSql={(sql) => handleSavePanel({ ...panel, sql })}
1394
1371
  editable={canEdit}
@@ -15,6 +15,66 @@ export interface PivotResult {
15
15
  seriesKeys: string[];
16
16
  }
17
17
 
18
+ const ISO_DAY_RE = /^\d{4}-\d{2}-\d{2}$/;
19
+ const MAX_DAILY_GAP_FILL_DAYS = 800;
20
+
21
+ function dayToUtcMs(day: string): number | null {
22
+ if (!ISO_DAY_RE.test(day)) return null;
23
+ const [year, month, date] = day.split("-").map(Number);
24
+ const ms = Date.UTC(year, month - 1, date);
25
+ return Number.isFinite(ms) ? ms : null;
26
+ }
27
+
28
+ function utcMsToDay(ms: number): string {
29
+ return new Date(ms).toISOString().slice(0, 10);
30
+ }
31
+
32
+ function fillMissingSeries(
33
+ row: Record<string, unknown>,
34
+ seriesKeys: string[],
35
+ ): Record<string, unknown> {
36
+ for (const key of seriesKeys) {
37
+ if (!(key in row)) row[key] = 0;
38
+ }
39
+ return row;
40
+ }
41
+
42
+ function fillMissingDailyRows(
43
+ rows: Record<string, unknown>[],
44
+ xKey: string,
45
+ seriesKeys: string[],
46
+ ): Record<string, unknown>[] {
47
+ if (rows.length < 2 || seriesKeys.length === 0) return rows;
48
+
49
+ const first = rows[0]?.[xKey];
50
+ const last = rows[rows.length - 1]?.[xKey];
51
+ if (typeof first !== "string" || typeof last !== "string") return rows;
52
+
53
+ const startMs = dayToUtcMs(first);
54
+ const endMs = dayToUtcMs(last);
55
+ if (startMs == null || endMs == null || endMs < startMs) return rows;
56
+
57
+ const dayCount = Math.floor((endMs - startMs) / 86_400_000) + 1;
58
+ if (dayCount > MAX_DAILY_GAP_FILL_DAYS) return rows;
59
+
60
+ const byDay = new Map<string, Record<string, unknown>>();
61
+ for (const row of rows) {
62
+ const day = row[xKey];
63
+ if (typeof day === "string" && ISO_DAY_RE.test(day)) {
64
+ byDay.set(day, row);
65
+ }
66
+ }
67
+
68
+ const filled: Record<string, unknown>[] = [];
69
+ for (let ms = startMs; ms <= endMs; ms += 86_400_000) {
70
+ const day = utcMsToDay(ms);
71
+ filled.push(
72
+ fillMissingSeries(byDay.get(day) ?? { [xKey]: day }, seriesKeys),
73
+ );
74
+ }
75
+ return filled;
76
+ }
77
+
18
78
  export function pivotRows(
19
79
  rows: Record<string, unknown>[],
20
80
  config: PivotConfig,
@@ -52,8 +112,11 @@ export function pivotRows(
52
112
  if (emitted.has(x)) continue;
53
113
  emitted.add(x);
54
114
  const bucket = byX.get(x);
55
- if (bucket) orderedRows.push(bucket);
115
+ if (bucket) orderedRows.push(fillMissingSeries(bucket, seriesKeys));
56
116
  }
57
117
 
58
- return { rows: orderedRows, seriesKeys };
118
+ return {
119
+ rows: fillMissingDailyRows(orderedRows, xKey, seriesKeys),
120
+ seriesKeys,
121
+ };
59
122
  }
@@ -88,10 +88,9 @@ export interface SqlPanel {
88
88
  source: DataSourceType;
89
89
  chartType: ChartType;
90
90
  /**
91
- * How many grid columns this panel spans. Defaults to 1. The renderer
92
- * clamps to the active section's column count, so a `width: 4` panel in a
93
- * 2-column section still spans the full row. Sections always span every
94
- * column regardless of this value.
91
+ * Legacy layout field retained for existing dashboards and action payloads.
92
+ * The renderer now auto-fits rows from panel order: one card in a row spans
93
+ * the row, two cards split it, and so on up to the section column count.
95
94
  */
96
95
  width: number;
97
96
  /**
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-24
4
+ ---
5
+
6
+ Dashboard charts now fill sparse template time series, support legend toggles, and sort tooltip values by impact.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-24
4
+ ---
5
+
6
+ Dashboard rows now auto-fit panels so one, two, or three charts fill the row naturally.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-06-24
4
+ ---
5
+
6
+ Dashboard traffic tables now show top URLs and shared Clips with clickable links.
@@ -0,0 +1,5 @@
1
+ ---
2
+ "analytics": fixed
3
+ ---
4
+
5
+ First-party signup and pageview dashboard panels now use the selected time range and email filters consistently.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-06-24
4
+ ---
5
+
6
+ First-party template dashboards now show key activity, signup, retention, DAU, and WAU trends over time by template.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-06-24
4
+ ---
5
+
6
+ Retention charts now break out 1-day and 7-day return rates by template.