diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 360085c..e94da76 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -1,3 +1,4 @@
+{"_type":"issue","id":"islandflow-sc6","title":"fix electron codex bridge preload loading","description":"Electron settings showed the browser-only Desktop Required fallback because the renderer did not see the native islandflowDesktop preload bridge or an Electron user-agent marker. Fix the desktop launch path so ChatGPT/Codex subscription controls are available inside Islandflow Desktop again.","status":"closed","priority":1,"issue_type":"bug","owner":"dishes@dpdrm.com","created_at":"2026-05-20T23:42:58Z","created_by":"dirtydishes","updated_at":"2026-05-20T23:44:24Z","closed_at":"2026-05-20T23:44:24Z","close_reason":"Fixed by loading the desktop preload as CommonJS via dist/preload.cjs, copying it during the desktop build, and adding an explicit IslandflowDesktop/Electron user-agent marker for renderer shell detection. Validated with desktop build, desktop tests, and web desktop-ai tests.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_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}
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index 8a3c3e5..0ce9815 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -5,7 +5,7 @@
"version": "0.1.0",
"main": "dist/main.js",
"scripts": {
- "build": "tsc -p tsconfig.json",
+ "build": "tsc -p tsconfig.json && cp src/preload.cjs dist/preload.cjs",
"typecheck": "tsc -p tsconfig.json --noEmit",
"test": "bun test src",
"start": "bun run build && electron-forge start",
diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts
index c3fde92..6b61199 100644
--- a/apps/desktop/src/main.ts
+++ b/apps/desktop/src/main.ts
@@ -27,7 +27,7 @@ const WINDOW_TITLE = "Islandflow";
let mainWindow: BrowserWindow | null = null;
let desktopAiService: IslandflowDesktopAiService | null = null;
-const PRELOAD_PATH = fileURLToPath(new URL("./preload.js", import.meta.url));
+const PRELOAD_PATH = fileURLToPath(new URL("./preload.cjs", import.meta.url));
const canOpenExternalUrl = (sourceUrl: string, targetUrl: string): boolean => {
return isTrustedAppUrl(sourceUrl) && isSafeExternalUrl(targetUrl);
@@ -87,6 +87,9 @@ const createMainWindow = (): BrowserWindow => {
});
installNavigationGuards(window);
+ window.webContents.setUserAgent(
+ `${window.webContents.getUserAgent()} IslandflowDesktop/${app.getVersion()} Electron/${process.versions.electron}`
+ );
window.once("ready-to-show", () => {
window.show();
diff --git a/apps/desktop/src/preload.cjs b/apps/desktop/src/preload.cjs
new file mode 100644
index 0000000..fc7b0b8
--- /dev/null
+++ b/apps/desktop/src/preload.cjs
@@ -0,0 +1,36 @@
+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";
+const DESKTOP_AI_CANCEL_TASK = "islandflow:desktop-ai:cancel-task";
+
+const bridge = {
+ ai: {
+ getState: () => ipcRenderer.invoke(DESKTOP_AI_GET_STATE),
+ loginWithBrowser: () => ipcRenderer.invoke(DESKTOP_AI_LOGIN_BROWSER),
+ loginWithDeviceCode: () => ipcRenderer.invoke(DESKTOP_AI_LOGIN_DEVICE),
+ cancelLogin: () => ipcRenderer.invoke(DESKTOP_AI_CANCEL_LOGIN),
+ logout: () => ipcRenderer.invoke(DESKTOP_AI_LOGOUT),
+ updatePreferences: (next) => ipcRenderer.invoke(DESKTOP_AI_UPDATE_PREFERENCES, next),
+ runTask: (request) => ipcRenderer.invoke(DESKTOP_AI_RUN_TASK, request),
+ cancelTask: (taskId) => ipcRenderer.invoke(DESKTOP_AI_CANCEL_TASK, taskId),
+ subscribe: (listener) => {
+ const handler = (_event, state) => {
+ listener(state);
+ };
+
+ ipcRenderer.on(DESKTOP_AI_STATE_CHANNEL, handler);
+ return () => {
+ ipcRenderer.off(DESKTOP_AI_STATE_CHANNEL, handler);
+ };
+ }
+ }
+};
+
+contextBridge.exposeInMainWorld("islandflowDesktop", bridge);
diff --git a/docs/turns/2026-05-20-fix-electron-codex-bridge-regression.html b/docs/turns/2026-05-20-fix-electron-codex-bridge-regression.html
new file mode 100644
index 0000000..f091bf8
--- /dev/null
+++ b/docs/turns/2026-05-20-fix-electron-codex-bridge-regression.html
@@ -0,0 +1,257 @@
+
+
+
+
+
+ 2026-05-20 · Fix Electron Codex Bridge Regression
+
+
+
+
+
+ Repository Turn Document
+ Fix Electron Codex Bridge Regression
+
+ Repaired the Electron desktop ChatGPT/Codex bridge after the Settings screen fell back to the
+ browser-only “Desktop Required” state. The desktop shell now loads its preload bridge from a
+ CommonJS file and stamps the renderer with an explicit Islandflow desktop user-agent marker.
+
+
+
+
+
+ Summary
+
+ The Electron app was presenting the same fallback shown in a regular browser, which blocked managed
+ ChatGPT login, Codex model loading, and native Copilot controls. The fix makes the preload file
+ unambiguously runnable by Electron and gives the web runtime a reliable desktop shell signal while
+ the bridge is coming online.
+
+
+
+
+ Changes Made
+
+ - Added
apps/desktop/src/preload.cjs, a CommonJS preload that exposes window.islandflowDesktop.ai.
+ - Updated the desktop build script to copy
src/preload.cjs into dist/preload.cjs.
+ - Pointed the Electron
BrowserWindow preload path at preload.cjs instead of preload.js.
+ - Added an explicit
IslandflowDesktop/<version> Electron/<version> user-agent token before loading the app URL.
+
+
+
+
+ Context
+
+ The visible failure mode was the Settings page labeling the session as browser-only even though it was
+ running inside Islandflow Desktop. That meant the renderer saw neither the native preload bridge nor
+ an Electron user-agent signature. The previous bridge code was emitted as preload.js inside
+ an ESM package, which is fragile for Electron preload loading because the script is intended to run as
+ classic CommonJS.
+
+
+
+
+ Important Implementation Details
+
+ - The preload bridge still exposes the same IPC methods, so the web UI contract did not change.
+ - The user-agent marker is a fallback signal, not the primary bridge. When the preload is healthy,
window.islandflowDesktop remains the source of truth for native controls.
+ - The app and Electron versions are read at runtime with
app.getVersion() and process.versions.electron.
+ - The older TypeScript preload source remains in place for now, but Electron no longer depends on its emitted
.js file.
+
+
+
+
+ Relevant Diff Snippets
+
+ Snippets are formatted as unified patch text for Diffs-style review. Reference:
+ https://diffs.com/docs.
+
+ diff --git a/apps/desktop/package.json b/apps/desktop/package.json
+@@
+- "build": "tsc -p tsconfig.json",
++ "build": "tsc -p tsconfig.json && cp src/preload.cjs dist/preload.cjs",
+
+diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts
+@@
+-const PRELOAD_PATH = fileURLToPath(new URL("./preload.js", import.meta.url));
++const PRELOAD_PATH = fileURLToPath(new URL("./preload.cjs", import.meta.url));
+@@
+ installNavigationGuards(window);
++ window.webContents.setUserAgent(
++ `${window.webContents.getUserAgent()} IslandflowDesktop/${app.getVersion()} Electron/${process.versions.electron}`
++ );
+
+diff --git a/apps/desktop/src/preload.cjs b/apps/desktop/src/preload.cjs
++const { contextBridge, ipcRenderer } = require("electron");
++...
++contextBridge.exposeInMainWorld("islandflowDesktop", bridge);
+
+
+
+ Expected Impact for End-Users
+
+ Opening Settings inside Islandflow Desktop should no longer show the browser-only fallback. Users should
+ see the managed auth surface, connect ChatGPT or Codex, load managed models, and use native Copilot
+ controls from the desktop app again.
+
+
+
+
+ Validation
+
+
Buildbun --cwd=apps/desktop run build passed.
+
Desktop Testsbun --cwd=apps/desktop run test passed, 12 tests.
+
Web Bridge Testsbun test apps/web/app/desktop-ai.test.ts passed, 17 tests.
+
+
+
+
+ Issues, Limitations, and Mitigations
+
+ - The live GUI was not fully re-driven in this turn; validation focused on the build artifact and bridge detection tests.
+ - The old
src/preload.ts still exists. A later cleanup can remove or consolidate it after confirming no packaging path still references it.
+ - An unrelated dirty file,
apps/web/next-env.d.ts, was already present and was not touched by this fix.
+
+
+
+
+ Follow-up Work
+
+ - Close
islandflow-sc6 after the commit lands.
+ - Consider adding a lightweight Electron smoke test that asserts
window.islandflowDesktop exists after launch.
+ - Remove the unused TypeScript preload path once the CommonJS preload has been verified in packaged and dev runs.
+
+
+
+
+