From 0629e71a50fc1d8934381d5343e733b02608a384 Mon Sep 17 00:00:00 2001 From: amosblomqvist Date: Sat, 19 Sep 2026 22:26:19 +0300 Subject: [PATCH] Settings-dialog round 1 staging repair: the provider-rewiring files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 1 (27d4f33) listed sidebar.tsx, keyboard-shortcuts.tsx, app/settings/page.tsx and the globals.css keyframes in its message but they were never staged. This commits exactly that content: the sidebar Settings row as a button calling the dialog provider, the mod+, chord opening the dialog, /settings redirecting to /chat/new, and the settings-pop keyframes (reduced-motion aware). No new changes — catch-up staging only. (next-env.d.ts churn left untracked-in-commit as dev-server noise.) --- app/globals.css | 24 ++++++++++++++++++++++++ app/settings/page.tsx | 17 ++++++++++------- components/keyboard-shortcuts.tsx | 15 ++++++++++----- components/sidebar.tsx | 19 +++++++++++-------- 4 files changed, 55 insertions(+), 20 deletions(-) diff --git a/app/globals.css b/app/globals.css index ac65329..e17e287 100644 --- a/app/globals.css +++ b/app/globals.css @@ -389,6 +389,30 @@ body { } } +/* Settings dialog pop (settings-app wave, 2026-09-20): a short fade + + 2% settle-scale — understated by design, the app's motion taste + (200ms sidebar, 150ms here); honors reduced-motion by appearing. */ +@keyframes settings-pop { + from { + opacity: 0; + scale: 0.98; + } + to { + opacity: 1; + scale: 1; + } +} + +.settings-dialog-pop { + animation: settings-pop 150ms ease-out; +} + +@media (prefers-reduced-motion: reduce) { + .settings-dialog-pop { + animation: none; + } +} + /* ───────────────────────────────────────────────────────────────────────── Electron merged chrome (owner, 2026-09-17). The system title bar is hidden (titleBarStyle: "hiddenInset" in electron/main.cjs); the app's diff --git a/app/settings/page.tsx b/app/settings/page.tsx index 35ba7f4..1b68fa9 100644 --- a/app/settings/page.tsx +++ b/app/settings/page.tsx @@ -1,8 +1,11 @@ -/** 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"; +import { redirect } from "next/navigation"; -export default function Page() { - return ; -} \ No newline at end of file +/** + * The settings surface graduated into a centered dialog over the app + * (settings-app wave, 2026-09-20 — owner decision: modal-only, no page). + * The URL stays routed but redirects: nothing should land on a dead + * page; the dialog opens via the sidebar row or mod+,. + */ +export default function SettingsPage() { + redirect("/chat/new"); +} diff --git a/components/keyboard-shortcuts.tsx b/components/keyboard-shortcuts.tsx index 5bbce11..2bcffb2 100644 --- a/components/keyboard-shortcuts.tsx +++ b/components/keyboard-shortcuts.tsx @@ -34,18 +34,21 @@ import { KEYBOARD_ACTIONS, } from "@/src/core"; import { isDictationActive } from "@/components/dictation"; +import { useSettingsDialog } from "@/components/settings/settings-dialog.tsx"; export function KeyboardShortcuts() { const router = useRouter(); const [cheatOpen, setCheatOpen] = useState(false); + const { open: openSettings } = useSettingsDialog(); - // Router + cheat state inside a ref so the keydown listener (attached - // once) never goes stale. + // Router + cheat + settings state inside a ref so the keydown listener + // (attached once) never goes stale. const handlersRef = useRef<{ router: ReturnType; setCheatOpen: (v: boolean) => void; - }>({ router, setCheatOpen }); - handlersRef.current = { router, setCheatOpen }; + openSettings: () => void; + }>({ router, setCheatOpen, openSettings }); + handlersRef.current = { router, setCheatOpen, openSettings }; useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { @@ -95,7 +98,9 @@ export function KeyboardShortcuts() { handlersRef.current.router.push("/chat/new"); break; case "open-settings": - handlersRef.current.router.push("/settings"); + // Settings-app wave: the dialog is the surface, the URL is + // gone — /settings redirects to the draft shell. + handlersRef.current.openSettings(); break; case "jump-graph": handlersRef.current.router.push("/"); diff --git a/components/sidebar.tsx b/components/sidebar.tsx index 89d05f6..6e1702a 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -8,6 +8,7 @@ import { ActivitySection } from "@/components/activity-dock"; import { LhMark } from "@/components/lh-mark"; import { BrailleSpinner } from "@/components/braille-spinner"; import { KeyboardShortcuts } from "@/components/keyboard-shortcuts"; +import { SettingsDialogProvider, useSettingsDialog } from "@/components/settings/settings-dialog.tsx"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { cn } from "@/lib/utils"; @@ -265,6 +266,7 @@ function SessionsSidebar() { const [query, setQuery] = useState(""); const pathname = usePathname(); const router = useRouter(); + const { open: openSettings } = useSettingsDialog(); /* New session (owner, 2026-09-13): NO POST — the button opens the sessionless draft view; the session is created only when the first @@ -483,18 +485,19 @@ function SessionsSidebar() { learner opted out (no ambient signal, user decision). */} - {/* Settings entry (thin settings wave): the same row language - as New session — quiet, bottom-pinned, one chord away is - mod+, (the keyboard registry's open-settings action). */} + {/* Settings entry (settings-app wave): same row language as + New session — quiet, bottom-pinned, opens the centered + dialog (also mod+, in the keyboard registry). */}
- Settings - +
@@ -504,13 +507,13 @@ function SessionsSidebar() { /** The app frame: sidebar inline + content. Rendered once in the root layout. */ export function AppShell({ children }: { children: React.ReactNode }) { return ( - <> +
{children}
{/* The single document-level keydown listener (keyboard wave A) — after the content in the tree, but it's window-level so order only matters for the events it dispatches, never for DOM priority. */} - +
); }