Settings-dialog round 1 staging repair: the provider-rewiring files
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.)
This commit is contained in:
@@ -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
|
Electron merged chrome (owner, 2026-09-17). The system title bar is
|
||||||
hidden (titleBarStyle: "hiddenInset" in electron/main.cjs); the app's
|
hidden (titleBarStyle: "hiddenInset" in electron/main.cjs); the app's
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
/** The settings route (thin settings wave): server shell over the client
|
import { redirect } from "next/navigation";
|
||||||
* 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 />;
|
* 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");
|
||||||
}
|
}
|
||||||
@@ -34,18 +34,21 @@ import {
|
|||||||
KEYBOARD_ACTIONS,
|
KEYBOARD_ACTIONS,
|
||||||
} from "@/src/core";
|
} from "@/src/core";
|
||||||
import { isDictationActive } from "@/components/dictation";
|
import { isDictationActive } from "@/components/dictation";
|
||||||
|
import { useSettingsDialog } from "@/components/settings/settings-dialog.tsx";
|
||||||
|
|
||||||
export function KeyboardShortcuts() {
|
export function KeyboardShortcuts() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [cheatOpen, setCheatOpen] = useState(false);
|
const [cheatOpen, setCheatOpen] = useState(false);
|
||||||
|
const { open: openSettings } = useSettingsDialog();
|
||||||
|
|
||||||
// Router + cheat state inside a ref so the keydown listener (attached
|
// Router + cheat + settings state inside a ref so the keydown listener
|
||||||
// once) never goes stale.
|
// (attached once) never goes stale.
|
||||||
const handlersRef = useRef<{
|
const handlersRef = useRef<{
|
||||||
router: ReturnType<typeof useRouter>;
|
router: ReturnType<typeof useRouter>;
|
||||||
setCheatOpen: (v: boolean) => void;
|
setCheatOpen: (v: boolean) => void;
|
||||||
}>({ router, setCheatOpen });
|
openSettings: () => void;
|
||||||
handlersRef.current = { router, setCheatOpen };
|
}>({ router, setCheatOpen, openSettings });
|
||||||
|
handlersRef.current = { router, setCheatOpen, openSettings };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKeyDown = (e: KeyboardEvent) => {
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
@@ -95,7 +98,9 @@ export function KeyboardShortcuts() {
|
|||||||
handlersRef.current.router.push("/chat/new");
|
handlersRef.current.router.push("/chat/new");
|
||||||
break;
|
break;
|
||||||
case "open-settings":
|
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;
|
break;
|
||||||
case "jump-graph":
|
case "jump-graph":
|
||||||
handlersRef.current.router.push("/");
|
handlersRef.current.router.push("/");
|
||||||
|
|||||||
+11
-8
@@ -8,6 +8,7 @@ import { ActivitySection } from "@/components/activity-dock";
|
|||||||
import { LhMark } from "@/components/lh-mark";
|
import { LhMark } from "@/components/lh-mark";
|
||||||
import { BrailleSpinner } from "@/components/braille-spinner";
|
import { BrailleSpinner } from "@/components/braille-spinner";
|
||||||
import { KeyboardShortcuts } from "@/components/keyboard-shortcuts";
|
import { KeyboardShortcuts } from "@/components/keyboard-shortcuts";
|
||||||
|
import { SettingsDialogProvider, useSettingsDialog } from "@/components/settings/settings-dialog.tsx";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -265,6 +266,7 @@ function SessionsSidebar() {
|
|||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { open: openSettings } = useSettingsDialog();
|
||||||
|
|
||||||
/* New session (owner, 2026-09-13): NO POST — the button opens the
|
/* New session (owner, 2026-09-13): NO POST — the button opens the
|
||||||
sessionless draft view; the session is created only when the first
|
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). */}
|
learner opted out (no ambient signal, user decision). */}
|
||||||
<ActivitySection />
|
<ActivitySection />
|
||||||
|
|
||||||
{/* Settings entry (thin settings wave): the same row language
|
{/* Settings entry (settings-app wave): same row language as
|
||||||
as New session — quiet, bottom-pinned, one chord away is
|
New session — quiet, bottom-pinned, opens the centered
|
||||||
mod+, (the keyboard registry's open-settings action). */}
|
dialog (also mod+, in the keyboard registry). */}
|
||||||
<div className="px-3 pb-3 pt-1">
|
<div className="px-3 pb-3 pt-1">
|
||||||
<Link
|
<button
|
||||||
href="/settings"
|
type="button"
|
||||||
|
onClick={openSettings}
|
||||||
data-testid="settings-entry"
|
data-testid="settings-entry"
|
||||||
className="flex items-center gap-1.5 rounded-md px-2 py-1.5 text-xs uppercase tracking-wider text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
className="flex items-center gap-1.5 rounded-md px-2 py-1.5 text-xs uppercase tracking-wider text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<SettingsIcon className="size-3" />
|
<SettingsIcon className="size-3" />
|
||||||
Settings
|
Settings
|
||||||
</Link>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -504,13 +507,13 @@ function SessionsSidebar() {
|
|||||||
/** The app frame: sidebar inline + content. Rendered once in the root layout. */
|
/** The app frame: sidebar inline + content. Rendered once in the root layout. */
|
||||||
export function AppShell({ children }: { children: React.ReactNode }) {
|
export function AppShell({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<SettingsDialogProvider>
|
||||||
<SessionsSidebar />
|
<SessionsSidebar />
|
||||||
<main className="min-w-0 flex-1">{children}</main>
|
<main className="min-w-0 flex-1">{children}</main>
|
||||||
{/* The single document-level keydown listener (keyboard wave A) —
|
{/* The single document-level keydown listener (keyboard wave A) —
|
||||||
after the content in the tree, but it's window-level so order only
|
after the content in the tree, but it's window-level so order only
|
||||||
matters for the events it dispatches, never for DOM priority. */}
|
matters for the events it dispatches, never for DOM priority. */}
|
||||||
<KeyboardShortcuts />
|
<KeyboardShortcuts />
|
||||||
</>
|
</SettingsDialogProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user