Thin working settings UI (owner-approved plan): /settings + live appearance + keyboard rebinding
The validation surface for the settings foundation — deliberately thin,
real read/write against the delta record, polish is a later wave.
Mount (owner decisions): dedicated page, single scroll; entry = sidebar
bottom row + ⌘/Ctrl+, (open-settings in the registry); chat defaults NOT
shown (they live on /chat/new); mode dark-only (light palette = design
wave); final tool list = research/demo/visual/pdf (master-adjudicated).
Sections (PUT /api/settings/[section], replace-whole per section):
- General: auto-update toggle.
- Agent: six background-role model+thinking selects (Default = config
pin; a stored id stays VISIBLE even when the catalog hasn't loaded or
dropped it — a select showing Default over a live override would lie),
the four tool toggles, prompt override switches (file-gated) with
source/override text editors over the segment routes.
- Appearance: applied LIVE as runtime CSS variables by an
AppearanceProvider at the root layout — proseFontSize → the one
reading-size var, fontFamily → --font-prose, background → --background
+ the derived raised/accent/edge palette (dark curve, today's exact
spacing: +8/+22/+47 per channel, clamped), sessionNameSerif →
--font-title (titles flip with ZERO chat.tsx edits), headingWeight →
new --lh-heading-weight token, bold/italic colors → the lh-ink vars.
Mode renders honestly DISABLED.
- Graph: the ladder grays a rung when any ANCESTOR is effectively off
(stored values persist, resolve inert — switch shows stored, graying
shows effective).
- Keyboard: view/rebind/reset over the registry; overrides persist in
lh:keymap and apply INSTANTLY (dispatcher reads per keydown); bare
modifier presses are ignored while armed; displayOnly actions list but
never rebind.
First-boot fix: {settings: null} is no-record, not fetch-failure.
CROSS-LANE (flagged for master): src/core/keyboard.ts gained the
open-settings action/binding and exports saveOverrides/loadOverrides
(one-liners; no existing pins); app/layout.tsx mounts the provider.
Live-verified: structure, record writes (general/tools/graph/appearance),
prose-size applying live (16→20px), rebind + chord navigation from
another page, ladder graying, no console errors. Gates: typecheck,
lint, 689/689 (28 new), build.
This commit is contained in:
+5
-1
@@ -48,6 +48,8 @@
|
||||
the reskin's hand-tuned 16.5 to the ladder's 16.) */
|
||||
--lh-prose-size: 16px;
|
||||
--lh-prose-leading: 1.5;
|
||||
/* Markdown heading weight (appearance settings; 900 = the Black cut). */
|
||||
--lh-heading-weight: 900;
|
||||
}
|
||||
|
||||
/* The app is always dark (<html class="dark">); mirror the same values so
|
||||
@@ -80,6 +82,8 @@
|
||||
the reskin's hand-tuned 16.5 to the ladder's 16.) */
|
||||
--lh-prose-size: 16px;
|
||||
--lh-prose-leading: 1.5;
|
||||
/* Markdown heading weight (appearance settings; 900 = the Black cut). */
|
||||
--lh-heading-weight: 900;
|
||||
}
|
||||
|
||||
/* CMU Serif bold (titles) — bundled local file, no CDN dependency. */
|
||||
@@ -185,7 +189,7 @@ body {
|
||||
:where keeps specificity zero. Chrome titles (CMU Serif) don't live
|
||||
under .lh-prose and stay untouched. */
|
||||
.lh-prose :where(h1, h2, h3, h4, h5, h6) {
|
||||
font-weight: 900;
|
||||
font-weight: var(--lh-heading-weight, 900);
|
||||
}
|
||||
|
||||
/* lh-input (owner, typography round 2): lh-prose's 1.5 READING leading
|
||||
|
||||
+8
-5
@@ -4,6 +4,7 @@ import "katex/dist/katex.min.css";
|
||||
import "./globals.css";
|
||||
import { AppShell, SidebarProvider } from "@/components/sidebar";
|
||||
import { ActivityProvider } from "@/components/activity-dock";
|
||||
import { AppearanceProvider } from "@/components/settings/appearance";
|
||||
|
||||
// Chrome = JetBrains Mono, teaching prose = Source Sans 3, titles = CMU
|
||||
// Serif (the bundled lh-cmu @font-face in globals.css — no CDN).
|
||||
@@ -19,11 +20,13 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
return (
|
||||
<html lang="en" className={`dark ${ui.variable} ${prose.variable}`}>
|
||||
<body className="min-h-dvh antialiased">
|
||||
<SidebarProvider>
|
||||
<ActivityProvider>
|
||||
<AppShell>{children}</AppShell>
|
||||
</ActivityProvider>
|
||||
</SidebarProvider>
|
||||
<AppearanceProvider>
|
||||
<SidebarProvider>
|
||||
<ActivityProvider>
|
||||
<AppShell>{children}</AppShell>
|
||||
</ActivityProvider>
|
||||
</SidebarProvider>
|
||||
</AppearanceProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/** The settings route (thin settings wave): server shell over the client
|
||||
* page — same shape as the chat routes (AppShell wraps via the root
|
||||
* layout, so the sidebar + its Settings entry come along). */
|
||||
import { SettingsPage } from "@/components/settings/settings-page.tsx";
|
||||
|
||||
export default function Page() {
|
||||
return <SettingsPage />;
|
||||
}
|
||||
Reference in New Issue
Block a user