Skip to content

fix(analytics): simplify desktop analytics and fix funnels#502

Merged
saddlepaddle merged 1 commit intomainfrom
mighty-earthworm-aeb0a3
Dec 26, 2025
Merged

fix(analytics): simplify desktop analytics and fix funnels#502
saddlepaddle merged 1 commit intomainfrom
mighty-earthworm-aeb0a3

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Dec 25, 2025

Summary

  • Simplify main process analytics to just setUserId + track
  • Add project_opened tracking for activation funnel
  • Split into activation funnel (app → signup → project → workspace) and marketing funnel (site → download → app)
  • Sync user ID between renderer and main via single tRPC call
  • Remove complex anonymous ID aliasing - just use user ID directly

Test plan

  • Verify analytics events are sent with correct user ID after sign in
  • Verify funnels show in admin dashboard
  • Verify sign out clears user ID

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Marketing Funnel chart added to the admin dashboard alongside an Activation Funnel.
  • Improvements

    • Dashboard funnels now have independent time-range controls for Activation and Marketing.
    • Analytics tracking added for project actions (open/init/clone) and analytics plumbing updated across desktop app flows for more consistent user identification and shutdown behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 25, 2025

Warning

Rate limit exceeded

@saddlepaddle has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 44 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between aee99d4 and c7e012f.

📒 Files selected for processing (9)
  • apps/admin/src/app/(dashboard)/page.tsx
  • apps/desktop/src/lib/trpc/routers/analytics/index.ts
  • apps/desktop/src/lib/trpc/routers/auth/index.ts
  • apps/desktop/src/lib/trpc/routers/index.ts
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/main/index.ts
  • apps/desktop/src/main/lib/analytics/index.ts
  • apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
  • packages/trpc/src/router/analytics/analytics.ts
📝 Walkthrough

Walkthrough

Splits the single dashboard funnel into Activation and Marketing funnels with independent time ranges; adds a TRPC analytics router and setUserId mutation; centralizes PostHog client and setUserId usage in desktop main/renderer flows; renames and adds funnel procedures in the TRPC analytics backend.

Changes

Cohort / File(s) Change Summary
Admin Dashboard - Funnel Split
apps/admin/src/app/(dashboard)/page.tsx
Replaces single funnelRange with activationFunnelRange and marketingFunnelRange; queries getActivationFunnel and getMarketingFunnel; renders two FunnelChart instances with separate TimeRangePickers.
TRPC Analytics Router (desktop)
apps/desktop/src/lib/trpc/routers/analytics/index.ts, apps/desktop/src/lib/trpc/routers/index.ts
Adds createAnalyticsRouter() and analytics entry on app router; exposes setUserId mutation.
Auth Router (desktop)
apps/desktop/src/lib/trpc/routers/auth/index.ts
Removes clearUserCache() call from signOut flow.
Projects Router - Tracking
apps/desktop/src/lib/trpc/routers/projects/projects.ts
Emits analytics events for project lifecycle points: "open" (openNew), "init" (initGitAndOpen), and "clone" (cloneRepo) with appropriate project IDs.
Desktop Analytics Core
apps/desktop/src/main/lib/analytics/index.ts, apps/desktop/src/main/index.ts
Exposes posthog singleton and `setUserId(id: string
Renderer - PostHog User Identification
apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
Adds TRPC mutation trpc.analytics.setUserId.useMutation; calls mutation with user.id after identify and with null after reset; updates effect deps.
TRPC Analytics Backend
packages/trpc/src/router/analytics/analytics.ts
Renames getFullJourneyFunnelgetActivationFunnel (updated event mapping); adds getMarketingFunnel; adjusts retention event mapping and simplifies zero-fill/data-filling logic across trends endpoints.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Renderer
  participant TRPC_Server
  participant MainProcess
  participant PostHog
  rect rgba(150,200,255,0.08)
    Note over Renderer,TRPC_Server: User identification flow (new)
    Renderer->>PostHog: posthog.identify(user.id)
    Renderer->>TRPC_Server: trpc.analytics.setUserId({ userId: user.id })
    TRPC_Server->>MainProcess: (rpc) setUserId -> set module userId
    MainProcess->>PostHog: posthog.identify / set internal user id
  end
  rect rgba(200,255,180,0.06)
    Note over Renderer,TRPC_Server: Sign-out / reset flow
    Renderer->>PostHog: posthog.reset()
    Renderer->>TRPC_Server: trpc.analytics.setUserId({ userId: null })
    TRPC_Server->>MainProcess: (rpc) setUserId(null)
    MainProcess->>PostHog: posthog.reset / clear id
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped through code, split one into two,
Funnels now bloom with ranges anew—
PostHog IDs hop from renderer to core,
TRPC carries the hop to the door.
Celebrate metrics, we dance and explore! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive The description provides a clear summary and test plan but is missing required template sections like Related Issues, Type of Change, Testing details, and Screenshots. Fill in the missing template sections, especially the Type of Change (appears to be Refactor and Bug fix), detailed Testing steps, and Related Issues if applicable.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: simplifying desktop analytics infrastructure and restructuring funnel definitions.

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
Contributor

@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: 1

🧹 Nitpick comments (1)
apps/desktop/src/main/lib/analytics/index.ts (1)

4-5: Consider making posthog read-only to prevent external reassignment.

The let posthog export is mutable, allowing consumers to accidentally reassign it. The userId is appropriately module-private, but posthog could benefit from the same encapsulation.

🔎 Alternative approach using getter
-export let posthog: PostHog | null = null;
+let _posthog: PostHog | null = null;
+
+export function getPostHog(): PostHog | null {
+	return _posthog;
+}
 let userId: string | null = null;

 function getClient(): PostHog | null {
 	if (!env.NEXT_PUBLIC_POSTHOG_KEY) {
 		return null;
 	}

-	if (!posthog) {
-		posthog = new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, {
+	if (!_posthog) {
+		_posthog = new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, {
 			host: env.NEXT_PUBLIC_POSTHOG_HOST,
 			flushAt: 1,
 			flushInterval: 0,
 		});
 	}
-	return posthog;
+	return _posthog;
 }

Then update apps/desktop/src/main/index.ts to use getPostHog()?.shutdown().

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b07d969 and 1cb1eb2.

📒 Files selected for processing (9)
  • apps/admin/src/app/(dashboard)/page.tsx
  • apps/desktop/src/lib/trpc/routers/analytics/index.ts
  • apps/desktop/src/lib/trpc/routers/auth/index.ts
  • apps/desktop/src/lib/trpc/routers/index.ts
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/main/index.ts
  • apps/desktop/src/main/lib/analytics/index.ts
  • apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
  • packages/trpc/src/router/analytics/analytics.ts
💤 Files with no reviewable changes (1)
  • apps/desktop/src/lib/trpc/routers/auth/index.ts
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Avoid using any type in TypeScript - maintain type safety unless absolutely necessary

Files:

  • apps/desktop/src/lib/trpc/routers/index.ts
  • apps/desktop/src/main/lib/analytics/index.ts
  • apps/desktop/src/lib/trpc/routers/analytics/index.ts
  • apps/admin/src/app/(dashboard)/page.tsx
  • apps/desktop/src/main/index.ts
  • apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • packages/trpc/src/router/analytics/analytics.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Run Biome for formatting, linting, import organization, and safe fixes at the root level using bun run lint:fix

Files:

  • apps/desktop/src/lib/trpc/routers/index.ts
  • apps/desktop/src/main/lib/analytics/index.ts
  • apps/desktop/src/lib/trpc/routers/analytics/index.ts
  • apps/admin/src/app/(dashboard)/page.tsx
  • apps/desktop/src/main/index.ts
  • apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • packages/trpc/src/router/analytics/analytics.ts
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc
Use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from @trpc/server/observable instead of async generators, as the library explicitly checks isObservable(result) and throws an error otherwise

Files:

  • apps/desktop/src/lib/trpc/routers/index.ts
  • apps/desktop/src/main/lib/analytics/index.ts
  • apps/desktop/src/lib/trpc/routers/analytics/index.ts
  • apps/desktop/src/main/index.ts
  • apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
apps/desktop/src/{main,renderer,preload}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use type-safe IPC communication - define channel types in apps/desktop/src/shared/ipc-channels.ts before implementing handlers

Files:

  • apps/desktop/src/main/lib/analytics/index.ts
  • apps/desktop/src/main/index.ts
  • apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
apps/desktop/src/main/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Accept object parameters in IPC handlers - do not use positional parameters in ipcMain.handle()

Files:

  • apps/desktop/src/main/lib/analytics/index.ts
  • apps/desktop/src/main/index.ts
**/*.{tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Use React + TailwindCSS v4 + shadcn/ui for UI development

Files:

  • apps/admin/src/app/(dashboard)/page.tsx
  • apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
apps/desktop/src/main/index.ts

📄 CodeRabbit inference engine (AGENTS.md)

Load environment variables in src/main/index.ts and electron.vite.config.ts with override: true before other imports

Files:

  • apps/desktop/src/main/index.ts
**/{components,features}/**/[!.]*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Organize project structure with one folder per component: ComponentName/ComponentName.tsx with index.ts barrel export

Files:

  • apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
**/{components,features}/**/*.{ts,tsx,test.ts,test.tsx,stories.tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Co-locate component dependencies (utils, hooks, constants, config, tests, stories) next to the file using them

Files:

  • apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
apps/desktop/src/renderer/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules in renderer process or shared code - use only in main process (src/main/)

Files:

  • apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
**/{components,features}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

**/{components,features}/**/*.tsx: Nest components in parent's components/ folder if used only once, promote to highest shared parent's components/ if used 2+ times
Use one component per file - do not combine multiple components in a single file

Files:

  • apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
🧬 Code graph analysis (7)
apps/desktop/src/lib/trpc/routers/index.ts (1)
apps/desktop/src/lib/trpc/routers/analytics/index.ts (1)
  • createAnalyticsRouter (5-13)
apps/desktop/src/main/lib/analytics/index.ts (1)
apps/desktop/src/main/env.main.ts (1)
  • env (12-44)
apps/desktop/src/lib/trpc/routers/analytics/index.ts (2)
apps/desktop/src/lib/trpc/index.ts (1)
  • router (47-47)
apps/desktop/src/main/lib/analytics/index.ts (1)
  • setUserId (22-24)
apps/admin/src/app/(dashboard)/page.tsx (2)
apps/admin/src/app/(dashboard)/components/TimeRangePicker/TimeRangePicker.tsx (2)
  • TimeRange (5-5)
  • TimeRangePicker (19-35)
apps/admin/src/app/(dashboard)/components/FunnelChart/FunnelChart.tsx (1)
  • FunnelChart (42-126)
apps/desktop/src/main/index.ts (1)
apps/desktop/src/main/lib/analytics/index.ts (1)
  • posthog (4-4)
apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx (2)
apps/desktop/src/main/lib/analytics/index.ts (1)
  • posthog (4-4)
apps/desktop/src/renderer/lib/posthog.ts (1)
  • posthog (28-28)
apps/desktop/src/lib/trpc/routers/projects/projects.ts (1)
apps/desktop/src/main/lib/analytics/index.ts (1)
  • track (26-44)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Deploy Web
  • GitHub Check: Deploy Docs
  • GitHub Check: Deploy API
  • GitHub Check: Deploy Marketing
  • GitHub Check: Deploy Admin
  • GitHub Check: Build
🔇 Additional comments (11)
packages/trpc/src/router/analytics/analytics.ts (2)

57-95: LGTM! Well-structured activation funnel.

The funnel events flow logically: App Opened → Signed Up → Opened Project → Created Workspace. The input validation with Zod is correct, and the default value handling is consistent.


97-126: LGTM! Marketing funnel complements the activation funnel well.

The marketing funnel captures the site-to-app journey (Site Visit → Download → App Opened), while activation tracks in-app progression. This separation provides clearer insights into each user journey phase.

apps/desktop/src/lib/trpc/routers/index.ts (1)

3-3: LGTM! Clean router integration.

The analytics router is properly imported and integrated into the app router, following the established pattern of other domain-specific routers.

Also applies to: 29-29

apps/desktop/src/lib/trpc/routers/analytics/index.ts (1)

1-15: LGTM! Clean analytics router implementation.

The router is minimal and focused. Using publicProcedure is appropriate here since setting the analytics user ID is part of the app initialization flow and doesn't require authentication gating.

apps/desktop/src/lib/trpc/routers/projects/projects.ts (3)

304-307: LGTM! Analytics tracking for project open.

Tracking is correctly placed after successful project upsert, with appropriate method: "open" property to distinguish the entry point.


359-362: LGTM! Analytics tracking for git init flow.

Consistent pattern with method: "init" to identify projects created via git initialization.


433-437: LGTM! Analytics tracking for clone flow.

Both code paths (existing project reuse and new clone) correctly track project_opened with method: "clone".

Also applies to: 481-484

apps/desktop/src/main/index.ts (1)

10-10: LGTM! Clean analytics lifecycle integration.

The import change and shutdown call are correct. Using posthog?.shutdown() with optional chaining properly handles the case where PostHog is not initialized (e.g., missing API key).

Also applies to: 148-151

apps/admin/src/app/(dashboard)/page.tsx (2)

22-42: LGTM! Clean funnel state separation.

The two funnel queries with independent time range controls provide users with granular control over each funnel's analysis period.


119-145: LGTM! Well-structured funnel chart components.

Both funnels have appropriate titles and descriptions that clearly communicate their purpose. The data binding and loading/error states are correctly wired.

apps/desktop/src/main/lib/analytics/index.ts (1)

26-43: LGTM! Clean event tracking implementation.

The guard on userId ensures events aren't sent before user identification, and the property augmentation with app_name and platform provides consistent metadata across all events.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 25, 2025

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

Service Status
Neon Database (Neon)

Thank you for your contribution! 🎉


Preview resources have been processed for cleanup

Copy link
Copy Markdown
Contributor

@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: 3

🧹 Nitpick comments (3)
apps/desktop/src/main/lib/analytics/index.ts (2)

4-5: Consider exporting posthog as a getter function instead of a mutable variable.

Exporting posthog as let allows external code to reassign it, which could break the singleton pattern. Consider either:

  1. Exporting a getter function: export const getPostHog = () => posthog
  2. Using export const posthog with a getter that initializes lazily

The current approach works but is unconventional and could lead to accidental reassignment.

</invoke_comment_end -->


26-44: Add error handling to prevent silent tracking failures.

The track() function silently fails if the PostHog client is unavailable or if an error occurs during capture. Consider adding error handling to log failures for debugging.

🔎 Proposed fix with error handling
 export function track(
 	event: string,
 	properties?: Record<string, unknown>,
 ): void {
 	if (!userId) return;
 
 	const client = getClient();
 	if (!client) return;
 
-	client.capture({
-		distinctId: userId,
-		event,
-		properties: {
-			...properties,
-			app_name: "desktop",
-			platform: process.platform,
-		},
-	});
+	try {
+		client.capture({
+			distinctId: userId,
+			event,
+			properties: {
+				...properties,
+				app_name: "desktop",
+				platform: process.platform,
+			},
+		});
+	} catch (error) {
+		console.error("[analytics] Failed to track event:", event, error);
+	}
 }
apps/desktop/src/lib/trpc/routers/analytics/index.ts (1)

7-11: Consider returning a value from the mutation for better debugging.

The setUserId mutation doesn't return anything. Consider returning a success indicator or void explicitly to make the API contract clearer.

🔎 Proposed enhancement
 		setUserId: publicProcedure
 			.input(z.object({ userId: z.string().nullable() }))
 			.mutation(({ input }) => {
 				setUserId(input.userId);
+				return { success: true };
 			}),
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1cb1eb2 and aee99d4.

📒 Files selected for processing (9)
  • apps/admin/src/app/(dashboard)/page.tsx
  • apps/desktop/src/lib/trpc/routers/analytics/index.ts
  • apps/desktop/src/lib/trpc/routers/auth/index.ts
  • apps/desktop/src/lib/trpc/routers/index.ts
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/main/index.ts
  • apps/desktop/src/main/lib/analytics/index.ts
  • apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
  • packages/trpc/src/router/analytics/analytics.ts
💤 Files with no reviewable changes (1)
  • apps/desktop/src/lib/trpc/routers/auth/index.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
  • apps/desktop/src/lib/trpc/routers/index.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Avoid using any type in TypeScript - maintain type safety unless absolutely necessary

Files:

  • apps/desktop/src/lib/trpc/routers/analytics/index.ts
  • apps/desktop/src/main/index.ts
  • packages/trpc/src/router/analytics/analytics.ts
  • apps/admin/src/app/(dashboard)/page.tsx
  • apps/desktop/src/main/lib/analytics/index.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Run Biome for formatting, linting, import organization, and safe fixes at the root level using bun run lint:fix

Files:

  • apps/desktop/src/lib/trpc/routers/analytics/index.ts
  • apps/desktop/src/main/index.ts
  • packages/trpc/src/router/analytics/analytics.ts
  • apps/admin/src/app/(dashboard)/page.tsx
  • apps/desktop/src/main/lib/analytics/index.ts
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc
Use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from @trpc/server/observable instead of async generators, as the library explicitly checks isObservable(result) and throws an error otherwise

Files:

  • apps/desktop/src/lib/trpc/routers/analytics/index.ts
  • apps/desktop/src/main/index.ts
  • apps/desktop/src/main/lib/analytics/index.ts
apps/desktop/src/{main,renderer,preload}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use type-safe IPC communication - define channel types in apps/desktop/src/shared/ipc-channels.ts before implementing handlers

Files:

  • apps/desktop/src/main/index.ts
  • apps/desktop/src/main/lib/analytics/index.ts
apps/desktop/src/main/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Accept object parameters in IPC handlers - do not use positional parameters in ipcMain.handle()

Files:

  • apps/desktop/src/main/index.ts
  • apps/desktop/src/main/lib/analytics/index.ts
apps/desktop/src/main/index.ts

📄 CodeRabbit inference engine (AGENTS.md)

Load environment variables in src/main/index.ts and electron.vite.config.ts with override: true before other imports

Files:

  • apps/desktop/src/main/index.ts
**/*.{tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Use React + TailwindCSS v4 + shadcn/ui for UI development

Files:

  • apps/admin/src/app/(dashboard)/page.tsx
🧬 Code graph analysis (3)
packages/trpc/src/router/analytics/analytics.ts (1)
packages/trpc/src/lib/posthog-client.ts (1)
  • executeFunnelQuery (190-210)
apps/admin/src/app/(dashboard)/page.tsx (2)
apps/admin/src/app/(dashboard)/components/TimeRangePicker/TimeRangePicker.tsx (2)
  • TimeRange (5-5)
  • TimeRangePicker (19-35)
apps/admin/src/app/(dashboard)/components/FunnelChart/FunnelChart.tsx (1)
  • FunnelChart (42-126)
apps/desktop/src/main/lib/analytics/index.ts (1)
apps/desktop/src/main/env.main.ts (1)
  • env (12-44)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Deploy Admin
  • GitHub Check: Deploy Docs
  • GitHub Check: Deploy Web
  • GitHub Check: Deploy Marketing
  • GitHub Check: Deploy API
  • GitHub Check: Build
🔇 Additional comments (8)
packages/trpc/src/router/analytics/analytics.ts (4)

138-183: LGTM: WAU trend calculation looks correct.

The Weekly Active User (WAU) calculation with a 7-day lookback window and 3-day minimum activity threshold is properly implemented. The zero-filling logic ensures consistent data points across the date range.

</invoke_comment_end -->


189-189: The terminal_opened event is properly tracked in the desktop app.

The event is tracked when terminals are created in apps/desktop/src/main/lib/terminal/manager.ts (line 81) with relevant metadata (workspace_id, pane_id). The change to use terminal_opened as the returning event for retention analysis is consistent with the actual tracking implementation.


57-95: Breaking change: Procedure renamed without deprecation path.

The procedure getFullJourneyFunnel has been renamed to getActivationFunnel. While this is a breaking change for external API consumers, all internal references within the codebase have been updated. This appears to be an intentional split as part of introducing separate activation and marketing funnels.


110-120: Marketing Funnel events are properly tracked.

All three events used in the funnel are being tracked across the codebase:

  • $pageview is enabled via capture_pageview: "history_change" in the marketing app's PostHog configuration
  • download_clicked is captured at apps/marketing/src/app/components/DownloadButton/DownloadButton.tsx
  • desktop_opened is captured at apps/desktop/src/renderer/screens/sign-in/index.tsx

PostHog is properly configured and initialized across all apps using the same project credentials, so events are correctly routed to the same instance.

apps/admin/src/app/(dashboard)/page.tsx (2)

22-42: LGTM: Clean separation of funnel state and queries.

The split of the single funnel into separate Activation and Marketing funnels with independent time ranges is well-structured. Each funnel has its own:

  • State variable for time range
  • TRPC query with proper options
  • Loading and error states

This follows the same pattern used for other dashboard metrics.

</invoke_comment_end -->


119-145: LGTM: Funnel charts properly configured.

Both funnel charts are correctly configured with:

  • Descriptive titles and descriptions
  • Proper data binding from queries
  • Loading and error state handling
  • Independent TimeRangePicker controls

The Activation Funnel focuses on in-app progression while the Marketing Funnel tracks the acquisition journey, which aligns with the PR objectives.

</invoke_comment_end -->

apps/desktop/src/main/index.ts (1)

150-150: The PostHog shutdown implementation is correct.

The code properly awaits posthog?.shutdown() via Promise.all(). PostHog's shutdown() method returns Promise<void>, which flushes queued events before exit. The pattern used here is correct and ensures proper cleanup.

apps/desktop/src/lib/trpc/routers/analytics/index.ts (1)

1-1: The import path and process boundaries are correctly configured. The wildcard tsconfig path alias "*": ["src/*"] properly resolves "main/lib/analytics" to the actual file at src/main/lib/analytics/index.ts. The createAnalyticsRouter() factory is instantiated only in the main process (via createAppRouter), ensuring that the Node.js module usage (PostHog, process.platform) remains within the main process context where it's safe. No action required.

Likely an incorrect or invalid review comment.

Comment on lines +12 to +19
if (!posthog) {
posthog = new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, {
host: env.NEXT_PUBLIC_POSTHOG_HOST,
flushAt: 1, // Send events immediately for desktop app
flushAt: 1,
flushInterval: 0,
});
}
return client;
return posthog;
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.

⚠️ Potential issue | 🟡 Minor

Potential race condition in PostHog client initialization.

If getClient() is called concurrently before posthog is initialized, multiple PostHog instances could be created. While this is unlikely in a single-threaded Node.js environment, consider initializing the client once during module load or using a more explicit initialization pattern.

🔎 Alternative initialization pattern
-export let posthog: PostHog | null = null;
+export let posthog: PostHog | null = initializePostHog();
 let userId: string | null = null;

-function getClient(): PostHog | null {
+function initializePostHog(): PostHog | null {
 	if (!env.NEXT_PUBLIC_POSTHOG_KEY) {
 		return null;
 	}
-
-	if (!posthog) {
-		posthog = new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, {
-			host: env.NEXT_PUBLIC_POSTHOG_HOST,
-			flushAt: 1,
-			flushInterval: 0,
-		});
-	}
-	return posthog;
+	return new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, {
+		host: env.NEXT_PUBLIC_POSTHOG_HOST,
+		flushAt: 1,
+		flushInterval: 0,
+	});
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!posthog) {
posthog = new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, {
host: env.NEXT_PUBLIC_POSTHOG_HOST,
flushAt: 1, // Send events immediately for desktop app
flushAt: 1,
flushInterval: 0,
});
}
return client;
return posthog;
export let posthog: PostHog | null = initializePostHog();
let userId: string | null = null;
function initializePostHog(): PostHog | null {
if (!env.NEXT_PUBLIC_POSTHOG_KEY) {
return null;
}
return new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, {
host: env.NEXT_PUBLIC_POSTHOG_HOST,
flushAt: 1,
flushInterval: 0,
});
}
function setUserId(id: string | null): void {
userId = id;
if (posthog && id) {
posthog.identify(id);
}
}
function track(event: string, properties?: Record<string, unknown>): void {
if (!posthog || !userId) {
return;
}
posthog.capture({
distinctId: userId,
event,
properties,
});
}
🤖 Prompt for AI Agents
In apps/desktop/src/main/lib/analytics/index.ts around lines 12-19, the lazy
getClient() code can create multiple PostHog instances if called concurrently;
replace the lazy unchecked init with a single-module initialization or an
explicit init/get pattern: either instantiate posthog once at module load using
env.NEXT_PUBLIC_POSTHOG_KEY and env.NEXT_PUBLIC_POSTHOG_HOST, or add a
module-level initPromise and an init(env) function that creates the PostHog
client exactly once (store the instance on success and have getClient await
initPromise), ensuring concurrent callers share the same in-flight
initialization and only one PostHog instance is created.

Comment on lines +15 to 16
flushAt: 1,
flushInterval: 0,
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n apps/desktop/src/main/lib/analytics/index.ts

Repository: superset-sh/superset

Length of output: 1181


🏁 Script executed:

# Search for any configuration or environment-based settings for PostHog
rg -i "flush|posthog" apps/desktop/src/main/lib/analytics/ -A 3 -B 3

Repository: superset-sh/superset

Length of output: 1533


🏁 Script executed:

# Check if there are environment-specific configurations
rg "NODE_ENV|process.env" apps/desktop/src/main/lib/analytics/ -A 2 -B 2

Repository: superset-sh/superset

Length of output: 46


🌐 Web query:

PostHog Electron desktop application recommended flush settings production

💡 Result:

Short answer

  • Use the SDK defaults for a desktop Electron app: flushAt = 20 and flushInterval = 10000 ms (10s). These are the sane production defaults for long‑running apps. [1][2]

When you might change them

  • Make flushAt smaller (eg. 1) and flushInterval = 0 only for short‑lived/serverless contexts to avoid losing events. [2]
  • If you want fewer network calls (reduce bandwidth/battery), increase flushAt (e.g. 50) and/or flushInterval (e.g. 60000 ms). If you need near‑real‑time events, lower flushAt or flushInterval. [1][2]

References

  • PostHog Node SDK options (defaults & serverless guidance). [1]
  • PostHog library docs showing flushAt/flushInterval settings and description. [2]

Sources
[1] PostHog Node.js docs — options and serverless recommendations.
[2] PostHog library docs (flushInterval / flushAt description).


Use PostHog's default flush settings for long-running desktop applications.

flushAt: 1 and flushInterval: 0 cause every event to be flushed immediately. PostHog recommends these settings only for short-lived/serverless contexts. Since this is an Electron desktop application (a long-running process), use the SDK defaults: flushAt: 20 and flushInterval: 10000 (10 seconds). The current settings will create unnecessary network calls on every tracking event, impacting performance and battery life in production.

If different settings are needed for testing, configure them based on the environment rather than hardcoding aggressive values.

🤖 Prompt for AI Agents
In apps/desktop/src/main/lib/analytics/index.ts around lines 15-16, the PostHog
client is configured with flushAt: 1 and flushInterval: 0 which forces immediate
flushes; change these to use PostHog SDK defaults for long-running desktop apps
(flushAt: 20 and flushInterval: 10000) and make the values
environment-configurable so tests/dev can override (e.g., read from env or a
config flag and fall back to defaults) to avoid per-event network calls in
production.

Comment on lines +72 to +89
event: "desktop_opened",
name: "App Opened",
},
{
kind: "EventsNode",
event: "desktop_opened",
name: "Desktop Opened",
event: "auth_completed",
name: "Signed Up",
},
{
kind: "EventsNode",
event: "auth_completed",
name: "Auth Completed",
event: "project_opened",
name: "Opened Project",
},
{
kind: "EventsNode",
event: "terminal_opened",
name: "Terminal Opened",
event: "workspace_created",
name: "Created Workspace",
},
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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for where these events are tracked in the desktop app

echo "=== Searching for desktop_opened tracking ==="
rg -n "desktop_opened" apps/desktop --type ts --type tsx -C 2

echo -e "\n=== Searching for project_opened tracking ==="
rg -n "project_opened" apps/desktop --type ts --type tsx -C 2

echo -e "\n=== Searching for auth_completed tracking ==="
rg -n "auth_completed" apps/desktop --type ts --type tsx -C 2

echo -e "\n=== Searching for workspace_created tracking ==="
rg -n "workspace_created" apps/desktop --type ts --type tsx -C 2

Repository: superset-sh/superset

Length of output: 382


🏁 Script executed:

#!/bin/bash
# Search for where these events are tracked in the desktop app
# Using just basic rg without problematic file types

echo "=== Checking directory structure ==="
ls -la apps/ 2>/dev/null | head -20

echo -e "\n=== Searching for desktop_opened tracking ==="
rg -n "desktop_opened" apps/desktop -C 2 2>/dev/null

echo -e "\n=== Searching for project_opened tracking ==="
rg -n "project_opened" apps/desktop -C 2 2>/dev/null

echo -e "\n=== Searching for auth_completed tracking ==="
rg -n "auth_completed" apps/desktop -C 2 2>/dev/null

echo -e "\n=== Searching for workspace_created tracking ==="
rg -n "workspace_created" apps/desktop -C 2 2>/dev/null

# Also search in the entire repo if not in apps/desktop
echo -e "\n=== Checking if events are tracked anywhere in codebase ==="
rg -n "desktop_opened" --type ts -C 1 2>/dev/null | head -20

Repository: superset-sh/superset

Length of output: 4061


The Activation Funnel includes auth_completed (Signed Up) event that is not tracked in the desktop application.

Three events are correctly tracked:

  • desktop_opened ✓ (apps/desktop/src/renderer/screens/sign-in/index.tsx:15)
  • project_opened ✓ (apps/desktop/src/lib/trpc/routers/projects/projects.ts)
  • workspace_created ✓ (apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts:199)

However, auth_completed has no corresponding tracking in the desktop application. Remove it from the funnel or add the tracking event using posthog.capture("auth_completed") or track("auth_completed") where signup completes.

🤖 Prompt for AI Agents
In packages/trpc/src/router/analytics/analytics.ts around lines 72-89 the
Activation Funnel lists an `auth_completed` ("Signed Up") event that the desktop
app does not emit; either remove this event from the funnel or add desktop
tracking: implement posthog.capture("auth_completed") or track("auth_completed")
at the point signup completes in the desktop app (e.g., the signup completion
handler in apps/desktop/src/renderer/screens/sign-in or the signup success
flow), ensuring the event name matches exactly and any user/context properties
required by analytics are passed.

- Simplify main process analytics to just setUserId + track
- Add project_opened tracking for activation funnel
- Split into activation funnel (app → signup → project → workspace)
  and marketing funnel (site → download → app)
- Sync user ID between renderer and main via single tRPC call
- Remove complex anonymous ID aliasing - just use user ID directly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@saddlepaddle saddlepaddle force-pushed the mighty-earthworm-aeb0a3 branch from aee99d4 to c7e012f Compare December 25, 2025 02:26
@saddlepaddle saddlepaddle merged commit 1a95930 into main Dec 26, 2025
12 checks passed
@Kitenite Kitenite deleted the mighty-earthworm-aeb0a3 branch December 28, 2025 20:10
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.

1 participant