fix desktop preload bridge loading
This commit is contained in:
parent
17b030f01f
commit
ebdc4ab8e6
3 changed files with 334 additions and 23 deletions
|
|
@ -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-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-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}
|
{"_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}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,34 @@
|
||||||
import { contextBridge, ipcRenderer } from "electron";
|
const { contextBridge, ipcRenderer } = require("electron");
|
||||||
import type {
|
|
||||||
IslandflowAiReasoningEffort,
|
const DESKTOP_AI_STATE_CHANNEL = "islandflow:desktop-ai:state";
|
||||||
IslandflowAiState,
|
const DESKTOP_AI_GET_STATE = "islandflow:desktop-ai:get-state";
|
||||||
IslandflowAiTaskRequest
|
const DESKTOP_AI_LOGIN_BROWSER = "islandflow:desktop-ai:login-browser";
|
||||||
} from "@islandflow/types";
|
const DESKTOP_AI_LOGIN_DEVICE = "islandflow:desktop-ai:login-device";
|
||||||
import {
|
const DESKTOP_AI_CANCEL_LOGIN = "islandflow:desktop-ai:cancel-login";
|
||||||
DESKTOP_AI_CANCEL_LOGIN,
|
const DESKTOP_AI_LOGOUT = "islandflow:desktop-ai:logout";
|
||||||
DESKTOP_AI_GET_STATE,
|
const DESKTOP_AI_UPDATE_PREFERENCES = "islandflow:desktop-ai:update-preferences";
|
||||||
DESKTOP_AI_LOGIN_BROWSER,
|
const DESKTOP_AI_RUN_TASK = "islandflow:desktop-ai:run-task";
|
||||||
DESKTOP_AI_LOGIN_DEVICE,
|
|
||||||
DESKTOP_AI_LOGOUT,
|
type DesktopAiState = any;
|
||||||
DESKTOP_AI_RUN_TASK,
|
type DesktopAiTaskRequest = any;
|
||||||
DESKTOP_AI_STATE_CHANNEL,
|
type DesktopAiPreferenceUpdate = Partial<{
|
||||||
DESKTOP_AI_UPDATE_PREFERENCES
|
model: string | null;
|
||||||
} from "./desktop-ai-ipc.js";
|
reasoningEffort: string | null;
|
||||||
|
}>;
|
||||||
|
|
||||||
const bridge = {
|
const bridge = {
|
||||||
ai: {
|
ai: {
|
||||||
getState: (): Promise<IslandflowAiState> => ipcRenderer.invoke(DESKTOP_AI_GET_STATE),
|
getState: (): Promise<DesktopAiState> => ipcRenderer.invoke(DESKTOP_AI_GET_STATE),
|
||||||
loginWithBrowser: (): Promise<void> => ipcRenderer.invoke(DESKTOP_AI_LOGIN_BROWSER),
|
loginWithBrowser: (): Promise<void> => ipcRenderer.invoke(DESKTOP_AI_LOGIN_BROWSER),
|
||||||
loginWithDeviceCode: (): Promise<void> => ipcRenderer.invoke(DESKTOP_AI_LOGIN_DEVICE),
|
loginWithDeviceCode: (): Promise<void> => ipcRenderer.invoke(DESKTOP_AI_LOGIN_DEVICE),
|
||||||
cancelLogin: (): Promise<void> => ipcRenderer.invoke(DESKTOP_AI_CANCEL_LOGIN),
|
cancelLogin: (): Promise<void> => ipcRenderer.invoke(DESKTOP_AI_CANCEL_LOGIN),
|
||||||
logout: (): Promise<void> => ipcRenderer.invoke(DESKTOP_AI_LOGOUT),
|
logout: (): Promise<void> => ipcRenderer.invoke(DESKTOP_AI_LOGOUT),
|
||||||
updatePreferences: (
|
updatePreferences: (next: DesktopAiPreferenceUpdate): Promise<void> =>
|
||||||
next: Partial<{ model: string | null; reasoningEffort: IslandflowAiReasoningEffort | null }>
|
ipcRenderer.invoke(DESKTOP_AI_UPDATE_PREFERENCES, next),
|
||||||
): Promise<void> => ipcRenderer.invoke(DESKTOP_AI_UPDATE_PREFERENCES, next),
|
runTask: (request: DesktopAiTaskRequest): Promise<{ taskId: string }> =>
|
||||||
runTask: (request: IslandflowAiTaskRequest): Promise<{ taskId: string }> =>
|
|
||||||
ipcRenderer.invoke(DESKTOP_AI_RUN_TASK, request),
|
ipcRenderer.invoke(DESKTOP_AI_RUN_TASK, request),
|
||||||
subscribe: (listener: (state: IslandflowAiState) => void): (() => void) => {
|
subscribe: (listener: (state: DesktopAiState) => void): (() => void) => {
|
||||||
const handler = (_event: Electron.IpcRendererEvent, state: IslandflowAiState) => {
|
const handler = (_event: Electron.IpcRendererEvent, state: DesktopAiState) => {
|
||||||
listener(state);
|
listener(state);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
310
docs/turns/2026-05-20-fix-electron-preload-bridge.html
Normal file
310
docs/turns/2026-05-20-fix-electron-preload-bridge.html
Normal file
|
|
@ -0,0 +1,310 @@
|
||||||
|
<!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 Preload Bridge</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bg: oklch(0.11 0.01 250);
|
||||||
|
--panel: oklch(0.16 0.013 250 / 0.92);
|
||||||
|
--panel-2: oklch(0.14 0.012 250 / 0.9);
|
||||||
|
--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);
|
||||||
|
--green-soft: oklch(0.75 0.12 151 / 0.12);
|
||||||
|
--red-soft: oklch(0.72 0.14 28 / 0.12);
|
||||||
|
--shadow: 0 24px 80px oklch(0.02 0.01 250 / 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at top left, oklch(0.8 0.12 74 / 0.09), transparent 28%),
|
||||||
|
linear-gradient(180deg, oklch(0.15 0.012 250) 0%, oklch(0.1 0.01 250) 100%);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
width: min(1160px, 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 { margin: 0; }
|
||||||
|
p, li { color: var(--muted); line-height: 1.7; }
|
||||||
|
a { color: inherit; }
|
||||||
|
main > * + * { margin-top: 22px; }
|
||||||
|
.hero, section {
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 24px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
.hero {
|
||||||
|
display: grid;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 28px;
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, oklch(0.2 0.017 250 / 0.96), oklch(0.14 0.012 250 / 0.98)),
|
||||||
|
var(--panel);
|
||||||
|
}
|
||||||
|
.eyebrow {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
color: var(--accent);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.18em;
|
||||||
|
font-size: 0.76rem;
|
||||||
|
}
|
||||||
|
.hero h1 {
|
||||||
|
font-size: clamp(2rem, 3.4vw, 3.5rem);
|
||||||
|
line-height: 0.96;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.hero-copy { max-width: 72ch; margin: 14px 0 0; }
|
||||||
|
.hero-grid, .two-col, .validation-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
.hero-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||||
|
.two-col { grid-template-columns: minmax(0, 1.15fr) minmax(320px, 0.85fr); }
|
||||||
|
.validation-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
|
.stat, .validation-card, .callout {
|
||||||
|
padding: 16px 18px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
background: oklch(0.12 0.01 250 / 0.5);
|
||||||
|
}
|
||||||
|
.stat span, .validation-card span {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--faint);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.16em;
|
||||||
|
font-size: 0.68rem;
|
||||||
|
}
|
||||||
|
.validation-card.good { background: var(--green-soft); }
|
||||||
|
.validation-card.warn { background: var(--red-soft); }
|
||||||
|
.callout { background: var(--accent-soft); }
|
||||||
|
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: 10px; }
|
||||||
|
.chip-row { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||||
|
.chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 30px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid oklch(0.8 0.12 74 / 0.28);
|
||||||
|
background: var(--accent-soft);
|
||||||
|
font-size: 0.74rem;
|
||||||
|
}
|
||||||
|
.meta { color: var(--faint); font-size: 0.82rem; }
|
||||||
|
pre.diff {
|
||||||
|
margin: 0;
|
||||||
|
padding: 16px 18px;
|
||||||
|
overflow: auto;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
background: oklch(0.08 0.008 250 / 0.95);
|
||||||
|
color: var(--text);
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
.diff-title {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: var(--faint);
|
||||||
|
font-size: 0.76rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
@media (max-width: 860px) {
|
||||||
|
main {
|
||||||
|
width: min(100vw - 24px, 1160px);
|
||||||
|
padding-top: 18px;
|
||||||
|
}
|
||||||
|
.hero-grid, .two-col, .validation-grid {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<article class="hero">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Repository Turn Document</p>
|
||||||
|
<h1>Fix Electron Preload Bridge</h1>
|
||||||
|
<p class="hero-copy">
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="hero-grid">
|
||||||
|
<div class="stat">
|
||||||
|
<span>Area</span>
|
||||||
|
<strong>Electron desktop shell</strong>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<span>Issue</span>
|
||||||
|
<strong>`islandflow-hj3`</strong>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<span>Validated</span>
|
||||||
|
<strong>Build, tests, live desktop restart</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Summary</h2>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Changes Made</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Rewrote the desktop preload runtime imports to use `require("electron")`.</li>
|
||||||
|
<li>Inlined the IPC channel names inside the preload file so no runtime module imports remain.</li>
|
||||||
|
<li>Removed the type-only module usage that caused TypeScript to emit a trailing `export {}`.</li>
|
||||||
|
<li>Verified the generated `apps/desktop/dist/preload.js` is now a plain script.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Context</h2>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="two-col">
|
||||||
|
<div>
|
||||||
|
<h2>Important Implementation Details</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Electron logged `Unable to load preload script` followed by `Cannot use import statement outside a module` during local startup.</li>
|
||||||
|
<li>The desktop main process configuration was already pointing at the correct preload path, so the failure was in emitted file format, not window wiring.</li>
|
||||||
|
<li>Keeping the preload free of runtime imports ensures Electron can execute it even though the desktop workspace is otherwise ESM-oriented.</li>
|
||||||
|
<li>The bridge API shape exposed to the web app did not change, so no renderer updates were required.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="callout">
|
||||||
|
<h2>Relevant Runtime Signal</h2>
|
||||||
|
<p class="meta">Observed in Electron startup logs during reproduction</p>
|
||||||
|
<p>
|
||||||
|
`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.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Relevant Diff Snippets</h2>
|
||||||
|
<div class="diff-title">apps/desktop/src/preload.ts</div>
|
||||||
|
<pre class="diff"><code class="language-diff">--- 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),
|
||||||
|
</code></pre>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Expected Impact for End-Users</h2>
|
||||||
|
<p>
|
||||||
|
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 <strong>Ready</strong>, and model controls become interactive
|
||||||
|
after launching or restarting the desktop shell.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Validation</h2>
|
||||||
|
<div class="validation-grid">
|
||||||
|
<div class="validation-card good">
|
||||||
|
<span>Desktop Build</span>
|
||||||
|
<strong>`bun --cwd=apps/desktop run build` succeeded and emitted a plain preload script.</strong>
|
||||||
|
</div>
|
||||||
|
<div class="validation-card good">
|
||||||
|
<span>Desktop Tests</span>
|
||||||
|
<strong>`bun --cwd=apps/desktop test` passed.</strong>
|
||||||
|
</div>
|
||||||
|
<div class="validation-card good">
|
||||||
|
<span>Web Tests</span>
|
||||||
|
<strong>`bun test apps/web/app/desktop-ai.test.ts` passed.</strong>
|
||||||
|
</div>
|
||||||
|
<div class="validation-card good">
|
||||||
|
<span>Manual Verification</span>
|
||||||
|
<strong>Restarted `bun run dev:desktop` and confirmed settings showed the connected account, ready transport, and populated model controls.</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Issues, Limitations, and Mitigations</h2>
|
||||||
|
<ul>
|
||||||
|
<li>The preload now duplicates the IPC channel strings instead of importing them. That is intentional to keep the emitted file import-free.</li>
|
||||||
|
<li>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.</li>
|
||||||
|
<li>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.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Follow-up Work</h2>
|
||||||
|
<div class="chip-row">
|
||||||
|
<span class="chip">No immediate follow-up required</span>
|
||||||
|
<span class="chip">Possible hardening: build-time preload output guard</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue