@agent-native/core 0.145.8 → 0.146.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/templates/analytics/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/analytics/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/analytics/actions/get-sql-dashboard.ts +20 -4
- package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +59 -24
- package/corpus/templates/analytics/changelog/2026-08-07-analytics-migrations-now-use-bounded-pressure-aware-backgrou.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-07-template-activity-charts-now-show-only-core-agent-native-app.md +6 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +110 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +13 -13
- package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +448 -0
- package/corpus/templates/analytics/server/jobs/analytics-rollup-backfill.ts +2 -2
- package/corpus/templates/analytics/server/lib/analytics-rollup-lock.ts +4 -5
- package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +35 -0
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +3 -5
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +10 -4
- package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +4 -11
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +74 -28
- package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +2 -5
- package/corpus/templates/analytics/server/plugins/db.ts +42 -0
- package/corpus/templates/analytics/server/routes/api/analytics-bigquery/backfill.post.ts +55 -0
- package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +22 -4
- package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/assets/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/brain/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/brain/server/lib/connectors.ts +38 -14
- package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/calendar/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/clips/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/content/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/crm/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/design/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/dispatch/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/factory/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/forms/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/macros/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/mail/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/plan/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/slides/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +13 -0
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -0
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +5 -0
- package/corpus/templates/slides/app/components/presentation/PresenterView.tsx +5 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +3 -0
- package/corpus/templates/slides/app/pages/Presentation.tsx +3 -0
- package/corpus/templates/slides/app/pages/SharedPresentation.tsx +5 -0
- package/corpus/templates/slides/app/routes/slide.tsx +10 -5
- package/corpus/templates/slides/changelog/2026-08-07-slide-previews-and-present-now-keep-deck-colors-consistent.md +6 -0
- package/corpus/templates/slides/server/db/schema.ts +1 -0
- package/corpus/templates/slides/server/handlers/share.ts +51 -3
- package/corpus/templates/slides/server/plugins/db.ts +5 -0
- package/corpus/templates/slides/shared/api.ts +2 -0
- package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/tasks/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/dist/cli/code-agent-executor.js +8 -3
- package/dist/client/ConnectBuilderCard.d.ts +7 -7
- package/dist/client/ConnectBuilderCard.js +45 -20
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/config.d.ts +9 -2
- package/dist/config.js +38 -2
- package/dist/db/client.d.ts +13 -1
- package/dist/db/client.js +76 -2
- package/dist/db/create-get-db.js +2 -2
- package/dist/deploy/build.js +5 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/docs/search.js +3 -2
- package/dist/server/agent-chat/prompt-resources.js +4 -3
- package/dist/server/agents-bundle.d.ts +20 -3
- package/dist/server/agents-bundle.js +57 -30
- package/dist/server/better-auth-instance.js +2 -2
- package/dist/shared/runtime-config.js +1 -1
- package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/dist/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/dist/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/dist/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/dist/vite/agent-native-config-loader.d.ts +10 -0
- package/dist/vite/agent-native-config-loader.js +56 -0
- package/dist/vite/agents-bundle-plugin.d.ts +5 -1
- package/dist/vite/agents-bundle-plugin.js +29 -6
- package/dist/vite/client.d.ts +4 -2
- package/dist/vite/client.js +4 -49
- package/dist/vite/index.d.ts +2 -2
- package/dist/vite/index.js +1 -1
- package/docs/content/agent-native-config.mdx +43 -17
- package/docs/content/environment-variables.mdx +1 -1
- package/package.json +3 -3
- package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/src/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/src/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/src/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
|
@@ -115,10 +115,40 @@ function fixed(sql: string): (window?: MetricWindow) => string {
|
|
|
115
115
|
|
|
116
116
|
const TEMPLATE_EXPR =
|
|
117
117
|
"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')";
|
|
118
|
+
/**
|
|
119
|
+
* The built-in traffic dashboard is about the shipped core apps, not every
|
|
120
|
+
* host or preview artifact that can share the browser analytics key.
|
|
121
|
+
*
|
|
122
|
+
* Keep this aligned with `coreTemplates()` in the shared app catalog. The
|
|
123
|
+
* analytics template is deliberately self-contained at runtime, so its
|
|
124
|
+
* production bundle does not depend on another workspace package being built
|
|
125
|
+
* before this template.
|
|
126
|
+
*/
|
|
127
|
+
export const FIRST_PARTY_TEMPLATE_NAMES = [
|
|
128
|
+
"analytics",
|
|
129
|
+
"assets",
|
|
130
|
+
"brain",
|
|
131
|
+
"calendar",
|
|
132
|
+
"chat",
|
|
133
|
+
"clips",
|
|
134
|
+
"content",
|
|
135
|
+
"design",
|
|
136
|
+
"dispatch",
|
|
137
|
+
"forms",
|
|
138
|
+
"mail",
|
|
139
|
+
"plan",
|
|
140
|
+
"slides",
|
|
141
|
+
] as const;
|
|
142
|
+
const FIRST_PARTY_TEMPLATE_SQL_LIST = FIRST_PARTY_TEMPLATE_NAMES.map(
|
|
143
|
+
(name) => `'${name}'`,
|
|
144
|
+
).join(", ");
|
|
145
|
+
const FIRST_PARTY_TEMPLATE_FILTER = `lower(${TEMPLATE_EXPR}) IN (${FIRST_PARTY_TEMPLATE_SQL_LIST})`;
|
|
118
146
|
const KNOWN_TEMPLATE_FILTER = `${TEMPLATE_EXPR} <> 'unknown'`;
|
|
119
147
|
const PRODUCT_ACTIVITY_TEMPLATE_FILTER = `lower(${TEMPLATE_EXPR}) <> 'docs'`;
|
|
120
148
|
const MARKETING_SITE_TEMPLATE_FILTER = `lower(${TEMPLATE_EXPR}) <> 'www'`;
|
|
121
149
|
const KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER = `${KNOWN_TEMPLATE_FILTER} AND ${PRODUCT_ACTIVITY_TEMPLATE_FILTER}`;
|
|
150
|
+
const FIRST_PARTY_PRODUCT_ACTIVITY_TEMPLATE_FILTER = `${PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${FIRST_PARTY_TEMPLATE_FILTER}`;
|
|
151
|
+
const FIRST_PARTY_KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER = `${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${FIRST_PARTY_TEMPLATE_FILTER}`;
|
|
122
152
|
const EVENT_DATE_SQL = "event_date";
|
|
123
153
|
const EVENT_DATE_FILTER_SQL = EVENT_DATE_SQL;
|
|
124
154
|
const USER_KEY_SQL = "NULLIF(user_key, '')";
|
|
@@ -225,7 +255,7 @@ const TOTAL_SIGNUPS_SQL = `SELECT COUNT(*) AS signups FROM analytics_events WHER
|
|
|
225
255
|
const LEGACY_SEED_SIGNUPS_TIME_RANGE_FILTER = `('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND ${EVENT_DATE_SQL} >= ${daysAgoSql(7)}) OR ('{{timeRange}}' = '30d' AND ${EVENT_DATE_SQL} >= ${daysAgoSql(30)}) OR ('{{timeRange}}' = '90d' AND ${EVENT_DATE_SQL} >= ${daysAgoSql(90)}) OR ('{{timeRange}}' = '180d' AND ${EVENT_DATE_SQL} >= ${daysAgoSql(180)}) OR ('{{timeRange}}' = '365d' AND ${EVENT_DATE_SQL} >= ${daysAgoSql(365)}))`;
|
|
226
256
|
export const LEGACY_SEED_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 ${LEGACY_SEED_SIGNUPS_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`;
|
|
227
257
|
export const LEGACY_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`;
|
|
228
|
-
export const SIGNUPS_OVER_TIME_SQL = `WITH digits AS (SELECT 0 AS n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9), offsets AS (SELECT ones.n + tens.n * 10 + hundreds.n * 100 AS n FROM digits ones CROSS JOIN digits tens CROSS JOIN digits hundreds WHERE hundreds.n < 8), 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`;
|
|
258
|
+
export const SIGNUPS_OVER_TIME_SQL = `WITH digits AS (SELECT 0 AS n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9), offsets AS (SELECT ones.n + tens.n * 10 + hundreds.n * 100 AS n FROM digits ones CROSS JOIN digits tens CROSS JOIN digits hundreds WHERE hundreds.n < 8), 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} AND ${FIRST_PARTY_TEMPLATE_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`;
|
|
229
259
|
export const LEGACY_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(14)} 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 '1-7d return' AS period UNION ALL SELECT '7-14d return' AS period), retained AS (SELECT cw.date, '1-7d return' 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 > cw.cohort_date AND b.event_date <= to_char(cw.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD') GROUP BY cw.date UNION ALL SELECT cw.date, '7-14d return' 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') AND b.event_date <= to_char(cw.cohort_date::date + INTERVAL '14 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`;
|
|
230
260
|
export const LEGACY_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}), ranked_first_seen AS (SELECT user_key, template, event_date AS cohort_date, ROW_NUMBER() OVER (PARTITION BY user_key ORDER BY event_date, template) AS rn FROM base), first_seen AS (SELECT user_key, template, cohort_date FROM ranked_first_seen WHERE rn = 1), cohorts AS (SELECT user_key, template, cohort_date FROM first_seen WHERE cohort_date <= ${daysAgoSql(7)} AND ${dashboardTimeRangeFilter("cohort_date")}), cohort_sizes AS (SELECT template, COUNT(DISTINCT user_key) AS users FROM cohorts GROUP BY template), retained AS (SELECT c.template, COUNT(DISTINCT c.user_key) AS retained FROM cohorts c JOIN base b ON b.user_key = c.user_key AND b.event_date > c.cohort_date AND b.event_date <= to_char(c.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD') GROUP BY c.template) SELECT 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.template = cs.template WHERE cs.users >= ${PER_TEMPLATE_RETENTION_MIN_COHORT_SIZE} ORDER BY rate DESC, cs.users DESC, cs.template`;
|
|
231
261
|
export const LEGACY_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}), ranked_first_seen AS (SELECT user_key, template, event_date AS cohort_date, ROW_NUMBER() OVER (PARTITION BY user_key ORDER BY event_date, template) AS rn FROM base), first_seen AS (SELECT user_key, template, cohort_date FROM ranked_first_seen WHERE rn = 1), cohorts AS (SELECT user_key, template, cohort_date FROM first_seen WHERE cohort_date <= ${daysAgoSql(14)} AND ${dashboardTimeRangeFilter("cohort_date")}), cohort_sizes AS (SELECT template, COUNT(DISTINCT user_key) AS users FROM cohorts GROUP BY template), retained AS (SELECT c.template, 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') AND b.event_date <= to_char(c.cohort_date::date + INTERVAL '14 days', 'YYYY-MM-DD') GROUP BY c.template) SELECT 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.template = cs.template WHERE cs.users >= ${PER_TEMPLATE_RETENTION_MIN_COHORT_SIZE} ORDER BY rate DESC, cs.users DESC, cs.template`;
|
|
@@ -259,7 +289,11 @@ export const LEGACY_V0_ONE_DAY_RETENTION_BY_TEMPLATE_SQL =
|
|
|
259
289
|
"CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))))",
|
|
260
290
|
"CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD')))",
|
|
261
291
|
);
|
|
262
|
-
const
|
|
292
|
+
export const LEGACY_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`;
|
|
293
|
+
export const SIGNUPS_BY_TEMPLATE_SQL = `${LEGACY_SIGNUPS_BY_TEMPLATE_SQL.replace(
|
|
294
|
+
` AND ${DASHBOARD_EMAIL_FILTER} GROUP BY`,
|
|
295
|
+
` AND ${DASHBOARD_EMAIL_FILTER} AND ${FIRST_PARTY_TEMPLATE_FILTER} GROUP BY`,
|
|
296
|
+
)}`;
|
|
263
297
|
export const LEGACY_RECURRING_USERS_BY_TEMPLATE_SQL = `WITH all_users AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${EVENT_DATE_SQL}, user_id, ${TEMPLATE_EXPR} AS template FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), first_seen AS (SELECT user_key, MIN(event_date) AS first_date FROM all_users GROUP BY user_key) SELECT a.event_date AS date, a.template AS template, COUNT(DISTINCT a.user_key) AS users FROM all_users a JOIN first_seen f ON f.user_key = a.user_key WHERE a.event_date <> f.first_date AND a.template <> 'unknown' AND ${dashboardTimeRangeFilter("a.event_date")} GROUP BY 1, 2 ORDER BY date, template`;
|
|
264
298
|
export const LEGACY_RECURRING_USERS_BY_TEMPLATE_WEEKLY_SQL = `WITH all_users AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${EVENT_DATE_SQL}, user_id, ${TEMPLATE_EXPR} AS template FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), first_seen AS (SELECT user_key, MIN(event_date) AS first_date FROM all_users GROUP BY user_key) SELECT to_char(date_trunc('week', a.event_date::date), 'YYYY-MM-DD') AS date, a.template AS template, COUNT(DISTINCT a.user_key) AS users FROM all_users a JOIN first_seen f ON f.user_key = a.user_key WHERE a.event_date <> f.first_date AND a.template <> 'unknown' AND ${dashboardTimeRangeFilter("a.event_date")} GROUP BY 1, 2 ORDER BY date, template`;
|
|
265
299
|
const OBSERVED_ACTIVITY_LOOKBACK_FILTER = `${EVENT_DATE_SQL} >= ${daysAgoSql(OBSERVED_ACTIVITY_LOOKBACK_DAYS)}`;
|
|
@@ -291,7 +325,7 @@ export const PRE_MARKETING_SITE_RETENTION_OVER_TIME_SQL =
|
|
|
291
325
|
const RETENTION_OVER_TIME_SQL =
|
|
292
326
|
PRE_MARKETING_SITE_RETENTION_OVER_TIME_SQL.replace(
|
|
293
327
|
PRODUCT_ACTIVITY_TEMPLATE_FILTER,
|
|
294
|
-
`${
|
|
328
|
+
`${FIRST_PARTY_PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${MARKETING_SITE_TEMPLATE_FILTER}`,
|
|
295
329
|
);
|
|
296
330
|
export const PRE_MARKETING_SITE_ONE_DAY_RETENTION_BY_TEMPLATE_SQL =
|
|
297
331
|
LEGACY_ONE_DAY_RETENTION_BY_TEMPLATE_SQL.replace(
|
|
@@ -303,7 +337,7 @@ export const MATERIALIZED_ONE_DAY_RETENTION_BY_TEMPLATE_SQL =
|
|
|
303
337
|
KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER,
|
|
304
338
|
`${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${MARKETING_SITE_TEMPLATE_FILTER}`,
|
|
305
339
|
);
|
|
306
|
-
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 FROM analytics_events WHERE ${SIGNED_IN_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${
|
|
340
|
+
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 FROM analytics_events WHERE ${SIGNED_IN_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${FIRST_PARTY_KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${MARKETING_SITE_TEMPLATE_FILTER} AND ${OBSERVED_ACTIVITY_LOOKBACK_FILTER} GROUP BY 1, 2, 3), observed AS (SELECT user_key, event_date, FIRST_VALUE(template) OVER (PARTITION BY user_key ORDER BY event_date, template) AS starting_template, MIN(event_date) OVER (PARTITION BY user_key ORDER BY event_date, template) AS cohort_date FROM base), cohorts AS (SELECT user_key, starting_template AS template, cohort_date, MAX(CASE WHEN event_date > cohort_date AND event_date <= to_char(cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD') THEN 1 ELSE 0 END) AS retained FROM observed WHERE cohort_date <= ${daysAgoSql(7)} AND ${dashboardTimeRangeFilter("cohort_date")} GROUP BY user_key, starting_template, cohort_date) SELECT template, SUM(retained) AS retained_users, COUNT(*) AS cohort_users, SUM(retained)::float / NULLIF(COUNT(*), 0) AS rate FROM cohorts GROUP BY template HAVING COUNT(*) >= ${PER_TEMPLATE_RETENTION_MIN_COHORT_SIZE} ORDER BY rate DESC, cohort_users DESC, template`;
|
|
307
341
|
export const PRE_MARKETING_SITE_SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL =
|
|
308
342
|
LEGACY_SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL.replace(
|
|
309
343
|
`${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER}), ranked_first_seen`,
|
|
@@ -312,7 +346,7 @@ export const PRE_MARKETING_SITE_SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL =
|
|
|
312
346
|
const SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL =
|
|
313
347
|
PRE_MARKETING_SITE_SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL.replace(
|
|
314
348
|
KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER,
|
|
315
|
-
`${
|
|
349
|
+
`${FIRST_PARTY_KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${MARKETING_SITE_TEMPLATE_FILTER}`,
|
|
316
350
|
);
|
|
317
351
|
export const PRE_MARKETING_SITE_RECURRING_USERS_BY_TEMPLATE_SQL = `WITH first_seen AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, MIN(${EVENT_DATE_SQL}) AS first_date FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${OBSERVED_ACTIVITY_LOOKBACK_FILTER} GROUP BY 1), activity AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${EVENT_DATE_SQL} AS event_date, ${TEMPLATE_EXPR} AS template FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${OBSERVED_ACTIVITY_LOOKBACK_FILTER} AND ${DASHBOARD_TIME_RANGE_FILTER}) SELECT a.event_date AS date, a.template AS template, COUNT(DISTINCT a.user_key) AS users FROM activity a JOIN first_seen f ON f.user_key = a.user_key WHERE a.event_date <> f.first_date AND a.template <> 'unknown' GROUP BY 1, 2 ORDER BY date, template`;
|
|
318
352
|
export const DOUBLE_SCAN_RECURRING_USERS_BY_TEMPLATE_SQL =
|
|
@@ -320,14 +354,14 @@ export const DOUBLE_SCAN_RECURRING_USERS_BY_TEMPLATE_SQL =
|
|
|
320
354
|
PRODUCT_ACTIVITY_TEMPLATE_FILTER,
|
|
321
355
|
`${PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${MARKETING_SITE_TEMPLATE_FILTER}`,
|
|
322
356
|
);
|
|
323
|
-
const RECURRING_USERS_BY_TEMPLATE_SQL = `WITH activity AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${EVENT_DATE_SQL} AS event_date, ${TEMPLATE_EXPR} AS template, MIN(${EVENT_DATE_SQL}) OVER (PARTITION BY ${SIGNED_IN_ACTIVITY_KEY_SQL}) AS first_date FROM analytics_events WHERE ${
|
|
357
|
+
const RECURRING_USERS_BY_TEMPLATE_SQL = `WITH activity AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${EVENT_DATE_SQL} AS event_date, ${TEMPLATE_EXPR} AS template, MIN(${EVENT_DATE_SQL}) OVER (PARTITION BY ${SIGNED_IN_ACTIVITY_KEY_SQL}) AS first_date FROM analytics_events WHERE ${SIGNED_IN_ACTIVITY_FILTER} AND ${FIRST_PARTY_PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${MARKETING_SITE_TEMPLATE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${OBSERVED_ACTIVITY_LOOKBACK_FILTER}) SELECT event_date AS date, template, COUNT(DISTINCT user_key) AS users FROM activity WHERE event_date <> first_date AND template <> 'unknown' AND ${DASHBOARD_TIME_RANGE_FILTER} GROUP BY 1, 2 ORDER BY date, template`;
|
|
324
358
|
export const PRE_MARKETING_SITE_RECURRING_USERS_BY_TEMPLATE_WEEKLY_SQL = `WITH first_seen AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, MIN(${EVENT_DATE_SQL}) AS first_date FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${OBSERVED_ACTIVITY_LOOKBACK_FILTER} GROUP BY 1), activity AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${EVENT_DATE_SQL} AS event_date, ${TEMPLATE_EXPR} AS template FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${OBSERVED_ACTIVITY_LOOKBACK_FILTER} AND ${DASHBOARD_TIME_RANGE_FILTER}) SELECT to_char(date_trunc('week', a.event_date::date), 'YYYY-MM-DD') AS date, a.template AS template, COUNT(DISTINCT a.user_key) AS users FROM activity a JOIN first_seen f ON f.user_key = a.user_key WHERE a.event_date <> f.first_date AND a.template <> 'unknown' GROUP BY 1, 2 ORDER BY date, template`;
|
|
325
359
|
export const DOUBLE_SCAN_RECURRING_USERS_BY_TEMPLATE_WEEKLY_SQL =
|
|
326
360
|
PRE_MARKETING_SITE_RECURRING_USERS_BY_TEMPLATE_WEEKLY_SQL.replace(
|
|
327
361
|
PRODUCT_ACTIVITY_TEMPLATE_FILTER,
|
|
328
362
|
`${PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${MARKETING_SITE_TEMPLATE_FILTER}`,
|
|
329
363
|
);
|
|
330
|
-
const RECURRING_USERS_BY_TEMPLATE_WEEKLY_SQL = `WITH activity AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${EVENT_DATE_SQL} AS event_date, ${TEMPLATE_EXPR} AS template, MIN(${EVENT_DATE_SQL}) OVER (PARTITION BY ${SIGNED_IN_ACTIVITY_KEY_SQL}) AS first_date FROM analytics_events WHERE ${
|
|
364
|
+
const RECURRING_USERS_BY_TEMPLATE_WEEKLY_SQL = `WITH activity AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${EVENT_DATE_SQL} AS event_date, ${TEMPLATE_EXPR} AS template, MIN(${EVENT_DATE_SQL}) OVER (PARTITION BY ${SIGNED_IN_ACTIVITY_KEY_SQL}) AS first_date FROM analytics_events WHERE ${SIGNED_IN_ACTIVITY_FILTER} AND ${FIRST_PARTY_PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${MARKETING_SITE_TEMPLATE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${OBSERVED_ACTIVITY_LOOKBACK_FILTER}) SELECT to_char(date_trunc('week', event_date::date), 'YYYY-MM-DD') AS date, template, COUNT(DISTINCT user_key) AS users FROM activity WHERE event_date <> first_date AND template <> 'unknown' AND ${DASHBOARD_TIME_RANGE_FILTER} GROUP BY 1, 2 ORDER BY date, template`;
|
|
331
365
|
const LEGACY_RECURRING_USERS_DESCRIPTION =
|
|
332
366
|
"Daily signed-in visitors who are NOT on their all-time first active day (Recurring only), stacked by inferred template/app used that day. Docs traffic and unknown template are excluded.";
|
|
333
367
|
const LEGACY_RECURRING_USERS_WEEKLY_DESCRIPTION =
|
|
@@ -509,20 +543,44 @@ export const LEGACY_DAU_BY_TEMPLATE_SQL = `SELECT ${EVENT_DATE_SQL} AS date, ${T
|
|
|
509
543
|
export const PRE_MARKETING_SITE_DAU_BY_TEMPLATE_SQL = `SELECT ${EVENT_DATE_SQL} AS date, ${TEMPLATE_EXPR} AS template, COUNT(DISTINCT user_key) 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`;
|
|
510
544
|
const DAU_BY_TEMPLATE_SQL = PRE_MARKETING_SITE_DAU_BY_TEMPLATE_SQL.replace(
|
|
511
545
|
` AND ${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER} GROUP BY`,
|
|
512
|
-
` AND ${
|
|
546
|
+
` AND ${FIRST_PARTY_KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${MARKETING_SITE_TEMPLATE_FILTER} GROUP BY`,
|
|
513
547
|
);
|
|
514
548
|
export const LEGACY_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`;
|
|
515
549
|
export const PRE_MARKETING_SITE_WAU_BY_TEMPLATE_SQL = `WITH base AS (SELECT user_key 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`;
|
|
516
550
|
const WAU_BY_TEMPLATE_SQL = PRE_MARKETING_SITE_WAU_BY_TEMPLATE_SQL.replace(
|
|
517
551
|
` AND ${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${DASHBOARD_WAU_BASE_RANGE_FILTER}`,
|
|
518
|
-
` AND ${
|
|
552
|
+
` AND ${FIRST_PARTY_KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${MARKETING_SITE_TEMPLATE_FILTER} AND ${DASHBOARD_WAU_BASE_RANGE_FILTER}`,
|
|
519
553
|
);
|
|
520
554
|
const PRE_MARKETING_SITE_REPEAT_USERS_SQL = `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`;
|
|
521
555
|
const REPEAT_USERS_SQL = PRE_MARKETING_SITE_REPEAT_USERS_SQL.replace(
|
|
522
556
|
PRODUCT_ACTIVITY_TEMPLATE_FILTER,
|
|
523
|
-
`${
|
|
557
|
+
`${FIRST_PARTY_PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${MARKETING_SITE_TEMPLATE_FILTER}`,
|
|
524
558
|
);
|
|
525
559
|
|
|
560
|
+
export const LEGACY_TEMPLATE_INTEREST_OVER_TIME_SQL = `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`;
|
|
561
|
+
export const TEMPLATE_INTEREST_OVER_TIME_SQL = `${LEGACY_TEMPLATE_INTEREST_OVER_TIME_SQL.replace(
|
|
562
|
+
` AND ${DASHBOARD_EMAIL_FILTER} GROUP BY`,
|
|
563
|
+
` AND ${DASHBOARD_EMAIL_FILTER} AND ${FIRST_PARTY_TEMPLATE_FILTER} GROUP BY`,
|
|
564
|
+
)}`;
|
|
565
|
+
export const LEGACY_CLICKS_BY_TEMPLATE_SQL =
|
|
566
|
+
LEGACY_TEMPLATE_INTEREST_OVER_TIME_SQL;
|
|
567
|
+
const CLICKS_BY_TEMPLATE_SQL = TEMPLATE_INTEREST_OVER_TIME_SQL;
|
|
568
|
+
export const LEGACY_DEMO_CLICKS_OVER_TIME_SQL = `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`;
|
|
569
|
+
export const DEMO_CLICKS_OVER_TIME_SQL = `${LEGACY_DEMO_CLICKS_OVER_TIME_SQL.replace(
|
|
570
|
+
` AND ${DASHBOARD_EMAIL_FILTER} GROUP BY`,
|
|
571
|
+
` AND ${DASHBOARD_EMAIL_FILTER} AND ${FIRST_PARTY_TEMPLATE_FILTER} GROUP BY`,
|
|
572
|
+
)}`;
|
|
573
|
+
export const LEGACY_CLI_COPIES_OVER_TIME_SQL = `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`;
|
|
574
|
+
export const CLI_COPIES_OVER_TIME_SQL = `${LEGACY_CLI_COPIES_OVER_TIME_SQL.replace(
|
|
575
|
+
` AND ${DASHBOARD_EMAIL_FILTER} GROUP BY`,
|
|
576
|
+
` AND ${DASHBOARD_EMAIL_FILTER} AND ${FIRST_PARTY_TEMPLATE_FILTER} GROUP BY`,
|
|
577
|
+
)}`;
|
|
578
|
+
export const LEGACY_PAGEVIEWS_OVER_TIME_SQL = `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`;
|
|
579
|
+
export const PAGEVIEWS_OVER_TIME_SQL = `${LEGACY_PAGEVIEWS_OVER_TIME_SQL.replace(
|
|
580
|
+
` AND ${DASHBOARD_EMAIL_FILTER} GROUP BY`,
|
|
581
|
+
` AND ${DASHBOARD_EMAIL_FILTER} AND ${FIRST_PARTY_TEMPLATE_FILTER} GROUP BY`,
|
|
582
|
+
)}`;
|
|
583
|
+
|
|
526
584
|
/**
|
|
527
585
|
* Catalog entries. Order here is the default panel order when a caller passes
|
|
528
586
|
* metrics; callers can reorder by listing keys in the order they want.
|
|
@@ -745,9 +803,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
745
803
|
source: "first-party",
|
|
746
804
|
width: 2,
|
|
747
805
|
windowed: false,
|
|
748
|
-
buildSql: fixed(
|
|
749
|
-
`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`,
|
|
750
|
-
),
|
|
806
|
+
buildSql: fixed(TEMPLATE_INTEREST_OVER_TIME_SQL),
|
|
751
807
|
config: {
|
|
752
808
|
xKey: "date",
|
|
753
809
|
yKey: "count",
|
|
@@ -767,9 +823,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
767
823
|
source: "first-party",
|
|
768
824
|
width: 2,
|
|
769
825
|
windowed: false,
|
|
770
|
-
buildSql: fixed(
|
|
771
|
-
`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`,
|
|
772
|
-
),
|
|
826
|
+
buildSql: fixed(CLICKS_BY_TEMPLATE_SQL),
|
|
773
827
|
config: {
|
|
774
828
|
xKey: "date",
|
|
775
829
|
yKey: "count",
|
|
@@ -789,9 +843,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
789
843
|
source: "first-party",
|
|
790
844
|
width: 2,
|
|
791
845
|
windowed: false,
|
|
792
|
-
buildSql: fixed(
|
|
793
|
-
`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`,
|
|
794
|
-
),
|
|
846
|
+
buildSql: fixed(DEMO_CLICKS_OVER_TIME_SQL),
|
|
795
847
|
config: {
|
|
796
848
|
xKey: "date",
|
|
797
849
|
yKey: "count",
|
|
@@ -811,9 +863,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
811
863
|
source: "first-party",
|
|
812
864
|
width: 2,
|
|
813
865
|
windowed: false,
|
|
814
|
-
buildSql: fixed(
|
|
815
|
-
`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`,
|
|
816
|
-
),
|
|
866
|
+
buildSql: fixed(CLI_COPIES_OVER_TIME_SQL),
|
|
817
867
|
config: {
|
|
818
868
|
xKey: "date",
|
|
819
869
|
yKey: "count",
|
|
@@ -833,9 +883,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
833
883
|
source: "first-party",
|
|
834
884
|
width: 2,
|
|
835
885
|
windowed: false,
|
|
836
|
-
buildSql: fixed(
|
|
837
|
-
`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`,
|
|
838
|
-
),
|
|
886
|
+
buildSql: fixed(CLI_COPIES_OVER_TIME_SQL),
|
|
839
887
|
config: {
|
|
840
888
|
xKey: "date",
|
|
841
889
|
yKey: "count",
|
|
@@ -859,9 +907,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
859
907
|
windowed: false,
|
|
860
908
|
// Browser telemetry emits explicit `pageview` events with URL context, so
|
|
861
909
|
// keep pageview panels scoped to that event instead of all tracked events.
|
|
862
|
-
buildSql: fixed(
|
|
863
|
-
`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`,
|
|
864
|
-
),
|
|
910
|
+
buildSql: fixed(PAGEVIEWS_OVER_TIME_SQL),
|
|
865
911
|
config: {
|
|
866
912
|
xKey: "date",
|
|
867
913
|
yKey: "count",
|
|
@@ -29,20 +29,17 @@ function intervalMs(): number {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export default function registerAnalyticsRollupBackfillJobs(): void {
|
|
32
|
-
const isProd = process.env.NODE_ENV === "production";
|
|
33
32
|
const flag =
|
|
34
33
|
process.env.ANALYTICS_ROLLUP_BACKFILL_JOBS ??
|
|
35
34
|
process.env.RUN_BACKGROUND_JOBS;
|
|
36
|
-
const enabled =
|
|
37
|
-
!platformSchedulerOwnsBackfill() &&
|
|
38
|
-
(flag === "1" || (isProd && flag !== "0"));
|
|
35
|
+
const enabled = !platformSchedulerOwnsBackfill() && flag === "1";
|
|
39
36
|
|
|
40
37
|
if (!enabled) {
|
|
41
38
|
if (!skippingLogged) {
|
|
42
39
|
console.log(
|
|
43
40
|
platformSchedulerOwnsBackfill()
|
|
44
41
|
? "[analytics-rollups] Skipping in-process cron because the platform scheduler owns historical rollup backfills."
|
|
45
|
-
: "[analytics-rollups] Skipping historical rollup backfill (set ANALYTICS_ROLLUP_BACKFILL_JOBS=1 or RUN_BACKGROUND_JOBS=1 to enable
|
|
42
|
+
: "[analytics-rollups] Skipping historical rollup backfill (set ANALYTICS_ROLLUP_BACKFILL_JOBS=1 or RUN_BACKGROUND_JOBS=1 to enable explicitly)",
|
|
46
43
|
);
|
|
47
44
|
skippingLogged = true;
|
|
48
45
|
}
|
|
@@ -1492,6 +1492,48 @@ export const runAnalyticsMigrations = runMigrations(
|
|
|
1492
1492
|
CREATE INDEX IF NOT EXISTS dashboards_folder_idx ON dashboards (folder_id)`,
|
|
1493
1493
|
},
|
|
1494
1494
|
},
|
|
1495
|
+
{
|
|
1496
|
+
version: 139,
|
|
1497
|
+
name: "analytics-bigquery-backfill-jobs",
|
|
1498
|
+
sql: {
|
|
1499
|
+
postgres: `CREATE TABLE IF NOT EXISTS analytics_bigquery_backfill_jobs (
|
|
1500
|
+
id TEXT PRIMARY KEY,
|
|
1501
|
+
org_id TEXT NOT NULL,
|
|
1502
|
+
owner_email TEXT NOT NULL,
|
|
1503
|
+
table_ref TEXT NOT NULL,
|
|
1504
|
+
batch_size INTEGER NOT NULL DEFAULT 250,
|
|
1505
|
+
backfill_cursor TEXT,
|
|
1506
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
1507
|
+
copied_count INTEGER NOT NULL DEFAULT 0,
|
|
1508
|
+
lease_token TEXT,
|
|
1509
|
+
lease_expires_at TEXT,
|
|
1510
|
+
next_run_at TEXT NOT NULL DEFAULT (now()::text),
|
|
1511
|
+
last_error TEXT,
|
|
1512
|
+
completed_at TEXT,
|
|
1513
|
+
updated_at TEXT NOT NULL DEFAULT (now()::text)
|
|
1514
|
+
);
|
|
1515
|
+
CREATE INDEX IF NOT EXISTS analytics_bigquery_backfill_jobs_due_idx
|
|
1516
|
+
ON analytics_bigquery_backfill_jobs (status, next_run_at, lease_expires_at, updated_at)`,
|
|
1517
|
+
sqlite: `CREATE TABLE IF NOT EXISTS analytics_bigquery_backfill_jobs (
|
|
1518
|
+
id TEXT PRIMARY KEY,
|
|
1519
|
+
org_id TEXT NOT NULL,
|
|
1520
|
+
owner_email TEXT NOT NULL,
|
|
1521
|
+
table_ref TEXT NOT NULL,
|
|
1522
|
+
batch_size INTEGER NOT NULL DEFAULT 250,
|
|
1523
|
+
backfill_cursor TEXT,
|
|
1524
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
1525
|
+
copied_count INTEGER NOT NULL DEFAULT 0,
|
|
1526
|
+
lease_token TEXT,
|
|
1527
|
+
lease_expires_at TEXT,
|
|
1528
|
+
next_run_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1529
|
+
last_error TEXT,
|
|
1530
|
+
completed_at TEXT,
|
|
1531
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1532
|
+
);
|
|
1533
|
+
CREATE INDEX IF NOT EXISTS analytics_bigquery_backfill_jobs_due_idx
|
|
1534
|
+
ON analytics_bigquery_backfill_jobs (status, next_run_at, lease_expires_at, updated_at)`,
|
|
1535
|
+
},
|
|
1536
|
+
},
|
|
1495
1537
|
],
|
|
1496
1538
|
{ table: "analytics_migrations" },
|
|
1497
1539
|
);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { timingSafeEqual } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { createError, defineEventHandler, getHeader } from "h3";
|
|
4
|
+
|
|
5
|
+
// guard:allow-action-twin — the generated scheduled worker authenticates before invoking this job route.
|
|
6
|
+
import { runFirstPartyAnalyticsBigQueryBackfillOnce } from "../../../jobs/analytics-bigquery-backfill.js";
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
var __AGENT_NATIVE_ANALYTICS_BIGQUERY_BACKFILL_SCHEDULED_RUNTIME__:
|
|
10
|
+
| boolean
|
|
11
|
+
| undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function scheduledFunctionRuntime(): boolean {
|
|
15
|
+
return (
|
|
16
|
+
globalThis.__AGENT_NATIVE_ANALYTICS_BIGQUERY_BACKFILL_SCHEDULED_RUNTIME__ ===
|
|
17
|
+
true
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function cronSecret(): string | null {
|
|
22
|
+
const secret = process.env.ANALYTICS_BIGQUERY_BACKFILL_CRON_SECRET?.trim();
|
|
23
|
+
return secret ? secret : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function headerMatchesSecret(
|
|
27
|
+
header: string | undefined,
|
|
28
|
+
secret: string,
|
|
29
|
+
): boolean {
|
|
30
|
+
const expected = `Bearer ${secret}`;
|
|
31
|
+
const value = header?.trim() ?? "";
|
|
32
|
+
if (value.length !== expected.length) return false;
|
|
33
|
+
return timingSafeEqual(Buffer.from(value), Buffer.from(expected));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default defineEventHandler(async (event) => {
|
|
37
|
+
const secret = cronSecret();
|
|
38
|
+
const scheduledRuntime = scheduledFunctionRuntime();
|
|
39
|
+
const authenticated =
|
|
40
|
+
secret !== null &&
|
|
41
|
+
headerMatchesSecret(getHeader(event, "authorization"), secret);
|
|
42
|
+
if (!scheduledRuntime && !authenticated) {
|
|
43
|
+
throw createError({
|
|
44
|
+
statusCode: secret ? 401 : 503,
|
|
45
|
+
statusMessage: secret
|
|
46
|
+
? "Unauthorized"
|
|
47
|
+
: "ANALYTICS_BIGQUERY_BACKFILL_CRON_SECRET is required",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
ok: true,
|
|
53
|
+
...(await runFirstPartyAnalyticsBigQueryBackfillOnce()),
|
|
54
|
+
};
|
|
55
|
+
});
|
|
@@ -16,8 +16,10 @@ import {
|
|
|
16
16
|
buildDashboardAgentContext,
|
|
17
17
|
buildDashboardSeedAgentContext,
|
|
18
18
|
} from "../../lib/agent-readable-resource-context.js";
|
|
19
|
+
import { repairCanonicalFirstPartyDashboardQueries } from "../../lib/canonical-first-party-dashboard-repair.js";
|
|
19
20
|
import { loadDashboardSeed } from "../../lib/dashboard-seeds.js";
|
|
20
21
|
import type { DashboardRecord } from "../../lib/dashboards-store.js";
|
|
22
|
+
import { FIRST_PARTY_DASHBOARD_ID } from "../../lib/first-party-metric-catalog.js";
|
|
21
23
|
|
|
22
24
|
function queryString(value: unknown): string {
|
|
23
25
|
if (typeof value === "string") return value;
|
|
@@ -91,7 +93,13 @@ export default defineEventHandler(async (event) => {
|
|
|
91
93
|
setResponseStatus(event, 403);
|
|
92
94
|
return { error: "Invalid or expired agent access token" };
|
|
93
95
|
}
|
|
94
|
-
return buildDashboardSeedAgentContext(
|
|
96
|
+
return buildDashboardSeedAgentContext(
|
|
97
|
+
id,
|
|
98
|
+
id === FIRST_PARTY_DASHBOARD_ID
|
|
99
|
+
? repairCanonicalFirstPartyDashboardQueries(seed).config
|
|
100
|
+
: seed,
|
|
101
|
+
{ includeConfig: true },
|
|
102
|
+
);
|
|
95
103
|
}
|
|
96
104
|
|
|
97
105
|
if (!row) {
|
|
@@ -104,7 +112,17 @@ export default defineEventHandler(async (event) => {
|
|
|
104
112
|
return { error: "Invalid or expired agent access token" };
|
|
105
113
|
}
|
|
106
114
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
115
|
+
const dashboard = rowToDashboard(row);
|
|
116
|
+
return buildDashboardAgentContext(
|
|
117
|
+
id === FIRST_PARTY_DASHBOARD_ID
|
|
118
|
+
? {
|
|
119
|
+
...dashboard,
|
|
120
|
+
config: repairCanonicalFirstPartyDashboardQueries(dashboard.config)
|
|
121
|
+
.config,
|
|
122
|
+
}
|
|
123
|
+
: dashboard,
|
|
124
|
+
{
|
|
125
|
+
includeConfig: true,
|
|
126
|
+
},
|
|
127
|
+
);
|
|
110
128
|
});
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Turn visible project context, a proven thread, skill, or workflow into a
|
|
5
5
|
runnable Agent-Native app with simple buttons, visible agent steps, preview,
|
|
6
6
|
and deployment handoff. Use when a user invokes `/turn-into-app` or asks to
|
|
7
|
-
make a workflow into an app.
|
|
7
|
+
make a workflow into an app, including from Claude or ChatGPT on the web.
|
|
8
8
|
user-invocable: true
|
|
9
9
|
scope: both
|
|
10
10
|
metadata:
|
|
@@ -13,9 +13,32 @@ metadata:
|
|
|
13
13
|
|
|
14
14
|
# Turn Into App
|
|
15
15
|
|
|
16
|
+
## Host execution boundary
|
|
17
|
+
|
|
18
|
+
The build location depends on the host. Treat this as a hard routing rule:
|
|
19
|
+
|
|
20
|
+
- In Claude Web, ChatGPT Web, or a Claude/ChatGPT Project on the web, you are
|
|
21
|
+
the source analyst and handoff orchestrator. You must not build the app in
|
|
22
|
+
the host sandbox. Do not run `npm`, `pnpm`, `npx`, `agent-native create`, or
|
|
23
|
+
`add-app`; do not edit files, create artifacts, or start a local dev server.
|
|
24
|
+
After writing the bounded source brief, call the connected Dispatch action
|
|
25
|
+
`start-workspace-app-creation`. Pass the brief and repeatable workflow in
|
|
26
|
+
`prompt`, plus the inferred `appId`, `description`, `template`, and selected
|
|
27
|
+
`resourceIds` when available. This is the Builder handoff.
|
|
28
|
+
- Do not substitute the generic `create_workspace_app` MCP tool in an online
|
|
29
|
+
host. That tool is a local workspace scaffolder, not the Builder handoff.
|
|
30
|
+
- If `start-workspace-app-creation` is not available or Dispatch is not
|
|
31
|
+
authenticated, stop with the connector setup needed. Do not fall back to a
|
|
32
|
+
local build or claim that the app exists.
|
|
33
|
+
- In Claude Code, Codex Code, or another local code-agent runtime with a target
|
|
34
|
+
workspace, follow the local implementation steps below. The local agent may
|
|
35
|
+
scaffold, edit, run, and verify the app there.
|
|
36
|
+
|
|
16
37
|
## Default behavior
|
|
17
38
|
|
|
18
|
-
|
|
39
|
+
For a local code-agent runtime this is an end-to-end build skill, not a request
|
|
40
|
+
for an app proposal. For an online host, the end-to-end result is a verified
|
|
41
|
+
Builder handoff and the resulting workspace app, not code written in the host.
|
|
19
42
|
|
|
20
43
|
- With no argument, choose the source in this order: visible project context,
|
|
21
44
|
then the current thread. A fresh Claude or ChatGPT Project is a valid source
|
|
@@ -47,9 +70,12 @@ private web access, invent an importer, add fake OAuth, or scrape a logged-in
|
|
|
47
70
|
page. If the needed context is not visible, ask for an export, transcript, or
|
|
48
71
|
attachment and treat that artifact as imported source material.
|
|
49
72
|
|
|
50
|
-
|
|
51
|
-
buttons and agent handoffs, start its dev
|
|
52
|
-
continue through build/deployment handoff. Do
|
|
73
|
+
For local code-agent runtimes, deliver a fresh app in a new directory,
|
|
74
|
+
implement the repeatable workflow with buttons and agent handoffs, start its dev
|
|
75
|
+
server, verify the main path, and continue through build/deployment handoff. Do
|
|
76
|
+
not stop at a plan. For online hosts, call Dispatch first after the source brief,
|
|
77
|
+
then report the returned Builder branch/path and verify the workspace app through
|
|
78
|
+
Dispatch when it becomes available.
|
|
53
79
|
|
|
54
80
|
## Fresh project context mode
|
|
55
81
|
|
|
@@ -190,12 +216,12 @@ Do not use `create` for an existing workspace; it scaffolds a new standalone
|
|
|
190
216
|
workspace rather than adding an app to the current one.
|
|
191
217
|
|
|
192
218
|
When the source came from a fresh external Project and the target is a Builder
|
|
193
|
-
workspace,
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
219
|
+
workspace, the online host path is mandatory: after writing the source brief,
|
|
220
|
+
call `start-workspace-app-creation` with a concise prompt, the inferred app id,
|
|
221
|
+
and the brief's repeatable workflow. Pass selected workspace resource IDs when
|
|
222
|
+
they are available, rather than pasting entire knowledge files. Continue from
|
|
223
|
+
the returned workspace path or Builder branch handoff and report the actual
|
|
224
|
+
verification result. Do not run the local scaffold commands in this host mode.
|
|
199
225
|
|
|
200
226
|
Use a first-party template only when it materially fits the workflow. Keep the
|
|
201
227
|
new app independent from the source thread's working tree unless the user
|
|
@@ -51,13 +51,24 @@ also place the skill instructions or this reference in the Project
|
|
|
51
51
|
instructions/knowledge files. Then add and authenticate the Dispatch MCP
|
|
52
52
|
connector from the packaged `adapters/chatgpt-mcp/connector.json`.
|
|
53
53
|
|
|
54
|
+
For Claude Web, ChatGPT Web, and their web Projects, the host is an
|
|
55
|
+
orchestrator, not the build environment. After forming the source brief, call
|
|
56
|
+
the Dispatch `start-workspace-app-creation` action so Builder creates the app
|
|
57
|
+
in the connected workspace. Do not use the host's code interpreter, shell,
|
|
58
|
+
artifact editor, `create_workspace_app`, or local `pnpm`/`npm` commands. If the
|
|
59
|
+
handoff action is unavailable, stop and ask the user to authenticate Dispatch;
|
|
60
|
+
do not fall back to a local sandbox build. Claude Code and Codex Code are the
|
|
61
|
+
local-agent exception and may follow the implementation steps in the main
|
|
62
|
+
skill.
|
|
63
|
+
|
|
54
64
|
In a new Project chat, say:
|
|
55
65
|
|
|
56
66
|
```text
|
|
57
67
|
Turn this project into an app. Use the visible Project instructions,
|
|
58
68
|
knowledge files, and any selected successful runs as the source. Create the
|
|
59
|
-
app in the connected Agent-Native workspace
|
|
60
|
-
and report the real path and
|
|
69
|
+
app in the connected Agent-Native workspace through Dispatch and Builder, keep
|
|
70
|
+
the source brief bounded, and report the real Builder branch/path and
|
|
71
|
+
verification result. Do not build it in this chat's sandbox.
|
|
61
72
|
```
|
|
62
73
|
|
|
63
74
|
If the host does not show the Project instructions or files to the model, stop
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Turn visible project context, a proven thread, skill, or workflow into a
|
|
5
5
|
runnable Agent-Native app with simple buttons, visible agent steps, preview,
|
|
6
6
|
and deployment handoff. Use when a user invokes `/turn-into-app` or asks to
|
|
7
|
-
make a workflow into an app.
|
|
7
|
+
make a workflow into an app, including from Claude or ChatGPT on the web.
|
|
8
8
|
user-invocable: true
|
|
9
9
|
scope: both
|
|
10
10
|
metadata:
|
|
@@ -13,9 +13,32 @@ metadata:
|
|
|
13
13
|
|
|
14
14
|
# Turn Into App
|
|
15
15
|
|
|
16
|
+
## Host execution boundary
|
|
17
|
+
|
|
18
|
+
The build location depends on the host. Treat this as a hard routing rule:
|
|
19
|
+
|
|
20
|
+
- In Claude Web, ChatGPT Web, or a Claude/ChatGPT Project on the web, you are
|
|
21
|
+
the source analyst and handoff orchestrator. You must not build the app in
|
|
22
|
+
the host sandbox. Do not run `npm`, `pnpm`, `npx`, `agent-native create`, or
|
|
23
|
+
`add-app`; do not edit files, create artifacts, or start a local dev server.
|
|
24
|
+
After writing the bounded source brief, call the connected Dispatch action
|
|
25
|
+
`start-workspace-app-creation`. Pass the brief and repeatable workflow in
|
|
26
|
+
`prompt`, plus the inferred `appId`, `description`, `template`, and selected
|
|
27
|
+
`resourceIds` when available. This is the Builder handoff.
|
|
28
|
+
- Do not substitute the generic `create_workspace_app` MCP tool in an online
|
|
29
|
+
host. That tool is a local workspace scaffolder, not the Builder handoff.
|
|
30
|
+
- If `start-workspace-app-creation` is not available or Dispatch is not
|
|
31
|
+
authenticated, stop with the connector setup needed. Do not fall back to a
|
|
32
|
+
local build or claim that the app exists.
|
|
33
|
+
- In Claude Code, Codex Code, or another local code-agent runtime with a target
|
|
34
|
+
workspace, follow the local implementation steps below. The local agent may
|
|
35
|
+
scaffold, edit, run, and verify the app there.
|
|
36
|
+
|
|
16
37
|
## Default behavior
|
|
17
38
|
|
|
18
|
-
|
|
39
|
+
For a local code-agent runtime this is an end-to-end build skill, not a request
|
|
40
|
+
for an app proposal. For an online host, the end-to-end result is a verified
|
|
41
|
+
Builder handoff and the resulting workspace app, not code written in the host.
|
|
19
42
|
|
|
20
43
|
- With no argument, choose the source in this order: visible project context,
|
|
21
44
|
then the current thread. A fresh Claude or ChatGPT Project is a valid source
|
|
@@ -47,9 +70,12 @@ private web access, invent an importer, add fake OAuth, or scrape a logged-in
|
|
|
47
70
|
page. If the needed context is not visible, ask for an export, transcript, or
|
|
48
71
|
attachment and treat that artifact as imported source material.
|
|
49
72
|
|
|
50
|
-
|
|
51
|
-
buttons and agent handoffs, start its dev
|
|
52
|
-
continue through build/deployment handoff. Do
|
|
73
|
+
For local code-agent runtimes, deliver a fresh app in a new directory,
|
|
74
|
+
implement the repeatable workflow with buttons and agent handoffs, start its dev
|
|
75
|
+
server, verify the main path, and continue through build/deployment handoff. Do
|
|
76
|
+
not stop at a plan. For online hosts, call Dispatch first after the source brief,
|
|
77
|
+
then report the returned Builder branch/path and verify the workspace app through
|
|
78
|
+
Dispatch when it becomes available.
|
|
53
79
|
|
|
54
80
|
## Fresh project context mode
|
|
55
81
|
|
|
@@ -190,12 +216,12 @@ Do not use `create` for an existing workspace; it scaffolds a new standalone
|
|
|
190
216
|
workspace rather than adding an app to the current one.
|
|
191
217
|
|
|
192
218
|
When the source came from a fresh external Project and the target is a Builder
|
|
193
|
-
workspace,
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
219
|
+
workspace, the online host path is mandatory: after writing the source brief,
|
|
220
|
+
call `start-workspace-app-creation` with a concise prompt, the inferred app id,
|
|
221
|
+
and the brief's repeatable workflow. Pass selected workspace resource IDs when
|
|
222
|
+
they are available, rather than pasting entire knowledge files. Continue from
|
|
223
|
+
the returned workspace path or Builder branch handoff and report the actual
|
|
224
|
+
verification result. Do not run the local scaffold commands in this host mode.
|
|
199
225
|
|
|
200
226
|
Use a first-party template only when it materially fits the workflow. Keep the
|
|
201
227
|
new app independent from the source thread's working tree unless the user
|
|
@@ -51,13 +51,24 @@ also place the skill instructions or this reference in the Project
|
|
|
51
51
|
instructions/knowledge files. Then add and authenticate the Dispatch MCP
|
|
52
52
|
connector from the packaged `adapters/chatgpt-mcp/connector.json`.
|
|
53
53
|
|
|
54
|
+
For Claude Web, ChatGPT Web, and their web Projects, the host is an
|
|
55
|
+
orchestrator, not the build environment. After forming the source brief, call
|
|
56
|
+
the Dispatch `start-workspace-app-creation` action so Builder creates the app
|
|
57
|
+
in the connected workspace. Do not use the host's code interpreter, shell,
|
|
58
|
+
artifact editor, `create_workspace_app`, or local `pnpm`/`npm` commands. If the
|
|
59
|
+
handoff action is unavailable, stop and ask the user to authenticate Dispatch;
|
|
60
|
+
do not fall back to a local sandbox build. Claude Code and Codex Code are the
|
|
61
|
+
local-agent exception and may follow the implementation steps in the main
|
|
62
|
+
skill.
|
|
63
|
+
|
|
54
64
|
In a new Project chat, say:
|
|
55
65
|
|
|
56
66
|
```text
|
|
57
67
|
Turn this project into an app. Use the visible Project instructions,
|
|
58
68
|
knowledge files, and any selected successful runs as the source. Create the
|
|
59
|
-
app in the connected Agent-Native workspace
|
|
60
|
-
and report the real path and
|
|
69
|
+
app in the connected Agent-Native workspace through Dispatch and Builder, keep
|
|
70
|
+
the source brief bounded, and report the real Builder branch/path and
|
|
71
|
+
verification result. Do not build it in this chat's sandbox.
|
|
61
72
|
```
|
|
62
73
|
|
|
63
74
|
If the host does not show the Project instructions or files to the model, stop
|