From d6ff24d3b8da043dca72325dcd1098c425f83f8c Mon Sep 17 00:00:00 2001 From: Jaap Haitsma Date: Sun, 5 Jul 2026 21:04:46 -0400 Subject: [PATCH 1/2] fix(app): reveal scrollbar in settings dialog panels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The settings dialog panels scrolled but hid their scrollbar entirely, so there was no indication content continued below the fold (e.g. the Appearance / Color scheme setting) and no thumb to drag. Route the General, Keybinds, Providers and Models panels — in both the current and new-layout dialogs — through the shared ScrollView so a thumb reveals on hover. Also make ScrollView defer to a descendant that already handled a key (e.defaultPrevented) so it no longer scrolls the panel when a focused dropdown is opened with the arrow keys. --- packages/app/src/components/settings-general.tsx | 5 +++-- packages/app/src/components/settings-keybinds.tsx | 10 ++++++---- packages/app/src/components/settings-models.tsx | 5 +++-- packages/app/src/components/settings-providers.tsx | 5 +++-- packages/app/src/components/settings-scroll.tsx | 10 ++++++++++ packages/app/src/components/settings-v2/general.tsx | 5 +++-- packages/app/src/components/settings-v2/models.tsx | 5 +++-- packages/app/src/components/settings-v2/providers.tsx | 5 +++-- packages/app/src/components/settings-v2/servers.tsx | 5 +++-- packages/ui/src/components/scroll-view.tsx | 3 +++ 10 files changed, 40 insertions(+), 18 deletions(-) create mode 100644 packages/app/src/components/settings-scroll.tsx diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 8adaa1f0fdf9..ebba0b4fc292 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -31,6 +31,7 @@ import { decode64 } from "@/utils/base64" import { playSoundById, SOUND_OPTIONS } from "@/utils/sound" import { ExternalLink } from "./external-link" import { SettingsList } from "./settings-list" +import { SettingsScroll } from "./settings-scroll" let demoSoundState = { cleanup: undefined as (() => void) | undefined, @@ -740,7 +741,7 @@ export const SettingsGeneral: Component = () => { ) return ( -
+

{language.t("settings.tab.general")}

@@ -772,7 +773,7 @@ export const SettingsGeneral: Component = () => {
-
+
) } diff --git a/packages/app/src/components/settings-keybinds.tsx b/packages/app/src/components/settings-keybinds.tsx index ce74be45864f..cc983f3de360 100644 --- a/packages/app/src/components/settings-keybinds.tsx +++ b/packages/app/src/components/settings-keybinds.tsx @@ -13,8 +13,10 @@ import fuzzysort from "fuzzysort" import { DEFAULT_PALETTE_KEYBIND, formatKeybind, parseKeybind, useCommand } from "@/context/command" import { useLanguage } from "@/context/language" import { useSettings } from "@/context/settings" +import { ScrollView } from "@opencode-ai/ui/scroll-view" import { SettingsList } from "./settings-list" import { SettingsListV2 } from "./settings-v2/parts/list" +import { SettingsScroll } from "./settings-scroll" const IconV2 = lazy(() => import("@opencode-ai/ui/v2/icon").then((module) => ({ default: module.Icon }))) @@ -448,7 +450,7 @@ function SettingsKeybindsV2View(props: { const hasResults = createMemo(() => props.groups.some((group) => (filtered().get(group)?.length ?? 0) > 0)) return ( - <> +

{language.t("settings.shortcuts.title")}

@@ -525,7 +527,7 @@ function SettingsKeybindsV2View(props: {
- +
) } @@ -745,7 +747,7 @@ export const SettingsKeybinds: Component<{ v2?: boolean }> = (props) => { ) return ( -
+
@@ -776,6 +778,6 @@ export const SettingsKeybinds: Component<{ v2?: boolean }> = (props) => {
{groups} -
+
) } diff --git a/packages/app/src/components/settings-models.tsx b/packages/app/src/components/settings-models.tsx index f3d9e1522ff4..41f2a9ca7d0e 100644 --- a/packages/app/src/components/settings-models.tsx +++ b/packages/app/src/components/settings-models.tsx @@ -9,6 +9,7 @@ import { useLanguage } from "@/context/language" import { useModels } from "@/context/models" import { popularProviders } from "@/hooks/use-providers" import { SettingsList } from "./settings-list" +import { SettingsScroll } from "./settings-scroll" import { SettingsServerPicker, SettingsServerScope } from "./settings-server-picker" type ModelItem = ReturnType["list"]>[number] @@ -67,7 +68,7 @@ const SettingsModelsContent: Component = () => { }) return ( -
+
@@ -144,6 +145,6 @@ const SettingsModelsContent: Component = () => {
-
+ ) } diff --git a/packages/app/src/components/settings-providers.tsx b/packages/app/src/components/settings-providers.tsx index 080e6c517bfe..c3867a744fd0 100644 --- a/packages/app/src/components/settings-providers.tsx +++ b/packages/app/src/components/settings-providers.tsx @@ -11,6 +11,7 @@ import { useServerSync } from "@/context/server-sync" import { DialogConnectProvider, useProviderConnectController } from "./dialog-connect-provider" import { DialogCustomProvider } from "./dialog-custom-provider" import { SettingsList } from "./settings-list" +import { SettingsScroll } from "./settings-scroll" import { SettingsServerPicker, SettingsServerScope } from "./settings-server-picker" type ProviderSource = "env" | "api" | "config" | "custom" @@ -146,7 +147,7 @@ const SettingsProvidersContent: Component<{ onBack?: () => void }> = (props) => } return ( -
+

{language.t("settings.providers.title")}

@@ -259,6 +260,6 @@ const SettingsProvidersContent: Component<{ onBack?: () => void }> = (props) =>
-
+ ) } diff --git a/packages/app/src/components/settings-scroll.tsx b/packages/app/src/components/settings-scroll.tsx new file mode 100644 index 000000000000..17a9a243a4ed --- /dev/null +++ b/packages/app/src/components/settings-scroll.tsx @@ -0,0 +1,10 @@ +import { type Component, type JSX } from "solid-js" +import { ScrollView } from "@opencode-ai/ui/scroll-view" + +export const SettingsScroll: Component<{ children: JSX.Element }> = (props) => { + return ( + +
{props.children}
+
+ ) +} diff --git a/packages/app/src/components/settings-v2/general.tsx b/packages/app/src/components/settings-v2/general.tsx index b41da107ed6e..110f11fea900 100644 --- a/packages/app/src/components/settings-v2/general.tsx +++ b/packages/app/src/components/settings-v2/general.tsx @@ -4,6 +4,7 @@ import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" import { SelectV2 } from "@opencode-ai/ui/v2/select-v2" import { Switch } from "@opencode-ai/ui/v2/switch-v2" import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2" +import { ScrollView } from "@opencode-ai/ui/scroll-view" import { useDialog } from "@opencode-ai/ui/context/dialog" import { useLanguage } from "@/context/language" import { usePlatform } from "@/context/platform" @@ -536,7 +537,7 @@ export const SettingsGeneralV2: Component<{ ) return ( - <> +

{language.t("settings.tab.general")}

@@ -566,6 +567,6 @@ export const SettingsGeneralV2: Component<{
- + ) } diff --git a/packages/app/src/components/settings-v2/models.tsx b/packages/app/src/components/settings-v2/models.tsx index 482e8cbffbd4..8bdf70add036 100644 --- a/packages/app/src/components/settings-v2/models.tsx +++ b/packages/app/src/components/settings-v2/models.tsx @@ -4,6 +4,7 @@ import { Switch } from "@opencode-ai/ui/v2/switch-v2" import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon" import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2" +import { ScrollView } from "@opencode-ai/ui/scroll-view" import { type Component, For, Show } from "solid-js" import { createStore } from "solid-js/store" import { useLanguage } from "@/context/language" @@ -51,7 +52,7 @@ export const SettingsModelsV2: Component = () => { }) return ( - <> +

{language.t("settings.models.title")}

- + ) } diff --git a/packages/app/src/components/settings-v2/providers.tsx b/packages/app/src/components/settings-v2/providers.tsx index acd73ddbe544..a02645f6f640 100644 --- a/packages/app/src/components/settings-v2/providers.tsx +++ b/packages/app/src/components/settings-v2/providers.tsx @@ -2,6 +2,7 @@ import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" import { Tag } from "@opencode-ai/ui/v2/badge-v2" import { useDialog } from "@opencode-ai/ui/context/dialog" import { ProviderIcon } from "@opencode-ai/ui/provider-icon" +import { ScrollView } from "@opencode-ai/ui/scroll-view" import { showToast } from "@/utils/toast" import { popularProviders, useProviders } from "@/hooks/use-providers" import { createMemo, type Accessor, type Component, For, Show } from "solid-js" @@ -143,7 +144,7 @@ export const SettingsProvidersV2: Component<{ } return ( - <> +

{language.t("settings.providers.title")}

@@ -262,6 +263,6 @@ export const SettingsProvidersV2: Component<{
- + ) } diff --git a/packages/app/src/components/settings-v2/servers.tsx b/packages/app/src/components/settings-v2/servers.tsx index b93f700d24cf..98fded32630b 100644 --- a/packages/app/src/components/settings-v2/servers.tsx +++ b/packages/app/src/components/settings-v2/servers.tsx @@ -2,6 +2,7 @@ import { Tag } from "@opencode-ai/ui/v2/badge-v2" import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon" import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2" +import { ScrollView } from "@opencode-ai/ui/scroll-view" import { useDialog } from "@opencode-ai/ui/context/dialog" import fuzzysort from "fuzzysort" import { type Component, For, Show, createMemo } from "solid-js" @@ -47,7 +48,7 @@ export const SettingsServersV2: Component = () => { } return ( - <> +
{
- +
) } diff --git a/packages/ui/src/components/scroll-view.tsx b/packages/ui/src/components/scroll-view.tsx index 5116d94f9ead..c815824f133b 100644 --- a/packages/ui/src/components/scroll-view.tsx +++ b/packages/ui/src/components/scroll-view.tsx @@ -291,6 +291,9 @@ export function ScrollView(props: ScrollViewProps) { // We can also explicitly catch PageUp/Down if we want smooth scroll or specific behavior, // but native usually handles this perfectly. Let's explicitly ensure it behaves well. const onKeyDown = (e: KeyboardEvent) => { + // Defer to a descendant that already handled the key (e.g. an opening/open + // dropdown, listbox, or menu) — don't additionally scroll the viewport. + if (e.defaultPrevented) return // If user is focused on an input inside the scroll view, don't hijack keys if (document.activeElement && ["INPUT", "TEXTAREA", "SELECT"].includes(document.activeElement.tagName)) { return From c2b630b0e3bef9cf6fd04acaf80c89445d470603 Mon Sep 17 00:00:00 2001 From: Jaap Haitsma Date: Mon, 3 Aug 2026 14:11:10 -0400 Subject: [PATCH 2/2] refactor(app): scope settings panel self-scrolling to its one consumer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The settings dialog tabs now wrap their content in ScrollView so a thumb reveals on hover, which left .settings-v2-panel's own overflow-y/hidden- scrollbar rules redundant there — and they were the original source of the invisible scrollbar. They are not dead globally, though: the Manage Models dialog has no ScrollView and relies on that element being the scroll container, both to scroll at all and to drive its scroll-timeline top fade in index.css. So move the declarations to a .settings-v2-panel--scroll modifier and apply it there instead of deleting them. The scroll-timeline selector still matches, since the element keeps the base class. No behavior change: the five settings tabs already scrolled via ScrollView, and Manage Models keeps its bare scroller. --- .../app/src/components/dialog-manage-models.tsx | 2 +- .../src/components/settings-v2/settings-v2.css | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/app/src/components/dialog-manage-models.tsx b/packages/app/src/components/dialog-manage-models.tsx index d6c5e9918670..06b37d054e08 100644 --- a/packages/app/src/components/dialog-manage-models.tsx +++ b/packages/app/src/components/dialog-manage-models.tsx @@ -195,7 +195,7 @@ export const DialogManageModelsV2: Component = () => {
-
+