Skip to content

Ergonomic update with escape and space keys - #4707

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

Ergonomic update with escape and space keys#4707
jphuguet wants to merge 3 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: 61b9ec41-6dfe-4351-afec-ea02e211fd59

📥 Commits

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

📒 Files selected for processing (2)
  • resources/js/v8/composables/usePanelShortcuts.ts
  • resources/js/v8/views/face-recog/PersonDetail.vue
🚧 Files skipped from review as they are similar to previous changes (2)
  • resources/js/v8/views/face-recog/PersonDetail.vue
  • resources/js/v8/composables/usePanelShortcuts.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 7 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 supports local selections and shared selection clearing. 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. PersonDetail clears local batch selection before fallback navigation.
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

Poem

A rabbit checks each shortcut key
Modal state keeps actions orderly
Local selections clear on cue
Dialog focus starts anew
English and French charts agree
Hops complete the review tree

🚥 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

@coderabbitai coderabbitai Bot Sep 2, 2026

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

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.

Add is_download_album_visible to the useTogglablesStateStore and reuse it instead of the refs:

const is_download_album_visible = ref(false);
const {
	is_share_album_visible,
	is_download_album_visible, // < HERE
	toggleDelete,
	toggleMergeAlbum,
	toggleMove,
	toggleRename,
	toggleShareAlbum,
	toggleEmbedCode,
	toggleTag,
	toggleLicense,
	toggleCopy,
	toggleUpload,
	toggleApplyRenamer,
	is_apply_renamer_visible,
	toggleWatermarkConfirm,
	is_watermark_confirm_visible,
} = useGalleryModals(togglableStore);

We could also do the same with isAssignmentOpen that would be more clean.

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.

@ildyria, agreed. Use useTogglablesStateStore for both dialog visibility states.

This change will make is_modal_open cover the download dialog and the face-assignment dialog. It will also remove the component-local visibility refs.

  • Add is_download_album_visible and a face-assignment visibility field to resources/js/stores/ModalsState.ts.
  • Include both fields in is_modal_open.
  • Replace the local refs in the affected components with store-backed state.
  • Add regression coverage for shortcut suppression while each dialog is open.

✏️ Learnings added
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 4707
File: resources/js/stores/ModalsState.ts:87-113
Timestamp: 2026-09-03T21:04:39.467Z
Learning: In the Lychee Vue frontend, dialog visibility that must disable `definePanelShortcuts` should use fields from `useTogglablesStateStore` and be included in its `is_modal_open` getter. Component-local dialog refs do not participate in shared shortcut gating.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


  • 📌 Create a pull request with these changes

You are interacting with an AI system.

Comment thread resources/js/v8/views/face-recog/PersonDetail.vue Outdated

@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