From 4506c18f531ade474fa1c2d8f02e2618890af5f1 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sun, 19 Apr 2026 15:52:54 +0300 Subject: [PATCH] UI refresh: Base UI migration + flat tinted button language + mobile polish (#960) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * deps: bump @opencode-ai/sdk to 1.4.6 and add @base-ui/react * build: align vite configs with Base UI and packages update * feat(ui): migrate primitives to Base UI Replace Radix-based wrappers (button, checkbox, collapsible, command, dialog, dropdown-menu, input, radio, scroll-area, select, separator, sonner, switch, textarea, toggle, tooltip) with @base-ui/react equivalents and a refreshed visual language: flexoki theme tokens, typography utilities, and a redesigned CommandPalette. Add new fancy-button variant set and a local slot helper for asChild composition. * refactor(ui consumers): adopt Base UI primitives across the app Update chat, layout, git views, settings, multirun, session and agent management screens to use the new Base UI-backed primitives (anchor width token rename, dropdown/select/dialog usage adjustments, new component APIs). * fix(ScrollableOverlay): forward `disabled` to inner element Add `disabled` to the type so it propagates via rest spread to the underlying element (e.g. textarea in simple mode). Resolves a type error reported after the main merge. * refactor(dropdown consumers): migrate preventDefault sites to closeOnClick Base UI's Menu.Item does not check `event.defaultPrevented` before closing the menu, so the Radix-era `onSelect={(e) => e.preventDefault()}` pattern no longer keeps the menu open. Replace those sites with the Base UI `closeOnClick={false}` prop (VSCodeLayout rate-limit info rows; AgentGroupDetail worktree actions). * docs(AGENTS): note Base UI as primary source for UI primitives * fix(ContextPanel): center expand/close buttons in tab header Header (h-8) had the action row pinned with items-end + pb-1.5, which placed the h-7 buttons ~2px above the header top and made them look cropped. Switch to items-center so they sit on the same baseline as the tabs. * style(tabs): drop transitions on default variant, soften active pill Default (underline) variant now switches instantly — no transform/width transition on the underline indicator, no color transition on tab buttons. Active pill bg is color-mixed down to 55% of --interactive-selection so light-theme selection doesn't scream. * style(button): pill-style elevation and introduce chip variant Solid variants (default/destructive/neutral/primary/basic) now use the active-pill shadow stack — hairline navy outer ring + stacked tight/soft drops — instead of the pure-black inset/border approach. Outline variant (and FancyButton basic) gets the same elevation without the white top highlight (no fill to catch light). Add a new chip variant: flat `border border-border/60` button without elevation, intended for one-of-N toggle groups where a raised button would read as the wrong affordance. * style(header): apply pill-style shadow to OpenIn and Project Actions Both split-button wrappers previously used a 1px CSS border for definition. Replace that with the pill elevation stack so the header buttons match the new Button variants and feel less hard-edged. * refactor(toggles): migrate one-of-N segmented groups to chip variant Color Mode, When-sessions-expire, voice/STT providers, auth method, agent mode, MCP transport, tunnel type and terminal Ctrl/Cmd modifier toggles all read as flat chip selectors rather than raised buttons. Switch them from variant="outline" to the new chip variant so the raised pill-style elevation doesn't fight the selected-state tint. * fix(elevation): theme-adaptive pill-style shadow for dark mode The navy (rgba(14,18,27,*)) outer ring was invisible on dark surfaces, erasing the button/pill edge. Add dark: variants that use a subtle white outer ring (rgba(255,255,255,0.10)) and darker drop shadows (rgba(0,0,0,0.30/0.20)) so the elevation reads on both themes. Applies to Button solid/outline variants, FancyButton equivalents, the OpenIn and Project Actions header split-buttons, and the active pill indicator in SortableTabsStrip. * style(button): flat tinted variants with squircle corner-shape Replace elevated pill-shadow design with a flat tinted language: pale tinted fill + hairline tinted border + saturated tinted text for default/destructive. Add CSS corner-shape: squircle with @supports gate so Chrome 136+ gets the softened superellipse corners at 50px, Safari falls back to a slightly larger round radius. * style(tabs): replace active pill shadow with tinted border * style(header): flat border and squircle corners for OpenIn / Actions Drop pill-style shadow stacks in favor of a plain hairline border and apply the same squircle corner-shape treatment as Button so header controls match the new visual language. * style(checkbox): drop filled background for tinted checkmark Checked / indeterminate states now keep the transparent box, show a primary-base check/minus glyph, and use a softened primary-tinted inset border (50% mix) so the control reads as selected without a solid swatch. * style(layout): widen sidebar-to-chat corner radius to 10px * style(button): retune tinted variants and unify outline fill - Bump primary fill toward noticeable but still soft (light 10/16/22, dark 16/22/30) and weaken both primary and destructive borders (12% light / 20% dark) so the tone reads without shouting. - Further calm destructive across fill and border so revert-style actions match the muted reference. - Align the outline variant fill with --surface-elevated so git-view generate/commit buttons share the header action button surface. * style(tabs): align pill variant with new button language Active pill now uses --surface-elevated fill and border-border/60 to match the outline button. Track and tab/pill geometry adopt the same squircle treatment as Button: 9-10px fallback with 50px radius under @supports(corner-shape:squircle). Also tighten track padding (inline 2px, block 2px) and grow active tab height so the indicator fills the track instead of floating inside it. * refactor(theme): hardcode radius scale, drop per-theme override Radius values are now fixed in the design system (flexoki-dark's scale becomes the app default). Themes can no longer override --radius-* — the config.radius block is removed from every theme JSON, dropped from the Theme type, and no longer emitted by the CSS generator. * chore(ui): lint/type cleanup across Base UI wrappers * style(tabs): dial pill track background down to near-invisible * refactor(chip): aria-pressed drives the selected tinted state The chip variant now carries the tinted primary palette (same as the default filled button) under aria-pressed, so consumers toggle selection via a single aria attribute instead of repeating the border/text/bg override classes. All existing chip consumers migrate to aria-pressed and drop their hand-rolled selected className. * chore(defaults): humanize variant label in thinking selector * fix(diff): render all lines in single-file mode Single-file mode wrapped PierreDiffViewer in its own layout=fill which creates a nested virtual-root; combined with the outer CSS containment on the wrapper, Pierre's virtualizer measured a clamped viewport and stopped rendering lines past ~150. Mirror the stacked-mode approach: hoist a ScrollableOverlay as the sole virtual root/content and render PierreDiffViewer inline, so the shared virtualizer sees the real scroll viewport and paints every line. Also drops an unused cn import in SessionRetentionSettings. * feat(files): collapse floating editor controls behind icon button The floating file editor toolbar now starts as a single pill icon button and expands into the full control row on hover or click. Clicking outside or moving the pointer away collapses it back, keeping the editor surface unobstructed while the file is being read/edited. Fullscreen overlay keeps its always-visible toolbar. * fix(chat): align mobile session status bar radius with chat input * fix(mobile): keep radio/checkbox/switch at their native size Mobile CSS forced min 36x36px on every