@agent-native/core 0.72.4 → 0.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +28 -0
- package/corpus/core/docs/content/skills-guide.md +14 -0
- package/corpus/core/docs/content/template-clips.md +7 -1
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +25 -12
- package/corpus/core/src/cli/create.ts +1 -1
- package/corpus/core/src/cli/skills.ts +450 -19
- package/corpus/core/src/server/auth.ts +7 -0
- package/corpus/core/src/server/onboarding-html.ts +95 -9
- package/corpus/core/src/templates/default/AGENTS.md +4 -0
- package/corpus/core/src/templates/default/package.json +2 -1
- package/corpus/core/src/templates/headless/AGENTS.md +4 -0
- package/corpus/core/src/templates/headless/package.json +1 -0
- package/corpus/core/src/templates/workspace-root/AGENTS.md +4 -0
- package/corpus/core/src/templates/workspace-root/README.md +4 -0
- package/corpus/core/src/templates/workspace-root/package.json +1 -0
- package/corpus/templates/analytics/actions/update-dashboard.ts +3 -4
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +144 -40
- package/corpus/templates/analytics/app/global.css +15 -13
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +9 -32
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +0 -26
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +0 -23
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/pivot.ts +65 -2
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +3 -4
- package/corpus/templates/analytics/changelog/2026-06-24-dashboard-charts-now-fill-sparse-template-time-series-suppor.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-dashboard-rows-now-auto-fit-panels-so-one-two-or-three-chart.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-dashboard-traffic-tables-now-show-top-urls-and-shared-clips-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-first-party-signup-and-pageview-panels-now-honor-dashboard-filters.md +5 -0
- package/corpus/templates/analytics/changelog/2026-06-24-first-party-template-dashboards-now-include-repeat-users-ret.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-retention-charts-now-break-out-1-day-and-7-day-return-rates-.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +256 -44
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +7 -2
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +35 -8
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +287 -42
- package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +1 -1
- package/corpus/templates/calendar/server/plugins/auth.ts +12 -0
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +7 -7
- package/corpus/templates/clips/AGENTS.md +6 -2
- package/corpus/templates/clips/app/components/capture-install-options.tsx +20 -4
- package/corpus/templates/clips/app/components/player/player-controls.tsx +62 -32
- package/corpus/templates/clips/app/lib/capture-install-options.ts +39 -5
- package/corpus/templates/clips/app/routes/download.tsx +2 -2
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -1
- package/corpus/templates/clips/changelog/2026-06-24-inline-slack-playback-controls-no-longer-shift-when-adjustin.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-console-log-stream-all-le.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-fetch-xhr-network-request.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-24-the-chrome-extension-option-now-only-appears-on-supported-hosts.md +5 -0
- package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +13 -8
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/scripts/package.ts +15 -2
- package/corpus/templates/clips/chrome-extension/src/overlay.css +4 -25
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +1 -3
- package/corpus/templates/clips/server/lib/public-agent-context.ts +67 -24
- package/corpus/templates/mail/server/plugins/auth.ts +4 -1
- package/corpus/templates/plan/app/pages/PlansPage.tsx +271 -197
- package/corpus/templates/plan/server/plan-content.ts +45 -2
- package/dist/agent/durable-background.d.ts +9 -4
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +24 -11
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/cli/create.js +1 -1
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +361 -19
- package/dist/cli/skills.js.map +1 -1
- package/dist/server/auth.d.ts +7 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +1 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/onboarding-html.d.ts +15 -0
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +73 -9
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/templates/default/AGENTS.md +4 -0
- package/dist/templates/default/package.json +2 -1
- package/dist/templates/headless/AGENTS.md +4 -0
- package/dist/templates/headless/package.json +1 -0
- package/dist/templates/workspace-root/AGENTS.md +4 -0
- package/dist/templates/workspace-root/README.md +4 -0
- package/dist/templates/workspace-root/package.json +1 -0
- package/docs/content/skills-guide.md +14 -0
- package/docs/content/template-clips.md +7 -1
- package/package.json +1 -1
- package/src/templates/default/AGENTS.md +4 -0
- package/src/templates/default/package.json +2 -1
- package/src/templates/headless/AGENTS.md +4 -0
- package/src/templates/headless/package.json +1 -0
- package/src/templates/workspace-root/AGENTS.md +4 -0
- package/src/templates/workspace-root/README.md +4 -0
- package/src/templates/workspace-root/package.json +1 -0
|
@@ -88,6 +88,18 @@ function fixed(sql: string): (window?: MetricWindow) => string {
|
|
|
88
88
|
return () => sql;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
const SIGNUP_TEMPLATE_EXPR =
|
|
92
|
+
"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')";
|
|
93
|
+
const DASHBOARD_TIME_RANGE_FILTER =
|
|
94
|
+
"('{{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')))";
|
|
95
|
+
const DASHBOARD_EMAIL_FILTER =
|
|
96
|
+
"('{{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'))";
|
|
97
|
+
const TOTAL_SIGNUPS_SQL = `SELECT COUNT(*) AS signups FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}`;
|
|
98
|
+
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, ${SIGNUP_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`;
|
|
99
|
+
const ONE_DAY_RETENTION_BY_TEMPLATE_SQL = `WITH base AS (SELECT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) AS user_key, ${SIGNUP_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}), 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`;
|
|
100
|
+
const SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL = `WITH base AS (SELECT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) AS user_key, ${SIGNUP_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}), 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`;
|
|
101
|
+
const SIGNUPS_BY_TEMPLATE_SQL = `SELECT ${SIGNUP_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 ${SIGNUP_TEMPLATE_EXPR} ORDER BY count DESC`;
|
|
102
|
+
|
|
91
103
|
/**
|
|
92
104
|
* Catalog entries. Order here is the default panel order when a caller passes
|
|
93
105
|
* metrics; callers can reorder by listing keys in the order they want.
|
|
@@ -96,19 +108,16 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
96
108
|
// --- Signups -------------------------------------------------------------
|
|
97
109
|
{
|
|
98
110
|
key: "total-signups",
|
|
99
|
-
title: "Signups",
|
|
111
|
+
title: "Total Signups",
|
|
100
112
|
chartType: "metric",
|
|
101
113
|
source: "first-party",
|
|
102
114
|
width: 1,
|
|
103
115
|
windowed: false,
|
|
104
|
-
buildSql: fixed(
|
|
105
|
-
"SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup'",
|
|
106
|
-
),
|
|
116
|
+
buildSql: fixed(TOTAL_SIGNUPS_SQL),
|
|
107
117
|
config: {
|
|
108
|
-
yKey: "
|
|
118
|
+
yKey: "signups",
|
|
109
119
|
yFormatter: "number",
|
|
110
|
-
description:
|
|
111
|
-
"Better Auth user creation events from all Agent Native templates",
|
|
120
|
+
description: "Total signup events in the selected time range.",
|
|
112
121
|
},
|
|
113
122
|
},
|
|
114
123
|
{
|
|
@@ -118,14 +127,18 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
118
127
|
source: "first-party",
|
|
119
128
|
width: 2,
|
|
120
129
|
windowed: false,
|
|
121
|
-
buildSql: fixed(
|
|
122
|
-
"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",
|
|
123
|
-
),
|
|
130
|
+
buildSql: fixed(SIGNUPS_OVER_TIME_SQL),
|
|
124
131
|
config: {
|
|
125
132
|
xKey: "date",
|
|
126
133
|
yKey: "count",
|
|
127
|
-
|
|
128
|
-
|
|
134
|
+
yFormatter: "number",
|
|
135
|
+
pivot: {
|
|
136
|
+
xKey: "date",
|
|
137
|
+
seriesKey: "template",
|
|
138
|
+
valueKey: "count",
|
|
139
|
+
},
|
|
140
|
+
stacked: true,
|
|
141
|
+
description: "Daily signup events stacked by inferred template/app.",
|
|
129
142
|
},
|
|
130
143
|
},
|
|
131
144
|
{
|
|
@@ -133,16 +146,15 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
133
146
|
title: "Signups by Template",
|
|
134
147
|
chartType: "bar",
|
|
135
148
|
source: "first-party",
|
|
136
|
-
width:
|
|
149
|
+
width: 2,
|
|
137
150
|
windowed: false,
|
|
138
|
-
buildSql: fixed(
|
|
139
|
-
"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",
|
|
140
|
-
),
|
|
151
|
+
buildSql: fixed(SIGNUPS_BY_TEMPLATE_SQL),
|
|
141
152
|
config: {
|
|
142
153
|
xKey: "template",
|
|
143
154
|
yKey: "count",
|
|
144
|
-
|
|
145
|
-
|
|
155
|
+
yFormatter: "number",
|
|
156
|
+
color: "var(--brand-purple)",
|
|
157
|
+
description: "Signup events grouped by inferred template/app.",
|
|
146
158
|
},
|
|
147
159
|
},
|
|
148
160
|
|
|
@@ -253,45 +265,85 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
253
265
|
width: 2,
|
|
254
266
|
windowed: false,
|
|
255
267
|
buildSql: fixed(
|
|
256
|
-
"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",
|
|
268
|
+
"SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
|
|
257
269
|
),
|
|
258
|
-
config: {
|
|
270
|
+
config: {
|
|
271
|
+
xKey: "date",
|
|
272
|
+
yKey: "count",
|
|
273
|
+
yFormatter: "number",
|
|
274
|
+
pivot: {
|
|
275
|
+
xKey: "date",
|
|
276
|
+
seriesKey: "template",
|
|
277
|
+
valueKey: "count",
|
|
278
|
+
},
|
|
279
|
+
stacked: true,
|
|
280
|
+
},
|
|
259
281
|
},
|
|
260
282
|
{
|
|
261
283
|
key: "clicks-by-template",
|
|
262
|
-
title: "Clicks by Template",
|
|
263
|
-
chartType: "
|
|
284
|
+
title: "Clicks by Template Over Time",
|
|
285
|
+
chartType: "area",
|
|
264
286
|
source: "first-party",
|
|
265
|
-
width:
|
|
287
|
+
width: 2,
|
|
266
288
|
windowed: false,
|
|
267
289
|
buildSql: fixed(
|
|
268
|
-
"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
|
|
290
|
+
"SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
|
|
269
291
|
),
|
|
270
|
-
config: {
|
|
292
|
+
config: {
|
|
293
|
+
xKey: "date",
|
|
294
|
+
yKey: "count",
|
|
295
|
+
yFormatter: "number",
|
|
296
|
+
pivot: {
|
|
297
|
+
xKey: "date",
|
|
298
|
+
seriesKey: "template",
|
|
299
|
+
valueKey: "count",
|
|
300
|
+
},
|
|
301
|
+
stacked: true,
|
|
302
|
+
},
|
|
271
303
|
},
|
|
272
304
|
{
|
|
273
305
|
key: "demo-clicks-by-template",
|
|
274
|
-
title: "Try-Demo Clicks
|
|
275
|
-
chartType: "
|
|
306
|
+
title: "Try-Demo Clicks Over Time",
|
|
307
|
+
chartType: "area",
|
|
276
308
|
source: "first-party",
|
|
277
|
-
width:
|
|
309
|
+
width: 2,
|
|
278
310
|
windowed: false,
|
|
279
311
|
buildSql: fixed(
|
|
280
|
-
"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
|
|
312
|
+
"SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click try demo' GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
|
|
281
313
|
),
|
|
282
|
-
config: {
|
|
314
|
+
config: {
|
|
315
|
+
xKey: "date",
|
|
316
|
+
yKey: "count",
|
|
317
|
+
yFormatter: "number",
|
|
318
|
+
pivot: {
|
|
319
|
+
xKey: "date",
|
|
320
|
+
seriesKey: "template",
|
|
321
|
+
valueKey: "count",
|
|
322
|
+
},
|
|
323
|
+
stacked: true,
|
|
324
|
+
},
|
|
283
325
|
},
|
|
284
326
|
{
|
|
285
327
|
key: "cli-copies-by-template",
|
|
286
|
-
title: "CLI Copies by Template",
|
|
287
|
-
chartType: "
|
|
328
|
+
title: "CLI Copies by Template Over Time",
|
|
329
|
+
chartType: "area",
|
|
288
330
|
source: "first-party",
|
|
289
|
-
width:
|
|
331
|
+
width: 2,
|
|
290
332
|
windowed: false,
|
|
291
333
|
buildSql: fixed(
|
|
292
|
-
"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
|
|
334
|
+
"SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
|
|
293
335
|
),
|
|
294
|
-
config: {
|
|
336
|
+
config: {
|
|
337
|
+
xKey: "date",
|
|
338
|
+
yKey: "count",
|
|
339
|
+
yFormatter: "number",
|
|
340
|
+
pivot: {
|
|
341
|
+
xKey: "date",
|
|
342
|
+
seriesKey: "template",
|
|
343
|
+
valueKey: "count",
|
|
344
|
+
},
|
|
345
|
+
stacked: true,
|
|
346
|
+
},
|
|
295
347
|
},
|
|
296
348
|
{
|
|
297
349
|
key: "cli-copies-over-time",
|
|
@@ -301,9 +353,19 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
301
353
|
width: 2,
|
|
302
354
|
windowed: false,
|
|
303
355
|
buildSql: fixed(
|
|
304
|
-
"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",
|
|
356
|
+
"SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
|
|
305
357
|
),
|
|
306
|
-
config: {
|
|
358
|
+
config: {
|
|
359
|
+
xKey: "date",
|
|
360
|
+
yKey: "count",
|
|
361
|
+
yFormatter: "number",
|
|
362
|
+
pivot: {
|
|
363
|
+
xKey: "date",
|
|
364
|
+
seriesKey: "template",
|
|
365
|
+
valueKey: "count",
|
|
366
|
+
},
|
|
367
|
+
stacked: true,
|
|
368
|
+
},
|
|
307
369
|
},
|
|
308
370
|
|
|
309
371
|
// --- Activity / pageviews ------------------------------------------------
|
|
@@ -314,17 +376,200 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
314
376
|
source: "first-party",
|
|
315
377
|
width: 2,
|
|
316
378
|
windowed: false,
|
|
317
|
-
//
|
|
318
|
-
//
|
|
379
|
+
// Browser telemetry emits explicit `pageview` events with URL context, so
|
|
380
|
+
// keep pageview panels scoped to that event instead of all tracked events.
|
|
319
381
|
buildSql: fixed(
|
|
320
|
-
|
|
382
|
+
`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`,
|
|
321
383
|
),
|
|
322
384
|
config: {
|
|
323
385
|
xKey: "date",
|
|
324
386
|
yKey: "count",
|
|
325
|
-
|
|
387
|
+
yFormatter: "number",
|
|
388
|
+
pivot: {
|
|
389
|
+
xKey: "date",
|
|
390
|
+
seriesKey: "template",
|
|
391
|
+
valueKey: "count",
|
|
392
|
+
},
|
|
393
|
+
stacked: true,
|
|
394
|
+
description: "Pageview events per day stacked by inferred template/app.",
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
key: "top-visited-urls",
|
|
399
|
+
title: "Top Visited URLs",
|
|
400
|
+
chartType: "table",
|
|
401
|
+
source: "first-party",
|
|
402
|
+
width: 2,
|
|
403
|
+
windowed: false,
|
|
404
|
+
buildSql: fixed(
|
|
405
|
+
`WITH pageviews AS (SELECT COALESCE(CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') END, NULLIF(url, ''), NULLIF(path, ''), 'unknown') AS url, CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') WHEN NULLIF(url, '') LIKE 'http%' THEN url ELSE COALESCE(NULLIF(url, ''), NULLIF(path, ''), '') END AS href, COUNT(*) AS views, COUNT(DISTINCT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))) AS users, MAX(substr(timestamp, 1, 10)) AS last_seen FROM analytics_events WHERE event_name = 'pageview' AND ${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`,
|
|
406
|
+
),
|
|
407
|
+
config: {
|
|
408
|
+
description:
|
|
409
|
+
"Most-viewed URLs in the selected time range. Links open in a new tab.",
|
|
410
|
+
sortable: true,
|
|
411
|
+
limit: 25,
|
|
412
|
+
columns: [
|
|
413
|
+
{ key: "url", label: "URL", format: "link", linkKey: "href" },
|
|
414
|
+
{ key: "views", label: "Views", format: "number" },
|
|
415
|
+
{ key: "users", label: "Users", format: "number" },
|
|
416
|
+
{ key: "last_seen", label: "Last seen", format: "date" },
|
|
417
|
+
{ key: "href", hidden: true },
|
|
418
|
+
],
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
key: "top-visited-clips",
|
|
423
|
+
title: "Top Visited Clips",
|
|
424
|
+
chartType: "table",
|
|
425
|
+
source: "first-party",
|
|
426
|
+
width: 2,
|
|
427
|
+
windowed: false,
|
|
428
|
+
buildSql: fixed(
|
|
429
|
+
`WITH clip_events AS (SELECT COALESCE(NULLIF(properties::jsonb ->> 'recording_id', ''), NULLIF(path, ''), NULLIF(url, ''), 'unknown') AS clip_key, CASE WHEN NULLIF(url, '') LIKE 'http%' THEN url WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') WHEN NULLIF(path, '') LIKE '/%' THEN 'https://clips.agent-native.com' || path ELSE COALESCE(NULLIF(url, ''), '') END AS href, user_id, anonymous_id, timestamp FROM analytics_events WHERE event_name = 'share_view' AND ${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 href LIKE 'http%' THEN href ELSE clip_key END AS clip, views, users, last_seen, href FROM clip_views ORDER BY views DESC LIMIT 25`,
|
|
430
|
+
),
|
|
431
|
+
config: {
|
|
432
|
+
description: "Most-viewed shared Clips pages in the selected time range.",
|
|
433
|
+
sortable: true,
|
|
434
|
+
limit: 25,
|
|
435
|
+
columns: [
|
|
436
|
+
{ key: "clip", label: "Clip URL", format: "link", linkKey: "href" },
|
|
437
|
+
{ key: "views", label: "Views", format: "number" },
|
|
438
|
+
{ key: "users", label: "Users", format: "number" },
|
|
439
|
+
{ key: "last_seen", label: "Last seen", format: "date" },
|
|
440
|
+
{ key: "href", hidden: true },
|
|
441
|
+
],
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
key: "repeat-users",
|
|
446
|
+
title: "Repeat Users",
|
|
447
|
+
chartType: "metric",
|
|
448
|
+
source: "first-party",
|
|
449
|
+
width: 1,
|
|
450
|
+
windowed: false,
|
|
451
|
+
buildSql: fixed(
|
|
452
|
+
"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",
|
|
453
|
+
),
|
|
454
|
+
config: {
|
|
455
|
+
yKey: "count",
|
|
456
|
+
yFormatter: "number",
|
|
457
|
+
description:
|
|
458
|
+
"Distinct users with activity on at least two days in the selected time range. Uses user_id first, then anonymous_id.",
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
key: "retention-over-time",
|
|
463
|
+
title: "1d / 7d Retention Over Time",
|
|
464
|
+
chartType: "line",
|
|
465
|
+
source: "first-party",
|
|
466
|
+
width: 3,
|
|
467
|
+
windowed: false,
|
|
468
|
+
buildSql: fixed(
|
|
469
|
+
"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",
|
|
470
|
+
),
|
|
471
|
+
config: {
|
|
472
|
+
xKey: "date",
|
|
473
|
+
yKey: "rate",
|
|
474
|
+
yFormatter: "percent",
|
|
475
|
+
pivot: {
|
|
476
|
+
xKey: "date",
|
|
477
|
+
seriesKey: "period",
|
|
478
|
+
valueKey: "rate",
|
|
479
|
+
},
|
|
480
|
+
colors: ["#10b981", "#8b5cf6"],
|
|
481
|
+
description:
|
|
482
|
+
"First-seen daily cohorts in the selected range. Cohorts must be at least seven days old so both lines are mature.",
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
key: "one-day-retention-by-template",
|
|
487
|
+
title: "1d Retention by Template",
|
|
488
|
+
chartType: "line",
|
|
489
|
+
source: "first-party",
|
|
490
|
+
width: 2,
|
|
491
|
+
windowed: false,
|
|
492
|
+
buildSql: fixed(ONE_DAY_RETENTION_BY_TEMPLATE_SQL),
|
|
493
|
+
config: {
|
|
494
|
+
xKey: "date",
|
|
495
|
+
yKey: "rate",
|
|
496
|
+
yFormatter: "percent",
|
|
497
|
+
pivot: {
|
|
498
|
+
xKey: "date",
|
|
499
|
+
seriesKey: "template",
|
|
500
|
+
valueKey: "rate",
|
|
501
|
+
},
|
|
502
|
+
description:
|
|
503
|
+
"Per-template first-seen cohorts in the selected range. A user is retained when they return to the same template the next day.",
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
key: "seven-day-retention-by-template",
|
|
508
|
+
title: "7d Retention by Template",
|
|
509
|
+
chartType: "line",
|
|
510
|
+
source: "first-party",
|
|
511
|
+
width: 2,
|
|
512
|
+
windowed: false,
|
|
513
|
+
buildSql: fixed(SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL),
|
|
514
|
+
config: {
|
|
515
|
+
xKey: "date",
|
|
516
|
+
yKey: "rate",
|
|
517
|
+
yFormatter: "percent",
|
|
518
|
+
pivot: {
|
|
519
|
+
xKey: "date",
|
|
520
|
+
seriesKey: "template",
|
|
521
|
+
valueKey: "rate",
|
|
522
|
+
},
|
|
523
|
+
description:
|
|
524
|
+
"Per-template first-seen cohorts in the selected range. A user is retained when they return to the same template seven days later.",
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
key: "dau-over-time",
|
|
529
|
+
title: "DAU by Template",
|
|
530
|
+
chartType: "area",
|
|
531
|
+
source: "first-party",
|
|
532
|
+
width: 2,
|
|
533
|
+
windowed: false,
|
|
534
|
+
buildSql: fixed(
|
|
535
|
+
"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",
|
|
536
|
+
),
|
|
537
|
+
config: {
|
|
538
|
+
xKey: "date",
|
|
539
|
+
yKey: "users",
|
|
540
|
+
yFormatter: "number",
|
|
541
|
+
pivot: {
|
|
542
|
+
xKey: "date",
|
|
543
|
+
seriesKey: "template",
|
|
544
|
+
valueKey: "users",
|
|
545
|
+
},
|
|
546
|
+
stacked: true,
|
|
547
|
+
description:
|
|
548
|
+
"Distinct active users per day in the selected time range, stacked by template. Uses user_id first, then anonymous_id.",
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
key: "wau-over-time",
|
|
553
|
+
title: "WAU by Template",
|
|
554
|
+
chartType: "area",
|
|
555
|
+
source: "first-party",
|
|
556
|
+
width: 2,
|
|
557
|
+
windowed: false,
|
|
558
|
+
buildSql: fixed(
|
|
559
|
+
"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",
|
|
560
|
+
),
|
|
561
|
+
config: {
|
|
562
|
+
xKey: "date",
|
|
563
|
+
yKey: "users",
|
|
564
|
+
yFormatter: "number",
|
|
565
|
+
pivot: {
|
|
566
|
+
xKey: "date",
|
|
567
|
+
seriesKey: "template",
|
|
568
|
+
valueKey: "users",
|
|
569
|
+
},
|
|
570
|
+
stacked: true,
|
|
326
571
|
description:
|
|
327
|
-
"
|
|
572
|
+
"Trailing 7-day distinct active users for each active date in the selected time range, stacked by template.",
|
|
328
573
|
},
|
|
329
574
|
},
|
|
330
575
|
|
|
@@ -3,4 +3,4 @@ type: added
|
|
|
3
3
|
date: 2026-06-23
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Added a heads-up explaining Google's 'app not verified' screen when connecting your calendar, so you know it's safe to continue
|
|
6
|
+
Added a heads-up explaining Google's 'app not verified' screen when signing in or connecting your calendar, so you know it's safe to continue
|
|
@@ -16,6 +16,18 @@ export default createAuthPlugin({
|
|
|
16
16
|
"Manages availability and booking links automatically",
|
|
17
17
|
"Answers schedule questions and resolves conflicts instantly",
|
|
18
18
|
],
|
|
19
|
+
runLocalCommand:
|
|
20
|
+
"npx @agent-native/core@latest create my-calendar-app --template calendar",
|
|
21
|
+
},
|
|
22
|
+
googleSignInNotice: {
|
|
23
|
+
host: "calendar.agent-native.com",
|
|
24
|
+
title: "Google may show a warning",
|
|
25
|
+
body: [
|
|
26
|
+
"You'll see this screen because this demo uses Agent-Native's Google app, not a Google-reviewed public app.",
|
|
27
|
+
"It's safe to continue: click Advanced, then “Go to … (unsafe)” to finish signing in.",
|
|
28
|
+
],
|
|
29
|
+
continueLabel: "Continue to Google",
|
|
30
|
+
cancelLabel: "Run locally",
|
|
19
31
|
},
|
|
20
32
|
publicPaths: [
|
|
21
33
|
"/book",
|
|
@@ -57,13 +57,13 @@ the recorder page itself. The Clips Chrome extension is the active-tab path for
|
|
|
57
57
|
browser logs: it launches `/record` with an extension capture session and passes
|
|
58
58
|
`developerLogs=1/0`, then saves diagnostics with source `extension`.
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
install-choice component
|
|
63
|
-
two options: Chrome extension for browser logs, and desktop app for the
|
|
64
|
-
seamless native capture.
|
|
65
|
-
|
|
66
|
-
listing.
|
|
60
|
+
The Web Store listing is live, so the public Chrome extension option shows by
|
|
61
|
+
default. UI prompts that otherwise say "Download desktop app" use the shared
|
|
62
|
+
install-choice component (`CaptureInstallButton` / `CaptureInstallInlineLink`)
|
|
63
|
+
to offer two options: Chrome extension for browser logs, and desktop app for the
|
|
64
|
+
most seamless native capture. Set `VITE_CLIPS_CHROME_EXTENSION_ENABLED=0` to hide
|
|
65
|
+
the Chrome option again, or `VITE_CLIPS_CHROME_EXTENSION_URL` to point at a
|
|
66
|
+
different listing.
|
|
67
67
|
|
|
68
68
|
## Pause / resume
|
|
69
69
|
|
|
@@ -65,8 +65,12 @@ Detailed media, meeting, dictation, editing, and sharing rules live in
|
|
|
65
65
|
the recording session. `save-browser-diagnostics` is UI/internal and stores
|
|
66
66
|
bounded console logs plus fetch/XHR method, URL path/query keys, status, and
|
|
67
67
|
duration; it never captures headers, bodies, cookies, or query values. Use
|
|
68
|
-
`get-recording-player-data` for full diagnostics when you have editor access
|
|
69
|
-
|
|
68
|
+
`get-recording-player-data` for full diagnostics when you have editor access.
|
|
69
|
+
Public agent context exposes the redacted console stream (all levels) as
|
|
70
|
+
`browserDiagnostics.consoleLogs` and the fetch/XHR stream as
|
|
71
|
+
`browserDiagnostics.networkRequests` (method, sanitized URL with query values
|
|
72
|
+
redacted, status, duration), plus `consoleIssues` and `failedNetworkRequests`
|
|
73
|
+
highlights. All bounded; page URL, headers, bodies, and cookies stay omitted.
|
|
70
74
|
- The Chrome extension lives in `chrome-extension/`. It launches `/record` with
|
|
71
75
|
`clipsExtensionId` and `clipsCaptureSessionId`, then the recorder sends
|
|
72
76
|
`CLIPS_CAPTURE_START/STOP/CANCEL` back to the extension. The extension uses
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import { Link } from "react-router";
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
IconBrandApple,
|
|
5
|
+
IconBrandChrome,
|
|
6
|
+
IconBrandWindows,
|
|
5
7
|
IconChevronDown,
|
|
6
8
|
IconDeviceDesktop,
|
|
7
9
|
IconExternalLink,
|
|
@@ -35,8 +37,22 @@ type CaptureInstallInlineLinkProps = PopoverPlacement & {
|
|
|
35
37
|
desktopHref?: string;
|
|
36
38
|
};
|
|
37
39
|
|
|
40
|
+
/**
|
|
41
|
+
* The desktop-app tile shows the icon for the visitor's current OS — Apple on
|
|
42
|
+
* macOS, Windows on Windows — and falls back to a neutral desktop glyph on other
|
|
43
|
+
* platforms or during SSR. The Chrome tile always uses the Chrome brand icon.
|
|
44
|
+
*/
|
|
45
|
+
function desktopOsIcon(): typeof IconDeviceDesktop {
|
|
46
|
+
if (typeof navigator === "undefined") return IconDeviceDesktop;
|
|
47
|
+
const ua = navigator.userAgent;
|
|
48
|
+
if (/Windows/i.test(ua)) return IconBrandWindows;
|
|
49
|
+
if (/Mac|iPhone|iPad/i.test(ua)) return IconBrandApple;
|
|
50
|
+
return IconDeviceDesktop;
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
function InstallOptionsContent({ desktopHref = "/download" }) {
|
|
39
54
|
const chromeAvailable = Boolean(clipsChromeExtensionUrl);
|
|
55
|
+
const DesktopIcon = desktopOsIcon();
|
|
40
56
|
|
|
41
57
|
return (
|
|
42
58
|
<div className="grid gap-2">
|
|
@@ -57,7 +73,7 @@ function InstallOptionsContent({ desktopHref = "/download" }) {
|
|
|
57
73
|
rel="noreferrer"
|
|
58
74
|
className="flex items-start gap-3 rounded-md border border-border p-3 text-left transition hover:bg-accent"
|
|
59
75
|
>
|
|
60
|
-
<
|
|
76
|
+
<IconBrandChrome className="mt-0.5 h-4 w-4 shrink-0 text-primary" />
|
|
61
77
|
<span className="min-w-0 flex-1">
|
|
62
78
|
<span className="block text-sm font-medium">Chrome extension</span>
|
|
63
79
|
<span className="mt-0.5 block text-xs leading-snug text-muted-foreground">
|
|
@@ -69,7 +85,7 @@ function InstallOptionsContent({ desktopHref = "/download" }) {
|
|
|
69
85
|
</a>
|
|
70
86
|
) : (
|
|
71
87
|
<div className="flex items-start gap-3 rounded-md border border-dashed border-border p-3 text-left opacity-70">
|
|
72
|
-
<
|
|
88
|
+
<IconBrandChrome className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
|
|
73
89
|
<span className="min-w-0 flex-1">
|
|
74
90
|
<span className="block text-sm font-medium">Chrome extension</span>
|
|
75
91
|
<span className="mt-0.5 block text-xs leading-snug text-muted-foreground">
|
|
@@ -83,7 +99,7 @@ function InstallOptionsContent({ desktopHref = "/download" }) {
|
|
|
83
99
|
to={desktopHref}
|
|
84
100
|
className="flex items-start gap-3 rounded-md border border-border p-3 text-left transition hover:bg-accent"
|
|
85
101
|
>
|
|
86
|
-
<
|
|
102
|
+
<DesktopIcon className="mt-0.5 h-4 w-4 shrink-0 text-primary" />
|
|
87
103
|
<span className="min-w-0 flex-1">
|
|
88
104
|
<span className="block text-sm font-medium">Desktop app</span>
|
|
89
105
|
<span className="mt-0.5 block text-xs leading-snug text-muted-foreground">
|