Skip to content

analytics: combine copy feature tracking events into one#708

Draft
ambergristle wants to merge 5 commits intovikejs:mainfrom
ambergristle:simplify-tracking
Draft

analytics: combine copy feature tracking events into one#708
ambergristle wants to merge 5 commits intovikejs:mainfrom
ambergristle:simplify-tracking

Conversation

@ambergristle
Copy link
Copy Markdown
Collaborator

@ambergristle ambergristle commented Mar 26, 2026

Summary by CodeRabbit

  • Refactor

    • Adjusted CLI hook discovery behavior.
    • Normalized selected feature metadata so selections include category information.
  • New Features

    • CLI flag generation now formats feature flags as standard CLI arguments (e.g., --flag).
  • Chores

    • Telemetry event renamed to "copy_scaffold" and now sends package manager plus per-category feature fields instead of a single flags array.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

📝 Walkthrough

Walkthrough

CLI hook file-ignore regex was commented out; tracking consolidated into a single copy_scaffold event with category-keyed properties; selectedFeaturesFlags now returns { flag, category }; InputGroup uses formatFeatureFlags and sends { package_manager, ...categories } to track; track signature and typings updated.

Changes

Cohort / File(s) Summary
Telemetry core
website/lib/track.ts
Reworked tracking API: track(name, data) signature/typings changed; CopyEvent renamed to copy_scaffold with data: { package_manager; [key: string]: string }; removed UUID/console/per-flag looping; added formatFeatureFlags(selectedFlags) helper that aggregates flags by normalized category.
Telemetry consumers
website/components/InputGroup.tsx, website/components/Store.tsx
selectedFeaturesFlags now returns objects { flag, category } (category normalized); InputGroup captures selectedFlags, calls formatFeatureFlags(selectedFlags), and tracks a single copy_scaffold event with { package_manager, ...formatted } instead of previous per-flag behavior.
CLI hook loader
packages/cli/index.ts
Commented-out the file-ignore regex (`// const reIgnoreFile = /^(chunk-
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The changes in packages/cli/index.ts appear unrelated to the tracking consolidation objective; the commented-out regex does not align with the PR's stated goals. Remove the commented-out reIgnoreFile definition from packages/cli/index.ts or explain its connection to the tracking consolidation effort.
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: combining multiple copy feature tracking events into a single event with an object payload.
Linked Issues check ✅ Passed All requirements from issue #702 are met: feature flags grouped by category, single tracking event call with object payload, specific event name 'copy_scaffold', parameter-level reporting preserved, and network calls reduced to one.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/cli/index.ts (1)

435-435: Remove commented-out dead code instead of leaving it in place.

The reIgnoreFile regex was unused here because the walk utility from @batijs/build already applies this same filter internally (see packages/build/src/index.ts:53). Rather than leaving this as a comment, delete the line entirely to keep the codebase clean.

🧹 Proposed fix
 async function retrieveHooks(hooks: Map<BoilerplateDefWithConfig, string>): Promise<Map<"after", Hook[]>> {
-  // const reIgnoreFile = /^(chunk-|asset-|#)/gi;
   const map = new Map<"after", Hook[]>();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/index.ts` at line 435, Remove the dead commented-out regex
declaration by deleting the line containing the commented `reIgnoreFile`
variable in packages/cli/index.ts; this unused `// const reIgnoreFile =
/^(chunk-|asset-|#)/gi;` should be removed because the `walk` utility in
`@batijs/build` already handles this filtering, so no other changes are required.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@website/components/InputGroup.tsx`:
- Around line 45-48: The reduce over selectedFeaturesFlags() (currently
producing const flags) overwrites flags when multiple selections share the same
category; change the accumulator so each category maps to an array of flags
instead of a single string: in the reduce callback for selectedFeaturesFlags()
check if agg[category] exists, push the current flag into the array or
initialize it with [flag], and return agg (so functions/components consuming
flags will get arrays per category rather than single values); update any
downstream usage of flags to handle arrays (or normalize to strings where
appropriate).

In `@website/components/Store.tsx`:
- Around line 32-35: selectedFeaturesFlags() currently maps features to objects
({ flag, category }) which breaks consumers like InputGroup.tsx that expect
strings and do `--${flag}`; change the mapping in selectedFeaturesFlags (the map
call shown) to return the flag string (e.g., f.flag) so consumers still receive
an array of flag string values, or alternatively update InputGroup.tsx to read
the flag property (e.g., use f.flag) if you prefer returning objects — pick one
approach and make all callers consistent (adjust selectedFeaturesFlags or
InputGroup.tsx references accordingly).

---

Nitpick comments:
In `@packages/cli/index.ts`:
- Line 435: Remove the dead commented-out regex declaration by deleting the line
containing the commented `reIgnoreFile` variable in packages/cli/index.ts; this
unused `// const reIgnoreFile = /^(chunk-|asset-|#)/gi;` should be removed
because the `walk` utility in `@batijs/build` already handles this filtering, so
no other changes are required.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 963199ef-4ad1-402d-87fc-2abf81505797

📥 Commits

Reviewing files that changed from the base of the PR and between 0c52e13 and 50e2123.

📒 Files selected for processing (4)
  • packages/cli/index.ts
  • website/components/InputGroup.tsx
  • website/components/Store.tsx
  • website/lib/track.ts

@ambergristle ambergristle changed the title chore: combine tracking analytics: combine copy feature tracking events into one Mar 26, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
website/lib/track.ts (1)

35-64: Refactor formatFeatureFlags to the two-phase aggregation pattern.

Line 40 uses a truthy check (if (data[category])), and the mixed string | string[] accumulator requires a final type assertion. A two-phase Record<string, string[]>Record<string, string> transform is simpler and more robust.

♻️ Proposed refactor
 export function formatFeatureFlags(selectedFlags: FeatureFlag[]): Record<string, string> {
-  const categoriesMultiple = new Set<string>();
-
-  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<string, string | string[]>,
-  );
-
-  // Recombine all arrays into strings
-  categoriesMultiple.forEach((category) => {
-    if (Array.isArray(flags[category])) {
-      flags[category] = flags[category].sort().join(":");
-    }
-  });
-
-  return flags as Record<string, string>;
+  const grouped = selectedFlags.reduce<Record<string, string[]>>((agg, { flag, category }) => {
+    (agg[category] ??= []).push(flag);
+    return agg;
+  }, {});
+
+  return Object.fromEntries(
+    Object.entries(grouped).map(([category, values]) => [
+      category,
+      values.length > 1 ? [...values].sort().join(":") : values[0],
+    ]),
+  );
 }

Based on learnings: “recommended implementation is a two-phase approach: reduce into Record<string, string[]>, then convert via Object.fromEntries, joining only multi-value keys with sorted :.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@website/lib/track.ts` around lines 35 - 64, The current formatFeatureFlags
uses a mixed accumulator (string | string[]) and a truthy check causing type
assertions; refactor it to a two-phase approach: in formatFeatureFlags first
reduce selectedFlags into a Record<string, string[]> (always push flags into
arrays for each category), then convert that record into Record<string, string>
by mapping each entry to a sorted joined string (join with ":"), using
Object.fromEntries or equivalent; remove the categoriesMultiple Set and the
mixed-type logic so no final type assertion is needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@website/lib/track.ts`:
- Around line 35-64: The current formatFeatureFlags uses a mixed accumulator
(string | string[]) and a truthy check causing type assertions; refactor it to a
two-phase approach: in formatFeatureFlags first reduce selectedFlags into a
Record<string, string[]> (always push flags into arrays for each category), then
convert that record into Record<string, string> by mapping each entry to a
sorted joined string (join with ":"), using Object.fromEntries or equivalent;
remove the categoriesMultiple Set and the mixed-type logic so no final type
assertion is needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4942932a-8df0-417b-a632-6fbfd86ff9f8

📥 Commits

Reviewing files that changed from the base of the PR and between 258bb66 and af90841.

📒 Files selected for processing (2)
  • website/components/InputGroup.tsx
  • website/lib/track.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • website/components/InputGroup.tsx

- add specificity to event name
- combine all feature events into a single tracking event with object payload, allowing for simpler analytics
- group multiple flags in arrays, then join with semicolon
- will surface analytics data points as combinations
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/cli/index.ts (1)

479-479: Consider removing dead code instead of commenting it out.

The reIgnoreFile regex is never used within retrieveHooks or anywhere else in this function's scope. Commenting out unused code adds noise—if it's no longer needed, delete it entirely.

Additionally, this change appears unrelated to the PR's main objective of consolidating tracking events. Was this intended to be part of this PR?

🧹 Suggested fix: remove instead of comment
 async function retrieveHooks(hooks: Map<BoilerplateDefWithConfig, string>): Promise<Map<"after", Hook[]>> {
-  // const reIgnoreFile = /^(chunk-|asset-|#)/gi;
   const map = new Map<"after", Hook[]>();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/index.ts` at line 479, The commented-out regex declaration
reIgnoreFile is dead code inside the retrieveHooks scope; remove the commented
line entirely (delete the line containing "// const reIgnoreFile =
/^(chunk-|asset-|#)/gi;") rather than leaving it commented so the function only
contains active code and to avoid unrelated noise in this PR.
website/lib/track.ts (1)

35-64: Consider simplifying with the two-phase approach.

The logic is correct and produces the expected colon-joined sorted strings for multi-select categories. However, the hybrid approach (keeping singles as strings, tracking array conversions in a Set) is more complex than necessary.

The two-phase approach from your earlier discussion would be cleaner:

♻️ Simpler two-phase implementation
 export function formatFeatureFlags(selectedFlags: FeatureFlag[]): Record<string, string> {
-  const categoriesMultiple = new Set<string>();
-
-  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<string, string | string[]>,
-  );
-
-  // Recombine all arrays into strings
-  categoriesMultiple.forEach((category) => {
-    if (Array.isArray(flags[category])) {
-      flags[category] = flags[category].sort().join(":");
-    }
-  });
-
-  return flags as Record<string, string>;
+  // Phase 1: Group flags by category into arrays
+  const grouped = selectedFlags.reduce(
+    (acc, { flag, category }) => {
+      (acc[category] ??= []).push(flag);
+      return acc;
+    },
+    {} as Record<string, string[]>,
+  );
+
+  // Phase 2: Convert arrays to strings (join multi-value with ":")
+  return Object.fromEntries(
+    Object.entries(grouped).map(([k, v]) =>
+      [k, v.length > 1 ? [...v].sort().join(":") : v[0]]
+    ),
+  );
 }

Based on learnings: the two-phase approach "avoids the split/sort/join overhead on every collision compared to operating on strings during the reduce."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@website/lib/track.ts` around lines 35 - 64, formatFeatureFlags uses a hybrid
"string-or-array" reduce and a categoriesMultiple Set which is more complex than
necessary; refactor formatFeatureFlags to use a two-phase approach: during the
reduce always accumulate flags[category] as string[] (use selectedFlags.reduce
to push into flags[category] arrays, removing categoriesMultiple and the
string-branch logic), then after the reduce iterate the keys of flags to sort
each array and join with ":" to produce the final Record<string,string>;
reference the existing identifiers selectedFlags, flags, category and remove the
special-case handling that treats singles as strings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/cli/index.ts`:
- Line 479: The commented-out regex declaration reIgnoreFile is dead code inside
the retrieveHooks scope; remove the commented line entirely (delete the line
containing "// const reIgnoreFile = /^(chunk-|asset-|#)/gi;") rather than
leaving it commented so the function only contains active code and to avoid
unrelated noise in this PR.

In `@website/lib/track.ts`:
- Around line 35-64: formatFeatureFlags uses a hybrid "string-or-array" reduce
and a categoriesMultiple Set which is more complex than necessary; refactor
formatFeatureFlags to use a two-phase approach: during the reduce always
accumulate flags[category] as string[] (use selectedFlags.reduce to push into
flags[category] arrays, removing categoriesMultiple and the string-branch
logic), then after the reduce iterate the keys of flags to sort each array and
join with ":" to produce the final Record<string,string>; reference the existing
identifiers selectedFlags, flags, category and remove the special-case handling
that treats singles as strings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b9cc2e17-75b7-4b14-9315-bd3cebd738d7

📥 Commits

Reviewing files that changed from the base of the PR and between af90841 and 52ffeb0.

📒 Files selected for processing (4)
  • packages/cli/index.ts
  • website/components/InputGroup.tsx
  • website/components/Store.tsx
  • website/lib/track.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • website/components/Store.tsx
  • website/components/InputGroup.tsx

@ambergristle ambergristle marked this pull request as draft March 27, 2026 20:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Suggestion] Combine + structure copy events

1 participant