Skip to content

feat(nav): command palette (Cmd+K) — global navigation + search (#88)#100

Merged
mikkisguy merged 3 commits into
mainfrom
issue/88-command-palette
Jun 21, 2026
Merged

feat(nav): command palette (Cmd+K) — global navigation + search (#88)#100
mikkisguy merged 3 commits into
mainfrom
issue/88-command-palette

Conversation

@mikkisguy

Copy link
Copy Markdown
Owner

Implements #88.

What

Adds a global command palette that replaces the planned 6-section top nav from #20. Pressing Cmd+K (macOS) or Ctrl+K (Windows/Linux) opens a centered modal that:

  • lists the 5 app routes (Browse, Chat, Graph, Tags, Settings) in the Pages group
  • fuzzy-filters the Pages group as the user types
  • debounces 300ms then fetches /api/search?q=…&limit=8 once the query is ≥ 2 chars, rendering hits in a Content group with the FTS5 <mark> snippets
  • renders "(no results)" inside the Content group header (not a collapsed section) on empty hits
  • includes a Sign out utility

The minimal top nav now exposes a search-input-styled trigger on desktop and a magnifying-glass icon on mobile — both open the same palette. A single CommandPaletteRoot (provider + dialog) is mounted in the root layout so the global Cmd+K listener and the trigger share state.

Behavior

  • Keyboard: / move selection, Enter activates, two-stage Esc (first blurs the input, second closes the dialog) per the spec's "standard pattern".
  • Click: backdrop / outside-click closes; selecting any item closes the palette and navigates / runs the action.
  • Mobile: full-screen variant (no centered modal under the iOS keyboard) via responsive classes.
  • Safety: FTS5 snippets are rendered as React nodes (not dangerouslySetInnerHTML), so user-imported <script> / &amp; in notes is escaped, not executed.

Routes

The 4 not-yet-built routes (/chat, /graph, /tags, /settings) get minimal "coming soon" placeholders so all 5 routes promised by the palette's default view are reachable.

Tests

55 new tests across the new files:

  • fuzzy-filter.test.ts (13) — subsequence matching, prefix preference, consecutive bonus, length tie-breaker, empty query
  • command-items.test.ts (7) — the 5 routes, stable IDs, /search excluded, search haystack projection
  • snippet.test.tsx (10) — <mark> rendering, XSS escape, unterminated-mark fallback, type-token mapping
  • use-command-palette.test.tsx (10) — Cmd+K / Ctrl+K, toggle, focus-doesn't-block-shortcut, modifier guards, listener cleanup
  • command-palette.test.tsx (15) — trigger open, Cmd+K open, default view, fuzzy filter, FTS5 debounce + render, empty results, snippet XSS, navigation on Enter, sign-out form, two-stage Esc, abort on keep-typing

Chore

  • Bumps 0.5.2 → 0.6.0 (new user-facing feature: keyboard-driven global navigation + global content search).
  • Updates the README version badge to match.
  • Adds the testing infra (jsdom, @testing-library/react, @testing-library/jest-dom, @testing-library/user-event) needed for client-component tests.
  • Adds shadcn dialog + command (cmdk) + their transitive components via pnpm exec shadcn add dialog command.
  • Knip entry updated to include src/components/ui/** and the new root component.

pnpm verify is green: lint (incl. react-hooks/set-state-in-effect), typecheck, build, 595 tests, knip.

Adds a global command palette that replaces the planned
6-section top nav from #20. Pressing Cmd+K / Ctrl+K opens
a centered modal that lists the 5 app routes, fuzzy-filters
them as the user types, and merges in FTS5 content results
from /api/search after a 300ms idle.

The minimal top nav now exposes a search-input-styled
trigger (desktop) and a magnifying-glass icon (mobile) that
both open the palette. A sign-out utility item is also
listed.

Two-stage Esc (first Esc blurs the input, second Esc closes
the dialog) follows the design spec's standard pattern.
FTS5 snippets are rendered with React nodes (no
dangerouslySetInnerHTML) so user-imported HTML in notes
cannot inject markup.

The 4 not-yet-built routes (chat, graph, tags, settings)
get minimal "coming soon" placeholders so the navigation
graph promised by the palette is intact.
…aph item

The dialog's search input was rendering much narrower than
the list rows below it. The shadcn CommandInput wrapper
adds an extra `p-1` div and renders its own SearchIcon,
which combined with the inline Esc kbd squeezed the
placeholder until the label truncated. Switched to cmdk's
Command.Input directly and dropped the redundant inline
Esc (the footer already documents it).

Also drops the literal "(placeholder)" suffix from the
Graph item description — leftover stub language from when
the route itself was a placeholder.
@mikkisguy mikkisguy merged commit 394c928 into main Jun 21, 2026
3 checks passed
@mikkisguy mikkisguy deleted the issue/88-command-palette branch June 21, 2026 19:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant