Skip to content

feat: integrate Outlit analytics SDK across all apps - #1357

Closed
Kitenite wants to merge 9 commits into
mainfrom
leo-paz/outlit-integration
Closed

Kitenite wants to merge 9 commits into
mainfrom
leo-paz/outlit-integration

Conversation

@Kitenite

@Kitenite Kitenite commented Feb 10, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Adds Outlit analytics SDK (@outlit/browser + @outlit/node) to marketing, web, docs, and desktop apps
  • Integrates alongside existing PostHog analytics with shared track() wrappers
  • Consent controls (CookieConsent on marketing, TelemetrySync on desktop) gate both PostHog and Outlit
  • Desktop main process fires user.activate() on project_opened events
  • Web and desktop OutlitProviders attach authenticated user identity

Key design decisions

  • Single Outlit instance per app: Marketing and desktop use a singleton (getOutlit()) passed as client to the React provider, ensuring consent toggles and track() calls operate on the same instance
  • Guard pattern for missing keys: All apps bail out of Outlit initialization when NEXT_PUBLIC_OUTLIT_KEY is absent (no empty-string fallback)
  • Type-safe property filtering: toOutlitProperties() filters analytics properties to primitives (string | number | boolean | null) instead of using unsafe type casts

Apps changed

App SDK Features
apps/marketing @outlit/browser Pageviews, form tracking, consent via CookieConsent
apps/web @outlit/browser Pageviews, authenticated user identity
apps/docs @outlit/browser Pageviews (conditional on env key)
apps/desktop (renderer) @outlit/browser Auto-tracking, user identity, telemetry sync
apps/desktop (main) @outlit/node Event tracking, user.activate() on project open

Test plan

  • Verify marketing site tracks events via Outlit when NEXT_PUBLIC_OUTLIT_KEY is set
  • Verify cookie consent opt-out disables both PostHog and Outlit tracking
  • Verify cookie consent accept enables both PostHog and Outlit tracking
  • Verify web app attaches user identity to Outlit events
  • Verify docs app skips Outlit when env key is missing
  • Verify desktop fires Outlit events independently of PostHog configuration
  • Verify all apps gracefully no-op when NEXT_PUBLIC_OUTLIT_KEY is unset

Summary by CodeRabbit

Release Notes

  • New Features

    • Integrated enhanced analytics capabilities to track user interactions, authentication events, and feature usage across the application.
    • Updated telemetry controls to manage analytics preferences.
  • Chores

    • Added analytics service provider configuration and dependencies.

leo-paz and others added 9 commits February 9, 2026 12:06
- Add NEXT_PUBLIC_OUTLIT_KEY to .env.example
- Add Outlit key to Vite define blocks (main + renderer)
- Fix UserIdentity type: move name to traits object
- Fix analytics wrapper type: cast properties for Outlit SDK
…der wrappers, and wire up consent controls

- Bump @outlit/browser from ^1.2.0 to ^1.4.0 across all apps
- Add OutlitProvider wrapper for desktop (passes singleton via client prop for shared instance + user identity)
- Add OutlitProvider wrapper for web (handles authenticated user identity)
- Wire up disableTracking() in desktop TelemetrySync and marketing CookieConsent
- Restore original comment in desktop main process
…esktop

- Use single Outlit instance in marketing (pass singleton as client to
  provider) so consent opt-out actually disables tracking
- Ungate desktop main-process Outlit tracking from PostHog availability
- Guard against empty publicKey in web/marketing (skip init when missing)
- Add posthog.opt_in_capturing() to CookieConsent handleAccept for symmetry
- Replace unsafe type casts with toOutlitProperties filter in all track wrappers
@coderabbitai

coderabbitai Bot commented Feb 10, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This pull request integrates Outlit Analytics as an alternative analytics provider alongside PostHog across the desktop, docs, marketing, and web applications. It adds environment configuration, dependencies, module initialization, and centralizes event tracking through abstraction layers that send events to both PostHog and Outlit simultaneously.

Changes

Cohort / File(s) Summary
Environment Configuration
.env.example, apps/desktop/src/main/env.main.ts, apps/desktop/src/renderer/env.renderer.ts, apps/docs/src/env.ts, apps/marketing/src/env.ts, apps/web/src/env.ts, apps/desktop/electron.vite.config.ts
Added optional NEXT_PUBLIC_OUTLIT_KEY environment variable schema and runtime mappings across all applications and build configurations.
Dependencies
apps/desktop/package.json, apps/docs/package.json, apps/marketing/package.json, apps/web/package.json
Added @outlit/browser ^1.4.0 dependency to all web applications and @outlit/node ^1.1.0 to desktop app.
Outlit Core Modules
apps/desktop/src/main/lib/outlit/index.ts, apps/desktop/src/renderer/lib/outlit/index.ts, apps/marketing/src/lib/outlit/index.ts
Introduced singleton-pattern Outlit accessor modules with getOutlit() function returning initialized Outlit instance based on environment key; desktop main process also exports shutdownOutlit() for graceful cleanup.
Analytics Abstraction
apps/desktop/src/main/lib/analytics/index.ts, apps/desktop/src/renderer/lib/analytics/index.ts, apps/marketing/src/lib/analytics/index.ts
Created centralized track() functions that send events to both PostHog and Outlit; includes toOutlitProperties() helper to sanitize and coerce properties to primitive types compatible with Outlit.
Provider Components
apps/desktop/src/renderer/providers/OutlitProvider/OutlitProvider.tsx, apps/desktop/src/renderer/providers/OutlitProvider/index.ts, apps/web/src/components/OutlitProvider/OutlitProvider.tsx, apps/web/src/components/OutlitProvider/index.ts, apps/docs/src/app/providers.tsx
Introduced OutlitProvider and OutlitProviderWrapper components that conditionally wrap children with Outlit browser provider when key is configured; maps authenticated user details to Outlit user shape.
Layout & Route Integration
apps/desktop/src/renderer/routes/-layout.tsx, apps/web/src/app/providers.tsx, apps/docs/src/app/layout.tsx, apps/desktop/src/renderer/index.tsx
Integrated OutlitProvider/OutlitProviderWrapper into component hierarchies and added pageview tracking via getOutlit()?.track("pageview", ...).
Event Tracking Migration
apps/desktop/src/renderer/components/Paywall/Paywall.tsx, apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx, apps/desktop/src/renderer/components/TelemetrySync/TelemetrySync.tsx, apps/desktop/src/renderer/routes/sign-in/page.tsx, apps/marketing/src/app/components/CTAButtons/HeaderCTA.tsx, apps/marketing/src/app/components/DownloadButton/DownloadButton.tsx, apps/marketing/src/app/providers.tsx
Replaced direct posthog.capture() calls with centralized track() function; added Outlit telemetry enable/disable integration in TelemetrySync and CookieConsent.
Lifecycle Management
apps/desktop/src/main/index.ts
Added await shutdownOutlit() invocation in the before-quit handler to gracefully shut down Outlit before application exit.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 Outlit joins the tracking crew,
Alongside PostHog's steady view,
Events now dance both ways with glee,
Analytics flow so elegantly!
Shutdown kindly, pageviews bright, ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: integrate Outlit analytics SDK across all apps' accurately describes the main change: adding Outlit analytics integration to multiple applications.
Description check ✅ Passed The pull request description comprehensively addresses all template sections including clear summary, related design decisions, apps changed, and a test plan with checkboxes.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch leo-paz/outlit-integration

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@apps/desktop/src/main/index.ts`:
- Line 161: Wrap the call to shutdownOutlit() in a try/catch (or
try/catch/finally) so any rejection is caught and cannot block the process; call
app.exit(0) in the finally (or after the catch) to guarantee the app always
exits. Specifically update the code that invokes shutdownOutlit() and
app.exit(0) so failures from shutdownOutlit() are logged (or ignored) but do not
prevent app.exit(0) from running.

In `@apps/marketing/src/components/CookieConsent/CookieConsent.tsx`:
- Line 3: CookieConsent currently calls useOutlit(), which will throw if
OutlitBrowserProvider (rendered by OutlitWrapper when NEXT_PUBLIC_OUTLIT_KEY
exists) is not mounted; change CookieConsent to avoid calling useOutlit
unguarded by either (a) import and use the singleton API from `@outlit/browser`
(e.g., track, user) instead of the hook so it works without a provider, or (b)
wrap the useOutlit() call in a try/catch and fall back to no-op functions when
the hook throws; locate useOutlit usage in CookieConsent.tsx and implement one
of these safe fallbacks so the component no longer throws when OutlitWrapper
skips the provider.

In `@apps/marketing/src/lib/analytics/index.ts`:
- Around line 23-29: The track function currently calls posthog.capture and then
getOutlit()?.track so if posthog.capture throws the Outlit call is skipped; fix
by invoking each provider in its own try/catch: wrap posthog.capture(event,
properties) in a try block and log any error (e.g., processLogger.error or
console.error) without rethrowing, then separately call
getOutlit()?.track(event, toOutlitProperties(properties)) inside its own
try/catch and likewise log errors; reference the existing function name track
and the calls posthog.capture and getOutlit()?.track (and toOutlitProperties)
when making the changes.

In `@apps/web/src/components/OutlitProvider/OutlitProvider.tsx`:
- Around line 25-31: OutlitProvider is currently forwarding user.email into the
Outlit analytics payload (user -> email) which sends PII to a third party;
change the component so it does not forward raw email by default—either remove
the email field, send a hashed/email-hash instead, or gate sending the email
behind an explicit consent flag (e.g., check user.hasConsentedToAnalytics or a
feature flag) before populating user.email; update the OutlitProvider logic
where the user object is constructed (the user prop/traits block) to implement
the chosen option and ensure any relevant tests and privacy docs are updated
accordingly.
- Around line 13-37: OutlitProvider currently passes publicKey and reads env
directly, which creates a separate internal Outlit instance; change it to use
the shared singleton by creating a getOutlit() in
apps/web/src/lib/outlit/index.ts (mirroring desktop/marketing), then update
OutlitProvider to import getOutlit(), call const client = getOutlit(), return
children when !client, and pass client={client} to OutlitBrowserProvider instead
of publicKey; also remove the env import/check and pick the appropriate tracking
options (e.g., trackPageviews/trackForms/autoTrack) consistent with other apps.
🧹 Nitpick comments (7)
apps/desktop/src/renderer/components/TelemetrySync/TelemetrySync.tsx (1)

10-26: Consider wrapping in try/catch for defensive error handling.

The previous version had try/catch around these SDK calls. While the typeof guards and optional chaining handle null cases, an unexpected SDK error (e.g., network failure during opt-in) would propagate unhandled and could break the effect. A lightweight try/catch with a logged warning would be more resilient.

🛡️ Suggested defensive wrapper
 	useEffect(() => {
 		if (telemetryEnabled === undefined) return;
 
+		try {
 		const outlit = getOutlit();
 
 		if (telemetryEnabled) {
 			if (typeof posthog?.opt_in_capturing === "function") {
 				posthog.opt_in_capturing();
 			}
 			outlit?.enableTracking();
 		} else {
 			if (typeof posthog?.opt_out_capturing === "function") {
 				posthog.opt_out_capturing();
 			}
 			outlit?.disableTracking();
 		}
+		} catch (error) {
+			console.warn("[telemetry/sync] Failed to update tracking state", error);
+		}
 	}, [telemetryEnabled]);
apps/marketing/src/lib/analytics/index.ts (1)

5-21: toOutlitProperties is duplicated across apps.

This helper is identical to the one in apps/desktop/src/renderer/lib/analytics/index.ts. Consider extracting it into a shared package (e.g., packages/shared or a new packages/analytics) to avoid drift between the two copies.

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

17-22: Consider wrapping shutdown() in a try/catch with logging.

If outlit.shutdown() rejects (e.g., network timeout flushing events), the error propagates to the before-quit handler. A failed analytics shutdown shouldn't prevent app exit.

Suggested improvement
 export async function shutdownOutlit(): Promise<void> {
 	if (outlit) {
-		await outlit.shutdown();
-		outlit = null;
+		try {
+			await outlit.shutdown();
+		} catch (error) {
+			console.error("[outlit/shutdown] Failed to shutdown Outlit", error);
+		} finally {
+			outlit = null;
+		}
 	}
 }
apps/desktop/src/renderer/lib/analytics/index.ts (1)

22-27: Missing error handling around Outlit tracking, unlike the main-process counterpart.

The main-process track() in apps/desktop/src/main/lib/analytics/index.ts wraps Outlit calls in a try/catch with a console.error log. This renderer version silently lets any Outlit exception propagate and potentially crash the caller.

Proposed fix
 export function track(
 	event: string,
 	properties?: Record<string, unknown>,
 ): void {
-	posthog.capture(event, properties);
-	getOutlit()?.track(event, toOutlitProperties(properties));
+	posthog?.capture(event, properties);
+	try {
+		getOutlit()?.track(event, toOutlitProperties(properties));
+	} catch (error) {
+		console.error("[analytics/outlit] Failed to track:", error);
+	}
 }
apps/desktop/src/renderer/index.tsx (1)

35-42: Pageview tracking bypasses the centralized track() helper — consider extracting a shared pattern.

The inline getOutlit()?.track(...) alongside the direct posthog.capture(...) is correct (using track() would double-post to PostHog), but it diverges from the unified track() pattern used everywhere else. If more event-specific overrides emerge, this will be hard to maintain.

Consider adding a dedicated trackPageview() to renderer/lib/analytics that encapsulates both calls, keeping the routing logic centralized. Also, wrapping the Outlit call in a try/catch would match the main-process convention.

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

76-80: Outlit events lack the enrichment properties sent to PostHog.

PostHog receives app_name, platform, and desktop_version in its properties (lines 63-68), but the Outlit call only forwards the raw user-supplied properties. This will make cross-provider analysis inconsistent.

If the omission is intentional (e.g., Outlit captures these via its own mechanism), a brief comment would help future readers. Otherwise, enrich the Outlit payload the same way:

♻️ Suggested diff
 			outlit.track({
 				eventName: event,
 				userId,
-				properties: toOutlitProperties(properties),
+				properties: toOutlitProperties({
+					...properties,
+					app_name: "desktop",
+					platform: process.platform,
+					desktop_version: app.getVersion(),
+				}),
 			});

83-83: Extract "project_opened" to a named constant.

This magic string is used as a behavioural trigger for user.activate(). A typo or drift between this literal and the caller would silently break activation tracking. Extracting it (and the "desktop" string on line 65) to module-level constants improves maintainability. As per coding guidelines: "Extract hardcoded magic numbers, strings, and enums to named constants at module top instead of leaving them inline in logic."

♻️ Suggested diff
+const PROJECT_OPENED_EVENT = "project_opened";
+const APP_NAME = "desktop";
+
 export function track(
 	event: string,
 	properties?: Record<string, unknown>,
 ): void {
 	...
 			properties: {
 				...properties,
-				app_name: "desktop",
+				app_name: APP_NAME,
 				...
 			},
 		...
-			if (event === "project_opened") {
+			if (event === PROJECT_OPENED_EVENT) {

// Quit confirmed or no confirmation needed - exit immediately
// Let OS clean up child processes, tray, etc.
isQuitting = true;
await shutdownOutlit();

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

Wrap shutdownOutlit() in try/catch to prevent quit from hanging.

If shutdownOutlit() rejects, app.exit(0) on Line 163 will never execute, potentially leaving the app in a stuck state. Analytics teardown should never block app exit.

🛡️ Proposed fix
 	isQuitting = true;
-	await shutdownOutlit();
+	try {
+		await shutdownOutlit();
+	} catch (error) {
+		console.error("[main] Outlit shutdown failed:", error);
+	}
 	disposeTray();
 	app.exit(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
await shutdownOutlit();
isQuitting = true;
try {
await shutdownOutlit();
} catch (error) {
console.error("[main] Outlit shutdown failed:", error);
}
disposeTray();
app.exit(0);
🤖 Prompt for AI Agents
In `@apps/desktop/src/main/index.ts` at line 161, Wrap the call to
shutdownOutlit() in a try/catch (or try/catch/finally) so any rejection is
caught and cannot block the process; call app.exit(0) in the finally (or after
the catch) to guarantee the app always exits. Specifically update the code that
invokes shutdownOutlit() and app.exit(0) so failures from shutdownOutlit() are
logged (or ignored) but do not prevent app.exit(0) from running.

@@ -1,5 +1,6 @@
"use client";

import { useOutlit } from "@outlit/browser/react";

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:

# First, find and examine the CookieConsent component
find . -name "CookieConsent.tsx" -type f

Repository: superset-sh/superset

Length of output: 127


🏁 Script executed:

# Find and examine the providers.tsx file to understand the OutlitWrapper
find . -name "providers.tsx" -type f | grep -E "(marketing|app)"

Repository: superset-sh/superset

Length of output: 204


🏁 Script executed:

# Check if `@outlit` package is in dependencies
find . -name "package.json" -type f | head -5 | xargs grep -l "@outlit"

Repository: superset-sh/superset

Length of output: 116


🏁 Script executed:

# Read CookieConsent.tsx to see the import and usage
cat -n ./apps/marketing/src/components/CookieConsent/CookieConsent.tsx

Repository: superset-sh/superset

Length of output: 2475


🏁 Script executed:

# Read marketing app providers.tsx to see OutlitWrapper implementation
cat -n ./apps/marketing/src/app/providers.tsx

Repository: superset-sh/superset

Length of output: 1297


🏁 Script executed:

# Find the getOutlit implementation
find . -path "*/lib/outlit*" -type f

Repository: superset-sh/superset

Length of output: 196


🏁 Script executed:

# Check what getOutlit returns and when
rg "getOutlit|NEXT_PUBLIC_OUTLIT" apps/marketing/src/lib -A 3 -B 1

Repository: superset-sh/superset

Length of output: 1249


🏁 Script executed:

# Read the full getOutlit implementation
cat -n ./apps/marketing/src/lib/outlit/index.ts

Repository: superset-sh/superset

Length of output: 573


🏁 Script executed:

# Search for other useOutlit usage patterns to see if guarded
rg "useOutlit" apps/marketing/src -B 2 -A 2

Repository: superset-sh/superset

Length of output: 1003


🏁 Script executed:

# Check if `@outlit` types are available locally (node_modules or type definitions)
find . -name "package.json" -path "*/apps/marketing/*" -exec cat {} \; | grep -A 10 -B 10 "@outlit" | head -20

Repository: superset-sh/superset

Length of output: 636


🏁 Script executed:

# Search for any try/catch or error handling around useOutlit or in CookieConsent usage
rg "CookieConsent|useOutlit" apps/marketing/src -B 5 -A 5 | head -50

Repository: superset-sh/superset

Length of output: 3238


🏁 Script executed:

# Check if `@outlit` types/definitions are in node_modules or installed
find . -path "*/node_modules/@outlit/browser*" -name "*.d.ts" -o -name "*.ts" | grep -E "(index|react)" | head -10

Repository: superset-sh/superset

Length of output: 792


🌐 Web query:

@outlit/browser react useOutlit hook throws missing provider context error

💡 Result:

That error happens when useOutlit() is called outside of the React context created by OutlitProvider (i.e., the component using the hook isn’t wrapped by the provider). The Outlit SDK’s React usage expects this wrapper. [1]

Fix (React): wrap your app (or at least the subtree that calls useOutlit) with OutlitProvider from @outlit/browser/react:

import { OutlitProvider, useOutlit } from "@outlit/browser/react";

function App() {
  return (
    <OutlitProvider publicKey="pk_xxx">
      <MyComponent />
    </OutlitProvider>
  );
}

function MyComponent() {
  const { track, user } = useOutlit();
  // ...
}

[1]

If you don’t want a provider: use the singleton API (init, track, user, customer) instead of useOutlit(). [1]

Common gotcha (Next.js App Router): make sure the provider is in a Client Component (add "use client" at the top) and that the component using useOutlit() is also rendered under that client-side provider.

Source
[1] OutlitAI/outlit-sdk README (React + singleton usage examples) (github.com)

Citations:


useOutlit() will throw if OutlitBrowserProvider is not in the tree.

useOutlit() requires the context provider to be present. When NEXT_PUBLIC_OUTLIT_KEY is absent, OutlitWrapper skips rendering OutlitBrowserProvider (providers.tsx, line 14), causing the hook to throw a context error at runtime. Guard the destructuring with a try/catch, or use the singleton API (track, user) from @outlit/browser instead of the hook if Outlit may not be initialized.

Also applies to: 13-13

🤖 Prompt for AI Agents
In `@apps/marketing/src/components/CookieConsent/CookieConsent.tsx` at line 3,
CookieConsent currently calls useOutlit(), which will throw if
OutlitBrowserProvider (rendered by OutlitWrapper when NEXT_PUBLIC_OUTLIT_KEY
exists) is not mounted; change CookieConsent to avoid calling useOutlit
unguarded by either (a) import and use the singleton API from `@outlit/browser`
(e.g., track, user) instead of the hook so it works without a provider, or (b)
wrap the useOutlit() call in a try/catch and fall back to no-op functions when
the hook throws; locate useOutlit usage in CookieConsent.tsx and implement one
of these safe fallbacks so the component no longer throws when OutlitWrapper
skips the provider.

Comment on lines +23 to +29
export function track(
event: string,
properties?: Record<string, unknown>,
): void {
posthog.capture(event, properties);
getOutlit()?.track(event, toOutlitProperties(properties));
}

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

An exception from posthog.capture would silently skip the Outlit call.

If posthog.capture throws (e.g., due to an uninitialized client or unexpected input), the Outlit track call on Line 28 is never reached. Wrapping each call independently would ensure one provider's failure doesn't block the other.

Proposed fix
 export function track(
 	event: string,
 	properties?: Record<string, unknown>,
 ): void {
-	posthog.capture(event, properties);
-	getOutlit()?.track(event, toOutlitProperties(properties));
+	try {
+		posthog.capture(event, properties);
+	} catch (e) {
+		console.error("[analytics/posthog] capture failed", e);
+	}
+	try {
+		getOutlit()?.track(event, toOutlitProperties(properties));
+	} catch (e) {
+		console.error("[analytics/outlit] track failed", e);
+	}
 }
🤖 Prompt for AI Agents
In `@apps/marketing/src/lib/analytics/index.ts` around lines 23 - 29, The track
function currently calls posthog.capture and then getOutlit()?.track so if
posthog.capture throws the Outlit call is skipped; fix by invoking each provider
in its own try/catch: wrap posthog.capture(event, properties) in a try block and
log any error (e.g., processLogger.error or console.error) without rethrowing,
then separately call getOutlit()?.track(event, toOutlitProperties(properties))
inside its own try/catch and likewise log errors; reference the existing
function name track and the calls posthog.capture and getOutlit()?.track (and
toOutlitProperties) when making the changes.

Comment on lines +13 to +37
export function OutlitProvider({ children }: OutlitProviderProps) {
const { data: session } = authClient.useSession();
const user = session?.user;

if (!env.NEXT_PUBLIC_OUTLIT_KEY) {
return <>{children}</>;
}

return (
<OutlitBrowserProvider
publicKey={env.NEXT_PUBLIC_OUTLIT_KEY}
trackPageviews
user={
user
? {
email: user.email,
userId: user.id,
traits: { name: user.name },
}
: null
}
>
{children}
</OutlitBrowserProvider>
);

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:

#!/bin/bash
echo "=== Search for outlit files in web app ==="
fd -t f "outlit" --full-path "apps/web/src" 2>/dev/null || echo "fd search completed"

echo -e "\n=== Search for getOutlit calls in web app ==="
rg "getOutlit" apps/web/src/ 2>/dev/null || echo "No getOutlit found"

echo -e "\n=== Search for track calls in web analytics ==="
rg "track\(" apps/web/src/lib/ 2>/dev/null | head -20 || echo "No track calls found"

echo -e "\n=== Search for Outlit in entire web app ==="
rg "Outlit" apps/web/src/ 2>/dev/null | head -30

echo -e "\n=== Check desktop OutlitProvider pattern ==="
find apps/desktop -name "*OutlitProvider*" -type f 2>/dev/null | head -5

Repository: superset-sh/superset

Length of output: 1306


🏁 Script executed:

#!/bin/bash
echo "=== Desktop OutlitProvider content ==="
cat apps/desktop/src/renderer/providers/OutlitProvider/OutlitProvider.tsx

echo -e "\n=== Search for getOutlit in entire codebase ==="
rg "getOutlit" . 2>/dev/null | head -20

echo -e "\n=== Search for Outlit singleton/initialization ==="
rg "new Outlit\|createOutlit\|initOutlit" . 2>/dev/null | head -20

echo -e "\n=== Check for outlit lib/util files ==="
fd -t f "outlit" --full-path "apps" 2>/dev/null

Repository: superset-sh/superset

Length of output: 2939


🏁 Script executed:

#!/bin/bash
echo "=== Desktop getOutlit() implementation ==="
cat apps/desktop/src/renderer/lib/outlit/index.ts

echo -e "\n=== Check if web/src/lib/outlit exists ==="
ls -la apps/web/src/lib/outlit 2>/dev/null || echo "Does not exist"

echo -e "\n=== Check if web app has any analytics setup ==="
find apps/web/src/lib -name "*analytic*" -o -name "*track*" 2>/dev/null

echo -e "\n=== Check web app's env.ts for OUTLIT references ==="
rg "OUTLIT" apps/web/src/ 2>/dev/null | head -10

echo -e "\n=== Check if web app's OutlitProvider is the only Outlit usage ==="
rg "Outlit" apps/web/src/ 2>/dev/null

Repository: superset-sh/superset

Length of output: 1871


🏁 Script executed:

#!/bin/bash
echo "=== Marketing getOutlit() implementation ==="
cat apps/marketing/src/lib/outlit/index.ts

echo -e "\n=== Check OutlitBrowserProvider API documentation or type hints ==="
rg -A 5 "OutlitBrowserProvider" apps/marketing/src/app/providers.tsx 2>/dev/null | head -20

Repository: superset-sh/superset

Length of output: 1000


Web OutlitProvider uses a different pattern than desktop/marketing — should align with the singleton approach.

The PR objective requires: "Single Outlit instance per app is used (getOutlit() singleton passed as client to React providers)." Desktop and marketing both follow this by using getOutlit() singleton with client={getOutlit()}, but this web provider passes publicKey directly instead.

While the web app currently has no track() calls, this inconsistency violates the "follow existing patterns" guideline and will cause problems when analytics tracking is added later. The OutlitBrowserProvider will create its own instance internally instead of using the shared singleton, leading to separate Outlit instances with divergent consent/identity state.

Align with the established pattern by:

  1. Creating apps/web/src/lib/outlit/index.ts with a getOutlit() singleton (similar to desktop/marketing)
  2. Passing client={getOutlit()} instead of publicKey prop
  3. Removing the env import and checking if (!client) instead

Note: The marketing app uses trackPageviews: true, trackForms: true, autoTrack: false while desktop uses trackPageviews: false, autoTrack: true — determine which configuration fits the web app's analytics needs and apply consistently.

🤖 Prompt for AI Agents
In `@apps/web/src/components/OutlitProvider/OutlitProvider.tsx` around lines 13 -
37, OutlitProvider currently passes publicKey and reads env directly, which
creates a separate internal Outlit instance; change it to use the shared
singleton by creating a getOutlit() in apps/web/src/lib/outlit/index.ts
(mirroring desktop/marketing), then update OutlitProvider to import getOutlit(),
call const client = getOutlit(), return children when !client, and pass
client={client} to OutlitBrowserProvider instead of publicKey; also remove the
env import/check and pick the appropriate tracking options (e.g.,
trackPageviews/trackForms/autoTrack) consistent with other apps.

Comment on lines +25 to +31
user={
user
? {
email: user.email,
userId: user.id,
traits: { name: user.name },
}

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

User email is sent to a third-party analytics provider.

user.email is forwarded to Outlit on Line 28. While this is consistent with the desktop provider, sending PII (email) to a third-party analytics service has GDPR/CCPA implications. Confirm this is intentional and covered by your privacy policy and data processing agreements with Outlit.

🤖 Prompt for AI Agents
In `@apps/web/src/components/OutlitProvider/OutlitProvider.tsx` around lines 25 -
31, OutlitProvider is currently forwarding user.email into the Outlit analytics
payload (user -> email) which sends PII to a third party; change the component
so it does not forward raw email by default—either remove the email field, send
a hashed/email-hash instead, or gate sending the email behind an explicit
consent flag (e.g., check user.hasConsentedToAnalytics or a feature flag) before
populating user.email; update the OutlitProvider logic where the user object is
constructed (the user prop/traits block) to implement the chosen option and
ensure any relevant tests and privacy docs are updated accordingly.

@Kitenite

Kitenite commented May 6, 2026

Copy link
Copy Markdown
Member Author

Closing as stale: created in Jan-Mar with no recent activity. If still relevant, re-open or re-create from a fresh branch. Bulk audit 2026-05-06.

@Kitenite Kitenite closed this May 6, 2026
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