Skip to content

Commit f64edee

Browse files
Quickbeardclaude
andcommitted
fix(app): confirm WSL server removal and clear its remembered projects
Same treatment as sandbox deletion, adapted to what removal actually does: the app never unregisters distributions, so removing a WSL server destroys nothing — the distro, its files and its sessions stay on the machine. The confirmation dialog says exactly that, and server.forget now runs for wsl: keys too, so re-adding the distribution starts with a clean project list instead of inheriting the removed server's projects and recently-closed folders. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 866b0e6 commit f64edee

6 files changed

Lines changed: 86 additions & 6 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ file is the human-readable summary of what changed and why.
2323
now states exactly that before anything happens — and clarifies that folders
2424
mounted from this computer are not deleted, the sandbox just loses access to
2525
them.
26+
- **Removing a WSL server asks for confirmation and forgets its projects** —
27+
the same treatment as sandboxes, adapted to what actually happens: removing
28+
a WSL server deletes nothing — the distribution, its files and its sessions
29+
stay on this computer, and it can be added back at any time — but the app
30+
now says so before removing it, and clears the server's remembered projects
31+
and recently closed folders so adding the distribution back starts with a
32+
clean list instead of inheriting its previous life.
2633
- **A recreated sandbox no longer inherits its predecessor's projects** —
2734
deleting a sandbox left its project list behind, keyed by the sandbox's
2835
name, so creating a new sandbox with the same name brought back every old

‎DIVERGENCES.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ from the squash message first, or use a plain merge.
268268
| `packages/opencode/src/command/template/initialize.txt` | "OpenCode" → "CoDev Code" (×2) | `/init` writes this guidance into the user's `AGENTS.md`. Edited in place rather than wrapped because the file is already fork-divergent (config-file rename row) and barely churns. | Keep our two strings; take upstream's surrounding text and rebrand any new "OpenCode" mention. |
269269
| `packages/core/package.json` | Added dependency `"@codev/brand": "workspace:*"` | The built-in skill registration rebrands its body through the fork-only `packages/codev-brand`. | Keep our dependency line; take upstream's other fields. |
270270
| `turbo.json` | Added task `"@codev/sandbox#test"` (no deps, no outputs) | CI's `bun turbo test` only runs test tasks registered here; the fork-only sandbox package's tests must gate PRs. | Keep our task entry; take upstream's other tasks. |
271-
| Sandbox delete cleanup + confirmation: `packages/app/src/context/server.tsx`, `packages/app/src/components/dialog-select-server.tsx`, `packages/app/src/i18n/{en,es}.ts` | `server.tsx` gains `forget(key)` beside `remove(key)`: it deletes the persisted per-server `projects`/`lastProject`/`recentlyClosed` map entries for one key. `dialog-select-server.tsx` `handleRemove` (already diverged, see the renderer-slice row) additionally: for `sandbox:` keys, awaits a confirmation through the fork-only `packages/app/src/sandbox/dialog-delete.tsx` (lazy-imported, pushed with `dialog.push` so it stacks on whatever dialog opened the menu) and returns without touching anything when declined; and calls `server.forget(key)` right after `server.remove(key)`. The controller gains a `useDialog()` binding for this. i18n gains the `sandbox.delete.*` family. | `remove()` only edits the stored HTTP server list; the project maps are keyed by server key and outlive the server, which is right for an HTTP server that still exists remotely but wrong for a sandbox whose disk was just destroyed — a recreated sandbox with the same name inherited the old projects (`server.projects["sandbox:<name>"]`) and, via the leftover `recentlyClosed`, silently refused to seed folders closed in the previous sandbox's life. And deleting a sandbox erases sessions, terminals and guest files permanently, so it must not be a one-click menu action; the confirmation lives in `handleRemove` because the sandbox settings row and the generic server list both funnel through it. | Keep the confirm-and-forget block at the top of `handleRemove` and the `forget` call between `server.remove(key)` and the awaited platform removals; take upstream's surrounding changes. `forget` is fork-only API — if upstream reworks the persisted `server` record shape, re-point the three deletes at the new maps. Do not gate `forget` behind the platform removal succeeding: the tab/workspace cleanup ahead of it already treats the sandbox as gone. HTTP and WSL removals deliberately keep their maps (an HTTP server still exists remotely; extending this to WSL distro deletion is an open follow-up). |
271+
| Sandbox/WSL delete cleanup + confirmation: `packages/app/src/context/server.tsx`, `packages/app/src/components/dialog-select-server.tsx`, `packages/app/src/i18n/{en,es}.ts` | `server.tsx` gains `forget(key)` beside `remove(key)`: it deletes the persisted per-server `projects`/`lastProject`/`recentlyClosed` map entries for one key. `dialog-select-server.tsx` `handleRemove` (already diverged, see the renderer-slice row) additionally: for `sandbox:` and `wsl:` keys, awaits a confirmation through the fork-only `packages/app/src/sandbox/dialog-delete.tsx` / `packages/app/src/wsl/dialog-delete.tsx` (lazy-imported, pushed with `dialog.push` so it stacks on whatever dialog opened the menu) and returns without touching anything when declined; and calls `server.forget(key)` right after `server.remove(key)` for both key kinds. The controller gains a `useDialog()` binding for this. i18n gains the `sandbox.delete.*` and `wsl.delete.*` families. The two dialogs state different consequences on purpose: deleting a sandbox erases its disk (sessions, terminals, guest files — permanently; mounted host folders survive), while removing a WSL server destroys nothing — the app never unregisters distributions — it only forgets the app-side project state. | `remove()` only edits the stored HTTP server list; the project maps are keyed by server key and outlive the server, which is right for an HTTP server that still exists remotely but wrong for a deleted sandbox or removed distro — a recreated sandbox with the same name inherited the old projects (`server.projects["sandbox:<name>"]`) and, via the leftover `recentlyClosed`, silently refused to seed folders closed in the previous sandbox's life; a re-added distro likewise inherited its previous project list. And deleting a sandbox erases guest data permanently, so it must not be a one-click menu action; the confirmations live in `handleRemove` because the settings rows and the generic server list all funnel through it. | Keep the confirm-and-forget block at the top of `handleRemove` and the `forget` calls between `server.remove(key)` and the awaited platform removals; take upstream's surrounding changes. `forget` is fork-only API — if upstream reworks the persisted `server` record shape, re-point the three deletes at the new maps. Do not gate `forget` behind the platform removal succeeding: the tab/workspace cleanup ahead of it already treats the server as gone. HTTP removals deliberately keep their maps — the server still exists remotely and re-adding it should restore its projects. If the app ever gains real distro unregistration, the WSL dialog's "nothing is deleted" wording must change with it. |
272272

273273
## Known rebrand gaps
274274

‎packages/app/src/components/dialog-select-server.tsx‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,8 @@ export function useServerManagementController(options: { onSelect?: () => void;
512512
resetEdit()
513513
})
514514

515-
// Fork: deleting a sandbox erases its disk, so it is confirmed with the
515+
// Fork: deleting a sandbox erases its disk, and removing a WSL server
516+
// forgets its remembered projects, so both are confirmed with the
516517
// consequences spelled out. Confirmation lives here rather than at the menu
517518
// items because every delete entry point funnels through handleRemove.
518519
function confirmSandboxDelete(key: ServerConnection.Key) {
@@ -528,9 +529,21 @@ export function useServerManagementController(options: { onSelect?: () => void;
528529
})
529530
}
530531

532+
function confirmWslDelete(key: ServerConnection.Key) {
533+
return new Promise<boolean>((resolve) => {
534+
void import("@/wsl/dialog-delete").then((module) => {
535+
dialog.push(
536+
() => <module.DialogDeleteWslServer distro={key.slice("wsl:".length)} onConfirm={() => resolve(true)} />,
537+
() => resolve(false),
538+
)
539+
})
540+
})
541+
}
542+
531543
async function handleRemove(key: ServerConnection.Key) {
532544
try {
533545
if (key.startsWith("sandbox:") && !(await confirmSandboxDelete(key))) return
546+
if (key.startsWith("wsl:") && !(await confirmWslDelete(key))) return
534547
// Close the tabs first: destroying a sandbox or a WSL distro takes as
535548
// long as deleting its disk, and the connection leaves the list as soon
536549
// as it completes. Cleaning up afterwards leaves tabs pointing at a
@@ -541,10 +554,10 @@ export function useServerManagementController(options: { onSelect?: () => void;
541554
// rather than after a delete that takes as long as erasing a disk.
542555
workspace.removedServer(key)
543556
server.remove(key)
544-
// Fork: a deleted sandbox's disk is gone, but the per-server project
545-
// maps are keyed by `sandbox:<name>` and outlive it, so a recreated
546-
// sandbox with the same name would inherit the old projects.
547-
if (key.startsWith("sandbox:")) server.forget(key)
557+
// Fork: the per-server project maps are keyed by `sandbox:<name>` /
558+
// `wsl:<distro>` and outlive the server, so a recreated or re-added
559+
// server with the same name would inherit the old projects.
560+
if (key.startsWith("sandbox:") || key.startsWith("wsl:")) server.forget(key)
548561
if (key.startsWith("wsl:")) await platform.wslServers?.removeServer(key)
549562
if (key.startsWith("sandbox:")) await platform.sandboxes?.remove(key)
550563
if ((await platform.getDefaultServer?.()) === key) {

‎packages/app/src/i18n/en.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,12 @@ export const dict = {
373373
"wsl.server.label": "WSL",
374374
"wsl.server.menu.label": "WSL server",
375375
"wsl.server.retryStart": "Retry start",
376+
"wsl.delete.title": "Remove WSL server",
377+
"wsl.delete.confirm": "Remove {{name}}?",
378+
"wsl.delete.distro":
379+
"The distribution is not deleted: its files and sessions stay on this computer, and you can add it back at any time.",
380+
"wsl.delete.projects": "Its remembered projects and tabs are removed; adding it back starts with a clean project list.",
381+
"wsl.delete.button": "Remove server",
376382
"wsl.server.updating": "Updating...",
377383
"workspace.code": "Code",
378384
"workspace.work": "Work",

‎packages/app/src/i18n/es.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,13 @@ export const dict = {
364364
"wsl.server.label": "WSL",
365365
"wsl.server.menu.label": "Servidor WSL",
366366
"wsl.server.retryStart": "Reintentar el inicio",
367+
"wsl.delete.title": "Quitar servidor WSL",
368+
"wsl.delete.confirm": "¿Quitar {{name}}?",
369+
"wsl.delete.distro":
370+
"La distribución no se elimina: sus archivos y sesiones permanecen en este equipo, y puedes volver a añadirla en cualquier momento.",
371+
"wsl.delete.projects":
372+
"Sus proyectos y pestañas recordados se quitan; al volver a añadirla se empieza con una lista de proyectos limpia.",
373+
"wsl.delete.button": "Quitar servidor",
367374
"wsl.server.updating": "Actualizando...",
368375
"workspace.code": "Código",
369376
"workspace.work": "Trabajo",
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { useDialog } from "@opencode-ai/ui/context/dialog"
2+
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
3+
import { Dialog, DialogBody, DialogFooter, DialogHeader, DialogTitle } from "@opencode-ai/ui/v2/dialog-v2"
4+
import { DividerV2 } from "@opencode-ai/ui/v2/divider-v2"
5+
import { useLanguage } from "@/context/language"
6+
7+
// Confirms removing a WSL server with the consequences spelled out. Unlike
8+
// deleting a sandbox this destroys nothing — the app never unregisters
9+
// distributions, so the distro's files and sessions stay on the machine — but
10+
// the app forgets the server's remembered projects and tabs, so adding the
11+
// distribution back starts with a clean list rather than inheriting its
12+
// previous life. Reached through useServerManagementController.handleRemove,
13+
// like the sandbox confirmation.
14+
export function DialogDeleteWslServer(props: { distro: string; onConfirm: () => void }) {
15+
const language = useLanguage()
16+
const dialog = useDialog()
17+
18+
return (
19+
<Dialog fit>
20+
<DialogHeader hideClose={true}>
21+
<DialogTitle>{language.t("wsl.delete.title")}</DialogTitle>
22+
</DialogHeader>
23+
<DividerV2 />
24+
<DialogBody class="flex max-w-[440px] flex-col gap-3 px-4 pt-3 pb-1">
25+
<p class="text-14">{language.t("wsl.delete.confirm", { name: props.distro })}</p>
26+
<ul class="flex list-disc flex-col gap-2 pl-5 text-12 text-v2-text-text-muted">
27+
<li>{language.t("wsl.delete.distro")}</li>
28+
<li>{language.t("wsl.delete.projects")}</li>
29+
</ul>
30+
</DialogBody>
31+
<DialogFooter>
32+
<ButtonV2 variant="neutral" onClick={() => dialog.close()}>
33+
{language.t("common.cancel")}
34+
</ButtonV2>
35+
<ButtonV2
36+
variant="danger"
37+
onClick={() => {
38+
props.onConfirm()
39+
dialog.close()
40+
}}
41+
>
42+
{language.t("wsl.delete.button")}
43+
</ButtonV2>
44+
</DialogFooter>
45+
</Dialog>
46+
)
47+
}

0 commit comments

Comments
 (0)