authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-02 15:56:28-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-02 15:56:28-07:00
logb59b7d0af9536a2d61ddfcf8d33420b890b2faca
tree30b4716b04307a9554c2bb9a77513f581fe93954
parentfb93902ee24d4ba380508df33d25478d3baf7a72
signature Signed by PGP key B5690EEEBB952194

fix(web): unify control sizing across settings pages (#9281)

Co-authored-by: Claude Code <noreply@anthropic.com>

20 files changed, 154 insertions(+), 93 deletions(-)

apps/web/src/components/BranchToolbarBranchSelector.tsx+1-1
......@@ -850,7 +850,7 @@ export function BranchToolbarBranchSelector({
850850 <Switch
851851 id={startFromOriginSwitchId}
852852 checked={startFromOrigin}
853 className="[--thumb-size:--spacing(3.5)]"
853 size="sm"
854854 aria-label="Start worktree from origin"
855855 onCheckedChange={(checked) => onStartFromOriginChange(Boolean(checked))}
856856 />
apps/web/src/components/ServerUpdateAction.tsx+4-2
......@@ -83,6 +83,7 @@ export function ServerUpdateAction({
8383 targetVersion,
8484 label = "Update",
8585 variant = "outline",
86 size = "xs",
8687}: {
8788 readonly environmentId: EnvironmentId;
8889 readonly serverLabel: string;
......@@ -95,6 +96,7 @@ export function ServerUpdateAction({
9596 readonly targetVersion: string;
9697 readonly label?: string;
9798 readonly variant?: ComponentProps<typeof Button>["variant"];
99 readonly size?: ComponentProps<typeof Button>["size"];
98100}) {
99101 const isDesktopAppUpdate = selfUpdate === "desktop-managed";
100102 const continueThreadsAfterServerUpdate = useClientSettings(
......@@ -185,14 +187,14 @@ export function ServerUpdateAction({
185187 if (selfUpdate === null) {
186188 const command = manualServerUpdateCommand(targetVersion);
187189 return (
188 <Button size="xs" variant={variant} onClick={() => copyToClipboard(command, { command })}>
190 <Button size={size} variant={variant} onClick={() => copyToClipboard(command, { command })}>
189191 Copy update command
190192 </Button>
191193 );
192194 }
193195
194196 return (
195 <Button size="xs" variant={variant} onClick={() => void handleUpdate()}>
197 <Button size={size} variant={variant} onClick={() => void handleUpdate()}>
196198 {label}
197199 </Button>
198200 );
apps/web/src/components/settings/AddProviderInstanceDialog.tsx+4-9
......@@ -380,9 +380,9 @@ export function AddProviderInstanceDialog({
380380 {accentColor ? (
381381 <Button
382382 type="button"
383 size="sm"
383 size="xs"
384384 variant="ghost"
385 className="h-7 px-2 text-xs text-muted-foreground"
385 className="text-muted-foreground"
386386 onClick={() => setAccentColor("")}
387387 >
388388 Clear
......@@ -417,7 +417,6 @@ export function AddProviderInstanceDialog({
417417 <DialogFooter variant="bare">
418418 <Button
419419 variant="outline"
420 size="sm"
421420 onClick={() => {
422421 if (wizardStep === 0) {
423422 onOpenChange(false);
......@@ -429,13 +428,9 @@ export function AddProviderInstanceDialog({
429428 {wizardStep === 0 ? "Cancel" : "Back"}
430429 </Button>
431430 {wizardStep < ADD_PROVIDER_WIZARD_STEPS.length - 1 ? (
432 <Button size="sm" onClick={() => navigateToStep(wizardStep + 1)}>
433 Next
434 </Button>
431 <Button onClick={() => navigateToStep(wizardStep + 1)}>Next</Button>
435432 ) : (
436 <Button size="sm" onClick={handleSave}>
437 Add instance
438 </Button>
433 <Button onClick={handleSave}>Add instance</Button>
439434 )}
440435 </DialogFooter>
441436 </div>
apps/web/src/components/settings/ConnectionsSettings.tsx+6-3
......@@ -785,7 +785,7 @@ const PairingLinkListRow = memo(function PairingLinkListRow({
785785 Done
786786 </Button>
787787 {canCopyToClipboard ? (
788 <Button variant="outline" size="xs" onClick={handleCopyCode}>
788 <Button variant="outline" onClick={handleCopyCode}>
789789 Copy code
790790 </Button>
791791 ) : null}
......@@ -2784,7 +2784,7 @@ export function ConnectionsSettings() {
27842784 status={<span className="block text-destructive">{desktopWslError}</span>}
27852785 control={
27862786 <Button
2787 size="xs"
2787 size="sm"
27882788 variant="outline"
27892789 onClick={loadWslState}
27902790 disabled={isLoadingWslState}
......@@ -2819,6 +2819,7 @@ export function ConnectionsSettings() {
28192819 }
28202820 control={
28212821 <Button
2822 size="sm"
28222823 variant="outline"
28232824 disabled={isUpdatingWslBackend}
28242825 onClick={() => handleSelectWslMode(BACKEND_VALUE_WSL_OFF)}
......@@ -2867,6 +2868,7 @@ export function ConnectionsSettings() {
28672868 }}
28682869 >
28692870 <SelectTrigger
2871 size="sm"
28702872 className="w-full sm:w-56"
28712873 aria-label="WSL backend"
28722874 disabled={isUpdatingWslBackend}
......@@ -3062,6 +3064,7 @@ export function ConnectionsSettings() {
30623064 primaryEnvironmentId !== null &&
30633065 primaryServerUpdateState.status !== "running" ? (
30643066 <ServerUpdateAction
3067 size="sm"
30653068 environmentId={primaryEnvironmentId}
30663069 serverLabel={
30673070 primaryEnvironment ? `${primaryEnvironment.label} server` : "server"
......@@ -3418,7 +3421,7 @@ export function ConnectionsSettings() {
34183421 <Button
34193422 size="xs"
34203423 variant="ghost"
3421 className="h-5 gap-1 rounded-sm px-1 text-[11px] font-normal text-muted-foreground/60 hover:text-muted-foreground"
3424 className="font-normal text-muted-foreground/60 hover:text-muted-foreground"
34223425 aria-label="Add environment"
34233426 >
34243427 <PlusIcon className="size-3" />
apps/web/src/components/settings/DiagnosticsSettings.tsx+4-4
......@@ -793,13 +793,13 @@ function DiagnosticsRefreshButton({
793793 <TooltipTrigger
794794 render={
795795 <Button
796 size="icon-micro"
796 size="icon-xs"
797797 variant="ghost-muted"
798798 disabled={isPending}
799799 onClick={onClick}
800800 aria-label={label}
801801 >
802 <RefreshCwIcon className={cn("size-3", isPending && "animate-spin")} />
802 <RefreshCwIcon className={cn(isPending && "animate-spin")} />
803803 </Button>
804804 }
805805 />
......@@ -1129,13 +1129,13 @@ export function DiagnosticsSettingsPanel() {
11291129 <TooltipTrigger
11301130 render={
11311131 <Button
1132 size="icon-micro"
1132 size="icon-xs"
11331133 variant="ghost-muted"
11341134 disabled={!observability?.logsDirectoryPath || isOpeningLogsDirectory}
11351135 onClick={openLogsDirectory}
11361136 aria-label="Open logs folder"
11371137 >
1138 <FolderOpenIcon className="size-3" />
1138 <FolderOpenIcon />
11391139 </Button>
11401140 }
11411141 />
apps/web/src/components/settings/FontFamilyPicker.tsx+3-5
......@@ -11,6 +11,7 @@ import {
1111 ComboboxPopup,
1212 ComboboxTrigger,
1313} from "../ui/combobox";
14import { selectTriggerVariants } from "../ui/select";
1415
1516const DEFAULT_FONT_VALUE = "__default__";
1617
......@@ -203,14 +204,11 @@ export function FontFamilyPicker({
203204 void listRef.current?.scrollIndexIntoView?.({ index: eventDetails.index, animated: false });
204205 }}
205206 >
206 <ComboboxTrigger
207 aria-label={ariaLabel}
208 className="relative inline-flex min-h-9 w-full min-w-36 cursor-pointer select-none items-center justify-between gap-2 rounded-lg border border-input bg-background px-[calc(--spacing(3)-1px)] text-left text-base text-foreground shadow-xs/5 outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/24 sm:min-h-8 sm:text-sm dark:bg-input/32"
209 >
207 <ComboboxTrigger aria-label={ariaLabel} className={selectTriggerVariants({ size: "sm" })}>
210208 <span className="min-w-0 truncate">
211209 {selectedFamily.length === 0 ? defaultFamily : selectedFamily}
212210 </span>
213 <ChevronDownIcon className="-me-1 size-3 shrink-0 text-muted-foreground opacity-50" />
211 <ChevronDownIcon className="-me-1 size-3 opacity-50" />
214212 </ComboboxTrigger>
215213 <ComboboxPopup align="end" className="flex w-72 flex-col">
216214 <div className="shrink-0 px-3 pt-2.5">
apps/web/src/components/settings/IntegrationsSettings.tsx+12-4
......@@ -354,7 +354,7 @@ function BrowserZoomSetting({ disabled }: { readonly disabled: boolean }) {
354354 if (next !== undefined) updateSettings({ browserDefaultZoomFactor: next });
355355 }}
356356 >
357 <SelectTrigger className="w-full sm:w-40" aria-label="Default browser zoom">
357 <SelectTrigger size="sm" className="w-full sm:w-40" aria-label="Default browser zoom">
358358 <SelectValue>{zoomLabel(zoomFactor)}</SelectValue>
359359 </SelectTrigger>
360360 <SelectPopup align="end" alignItemWithTrigger={false}>
......@@ -396,7 +396,11 @@ function BrowserAppearanceSetting({ disabled }: { readonly disabled: boolean })
396396 }
397397 }}
398398 >
399 <SelectTrigger className="w-full sm:w-40" aria-label="Default browser appearance">
399 <SelectTrigger
400 size="sm"
401 className="w-full sm:w-40"
402 aria-label="Default browser appearance"
403 >
400404 <SelectValue>{APPEARANCE_LABELS[appearance]}</SelectValue>
401405 </SelectTrigger>
402406 <SelectPopup align="end" alignItemWithTrigger={false}>
......@@ -441,7 +445,11 @@ function BrowserRecordingFrameRateSetting({ disabled }: { readonly disabled: boo
441445 }
442446 }}
443447 >
444 <SelectTrigger className="w-full sm:w-40" aria-label="Browser recording frame rate">
448 <SelectTrigger
449 size="sm"
450 className="w-full sm:w-40"
451 aria-label="Browser recording frame rate"
452 >
445453 <SelectValue>{frameRate} fps</SelectValue>
446454 </SelectTrigger>
447455 <SelectPopup align="end" alignItemWithTrigger={false}>
......@@ -706,7 +714,7 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {
706714 render={
707715 <span className="inline-flex" {...(!removalAvailable ? { tabIndex: 0 } : {})}>
708716 <Button
709 size="icon-sm"
717 size="icon-xs"
710718 variant="ghost-muted"
711719 disabled={profileWritesDisabled || !removalAvailable}
712720 aria-label={`Remove ${profile.name}`}
apps/web/src/components/settings/KeybindingsSettings.tsx+15-15
......@@ -131,12 +131,12 @@ function ExpandableHeaderSearch({
131131 render={
132132 <Button
133133 type="button"
134 size="icon-micro"
134 size="icon-xs"
135135 variant="ghost-muted"
136136 onClick={() => onOpenChange(true)}
137137 aria-label="Search keybindings"
138138 >
139 <SearchIcon className="size-3" />
139 <SearchIcon />
140140 </Button>
141141 }
142142 />
......@@ -168,7 +168,7 @@ function ExpandableHeaderSearch({
168168 placeholder="Search keybindings"
169169 aria-label="Search keybindings"
170170 className="w-44 [&_[data-slot=input]]:pl-7"
171 size="compact"
171 size="sm"
172172 />
173173 </div>
174174 );
......@@ -850,7 +850,7 @@ function KeybindingKeyControl({
850850 <>
851851 {isDirty ? (
852852 <Button
853 size="compact"
853 size="sm"
854854 disabled={isSaving || keyDraft.trim().length === 0 || !isWhenDraftValid}
855855 onClick={save}
856856 >
......@@ -863,7 +863,7 @@ function KeybindingKeyControl({
863863 onClick={() => setDraft({ isRecording: true })}
864864 aria-label={`Edit shortcut for ${commandLabel(row.command)}: ${formatShortcutLabel(row.binding.shortcut)}`}
865865 className={cn(
866 "inline-flex h-7 cursor-pointer items-center rounded-md border border-transparent px-1.5 outline-none transition-colors hover:border-border/70 hover:bg-accent focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/24",
866 "inline-flex h-8 cursor-pointer items-center rounded-md border border-transparent px-1.5 sm:h-7 outline-none transition-colors hover:border-border/70 hover:bg-accent focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/24",
867867 pillClassName,
868868 )}
869869 >
......@@ -876,7 +876,7 @@ function KeybindingKeyControl({
876876 aria-label={`Keybinding for ${commandLabel(row.command)}`}
877877 value={isRecording ? "" : keyDraft}
878878 placeholder={isRecording ? "Press shortcut" : "Unassigned"}
879 size="compact"
879 size="sm"
880880 className={cn("w-44 font-mono", isRecording && "border-primary/70 bg-primary/5")}
881881 onFocus={() => setDraft({ isRecording: true })}
882882 onBlur={() => setDraft({ isRecording: false })}
......@@ -954,7 +954,7 @@ function KeybindingRowMenu({
954954 type="button"
955955 variant="ghost"
956956 size="icon-sm"
957 className="size-7 text-muted-foreground hover:text-foreground sm:size-7"
957 className="text-muted-foreground hover:text-foreground"
958958 disabled={isSaving}
959959 aria-label={`Actions for ${commandLabel(row.command)}`}
960960 />
......@@ -1155,7 +1155,7 @@ function NewKeybindingCommandSelect({
11551155 value={draft.commandDraft}
11561156 onValueChange={(value) => draft.setCommandDraft(value as KeybindingCommand)}
11571157 >
1158 <SelectTrigger size="compact" className={className}>
1158 <SelectTrigger size="sm" className={className}>
11591159 <SelectValue placeholder="Command" />
11601160 </SelectTrigger>
11611161 <SelectContent
......@@ -1189,7 +1189,7 @@ function NewKeybindingKeyInput({
11891189 aria-label={`Keybinding for ${draft.commandLabelText}`}
11901190 value={draft.isRecording ? "" : draft.keyDraft}
11911191 placeholder={draft.isRecording ? "Press shortcut" : "Unassigned"}
1192 size="compact"
1192 size="sm"
11931193 className={cn("font-mono", draft.isRecording && "border-primary/70 bg-primary/5", className)}
11941194 onFocus={() => draft.setDraft({ isRecording: true })}
11951195 onBlur={() => draft.setDraft({ isRecording: false })}
......@@ -1233,7 +1233,7 @@ function NewKeybindingCancelIcon({
12331233 type="button"
12341234 variant="ghost"
12351235 size="icon-sm"
1236 className="size-7 text-muted-foreground hover:text-foreground"
1236 className="text-muted-foreground hover:text-foreground"
12371237 disabled={isSaving}
12381238 aria-label="Cancel new keybinding"
12391239 onClick={onCancel}
......@@ -1271,7 +1271,7 @@ function NewKeybindingSettingsRow(props: NewKeybindingProps) {
12711271 />
12721272 <KeybindingConflictWarning labels={draft.conflictLabels} />
12731273 <NewKeybindingKeyInput draft={draft} className="w-44" />
1274 <Button size="compact" disabled={isSaving || !draft.canSave} onClick={draft.save}>
1274 <Button size="sm" disabled={isSaving || !draft.canSave} onClick={draft.save}>
12751275 {isSaving ? "Saving" : "Save"}
12761276 </Button>
12771277 <NewKeybindingCancelIcon isSaving={isSaving} onCancel={onCancel} />
......@@ -1514,12 +1514,12 @@ export function KeybindingsSettingsPanel() {
15141514 render={
15151515 <Button
15161516 type="button"
1517 size="icon-micro"
1517 size="icon-xs"
15181518 variant="ghost-muted"
15191519 onClick={() => setIsAddingBinding(true)}
15201520 aria-label="Add keybinding"
15211521 >
1522 <PlusIcon className="size-3" />
1522 <PlusIcon />
15231523 </Button>
15241524 }
15251525 />
......@@ -1530,13 +1530,13 @@ export function KeybindingsSettingsPanel() {
15301530 render={
15311531 <Button
15321532 type="button"
1533 size="icon-micro"
1533 size="icon-xs"
15341534 variant="ghost-muted"
15351535 disabled={!keybindingsConfigPath}
15361536 onClick={openKeybindingsFile}
15371537 aria-label="Open keybindings.json"
15381538 >
1539 <FileJsonIcon className="size-3" />
1539 <FileJsonIcon />
15401540 </Button>
15411541 }
15421542 />
apps/web/src/components/settings/ProjectSettingsPanel.tsx+10-7
......@@ -106,6 +106,7 @@ import {
106106} from "../WorkspaceBreadcrumb";
107107import { WorkspacePageHeader } from "../WorkspacePageHeader";
108108import {
109 SETTINGS_PICKER_TRIGGER_CLASSNAME,
109110 SettingResetButton,
110111 SettingsPageContainer,
111112 SettingsRow,
......@@ -803,6 +804,7 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) {
803804 control={
804805 <Input
805806 key={`${group.projectKey}:${group.displayName}`}
807 size="sm"
806808 className="w-full sm:w-64"
807809 aria-label="Project name"
808810 defaultValue={group.displayName}
......@@ -841,7 +843,7 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) {
841843 className="size-6"
842844 />
843845 <Button
844 size="xs"
846 size="sm"
845847 variant="outline"
846848 type="button"
847849 aria-label="Choose a project icon file"
......@@ -874,7 +876,7 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) {
874876 instanceEntries={instanceEntries}
875877 modelOptionsByInstance={modelOptionsByInstance}
876878 triggerVariant="outline"
877 triggerClassName="min-w-0 max-w-none shrink-0 text-foreground/90 hover:text-foreground"
879 triggerClassName={SETTINGS_PICKER_TRIGGER_CLASSNAME}
878880 onInstanceModelChange={(instanceId, model) => {
879881 setDefaultModel(createModelSelection(instanceId, model));
880882 }}
......@@ -889,7 +891,7 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) {
889891 allowPromptInjectedEffort={false}
890892 planModeEnabled={projectSettings.planModeEnabled}
891893 triggerVariant="outline"
892 triggerClassName="min-w-0 max-w-none shrink-0 text-foreground/90 hover:text-foreground"
894 triggerClassName={SETTINGS_PICKER_TRIGGER_CLASSNAME}
893895 onModelOptionsChange={(nextOptions) => {
894896 setDefaultModel(
895897 createModelSelection(
......@@ -928,7 +930,7 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) {
928930 }
929931 }}
930932 >
931 <SelectTrigger aria-label="New-thread workspace">
933 <SelectTrigger size="sm" aria-label="New-thread workspace">
932934 <SelectValue>
933935 {storedEnvMode === null
934936 ? group.memberProjects.length > 1
......@@ -969,7 +971,7 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) {
969971 value={selectedCheckout.physicalProjectKey}
970972 onValueChange={(value) => setSelectedCheckoutKey(String(value))}
971973 >
972 <SelectTrigger className="max-w-64" aria-label="Selected checkout">
974 <SelectTrigger size="sm" className="max-w-64" aria-label="Selected checkout">
973975 <SelectValue>{selectedCheckoutLabel}</SelectValue>
974976 </SelectTrigger>
975977 <SelectPopup align="end" alignItemWithTrigger={false}>
......@@ -1034,7 +1036,7 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) {
10341036 }
10351037 }}
10361038 >
1037 <SelectTrigger aria-label={`Grouping rule for ${selectedCheckoutLabel}`}>
1039 <SelectTrigger size="sm" aria-label={`Grouping rule for ${selectedCheckoutLabel}`}>
10381040 <SelectValue>
10391041 {selectedCheckoutGrouping === "inherit"
10401042 ? `Default (${PROJECT_GROUPING_MODE_LABELS[projectGroupingSettings.sidebarProjectGroupingMode]})`
......@@ -1064,7 +1066,7 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) {
10641066 description="Removes this checkout and its threads from the project group. Files on disk are not touched."
10651067 control={
10661068 <Button
1067 size="xs"
1069 size="sm"
10681070 variant="destructive-outline"
10691071 onClick={() => void removeMembers([selectedCheckout])}
10701072 >
......@@ -1210,6 +1212,7 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) {
12101212 }
12111213 control={
12121214 <Button
1215 size="sm"
12131216 variant="destructive-outline"
12141217 onClick={() => void removeMembers(group.memberProjects)}
12151218 >
apps/web/src/components/settings/ProviderInstanceCard.tsx+6-5
......@@ -258,7 +258,7 @@ function ProviderEnvironmentSection(props: {
258258 {rows.map((variable, index) => (
259259 <div key={variable.id} className="flex min-w-0 items-center gap-1.5">
260260 <DraftInput
261 size="compact"
261 size="sm"
262262 className="w-44 shrink-0 font-mono"
263263 value={variable.name}
264264 onCommit={(name) => updateVariable(variable.id, { name: name.trim() })}
......@@ -270,7 +270,7 @@ function ProviderEnvironmentSection(props: {
270270 =
271271 </span>
272272 <DraftInput
273 size="compact"
273 size="sm"
274274 className="min-w-0 flex-1 font-mono"
275275 value={variable.valueRedacted ? "" : variable.value}
276276 onCommit={(value) => updateVariable(variable.id, { value })}
......@@ -679,10 +679,10 @@ export function ProviderInstanceCard({
679679 render={
680680 <Button
681681 type="button"
682 size="icon-xs"
682 size="icon-micro"
683683 variant="ghost"
684684 className={cn(
685 "size-5 rounded-sm p-0 [--control-icon-color:currentColor]",
685 "[--control-icon-color:currentColor]",
686686 versionAdvisory.emphasis === "strong"
687687 ? "text-warning hover:text-warning"
688688 : "text-muted-foreground hover:text-foreground",
......@@ -748,7 +748,7 @@ export function ProviderInstanceCard({
748748 type="button"
749749 size="icon-xs"
750750 variant="ghost"
751 className="size-6 shrink-0 rounded-sm p-0 text-muted-foreground hover:text-foreground"
751 className="shrink-0 text-muted-foreground hover:text-foreground"
752752 onClick={() =>
753753 copyToClipboard(updateCommand, {
754754 providerName: displayName,
......@@ -858,6 +858,7 @@ export function ProviderInstanceCard({
858858 <div className="flex min-w-0 flex-wrap items-center gap-3">
859859 <DraftInput
860860 id={`provider-instance-${instanceId}-display-name`}
861 size="sm"
861862 className="w-44"
862863 value={instance.displayName ?? ""}
863864 onCommit={updateDisplayName}
apps/web/src/components/settings/ProviderModelsSection.tsx+5-4
......@@ -386,7 +386,7 @@ export function ProviderModelsSection({
386386 <Tooltip>
387387 <TooltipTrigger render={<span className="flex shrink-0 items-center" />}>
388388 <Switch
389 className="sm:[--thumb-size:--spacing(3.5)]"
389 size="sm"
390390 checked={!isHidden}
391391 disabled={model.isCustom}
392392 onCheckedChange={(checked) => setHidden(model.slug, !checked)}
......@@ -463,7 +463,7 @@ export function ProviderModelsSection({
463463 value={filter}
464464 onChange={(event) => setFilter(event.target.value)}
465465 placeholder="Filter models"
466 size="compact"
466 size="sm"
467467 className="w-56"
468468 spellCheck={false}
469469 aria-label="Filter models"
......@@ -509,6 +509,7 @@ export function ProviderModelsSection({
509509 <div className="mt-3 flex flex-col gap-2 sm:flex-row">
510510 <Input
511511 id={`provider-instance-${instanceId}-custom-model`}
512 size="sm"
512513 autoFocus
513514 value={input}
514515 onChange={(event) => {
......@@ -529,10 +530,10 @@ export function ProviderModelsSection({
529530 spellCheck={false}
530531 />
531532 <div className="flex shrink-0 gap-2">
532 <Button variant="outline" onClick={handleAdd}>
533 <Button size="sm" variant="outline" onClick={handleAdd}>
533534 Add
534535 </Button>
535 <Button variant="ghost" onClick={cancelAdd}>
536 <Button size="sm" variant="ghost" onClick={cancelAdd}>
536537 Cancel
537538 </Button>
538539 </div>
apps/web/src/components/settings/ProviderSettingsForm.tsx+3-1
......@@ -219,7 +219,7 @@ function ProviderSettingsFieldRow({
219219 )}
220220 <div className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1">
221221 {field.control === "switch" ? (
222 <span className="flex h-8 items-center">
222 <span className="flex h-8 items-center sm:h-7">
223223 <Switch
224224 checked={readProviderConfigBoolean(value, field.key, field.defaultBooleanValue)}
225225 onCheckedChange={(checked) =>
......@@ -245,6 +245,7 @@ function ProviderSettingsFieldRow({
245245 <DraftInput
246246 id={inputId}
247247 aria-describedby={descriptionId}
248 size="sm"
248249 className="w-56"
249250 type={field.control === "password" ? "password" : undefined}
250251 autoComplete={field.control === "password" ? "off" : undefined}
......@@ -313,6 +314,7 @@ function ProviderSettingsFieldRow({
313314 {variant === "card" ? (
314315 <DraftInput
315316 id={inputId}
317 size="sm"
316318 className="mt-1.5"
317319 type={type}
318320 autoComplete={field.control === "password" ? "off" : undefined}
apps/web/src/components/settings/ProviderSettingsPanel.tsx+3-3
......@@ -869,7 +869,7 @@ export function EnvironmentProviderSettings({
869869 <TooltipTrigger
870870 render={
871871 <Button
872 size="compact"
872 size="xs"
873873 variant="ghost-muted"
874874 disabled={isRefreshingProviders}
875875 onClick={() => void refreshProviders()}
......@@ -892,12 +892,12 @@ export function EnvironmentProviderSettings({
892892 <TooltipTrigger
893893 render={
894894 <Button
895 size="icon-micro"
895 size="icon-xs"
896896 variant="ghost-muted"
897897 onClick={() => setIsAddInstanceDialogOpen(true)}
898898 aria-label="Add provider"
899899 >
900 <PlusIcon className="size-3" />
900 <PlusIcon />
901901 </Button>
902902 }
903903 />
apps/web/src/components/settings/SettingsPanels.tsx+35-14
......@@ -146,6 +146,7 @@ import {
146146} from "./SettingsPanels.logic";
147147import {
148148 PolicyTooltip,
149 SETTINGS_PICKER_TRIGGER_CLASSNAME,
149150 SettingResetButton,
150151 SettingsPageContainer,
151152 SettingsRow,
......@@ -391,7 +392,7 @@ function AboutVersionSection() {
391392 <TooltipTrigger
392393 render={
393394 <Button
394 size="xs"
395 size="sm"
395396 variant="outline"
396397 disabled={buttonDisabled || isUpdateActionPending}
397398 onClick={handleButtonClick}
......@@ -416,6 +417,7 @@ function AboutVersionSection() {
416417 }}
417418 >
418419 <SelectTrigger
420 size="sm"
419421 className="w-full sm:w-40"
420422 aria-label="Update track"
421423 disabled={isChangingUpdateChannel}
......@@ -449,7 +451,7 @@ function AboutVersionSection() {
449451 );
450452 }}
451453 >
452 <SelectTrigger className="w-full sm:w-40" aria-label="Update track">
454 <SelectTrigger size="sm" className="w-full sm:w-40" aria-label="Update track">
453455 <SelectValue>{HOSTED_APP_CHANNEL_LABEL}</SelectValue>
454456 </SelectTrigger>
455457 <SelectPopup align="end" alignItemWithTrigger={false}>
......@@ -805,7 +807,11 @@ function BackgroundActivityAdvancedDialog({
805807 }
806808 }}
807809 >
808 <SelectTrigger className="w-full sm:w-40" aria-label="Shared background policy">
810 <SelectTrigger
811 size="sm"
812 className="w-full sm:w-40"
813 aria-label="Shared background policy"
814 >
809815 <SelectValue>{BACKGROUND_ACTIVITY_PROFILE_LABELS[activeProfile]}</SelectValue>
810816 </SelectTrigger>
811817 <SelectPopup align="end" alignItemWithTrigger={false}>
......@@ -1194,7 +1200,11 @@ export function AppearanceSettingsPanel() {
11941200 }
11951201 }}
11961202 >
1197 <SelectTrigger className="w-full sm:w-40" aria-label="Environment identification">
1203 <SelectTrigger
1204 size="sm"
1205 className="w-full sm:w-40"
1206 aria-label="Environment identification"
1207 >
11981208 <SelectValue>
11991209 {ENVIRONMENT_IDENTIFICATION_LABELS[settings.environmentIdentificationMode]}
12001210 </SelectValue>
......@@ -1701,6 +1711,7 @@ function FontFamilySettingsRow({
17011711 />
17021712 ) : (
17031713 <Input
1714 size="sm"
17041715 aria-label={`${title} family`}
17051716 aria-invalid={draftPending || undefined}
17061717 autoCapitalize="off"
......@@ -1760,7 +1771,7 @@ function FontFamilySettingsRow({
17601771 }
17611772 }}
17621773 >
1763 <SelectTrigger className="w-22 shrink-0" aria-label={size.label}>
1774 <SelectTrigger size="sm" className="w-22 shrink-0" aria-label={size.label}>
17641775 <SelectValue>{size.value} px</SelectValue>
17651776 </SelectTrigger>
17661777 <SelectPopup align="end" alignItemWithTrigger={false}>
......@@ -1806,6 +1817,7 @@ function AutoSettleDaysInput({
18061817
18071818 return (
18081819 <Input
1820 size="sm"
18091821 type="number"
18101822 min={MIN_SIDEBAR_AUTO_SETTLE_AFTER_DAYS}
18111823 max={MAX_SIDEBAR_AUTO_SETTLE_AFTER_DAYS}
......@@ -2167,7 +2179,7 @@ export function GeneralSettingsPanel() {
21672179 }
21682180 }}
21692181 >
2170 <SelectTrigger className="w-full sm:w-40" aria-label="Timestamp format">
2182 <SelectTrigger size="sm" className="w-full sm:w-40" aria-label="Timestamp format">
21712183 <SelectValue>{TIMESTAMP_FORMAT_LABELS[settings.timestampFormat]}</SelectValue>
21722184 </SelectTrigger>
21732185 <SelectPopup align="end" alignItemWithTrigger={false}>
......@@ -2358,7 +2370,11 @@ export function GeneralSettingsPanel() {
23582370 }
23592371 }}
23602372 >
2361 <SelectTrigger className="w-full sm:w-40" aria-label="Background activity profile">
2373 <SelectTrigger
2374 size="sm"
2375 className="w-full sm:w-40"
2376 aria-label="Background activity profile"
2377 >
23622378 <SelectValue>
23632379 {BACKGROUND_ACTIVITY_PROFILE_OPTION_LABELS[backgroundActivityProfileOption]}
23642380 </SelectValue>
......@@ -2432,7 +2448,7 @@ export function GeneralSettingsPanel() {
24322448 }
24332449 }}
24342450 >
2435 <SelectTrigger className="w-full sm:w-44" aria-label="Default thread mode">
2451 <SelectTrigger size="sm" className="w-full sm:w-44" aria-label="Default thread mode">
24362452 <SelectValue>
24372453 {settings.defaultThreadEnvMode === "worktree" ? "New worktree" : "Local"}
24382454 </SelectValue>
......@@ -2500,6 +2516,7 @@ export function GeneralSettingsPanel() {
25002516 }
25012517 control={
25022518 <DraftInput
2519 size="sm"
25032520 className="w-full sm:w-72"
25042521 value={settings.addProjectBaseDirectory}
25052522 onCommit={(next) => updateSettings({ addProjectBaseDirectory: next })}
......@@ -2611,7 +2628,11 @@ export function GeneralSettingsPanel() {
26112628 }
26122629 }}
26132630 >
2614 <SelectTrigger className="w-full sm:w-40" aria-label="Quit shortcut behavior">
2631 <SelectTrigger
2632 size="sm"
2633 className="w-full sm:w-40"
2634 aria-label="Quit shortcut behavior"
2635 >
26152636 <SelectValue>{QUIT_CONFIRMATION_MODE_LABELS[settings.confirmQuit]}</SelectValue>
26162637 </SelectTrigger>
26172638 <SelectPopup align="end" alignItemWithTrigger={false}>
......@@ -2652,7 +2673,7 @@ export function GeneralSettingsPanel() {
26522673 instanceEntries={textGenerationModelInstanceEntries}
26532674 modelOptionsByInstance={textGenerationModelOptionsByInstance}
26542675 triggerVariant="outline"
2655 triggerClassName="min-w-0 max-w-none shrink-0 text-foreground/90 hover:text-foreground"
2676 triggerClassName={SETTINGS_PICKER_TRIGGER_CLASSNAME}
26562677 onInstanceModelChange={(instanceId, model) => {
26572678 updateSettings({
26582679 textGenerationModelSelection: resolveAppModelSelectionState(
......@@ -2681,7 +2702,7 @@ export function GeneralSettingsPanel() {
26812702 allowPromptInjectedEffort={false}
26822703 planModeEnabled={settings.planModeEnabled}
26832704 triggerVariant="outline"
2684 triggerClassName="min-w-0 max-w-none shrink-0 text-foreground/90 hover:text-foreground"
2705 triggerClassName={SETTINGS_PICKER_TRIGGER_CLASSNAME}
26852706 onModelOptionsChange={(nextOptions) => {
26862707 updateSettings({
26872708 textGenerationModelSelection: resolveAppModelSelectionState(
......@@ -2716,7 +2737,7 @@ export function GeneralSettingsPanel() {
27162737 {...searchableSetting("diagnostics")}
27172738 description={diagnosticsDescription}
27182739 control={
2719 <Button render={<Link to="/settings/diagnostics" />} size="xs" variant="outline">
2740 <Button render={<Link to="/settings/diagnostics" />} size="sm" variant="outline">
27202741 View diagnostics
27212742 </Button>
27222743 }
......@@ -2924,8 +2945,8 @@ export function ArchivedThreadsPanel() {
29242945 <Button
29252946 type="button"
29262947 variant="outline"
2927 size="sm"
2928 className="h-7 shrink-0 cursor-pointer gap-1.5 px-2.5"
2948 size="xs"
2949 className="shrink-0"
29292950 onClick={() => {
29302951 void (async () => {
29312952 const result = await unarchiveThread(
apps/web/src/components/settings/SharedSettingsMismatchAlert.tsx+1-1
......@@ -23,7 +23,7 @@ export function SharedSettingsMismatchAlert() {
2323 every environment.
2424 </AlertDescription>
2525 <AlertAction>
26 <Button variant="outline" size="compact" onClick={applyToAll}>
26 <Button variant="outline" size="xs" onClick={applyToAll}>
2727 Apply to all
2828 </Button>
2929 </AlertAction>
apps/web/src/components/settings/SourceControlSettings.tsx+4-10
......@@ -312,7 +312,7 @@ function DiscoveryItemRow({
312312 <div className="flex w-full shrink-0 items-center gap-2 sm:w-auto sm:justify-end">
313313 {hasDetails ? (
314314 <Button
315 size="compact"
315 size="icon-xs"
316316 variant="ghost-muted"
317317 onClick={() => setIsExpanded((open) => !open)}
318318 aria-expanded={isExpanded}
......@@ -487,13 +487,7 @@ function EmptySourceControlDiscovery({
487487 </EmptyDescription>
488488 </EmptyHeader>
489489 <EmptyContent>
490 <Button
491 size="sm"
492 variant="outline"
493 className="h-8 gap-1.5 px-3 text-xs"
494 onClick={onScan}
495 disabled={isPending}
496 >
490 <Button size="sm" variant="outline" onClick={onScan} disabled={isPending}>
497491 <RefreshCwIcon className={cn("size-3.5", isPending && "animate-spin")} />
498492 Scan
499493 </Button>
......@@ -533,13 +527,13 @@ export function SourceControlSettingsPanel() {
533527 <TooltipTrigger
534528 render={
535529 <Button
536 size="icon-micro"
530 size="icon-xs"
537531 variant="ghost-muted"
538532 onClick={handleScan}
539533 disabled={discovery.isPending}
540534 aria-label="Rescan server environment"
541535 >
542 <RefreshCwIcon className={cn("size-3", discovery.isPending && "animate-spin")} />
536 <RefreshCwIcon className={cn(discovery.isPending && "animate-spin")} />
543537 </Button>
544538 }
545539 />
apps/web/src/components/settings/SourceControlWritingSettings.tsx+12-3
......@@ -20,7 +20,12 @@ import { ProviderModelPicker } from "../chat/ProviderModelPicker";
2020import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select";
2121import { Switch } from "../ui/switch";
2222import { Textarea } from "../ui/textarea";
23import { SettingResetButton, SettingsRow, SettingsSection } from "./settingsLayout";
23import {
24 SETTINGS_PICKER_TRIGGER_CLASSNAME,
25 SettingResetButton,
26 SettingsRow,
27 SettingsSection,
28} from "./settingsLayout";
2429import { searchableSetting } from "./settingsSearch";
2530
2631const MODE_OPTIONS: Record<SourceControlWritingStyleMode, { label: string; description: string }> =
......@@ -105,7 +110,11 @@ export function SourceControlWritingSettingsSection() {
105110 });
106111 }}
107112 >
108 <SelectTrigger className="w-full sm:w-56" aria-label="Source control writing style">
113 <SelectTrigger
114 size="sm"
115 className="w-full sm:w-56"
116 aria-label="Source control writing style"
117 >
109118 <SelectValue>{MODE_OPTIONS[style.mode].label}</SelectValue>
110119 </SelectTrigger>
111120 <SelectPopup align="end" alignItemWithTrigger={false}>
......@@ -185,7 +194,7 @@ export function SourceControlWritingSettingsSection() {
185194 instanceEntries={instanceEntries}
186195 modelOptionsByInstance={modelOptionsByInstance}
187196 triggerVariant="outline"
188 triggerClassName="min-w-0 max-w-none shrink-0 text-foreground/90 hover:text-foreground"
197 triggerClassName={SETTINGS_PICKER_TRIGGER_CLASSNAME}
189198 triggerAriaLabel="Source control writer model"
190199 onInstanceModelChange={(instanceId, model) => {
191200 updateSettings({
apps/web/src/components/settings/ThemeEditorPanel.tsx+2
......@@ -928,6 +928,7 @@ export function ThemeEditorPanel({
928928 <span className="text-sm font-medium">Theme name</span>
929929 <Input
930930 autoFocus
931 size="sm"
931932 onChange={(event) => {
932933 setName(event.currentTarget.value);
933934 // Most save failures are name collisions; retyping is the fix, so
......@@ -949,6 +950,7 @@ export function ThemeEditorPanel({
949950 <Button
950951 aria-disabled={lockReason !== null}
951952 aria-pressed={isActive}
953 size="sm"
952954 className={lockReason !== null ? "opacity-50" : undefined}
953955 style={isActive ? { boxShadow: "inset 0 0 0 1px var(--ring)" } : undefined}
954956 variant={isActive ? "secondary" : "outline"}
apps/web/src/components/settings/settingsLayout.tsx+14
......@@ -100,6 +100,14 @@ export function SettingsSearchTarget({
100100 );
101101}
102102
103/**
104 * Trigger classes for the composer model/traits pickers when they sit in a
105 * settings row: match the `sm` control box (the composer pins them to 28px at
106 * every breakpoint) and drop the composer's max-width.
107 */
108export const SETTINGS_PICKER_TRIGGER_CLASSNAME =
109 "h-8 min-h-8 min-w-0 max-w-none shrink-0 text-foreground/90 hover:text-foreground sm:h-7 sm:min-h-7";
110
103111/** Info affordance explaining how a setting interacts with the shared background policy. */
104112export function PolicyTooltip({ children }: { readonly children: string }) {
105113 return (
......@@ -168,6 +176,12 @@ export function SettingsSection({
168176 * environment's settings.json; where there is no primary (the hosted app)
169177 * the control goes inert with a tooltip instead of showing an editable
170178 * default that would never save.
179 *
180 * Control sizing across settings follows three tiers so rows share a baseline:
181 * - `control` slot: `size="sm"` (Button, Select, Input, NumberField) or `icon-sm`.
182 * - Section `headerAction`s and buttons inside list items, cards, toolbars: `xs` / `icon-xs`.
183 * - Inline affordances (reset arrows, info tooltips, table-cell buttons): `icon-micro`.
184 * Dialog footers keep the app-wide default button size.
171185 */
172186export function SettingsRow({
173187 title,
apps/web/src/components/ui/switch.tsx+10-2
......@@ -4,13 +4,21 @@ import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
44
55import { cn } from "~/lib/utils";
66
7function Switch({ className, ...props }: SwitchPrimitive.Root.Props) {
7function Switch({
8 className,
9 size = "default",
10 ...props
11}: SwitchPrimitive.Root.Props & { size?: "default" | "sm" }) {
812 return (
913 <SwitchPrimitive.Root
1014 className={cn(
11 "inline-flex h-[calc(var(--thumb-size)+2px)] w-[calc(var(--thumb-size)*2-2px)] shrink-0 cursor-pointer items-center rounded-full p-px outline-none transition-[background-color,box-shadow] duration-200 [--thumb-size:--spacing(5)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-checked:bg-primary data-unchecked:bg-input data-disabled:cursor-not-allowed data-disabled:opacity-64 sm:[--thumb-size:--spacing(4)]",
15 "inline-flex h-[calc(var(--thumb-size)+2px)] w-[calc(var(--thumb-size)*2-2px)] shrink-0 cursor-pointer items-center rounded-full p-px outline-none transition-[background-color,box-shadow] duration-200 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-checked:bg-primary data-unchecked:bg-input data-disabled:cursor-not-allowed data-disabled:opacity-64",
16 size === "sm"
17 ? "[--thumb-size:--spacing(4)] sm:[--thumb-size:--spacing(3.5)]"
18 : "[--thumb-size:--spacing(5)] sm:[--thumb-size:--spacing(4)]",
1219 className,
1320 )}
21 data-size={size}
1422 data-slot="switch"
1523 {...props}
1624 >