Skip to content

feat(keyboard): add keyboard shortcuts for log viewer#10

Merged
Divkix merged 28 commits intomainfrom
feat/keyboard-shortcuts
Jan 16, 2026
Merged

feat(keyboard): add keyboard shortcuts for log viewer#10
Divkix merged 28 commits intomainfrom
feat/keyboard-shortcuts

Conversation

@Divkix
Copy link
Owner

@Divkix Divkix commented Jan 15, 2026

Summary

Implements Gmail/GitHub-style keyboard shortcuts for the log viewer, enabling power users to navigate and interact with logs without using the mouse.

Implemented shortcuts:

  • j / k - Navigate down/up through logs
  • Enter - Open detail modal for selected log
  • Escape - Close modals, blur search
  • / - Focus search input
  • l - Toggle live mode (when not paused)
  • ? - Open keyboard shortcuts help modal

Key features:

  • Visual selection highlighting (ring + background color) on both desktop (LogRow) and mobile (LogCard)
  • Automatic scroll-into-view for selected logs
  • Screen reader announcements for navigation
  • Selection resets on filter changes (search, level, time range)
  • Shortcuts blocked during IME composition and in form fields
  • Help modal shows all available shortcuts

Files Created

  • src/lib/utils/keyboard.ts - Keyboard utilities (shouldBlockShortcut, SHORTCUTS)
  • src/lib/components/keyboard-help-modal.svelte - Help modal component
  • src/lib/utils/keyboard.unit.test.ts - Unit tests for keyboard utilities
  • src/lib/components/log-row.component.test.ts - Component tests for LogRow
  • src/lib/components/log-card.component.test.ts - Component tests for LogCard
  • src/lib/components/search-input.component.test.ts - Component tests for SearchInput
  • src/lib/components/keyboard-help-modal.component.test.ts - Component tests for KeyboardHelpModal

Files Modified

  • src/routes/(app)/projects/[id]/+page.svelte - Main keyboard handler
  • src/lib/components/log-table.svelte - Accept selectedIndex prop
  • src/lib/components/log-row.svelte - Accept isSelected, add visual styles
  • src/lib/components/log-card.svelte - Accept isSelected, add visual styles
  • src/lib/components/search-input.svelte - Expose ref, add onEscape callback

Test Coverage

  • Unit tests: shouldBlockShortcut function, SHORTCUTS array
  • Component tests: LogRow selection, LogCard selection, SearchInput ref/onEscape, KeyboardHelpModal open/close
  • All existing E2E tests continue to pass

Test Plan

  • Navigate logs with j/k, verify visual selection and scroll behavior
  • Press Enter on selected log, verify modal opens
  • Press Escape in modal, verify it closes and selection preserved
  • Press / to focus search, type query, press Escape to blur
  • Press l to toggle live mode, verify visual feedback
  • Press ? to open help modal, verify shortcuts displayed
  • Apply filters, verify selection resets
  • Test on mobile viewport with LogCard display

Divkix added 25 commits January 15, 2026 15:52
Spec artifacts:
- research.md: feasibility analysis and codebase exploration
- requirements.md: user stories and acceptance criteria
- design.md: architecture and technical decisions
- tasks.md: POC-first implementation plan (22 tasks)

Ready for implementation.
- Add selectedIndex state initialized to -1 (no selection)
- Add svelte:document onkeydown handler for global shortcuts
- Implement handleKeyboardShortcut with j/k key handling
- Add scrollSelectedIntoView() to scroll selected log into view
- Guard shortcuts when modal open or in form elements
- Add selectedIndex prop to LogTable component
- Pass isSelected={i === selectedIndex} to LogRow and LogCard
- Add isSelected prop to LogRow and LogCard with data-selected attribute
- Pass selectedIndex from page to LogTable

The data-selected attribute enables scrollSelectedIntoView() to find
selected elements. Visual styling will be added in subsequent tasks.
- Added conditional bg-primary/10 and ring-1 ring-primary/50 classes when isSelected is true
- Added aria-selected attribute for accessibility
- Note: svelte-check warns about aria-selected with role="button" but task requires it for screen reader support
- Fixed a11y warnings: aria-selected is not valid for role="button"
- Changed to aria-current="true" for selected state on LogRow and LogCard
- All lint and type checks now pass with 0 errors and 0 warnings
Verified existing LogDetailModal implementation:
- Escape handling via <svelte:document onkeydown>
- Focus restoration via previouslyFocusedElement
- Selection preserved after modal close (selectedIndex independent)
- bun run lint: passed (254 files checked, no issues)
- bun run check: passed (0 errors, 0 warnings)
- No fixes needed, all POC shortcuts working correctly
- Import announceToScreenReader from focus-trap utility
- Add position announcements on j/k navigation (e.g., "Log 3 of 10")
- Only announce when selection actually changes (check prevIndex)
- Remove unused ShortcutDefinition export (knip compliance)
- Add loading state guards to j/k navigation
- Ensure empty log list check for navigation shortcuts
- keyboard.ts already has proper JSDoc comments
- No console.log statements found
- Test shouldBlockShortcut function:
  - Blocks shortcuts for form elements (INPUT, TEXTAREA, SELECT)
  - Blocks shortcuts during IME composition
  - Blocks shortcuts when modifier keys are pressed (Ctrl, Alt, Meta)
  - Allows shortcuts for regular elements (DIV, TABLE, BUTTON, BODY)
  - Handles null target gracefully
- Test FORM_ELEMENTS constant contains all expected elements
- Test SHORTCUTS array contains all expected shortcuts with properties
Add test coverage for keyboard shortcut related functionality:
- Test ref bindable prop exposes input element
- Test programmatic focus via ref works
- Test onEscape callback when Escape is pressed
- Test input blurs on Escape key
- Test onEscape not called for other keys
- Test graceful handling when onEscape not provided
Comprehensive test coverage for the keyboard shortcuts help modal:
- Test rendering when open/closed
- Test all shortcuts from SHORTCUTS array are displayed
- Test group headers (Navigation, Search & Filters, Other)
- Test onClose callback on Escape key press
- Test backdrop click closes modal
- Test close button functionality
- Test accessibility: dialog role, aria-modal, aria-labelledby
- Test kbd elements have aria-labels for screen readers
Fixes E2E test failure where Enter key was being intercepted by the
page-level keyboard handler even when no log was selected, preventing
buttons like the filter-toggle from working properly.

The fix moves preventDefault() inside the condition that checks for
a valid selection, allowing buttons to work normally when no log is
selected.
@Divkix Divkix changed the title Add keyboard shortcuts feat(keyboard): add keyboard shortcuts for log viewer Jan 16, 2026
@Divkix Divkix marked this pull request as ready for review January 16, 2026 00:09
Copilot AI review requested due to automatic review settings January 16, 2026 00:09
Copy link

Copilot AI left a comment

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 Gmail/GitHub-style keyboard shortcuts for the log viewer to enable power users to navigate and interact with logs without using the mouse. The implementation adds comprehensive keyboard navigation (j/k for up/down, Enter to open details, / for search, l for live toggle, ? for help) with visual selection highlighting and accessibility features.

Changes:

  • Added keyboard shortcut handler at page level using <svelte:document> pattern
  • Implemented visual selection state with selectedIndex tracking and highlighting on LogRow/LogCard
  • Created keyboard utilities module with shortcut guards and definitions
  • Built KeyboardHelpModal component to display available shortcuts
  • Extended SearchInput component to support programmatic focus and Escape handling

Reviewed changes

Copilot reviewed 18 out of 19 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/routes/(app)/projects/[id]/+page.svelte Main keyboard handler with j/k/Enter/Escape///l/? shortcuts and selection state management
src/lib/utils/keyboard.ts Keyboard guard utilities and shortcut definitions for help modal
src/lib/utils/keyboard.unit.test.ts Unit tests for shouldBlockShortcut function and SHORTCUTS array
src/lib/components/keyboard-help-modal.svelte New modal component displaying available keyboard shortcuts
src/lib/components/keyboard-help-modal.component.test.ts Component tests for KeyboardHelpModal
src/lib/components/search-input.svelte Added ref bindable and onEscape callback for / shortcut
src/lib/components/tests/search-input.component.test.ts Tests for ref exposure and onEscape handling
src/lib/components/log-table.svelte Accepts selectedIndex prop and passes isSelected to children
src/lib/components/log-row.svelte Added isSelected prop with visual selection styling
src/lib/components/tests/log-row.component.test.ts Tests for isSelected visual styling
src/lib/components/log-card.svelte Added isSelected prop with visual selection styling
src/lib/components/tests/log-card.component.test.ts Tests for isSelected visual styling
specs/keyboard-shortcuts/*.md Comprehensive spec documentation (research, requirements, design, tasks)
.gitignore Added spec tracking files to gitignore

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- PR #10 created with full description
- Fixed E2E test failure (Enter key preventDefault issue)
- All CI checks passing
Use `isSelected || undefined` instead of ternary for cleaner code.
Addresses Copilot review feedback on PR #10.
All 28 acceptance criteria verified and passing.
Spec execution finished - 22/22 tasks complete.
@Divkix Divkix merged commit 8d38c98 into main Jan 16, 2026
10 checks passed
@Divkix Divkix deleted the feat/keyboard-shortcuts branch January 16, 2026 00:37
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