{language.t("settings.providers.section.connected")}
diff --git a/packages/app/src/settings/settings.css b/packages/app/src/settings/settings.css
index 2ab7007b3298..efd81651ffa2 100644
--- a/packages/app/src/settings/settings.css
+++ b/packages/app/src/settings/settings.css
@@ -54,8 +54,7 @@
}
.settings-screen .settings-tab-header {
- padding: 48px 0 32px;
- background: linear-gradient(to bottom, var(--v2-background-bg-base) calc(100% - 24px), transparent);
+ padding: 48px 0 0;
}
.settings-screen .settings-tab-body {
@@ -249,8 +248,18 @@
position: sticky;
top: 0;
z-index: 10;
- padding: 40px 40px 32px;
- background: linear-gradient(to bottom, var(--v2-background-bg-base) calc(100% - 24px), transparent);
+ padding: 40px 40px 0;
+ background: var(--v2-background-bg-base);
+}
+
+.settings-tab-header::before {
+ content: "";
+ position: absolute;
+ inset-inline: 0;
+ top: 100%;
+ height: 24px;
+ background: linear-gradient(to bottom, var(--v2-background-bg-base), transparent);
+ pointer-events: none;
}
.settings-tab-header-row {
@@ -271,9 +280,13 @@
.settings-tab-body {
display: flex;
flex-direction: column;
- gap: 36px;
+ gap: 32px;
width: 100%;
- padding: 0 40px calc(80px + var(--settings-bottom-inset, 0px));
+ padding: 24px 40px calc(80px + var(--settings-bottom-inset, 0px));
+}
+
+.settings-tab-body--sectioned {
+ padding-top: 32px;
}
[data-slot="settings-row-description"] a.settings-link {
@@ -293,18 +306,25 @@
}
.settings-section-title {
- padding-bottom: 8px;
- font-size: 15px;
- font-weight: 640;
- line-height: 1;
+ padding: 0;
+ font-size: 13px;
+ font-weight: 530;
+ line-height: var(--line-height-compact);
color: var(--v2-text-text-base);
}
.settings-section-title + [data-component="settings-list"] {
- margin-top: -4px;
+ margin-top: 0;
margin-bottom: 0;
}
+.settings-section-stack {
+ display: flex;
+ width: 100%;
+ flex-direction: column;
+ gap: 32px;
+}
+
[data-component="settings-list"] {
border-radius: 8px;
background-color: var(--v2-background-bg-layer-01);
@@ -465,8 +485,7 @@
padding-top: 12px;
}
- .settings-screen .settings-tab-title,
- .settings-screen .settings-section-title {
+ .settings-screen .settings-tab-title {
line-height: var(--line-height-base);
}
@@ -612,30 +631,10 @@
color: var(--v2-text-text-accent-hover);
}
-.settings-tab-body.settings-providers {
- gap: 32px;
-}
-
-.settings-tab-header:has(+ .settings-tab-body.settings-providers) {
- padding-bottom: 32px;
-}
-
-.settings-providers .settings-section-title {
- padding-bottom: 0;
- font-size: 13px;
- font-weight: 530;
- line-height: var(--line-height-compact);
-}
-
-.settings-providers .settings-section-title + [data-component="settings-list"] {
- margin-top: 16px;
-}
-
.settings-tab-header.settings-tab-header--stacked {
display: flex;
flex-direction: column;
- gap: 32px;
- padding-bottom: 32px;
+ gap: 24px;
}
.settings-tab-header--stacked > .settings-tab-header-row {
@@ -728,11 +727,10 @@
gap: 8px;
}
-.settings-models .settings-section-title {
- padding-bottom: 0;
+.settings-models-group-title {
font-size: 13px;
font-weight: 530;
- line-height: 16px;
+ line-height: var(--line-height-compact);
}
.settings-models [data-component="provider-icon"] {
@@ -743,10 +741,6 @@
padding-inline: 16px;
}
-.settings-models .settings-section-title + [data-component="settings-list"] {
- margin-top: 0;
-}
-
.settings-models [data-slot="settings-row-description"]:empty {
display: none;
}
@@ -792,17 +786,6 @@
color: var(--v2-text-text-base);
}
-.settings-shortcuts .settings-section {
- gap: 16px;
-}
-
-.settings-shortcuts .settings-section-title {
- padding-bottom: 0;
- font-size: 13px;
- font-weight: 530;
- line-height: var(--line-height-compact);
-}
-
.settings-shortcuts [data-component="settings-list"] {
display: flex;
flex-direction: column;
@@ -895,22 +878,6 @@
gap: 0;
}
-.settings-tab-header.settings-servers-header {
- padding-bottom: 24px;
-}
-
-.settings-servers-header .settings-tab-header-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 16px;
-}
-
-.settings-tab-header.settings-servers-header.settings-tab-header--stacked {
- gap: 24px;
- padding-bottom: 24px;
-}
-
.settings-servers [data-component="settings-list"] {
display: flex;
flex-direction: column;
@@ -988,16 +955,8 @@
color: var(--v2-text-text-base);
}
-.settings-tab-header.settings-workspaces-header {
- padding-bottom: 24px;
-}
-
-.settings-workspaces-header .settings-tab-title {
- font-weight: 610;
-}
-
.settings-tab-body.settings-workspaces {
- gap: 16px;
+ gap: 0;
}
.settings-workspaces-toolbar {
@@ -1006,13 +965,7 @@
align-items: center;
justify-content: space-between;
gap: 16px;
-}
-
-.settings-workspaces-count {
- font-size: 15px;
- font-weight: 530;
- line-height: 1;
- color: var(--v2-text-text-base);
+ padding-bottom: 8px;
}
.settings-workspaces-toolbar-actions {
@@ -1255,7 +1208,7 @@
@media (max-width: 639px) {
.settings-workspaces-header {
- padding: 24px 20px 20px;
+ padding: 24px 20px 0;
}
.settings-tab-body.settings-workspaces {
@@ -1374,6 +1327,10 @@
padding-inline: 0 !important;
}
+.settings-subtabs > [data-slot="tabs-v2-content"] {
+ padding-top: 24px;
+}
+
.settings-extensions-tabs[data-component="tabs-v2"][data-variant="pill"] > [data-slot="tabs-v2-list"]::before {
display: none;
}
diff --git a/packages/app/src/settings/workspaces/projects.tsx b/packages/app/src/settings/workspaces/projects.tsx
index 17bfa636855f..4eb68c3e83d3 100644
--- a/packages/app/src/settings/workspaces/projects.tsx
+++ b/packages/app/src/settings/workspaces/projects.tsx
@@ -115,7 +115,7 @@ export const SettingsProjects: Component = () => {
}
>
-
+
0}
fallback={
diff --git a/packages/app/src/settings/workspaces/workspaces.tsx b/packages/app/src/settings/workspaces/workspaces.tsx
index eaf9a3b6c192..f7fdc7c3e619 100644
--- a/packages/app/src/settings/workspaces/workspaces.tsx
+++ b/packages/app/src/settings/workspaces/workspaces.tsx
@@ -335,7 +335,7 @@ export const SettingsWorkspaces: Component<{ activeDirectory?: string; resetProj
0}>
["name"]
+ disabled?: boolean
+ onSelect?: () => void
+}
+
+export type SettingsNavGroup = {
+ label?: string
+ action?: JSX.Element
+ items: readonly SettingsNavItem[]
+}
+
+export function SettingsNavigation(props: {
+ value: string
+ groups: readonly SettingsNavGroup[]
+ backLabel: string
+ onBack: () => void
+ onChange: (value: string) => void
+ mobileAction?: JSX.Element
+ children: JSX.Element
+}) {
+ const current = () => props.groups.flatMap((group) => group.items).find((item) => item.value === props.value)
+
+ return (
+
+
+
+
+ {props.mobileAction}
+
+
+
+
+
+
+
+
+ {(group) => (
+
+
+
+
+
+ {(item) => (
+
+
+ {item.label}
+
+ )}
+
+
+ )}
+
+
+
+
+ {props.children}
+
+ )
+}
diff --git a/packages/app/src/settings/providers/extensions.tsx b/packages/app/src/settings/providers/extensions.tsx
index 6a7489755b0b..c24c4d0a22f5 100644
--- a/packages/app/src/settings/providers/extensions.tsx
+++ b/packages/app/src/settings/providers/extensions.tsx
@@ -8,7 +8,6 @@ import { useServerSDK } from "@/runtime/server/client"
import { useMcpToggle } from "@/providers/connect/mcp"
import { pluginLabels } from "@/providers/catalog/plugin"
import { ExternalLink } from "@/runtime/platform/external-link"
-import { InlineServerSelect } from "@/settings/server-select"
import "@/settings/settings.css"
interface McpRowItem {
@@ -63,7 +62,6 @@ export const SettingsExtensions: Component = () => {
{language.t("settings.tab.extensions")}
{language.t("settings.extensions.description")}
-
@@ -78,18 +76,20 @@ export const SettingsExtensions: Component = () => {
-
+
{language.t("settings.extensions.availableAll")}
- {language.t("settings.extensions.manageConfig")}
+
+ {language.t("settings.extensions.manageConfig")}
+
-
+
{(item) => (
- {item.name}
+ {item.name}
handleMcpToggle(item, checked)} hideLabel>
{item.name}
@@ -104,18 +104,20 @@ export const SettingsExtensions: Component = () => {
-
+
{language.t("settings.extensions.availableAll")}
- {language.t("settings.extensions.manageConfig")}
+
+ {language.t("settings.extensions.manageConfig")}
+
-
+
{(plugin) => (
- {plugin.name}
+ {plugin.name}
)}
@@ -127,23 +129,23 @@ export const SettingsExtensions: Component = () => {
-
+
{language.t("settings.extensions.availableAll")}
{language.t("settings.extensions.addSkills")}
-
+
{(skill) => (
- {skill.name}
+ {skill.name}
)}
diff --git a/packages/app/src/settings/providers/providers.tsx b/packages/app/src/settings/providers/providers.tsx
index 4c32fcbc4c03..54016b6c623e 100644
--- a/packages/app/src/settings/providers/providers.tsx
+++ b/packages/app/src/settings/providers/providers.tsx
@@ -9,8 +9,6 @@ import { createMemo, type Component, For, Show } from "solid-js"
import { useLanguage } from "@/runtime/i18n/language"
import { useServerSDK } from "@/runtime/server/client"
import { DialogConnectProvider, useProviderConnectController } from "@/providers/connect/dialog"
-import { SettingsServerScope } from "@/settings/server-scope"
-import { InlineServerSelect } from "@/settings/server-select"
import { SettingsList } from "@/settings/list"
import "@/settings/settings.css"
@@ -44,11 +42,7 @@ export const SettingsProviders: Component<{
const connect = (provider?: string) => {
providerConnect.select(provider)
- void dialog.show(() => (
-
-
-
- ))
+ void dialog.show(() => )
}
const connected = createMemo(() => {
@@ -134,7 +128,6 @@ export const SettingsProviders: Component<{
{language.t("settings.providers.title")}
{language.t("settings.providers.description")}
-
diff --git a/packages/app/src/settings/server-scope.tsx b/packages/app/src/settings/server-scope.tsx
index 428943b9c6da..1db9c94ed6fb 100644
--- a/packages/app/src/settings/server-scope.tsx
+++ b/packages/app/src/settings/server-scope.tsx
@@ -1,22 +1,8 @@
-import { type ParentProps, Show } from "solid-js"
-import { useGlobal } from "@/runtime/server/runtime"
+import { type ParentProps } from "solid-js"
import { ModelsProvider } from "@/providers/models/models"
import { ServerProvider } from "@/runtime/server/current"
import { ServerConnection } from "@/runtime/server/registry"
-export function SettingsServerScope(props: ParentProps<{ directory?: string }>) {
- const global = useGlobal()
- return (
-
- {(server) => (
-
- {props.children}
-
- )}
-
- )
-}
-
export function SettingsServerDataScope(props: ParentProps<{ server: ServerConnection.Any; directory?: string }>) {
return (
diff --git a/packages/app/src/settings/server-select.tsx b/packages/app/src/settings/server-select.tsx
deleted file mode 100644
index 82941102d37f..000000000000
--- a/packages/app/src/settings/server-select.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { Show, createMemo, type Component } from "solid-js"
-import { Select } from "@opencode/ui/select"
-import { useGlobal } from "@/runtime/server/runtime"
-import { ServerConnection, serverName } from "@/runtime/server/registry"
-
-const allServers = { type: "all" } as const
-type ServerOption = ServerConnection.Any | typeof allServers
-
-export const InlineServerSelect: Component<{
- all?: {
- label: string
- selected: () => boolean
- onSelect: () => void
- }
- onServerSelect?: () => void
-}> = (props) => {
- const global = useGlobal()
- const options = createMemo(() => [...(props.all ? [allServers] : []), ...global.servers.list()])
- const current = () => (props.all?.selected() ? allServers : global.settings.server.selected())
-
- return (
- 1}>
-
- )
-}
diff --git a/packages/app/src/settings/servers/inventory.ts b/packages/app/src/settings/servers/inventory.ts
new file mode 100644
index 000000000000..9a2b5cb72051
--- /dev/null
+++ b/packages/app/src/settings/servers/inventory.ts
@@ -0,0 +1,43 @@
+import { createMemo } from "solid-js"
+import { ServerConnection, serverName, useServers } from "@/runtime/server/registry"
+import { useWslServers } from "@/servers/wsl/context"
+import type { WslServerItem } from "@/servers/wsl/types"
+
+export type SettingsServer = {
+ key: ServerConnection.Key
+ name: string
+ connection?: ServerConnection.Any
+ wsl?: WslServerItem
+}
+
+export function settingsServers(connections: readonly ServerConnection.Any[], wsl: readonly WslServerItem[]) {
+ const configured = new Map(wsl.map((item) => [item.config.id, item]))
+ const connected = new Set(connections.map(ServerConnection.key))
+ return [
+ ...connections.map((connection): SettingsServer => {
+ const key = ServerConnection.key(connection)
+ const item = configured.get(key)
+ return {
+ key,
+ name: item?.config.distro ?? (serverName(connection) || key),
+ connection: item && item.runtime.kind !== "ready" ? undefined : connection,
+ wsl: item,
+ }
+ }),
+ ...wsl
+ .filter((item) => !connected.has(ServerConnection.Key.make(item.config.id)))
+ .map(
+ (item): SettingsServer => ({
+ key: ServerConnection.Key.make(item.config.id),
+ name: item.config.distro,
+ wsl: item,
+ }),
+ ),
+ ]
+}
+
+export function useSettingsServers() {
+ const servers = useServers()
+ const wsl = useWslServers()
+ return createMemo(() => settingsServers(servers.list, wsl.data?.servers ?? []))
+}
diff --git a/packages/app/src/settings/servers/servers.tsx b/packages/app/src/settings/servers/servers.tsx
index ddd769001fa8..7488ccdea15a 100644
--- a/packages/app/src/settings/servers/servers.tsx
+++ b/packages/app/src/settings/servers/servers.tsx
@@ -1,135 +1,129 @@
import { Badge } from "@opencode/ui/badge"
-import { Icon } from "@opencode/ui/icon"
-import { IconButton } from "@opencode/ui/icon-button"
-import { TextInput } from "@opencode/ui/text-input"
+import { Select } from "@opencode/ui/select"
import { useDialog } from "@opencode/ui/context/dialog"
-import fuzzysort from "fuzzysort"
-import { type Component, For, Show, createMemo } from "solid-js"
-import { createStore } from "solid-js/store"
+import { createMemo, Show, type Component } from "solid-js"
import { ServerRowMenu } from "@/servers/registry/row-menu"
import { ServerHealthIndicator } from "@/servers/registry/row"
import { useLanguage } from "@/runtime/i18n/language"
import { ServerConnection, serverName } from "@/runtime/server/registry"
import { useServerCollectionController } from "@/servers/registry/controller"
import { DialogServer } from "@/servers/connect/dialog"
+import { AddServerMenu, WslServerSettings } from "@/servers/wsl/settings"
import { SettingsList } from "@/settings/list"
-import { AddServerMenu, isWslServer, useFilteredWslServers, WslServerSettings } from "@/servers/wsl/settings"
+import { SettingsRow } from "@/settings/row"
+import { ShellSetting } from "@/settings/general/general"
+import { createServerPreferencesController } from "@/settings/general/controllers"
+import type { SettingsServer } from "./inventory"
import "@/settings/settings.css"
-export const SettingsServers: Component = () => {
- const dialog = useDialog()
+const WebSearchSetting: Component<{
+ controller: ReturnType["websearch"]
+}> = (props) => {
const language = useLanguage()
- const controller = useServerCollectionController()
- const [store, setStore] = createStore({ filter: "" })
- const wslServers = useFilteredWslServers(() => store.filter)
-
- const showSearch = createMemo(
- () => controller.collection.items().filter((item) => !isWslServer(item)).length + wslServers().length > 1,
+ return (
+
+
)
+}
- const filtered = createMemo(() => {
- const items = controller.collection.items().filter((item) => !isWslServer(item))
- const query = store.filter.trim()
- if (!query) return items
- return fuzzysort
- .go(query, items, {
- keys: [(item) => serverName(item), (item) => item.http.url],
- })
- .map((result) => result.obj)
- })
-
- const openAdd = () => {
- void dialog.push(() => )
- }
-
- const openEdit = (server: ServerConnection.Http) => {
- void dialog.push(() => )
- }
+export const SettingsServerGeneral: Component<{
+ entry: SettingsServer
+ nested?: boolean
+ onAddServer?: () => void
+ onServerChange?: (server: ServerConnection.Any) => void
+}> = (props) => {
+ const dialog = useDialog()
+ const language = useLanguage()
+ const controller = useServerCollectionController()
+ const health = createMemo(() => controller.collection.health()[props.entry.key])
+ const edit = (server: ServerConnection.Http) =>
+ void dialog.push(() => )
return (
<>
-