Skip to content

fix(search): navigation clavier dans les résultats Cmd+K#259

Merged
InstaZDLL merged 4 commits into
mainfrom
fix/global-search-keyboard-nav
May 5, 2026
Merged

fix(search): navigation clavier dans les résultats Cmd+K#259
InstaZDLL merged 4 commits into
mainfrom
fix/global-search-keyboard-nav

Conversation

@InstaZDLL
Copy link
Copy Markdown
Owner

@InstaZDLL InstaZDLL commented May 4, 2026

Summary

La palette Cmd+K affichait les résultats mais aucun moyen clavier de les sélectionner — il fallait lâcher le clavier et cliquer. Ce PR ajoute :

  • ↑ / ↓ pour naviguer dans les résultats
  • Enter pour ouvrir le résultat sélectionné
  • Hover souris synchronise le curseur clavier (pas de désaccord visuel)
  • Le résultat sélectionné scroll automatiquement dans la vue si la liste déborde
  • Reset à la première position dès que la requête change

Implémentation React Compiler-friendly : pas de setState dans useEffect, calcul activeIndex dérivé pour clamp si la data change pendant la nav.

Test plan

  • Cmd+K → tape "réun" → ↓ ↓ ↑ Enter → ouvre le bon résultat
  • Hover souris sur un résultat → la sélection clavier suit
  • Liste très longue → ↓ scroll
  • Modifie la requête → curseur revient en haut
  • Esc ferme toujours

Summary by CodeRabbit

  • Nouvelles Fonctionnalités
    • Navigation clavier pour la recherche globale : flèches haut/bas pour parcourir, Entrée pour sélectionner.
  • Accessibilité
    • Amélioration des attributs ARIA et du balisage (listbox/option) avec IDs stables pour meilleure prise en charge des lecteurs d’écran.
  • Corrections
    • Réinitialisation de la sélection lors de la sélection d’un résultat, du changement/effacement de la requête et de la fermeture via Échap.
  • Comportement
    • Défilement automatique vers l’élément actif ; survol synchronise la surbrillance.

The Cmd+K palette had no keyboard navigation: users had to leave the
keyboard and click to pick a result. Add Arrow Up/Down to move the
selection and Enter to open it. The hovered row syncs with the
keyboard cursor and the selected row scrolls into view as needed.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4e84f8ad-ea67-41c0-99f0-d5151525fb21

📥 Commits

Reviewing files that changed from the base of the PR and between e2ddff3 and aa397e1.

📒 Files selected for processing (1)
  • apps/web/src/components/global-search.tsx

📝 Walkthrough

Walkthrough

GlobalSearch suit l’index de sélection, borne l’index selon la taille des résultats, réinitialise l’index à 0 sur sélection/clear/Escape, permet navigation clavier (ArrowUp/ArrowDown/Enter), fait défiler l’option active en vue et remplace le rendu par des rôles ARIA listbox/option avec IDs stables et refs indexées. (≤50 mots)

Changes

Navigation et Surlignage des Résultats

Layer / File(s) Summary
Imports & Types
apps/web/src/components/global-search.tsx
Ajout du typedef type KeyboardEvent as ReactKeyboardEvent.
Constantes & Helpers
apps/web/src/components/global-search.tsx
Ajout de getSuggestionId(index) et LISTBOX_ID pour IDs ARIA stables.
État & Références
apps/web/src/components/global-search.tsx
Ajout de l’état selectedIndex (initial 0) et de resultRefs (array-ref) pour référencer chaque bouton option.
Active Index / Bornes
apps/web/src/components/global-search.tsx
Calcul de activeIndex comme version bornée de selectedIndex en fonction de results.length.
Effet de scroll
apps/web/src/components/global-search.tsx
useEffect qui scrollIntoView sur resultRefs.current[activeIndex] quand activeIndex change.
Handlers de sélection & query
apps/web/src/components/global-search.tsx
handleSelect remet selectedIndex à 0; handleQueryChange remplace setQuery et remet selectedIndex à 0.
Navigation clavier
apps/web/src/components/global-search.tsx
handleInputKeyDown gère ArrowDown/ArrowUp (mise à jour bornée de selectedIndex) et Enter (sélectionne results[activeIndex]).
Événements globaux
apps/web/src/components/global-search.tsx
Le handler global keydown (Escape) ferme la modal, vide la query et remet selectedIndex à 0.
Rendu & ARIA
apps/web/src/components/global-search.tsx
Le input utilise aria-controls/aria-activedescendant; la liste a role="listbox" avec id=LISTBOX_ID; chaque résultat rendu en role="option" a id stable, aria-selected, onMouseEnter met à jour selectedIndex, et les boutons sont stockés dans resultRefs.
Bouton clear
apps/web/src/components/global-search.tsx
Le clear button appelle handleQueryChange("") pour vider la query et réinitialiser le highlight.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

Flèches montent, flèches descendent, l’option se place,
L’index se remet à zéro quand la recherche s’efface,
Un scroll discret ramène la ligne choisie,
ARIA et refs en rang — la sélection bénie. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Le titre décrit précisément le changement principal : ajout de la navigation au clavier dans les résultats de la palette Cmd+K, ce qui correspond exactement au contenu du PR.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/global-search-keyboard-nav

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

@github-actions github-actions Bot added scope: frontend apps/web type: fix Bug fix size: m 50-200 lines labels May 4, 2026
@InstaZDLL InstaZDLL self-assigned this May 4, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@apps/web/src/components/global-search.tsx`:
- Around line 175-179: The input's keyboard selection isn't exposed to screen
readers; wire up ARIA so SRs announce the active item: give the combobox
container role="combobox" (or use aria-haspopup/listbox pattern), add
aria-expanded and aria-owns/aria-controls linking the Input to the suggestions
list, add aria-activedescendant on the Input pointing to the id of the currently
highlighted suggestion (use the existing activeIndex/activeSuggestionIndex
state), and ensure each suggestion element rendered in the list has a stable id
(e.g., getSuggestionId(index)), role="option", and aria-selected={index ===
activeIndex}; update these attributes in the handlers (handleQueryChange,
handleInputKeyDown) so activedescendant and aria-selected reflect the
highlighted item.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4aa53784-91be-4051-b987-f7b3dd7ff161

📥 Commits

Reviewing files that changed from the base of the PR and between 5e58509 and ad2e6e7.

📒 Files selected for processing (1)
  • apps/web/src/components/global-search.tsx

Comment thread apps/web/src/components/global-search.tsx
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 1 file(s) based on 1 unresolved review comment.

Files modified:

  • apps/web/src/components/global-search.tsx

Commit: 9f155896e2af7053cd53740900d63fa03bde4e80

The changes have been pushed to the fix/global-search-keyboard-nav branch.

Time taken: 4m 28s

Fixed 1 file(s) based on 1 unresolved review comment.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@apps/web/src/components/global-search.tsx`:
- Around line 179-190: The combobox currently always sets aria-owns and
aria-controls to LISTBOX_ID even when the listbox isn't rendered (e.g.
query.length < 2 or no results), breaking the ARIA relationship; update the JSX
around the div with role="combobox" / the Input props (where LISTBOX_ID, isOpen,
query and activeDescendantId are used) to only include aria-owns and
aria-controls when the listbox will actually be rendered (e.g. isOpen &&
query.length >= 2 && resultsExist), otherwise omit those attributes (or set them
to undefined/null) so the ARIA relationship is only present when the listbox
exists. Ensure activeDescendantId handling remains consistent with the same
render condition.
- Around line 125-140: The ArrowUp/ArrowDown handlers in handleInputKeyDown move
relative to the raw selectedIndex value, which can be out-of-bounds if results
changed; change them to step from the clamp-safe activeIndex instead. Replace
setSelectedIndex((i) => Math.min(i + 1, results.length - 1)) with
setSelectedIndex(() => Math.min(activeIndex + 1, results.length - 1)) and
similarly replace setSelectedIndex((i) => Math.max(i - 1, 0)) with
setSelectedIndex(() => Math.max(activeIndex - 1, 0)), keeping
handleInputKeyDown, results, activeIndex and handleSelect references.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a6e76a01-3ec5-4997-bdef-af3f17277714

📥 Commits

Reviewing files that changed from the base of the PR and between ad2e6e7 and 9f15589.

📒 Files selected for processing (1)
  • apps/web/src/components/global-search.tsx

Comment thread apps/web/src/components/global-search.tsx
Comment thread apps/web/src/components/global-search.tsx Outdated
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 1 file(s) based on 2 unresolved review comments.

Files modified:

  • apps/web/src/components/global-search.tsx

Commit: e2ddff3d5d9b6d01567be0b3685b6a4e9ecdf47f

The changes have been pushed to the fix/global-search-keyboard-nav branch.

Time taken: 2m 13s

Fixed 1 file(s) based on 2 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
apps/web/src/components/global-search.tsx (1)

180-180: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

aria-expanded devrait refléter la visibilité réelle de la listbox.

Actuellement aria-expanded={isOpen} est true même quand la listbox n'est pas affichée (requête trop courte ou zéro résultat). Les lecteurs d'écran annoncent "développé" alors qu'il n'y a rien à sélectionner.

🔧 Correctif proposé
-        <div className="relative" role="combobox" aria-expanded={isOpen} aria-haspopup="listbox" aria-owns={shouldRenderListbox ? LISTBOX_ID : undefined}>
+        <div className="relative" role="combobox" aria-expanded={shouldRenderListbox} aria-haspopup="listbox" aria-owns={shouldRenderListbox ? LISTBOX_ID : undefined}>
🤖 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 `@apps/web/src/components/global-search.tsx` at line 180, Le composant expose
currently aria-expanded={isOpen} même quand la listbox n'est pas rendue, ce qui
fait annoncer "développé" sans éléments; changez l'attribut aria-expanded sur
l'élément role="combobox" pour refléter la visibilité réelle de la listbox en
utilisant shouldRenderListbox (ou isOpen && shouldRenderListbox) au lieu de
isOpen seul; repérez la div avec role="combobox" qui utilise isOpen, LISTBOX_ID
et shouldRenderListbox et mettez à jour aria-expanded pour n'être true que quand
la listbox est effectivement rendue.
🤖 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.

Duplicate comments:
In `@apps/web/src/components/global-search.tsx`:
- Line 180: Le composant expose currently aria-expanded={isOpen} même quand la
listbox n'est pas rendue, ce qui fait annoncer "développé" sans éléments;
changez l'attribut aria-expanded sur l'élément role="combobox" pour refléter la
visibilité réelle de la listbox en utilisant shouldRenderListbox (ou isOpen &&
shouldRenderListbox) au lieu de isOpen seul; repérez la div avec role="combobox"
qui utilise isOpen, LISTBOX_ID et shouldRenderListbox et mettez à jour
aria-expanded pour n'être true que quand la listbox est effectivement rendue.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3d4b10a5-8c76-450e-88d4-966b9e61b682

📥 Commits

Reviewing files that changed from the base of the PR and between 9f15589 and e2ddff3.

📒 Files selected for processing (1)
  • apps/web/src/components/global-search.tsx

@InstaZDLL InstaZDLL merged commit 3177829 into main May 5, 2026
13 checks passed
@InstaZDLL InstaZDLL deleted the fix/global-search-keyboard-nav branch May 5, 2026 07:29
@github-actions github-actions Bot locked and limited conversation to collaborators May 5, 2026
Repository owner unlocked this conversation May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: frontend apps/web size: m 50-200 lines type: fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant