feat: run-local-gateway toggle for pure-client desktop mode (#2909) - #2926
feat: run-local-gateway toggle for pure-client desktop mode (#2909)#2926patrigao wants to merge 1 commit into
Conversation
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
Design Review (Fable 5) — ✅ PASSAdvisory design-level review of Design-Verdict: PASS Real resource/misdirection harm, fixed at the actual decision point (intent input to the spawn gate), with anti-brick, recovery, and rollback semantics all thought through. [DESIGN-REVIEWED] 3e6ce46 |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsThe candidate's scenario requires
So whenever Condition (a) — a No findings. [OPUS-REVIEWED] 3e6ce46 Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
UX Review (Fable 5) — ✅ PASSAdvisory UX-level review of UX-Verdict: PASS Honest, layered copy at every surface — toggle, unsupported row, and failure dialog each disclose the exact consequence — so a first-time user is never misled. Suggestions
[UX-REVIEWED] 3e6ce46 |
caaa86b to
2098fbf
Compare
|
Dispositions for the UX Review (Fable 5) CONCERNS on Watch 1 — circular recovery in the unreachable-remote dialog: fixed in Watch 2 — off-with-no-remote is silently ignored: accepted-and-deferred to #2928. The override itself is deliberate and documented in Suggestion — lead the description with the off-decision: rebutted. The Developer panel's existing toggles describe the enabled behavior first (e.g. Developer Mode: "Show Developer page in sidebar…"), and this toggle defaults on, so sentence one states what the default does; sentence two states what turning it off is for. Reordering would make this row read differently from its siblings for no comprehension gain. |
2098fbf to
785d4de
Compare
|
Dispositions for the UX Review (Fable 5) CONCERNS on Watch 1 — off can be silently ignored (no-remote case): fixed with the reviewer's proposed one-sentence remedy. Watch 2 — command-palette dead end in browsers: fixed. Suggestion — paragraph-wall dialog message: fixed. The unreachable-remote message is now three short paragraphs (what happened / Retry remedy / terminal escape), and the shared dialog's |
785d4de to
889f5f7
Compare
889f5f7 to
8765eba
Compare
8765eba to
bcff975
Compare
|
Dispositions for the UX Review (Fable 5) CONCERNS on Suggestion — stale screenshots: fixed. Both Suggestion — trailing ellipsis on the decline status: fixed. Watch 1 — no in-app recovery ("Start a local gateway instead" button): accepted-and-deferred. This escalates a concern already dispositioned on Watch 2 — toggle renders "off" while spawning (disable-when-no-remote): accepted-and-deferred, same grounds as its two prior dispositions: disabling the toggle requires the renderer to know whether a remote host is configured for the primary port — the exact new IPC read that #2928 scopes. The one-sentence disclosure shipped on Both deferred items are consolidated in #2928 so the off-state UX ships as one coherent change rather than piecemeal advisory patches. |
bcff975 to
a11e29e
Compare
The desktop app always spawns a local gateway when nothing answers its port probe, even for users who point it at a remote gateway and want this machine to act as a pure client. That costs laptop memory/CPU for a backend nothing uses, and a silently spawned local backend can wear a remote tab's name when the remote is briefly unreachable at launch. Add one store-backed boolean, runLocalGateway (default on = today's behavior), surfaced as a Settings > Developer toggle via an IPC bridge shaped like zoomAPI. startGateway() routes every spawn arm through a pure decision module: only the explicit opt-out PAIRED with a configured remote host declines to spawn — off-with-no-remote still spawns (refusing would brick the launch), and a gateway THIS session already spawned keeps its recovery respawns regardless of the persisted value (the setting gates launches, not the running session). When the spawn is declined, boot fail-fasts to the existing gateway error dialog with a plain "can't reach <host>" message; Retry re-probes through the same gate and never spawns. Ownership booleans stay untouched on the declined path. Next-launch scoped by design; the helper text says so. Closes #2909
a11e29e to
3e6ce46
Compare
|
🤖 Kiro Crew Auto-Pipeline [operator: patrigao] — closing as superseded: #2917 (merged 2026-08-13T02:19Z, commit 5d64560) landed the same run-local-gateway Developer setting and closed #2909. The both-added conflicts on this branch are against that implementation, so a rebase would just duplicate shipped code. |
Problem
The desktop app has no way to say "do not run a gateway on this machine". A user who points the app at a remote gateway still gets a local Python backend spawned whenever the port probe finds nothing —
startGateway()decides on probe outcome, not intent. That costs laptop memory and CPU for a backend nothing uses (akiro-cliprocess per session plus sub-agents and shells), and when the remote is briefly unreachable at launch, a silently spawned local backend answers on the remote's port and the tab keeps wearing the remote's configured name.Why it matters
Remote-gateway users pay a permanent resource tax on every launch, and the silent local spawn actively misleads: the tab says "my remote crew" while everything runs against a fresh local backend. There is no config key or environment variable to express pure-client intent.
Fix (symptoms → root cause → change)
Root cause: the spawn decision has no intent input. The change adds one, mirroring the existing
zoomAPIpattern end to end:runLocalGateway(defaulttrue= today's behavior), beside the other defaults inwebsite/electron/main.js.startGateway()arms (probe-failed and conflict fall-through) route throughspawnGatewayIfEnabled(), whose decision table lives in the new pure modulewebsite/electron/local-gateway-policy.js:gatewayStartFailureto the existingshowGatewayErrorDialogwith a plain "can't reach<host>" message; Retry re-probes through the same gate and never spawns.weSpawnedGateway/reusedLocalGatewaystay untouched on this path, so recovery treats the remote like any external gateway (reconnect, never respawn).local-gateway:get/sethandlers beside the zoom handlers;localGatewayAPIinpreload.jsshaped exactly likezoomAPI. Absent in plain browsers and the PWA.useLocalGatewayhook modeled onuseZoom(supported = !!bridge), and aSettingsTogglein Settings → Developer that renders only when the bridge exists. Helper text states the change takes effect on next launch. i18n keys added across all 12 catalogs + regenerateden-XA; settings registry regenerated; the new module is registered in electron-builderbuild.files.Deliberately out of scope (the issue defers both): applying the flip to an already-running local gateway, and any new Settings group — Developer is the interim home. The ownership booleans are untouched; #2282 proposes that refactor separately.
Tests
website/electron/test/local-gateway-policy.test.js(node:test, 7 cases): default-on, on-with-remote, the one declining combination (host carried for the dialog), off-with-no-remote spawns, only literalfalsedisables (corrupted values degrade to spawn), owned-gateway recovery overrides off, and the override is ownership-scoped. The recovery-override test fails if the override is removed (mutation-checked).website/src/test/useLocalGateway.test.ts(5 cases): unsupported without the bridge with safe no-op setter, default-on read, persisted-off read, set round-trip, and main-process-applied-value-wins reconciliation.website/src/test/DeveloperPanel.test.tsx(3 new cases): toggle absent without the bridge, default-on render + next-launch helper text, flip round-tripsset(false).There is no harness for executing
main.jsitself; the repo's precedent for main-process logic is extraction into a pure module with node:test (gateway-recovery.js,gateway-wait.js), which this follows.Manual verification
Rendered the real
DeveloperPanel(real stylesheet, stubbed bridge) in headless Chromium and exercised both toggle states — screenshots below. Full desktop launch-path behavior (decline → dialog → retry) is covered by reading the existingwaitForGatewayfail-fast contract (getFailure()is checked before the timeout on the primary boot path) rather than a packaged-app run; the Electron binary cannot be installed in this environment.Screenshots
Closes #2909