From 14502010ed44b290e4b6e040d2f2d90cb845e7db Mon Sep 17 00:00:00 2001 From: ambergristle <101149854+ambergristle@users.noreply.github.com> Date: Thu, 26 Mar 2026 08:22:30 -0700 Subject: [PATCH 1/5] chore: combine tracking - add specificity to event name - combine all feature events into a single tracking event with object payload, allowing for simpler analytics --- packages/cli/index.ts | 1 + website/components/InputGroup.tsx | 9 ++++- website/components/Store.tsx | 5 ++- website/lib/track.ts | 65 ++++++++++--------------------- 4 files changed, 32 insertions(+), 48 deletions(-) diff --git a/packages/cli/index.ts b/packages/cli/index.ts index 0bc437ca4..9b1b5b67b 100644 --- a/packages/cli/index.ts +++ b/packages/cli/index.ts @@ -476,6 +476,7 @@ function checkRules(flags: string[]) { } async function retrieveHooks(hooks: Map): Promise> { + // const reIgnoreFile = /^(chunk-|asset-|#)/gi; const map = new Map<"after", Hook[]>(); const sortedHooks = Array.from(hooks.entries()).sort(([b1], [b2]) => { if (b1.config.enforce === "pre") return 1; diff --git a/website/components/InputGroup.tsx b/website/components/InputGroup.tsx index 8334a26c9..3898ee029 100644 --- a/website/components/InputGroup.tsx +++ b/website/components/InputGroup.tsx @@ -42,9 +42,14 @@ export default function InputGroup() { }; const handleCopy = (packageManager: string) => { - track("copy", { - flags: selectedFeaturesFlags(), + const flags = selectedFeaturesFlags().reduce((agg: Record, { flag, category }) => { + agg[category] = flag; + return agg; + }, {}); + + track("copy_scaffold", { package_manager: packageManager, + ...flags, }); setTooltipText("Copied to Clipboard!"); }; diff --git a/website/components/Store.tsx b/website/components/Store.tsx index 528a52614..29c905778 100644 --- a/website/components/Store.tsx +++ b/website/components/Store.tsx @@ -29,7 +29,10 @@ function initStore() { const selectedFeaturesFlags = createMemo(() => selectedFeatures() .filter((f) => !f.invisibleCli) - .map((f) => f.flag), + .map((f) => ({ + flag: f.flag, + category: f.category.toLowerCase().replaceAll(" ", "_"), + })), ); function selectPreset(ks: (Flags | CategoryLabels)[]) { diff --git a/website/lib/track.ts b/website/lib/track.ts index eede86361..644e3dbb4 100644 --- a/website/lib/track.ts +++ b/website/lib/track.ts @@ -1,53 +1,28 @@ -type Events = CopyEvent; - -interface CopyEvent { - name: "copy"; - data: { - flags: string[]; - package_manager: string; - }; -} - -interface Zaraz { - track: (event: T["name"], data: unknown) => Promise; -} - declare global { // eslint-disable-next-line no-var var zaraz: Zaraz | undefined; } -function generateUUID() { - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { - const r = (Math.random() * 16) | 0, - v = c === "x" ? r : (r & 0x3) | 0x8; - return v.toString(16); - }); +/** + * This signature is compatible with both Umami and GA + */ +export function track(name: T["name"], data: T["data"]) { + globalThis.zaraz?.track(name, data); +} + +interface Zaraz { + track: (event: T["name"], data: unknown) => Promise; } -export function track(event: T["name"], data: T["data"]) { - const interactionid = generateUUID(); - console.log({ - interactionid, - event, - data, - }); - switch (event) { - case "copy": { - if (data.flags.length === 0) { - globalThis.zaraz?.track(event, { - flag: undefined, - package_manager: data.package_manager, - interactionid, - }); - } - for (const flag of data.flags) { - globalThis.zaraz?.track(event, { - flag, - package_manager: data.package_manager, - interactionid, - }); - } - } - } +type TrackingEvent = CopyEvent; + +export interface CopyEvent { + name: "copy_scaffold"; + data: { + package_manager: string; + // could be interesting, but maybe tracking + // button clicks as part of a funnel is better + // is_preset: boolean; + [key: string]: string; + }; } From 8f8a4406d98f56a82b8e5b167f2e28f3ba9d4717 Mon Sep 17 00:00:00 2001 From: ambergristle <101149854+ambergristle@users.noreply.github.com> Date: Thu, 26 Mar 2026 08:41:11 -0700 Subject: [PATCH 2/5] chore: desctructure flag when creating command --- website/components/InputGroup.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/components/InputGroup.tsx b/website/components/InputGroup.tsx index 3898ee029..daa60766a 100644 --- a/website/components/InputGroup.tsx +++ b/website/components/InputGroup.tsx @@ -28,7 +28,7 @@ export default function InputGroup() { const [tooltipText, setTooltipText] = createSignal("Copy to Clipboard"); function getFlags() { - return selectedFeaturesFlags().map((flag) => `--${flag}`); + return selectedFeaturesFlags().map(({ flag }) => `--${flag}`); } const npm = createMemo(() => ["npm", "create", "vike@latest", "---", ...getFlags()]); From c33d108cf3a15697f6fd377e1774e52dad288756 Mon Sep 17 00:00:00 2001 From: ambergristle <101149854+ambergristle@users.noreply.github.com> Date: Thu, 26 Mar 2026 11:33:09 -0700 Subject: [PATCH 3/5] chore: flatten `multiple` categories - group multiple flags in arrays, then join with semicolon - will surface analytics data points as combinations --- website/components/InputGroup.tsx | 10 +++------- website/lib/track.ts | 33 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/website/components/InputGroup.tsx b/website/components/InputGroup.tsx index daa60766a..1c967f1b7 100644 --- a/website/components/InputGroup.tsx +++ b/website/components/InputGroup.tsx @@ -1,7 +1,7 @@ import { createMemo, createSignal, type JSX, useContext } from "solid-js"; import { isServer } from "solid-js/web"; import { StoreContext } from "#components/Store"; -import { track } from "../lib/track"; +import { formatFeatureFlags, track } from "../lib/track"; import { Cli } from "./Cli"; import Stackblitz from "./Stackblitz"; @@ -42,14 +42,10 @@ export default function InputGroup() { }; const handleCopy = (packageManager: string) => { - const flags = selectedFeaturesFlags().reduce((agg: Record, { flag, category }) => { - agg[category] = flag; - return agg; - }, {}); - + const selectedFlags = selectedFeaturesFlags(); track("copy_scaffold", { package_manager: packageManager, - ...flags, + ...formatFeatureFlags(selectedFlags), }); setTooltipText("Copied to Clipboard!"); }; diff --git a/website/lib/track.ts b/website/lib/track.ts index 644e3dbb4..3131fec29 100644 --- a/website/lib/track.ts +++ b/website/lib/track.ts @@ -26,3 +26,36 @@ export interface CopyEvent { [key: string]: string; }; } + +type FeatureFlag = { + flag: string; + category: string; +} + +export function formatFeatureFlags(selectedFlags: FeatureFlag[]): Record { + const categoriesMultiple = new Set(); + + const flags = selectedFlags.reduce((data, { flag, category }) => { + if (data[category]) { + if (Array.isArray(data[category])) { + data[category].push(flag); + } else { + // Track all keys converted to arrays + categoriesMultiple.add(category); + data[category] = [data[category], flag]; + } + } else { + data[category] = flag + } + return data; + }, {} as Record); + + // Recombine all arrays into strings + categoriesMultiple.forEach((category) => { + if (Array.isArray(flags[category])) { + flags[category] = flags[category].sort().join(':'); + } + }); + + return flags as Record; +} From 52ffeb0b7102483f808c85bd12790adc62fd46ad Mon Sep 17 00:00:00 2001 From: ambergristle <101149854+ambergristle@users.noreply.github.com> Date: Thu, 26 Mar 2026 11:33:44 -0700 Subject: [PATCH 4/5] chore: format --- website/lib/track.ts | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/website/lib/track.ts b/website/lib/track.ts index 3131fec29..277fd28e7 100644 --- a/website/lib/track.ts +++ b/website/lib/track.ts @@ -30,30 +30,33 @@ export interface CopyEvent { type FeatureFlag = { flag: string; category: string; -} +}; export function formatFeatureFlags(selectedFlags: FeatureFlag[]): Record { const categoriesMultiple = new Set(); - const flags = selectedFlags.reduce((data, { flag, category }) => { - if (data[category]) { - if (Array.isArray(data[category])) { - data[category].push(flag); + const flags = selectedFlags.reduce( + (data, { flag, category }) => { + if (data[category]) { + if (Array.isArray(data[category])) { + data[category].push(flag); + } else { + // Track all keys converted to arrays + categoriesMultiple.add(category); + data[category] = [data[category], flag]; + } } else { - // Track all keys converted to arrays - categoriesMultiple.add(category); - data[category] = [data[category], flag]; + data[category] = flag; } - } else { - data[category] = flag - } - return data; - }, {} as Record); + return data; + }, + {} as Record, + ); // Recombine all arrays into strings categoriesMultiple.forEach((category) => { if (Array.isArray(flags[category])) { - flags[category] = flags[category].sort().join(':'); + flags[category] = flags[category].sort().join(":"); } }); From 81f8e25e5051b245d930e57d0d476eebef58954a Mon Sep 17 00:00:00 2001 From: ambergristle <101149854+ambergristle@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:46:12 -0700 Subject: [PATCH 5/5] chore: remove comment --- packages/cli/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cli/index.ts b/packages/cli/index.ts index 9b1b5b67b..0bc437ca4 100644 --- a/packages/cli/index.ts +++ b/packages/cli/index.ts @@ -476,7 +476,6 @@ function checkRules(flags: string[]) { } async function retrieveHooks(hooks: Map): Promise> { - // const reIgnoreFile = /^(chunk-|asset-|#)/gi; const map = new Map<"after", Hook[]>(); const sortedHooks = Array.from(hooks.entries()).sort(([b1], [b2]) => { if (b1.config.enforce === "pre") return 1;