@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
@@ -1,18 +1,45 @@
1
1
  {
2
2
  "name": "Agent Native Templates (First-party)",
3
3
  "description": "Template engagement and per-app activity recorded through analytics.agent-native.com/track. Source: first-party analytics_events.",
4
+ "filters": [
5
+ {
6
+ "id": "timeRange",
7
+ "type": "select",
8
+ "label": "Time range",
9
+ "default": "90d",
10
+ "options": [
11
+ { "value": "7d", "label": "Last 7 days" },
12
+ { "value": "30d", "label": "Last 30 days" },
13
+ { "value": "90d", "label": "Last 90 days" },
14
+ { "value": "180d", "label": "Last 180 days" },
15
+ { "value": "365d", "label": "Last 365 days" },
16
+ { "value": "all", "label": "All time" }
17
+ ]
18
+ },
19
+ {
20
+ "id": "emailFilter",
21
+ "type": "select",
22
+ "label": "Email filter",
23
+ "default": "all",
24
+ "options": [
25
+ { "value": "all", "label": "All users" },
26
+ { "value": "exclude_builder", "label": "Exclude @builder.io" },
27
+ { "value": "only_builder", "label": "Only @builder.io" }
28
+ ]
29
+ }
30
+ ],
4
31
  "panels": [
5
32
  {
6
33
  "id": "total-signups",
7
- "title": "Signups",
34
+ "title": "Total Signups",
8
35
  "chartType": "metric",
9
36
  "source": "first-party",
10
37
  "width": 1,
11
- "sql": "SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup'",
38
+ "sql": "SELECT COUNT(*) AS signups FROM analytics_events WHERE event_name = 'signup' AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io'))",
12
39
  "config": {
13
- "yKey": "count",
40
+ "yKey": "signups",
14
41
  "yFormatter": "number",
15
- "description": "Better Auth user creation events from all Agent Native templates"
42
+ "description": "Total signup events in the selected time range."
16
43
  }
17
44
  },
18
45
  {
@@ -21,12 +48,18 @@
21
48
  "chartType": "area",
22
49
  "source": "first-party",
23
50
  "width": 2,
24
- "sql": "SELECT substr(timestamp, 1, 10) AS date, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' GROUP BY substr(timestamp, 1, 10) ORDER BY date",
51
+ "sql": "WITH offsets AS (SELECT (ROW_NUMBER() OVER (ORDER BY timestamp) - 1)::int AS n FROM analytics_events LIMIT 800), signup_events AS (SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), NULLIF(properties::jsonb ->> 'templateId', ''), NULLIF(properties::jsonb ->> 'agent_native_template', ''), NULLIF(properties::jsonb ->> 'agentNativeTemplate', ''), NULLIF(app, ''), NULLIF(properties::jsonb ->> 'agent_native_app', ''), NULLIF(properties::jsonb ->> 'agentNativeApp', ''), 'unknown') AS template FROM analytics_events WHERE event_name = 'signup' AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io'))), bounds AS (SELECT MIN(date::date) AS start_date, MAX(date::date) AS end_date FROM signup_events), dates AS (SELECT to_char(bounds.start_date + offsets.n, 'YYYY-MM-DD') AS date FROM bounds CROSS JOIN offsets WHERE bounds.start_date IS NOT NULL AND bounds.start_date + offsets.n <= bounds.end_date), templates AS (SELECT DISTINCT template FROM signup_events), daily AS (SELECT date, template, COUNT(*) AS count FROM signup_events GROUP BY date, template) SELECT dates.date, templates.template, COALESCE(daily.count, 0) AS count FROM dates CROSS JOIN templates LEFT JOIN daily ON daily.date = dates.date AND daily.template = templates.template ORDER BY dates.date, templates.template",
25
52
  "config": {
26
53
  "xKey": "date",
27
54
  "yKey": "count",
28
- "color": "#8b5cf6",
29
- "description": "Daily signup events across all Agent Native templates"
55
+ "yFormatter": "number",
56
+ "pivot": {
57
+ "xKey": "date",
58
+ "seriesKey": "template",
59
+ "valueKey": "count"
60
+ },
61
+ "stacked": true,
62
+ "description": "Daily signup events stacked by inferred template/app."
30
63
  }
31
64
  },
32
65
  {
@@ -34,13 +67,14 @@
34
67
  "title": "Signups by Template",
35
68
  "chartType": "bar",
36
69
  "source": "first-party",
37
- "width": 1,
38
- "sql": "SELECT COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' GROUP BY COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') ORDER BY count DESC LIMIT 20",
70
+ "width": 2,
71
+ "sql": "SELECT COALESCE(NULLIF(template, ''), NULLIF(properties::jsonb ->> 'templateId', ''), NULLIF(properties::jsonb ->> 'agent_native_template', ''), NULLIF(properties::jsonb ->> 'agentNativeTemplate', ''), NULLIF(app, ''), NULLIF(properties::jsonb ->> 'agent_native_app', ''), NULLIF(properties::jsonb ->> 'agentNativeApp', ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io')) GROUP BY COALESCE(NULLIF(template, ''), NULLIF(properties::jsonb ->> 'templateId', ''), NULLIF(properties::jsonb ->> 'agent_native_template', ''), NULLIF(properties::jsonb ->> 'agentNativeTemplate', ''), NULLIF(app, ''), NULLIF(properties::jsonb ->> 'agent_native_app', ''), NULLIF(properties::jsonb ->> 'agentNativeApp', ''), 'unknown') ORDER BY count DESC",
39
72
  "config": {
40
73
  "xKey": "template",
41
74
  "yKey": "count",
42
- "color": "#8b5cf6",
43
- "description": "Signup events grouped by inferred template/app"
75
+ "yFormatter": "number",
76
+ "color": "var(--brand-purple)",
77
+ "description": "Signup events grouped by inferred template/app."
44
78
  }
45
79
  },
46
80
  {
@@ -88,63 +122,75 @@
88
122
  "chartType": "area",
89
123
  "source": "first-party",
90
124
  "width": 2,
91
- "sql": "SELECT substr(timestamp, 1, 10) AS date, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' GROUP BY substr(timestamp, 1, 10) ORDER BY date",
125
+ "sql": "SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
92
126
  "config": {
93
127
  "xKey": "date",
94
128
  "yKey": "count",
95
- "color": "var(--brand-blue)"
96
- }
97
- },
98
- {
99
- "id": "clicks-by-template",
100
- "title": "Clicks by Template",
101
- "chartType": "bar",
102
- "source": "first-party",
103
- "width": 1,
104
- "sql": "SELECT COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' GROUP BY COALESCE(NULLIF(template, ''), 'unknown') ORDER BY count DESC LIMIT 20",
105
- "config": {
106
- "xKey": "template",
107
- "yKey": "count",
108
- "color": "var(--brand-blue)"
129
+ "yFormatter": "number",
130
+ "pivot": {
131
+ "xKey": "date",
132
+ "seriesKey": "template",
133
+ "valueKey": "count"
134
+ },
135
+ "stacked": true
109
136
  }
110
137
  },
111
138
  {
112
- "id": "demo-clicks-by-template",
113
- "title": "Try-Demo Clicks by Template",
114
- "chartType": "bar",
139
+ "id": "demo-clicks-over-time",
140
+ "title": "Try-Demo Clicks Over Time",
141
+ "chartType": "area",
115
142
  "source": "first-party",
116
- "width": 1,
117
- "sql": "SELECT COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click try demo' GROUP BY COALESCE(NULLIF(template, ''), 'unknown') ORDER BY count DESC LIMIT 20",
143
+ "width": 2,
144
+ "sql": "SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click try demo' GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
118
145
  "config": {
119
- "xKey": "template",
146
+ "xKey": "date",
120
147
  "yKey": "count",
121
- "color": "var(--brand-teal)"
148
+ "yFormatter": "number",
149
+ "pivot": {
150
+ "xKey": "date",
151
+ "seriesKey": "template",
152
+ "valueKey": "count"
153
+ },
154
+ "stacked": true
122
155
  }
123
156
  },
124
157
  {
125
- "id": "cli-copies-by-template",
126
- "title": "CLI Copies by Template",
127
- "chartType": "bar",
158
+ "id": "cli-copies-over-time",
159
+ "title": "CLI Copies Over Time",
160
+ "chartType": "area",
128
161
  "source": "first-party",
129
- "width": 1,
130
- "sql": "SELECT COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' GROUP BY COALESCE(NULLIF(template, ''), 'unknown') ORDER BY count DESC LIMIT 20",
162
+ "width": 2,
163
+ "sql": "SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
131
164
  "config": {
132
- "xKey": "template",
165
+ "xKey": "date",
133
166
  "yKey": "count",
134
- "color": "#06b6d4"
167
+ "yFormatter": "number",
168
+ "pivot": {
169
+ "xKey": "date",
170
+ "seriesKey": "template",
171
+ "valueKey": "count"
172
+ },
173
+ "stacked": true
135
174
  }
136
175
  },
137
176
  {
138
- "id": "cli-copies-over-time",
139
- "title": "CLI Copies Over Time",
177
+ "id": "pageviews-over-time",
178
+ "title": "Pageviews Over Time",
140
179
  "chartType": "area",
141
180
  "source": "first-party",
142
181
  "width": 2,
143
- "sql": "SELECT substr(timestamp, 1, 10) AS date, COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' GROUP BY substr(timestamp, 1, 10) ORDER BY date",
182
+ "sql": "SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'pageview' AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io')) GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') ORDER BY date, template",
144
183
  "config": {
145
184
  "xKey": "date",
146
185
  "yKey": "count",
147
- "color": "#06b6d4"
186
+ "yFormatter": "number",
187
+ "pivot": {
188
+ "xKey": "date",
189
+ "seriesKey": "template",
190
+ "valueKey": "count"
191
+ },
192
+ "stacked": true,
193
+ "description": "Pageview events per day stacked by inferred template/app."
148
194
  }
149
195
  },
150
196
  {
@@ -188,6 +234,172 @@
188
234
  "color": "#10b981"
189
235
  }
190
236
  },
237
+ {
238
+ "id": "top-visited-urls",
239
+ "title": "Top Visited URLs",
240
+ "chartType": "table",
241
+ "source": "first-party",
242
+ "width": 2,
243
+ "sql": "WITH pageviews AS (SELECT COALESCE(CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') END, NULLIF(url, ''), NULLIF(path, ''), 'unknown') AS url, CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') WHEN NULLIF(url, '') LIKE 'http%' THEN url ELSE COALESCE(NULLIF(url, ''), NULLIF(path, ''), '') END AS href, COUNT(*) AS views, COUNT(DISTINCT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))) AS users, MAX(substr(timestamp, 1, 10)) AS last_seen FROM analytics_events WHERE event_name = 'pageview' AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io')) GROUP BY 1, 2) SELECT url, views, users, last_seen, href FROM pageviews ORDER BY views DESC LIMIT 25",
244
+ "config": {
245
+ "description": "Most-viewed URLs in the selected time range. Links open in a new tab.",
246
+ "sortable": true,
247
+ "limit": 25,
248
+ "columns": [
249
+ { "key": "url", "label": "URL", "format": "link", "linkKey": "href" },
250
+ { "key": "views", "label": "Views", "format": "number" },
251
+ { "key": "users", "label": "Users", "format": "number" },
252
+ { "key": "last_seen", "label": "Last seen", "format": "date" },
253
+ { "key": "href", "hidden": true }
254
+ ]
255
+ }
256
+ },
257
+ {
258
+ "id": "top-visited-clips",
259
+ "title": "Top Visited Clips",
260
+ "chartType": "table",
261
+ "source": "first-party",
262
+ "width": 2,
263
+ "sql": "WITH clip_events AS (SELECT COALESCE(NULLIF(properties::jsonb ->> 'recording_id', ''), NULLIF(path, ''), NULLIF(url, ''), 'unknown') AS clip_key, CASE WHEN NULLIF(url, '') LIKE 'http%' THEN url WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') WHEN NULLIF(path, '') LIKE '/%' THEN 'https://clips.agent-native.com' || path ELSE COALESCE(NULLIF(url, ''), '') END AS href, user_id, anonymous_id, timestamp FROM analytics_events WHERE event_name = 'share_view' AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io')) AND COALESCE(NULLIF(properties::jsonb ->> 'surface', ''), 'clip') = 'clip'), clip_views AS (SELECT clip_key, COALESCE(MAX(NULLIF(href, '')), clip_key) AS href, COUNT(*) AS views, COUNT(DISTINCT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))) AS users, MAX(substr(timestamp, 1, 10)) AS last_seen FROM clip_events GROUP BY clip_key) SELECT CASE WHEN href LIKE 'http%' THEN href ELSE clip_key END AS clip, views, users, last_seen, href FROM clip_views ORDER BY views DESC LIMIT 25",
264
+ "config": {
265
+ "description": "Most-viewed shared Clips pages in the selected time range.",
266
+ "sortable": true,
267
+ "limit": 25,
268
+ "columns": [
269
+ {
270
+ "key": "clip",
271
+ "label": "Clip URL",
272
+ "format": "link",
273
+ "linkKey": "href"
274
+ },
275
+ { "key": "views", "label": "Views", "format": "number" },
276
+ { "key": "users", "label": "Users", "format": "number" },
277
+ { "key": "last_seen", "label": "Last seen", "format": "date" },
278
+ { "key": "href", "hidden": true }
279
+ ]
280
+ }
281
+ },
282
+ {
283
+ "id": "retention-activity-section",
284
+ "title": "Retention & Active Users",
285
+ "chartType": "section",
286
+ "width": 1,
287
+ "columns": 4,
288
+ "config": {
289
+ "description": "Repeat users, first-seen cohort retention, and distinct active user trends. These panels honor the dashboard time range and email filters."
290
+ }
291
+ },
292
+ {
293
+ "id": "repeat-users",
294
+ "title": "Repeat Users",
295
+ "chartType": "metric",
296
+ "source": "first-party",
297
+ "width": 1,
298
+ "sql": "WITH user_days AS (SELECT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) AS user_key, COUNT(DISTINCT substr(timestamp, 1, 10)) AS active_days FROM analytics_events WHERE COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) IS NOT NULL AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io')) GROUP BY COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))) SELECT COUNT(*) AS count FROM user_days WHERE active_days >= 2",
299
+ "config": {
300
+ "yKey": "count",
301
+ "yFormatter": "number",
302
+ "description": "Distinct users with activity on at least two days in the selected time range. Uses user_id first, then anonymous_id."
303
+ }
304
+ },
305
+ {
306
+ "id": "retention-over-time",
307
+ "title": "1d / 7d Retention Over Time",
308
+ "chartType": "line",
309
+ "source": "first-party",
310
+ "width": 3,
311
+ "sql": "WITH base AS (SELECT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) AS user_key, substr(timestamp, 1, 10) AS event_date, user_id FROM analytics_events WHERE COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) IS NOT NULL AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io'))), first_seen AS (SELECT user_key, MIN(event_date) AS cohort_date FROM base GROUP BY user_key), cohorts AS (SELECT user_key, cohort_date FROM first_seen WHERE cohort_date <= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD') AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD')))), retention AS (SELECT c.cohort_date AS date, '1d retention' AS period, COALESCE(COUNT(DISTINCT c.user_key) FILTER (WHERE b.event_date = to_char(c.cohort_date::date + INTERVAL '1 day', 'YYYY-MM-DD'))::float / NULLIF(COUNT(DISTINCT c.user_key), 0), 0) AS rate FROM cohorts c LEFT JOIN base b ON b.user_key = c.user_key GROUP BY c.cohort_date UNION ALL SELECT c.cohort_date AS date, '7d retention' AS period, COALESCE(COUNT(DISTINCT c.user_key) FILTER (WHERE b.event_date = to_char(c.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD'))::float / NULLIF(COUNT(DISTINCT c.user_key), 0), 0) AS rate FROM cohorts c LEFT JOIN base b ON b.user_key = c.user_key GROUP BY c.cohort_date) SELECT date, period, rate FROM retention ORDER BY date, period",
312
+ "config": {
313
+ "xKey": "date",
314
+ "yKey": "rate",
315
+ "yFormatter": "percent",
316
+ "pivot": {
317
+ "xKey": "date",
318
+ "seriesKey": "period",
319
+ "valueKey": "rate"
320
+ },
321
+ "colors": ["#10b981", "#8b5cf6"],
322
+ "description": "First-seen daily cohorts in the selected range. Cohorts must be at least seven days old so both lines are mature."
323
+ }
324
+ },
325
+ {
326
+ "id": "one-day-retention-by-template",
327
+ "title": "1d Retention by Template",
328
+ "chartType": "line",
329
+ "source": "first-party",
330
+ "width": 2,
331
+ "sql": "WITH base AS (SELECT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) AS user_key, COALESCE(NULLIF(template, ''), NULLIF(properties::jsonb ->> 'templateId', ''), NULLIF(properties::jsonb ->> 'agent_native_template', ''), NULLIF(properties::jsonb ->> 'agentNativeTemplate', ''), NULLIF(app, ''), NULLIF(properties::jsonb ->> 'agent_native_app', ''), NULLIF(properties::jsonb ->> 'agentNativeApp', ''), 'unknown') AS template, substr(timestamp, 1, 10) AS event_date, user_id FROM analytics_events WHERE COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) IS NOT NULL AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io'))), first_seen AS (SELECT user_key, template, MIN(event_date) AS cohort_date FROM base GROUP BY user_key, template), cohorts AS (SELECT user_key, template, cohort_date FROM first_seen WHERE cohort_date <= to_char(CURRENT_DATE - INTERVAL '1 day', 'YYYY-MM-DD') AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD')))) SELECT c.cohort_date AS date, c.template, COALESCE(COUNT(DISTINCT c.user_key) FILTER (WHERE b.event_date = to_char(c.cohort_date::date + INTERVAL '1 day', 'YYYY-MM-DD'))::float / NULLIF(COUNT(DISTINCT c.user_key), 0), 0) AS rate FROM cohorts c LEFT JOIN base b ON b.user_key = c.user_key AND b.template = c.template GROUP BY c.cohort_date, c.template ORDER BY c.cohort_date, c.template",
332
+ "config": {
333
+ "xKey": "date",
334
+ "yKey": "rate",
335
+ "yFormatter": "percent",
336
+ "pivot": {
337
+ "xKey": "date",
338
+ "seriesKey": "template",
339
+ "valueKey": "rate"
340
+ },
341
+ "description": "Per-template first-seen cohorts in the selected range. A user is retained when they return to the same template the next day."
342
+ }
343
+ },
344
+ {
345
+ "id": "seven-day-retention-by-template",
346
+ "title": "7d Retention by Template",
347
+ "chartType": "line",
348
+ "source": "first-party",
349
+ "width": 2,
350
+ "sql": "WITH base AS (SELECT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) AS user_key, COALESCE(NULLIF(template, ''), NULLIF(properties::jsonb ->> 'templateId', ''), NULLIF(properties::jsonb ->> 'agent_native_template', ''), NULLIF(properties::jsonb ->> 'agentNativeTemplate', ''), NULLIF(app, ''), NULLIF(properties::jsonb ->> 'agent_native_app', ''), NULLIF(properties::jsonb ->> 'agentNativeApp', ''), 'unknown') AS template, substr(timestamp, 1, 10) AS event_date, user_id FROM analytics_events WHERE COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) IS NOT NULL AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io'))), first_seen AS (SELECT user_key, template, MIN(event_date) AS cohort_date FROM base GROUP BY user_key, template), cohorts AS (SELECT user_key, template, cohort_date FROM first_seen WHERE cohort_date <= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD') AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD')))) SELECT c.cohort_date AS date, c.template, COALESCE(COUNT(DISTINCT c.user_key) FILTER (WHERE b.event_date = to_char(c.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD'))::float / NULLIF(COUNT(DISTINCT c.user_key), 0), 0) AS rate FROM cohorts c LEFT JOIN base b ON b.user_key = c.user_key AND b.template = c.template GROUP BY c.cohort_date, c.template ORDER BY c.cohort_date, c.template",
351
+ "config": {
352
+ "xKey": "date",
353
+ "yKey": "rate",
354
+ "yFormatter": "percent",
355
+ "pivot": {
356
+ "xKey": "date",
357
+ "seriesKey": "template",
358
+ "valueKey": "rate"
359
+ },
360
+ "description": "Per-template first-seen cohorts in the selected range. A user is retained when they return to the same template seven days later."
361
+ }
362
+ },
363
+ {
364
+ "id": "dau-over-time",
365
+ "title": "DAU by Template",
366
+ "chartType": "area",
367
+ "source": "first-party",
368
+ "width": 2,
369
+ "sql": "SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') AS template, COUNT(DISTINCT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))) AS users FROM analytics_events WHERE COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) IS NOT NULL AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io')) GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') ORDER BY date, template",
370
+ "config": {
371
+ "xKey": "date",
372
+ "yKey": "users",
373
+ "yFormatter": "number",
374
+ "pivot": {
375
+ "xKey": "date",
376
+ "seriesKey": "template",
377
+ "valueKey": "users"
378
+ },
379
+ "stacked": true,
380
+ "description": "Distinct active users per day in the selected time range, stacked by template. Uses user_id first, then anonymous_id."
381
+ }
382
+ },
383
+ {
384
+ "id": "wau-over-time",
385
+ "title": "WAU by Template",
386
+ "chartType": "area",
387
+ "source": "first-party",
388
+ "width": 2,
389
+ "sql": "WITH base AS (SELECT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) AS user_key, COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') AS template, substr(timestamp, 1, 10) AS event_date, user_id FROM analytics_events WHERE COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) IS NOT NULL AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io'))), days AS (SELECT DISTINCT event_date AS date FROM base WHERE ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND event_date >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND event_date >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND event_date >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND event_date >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND event_date >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD')))) SELECT d.date, b.template, COUNT(DISTINCT b.user_key) AS users FROM days d JOIN base b ON b.event_date >= to_char(d.date::date - INTERVAL '6 days', 'YYYY-MM-DD') AND b.event_date <= d.date GROUP BY d.date, b.template ORDER BY d.date, b.template",
390
+ "config": {
391
+ "xKey": "date",
392
+ "yKey": "users",
393
+ "yFormatter": "number",
394
+ "pivot": {
395
+ "xKey": "date",
396
+ "seriesKey": "template",
397
+ "valueKey": "users"
398
+ },
399
+ "stacked": true,
400
+ "description": "Trailing 7-day distinct active users for each active date in the selected time range, stacked by template."
401
+ }
402
+ },
191
403
  {
192
404
  "id": "virality-referrals-section",
193
405
  "title": "Virality & Referrals",
@@ -920,7 +920,7 @@ export const dashboardCatalogEntries: DashboardCatalogEntry[] = [
920
920
  id: "first-party-template-traffic",
921
921
  name: "First-party Template Traffic",
922
922
  description:
923
- "Template signups, clicks, demo starts, CLI copies, and first-party session activity.",
923
+ "Template signups, clicks, demo starts, CLI copies, activity, top URLs, top clips, retention by template, and active users over time.",
924
924
  category: "Product",
925
925
  defaultDashboardId: "agent-native-templates-first-party",
926
926
  dataSources: ["first-party"],
@@ -929,10 +929,15 @@ export const dashboardCatalogEntries: DashboardCatalogEntry[] = [
929
929
  "traffic",
930
930
  "signups",
931
931
  "sessions",
932
+ "retention",
933
+ "active users",
934
+ "pageviews",
935
+ "urls",
936
+ "clips",
932
937
  "referrals",
933
938
  "virality",
934
939
  ],
935
- panelCount: 25,
940
+ panelCount: 33,
936
941
  version: CATALOG_VERSION,
937
942
  recommended: true,
938
943
  buildConfig: () => seedConfig("agent-native-templates-first-party"),
@@ -203,6 +203,36 @@ function urlParts(url: string | null): {
203
203
  }
204
204
  }
205
205
 
206
+ export function resolveAnalyticsEventDimensions({
207
+ properties,
208
+ context,
209
+ hostname,
210
+ }: {
211
+ properties: Record<string, unknown>;
212
+ context: Record<string, unknown>;
213
+ hostname: string | null;
214
+ }): { app: string | null; template: string | null } {
215
+ const app =
216
+ asString(properties.app) ||
217
+ asString((properties as any).agent_native_app) ||
218
+ asString((properties as any).agentNativeApp) ||
219
+ asString((context as any).app) ||
220
+ asString((context as any).agent_native_app) ||
221
+ asString((context as any).agentNativeApp) ||
222
+ (hostname ? hostname.split(".")[0] : null);
223
+ const template =
224
+ asString(properties.template) ||
225
+ asString((properties as any).templateId) ||
226
+ asString((properties as any).agent_native_template) ||
227
+ asString((properties as any).agentNativeTemplate) ||
228
+ asString((context as any).template) ||
229
+ asString((context as any).templateId) ||
230
+ asString((context as any).agent_native_template) ||
231
+ asString((context as any).agentNativeTemplate) ||
232
+ app;
233
+ return { app, template };
234
+ }
235
+
206
236
  export function parseAnalyticsTrackPayload(raw: unknown): {
207
237
  publicKey: string;
208
238
  events: IncomingAnalyticsEvent[];
@@ -279,14 +309,11 @@ export async function recordAnalyticsEvents(
279
309
  parts.hostname ||
280
310
  asString(properties.hostname) ||
281
311
  asString((context as any).hostname);
282
- const app =
283
- asString(properties.app) ||
284
- asString((context as any).app) ||
285
- (hostname ? hostname.split(".")[0] : null);
286
- const template =
287
- asString(properties.template) ||
288
- asString((properties as any).templateId) ||
289
- app;
312
+ const { app, template } = resolveAnalyticsEventDimensions({
313
+ properties,
314
+ context,
315
+ hostname,
316
+ });
290
317
  const signedIn =
291
318
  asString((properties as any).signed_in) ||
292
319
  asString((properties as any).signedIn) ||