Skip to content

Conversation

@Custard7
Copy link
Collaborator

@Custard7 Custard7 commented Jan 22, 2026

Overview

🎟 Relevant Jira Issues

📚 What is the context and goal of this PR?

🥴 TL; RL:

💡 Feature Breakdown (screenshots & videos encouraged!)

🛠 Important tradeoffs made:

🔍 Types of Changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Chore (refactor, documentation update, etc)

💳 Does This Create Any New Technical Debt? ( If yes, please describe and add JIRA TODOs )

  • No
  • Yes

Testing

🔬 How Can Someone QA This?

📱 🖥 Which devices would you like help testing on?

🧪 Code Coverage

Documentation

📝 Documentation Checklist

User-Facing Docs (docs/docs.learncard.com)

  • Tutorial — New capability that users need to learn (docs/tutorials/)
  • How-To Guide — New workflow or integration (docs/how-to-guides/)
  • Reference — New/changed API, config, or SDK method (docs/sdks/)
  • Concept — New mental model or architecture explanation (docs/core-concepts/)
  • App Flows — Changes to LearnCard App or ScoutPass user flows (docs/apps/)

Internal/AI Docs

  • CLAUDE.md — New pattern, flow, or context that AI assistants need
  • Code comments/JSDoc — Complex logic that needs inline explanation

Visual Documentation

  • Mermaid diagram — Complex flow, state machine, or architecture

💭 Documentation Notes

✅ PR Checklist

  • Related to a Jira issue (create one if not)
  • 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)

🚀 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

✨ PR Description

Purpose: Add provider-agnostic analytics abstraction layer to support multiple analytics backends (PostHog, Firebase) for tracking user interactions and product events across the Learn Card application.

Main changes:

  • Implemented pluggable analytics system with provider abstraction supporting PostHog, Firebase, and no-op modes via environment configuration
  • Migrated all Firebase Analytics event tracking to use centralized analytics API with type-safe event catalog and payloads
  • Added analytics context provider with lazy loading and automatic user identification via useSetAnalyticsUserId hook

Generated by LinearB AI and added by gitStream.
AI-generated content may contain inaccuracies. Please verify before using.
💡 Tip: You can customize your AI Description using Guidelines Learn how

@changeset-bot
Copy link

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

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

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
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
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
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
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

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