@agent-native/core 0.72.4 → 0.74.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 (129) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +67 -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 +1 -1
  6. package/corpus/core/src/agent/durable-background.ts +35 -12
  7. package/corpus/core/src/agent/production-agent.ts +55 -8
  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/ConnectBuilderCard.tsx +7 -7
  11. package/corpus/core/src/client/NewWorkspaceAppFlow.tsx +1 -1
  12. package/corpus/core/src/client/blocks/library/AnnotatedCodeBlock.tsx +33 -18
  13. package/corpus/core/src/client/blocks/library/annotation-rail.tsx +3 -4
  14. package/corpus/core/src/client/components/CodeRequiredDialog.tsx +5 -8
  15. package/corpus/core/src/server/agent-chat-plugin.ts +7 -7
  16. package/corpus/core/src/server/auth.ts +7 -0
  17. package/corpus/core/src/server/onboarding-html.ts +95 -9
  18. package/corpus/core/src/server/self-dispatch.ts +8 -1
  19. package/corpus/core/src/templates/default/AGENTS.md +4 -0
  20. package/corpus/core/src/templates/default/package.json +2 -1
  21. package/corpus/core/src/templates/headless/AGENTS.md +4 -0
  22. package/corpus/core/src/templates/headless/package.json +1 -0
  23. package/corpus/core/src/templates/workspace-root/AGENTS.md +4 -0
  24. package/corpus/core/src/templates/workspace-root/README.md +4 -0
  25. package/corpus/core/src/templates/workspace-root/package.json +1 -0
  26. package/corpus/templates/analytics/actions/compose-dashboard.ts +34 -0
  27. package/corpus/templates/analytics/actions/install-dashboard-template.ts +51 -3
  28. package/corpus/templates/analytics/actions/update-dashboard.ts +3 -4
  29. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +175 -48
  30. package/corpus/templates/analytics/app/global.css +15 -13
  31. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +9 -32
  32. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +0 -26
  33. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +0 -23
  34. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/pivot.ts +65 -2
  35. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +3 -4
  36. package/corpus/templates/analytics/changelog/2026-06-24-dashboard-charts-now-fill-sparse-template-time-series-suppor.md +6 -0
  37. package/corpus/templates/analytics/changelog/2026-06-24-dashboard-filters-now-apply-consistently-to-first-party-traf.md +6 -0
  38. package/corpus/templates/analytics/changelog/2026-06-24-dashboard-rows-now-auto-fit-panels-so-one-two-or-three-chart.md +6 -0
  39. package/corpus/templates/analytics/changelog/2026-06-24-dashboard-traffic-tables-now-show-top-urls-and-shared-clips-.md +6 -0
  40. package/corpus/templates/analytics/changelog/2026-06-24-first-party-signup-and-pageview-panels-now-honor-dashboard-filters.md +5 -0
  41. package/corpus/templates/analytics/changelog/2026-06-24-first-party-template-dashboards-now-include-repeat-users-ret.md +6 -0
  42. package/corpus/templates/analytics/changelog/2026-06-24-retention-charts-now-break-out-1-day-and-7-day-return-rates-.md +6 -0
  43. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +330 -49
  44. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +7 -2
  45. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +35 -8
  46. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +337 -45
  47. package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +1 -1
  48. package/corpus/templates/calendar/server/plugins/auth.ts +12 -0
  49. package/corpus/templates/clips/.agents/skills/recording/SKILL.md +7 -7
  50. package/corpus/templates/clips/AGENTS.md +6 -2
  51. package/corpus/templates/clips/app/components/capture-install-options.tsx +20 -4
  52. package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -0
  53. package/corpus/templates/clips/app/components/player/player-controls.tsx +62 -32
  54. package/corpus/templates/clips/app/lib/capture-install-options.ts +39 -5
  55. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +786 -323
  56. package/corpus/templates/clips/app/routes/download.tsx +2 -2
  57. package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -1
  58. package/corpus/templates/clips/changelog/2026-06-24-inline-slack-playback-controls-no-longer-shift-when-adjustin.md +6 -0
  59. package/corpus/templates/clips/changelog/2026-06-24-settings-now-puts-what-s-new-in-a-compact-sidebar-prioritize.md +6 -0
  60. package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-console-log-stream-all-le.md +6 -0
  61. package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-fetch-xhr-network-request.md +6 -0
  62. package/corpus/templates/clips/changelog/2026-06-24-the-chrome-extension-option-now-only-appears-on-supported-hosts.md +5 -0
  63. package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +13 -8
  64. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  65. package/corpus/templates/clips/chrome-extension/scripts/package.ts +15 -2
  66. package/corpus/templates/clips/chrome-extension/src/overlay.css +4 -25
  67. package/corpus/templates/clips/chrome-extension/src/overlay.ts +1 -3
  68. package/corpus/templates/clips/server/lib/public-agent-context.ts +67 -24
  69. package/corpus/templates/mail/server/plugins/auth.ts +4 -1
  70. package/corpus/templates/plan/app/pages/PlansPage.tsx +271 -197
  71. package/corpus/templates/plan/changelog/2026-06-24-annotated-code-callouts-now-use-numbered-gutter-markers-that.md +6 -0
  72. package/corpus/templates/plan/server/plan-content.ts +45 -2
  73. package/dist/agent/durable-background.d.ts +9 -4
  74. package/dist/agent/durable-background.d.ts.map +1 -1
  75. package/dist/agent/durable-background.js +34 -11
  76. package/dist/agent/durable-background.js.map +1 -1
  77. package/dist/agent/production-agent.d.ts.map +1 -1
  78. package/dist/agent/production-agent.js +52 -8
  79. package/dist/agent/production-agent.js.map +1 -1
  80. package/dist/cli/create.js +1 -1
  81. package/dist/cli/create.js.map +1 -1
  82. package/dist/cli/skills.d.ts.map +1 -1
  83. package/dist/cli/skills.js +361 -19
  84. package/dist/cli/skills.js.map +1 -1
  85. package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
  86. package/dist/client/ConnectBuilderCard.js +6 -6
  87. package/dist/client/ConnectBuilderCard.js.map +1 -1
  88. package/dist/client/NewWorkspaceAppFlow.js +1 -1
  89. package/dist/client/NewWorkspaceAppFlow.js.map +1 -1
  90. package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
  91. package/dist/client/blocks/library/AnnotatedCodeBlock.js +14 -10
  92. package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
  93. package/dist/client/blocks/library/annotation-rail.d.ts +3 -4
  94. package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -1
  95. package/dist/client/blocks/library/annotation-rail.js +3 -4
  96. package/dist/client/blocks/library/annotation-rail.js.map +1 -1
  97. package/dist/client/components/CodeRequiredDialog.d.ts.map +1 -1
  98. package/dist/client/components/CodeRequiredDialog.js +2 -1
  99. package/dist/client/components/CodeRequiredDialog.js.map +1 -1
  100. package/dist/server/agent-chat-plugin.js +7 -7
  101. package/dist/server/agent-chat-plugin.js.map +1 -1
  102. package/dist/server/auth.d.ts +7 -0
  103. package/dist/server/auth.d.ts.map +1 -1
  104. package/dist/server/auth.js +1 -0
  105. package/dist/server/auth.js.map +1 -1
  106. package/dist/server/onboarding-html.d.ts +15 -0
  107. package/dist/server/onboarding-html.d.ts.map +1 -1
  108. package/dist/server/onboarding-html.js +73 -9
  109. package/dist/server/onboarding-html.js.map +1 -1
  110. package/dist/server/self-dispatch.d.ts.map +1 -1
  111. package/dist/server/self-dispatch.js +5 -1
  112. package/dist/server/self-dispatch.js.map +1 -1
  113. package/dist/templates/default/AGENTS.md +4 -0
  114. package/dist/templates/default/package.json +2 -1
  115. package/dist/templates/headless/AGENTS.md +4 -0
  116. package/dist/templates/headless/package.json +1 -0
  117. package/dist/templates/workspace-root/AGENTS.md +4 -0
  118. package/dist/templates/workspace-root/README.md +4 -0
  119. package/dist/templates/workspace-root/package.json +1 -0
  120. package/docs/content/skills-guide.md +14 -0
  121. package/docs/content/template-clips.md +7 -1
  122. package/package.json +1 -1
  123. package/src/templates/default/AGENTS.md +4 -0
  124. package/src/templates/default/package.json +2 -1
  125. package/src/templates/headless/AGENTS.md +4 -0
  126. package/src/templates/headless/package.json +1 -0
  127. package/src/templates/workspace-root/AGENTS.md +4 -0
  128. package/src/templates/workspace-root/README.md +4 -0
  129. package/src/templates/workspace-root/package.json +1 -0
@@ -1,18 +1,72 @@
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
+ {
12
+ "value": "7d",
13
+ "label": "Last 7 days"
14
+ },
15
+ {
16
+ "value": "30d",
17
+ "label": "Last 30 days"
18
+ },
19
+ {
20
+ "value": "90d",
21
+ "label": "Last 90 days"
22
+ },
23
+ {
24
+ "value": "180d",
25
+ "label": "Last 180 days"
26
+ },
27
+ {
28
+ "value": "365d",
29
+ "label": "Last 365 days"
30
+ },
31
+ {
32
+ "value": "all",
33
+ "label": "All time"
34
+ }
35
+ ]
36
+ },
37
+ {
38
+ "id": "emailFilter",
39
+ "type": "select",
40
+ "label": "Email filter",
41
+ "default": "all",
42
+ "options": [
43
+ {
44
+ "value": "all",
45
+ "label": "All users"
46
+ },
47
+ {
48
+ "value": "exclude_builder",
49
+ "label": "Exclude @builder.io"
50
+ },
51
+ {
52
+ "value": "only_builder",
53
+ "label": "Only @builder.io"
54
+ }
55
+ ]
56
+ }
57
+ ],
4
58
  "panels": [
5
59
  {
6
60
  "id": "total-signups",
7
- "title": "Signups",
61
+ "title": "Total Signups",
8
62
  "chartType": "metric",
9
63
  "source": "first-party",
10
64
  "width": 1,
11
- "sql": "SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup'",
65
+ "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
66
  "config": {
13
- "yKey": "count",
67
+ "yKey": "signups",
14
68
  "yFormatter": "number",
15
- "description": "Better Auth user creation events from all Agent Native templates"
69
+ "description": "Total signup events in the selected time range."
16
70
  }
17
71
  },
18
72
  {
@@ -21,12 +75,18 @@
21
75
  "chartType": "area",
22
76
  "source": "first-party",
23
77
  "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",
78
+ "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
79
  "config": {
26
80
  "xKey": "date",
27
81
  "yKey": "count",
28
- "color": "#8b5cf6",
29
- "description": "Daily signup events across all Agent Native templates"
82
+ "yFormatter": "number",
83
+ "pivot": {
84
+ "xKey": "date",
85
+ "seriesKey": "template",
86
+ "valueKey": "count"
87
+ },
88
+ "stacked": true,
89
+ "description": "Daily signup events stacked by inferred template/app."
30
90
  }
31
91
  },
32
92
  {
@@ -34,13 +94,14 @@
34
94
  "title": "Signups by Template",
35
95
  "chartType": "bar",
36
96
  "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",
97
+ "width": 2,
98
+ "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
99
  "config": {
40
100
  "xKey": "template",
41
101
  "yKey": "count",
42
- "color": "#8b5cf6",
43
- "description": "Signup events grouped by inferred template/app"
102
+ "yFormatter": "number",
103
+ "color": "var(--brand-purple)",
104
+ "description": "Signup events grouped by inferred template/app."
44
105
  }
45
106
  },
46
107
  {
@@ -49,7 +110,7 @@
49
110
  "chartType": "metric",
50
111
  "source": "first-party",
51
112
  "width": 1,
52
- "sql": "SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template'",
113
+ "sql": "SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' 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'))",
53
114
  "config": {
54
115
  "yKey": "count",
55
116
  "yFormatter": "number",
@@ -62,7 +123,7 @@
62
123
  "chartType": "metric",
63
124
  "source": "first-party",
64
125
  "width": 1,
65
- "sql": "SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'click try demo'",
126
+ "sql": "SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'click try demo' 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'))",
66
127
  "config": {
67
128
  "yKey": "count",
68
129
  "yFormatter": "number",
@@ -75,7 +136,7 @@
75
136
  "chartType": "metric",
76
137
  "source": "first-party",
77
138
  "width": 1,
78
- "sql": "SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command'",
139
+ "sql": "SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' 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'))",
79
140
  "config": {
80
141
  "yKey": "count",
81
142
  "yFormatter": "number",
@@ -88,63 +149,75 @@
88
149
  "chartType": "area",
89
150
  "source": "first-party",
90
151
  "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",
152
+ "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' 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, ''), 'unknown') ORDER BY date, template",
92
153
  "config": {
93
154
  "xKey": "date",
94
155
  "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)"
156
+ "yFormatter": "number",
157
+ "pivot": {
158
+ "xKey": "date",
159
+ "seriesKey": "template",
160
+ "valueKey": "count"
161
+ },
162
+ "stacked": true
109
163
  }
110
164
  },
111
165
  {
112
- "id": "demo-clicks-by-template",
113
- "title": "Try-Demo Clicks by Template",
114
- "chartType": "bar",
166
+ "id": "demo-clicks-over-time",
167
+ "title": "Try-Demo Clicks Over Time",
168
+ "chartType": "area",
115
169
  "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",
170
+ "width": 2,
171
+ "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' 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, ''), 'unknown') ORDER BY date, template",
118
172
  "config": {
119
- "xKey": "template",
173
+ "xKey": "date",
120
174
  "yKey": "count",
121
- "color": "var(--brand-teal)"
175
+ "yFormatter": "number",
176
+ "pivot": {
177
+ "xKey": "date",
178
+ "seriesKey": "template",
179
+ "valueKey": "count"
180
+ },
181
+ "stacked": true
122
182
  }
123
183
  },
124
184
  {
125
- "id": "cli-copies-by-template",
126
- "title": "CLI Copies by Template",
127
- "chartType": "bar",
185
+ "id": "cli-copies-over-time",
186
+ "title": "CLI Copies Over Time",
187
+ "chartType": "area",
128
188
  "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",
189
+ "width": 2,
190
+ "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' 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, ''), 'unknown') ORDER BY date, template",
131
191
  "config": {
132
- "xKey": "template",
192
+ "xKey": "date",
133
193
  "yKey": "count",
134
- "color": "#06b6d4"
194
+ "yFormatter": "number",
195
+ "pivot": {
196
+ "xKey": "date",
197
+ "seriesKey": "template",
198
+ "valueKey": "count"
199
+ },
200
+ "stacked": true
135
201
  }
136
202
  },
137
203
  {
138
- "id": "cli-copies-over-time",
139
- "title": "CLI Copies Over Time",
204
+ "id": "pageviews-over-time",
205
+ "title": "Pageviews Over Time",
140
206
  "chartType": "area",
141
207
  "source": "first-party",
142
208
  "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",
209
+ "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
210
  "config": {
145
211
  "xKey": "date",
146
212
  "yKey": "count",
147
- "color": "#06b6d4"
213
+ "yFormatter": "number",
214
+ "pivot": {
215
+ "xKey": "date",
216
+ "seriesKey": "template",
217
+ "valueKey": "count"
218
+ },
219
+ "stacked": true,
220
+ "description": "Pageview events per day stacked by inferred template/app."
148
221
  }
149
222
  },
150
223
  {
@@ -188,6 +261,207 @@
188
261
  "color": "#10b981"
189
262
  }
190
263
  },
264
+ {
265
+ "id": "top-visited-urls",
266
+ "title": "Top Visited URLs",
267
+ "chartType": "table",
268
+ "source": "first-party",
269
+ "width": 2,
270
+ "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 lower(COALESCE(NULLIF(url, ''), '')) LIKE 'http://%' OR lower(COALESCE(NULLIF(url, ''), '')) LIKE 'https://%' THEN url WHEN NULLIF(path, '') IS NOT NULL AND substr(path, 1, 1) = '/' AND substr(path, 1, 2) != '//' THEN path ELSE '' 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",
271
+ "config": {
272
+ "description": "Most-viewed URLs in the selected time range. Links open in a new tab.",
273
+ "sortable": true,
274
+ "limit": 25,
275
+ "columns": [
276
+ {
277
+ "key": "url",
278
+ "label": "URL",
279
+ "format": "link",
280
+ "linkKey": "href"
281
+ },
282
+ {
283
+ "key": "views",
284
+ "label": "Views",
285
+ "format": "number"
286
+ },
287
+ {
288
+ "key": "users",
289
+ "label": "Users",
290
+ "format": "number"
291
+ },
292
+ {
293
+ "key": "last_seen",
294
+ "label": "Last seen",
295
+ "format": "date"
296
+ },
297
+ {
298
+ "key": "href",
299
+ "hidden": true
300
+ }
301
+ ]
302
+ }
303
+ },
304
+ {
305
+ "id": "top-visited-clips",
306
+ "title": "Top Visited Clips",
307
+ "chartType": "table",
308
+ "source": "first-party",
309
+ "width": 2,
310
+ "sql": "WITH clip_events AS (SELECT COALESCE(NULLIF(properties::jsonb ->> 'recording_id', ''), NULLIF(path, ''), NULLIF(url, ''), 'unknown') AS clip_key, CASE WHEN lower(COALESCE(NULLIF(url, ''), '')) LIKE 'http://%' OR lower(COALESCE(NULLIF(url, ''), '')) LIKE 'https://%' 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 '' 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 lower(COALESCE(href, '')) LIKE 'http://%' OR lower(COALESCE(href, '')) LIKE 'https://%' THEN href ELSE clip_key END AS clip, views, users, last_seen, href FROM clip_views ORDER BY views DESC LIMIT 25",
311
+ "config": {
312
+ "description": "Most-viewed shared Clips pages in the selected time range.",
313
+ "sortable": true,
314
+ "limit": 25,
315
+ "columns": [
316
+ {
317
+ "key": "clip",
318
+ "label": "Clip URL",
319
+ "format": "link",
320
+ "linkKey": "href"
321
+ },
322
+ {
323
+ "key": "views",
324
+ "label": "Views",
325
+ "format": "number"
326
+ },
327
+ {
328
+ "key": "users",
329
+ "label": "Users",
330
+ "format": "number"
331
+ },
332
+ {
333
+ "key": "last_seen",
334
+ "label": "Last seen",
335
+ "format": "date"
336
+ },
337
+ {
338
+ "key": "href",
339
+ "hidden": true
340
+ }
341
+ ]
342
+ }
343
+ },
344
+ {
345
+ "id": "retention-activity-section",
346
+ "title": "Retention & Active Users",
347
+ "chartType": "section",
348
+ "width": 1,
349
+ "columns": 4,
350
+ "config": {
351
+ "description": "Repeat users, first-seen cohort retention, and distinct active user trends. These panels honor the dashboard time range and email filters."
352
+ }
353
+ },
354
+ {
355
+ "id": "repeat-users",
356
+ "title": "Repeat Users",
357
+ "chartType": "metric",
358
+ "source": "first-party",
359
+ "width": 1,
360
+ "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",
361
+ "config": {
362
+ "yKey": "count",
363
+ "yFormatter": "number",
364
+ "description": "Distinct users with activity on at least two days in the selected time range. Uses user_id first, then anonymous_id."
365
+ }
366
+ },
367
+ {
368
+ "id": "retention-over-time",
369
+ "title": "1d / 7d Retention Over Time",
370
+ "chartType": "line",
371
+ "source": "first-party",
372
+ "width": 3,
373
+ "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",
374
+ "config": {
375
+ "xKey": "date",
376
+ "yKey": "rate",
377
+ "yFormatter": "percent",
378
+ "pivot": {
379
+ "xKey": "date",
380
+ "seriesKey": "period",
381
+ "valueKey": "rate"
382
+ },
383
+ "colors": ["#10b981", "#8b5cf6"],
384
+ "description": "First-seen daily cohorts in the selected range. Cohorts must be at least seven days old so both lines are mature."
385
+ }
386
+ },
387
+ {
388
+ "id": "one-day-retention-by-template",
389
+ "title": "1d Retention by Template",
390
+ "chartType": "line",
391
+ "source": "first-party",
392
+ "width": 2,
393
+ "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",
394
+ "config": {
395
+ "xKey": "date",
396
+ "yKey": "rate",
397
+ "yFormatter": "percent",
398
+ "pivot": {
399
+ "xKey": "date",
400
+ "seriesKey": "template",
401
+ "valueKey": "rate"
402
+ },
403
+ "description": "Per-template first-seen cohorts in the selected range. A user is retained when they return to the same template the next day."
404
+ }
405
+ },
406
+ {
407
+ "id": "seven-day-retention-by-template",
408
+ "title": "7d Retention by Template",
409
+ "chartType": "line",
410
+ "source": "first-party",
411
+ "width": 2,
412
+ "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",
413
+ "config": {
414
+ "xKey": "date",
415
+ "yKey": "rate",
416
+ "yFormatter": "percent",
417
+ "pivot": {
418
+ "xKey": "date",
419
+ "seriesKey": "template",
420
+ "valueKey": "rate"
421
+ },
422
+ "description": "Per-template first-seen cohorts in the selected range. A user is retained when they return to the same template seven days later."
423
+ }
424
+ },
425
+ {
426
+ "id": "dau-over-time",
427
+ "title": "DAU by Template",
428
+ "chartType": "area",
429
+ "source": "first-party",
430
+ "width": 2,
431
+ "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",
432
+ "config": {
433
+ "xKey": "date",
434
+ "yKey": "users",
435
+ "yFormatter": "number",
436
+ "pivot": {
437
+ "xKey": "date",
438
+ "seriesKey": "template",
439
+ "valueKey": "users"
440
+ },
441
+ "stacked": true,
442
+ "description": "Distinct active users per day in the selected time range, stacked by template. Uses user_id first, then anonymous_id."
443
+ }
444
+ },
445
+ {
446
+ "id": "wau-over-time",
447
+ "title": "WAU by Template",
448
+ "chartType": "area",
449
+ "source": "first-party",
450
+ "width": 2,
451
+ "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",
452
+ "config": {
453
+ "xKey": "date",
454
+ "yKey": "users",
455
+ "yFormatter": "number",
456
+ "pivot": {
457
+ "xKey": "date",
458
+ "seriesKey": "template",
459
+ "valueKey": "users"
460
+ },
461
+ "stacked": true,
462
+ "description": "Trailing 7-day distinct active users for each active date in the selected time range, stacked by template."
463
+ }
464
+ },
191
465
  {
192
466
  "id": "virality-referrals-section",
193
467
  "title": "Virality & Referrals",
@@ -275,8 +549,15 @@
275
549
  "config": {
276
550
  "description": "Users (by id) driving the most referred signups over the last 90 days.",
277
551
  "columns": [
278
- { "key": "referrer_user", "label": "Referrer (user id)" },
279
- { "key": "signups", "label": "Signups", "format": "number" }
552
+ {
553
+ "key": "referrer_user",
554
+ "label": "Referrer (user id)"
555
+ },
556
+ {
557
+ "key": "signups",
558
+ "label": "Signups",
559
+ "format": "number"
560
+ }
280
561
  ]
281
562
  }
282
563
  },
@@ -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) ||