From e77909d07ef375e5190896d67f1f381f491a2ae9 Mon Sep 17 00:00:00 2001 From: Quickbeard Date: Mon, 17 Aug 2026 12:22:41 +0700 Subject: [PATCH 1/2] feat(app): mount folders from the Work sidebar button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The folder button beside Projects on the Work side opened the Code behavior — a directory picker over the sandbox's own filesystem — but folders reach a sandbox by being mounted, not picked. On Work it now opens the sandbox's mount dialog, the same one as the server menu item in Settings, which is renamed from "Folders..." to "Mount project" to match, as is the dialog title. The dialog's intro line drops the without-asking warning. Code keeps the existing Add project behavior. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 9 ++++++++ packages/app/src/components/app-sidebar.tsx | 25 +++++++++++++++++---- packages/app/src/i18n/en.ts | 7 +++--- packages/app/src/i18n/es.ts | 7 +++--- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccd9cef9b1..14fd108085 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,15 @@ file is the human-readable summary of what changed and why. ### Desktop / Web UI +- **The Work sidebar's folder button now mounts a folder into the sandbox** — + the button next to **Projects** on the Work side used to open a directory + picker over the sandbox's own filesystem, the same "Add project" it is on + Code — where folders reach a sandbox by being mounted, not picked. It now + opens the mount dialog for the sandbox, the same one as the server's menu + item in Settings, which is itself renamed from **Folders...** to **Mount + project** to match. The dialog's title follows suit (it was **Sandbox + folders**), and its intro line no longer warns that the agent edits mounted + files without asking. Code keeps the existing Add project behavior. - **First-run setup on the desktop app** — with no AI provider configured, the desktop app now opens a setup dialog that cannot be dismissed until a provider exists, offering the same three paths as the CLI's `codev diff --git a/packages/app/src/components/app-sidebar.tsx b/packages/app/src/components/app-sidebar.tsx index 062294d532..cf87ac375b 100644 --- a/packages/app/src/components/app-sidebar.tsx +++ b/packages/app/src/components/app-sidebar.tsx @@ -102,16 +102,33 @@ export function AppSidebar(props: { class?: string; debugTools?: { visible: bool !(model.projects().length === 0 && model.recentlyClosed().length > 0) } > - + {/* Fork: on Work this mounts a host folder into the sandbox — the + same dialog as the server row's Mount project item. The Code + behavior (a picker over the server's own filesystem) would + browse the guest here, which is not how folders reach a + sandbox. */} + } disabled={global.servers.health[ServerConnection.key(sideServers()[0]!)]?.healthy === false} - onClick={() => model.chooseProject(sideServers()[0]!)} - aria-label={language.t("home.project.add")} + onClick={() => { + const conn = sideServers()[0]! + if (side() === "work") { + void import("@/sandbox/dialog-mounts").then((module) => { + dialog.show(() => ) + }) + return + } + model.chooseProject(conn) + }} + aria-label={language.t(side() === "work" ? "sandbox.server.mounts" : "home.project.add")} /> diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 06152e3f8d..b79a42317e 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -386,7 +386,7 @@ export const dict = { "sandbox.server.start": "Start", "sandbox.server.stop": "Stop", "sandbox.server.reset": "Reset", - "sandbox.server.mounts": "Folders...", + "sandbox.server.mounts": "Mount project", "sandbox.status.starting": "Starting...", "sandbox.status.ready": "Ready", "sandbox.status.stopped": "Stopped", @@ -425,9 +425,8 @@ export const dict = { "sandbox.add.resourcesInvalid": "CPUs must be {{minCpus}}\u2013{{maxCpus}} and memory {{minMemory}}\u2013{{maxMemory}} GB.", "sandbox.add.downloadProgress": "Downloading image… {{done}}/{{total}} MB", "sandbox.add.creating": "Creating the sandbox\u2026 this takes a few minutes.", - "sandbox.mounts.title": "Sandbox folders", - "sandbox.mounts.hint": - "Folders mounted here are the only ones on this computer the sandbox can reach, and the agent edits files in them without asking.", + "sandbox.mounts.title": "Mount project", + "sandbox.mounts.hint": "Folders mounted here are the only ones on this computer the sandbox can reach.", "sandbox.mounts.primary": "Project folder", "sandbox.mounts.empty": "No extra folders are mounted.", "sandbox.mounts.add": "Add folder...", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index 91bbd368d5..5a7f627b76 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -377,7 +377,7 @@ export const dict = { "sandbox.server.start": "Iniciar", "sandbox.server.stop": "Detener", "sandbox.server.reset": "Restablecer", - "sandbox.server.mounts": "Carpetas...", + "sandbox.server.mounts": "Montar proyecto", "sandbox.status.starting": "Iniciando...", "sandbox.status.ready": "Listo", "sandbox.status.stopped": "Detenido", @@ -416,9 +416,8 @@ export const dict = { "sandbox.add.resourcesInvalid": "Las CPU deben ser {{minCpus}}\u2013{{maxCpus}} y la memoria {{minMemory}}\u2013{{maxMemory}} GB.", "sandbox.add.downloadProgress": "Descargando imagen… {{done}}/{{total}} MB", "sandbox.add.creating": "Creando el sandbox\u2026 esto tarda unos minutos.", - "sandbox.mounts.title": "Carpetas del sandbox", - "sandbox.mounts.hint": - "Las carpetas montadas aquí son las únicas de este equipo a las que el sandbox puede acceder, y el agente edita archivos en ellas sin preguntar.", + "sandbox.mounts.title": "Montar proyecto", + "sandbox.mounts.hint": "Las carpetas montadas aquí son las únicas de este equipo a las que el sandbox puede acceder.", "sandbox.mounts.primary": "Carpeta del proyecto", "sandbox.mounts.empty": "No hay carpetas adicionales montadas.", "sandbox.mounts.add": "Añadir carpeta...", From 2ec0e04fb305933833c7e0d6cc166354c5c6b5fc Mon Sep 17 00:00:00 2001 From: Quickbeard Date: Mon, 17 Aug 2026 12:36:48 +0700 Subject: [PATCH 2/2] fix(app): stop the mounts dialog wiping mounts it never loaded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DialogSandboxMounts snapshotted config.mounts into its store once, at component creation. Opened before the sandboxes query had data — easy moments after launch — the snapshot froze as an empty list while the rest of the dialog filled in reactively, so the dialog showed only the project folder, read as dirty, and Save wrote the empty list back: every extra folder silently unmounted while its seeded project stayed in the sidebar looking mounted. The editor is now created only once the sandbox's state exists, behind a loading dialog, so the snapshot is always taken from loaded state. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 8 ++++++ packages/app/src/sandbox/dialog-mounts.tsx | 29 ++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14fd108085..7275f96002 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,14 @@ file is the human-readable summary of what changed and why. ### Desktop / Web UI +- **The Mount project dialog can no longer silently unmount everything** — + opened moments after launch, before the sandbox state had loaded, the dialog + showed the mounted folders as an empty list: the list was captured once when + the dialog opened and never updated, so it stayed empty even after the state + arrived a beat later. Saving from that dialog then wrote the empty list back, + quietly unmounting every extra folder — while their projects stayed in the + sidebar, still looking mounted. The dialog now waits for the sandbox's state + before it builds the folder list. - **The Work sidebar's folder button now mounts a folder into the sandbox** — the button next to **Projects** on the Work side used to open a directory picker over the sandbox's own filesystem, the same "Add project" it is on diff --git a/packages/app/src/sandbox/dialog-mounts.tsx b/packages/app/src/sandbox/dialog-mounts.tsx index a3622b75af..60487a6f4e 100644 --- a/packages/app/src/sandbox/dialog-mounts.tsx +++ b/packages/app/src/sandbox/dialog-mounts.tsx @@ -18,6 +18,35 @@ import { validateSandboxMounts } from "./mounts-model" // else can be added and removed, applied hot on Windows and by restarting the // sandbox elsewhere. export function DialogSandboxMounts(props: { id: string }) { + const language = useLanguage() + const sandboxes = useSandboxes() + const item = () => sandboxes.state.data?.sandboxes.find((x) => x.config.id === props.id) + // The editor snapshots the mount list when it is created, so it must not be + // created before the sandbox state has loaded: a snapshot taken while the + // query is still empty freezes as an empty list, reads as a pending removal + // of every mount once the state arrives, and saving it silently unmounts + // everything. + return ( + + + {language.t("sandbox.mounts.title")} + + + + + + + } + > + + + ) +} + +function SandboxMountsEditor(props: { id: string }) { const language = useLanguage() const platform = usePlatform() const dialog = useDialog()