Studio: size Agents and System to their pane, restore palette focus

- The GPU row's breakpoint could never fire. The settings pane is about
  664px at any window size, and the name, three figures, meter and
  percentage need more than that, so the one-row layout was dead code.
  Each device is now two compact lines: name with the percentage, then
  the figures with a fixed-width meter. The name stays readable, the
  meter no longer runs the width of the pane, and there is no breakpoint
  that cannot be reached.
- The agent, model and quantization grid switched on the viewport, so
  between 768px and 1024px it went to three columns inside a 440px pane
  and crushed every control. It now keys on the pane and stacks until
  the columns genuinely fit.
- Dark mode dropped the palette card's resting border, but that border
  is the only focus indicator since the card removes its outline, and an
  unlayered rule beats Tailwind's layered focus-visible utility. Keyboard
  focus paints the ring colour again.
This commit is contained in:
Unsloth 2026-07-29 00:47:41 -07:00
commit 29131cc85e
3 changed files with 261 additions and 249 deletions

View file

@ -1113,233 +1113,240 @@ export function AgentsTab() {
aria-label={t("settings.agents.commandBuilder")}
className="flex w-full flex-col gap-6"
>
<div className="grid grid-cols-[minmax(0,0.8fr)_minmax(0,1fr)_minmax(9rem,0.5fr)] items-start gap-3 max-md:grid-cols-1">
<div className="flex min-w-0 flex-col gap-1.5">
<div className="flex items-center justify-between gap-3">
{/* Keyed to this pane, not the viewport. The dialog leaves the tab
about 440px at a 768px window, where three columns crush the agent
and model controls; 34rem is the point all three stay usable. */}
<div className="@container">
<div className="grid grid-cols-1 items-start gap-3 @[34rem]:grid-cols-[minmax(0,0.8fr)_minmax(0,1fr)_minmax(9rem,0.5fr)]">
<div className="flex min-w-0 flex-col gap-1.5">
<div className="flex items-center justify-between gap-3">
<span
data-settings-label={t("settings.agents.agent")}
className="text-xs font-medium text-foreground"
>
{t("settings.agents.agent")}
</span>
<a
href={selectedAgentDetails.docsUrl}
target="_blank"
rel="noreferrer"
aria-label={t("settings.agents.agentDocs", {
agent: selectedAgentDetails.name,
})}
className="inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-ui-11 font-medium text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
{t("settings.agents.docs")}
<HugeiconsIcon icon={ArrowUpRight01Icon} className="size-3" />
</a>
</div>
<Select
value={selectedAgent}
onValueChange={(agent) => {
agentSelectionChanged.current = true;
setSelectedAgent(agent);
resetCopied();
}}
>
<SelectTrigger
aria-label={t("settings.agents.agent")}
className="w-full rounded-lg"
>
<SelectValue>
<span className="flex min-w-0 items-center gap-2">
<AgentIcon
logo={selectedAgentDetails.logo}
icon={selectedAgentDetails.icon}
darkIcon={selectedAgentDetails.darkIcon}
invertIconInDark={selectedAgentDetails.invertIconInDark}
color={selectedAgentDetails.color}
mark={selectedAgentDetails.mark}
/>
<span className="truncate">
{selectedAgentDetails.name}
</span>
</span>
</SelectValue>
</SelectTrigger>
<SelectContent align="start">
{agents.map((agentId) => {
const agent = detailsFor(agentId);
return (
<SelectItem key={agent.id} value={agent.id}>
<span className="flex min-w-0 items-center gap-2">
<AgentIcon
logo={agent.logo}
icon={agent.icon}
darkIcon={agent.darkIcon}
invertIconInDark={agent.invertIconInDark}
color={agent.color}
mark={agent.mark}
/>
<span className="truncate">{agent.name}</span>
{localDetection &&
loaded &&
detectedAgents.has(agent.id) ? (
<span className="shrink-0 rounded-full bg-control-accent/10 px-2 py-1 text-ui-10 leading-none font-semibold text-control-accent">
{t("settings.agents.quickstart.installed")}
</span>
) : null}
</span>
</SelectItem>
);
})}
</SelectContent>
</Select>
</div>
<div className="flex min-w-0 flex-col gap-1.5">
<span
data-settings-label={t("settings.agents.agent")}
data-settings-label={t("settings.agents.model")}
className="text-xs font-medium text-foreground"
>
{t("settings.agents.agent")}
{t("settings.agents.model")}
</span>
<a
href={selectedAgentDetails.docsUrl}
target="_blank"
rel="noreferrer"
aria-label={t("settings.agents.agentDocs", {
agent: selectedAgentDetails.name,
})}
className="inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-ui-11 font-medium text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
<Popover
open={modelPickerOpen}
onOpenChange={(open) => {
setModelPickerOpen(open);
if (!open) {
setModelSearch("");
}
}}
>
{t("settings.agents.docs")}
<HugeiconsIcon icon={ArrowUpRight01Icon} className="size-3" />
</a>
</div>
<Select
value={selectedAgent}
onValueChange={(agent) => {
agentSelectionChanged.current = true;
setSelectedAgent(agent);
resetCopied();
}}
>
<SelectTrigger
aria-label={t("settings.agents.agent")}
className="w-full rounded-lg"
>
<SelectValue>
<span className="flex min-w-0 items-center gap-2">
<AgentIcon
logo={selectedAgentDetails.logo}
icon={selectedAgentDetails.icon}
darkIcon={selectedAgentDetails.darkIcon}
invertIconInDark={selectedAgentDetails.invertIconInDark}
color={selectedAgentDetails.color}
mark={selectedAgentDetails.mark}
/>
<span className="truncate">
{selectedAgentDetails.name}
<PopoverTrigger asChild={true}>
<button
type="button"
aria-label={t("settings.agents.model")}
aria-expanded={modelPickerOpen}
title={selectedModel}
className="flex h-9 w-full items-center justify-between gap-2 rounded-lg border border-border bg-background px-3 text-left transition-colors hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring dark:border-transparent dark:bg-white/[0.06] dark:hover:bg-white/10"
>
<span className="min-w-0 truncate font-mono text-xs">
{labelFor(selectedModel)}
</span>
</span>
</SelectValue>
</SelectTrigger>
<SelectContent align="start">
{agents.map((agentId) => {
const agent = detailsFor(agentId);
return (
<SelectItem key={agent.id} value={agent.id}>
<span className="flex min-w-0 items-center gap-2">
<AgentIcon
logo={agent.logo}
icon={agent.icon}
darkIcon={agent.darkIcon}
invertIconInDark={agent.invertIconInDark}
color={agent.color}
mark={agent.mark}
/>
<span className="truncate">{agent.name}</span>
{localDetection &&
loaded &&
detectedAgents.has(agent.id) ? (
<span className="shrink-0 rounded-full bg-control-accent/10 px-2 py-1 text-ui-10 leading-none font-semibold text-control-accent">
{t("settings.agents.quickstart.installed")}
<HugeiconsIcon
icon={ChevronDownStandardIcon}
strokeWidth={2}
className="size-4 shrink-0 text-muted-foreground"
/>
</button>
</PopoverTrigger>
<PopoverContent
align="start"
sideOffset={4}
className="w-[var(--radix-popover-trigger-width)] max-w-[calc(100vw-2rem)] gap-0 rounded-lg p-1"
>
<Command
shouldFilter={false}
className="rounded-none bg-transparent p-0"
>
<CommandInput
value={modelSearch}
onValueChange={setModelSearch}
aria-label={t("settings.agents.searchModels")}
placeholder={t("settings.agents.searchModels")}
className="font-mono text-xs"
/>
<CommandList>
<CommandEmpty>
{t("settings.agents.noModels")}
</CommandEmpty>
{visibleModels.map((model) => (
<CommandItem
key={model}
value={model}
data-checked={model === selectedModel}
onSelect={() => {
modelSelectionChanged.current = true;
setSelectedModel(model);
setSelectedVariant(knownVariants[model] ?? null);
setVariants([]);
setVariantsFailed(false);
setVariantsLoading(isHuggingFaceRepo(model));
setModelSearch("");
setModelPickerOpen(false);
resetCopied();
}}
className="cursor-pointer font-mono text-xs"
>
<span className="min-w-0 truncate" title={model}>
{labelFor(model)}
</span>
</CommandItem>
))}
</CommandList>
{matchingModels.length > visibleModels.length ? (
<p className="border-t border-border/60 px-3 py-2 text-ui-11 text-muted-foreground">
{t("settings.agents.showingModels", {
shown: visibleModels.length,
total: matchingModels.length,
})}
</p>
) : null}
</Command>
</PopoverContent>
</Popover>
</div>
<div className="flex min-w-0 flex-col gap-1.5">
<span
data-settings-label={t("settings.agents.quantization")}
className="text-xs font-medium text-foreground"
>
{t("settings.agents.quantization")}
</span>
<Select
value={selectedVariant ?? undefined}
onValueChange={(variant) => {
chosenVariant.current = { model: selectedModel, variant };
setSelectedVariant(variant);
resetCopied();
}}
disabled={variantsLoading || variants.length === 0}
>
<SelectTrigger
aria-label={t("settings.agents.quantization")}
className="w-full rounded-lg font-mono text-xs"
>
<SelectValue
placeholder={
variantsLoading
? t("settings.agents.loadingQuantizations")
: t("settings.agents.noQuantizations")
}
>
{selectedVariant}
</SelectValue>
</SelectTrigger>
<SelectContent align="start" className="min-w-[16rem]">
{variants.map((variant) => {
// Size only: the recommended/downloaded tags wrapped every
// row onto two lines and made the list hard to scan.
const size = formatBytes(
variant.download_size_bytes ?? variant.size_bytes,
);
return (
<SelectItem
key={variant.quant}
value={variant.quant}
// Stretch the item text so the size can sit flush right,
// giving the list a clean two-column read.
className="[&>span:last-child]:w-full [&>span:last-child]:justify-between"
>
<span className="font-mono text-xs whitespace-nowrap">
{variant.quant}
</span>
{size ? (
<span className="text-ui-10 whitespace-nowrap text-muted-foreground">
{size}
</span>
) : null}
</span>
</SelectItem>
);
})}
</SelectContent>
</Select>
</div>
<div className="flex min-w-0 flex-col gap-1.5">
<span
data-settings-label={t("settings.agents.model")}
className="text-xs font-medium text-foreground"
>
{t("settings.agents.model")}
</span>
<Popover
open={modelPickerOpen}
onOpenChange={(open) => {
setModelPickerOpen(open);
if (!open) {
setModelSearch("");
}
}}
>
<PopoverTrigger asChild={true}>
<button
type="button"
aria-label={t("settings.agents.model")}
aria-expanded={modelPickerOpen}
title={selectedModel}
className="flex h-9 w-full items-center justify-between gap-2 rounded-lg border border-border bg-background px-3 text-left transition-colors hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring dark:border-transparent dark:bg-white/[0.06] dark:hover:bg-white/10"
>
<span className="min-w-0 truncate font-mono text-xs">
{labelFor(selectedModel)}
</span>
<HugeiconsIcon
icon={ChevronDownStandardIcon}
strokeWidth={2}
className="size-4 shrink-0 text-muted-foreground"
/>
</button>
</PopoverTrigger>
<PopoverContent
align="start"
sideOffset={4}
className="w-[var(--radix-popover-trigger-width)] max-w-[calc(100vw-2rem)] gap-0 rounded-lg p-1"
>
<Command
shouldFilter={false}
className="rounded-none bg-transparent p-0"
>
<CommandInput
value={modelSearch}
onValueChange={setModelSearch}
aria-label={t("settings.agents.searchModels")}
placeholder={t("settings.agents.searchModels")}
className="font-mono text-xs"
/>
<CommandList>
<CommandEmpty>{t("settings.agents.noModels")}</CommandEmpty>
{visibleModels.map((model) => (
<CommandItem
key={model}
value={model}
data-checked={model === selectedModel}
onSelect={() => {
modelSelectionChanged.current = true;
setSelectedModel(model);
setSelectedVariant(knownVariants[model] ?? null);
setVariants([]);
setVariantsFailed(false);
setVariantsLoading(isHuggingFaceRepo(model));
setModelSearch("");
setModelPickerOpen(false);
resetCopied();
}}
className="cursor-pointer font-mono text-xs"
>
<span className="min-w-0 truncate" title={model}>
{labelFor(model)}
</span>
</CommandItem>
))}
</CommandList>
{matchingModels.length > visibleModels.length ? (
<p className="border-t border-border/60 px-3 py-2 text-ui-11 text-muted-foreground">
{t("settings.agents.showingModels", {
shown: visibleModels.length,
total: matchingModels.length,
})}
</p>
) : null}
</Command>
</PopoverContent>
</Popover>
</div>
<div className="flex min-w-0 flex-col gap-1.5">
<span
data-settings-label={t("settings.agents.quantization")}
className="text-xs font-medium text-foreground"
>
{t("settings.agents.quantization")}
</span>
<Select
value={selectedVariant ?? undefined}
onValueChange={(variant) => {
chosenVariant.current = { model: selectedModel, variant };
setSelectedVariant(variant);
resetCopied();
}}
disabled={variantsLoading || variants.length === 0}
>
<SelectTrigger
aria-label={t("settings.agents.quantization")}
className="w-full rounded-lg font-mono text-xs"
>
<SelectValue
placeholder={
variantsLoading
? t("settings.agents.loadingQuantizations")
: t("settings.agents.noQuantizations")
}
>
{selectedVariant}
</SelectValue>
</SelectTrigger>
<SelectContent align="start" className="min-w-[16rem]">
{variants.map((variant) => {
// Size only: the recommended/downloaded tags wrapped every
// row onto two lines and made the list hard to scan.
const size = formatBytes(
variant.download_size_bytes ?? variant.size_bytes,
);
return (
<SelectItem
key={variant.quant}
value={variant.quant}
// Stretch the item text so the size can sit flush right,
// giving the list a clean two-column read.
className="[&>span:last-child]:w-full [&>span:last-child]:justify-between"
>
<span className="font-mono text-xs whitespace-nowrap">
{variant.quant}
</span>
{size ? (
<span className="text-ui-10 whitespace-nowrap text-muted-foreground">
{size}
</span>
) : null}
</SelectItem>
);
})}
</SelectContent>
</Select>
</SelectItem>
);
})}
</SelectContent>
</Select>
</div>
</div>
</div>

View file

@ -456,9 +456,7 @@ export function ResourcesTab() {
</span>
</div>
)}
{/* Sized against this pane, not the viewport: the Settings dialog caps
the tab at roughly 650px however wide the window is. */}
<div className="@container">
<div>
{hasGpu ? (
metrics.devices.map((device, index) => {
const ordinal = deviceOrdinal(device);
@ -490,29 +488,37 @@ export function ResourcesTab() {
? formatPercent(safePercent)
: unknownLabel;
return (
// One row per device: a full-width bar under a single GPU read
// as a page-wide rule, so the bar is sized to the row instead.
// Two compact lines per device. The settings pane is about
// 664px at any window size, which cannot hold the name, three
// figures, meter and percentage on one row, so no breakpoint
// would ever fire. Splitting them keeps the name readable and
// the meter narrow instead of running the width of the pane.
<div
key={`${device.index ?? index}-${device.name ?? "gpu"}`}
className="flex min-w-0 flex-col items-stretch gap-2 py-3 @[50rem]:flex-row @[50rem]:items-center @[50rem]:gap-4"
className="flex min-w-0 flex-col gap-2 py-3"
>
<div className="min-w-0 flex-1">
<div className="truncate text-sm font-medium text-foreground">
{device.name ?? t("settings.resources.gpu.unknownDevice")}
<div className="flex min-w-0 items-center justify-between gap-4">
<div className="min-w-0">
<div className="truncate text-sm font-medium text-foreground">
{device.name ??
t("settings.resources.gpu.unknownDevice")}
</div>
<div className="mt-0.5 truncate text-xs text-muted-foreground">
{ordinal === undefined
? backendLabel
: `${t("settings.resources.gpu.deviceWithIndex", {
index: ordinal,
})}, ${backendLabel}`}
</div>
</div>
<div className="mt-0.5 truncate text-xs text-muted-foreground">
{ordinal === undefined
? backendLabel
: `${t("settings.resources.gpu.deviceWithIndex", {
index: ordinal,
})}, ${backendLabel}`}
<div className="shrink-0 font-mono text-xs tabular-nums text-muted-foreground">
{percentText}{" "}
{t("settings.resources.gpu.vramUtilization")}
</div>
</div>
{/* Stacked until the pane is genuinely wide enough: the
figures, meter and percentage need about 50rem before the
name has room left to read. */}
<div className="flex flex-col items-stretch gap-2 @[50rem]:flex-row @[50rem]:shrink-0 @[50rem]:items-center @[50rem]:gap-4">
<div className="flex justify-between gap-3 font-mono text-xs tabular-nums text-muted-foreground @[50rem]:shrink-0 @[50rem]:justify-start">
{/* Wraps rather than overflows once the pane gets narrow. */}
<div className="flex min-w-0 flex-wrap items-center justify-between gap-x-4 gap-y-2">
<div className="flex min-w-0 gap-3 font-mono text-xs tabular-nums text-muted-foreground">
<span className="truncate">
{t("settings.resources.gpu.used", { value: usedText })}
</span>
@ -525,20 +531,12 @@ export function ResourcesTab() {
})}
</span>
</div>
<div className="flex min-w-0 items-center gap-4">
{/* Fills the space left by the percentage rather than
claiming the full row, which overflowed once stacked. */}
<Progress
value={safePercent}
aria-label={device.name ?? "GPU"}
className="h-1.5 min-w-0 flex-1 rounded-full bg-muted @[50rem]:w-40 @[50rem]:flex-none dark:bg-black/40"
indicatorClassName={usageIndicatorClass(safePercent)}
/>
<div className="w-[5.5rem] shrink-0 text-right font-mono text-xs tabular-nums text-muted-foreground">
{percentText}{" "}
{t("settings.resources.gpu.vramUtilization")}
</div>
</div>
<Progress
value={safePercent}
aria-label={device.name ?? "GPU"}
className="h-1.5 w-40 max-w-full shrink-0 rounded-full bg-muted dark:bg-black/40"
indicatorClassName={usageIndicatorClass(safePercent)}
/>
</div>
</div>
);

View file

@ -671,6 +671,13 @@ html[data-chat-font] .aui-root {
border-color: transparent;
}
/* The card removes its outline, so the border is the only focus indicator.
This rule is unlayered like the one above, which would otherwise beat
Tailwind's layered focus-visible:border-ring and leave nothing visible. */
.dark .palette-card:focus-visible {
border-color: var(--ring);
}
.palette-card:hover {
border-color: var(--ring-soft);
}