Skip to content

[LC-1534]: Modular Product Analytics#948

Closed
Custard7 wants to merge 4 commits intomainfrom
lc-1534
Closed

[LC-1534]: Modular Product Analytics#948
Custard7 wants to merge 4 commits intomainfrom
lc-1534

Conversation

@Custard7
Copy link
Copy Markdown
Collaborator

@Custard7 Custard7 commented Jan 22, 2026

Overview

🎟 Relevant Jira Issues

Fixes: LC-1534

πŸ“š What is the context and goal of this PR?

This PR introduces a provider-agnostic analytics abstraction layer for learn-card-app. The goal is to decouple event tracking from specific analytics backends (Firebase), enabling us to:

  1. Switch analytics providers via environment variables without code changes
  2. Add PostHog as an alternative/replacement analytics provider
  3. Create a centralized, type-safe event catalog
  4. Lazy-load provider SDKs for bundle optimization

πŸ₯΄ TL;DR:

New @analytics module with pluggable providers (Firebase, PostHog, Noop). All existing useFirebaseAnalytics calls migrated to useAnalytics with typed events. Set VITE_ANALYTICS_PROVIDER=posthog to switch providers.

πŸ’‘ Feature Breakdown

New Analytics Module (src/analytics/)

File Purpose
events.ts Centralized event catalog with 11 typed events
types.ts AnalyticsProvider interface definition
providers/noop.ts No-op provider for dev/testing
providers/posthog.ts PostHog provider with lazy SDK loading
providers/firebase.ts Firebase provider (wraps existing implementation)
context.tsx React context + useAnalytics() hook
useSetAnalyticsUserId.ts Hook to identify users across providers
index.ts Barrel exports for @analytics alias

Usage:

import { useAnalytics, AnalyticsEvents } from '@analytics';

const { track } = useAnalytics();
track(AnalyticsEvents.CLAIM_BOOST, { method: 'Dashboard', category: 'achievement' });

Configuration:

VITE_ANALYTICS_PROVIDER=posthog   # "posthog" | "firebase" | "noop"
VITE_POSTHOG_KEY=phc_xxx          # Required for PostHog
VITE_POSTHOG_HOST=https://...     # Optional

πŸ›  Important tradeoffs made:

  • Provider SDK is lazy-loaded on first use (not at app init) to minimize bundle impact
  • Firebase provider wraps existing useFirebaseAnalytics rather than rewriting to maintain compatibility
  • Events are queued during provider initialization to prevent data loss

πŸ” Types of Changes

  • New feature (non-breaking change which adds functionality)

πŸ’³ Does This Create Any New Technical Debt?

  • No

Testing

πŸ”¬ How Can Someone QA This?

  1. Test Firebase provider (default behavior):

    • Build app without VITE_ANALYTICS_PROVIDER set
    • Verify events appear in Firebase Analytics (existing behavior preserved)
  2. Test PostHog provider:

    • Set VITE_ANALYTICS_PROVIDER=posthog and add VITE_POSTHOG_KEY
    • Claim a boost β†’ verify claim_boost event in PostHog dashboard
    • Create a boost β†’ verify boostCMS_publish event
  3. Test Noop provider:

    • Set VITE_ANALYTICS_PROVIDER=noop
    • Verify no errors, no tracking calls made
  4. Test user identification:

    • Log in β†’ verify user ID is linked to events in analytics dashboard

πŸ“± πŸ–₯ Which devices would you like help testing on?

iOS Simulator / Android Simulator / Chromium

πŸ§ͺ Code Coverage

No new tests added - this is a refactor of existing analytics calls with new provider support. Manual QA via analytics dashboards.


Documentation

πŸ“ Documentation Checklist

Internal/AI Docs

  • Code comments/JSDoc β€” Provider interface and event types are self-documenting

πŸ’­ Documentation Notes

Internal refactor with new provider support. Configuration is via standard env vars documented in ticket.


βœ… PR Checklist

  • Related to a Jira issue (LC-1534)
  • My code follows style guidelines (eslint / prettier)
  • I have manually tested common end-2-end cases
  • I have reviewed my code
  • I have commented my code, particularly where ambiguous
  • New and existing unit tests pass locally with my changes
  • I have completed the Documentation Checklist above (or explained why N/A)
  • I have considered product analytics for user-facing features (use @analytics in learn-card-app)

πŸš€ Ready to squash-and-merge?:

  • Code is backwards compatible
  • There is not a "Do Not Merge" label on this PR
  • I have thoughtfully considered the security implications of this change.
  • This change does not expose new public facing endpoints that do not have authentication

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jan 22, 2026

⚠️ No Changeset found

Latest commit: 1ffb58d

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@netlify
Copy link
Copy Markdown

netlify Bot commented Jan 22, 2026

βœ… Deploy Preview for learncarddocs canceled.

Name Link
πŸ”¨ Latest commit 1ffb58d
πŸ” Latest deploy log https://app.netlify.com/projects/learncarddocs/deploys/69727f11ae43800007a146b5

@netlify
Copy link
Copy Markdown

netlify Bot commented Jan 22, 2026

βœ… Deploy Preview for staging-learncardapp ready!

Name Link
πŸ”¨ Latest commit 1ffb58d
πŸ” Latest deploy log https://app.netlify.com/projects/staging-learncardapp/deploys/69727f11e76fa8000863e7bc
😎 Deploy Preview https://deploy-preview-948--staging-learncardapp.netlify.app
πŸ“± Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@Custard7 Custard7 changed the title [LC-1534]: Product Analytics [LC-1534]: Modular Product Analytics Jan 22, 2026
@github-actions
Copy link
Copy Markdown
Contributor

πŸ‘‹ Hey there! It looks like you modified code, but didn't update the documentation in /docs.

If this PR introduces new features, changes APIs, or modifies behavior that users or developers need to know about, please consider updating the docs.


πŸ„ Windsurf Tip

You can ask Windsurf to help:

"Analyze the changes in this PR and update the gitbook docs in /docs accordingly."

Windsurf will review your changes and suggest appropriate documentation updates based on what was modified.


πŸ“š Documentation Guide
Change Type Doc Location
New feature/API docs/tutorials/ or docs/how-to-guides/
SDK/API changes docs/sdks/
New concepts docs/core-concepts/
App UI/UX flows docs/apps/ (LearnCard App, ScoutPass)
Internal patterns CLAUDE.md

This is an automated reminder. If no docs are needed, feel free to ignore this message.

@Custard7 Custard7 marked this pull request as draft January 22, 2026 19:48
Copy link
Copy Markdown
Contributor

@gitstream-cm gitstream-cm Bot left a comment

Choose a reason for hiding this comment

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

✨ PR Review

The PR introduces a well-structured analytics abstraction layer with provider-agnostic tracking. However, there are critical issues with event loss during initialization, type safety violations in the Firebase provider, and performance problems from unstable dependencies causing infinite effect re-runs.

3 issues detected:

πŸš€ Performance - Non-memoized function dependencies cause infinite effect re-execution πŸ› οΈ

Details: The useEffect has dependencies on getDID and identify functions. The getDID function from useWallet() is not memoized and creates a new reference on every render, causing this effect to re-run constantly. This leads to redundant identify() calls and unnecessary performance overhead.
File: apps/learn-card-app/src/analytics/useSetAnalyticsUserId.ts (44-44)
πŸ› οΈ A suggested code correction is included in the review comments.

🐞 Bug - Unsafe type cast forces null through string-typed API that may not accept it πŸ› οΈ

Details: The reset() function uses a dangerous type cast null as unknown as string to bypass TypeScript's type checking when calling setUserId. This could cause runtime failures if Firebase Analytics doesn't properly handle null values, and indicates the API is being used incorrectly.
File: apps/learn-card-app/src/analytics/providers/firebase.ts (54-54)
πŸ› οΈ A suggested code correction is included in the review comments.

🐞 Bug - Analytics events fired before async provider initialization are lost in production

Details: The provider initializes as NoopProvider and asynchronously loads the real provider. Between mount and provider initialization completing, any analytics calls (track, identify, etc.) are sent to NoopProvider which only logs in dev mode, causing production events to be lost. Critical startup events like user identification or first page views may never be tracked.
File: apps/learn-card-app/src/analytics/context.tsx

Generated by LinearB AI and added by gitStream.
AI-generated content may contain inaccuracies. Please verify before using.
πŸ’‘ Tip: You can customize your AI Review using Guidelines Learn how

if (currentUser) {
setUserId();
}
}, [currentUser, currentLCNUser, getDID, identify, options.debug]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

πŸš€ Performance - Infinite Effect Loop: Remove getDID and identify from the dependency array, or ensure useWallet() memoizes the getDID function with useCallback. The effect should primarily depend on currentUser changes, and the functions can be safely used without being dependencies since they're stable across the hook's lifecycle.

Suggested change
}, [currentUser, currentLCNUser, getDID, identify, options.debug]);
}, [currentUser, currentLCNUser, options.debug]);
Is this review accurate? Use πŸ‘ or πŸ‘Ž to rate it

If you want to tell us more, use /gs feedback e.g. /gs feedback this review doesn't make sense, I disagree, and it keeps repeating over and over


async reset(): Promise<void> {
try {
await FirebaseAnalytics.setUserId({ userId: null as unknown as string });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🐞 Bug - Type Safety Violation: Use an empty string '' instead of null, or check the Firebase Analytics documentation for the proper way to clear/reset the user ID. If null is genuinely required, add error handling and documentation explaining why this unsafe cast is necessary.

Suggested change
await FirebaseAnalytics.setUserId({ userId: null as unknown as string });
await FirebaseAnalytics.setUserId({ userId: '' });
Is this review accurate? Use πŸ‘ or πŸ‘Ž to rate it

If you want to tell us more, use /gs feedback e.g. /gs feedback this review doesn't make sense, I disagree, and it keeps repeating over and over

@goblincore
Copy link
Copy Markdown
Collaborator

@Custard7 We can close this PR now that #1018 is up - it is based off this branch, just adds to it - seems like you got most of it done so I just added the remaining items from the JIRA and tested Posthog.

@Custard7 Custard7 closed this Feb 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants