fix electron codex bridge loading
This commit is contained in:
parent
32e965d782
commit
31a8e07a5b
5 changed files with 299 additions and 2 deletions
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
36
apps/desktop/src/preload.cjs
Normal file
36
apps/desktop/src/preload.cjs
Normal file
|
|
@ -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);
|
||||
257
docs/turns/2026-05-20-fix-electron-codex-bridge-regression.html
Normal file
257
docs/turns/2026-05-20-fix-electron-codex-bridge-regression.html
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>2026-05-20 · Fix Electron Codex Bridge Regression</title>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--bg: oklch(0.11 0.01 250);
|
||||
--panel: oklch(0.16 0.013 250 / 0.94);
|
||||
--panel-2: oklch(0.135 0.012 250 / 0.96);
|
||||
--text: oklch(0.93 0.014 250);
|
||||
--muted: oklch(0.74 0.018 250);
|
||||
--faint: oklch(0.6 0.016 250);
|
||||
--line: oklch(0.75 0.014 250 / 0.14);
|
||||
--accent: oklch(0.79 0.12 74);
|
||||
--accent-soft: oklch(0.79 0.12 74 / 0.12);
|
||||
--ok: oklch(0.75 0.12 151 / 0.13);
|
||||
--warn: oklch(0.72 0.14 28 / 0.13);
|
||||
--shadow: 0 24px 80px oklch(0.02 0.01 250 / 0.46);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;
|
||||
background:
|
||||
radial-gradient(circle at 12% 0%, oklch(0.79 0.12 74 / 0.08), transparent 30%),
|
||||
linear-gradient(180deg, oklch(0.15 0.012 250), var(--bg));
|
||||
color: var(--text);
|
||||
}
|
||||
main {
|
||||
width: min(1120px, calc(100vw - 40px));
|
||||
margin: 0 auto;
|
||||
padding: 36px 0 64px;
|
||||
}
|
||||
h1, h2, h3, .eyebrow, .meta, code, pre {
|
||||
font-family: "IBM Plex Mono", ui-monospace, monospace;
|
||||
}
|
||||
h1, h2, h3, p { margin: 0; }
|
||||
p, li { color: var(--muted); line-height: 1.68; }
|
||||
a { color: var(--accent); }
|
||||
main > * + * { margin-top: 22px; }
|
||||
.hero, section {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 18px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.hero {
|
||||
padding: 28px;
|
||||
background: linear-gradient(135deg, oklch(0.2 0.017 250 / 0.96), var(--panel));
|
||||
}
|
||||
.eyebrow {
|
||||
margin-bottom: 10px;
|
||||
color: var(--accent);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.18em;
|
||||
font-size: 0.74rem;
|
||||
}
|
||||
h1 {
|
||||
max-width: 880px;
|
||||
font-size: clamp(2rem, 3.3vw, 3.35rem);
|
||||
line-height: 1;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.hero-copy {
|
||||
max-width: 76ch;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.meta-grid, .validation-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
margin-top: 22px;
|
||||
}
|
||||
.meta-card, .validation-card, .callout {
|
||||
padding: 15px 16px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--line);
|
||||
background: oklch(0.1 0.009 250 / 0.52);
|
||||
}
|
||||
.meta-card span, .validation-card span {
|
||||
display: block;
|
||||
margin-bottom: 7px;
|
||||
color: var(--faint);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.15em;
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
section {
|
||||
padding: 24px;
|
||||
background: var(--panel-2);
|
||||
}
|
||||
section h2 {
|
||||
margin-bottom: 14px;
|
||||
font-size: 1rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
ul { margin: 0; padding-left: 20px; }
|
||||
li + li { margin-top: 9px; }
|
||||
code {
|
||||
color: var(--text);
|
||||
background: oklch(0.08 0.008 250 / 0.85);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
padding: 0.08rem 0.32rem;
|
||||
}
|
||||
pre {
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--line);
|
||||
background: oklch(0.08 0.008 250 / 0.95);
|
||||
color: var(--text);
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.52;
|
||||
}
|
||||
.callout { background: var(--accent-soft); }
|
||||
.validation-card.good { background: var(--ok); }
|
||||
.validation-card.warn { background: var(--warn); }
|
||||
.diff-note {
|
||||
margin-bottom: 12px;
|
||||
color: var(--faint);
|
||||
}
|
||||
@media (max-width: 820px) {
|
||||
main { width: min(100vw - 24px, 1120px); padding-top: 18px; }
|
||||
.meta-grid, .validation-grid { grid-template-columns: minmax(0, 1fr); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<article class="hero">
|
||||
<p class="eyebrow">Repository Turn Document</p>
|
||||
<h1>Fix Electron Codex Bridge Regression</h1>
|
||||
<p class="hero-copy">
|
||||
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.
|
||||
</p>
|
||||
<div class="meta-grid">
|
||||
<div class="meta-card"><span>Issue</span>islandflow-sc6</div>
|
||||
<div class="meta-card"><span>Scope</span>Electron desktop bridge loading</div>
|
||||
<div class="meta-card"><span>Validation</span>Desktop build and focused tests passed</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<section>
|
||||
<h2>Summary</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Changes Made</h2>
|
||||
<ul>
|
||||
<li>Added <code>apps/desktop/src/preload.cjs</code>, a CommonJS preload that exposes <code>window.islandflowDesktop.ai</code>.</li>
|
||||
<li>Updated the desktop build script to copy <code>src/preload.cjs</code> into <code>dist/preload.cjs</code>.</li>
|
||||
<li>Pointed the Electron <code>BrowserWindow</code> preload path at <code>preload.cjs</code> instead of <code>preload.js</code>.</li>
|
||||
<li>Added an explicit <code>IslandflowDesktop/<version> Electron/<version></code> user-agent token before loading the app URL.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Context</h2>
|
||||
<p>
|
||||
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 <code>preload.js</code> inside
|
||||
an ESM package, which is fragile for Electron preload loading because the script is intended to run as
|
||||
classic CommonJS.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Important Implementation Details</h2>
|
||||
<ul>
|
||||
<li>The preload bridge still exposes the same IPC methods, so the web UI contract did not change.</li>
|
||||
<li>The user-agent marker is a fallback signal, not the primary bridge. When the preload is healthy, <code>window.islandflowDesktop</code> remains the source of truth for native controls.</li>
|
||||
<li>The app and Electron versions are read at runtime with <code>app.getVersion()</code> and <code>process.versions.electron</code>.</li>
|
||||
<li>The older TypeScript preload source remains in place for now, but Electron no longer depends on its emitted <code>.js</code> file.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Relevant Diff Snippets</h2>
|
||||
<p class="diff-note">
|
||||
Snippets are formatted as unified patch text for Diffs-style review. Reference:
|
||||
<a href="https://diffs.com/docs">https://diffs.com/docs</a>.
|
||||
</p>
|
||||
<pre><code>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);</code></pre>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Expected Impact for End-Users</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Validation</h2>
|
||||
<div class="validation-grid">
|
||||
<div class="validation-card good"><span>Build</span><code>bun --cwd=apps/desktop run build</code> passed.</div>
|
||||
<div class="validation-card good"><span>Desktop Tests</span><code>bun --cwd=apps/desktop run test</code> passed, 12 tests.</div>
|
||||
<div class="validation-card good"><span>Web Bridge Tests</span><code>bun test apps/web/app/desktop-ai.test.ts</code> passed, 17 tests.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Issues, Limitations, and Mitigations</h2>
|
||||
<ul>
|
||||
<li>The live GUI was not fully re-driven in this turn; validation focused on the build artifact and bridge detection tests.</li>
|
||||
<li>The old <code>src/preload.ts</code> still exists. A later cleanup can remove or consolidate it after confirming no packaging path still references it.</li>
|
||||
<li>An unrelated dirty file, <code>apps/web/next-env.d.ts</code>, was already present and was not touched by this fix.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Follow-up Work</h2>
|
||||
<ul>
|
||||
<li>Close <code>islandflow-sc6</code> after the commit lands.</li>
|
||||
<li>Consider adding a lightweight Electron smoke test that asserts <code>window.islandflowDesktop</code> exists after launch.</li>
|
||||
<li>Remove the unused TypeScript preload path once the CommonJS preload has been verified in packaged and dev runs.</li>
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue