Compare commits

...
Author SHA1 Message Date
David Hill 5516f8f626 feat(app): give experimental settings a dedicated page 2026-09-02 21:35:49 -06:00
7 changed files with 101 additions and 57 deletions
@@ -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")
})
+2
View File
@@ -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",
@@ -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 = () => {
<FontSetting kind="terminal" fonts={appearance.fonts} />
</SettingsList>
</div>
<div class="settings-section">
<h3 class="settings-section-title">{language.t("settings.appearance.section.experimental")}</h3>
<SettingsList>
<SettingsRow
title={language.t("settings.appearance.row.tabs.title")}
description={language.t("settings.appearance.row.tabs.description")}
>
<Select
data-action="settings-tab-layout"
options={tabLayoutOptions}
current={tabLayoutOptions.find((option) => option === appearance.tabs.current())}
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 && appearance.tabs.select(option)}
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.appearance.row.projectName.title")}
description={language.t("settings.appearance.row.projectName.description")}
>
<div data-action="settings-show-project-name">
<Switch
checked={appearance.projectName.current()}
onChange={appearance.projectName.set}
hideLabel
>
{language.t("settings.appearance.row.projectName.title")}
</Switch>
</div>
</SettingsRow>
</SettingsList>
</div>
</div>
</>
)
@@ -0,0 +1,69 @@
import { Component } from "solid-js"
import { Select } from "@opencode-ai/ui/select"
import { Switch } from "@opencode-ai/ui/switch"
import { useLanguage } from "@/runtime/i18n/language"
import { SettingsList } from "@/settings/list"
import { useSettings } from "@/settings/model"
import { SettingsRow } from "@/settings/row"
import "@/settings/settings.css"
const tabLayoutOptions: ("horizontal" | "vertical")[] = ["horizontal", "vertical"]
export const SettingsExperimental: Component = () => {
const language = useLanguage()
const settings = useSettings()
return (
<>
<div class="settings-tab-header">
<div class="settings-tab-header-row">
<div class="flex flex-col gap-1">
<h2 class="settings-tab-title">{language.t("settings.tab.experimental")}</h2>
<span class="text-11-regular text-v2-text-text-muted">
{language.t("settings.experimental.description")}
</span>
</div>
</div>
</div>
<div class="settings-tab-body">
<div class="settings-section">
<SettingsList>
<SettingsRow
title={language.t("settings.appearance.row.tabs.title")}
description={language.t("settings.appearance.row.tabs.description")}
>
<Select
data-action="settings-tab-layout"
options={tabLayoutOptions}
current={tabLayoutOptions.find((option) => 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)}
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.appearance.row.projectName.title")}
description={language.t("settings.appearance.row.projectName.description")}
>
<div data-action="settings-show-project-name">
<Switch
checked={settings.appearance.showProjectName()}
onChange={settings.appearance.setShowProjectName}
hideLabel
>
{language.t("settings.appearance.row.projectName.title")}
</Switch>
</div>
</SettingsRow>
</SettingsList>
</div>
</div>
</>
)
}
@@ -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,
},
}
}
+5
View File
@@ -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<{
<Tabs.Content value="shortcuts" class="settings-panel">
<SettingsKeybinds />
</Tabs.Content>
<Tabs.Content value="experimental" class="settings-panel">
<SettingsExperimental />
</Tabs.Content>
<Tabs.Content value="servers" class="settings-panel">
<SettingsServers />
</Tabs.Content>
+4
View File
@@ -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: `<path d="M5.5 2H10.5M6 2V6L2.5 12C2 13 2.5 14 3.5 14H12.5C13.5 14 14 13 13.5 12L10 6V2M4.25 9H11.75" stroke="currentColor" stroke-linecap="square" stroke-linejoin="round"/>`,
},
edit: {
viewBox: "0 0 16 16",
body: `<path d="M13.5555 8.21534V13.5556H2.44434L2.44434 2.4445H7.78462M6.88878 9.11119C6.88878 9.11119 8.96327 9.0367 9.69678 8.3032L14.0301 3.96986C14.5824 3.4176 14.5824 2.52213 14.0301 1.96986C13.4778 1.4176 12.5824 1.4176 12.0301 1.96986L7.69678 6.3032C7.00513 6.99484 6.88878 9.11119 6.88878 9.11119Z" stroke="currentColor"/>`,