diff --git a/apps/desktop/electron.vite.config.ts b/apps/desktop/electron.vite.config.ts
index a832251f309..8644e787fae 100644
--- a/apps/desktop/electron.vite.config.ts
+++ b/apps/desktop/electron.vite.config.ts
@@ -109,6 +109,12 @@ export default defineConfig({
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
"process.platform": JSON.stringify(process.platform),
"import.meta.env.DEV_SERVER_PORT": JSON.stringify(DEV_SERVER_PORT),
+ "import.meta.env.NEXT_PUBLIC_POSTHOG_KEY": JSON.stringify(
+ process.env.NEXT_PUBLIC_POSTHOG_KEY,
+ ),
+ "import.meta.env.NEXT_PUBLIC_POSTHOG_HOST": JSON.stringify(
+ process.env.NEXT_PUBLIC_POSTHOG_HOST,
+ ),
},
server: {
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index 2fc930168e3..7da58d28c4f 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -78,6 +78,7 @@
"nanoid": "^5.1.6",
"node-pty": "1.1.0-beta30",
"os-locale": "^6.0.2",
+ "posthog-js": "^1.306.1",
"react": "^19.2.3",
"react-arborist": "^3.4.3",
"react-dnd": "^16.0.1",
diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts
index 3a0a302c6a8..ced4287f82a 100644
--- a/apps/desktop/src/main/index.ts
+++ b/apps/desktop/src/main/index.ts
@@ -10,6 +10,11 @@ import { initDb } from "./lib/db";
import { terminalManager } from "./lib/terminal";
import { MainWindow } from "./windows/main";
+// Set different app name for dev to avoid singleton lock conflicts with production
+if (process.env.NODE_ENV === "development") {
+ app.setName("Superset Dev");
+}
+
// Register protocol handler for deep linking
// In development, we need to provide the execPath and args
if (process.defaultApp) {
diff --git a/apps/desktop/src/renderer/contexts/AppProviders.tsx b/apps/desktop/src/renderer/contexts/AppProviders.tsx
index abb72b78760..0ecd8980969 100644
--- a/apps/desktop/src/renderer/contexts/AppProviders.tsx
+++ b/apps/desktop/src/renderer/contexts/AppProviders.tsx
@@ -1,5 +1,6 @@
import type React from "react";
import { MonacoProvider } from "./MonacoProvider";
+import { PostHogProvider } from "./PostHogProvider";
import { TRPCProvider } from "./TRPCProvider";
interface AppProvidersProps {
@@ -8,8 +9,10 @@ interface AppProvidersProps {
export function AppProviders({ children }: AppProvidersProps) {
return (
-
- {children}
-
+
+
+ {children}
+
+
);
}
diff --git a/apps/desktop/src/renderer/contexts/PostHogProvider.tsx b/apps/desktop/src/renderer/contexts/PostHogProvider.tsx
new file mode 100644
index 00000000000..100e0a43f19
--- /dev/null
+++ b/apps/desktop/src/renderer/contexts/PostHogProvider.tsx
@@ -0,0 +1,56 @@
+import posthog from "posthog-js";
+import { PostHogProvider as PHProvider } from "posthog-js/react";
+import type React from "react";
+import { useEffect, useState } from "react";
+
+const POSTHOG_KEY = import.meta.env.NEXT_PUBLIC_POSTHOG_KEY as
+ | string
+ | undefined;
+const POSTHOG_HOST =
+ (import.meta.env.NEXT_PUBLIC_POSTHOG_HOST as string | undefined) ||
+ "https://us.i.posthog.com";
+
+interface PostHogProviderProps {
+ children: React.ReactNode;
+}
+
+export function PostHogProvider({ children }: PostHogProviderProps) {
+ const [isInitialized, setIsInitialized] = useState(false);
+
+ useEffect(() => {
+ if (!POSTHOG_KEY) {
+ console.log("[posthog] No PostHog key configured, skipping init");
+ setIsInitialized(true);
+ return;
+ }
+
+ posthog.init(POSTHOG_KEY, {
+ api_host: POSTHOG_HOST,
+ // Electron apps don't have traditional page views
+ capture_pageview: false,
+ // Disable session recording for now
+ disable_session_recording: true,
+ // Persist across sessions
+ persistence: "localStorage",
+ // Load feature flags on init
+ bootstrap: {
+ featureFlags: {},
+ },
+ });
+
+ setIsInitialized(true);
+ console.log("[posthog] Initialized");
+ }, []);
+
+ // Don't render children until PostHog is initialized (or skipped)
+ if (!isInitialized) {
+ return null;
+ }
+
+ // If no PostHog key, just render children without the provider
+ if (!POSTHOG_KEY) {
+ return <>{children}>;
+ }
+
+ return {children};
+}
diff --git a/apps/desktop/src/renderer/contexts/index.ts b/apps/desktop/src/renderer/contexts/index.ts
index 5ffa9434eed..b6a0b6e0d28 100644
--- a/apps/desktop/src/renderer/contexts/index.ts
+++ b/apps/desktop/src/renderer/contexts/index.ts
@@ -1,3 +1,4 @@
export { AppProviders } from "./AppProviders";
export { MonacoProvider, SUPERSET_THEME } from "./MonacoProvider";
+export { PostHogProvider } from "./PostHogProvider";
export { TRPCProvider } from "./TRPCProvider";
diff --git a/apps/desktop/src/renderer/index.html b/apps/desktop/src/renderer/index.html
index 024660e406e..395a5d00acd 100644
--- a/apps/desktop/src/renderer/index.html
+++ b/apps/desktop/src/renderer/index.html
@@ -9,13 +9,13 @@
https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- default-src 'self': Only allow resources from same origin
- - script-src 'self': Only allow scripts from same origin (no inline)
+ - script-src 'self' https://*.posthog.com: Allow scripts from same origin + PostHog
- style-src 'self' 'unsafe-inline': Allow styles from same origin + inline (needed for CSS-in-JS)
- - connect-src 'self' ws: wss:: Allow WebSocket connections for HMR in dev
+ - connect-src 'self' ws: wss: https://*.posthog.com: Allow WebSocket connections for HMR + PostHog analytics
- img-src 'self' data:: Allow images from same origin + data URIs
- font-src 'self': Allow fonts from same origin
-->
-
+
diff --git a/apps/desktop/src/renderer/screens/main/index.tsx b/apps/desktop/src/renderer/screens/main/index.tsx
index a0253c21488..babb0cc2dc9 100644
--- a/apps/desktop/src/renderer/screens/main/index.tsx
+++ b/apps/desktop/src/renderer/screens/main/index.tsx
@@ -1,5 +1,7 @@
+import { FEATURE_FLAGS } from "@superset/shared/constants";
import { Button } from "@superset/ui/button";
-import { useCallback, useState } from "react";
+import { useFeatureFlagEnabled, usePostHog } from "posthog-js/react";
+import { useCallback, useEffect, useState } from "react";
import { DndProvider } from "react-dnd";
import { useHotkeys } from "react-hotkeys-hook";
import { HiArrowPath } from "react-icons/hi2";
@@ -30,10 +32,24 @@ function LoadingSpinner() {
export function MainScreen() {
const utils = trpc.useUtils();
+ const posthog = usePostHog();
const { data: authState } = trpc.auth.getState.useQuery();
const isSignedIn = authState?.isSignedIn ?? false;
const isAuthLoading = !authState;
+ // Feature flag to control auth requirement
+ const requireAuth = useFeatureFlagEnabled(FEATURE_FLAGS.REQUIRE_DESKTOP_AUTH);
+ const [flagsLoaded, setFlagsLoaded] = useState(false);
+
+ // Track when feature flags are loaded
+ useEffect(() => {
+ if (posthog) {
+ posthog.onFeatureFlags(() => {
+ setFlagsLoaded(true);
+ });
+ }
+ }, [posthog]);
+
// Subscribe to auth state changes
trpc.auth.onStateChange.useSubscription(undefined, {
onData: () => utils.auth.getState.invalidate(),
@@ -157,8 +173,23 @@ export function MainScreen() {
const showStartView =
!isLoading && !activeWorkspace && currentView !== "settings";
- // Show sign-in screen if not authenticated
- if (isAuthLoading) {
+ // Wait for feature flags to load before deciding on auth
+ const shouldRequireAuth = flagsLoaded && requireAuth === true;
+
+ // Show empty screen while feature flags are loading
+ if (!flagsLoaded) {
+ return (
+ <>
+
+
+
+
+ >
+ );
+ }
+
+ // Show loading while auth state is being determined (only if auth is required)
+ if (shouldRequireAuth && isAuthLoading) {
return (
<>
@@ -171,7 +202,8 @@ export function MainScreen() {
);
}
- if (!isSignedIn) {
+ // Show sign-in screen if auth is required and user is not signed in
+ if (shouldRequireAuth && !isSignedIn) {
return (
<>
diff --git a/bun.lock b/bun.lock
index 20f35dbd9f0..87398a21558 100644
--- a/bun.lock
+++ b/bun.lock
@@ -107,7 +107,7 @@
},
"apps/desktop": {
"name": "@superset/desktop",
- "version": "0.0.16",
+ "version": "0.0.20",
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
@@ -156,6 +156,7 @@
"nanoid": "^5.1.6",
"node-pty": "1.1.0-beta30",
"os-locale": "^6.0.2",
+ "posthog-js": "^1.306.1",
"react": "^19.2.3",
"react-arborist": "^3.4.3",
"react-dnd": "^16.0.1",
@@ -833,6 +834,8 @@
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
+ "@posthog/core": ["@posthog/core@1.7.1", "", { "dependencies": { "cross-spawn": "^7.0.6" } }, "sha512-kjK0eFMIpKo9GXIbts8VtAknsoZ18oZorANdtuTj1CbgS28t4ZVq//HAWhnxEuXRTrtkd+SUJ6Ux3j2Af8NCuA=="],
+
"@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="],
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
@@ -1577,6 +1580,8 @@
"copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="],
+ "core-js": ["core-js@3.47.0", "", {}, "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg=="],
+
"core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="],
"cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="],
@@ -1869,7 +1874,7 @@
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
- "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
+ "fflate": ["fflate@0.4.8", "", {}, "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="],
"figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="],
@@ -2585,10 +2590,14 @@
"postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
+ "posthog-js": ["posthog-js@1.306.1", "", { "dependencies": { "@posthog/core": "1.7.1", "core-js": "^3.38.1", "fflate": "^0.4.8", "preact": "^10.19.3", "web-vitals": "^4.2.4" } }, "sha512-wO7bliv/5tlAlfoKCUzwkGXZVNexk0dHigMf9tNp0q1rzs62wThogREY7Tz7h/iWKYiuXy1RumtVlTmHuBXa1w=="],
+
"postject": ["postject@1.0.0-alpha.6", "", { "dependencies": { "commander": "^9.4.0" }, "bin": { "postject": "dist/cli.js" } }, "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A=="],
"potpack": ["potpack@1.0.2", "", {}, "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="],
+ "preact": ["preact@10.28.0", "", {}, "sha512-rytDAoiXr3+t6OIP3WGlDd0ouCUG1iCWzkcY3++Nreuoi17y6T5i/zRhe6uYfoVcxq6YU+sBtJouuRDsq8vvqA=="],
+
"pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="],
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
@@ -3173,6 +3182,8 @@
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
+ "web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="],
+
"webgl-constants": ["webgl-constants@1.1.1", "", {}, "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg=="],
"webgl-sdf-generator": ["webgl-sdf-generator@1.1.1", "", {}, "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA=="],
@@ -3321,6 +3332,8 @@
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+ "@types/three/fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
+
"@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"@xyflow/react/zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="],
diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts
index d7137c92094..709ecc21aee 100644
--- a/packages/shared/src/constants.ts
+++ b/packages/shared/src/constants.ts
@@ -31,3 +31,9 @@ export const TOKEN_CONFIG = {
/** Refresh access token when this many seconds remain (5 minutes) */
REFRESH_THRESHOLD: 5 * 60,
} as const;
+
+// PostHog Feature Flags
+export const FEATURE_FLAGS = {
+ /** When enabled, users must sign in to use the desktop app */
+ REQUIRE_DESKTOP_AUTH: "require-desktop-auth",
+} as const;