From 2212de6dad629757b4fed8e53eab6f9a492c9388 Mon Sep 17 00:00:00 2001 From: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com> Date: Tue, 22 Sep 2026 20:38:15 -0700 Subject: [PATCH] refactor(web): composer controls own their look instead of restyling ui components The chat composer restyled Button, SelectTrigger and ComboboxItem with className in 101 places. components/ui stays generic, so nothing here adds a ui variant; each cluster either becomes a composer-owned element or moves onto an existing primitive. - ComposerControl / ComposerSelectControl: the composer toolbar control is a real named concept but composer-specific, so it is now an app control that renders its own element (useRender button, Select.Trigger) with one class recipe for the sm toolbar and the dimmer xs resting strip. Plan mode is marked with aria-pressed instead of call-site color classes. Settings pickers that pass triggerVariant still get a plain Button in that variant. - Provider-unavailable action becomes a ComposerControl (was a ghost Button restyled to look like one). - ComposerPrimaryActions: Submit/Refine/Implement pills share the send button's message-action look, which only the composer uses, so they are plain composer-owned buttons with one local class recipe, like send and stop already were. Previous buttons drop rounded-full (plain outline). - Attachment chips: retry uses Button's overlay variant and remove uses media-close (drift from bg-background/85). The snap-shot hover reveal moves to a plain wrapper span. - Model picker: ModelListRow and the legacy row drop their padding, radius and highlight overrides and use ComboboxItem's own states; the favorite star uses ghost-muted; provider setup link becomes InlineButton; the list and empty state use their defaults. ComboboxItem's hover no longer paints disabled rows (generic fix, replaces data-disabled:hover:bg-transparent). Co-Authored-By: Claude Opus 5.5 (1M context) --- apps/web/src/components/chat/ChatComposer.tsx | 53 ++++++------- .../chat/CompactComposerControlsMenu.tsx | 3 +- .../src/components/chat/ComposerControl.tsx | 78 ++++++++++--------- .../chat/ComposerPrimaryActions.tsx | 44 +++++------ apps/web/src/components/chat/ModelListRow.tsx | 12 +-- .../components/chat/ModelPickerContent.tsx | 18 ++--- .../components/chat/ProviderModelPicker.tsx | 3 - apps/web/src/components/chat/TraitsPicker.tsx | 4 - .../components/chat/composerProviderState.tsx | 4 - .../settings/ProjectDefaultsSettings.tsx | 2 - .../components/settings/SettingsPanels.tsx | 2 - .../settings/SourceControlWritingSettings.tsx | 1 - .../components/settings/settingsLayout.tsx | 9 +-- apps/web/src/components/ui/combobox.tsx | 2 +- scripts/lint-restyle-ceiling.ts | 2 +- 15 files changed, 100 insertions(+), 137 deletions(-) diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx index 7955e1e02b25..b377439882d5 100644 --- a/apps/web/src/components/chat/ChatComposer.tsx +++ b/apps/web/src/components/chat/ChatComposer.tsx @@ -1100,14 +1100,8 @@ const ComposerFooterModeControls = memo(function ComposerFooterModeControls(prop render={ } @@ -4994,10 +4987,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) .slice(restingBlockDefs.length - restingHiddenBlockCount) .map((def) => def.id); const composerControls = showProviderUnavailable ? ( - + ) : ( <> {composerControlsInStrip && restingControlsHaveLeadingContext ? ( @@ -6590,9 +6581,9 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) retryAttachmentUpload({ environmentId, @@ -6609,19 +6600,23 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) {upload.reason} )} - + + ); }) @@ -6685,9 +6680,9 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) retryAttachmentUpload({ environmentId, @@ -6705,9 +6700,9 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) )} ); } @@ -161,39 +160,36 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({ if (showPlanFollowUpPrompt) { if (promptHasText) { return ( - + ); } return (
- +
@@ -103,11 +102,8 @@ export const ModelListRow = memo(function ModelListRow(props: { render={ +
))}
) : ( - - No models found - + No models found )} diff --git a/apps/web/src/components/chat/ProviderModelPicker.tsx b/apps/web/src/components/chat/ProviderModelPicker.tsx index 21c90b02131c..4211f994b197 100644 --- a/apps/web/src/components/chat/ProviderModelPicker.tsx +++ b/apps/web/src/components/chat/ProviderModelPicker.tsx @@ -6,7 +6,6 @@ import { } from "@t3tools/contracts"; import { memo, useEffect, useMemo, useState } from "react"; import { Badge } from "../ui/badge"; -import type { ButtonVariant } from "../ui/button"; import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { cn } from "~/lib/utils"; @@ -48,7 +47,6 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { disabled?: boolean; terminalOpen?: boolean; open?: boolean; - triggerVariant?: ButtonVariant; triggerClassName?: string; /** Aggregate settings can show a neutral value without claiming one provider is selected. */ triggerLabel?: string; @@ -206,7 +204,6 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { render={