From 4d8c5e29025414c0b88bc6cfe7a320ef29cf9933 Mon Sep 17 00:00:00 2001 From: minhn4 Date: Mon, 17 Aug 2026 14:36:32 +0700 Subject: [PATCH 1/7] feat(app): unmount a Work folder from its own menu, and colour it as destructive A project row on Work is a folder mounted into a sandbox, but its menu offered Close, which only took the row out of this window's sidebar. The folder stayed mounted, stayed listed under Folders..., and the row could come back on the next launch -- the removal gesture that looked most like removal did the least, while the real one was buried in a dialog. The row's trailing separator and item now come from the fork-only ProjectRemovalMenuItems, so pages/home.tsx keeps one mount point instead of a branch: - a plain server renders upstream's Close, unchanged; - an extra mounted folder gets Unmount, in the same danger colour the server and session menus use, which removes it from SandboxConfig.mounts and then drops the row; - the folder the sandbox was created with offers neither. It is fixed for the sandbox's life, and a disabled item that can never be enabled says less than no item at all; - a row whose folder is already gone -- unmounted from the dialog instead, which deliberately leaves the row behind -- keeps Close, or it could never be cleared away. The row is dropped with projects.remove(), not the menu's close(): the folder is leaving the sandbox, which is the internal, non-user removal that store documents. close() would also record it as user-closed and the seeding effect honours that, so remounting the folder would leave the user with no project for it. Also colours the sandbox Delete item (Settings -> Servers) red, which the generic and WSL server rows already were. Driven against the real app over CDP: Work rows offer Unmount in rgb(241, 116, 113) and the sandbox's own folder offers nothing, Code still offers Close, unmounting takes the folder out of the config and the row out of the sidebar, and remounting brings both back. That run also caught the IPC rejecting Solid store proxies ("An object could not be cloned"), so the mount list is rebuilt as plain objects before it crosses. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 13 +++ DIVERGENCES.md | 1 + packages/app/src/i18n/en.ts | 1 + packages/app/src/i18n/es.ts | 1 + packages/app/src/pages/home.tsx | 15 ++- packages/app/src/sandbox/mounts-model.test.ts | 59 ++++++++++- packages/app/src/sandbox/mounts-model.ts | 30 ++++++ packages/app/src/sandbox/project-menu.tsx | 98 +++++++++++++++++++ packages/app/src/sandbox/settings.tsx | 9 +- packages/app/src/workspace/tabs.tsx | 5 +- 10 files changed, 224 insertions(+), 8 deletions(-) create mode 100644 packages/app/src/sandbox/project-menu.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 0979de5e3c..6e6aad8136 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,19 @@ file is the human-readable summary of what changed and why. ### Desktop / Web UI +- **A folder on Work is unmounted from its own menu** — the menu on a **Work** + project offered **Close**, which only took the row out of the sidebar while + the sandbox went on holding the folder: the folder was still mounted, still + listed under **Folders…**, and the row could come back on the next launch. + That entry is now **Unmount**, in red, and it does what it says — the folder + leaves the sandbox, and the row goes with it. Mounting the folder again + brings its project back. The folder the sandbox was created with is fixed for + its life, so its menu offers neither; deleting the sandbox is still the way + to be rid of that one. On macOS and Linux unmounting restarts the sandbox, as + it does from the Folders… dialog. **Code** keeps Close, unchanged. +- **Deleting a sandbox now looks destructive** — **Delete** in a sandbox's menu + (Settings → Servers) sat in the same colour as Reset and Mount project. It is + now red, matching ordinary and WSL servers. - **A new sandbox's project no longer flashes "workspace"** — a sandbox's project folder is mounted inside the guest at a fixed path, and until the app finished renaming the project after your folder — a round trip through diff --git a/DIVERGENCES.md b/DIVERGENCES.md index 7002bb8b8d..d72a6fbde7 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -126,6 +126,7 @@ from the squash message first, or use a plain merge. | `packages/ui/src/theme/context.tsx`, `packages/app/public/oc-theme-preload.js`, `packages/app/src/theme-preload.test.ts`, `packages/app/src/pages/layout.tsx`, `packages/app/src/components/settings-general.tsx`, `packages/app/src/components/settings-v2/general.tsx` | **One theme in the web/desktop UI; light and dark are the only axis.** Upstream ships 36 selectable palettes with a Settings → Appearance **Theme** picker and command-palette entries. The fork exposes none of them: OC-2 is the theme, `colorScheme` (system/light/dark) is the only appearance choice. The lock lives at `normalize()` in `theme/context.tsx`, renamed to the exported `normalizeThemeID()` and returning the exported `SHIPPED_THEME_ID` unconditionally — that one function is already on every path (provider init, the `onMount` migration, `load`, the cross-tab `storage` listener, `setTheme`, `previewTheme`), so a stored id from before the picker was removed, or one written by another tab, resolves to OC-2 without a second guard. It is exported (and the six call sites renamed) so `theme/context.test.ts` can cover the invariant directly instead of only through its call sites — the module imports cleanly under `bun test` because `import.meta.glob` is called lazily inside `getFiles()`. It also gives the migration for free: `onMount` already rewrites `opencode-theme-id` and calls `clear()` when the raw value differs from the normalized one, which now drops the stale `opencode-theme-css-{light,dark}` cache. `ids()` returns `["oc-2"]` and `loadThemes()` loads only OC-2 instead of all 36 (it was `Promise.all(themeIDs().map(load))`). The bundled theme JSON, the `names` map and `registerTheme` are **left alone** — unreferenced data costs nothing and keeps upstream's theme edits applying cleanly. `oc-theme-preload.js` does the same rewrite *before* mount, so the first launch after upgrading does not paint the user's old palette for a frame; with no other theme reachable its cached-CSS `