diff --git a/plan.md b/plan.md index 45d3eabc39..4862eed89a 100644 --- a/plan.md +++ b/plan.md @@ -1,320 +1,138 @@ -# WSL Local Server Implementation Backlog +# WSL Server Implementation Backlog This backlog assumes Electron only. It is ordered chronologically. Each task is intended to be small enough for a new engineer to pick up directly. -## 01 Foundation +## Direction -- [ ] Add a new persisted `localServer` settings key in `packages/desktop-electron/src/main/constants.ts`. -- [ ] Define a `LocalServerMode` type with `"windows" | "wsl"` in the Electron preload types. -- [ ] Define a persisted `LocalServerConfig` shape in the Electron preload types. -- [ ] Include `mode`, `distro`, onboarding metadata, root acknowledgements, and mismatch acknowledgements in `LocalServerConfig`. -- [ ] Remove the old `WslConfig` type from `packages/desktop-electron/src/preload/types.ts`. -- [ ] Remove the old `WSL_ENABLED_KEY` constant and stop adding new code that depends on it. -- [ ] Add a single source of truth helper in Electron main for reading `LocalServerConfig` from `electron-store`. -- [ ] Add a single source of truth helper in Electron main for writing `LocalServerConfig` to `electron-store`. -- [ ] Ignore any legacy `wslEnabled` value during reads of the new Local Server config. -- [ ] Add explicit comments in the new config helpers that this feature is Electron-only and replaces the legacy WSL boolean. +- Local Server is **always** Windows-native local on Windows. +- Local Server has no runtime swap and no WSL mode. +- WSL servers are a **separate, additive** concept. +- A Windows user can add zero or more WSL servers; each is bound to a specific distro. +- Each WSL server runs as its own sidecar alongside the Windows Local Server. +- Adding/removing a WSL server is hot; no app restart required. +- Manage Servers UI exposes an `Add WSL` button (Windows only) that opens the wizard. -## 02 Main-Process Local Server Controller +## 01 Electron Config Split -- [ ] Create a dedicated Electron main module for Local Server orchestration, for example `packages/desktop-electron/src/main/local-server.ts`. -- [ ] Move local runtime orchestration out of ad hoc startup code and into the Local Server controller. -- [ ] Define an in-memory `LocalServerState` shape for runtime status, current job, current transcript, and startup failure. -- [ ] Keep `LocalServerState` in memory only; do not persist runtime status or raw logs. -- [ ] Add a typed event emitter inside the Local Server controller. -- [ ] Support one active Local Server job at a time in the controller. -- [ ] Add a helper to reject or cancel a previous Local Server job before starting a new one. -- [ ] Allocate the loopback port once per app launch and keep it stable for all Local Server restarts in that launch. -- [ ] Allocate the local auth password once per app launch and keep it stable for all Local Server restarts in that launch. -- [ ] Expose a controller method to return a full current Local Server snapshot. -- [ ] Expose a controller method to subscribe to Local Server events. -- [ ] Expose a controller method to update persisted Local Server config. -- [ ] Expose a controller method to run a specific wizard step. -- [ ] Expose a controller method to apply Local Server runtime changes in the background. -- [ ] Expose a controller method to cancel the current Local Server job. -- [ ] Expose a controller method to open a terminal for the selected distro. +- [x] Remove runtime `mode` / `distro` from the persisted Local Server config. +- [x] Introduce a new persisted `wslServers` key holding an array of `WslServerConfig`. +- [x] Define `WslServerConfig` as `{ id, distro, onboarding, acknowledgements }`. +- [x] Keep onboarding metadata per WSL server, not globally. +- [x] Keep acknowledgement state per WSL server. +- [x] Migrate any legacy `localServer.mode === "wsl"` entry into a single `wslServers` entry on read. +- [x] Drop all references to `LocalServerMode` from the preload types. -## 03 Windows Local Runtime Path +## 02 Main-Process Multi-Sidecar Startup -- [ ] Move the existing Windows local sidecar startup path from `packages/desktop-electron/src/main/server.ts` into the Local Server controller. -- [ ] Keep the existing Windows local runtime behavior unchanged when `mode === "windows"`. -- [ ] Keep the existing eager local startup behavior unchanged for Windows local mode. -- [ ] Keep the existing loopback no-proxy behavior unchanged for Windows local mode. -- [ ] Update the controller snapshot so Windows local mode reports a distinct runtime key instead of the old implicit bare `sidecar` assumption. +- [x] Always start the Windows local sidecar on app launch. +- [x] After Windows local is spawned, iterate each `WslServerConfig` and spawn a WSL sidecar per entry. +- [x] Give each WSL sidecar its own port and password. +- [x] Allocate the Windows local port/password once per launch (unchanged). +- [x] Track all sidecars in a single map keyed by server id. +- [x] Kill all sidecars on `before-quit` / `will-quit` / signal. +- [x] Include Windows local data in the startup payload unchanged. +- [x] Include the initial set of WSL servers (with url/password/status) in the startup payload. +- [x] Emit per-WSL-server lifecycle events (`starting`, `ready`, `failed`, `stopping`, `removed`). +- [x] On startup, do not block the main window on WSL sidecar health. +- [x] If a WSL sidecar fails health, keep it in the list and mark it failed instead of hanging startup. -## 04 WSL Process Helpers +## 03 WSL Server Controller -- [ ] Add a helper to spawn `wsl.exe` with a selected distro and stream stdout/stderr lines. -- [ ] Standardize all WSL command execution on `wsl.exe -d -- bash -lc ...`. -- [ ] Add a helper to kill the currently spawned WSL child process when a job is canceled. -- [ ] Do not call `wsl --terminate ` as part of normal cancel behavior. -- [ ] Add a helper to run a short command and collect stdout/stderr for probe steps. -- [ ] Add a helper to resolve the selected distro home directory via `~`. -- [ ] Add a helper to run `command -v opencode` inside the selected distro. -- [ ] Add a helper to resolve `opencode --version` inside the selected distro. -- [ ] Add a helper to detect the selected distro default username via shell commands. -- [ ] Add a helper to detect whether the selected distro default user is `root`. -- [ ] Add a helper to detect whether `bash` exists in the selected distro. -- [ ] Add a helper to detect whether `curl` exists in the selected distro. -- [ ] Add a helper to run `opencode upgrade ` inside the selected distro. -- [ ] Add a helper to launch the Local Server with a resolved absolute executable path instead of bare `opencode`. +- [x] Create a main-process controller that owns `wslServers` persistence and runtime state. +- [x] Expose typed events (`state`, per-item status changes) from the controller. +- [x] Support one in-flight job per WSL server (not a global in-flight job). +- [x] Implement `addServer(distro)` that persists, then spawns and health-checks a new sidecar. +- [x] Implement `removeServer(id)` that stops the sidecar and removes it from config. +- [x] Implement per-server `runStep`, `cancelJob`, `installWsl`, `installDistro`, `installOpencode`, `openTerminal`. +- [x] Reuse the existing WSL process helpers unchanged. +- [x] Keep transcripts per server, only for the current app launch. -## 05 WSL Runtime and Distro Probes +## 04 IPC / Preload Surface -- [ ] Add a helper to probe whether `wsl.exe` is available and usable. -- [ ] Add a helper to list installed distros with `wsl --list --verbose`. -- [ ] Add a helper to list online distros with `wsl --list --online`. -- [ ] Do not add system-distro filtering logic; keep all returned distros visible. -- [ ] Add probe parsing for `WSL 1` vs `WSL 2` from installed distro data. -- [ ] Treat `WSL 2` as required for first-class onboarding. -- [ ] Add explicit probe output for `missing bash`. -- [ ] Add explicit probe output for `missing curl`. -- [ ] Add explicit probe output for `cannot execute commands in distro`. -- [ ] Add explicit probe output for `default user is root`. -- [ ] Add explicit probe output for `distro not found`. +- [x] Rename `localServer.*` IPC channels to `wslServers.*`. +- [x] Add `wslServers.getState()` returning the full list plus per-server runtime info. +- [x] Add `wslServers.subscribe()` with unsubscribe support. +- [x] Add `wslServers.add(distro)` (persists config + starts sidecar). +- [x] Add `wslServers.remove(id)`. +- [x] Add `wslServers.runStep(id, step)`. +- [x] Add `wslServers.cancelJob(id)`. +- [x] Add `wslServers.installWsl(id)`. +- [x] Add `wslServers.installDistro(id, distro)`. +- [x] Add `wslServers.installOpencode(id)`. +- [x] Add `wslServers.openTerminal(id)`. +- [x] Remove obsolete `localServer.setConfig` / `localServer.*` channels. +- [x] Include url/username/password for each WSL server in the state payload (after sidecar start). -## 06 WSL Install Helpers +## 05 Renderer Platform Wiring -- [ ] Add a helper to run elevated `wsl --install --no-distribution` from Electron main. -- [ ] Implement that elevation path with a shell-based helper invocation rather than a bundled helper binary. -- [ ] Add a helper to install a distro with `wsl --install -d --web-download --no-launch`. -- [ ] Do not auto-install a default distro as part of the WSL runtime install step. -- [ ] Add a helper to detect whether a WSL install requires reboot. -- [ ] Add a helper to expose a `Restart now` action. -- [ ] Add a helper to mark onboarding as pending reboot when the user chooses `Later`. +- [x] Expose `platform.wslServers` as a reactive accessor (list + subscribe). +- [x] Remove `platform.localServer` runtime swap APIs. +- [x] Keep `platform.wslServers` API available on Windows only. +- [x] Keep distro-aware path conversion keyed by the active WSL server. +- [x] When the active server is a WSL sidecar, default pickers to that distro's home. +- [x] When the active server is the Windows Local Server, keep native Windows picker defaults. -## 07 OpenCode Runtime Detection and Repair +## 06 Renderer Server List -- [ ] Resolve `command -v opencode` on each startup when `mode === "wsl"`. -- [ ] Re-resolve `command -v opencode` on each explicit WSL apply/retry action. -- [ ] Compare the detected WSL `opencode` version to the desktop app version. -- [ ] Record mismatch acknowledgement once per resolved path plus version pair. -- [ ] Keep version mismatch non-blocking. -- [ ] Treat `Use anyway` as sufficient to complete the OpenCode step with warning. -- [ ] Implement `Install matching version` by running `opencode upgrade ` first. -- [ ] If `opencode upgrade` hangs, prompts, or fails, mark the repair attempt failed and stop automation. -- [ ] Surface the failed upgrade transcript in the Local Server UI. -- [ ] Do not add an automatic fallback installer path after `opencode upgrade` fails. -- [ ] Surface manual recovery commands instead. +- [x] Always include the Windows Local Server in the server list. +- [x] Include each configured WSL server in the server list with `ServerConnection.Sidecar` variant `wsl`. +- [x] Keep `ServerConnection.key` returning `local:windows` for the Windows Local Server. +- [x] Keep `ServerConnection.key` returning `wsl:` for WSL servers (one per distro). +- [x] Keep distinct `projectsKey` buckets for Windows local vs each WSL server. +- [x] Do not collapse a WSL server into the `local` projects bucket. -## 08 Startup Handshake and App Boot +## 07 Manage Servers UI -- [ ] Replace the current success-only startup payload with a ready-or-failed startup union. -- [ ] Include local runtime metadata in the startup payload. -- [ ] Include the local runtime key in the startup payload. -- [ ] Include runtime variant details in the startup payload. -- [ ] Include selected distro in the startup payload when `mode === "wsl"`. -- [ ] Include loopback URL and credentials in the startup payload even when the local runtime later fails health. -- [ ] Include startup failure step and message in the startup payload when the local runtime fails. -- [ ] Update `packages/desktop-electron/src/main/index.ts` to initialize Local Server through the controller. -- [ ] Keep the loading overlay generic and do not add WSL-specific overlay phases in v1. -- [ ] Add a startup health-verdict timeout for WSL local startup so the app can open after failure. -- [ ] Scope the startup timeout to the local health verdict only, not to sqlite migration. -- [ ] Open the main window after startup reaches a ready-or-failed local verdict. +- [x] Remove `Swap to WSL` and `Swap to Windows` buttons from the Local Server row. +- [x] Show the Local Server row exactly like any other server (health, name, active check). +- [x] Add an `Add WSL` button next to `Add server`, visible only on Windows when the platform supports WSL. +- [x] `Add WSL` opens the same wizard stepper, scoped to a new WSL server draft. +- [x] Each WSL server row behaves like a sidecar entry (selectable, default-able, removable). +- [x] Add a `Remove` action to the WSL server row menu. +- [x] Add a `Retry setup` action when a WSL server is unhealthy. -## 09 IPC and Preload API +## 08 Add WSL Wizard -- [ ] Add a namespaced `localServer` API to the Electron preload surface. -- [ ] Implement `localServer.getState()`. -- [ ] Implement `localServer.subscribe()` with unsubscribe support. -- [ ] Implement `localServer.setConfig()`. -- [ ] Implement `localServer.runStep()`. -- [ ] Implement `localServer.apply()` for background runtime switching. -- [ ] Implement `localServer.cancelJob()`. -- [ ] Implement `localServer.openTerminal()`. -- [ ] Implement `localServer.restartNow()` for reboot-required flows. -- [ ] Implement `localServer.copyTranscript()` or equivalent transcript fetch action. -- [ ] Emit typed step/state events from main to renderer. -- [ ] Emit raw stdout/stderr line events from main to renderer. -- [ ] Remove `getWslConfig` from the preload API. -- [ ] Remove `setWslConfig` from the preload API. -- [ ] Remove `get-wsl-config` and `set-wsl-config` IPC handlers. +- [x] Replace the "Switch" step with a `Done` step. +- [x] Step order becomes `WSL -> Distro -> OpenCode -> Done`. +- [x] On `Done`, persist the new WSL server, start the sidecar, and close the dialog. +- [x] If the user cancels, do not persist anything. +- [x] Allow resuming an incomplete WSL server wizard from Manage Servers. +- [x] Remove restart-to-apply copy, restart toasts, and "Use Windows" CTA. +- [x] Keep failure-only diagnostics panel behavior. -## 10 Renderer Startup and Platform Wiring +## 09 Per-WSL Onboarding State -- [ ] Update the desktop renderer startup resource to consume the new startup union shape. -- [ ] Build the Local Server `ServerConnection.Sidecar` from structured startup metadata instead of hardcoding `variant: "base"`. -- [ ] Keep the visible Local Server display name as `Local Server` in both Windows and WSL modes. -- [ ] Add a WSL badge or subtitle in the row UI instead of renaming the server. -- [ ] Change the implicit local fallback key to follow the configured Local Server runtime. -- [ ] Remove all uses of `window.__OPENCODE__.wsl` from the renderer. -- [ ] Derive WSL picker/path behavior from structured Local Server state instead of a global boolean. -- [ ] Update `createPlatform()` so WSL path conversion only activates when Local Server mode is WSL. -- [ ] Default native pickers to the selected distro home path when Local Server mode is WSL. -- [ ] Keep native pickers on normal Windows behavior when Local Server mode is Windows. +- [x] Keep `WslServerConfig.onboarding` per server. +- [x] Keep `WslServerConfig.acknowledgements` per server. +- [x] Resume the wizard for any server where `onboarding.complete === false`. +- [x] Mark onboarding complete only after the sidecar becomes healthy. -## 11 Distro-Aware Path Conversion +## 10 Connection Error Path -- [ ] Update `packages/desktop-electron/src/main/apps.ts` so `wslPath()` accepts a distro parameter. -- [ ] Stop using the ambient default WSL distro for path conversion. -- [ ] Use the selected Local Server distro for all `~` resolution. -- [ ] Use the selected Local Server distro for all Windows-to-Linux path conversion. -- [ ] Use the selected Local Server distro for all Linux-to-Windows path conversion. -- [ ] Update open-path behavior to use distro-aware conversion when Local Server mode is WSL. +- [x] If the active server is a WSL sidecar and health fails, offer `Open setup` that deep-links into the wizard for that server. +- [x] Keep behavior unchanged for the Windows Local Server. +- [x] Keep behavior unchanged for remote HTTP servers. -## 12 App Server Model Changes +## 11 Legacy Removal -- [ ] Introduce a distinct explicit key for Windows Local Server. -- [ ] Keep WSL Local Server keyed by distro identity. -- [ ] Update `packages/app/src/context/server.tsx` so Windows local and WSL local do not collapse into the same project-history bucket. -- [ ] Update `projectsKey()` to keep Windows local and WSL local histories separate. -- [ ] Update `isLocal()` so WSL Local Server still counts as local. -- [ ] Ensure Local Server key changes force the expected remount behavior through `ServerKey` in `packages/app/src/app.tsx`. -- [ ] If Local Server is currently active, make successful runtime switches follow the new local key automatically. +- [x] Remove `Swap to WSL` / `Swap to Windows` popover components. +- [x] Remove `restart-to-apply` banner copy and helpers from `dialog-local-server.tsx`. +- [x] Remove legacy `wslEnabled` preload shims. +- [x] Remove the old `DialogSelectServer` `initialTargetMode` prop. +- [x] Drop `localServerKey(config)` distinguishing `wsl` vs `windows` in the controller (local is always Windows). -## 13 Manage Servers Dialog Shell +## 12 Verification -- [ ] Add a pinned `Local Server` row to `packages/app/src/components/dialog-select-server.tsx` list mode. -- [ ] Extract a dedicated Local Server page component instead of growing `dialog-select-server.tsx` further. -- [ ] Add a dialog mode or route that opens the dedicated Local Server page from the server list. -- [ ] Keep existing HTTP add/edit/delete/default flows untouched while adding the Local Server entry. -- [ ] Add an initial-view prop so the Manage Servers dialog can open directly to Local Server. - -## 14 Local Server Wizard - -- [ ] Implement a dedicated Local Server wizard component. -- [ ] Implement step order exactly as `WSL -> Distro -> OpenCode -> Switch`. -- [ ] Allow the user to go back and edit earlier steps. -- [ ] Persist wizard progress inside `LocalServerConfig`. -- [ ] Auto-resume the wizard after app relaunch when onboarding is incomplete. -- [ ] Auto-resume the wizard after reboot when onboarding was waiting for restart. -- [ ] Mark the wizard complete only after Local Server hot restart succeeds and health passes. - -## 15 WSL Step UI - -- [ ] Show current WSL runtime probe result in the WSL step. -- [ ] Add an `Install WSL` action that starts elevated `wsl --install --no-distribution`. -- [ ] Show reboot-required state in the WSL step when the install path requires restart. -- [ ] Add `Restart now` and `Later` actions. -- [ ] Keep the WSL step editable after the user returns from reboot. - -## 16 Distro Step UI - -- [ ] Show installed distros with explicit probe status in the Distro step. -- [ ] Show quick install actions for `Debian` and `Ubuntu 24`. -- [ ] Show an `Other distro...` action that reads from the online distro list. -- [ ] After distro install, auto-select the newly installed distro and continue probing automatically. -- [ ] Surface `WSL 1` as unsupported with manual conversion instructions. -- [ ] Surface `missing bash` as an explicit unsupported reason. -- [ ] Surface `missing curl` as an explicit unsupported reason. -- [ ] Surface `cannot execute commands` as an explicit unsupported reason. -- [ ] Surface `default user is root` as a warning in the Distro step. -- [ ] Require explicit root acknowledgement once per distro. -- [ ] Keep all distros visible even when unsupported. -- [ ] If the selected distro disappears, show an explicit missing-distro error instead of auto-switching away. - -## 17 OpenCode Step UI - -- [ ] Show the resolved absolute `opencode` path for the selected distro. -- [ ] Show the detected `opencode` version for the selected distro. -- [ ] Show version mismatch as a non-blocking warning. -- [ ] Add `Use anyway` in the mismatch state. -- [ ] Add `Install matching version` in the mismatch state. -- [ ] Keep mismatch acknowledgement scoped to path plus version. -- [ ] Re-warn only when the resolved path or resolved version changes later. -- [ ] If `command -v opencode` resolves nothing, show explicit manual recovery guidance. -- [ ] If `opencode upgrade` fails, keep the step incomplete and show the transcript. - -## 18 Switch Step UI - -- [ ] Add a `Switch Local Server` action that applies the new Local Server runtime in the background. -- [ ] Keep remote sessions usable while the Switch step runs. -- [ ] Reuse the current app-launch port and password during background Local Server restarts. -- [ ] Keep the Local Server active selection on Local Server when the switch succeeds and Local Server was already active. -- [ ] Show success only after `/global/health` succeeds for the new runtime. -- [ ] If background apply fails, show a `Restart OpenCode` fallback prompt. - -## 19 Local Server Status Dashboard - -- [ ] Replace the wizard with a steady-state dashboard after onboarding completes. -- [ ] Show current mode on the dashboard. -- [ ] Show selected distro on the dashboard when in WSL mode. -- [ ] Show current Local Server health on the dashboard. -- [ ] Show current failure state on the dashboard when the last startup or apply failed. -- [ ] Show version mismatch warning on the dashboard when the user chose `Use anyway`. -- [ ] Show root warning on the dashboard when the selected distro is root-backed and acknowledged. -- [ ] Do not show stale last-known-good probe values outside the current failure context. -- [ ] Add dashboard actions for `Retry`, `Open terminal`, and transcript copy. - -## 20 Live Diagnostics - -- [ ] Add a live diagnostics panel to the Local Server UI. -- [ ] Stream merged stdout/stderr lines into the panel while jobs are running. -- [ ] Keep the panel usable for startup failures from the current app launch. -- [ ] Retain the full Local Server transcript only for the current app launch. -- [ ] Clear the retained transcript on full app relaunch. -- [ ] Show exact commands in the diagnostics details area. -- [ ] Make `Copy commands` copy the same transcript content as the transcript-copy action. -- [ ] Keep diagnostics collapsible by default. - -## 21 Connection Error and Deep Linking - -- [ ] Add a direct `Open Local Server` CTA to the existing `ConnectionError` screen when the failing server is Local Server. -- [ ] Make that CTA open the Manage Servers dialog directly to the Local Server page. -- [ ] When Local Server startup failed earlier in the same launch, jump the Local Server UI directly to the failing step or dashboard state. -- [ ] Keep existing retry behavior for non-local remote servers unchanged. - -## 22 Runtime Apply and Background Behavior - -- [ ] Apply Local Server runtime changes in the background when the active server is remote. -- [ ] Do not navigate away from a remote session during a Local Server background apply. -- [ ] Keep Local Server config separate from active/default server selection logic. -- [ ] Do not auto-select Local Server just because its runtime config changed. -- [ ] Do not auto-change the user's default remote server selection when Local Server mode changes. - -## 23 Main Window Startup Failure Handling - -- [ ] If WSL Local Server fails during startup, keep the app launch going after the health-verdict timeout. -- [ ] Represent that failure in the startup payload instead of throwing away initialization. -- [ ] Keep the Local Server row present in the server list even when startup failed. -- [ ] Mark the Local Server row unhealthy when startup failed. -- [ ] Keep the startup loading overlay generic even in this failed case. - -## 24 API Cleanup and Legacy Removal - -- [ ] Remove the old hidden WSL settings UI branch from `packages/app/src/components/settings-general.tsx`. -- [ ] Remove legacy renderer calls that assume a boolean WSL mode. -- [ ] Remove legacy IPC registrations for the boolean WSL config. -- [ ] Remove legacy preload typing for the boolean WSL config. -- [ ] Remove legacy main-process store helpers that only read/write `wslEnabled`. - -## 25 Manual Recovery and Power Actions - -- [ ] Implement `Open terminal` as `open selected distro shell only` and do not auto-run recovery commands. -- [ ] Make `Open terminal` target the selected distro explicitly. -- [ ] Add a transcript copy action that is available even after failed jobs. -- [ ] Keep manual recovery command text aligned with the actual commands the controller runs. -- [ ] Include manual commands for WSL 1 conversion in the Distro step. -- [ ] Include manual commands for missing `curl` in the Distro or OpenCode step as appropriate. -- [ ] Include manual commands for PATH install version repair in the OpenCode step failure state. - -## 26 Verification and QA - -- [ ] Verify Windows Local Server behavior is unchanged when `mode === "windows"`. -- [ ] Verify the app still boots normally with no Local Server config present. -- [ ] Verify the app opens after a WSL startup failure instead of hanging forever. -- [ ] Verify `Install WSL` can reach a reboot-required state and resume after relaunch. -- [ ] Verify `Restart now` and `Later` both preserve onboarding state correctly. -- [ ] Verify Debian quick install auto-selects the new distro and continues onboarding. -- [ ] Verify Ubuntu 24 quick install auto-selects the new distro and continues onboarding. -- [ ] Verify `Other distro...` uses the live online catalog. -- [ ] Verify a WSL 1 distro surfaces manual conversion instructions. -- [ ] Verify a distro missing `bash` surfaces an explicit unsupported reason. -- [ ] Verify a distro missing `curl` surfaces an explicit unsupported reason. -- [ ] Verify a root-backed distro requires acknowledgement once per distro. -- [ ] Verify PATH-installed `opencode` is re-resolved on each startup. -- [ ] Verify mismatch acknowledgement only reappears when path or version changes. -- [ ] Verify `Use anyway` completes onboarding with a lingering dashboard warning. -- [ ] Verify `Install matching version` runs `opencode upgrade `. -- [ ] Verify an upgrade hang or prompt can be canceled and leaves a usable transcript. -- [ ] Verify Local Server hot restart keeps the same port and password within one app launch. -- [ ] Verify Local Server hot restart does not interrupt an active remote session. -- [ ] Verify active Local Server selection follows the new local key after a successful runtime switch. -- [ ] Verify Windows local and WSL local project histories remain separate. -- [ ] Verify the `ConnectionError` CTA opens the Local Server page directly. -- [ ] Verify selected-distro path conversion is used everywhere in WSL mode. -- [ ] Verify selected-distro home is used as the picker default in WSL mode. -- [ ] Verify deleting the selected distro produces an explicit error instead of silent fallback. -- [ ] Verify transcripts are only retained for the current app launch. +- [ ] Verify the Windows Local Server starts unchanged on app launch. +- [ ] Verify `Add WSL` opens the wizard with the default installed distro preselected. +- [ ] Verify adding a WSL server spawns a new sidecar without restarting the app. +- [ ] Verify removing a WSL server stops the sidecar and removes it from the list. +- [ ] Verify multiple WSL servers can coexist, one per distro. +- [ ] Verify Windows Local Server stays active while WSL sidecars come and go. +- [ ] Verify a failed WSL sidecar does not block app startup or window creation. +- [ ] Verify the `ConnectionError` deep-link reaches the right wizard scope. +- [ ] Verify legacy `localServer.mode === "wsl"` persisted config migrates into `wslServers` on first launch. +- [ ] Verify project histories stay separate per server key.