import React, { useState, useEffect, useRef, createContext, useContext } from 'react' import { createRootRoute, Outlet, useNavigate } from '@tanstack/react-router' import { createKeyboardController } from '../lib/keyboard' import type { RouteName } from '../lib/keyboard' import { TopBar } from '../shell/TopBar' import { Palette } from '../shell/Palette' import { AuthProvider } from '../lib/authContext' import '../styles/tokens.css' import '../styles/primitives.css' import '../styles/shell.css' import '../styles/palette.css' interface CursorHandle { move(d: 1 | -1): void activate(): void } const noopCursor: CursorHandle = { move: (_d: 1 | -1) => { /* no-op */ }, activate: () => { /* no-op */ }, } // Context that routes use to register/unregister their cursor handle export const KeyboardCursorContext = createContext | null>(null) export function useKeyboardCursor(): React.MutableRefObject { const ctx = useContext(KeyboardCursorContext) if (ctx === null) throw new Error('useKeyboardCursor must be used within RootComponent') return ctx } export const Route = createRootRoute({ component: RootComponent, }) function RootComponent(): React.JSX.Element { const [paletteOpen, setPaletteOpen] = useState(false) const navigate = useNavigate() // Ref so keyboard controller callbacks always see current state const paletteOpenRef = useRef(paletteOpen) useEffect(() => { paletteOpenRef.current = paletteOpen }, [paletteOpen]) // Ref that routes swap in to wire their cursor into the global keyboard handler const cursorRef = useRef(noopCursor) useEffect(() => { const controller = createKeyboardController({ go: (route: RouteName) => { const paths: Record = { home: '/', projects: '/projects', stats: '/stats', health: '/health', } void navigate({ to: paths[route] }) }, openPalette: () => setPaletteOpen(true), closePalette: () => setPaletteOpen(false), isPaletteOpen: () => paletteOpenRef.current, cursor: { move: (d) => cursorRef.current.move(d), activate: () => cursorRef.current.activate(), }, }) document.addEventListener('keydown', controller.onKeyDown) return () => { document.removeEventListener('keydown', controller.onKeyDown) controller.teardown() } // eslint-disable-next-line react-hooks/exhaustive-deps }, []) // navigate is stable; intentional empty-dep mount effect return (
setPaletteOpen(true)} /> setPaletteOpen(false)} />
) }