Settings-app refinement round 2 (owner review): toggle fix, shared model dropdown, groups, graph-view graying, pruned hints

- Toggle knob (bug report): the knob span had no left anchor, so it sat
  at its inline static position — right-edge in off, overflowing in on.
  Now left-0 + the intended 2px/18px translate; verified live: 3px from
  left when off, 1px right inset both states.
- Role model/thinking dropdowns (owner ask): the EXACT chat model
  selector dropdown, not a lookalike — the fuzzy-search + arrow-walk +
  Enter machine moved from model-picker.tsx into the shared
  components/settings/model-menu.tsx (ModelMenuList/ThinkingMenuList),
  ModelControl now renders the same bodies, and the agent roles open
  them downward with a pinned Default row (clears the override). The
  collapsed trigger keeps the thin select look (owner: 'the collapsed
  look is good'). Esc claims and closes only the menu (the dialog
  stays); a backdrop click is left to the dialog's own close.
- Sub-groups (owner ask): SettingsGroup — Models / Tools / System
  prompts inside Agent, each with a separator + quiet label; section
  headings more pronounced (text-sm, full-foreground).
- Graying (owner ask): graph-view render-panel convention — the WHOLE
  row dims (opacity-40) and the control locks; the row owns the dim so
  no double-opacity. Applied to the graph ladder, the mode row, and
  prompt rows without an override file. The 'needs X on' prose is gone.
- Hints pruned (owner ask): mechanics-explaining lines removed — the
  ladder mention, 'App-level behavior', 'Applied live as CSS
  variables', per-row explanations. Only the keyboard storage note
  stays, reworded 'Stored on this device' (desktop app — localStorage
  is the Chromium profile in Application Support, not 'this browser').

Live-verified: knob geometry both states + PUT round-trip, menu
search/pick/clear through the record, thinking set/clear, Esc layering,
ladder dim 0.4 with siblings full, pruned phrases absent. Gates:
typecheck, lint, 701/701.
This commit is contained in:
amosblomqvist
2026-09-19 22:29:17 +03:00
parent 5e21561997
commit fd63e4a575
8 changed files with 536 additions and 294 deletions
+39 -15
View File
@@ -1,16 +1,17 @@
"use client";
/**
* Thin settings primitives (thin settings wave): the minimal control kit
* the settings page needs — deliberately plain. No design system, no
* animation polish: this wave is a WORKING validation surface over the
* settings record, not the designed settings app (that's a later wave).
* Thin settings primitives (settings-app wave): the control kit the
* settings dialog needs. Sections → groups → rows; everything SSRs clean
* (render tests pin the sections' markup).
*
* Everything SSRs clean (render tests pin the sections' markup).
* The dependency graying (owner, 2026-09-20) follows the graph viewer's
* render-panel convention: the WHOLE row dims (opacity-40, label and
* control together) and the control itself locks — the row owns the dim.
*/
import { cn } from "@/lib/utils";
/** A section block: header + rows, separated by the app's border color. */
/** A section block: pronounced mono heading + content. */
export function SettingsSection({
title,
hint,
@@ -18,20 +19,39 @@ export function SettingsSection({
testid,
}: {
title: string;
/** One-line quiet note under the header (what the section controls). */
/** One quiet line under the header — only where it earns its place. */
hint?: string;
children: React.ReactNode;
testid?: string;
}) {
return (
<section data-testid={testid} className="border-t border-border px-6 py-5 first:border-t-0">
<h2 className="text-xs uppercase tracking-widest text-muted-foreground/70">{title}</h2>
{hint && <p className="mt-1 text-xs text-muted-foreground/70">{hint}</p>}
<div className="mt-3 flex flex-col gap-3">{children}</div>
<section data-testid={testid} className="px-6 py-5">
<h2 className="text-sm uppercase tracking-widest text-foreground">{title}</h2>
{hint && <p className="mt-1 text-xs text-muted-foreground">{hint}</p>}
<div className="mt-4 flex flex-col gap-3">{children}</div>
</section>
);
}
/** A labeled sub-group inside a section (owner, 2026-09-20: clear
* separation — Models / Tools / Prompts read as three blocks). */
export function SettingsGroup({
label,
children,
testid,
}: {
label: string;
children: React.ReactNode;
testid?: string;
}) {
return (
<div data-testid={testid} className="mt-5 border-t border-border/60 pt-4 first:mt-0 first:border-t-0 first:pt-0">
<p className="mb-2 text-xs uppercase tracking-widest text-muted-foreground">{label}</p>
<div className="flex flex-col gap-2">{children}</div>
</div>
);
}
/** One label + one control. Label left (with optional muted hint), control
* right — the settings row shape. */
export function SettingsRow({
@@ -39,14 +59,18 @@ export function SettingsRow({
hint,
children,
testid,
dimmed,
}: {
label: string;
hint?: string;
children: React.ReactNode;
testid?: string;
/** Dependency graying, graph-view style: the whole row dims when the
* control is inert (parent off, no file yet, …). */
dimmed?: boolean;
}) {
return (
<div data-testid={testid} className="flex items-center justify-between gap-4 py-1">
<div data-testid={testid} className={cn("flex items-center justify-between gap-4 py-1", dimmed && "opacity-40")}>
<span className="min-w-0">
<span className="block font-prose text-(length:--lh-prose-size)">{label}</span>
{hint && <span className="block text-xs text-muted-foreground/70">{hint}</span>}
@@ -81,12 +105,12 @@ export function Toggle({
className={cn(
"relative h-4.5 w-8 shrink-0 rounded-full border border-border transition-colors",
checked ? "bg-foreground" : "bg-muted",
disabled && "cursor-not-allowed opacity-40",
disabled && "cursor-not-allowed",
)}
>
<span
className={cn(
"absolute top-0.5 size-3 rounded-full transition-transform",
"absolute left-0 top-0.5 size-3 rounded-full transition-transform",
checked ? "translate-x-4.5 bg-background" : "translate-x-0.5 bg-muted-foreground",
)}
/>
@@ -119,7 +143,7 @@ export function ThinSelect({
onChange={(e) => onChange(e.target.value)}
className={cn(
"h-7 rounded-sm border border-border bg-background px-1.5 font-ui text-xs text-foreground outline-none",
disabled && "cursor-not-allowed opacity-40",
disabled && "cursor-not-allowed",
)}
>
{children}