Skip to content

Ergonomic update with escape and space keys - #4707

Open
jphuguet wants to merge 2 commits into
LycheeOrg:masterfrom
jphuguet:chore/improve-some-escape-shortcuts
Open

Ergonomic update with escape and space keys#4707
jphuguet wants to merge 2 commits into
LycheeOrg:masterfrom
jphuguet:chore/improve-some-escape-shortcuts

Conversation

@jphuguet

@jphuguet jphuguet commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

escape close the modal, not the background page
escape allow to unselect
while deleteing many items, no action on space keypress othgerwise the number for items to be deleted is empty

Summary by CodeRabbit

  • New Features

    • Added a bilingual English/French keyboard shortcuts reference covering gallery, photo, face recognition, widget, and navigation shortcuts.
    • Keyboard shortcuts are disabled while dialogs are open.
    • Escape now handles active selections consistently across panels and navigates back where appropriate.
  • Bug Fixes

    • Improved focus behavior when opening delete confirmations.
    • Standardized shortcut handling across gallery views, statistics, and face recognition.

escape allow to unselect
while deleteing many items, no action on space keypress othgerwise the number for items to be deleted is empty
@jphuguet
jphuguet requested a review from a team as a code owner September 2, 2026 16:32
@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: f52a4bc6-8b7d-4967-aa12-2e1610c88c96

📥 Commits

Reviewing files that changed from the base of the PR and between d72c58d and 5a7abb2.

📒 Files selected for processing (1)
  • docs/specs/3-reference/keyboard-shortcuts.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/specs/3-reference/keyboard-shortcuts.md

Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

The PR adds shared modal-aware panel shortcut handling, applies it across gallery and face-recognition views, adjusts delete-dialog focus behavior, changes Escape selection handling, and documents shortcuts in English and French.

Changes

Shortcut coordination

Layer / File(s) Summary
Shared shortcut gating
resources/js/stores/ModalsState.ts, resources/js/v8/composables/usePanelShortcuts.ts, resources/js/v8/components/forms/gallery-dialogs/DeleteDialog.vue
The store exposes modal state. The composable disables panel shortcuts during modals and clears selection on Escape when applicable. The delete dialog focuses its content element on open.
Gallery shortcut adoption
resources/js/v8/components/gallery/photoModule/FaceOverlay.vue, resources/js/v8/components/headers/*, resources/js/v8/views/gallery-panels/*
Gallery components use definePanelShortcuts. Album and tag panels navigate back directly when Escape is pressed with an active selection.
Specialized view shortcut adoption
resources/js/v8/views/Statistics.vue, resources/js/v8/views/face-recog/*
Statistics and face-recognition views use the shared shortcut composable.
Bilingual shortcut reference
docs/specs/3-reference/keyboard-shortcuts.md
The reference lists global, gallery, photo, face-recognition, mouse, widget, and focus-related shortcuts in English and French.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: 🟡 Moderate · up to 5a7ab

Escape handling can still trigger background actions while dialogs are open and can navigate away instead of clearing selections in the person view, creating unintended user actions. The PR should not be considered merge-ready until these bounded shortcut conflicts are fixed or explicitly accepted by the owner.

Poem

A rabbit taps keys in a neat little row
The modal sleeps, so the shortcuts slow
Escape clears paths when selections remain
Dialogs focus softly, without strain
English and French charts now glow
Hop through the panels, steady and low

🚥 Pre-merge checks | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 2 files. (1 skipped: 1 … Write docstrings for the functions missing them to satisfy the coverage threshold.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 2 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

🧹 Nitpick comments (1)
resources/js/v8/components/forms/gallery-dialogs/DeleteDialog.vue (1)

2-2: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Update the Vue component-library guideline for v8.

The v8 entry point registers @nuxt/ui/vue-plugin, and v8 components use UModal. Keep UModal here and document the v8 exception to the PrimeVue guideline.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 97bff0b8-8df0-4f04-a5e3-96b01ba36ae2

📥 Commits

Reviewing files that changed from the base of the PR and between 4a8bbe5 and d72c58d.

📒 Files selected for processing (19)
  • docs/specs/3-reference/keyboard-shortcuts.md
  • resources/js/stores/ModalsState.ts
  • resources/js/v8/components/forms/gallery-dialogs/DeleteDialog.vue
  • resources/js/v8/components/gallery/photoModule/FaceOverlay.vue
  • resources/js/v8/components/headers/AlbumsHeader.vue
  • resources/js/v8/components/headers/TimelineHeader.vue
  • resources/js/v8/composables/usePanelShortcuts.ts
  • resources/js/v8/views/Statistics.vue
  • resources/js/v8/views/face-recog/FaceClusters.vue
  • resources/js/v8/views/face-recog/PersonDetail.vue
  • resources/js/v8/views/gallery-panels/Album.vue
  • resources/js/v8/views/gallery-panels/Albums.vue
  • resources/js/v8/views/gallery-panels/Favourites.vue
  • resources/js/v8/views/gallery-panels/Flow.vue
  • resources/js/v8/views/gallery-panels/Frame.vue
  • resources/js/v8/views/gallery-panels/Map.vue
  • resources/js/v8/views/gallery-panels/Search.vue
  • resources/js/v8/views/gallery-panels/Tag.vue
  • resources/js/v8/views/gallery-panels/Timeline.vue

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread docs/specs/3-reference/keyboard-shortcuts.md Outdated
Comment thread docs/specs/3-reference/keyboard-shortcuts.md
Comment on lines +87 to +113
is_modal_open(state): boolean {
return (
state.is_login_open ||
state.is_webauthn_open ||
state.is_metrics_open ||
state.is_upload_visible ||
state.is_camera_capture_visible ||
state.is_create_album_visible ||
state.is_create_tag_album_visible ||
state.is_create_person_album_visible ||
state.is_album_edit_open ||
state.is_photo_edit_open ||
state.is_rename_visible ||
state.is_move_visible ||
state.is_delete_visible ||
state.is_merge_album_visible ||
state.is_share_album_visible ||
state.is_embed_code_visible ||
state.is_import_from_link_open ||
state.is_import_from_dropbox_open ||
state.is_import_from_server_open ||
state.is_tag_visible ||
state.is_license_visible ||
state.is_copy_visible ||
state.is_apply_renamer_visible ||
state.is_watermark_confirm_visible ||
state.is_keybindings_help_open

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

printf '%s\n' '--- scoped repository guidance ---'
find /tmp/coderabbit-repo-knowledge/lycheeorg-lychee-cc271fdf -maxdepth 2 -type f -name '*.md' -print
printf '%s\n' '--- frontend conventions ---'
sed -n '1,240p' docs/specs/3-reference/coding-conventions.md
printf '%s\n' '--- target symbols and files ---'
rg -n -C 5 'is_modal_open|definePanelShortcuts|isAssignmentOpen|is_download_album_visible|Escape|KeyP|key.*p|modal' \
  resources/js/stores/ModalsState.ts \
  resources/js/v8/components/gallery/photoModule/FaceOverlay.vue \
  resources/js/v8/views/gallery-panels/Albums.vue \
  resources/js/v8 \
  resources/js/stores

Repository: LycheeOrg/Lychee

Length of output: 50373


🏁 Script executed:

printf '%s\n' '--- relevant repository guidance files ---'
find /tmp/coderabbit-repo-knowledge/lycheeorg-lychee-cc271fdf -maxdepth 2 -type f -name '*.md' \
  | grep -Ei 'frontend|vue|modal|gallery|shortcut|coding|learn'
printf '%s\n' '--- ModalsState ---'
cat -n resources/js/stores/ModalsState.ts | sed -n '1,150p'
printf '%s\n' '--- panel shortcut composable ---'
fd -i 'usePanelShortcuts' resources/js
for f in $(fd -i 'usePanelShortcuts' resources/js); do
  echo "### $f"
  cat -n "$f"
done
printf '%s\n' '--- FaceOverlay relevant section ---'
cat -n resources/js/v8/components/gallery/photoModule/FaceOverlay.vue | sed -n '1,150p'
printf '%s\n' '--- Albums shortcut and state sections ---'
cat -n resources/js/v8/views/gallery-panels/Albums.vue | sed -n '250,375p'
printf '%s\n' '--- modal implementations ---'
fd -i 'DownloadAlbum|FaceAssignment|Assignment' resources/js/v8

Repository: LycheeOrg/Lychee

Length of output: 20193


🏁 Script executed:

printf '%s\n' '--- remaining is_modal_open consumers and shortcut registrations ---'
rg -n -C 4 'is_modal_open|definePanelShortcuts|defineShortcuts' resources/js --glob '*.ts' --glob '*.vue'
printf '%s\n' '--- Face assignment modal ---'
cat -n resources/js/v8/components/modals/faceRecog/FaceAssignmentModal.vue | sed -n '1,240p'
printf '%s\n' '--- Download album modal ---'
cat -n resources/js/v8/components/modals/DownloadAlbum.vue | sed -n '1,240p'
printf '%s\n' '--- frontend dependency contract ---'
rg -n -C 3 '"`@nuxt/ui`"|nuxt/ui|defineShortcuts' package.json package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null || true

Repository: LycheeOrg/Lychee

Length of output: 43048


🌐 Web query:

Nuxt UI 4.11.0 defineShortcuts reactive computed config empty object documentation

💡 Result:

In Nuxt UI 4.11.0, the defineShortcuts composable accepts a MaybeRef configuration object, allowing you to pass a ref or a computed property to update shortcuts reactively [1][2]. When you provide a reactive source (like a computed property) to defineShortcuts, the composable tracks changes to that object [1][2]. If you want to disable or remove shortcuts dynamically, you can set the corresponding key in your configuration object to false, null, or undefined [1][3][4]. Regarding your query about an "empty object": 1. If the reactive source resolves to an empty object {}, all existing keyboard shortcuts previously defined by that specific defineShortcuts call will effectively be disabled or ignored because the configuration no longer maps any keys to handlers [1][5][2]. 2. The internal implementation uses a computed property that iterates over Object.entries(toValue(config)) [5]. If the object is empty, no shortcut logic is executed or registered for that reactive instance [5]. Best Practices: - Reactive Updates: To swap shortcuts, use a computed property that returns a full configuration object based on your application state, rather than mutating an object in place [2]. - Disabling: To conditionally enable or disable individual shortcuts, explicitly set them to false or use the whenever option (if available in your specific version/variant) to add conditional constraints to the handler [1][6][7]. - Cleanup: defineShortcuts returns a function that removes the registered event listeners [1][2]. If you are replacing shortcuts entirely, you may want to ensure you are not creating redundant listeners if the composable is called multiple times rather than updated reactively [7].

Citations:


🏁 Script executed:

printf '%s\n' '--- scoped learnings for frontend and modals ---'
sed -n '1,220p' /tmp/coderabbit-repo-knowledge/lycheeorg-lychee-cc271fdf/learnings/resources-js-v8.md
sed -n '1,220p' /tmp/coderabbit-repo-knowledge/lycheeorg-lychee-cc271fdf/learnings/resources-js.md
printf '%s\n' '--- exact diff summary for the reviewed paths ---'
git diff --stat -- resources/js/stores/ModalsState.ts resources/js/v8/components/gallery/photoModule/FaceOverlay.vue resources/js/v8/views/gallery-panels/Albums.vue
git diff -- resources/js/stores/ModalsState.ts resources/js/v8/components/gallery/photoModule/FaceOverlay.vue resources/js/v8/views/gallery-panels/Albums.vue | sed -n '1,260p'

Repository: LycheeOrg/Lychee

Length of output: 1097


Track locally owned dialogs in the shared modal state.

definePanelShortcuts disables panel shortcuts only when togglableStore.is_modal_open is true. The local isAssignmentOpen and is_download_album_visible refs are not included, so panel shortcuts remain active while either UModal is open. In FaceOverlay.vue, P can also unmount the assignment modal through its parent v-if.

Bind both dialog states to the shared modal state. Add regression tests for Escape in both dialogs and P in the face-assignment dialog.

📍 Affects 3 files
  • resources/js/stores/ModalsState.ts#L87-L113 (this comment)
  • resources/js/v8/components/gallery/photoModule/FaceOverlay.vue#L85-L85
  • resources/js/v8/views/gallery-panels/Albums.vue#L349-L349


// Keybindings
defineShortcuts({
definePanelShortcuts({

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Clear the local batch selection before navigating away.

definePanelShortcuts detects only togglableStore.selectedPhotosIds and togglableStore.selectedAlbumsIds. PersonDetail.vue stores its batch selection in local selectedPhotoIds, so Escape does not use the shared selection-clearing path. When batch mode has selected photos and no photo is open, Escape still executes the fallback navigation to people instead of clearing the selection. Handle the local selection before the navigation branch, or extend the composable to accept a local selection and clear callback.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@jphuguet that is quite an annoying one :/

@ildyria ildyria left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That is 99% there.
Just the person selection issue.

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