Skip to content

[LKPR-124] feat: Command Palette (⌘K) - #310

Merged
Jessinra merged 6 commits into
mainfrom
feat/LKPR-124-command-palette
Jul 9, 2026
Merged

[LKPR-124] feat: Command Palette (⌘K)#310
Jessinra merged 6 commits into
mainfrom
feat/LKPR-124-command-palette

Conversation

@jessinra-megumi-dev

Copy link
Copy Markdown
Contributor

Summary

Implements the Command Palette behind a ⌘K / Ctrl+K global hotkey.

New files

  • src/lib/commands.ts — Command registry with buildCommands, filterCommands, GROUP_ORDER; jump commands auto-generated from NAV_ROUTES
  • src/lib/hotkeys.ts — Global keydown listener for ⌘K (Mac) / Ctrl+K; returns cleanup fn
  • src/lib/components/overlays/CommandPalette.svelte — 640px centered modal, blurred/dimmed scrim, ↑↓Enter keyboard nav, live filter, 3 groups (Recent / Jump to / Actions), footer hint bar

Modified files

  • AppShell.svelte — mounts CommandPalette + registers hotkey via attachCommandPaletteHotkey
  • TopBar.svelte — search-trigger button now calls onOpenPalette prop (previously was a dead button)

Tests

  • 6 tests for hotkeys.ts (⌘K, Ctrl+K, bare K, ⌘J, cleanup, Mac-vs-Win modifier)
  • 17 tests for commands.ts (buildCommands shape, unique IDs, jump wiring, action callbacks, filterCommands empty/whitespace/case/no-match)
  • All 48 dashboard tests pass; 457 Python unit tests pass; 0 svelte-check errors

ACs met

  • CommandPalette.svelte created
  • commands.ts registry created
  • hotkeys.ts ⌘K/Ctrl+K listener created
  • AppShell.svelte mounts palette + registers hotkey
  • TopBar.svelte wires search trigger to open palette
  • Centered 640px modal
  • Blurred/dimmed scrim (OverlayScrim, z-index 800)
  • ↑↓Enter keyboard nav, Esc closes
  • Debounce-free live filter (reactive $derived)
  • 3 groups: Recent / Jump to / Actions

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added a command palette overlay with grouped, searchable commands, an empty-results state, and full keyboard/mouse navigation.
    • Added a global shortcut and top-bar trigger to open the palette.
  • Bug Fixes
    • Refined palette filtering for empty/whitespace queries, case-insensitive matching (label and hints), and stable ordering.
  • Tests
    • Added coverage for command creation, filtering, hotkey handling, palette interactions, and command routing.
    • Updated test setup to stub scrollIntoView to keep component tests reliable.
  • Chores
    • Centralized icon and UI text resources; updated overlay/toast/confirm/scrim visuals to use shared design tokens.

Walkthrough

This PR adds a command palette for dashboard v2, with shared command data and constants, a global keyboard shortcut, a searchable overlay, and shell wiring to open and close it from the top bar or hotkey.

Changes

Command Palette

Layer / File(s) Summary
Shared constants and route icons
src/dashboard_v2/src/lib/constants/icons.ts, src/dashboard_v2/src/lib/constants/keybindings.ts, src/dashboard_v2/src/lib/constants/strings.ts, src/dashboard_v2/src/lib/constants/routes.ts, src/dashboard_v2/src/app.css, src/dashboard_v2/src/lib/components/ui/OverlayScrim.svelte
Adds shared icon, keybinding, and string constants, updates route icons to use shared values, and expands theme tokens used by overlay components.
Command data and filtering
src/dashboard_v2/src/lib/commands.ts, src/dashboard_v2/src/lib/commands.test.ts
Builds jump and action commands from routes and shell callbacks, exports grouping metadata, and filters commands by query with tests.
Command palette hotkey
src/dashboard_v2/src/lib/hotkeys.ts, src/dashboard_v2/src/lib/hotkeys.test.ts
Implements the global command-palette keyboard listener and verifies platform-specific shortcut matching and cleanup behavior.
CommandPalette overlay
src/dashboard_v2/src/lib/components/overlays/CommandPalette.svelte, src/dashboard_v2/src/lib/components/overlays/CommandPalette.test.ts, src/dashboard_v2/src/test-setup.ts
Adds the searchable command palette dialog with grouped rendering, keyboard navigation, interaction handling, and component styling, plus overlay tests and a test JSDOM stub.
Shell wiring for palette
src/dashboard_v2/src/lib/components/shell/AppShell.svelte, src/dashboard_v2/src/lib/components/shell/TopBar.svelte
Wires AppShell to build palette commands, register the hotkey, open and close the overlay, and pass a palette opener into TopBar.
Shared overlay and navigation UI
src/dashboard_v2/src/lib/components/overlays/ConfirmDialog.svelte, src/dashboard_v2/src/lib/components/overlays/Toast.svelte, src/dashboard_v2/src/lib/components/shell/NavRail.svelte, src/dashboard_v2/src/lib/components/ui/OverlayScrim.svelte
Replaces inline overlay, toast, confirm, and navigation text and icon values with shared constants.

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Ci ❌ Error The PR diff has 1347 insertions, exceeding CI’s 1000-insertion size gate, so the GitHub pipeline would fail. Reduce the PR size below 1000 insertions or add the skip_size_check label if the project allows bypassing the size gate.
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title includes the LKPR ticket tag and clearly describes the Command Palette feature change.
Description check ✅ Passed The description is directly about the Command Palette hotkey, components, and tests, matching the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/dashboard_v2/src/lib/components/overlays/CommandPalette.svelte`:
- Around line 133-141: The CommandPalette dialog currently exposes modal
semantics but does not keep keyboard focus inside the overlay. Update the
CommandPalette.svelte dialog handling around the open state and the palette-card
container so Tab/Shift+Tab are trapped within the command palette while it is
open, using the existing open/onClose flow and the dialog element marked with
role="dialog" and aria-modal="true". Ensure focus moves into the palette on open
and cannot escape to background content until onClose runs.
- Around line 46-105: Selection logic in CommandPalette.svelte is still using
filtered for Arrow/Enter and aria-activedescendant, which can drift from the
grouped UI order defined by renderItems. Add a command-only list derived from
renderItems (excluding headers) and use that list for activeIndex navigation,
Enter selection, and the active descendant reference. Keep the grouping/order
behavior from GROUP_ORDER, but make all keyboard focus and invocation paths
operate on the rendered command sequence so recent items or reordered groups
stay aligned.

In `@src/dashboard_v2/src/lib/hotkeys.ts`:
- Line 19: The `hotkeys.ts` platform check using `navigator.platform` may be
flagged by lint/CI even though it is acceptable for deciding between ⌘ and ⌃;
confirm whether there is a repo rule against deprecated Web APIs, and if so,
update the `isMac` detection logic in `hotkeys.ts` to the approved alternative
while keeping the same modifier-key behavior used by the hotkey handling code.
- Line 22: The hotkey check in the hotkeys handler is too strict, so the
shortcut fails when Caps Lock makes the key uppercase. Update the key comparison
in the hotkey listener logic (the branch that checks modifierHeld and e.key) to
normalize the pressed key before matching, so both 'k' and 'K' trigger the same
shortcut without changing the existing modifier requirement.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6a46d407-35c8-4cf8-9dd0-73255d4cff48

📥 Commits

Reviewing files that changed from the base of the PR and between 811f4d1 and 284d38d.

📒 Files selected for processing (7)
  • src/dashboard_v2/src/lib/commands.test.ts
  • src/dashboard_v2/src/lib/commands.ts
  • src/dashboard_v2/src/lib/components/overlays/CommandPalette.svelte
  • src/dashboard_v2/src/lib/components/shell/AppShell.svelte
  • src/dashboard_v2/src/lib/components/shell/TopBar.svelte
  • src/dashboard_v2/src/lib/hotkeys.test.ts
  • src/dashboard_v2/src/lib/hotkeys.ts

Comment thread src/dashboard_v2/src/lib/components/overlays/CommandPalette.svelte
Comment thread src/dashboard_v2/src/lib/components/overlays/CommandPalette.svelte
Comment thread src/dashboard_v2/src/lib/hotkeys.ts Outdated
Comment thread src/dashboard_v2/src/lib/hotkeys.ts Outdated
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Coverage report

  • Total: 78%
  • Workflow run: 29011311289
  • HTML artifact: coverage-html

The report is also available in the GitHub Actions job summary.

…lette logic

- Extract all SVG paths to constants/icons.ts (single source of truth)
- Extract hotkey config to constants/keybindings.ts (key, modifier, display symbols)
- routes.ts imports from icons.ts — no more inline path strings
- commands.ts imports from icons.ts — removes local ICON_* duplicates
- hotkeys.ts driven by COMMAND_PALETTE_HOTKEY config — no magic strings
- CommandPalette.svelte: pure UI component; accepts commands as prop,
  removes goto/buildCommands coupling (no SvelteKit knowledge)
- AppShell.svelte: owns command construction + goto wiring (composition root)
- TopBar.svelte: derives modifier hint from keybinding config (⌘ vs Ctrl)
  stays in sync automatically if shortcut ever changes

48/48 tests pass, 0 build errors

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a global Command Palette (⌘K / Ctrl+K) for the dashboard_v2 SvelteKit app (LKPR-124). It adds a typed command registry, a global hotkey listener, and a centered modal overlay, then wires them into the existing app shell so the previously dead TopBar search button now opens the palette. It fits into the ongoing dashboard-v2 shell epic, reusing existing primitives (OverlayScrim, Icon, NAV_ROUTES, design tokens).

Changes:

  • New commands.ts registry (buildCommands, filterCommands, GROUP_ORDER/GROUP_LABELS) with jump commands auto-generated from NAV_ROUTES and two static actions.
  • New CommandPalette.svelte (640px centered modal, live filter, ↑↓/Enter/Esc keyboard nav, grouped rendering) plus hotkeys.ts global ⌘K/Ctrl+K listener; wired into AppShell.svelte and TopBar.svelte.
  • Unit tests for commands.ts (17) and hotkeys.ts (6); the component itself is not tested.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/dashboard_v2/src/lib/commands.ts New typed command registry; builds jump commands from NAV_ROUTES and static actions, plus filterCommands substring filter.
src/dashboard_v2/src/lib/commands.test.ts Unit tests for command shape, unique IDs, jump/action wiring, and filtering.
src/dashboard_v2/src/lib/hotkeys.ts Global keydown listener for ⌘K (Mac) / Ctrl+K; returns cleanup fn.
src/dashboard_v2/src/lib/hotkeys.test.ts Tests modifier handling, negative cases, and cleanup.
src/dashboard_v2/src/lib/components/overlays/CommandPalette.svelte New palette modal: input, grouped results, keyboard nav, empty state, footer hints. Selection indexing relies on filtered/flatIndex orders staying in sync.
src/dashboard_v2/src/lib/components/shell/AppShell.svelte Mounts CommandPalette, manages open state, registers hotkey in onMount.
src/dashboard_v2/src/lib/components/shell/TopBar.svelte Adds onOpenPalette prop and wires the search trigger button to it.

Comment thread src/dashboard_v2/src/lib/components/overlays/CommandPalette.svelte
Comment thread src/dashboard_v2/src/lib/components/overlays/CommandPalette.svelte
Comment thread src/dashboard_v2/src/lib/components/overlays/CommandPalette.svelte Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/dashboard_v2/src/lib/hotkeys.ts (2)

19-28: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Caps Lock still breaks the shortcut.

e.key === COMMAND_PALETTE_HOTKEY.key is a strict lowercase comparison; with Caps Lock on, e.key is 'K', so the shortcut silently fails to fire. This was flagged on a previous commit and remains unaddressed in this version.

🔧 Proposed fix
-		if (e[modifier] && e.key === COMMAND_PALETTE_HOTKEY.key) {
+		if (e[modifier] && e.key.toLowerCase() === COMMAND_PALETTE_HOTKEY.key) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dashboard_v2/src/lib/hotkeys.ts` around lines 19 - 28, The command
palette shortcut in handleKeydown is still failing when Caps Lock changes the
key casing. Update the key check in hotkeys.ts so it normalizes e.key before
comparing against COMMAND_PALETTE_HOTKEY.key, while keeping the existing
modifier logic with COMMAND_PALETTE_HOTKEY.macModifier and
COMMAND_PALETTE_HOTKEY.otherModifier. Make sure onOpen still only fires when the
intended modifier plus the hotkey key are pressed, regardless of
uppercase/lowercase input.

20-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

navigator.platform is deprecated.

Previously flagged as acceptable-but-worth-confirming for this specific ⌘/Ctrl detection use case; still unresolved. Feature-detection alternatives exist but this remains one of the few legitimate uses of navigator.platform.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dashboard_v2/src/lib/hotkeys.ts` at line 20, Replace the deprecated
navigator.platform check in hotkeys.ts with a non-deprecated OS detection
approach inside the hotkey handling logic (for example, prefer
navigator.userAgentData.platform when available and fall back safely). Update
the isMac computation in the hotkeys utility so ⌘/Ctrl detection still works
without relying on navigator.platform.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/dashboard_v2/src/lib/components/shell/TopBar.svelte`:
- Around line 15-20: The Mac platform check is duplicated between TopBar.svelte
and hotkeys.ts, so extract the detection logic into a shared isMacPlatform()
helper and use it from both places. Update TopBar’s isMac computation to call
the shared helper, and make attachCommandPaletteHotkey reuse the same helper so
any future platform-detection change stays consistent.

---

Duplicate comments:
In `@src/dashboard_v2/src/lib/hotkeys.ts`:
- Around line 19-28: The command palette shortcut in handleKeydown is still
failing when Caps Lock changes the key casing. Update the key check in
hotkeys.ts so it normalizes e.key before comparing against
COMMAND_PALETTE_HOTKEY.key, while keeping the existing modifier logic with
COMMAND_PALETTE_HOTKEY.macModifier and COMMAND_PALETTE_HOTKEY.otherModifier.
Make sure onOpen still only fires when the intended modifier plus the hotkey key
are pressed, regardless of uppercase/lowercase input.
- Line 20: Replace the deprecated navigator.platform check in hotkeys.ts with a
non-deprecated OS detection approach inside the hotkey handling logic (for
example, prefer navigator.userAgentData.platform when available and fall back
safely). Update the isMac computation in the hotkeys utility so ⌘/Ctrl detection
still works without relying on navigator.platform.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3e4b626f-f942-4984-abc1-a7e172c2a77b

📥 Commits

Reviewing files that changed from the base of the PR and between 284d38d and 603debf.

📒 Files selected for processing (8)
  • src/dashboard_v2/src/lib/commands.ts
  • src/dashboard_v2/src/lib/components/overlays/CommandPalette.svelte
  • src/dashboard_v2/src/lib/components/shell/AppShell.svelte
  • src/dashboard_v2/src/lib/components/shell/TopBar.svelte
  • src/dashboard_v2/src/lib/constants/icons.ts
  • src/dashboard_v2/src/lib/constants/keybindings.ts
  • src/dashboard_v2/src/lib/constants/routes.ts
  • src/dashboard_v2/src/lib/hotkeys.ts

Comment thread src/dashboard_v2/src/lib/components/shell/TopBar.svelte Outdated
… color tokens

constants/icons.ts
- Added ICON_CHECK, ICON_X_CLOSE, ICON_INFO_DOT (Toast)
- Added ICON_INFO_CIRCLE, ICON_TRASH (ConfirmDialog)
- Added ICON_SEARCH (TopBar — replaces inline SVG structure)

constants/keybindings.ts
- Added macAriaKeyshortcuts / otherAriaKeyshortcuts fields
- TopBar derives aria-keyshortcuts value from config (was hardcoded 'Meta+k')

constants/strings.ts (NEW)
- PALETTE_STRINGS: dialogAriaLabel, inputPlaceholder, closeButtonAriaLabel,
  resultsListAriaLabel, emptyStatePreamble, footerNavigate/Select/Close
- TOP_BAR_STRINGS: breadcrumbNavAriaLabel, breadcrumbRoot, searchTriggerPlaceholder
- NAV_RAIL_STRINGS: navAriaLabel, logoAlt, healthDotTitle/AriaLabel, badgePendingSuffix
- TOAST_STRINGS: dismissAriaLabel
- CONFIRM_STRINGS: cancelLabel

app.css — added missing tokens:
- --color-danger-hover (#b91c1c)
- --color-scrim, --color-shadow-sm/md/lg/xl (overlay shadows)
- --color-toast-bg/text/dismiss/dismiss-hover
- --color-text-on-filled (#ffffff for filled button text)

Components:
- OverlayScrim: rgba(0,0,0,0.4) → var(--color-scrim)
- Toast: rgba/hex colours → tokens; icons → ICON_* constants; text → TOAST_STRINGS
- ConfirmDialog: rgba shadows → tokens; hex white → tokens; icons → ICON_* constants;
  hardcoded 'b91c1c' fallback → var(--color-danger-hover); text → CONFIRM_STRINGS
- CommandPalette: shadows → tokens; all aria/placeholder text → PALETTE_STRINGS
- TopBar: inline SVG → <Icon path={ICON_SEARCH}>; all text → TOP_BAR_STRINGS;
  aria-keyshortcuts → derived from config
- NavRail: all aria text → NAV_RAIL_STRINGS

48/48 tests pass, 0 build errors

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.

Comment thread src/dashboard_v2/src/lib/components/overlays/CommandPalette.svelte
Comment thread src/dashboard_v2/src/lib/components/overlays/CommandPalette.svelte
Comment thread src/dashboard_v2/src/lib/components/overlays/CommandPalette.svelte Outdated
Diana added 2 commits July 9, 2026 18:13
… Caps Lock, navigator.platform deprecation, inline SVG
…tkeys.test for userAgent mock; fix scrollIntoView in jsdom
@jessinra-megumi-dev

Copy link
Copy Markdown
Contributor Author

Review comments resolved (dee1c91 + 35b4e1e)

All valid bot comments addressed. Here's what was done and why:

✅ Fixed

# Comment Fix
CR/Copilot Index driftfiltered[activeIndex] diverges from renderItems group order when recent commands exist Derived flatCommands from renderItems (command-only rows, GROUP_ORDER-sorted). All keyboard nav (↑↓/Enter), aria-activedescendant, and activeIndex now index into flatCommands
CR/Copilot Double closeonClose() fired twice per selection (AppShell callback + palette's own handler) Removed closePalette() from each buildCommands callback; palette's handleSelect is the single close path
CR Caps Lock breaks shortcute.key === 'k' misses 'K' e.key.toLowerCase() === COMMAND_PALETTE_HOTKEY.key
CR navigator.platform deprecated in hotkeys.ts Replaced with navigator.userAgentData?.platform?.includes('Mac') ?? navigator.userAgent.includes('Mac'); typed via inline intersection cast (lib.dom gap)
Copilot navigator.platform deprecated in TopBar.svelte Same userAgentData/userAgent fallback pattern
Copilot Inline SVG duplicates ICON_SEARCH / bypasses <Icon> Replaced with <Icon path={ICON_SEARCH} size={16} />; CSS updated to target wrapper <span>
Copilot No component tests Added CommandPalette.test.ts — 16 cases covering render, filter, keyboard nav (wrap-around, Enter, Escape), mouse hover, click; scrollIntoView stubbed in test-setup.ts

❌ Not fixed — intentional

# Comment Reason
CR Focus trap (Tab/Shift+Tab confined to palette) CodeRabbit itself rated this "poor tradeoff" (trivial severity). OverlayScrim handles click-outside; Escape handles keyboard dismiss. Full focus trap implementation is scope creep for this PR and can be a follow-up if required by accessibility audit.

Test results

Dashboard: 65 passed (65)   ← +17 new cases
Backend:  457 passed        ← no regressions
E2E:       10 passed
svelte-check: 0 errors
tsc:          clean

@Jessinra
Jessinra merged commit 6168b6f into main Jul 9, 2026
5 checks passed
@Jessinra
Jessinra deleted the feat/LKPR-124-command-palette branch July 9, 2026 10:24

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/dashboard_v2/src/lib/components/shell/TopBar.svelte`:
- Around line 17-35: The platform sniffing in TopBar.svelte duplicates the
mac-detection logic from hotkeys.ts. Replace the inline navigator/userAgentData
fallback chain in this block with the shared isMacPlatform() helper so the
modifier display and ariaKeyshortcuts derive from one source of truth. Keep the
SSR-safe behavior in TopBar by relying on the helper rather than reimplementing
the detection here.

In `@src/dashboard_v2/src/lib/constants/strings.ts`:
- Around line 18-44: The placeholder text is duplicated between
PALETTE_STRINGS.inputPlaceholder and TOP_BAR_STRINGS.searchTriggerPlaceholder,
so update the strings in src/dashboard_v2/src/lib/constants/strings.ts to derive
one from the other instead of hardcoding both. Keep the shared value in a single
source of truth within the PALETTE_STRINGS/TOP_BAR_STRINGS constants so any
future copy change stays synchronized.

In `@src/dashboard_v2/src/lib/hotkeys.test.ts`:
- Around line 1-20: The hotkey tests only cover the navigator.userAgent fallback
and never exercise the primary navigator.userAgentData.platform path. Add a test
in hotkeys.test.ts that configures navigator.userAgentData with platform set to
macOS and verifies attachCommandPaletteHotkey() triggers onOpen for the correct
modifier, while keeping the existing mockMac()/mockWindows() fallback tests
intact.

In `@src/dashboard_v2/src/lib/hotkeys.ts`:
- Around line 20-31: The platform-detection logic in hotkeys handling is
duplicated with TopBar.svelte and should be extracted into a shared helper. Move
the `userAgentData`/`userAgent` Mac check into a reusable function in a shared
module such as `platform.ts`, then import and use it from the hotkeys code
instead of inlining the detection. Keep the existing `COMMAND_PALETTE_HOTKEY`
modifier selection and `isMac` decision intact, but source the platform result
from the shared helper so both call sites stay in sync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 33a0869c-d741-4193-b1c9-b53314380278

📥 Commits

Reviewing files that changed from the base of the PR and between 603debf and 35b4e1e.

📒 Files selected for processing (15)
  • src/dashboard_v2/src/app.css
  • src/dashboard_v2/src/lib/components/overlays/CommandPalette.svelte
  • src/dashboard_v2/src/lib/components/overlays/CommandPalette.test.ts
  • src/dashboard_v2/src/lib/components/overlays/ConfirmDialog.svelte
  • src/dashboard_v2/src/lib/components/overlays/Toast.svelte
  • src/dashboard_v2/src/lib/components/shell/AppShell.svelte
  • src/dashboard_v2/src/lib/components/shell/NavRail.svelte
  • src/dashboard_v2/src/lib/components/shell/TopBar.svelte
  • src/dashboard_v2/src/lib/components/ui/OverlayScrim.svelte
  • src/dashboard_v2/src/lib/constants/icons.ts
  • src/dashboard_v2/src/lib/constants/keybindings.ts
  • src/dashboard_v2/src/lib/constants/strings.ts
  • src/dashboard_v2/src/lib/hotkeys.test.ts
  • src/dashboard_v2/src/lib/hotkeys.ts
  • src/dashboard_v2/src/test-setup.ts
💤 Files with no reviewable changes (1)
  • src/dashboard_v2/src/lib/components/shell/AppShell.svelte

Comment on lines +17 to +35
// Derive the correct modifier symbol from config — stays in sync with hotkeys.ts
// navigator.platform is deprecated; prefer userAgentData.platform (Chrome 90+)
// with a userAgent fallback for Firefox/Safari.
// Cast needed because userAgentData is not yet in the lib.dom typings.
const ua = typeof navigator !== 'undefined'
? (navigator as Navigator & { userAgentData?: { platform: string } })
: null;
const isMac = ua
? (ua.userAgentData
? ua.userAgentData.platform === 'macOS'
: /Mac|iPhone|iPad|iPod/.test(navigator.userAgent))
: false;
const modifierDisplay = isMac
? COMMAND_PALETTE_HOTKEY.macModifierDisplay
: COMMAND_PALETTE_HOTKEY.otherModifierDisplay;
const keyDisplay = COMMAND_PALETTE_HOTKEY.keyDisplay;
const ariaKeyshortcuts = isMac
? COMMAND_PALETTE_HOTKEY.macAriaKeyshortcuts
: COMMAND_PALETTE_HOTKEY.otherAriaKeyshortcuts;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Same platform-detection logic duplicated from hotkeys.ts.

This is the same userAgentData/userAgent fallback chain as in hotkeys.ts (with an added SSR guard). This is the same duplication concern raised previously for the mac-sniffing check — once a shared isMacPlatform() helper exists (see comment on hotkeys.ts), this block reduces to a single call.

♻️ Suggested change (after extracting the shared helper)
-	const ua = typeof navigator !== 'undefined'
-		? (navigator as Navigator & { userAgentData?: { platform: string } })
-		: null;
-	const isMac = ua
-		? (ua.userAgentData
-			? ua.userAgentData.platform === 'macOS'
-			: /Mac|iPhone|iPad|iPod/.test(navigator.userAgent))
-		: false;
+	const isMac = isMacPlatform();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dashboard_v2/src/lib/components/shell/TopBar.svelte` around lines 17 -
35, The platform sniffing in TopBar.svelte duplicates the mac-detection logic
from hotkeys.ts. Replace the inline navigator/userAgentData fallback chain in
this block with the shared isMacPlatform() helper so the modifier display and
ariaKeyshortcuts derive from one source of truth. Keep the SSR-safe behavior in
TopBar by relying on the helper rather than reimplementing the detection here.

Comment on lines +18 to +44
export const PALETTE_STRINGS = {
/** role="dialog" aria-label */
dialogAriaLabel: 'Command palette',
/** Text input placeholder */
inputPlaceholder: 'Search or jump to…',
/** Close button aria-label */
closeButtonAriaLabel: 'Close palette',
/** Results listbox aria-label */
resultsListAriaLabel: 'Commands',
/** Static prefix for the empty-state message — e.g. 'No results for "foo"' */
emptyStatePreamble: 'No results for',
/** Footer keyboard hint labels */
footerNavigate: 'navigate',
footerSelect: 'select',
footerClose: 'close',
} as const;

// ── Top Bar ───────────────────────────────────────────────────────────────────

export const TOP_BAR_STRINGS = {
/** aria-label for the breadcrumb <nav> */
breadcrumbNavAriaLabel: 'Breadcrumb',
/** Static "root" segment shown in the breadcrumb trail */
breadcrumbRoot: APP_NAME,
/** Visible placeholder inside the search trigger button */
searchTriggerPlaceholder: 'Search or jump to…',
} as const;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate literal between PALETTE_STRINGS.inputPlaceholder and TOP_BAR_STRINGS.searchTriggerPlaceholder.

Both are 'Search or jump to…'. Consider deriving one from the other to keep them in sync if either changes.

♻️ Suggested consolidation
 export const TOP_BAR_STRINGS = {
 	breadcrumbNavAriaLabel: 'Breadcrumb',
 	breadcrumbRoot: APP_NAME,
-	searchTriggerPlaceholder: 'Search or jump to…',
+	searchTriggerPlaceholder: PALETTE_STRINGS.inputPlaceholder,
 } as const;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const PALETTE_STRINGS = {
/** role="dialog" aria-label */
dialogAriaLabel: 'Command palette',
/** Text input placeholder */
inputPlaceholder: 'Search or jump to…',
/** Close button aria-label */
closeButtonAriaLabel: 'Close palette',
/** Results listbox aria-label */
resultsListAriaLabel: 'Commands',
/** Static prefix for the empty-state message — e.g. 'No results for "foo"' */
emptyStatePreamble: 'No results for',
/** Footer keyboard hint labels */
footerNavigate: 'navigate',
footerSelect: 'select',
footerClose: 'close',
} as const;
// ── Top Bar ───────────────────────────────────────────────────────────────────
export const TOP_BAR_STRINGS = {
/** aria-label for the breadcrumb <nav> */
breadcrumbNavAriaLabel: 'Breadcrumb',
/** Static "root" segment shown in the breadcrumb trail */
breadcrumbRoot: APP_NAME,
/** Visible placeholder inside the search trigger button */
searchTriggerPlaceholder: 'Search or jump to…',
} as const;
export const PALETTE_STRINGS = {
/** role="dialog" aria-label */
dialogAriaLabel: 'Command palette',
/** Text input placeholder */
inputPlaceholder: 'Search or jump to…',
/** Close button aria-label */
closeButtonAriaLabel: 'Close palette',
/** Results listbox aria-label */
resultsListAriaLabel: 'Commands',
/** Static prefix for the empty-state message — e.g. 'No results for "foo"' */
emptyStatePreamble: 'No results for',
/** Footer keyboard hint labels */
footerNavigate: 'navigate',
footerSelect: 'select',
footerClose: 'close',
} as const;
// ── Top Bar ───────────────────────────────────────────────────────────────────
export const TOP_BAR_STRINGS = {
/** aria-label for the breadcrumb <nav> */
breadcrumbNavAriaLabel: 'Breadcrumb',
/** Static "root" segment shown in the breadcrumb trail */
breadcrumbRoot: APP_NAME,
/** Visible placeholder inside the search trigger button */
searchTriggerPlaceholder: PALETTE_STRINGS.inputPlaceholder,
} as const;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dashboard_v2/src/lib/constants/strings.ts` around lines 18 - 44, The
placeholder text is duplicated between PALETTE_STRINGS.inputPlaceholder and
TOP_BAR_STRINGS.searchTriggerPlaceholder, so update the strings in
src/dashboard_v2/src/lib/constants/strings.ts to derive one from the other
instead of hardcoding both. Keep the shared value in a single source of truth
within the PALETTE_STRINGS/TOP_BAR_STRINGS constants so any future copy change
stays synchronized.

Comment on lines +1 to +20
import { describe, it, expect, vi, afterEach } from 'vitest';
import { attachCommandPaletteHotkey } from '$lib/hotkeys.js';

// Helper to mock platform detection.
// hotkeys.ts reads navigator.userAgentData.platform (Chrome 90+) with a
// navigator.userAgent fallback, so we mock via userAgent which jsdom exposes.
function mockMac() {
vi.spyOn(navigator, 'userAgent', 'get').mockReturnValue(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
);
// Ensure userAgentData is absent so userAgent fallback is used
Object.defineProperty(navigator, 'userAgentData', { value: undefined, configurable: true });
}

function mockWindows() {
vi.spyOn(navigator, 'userAgent', 'get').mockReturnValue(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
);
Object.defineProperty(navigator, 'userAgentData', { value: undefined, configurable: true });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Primary userAgentData detection path is never actually exercised.

Both mockMac() and mockWindows() force navigator.userAgentData to undefined, so every test only covers the userAgent regex fallback branch. The ua.userAgentData.platform === 'macOS' branch — the one used in modern Chrome/Edge (90+) — has zero test coverage. Add at least one test that sets userAgentData to confirm that path also resolves the modifier correctly.

🧪 Suggested addition
function mockMacUserAgentData() {
	Object.defineProperty(navigator, 'userAgentData', {
		value: { platform: 'macOS' },
		configurable: true
	});
}
it('calls onOpen on ⌘K via userAgentData.platform', () => {
	mockMacUserAgentData();
	const onOpen = vi.fn();
	cleanup = attachCommandPaletteHotkey(onOpen);
	fire('k', { metaKey: true });
	expect(onOpen).toHaveBeenCalledOnce();
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dashboard_v2/src/lib/hotkeys.test.ts` around lines 1 - 20, The hotkey
tests only cover the navigator.userAgent fallback and never exercise the primary
navigator.userAgentData.platform path. Add a test in hotkeys.test.ts that
configures navigator.userAgentData with platform set to macOS and verifies
attachCommandPaletteHotkey() triggers onOpen for the correct modifier, while
keeping the existing mockMac()/mockWindows() fallback tests intact.

Comment on lines +20 to +31
// navigator.platform is deprecated; prefer userAgentData.platform (Chrome 90+)
// with a userAgent fallback for Firefox/Safari.
// Cast needed because userAgentData is not yet in the lib.dom typings.
const ua = navigator as Navigator & { userAgentData?: { platform: string } };
const isMac = ua.userAgentData
? ua.userAgentData.platform === 'macOS'
: /Mac|iPhone|iPad|iPod/.test(navigator.userAgent);
const modifier = isMac
? COMMAND_PALETTE_HOTKEY.macModifier
: COMMAND_PALETTE_HOTKEY.otherModifier;

if (e[modifier] && e.key.toLowerCase() === COMMAND_PALETTE_HOTKEY.key) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Platform-detection logic duplicated with TopBar.svelte.

The userAgentDatauserAgent fallback chain here is nearly byte-for-byte duplicated in TopBar.svelte (lines 21-28). A prior review already flagged this duplication for the earlier navigator.platform implementation — the new, more complex fallback logic re-introduces the same DRY problem and doubles the maintenance/drift risk (e.g., if the macOS string check or fallback regex needs a fix later).

Extract into a shared helper (e.g. $lib/platform.ts) and import it from both files.

♻️ Suggested extraction
// src/dashboard_v2/src/lib/platform.ts
export function isMacPlatform(): boolean {
	if (typeof navigator === 'undefined') return false;
	const ua = navigator as Navigator & { userAgentData?: { platform: string } };
	return ua.userAgentData
		? ua.userAgentData.platform === 'macOS'
		: /Mac|iPhone|iPad|iPod/.test(navigator.userAgent);
}
-import { COMMAND_PALETTE_HOTKEY } from '$lib/constants/keybindings.js';
+import { COMMAND_PALETTE_HOTKEY } from '$lib/constants/keybindings.js';
+import { isMacPlatform } from '$lib/platform.js';
...
-		const ua = navigator as Navigator & { userAgentData?: { platform: string } };
-		const isMac = ua.userAgentData
-			? ua.userAgentData.platform === 'macOS'
-			: /Mac|iPhone|iPad|iPod/.test(navigator.userAgent);
+		const isMac = isMacPlatform();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dashboard_v2/src/lib/hotkeys.ts` around lines 20 - 31, The
platform-detection logic in hotkeys handling is duplicated with TopBar.svelte
and should be extracted into a shared helper. Move the
`userAgentData`/`userAgent` Mac check into a reusable function in a shared
module such as `platform.ts`, then import and use it from the hotkeys code
instead of inlining the detection. Keep the existing `COMMAND_PALETTE_HOTKEY`
modifier selection and `isMac` decision intact, but source the platform result
from the shared helper so both call sites stay in sync.

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.

2 participants