From 16bc1849ea531964d01fa62e5fb71fee8bbed1ce Mon Sep 17 00:00:00 2001 From: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com> Date: Tue, 22 Sep 2026 19:43:41 -0700 Subject: [PATCH] refactor(web): ui components drop their secondary className props Several components/ui exports took a second className for an inner part (viewportClassName, popupClassName, inputClassName, contentClassName, backdropClassName, wrapperClassName). Each was a restyle hatch the lint rule could not see. - Popover: padding="default" | "compact" | "none" names the three insets callers actually wanted (dense lists and excerpts, and content that draws its own frame). None rounds the viewport so edge-to-edge content clips to the popup's corners. - Sheet: sheets sit at a fixed tier (46), under the floating preview player (47-49) and under dialogs (50), so the right-panel sheet no longer needs a flag to drop below the player. - Dialog: the media variant owns its own layering and centering. - ComboboxItem content always lays out as one row: a truncating label, then trailing meta. Every consumer already had that shape. - The model picker search uses ComboboxSearchInput like the branch picker, which deletes inputClassName. - The sidebar snooze list is a Menu with MenuItem and MenuShortcut instead of hand-built buttons in a popover. - An input addon that holds a button takes clicks, so the command palette's back arrow no longer needs wrapperClassName. - Select popupClassName had no callers. no-restyle findings: 604 -> 583. Co-Authored-By: Claude Opus 5.5 (1M context) --- apps/web/src/components/ChatView.tsx | 1 - apps/web/src/components/CommandPalette.tsx | 3 - apps/web/src/components/DiffPanel.tsx | 2 - apps/web/src/components/RightPanelSheet.tsx | 12 +- apps/web/src/components/Sidebar.tsx | 39 ++---- .../components/chat/AssistantCitationChip.tsx | 2 +- .../components/chat/ContextWindowMeter.tsx | 2 +- .../components/chat/ExpandedImageDialog.tsx | 2 - .../src/components/chat/MessagesTimeline.tsx | 2 +- apps/web/src/components/chat/ModelListRow.tsx | 1 - .../components/chat/ModelPickerContent.tsx | 130 ++++++++---------- .../components/chat/ProviderModelPicker.tsx | 4 +- .../chat/SnapShotAttachmentDetails.tsx | 9 +- .../chat/TerminalContextInlineChip.tsx | 2 - apps/web/src/components/contextChipParts.tsx | 10 +- .../preview/previewMiniPlayerLayout.ts | 2 +- .../PullRequestCandidatePicker.tsx | 1 - .../pullRequest/PullRequestDetailPanel.tsx | 7 +- .../pullRequest/PullRequestReactions.tsx | 2 +- .../settings/SettingInheritance.tsx | 6 +- apps/web/src/components/ui/autocomplete.tsx | 2 +- apps/web/src/components/ui/combobox.tsx | 16 +-- apps/web/src/components/ui/command.tsx | 12 +- apps/web/src/components/ui/dialog.tsx | 10 +- apps/web/src/components/ui/popover.tsx | 21 ++- apps/web/src/components/ui/select.tsx | 3 - apps/web/src/components/ui/sheet.tsx | 22 ++- apps/web/src/index.css | 3 + apps/web/src/rightPanelLayout.ts | 2 - scripts/lint-restyle-ceiling.ts | 2 +- 30 files changed, 127 insertions(+), 205 deletions(-) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 3ce6566c041d..62b7e41fdd6c 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -10378,7 +10378,6 @@ export default function ChatView(props: ChatViewProps) { Automatic @@ -788,7 +787,6 @@ export default function DiffPanel({
diff --git a/apps/web/src/components/RightPanelSheet.tsx b/apps/web/src/components/RightPanelSheet.tsx index 9f4838f5666a..e3468034396b 100644 --- a/apps/web/src/components/RightPanelSheet.tsx +++ b/apps/web/src/components/RightPanelSheet.tsx @@ -1,16 +1,12 @@ import { type ReactNode } from "react"; -import { - RIGHT_PANEL_SHEET_CLASS_NAME, - RIGHT_PANEL_SHEET_LAYER_CLASS_NAME, -} from "../rightPanelLayout"; +import { RIGHT_PANEL_SHEET_CLASS_NAME } from "../rightPanelLayout"; import { Sheet, SheetPopup } from "./ui/sheet"; export function RightPanelSheet(props: { animationDurationMs: number; children: ReactNode; open: boolean; - underFloatingPreview?: boolean; onClose: () => void; }) { return ( @@ -27,12 +23,6 @@ export function RightPanelSheet(props: { side="right" showCloseButton={false} keepMounted - {...(props.underFloatingPreview - ? { - backdropClassName: RIGHT_PANEL_SHEET_LAYER_CLASS_NAME, - viewportClassName: RIGHT_PANEL_SHEET_LAYER_CLASS_NAME, - } - : {})} className={RIGHT_PANEL_SHEET_CLASS_NAME} > {props.children} diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 3ec0f981d386..2e18d0894129 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -235,7 +235,7 @@ import { import { SidebarContent, SidebarGroup, useSidebar } from "./ui/sidebar"; import { SidebarChromeFooter, SidebarChromeHeader } from "./sidebar/SidebarChrome"; import { SidebarHeaderIconButton, SidebarThreadHeader } from "./sidebar/SidebarThreadHeader"; -import { Popover, PopoverPopup, PopoverTrigger } from "./ui/popover"; +import { Menu, MenuItem, MenuPopup, MenuSeparator, MenuShortcut, MenuTrigger } from "./ui/menu"; import { Tooltip, TooltipPopup, TooltipProvider, TooltipTrigger } from "./ui/tooltip"; import { MiddleTruncate } from "./ui/middle-truncate"; import { @@ -440,7 +440,7 @@ function SidebarThreadTooltip({ * Controlled by the row (which also uses the open state to pin its hover * actions while the menu is up). */ -function SnoozePopoverButton(props: { +function SnoozeMenuButton(props: { open: boolean; onOpenChange: (open: boolean) => void; onSnooze: (preset: Pick) => void; @@ -454,11 +454,11 @@ function SnoozePopoverButton(props: { [open, timestampFormat], ); return ( - + Snooze thread - + {presets.map((preset) => ( - + {preset.label} + {preset.whenLabel} + ))} -
- - - + + +
); } @@ -1883,7 +1875,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { ) : null} {showSnoozeButton ? ( - { if (project) handleProjectSettings(event, project); }} diff --git a/apps/web/src/components/chat/AssistantCitationChip.tsx b/apps/web/src/components/chat/AssistantCitationChip.tsx index 6aad25b84d25..d838c53d27d2 100644 --- a/apps/web/src/components/chat/AssistantCitationChip.tsx +++ b/apps/web/src/components/chat/AssistantCitationChip.tsx @@ -171,7 +171,7 @@ export function AssistantCitationChip({ }} aria-label="Edit citation comment" width="md" - viewportClassName="p-3" + padding="compact" onPointerDown={(event) => event.stopPropagation()} > diff --git a/apps/web/src/components/chat/ExpandedImageDialog.tsx b/apps/web/src/components/chat/ExpandedImageDialog.tsx index 3c4f521b7ccc..aaa4dc2daa59 100644 --- a/apps/web/src/components/chat/ExpandedImageDialog.tsx +++ b/apps/web/src/components/chat/ExpandedImageDialog.tsx @@ -179,8 +179,6 @@ export const ExpandedImageDialog = memo(function ExpandedImageDialog({ variant="media" showCloseButton={false} bottomStickOnMobile={false} - backdropClassName="z-[60]" - viewportClassName="z-[60] grid-rows-1 place-items-center px-4 py-6 [-webkit-app-region:no-drag]" className="row-start-1 max-h-[92vh] w-[92vw] max-w-[92vw] items-center overflow-visible [--media-width:92vw] [--media-height:min(86vh,calc(100vh-160px))] sm:[--media-width:calc(92vw-96px)]" onKeyDown={onKeyDown} initialFocus={closeButtonRef} diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx index da5108f917c1..0d31429fe994 100644 --- a/apps/web/src/components/chat/MessagesTimeline.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.tsx @@ -2567,7 +2567,7 @@ function BackgroundWorktreeSetupChip({ snapshot }: { snapshot: WorktreeSetupSnap align="end" width="lg" className="surface-glass!" - viewportClassName="py-3 [--viewport-inline-padding:--spacing(3)]" + padding="compact" > - {/* Search bar */} -
-
- - } - value={searchQuery} - onChange={(e) => setSearchQuery(e.target.value)} - onKeyDown={(e) => { - if ( - showSidebar && - !e.altKey && - !e.ctrlKey && - !e.metaKey && - ((e.key === "ArrowLeft" && !e.shiftKey && searchQuery.length === 0) || - (e.key === "Tab" && e.shiftKey)) - ) { - const sidebar = e.currentTarget - .closest("[data-model-picker-content]") - ?.querySelector("[data-model-picker-sidebar]"); - const button = - sidebar?.querySelector( - 'button[aria-pressed="true"]:not(:disabled)', - ) ?? sidebar?.querySelector("button:not(:disabled)"); - if (button) { - e.preventDefault(); - e.stopPropagation(); - button.focus(); - return; - } - } - if (e.key === "Escape") { - e.preventDefault(); - e.stopPropagation(); - props.onRequestClose?.(); - return; - } - if (e.key === "Enter" && highlightedModelKeyRef.current) { - ( - e as typeof e & { preventBaseUIHandler?: () => void } - ).preventBaseUIHandler?.(); - e.preventDefault(); - e.stopPropagation(); - const legacyInstanceId = parseModelPickerLegacySectionKey( - highlightedModelKeyRef.current, - ); - if (legacyInstanceId) { - toggleLegacySection(legacyInstanceId); - return; - } - const model = parseModelPickerModelKey(highlightedModelKeyRef.current); - if (model) { - handleModelSelect(model.slug, model.instanceId, e.shiftKey); - } - return; - } + setSearchQuery(e.target.value)} + onKeyDown={(e) => { + if ( + showSidebar && + !e.altKey && + !e.ctrlKey && + !e.metaKey && + ((e.key === "ArrowLeft" && !e.shiftKey && searchQuery.length === 0) || + (e.key === "Tab" && e.shiftKey)) + ) { + const sidebar = e.currentTarget + .closest("[data-model-picker-content]") + ?.querySelector("[data-model-picker-sidebar]"); + const button = + sidebar?.querySelector( + 'button[aria-pressed="true"]:not(:disabled)', + ) ?? sidebar?.querySelector("button:not(:disabled)"); + if (button) { + e.preventDefault(); e.stopPropagation(); - }} - onMouseDown={(e) => e.stopPropagation()} - onTouchStart={(e) => e.stopPropagation()} - size="sm" - unstyled - /> -
-
+ button.focus(); + return; + } + } + if (e.key === "Escape") { + e.preventDefault(); + e.stopPropagation(); + props.onRequestClose?.(); + return; + } + if (e.key === "Enter" && highlightedModelKeyRef.current) { + (e as typeof e & { preventBaseUIHandler?: () => void }).preventBaseUIHandler?.(); + e.preventDefault(); + e.stopPropagation(); + const legacyInstanceId = parseModelPickerLegacySectionKey( + highlightedModelKeyRef.current, + ); + if (legacyInstanceId) { + toggleLegacySection(legacyInstanceId); + return; + } + const model = parseModelPickerModelKey(highlightedModelKeyRef.current); + if (model) { + handleModelSelect(model.slug, model.instanceId, e.shiftKey); + } + return; + } + e.stopPropagation(); + }} + onMouseDown={(e) => e.stopPropagation()} + onTouchStart={(e) => e.stopPropagation()} + /> {/* Model list */}
@@ -971,7 +956,6 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { value={modelKey} aria-expanded={legacySection.isExpanded} className="group w-full cursor-pointer rounded-md px-2 py-2" - contentClassName="flex w-full items-center gap-3" >
Legacy models
diff --git a/apps/web/src/components/chat/ProviderModelPicker.tsx b/apps/web/src/components/chat/ProviderModelPicker.tsx index 7ccf4f93ba6c..21c90b02131c 100644 --- a/apps/web/src/components/chat/ProviderModelPicker.tsx +++ b/apps/web/src/components/chat/ProviderModelPicker.tsx @@ -284,8 +284,8 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { {tooltip} - -
+ +
Accessibility data {accessibilityDetails ? ( } label={label} accessibleLabel={`Terminal excerpt, ${label}`} - popupClassName="w-[min(40rem,calc(100vw-2rem))]" - viewportClassName="overflow-hidden p-2" >
diff --git a/apps/web/src/components/contextChipParts.tsx b/apps/web/src/components/contextChipParts.tsx index faa6fa98c7d1..4517bbd61056 100644 --- a/apps/web/src/components/contextChipParts.tsx +++ b/apps/web/src/components/contextChipParts.tsx @@ -8,7 +8,6 @@ import { type ReactNode, } from "react"; -import { cn } from "~/lib/utils"; import { PULL_REQUEST_STATE_PRESENTATION } from "~/components/pullRequest/pullRequestIcons"; import type { PullRequestContextDisplayState } from "~/lib/composerContextRecords"; import { PierreEntryIcon } from "./chat/PierreEntryIcon"; @@ -70,8 +69,6 @@ export function ContextChipPopover(props: { copyMarkdown?: string; accessibleLabel: string; children: ReactNode; - popupClassName?: string; - viewportClassName?: string; }) { return ( @@ -88,12 +85,7 @@ export function ContextChipPopover(props: { {props.icon} {props.label} - + {props.accessibleLabel} {props.children} diff --git a/apps/web/src/components/preview/previewMiniPlayerLayout.ts b/apps/web/src/components/preview/previewMiniPlayerLayout.ts index faa529ae91c2..c2db453b29a3 100644 --- a/apps/web/src/components/preview/previewMiniPlayerLayout.ts +++ b/apps/web/src/components/preview/previewMiniPlayerLayout.ts @@ -11,7 +11,7 @@ import type { DeviceScreenSize } from "@t3tools/client-runtime/device/stream"; export const PREVIEW_MINI_PLAYER_EDGE_GAP = 12; export const PREVIEW_MINI_PLAYER_CORNER_RADIUS = 12; -// The mini-player shell straddles this webview at 47 and 49; dialogs begin at 50. +// The mini-player shell straddles this webview at 47 and 49: above --z-sheet, under dialogs (50). export const PREVIEW_MINI_PLAYER_WEBVIEW_Z_INDEX = 48; // A fresh player is the largest box at the source aspect ratio that fits here. const PREVIEW_MINI_PLAYER_DEFAULT_BOX = { width: 320, height: 320 } as const; diff --git a/apps/web/src/components/pullRequest/PullRequestCandidatePicker.tsx b/apps/web/src/components/pullRequest/PullRequestCandidatePicker.tsx index 00ced09cfbf0..d8e7058783cb 100644 --- a/apps/web/src/components/pullRequest/PullRequestCandidatePicker.tsx +++ b/apps/web/src/components/pullRequest/PullRequestCandidatePicker.tsx @@ -147,7 +147,6 @@ export function PullRequestCandidatePicker({ index={index} value={keys[index]} disabled={disabled} - contentClassName="flex min-w-0 items-center gap-2" > {children(candidate)} diff --git a/apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx b/apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx index 3155ad665150..5d3efe70723f 100644 --- a/apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx +++ b/apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx @@ -388,12 +388,7 @@ function PullRequestBaseFreshnessWarning({ {children} - +

{summary}

Changes can be cleanly merged.

{/* Each way the host offers and this reader may take, as its own button: a split button diff --git a/apps/web/src/components/pullRequest/PullRequestReactions.tsx b/apps/web/src/components/pullRequest/PullRequestReactions.tsx index 22101b037a5a..f653dcea94d2 100644 --- a/apps/web/src/components/pullRequest/PullRequestReactions.tsx +++ b/apps/web/src/components/pullRequest/PullRequestReactions.tsx @@ -134,7 +134,7 @@ export function PullRequestReactionBar({ > - +
{PULL_REQUEST_REACTION_ORDER.map((content) => { const reacted = diff --git a/apps/web/src/components/settings/SettingInheritance.tsx b/apps/web/src/components/settings/SettingInheritance.tsx index da524448f0b2..e08ca680649f 100644 --- a/apps/web/src/components/settings/SettingInheritance.tsx +++ b/apps/web/src/components/settings/SettingInheritance.tsx @@ -223,11 +223,7 @@ export function SettingInheritance({ {overrideSummary} - +
{chains.map(({ target, environment, machine, layers }) => (