From ebdc4ab8e62a546891d5b9071ea54554bee22d8e Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Wed, 20 May 2026 19:20:25 -0400 Subject: [PATCH] fix desktop preload bridge loading --- .beads/issues.jsonl | 1 + apps/desktop/src/preload.ts | 46 +-- ...026-05-20-fix-electron-preload-bridge.html | 310 ++++++++++++++++++ 3 files changed, 334 insertions(+), 23 deletions(-) create mode 100644 docs/turns/2026-05-20-fix-electron-preload-bridge.html diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 90b903b..e98d086 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,3 +1,4 @@ +{"_type":"issue","id":"islandflow-hj3","title":"Fix Electron preload for desktop AI bridge","description":"## Why\\nThe desktop settings page reports the native AI bridge as unavailable because Electron fails to load the preload script in local dev.\\n\\n## What\\nUpdate the desktop preload implementation/build so Electron can execute it, restore window.islandflowDesktop, and verify the Copilot settings panel detects the bridge again.\\n\\n## Acceptance Criteria\\n- Electron no longer logs a preload syntax error\\n- window.islandflowDesktop is available in the desktop renderer\\n- The settings page no longer shows bridge unavailable solely because preload failed\\n- Relevant desktop/web tests pass","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-20T23:16:39Z","created_by":"dirtydishes","updated_at":"2026-05-20T23:20:20Z","started_at":"2026-05-20T23:16:48Z","closed_at":"2026-05-20T23:20:20Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-199","title":"fix desktop copilot fallback inside electron","description":"## Why\\nThe settings page can render the browser-only fallback even when Islandflow is running inside the Electron desktop shell.\\n\\n## What\\nSeparate desktop-shell detection from desktop AI transport state, make the provider recover if the bridge appears late or initial state loading fails, and cover the regression with tests.\\n\\n## Acceptance Criteria\\n- The desktop shell no longer shows the browser-only fallback solely because initial bridge state failed or arrived late\\n- Desktop-only actions can distinguish between missing Electron bridge and transport/auth problems\\n- Automated tests cover the recovery behavior","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-20T22:30:16Z","created_by":"dirtydishes","updated_at":"2026-05-20T22:37:21Z","started_at":"2026-05-20T22:30:23Z","closed_at":"2026-05-20T22:37:21Z","close_reason":"Fixed desktop-shell Copilot fallback handling, added bridge recovery logic, updated desktop-vs-bridge UI messaging, and added regression tests. Follow-up tracked in islandflow-c8f for unrelated web build blocker.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-yza","title":"Persist historical flow packets for alert detail replay","description":"## Why\nAlert details can show a missing persisted flow packet when the packet is no longer present in the Redis hot cache, even though the associated historical alert and evidence were loaded from ClickHouse.\n\n## What needs to be done\nTrace the API path that resolves alert detail flow packets, compare Redis hot-cache lookups with ClickHouse historical fetches, and ensure historical flow packet payloads are treated as first-class persisted data with context preserved when replaying or loading older alerts.\n\n## Acceptance Criteria\n- Alert detail flow packets load for historical alerts even when the packet is absent from Redis hot cache\n- Historical ClickHouse-backed flow packet responses preserve the context required by the UI\n- Relevant automated tests cover the regression or the gap is explicitly documented","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-20T06:52:04Z","created_by":"dirtydishes","updated_at":"2026-05-20T06:59:26Z","started_at":"2026-05-20T06:52:09Z","closed_at":"2026-05-20T06:59:26Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-jor","title":"Support Forgejo pull request status in desktop git panel","description":"The desktop app currently reports pull request status unavailable when a repository only has a Forgejo remote. Add native Forgejo/Gitea-style remote detection and pull request status lookup so Forgejo-only repositories can show PR state in the Codex app git panel.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-19T20:55:15Z","created_by":"dirtydishes","updated_at":"2026-05-19T20:59:46Z","started_at":"2026-05-19T20:55:25Z","closed_at":"2026-05-19T20:59:46Z","close_reason":"Patched the installed Codex desktop app bundle with a Forgejo PR status fallback and documented the local change.","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/apps/desktop/src/preload.ts b/apps/desktop/src/preload.ts index ed6f8df..5034033 100644 --- a/apps/desktop/src/preload.ts +++ b/apps/desktop/src/preload.ts @@ -1,34 +1,34 @@ -import { contextBridge, ipcRenderer } from "electron"; -import type { - IslandflowAiReasoningEffort, - IslandflowAiState, - IslandflowAiTaskRequest -} from "@islandflow/types"; -import { - DESKTOP_AI_CANCEL_LOGIN, - DESKTOP_AI_GET_STATE, - DESKTOP_AI_LOGIN_BROWSER, - DESKTOP_AI_LOGIN_DEVICE, - DESKTOP_AI_LOGOUT, - DESKTOP_AI_RUN_TASK, - DESKTOP_AI_STATE_CHANNEL, - DESKTOP_AI_UPDATE_PREFERENCES -} from "./desktop-ai-ipc.js"; +const { contextBridge, ipcRenderer } = require("electron"); + +const DESKTOP_AI_STATE_CHANNEL = "islandflow:desktop-ai:state"; +const DESKTOP_AI_GET_STATE = "islandflow:desktop-ai:get-state"; +const DESKTOP_AI_LOGIN_BROWSER = "islandflow:desktop-ai:login-browser"; +const DESKTOP_AI_LOGIN_DEVICE = "islandflow:desktop-ai:login-device"; +const DESKTOP_AI_CANCEL_LOGIN = "islandflow:desktop-ai:cancel-login"; +const DESKTOP_AI_LOGOUT = "islandflow:desktop-ai:logout"; +const DESKTOP_AI_UPDATE_PREFERENCES = "islandflow:desktop-ai:update-preferences"; +const DESKTOP_AI_RUN_TASK = "islandflow:desktop-ai:run-task"; + +type DesktopAiState = any; +type DesktopAiTaskRequest = any; +type DesktopAiPreferenceUpdate = Partial<{ + model: string | null; + reasoningEffort: string | null; +}>; const bridge = { ai: { - getState: (): Promise => ipcRenderer.invoke(DESKTOP_AI_GET_STATE), + getState: (): Promise => ipcRenderer.invoke(DESKTOP_AI_GET_STATE), loginWithBrowser: (): Promise => ipcRenderer.invoke(DESKTOP_AI_LOGIN_BROWSER), loginWithDeviceCode: (): Promise => ipcRenderer.invoke(DESKTOP_AI_LOGIN_DEVICE), cancelLogin: (): Promise => ipcRenderer.invoke(DESKTOP_AI_CANCEL_LOGIN), logout: (): Promise => ipcRenderer.invoke(DESKTOP_AI_LOGOUT), - updatePreferences: ( - next: Partial<{ model: string | null; reasoningEffort: IslandflowAiReasoningEffort | null }> - ): Promise => ipcRenderer.invoke(DESKTOP_AI_UPDATE_PREFERENCES, next), - runTask: (request: IslandflowAiTaskRequest): Promise<{ taskId: string }> => + updatePreferences: (next: DesktopAiPreferenceUpdate): Promise => + ipcRenderer.invoke(DESKTOP_AI_UPDATE_PREFERENCES, next), + runTask: (request: DesktopAiTaskRequest): Promise<{ taskId: string }> => ipcRenderer.invoke(DESKTOP_AI_RUN_TASK, request), - subscribe: (listener: (state: IslandflowAiState) => void): (() => void) => { - const handler = (_event: Electron.IpcRendererEvent, state: IslandflowAiState) => { + subscribe: (listener: (state: DesktopAiState) => void): (() => void) => { + const handler = (_event: Electron.IpcRendererEvent, state: DesktopAiState) => { listener(state); }; diff --git a/docs/turns/2026-05-20-fix-electron-preload-bridge.html b/docs/turns/2026-05-20-fix-electron-preload-bridge.html new file mode 100644 index 0000000..962df4d --- /dev/null +++ b/docs/turns/2026-05-20-fix-electron-preload-bridge.html @@ -0,0 +1,310 @@ + + + + + + 2026-05-20 · Fix Electron Preload Bridge + + + +
+
+
+

Repository Turn Document

+

Fix Electron Preload Bridge

+

+ Repaired the desktop ChatGPT login bridge by making the Electron preload + script emit a classic runnable script instead of ESM. Local desktop + settings now reconnect to the native bridge and show the managed + account, transport readiness, and model controls again. +

+
+
+
+ Area + Electron desktop shell +
+
+ Issue + `islandflow-hj3` +
+
+ Validated + Build, tests, live desktop restart +
+
+
+ +
+

Summary

+

+ The desktop Copilot settings page was not failing because login was broken. + Electron was failing before the bridge could even load: the preload bundle + contained runtime `import` syntax, so the renderer never got + `window.islandflowDesktop`. After switching the preload implementation to + CommonJS-compatible runtime code, the local Electron shell resumed exposing + the bridge and the settings page recovered immediately after restart. +

+
+ +
+

Changes Made

+
    +
  • Rewrote the desktop preload runtime imports to use `require("electron")`.
  • +
  • Inlined the IPC channel names inside the preload file so no runtime module imports remain.
  • +
  • Removed the type-only module usage that caused TypeScript to emit a trailing `export {}`.
  • +
  • Verified the generated `apps/desktop/dist/preload.js` is now a plain script.
  • +
+
+ +
+

Context

+

+ The UI state on `localhost:3000/settings` was already designed to distinguish + between “not in desktop” and “desktop shell detected but native bridge + missing.” In this case the latter message was accurate but incomplete: Electron + was present, yet the preload never executed because the emitted file was not + valid for the preload runtime. That left the renderer with an Electron user + agent but no bridge object. +

+
+ +
+
+

Important Implementation Details

+
    +
  • Electron logged `Unable to load preload script` followed by `Cannot use import statement outside a module` during local startup.
  • +
  • The desktop main process configuration was already pointing at the correct preload path, so the failure was in emitted file format, not window wiring.
  • +
  • Keeping the preload free of runtime imports ensures Electron can execute it even though the desktop workspace is otherwise ESM-oriented.
  • +
  • The bridge API shape exposed to the web app did not change, so no renderer updates were required.
  • +
+
+
+

Relevant Runtime Signal

+

Observed in Electron startup logs during reproduction

+

+ `Unable to load preload script: .../dist/preload.js` and + `SyntaxError: Cannot use import statement outside a module` + were the decisive clues that isolated the problem to preload emission. +

+
+
+ +
+

Relevant Diff Snippets

+
apps/desktop/src/preload.ts
+
--- a/apps/desktop/src/preload.ts
++++ b/apps/desktop/src/preload.ts
+@@
+-import { contextBridge, ipcRenderer } from "electron";
+-import { ... } from "./desktop-ai-ipc.js";
++const { contextBridge, ipcRenderer } = require("electron");
++const DESKTOP_AI_STATE_CHANNEL = "islandflow:desktop-ai:state";
++const DESKTOP_AI_GET_STATE = "islandflow:desktop-ai:get-state";
++const DESKTOP_AI_LOGIN_BROWSER = "islandflow:desktop-ai:login-browser";
++const DESKTOP_AI_LOGIN_DEVICE = "islandflow:desktop-ai:login-device";
++const DESKTOP_AI_CANCEL_LOGIN = "islandflow:desktop-ai:cancel-login";
++const DESKTOP_AI_LOGOUT = "islandflow:desktop-ai:logout";
++const DESKTOP_AI_UPDATE_PREFERENCES = "islandflow:desktop-ai:update-preferences";
++const DESKTOP_AI_RUN_TASK = "islandflow:desktop-ai:run-task";
+@@
+-    updatePreferences: (
+-      next: Partial<{ model: string | null; reasoningEffort: IslandflowAiReasoningEffort | null }>
+-    ): Promise<void> => ipcRenderer.invoke(DESKTOP_AI_UPDATE_PREFERENCES, next),
++    updatePreferences: (next: DesktopAiPreferenceUpdate): Promise<void> =>
++      ipcRenderer.invoke(DESKTOP_AI_UPDATE_PREFERENCES, next),
+        
+
+ +
+

Expected Impact for End-Users

+

+ Local Islandflow Desktop sessions no longer get stuck in the misleading + “Bridge unavailable in this window” state when the actual problem is preload + execution. Users should see their managed ChatGPT session reconnect, transport + status move to Ready, and model controls become interactive + after launching or restarting the desktop shell. +

+
+ +
+

Validation

+
+
+ Desktop Build + `bun --cwd=apps/desktop run build` succeeded and emitted a plain preload script. +
+
+ Desktop Tests + `bun --cwd=apps/desktop test` passed. +
+
+ Web Tests + `bun test apps/web/app/desktop-ai.test.ts` passed. +
+
+ Manual Verification + Restarted `bun run dev:desktop` and confirmed settings showed the connected account, ready transport, and populated model controls. +
+
+
+ +
+

Issues, Limitations, and Mitigations

+
    +
  • The preload now duplicates the IPC channel strings instead of importing them. That is intentional to keep the emitted file import-free.
  • +
  • The desktop startup logs still show unrelated remote plugin 403 warnings from the managed app-server environment. They did not block bridge recovery in this turn.
  • +
  • The preload uses loose local types to avoid turning the file back into a runtime module. If preload complexity grows, a dedicated CommonJS-safe shared typing strategy may be worth adding later.
  • +
+
+ +
+

Follow-up Work

+
+ No immediate follow-up required + Possible hardening: build-time preload output guard +
+
+
+ +