@agent-native/core 0.77.16 → 0.77.17

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 (60) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +8 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/AgentPanel.tsx +4 -0
  5. package/corpus/core/src/client/org/OrgSwitcher.tsx +4 -0
  6. package/corpus/core/src/client/use-db-sync.ts +19 -2
  7. package/corpus/core/src/server/auth-marketing.ts +15 -0
  8. package/corpus/core/src/server/onboarding-html.ts +47 -1
  9. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +76 -0
  10. package/corpus/templates/analytics/actions/update-dashboard.ts +1 -0
  11. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +13 -2
  12. package/corpus/templates/analytics/app/i18n-data.ts +10 -0
  13. package/corpus/templates/analytics/app/lib/sql-query.ts +97 -21
  14. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +22 -1
  15. package/corpus/templates/analytics/app/root.tsx +15 -6
  16. package/corpus/templates/analytics/changelog/2026-06-25-dashboard-charts-no-longer-refresh-in-the-background-during-.md +6 -0
  17. package/corpus/templates/analytics/changelog/2026-06-25-first-party-dashboards-use-indexed-event-dates-for-faster-da.md +6 -0
  18. package/corpus/templates/analytics/changelog/2026-06-25-retention-and-active-user-dashboard-panels-now-count-account.md +6 -0
  19. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +58 -58
  20. package/corpus/templates/analytics/server/db/schema.ts +2 -0
  21. package/corpus/templates/analytics/server/handlers/sql-query.ts +1 -1
  22. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +1 -1
  23. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +16 -6
  24. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +88 -56
  25. package/corpus/templates/analytics/server/plugins/db.ts +73 -0
  26. package/corpus/templates/clips/AGENTS.md +5 -0
  27. package/corpus/templates/clips/changelog/2026-06-25-github-issue-and-pull-request-pages-can-now-preview-playable.md +6 -0
  28. package/corpus/templates/clips/chrome-extension/PERMISSIONS.md +8 -2
  29. package/corpus/templates/clips/chrome-extension/public/manifest.json +14 -2
  30. package/corpus/templates/clips/chrome-extension/src/github-preview-content.ts +233 -0
  31. package/corpus/templates/clips/chrome-extension/src/github-preview.html +12 -0
  32. package/corpus/templates/clips/chrome-extension/src/github-preview.ts +414 -0
  33. package/corpus/templates/clips/chrome-extension/vite.config.ts +5 -0
  34. package/corpus/templates/mail/actions/archive-email.ts +13 -3
  35. package/corpus/templates/mail/app/hooks/use-emails.ts +5 -2
  36. package/corpus/templates/mail/changelog/2026-06-25-archive-failures-now-explain-when-gmail-needs-reconnecting-p.md +6 -0
  37. package/corpus/templates/mail/shared/archive-errors.ts +137 -0
  38. package/corpus/templates/plan/changelog/2026-06-25-hosted-signup-now-shows-how-to-switch-visual-plan-to-local-files.md +6 -0
  39. package/dist/client/AgentPanel.d.ts.map +1 -1
  40. package/dist/client/AgentPanel.js +2 -1
  41. package/dist/client/AgentPanel.js.map +1 -1
  42. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  43. package/dist/client/org/OrgSwitcher.js +3 -1
  44. package/dist/client/org/OrgSwitcher.js.map +1 -1
  45. package/dist/client/use-db-sync.d.ts +9 -0
  46. package/dist/client/use-db-sync.d.ts.map +1 -1
  47. package/dist/client/use-db-sync.js +8 -1
  48. package/dist/client/use-db-sync.js.map +1 -1
  49. package/dist/collab/awareness.d.ts +2 -2
  50. package/dist/collab/awareness.d.ts.map +1 -1
  51. package/dist/observability/routes.d.ts +5 -5
  52. package/dist/server/agent-engine-api-key-route.d.ts +2 -2
  53. package/dist/server/auth-marketing.d.ts +4 -0
  54. package/dist/server/auth-marketing.d.ts.map +1 -1
  55. package/dist/server/auth-marketing.js +9 -0
  56. package/dist/server/auth-marketing.js.map +1 -1
  57. package/dist/server/onboarding-html.d.ts.map +1 -1
  58. package/dist/server/onboarding-html.js +46 -1
  59. package/dist/server/onboarding-html.js.map +1 -1
  60. package/package.json +1 -1
@@ -120,8 +120,10 @@ export const analyticsEvents = table("analytics_events", {
120
120
  eventName: text("event_name").notNull(),
121
121
  userId: text("user_id"),
122
122
  anonymousId: text("anonymous_id"),
123
+ userKey: text("user_key"),
123
124
  sessionId: text("session_id"),
124
125
  timestamp: text("timestamp").notNull(),
126
+ eventDate: text("event_date"),
125
127
  receivedAt: text("received_at").notNull().default(now()),
126
128
  url: text("url"),
127
129
  path: text("path"),
@@ -369,7 +369,7 @@ export const handleSqlQuery = defineEventHandler(async (event) => {
369
369
  } catch (error: any) {
370
370
  const message = error?.message || String(error);
371
371
  console.error(`SQL query error (${source}):`, message);
372
- setResponseStatus(event, 400);
372
+ setResponseStatus(event, /DB query timed out/i.test(message) ? 504 : 400);
373
373
  return { error: message };
374
374
  }
375
375
  });
@@ -937,7 +937,7 @@ export const dashboardCatalogEntries: DashboardCatalogEntry[] = [
937
937
  "referrals",
938
938
  "virality",
939
939
  ],
940
- panelCount: 33,
940
+ panelCount: 34,
941
941
  version: CATALOG_VERSION,
942
942
  recommended: true,
943
943
  buildConfig: () => seedConfig("agent-native-templates-first-party"),
@@ -185,6 +185,10 @@ function normalizeTimestamp(value: unknown): string {
185
185
  return nowIso();
186
186
  }
187
187
 
188
+ function eventDateFromTimestamp(timestamp: string): string {
189
+ return timestamp.slice(0, 10);
190
+ }
191
+
188
192
  function urlParts(url: string | null): {
189
193
  url: string | null;
190
194
  path: string | null;
@@ -320,18 +324,24 @@ export async function recordAnalyticsEvents(
320
324
  asString((properties as any).signedIn) ||
321
325
  asString((context as any).signed_in) ||
322
326
  asString((context as any).signedIn);
327
+ const userId = event.userId ?? asString((properties as any).userId);
328
+ const anonymousId =
329
+ event.anonymousId ??
330
+ asString((properties as any).anonymousId) ??
331
+ asString((properties as any).distinctId);
332
+ const userKey = userId || anonymousId;
333
+ const timestamp = normalizeTimestamp(event.timestamp);
323
334
 
324
335
  return {
325
336
  id: id("evt"),
326
337
  publicKeyId: key.id,
327
338
  eventName: event.event,
328
- userId: event.userId ?? asString((properties as any).userId),
329
- anonymousId:
330
- event.anonymousId ??
331
- asString((properties as any).anonymousId) ??
332
- asString((properties as any).distinctId),
339
+ userId,
340
+ anonymousId,
341
+ userKey,
333
342
  sessionId: event.sessionId ?? asString((properties as any).sessionId),
334
- timestamp: normalizeTimestamp(event.timestamp),
343
+ timestamp,
344
+ eventDate: eventDateFromTimestamp(timestamp),
335
345
  receivedAt,
336
346
  url: parts.url,
337
347
  path: parts.path ?? asString(properties.path),
@@ -11,10 +11,11 @@
11
11
  * already ships (and is exercised) in the
12
12
  * `agent-native-templates-first-party` seed. See `compose-dashboard.ts`.
13
13
  *
14
- * The SQL here is copied verbatim from
15
- * `seeds/dashboards/agent-native-templates-first-party.json` do NOT re-invent
16
- * it. Windowed metrics swap the `interval 'N days'` literal for the requested
17
- * window via `buildSql(window)`.
14
+ * Default seed panels with matching catalog keys are generated from this file.
15
+ * The seed can also include layout-only section panels, and the catalog can keep
16
+ * extra reusable panels that are not on the default dashboard. Windowed metrics
17
+ * swap the `interval 'N days'` literal for the requested window via
18
+ * `buildSql(window)`.
18
19
  *
19
20
  * Distinct from the `<data-dictionary>` (the user/org-scoped catalog of business
20
21
  * metric definitions the agent consults before writing ad-hoc SQL). That is a
@@ -62,14 +63,28 @@ const WINDOW_DAYS: Record<Exclude<MetricWindow, "all">, number> = {
62
63
  * Apply a window to SQL that contains `interval 'N days'` clauses.
63
64
  *
64
65
  * - "30d" / "90d": replace every `interval '<n> days'` with the requested days.
65
- * - "all": strip the entire `AND timestamp::timestamptz >= now() - interval 'N days'`
66
- * clause so the metric covers all time. (Only the standard
67
- * `>= now() - interval` window clause is removed; any other interval usage is
68
- * left intact.)
66
+ * - "all": strip the standard date-window clause so the metric covers all
67
+ * time. (Other interval usage is left intact.)
69
68
  */
70
69
  function applyWindow(sql: string, window: MetricWindow): string {
71
70
  if (window === "all") {
72
71
  return sql
72
+ .replace(
73
+ /\s+AND\s+substr\s*\(\s*timestamp\s*,\s*1\s*,\s*10\s*\)\s*>=\s*to_char\s*\(\s*CURRENT_DATE\s*-\s*INTERVAL\s*'\d+\s*days?'\s*,\s*'YYYY-MM-DD'\s*\)/gi,
74
+ "",
75
+ )
76
+ .replace(
77
+ /\s+WHERE\s+substr\s*\(\s*timestamp\s*,\s*1\s*,\s*10\s*\)\s*>=\s*to_char\s*\(\s*CURRENT_DATE\s*-\s*INTERVAL\s*'\d+\s*days?'\s*,\s*'YYYY-MM-DD'\s*\)\s+AND\s+/gi,
78
+ " WHERE ",
79
+ )
80
+ .replace(
81
+ /\s+AND\s+event_date\s*>=\s*to_char\s*\(\s*CURRENT_DATE\s*-\s*INTERVAL\s*'\d+\s*days?'\s*,\s*'YYYY-MM-DD'\s*\)/gi,
82
+ "",
83
+ )
84
+ .replace(
85
+ /\s+WHERE\s+event_date\s*>=\s*to_char\s*\(\s*CURRENT_DATE\s*-\s*INTERVAL\s*'\d+\s*days?'\s*,\s*'YYYY-MM-DD'\s*\)\s+AND\s+/gi,
86
+ " WHERE ",
87
+ )
73
88
  .replace(
74
89
  /\s+AND\s+timestamp::timestamptz\s*>=\s*now\(\)\s*-\s*interval\s*'\d+\s*days?'/gi,
75
90
  "",
@@ -99,19 +114,33 @@ function fixed(sql: string): (window?: MetricWindow) => string {
99
114
  const TEMPLATE_EXPR =
100
115
  "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')";
101
116
  const KNOWN_TEMPLATE_FILTER = `${TEMPLATE_EXPR} <> 'unknown'`;
102
- const LOCAL_EVENT_DATE_SQL = "substr(timestamp, 1, 10)";
117
+ const PRODUCT_ACTIVITY_TEMPLATE_FILTER = `${TEMPLATE_EXPR} <> 'docs'`;
118
+ const KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER = `${KNOWN_TEMPLATE_FILTER} AND ${PRODUCT_ACTIVITY_TEMPLATE_FILTER}`;
119
+ const EVENT_DATE_SQL = "event_date";
120
+ const EVENT_DATE_FILTER_SQL = EVENT_DATE_SQL;
121
+ const USER_KEY_SQL = "NULLIF(user_key, '')";
122
+ const RETENTION_ROLLING_DAYS = 7;
123
+ const RETENTION_MIN_COHORT_SIZE = 5;
103
124
 
104
125
  function daysAgoSql(days: number): string {
105
126
  const unit = days === 1 ? "day" : "days";
106
127
  return `to_char(CURRENT_DATE - INTERVAL '${days} ${unit}', 'YYYY-MM-DD')`;
107
128
  }
108
129
 
109
- function dashboardTimeRangeFilter(dateExpr = LOCAL_EVENT_DATE_SQL): string {
130
+ function windowStartFilter(days: number): string {
131
+ return `${EVENT_DATE_SQL} >= ${daysAgoSql(days)}`;
132
+ }
133
+
134
+ function rollingWindowStartSql(anchorExpr = "a.date"): string {
135
+ return `to_char(${anchorExpr}::date - INTERVAL '${RETENTION_ROLLING_DAYS - 1} days', 'YYYY-MM-DD')`;
136
+ }
137
+
138
+ function dashboardTimeRangeFilter(dateExpr = EVENT_DATE_FILTER_SQL): string {
110
139
  return `('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND ${dateExpr} >= ${daysAgoSql(7)}) OR ('{{timeRange}}' = '30d' AND ${dateExpr} >= ${daysAgoSql(30)}) OR ('{{timeRange}}' = '90d' AND ${dateExpr} >= ${daysAgoSql(90)}) OR ('{{timeRange}}' = '180d' AND ${dateExpr} >= ${daysAgoSql(180)}) OR ('{{timeRange}}' = '365d' AND ${dateExpr} >= ${daysAgoSql(365)}))`;
111
140
  }
112
141
 
113
142
  function dashboardLookbackTimeRangeFilter(
114
- dateExpr = LOCAL_EVENT_DATE_SQL,
143
+ dateExpr = EVENT_DATE_FILTER_SQL,
115
144
  lookbackDays = 0,
116
145
  ): string {
117
146
  return `('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND ${dateExpr} >= ${daysAgoSql(7 + lookbackDays)}) OR ('{{timeRange}}' = '30d' AND ${dateExpr} >= ${daysAgoSql(30 + lookbackDays)}) OR ('{{timeRange}}' = '90d' AND ${dateExpr} >= ${daysAgoSql(90 + lookbackDays)}) OR ('{{timeRange}}' = '180d' AND ${dateExpr} >= ${daysAgoSql(180 + lookbackDays)}) OR ('{{timeRange}}' = '365d' AND ${dateExpr} >= ${daysAgoSql(365 + lookbackDays)}))`;
@@ -121,11 +150,14 @@ const DASHBOARD_TIME_RANGE_FILTER = dashboardTimeRangeFilter();
121
150
  const DASHBOARD_EVENT_DATE_RANGE_FILTER =
122
151
  dashboardTimeRangeFilter("event_date");
123
152
  const DASHBOARD_WAU_BASE_RANGE_FILTER = dashboardLookbackTimeRangeFilter(
124
- LOCAL_EVENT_DATE_SQL,
153
+ EVENT_DATE_FILTER_SQL,
125
154
  6,
126
155
  );
127
156
  const DASHBOARD_EMAIL_FILTER =
128
157
  "('{{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'))";
158
+ const SIGNED_IN_ACTIVITY_KEY_SQL = USER_KEY_SQL;
159
+ const SIGNED_IN_ACTIVITY_FILTER = `event_name = 'session status' AND signed_in = 'true' AND ${SIGNED_IN_ACTIVITY_KEY_SQL} IS NOT NULL`;
160
+ const SIGNED_IN_PRODUCT_ACTIVITY_FILTER = `${SIGNED_IN_ACTIVITY_FILTER} AND ${PRODUCT_ACTIVITY_TEMPLATE_FILTER}`;
129
161
  export const FIRST_PARTY_DASHBOARD_FILTERS: FirstPartyDashboardFilter[] = [
130
162
  {
131
163
  id: "timeRange",
@@ -166,13 +198,13 @@ export function usesFirstPartyDashboardFilters(sql: string): boolean {
166
198
  }
167
199
 
168
200
  const TOTAL_SIGNUPS_SQL = `SELECT COUNT(*) AS signups FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}`;
169
- const SIGNUPS_OVER_TIME_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, ${TEMPLATE_EXPR} AS template FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), 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`;
170
- const RETENTION_OVER_TIME_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 ${DASHBOARD_EMAIL_FILTER}), 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 <= ${daysAgoSql(7)} AND ${dashboardTimeRangeFilter("cohort_date")}), cohort_sizes AS (SELECT cohort_date AS date, COUNT(DISTINCT user_key) AS users FROM cohorts GROUP BY cohort_date), periods AS (SELECT '1d retention' AS period UNION ALL SELECT '7d retention' AS period), retained AS (SELECT c.cohort_date AS date, '1d retention' AS period, COUNT(DISTINCT c.user_key) AS retained FROM cohorts c JOIN base b ON b.user_key = c.user_key AND b.event_date = to_char(c.cohort_date::date + INTERVAL '1 day', 'YYYY-MM-DD') GROUP BY c.cohort_date UNION ALL SELECT c.cohort_date AS date, '7d retention' AS period, COUNT(DISTINCT c.user_key) AS retained FROM cohorts c JOIN base b ON b.user_key = c.user_key AND b.event_date = to_char(c.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD') GROUP BY c.cohort_date) SELECT cs.date, p.period, COALESCE(r.retained::float / NULLIF(cs.users, 0), 0) AS rate FROM cohort_sizes cs CROSS JOIN periods p LEFT JOIN retained r ON r.date = cs.date AND r.period = p.period ORDER BY cs.date, p.period`;
171
- const ONE_DAY_RETENTION_BY_TEMPLATE_SQL = `WITH base AS (SELECT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) AS user_key, ${TEMPLATE_EXPR} 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 ${DASHBOARD_EMAIL_FILTER} AND ${KNOWN_TEMPLATE_FILTER}), 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 <= ${daysAgoSql(1)} AND ${dashboardTimeRangeFilter("cohort_date")}), cohort_sizes AS (SELECT cohort_date AS date, template, COUNT(DISTINCT user_key) AS users FROM cohorts GROUP BY cohort_date, template), retained AS (SELECT c.cohort_date AS date, c.template, COUNT(DISTINCT c.user_key) AS retained FROM cohorts c JOIN base b ON b.user_key = c.user_key AND b.template = c.template AND b.event_date = to_char(c.cohort_date::date + INTERVAL '1 day', 'YYYY-MM-DD') GROUP BY c.cohort_date, c.template) SELECT cs.date, cs.template, COALESCE(r.retained::float / NULLIF(cs.users, 0), 0) AS rate FROM cohort_sizes cs LEFT JOIN retained r ON r.date = cs.date AND r.template = cs.template ORDER BY cs.date, cs.template`;
172
- const SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL = `WITH base AS (SELECT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) AS user_key, ${TEMPLATE_EXPR} 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 ${DASHBOARD_EMAIL_FILTER} AND ${KNOWN_TEMPLATE_FILTER}), 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 <= ${daysAgoSql(7)} AND ${dashboardTimeRangeFilter("cohort_date")}), cohort_sizes AS (SELECT cohort_date AS date, template, COUNT(DISTINCT user_key) AS users FROM cohorts GROUP BY cohort_date, template), retained AS (SELECT c.cohort_date AS date, c.template, COUNT(DISTINCT c.user_key) AS retained FROM cohorts c JOIN base b ON b.user_key = c.user_key AND b.template = c.template AND b.event_date = to_char(c.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD') GROUP BY c.cohort_date, c.template) SELECT cs.date, cs.template, COALESCE(r.retained::float / NULLIF(cs.users, 0), 0) AS rate FROM cohort_sizes cs LEFT JOIN retained r ON r.date = cs.date AND r.template = cs.template ORDER BY cs.date, cs.template`;
201
+ const SIGNUPS_OVER_TIME_SQL = `WITH offsets AS (SELECT (ROW_NUMBER() OVER (ORDER BY ${EVENT_DATE_SQL}) - 1)::int AS n FROM analytics_events LIMIT 800), signup_events AS (SELECT ${EVENT_DATE_SQL} AS date, ${TEMPLATE_EXPR} AS template FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), 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`;
202
+ const RETENTION_OVER_TIME_SQL = `WITH base AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${EVENT_DATE_SQL} AS event_date, user_id FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), first_seen AS (SELECT user_key, MIN(event_date) AS cohort_date FROM base GROUP BY user_key), anchor_dates AS (SELECT DISTINCT cohort_date AS date FROM first_seen WHERE cohort_date <= ${daysAgoSql(7)} AND ${dashboardTimeRangeFilter("cohort_date")}), cohort_windows AS (SELECT a.date, f.user_key, f.cohort_date FROM anchor_dates a JOIN first_seen f ON f.cohort_date >= ${rollingWindowStartSql()} AND f.cohort_date <= a.date), cohort_sizes AS (SELECT date, COUNT(DISTINCT user_key) AS users FROM cohort_windows GROUP BY date), periods AS (SELECT '1d retention' AS period UNION ALL SELECT '7d retention' AS period), retained AS (SELECT cw.date, '1d retention' AS period, COUNT(DISTINCT cw.user_key) AS retained FROM cohort_windows cw JOIN base b ON b.user_key = cw.user_key AND b.event_date = to_char(cw.cohort_date::date + INTERVAL '1 day', 'YYYY-MM-DD') GROUP BY cw.date UNION ALL SELECT cw.date, '7d retention' AS period, COUNT(DISTINCT cw.user_key) AS retained FROM cohort_windows cw JOIN base b ON b.user_key = cw.user_key AND b.event_date = to_char(cw.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD') GROUP BY cw.date) SELECT cs.date, p.period, COALESCE(r.retained, 0) AS retained_users, cs.users AS cohort_users, COALESCE(r.retained::float / NULLIF(cs.users, 0), 0) AS rate FROM cohort_sizes cs CROSS JOIN periods p LEFT JOIN retained r ON r.date = cs.date AND r.period = p.period WHERE cs.users >= ${RETENTION_MIN_COHORT_SIZE} ORDER BY cs.date, p.period`;
203
+ const ONE_DAY_RETENTION_BY_TEMPLATE_SQL = `WITH base AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${TEMPLATE_EXPR} AS template, ${EVENT_DATE_SQL} AS event_date, user_id FROM analytics_events WHERE ${SIGNED_IN_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER}), first_seen AS (SELECT user_key, template, MIN(event_date) AS cohort_date FROM base GROUP BY user_key, template), anchor_dates AS (SELECT DISTINCT cohort_date AS date, template FROM first_seen WHERE cohort_date <= ${daysAgoSql(1)} AND ${dashboardTimeRangeFilter("cohort_date")}), cohort_windows AS (SELECT a.date, a.template, f.user_key, f.cohort_date FROM anchor_dates a JOIN first_seen f ON f.template = a.template AND f.cohort_date >= ${rollingWindowStartSql()} AND f.cohort_date <= a.date), cohort_sizes AS (SELECT date, template, COUNT(DISTINCT user_key) AS users FROM cohort_windows GROUP BY date, template), retained AS (SELECT cw.date, cw.template, COUNT(DISTINCT cw.user_key) AS retained FROM cohort_windows cw JOIN base b ON b.user_key = cw.user_key AND b.template = cw.template AND b.event_date = to_char(cw.cohort_date::date + INTERVAL '1 day', 'YYYY-MM-DD') GROUP BY cw.date, cw.template) SELECT cs.date, cs.template, COALESCE(r.retained, 0) AS retained_users, cs.users AS cohort_users, COALESCE(r.retained::float / NULLIF(cs.users, 0), 0) AS rate FROM cohort_sizes cs LEFT JOIN retained r ON r.date = cs.date AND r.template = cs.template WHERE cs.users >= ${RETENTION_MIN_COHORT_SIZE} ORDER BY cs.date, cs.template`;
204
+ const SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL = `WITH base AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${TEMPLATE_EXPR} AS template, ${EVENT_DATE_SQL} AS event_date, user_id FROM analytics_events WHERE ${SIGNED_IN_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER}), first_seen AS (SELECT user_key, template, MIN(event_date) AS cohort_date FROM base GROUP BY user_key, template), anchor_dates AS (SELECT DISTINCT cohort_date AS date, template FROM first_seen WHERE cohort_date <= ${daysAgoSql(7)} AND ${dashboardTimeRangeFilter("cohort_date")}), cohort_windows AS (SELECT a.date, a.template, f.user_key, f.cohort_date FROM anchor_dates a JOIN first_seen f ON f.template = a.template AND f.cohort_date >= ${rollingWindowStartSql()} AND f.cohort_date <= a.date), cohort_sizes AS (SELECT date, template, COUNT(DISTINCT user_key) AS users FROM cohort_windows GROUP BY date, template), retained AS (SELECT cw.date, cw.template, COUNT(DISTINCT cw.user_key) AS retained FROM cohort_windows cw JOIN base b ON b.user_key = cw.user_key AND b.template = cw.template AND b.event_date = to_char(cw.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD') GROUP BY cw.date, cw.template) SELECT cs.date, cs.template, COALESCE(r.retained, 0) AS retained_users, cs.users AS cohort_users, COALESCE(r.retained::float / NULLIF(cs.users, 0), 0) AS rate FROM cohort_sizes cs LEFT JOIN retained r ON r.date = cs.date AND r.template = cs.template WHERE cs.users >= ${RETENTION_MIN_COHORT_SIZE} ORDER BY cs.date, cs.template`;
173
205
  const SIGNUPS_BY_TEMPLATE_SQL = `SELECT ${TEMPLATE_EXPR} AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${TEMPLATE_EXPR} ORDER BY count DESC`;
174
- const DAU_BY_TEMPLATE_SQL = `SELECT substr(timestamp, 1, 10) AS date, ${TEMPLATE_EXPR} 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 ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${KNOWN_TEMPLATE_FILTER} GROUP BY substr(timestamp, 1, 10), ${TEMPLATE_EXPR} ORDER BY date, template`;
175
- const WAU_BY_TEMPLATE_SQL = `WITH base AS (SELECT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) AS user_key, ${TEMPLATE_EXPR} 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 ${DASHBOARD_EMAIL_FILTER} AND ${KNOWN_TEMPLATE_FILTER} AND ${DASHBOARD_WAU_BASE_RANGE_FILTER}), days AS (SELECT DISTINCT event_date AS date FROM base WHERE ${DASHBOARD_EVENT_DATE_RANGE_FILTER}) 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`;
206
+ const DAU_BY_TEMPLATE_SQL = `SELECT ${EVENT_DATE_SQL} AS date, ${TEMPLATE_EXPR} AS template, COUNT(DISTINCT ${SIGNED_IN_ACTIVITY_KEY_SQL}) AS visitors FROM analytics_events WHERE ${SIGNED_IN_ACTIVITY_FILTER} AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER} GROUP BY ${EVENT_DATE_SQL}, ${TEMPLATE_EXPR} ORDER BY date, template`;
207
+ const WAU_BY_TEMPLATE_SQL = `WITH base AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS visitor_key, ${TEMPLATE_EXPR} AS template, ${EVENT_DATE_SQL} AS event_date, user_id FROM analytics_events WHERE ${SIGNED_IN_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${DASHBOARD_WAU_BASE_RANGE_FILTER}), days AS (SELECT DISTINCT event_date AS date FROM base WHERE ${DASHBOARD_EVENT_DATE_RANGE_FILTER}) SELECT d.date, b.template, COUNT(DISTINCT b.visitor_key) AS visitors 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`;
176
208
 
177
209
  /**
178
210
  * Catalog entries. Order here is the default panel order when a caller passes
@@ -259,7 +291,7 @@ const ENTRIES: FirstPartyMetric[] = [
259
291
  width: 1,
260
292
  windowed: false,
261
293
  buildSql: fixed(
262
- "SELECT substr(timestamp, 1, 10) AS date, COUNT(*) AS count FROM analytics_events WHERE event_name = 'session status' GROUP BY substr(timestamp, 1, 10) ORDER BY date",
294
+ `SELECT ${EVENT_DATE_SQL} AS date, COUNT(*) AS count FROM analytics_events WHERE event_name = 'session status' GROUP BY ${EVENT_DATE_SQL} ORDER BY date`,
263
295
  ),
264
296
  config: { xKey: "date", yKey: "count", color: "#10b981" },
265
297
  },
@@ -339,7 +371,7 @@ const ENTRIES: FirstPartyMetric[] = [
339
371
  width: 2,
340
372
  windowed: false,
341
373
  buildSql: fixed(
342
- `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 ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
374
+ `SELECT ${EVENT_DATE_SQL} AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${EVENT_DATE_SQL}, COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
343
375
  ),
344
376
  config: {
345
377
  xKey: "date",
@@ -361,7 +393,7 @@ const ENTRIES: FirstPartyMetric[] = [
361
393
  width: 2,
362
394
  windowed: false,
363
395
  buildSql: fixed(
364
- `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 ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
396
+ `SELECT ${EVENT_DATE_SQL} AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${EVENT_DATE_SQL}, COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
365
397
  ),
366
398
  config: {
367
399
  xKey: "date",
@@ -376,14 +408,14 @@ const ENTRIES: FirstPartyMetric[] = [
376
408
  },
377
409
  },
378
410
  {
379
- key: "demo-clicks-by-template",
411
+ key: "demo-clicks-over-time",
380
412
  title: "Try-Demo Clicks Over Time",
381
413
  chartType: "area",
382
414
  source: "first-party",
383
415
  width: 2,
384
416
  windowed: false,
385
417
  buildSql: fixed(
386
- `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 ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
418
+ `SELECT ${EVENT_DATE_SQL} AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click try demo' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${EVENT_DATE_SQL}, COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
387
419
  ),
388
420
  config: {
389
421
  xKey: "date",
@@ -405,7 +437,7 @@ const ENTRIES: FirstPartyMetric[] = [
405
437
  width: 2,
406
438
  windowed: false,
407
439
  buildSql: fixed(
408
- `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 ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
440
+ `SELECT ${EVENT_DATE_SQL} AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${EVENT_DATE_SQL}, COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
409
441
  ),
410
442
  config: {
411
443
  xKey: "date",
@@ -427,7 +459,7 @@ const ENTRIES: FirstPartyMetric[] = [
427
459
  width: 2,
428
460
  windowed: false,
429
461
  buildSql: fixed(
430
- `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 ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
462
+ `SELECT ${EVENT_DATE_SQL} AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${EVENT_DATE_SQL}, COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
431
463
  ),
432
464
  config: {
433
465
  xKey: "date",
@@ -453,7 +485,7 @@ const ENTRIES: FirstPartyMetric[] = [
453
485
  // Browser telemetry emits explicit `pageview` events with URL context, so
454
486
  // keep pageview panels scoped to that event instead of all tracked events.
455
487
  buildSql: fixed(
456
- `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 ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') ORDER BY date, template`,
488
+ `SELECT ${EVENT_DATE_SQL} AS date, COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'pageview' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${EVENT_DATE_SQL}, COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') ORDER BY date, template`,
457
489
  ),
458
490
  config: {
459
491
  xKey: "date",
@@ -476,7 +508,7 @@ const ENTRIES: FirstPartyMetric[] = [
476
508
  width: 2,
477
509
  windowed: false,
478
510
  buildSql: fixed(
479
- `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 ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY 1, 2) SELECT url, views, users, last_seen, href FROM pageviews ORDER BY views DESC LIMIT 25`,
511
+ `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 ${USER_KEY_SQL}) AS users, MAX(${EVENT_DATE_SQL}) AS last_seen FROM analytics_events WHERE event_name = 'pageview' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY 1, 2) SELECT url, views, users, last_seen, href FROM pageviews ORDER BY views DESC LIMIT 25`,
480
512
  ),
481
513
  config: {
482
514
  description:
@@ -500,7 +532,7 @@ const ENTRIES: FirstPartyMetric[] = [
500
532
  width: 2,
501
533
  windowed: false,
502
534
  buildSql: fixed(
503
- `WITH raw_referrers AS (SELECT COALESCE(NULLIF(referrer, ''), NULLIF(properties::jsonb ->> 'referrer', ''), NULLIF(properties::jsonb ->> 'landing_referrer', '')) AS raw_referrer, NULLIF(hostname, '') AS page_host, COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) AS user_key FROM analytics_events WHERE event_name = 'pageview' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), referrer_domains AS (SELECT lower(CASE WHEN lower(raw_referrer) LIKE 'http://%' OR lower(raw_referrer) LIKE 'https://%' THEN split_part(split_part(split_part(raw_referrer, '://', 2), '/', 1), chr(63), 1) WHEN raw_referrer LIKE '//%' THEN split_part(split_part(substr(raw_referrer, 3), '/', 1), chr(63), 1) ELSE split_part(split_part(raw_referrer, '/', 1), chr(63), 1) END) AS referrer_domain, lower(page_host) AS page_host, user_key FROM raw_referrers WHERE raw_referrer IS NOT NULL) SELECT referrer_domain, COUNT(*) AS visits, COUNT(DISTINCT user_key) AS users FROM referrer_domains WHERE referrer_domain <> '' AND (page_host IS NULL OR referrer_domain <> page_host) GROUP BY referrer_domain ORDER BY visits DESC LIMIT 20`,
535
+ `WITH raw_referrers AS (SELECT COALESCE(NULLIF(referrer, ''), NULLIF(properties::jsonb ->> 'referrer', ''), NULLIF(properties::jsonb ->> 'landing_referrer', '')) AS raw_referrer, NULLIF(hostname, '') AS page_host, ${USER_KEY_SQL} AS user_key FROM analytics_events WHERE event_name = 'pageview' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), referrer_domains AS (SELECT lower(CASE WHEN lower(raw_referrer) LIKE 'http://%' OR lower(raw_referrer) LIKE 'https://%' THEN split_part(split_part(split_part(raw_referrer, '://', 2), '/', 1), chr(63), 1) WHEN raw_referrer LIKE '//%' THEN split_part(split_part(substr(raw_referrer, 3), '/', 1), chr(63), 1) ELSE split_part(split_part(raw_referrer, '/', 1), chr(63), 1) END) AS referrer_domain, lower(page_host) AS page_host, user_key FROM raw_referrers WHERE raw_referrer IS NOT NULL) SELECT referrer_domain, COUNT(*) AS visits, COUNT(DISTINCT user_key) AS users FROM referrer_domains WHERE referrer_domain <> '' AND (page_host IS NULL OR referrer_domain <> page_host) GROUP BY referrer_domain ORDER BY visits DESC LIMIT 20`,
504
536
  ),
505
537
  config: {
506
538
  description:
@@ -522,7 +554,7 @@ const ENTRIES: FirstPartyMetric[] = [
522
554
  width: 2,
523
555
  windowed: false,
524
556
  buildSql: fixed(
525
- `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 ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} 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`,
557
+ `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_KEY_SQL} AS user_key, ${EVENT_DATE_SQL} AS event_date FROM analytics_events WHERE event_name = 'share_view' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} 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 user_key) AS users, MAX(event_date) 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`,
526
558
  ),
527
559
  config: {
528
560
  description: "Most-viewed shared Clips pages in the selected time range.",
@@ -539,24 +571,24 @@ const ENTRIES: FirstPartyMetric[] = [
539
571
  },
540
572
  {
541
573
  key: "repeat-users",
542
- title: "Repeat Users",
574
+ title: "Repeat Signed-In Visitors",
543
575
  chartType: "metric",
544
576
  source: "first-party",
545
577
  width: 1,
546
578
  windowed: false,
547
579
  buildSql: fixed(
548
- `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 ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))) SELECT COUNT(*) AS count FROM user_days WHERE active_days >= 2`,
580
+ `WITH user_days AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, COUNT(DISTINCT ${EVENT_DATE_SQL}) AS active_days FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${SIGNED_IN_ACTIVITY_KEY_SQL}) SELECT COUNT(*) AS count FROM user_days WHERE active_days >= 2`,
549
581
  ),
550
582
  config: {
551
583
  yKey: "count",
552
584
  yFormatter: "number",
553
585
  description:
554
- "Distinct users with activity on at least two days in the selected time range. Uses user_id first, then anonymous_id.",
586
+ "Signed-in browser identities with non-docs app session activity on at least two days in the selected time range.",
555
587
  },
556
588
  },
557
589
  {
558
590
  key: "retention-over-time",
559
- title: "1d / 7d Retention Over Time",
591
+ title: "7d Rolling Signed-In Retention",
560
592
  chartType: "line",
561
593
  source: "first-party",
562
594
  width: 3,
@@ -573,12 +605,12 @@ const ENTRIES: FirstPartyMetric[] = [
573
605
  },
574
606
  colors: ["#10b981", "#8b5cf6"],
575
607
  description:
576
- "First-seen daily cohorts in the selected range. Cohorts must be at least seven days old so both lines are mature.",
608
+ "Trailing 7-day first-seen signed-in app session cohorts, keyed by browser identity. Docs traffic is excluded; windows under 5 identities are hidden.",
577
609
  },
578
610
  },
579
611
  {
580
612
  key: "one-day-retention-by-template",
581
- title: "1d Retention by Template",
613
+ title: "7d Rolling 1d Signed-In Retention by Template",
582
614
  chartType: "line",
583
615
  source: "first-party",
584
616
  width: 2,
@@ -594,12 +626,12 @@ const ENTRIES: FirstPartyMetric[] = [
594
626
  valueKey: "rate",
595
627
  },
596
628
  description:
597
- "Per-template first-seen cohorts in the selected range, excluding unassigned telemetry. A user is retained when they return to the same template the next day.",
629
+ "Trailing 7-day per-template signed-in app session cohorts. Docs traffic is excluded; a browser identity is retained when it returns signed in to the same template the next day. Windows under 5 identities are hidden.",
598
630
  },
599
631
  },
600
632
  {
601
633
  key: "seven-day-retention-by-template",
602
- title: "7d Retention by Template",
634
+ title: "7d Rolling 7d Signed-In Retention by Template",
603
635
  chartType: "line",
604
636
  source: "first-party",
605
637
  width: 2,
@@ -615,12 +647,12 @@ const ENTRIES: FirstPartyMetric[] = [
615
647
  valueKey: "rate",
616
648
  },
617
649
  description:
618
- "Per-template first-seen cohorts in the selected range, excluding unassigned telemetry. A user is retained when they return to the same template seven days later.",
650
+ "Trailing 7-day per-template signed-in app session cohorts. Docs traffic is excluded; a browser identity is retained when it returns signed in to the same template seven days later. Windows under 5 identities are hidden.",
619
651
  },
620
652
  },
621
653
  {
622
654
  key: "dau-over-time",
623
- title: "DAU by Template",
655
+ title: "Signed-In Daily Active Visitors by Template",
624
656
  chartType: "area",
625
657
  source: "first-party",
626
658
  width: 2,
@@ -628,21 +660,21 @@ const ENTRIES: FirstPartyMetric[] = [
628
660
  buildSql: fixed(DAU_BY_TEMPLATE_SQL),
629
661
  config: {
630
662
  xKey: "date",
631
- yKey: "users",
663
+ yKey: "visitors",
632
664
  yFormatter: "number",
633
665
  pivot: {
634
666
  xKey: "date",
635
667
  seriesKey: "template",
636
- valueKey: "users",
668
+ valueKey: "visitors",
637
669
  },
638
670
  stacked: true,
639
671
  description:
640
- "Distinct active users per day in the selected time range, stacked by inferred template/app. Unassigned telemetry is excluded.",
672
+ "Distinct signed-in browser identities per day, stacked by inferred template/app. Docs traffic is excluded. This is signed-in activity, not true account-level DAU, until session telemetry includes account identity.",
641
673
  },
642
674
  },
643
675
  {
644
676
  key: "wau-over-time",
645
- title: "WAU by Template",
677
+ title: "Signed-In Weekly Active Visitors by Template",
646
678
  chartType: "area",
647
679
  source: "first-party",
648
680
  width: 2,
@@ -650,16 +682,16 @@ const ENTRIES: FirstPartyMetric[] = [
650
682
  buildSql: fixed(WAU_BY_TEMPLATE_SQL),
651
683
  config: {
652
684
  xKey: "date",
653
- yKey: "users",
685
+ yKey: "visitors",
654
686
  yFormatter: "number",
655
687
  pivot: {
656
688
  xKey: "date",
657
689
  seriesKey: "template",
658
- valueKey: "users",
690
+ valueKey: "visitors",
659
691
  },
660
692
  stacked: true,
661
693
  description:
662
- "Trailing 7-day distinct active users for each active date in the selected time range, stacked by inferred template/app. Unassigned telemetry is excluded.",
694
+ "Trailing 7-day distinct signed-in browser identities for each active date, stacked by inferred template/app. Docs traffic is excluded. This is signed-in activity, not true account-level WAU, until session telemetry includes account identity.",
663
695
  },
664
696
  },
665
697
 
@@ -672,7 +704,7 @@ const ENTRIES: FirstPartyMetric[] = [
672
704
  width: 1,
673
705
  windowed: true,
674
706
  buildSql: windowed(
675
- "SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND timestamp::timestamptz >= now() - interval '30 days' AND properties::jsonb ->> 'referral_source' IS NOT NULL AND properties::jsonb ->> 'referral_source' <> '' AND properties::jsonb ->> 'referral_source' <> 'direct'",
707
+ `SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(30)} AND properties::jsonb ->> 'referral_source' IS NOT NULL AND properties::jsonb ->> 'referral_source' <> '' AND properties::jsonb ->> 'referral_source' <> 'direct'`,
676
708
  ),
677
709
  config: {
678
710
  yKey: "count",
@@ -689,7 +721,7 @@ const ENTRIES: FirstPartyMetric[] = [
689
721
  width: 1,
690
722
  windowed: true,
691
723
  buildSql: windowed(
692
- "SELECT CASE WHEN COUNT(*) = 0 THEN 0 ELSE 1.0 * COUNT(*) FILTER (WHERE properties::jsonb ->> 'referral_source' IS NOT NULL AND properties::jsonb ->> 'referral_source' <> '' AND properties::jsonb ->> 'referral_source' <> 'direct') / COUNT(*) END AS rate FROM analytics_events WHERE event_name = 'signup' AND timestamp::timestamptz >= now() - interval '30 days'",
724
+ `SELECT CASE WHEN COUNT(*) = 0 THEN 0 ELSE 1.0 * COUNT(*) FILTER (WHERE properties::jsonb ->> 'referral_source' IS NOT NULL AND properties::jsonb ->> 'referral_source' <> '' AND properties::jsonb ->> 'referral_source' <> 'direct') / COUNT(*) END AS rate FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(30)}`,
693
725
  ),
694
726
  config: {
695
727
  yKey: "rate",
@@ -706,7 +738,7 @@ const ENTRIES: FirstPartyMetric[] = [
706
738
  width: 1,
707
739
  windowed: true,
708
740
  buildSql: windowed(
709
- "SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND timestamp::timestamptz >= now() - interval '30 days' AND properties::jsonb ->> 'referral_source' = 'clip_share'",
741
+ `SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(30)} AND properties::jsonb ->> 'referral_source' = 'clip_share'`,
710
742
  ),
711
743
  config: {
712
744
  yKey: "count",
@@ -722,7 +754,7 @@ const ENTRIES: FirstPartyMetric[] = [
722
754
  width: 1,
723
755
  windowed: true,
724
756
  buildSql: windowed(
725
- "SELECT COALESCE(NULLIF(properties::jsonb ->> 'referral_source', ''), 'direct') AS referral_source, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND timestamp::timestamptz >= now() - interval '90 days' GROUP BY COALESCE(NULLIF(properties::jsonb ->> 'referral_source', ''), 'direct') ORDER BY count DESC LIMIT 20",
757
+ `SELECT COALESCE(NULLIF(properties::jsonb ->> 'referral_source', ''), 'direct') AS referral_source, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(90)} GROUP BY COALESCE(NULLIF(properties::jsonb ->> 'referral_source', ''), 'direct') ORDER BY count DESC LIMIT 20`,
726
758
  ),
727
759
  config: {
728
760
  xKey: "referral_source",
@@ -740,7 +772,7 @@ const ENTRIES: FirstPartyMetric[] = [
740
772
  width: 2,
741
773
  windowed: true,
742
774
  buildSql: windowed(
743
- "SELECT substr(timestamp, 1, 10) AS date, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND timestamp::timestamptz >= now() - interval '90 days' AND properties::jsonb ->> 'referral_source' IS NOT NULL AND properties::jsonb ->> 'referral_source' <> '' AND properties::jsonb ->> 'referral_source' <> 'direct' GROUP BY substr(timestamp, 1, 10) ORDER BY date",
775
+ `SELECT ${EVENT_DATE_SQL} AS date, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(90)} AND properties::jsonb ->> 'referral_source' IS NOT NULL AND properties::jsonb ->> 'referral_source' <> '' AND properties::jsonb ->> 'referral_source' <> 'direct' GROUP BY ${EVENT_DATE_SQL} ORDER BY date`,
744
776
  ),
745
777
  config: {
746
778
  xKey: "date",
@@ -757,7 +789,7 @@ const ENTRIES: FirstPartyMetric[] = [
757
789
  width: 1,
758
790
  windowed: true,
759
791
  buildSql: windowed(
760
- "SELECT properties::jsonb ->> 'referrer_user' AS referrer_user, COUNT(*) AS signups FROM analytics_events WHERE event_name = 'signup' AND timestamp::timestamptz >= now() - interval '90 days' AND properties::jsonb ->> 'referrer_user' IS NOT NULL AND properties::jsonb ->> 'referrer_user' <> '' GROUP BY properties::jsonb ->> 'referrer_user' ORDER BY signups DESC LIMIT 20",
792
+ `SELECT properties::jsonb ->> 'referrer_user' AS referrer_user, COUNT(*) AS signups FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(90)} AND properties::jsonb ->> 'referrer_user' IS NOT NULL AND properties::jsonb ->> 'referrer_user' <> '' GROUP BY properties::jsonb ->> 'referrer_user' ORDER BY signups DESC LIMIT 20`,
761
793
  ),
762
794
  config: {
763
795
  description:
@@ -776,7 +808,7 @@ const ENTRIES: FirstPartyMetric[] = [
776
808
  width: 2,
777
809
  windowed: true,
778
810
  buildSql: windowed(
779
- "SELECT 'Share views' AS stage, COUNT(*) AS count FROM analytics_events WHERE event_name = 'share_view' AND timestamp::timestamptz >= now() - interval '30 days' UNION ALL SELECT 'CTA clicks' AS stage, COUNT(*) AS count FROM analytics_events WHERE event_name = 'share_cta_click' AND timestamp::timestamptz >= now() - interval '30 days' UNION ALL SELECT 'Clip-share signups' AS stage, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND timestamp::timestamptz >= now() - interval '30 days' AND properties::jsonb ->> 'referral_source' = 'clip_share'",
811
+ `SELECT 'Share views' AS stage, COUNT(*) AS count FROM analytics_events WHERE event_name = 'share_view' AND ${windowStartFilter(30)} UNION ALL SELECT 'CTA clicks' AS stage, COUNT(*) AS count FROM analytics_events WHERE event_name = 'share_cta_click' AND ${windowStartFilter(30)} UNION ALL SELECT 'Clip-share signups' AS stage, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(30)} AND properties::jsonb ->> 'referral_source' = 'clip_share'`,
780
812
  ),
781
813
  config: {
782
814
  xKey: "stage",
@@ -794,7 +826,7 @@ const ENTRIES: FirstPartyMetric[] = [
794
826
  width: 1,
795
827
  windowed: true,
796
828
  buildSql: windowed(
797
- "SELECT COALESCE(COUNT(*) FILTER (WHERE recent.auth_user_id IN (SELECT properties::jsonb ->> 'referrer_user' FROM analytics_events WHERE event_name = 'signup' AND properties::jsonb ->> 'referrer_user' IS NOT NULL AND properties::jsonb ->> 'referrer_user' <> ''))::float / NULLIF(COUNT(*), 0), 0) AS rate FROM (SELECT DISTINCT properties::jsonb ->> 'auth_user_id' AS auth_user_id FROM analytics_events WHERE event_name = 'signup' AND timestamp::timestamptz >= now() - interval '90 days' AND properties::jsonb ->> 'auth_user_id' IS NOT NULL AND properties::jsonb ->> 'auth_user_id' <> '') AS recent",
829
+ `SELECT COALESCE(COUNT(*) FILTER (WHERE recent.auth_user_id IN (SELECT properties::jsonb ->> 'referrer_user' FROM analytics_events WHERE event_name = 'signup' AND properties::jsonb ->> 'referrer_user' IS NOT NULL AND properties::jsonb ->> 'referrer_user' <> ''))::float / NULLIF(COUNT(*), 0), 0) AS rate FROM (SELECT DISTINCT properties::jsonb ->> 'auth_user_id' AS auth_user_id FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(90)} AND properties::jsonb ->> 'auth_user_id' IS NOT NULL AND properties::jsonb ->> 'auth_user_id' <> '') AS recent`,
798
830
  ),
799
831
  config: {
800
832
  yKey: "rate",
@@ -811,7 +843,7 @@ const ENTRIES: FirstPartyMetric[] = [
811
843
  width: 1,
812
844
  windowed: true,
813
845
  buildSql: windowed(
814
- "SELECT COALESCE(COUNT(*) FILTER (WHERE properties::jsonb ->> 'referrer_user' IS NOT NULL AND properties::jsonb ->> 'referrer_user' <> '')::float / NULLIF(COUNT(DISTINCT properties::jsonb ->> 'auth_user_id'), 0), 0) AS k FROM analytics_events WHERE event_name = 'signup' AND timestamp::timestamptz >= now() - interval '90 days'",
846
+ `SELECT COALESCE(COUNT(*) FILTER (WHERE properties::jsonb ->> 'referrer_user' IS NOT NULL AND properties::jsonb ->> 'referrer_user' <> '')::float / NULLIF(COUNT(DISTINCT properties::jsonb ->> 'auth_user_id'), 0), 0) AS k FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(90)}`,
815
847
  ),
816
848
  config: {
817
849
  yKey: "k",
@@ -828,7 +860,7 @@ const ENTRIES: FirstPartyMetric[] = [
828
860
  width: 1,
829
861
  windowed: true,
830
862
  buildSql: windowed(
831
- "SELECT COUNT(DISTINCT properties::jsonb ->> 'referrer_user') AS count FROM analytics_events WHERE event_name = 'signup' AND timestamp::timestamptz >= now() - interval '90 days' AND properties::jsonb ->> 'referrer_user' IS NOT NULL AND properties::jsonb ->> 'referrer_user' <> ''",
863
+ `SELECT COUNT(DISTINCT properties::jsonb ->> 'referrer_user') AS count FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(90)} AND properties::jsonb ->> 'referrer_user' IS NOT NULL AND properties::jsonb ->> 'referrer_user' <> ''`,
832
864
  ),
833
865
  config: {
834
866
  yKey: "count",