{
- if (props.dismissOutside === false) e.preventDefault()
- }}
- onPointerDownOutside={(e) => {
- if (props.dismissOutside === false) e.preventDefault()
- }}
onOpenAutoFocus={(e) => {
const target = e.currentTarget as HTMLElement | null
const autofocusEl = target?.querySelector("[autofocus]") as HTMLElement | null
diff --git a/packages/ui/src/context/dialog.tsx b/packages/ui/src/context/dialog.tsx
index b4d866768b..a39f5a0f3f 100644
--- a/packages/ui/src/context/dialog.tsx
+++ b/packages/ui/src/context/dialog.tsx
@@ -23,10 +23,14 @@ type Active = {
owner: Owner
onClose?: () => void
setClosing: (closing: boolean) => void
+ dismissOutside: () => boolean
+ setDismissOutside: (value: boolean) => void
}
const Context = createContext
>()
+export const DialogContext = Context
+
function init() {
const [active, setActive] = createSignal()
const timer = { current: undefined as ReturnType | undefined }
@@ -89,12 +93,17 @@ function init() {
const id = Math.random().toString(36).slice(2)
let dispose: (() => void) | undefined
let setClosing: ((closing: boolean) => void) | undefined
+ let setDismissOutsideSignal: ((value: boolean) => void) | undefined
+ let dismissOutsideAccessor: (() => boolean) | undefined
const node = runWithOwner(owner, () =>
createRoot((d: () => void) => {
dispose = d
const [closing, setClosingSignal] = createSignal(false)
setClosing = setClosingSignal
+ const [dismissOutside, setDismissOutside] = createSignal(true)
+ dismissOutsideAccessor = dismissOutside
+ setDismissOutsideSignal = setDismissOutside
return (
-
+ {
+ if (dismissOutside()) close()
+ }}
+ />
{element()}
@@ -113,9 +127,18 @@ function init() {
}),
)
- if (!dispose || !setClosing) return
+ if (!dispose || !setClosing || !dismissOutsideAccessor || !setDismissOutsideSignal) return
- setActive({ id, node, dispose, owner, onClose, setClosing })
+ setActive({
+ id,
+ node,
+ dispose,
+ owner,
+ onClose,
+ setClosing,
+ dismissOutside: dismissOutsideAccessor,
+ setDismissOutside: setDismissOutsideSignal,
+ })
}
return {
@@ -159,5 +182,8 @@ export function useDialog() {
close() {
ctx.close()
},
+ setDismissOutside(value: boolean) {
+ ctx.active?.setDismissOutside(value)
+ },
}
}
diff --git a/plan.md b/plan.md
deleted file mode 100644
index 4862eed89a..0000000000
--- a/plan.md
+++ /dev/null
@@ -1,138 +0,0 @@
-# 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.
-
-## Direction
-
-- 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.
-
-## 01 Electron Config Split
-
-- [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.
-
-## 02 Main-Process Multi-Sidecar Startup
-
-- [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.
-
-## 03 WSL Server Controller
-
-- [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.
-
-## 04 IPC / Preload Surface
-
-- [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).
-
-## 05 Renderer Platform Wiring
-
-- [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.
-
-## 06 Renderer Server List
-
-- [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.
-
-## 07 Manage Servers UI
-
-- [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.
-
-## 08 Add WSL Wizard
-
-- [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.
-
-## 09 Per-WSL Onboarding State
-
-- [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.
-
-## 10 Connection Error Path
-
-- [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.
-
-## 11 Legacy Removal
-
-- [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).
-
-## 12 Verification
-
-- [ ] 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.