diff --git a/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts b/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts index d12e0bfc92d0..ff09c72327fd 100644 --- a/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts +++ b/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts @@ -231,7 +231,7 @@ test("vertical tabs show project details, resize, and navigate", async ({ page } await expect(tabB).toBeVisible() }) -test("appearance experimental settings control vertical tab details", async ({ page }) => { +test("dedicated experimental settings control vertical tab details", async ({ page }) => { await mockServer(page) await page.addInitScript( ({ server, sessionA }) => { @@ -253,7 +253,11 @@ test("appearance experimental settings control vertical tab details", async ({ p await expect(settings.getByRole("tablist").getByText("OpenCode Desktop", { exact: true })).toBeInViewport() await expect(version).toBeInViewport() await settings.getByRole("tab", { name: "Appearance" }).click() - await expect(settings.getByRole("heading", { name: "Experimental" })).toBeVisible() + await expect(settings.getByRole("heading", { name: "Appearance", exact: true })).toBeVisible() + await expect(settings.locator('[data-action="settings-tab-layout"]')).toHaveCount(0) + await expect(settings.getByRole("switch", { name: "Show project names", exact: true })).toHaveCount(0) + await settings.getByRole("tab", { name: "Experimental", exact: true }).click() + await expect(settings.getByRole("heading", { name: "Experimental", level: 2, exact: true })).toBeVisible() const layout = settings.locator('[data-action="settings-tab-layout"]') await expect(layout).toContainText("Horizontal") @@ -274,19 +278,27 @@ test("appearance experimental settings control vertical tab details", async ({ p await page.setViewportSize({ width: 920, height: 720 }) await expect(page.locator('[data-slot="vertical-tabs-sidebar"]')).toHaveCSS("width", "260px") await expect(settings.getByRole("tablist")).toBeHidden() - await expect(settings.getByRole("button", { name: "Appearance", exact: true })).toBeVisible() + await expect(settings.getByRole("button", { name: "Experimental", exact: true })).toBeVisible() await page.setViewportSize({ width: 800, height: 720 }) await expect(settings.getByRole("tablist")).toBeHidden() - await expect(settings.getByRole("button", { name: "Appearance", exact: true })).toBeVisible() + await expect(settings.getByRole("button", { name: "Experimental", exact: true })).toBeVisible() await page.setViewportSize({ width: 390, height: 720 }) - await expect(settings.getByRole("button", { name: "Appearance", exact: true })).toBeVisible() + await settings.getByRole("button", { name: "Experimental", exact: true }).click() + await page.getByRole("menuitemradio", { name: "Appearance", exact: true }).click() + await expect(settings.getByRole("heading", { name: "Appearance", exact: true })).toBeVisible() + await expect(layout).toHaveCount(0) + await settings.getByRole("button", { name: "Appearance", exact: true }).click() + await page.getByRole("menuitemradio", { name: "Experimental", exact: true }).click() + await expect(settings.getByRole("heading", { name: "Experimental", level: 2, exact: true })).toBeVisible() + await expect(layout).toContainText("Vertical") + await expect(projectNameSwitch).toBeChecked() await settings.evaluate((element) => element.setAttribute("dir", "rtl")) - await expect(settings.getByRole("button", { name: "Appearance", exact: true })).toBeInViewport() + await expect(settings.getByRole("button", { name: "Experimental", exact: true })).toBeInViewport() await page.setViewportSize({ width: 390, height: 360 }) - await expect(settings.getByRole("button", { name: "Appearance", exact: true })).toBeInViewport() + await expect(settings.getByRole("button", { name: "Experimental", exact: true })).toBeInViewport() // Reload the UI-selected preference without seeding settings storage. await page.reload() @@ -310,7 +322,7 @@ test("appearance experimental settings control vertical tab details", async ({ p ).toBeVisible() await expect(page.locator('[data-slot="titlebar-tabs"]')).toHaveCount(0) await page.keyboard.press("Control+,") - await settings.getByRole("tab", { name: "Appearance" }).click() + await settings.getByRole("tab", { name: "Experimental", exact: true }).click() await expect(layout).toContainText("Vertical") }) diff --git a/packages/app/src/runtime/i18n/en.ts b/packages/app/src/runtime/i18n/en.ts index fe644ac5f134..d1ffe173b26a 100644 --- a/packages/app/src/runtime/i18n/en.ts +++ b/packages/app/src/runtime/i18n/en.ts @@ -904,6 +904,8 @@ export const dict = { "settings.tab.notifications": "Notifications", "settings.tab.projects": "Projects", "settings.tab.extensions": "Extensions", + "settings.tab.experimental": "Experimental", + "settings.experimental.description": "Try experimental features", "settings.preferences.description": "Customize preferences and theme and default behavior", "settings.appearance.description": "Customize theme and fonts", "settings.appearance.section.experimental": "Experimental", diff --git a/packages/app/src/settings/appearance/appearance.tsx b/packages/app/src/settings/appearance/appearance.tsx index 1f5048e3f560..586f728d4736 100644 --- a/packages/app/src/settings/appearance/appearance.tsx +++ b/packages/app/src/settings/appearance/appearance.tsx @@ -1,7 +1,6 @@ -import { Component, createMemo } from "solid-js" +import { Component } from "solid-js" import { Select } from "@opencode-ai/ui/select" import { TextInput } from "@opencode-ai/ui/text-input" -import { Switch } from "@opencode-ai/ui/switch" import { useLanguage } from "@/runtime/i18n/language" import { ExternalLink } from "@/runtime/platform/external-link" import { SettingsList } from "@/settings/list" @@ -10,7 +9,6 @@ import { createAppearanceSettingsController, type AppearanceSettingsController } import "@/settings/settings.css" const schemeOptions: ("system" | "light" | "dark")[] = ["system", "light", "dark"] -const tabLayoutOptions: ("horizontal" | "vertical")[] = ["horizontal", "vertical"] const fontSettings = { ui: { action: "settings-ui-font", @@ -128,44 +126,6 @@ export const SettingsAppearance: Component = () => { - -
-

{language.t("settings.appearance.section.experimental")}

- - - option === settings.appearance.tabLayout())} + placement="bottom-end" + gutter={6} + label={(option) => + option === "horizontal" + ? language.t("settings.appearance.row.tabs.horizontal") + : language.t("settings.appearance.row.tabs.vertical") + } + onSelect={(option) => option && settings.appearance.setTabLayout(option)} + /> + + +
+ + {language.t("settings.appearance.row.projectName.title")} + +
+
+
+
+ + + ) +} diff --git a/packages/app/src/settings/general/controllers.ts b/packages/app/src/settings/general/controllers.ts index a1973ed4cde5..c67e3ce8d472 100644 --- a/packages/app/src/settings/general/controllers.ts +++ b/packages/app/src/settings/general/controllers.ts @@ -81,14 +81,6 @@ export function createAppearanceSettingsController() { setCode: (value: string) => settings.appearance.setFont(value), setTerminal: (value: string) => settings.appearance.setTerminalFont(value), }, - tabs: { - current: settings.appearance.tabLayout, - select: settings.appearance.setTabLayout, - }, - projectName: { - current: settings.appearance.showProjectName, - set: settings.appearance.setShowProjectName, - }, } } diff --git a/packages/app/src/settings/shell.tsx b/packages/app/src/settings/shell.tsx index 372c66ba6700..d49c2ce9c8d9 100644 --- a/packages/app/src/settings/shell.tsx +++ b/packages/app/src/settings/shell.tsx @@ -17,6 +17,7 @@ import { useLanguage } from "@/runtime/i18n/language" import { usePlatform } from "@/runtime/platform/platform" import { SettingsGeneral } from "./general/general" import { SettingsAppearance } from "./appearance/appearance" +import { SettingsExperimental } from "./experimental/experimental" import { SettingsKeybinds } from "./keybinds/keybinds" import { SettingsNotifications } from "./notifications/notifications" import { SettingsProviders } from "./providers/providers" @@ -52,6 +53,7 @@ const sections = [ { value: "models", icon: "models", label: "settings.models.title" }, { value: "extensions", icon: "extensions", label: "settings.tab.extensions" }, ], + [{ value: "experimental", icon: "flask", label: "settings.tab.experimental" }], ] as const export const SettingsScreen: Component<{ @@ -216,6 +218,9 @@ export const SettingsScreen: Component<{ + + + diff --git a/packages/ui/src/icons/icon/icon.tsx b/packages/ui/src/icons/icon/icon.tsx index eaf0fc29646c..073f45c51e55 100644 --- a/packages/ui/src/icons/icon/icon.tsx +++ b/packages/ui/src/icons/icon/icon.tsx @@ -4,6 +4,10 @@ import "./icon.css" // Consumers center the SVG viewport, so each icon must center its artwork within its viewBox. const icons = { + flask: { + viewBox: "0 0 16 16", + body: ``, + }, edit: { viewBox: "0 0 16 16", body: ``,