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:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user