Skip to content

Analytics implement posthog - #10

Merged
eloiberlinger1 merged 3 commits into
mainfrom
analytics-implement-posthog
Apr 3, 2026
Merged

Analytics implement posthog#10
eloiberlinger1 merged 3 commits into
mainfrom
analytics-implement-posthog

Conversation

@eloiberlinger1

Copy link
Copy Markdown
Member

No description provided.

Copilot AI review requested due to automatic review settings April 3, 2026 20:12
@eloiberlinger1
eloiberlinger1 merged commit b651e8c into main Apr 3, 2026
3 of 4 checks passed

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request integrates PostHog analytics into the SetRsoft climbing route editor frontend. It adds the posthog-js dependency, configures a shared singleton client, and instruments several key user events such as editor CTA clicks, session loads, hold placements/removals, and layout saves. However, several critical issues were identified in the implementation: the version of posthog-js specified in package.json is invalid, and the posthog.capture and posthog.captureException methods are being called using the Node.js SDK syntax (passing an object) instead of the browser SDK syntax (passing an event name string). Furthermore, hardcoding distinctId values like 'demo' or 'anonymous' will corrupt analytics data by merging all user sessions into single profiles. These issues must be addressed to ensure accurate tracking and successful installation.

Comment thread frontend/package.json
"@tanstack/react-query": "^5.90.21",
"i18next": "^25.10.3",
"i18next-browser-languagedetector": "^8.2.1",
"posthog-js": "^1.364.7",

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.

high

The version 1.364.7 for posthog-js does not appear to be a valid stable version. Current stable versions are in the 1.x.x range (e.g., 1.160.0). Using a non-existent version number will cause installation failures.

Comment on lines +52 to +56
posthog.capture({
distinctId: 'demo',
event: 'session layout saved',
properties: { session_id: session_data?.id, hold_count: objects.filter((o) => o.type === 'hold').length },
});

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.

high

The posthog.capture method in the posthog-js browser SDK expects the event name as a string for the first argument, not an object (which is the Node.js SDK style). Additionally, hardcoding distinctId: 'demo' is incorrect for client-side tracking as it will merge all user sessions into a single profile. The browser SDK automatically manages unique anonymous IDs for you.

      posthog.capture('session layout saved', {
        session_id: session_data?.id,
        hold_count: objects.filter((o) => o.type === 'hold').length,
      });

} catch (err) {
alert(t("Error saving layout to server"));
console.error(err);
posthog.captureException(err, 'demo', { session_id: session_data?.id });

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.

high

In posthog-js, the captureException method takes the error as the first argument and a properties object as the second. Passing a hardcoded distinct ID string as the second argument is incorrect.

Suggested change
posthog.captureException(err, 'demo', { session_id: session_data?.id });
posthog.captureException(err, { session_id: session_data?.id });

Comment on lines +81 to +85
posthog.capture({
distinctId: 'demo',
event: 'session layout saved and exited',
properties: { session_id: session_data?.id, hold_count: objects.filter((o) => o.type === 'hold').length },
});

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.

high

Incorrect capture syntax and hardcoded distinctId. Use the event name string as the first argument and let the SDK handle user identity automatically.

      posthog.capture('session layout saved and exited', {
        session_id: session_data?.id,
        hold_count: objects.filter((o) => o.type === 'hold').length,
      });

Comment on lines +105 to +109
posthog.capture({
distinctId: 'demo',
event: 'session name updated',
properties: { session_id: session_data?.id },
});

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.

high

Incorrect capture syntax and hardcoded distinctId. The browser SDK should be called with the event name as a string.

        posthog.capture('session name updated', {
          session_id: session_data?.id,
        });

Comment on lines +128 to +132
posthog.capture({
distinctId: 'demo',
event: 'hold placed',
properties: { hold_name: holdName, has_parent: !!parentId },
});

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.

high

The browser SDK capture method requires the event name as a string. Hardcoding distinctId: 'demo' will corrupt your analytics data by grouping all users together.

          posthog.capture('hold placed', {
            hold_name: holdName,
            has_parent: !!parentId,
          });

Comment on lines +94 to +98
posthog.capture({
distinctId: 'demo',
event: 'hold removed from collection',
properties: { hold_name: hold.name, hold_id: hold.id, session_id: session_data?.id },
});

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.

high

Incorrect capture syntax and hardcoded distinctId. Use the string event name as the first argument and omit distinctId to allow the SDK to track users correctly.

Suggested change
posthog.capture({
distinctId: 'demo',
event: 'hold removed from collection',
properties: { hold_name: hold.name, hold_id: hold.id, session_id: session_data?.id },
});
posthog.capture('hold removed from collection', {
hold_name: hold.name,
hold_id: hold.id,
session_id: session_data?.id,
});

<Link
to={ROUTES.EDITOR}
className="bg-gradient-to-br from-mint-dim to-mint text-on-primary font-bold px-8 py-3.5 rounded-sm transition-transform hover:scale-105 shadow-lg shadow-mint/20"
onClick={() => posthog.capture({ distinctId: 'anonymous', event: 'editor cta clicked', properties: { source: 'hero' } })}

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.

high

Incorrect capture syntax and hardcoded distinctId. Hardcoding 'anonymous' as the distinct ID will prevent PostHog from distinguishing between different anonymous visitors.

Suggested change
onClick={() => posthog.capture({ distinctId: 'anonymous', event: 'editor cta clicked', properties: { source: 'hero' } })}
onClick={() => posthog.capture('editor cta clicked', { source: 'hero' })}

<wizard-report>
# PostHog post-wizard report

The wizard has completed a deep integration of PostHog analytics into the SetRsoft climbing route editor frontend. `posthog-node` (v5.28.11) was installed and configured to use the browser-compatible edge entrypoint via a Vite `resolve.conditions` update. A shared singleton client was created at `src/shared/analytics/posthog.ts`. Eight events were instrumented across six files, covering the full user journey from the homepage CTA through active route-setting to session saves. Error tracking via `captureException` was added to all save/update flows.

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.

medium

The report incorrectly states that posthog-node (v5.28.11) was installed. The package.json shows posthog-js is being used, which is the correct SDK for a React frontend. Additionally, the version number 5.28.11 appears to be hallucinated as it does not match any official PostHog SDK release.

Comment on lines +6 to +9
posthog.init(apiKey, {
api_host: host,
autocapture: true,
});

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.

medium

It is recommended to check if the apiKey is defined before calling posthog.init. Initializing the SDK with an undefined key can lead to console errors or unexpected behavior in environments where analytics are not configured.

Suggested change
posthog.init(apiKey, {
api_host: host,
autocapture: true,
});
if (apiKey) {
posthog.init(apiKey, {
api_host: host,
autocapture: true,
});
}

Copilot AI left a comment

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.

Pull request overview

This PR adds PostHog analytics to the frontend by initializing the PostHog browser SDK at app startup and instrumenting key user actions across the homepage and editor flows.

Changes:

  • Add posthog-js dependency and initialize PostHog via a shared analytics module imported from main.tsx.
  • Instrument multiple editor/homepage user actions (CTA click, session open, hold place/remove, save flows) and add exception capture on save/update failures.
  • Update Vite module resolution conditions and document the integration via a setup report and env example updates.

Reviewed changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
frontend/vite.config.ts Adjusts Vite resolve.conditions potentially to influence package export resolution for analytics SDK usage.
frontend/src/shared/analytics/posthog.ts Adds a shared PostHog client initialization module.
frontend/src/main.tsx Imports analytics module to initialize PostHog at application startup.
frontend/src/features/showcase/HomePage.tsx Instruments homepage “Test Editor” CTA click event.
frontend/src/features/editor/EditorApp.tsx Instruments editor session opened event.
frontend/src/features/editor/components/SidebarHoldsSection.tsx Instruments “hold removed from collection” event when deleting a hold from the sidebar collection.
frontend/src/features/editor/components/MainCanvas.tsx Instruments “hold placed” event on drop in the canvas drag flow.
frontend/src/features/editor/components/HoldInspector.tsx Instruments “hold removed” events when deleting parent/child holds.
frontend/src/features/editor/components/FileManager.tsx Instruments save/name-update events and adds exception capture on save/update failures.
frontend/posthog-setup-report.md Adds a generated integration report and links to PostHog dashboards/insights.
frontend/package.json Adds posthog-js dependency.
frontend/package-lock.json Locks posthog-js and its transitive dependencies.
frontend/.gitignore Adds ignoring of .env within frontend/.
.env.example Adds PostHog env vars (token/host) to the example env file.
.context/architecture.md Removes the existing architecture overview document.
.claude/skills/integration-javascript_node/SKILL.md Adds an agent “skill” document for PostHog Node integration guidance.
.claude/skills/integration-javascript_node/references/posthog-node.md Adds PostHog Node SDK reference documentation.
.claude/skills/integration-javascript_node/references/node.md Adds Node.js docs reference related to PostHog Node usage.
.claude/skills/integration-javascript_node/references/identify-users.md Adds reference documentation on identifying users in PostHog.
.claude/skills/integration-javascript_node/references/basic-integration-1.3-conclude.md Adds reference “conclude” step for the PostHog wizard workflow.
.claude/skills/integration-javascript_node/references/basic-integration-1.2-revise.md Adds reference “revise” step for the PostHog wizard workflow.
.claude/skills/integration-javascript_node/references/basic-integration-1.1-edit.md Adds reference “edit” step for the PostHog wizard workflow.
.claude/skills/integration-javascript_node/references/basic-integration-1.0-begin.md Adds reference “begin” step for the PostHog wizard workflow.
Files not reviewed (1)
  • frontend/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +3 to +9
const apiKey = import.meta.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN as string;
const host = import.meta.env.VITE_PUBLIC_POSTHOG_HOST as string;

posthog.init(apiKey, {
api_host: host,
autocapture: true,
});

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

posthog.init() is executed unconditionally at module import time, but VITE_PUBLIC_POSTHOG_PROJECT_TOKEN/VITE_PUBLIC_POSTHOG_HOST may be unset in some environments (e.g. production Docker build currently injects VITE_POSTHOG_KEY). This can initialize PostHog with undefined values and potentially break app startup. Consider aligning env var names with the existing Docker build args (or updating Dockerfile/compose) and guarding init (no-op) when the token/host are missing.

Suggested change
const apiKey = import.meta.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN as string;
const host = import.meta.env.VITE_PUBLIC_POSTHOG_HOST as string;
posthog.init(apiKey, {
api_host: host,
autocapture: true,
});
const apiKey =
import.meta.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN ||
import.meta.env.VITE_POSTHOG_KEY;
const host = import.meta.env.VITE_PUBLIC_POSTHOG_HOST;
if (apiKey && host) {
posthog.init(apiKey, {
api_host: host,
autocapture: true,
});
}

Copilot uses AI. Check for mistakes.
<Link
to={ROUTES.EDITOR}
className="bg-gradient-to-br from-mint-dim to-mint text-on-primary font-bold px-8 py-3.5 rounded-sm transition-transform hover:scale-105 shadow-lg shadow-mint/20"
onClick={() => posthog.capture({ distinctId: 'anonymous', event: 'editor cta clicked', properties: { source: 'hero' } })}

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

posthog.capture() in posthog-js expects an event name (string) plus optional properties, but this call passes a Node-style object with distinctId/event/properties. This will either fail type-checking or be ignored at runtime, and the hardcoded distinctId: 'anonymous' would also merge all users into one person. Use the browser SDK signature (and let PostHog manage distinct IDs via its persistence/identify APIs).

Suggested change
onClick={() => posthog.capture({ distinctId: 'anonymous', event: 'editor cta clicked', properties: { source: 'hero' } })}
onClick={() => posthog.capture('editor cta clicked', { source: 'hero' })}

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +97
posthog.capture({
distinctId: 'demo',
event: 'hold removed from collection',
properties: { hold_name: hold.name, hold_id: hold.id, session_id: session_data?.id },

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

posthog.capture() is used with a Node-style object (distinctId/event/properties). With posthog-js, capture should be called with an event name string and properties; passing distinctId: 'demo' will also collapse all users into a single identity. Switch to the browser SDK capture signature and rely on PostHog's stored distinct ID (or call posthog.identify() when you have a real user id).

Suggested change
posthog.capture({
distinctId: 'demo',
event: 'hold removed from collection',
properties: { hold_name: hold.name, hold_id: hold.id, session_id: session_data?.id },
posthog.capture("hold removed from collection", {
hold_name: hold.name,
hold_id: hold.id,
session_id: session_data?.id,

Copilot uses AI. Check for mistakes.
Comment on lines +128 to +131
posthog.capture({
distinctId: 'demo',
event: 'hold placed',
properties: { hold_name: holdName, has_parent: !!parentId },

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

This posthog.capture() call uses a Node-style object with distinctId/event/properties. In the browser posthog-js SDK, capture should be called as capture(eventName, properties) (and you generally should not hardcode distinctId). Update this to the correct API to avoid runtime/type errors and incorrect user attribution.

Suggested change
posthog.capture({
distinctId: 'demo',
event: 'hold placed',
properties: { hold_name: holdName, has_parent: !!parentId },
posthog.capture('hold placed', {
hold_name: holdName,
has_parent: !!parentId,

Copilot uses AI. Check for mistakes.
className="flex-1 inline-flex items-center justify-center px-3 py-2 text-sm font-medium text-red-700 bg-red-50 border border-red-200 rounded-lg hover:bg-red-100 transition-colors"
onClick={() => removeObject(selected.id)}
onClick={() => {
posthog.capture({ distinctId: 'demo', event: 'hold removed', properties: { hold_name: selected.name, hold_id: selected.id } });

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

posthog.capture() is called with a Node-style object here. With posthog-js, capture should be invoked with an event name string and optional properties; hardcoding distinctId: 'demo' will also merge all users. Please update to the correct browser SDK signature and rely on PostHog's persisted distinct ID / identify() when available.

Suggested change
posthog.capture({ distinctId: 'demo', event: 'hold removed', properties: { hold_name: selected.name, hold_id: selected.id } });
posthog.capture("hold removed", {
hold_name: selected.name,
hold_id: selected.id,
});

Copilot uses AI. Check for mistakes.
Comment thread frontend/vite.config.ts
'@/features': path.resolve(__dirname, 'src/features'),
'@/app': path.resolve(__dirname, 'src/app'),
},
conditions: ['edge', 'browser', 'module', 'import', 'default'],

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

Adding a custom resolve.conditions list (especially including 'edge') can change which export condition Vite picks for many dependencies and may lead to unexpected browser bundles. Since this PR uses posthog-js (browser SDK), consider removing this unless you have a specific, verified need for overriding Vite's default conditions (or document why it’s required).

Suggested change
conditions: ['edge', 'browser', 'module', 'import', 'default'],

Copilot uses AI. Check for mistakes.
<wizard-report>
# PostHog post-wizard report

The wizard has completed a deep integration of PostHog analytics into the SetRsoft climbing route editor frontend. `posthog-node` (v5.28.11) was installed and configured to use the browser-compatible edge entrypoint via a Vite `resolve.conditions` update. A shared singleton client was created at `src/shared/analytics/posthog.ts`. Eight events were instrumented across six files, covering the full user journey from the homepage CTA through active route-setting to session saves. Error tracking via `captureException` was added to all save/update flows.

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

This report claims posthog-node was installed/configured, but the actual dependency added is posthog-js and the code imports from posthog-js. Either update this report to match the real implementation or remove it to avoid confusing future maintainers.

Suggested change
The wizard has completed a deep integration of PostHog analytics into the SetRsoft climbing route editor frontend. `posthog-node` (v5.28.11) was installed and configured to use the browser-compatible edge entrypoint via a Vite `resolve.conditions` update. A shared singleton client was created at `src/shared/analytics/posthog.ts`. Eight events were instrumented across six files, covering the full user journey from the homepage CTA through active route-setting to session saves. Error tracking via `captureException` was added to all save/update flows.
The wizard has completed a deep integration of PostHog analytics into the SetRsoft climbing route editor frontend. `posthog-js` was installed and configured. A shared singleton client was created at `src/shared/analytics/posthog.ts`. Eight events were instrumented across six files, covering the full user journey from the homepage CTA through active route-setting to session saves. Error tracking via `captureException` was added to all save/update flows.

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +27
We've built some insights and a dashboard for you to keep an eye on user behavior, based on the events we just instrumented:

- **Dashboard — Analytics basics**: https://eu.posthog.com/project/152303/dashboard/603208
- **Editor CTA → Session Opened (Conversion Funnel)**: https://eu.posthog.com/project/152303/insights/7yK5mNRr
- **Holds Placed vs Removed Over Time**: https://eu.posthog.com/project/152303/insights/nhR0ydps
- **Session Layout Saves Over Time**: https://eu.posthog.com/project/152303/insights/CkOjFSrc
- **Editor Sessions Opened**: https://eu.posthog.com/project/152303/insights/qLf3ufK6
- **Route Setting Funnel: CTA → Place Hold → Save**: https://eu.posthog.com/project/152303/insights/MfhT00ik

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

This file includes direct links to a specific PostHog project/dashboard/insights. If this repo is public or shared widely, committing these URLs can unintentionally expose internal analytics project identifiers and structure. Consider moving these links to internal docs (or omitting them) and keeping only implementation-neutral setup instructions in-repo.

Suggested change
We've built some insights and a dashboard for you to keep an eye on user behavior, based on the events we just instrumented:
- **Dashboard — Analytics basics**: https://eu.posthog.com/project/152303/dashboard/603208
- **Editor CTA → Session Opened (Conversion Funnel)**: https://eu.posthog.com/project/152303/insights/7yK5mNRr
- **Holds Placed vs Removed Over Time**: https://eu.posthog.com/project/152303/insights/nhR0ydps
- **Session Layout Saves Over Time**: https://eu.posthog.com/project/152303/insights/CkOjFSrc
- **Editor Sessions Opened**: https://eu.posthog.com/project/152303/insights/qLf3ufK6
- **Route Setting Funnel: CTA → Place Hold → Save**: https://eu.posthog.com/project/152303/insights/MfhT00ik
We've outlined a dashboard and several insights to help monitor user behavior based on the events instrumented above. To avoid exposing internal PostHog project structure in-repo, keep direct dashboard and insight URLs in internal documentation only, or navigate to them from your authenticated PostHog workspace.
Recommended views to maintain internally:
- **Dashboard — Analytics basics**
- **Editor CTA → Session Opened (Conversion Funnel)**
- **Holds Placed vs Removed Over Time**
- **Session Layout Saves Over Time**
- **Editor Sessions Opened**
- **Route Setting Funnel: CTA → Place Hold → Save**
When recreating or reviewing these insights, use the event names listed above as the source events.

Copilot uses AI. Check for mistakes.
Comment thread frontend/.gitignore
@@ -0,0 +1 @@
.env

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

The repo already ignores .env at the root .gitignore, so adding a separate frontend/.gitignore just for .env is redundant and can be confusing about where env files live. Consider removing this file (or expanding it to include frontend-specific ignores if needed).

Suggested change
.env

Copilot uses AI. Check for mistakes.
Comment thread .env.example
Comment on lines 34 to +46
# PostHog analytics key (Project Settings → Project API Key). Leave empty to disable.
VITE_POSTHOG_KEY=

# --- Hugging Face CDN ---
# Override to pin a specific branch or commit hash (e.g. resolve/v1 or resolve/<sha>).
# Defaults to "main" if not set.
HOLDS_CDN_BASE=https://huggingface.co/datasets/setrsoft/climbing-holds/resolve/main
WALLS_CDN_BASE=https://huggingface.co/datasets/setrsoft/climbing-walls/resolve/main


# POSTHOG analytics
VITE_PUBLIC_POSTHOG_PROJECT_TOKEN=phc_xxxxxxxxxxxxxxxxxxxx
VITE_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

.env.example still documents VITE_POSTHOG_KEY (and production Docker builds inject that variable), but the new PostHog initialization code reads VITE_PUBLIC_POSTHOG_PROJECT_TOKEN/VITE_PUBLIC_POSTHOG_HOST. This mismatch will likely disable analytics in production (or initialize with undefined). Please align on a single env var set and update the build configuration accordingly.

Copilot uses AI. Check for mistakes.
@eloiberlinger1
eloiberlinger1 deleted the analytics-implement-posthog branch April 6, 2026 08:45
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.

2 participants