fix electron desktop ai runtime fallback detection

This commit is contained in:
dirtydishes 2026-05-20 20:11:37 -04:00
parent a54e847c8e
commit d15f96d7be
4 changed files with 390 additions and 47 deletions

View file

@ -0,0 +1,194 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Turn Doc - Fix False Browser Fallback In Electron</title>
<style>
:root {
color-scheme: light;
--bg: #f5f7fb;
--panel: #ffffff;
--text: #142032;
--muted: #4d607a;
--accent: #2359d1;
--border: #d9e2f0;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
}
main {
max-width: 980px;
margin: 0 auto;
padding: 2rem 1.25rem 3rem;
}
h1, h2 { line-height: 1.25; }
h1 {
margin: 0 0 0.4rem;
font-size: 1.9rem;
}
.lede {
margin: 0 0 1.5rem;
color: var(--muted);
}
section {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1rem 1.1rem;
margin: 0 0 0.9rem;
}
h2 { margin: 0 0 0.55rem; font-size: 1.1rem; }
p, li { font-size: 0.98rem; }
ul { margin: 0.4rem 0 0 1.25rem; padding: 0; }
code, pre {
font-family: "IBM Plex Mono", "SFMono-Regular", monospace;
}
pre {
background: #0f1725;
color: #d5e3ff;
border-radius: 10px;
padding: 0.85rem;
overflow: auto;
margin: 0.55rem 0 0;
}
.meta {
display: inline-block;
color: var(--muted);
font-size: 0.9rem;
margin-bottom: 0.75rem;
}
a { color: var(--accent); }
</style>
</head>
<body>
<main>
<h1>Fix False Browser Fallback In Electron</h1>
<p class="meta">Date: 2026-05-20 · Issue: islandflow-y7b</p>
<p class="lede">
Updated the desktop AI runtime handling in the web renderer so Electron runtime detection is treated as an immediate signal,
independent from async bridge state loading, which prevents false browser-only fallback UI in desktop windows.
</p>
<section>
<h2>Summary</h2>
<p>
The renderer now initializes desktop shell and bridge availability from a synchronous runtime snapshot, then handles
AI state loading separately. If state loading hangs or fails, the UI remains in desktop mode and shows bridge/login guidance
instead of browser-only "open desktop app" fallback messaging.
</p>
</section>
<section>
<h2>Changes Made</h2>
<ul>
<li>Refactored <code>DesktopAiProvider</code> to initialize from <code>resolveCurrentDesktopAiRuntime()</code> instead of browser-first defaults.</li>
<li>Separated runtime synchronization from <code>bridge.ai.getState()</code> and subscription state flow.</li>
<li>Added guarded bridge attachment logic to avoid duplicate subscriptions across runtime resync events.</li>
<li>Added bounded runtime retry window plus lifecycle resync hooks on <code>focus</code> and <code>pageshow</code>.</li>
<li>Extended runtime regression coverage in <code>apps/web/app/desktop-ai.test.ts</code> for pending/rejecting bridge state and late marker availability.</li>
</ul>
</section>
<section>
<h2>Context</h2>
<p>
In some Electron sessions, the settings UI could render browser-only fallback copy before or during bridge startup.
The root issue was coupling runtime identity and async bridge state fetch outcomes. This patch keeps runtime identity
authoritative even when bridge state is delayed or unavailable.
</p>
</section>
<section>
<h2>Important Implementation Details</h2>
<ul>
<li><code>BROWSER_RUNTIME</code> provides explicit non-desktop defaults only when no client window exists.</li>
<li><code>isSameDesktopAiRuntime()</code> avoids unnecessary runtime state churn on repeated sync checks.</li>
<li><code>attachBridge()</code> tracks active bridge identity and ignores stale async callbacks from superseded bridges.</li>
<li>When runtime is desktop but bridge is missing, unavailable state remains desktop-scoped and never browser-scoped.</li>
</ul>
</section>
<section>
<h2>Relevant Diff Snippets</h2>
<p>Rendered in standard unified diff format compatible with <a href="https://diffs.com/docs">diffs.com</a>.</p>
<pre><code class="language-diff">diff --git a/apps/web/app/desktop-ai.tsx b/apps/web/app/desktop-ai.tsx
@@
+export const resolveCurrentDesktopAiRuntime = (): DesktopAiRuntime =&gt;
+ typeof window === "undefined" ? BROWSER_RUNTIME : resolveDesktopAiRuntime(window);
@@
- const [state, setState] = useState&lt;IslandflowAiState&gt;(() =&gt; createUnavailableState());
- const [bridge, setBridge] = useState&lt;DesktopAiBridge | null&gt;(null);
- const [shellAvailable, setShellAvailable] = useState(false);
+ const initialRuntime = resolveCurrentDesktopAiRuntime();
+ const [runtime, setRuntime] = useState&lt;DesktopAiRuntime&gt;(initialRuntime);
+ const [state, setState] = useState&lt;IslandflowAiState&gt;(() =&gt;
+ createUnavailableState(initialRuntime)
+ );
@@
+ window.addEventListener("focus", onWindowLifecycle);
+ window.addEventListener("pageshow", onWindowLifecycle);
</code></pre>
<pre><code class="language-diff">diff --git a/apps/web/app/desktop-ai.test.ts b/apps/web/app/desktop-ai.test.ts
@@
+ it("keeps desktop runtime when the bridge exists before ai state resolves", () =&gt; {
+ ...
+ expect(runtime.shellAvailable).toBe(true);
+ expect(runtime.bridgeAvailable).toBe(true);
+ });
+
+ it("keeps bridge-specific recovery copy when bridge state loading fails", async () =&gt; {
+ ...
+ expect(requireDesktopActionCopy(...)).not.toContain("Open Islandflow Desktop");
+ });
+
+ it("flips from browser runtime to desktop runtime when the marker appears later", () =&gt; {
+ ...
+ expect(nextRuntime.shellAvailable).toBe(true);
+ });
</code></pre>
</section>
<section>
<h2>Expected Impact for End-Users</h2>
<ul>
<li>Electron users should no longer see false browser-only "Desktop app required" fallback while already inside desktop shell.</li>
<li>When bridge state is delayed or fails, users see bridge/login recovery messaging that matches desktop context.</li>
<li>Pure browser sessions still correctly show desktop-required fallback copy.</li>
</ul>
</section>
<section>
<h2>Validation</h2>
<ul>
<li><code>bun test apps/web/app/desktop-ai.test.ts</code> (pass)</li>
<li><code>bun test apps/desktop</code> (pass)</li>
<li><code>bun --cwd=apps/web run build</code> (fails on pre-existing typecheck issue in <code>packages/types/src/desktop-ai.ts</code> using <code>.ts</code> import-path extensions)</li>
</ul>
</section>
<section>
<h2>Issues, Limitations, and Mitigations</h2>
<ul>
<li>Manual Electron UI validation at <code>127.0.0.1:3000/settings</code> was not executed in this CLI pass.</li>
<li>Web production build remains blocked by an unrelated repository typecheck issue outside the touched files.</li>
<li>Mitigation: runtime behavior is covered with focused regression tests and desktop-side test suite remains green.</li>
</ul>
</section>
<section>
<h2>Follow-up Work</h2>
<ul>
<li>Perform manual Electron verification of settings copy and fallback pane behavior once local desktop app is running.</li>
<li>Resolve existing <code>packages/types</code> TypeScript extension import issue to restore green web production builds.</li>
<li>No additional follow-up issue created in this pass; current bug tracked as <code>islandflow-y7b</code>.</li>
</ul>
</section>
</main>
</body>
</html>