diff --git a/CHANGELOG.md b/CHANGELOG.md index 0195b7a7d3..00bc45ceb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ file is the human-readable summary of what changed and why. ### Desktop / Web UI +- **Switching to Work with no sandbox explains itself** — clicking **Work** + before you have a sandbox opens the Add sandbox dialog, which was a dialog + you did not ask for with nothing to say about why it appeared. It now leads + with the reason: Work requires a sandbox environment to run sessions. + Reaching the same dialog deliberately, from Settings, is unchanged. - **The CPU and memory fields explain themselves** — the Add sandbox dialog offered two unlabelled optional boxes with no hint of what a sandbox gets if you leave them empty, and the memory box ran past the edge of the dialog. diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 49ea12586b..fa0b567b0d 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -379,6 +379,7 @@ export const dict = { "workspace.empty.none": "No sandbox yet. Create one to work in an isolated environment.", "workspace.empty.starting": "Your sandbox is not running yet. It appears here once it is ready.", "workspace.empty.stopped": "Your sandbox is stopped. Start it again from Settings → Servers.", + "sandbox.add.workIntro": "Work requires a sandbox environment to run sessions.", "sandbox.server.add": "Add sandbox", "sandbox.server.label": "Sandbox", "sandbox.server.menu.label": "Sandbox", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index d064e3f931..ad1b43daa1 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -370,6 +370,7 @@ export const dict = { "workspace.empty.none": "Aún no hay ningún sandbox. Crea uno para trabajar en un entorno aislado.", "workspace.empty.starting": "Tu sandbox todavía no está en marcha. Aparecerá aquí cuando esté listo.", "workspace.empty.stopped": "Tu sandbox está detenido. Vuelve a iniciarlo desde Ajustes → Servidores.", + "sandbox.add.workIntro": "Trabajo requiere un entorno de sandbox para ejecutar sesiones.", "sandbox.server.add": "Añadir sandbox", "sandbox.server.label": "Sandbox", "sandbox.server.menu.label": "Sandbox", diff --git a/packages/app/src/sandbox/dialog-add.tsx b/packages/app/src/sandbox/dialog-add.tsx index 80ea641738..9017cc7a6a 100644 --- a/packages/app/src/sandbox/dialog-add.tsx +++ b/packages/app/src/sandbox/dialog-add.tsx @@ -1,7 +1,14 @@ import { useDialog } from "@opencode-ai/ui/context/dialog" import { showToast } from "@opencode-ai/ui/toast" import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" -import { Dialog, DialogBody, DialogFooter, DialogHeader, DialogTitle } from "@opencode-ai/ui/v2/dialog-v2" +import { + Dialog, + DialogBody, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTitleGroup, +} from "@opencode-ai/ui/v2/dialog-v2" import { DividerV2 } from "@opencode-ai/ui/v2/divider-v2" import { LoaderV2 } from "@opencode-ai/ui/v2/loader-v2" import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2" @@ -25,7 +32,13 @@ import { // need to react to a creation must use it rather than watching the sandbox // list: that list arrives over a separate IPC event, so it is still empty for // a beat after add() resolves. -export function DialogAddSandbox(props: { onCreated?: (config: SandboxConfig) => void }) { +export function DialogAddSandbox(props: { + onCreated?: (config: SandboxConfig) => void + // Set when the dialog opened because Work needs a sandbox and there is none, + // rather than because the user asked to add one. That dialog arrives + // unbidden, so it has to say why it is there. + requiredForWork?: boolean +}) { const language = useLanguage() const platform = usePlatform() const dialog = useDialog() @@ -193,7 +206,19 @@ export function DialogAddSandbox(props: { onCreated?: (config: SandboxConfig) => return ( - {language.t("sandbox.server.add")} + {/* As a description under the title rather than a line in the body: + it explains the whole dialog, not the field it happens to sit + above, and DialogTitleGroup ties it to the dialog for screen + readers. */} + {language.t("sandbox.server.add")}} + > + + diff --git a/packages/app/src/workspace/tabs.tsx b/packages/app/src/workspace/tabs.tsx index 446db5a5dc..dbd3b926e6 100644 --- a/packages/app/src/workspace/tabs.tsx +++ b/packages/app/src/workspace/tabs.tsx @@ -82,7 +82,7 @@ export function useWorkspaceSwitch() { // list arrives on a separate IPC event and is still empty for a beat // after the create resolves, so checking it here left the user on // Code until they toggled the tabs by hand. - void dialog.show(() => enter("work")} />) + void dialog.show(() => enter("work")} />) }) return }