1.5.4 Theme toggle (tri-state) + cmd-k command palette (navigation actions)
Estimate: 28m · Depends on: 1.5.3
Land the two cross-cutting shell features the top-nav reserves slots for: the theme toggle and the cmd-k command palette. Both fill the empty slots 1.5.3 left in TopNav.
Theme toggle ((authed)/_components/ThemeToggle.tsx): tri-state (light → dark → system), reads + writes the existing 1.0.5 ThemeProvider via its useTheme() hook. The icon cycles sun ↔ moon ↔ monitor (lucide-react icons). A Tooltip shows the current resolved theme on hover. Placed in TopNav in the slot between the workspace switcher and the cmd-k trigger.
Command palette:
components/ui/CommandPalette.tsx— generic primitive: a RadixDialog.Rootwith an Input at top, a list of grouped actions below. Props:open/onOpenChange+groups: [{ heading, actions: [{ id, label, kbd?, onSelect }] }]. Keyboard:↑↓moves the highlighted row;↵invokesonSelect;esccloses. Filtering: simple substring match againstlabel(no fuzz library — over-engineering for v1, document the deferral). Lives in/tokenswith its variant matrix (empty query, filtered query, empty results).(authed)/_components/AppCommandPalette.tsx— the application composition: takes the same workspace/project list the layout already fetches, builds the action groups (Navigation: Go to Dashboard/Issues/Boards/Reports/Settings; Workspace: Switch to <name> for each workspace; Project: Switch to <name> for each project in active workspace; Account: Toggle theme, Sign out). Each action'sonSelectdispatches the corresponding router push or Server Action.(authed)/_components/CommandPaletteProvider.tsx— a tiny client provider that owns theopenstate and registers the global⌘K/Ctrl+Kshortcut via theuseShortcuthook from 1.5.2. Exposes a context methodopenCommandPalette()for the TopNav's "Search" button trigger.- TopNav's cmd-k slot renders a
Buttonlabeled "Search" with a trailing<kbd>⌘K</kbd>hint chip; clicking callsopenCommandPalette(). - Shortcut cheatsheet: bind
?globally to open aShortcutsCheatsheetDialog that lists every shortcut the shell registers (⌘K, ⌘, ?, esc). One-column table with a kbd-chip on the left and the action label on the right. Source the list from a single shared modulelib/shortcuts.tsso the cheatsheet and the actual handlers can't diverge.
What's deliberately NOT in here: fuzzy search across issues / projects / docs (Epic 6's Search Story owns that — when search lands, the palette grows a "Search" group fed by the search service; the primitive doesn't change shape). AI / chat affordances (Epic 7). Recent-items history (a finding to log: "palette has no recent-items list; add when usage data shows demand").
Acceptance criteria
(authed)/_components/ThemeToggle.tsxships and renders in TopNav; cycles light → dark → system; reads/writes via the existinguseTheme()hook; an accessible label / tooltip announces the current resolved theme.components/ui/CommandPalette.tsxships as a generic primitive (lives in/tokenswith its variant matrix); takes typed group/action props.AppCommandPaletteassembles navigation + workspace-switch + project-switch + account groups from the layout's already-fetched data; opens via⌘K/Ctrl+KAND via the TopNav "Search" button.- Filtering: typing into the input narrows the action list by substring match against
label; empty query shows all actions grouped by heading; no-match state shows "No actions match" hint. - Keyboard:
↑↓cycles the highlighted row;↵invokesonSelect;esccloses; focus returns to the trigger on close. ?opens theShortcutsCheatsheetDialog enumerating every shell shortcut; the list is sourced fromlib/shortcuts.ts(single source of truth shared with the actual handlers).- Vitest tests under
tests/components/command-palette.test.tsxcover: opens on ⌘K, filters on typed input, ↵ invokes the selected action, esc closes;tests/components/theme-toggle.test.tsxcovers the tri-state cycle. - All quality gates green; existing suite stays green; bundle-size impact under +10 KB gzipped (the palette is just a Radix Dialog + a controlled Input + a list — no heavy fuzz lib).
Context refs
lib/theme/— existing ThemeProvider +useTheme()hookcomponents/ui/Dialog.tsx+Button.tsx+Tooltip.tsx— primitives to composeapp/(authed)/layout.tsx+TopNav.tsx— the slots to fill (1.5.3 left them empty)lib/hooks/useShortcut.tsfrom 1.5.2 — the keyboard-shortcut registration hook/design/shell/cmd-k.pen+shortcuts.penfrom 1.5.1 — the visual contract