islandflow/docs/turns/2026-05-20-fix-electron-codex-bridge-regression.html

268 lines
11 KiB
HTML

<!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, exposes an explicit desktop runtime marker, and stamps the renderer with an
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; web build hit tracked shared-types blocker</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 reliable desktop shell signals 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>Exposed <code>window.islandflowDesktopRuntime</code> from preload as a minimal Electron shell marker.</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/&lt;version&gt; Electron/&lt;version&gt;</code> user-agent token before loading the app URL.</li>
<li>Updated the web runtime detector to accept the preload runtime marker and the Islandflow desktop user-agent token.</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
a desktop-shell signal it trusted. 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. The follow-up patch also protects against Electron user-agent stripping by using a
dedicated preload marker.
</p>
</section>
<section>
<h2>Important Implementation Details</h2>
<ul>
<li>The preload bridge still exposes the same IPC methods, so the native AI control contract did not change.</li>
<li>The runtime marker is intentionally tiny: it says only that this renderer is Islandflow running in Electron.</li>
<li>The user-agent marker is a secondary fallback signal. When the preload bridge 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 &amp;&amp; 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("islandflowDesktopRuntime", {
+ shell: "electron",
+ app: "islandflow"
+});
+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. If the
native bridge itself is unavailable, users should see bridge recovery guidance instead of being told
they are not in the desktop app. When the bridge is available, users should be able to 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, 19 tests.</div>
<div class="validation-card warn"><span>Web Build</span><code>bun --cwd=apps/web run build</code> compiled, then failed on tracked issue <code>islandflow-c8f</code>: shared <code>packages/types</code> imports still use explicit <code>.ts</code> extensions.</div>
</div>
</section>
<section>
<h2>Issues, Limitations, and Mitigations</h2>
<ul>
<li>The live GUI screenshot after the first patch still showed the browser-only fallback, which prompted the explicit preload runtime marker added here.</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>The web production build remains blocked by <code>islandflow-c8f</code>, unrelated to the desktop runtime marker change.</li>
</ul>
</section>
<section>
<h2>Follow-up Work</h2>
<ul>
<li>Close <code>islandflow-sc6</code> after the follow-up 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>