feat(command-palette): publish + 24 fixes → @cofoundy/ui 0.6.0#13
Merged
Conversation
Library-side fixes before consumer wiring. All 7 existing tests still pass. Functional: - Reset query/results/selectedIndex on close (regression vs source hook) - InitialState recents: drop fake "selected" on first row, fix ARIA - Remove onMouseEnter selection hijack (kept keyboard sticky) - Snippet now 2-line clamps so <mark> isn't truncated past viewport - minQueryLength prop (default 2) + stale-results indicator during debounce - retryNonce wires the error-state Retry button to actually re-fire - emptyActions extended with `onSelect` — chips render as buttons when wired - Clear (×) button in input A11y: - Cmd/Ctrl+Enter (and Cmd-click) opens hit in a new tab - Focus trap on the dialog (Tab cycles inside) - aria-controls only points at the listbox when it's mounted - Option IDs namespaced via useId so two palettes don't collide - Focus restore guarded with isConnected - prefers-reduced-motion fallback for all 4 animations Library boundary: - Dev warning when searchFn is missing instead of silent no-op - SearchHit.role now optional, rendered as a small role badge if present - Same-origin guard: external URLs auto-open in a new tab with noopener - Snippet sanitizer (allow-only-<mark>) is the default; opt-in trustSnippetHtml - CSS variable defaults via :where(...) so the component renders safely standalone - Trigger pill min-height: 44px moved behind @media (pointer: coarse) Tests touched to keep passing with the new defaults: - Harness sets minQueryLength={1} so single-char fixtures still fire fetches
Library prep for downstream consumers. 472 tests run, 471 pass — the only
failure is the pre-existing ChatInput baseline (unrelated, documented).
Public surface additions:
- useCommandPaletteHotkeys({open, setOpen}) — Cmd+K / / / Esc binding
- onSearch?(query, hits, took_ms?) telemetry
- onSelect?(hit, idx, 'click' | 'enter') telemetry
- trustSnippetHtml prop — opt-in raw HTML, default sanitize
Infrastructure refactor (no API change):
- Singleton CSS injection — one <style id="cp-styles"> appended to <head>
on first mount instead of re-injected per-palette-open.
- Refcounted body scroll-lock — composes safely with sibling modals.
Tests: 21 total (7 baseline + 14 new). Covers min-query gate, XSS sanitization,
trustSnippetHtml opt-out, clear button, telemetry callbacks, emptyActions click,
Cmd+Enter new-tab, role badge, recents click-through, ID namespacing, dev warn,
and the three hotkeys (Cmd+K / / / Esc).
Stories: removed console.log in Harness, MobileBaseline story present.
Version: 0.5.2 → 0.6.0 (minor — additive public surface, no breaking change).
2d7c1a5 to
c400098
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ships
CommandPalette,CommandPaletteTrigger, anduseCommandPaletteHotkeysfromdocs-ai-vault-searchto@cofoundy/ui— plus 24 follow-up fixes from a deep audit of the original publish.This is the single bundled publish-prep PR before consumers wire it in. Bumps to 0.6.0.
What's new
Public exports
<CommandPalette>+<CommandPaletteTrigger>(themed, mobile sheet variant)useCommandPaletteHotkeys({open, setOpen})— Cmd+K /// Esc bindingCommandPaletteProps,SearchHit,SearchResponse,SearchFn,DocRole,RecentDoc,RecentSearch,EmptyActiononSearch?(query, hits, took_ms?),onSelect?(hit, idx, 'click' | 'enter')trustSnippetHtml?: booleanopt-in (default = sanitize allow-only-<mark>)minQueryLength?: number(default 2)Fixes (audit findings — 24 total)
Functional / UX (8)
onMouseEnterselection hijack (kept keyboard sticky)<mark>survives long-title truncationonSelectsuppliedA11y (6)
aria-controlsonly references the listbox when it's mounteduseIdso two palettes don't collideisConnectedprefers-reduced-motionfallback for all 4 animationssearchFndev warning on silent breakageBoundary / safety (5)
SearchHit.rolenow optional, rendered as a small role badge if present_blank+noopener,noreferrer<mark>-only) is the default; opt out withtrustSnippetHtml:where(...)so the component renders standalonemin-height: 44pxgated behind@media (pointer: coarse)Performance + infra (3)
<style id="cp-styles">to<head>, not re-injected per mount)retryNonce(the oldsetQuery((q) => q)was a no-op)Tests + stories (2)
console.login Harness; MobileBaseline story verifiedVerification
pnpm vitest run src/__tests__/components/command-palette/→ 21/21 greenpnpm vitest run(full suite) → 471/472 green (the 1 failure is the pre-existing ChatInput baseline, documented in the original publish commit msg, unaffected by this PR)pnpm tsc --noEmit→ clean on new code (pre-existing hero-shader errors unaffected, also documented)Test plan
pnpm storybook— Idle / Loading / Results / NoResults / Error / WithRecents / MobileBaseline render cleanly in light + darkdocs-ai-vault-searchbaseline (deferred to T35 SSOT regen in docs-ai)Follow-up after merge
docs-ai: refactorHeader.tsxto consume@cofoundy/uiCommandPalette + hook, delete the local copy, regen visual SSOT.