"use client"; /** * The Appearance + Graph sections (thin settings wave). * * Appearance controls resolve against the factory in the provider; writes * PUT the whole appearance section and hand the delta back so the CSS * variables move IMMEDIATELY (the section's whole point — it must DO * something). Hex inputs commit on blur/Enter; the background derives the * raised/accent/edge palette in code (components/settings/appearance.tsx). * Mode is dark-only this wave: control rendered DISABLED with a note — the * light palette is the design wave's work, and a lying control is worse * than an honest disabled one. * * Graph graying (owner, 2026-09-20) follows the graph viewer's render-panel * convention: the whole row dims when a parent is off (stored values * persist but resolve inert — the switch shows stored, the dim effective). */ import type { AppearanceSettings, GraphSettings } from "@/src/core"; import { useAppearance } from "./appearance.tsx"; import { CommitInput, SettingsRow, ThinSelect, Toggle } from "./controls.tsx"; export function AppearanceSection({ appearance, onSectionChange, }: { /** The record's appearance delta (absent = factory). */ appearance: AppearanceSettings | undefined; /** PUT the whole appearance section; the page re-applies on success. */ onSectionChange: (next: AppearanceSettings) => void; }) { const { appearance: resolved } = useAppearance(); const md = appearance?.mdRendering; return ( <> {}} testid="select-mode" disabled> onSectionChange({ ...appearance, background: v === "" ? null : v })} /> onSectionChange({ ...appearance, fontFamily: v === "" ? null : v })} /> { // Empty = factory default (owner, fix wave): omit the key so the record // UNSETS and the field stays empty (absence, not null, is the // unset for numbers). Number("") is 0, so the old clamp wrote 12. if (v === "") { onSectionChange({ ...appearance, proseFontSize: undefined }); return; } const n = Number(v); if (!Number.isFinite(n)) return; // bad input: no write, factory stays onSectionChange({ ...appearance, proseFontSize: Math.min(24, Math.max(12, Math.round(n))) }); }} /> onSectionChange({ ...appearance, sessionNameSerif: on })} /> { // Empty = factory default (owner, fix wave): Number("") is 0, so // Absence is the unset representation — the old clamp wrote 100. if (v === "") { onSectionChange({ ...appearance, mdRendering: { ...md, headingWeight: undefined } }); return; } const n = Number(v); if (!Number.isFinite(n)) return; onSectionChange({ ...appearance, mdRendering: { ...md, headingWeight: Math.min(900, Math.max(100, Math.round(n))) }, }); }} /> onSectionChange({ ...appearance, mdRendering: { ...md, boldColor: v === "" ? null : v } })} /> onSectionChange({ ...appearance, mdRendering: { ...md, italicColor: v === "" ? null : v } })} /> ); } export function GraphSection({ graph, onSectionChange, }: { graph: GraphSettings | undefined; onSectionChange: (next: GraphSettings) => void; }) { // Wave 7 collapse (owner ruling 2026-09-23): the section is the MASTER // SWITCH ONLY. The embedding/clustering/labeling rungs and the BYOK // embedding-model picker are gone — clustering is the free birth-session // partition derived on read, labels ARE session names, and the prober's // graph tools read the ledgers directly. There is nothing left to // configure but on/off. return ( onSectionChange({ enabled: on })} /> ); }