diff --git a/ui/docs/design-system/settings-design.md b/ui/docs/design-system/settings-design.md index d4c255f196de..a3aa0381ff20 100644 --- a/ui/docs/design-system/settings-design.md +++ b/ui/docs/design-system/settings-design.md @@ -1,6 +1,6 @@ # Settings Design Language -Every settings surface (the `/settings` takeover pages plus the Plugins/Skills hubs) uses one structural pattern. Styles live in `ui/src/styles/settings.css`; templates are built through the helpers in `ui/src/components/settings-ui.ts`. +Every settings surface (the `/settings` takeover pages plus the Plugins/Skills hubs) uses one structural pattern. Workspace styles live in `ui/src/styles/settings.css`, while controls shared with startup surfaces live in `ui/src/styles/settings-controls.css`; templates are built through the helpers in `ui/src/components/settings-ui.ts`. ## Anatomy diff --git a/ui/src/components/form-controls.browser.test.ts b/ui/src/components/form-controls.browser.test.ts index 60334da8354f..bcfac9f83f9b 100644 --- a/ui/src/components/form-controls.browser.test.ts +++ b/ui/src/components/form-controls.browser.test.ts @@ -26,6 +26,7 @@ function readUiCss(): string { "ui/src/styles/layout.css", "ui/src/styles/layout.mobile.css", "ui/src/styles/components.css", + "ui/src/styles/settings-controls.css", "ui/src/styles/settings.css", "ui/src/styles/config.css", "ui/src/styles/usage.css", diff --git a/ui/src/components/select-picker.ts b/ui/src/components/select-picker.ts index 19766a803e02..9323cbc7c3da 100644 --- a/ui/src/components/select-picker.ts +++ b/ui/src/components/select-picker.ts @@ -1,5 +1,6 @@ import { html, nothing } from "lit"; import "./web-awesome-select.ts"; +import "../styles/select-picker.css"; export type PickerOption = { value: string; diff --git a/ui/src/components/settings-ui.ts b/ui/src/components/settings-ui.ts index 10eac794eb23..007e42557d63 100644 --- a/ui/src/components/settings-ui.ts +++ b/ui/src/components/settings-ui.ts @@ -1,7 +1,7 @@ // Settings design-language primitives. Every settings surface builds its // layout through these helpers so pages cannot drift back into bespoke -// card/pill markup. Styles live in ui/src/styles/settings.css; rules in -// ui/docs/settings-design.md. +// card/pill markup. Styles live in ui/src/styles/settings.css and the shared +// ui/src/styles/settings-controls.css; rules in ui/docs/design-system/settings-design.md. import "@awesome.me/webawesome/dist/components/radio/radio.js"; import "@awesome.me/webawesome/dist/components/radio-group/radio-group.js"; import "@awesome.me/webawesome/dist/components/switch/switch.js"; diff --git a/ui/src/components/settings-workspace.ts b/ui/src/components/settings-workspace.ts index fceb5b2a621f..6757eeb6d395 100644 --- a/ui/src/components/settings-workspace.ts +++ b/ui/src/components/settings-workspace.ts @@ -2,6 +2,7 @@ // section navigation lives in the takeover sidebar (settings-sidebar.ts). import { html } from "lit"; import { ifDefined } from "lit/directives/if-defined.js"; +import "../styles/settings.css"; export function renderSettingsWorkspace( body: unknown, diff --git a/ui/src/pages/chat/chat-responsive.browser.test.ts b/ui/src/pages/chat/chat-responsive.browser.test.ts index 92f83f797b79..ad1cd2056131 100644 --- a/ui/src/pages/chat/chat-responsive.browser.test.ts +++ b/ui/src/pages/chat/chat-responsive.browser.test.ts @@ -930,9 +930,9 @@ describeBrowserLayout.concurrent("chat responsive browser layout", () => { try { const splitViewCss = readStyleSheet("ui/src/styles/chat/split-view.css"); const boardCss = readStyleSheet("ui/src/styles/chat/board.css"); - const settingsCss = readStyleSheet("ui/src/styles/settings.css"); + const settingsControlsCss = readStyleSheet("ui/src/styles/settings-controls.css"); await page.setContent( - ` + `
diff --git a/ui/src/pages/config/language-select.ts b/ui/src/pages/config/language-select.ts index 66fe1b3f1427..3a5bcb2a120d 100644 --- a/ui/src/pages/config/language-select.ts +++ b/ui/src/pages/config/language-select.ts @@ -1,5 +1,6 @@ import { html } from "lit"; import "../../components/web-awesome-select.ts"; +import "../../styles/select-picker.css"; import { SUPPORTED_LOCALES, t, type Locale } from "../../i18n/index.ts"; export function languageLabel(locale: Locale) { diff --git a/ui/src/pages/sessions/view.browser.test.ts b/ui/src/pages/sessions/view.browser.test.ts index 678314a1682d..933c3e6fd762 100644 --- a/ui/src/pages/sessions/view.browser.test.ts +++ b/ui/src/pages/sessions/view.browser.test.ts @@ -29,6 +29,7 @@ function readUiCss(): string { "ui/src/styles/layout.css", "ui/src/styles/layout.mobile.css", "ui/src/styles/components.css", + "ui/src/styles/settings-controls.css", "ui/src/styles/settings.css", "ui/src/styles/sessions.css", ]; diff --git a/ui/src/pages/workboard/workboard.e2e.test.ts b/ui/src/pages/workboard/workboard.e2e.test.ts index 4b9ad6a07b62..e80fa3adf1a5 100644 --- a/ui/src/pages/workboard/workboard.e2e.test.ts +++ b/ui/src/pages/workboard/workboard.e2e.test.ts @@ -408,6 +408,21 @@ suite.define(() => { .locator(".workboard-toolbar__filters .workboard-select") .nth(1); const priorityCombobox = prioritySelect.getByRole("combobox"); + const directRoutePickerStyles = await prioritySelect.evaluate((select) => { + const label = select.querySelector(".picker-select__label"); + const copy = select.querySelector(".picker-select__copy"); + if (!label || !copy) { + throw new Error("Workboard picker style probe did not render"); + } + return { + copyDisplay: getComputedStyle(copy).display, + labelFontWeight: getComputedStyle(label).fontWeight, + }; + }); + expect(directRoutePickerStyles).toEqual({ + copyDisplay: "grid", + labelFontWeight: "650", + }); await priorityCombobox.focus(); await writable.page.keyboard.press("ArrowDown"); await expect.poll(() => priorityCombobox.getAttribute("aria-expanded")).toBe("true"); diff --git a/ui/src/styles.css b/ui/src/styles.css index 682514523a07..9adbd647e6e6 100644 --- a/ui/src/styles.css +++ b/ui/src/styles.css @@ -5,7 +5,7 @@ @import "./styles/components.css"; @import "./styles/chat/progress-card.css"; @import "./styles/approval-boot.css"; -@import "./styles/settings.css"; +@import "./styles/settings-controls.css"; /* Contract read by stale-chunk-reload.ts to detect a failed entry stylesheet load. * Removing it leaves a fully unstyled page undetected. */ diff --git a/ui/src/styles/corner-shape.browser.test.ts b/ui/src/styles/corner-shape.browser.test.ts index 3e76d439fe1f..100ffbe1d39f 100644 --- a/ui/src/styles/corner-shape.browser.test.ts +++ b/ui/src/styles/corner-shape.browser.test.ts @@ -223,6 +223,7 @@ function readUiCss(): string { "ui/src/styles/layout.css", "ui/src/styles/option-card.css", "ui/src/styles/chat/layout.css", + "ui/src/styles/settings-controls.css", "ui/src/styles/settings.css", "ui/src/pages/activity/run-inspector.css", ] diff --git a/ui/src/styles/cursor-policy.browser.test.ts b/ui/src/styles/cursor-policy.browser.test.ts index ba01f2f26161..32ad09e55784 100644 --- a/ui/src/styles/cursor-policy.browser.test.ts +++ b/ui/src/styles/cursor-policy.browser.test.ts @@ -61,6 +61,7 @@ function readUiCss(): string { "ui/src/styles/components.css", "ui/src/styles/layout.css", "ui/src/styles/sessions.css", + "ui/src/styles/settings-controls.css", "ui/src/styles/settings.css", "ui/src/styles/skill-workshop.css", "ui/src/styles/chat/layout.css", diff --git a/ui/src/styles/select-picker.css b/ui/src/styles/select-picker.css new file mode 100644 index 000000000000..103b7e1432d5 --- /dev/null +++ b/ui/src/styles/select-picker.css @@ -0,0 +1,129 @@ +wa-select.settings-select { + --wa-form-control-activated-color: var(--primary); +} + +wa-select.settings-select::part(combobox) { + min-height: var(--settings-control-height); + padding-inline: 10px; + border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); + border-radius: var(--radius-md); + background: color-mix(in srgb, var(--bg) 80%, var(--bg-elevated) 20%); + color: var(--text); + cursor: var(--cursor-action); + transition: border-color var(--duration-fast) var(--ease-out); +} + +/* [open] is required alongside :focus-within: Chromium does not propagate + :focus-within to the host while a slotted option holds focus, so the open + state must key off the reflected attribute or the stock ring leaks through. */ +wa-select.settings-select[open]::part(combobox), +wa-select.settings-select:focus-within::part(combobox) { + outline: none; + border-color: color-mix(in srgb, var(--accent) 55%, var(--border) 45%); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent); +} + +wa-select.settings-select::part(expand-icon) { + margin-inline-start: var(--space-2); + font-size: var(--control-ui-text-sm); + color: var(--muted); +} + +wa-select.settings-select::part(listbox) { + /* Keep Web Awesome's collision-aware bound in the cap; replacing it outright + would let the listbox overflow when the popup has little room to open. */ + max-height: min(320px, var(--auto-size-available-height, calc(100vh - 48px))); + padding: 6px; + border: 1px solid color-mix(in srgb, var(--border-strong) 86%, transparent); + border-radius: var(--radius-md); + background: var(--bg-elevated); + box-shadow: var(--shadow-lg); + color: var(--text); +} + +wa-select.settings-select wa-option { + padding: 6px 8px; + border-radius: calc(var(--radius-md) - 4px); + font-size: var(--control-ui-text-md); + color: var(--text); + cursor: var(--cursor-action); +} + +/* The roving "current" option already carries the accent fill; the global + :focus-visible accent outline on top of it is pure noise. */ +wa-select.settings-select wa-option:focus-visible { + outline: none; +} + +wa-select.settings-select wa-option:state(current) { + color: var(--primary-foreground); +} + +.picker-select__option { + color: var(--text); +} + +.picker-select [slot="start"] { + display: inline-flex; + align-items: center; +} + +.picker-select__label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 650; +} + +.picker-select__copy { + display: grid; + gap: 2px; + min-width: 0; +} + +.picker-select__description { + min-width: 0; + overflow: hidden; + color: var(--muted); + font-size: 0.75rem; + line-height: 1.2; + text-overflow: ellipsis; + white-space: normal; +} + +/* The model picker composes the shared picker and loads this same stylesheet. */ +.model-picker { + display: grid; + gap: 6px; + min-width: 0; +} + +.model-picker__provider-icon { + width: 20px; + height: 20px; + flex: 0 0 20px; +} + +.model-picker__provider-icon.provider-brand-icon--fallback { + font-size: 9px; +} + +.model-picker__custom[hidden] { + display: none; +} + +/* Forced colors suppresses the box-shadow ring and accent fill that stand in + for the outlines removed above, so restore system-color outlines there. */ +@media (forced-colors: active) { + wa-select.settings-select[open]::part(combobox), + wa-select.settings-select:focus-within::part(combobox) { + outline: 2px solid SelectedItem; + outline-offset: 2px; + } + + wa-select.settings-select wa-option:focus-visible { + outline: 2px solid SelectedItem; + outline-offset: 2px; + } +} diff --git a/ui/src/styles/settings-controls.css b/ui/src/styles/settings-controls.css new file mode 100644 index 000000000000..1d92008877dc --- /dev/null +++ b/ui/src/styles/settings-controls.css @@ -0,0 +1,210 @@ +:root { + --settings-control-height: 32px; +} + +/* Settings controls also appear outside the settings workspace: the login + gate, chat board controls, and the chat MCP form all use these primitives. */ + +/* Segmented keeps its 2px track inset; the buttons fill the inner height. + min-height (not height) so many-option enums can still wrap to two lines. */ +.settings-segmented { + min-height: var(--settings-control-height); + box-sizing: border-box; + align-items: stretch; + display: inline-flex; + flex-wrap: wrap; + gap: 2px; + max-width: 100%; + background: color-mix(in srgb, var(--bg) 80%, var(--bg-elevated) 20%); + border: 1px solid color-mix(in srgb, var(--border) 50%, transparent); + border-radius: var(--radius-md); + padding: 2px; +} + +.settings-segmented > .settings-segmented__btn { + display: inline-flex; + align-items: center; + padding-block: 0; +} + +.settings-control__sr-label { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +wa-radio-group.settings-segmented::part(form-control-label) { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); +} + +wa-radio-group.settings-segmented::part(radios) { + display: flex; + flex-wrap: wrap; + gap: 2px; +} + +.settings-segmented__btn { + font-size: var(--control-ui-text-sm); + font-weight: 550; + padding: 4px 12px; + border: none; + border-radius: calc(var(--radius-md) - 3px); + background: transparent; + color: var(--muted); + white-space: nowrap; + cursor: var(--cursor-action); + transition: + color var(--duration-normal) var(--ease-out), + background var(--duration-normal) var(--ease-out), + box-shadow var(--duration-normal) var(--ease-out); +} + +.settings-segmented__btn:hover { + color: var(--text); +} + +.settings-segmented__btn:focus-visible { + outline: none; + color: var(--text); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent); +} + +.settings-segmented__btn--active { + background: var(--bg-elevated); + color: var(--text-strong); + box-shadow: var(--shadow-sm); +} + +.settings-segmented__btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +input.settings-input, +select.settings-select:not([multiple]) { + height: var(--settings-control-height); + padding-block: 0; + box-sizing: border-box; +} + +.settings-input, +.settings-select { + --control-ui-touch-input-size: var(--control-ui-input-text-size); + + width: 100%; + min-width: 0; + font: inherit; + font-size: var(--control-ui-input-text-size); +} + +/* Native controls own their box. Web Awesome selects are styled by their + component-owned stylesheet. */ +.settings-input, +select.settings-select { + border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); + border-radius: var(--radius-md); + background-color: color-mix(in srgb, var(--bg) 80%, var(--bg-elevated) 20%); + color: var(--text); + padding: 7px 10px; + transition: border-color var(--duration-fast) var(--ease-out); +} + +select.settings-select:not([multiple]) { + appearance: none; + -webkit-appearance: none; + padding-right: 36px; + background-image: var(--select-chevron); + background-repeat: no-repeat; + background-position: right 10px center; +} + +.settings-input::placeholder { + color: var(--muted); +} + +.settings-input:focus-visible, +select.settings-select:focus-visible { + outline: none; + border-color: color-mix(in srgb, var(--accent) 55%, var(--border) 45%); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent); +} + +/* Secret inputs also appear in the startup login gate. Keep these selectors + unscoped so the gate's mobile sizing rules retain higher specificity. */ +.settings-secret { + position: relative; + display: inline-flex; + align-items: center; + min-width: 0; +} + +.settings-secret > input, +.settings-secret > textarea { + width: 100%; + min-width: 0; + padding-right: 36px; +} + +.settings-secret__toggle { + position: absolute; + right: 5px; + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + padding: 0; + border: none; + border-radius: var(--radius-sm); + background: transparent; + color: var(--muted); + cursor: var(--cursor-action); + transition: color var(--duration-fast) var(--ease-out); +} + +.settings-secret__toggle:hover { + color: var(--text); +} + +.settings-secret__toggle:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.settings-secret__toggle:focus-visible { + outline: none; + box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent); +} + +.settings-secret__toggle svg { + width: 16px; + height: 16px; + fill: none; + stroke: currentColor; + stroke-width: 2px; + stroke-linecap: round; + stroke-linejoin: round; +} + +/* Quiet count badge used by settings pages and the Skill Workshop. */ +.settings-count { + display: inline-flex; + align-items: center; + padding: 1px 7px; + border-radius: var(--radius-full); + background: color-mix(in srgb, var(--bg-elevated) 80%, var(--border) 20%); + color: var(--muted); + font-size: var(--control-ui-text-xs); + font-weight: 600; + font-variant-numeric: tabular-nums; +} diff --git a/ui/src/styles/settings.css b/ui/src/styles/settings.css index c830d2fb2f76..b645b99fd119 100644 --- a/ui/src/styles/settings.css +++ b/ui/src/styles/settings.css @@ -1,7 +1,3 @@ -:root { - --settings-control-height: 32px; -} - /* ── Settings design language ────────────────────────────────────────────── * * One structural pattern for every settings surface: @@ -441,36 +437,12 @@ Multi-select list boxes and textareas keep their natural height. */ .settings-row__control .btn, .settings-section__actions .btn, -.settings-group .data-table-search input, -input.settings-input, -select.settings-select:not([multiple]) { +.settings-group .data-table-search input { height: var(--settings-control-height); padding-block: 0; box-sizing: border-box; } -/* Segmented keeps its 2px track inset; the buttons fill the inner height. - min-height (not height) so many-option enums can still wrap to two lines. */ -.settings-segmented { - min-height: var(--settings-control-height); - box-sizing: border-box; - align-items: stretch; - display: inline-flex; - flex-wrap: wrap; - gap: 2px; - max-width: 100%; - background: color-mix(in srgb, var(--bg) 80%, var(--bg-elevated) 20%); - border: 1px solid color-mix(in srgb, var(--border) 50%, transparent); - border-radius: var(--radius-md); - padding: 2px; -} - -.settings-segmented > .settings-segmented__btn { - display: inline-flex; - align-items: center; - padding-block: 0; -} - .settings-row__control .btn--icon, .settings-section__actions .btn--icon { width: var(--settings-control-height); @@ -485,18 +457,6 @@ select.settings-select:not([multiple]) { cursor: var(--cursor-action); } -.settings-control__sr-label { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} - wa-switch.settings-toggle { /* Web Awesome defaults to brand blue; settings controls follow the active scene. */ --wa-form-control-activated-color: var(--accent); @@ -504,94 +464,6 @@ wa-switch.settings-toggle { flex: none; } -wa-radio-group.settings-segmented::part(form-control-label) { - position: absolute; - width: 1px; - height: 1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); -} - -wa-radio-group.settings-segmented::part(radios) { - display: flex; - flex-wrap: wrap; - gap: 2px; -} - -.settings-segmented__btn { - font-size: var(--control-ui-text-sm); - font-weight: 550; - padding: 4px 12px; - border: none; - border-radius: calc(var(--radius-md) - 3px); - background: transparent; - color: var(--muted); - white-space: nowrap; - cursor: var(--cursor-action); - transition: - color var(--duration-normal) var(--ease-out), - background var(--duration-normal) var(--ease-out), - box-shadow var(--duration-normal) var(--ease-out); -} - -.settings-segmented__btn:hover { - color: var(--text); -} - -.settings-segmented__btn:focus-visible { - outline: none; - color: var(--text); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent); -} - -.settings-segmented__btn--active { - background: var(--bg-elevated); - color: var(--text-strong); - box-shadow: var(--shadow-sm); -} - -.settings-segmented__btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.settings-input, -.settings-select { - --control-ui-touch-input-size: var(--control-ui-input-text-size); - - width: 100%; - min-width: 0; - font: inherit; - font-size: var(--control-ui-input-text-size); -} - -/* Native controls own their box. wa-select draws the same box on its shadow - combobox part below; host chrome would double-border the web component. */ -.settings-input, -select.settings-select { - border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - border-radius: var(--radius-md); - /* background-color, not the background shorthand: the shorthand resets the - .field select chevron longhands (image/repeat/position) and tiles the - arrow across Model Providers selects. */ - background-color: color-mix(in srgb, var(--bg) 80%, var(--bg-elevated) 20%); - color: var(--text); - padding: 7px 10px; - transition: border-color var(--duration-fast) var(--ease-out); -} - -/* Single-value selects draw the themed chevron; the engine's stock arrow - ignores the 32px control box and renders misaligned. Multi-select list - boxes keep native rendering. */ -select.settings-select:not([multiple]) { - appearance: none; - -webkit-appearance: none; - padding-right: 36px; - background-image: var(--select-chevron); - background-repeat: no-repeat; - background-position: right 10px center; -} - .settings-row:not(.settings-row--stacked) .settings-row__control > .settings-select { width: auto; min-width: min(220px, 100%); @@ -648,22 +520,6 @@ select.settings-select:not([multiple]) { /* ── Secret input (inset reveal toggle; no trailing button) ── */ -.settings-secret { - position: relative; - display: inline-flex; - align-items: center; - min-width: 0; -} - -/* Class-agnostic child selectors: the login gate and config form reuse this - wrapper around their own input/textarea styling. */ -.settings-secret > input, -.settings-secret > textarea { - width: 100%; - min-width: 0; - padding-right: 36px; -} - /* Stacked rows (config form arrays/maps) give the control full width; the wrapper must grow or the inner 100% resolves against fit-content. */ .settings-row--stacked .settings-row__control > .settings-secret { @@ -679,189 +535,6 @@ select.settings-select:not([multiple]) { top: 6px; } -.settings-secret__toggle { - position: absolute; - right: 5px; - display: inline-flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - padding: 0; - border: none; - border-radius: var(--radius-sm); - background: transparent; - color: var(--muted); - cursor: var(--cursor-action); - transition: color var(--duration-fast) var(--ease-out); -} - -.settings-secret__toggle:hover { - color: var(--text); -} - -.settings-secret__toggle:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.settings-secret__toggle:focus-visible { - outline: none; - box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent); -} - -.settings-secret__toggle svg { - width: 16px; - height: 16px; - fill: none; - stroke: currentColor; - stroke-width: 2px; - stroke-linecap: round; - stroke-linejoin: round; -} - -.settings-input::placeholder { - color: var(--muted); -} - -.settings-input:focus-visible, -select.settings-select:focus-visible { - outline: none; - border-color: color-mix(in srgb, var(--accent) 55%, var(--border) 45%); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent); -} - -/* ── Web Awesome select ── */ - -wa-select.settings-select { - --wa-form-control-activated-color: var(--primary); -} - -wa-select.settings-select::part(combobox) { - min-height: var(--settings-control-height); - padding-inline: 10px; - border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - border-radius: var(--radius-md); - background: color-mix(in srgb, var(--bg) 80%, var(--bg-elevated) 20%); - color: var(--text); - cursor: var(--cursor-action); - transition: border-color var(--duration-fast) var(--ease-out); -} - -/* [open] is required alongside :focus-within: Chromium does not propagate - :focus-within to the host while a slotted option holds focus, so the open - state must key off the reflected attribute or the stock ring leaks through. */ -wa-select.settings-select[open]::part(combobox), -wa-select.settings-select:focus-within::part(combobox) { - outline: none; - border-color: color-mix(in srgb, var(--accent) 55%, var(--border) 45%); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent); -} - -wa-select.settings-select::part(expand-icon) { - margin-inline-start: var(--space-2); - font-size: var(--control-ui-text-sm); - color: var(--muted); -} - -wa-select.settings-select::part(listbox) { - /* Keep Web Awesome's collision-aware bound in the cap; replacing it outright - would let the listbox overflow when the popup has little room to open. */ - max-height: min(320px, var(--auto-size-available-height, calc(100vh - 48px))); - padding: 6px; - border: 1px solid color-mix(in srgb, var(--border-strong) 86%, transparent); - border-radius: var(--radius-md); - background: var(--bg-elevated); - box-shadow: var(--shadow-lg); - color: var(--text); -} - -wa-select.settings-select wa-option { - padding: 6px 8px; - border-radius: calc(var(--radius-md) - 4px); - font-size: var(--control-ui-text-md); - color: var(--text); - cursor: var(--cursor-action); -} - -/* The roving "current" option already carries the accent fill; the global - :focus-visible accent outline on top of it is pure noise. */ -wa-select.settings-select wa-option:focus-visible { - outline: none; -} - -wa-select.settings-select wa-option:state(current) { - color: var(--primary-foreground); -} - -.picker-select__option { - color: var(--text); -} - -.picker-select [slot="start"] { - display: inline-flex; - align-items: center; -} - -.picker-select__label { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-weight: 650; -} - -.picker-select__copy { - display: grid; - gap: 2px; - min-width: 0; -} - -.picker-select__description { - min-width: 0; - overflow: hidden; - color: var(--muted); - font-size: 0.75rem; - line-height: 1.2; - text-overflow: ellipsis; - white-space: normal; -} - -.model-picker { - display: grid; - gap: 6px; - min-width: 0; -} - -.model-picker__provider-icon { - width: 20px; - height: 20px; - flex: 0 0 20px; -} - -.model-picker__provider-icon.provider-brand-icon--fallback { - font-size: 9px; -} - -.model-picker__custom[hidden] { - display: none; -} - -/* Forced colors suppresses the box-shadow ring and accent fill that stand in - for the outlines removed above, so restore system-color outlines there. */ -@media (forced-colors: active) { - wa-select.settings-select[open]::part(combobox), - wa-select.settings-select:focus-within::part(combobox) { - outline: 2px solid SelectedItem; - outline-offset: 2px; - } - - wa-select.settings-select wa-option:focus-visible { - outline: 2px solid SelectedItem; - outline-offset: 2px; - } -} - textarea.settings-input { min-height: 88px; resize: vertical; @@ -952,20 +625,6 @@ textarea.settings-input { overflow-wrap: anywhere; } -/* ── Count badge (quiet; genuine counts/alerts only, not status) ── */ - -.settings-count { - display: inline-flex; - align-items: center; - padding: 1px 7px; - border-radius: var(--radius-full); - background: color-mix(in srgb, var(--bg-elevated) 80%, var(--border) 20%); - color: var(--muted); - font-size: var(--control-ui-text-xs); - font-weight: 600; - font-variant-numeric: tabular-nums; -} - /* ── Empty state ── */ .settings-empty {