From e98b90fc5122a1889d1dc4cf30a7398612a91bfe Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:56:51 +1000 Subject: [PATCH] feat(app): align workspace flows with Q3 designs --- .../app/e2e/regression/workspaces.spec.ts | 15 +- .../components/prompt-workspace-selector.tsx | 106 ++++--------- .../src/components/session-workspace-menu.tsx | 7 +- .../settings-v2/dialog-settings-v2.tsx | 13 +- .../src/components/settings-v2/workspaces.tsx | 12 +- packages/app/src/context/tabs.tsx | 5 + packages/app/src/i18n/ar.ts | 10 +- packages/app/src/i18n/br.ts | 10 +- packages/app/src/i18n/bs.ts | 10 +- packages/app/src/i18n/da.ts | 10 +- packages/app/src/i18n/de.ts | 10 +- packages/app/src/i18n/en.ts | 13 +- packages/app/src/i18n/es.ts | 10 +- packages/app/src/i18n/fr.ts | 10 +- packages/app/src/i18n/ja.ts | 10 +- packages/app/src/i18n/ko.ts | 10 +- packages/app/src/i18n/no.ts | 10 +- packages/app/src/i18n/pl.ts | 10 +- packages/app/src/i18n/ru.ts | 10 +- packages/app/src/i18n/th.ts | 10 +- packages/app/src/i18n/tr.ts | 10 +- packages/app/src/i18n/uk.ts | 10 +- packages/app/src/i18n/zh.ts | 10 +- packages/app/src/i18n/zht.ts | 10 +- packages/app/src/pages/layout.tsx | 2 +- .../pages/new-session/new-session-view.tsx | 37 ++++- .../new-session-workspace-controller.ts | 7 +- .../session/timeline/message-timeline.tsx | 149 +++++++++++------- packages/app/src/utils/workspace.test.ts | 24 ++- packages/app/src/utils/workspace.ts | 14 +- packages/ui/src/v2/components/dialog-v2.css | 3 + 31 files changed, 268 insertions(+), 309 deletions(-) diff --git a/packages/app/e2e/regression/workspaces.spec.ts b/packages/app/e2e/regression/workspaces.spec.ts index 0f62cabdfe9..c853a579028 100644 --- a/packages/app/e2e/regression/workspaces.spec.ts +++ b/packages/app/e2e/regression/workspaces.spec.ts @@ -82,25 +82,18 @@ test("selects local, new, and existing workspaces from the ready-ish start menu" await trigger.hover() await expect(page.getByRole("tooltip")).toContainText("Select where to run session") await trigger.click() - await expect(page.getByRole("menuitem", { name: /Local repository/ })).toContainText("Use current checkout") + await expect(page.getByRole("menuitem", { name: /Local repository/ })).toBeVisible() const newWorkspace = page.getByRole("menuitem", { name: /New workspace/ }) - await expect(newWorkspace).toContainText("Work in parallel") - await expect(page.getByRole("menuitem", { name: /Workspace/ })).toContainText("Pick from existing") + await expect(newWorkspace).toBeVisible() + await expect(page.getByRole("menuitem", { name: /Workspace/ })).toBeVisible() await expect(page.getByRole("menuitem", { name: "View all" })).toBeVisible() - await newWorkspace.hover() - await expect(page.getByText("Creates an isolated copy from your current checkout", { exact: true })).toBeVisible({ - timeout: 3_000, - }) - await page.keyboard.press("Escape") - await expect(page.getByText("Creates an isolated copy from your current checkout", { exact: true })).toHaveCount(0) await newWorkspace.click() await expect(page.getByRole("button", { name: /New workspace/ })).toBeVisible() await expect(page.getByText("from main", { exact: true })).toBeVisible() await page.getByRole("button", { name: /New workspace/ }).click() await page.getByRole("menuitem", { name: /Workspace/ }).hover() - await expect(page.getByText("Reuse an existing isolated workspace", { exact: true })).toBeVisible() await page.getByRole("menuitem", { name: "feature" }).click() await expect(page.getByRole("button", { name: /feature/ })).toBeVisible() }) @@ -126,7 +119,7 @@ test("searches long workspace lists within the available viewport", async ({ pag const submenu = page .locator('[data-component="menu-v2-content"]') - .filter({ hasText: "Reuse an existing isolated workspace" }) + .filter({ has: page.getByPlaceholder("Search workspaces") }) const search = submenu.getByPlaceholder("Search workspaces") await expect(search).toBeFocused() const box = await submenu.boundingBox() diff --git a/packages/app/src/components/prompt-workspace-selector.tsx b/packages/app/src/components/prompt-workspace-selector.tsx index d986934fc93..cad04aaa0c0 100644 --- a/packages/app/src/components/prompt-workspace-selector.tsx +++ b/packages/app/src/components/prompt-workspace-selector.tsx @@ -1,4 +1,4 @@ -import { createMemo, createSignal, For, onCleanup, Show } from "solid-js" +import { createMemo, createSignal, For, Show } from "solid-js" import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2" import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" import { Icon } from "@opencode-ai/ui/v2/icon" @@ -10,14 +10,13 @@ export function PromptWorkspaceSelector(props: { projectRoot: string workspaces: string[] branch?: string + onboarding?: boolean onChange: (value: string) => void onDone: () => void onViewAll: () => void }) { const language = useLanguage() const [search, setSearch] = createSignal("") - const [explanation, setExplanation] = createSignal<"local" | "new">() - let explanationTimer: ReturnType | undefined let searchInput: HTMLInputElement | undefined let focusSearch = false let pending: { type: "select"; value: string } | { type: "viewAll" } | undefined @@ -35,21 +34,7 @@ export function PromptWorkspaceSelector(props: { const select = (value: string) => { pending = { type: "select", value } } - const hideExplanation = () => { - if (explanationTimer) clearTimeout(explanationTimer) - explanationTimer = undefined - setExplanation() - } - const showExplanation = (value: "local" | "new") => { - hideExplanation() - explanationTimer = setTimeout(() => { - explanationTimer = undefined - setExplanation(value) - }, 800) - } - onCleanup(hideExplanation) const onOpenChange = (open: boolean) => { - hideExplanation() if (open) { setSearch("") return @@ -85,65 +70,52 @@ export function PromptWorkspaceSelector(props: { > {label()} + + - + {language.t("session.new.workspace.runIn")} - showExplanation("local")} - onPointerLeave={hideExplanation} - onFocusIn={() => showExplanation("local")} - onFocusOut={hideExplanation} - onKeyDown={(event: KeyboardEvent) => { - if (event.key === "Escape") hideExplanation() - }} - onSelect={() => select("main")} - > + select("main")}> + {language.t("session.new.workspace.local")} + + {language.t("session.new.workspace.local.tooltip")} + + + } class="min-w-0 flex-1" - contentClass="max-w-[min(240px,calc(100vw-16px))] whitespace-normal break-words" > - + {language.t("session.new.workspace.local")} - showExplanation("new")} - onPointerLeave={hideExplanation} - onFocusIn={() => showExplanation("new")} - onFocusOut={hideExplanation} - onKeyDown={(event: KeyboardEvent) => { - if (event.key === "Escape") hideExplanation() - }} - onSelect={() => select("create")} - > + select("create")}> + {language.t("workspace.new")} + + {language.t("session.new.workspace.new.tooltip")} + + + } class="min-w-0 flex-1" - contentClass="max-w-[min(240px,calc(100vw-16px))] whitespace-normal break-words" > - + {language.t("workspace.new")} @@ -167,7 +139,6 @@ export function PromptWorkspaceSelector(props: { }} > { if ( event.key === "ArrowRight" || @@ -179,16 +150,12 @@ export function PromptWorkspaceSelector(props: { }} > - + + {language.t("session.new.workspace.existing").replace(/…$/, "")} + - -
- {language.t("session.new.workspace.existing.tooltip")} -
+ = 10}>
@@ -281,12 +248,3 @@ export function PromptGitStatus(props: { branch?: string; noGit?: boolean; from? ) } - -function WorkspaceMenuCopy(props: { label: string; description: string }) { - return ( - - {props.label} - {props.description} - - ) -} diff --git a/packages/app/src/components/session-workspace-menu.tsx b/packages/app/src/components/session-workspace-menu.tsx index 464e5a42340..eac8687833a 100644 --- a/packages/app/src/components/session-workspace-menu.tsx +++ b/packages/app/src/components/session-workspace-menu.tsx @@ -12,6 +12,7 @@ import { Worktree } from "@/utils/worktree" import { WorkspaceOperation } from "@/utils/workspace-operation" import { showToast } from "@/utils/toast" import type { ServerScope } from "@/utils/server-scope" +import { workspaceDirectories } from "@/utils/workspace" import { WORKSPACE_PLACEMENT_REFRESH_TIMEOUT_MS, WORKSPACE_PREPARATION_TIMEOUT_MS, @@ -38,7 +39,7 @@ export function SessionWorkspaceMenu(props: { const blocked = () => props.eligible === false || operationPending() || serverSync().session.data.session_working(props.sessionID) const workspaces = () => - (props.project.sandboxes ?? []).filter((workspace) => pathKey(workspace) !== pathKey(props.directory)) + workspaceDirectories(props.project).filter((workspace) => pathKey(workspace) !== pathKey(props.directory)) const fail = (scope: ServerScope, sessionID: string, message: string) => { setStore("selected", undefined) @@ -117,7 +118,7 @@ export function SessionWorkspaceMenu(props: { {props.children} - + {language.t("workspace.move.title")} void move("create")}> @@ -133,7 +134,7 @@ export function SessionWorkspaceMenu(props: { {language.t("session.new.workspace.existing").replace(/…$/, "")} - + {(workspace) => ( void move(workspace)}> diff --git a/packages/app/src/components/settings-v2/dialog-settings-v2.tsx b/packages/app/src/components/settings-v2/dialog-settings-v2.tsx index 5b2478c04c1..d3bc5237775 100644 --- a/packages/app/src/components/settings-v2/dialog-settings-v2.tsx +++ b/packages/app/src/components/settings-v2/dialog-settings-v2.tsx @@ -12,7 +12,6 @@ import "./settings-v2.css" import { SettingsServersV2 } from "./servers" import { SettingsWorkspacesV2 } from "./workspaces" import { useDialog } from "@opencode-ai/ui/context/dialog" -import { useLayout } from "@/context/layout" import { useTabs } from "@/context/tabs" import { useServerSync } from "@/context/server-sync" @@ -23,18 +22,16 @@ export const DialogSettings: Component<{ const language = useLanguage() const platform = usePlatform() const dialog = useDialog() - const layout = useLayout() const tabs = useTabs() const serverSync = useServerSync() const [tab, setTab] = createSignal(props.defaultValue ?? "general") const directory = createMemo(() => { - const route = layout.route() - if (route.type === "dir-new-sesssion") return route.dir - if (route.type === "draft") { - const draft = tabs.store.find((item) => item.type === "draft" && item.draftID === route.draftID) - return draft?.type === "draft" ? draft.directory : undefined + const active = tabs.active() + if (active?.type === "draft") return active.directory + if (active?.type === "session") { + const session = serverSync().session.get(active.sessionId) + if (session) return session.directory } - if (route.type === "session") return serverSync().session.get(route.sessionId)?.directory return undefined }) diff --git a/packages/app/src/components/settings-v2/workspaces.tsx b/packages/app/src/components/settings-v2/workspaces.tsx index 4c7b81530a6..ecc904673db 100644 --- a/packages/app/src/components/settings-v2/workspaces.tsx +++ b/packages/app/src/components/settings-v2/workspaces.tsx @@ -146,7 +146,7 @@ export const SettingsWorkspacesV2: Component<{ activeDirectory?: string }> = (pr const remove = async (workspace: Workspace, bulk = false, context = captureDeleteContext()) => { if (!bulk) setStore("deleting", pathKey(workspace.directory)) const preflight = await inspect(workspace, context) - if (preflight.result !== "safe") { + if (preflight.result !== "safe" && (bulk || preflight.result !== "dirty")) { blocked(preflight.result) if (!bulk) setStore("deleting", undefined) return @@ -443,7 +443,9 @@ function DialogDeleteWorkspace(props: { <> {language.t("workspace.delete.confirm", { name: getFilename(props.workspace.directory) })}
- {props.workspace.directory} + + {props.workspace.directory} +
{language.t("settings.workspaces.delete.warning")}
@@ -459,7 +461,11 @@ function DialogDeleteWorkspace(props: { {language.t("workspace.delete.button")} diff --git a/packages/app/src/context/tabs.tsx b/packages/app/src/context/tabs.tsx index b6608e86811..0ca28d46206 100644 --- a/packages/app/src/context/tabs.tsx +++ b/packages/app/src/context/tabs.tsx @@ -177,6 +177,11 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({ } const actions = { + active() { + if (location.pathname === "/") return + const key = recentKey() + return store.find((tab) => tabKey(tab) === key) + }, addSessionTab: (tab: Omit) => { const next = { type: "session" as const, ...tab } const existing = store.find((item) => tabKey(item) === tabKey(next)) diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index c97c9db63f0..c6dc8bb98c1 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -896,13 +896,9 @@ export const dict = { "session.delete.button": "حذف الجلسة", "workspace.new": "مساحة عمل جديدة", "common.viewAll": "عرض الكل", + "session.new.workspace.local.tooltip": "استخدام نسخة العمل الحالية", + "session.new.workspace.new.tooltip": "إنشاء نسخة عمل معزولة", "session.new.workspace.fromBranch": "من {{branch}}", - "session.new.workspace.local.description": "استخدام نسخة العمل الحالية", - "session.new.workspace.new.description": "العمل بالتوازي", - "session.new.workspace.existing.description": "الاختيار من مساحات العمل الموجودة", - "session.new.workspace.local.tooltip": "تعمل الجلسة في مجلد مشروعك", - "session.new.workspace.new.tooltip": "ينشئ نسخة معزولة من نسخة العمل الحالية لديك", - "session.new.workspace.existing.tooltip": "إعادة استخدام مساحة عمل معزولة موجودة", "session.new.workspace.trigger.tooltip": "تحديد مكان تشغيل الجلسة", "session.new.workspace.search.placeholder": "البحث في مساحات العمل", "settings.tab.workspaces": "مساحات العمل", @@ -912,7 +908,6 @@ export const dict = { "settings.workspaces.count.other": "{{count}} مساحات عمل", "settings.workspaces.sessions.one": "{{count}} جلسة في {{project}}", "settings.workspaces.sessions.other": "{{count}} جلسات في {{project}}", - "settings.workspaces.lastActive": "آخر نشاط: {{time}}", "settings.workspaces.lastActiveSession": "آخر جلسة نشطة", "settings.workspaces.deleteAll": "حذف جميع مساحات العمل", "settings.workspaces.deleteAll.confirm": "حذف جميع مساحات العمل البالغ عددها {{count}}؟", @@ -922,7 +917,6 @@ export const dict = { "سيتم حذف مساحات العمل المحددة وعددها {{count}} في {{project}} نهائيًا فقط إذا كانت كل واحدة نظيفة وغير نشطة ولا تحتوي على جلسات مرتبطة.", "settings.workspaces.delete.blocked.active": "لا يمكن حذف مساحة العمل النشطة.", "settings.workspaces.delete.blocked.linked": "تحتوي مساحة العمل هذه على جلسات مرتبطة ولا يمكن حذفها.", - "settings.workspaces.section.defaults": "الجلسات الجديدة", "settings.workspaces.default.title": "البيئة الافتراضية", "settings.workspaces.default.description": "اختر مكان بدء الجلسات الجديدة", "settings.workspaces.default.lastUsed": "آخر بيئة مستخدمة لكل مشروع", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index cd13709b6d5..1ee596844bf 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -910,13 +910,9 @@ export const dict = { "session.delete.button": "Excluir sessão", "workspace.new": "Novo espaço de trabalho", "common.viewAll": "Ver todos", + "session.new.workspace.local.tooltip": "Usar o checkout atual", + "session.new.workspace.new.tooltip": "Criar checkout isolado", "session.new.workspace.fromBranch": "de {{branch}}", - "session.new.workspace.local.description": "Usar o checkout atual", - "session.new.workspace.new.description": "Trabalhar em paralelo", - "session.new.workspace.existing.description": "Escolher um espaço de trabalho existente", - "session.new.workspace.local.tooltip": "A sessão é executada na pasta do seu projeto", - "session.new.workspace.new.tooltip": "Cria uma cópia isolada a partir do seu checkout atual", - "session.new.workspace.existing.tooltip": "Reutilizar um espaço de trabalho isolado existente", "session.new.workspace.trigger.tooltip": "Selecionar onde executar a sessão", "session.new.workspace.search.placeholder": "Pesquisar espaços de trabalho", "settings.tab.workspaces": "Espaços de trabalho", @@ -926,7 +922,6 @@ export const dict = { "settings.workspaces.count.other": "{{count}} espaços de trabalho", "settings.workspaces.sessions.one": "{{count}} sessão em {{project}}", "settings.workspaces.sessions.other": "{{count}} sessões em {{project}}", - "settings.workspaces.lastActive": "Última atividade: {{time}}", "settings.workspaces.lastActiveSession": "Última sessão ativa", "settings.workspaces.deleteAll": "Excluir todos os espaços de trabalho", "settings.workspaces.deleteAll.confirm": "Excluir todos os {{count}} espaços de trabalho?", @@ -937,7 +932,6 @@ export const dict = { "settings.workspaces.delete.blocked.active": "O espaço de trabalho ativo não pode ser excluído.", "settings.workspaces.delete.blocked.linked": "Este espaço de trabalho tem sessões vinculadas e não pode ser excluído.", - "settings.workspaces.section.defaults": "Novas sessões", "settings.workspaces.default.title": "Ambiente padrão", "settings.workspaces.default.description": "Escolha onde as novas sessões começam", "settings.workspaces.default.lastUsed": "Último usado por projeto", diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts index 3cc60542e97..25b1d3691bd 100644 --- a/packages/app/src/i18n/bs.ts +++ b/packages/app/src/i18n/bs.ts @@ -986,13 +986,9 @@ export const dict = { "workspace.new": "Novi radni prostor", "common.viewAll": "Prikaži sve", + "session.new.workspace.local.tooltip": "Koristi trenutni checkout", + "session.new.workspace.new.tooltip": "Kreiraj izolovani checkout", "session.new.workspace.fromBranch": "iz {{branch}}", - "session.new.workspace.local.description": "Koristi trenutni checkout", - "session.new.workspace.new.description": "Radi paralelno", - "session.new.workspace.existing.description": "Odaberi postojeći radni prostor", - "session.new.workspace.local.tooltip": "Sesija se pokreće u folderu vašeg projekta", - "session.new.workspace.new.tooltip": "Kreira izolovanu kopiju vašeg trenutnog checkouta", - "session.new.workspace.existing.tooltip": "Ponovo koristi postojeći izolovani radni prostor", "session.new.workspace.trigger.tooltip": "Odaberi gdje pokrenuti sesiju", "session.new.workspace.search.placeholder": "Pretraži radne prostore", "settings.tab.workspaces": "Radni prostori", @@ -1002,7 +998,6 @@ export const dict = { "settings.workspaces.count.other": "{{count}} radnih prostora", "settings.workspaces.sessions.one": "{{count}} sesija u projektu {{project}}", "settings.workspaces.sessions.other": "{{count}} sesija u projektu {{project}}", - "settings.workspaces.lastActive": "Posljednja aktivnost: {{time}}", "settings.workspaces.lastActiveSession": "Posljednja aktivna sesija", "settings.workspaces.deleteAll": "Izbriši sve radne prostore", "settings.workspaces.deleteAll.confirm": "Izbrisati svih {{count}} radnih prostora?", @@ -1012,7 +1007,6 @@ export const dict = { "Odabranih {{count}} radnih prostora u {{project}} bit će trajno izbrisano samo ako je svaki čist, neaktivan i nema povezanih sesija.", "settings.workspaces.delete.blocked.active": "Aktivni radni prostor nije moguće izbrisati.", "settings.workspaces.delete.blocked.linked": "Ovaj radni prostor ima povezane sesije i nije ga moguće izbrisati.", - "settings.workspaces.section.defaults": "Nove sesije", "settings.workspaces.default.title": "Zadano okruženje", "settings.workspaces.default.description": "Odaberite gdje počinju nove sesije", "settings.workspaces.default.lastUsed": "Posljednje korišteno po projektu", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index d6733fc916b..143ad7c2b20 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -978,13 +978,9 @@ export const dict = { "workspace.new": "Nyt arbejdsområde", "common.viewAll": "Vis alle", + "session.new.workspace.local.tooltip": "Brug det aktuelle checkout", + "session.new.workspace.new.tooltip": "Opret et isoleret checkout", "session.new.workspace.fromBranch": "fra {{branch}}", - "session.new.workspace.local.description": "Brug det aktuelle checkout", - "session.new.workspace.new.description": "Arbejd parallelt", - "session.new.workspace.existing.description": "Vælg blandt eksisterende arbejdsområder", - "session.new.workspace.local.tooltip": "Sessionen kører i din projektmappe", - "session.new.workspace.new.tooltip": "Opretter en isoleret kopi af dit aktuelle checkout", - "session.new.workspace.existing.tooltip": "Genbrug et eksisterende isoleret arbejdsområde", "session.new.workspace.trigger.tooltip": "Vælg, hvor sessionen skal køre", "session.new.workspace.search.placeholder": "Søg i arbejdsområder", "settings.tab.workspaces": "Arbejdsområder", @@ -994,7 +990,6 @@ export const dict = { "settings.workspaces.count.other": "{{count}} arbejdsområder", "settings.workspaces.sessions.one": "{{count}} session i {{project}}", "settings.workspaces.sessions.other": "{{count}} sessioner i {{project}}", - "settings.workspaces.lastActive": "Senest aktiv: {{time}}", "settings.workspaces.lastActiveSession": "Senest aktive session", "settings.workspaces.deleteAll": "Slet alle arbejdsområder", "settings.workspaces.deleteAll.confirm": "Slet alle {{count}} arbejdsområder?", @@ -1004,7 +999,6 @@ export const dict = { "De {{count}} valgte arbejdsområder i {{project}} fjernes permanent, men kun hvis hvert område er rent, inaktivt og ikke har tilknyttede sessioner.", "settings.workspaces.delete.blocked.active": "Det aktive arbejdsområde kan ikke slettes.", "settings.workspaces.delete.blocked.linked": "Dette arbejdsområde har tilknyttede sessioner og kan ikke slettes.", - "settings.workspaces.section.defaults": "Nye sessioner", "settings.workspaces.default.title": "Standardmiljø", "settings.workspaces.default.description": "Vælg, hvor nye sessioner starter", "settings.workspaces.default.lastUsed": "Senest brugt pr. projekt", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index 1c2a4bfabe2..e6dd154647b 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -922,13 +922,9 @@ export const dict = { "session.delete.button": "Sitzung löschen", "workspace.new": "Neuer Arbeitsbereich", "common.viewAll": "Alle anzeigen", + "session.new.workspace.local.tooltip": "Aktuellen Checkout verwenden", + "session.new.workspace.new.tooltip": "Isolierten Checkout erstellen", "session.new.workspace.fromBranch": "aus {{branch}}", - "session.new.workspace.local.description": "Aktuellen Checkout verwenden", - "session.new.workspace.new.description": "Parallel arbeiten", - "session.new.workspace.existing.description": "Vorhandenen Arbeitsbereich auswählen", - "session.new.workspace.local.tooltip": "Die Sitzung läuft in Ihrem Projektverzeichnis", - "session.new.workspace.new.tooltip": "Erstellt eine isolierte Kopie Ihres aktuellen Checkouts", - "session.new.workspace.existing.tooltip": "Vorhandenen isolierten Arbeitsbereich wiederverwenden", "session.new.workspace.trigger.tooltip": "Auswählen, wo die Sitzung ausgeführt werden soll", "session.new.workspace.search.placeholder": "Arbeitsbereiche durchsuchen", "settings.tab.workspaces": "Arbeitsbereiche", @@ -938,7 +934,6 @@ export const dict = { "settings.workspaces.count.other": "{{count}} Arbeitsbereiche", "settings.workspaces.sessions.one": "{{count}} Sitzung in {{project}}", "settings.workspaces.sessions.other": "{{count}} Sitzungen in {{project}}", - "settings.workspaces.lastActive": "Zuletzt aktiv: {{time}}", "settings.workspaces.lastActiveSession": "Zuletzt aktive Sitzung", "settings.workspaces.deleteAll": "Alle Arbeitsbereiche löschen", "settings.workspaces.deleteAll.confirm": "Alle {{count}} Arbeitsbereiche löschen?", @@ -949,7 +944,6 @@ export const dict = { "settings.workspaces.delete.blocked.active": "Der aktive Arbeitsbereich kann nicht gelöscht werden.", "settings.workspaces.delete.blocked.linked": "Dieser Arbeitsbereich hat verknüpfte Sitzungen und kann nicht gelöscht werden.", - "settings.workspaces.section.defaults": "Neue Sitzungen", "settings.workspaces.default.title": "Standardumgebung", "settings.workspaces.default.description": "Wählen Sie, wo neue Sitzungen starten", "settings.workspaces.default.lastUsed": "Zuletzt pro Projekt verwendet", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 1d408726cd6..2f06943c472 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -1077,13 +1077,9 @@ export const dict = { "workspace.new": "New workspace", "common.viewAll": "View all", + "session.new.workspace.local.tooltip": "Use current checkout", + "session.new.workspace.new.tooltip": "Create isolated checkout", "session.new.workspace.fromBranch": "from {{branch}}", - "session.new.workspace.local.description": "Use current checkout", - "session.new.workspace.new.description": "Work in parallel", - "session.new.workspace.existing.description": "Pick from existing", - "session.new.workspace.local.tooltip": "Session runs in your project folder", - "session.new.workspace.new.tooltip": "Creates an isolated copy from your current checkout", - "session.new.workspace.existing.tooltip": "Reuse an existing isolated workspace", "session.new.workspace.trigger.tooltip": "Select where to run session", "session.new.workspace.search.placeholder": "Search workspaces", "settings.tab.workspaces": "Workspaces", @@ -1093,7 +1089,6 @@ export const dict = { "settings.workspaces.count.other": "{{count}} workspaces", "settings.workspaces.sessions.one": "{{count}} session in {{project}}", "settings.workspaces.sessions.other": "{{count}} sessions in {{project}}", - "settings.workspaces.lastActive": "Last active {{time}}", "settings.workspaces.lastActiveSession": "Last active session", "settings.workspaces.deleteAll": "Delete all workspaces", "settings.workspaces.deleteAll.confirm": "Delete all {{count}} workspaces?", @@ -1103,7 +1098,6 @@ export const dict = { "The {{count}} selected workspaces in {{project}} will be permanently removed only if each is clean, inactive, and has no linked sessions.", "settings.workspaces.delete.blocked.active": "The active workspace cannot be deleted.", "settings.workspaces.delete.blocked.linked": "This workspace has linked sessions and cannot be deleted.", - "settings.workspaces.section.defaults": "New sessions", "settings.workspaces.default.title": "Default environment", "settings.workspaces.default.description": "Choose where new sessions start", "settings.workspaces.default.lastUsed": "Last used per project", @@ -1113,6 +1107,9 @@ export const dict = { "workspace.move.failed": "Failed to move session", "workspace.lifecycle.creating": "Creating workspace", "workspace.lifecycle.created": "Workspace created", + "workspace.lifecycle.starting": "Starting session", + "workspace.onboarding.title": "Isolate sessions with workspaces", + "workspace.onboarding.description": "Each gets its own checkout, so nothing interferes with your local repository", "workspace.lifecycle.moving": "Moving to workspace", "workspace.lifecycle.set": "Workspace set", "workspace.type.local": "local", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index f6a935cbc78..ae26683066e 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -994,13 +994,9 @@ export const dict = { "workspace.new": "Nuevo espacio de trabajo", "common.viewAll": "Ver todo", + "session.new.workspace.local.tooltip": "Usar el checkout actual", + "session.new.workspace.new.tooltip": "Crear un checkout aislado", "session.new.workspace.fromBranch": "desde {{branch}}", - "session.new.workspace.local.description": "Usar el checkout actual", - "session.new.workspace.new.description": "Trabajar en paralelo", - "session.new.workspace.existing.description": "Elegir un espacio de trabajo existente", - "session.new.workspace.local.tooltip": "La sesión se ejecuta en la carpeta de tu proyecto", - "session.new.workspace.new.tooltip": "Crea una copia aislada a partir de tu checkout actual", - "session.new.workspace.existing.tooltip": "Reutilizar un espacio de trabajo aislado existente", "session.new.workspace.trigger.tooltip": "Seleccionar dónde ejecutar la sesión", "session.new.workspace.search.placeholder": "Buscar espacios de trabajo", "settings.tab.workspaces": "Espacios de trabajo", @@ -1010,7 +1006,6 @@ export const dict = { "settings.workspaces.count.other": "{{count}} espacios de trabajo", "settings.workspaces.sessions.one": "{{count}} sesión en {{project}}", "settings.workspaces.sessions.other": "{{count}} sesiones en {{project}}", - "settings.workspaces.lastActive": "Última actividad: {{time}}", "settings.workspaces.lastActiveSession": "Última sesión activa", "settings.workspaces.deleteAll": "Eliminar todos los espacios de trabajo", "settings.workspaces.deleteAll.confirm": "¿Eliminar los {{count}} espacios de trabajo?", @@ -1021,7 +1016,6 @@ export const dict = { "settings.workspaces.delete.blocked.active": "El espacio de trabajo activo no se puede eliminar.", "settings.workspaces.delete.blocked.linked": "Este espacio de trabajo tiene sesiones vinculadas y no se puede eliminar.", - "settings.workspaces.section.defaults": "Nuevas sesiones", "settings.workspaces.default.title": "Entorno predeterminado", "settings.workspaces.default.description": "Elige dónde se inician las nuevas sesiones", "settings.workspaces.default.lastUsed": "Último usado en cada proyecto", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index 5e40aefaced..7b73d1aae0b 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -921,13 +921,9 @@ export const dict = { "session.delete.button": "Supprimer la session", "workspace.new": "Nouvel espace de travail", "common.viewAll": "Tout afficher", + "session.new.workspace.local.tooltip": "Utiliser le checkout actuel", + "session.new.workspace.new.tooltip": "Créer un checkout isolé", "session.new.workspace.fromBranch": "depuis {{branch}}", - "session.new.workspace.local.description": "Utiliser le checkout actuel", - "session.new.workspace.new.description": "Travailler en parallèle", - "session.new.workspace.existing.description": "Choisir un espace de travail existant", - "session.new.workspace.local.tooltip": "La session s'exécute dans le dossier de votre projet", - "session.new.workspace.new.tooltip": "Crée une copie isolée à partir de votre checkout actuel", - "session.new.workspace.existing.tooltip": "Réutiliser un espace de travail isolé existant", "session.new.workspace.trigger.tooltip": "Choisir où exécuter la session", "session.new.workspace.search.placeholder": "Rechercher des espaces de travail", "settings.tab.workspaces": "Espaces de travail", @@ -937,7 +933,6 @@ export const dict = { "settings.workspaces.count.other": "{{count}} espaces de travail", "settings.workspaces.sessions.one": "{{count}} session dans {{project}}", "settings.workspaces.sessions.other": "{{count}} sessions dans {{project}}", - "settings.workspaces.lastActive": "Dernière activité : {{time}}", "settings.workspaces.lastActiveSession": "Dernière session active", "settings.workspaces.deleteAll": "Supprimer tous les espaces de travail", "settings.workspaces.deleteAll.confirm": "Supprimer les {{count}} espaces de travail ?", @@ -948,7 +943,6 @@ export const dict = { "settings.workspaces.delete.blocked.active": "L’espace de travail actif ne peut pas être supprimé.", "settings.workspaces.delete.blocked.linked": "Cet espace de travail contient des sessions liées et ne peut pas être supprimé.", - "settings.workspaces.section.defaults": "Nouvelles sessions", "settings.workspaces.default.title": "Environnement par défaut", "settings.workspaces.default.description": "Choisissez où démarrent les nouvelles sessions", "settings.workspaces.default.lastUsed": "Dernier utilisé par projet", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index 69a3569562b..8087701cb08 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -903,13 +903,9 @@ export const dict = { "session.delete.button": "セッションを削除", "workspace.new": "新しいワークスペース", "common.viewAll": "すべて表示", + "session.new.workspace.local.tooltip": "現在のチェックアウトを使用", + "session.new.workspace.new.tooltip": "隔離されたチェックアウトを作成", "session.new.workspace.fromBranch": "{{branch}} から", - "session.new.workspace.local.description": "現在のチェックアウトを使用", - "session.new.workspace.new.description": "並行して作業", - "session.new.workspace.existing.description": "既存のワークスペースから選択", - "session.new.workspace.local.tooltip": "セッションはプロジェクトフォルダで実行されます", - "session.new.workspace.new.tooltip": "現在のチェックアウトから独立したコピーを作成します", - "session.new.workspace.existing.tooltip": "既存の独立したワークスペースを再利用します", "session.new.workspace.trigger.tooltip": "セッションの実行先を選択します", "session.new.workspace.search.placeholder": "ワークスペースを検索", "settings.tab.workspaces": "ワークスペース", @@ -919,7 +915,6 @@ export const dict = { "settings.workspaces.count.other": "{{count}}件のワークスペース", "settings.workspaces.sessions.one": "{{project}}のセッション{{count}}件", "settings.workspaces.sessions.other": "{{project}}のセッション{{count}}件", - "settings.workspaces.lastActive": "最終アクティブ: {{time}}", "settings.workspaces.lastActiveSession": "最後にアクティブだったセッション", "settings.workspaces.deleteAll": "すべてのワークスペースを削除", "settings.workspaces.deleteAll.confirm": "{{count}}件のワークスペースをすべて削除しますか?", @@ -929,7 +924,6 @@ export const dict = { "{{project}} で選択された{{count}}件のワークスペースは、それぞれがクリーンで非アクティブかつリンクされたセッションがない場合にのみ完全に削除されます。", "settings.workspaces.delete.blocked.active": "アクティブなワークスペースは削除できません。", "settings.workspaces.delete.blocked.linked": "このワークスペースにはリンクされたセッションがあるため削除できません。", - "settings.workspaces.section.defaults": "新しいセッション", "settings.workspaces.default.title": "デフォルト環境", "settings.workspaces.default.description": "新しいセッションの開始場所を選択します", "settings.workspaces.default.lastUsed": "プロジェクトごとに前回使用した環境", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index f7cceb21086..284677949fa 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -744,13 +744,9 @@ export const dict = { "session.delete.button": "세션 삭제", "workspace.new": "새 작업 공간", "common.viewAll": "모두 보기", + "session.new.workspace.local.tooltip": "현재 체크아웃 사용", + "session.new.workspace.new.tooltip": "격리된 체크아웃 생성", "session.new.workspace.fromBranch": "{{branch}}에서", - "session.new.workspace.local.description": "현재 체크아웃 사용", - "session.new.workspace.new.description": "병렬로 작업", - "session.new.workspace.existing.description": "기존 작업 공간 중에서 선택", - "session.new.workspace.local.tooltip": "세션은 프로젝트 폴더에서 실행됩니다", - "session.new.workspace.new.tooltip": "현재 체크아웃을 기반으로 격리된 복사본을 만듭니다", - "session.new.workspace.existing.tooltip": "기존의 격리된 작업 공간을 재사용합니다", "session.new.workspace.trigger.tooltip": "세션을 실행할 위치를 선택하세요", "session.new.workspace.search.placeholder": "작업 공간 검색", "settings.tab.workspaces": "작업 공간", @@ -760,7 +756,6 @@ export const dict = { "settings.workspaces.count.other": "작업 공간 {{count}}개", "settings.workspaces.sessions.one": "{{project}}에 세션 {{count}}개", "settings.workspaces.sessions.other": "{{project}}에 세션 {{count}}개", - "settings.workspaces.lastActive": "마지막 활동: {{time}}", "settings.workspaces.lastActiveSession": "마지막 활성 세션", "settings.workspaces.deleteAll": "모든 작업 공간 삭제", "settings.workspaces.deleteAll.confirm": "작업 공간 {{count}}개를 모두 삭제할까요?", @@ -770,7 +765,6 @@ export const dict = { "{{project}}에서 선택한 작업 공간 {{count}}개는 각각 깨끗하고 비활성 상태이며 연결된 세션이 없는 경우에만 영구적으로 삭제됩니다.", "settings.workspaces.delete.blocked.active": "활성 작업 공간은 삭제할 수 없습니다.", "settings.workspaces.delete.blocked.linked": "이 작업 공간에는 연결된 세션이 있어 삭제할 수 없습니다.", - "settings.workspaces.section.defaults": "새 세션", "settings.workspaces.default.title": "기본 환경", "settings.workspaces.default.description": "새 세션을 시작할 위치를 선택하세요", "settings.workspaces.default.lastUsed": "프로젝트별 마지막 사용 환경", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index 822e7faaddf..4cbaac431dd 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -831,13 +831,9 @@ export const dict = { "workspace.new": "Nytt arbeidsområde", "common.viewAll": "Vis alle", + "session.new.workspace.local.tooltip": "Bruk gjeldende checkout", + "session.new.workspace.new.tooltip": "Opprett isolert checkout", "session.new.workspace.fromBranch": "fra {{branch}}", - "session.new.workspace.local.description": "Bruk gjeldende checkout", - "session.new.workspace.new.description": "Arbeid parallelt", - "session.new.workspace.existing.description": "Velg et eksisterende arbeidsområde", - "session.new.workspace.local.tooltip": "Sesjonen kjører i prosjektmappen din", - "session.new.workspace.new.tooltip": "Oppretter en isolert kopi fra checkouten du bruker nå", - "session.new.workspace.existing.tooltip": "Gjenbruk et eksisterende isolert arbeidsområde", "session.new.workspace.trigger.tooltip": "Velg hvor sesjonen skal kjøres", "session.new.workspace.search.placeholder": "Søk i arbeidsområder", "settings.tab.workspaces": "Arbeidsområder", @@ -847,7 +843,6 @@ export const dict = { "settings.workspaces.count.other": "{{count}} arbeidsområder", "settings.workspaces.sessions.one": "{{count}} økt i {{project}}", "settings.workspaces.sessions.other": "{{count}} økter i {{project}}", - "settings.workspaces.lastActive": "Sist aktiv: {{time}}", "settings.workspaces.lastActiveSession": "Sist aktive økt", "settings.workspaces.deleteAll": "Slett alle arbeidsområder", "settings.workspaces.deleteAll.confirm": "Slette alle {{count}} arbeidsområder?", @@ -857,7 +852,6 @@ export const dict = { "De {{count}} valgte arbeidsområdene i {{project}} fjernes permanent bare hvis hvert område er rent, inaktivt og ikke har tilknyttede økter.", "settings.workspaces.delete.blocked.active": "Det aktive arbeidsområdet kan ikke slettes.", "settings.workspaces.delete.blocked.linked": "Dette arbeidsområdet har tilknyttede økter og kan ikke slettes.", - "settings.workspaces.section.defaults": "Nye økter", "settings.workspaces.default.title": "Standardmiljø", "settings.workspaces.default.description": "Velg hvor nye økter starter", "settings.workspaces.default.lastUsed": "Sist brukt per prosjekt", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index d45b89c2a05..957221093fa 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -909,13 +909,9 @@ export const dict = { "session.delete.button": "Usuń sesję", "workspace.new": "Nowa przestrzeń robocza", "common.viewAll": "Pokaż wszystko", + "session.new.workspace.local.tooltip": "Użyj bieżącego checkoutu", + "session.new.workspace.new.tooltip": "Utwórz izolowany checkout", "session.new.workspace.fromBranch": "z {{branch}}", - "session.new.workspace.local.description": "Użyj bieżącego checkoutu", - "session.new.workspace.new.description": "Pracuj równolegle", - "session.new.workspace.existing.description": "Wybierz istniejącą przestrzeń roboczą", - "session.new.workspace.local.tooltip": "Sesja działa w folderze Twojego projektu", - "session.new.workspace.new.tooltip": "Tworzy izolowaną kopię bieżącego checkoutu", - "session.new.workspace.existing.tooltip": "Użyj ponownie istniejącej izolowanej przestrzeni roboczej", "session.new.workspace.trigger.tooltip": "Wybierz, gdzie uruchomić sesję", "session.new.workspace.search.placeholder": "Szukaj przestrzeni roboczych", "settings.tab.workspaces": "Obszary robocze", @@ -925,7 +921,6 @@ export const dict = { "settings.workspaces.count.other": "{{count}} obszarów roboczych", "settings.workspaces.sessions.one": "{{count}} sesja w {{project}}", "settings.workspaces.sessions.other": "{{count}} sesji w {{project}}", - "settings.workspaces.lastActive": "Ostatnia aktywność: {{time}}", "settings.workspaces.lastActiveSession": "Ostatnia aktywna sesja", "settings.workspaces.deleteAll": "Usuń wszystkie obszary robocze", "settings.workspaces.deleteAll.confirm": "Usunąć wszystkie obszary robocze ({{count}})?", @@ -935,7 +930,6 @@ export const dict = { "Wybrane obszary robocze ({{count}}) w {{project}} zostaną trwale usunięte tylko wtedy, gdy każdy jest czysty, nieaktywny i nie ma powiązanych sesji.", "settings.workspaces.delete.blocked.active": "Nie można usunąć aktywnego obszaru roboczego.", "settings.workspaces.delete.blocked.linked": "Ten obszar roboczy ma powiązane sesje i nie można go usunąć.", - "settings.workspaces.section.defaults": "Nowe sesje", "settings.workspaces.default.title": "Domyślne środowisko", "settings.workspaces.default.description": "Wybierz, gdzie mają się rozpoczynać nowe sesje", "settings.workspaces.default.lastUsed": "Ostatnio używane w każdym projekcie", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index 0e588f87d38..1abf215dcbe 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -989,13 +989,9 @@ export const dict = { "workspace.new": "Новое рабочее пространство", "common.viewAll": "Показать все", + "session.new.workspace.local.tooltip": "Использовать текущую рабочую копию", + "session.new.workspace.new.tooltip": "Создать изолированную копию", "session.new.workspace.fromBranch": "из {{branch}}", - "session.new.workspace.local.description": "Использовать текущую рабочую копию", - "session.new.workspace.new.description": "Работать параллельно", - "session.new.workspace.existing.description": "Выбрать существующее рабочее пространство", - "session.new.workspace.local.tooltip": "Сессия запускается в папке вашего проекта", - "session.new.workspace.new.tooltip": "Создаёт изолированную копию на основе вашей текущей рабочей копии", - "session.new.workspace.existing.tooltip": "Повторно использовать существующее изолированное рабочее пространство", "session.new.workspace.trigger.tooltip": "Выбрать, где запустить сессию", "session.new.workspace.search.placeholder": "Поиск рабочих пространств", "settings.tab.workspaces": "Рабочие пространства", @@ -1005,7 +1001,6 @@ export const dict = { "settings.workspaces.count.other": "{{count}} рабочих пространств", "settings.workspaces.sessions.one": "{{count}} сессия в {{project}}", "settings.workspaces.sessions.other": "{{count}} сессий в {{project}}", - "settings.workspaces.lastActive": "Последняя активность: {{time}}", "settings.workspaces.lastActiveSession": "Последний активный сеанс", "settings.workspaces.deleteAll": "Удалить все рабочие пространства", "settings.workspaces.deleteAll.confirm": "Удалить все рабочие пространства ({{count}})?", @@ -1016,7 +1011,6 @@ export const dict = { "settings.workspaces.delete.blocked.active": "Активное рабочее пространство нельзя удалить.", "settings.workspaces.delete.blocked.linked": "У этого рабочего пространства есть связанные сеансы, поэтому его нельзя удалить.", - "settings.workspaces.section.defaults": "Новые сеансы", "settings.workspaces.default.title": "Среда по умолчанию", "settings.workspaces.default.description": "Выберите, где будут запускаться новые сеансы", "settings.workspaces.default.lastUsed": "Последняя использованная среда проекта", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index f60332dff0b..20b59902bd4 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -973,13 +973,9 @@ export const dict = { "workspace.new": "พื้นที่ทำงานใหม่", "common.viewAll": "ดูทั้งหมด", + "session.new.workspace.local.tooltip": "ใช้ checkout ปัจจุบัน", + "session.new.workspace.new.tooltip": "สร้าง checkout ที่แยกอิสระ", "session.new.workspace.fromBranch": "จาก {{branch}}", - "session.new.workspace.local.description": "ใช้ checkout ปัจจุบัน", - "session.new.workspace.new.description": "ทำงานแบบคู่ขนาน", - "session.new.workspace.existing.description": "เลือกพื้นที่ทำงานที่มีอยู่", - "session.new.workspace.local.tooltip": "เซสชันทำงานในโฟลเดอร์โปรเจกต์ของคุณ", - "session.new.workspace.new.tooltip": "สร้างสำเนาที่แยกออกมาจาก checkout ปัจจุบันของคุณ", - "session.new.workspace.existing.tooltip": "นำพื้นที่ทำงานที่แยกไว้แล้วกลับมาใช้", "session.new.workspace.trigger.tooltip": "เลือกตำแหน่งที่จะเรียกใช้เซสชัน", "session.new.workspace.search.placeholder": "ค้นหาพื้นที่ทำงาน", "settings.tab.workspaces": "พื้นที่ทำงาน", @@ -989,7 +985,6 @@ export const dict = { "settings.workspaces.count.other": "{{count}} พื้นที่ทำงาน", "settings.workspaces.sessions.one": "{{count}} เซสชันใน {{project}}", "settings.workspaces.sessions.other": "{{count}} เซสชันใน {{project}}", - "settings.workspaces.lastActive": "ใช้งานล่าสุด: {{time}}", "settings.workspaces.lastActiveSession": "เซสชันที่ใช้งานล่าสุด", "settings.workspaces.deleteAll": "ลบพื้นที่ทำงานทั้งหมด", "settings.workspaces.deleteAll.confirm": "ลบพื้นที่ทำงานทั้งหมด {{count}} รายการหรือไม่", @@ -999,7 +994,6 @@ export const dict = { "พื้นที่ทำงานที่เลือก {{count}} รายการใน {{project}} จะถูกลบอย่างถาวรเมื่อแต่ละรายการสะอาด ไม่ได้ใช้งาน และไม่มีเซสชันที่เชื่อมโยงเท่านั้น", "settings.workspaces.delete.blocked.active": "ไม่สามารถลบพื้นที่ทำงานที่กำลังใช้งานอยู่ได้", "settings.workspaces.delete.blocked.linked": "พื้นที่ทำงานนี้มีเซสชันที่เชื่อมโยงอยู่และไม่สามารถลบได้", - "settings.workspaces.section.defaults": "เซสชันใหม่", "settings.workspaces.default.title": "สภาพแวดล้อมเริ่มต้น", "settings.workspaces.default.description": "เลือกตำแหน่งเริ่มต้นของเซสชันใหม่", "settings.workspaces.default.lastUsed": "ใช้ล่าสุดในแต่ละโปรเจกต์", diff --git a/packages/app/src/i18n/tr.ts b/packages/app/src/i18n/tr.ts index 7a8b75e603b..d2662a5dd5a 100644 --- a/packages/app/src/i18n/tr.ts +++ b/packages/app/src/i18n/tr.ts @@ -993,13 +993,9 @@ export const dict = { "workspace.new": "Yeni çalışma alanı", "common.viewAll": "Tümünü göster", + "session.new.workspace.local.tooltip": "Mevcut çalışma kopyasını kullan", + "session.new.workspace.new.tooltip": "Yalıtılmış çalışma kopyası oluştur", "session.new.workspace.fromBranch": "{{branch}} dalından", - "session.new.workspace.local.description": "Mevcut çalışma kopyasını kullan", - "session.new.workspace.new.description": "Paralel çalış", - "session.new.workspace.existing.description": "Mevcut bir çalışma alanını seç", - "session.new.workspace.local.tooltip": "Oturum proje klasörünüzde çalışır", - "session.new.workspace.new.tooltip": "Mevcut çalışma kopyanızdan yalıtılmış bir kopya oluşturur", - "session.new.workspace.existing.tooltip": "Mevcut yalıtılmış bir çalışma alanını yeniden kullan", "session.new.workspace.trigger.tooltip": "Oturumun nerede çalıştırılacağını seç", "session.new.workspace.search.placeholder": "Çalışma alanlarında ara", "settings.tab.workspaces": "Çalışma alanları", @@ -1009,7 +1005,6 @@ export const dict = { "settings.workspaces.count.other": "{{count}} çalışma alanı", "settings.workspaces.sessions.one": "{{project}} projesinde {{count}} oturum", "settings.workspaces.sessions.other": "{{project}} projesinde {{count}} oturum", - "settings.workspaces.lastActive": "Son etkinlik: {{time}}", "settings.workspaces.lastActiveSession": "Son etkin oturum", "settings.workspaces.deleteAll": "Tüm çalışma alanlarını sil", "settings.workspaces.deleteAll.confirm": "{{count}} çalışma alanının tümü silinsin mi?", @@ -1019,7 +1014,6 @@ export const dict = { "{{project}} içindeki seçili {{count}} çalışma alanı, yalnızca her biri temiz, etkin değil ve bağlantılı oturumu yoksa kalıcı olarak kaldırılacak.", "settings.workspaces.delete.blocked.active": "Etkin çalışma alanı silinemez.", "settings.workspaces.delete.blocked.linked": "Bu çalışma alanında bağlantılı oturumlar var ve silinemez.", - "settings.workspaces.section.defaults": "Yeni oturumlar", "settings.workspaces.default.title": "Varsayılan ortam", "settings.workspaces.default.description": "Yeni oturumların nerede başlayacağını seçin", "settings.workspaces.default.lastUsed": "Proje başına son kullanılan", diff --git a/packages/app/src/i18n/uk.ts b/packages/app/src/i18n/uk.ts index 599cb4ea488..61cc8b389ca 100644 --- a/packages/app/src/i18n/uk.ts +++ b/packages/app/src/i18n/uk.ts @@ -1080,13 +1080,9 @@ export const dict = { "workspace.new": "Нова робоча область", "common.viewAll": "Показати все", + "session.new.workspace.local.tooltip": "Використати поточну робочу копію", + "session.new.workspace.new.tooltip": "Створити ізольовану копію", "session.new.workspace.fromBranch": "з {{branch}}", - "session.new.workspace.local.description": "Використати поточну робочу копію", - "session.new.workspace.new.description": "Працювати паралельно", - "session.new.workspace.existing.description": "Вибрати наявну робочу область", - "session.new.workspace.local.tooltip": "Сесія запускається в папці вашого проєкту", - "session.new.workspace.new.tooltip": "Створює ізольовану копію на основі вашої поточної робочої копії", - "session.new.workspace.existing.tooltip": "Повторно використати наявну ізольовану робочу область", "session.new.workspace.trigger.tooltip": "Вибрати, де запустити сесію", "session.new.workspace.search.placeholder": "Пошук робочих областей", "settings.tab.workspaces": "Робочі простори", @@ -1096,7 +1092,6 @@ export const dict = { "settings.workspaces.count.other": "{{count}} робочих просторів", "settings.workspaces.sessions.one": "{{count}} сесія в {{project}}", "settings.workspaces.sessions.other": "{{count}} сесій у {{project}}", - "settings.workspaces.lastActive": "Остання активність: {{time}}", "settings.workspaces.lastActiveSession": "Останній активний сеанс", "settings.workspaces.deleteAll": "Видалити всі робочі простори", "settings.workspaces.deleteAll.confirm": "Видалити всі робочі простори ({{count}})?", @@ -1106,7 +1101,6 @@ export const dict = { "Вибрані робочі простори ({{count}}) у {{project}} буде видалено назавжди, лише якщо кожен із них чистий, неактивний і не має пов’язаних сеансів.", "settings.workspaces.delete.blocked.active": "Активний робочий простір не можна видалити.", "settings.workspaces.delete.blocked.linked": "Цей робочий простір має пов’язані сеанси, тому його не можна видалити.", - "settings.workspaces.section.defaults": "Нові сеанси", "settings.workspaces.default.title": "Середовище за замовчуванням", "settings.workspaces.default.description": "Виберіть, де починатимуться нові сеанси", "settings.workspaces.default.lastUsed": "Останнє використане для кожного проєкту", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 333f943ec40..e637fa4108f 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -966,13 +966,9 @@ export const dict = { "workspace.new": "新建工作区", "common.viewAll": "查看全部", + "session.new.workspace.local.tooltip": "使用当前工作副本", + "session.new.workspace.new.tooltip": "创建隔离副本", "session.new.workspace.fromBranch": "从 {{branch}}", - "session.new.workspace.local.description": "使用当前工作副本", - "session.new.workspace.new.description": "并行工作", - "session.new.workspace.existing.description": "从现有工作区中选择", - "session.new.workspace.local.tooltip": "会话在你的项目文件夹中运行", - "session.new.workspace.new.tooltip": "基于你当前的工作副本创建隔离副本", - "session.new.workspace.existing.tooltip": "复用现有的隔离工作区", "session.new.workspace.trigger.tooltip": "选择会话的运行位置", "session.new.workspace.search.placeholder": "搜索工作区", "settings.tab.workspaces": "工作区", @@ -982,7 +978,6 @@ export const dict = { "settings.workspaces.count.other": "{{count}} 个工作区", "settings.workspaces.sessions.one": "{{project}} 中有 {{count}} 个会话", "settings.workspaces.sessions.other": "{{project}} 中有 {{count}} 个会话", - "settings.workspaces.lastActive": "上次活跃:{{time}}", "settings.workspaces.lastActiveSession": "最近活跃的会话", "settings.workspaces.deleteAll": "删除所有工作区", "settings.workspaces.deleteAll.confirm": "删除全部 {{count}} 个工作区?", @@ -992,7 +987,6 @@ export const dict = { "仅当 {{project}} 中选定的 {{count}} 个工作区均干净、未处于活动状态且没有关联会话时,才会永久删除它们。", "settings.workspaces.delete.blocked.active": "无法删除当前活动的工作区。", "settings.workspaces.delete.blocked.linked": "此工作区有关联会话,无法删除。", - "settings.workspaces.section.defaults": "新会话", "settings.workspaces.default.title": "默认环境", "settings.workspaces.default.description": "选择新会话的启动位置", "settings.workspaces.default.lastUsed": "每个项目上次使用的环境", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 376e97b3a41..82239c5af3a 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -962,13 +962,9 @@ export const dict = { "workspace.new": "新增工作區", "common.viewAll": "查看全部", + "session.new.workspace.local.tooltip": "使用目前的工作複本", + "session.new.workspace.new.tooltip": "建立隔離複本", "session.new.workspace.fromBranch": "從 {{branch}}", - "session.new.workspace.local.description": "使用目前的工作複本", - "session.new.workspace.new.description": "平行作業", - "session.new.workspace.existing.description": "從現有工作區中選擇", - "session.new.workspace.local.tooltip": "工作階段會在你的專案資料夾中執行", - "session.new.workspace.new.tooltip": "根據你目前的工作複本建立隔離複本", - "session.new.workspace.existing.tooltip": "重新使用現有的隔離工作區", "session.new.workspace.trigger.tooltip": "選擇工作階段的執行位置", "session.new.workspace.search.placeholder": "搜尋工作區", "settings.tab.workspaces": "工作區", @@ -978,7 +974,6 @@ export const dict = { "settings.workspaces.count.other": "{{count}} 個工作區", "settings.workspaces.sessions.one": "{{project}} 中有 {{count}} 個工作階段", "settings.workspaces.sessions.other": "{{project}} 中有 {{count}} 個工作階段", - "settings.workspaces.lastActive": "上次活動:{{time}}", "settings.workspaces.lastActiveSession": "最近活動的工作階段", "settings.workspaces.deleteAll": "刪除所有工作區", "settings.workspaces.deleteAll.confirm": "刪除全部 {{count}} 個工作區?", @@ -988,7 +983,6 @@ export const dict = { "只有在 {{project}} 中選取的 {{count}} 個工作區都乾淨、未處於活動狀態且沒有關聯工作階段時,才會永久刪除它們。", "settings.workspaces.delete.blocked.active": "無法刪除目前作用中的工作區。", "settings.workspaces.delete.blocked.linked": "此工作區有關聯工作階段,無法刪除。", - "settings.workspaces.section.defaults": "新工作階段", "settings.workspaces.default.title": "預設環境", "settings.workspaces.default.description": "選擇新工作階段的啟動位置", "settings.workspaces.default.lastUsed": "每個專案上次使用的環境", diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index ee09a6a3cf9..62cd1691641 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -1119,7 +1119,7 @@ export default function LegacyLayout(props: ParentProps) { : import("@/components/dialog-settings") void module.then((x) => { if (dialogDead || dialogRun !== run) return - dialog.show(() => ) + dialog.show(() => ) }) } diff --git a/packages/app/src/pages/new-session/new-session-view.tsx b/packages/app/src/pages/new-session/new-session-view.tsx index f5eff506c54..2668f49186e 100644 --- a/packages/app/src/pages/new-session/new-session-view.tsx +++ b/packages/app/src/pages/new-session/new-session-view.tsx @@ -31,6 +31,19 @@ export function NewSessionView(props: { project: PromptProjectController workspace: NewSessionWorkspaceController }) { + const language = useLanguage() + const [onboarding, setOnboarding, , onboardingReady] = persisted( + Persist.global("workspace-onboarding"), + createStore({ cardDismissed: false, used: false }), + ) + const showCard = createMemo( + () => onboardingReady() && props.workspace.bar.visible() && !onboarding.cardDismissed && !onboarding.used, + ) + const select = (value: string) => { + props.workspace.selection.set(value) + setOnboarding("used", true) + } + return (
@@ -69,6 +83,27 @@ export function NewSessionView(props: {
+ +
+
+
+ + {language.t("workspace.onboarding.title")} + +
+

+ {language.t("workspace.onboarding.description")} +

+
+
+
diff --git a/packages/app/src/pages/new-session/new-session-workspace-controller.ts b/packages/app/src/pages/new-session/new-session-workspace-controller.ts index ab7bc61a368..efcda27b6ac 100644 --- a/packages/app/src/pages/new-session/new-session-workspace-controller.ts +++ b/packages/app/src/pages/new-session/new-session-workspace-controller.ts @@ -5,7 +5,7 @@ import { useServerSync } from "@/context/server-sync" import { useSettings } from "@/context/settings" import { useSync } from "@/context/sync" import { pathKey } from "@/utils/path-key" -import { isWorkspaceSelection, workspaceDefaultSelection } from "@/utils/workspace" +import { isWorkspaceSelection, workspaceDefaultSelection, workspaceDirectories } from "@/utils/workspace" export function resolveNewSessionWorktree(input: { enabled: boolean @@ -96,7 +96,10 @@ export function createNewSessionWorkspaceController(input: { }, project: { root: projectRoot, - workspaces: () => sync().project?.sandboxes ?? [], + workspaces: () => { + const project = sync().project + return project ? workspaceDirectories(project) : [] + }, git: () => sync().project?.vcs === "git", openAll: input.onViewAll, }, diff --git a/packages/app/src/pages/session/timeline/message-timeline.tsx b/packages/app/src/pages/session/timeline/message-timeline.tsx index 4ebc314f419..528481b4d87 100644 --- a/packages/app/src/pages/session/timeline/message-timeline.tsx +++ b/packages/app/src/pages/session/timeline/message-timeline.tsx @@ -271,8 +271,8 @@ function WorkspaceMoveAction(props: {
@@ -286,16 +286,20 @@ function WorkspaceMoveAction(props: { gutter={inline() ? 4 : -2} class={ inline() - ? "flex h-6 w-full items-center gap-1.5 rounded-[4px] px-1.5 pr-7 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-faint hover:bg-v2-overlay-simple-overlay-hover focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:outline-none data-[expanded]:bg-v2-overlay-simple-overlay-pressed" - : "flex h-[46px] w-full items-center gap-1.5 rounded-b-[6px] px-3 pr-9 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-base hover:bg-v2-overlay-simple-overlay-hover focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:outline-none data-[expanded]:bg-v2-overlay-simple-overlay-pressed" + ? "flex h-5 w-full items-center gap-1.5 rounded-[4px] pr-6 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-faint hover:bg-v2-overlay-simple-overlay-hover focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:outline-none data-[expanded]:bg-v2-overlay-simple-overlay-pressed" + : "flex h-[46px] w-full items-center gap-1.5 rounded-b-[6px] px-3 pr-9 pt-[10px] text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-muted focus-visible:outline-none data-[expanded]:bg-v2-background-bg-layer-03" } > - + {language.t("workspace.move.title")}
diff --git a/packages/app/src/utils/workspace.test.ts b/packages/app/src/utils/workspace.test.ts index 9025569f823..8c926fe53bd 100644 --- a/packages/app/src/utils/workspace.test.ts +++ b/packages/app/src/utils/workspace.test.ts @@ -11,6 +11,7 @@ import { runWorkspaceDeleteTransaction, sessionsForWorkspace, workspaceDefaultSelection, + workspaceDirectories, workspaceInventory, } from "./workspace" @@ -73,7 +74,7 @@ describe("isProjectDirectory", () => { test("groups and filters workspace inventory by project", () => { const inventory = workspaceInventory([ - { id: "a", worktree: "/a", sandboxes: ["/a/one", "/a/two"] }, + { id: "a", worktree: "/a", sandboxes: ["/a", "/a/one", "/a/two"] }, { id: "b", worktree: "/b", sandboxes: ["/b/one"] }, ]) @@ -86,6 +87,12 @@ test("groups and filters workspace inventory by project", () => { expect(filterWorkspaceInventory(inventory, "all")).toEqual(inventory) }) +test("excludes the project checkout from workspace directories", () => { + expect(workspaceDirectories({ worktree: "C:\\repo", sandboxes: ["c:\\repo\\", "C:\\workspaces\\one"] })).toEqual([ + "C:\\workspaces\\one", + ]) +}) + test("deletes all workspaces sequentially", async () => { const calls: string[] = [] let release = () => {} @@ -131,6 +138,21 @@ test("blocks unsafe workspace deletion", () => { status: "clean", }), ).toBe("linked") + expect( + inspectWorkspaceDeletion({ + workspace: "/workspace", + activeDirectory: "/workspace/app", + sessions: [], + status: "dirty", + }), + ).toBe("active") + expect( + inspectWorkspaceDeletion({ + workspace: "/workspace", + sessions: [session("/workspace/packages/app")], + status: "dirty", + }), + ).toBe("linked") expect(inspectWorkspaceDeletion({ workspace: "/workspace", sessions: [], status: "dirty" })).toBe("dirty") expect(inspectWorkspaceDeletion({ workspace: "/workspace", sessions: [], status: "unknown" })).toBe("unknown") expect(inspectWorkspaceDeletion({ workspace: "/workspace", sessions: [], status: "clean" })).toBe("safe") diff --git a/packages/app/src/utils/workspace.ts b/packages/app/src/utils/workspace.ts index 7ab5950ee96..38c5890ba1b 100644 --- a/packages/app/src/utils/workspace.ts +++ b/packages/app/src/utils/workspace.ts @@ -4,8 +4,14 @@ import type { Session } from "@opencode-ai/sdk/v2/client" type WorkspaceProject = { worktree: string; sandboxes?: readonly string[] } +export function workspaceDirectories(project: WorkspaceProject) { + return (project.sandboxes ?? []).filter( + (directory) => !containsDirectory(project.worktree, directory) || !containsDirectory(directory, project.worktree), + ) +} + export function workspaceInventory(projects: readonly T[]) { - return projects.flatMap((project) => (project.sandboxes ?? []).map((directory) => ({ directory, project }))) + return projects.flatMap((project) => workspaceDirectories(project).map((directory) => ({ directory, project }))) } export function filterWorkspaceInventory( @@ -66,16 +72,16 @@ export function inspectWorkspaceDeletion(input: { sessions: readonly Session[] status: "clean" | "dirty" | "unknown" }): WorkspaceDeleteInspection { - if (input.status === "unknown") return "unknown" - if (input.status === "dirty") return "dirty" if (input.activeDirectory && containsDirectory(input.workspace, input.activeDirectory)) return "active" if (input.sessions.some((session) => containsDirectory(input.workspace, session.directory))) return "linked" + if (input.status === "unknown") return "unknown" + if (input.status === "dirty") return "dirty" return "safe" } export function isWorkspaceDirectory(project: WorkspaceProject | undefined, directory: string) { if (!project || containsDirectory(project.worktree, directory)) return false - return project.sandboxes?.some((workspace) => containsDirectory(workspace, directory)) ?? false + return workspaceDirectories(project).some((workspace) => containsDirectory(workspace, directory)) } export function isProjectDirectory(project: WorkspaceProject | undefined, directory: string) { diff --git a/packages/ui/src/v2/components/dialog-v2.css b/packages/ui/src/v2/components/dialog-v2.css index b74d2723972..afa3d5b606a 100644 --- a/packages/ui/src/v2/components/dialog-v2.css +++ b/packages/ui/src/v2/components/dialog-v2.css @@ -93,6 +93,9 @@ [data-slot="dialog-description"] { flex: none; flex-grow: 0; + min-width: 0; + max-width: 100%; + overflow-wrap: anywhere; user-select: none; font-weight: 440; font-size: 13px;