MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-56

1.5.4 Theme toggle (tri-state) + cmd-k command palette (navigation actions)

Done
Description

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 Radix Dialog.Root with 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; invokes onSelect; esc closes. Filtering: simple substring match against label (no fuzz library — over-engineering for v1, document the deferral). Lives in /tokens with 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 for each workspace; Project: Switch to for each project in active workspace; Account: Toggle theme, Sign out). Each action's onSelect dispatches the corresponding router push or Server Action.
  • (authed)/_components/CommandPaletteProvider.tsx — a tiny client provider that owns the open state and registers the global ⌘K / Ctrl+K shortcut via the useShortcut hook from 1.5.2. Exposes a context method openCommandPalette() for the TopNav's "Search" button trigger.
  • TopNav's cmd-k slot renders a Button labeled "Search" with a trailing <kbd>⌘K</kbd> hint chip; clicking calls openCommandPalette().
  • Shortcut cheatsheet: bind ? globally to open a ShortcutsCheatsheet Dialog 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 module lib/shortcuts.ts so 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.tsx ships and renders in TopNav; cycles light → dark → system; reads/writes via the existing useTheme() hook; an accessible label / tooltip announces the current resolved theme.
  • components/ui/CommandPalette.tsx ships as a generic primitive (lives in /tokens with its variant matrix); takes typed group/action props.
  • AppCommandPalette assembles navigation + workspace-switch + project-switch + account groups from the layout's already-fetched data; opens via ⌘K / Ctrl+K AND 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; invokes onSelect; esc closes; focus returns to the trigger on close.
  • ? opens the ShortcutsCheatsheet Dialog enumerating every shell shortcut; the list is sourced from lib/shortcuts.ts (single source of truth shared with the actual handlers).
  • Vitest tests under tests/components/command-palette.test.tsx cover: opens on ⌘K, filters on typed input, ↵ invokes the selected action, esc closes; tests/components/theme-toggle.test.tsx covers 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() hook
  • components/ui/Dialog.tsx + Button.tsx + Tooltip.tsx — primitives to compose
  • app/(authed)/layout.tsx + TopNav.tsx — the slots to fill (1.5.3 left them empty)
  • lib/hooks/useShortcut.ts from 1.5.2 — the keyboard-shortcut registration hook
  • /design/shell/cmd-k.pen + shortcuts.pen from 1.5.1 — the visual contract
Status
Done
Type
Sub-task