diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index e94da76..2d7b340 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,4 +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-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.","notes":"Reopened after live Electron still showed the browser-only fallback. Follow-up fix adds an explicit preload runtime marker and web runtime detection for that marker so Electron is recognized even when the bridge is not ready and the user agent lacks an Electron token.","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:51:43Z","closed_at":"2026-05-20T23:51:43Z","close_reason":"Follow-up fix added an explicit islandflowDesktopRuntime preload marker and taught the web runtime to recognize that marker plus IslandflowDesktop user-agent tokens, so Electron no longer falls into the browser-only fallback when the AI bridge is delayed or unavailable. Desktop build and focused desktop/web tests pass; full web build still blocked by islandflow-c8f.","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/src/preload.cjs b/apps/desktop/src/preload.cjs index fc7b0b8..4ed6535 100644 --- a/apps/desktop/src/preload.cjs +++ b/apps/desktop/src/preload.cjs @@ -33,4 +33,8 @@ const bridge = { } }; +contextBridge.exposeInMainWorld("islandflowDesktopRuntime", { + shell: "electron", + app: "islandflow" +}); contextBridge.exposeInMainWorld("islandflowDesktop", bridge); diff --git a/apps/web/app/desktop-ai.test.ts b/apps/web/app/desktop-ai.test.ts index 699762c..dd0b00b 100644 --- a/apps/web/app/desktop-ai.test.ts +++ b/apps/web/app/desktop-ai.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "bun:test"; import { createUnavailableState, + detectDesktopRuntimeMarker, detectDesktopShell, resolveDesktopAiRuntime, } from "./desktop-ai"; @@ -18,6 +19,22 @@ import { } from "./desktop-ai-panels"; describe("desktop ai runtime detection", () => { + it("recognizes the explicit desktop preload marker before the bridge is available", () => { + const runtime = resolveDesktopAiRuntime({ + islandflowDesktopRuntime: { + app: "islandflow", + shell: "electron", + }, + navigator: { + userAgent: "Mozilla/5.0 Safari/537.36", + }, + }); + + expect(runtime.shellAvailable).toBe(true); + expect(runtime.bridgeAvailable).toBe(false); + expect(runtime.bridge).toBeNull(); + }); + it("recognizes Electron user agents before the bridge is available", () => { const runtime = resolveDesktopAiRuntime({ navigator: { @@ -102,8 +119,20 @@ describe("desktop action copy", () => { }); describe("desktop shell detection", () => { + it("matches the explicit preload runtime marker", () => { + expect( + detectDesktopRuntimeMarker({ app: "islandflow", shell: "electron" }), + ).toBe(true); + expect( + detectDesktopRuntimeMarker({ app: "islandflow", shell: "browser" }), + ).toBe(false); + }); + it("matches Electron signatures", () => { expect(detectDesktopShell("Mozilla/5.0 Electron/39.0.0")).toBe(true); + expect(detectDesktopShell("Mozilla/5.0 IslandflowDesktop/0.1.0")).toBe( + true, + ); expect( detectDesktopShell("Mozilla/5.0 Chrome/136.0.0.0 Safari/537.36"), ).toBe(false); diff --git a/apps/web/app/desktop-ai.tsx b/apps/web/app/desktop-ai.tsx index 7234a9e..5165662 100644 --- a/apps/web/app/desktop-ai.tsx +++ b/apps/web/app/desktop-ai.tsx @@ -38,6 +38,10 @@ type DesktopAiRuntime = { declare global { interface Window { + islandflowDesktopRuntime?: { + app?: string | null; + shell?: string | null; + }; islandflowDesktop?: DesktopAiBridge; } } @@ -59,14 +63,28 @@ type DesktopAiContextValue = { const BRIDGE_POLL_INTERVAL_MS = 250; const BRIDGE_POLL_MAX_ATTEMPTS = 20; -const ELECTRON_USER_AGENT_PATTERN = /\bElectron\/\S+/i; +const ELECTRON_USER_AGENT_PATTERN = /\b(?:Electron|IslandflowDesktop)\/\S+/i; export const detectDesktopShell = (userAgent: string | null | undefined): boolean => Boolean(userAgent && ELECTRON_USER_AGENT_PATTERN.test(userAgent)); +export const detectDesktopRuntimeMarker = ( + runtime: + | { + app?: string | null; + shell?: string | null; + } + | null + | undefined +): boolean => runtime?.app === "islandflow" && runtime.shell === "electron"; + export const resolveDesktopAiRuntime = ( value: | { + islandflowDesktopRuntime?: { + app?: string | null; + shell?: string | null; + }; islandflowDesktop?: DesktopAiBridge; navigator?: { userAgent?: string | null }; } @@ -75,7 +93,10 @@ export const resolveDesktopAiRuntime = ( ): DesktopAiRuntime => { const bridge = value?.islandflowDesktop?.ai ? value.islandflowDesktop : null; const bridgeAvailable = Boolean(bridge?.ai); - const shellAvailable = bridgeAvailable || detectDesktopShell(value?.navigator?.userAgent); + const shellAvailable = + bridgeAvailable || + detectDesktopRuntimeMarker(value?.islandflowDesktopRuntime) || + detectDesktopShell(value?.navigator?.userAgent); return { shellAvailable, 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 index f091bf8..4d9fea1 100644 --- a/docs/turns/2026-05-20-fix-electron-codex-bridge-regression.html +++ b/docs/turns/2026-05-20-fix-electron-codex-bridge-regression.html @@ -140,12 +140,13 @@
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. + CommonJS file, exposes an explicit desktop runtime marker, and stamps the renderer with an + Islandflow desktop user-agent marker.
@@ -154,7 +155,7 @@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 + unambiguously runnable by Electron and gives the web runtime reliable desktop shell signals while the bridge is coming online.
@@ -163,9 +164,11 @@apps/desktop/src/preload.cjs, a CommonJS preload that exposes window.islandflowDesktop.ai.window.islandflowDesktopRuntime from preload as a minimal Electron shell marker.src/preload.cjs into dist/preload.cjs.BrowserWindow preload path at preload.cjs instead of preload.js.IslandflowDesktop/<version> Electron/<version> user-agent token before loading the app URL.
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
+ a desktop-shell signal it trusted. 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.
+ classic CommonJS. The follow-up patch also protects against Electron user-agent stripping by using a
+ dedicated preload marker.
window.islandflowDesktop remains the source of truth for native controls.window.islandflowDesktop remains the source of truth for native controls.app.getVersion() and process.versions.electron..js file.- 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. + 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.
bun --cwd=apps/desktop run build passed.bun --cwd=apps/desktop run test passed, 12 tests.bun test apps/web/app/desktop-ai.test.ts passed, 17 tests.bun test apps/web/app/desktop-ai.test.ts passed, 19 tests.bun --cwd=apps/web run build compiled, then failed on tracked issue islandflow-c8f: shared packages/types imports still use explicit .ts extensions.src/preload.ts still exists. A later cleanup can remove or consolidate it after confirming no packaging path still references it.apps/web/next-env.d.ts, was already present and was not touched by this fix.islandflow-c8f, unrelated to the desktop runtime marker change.islandflow-sc6 after the commit lands.islandflow-sc6 after the follow-up commit lands.window.islandflowDesktop exists after launch.