194 lines
8 KiB
HTML
194 lines
8 KiB
HTML
<!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 =>
|
|
+ typeof window === "undefined" ? BROWSER_RUNTIME : resolveDesktopAiRuntime(window);
|
|
@@
|
|
- const [state, setState] = useState<IslandflowAiState>(() => createUnavailableState());
|
|
- const [bridge, setBridge] = useState<DesktopAiBridge | null>(null);
|
|
- const [shellAvailable, setShellAvailable] = useState(false);
|
|
+ const initialRuntime = resolveCurrentDesktopAiRuntime();
|
|
+ const [runtime, setRuntime] = useState<DesktopAiRuntime>(initialRuntime);
|
|
+ const [state, setState] = useState<IslandflowAiState>(() =>
|
|
+ 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", () => {
|
|
+ ...
|
|
+ expect(runtime.shellAvailable).toBe(true);
|
|
+ expect(runtime.bridgeAvailable).toBe(true);
|
|
+ });
|
|
+
|
|
+ it("keeps bridge-specific recovery copy when bridge state loading fails", async () => {
|
|
+ ...
|
|
+ expect(requireDesktopActionCopy(...)).not.toContain("Open Islandflow Desktop");
|
|
+ });
|
|
+
|
|
+ it("flips from browser runtime to desktop runtime when the marker appears later", () => {
|
|
+ ...
|
|
+ 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>
|