From b4ca5f276466fbd22955d6443145e5d30abfe3c6 Mon Sep 17 00:00:00 2001 From: Sandman-Ren Date: Thu, 19 Feb 2026 04:01:50 -0500 Subject: [PATCH 01/12] feat(desktop): add Windows build to CI/CD pipeline Add a `build-windows` job to the reusable build workflow targeting `windows-latest` with NSIS installer output. Update both stable and canary release workflows to handle `.exe` artifacts and `*-win.yml` auto-update manifests alongside existing macOS and Linux artifacts. --- .github/workflows/build-desktop.yml | 100 +++++++++++++++++++ .github/workflows/release-desktop-canary.yml | 15 +++ .github/workflows/release-desktop.yml | 15 +++ 3 files changed, 130 insertions(+) diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index f62b6de29f4..cee39fe8dc9 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -234,3 +234,103 @@ jobs: path: apps/desktop/release/*-linux.yml retention-days: ${{ inputs.artifact_retention_days }} if-no-files-found: error + + build-windows: + name: Build - Windows (x64) + runs-on: windows-latest + environment: production + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: "1.3.2" + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: | + ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install dependencies + shell: bash + run: bun install --frozen + + - name: Set version suffix + if: inputs.version_suffix != '' + shell: bash + working-directory: apps/desktop + run: | + CURRENT_VERSION=$(node -p "require('./package.json').version") + NEW_VERSION="${CURRENT_VERSION}${{ inputs.version_suffix }}" + echo "Setting version to: $NEW_VERSION" + node -e " + const fs = require('fs'); + const pkg = require('./package.json'); + pkg.version = '$NEW_VERSION'; + fs.writeFileSync('./package.json', JSON.stringify(pkg, null, '\t') + '\n'); + " + echo "Updated package.json version to $NEW_VERSION" + + - name: Clean dev folder + shell: bash + working-directory: apps/desktop + run: bun run clean:dev + + - name: Compile app with electron-vite + shell: bash + working-directory: apps/desktop + env: + NEXT_PUBLIC_POSTHOG_KEY: ${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }} + NEXT_PUBLIC_POSTHOG_HOST: ${{ secrets.NEXT_PUBLIC_POSTHOG_HOST }} + GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} + GH_CLIENT_ID: ${{ secrets.GH_CLIENT_ID }} + NEXT_PUBLIC_WEB_URL: ${{ secrets.NEXT_PUBLIC_WEB_URL }} + NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }} + NEXT_PUBLIC_DOCS_URL: ${{ secrets.NEXT_PUBLIC_DOCS_URL }} + NEXT_PUBLIC_STREAMS_URL: ${{ secrets.NEXT_PUBLIC_STREAMS_URL }} + SENTRY_DSN_DESKTOP: ${{ secrets.SENTRY_DSN_DESKTOP }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SUPERSET_WORKSPACE_NAME: superset + run: bun run compile:app + + - name: Build Electron app + shell: bash + working-directory: apps/desktop + run: bun run package -- --publish never --config ${{ inputs.electron_builder_config }} + + - name: Verify Windows installer + update manifest exist + shell: bash + working-directory: apps/desktop + run: | + ls -la release + test -n "$(ls -1 release/*.exe 2>/dev/null)" || { + echo "::error::No .exe installer generated in apps/desktop/release" + exit 1 + } + test -n "$(ls -1 release/*-win.yml 2>/dev/null)" || { + echo "::error::No Windows auto-update manifest generated in apps/desktop/release" + exit 1 + } + + - name: Upload EXE artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact_prefix }}-win-exe + path: apps/desktop/release/*.exe + retention-days: ${{ inputs.artifact_retention_days }} + if-no-files-found: error + + - name: Upload Windows auto-update manifest + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact_prefix }}-win-update-manifest + path: apps/desktop/release/*-win.yml + retention-days: ${{ inputs.artifact_retention_days }} + if-no-files-found: error diff --git a/.github/workflows/release-desktop-canary.yml b/.github/workflows/release-desktop-canary.yml index ba534f88d9f..df79f0acb14 100644 --- a/.github/workflows/release-desktop-canary.yml +++ b/.github/workflows/release-desktop-canary.yml @@ -106,6 +106,13 @@ jobs: echo "Created canary copy: Superset-Canary-${arch}.AppImage" fi done + for file in *.exe; do + if [[ -f "$file" ]]; then + arch=$(echo "$file" | sed -E 's/.*-([^-]+)\.exe$/\1/') + cp "$file" "Superset-Canary-${arch}.exe" + echo "Created canary copy: Superset-Canary-${arch}.exe" + fi + done # Prerelease builds may request canary-linux.yml; keep latest-linux.yml as fallback. for file in *-linux.yml; do if [[ -f "$file" && "$file" != "canary-linux.yml" && "$file" != "latest-linux.yml" ]]; then @@ -115,6 +122,14 @@ jobs: break fi done + for file in *-win.yml; do + if [[ -f "$file" && "$file" != "canary-win.yml" && "$file" != "latest-win.yml" ]]; then + cp "$file" "canary-win.yml" + cp "$file" "latest-win.yml" + echo "Created canary manifests: canary-win.yml, latest-win.yml from $file" + break + fi + done - name: List artifacts run: | diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 566a6280fa4..98b0df319cd 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -86,6 +86,14 @@ jobs: echo "Created stable copy: Superset-${arch}.AppImage" fi done + for file in *.exe; do + if [[ -f "$file" ]]; then + # Extract architecture from filename (e.g., Superset-0.0.1-x64.exe -> x64) + arch=$(echo "$file" | sed -E 's/.*-([^-]+)\.exe$/\1/') + cp "$file" "Superset-${arch}.exe" + echo "Created stable copy: Superset-${arch}.exe" + fi + done # Keep Linux updater manifest at a stable filename for generic provider lookups. for file in *-linux.yml; do if [[ -f "$file" && "$file" != "latest-linux.yml" ]]; then @@ -94,6 +102,13 @@ jobs: break fi done + for file in *-win.yml; do + if [[ -f "$file" && "$file" != "latest-win.yml" ]]; then + cp "$file" "latest-win.yml" + echo "Created stable copy: latest-win.yml" + break + fi + done echo "Release artifacts:" ls -la From c84251244555e4ed7b893eaec94f18e6c6c57d56 Mon Sep 17 00:00:00 2001 From: Sandman-Ren Date: Thu, 19 Feb 2026 04:32:14 -0500 Subject: [PATCH 02/12] fix(desktop): skip postinstall on Windows build The root postinstall script is a bash .sh file that Bun on Windows cannot execute directly. Use --ignore-scripts and manually run the desktop native deps install step instead. --- .github/workflows/build-desktop.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index cee39fe8dc9..efa91246673 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -260,7 +260,10 @@ jobs: - name: Install dependencies shell: bash - run: bun install --frozen + run: | + # Skip postinstall (bash script not executable on Windows), then run desktop deps manually + bun install --frozen --ignore-scripts + bun run --filter=@superset/desktop install:deps - name: Set version suffix if: inputs.version_suffix != '' From f6e8d3d9acff92535518f496fb8069ff46ea022a Mon Sep 17 00:00:00 2001 From: Sandman-Ren Date: Thu, 19 Feb 2026 04:44:25 -0500 Subject: [PATCH 03/12] fix(desktop): make postinstall cross-platform Replace the bash-only postinstall.sh with a Node script (postinstall.mjs) so bun install works on Windows without needing --ignore-scripts. The script does the same two things: runs sherif for workspace validation and installs native desktop deps. --- .github/workflows/build-desktop.yml | 5 +---- package.json | 2 +- scripts/postinstall.mjs | 34 +++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 scripts/postinstall.mjs diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index efa91246673..cee39fe8dc9 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -260,10 +260,7 @@ jobs: - name: Install dependencies shell: bash - run: | - # Skip postinstall (bash script not executable on Windows), then run desktop deps manually - bun install --frozen --ignore-scripts - bun run --filter=@superset/desktop install:deps + run: bun install --frozen - name: Set version suffix if: inputs.version_suffix != '' diff --git a/package.json b/package.json index 73fbcbe4671..75588e39e12 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "format:check": "biome format .", "typecheck": "turbo typecheck", "ui-add": "turbo run ui-add", - "postinstall": "./scripts/postinstall.sh", + "postinstall": "node scripts/postinstall.mjs", "clean": "git clean -xdf node_modules", "clean:workspaces": "turbo clean", "release:desktop": "./apps/desktop/create-release.sh", diff --git a/scripts/postinstall.mjs b/scripts/postinstall.mjs new file mode 100644 index 00000000000..f0c8c5c7ff3 --- /dev/null +++ b/scripts/postinstall.mjs @@ -0,0 +1,34 @@ +/** + * Cross-platform postinstall script. + * + * Replaces the bash-only postinstall.sh so that `bun install` works on + * Windows, macOS and Linux without special flags. + * + * Steps: + * 1. Guard against infinite recursion (electron-builder install-app-deps + * can trigger nested bun installs which would re-run this script). + * 2. Run sherif for workspace validation. + * 3. Install native dependencies for the desktop app. + */ + +import { execSync } from "node:child_process"; + +// Prevent infinite recursion during postinstall +if (process.env.SUPERSET_POSTINSTALL_RUNNING) { + process.exit(0); +} +process.env.SUPERSET_POSTINSTALL_RUNNING = "1"; + +/** Run a command, inheriting stdio so output is visible. */ +function run(cmd) { + execSync(cmd, { + stdio: "inherit", + env: { ...process.env, SUPERSET_POSTINSTALL_RUNNING: "1" }, + }); +} + +// Run sherif for workspace validation +run("sherif"); + +// Install native dependencies for desktop app +run("bun run --filter=@superset/desktop install:deps"); From 8cf7432a700b4653c56ea4e33d938164f5b7bb37 Mon Sep 17 00:00:00 2001 From: Sandman-Ren Date: Thu, 19 Feb 2026 05:08:26 -0500 Subject: [PATCH 04/12] fix(desktop): handle Windows junctions in copy-native-modules Bun creates junctions instead of symlinks on Windows. rmSync() without recursive/force flags fails with EFAULT on junctions. --- apps/desktop/scripts/copy-native-modules.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/desktop/scripts/copy-native-modules.ts b/apps/desktop/scripts/copy-native-modules.ts index 0f28a56b4bd..0ef11eef295 100644 --- a/apps/desktop/scripts/copy-native-modules.ts +++ b/apps/desktop/scripts/copy-native-modules.ts @@ -46,8 +46,8 @@ function copyModuleIfSymlink( console.log(` ${moduleName}: symlink -> replacing with real files`); console.log(` Real path: ${realPath}`); - // Remove the symlink - rmSync(modulePath); + // Remove the symlink (use recursive + force for Windows junction compatibility) + rmSync(modulePath, { recursive: true, force: true }); // Copy the actual files cpSync(realPath, modulePath, { recursive: true }); From ceffac8eb1d6cca40b2ed66c97b01084ecf85525 Mon Sep 17 00:00:00 2001 From: Sandman-Ren Date: Thu, 19 Feb 2026 05:40:21 -0500 Subject: [PATCH 05/12] fix(desktop): use correct Windows manifest filename electron-builder names the Windows auto-update manifest latest.yml (no -win suffix), unlike macOS (latest-mac.yml) and Linux (latest-linux.yml). Update verify, upload, and release steps to match the actual filename. --- .github/workflows/build-desktop.yml | 9 ++++++--- .github/workflows/release-desktop-canary.yml | 11 +++-------- .github/workflows/release-desktop.yml | 9 ++------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index cee39fe8dc9..5504f3c96b3 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -314,8 +314,9 @@ jobs: echo "::error::No .exe installer generated in apps/desktop/release" exit 1 } - test -n "$(ls -1 release/*-win.yml 2>/dev/null)" || { - echo "::error::No Windows auto-update manifest generated in apps/desktop/release" + # electron-builder names the Windows manifest latest.yml (no -win suffix) + test -f "release/latest.yml" || { + echo "::error::No Windows auto-update manifest (latest.yml) generated in apps/desktop/release" exit 1 } @@ -331,6 +332,8 @@ jobs: uses: actions/upload-artifact@v4 with: name: ${{ inputs.artifact_prefix }}-win-update-manifest - path: apps/desktop/release/*-win.yml + path: | + apps/desktop/release/*.yml + !apps/desktop/release/builder-debug.yml retention-days: ${{ inputs.artifact_retention_days }} if-no-files-found: error diff --git a/.github/workflows/release-desktop-canary.yml b/.github/workflows/release-desktop-canary.yml index df79f0acb14..c13659f4b46 100644 --- a/.github/workflows/release-desktop-canary.yml +++ b/.github/workflows/release-desktop-canary.yml @@ -122,14 +122,9 @@ jobs: break fi done - for file in *-win.yml; do - if [[ -f "$file" && "$file" != "canary-win.yml" && "$file" != "latest-win.yml" ]]; then - cp "$file" "canary-win.yml" - cp "$file" "latest-win.yml" - echo "Created canary manifests: canary-win.yml, latest-win.yml from $file" - break - fi - done + # Windows manifest is named latest.yml by electron-builder (no -win suffix). + # With generateUpdatesFilesForAllChannels, canary.yml is also generated. + # Both are already correctly named for electron-updater. - name: List artifacts run: | diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 98b0df319cd..e3ed5e36a3b 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -102,13 +102,8 @@ jobs: break fi done - for file in *-win.yml; do - if [[ -f "$file" && "$file" != "latest-win.yml" ]]; then - cp "$file" "latest-win.yml" - echo "Created stable copy: latest-win.yml" - break - fi - done + # Windows manifest is already named latest.yml by electron-builder + # (no -win suffix, unlike macOS/Linux). No stable copy needed. echo "Release artifacts:" ls -la From d59d939b8b56a9c262ce4da5147e671958636419 Mon Sep 17 00:00:00 2001 From: Sandman-Ren Date: Thu, 19 Feb 2026 07:35:11 -0500 Subject: [PATCH 06/12] fix(desktop): disable GPU hardware acceleration on Windows to prevent black screen GPU driver incompatibilities with Chromium's compositor can cause a completely black window on Windows. Extend the existing Linux GPU disable to Windows. Also add missing NEXT_PUBLIC_ELECTRIC_URL env var to macOS and Windows builds. --- .github/workflows/build-desktop.yml | 2 ++ apps/desktop/src/lib/electron-app/factories/app/setup.ts | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 5504f3c96b3..f3969b50a5f 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -92,6 +92,7 @@ jobs: NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }} NEXT_PUBLIC_DOCS_URL: ${{ secrets.NEXT_PUBLIC_DOCS_URL }} NEXT_PUBLIC_STREAMS_URL: ${{ secrets.NEXT_PUBLIC_STREAMS_URL }} + NEXT_PUBLIC_ELECTRIC_URL: ${{ secrets.NEXT_PUBLIC_ELECTRIC_URL }} SENTRY_DSN_DESKTOP: ${{ secrets.SENTRY_DSN_DESKTOP }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SUPERSET_WORKSPACE_NAME: superset @@ -295,6 +296,7 @@ jobs: NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }} NEXT_PUBLIC_DOCS_URL: ${{ secrets.NEXT_PUBLIC_DOCS_URL }} NEXT_PUBLIC_STREAMS_URL: ${{ secrets.NEXT_PUBLIC_STREAMS_URL }} + NEXT_PUBLIC_ELECTRIC_URL: ${{ secrets.NEXT_PUBLIC_ELECTRIC_URL }} SENTRY_DSN_DESKTOP: ${{ secrets.SENTRY_DSN_DESKTOP }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SUPERSET_WORKSPACE_NAME: superset diff --git a/apps/desktop/src/lib/electron-app/factories/app/setup.ts b/apps/desktop/src/lib/electron-app/factories/app/setup.ts index 4d41962a616..8005ebf4d80 100644 --- a/apps/desktop/src/lib/electron-app/factories/app/setup.ts +++ b/apps/desktop/src/lib/electron-app/factories/app/setup.ts @@ -70,7 +70,10 @@ export async function makeAppSetup( return window; } -PLATFORM.IS_LINUX && app.disableHardwareAcceleration(); +// Disable GPU hardware acceleration on Linux and Windows to prevent black/blank +// screens caused by GPU driver incompatibilities with Chromium's compositor. +// macOS uses a separate set of GPU workarounds (see below and MainWindow). +(PLATFORM.IS_LINUX || PLATFORM.IS_WINDOWS) && app.disableHardwareAcceleration(); // macOS Sequoia+: occluded window throttling can corrupt GPU compositor layers if (PLATFORM.IS_MAC) { From 938a13149f3c901930af76969f6b40a206e0ebd9 Mon Sep 17 00:00:00 2001 From: Sandman-Ren Date: Thu, 19 Feb 2026 10:48:47 -0500 Subject: [PATCH 07/12] fix(desktop): strip crossorigin attr to fix black screen in ASAR builds Vite adds `crossorigin` to script/link tags by default. Electron's ASAR file:// handler doesn't support CORS, so this causes the main JS bundle and CSS to silently fail to load, resulting in a black screen when the app is packaged. Add a post-order Vite plugin that strips the attribute. --- apps/desktop/electron.vite.config.ts | 2 ++ apps/desktop/vite/helpers.ts | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/apps/desktop/electron.vite.config.ts b/apps/desktop/electron.vite.config.ts index 8b6f67d507f..085aa0a7323 100644 --- a/apps/desktop/electron.vite.config.ts +++ b/apps/desktop/electron.vite.config.ts @@ -15,6 +15,7 @@ import { defineEnv, devPath, htmlEnvTransformPlugin, + stripCrossOriginPlugin, } from "./vite/helpers"; // override: true ensures .env values take precedence over inherited env vars @@ -216,6 +217,7 @@ export default defineConfig({ port: Number(process.env.CODE_INSPECTOR_PORT) || undefined, }), htmlEnvTransformPlugin(), + stripCrossOriginPlugin(), ], worker: { diff --git a/apps/desktop/vite/helpers.ts b/apps/desktop/vite/helpers.ts index 577f5756c68..ce4a30c1d12 100644 --- a/apps/desktop/vite/helpers.ts +++ b/apps/desktop/vite/helpers.ts @@ -58,6 +58,23 @@ export function copyResourcesPlugin(): Plugin { }; } +/** + * Strips the `crossorigin` attribute that Vite adds to script/link tags. + * Electron's ASAR file:// handler doesn't support CORS, so crossorigin + * causes the main JS bundle and CSS to silently fail to load (black screen). + */ +export function stripCrossOriginPlugin(): Plugin { + return { + name: "strip-crossorigin", + transformIndexHtml: { + order: "post", + handler(html) { + return html.replace(/ crossorigin/g, ""); + }, + }, + }; +} + /** * Injects environment variables into index.html CSP. */ From 5b1d2a4b90603965b6450e783a9898a9a85722e0 Mon Sep 17 00:00:00 2001 From: Sandman-Ren Date: Thu, 19 Feb 2026 16:16:21 -0500 Subject: [PATCH 08/12] chore(desktop): add diagnostic logging for black screen debugging - Forward renderer console messages to main process stdout - Add step-by-step logging to renderer entry point - Auto-open DevTools in production (temporary) --- apps/desktop/src/main/windows/main.ts | 15 +++++++++++++++ apps/desktop/src/renderer/index.tsx | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/apps/desktop/src/main/windows/main.ts b/apps/desktop/src/main/windows/main.ts index f77fe8eb42f..75807d5843f 100644 --- a/apps/desktop/src/main/windows/main.ts +++ b/apps/desktop/src/main/windows/main.ts @@ -220,8 +220,17 @@ export async function MainWindow() { }); } + // Forward renderer console messages to main process stdout for debugging. + // Run the packaged app from a terminal to see these messages. + window.webContents.on("console-message", (_event, level, message, line, sourceId) => { + const levelStr = ["verbose", "info", "warning", "error"][level] ?? "unknown"; + const source = sourceId ? ` (${sourceId}:${line})` : ""; + console.log(`[renderer:${levelStr}] ${message}${source}`); + }); + window.webContents.on("did-finish-load", async () => { console.log("[main-window] Renderer loaded successfully"); + console.log("[main-window] URL:", window.webContents.getURL()); if (initialBounds.isMaximized) { window.maximize(); } @@ -229,6 +238,12 @@ export async function MainWindow() { window.webContents.setZoomLevel(savedWindowState.zoomLevel); } window.show(); + + // Open DevTools in production to help diagnose rendering issues. + // TODO: Remove this after debugging the Windows black screen issue. + if (env.NODE_ENV !== "development") { + window.webContents.openDevTools({ mode: "detach" }); + } }); window.webContents.on( diff --git a/apps/desktop/src/renderer/index.tsx b/apps/desktop/src/renderer/index.tsx index f636d9f3341..a413c7c9308 100644 --- a/apps/desktop/src/renderer/index.tsx +++ b/apps/desktop/src/renderer/index.tsx @@ -1,5 +1,8 @@ +console.log("[renderer] Script executing, location:", window.location.href); + import { initSentry } from "./lib/sentry"; +console.log("[renderer] Calling initSentry..."); initSentry(); import { createRouter, RouterProvider } from "@tanstack/react-router"; @@ -19,9 +22,14 @@ import { routeTree } from "./routeTree.gen"; import "./globals.css"; +console.log("[renderer] Imports loaded successfully"); +console.log("[renderer] window.ipcRenderer available:", !!window.ipcRenderer); +console.log("[renderer] document.querySelector('app'):", !!document.querySelector("app")); + const rootElement = document.querySelector("app"); initBootErrorHandling(rootElement); +console.log("[renderer] Creating router..."); const router = createRouter({ routeTree, history: persistentHistory, @@ -30,6 +38,7 @@ const router = createRouter({ queryClient: electronQueryClient, }, }); +console.log("[renderer] Router created"); const unsubscribe = router.subscribe("onResolved", (event) => { posthog.capture("$pageview", { @@ -44,7 +53,9 @@ const handleDeepLink = (path: string) => { const ipcRenderer = window.ipcRenderer as typeof window.ipcRenderer | undefined; if (ipcRenderer) { ipcRenderer.on("deep-link-navigate", handleDeepLink); + console.log("[renderer] IPC renderer connected"); } else { + console.error("[renderer] window.ipcRenderer is MISSING - preload failed"); reportBootError( "Renderer preload not available (window.ipcRenderer missing)", ); @@ -67,8 +78,10 @@ declare module "@tanstack/react-router" { } if (!rootElement) { + console.error("[renderer] Missing root element"); reportBootError("Missing root element"); } else if (!isBootErrorReported()) { + console.log("[renderer] Mounting React..."); ReactDom.createRoot(rootElement).render( reportBootError("Render failed", error)} @@ -77,4 +90,7 @@ if (!rootElement) { , ); markBootMounted(); + console.log("[renderer] React mounted successfully"); +} else { + console.error("[renderer] Boot error was reported, skipping React mount"); } From 1d0ac113d88e3b63b6dc3da76fcaf1578a6503ca Mon Sep 17 00:00:00 2001 From: Sandman-Ren Date: Thu, 19 Feb 2026 18:31:02 -0500 Subject: [PATCH 09/12] fix(desktop): use || instead of ?? in defineEnv for empty string fallback GitHub Actions resolves unset secrets to empty strings. The nullish coalescing operator (??) doesn't treat "" as missing, so env var defaults like "https://api.superset.sh" never kick in. This causes Zod URL validation to fail on empty strings, crashing the renderer before React mounts (black screen). Also removes temporary debug logging added in previous commit. --- apps/desktop/src/main/windows/main.ts | 6 ------ apps/desktop/src/renderer/index.tsx | 16 ---------------- apps/desktop/vite/helpers.ts | 4 +++- 3 files changed, 3 insertions(+), 23 deletions(-) diff --git a/apps/desktop/src/main/windows/main.ts b/apps/desktop/src/main/windows/main.ts index 75807d5843f..fc61a7ba8cb 100644 --- a/apps/desktop/src/main/windows/main.ts +++ b/apps/desktop/src/main/windows/main.ts @@ -238,12 +238,6 @@ export async function MainWindow() { window.webContents.setZoomLevel(savedWindowState.zoomLevel); } window.show(); - - // Open DevTools in production to help diagnose rendering issues. - // TODO: Remove this after debugging the Windows black screen issue. - if (env.NODE_ENV !== "development") { - window.webContents.openDevTools({ mode: "detach" }); - } }); window.webContents.on( diff --git a/apps/desktop/src/renderer/index.tsx b/apps/desktop/src/renderer/index.tsx index a413c7c9308..f636d9f3341 100644 --- a/apps/desktop/src/renderer/index.tsx +++ b/apps/desktop/src/renderer/index.tsx @@ -1,8 +1,5 @@ -console.log("[renderer] Script executing, location:", window.location.href); - import { initSentry } from "./lib/sentry"; -console.log("[renderer] Calling initSentry..."); initSentry(); import { createRouter, RouterProvider } from "@tanstack/react-router"; @@ -22,14 +19,9 @@ import { routeTree } from "./routeTree.gen"; import "./globals.css"; -console.log("[renderer] Imports loaded successfully"); -console.log("[renderer] window.ipcRenderer available:", !!window.ipcRenderer); -console.log("[renderer] document.querySelector('app'):", !!document.querySelector("app")); - const rootElement = document.querySelector("app"); initBootErrorHandling(rootElement); -console.log("[renderer] Creating router..."); const router = createRouter({ routeTree, history: persistentHistory, @@ -38,7 +30,6 @@ const router = createRouter({ queryClient: electronQueryClient, }, }); -console.log("[renderer] Router created"); const unsubscribe = router.subscribe("onResolved", (event) => { posthog.capture("$pageview", { @@ -53,9 +44,7 @@ const handleDeepLink = (path: string) => { const ipcRenderer = window.ipcRenderer as typeof window.ipcRenderer | undefined; if (ipcRenderer) { ipcRenderer.on("deep-link-navigate", handleDeepLink); - console.log("[renderer] IPC renderer connected"); } else { - console.error("[renderer] window.ipcRenderer is MISSING - preload failed"); reportBootError( "Renderer preload not available (window.ipcRenderer missing)", ); @@ -78,10 +67,8 @@ declare module "@tanstack/react-router" { } if (!rootElement) { - console.error("[renderer] Missing root element"); reportBootError("Missing root element"); } else if (!isBootErrorReported()) { - console.log("[renderer] Mounting React..."); ReactDom.createRoot(rootElement).render( reportBootError("Render failed", error)} @@ -90,7 +77,4 @@ if (!rootElement) { , ); markBootMounted(); - console.log("[renderer] React mounted successfully"); -} else { - console.error("[renderer] Boot error was reported, skipping React mount"); } diff --git a/apps/desktop/vite/helpers.ts b/apps/desktop/vite/helpers.ts index ce4a30c1d12..17419e2fe8e 100644 --- a/apps/desktop/vite/helpers.ts +++ b/apps/desktop/vite/helpers.ts @@ -20,7 +20,9 @@ export function defineEnv( value: string | undefined, fallback?: string, ): string { - return JSON.stringify(value ?? fallback); + // Use || instead of ?? so empty strings (from unresolved GitHub Actions + // secrets) also fall through to the default value. + return JSON.stringify(value || fallback); } const RESOURCES_TO_COPY = [ From e0071d4a4aba3d746bfaae0d82d18a92ff7eaaa1 Mon Sep 17 00:00:00 2001 From: Sandman-Ren Date: Mon, 23 Feb 2026 00:25:36 -0500 Subject: [PATCH 10/12] chore(desktop): address PR review feedback - Delete orphaned scripts/postinstall.sh (replaced by postinstall.mjs) - Filter console-message forwarder to warnings/errors only - Use console.warn/console.error to preserve log severity - Simplify postinstall.mjs run() by removing redundant env spread - Make defineEnv return type-safe when both args are undefined - Tighten Windows manifest upload glob to latest.yml --- .github/workflows/build-desktop.yml | 4 +--- apps/desktop/src/main/windows/main.ts | 7 +++++-- apps/desktop/vite/helpers.ts | 5 +++-- scripts/postinstall.mjs | 5 +---- scripts/postinstall.sh | 16 ---------------- 5 files changed, 10 insertions(+), 27 deletions(-) delete mode 100755 scripts/postinstall.sh diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index f3969b50a5f..9c6c37df81c 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -334,8 +334,6 @@ jobs: uses: actions/upload-artifact@v4 with: name: ${{ inputs.artifact_prefix }}-win-update-manifest - path: | - apps/desktop/release/*.yml - !apps/desktop/release/builder-debug.yml + path: apps/desktop/release/latest.yml retention-days: ${{ inputs.artifact_retention_days }} if-no-files-found: error diff --git a/apps/desktop/src/main/windows/main.ts b/apps/desktop/src/main/windows/main.ts index fc61a7ba8cb..6ccfe0dad76 100644 --- a/apps/desktop/src/main/windows/main.ts +++ b/apps/desktop/src/main/windows/main.ts @@ -220,12 +220,15 @@ export async function MainWindow() { }); } - // Forward renderer console messages to main process stdout for debugging. + // Forward renderer warning/error messages to main process stdout for debugging. // Run the packaged app from a terminal to see these messages. window.webContents.on("console-message", (_event, level, message, line, sourceId) => { + if (level < 2) return; const levelStr = ["verbose", "info", "warning", "error"][level] ?? "unknown"; const source = sourceId ? ` (${sourceId}:${line})` : ""; - console.log(`[renderer:${levelStr}] ${message}${source}`); + const formatted = `[renderer:${levelStr}] ${message}${source}`; + if (level === 3) console.error(formatted); + else console.warn(formatted); }); window.webContents.on("did-finish-load", async () => { diff --git a/apps/desktop/vite/helpers.ts b/apps/desktop/vite/helpers.ts index ed9023d1df9..b5f66e3a8f2 100644 --- a/apps/desktop/vite/helpers.ts +++ b/apps/desktop/vite/helpers.ts @@ -19,10 +19,11 @@ function copyDir({ src, dest }: { src: string; dest: string }): void { export function defineEnv( value: string | undefined, fallback?: string, -): string { +): string | undefined { // Use || instead of ?? so empty strings (from unresolved GitHub Actions // secrets) also fall through to the default value. - return JSON.stringify(value || fallback); + const resolved = value || fallback; + return resolved !== undefined ? JSON.stringify(resolved) : undefined; } const RESOURCES_TO_COPY = [ diff --git a/scripts/postinstall.mjs b/scripts/postinstall.mjs index f0c8c5c7ff3..b64a571eb31 100644 --- a/scripts/postinstall.mjs +++ b/scripts/postinstall.mjs @@ -21,10 +21,7 @@ process.env.SUPERSET_POSTINSTALL_RUNNING = "1"; /** Run a command, inheriting stdio so output is visible. */ function run(cmd) { - execSync(cmd, { - stdio: "inherit", - env: { ...process.env, SUPERSET_POSTINSTALL_RUNNING: "1" }, - }); + execSync(cmd, { stdio: "inherit" }); } // Run sherif for workspace validation diff --git a/scripts/postinstall.sh b/scripts/postinstall.sh deleted file mode 100755 index de2a60239a2..00000000000 --- a/scripts/postinstall.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# Prevent infinite recursion during postinstall -# electron-builder install-app-deps can trigger nested bun installs -# which would re-run postinstall, spawning hundreds of processes - -if [ -n "$SUPERSET_POSTINSTALL_RUNNING" ]; then - exit 0 -fi - -export SUPERSET_POSTINSTALL_RUNNING=1 - -# Run sherif for workspace validation -sherif - -# Install native dependencies for desktop app -bun run --filter=@superset/desktop install:deps From bf257f48ccb47235edc6501ea65d3604be827214 Mon Sep 17 00:00:00 2001 From: Sandman-Ren Date: Tue, 24 Feb 2026 20:46:40 -0500 Subject: [PATCH 11/12] chore(desktop): gate Windows-specific changes behind platform checks Address PR review feedback by ensuring shared code paths are untouched on macOS and Linux: - Revert defineEnv back to ?? (|| was only needed for fork CI secrets) - Gate stripCrossOriginPlugin to Windows only, tighten regex - Gate rmSync({ recursive }) to Windows only, drop force flag - Gate renderer console-message forwarding to Windows only - Remove extra URL log line from did-finish-load - Restore postinstall.sh alongside postinstall.mjs as fallback --- apps/desktop/scripts/copy-native-modules.ts | 9 ++++++-- apps/desktop/src/main/windows/main.ts | 25 ++++++++++++--------- apps/desktop/vite/helpers.ts | 15 ++++++------- scripts/postinstall.sh | 16 +++++++++++++ 4 files changed, 45 insertions(+), 20 deletions(-) create mode 100755 scripts/postinstall.sh diff --git a/apps/desktop/scripts/copy-native-modules.ts b/apps/desktop/scripts/copy-native-modules.ts index 0ef11eef295..e4004cc55ec 100644 --- a/apps/desktop/scripts/copy-native-modules.ts +++ b/apps/desktop/scripts/copy-native-modules.ts @@ -46,8 +46,13 @@ function copyModuleIfSymlink( console.log(` ${moduleName}: symlink -> replacing with real files`); console.log(` Real path: ${realPath}`); - // Remove the symlink (use recursive + force for Windows junction compatibility) - rmSync(modulePath, { recursive: true, force: true }); + // Windows uses junctions (directory-like) instead of symlinks; + // rmSync needs { recursive: true } to remove them. + if (process.platform === "win32") { + rmSync(modulePath, { recursive: true }); + } else { + rmSync(modulePath); + } // Copy the actual files cpSync(realPath, modulePath, { recursive: true }); diff --git a/apps/desktop/src/main/windows/main.ts b/apps/desktop/src/main/windows/main.ts index 6ccfe0dad76..55131d297fc 100644 --- a/apps/desktop/src/main/windows/main.ts +++ b/apps/desktop/src/main/windows/main.ts @@ -220,20 +220,25 @@ export async function MainWindow() { }); } - // Forward renderer warning/error messages to main process stdout for debugging. + // Forward renderer warning/error messages to main process stdout for Windows debugging. // Run the packaged app from a terminal to see these messages. - window.webContents.on("console-message", (_event, level, message, line, sourceId) => { - if (level < 2) return; - const levelStr = ["verbose", "info", "warning", "error"][level] ?? "unknown"; - const source = sourceId ? ` (${sourceId}:${line})` : ""; - const formatted = `[renderer:${levelStr}] ${message}${source}`; - if (level === 3) console.error(formatted); - else console.warn(formatted); - }); + if (PLATFORM.IS_WINDOWS) { + window.webContents.on( + "console-message", + (_event, level, message, line, sourceId) => { + if (level < 2) return; + const levelStr = + ["verbose", "info", "warning", "error"][level] ?? "unknown"; + const source = sourceId ? ` (${sourceId}:${line})` : ""; + const formatted = `[renderer:${levelStr}] ${message}${source}`; + if (level === 3) console.error(formatted); + else console.warn(formatted); + }, + ); + } window.webContents.on("did-finish-load", async () => { console.log("[main-window] Renderer loaded successfully"); - console.log("[main-window] URL:", window.webContents.getURL()); if (initialBounds.isMaximized) { window.maximize(); } diff --git a/apps/desktop/vite/helpers.ts b/apps/desktop/vite/helpers.ts index b5f66e3a8f2..0af97c3ca5f 100644 --- a/apps/desktop/vite/helpers.ts +++ b/apps/desktop/vite/helpers.ts @@ -19,11 +19,8 @@ function copyDir({ src, dest }: { src: string; dest: string }): void { export function defineEnv( value: string | undefined, fallback?: string, -): string | undefined { - // Use || instead of ?? so empty strings (from unresolved GitHub Actions - // secrets) also fall through to the default value. - const resolved = value || fallback; - return resolved !== undefined ? JSON.stringify(resolved) : undefined; +): string { + return JSON.stringify(value ?? fallback); } const RESOURCES_TO_COPY = [ @@ -67,8 +64,9 @@ export function copyResourcesPlugin(): Plugin { /** * Strips the `crossorigin` attribute that Vite adds to script/link tags. - * Electron's ASAR file:// handler doesn't support CORS, so crossorigin - * causes the main JS bundle and CSS to silently fail to load (black screen). + * Windows-only: Electron's ASAR file:// handler doesn't support CORS on + * Windows, so crossorigin causes scripts/styles to silently fail to load + * (black screen). macOS and Linux are unaffected. */ export function stripCrossOriginPlugin(): Plugin { return { @@ -76,7 +74,8 @@ export function stripCrossOriginPlugin(): Plugin { transformIndexHtml: { order: "post", handler(html) { - return html.replace(/ crossorigin/g, ""); + if (process.platform !== "win32") return html; + return html.replace(/ crossorigin(?:="[^"]*")?/g, ""); }, }, }; diff --git a/scripts/postinstall.sh b/scripts/postinstall.sh new file mode 100755 index 00000000000..de2a60239a2 --- /dev/null +++ b/scripts/postinstall.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Prevent infinite recursion during postinstall +# electron-builder install-app-deps can trigger nested bun installs +# which would re-run postinstall, spawning hundreds of processes + +if [ -n "$SUPERSET_POSTINSTALL_RUNNING" ]; then + exit 0 +fi + +export SUPERSET_POSTINSTALL_RUNNING=1 + +# Run sherif for workspace validation +sherif + +# Install native dependencies for desktop app +bun run --filter=@superset/desktop install:deps From 56bf5f69fd2067a7a62a501fa634c1d959861988 Mon Sep 17 00:00:00 2001 From: Sandman-Ren Date: Tue, 24 Feb 2026 23:59:18 -0500 Subject: [PATCH 12/12] fix(ci): enable long paths for Windows git checkout Windows has a 260-character MAX_PATH limit. Some upstream file paths (e.g. deeply nested component directories) exceed this, causing `git checkout` to fail on Windows runners. Setting core.longPaths before checkout resolves this. --- .github/workflows/build-desktop.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 9c6c37df81c..031faff625e 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -242,6 +242,9 @@ jobs: environment: production steps: + - name: Enable long paths + run: git config --system core.longPaths true + - name: Checkout code uses: actions/checkout@v4