fix(busy): gate the document-level shortcuts too

inert removes elements from focus and hit testing, but shortcuts are listened for on the document and never traverse an element. Ctrl+A still selected every entry under the overlay, Ctrl+K would open the palette behind it, j/k moved the cursor - and Delete ran a batch delete while a sync was writing to the same rows.
This commit is contained in:
r-zakarya
2026-08-03 00:04:56 +01:00
parent a36164174e
commit 5236aa8b98
2 changed files with 15 additions and 0 deletions
+3
View File
@@ -193,7 +193,10 @@ plus bas.
### B18 · clavier pendant une opération bloquante ✅
- **ACTION** : lancer un *Sync now* (ou un export) → pendant l'overlay, presser `Tab` plusieurs fois puis `Espace` / `Entrée`.
- **ATTENDU** : rien ne se passe. Aucun bouton derrière l'overlay ne prend le focus ni ne s'active. Tout redevient normal ensuite, focus compris.
- **ACTION 2 — les raccourcis** : pendant l'overlay, `Ctrl+A` (tout sélectionner), `Ctrl+K` (palette), `j`/`k` (curseur), **`Suppr`** (suppression par lot).
- **ATTENDU** : aucun effet. Le plus important est `Suppr` : supprimer des entrées pendant qu'un sync ou une rotation écrit sur les mêmes lignes est exactement ce que l'overlay doit empêcher.
- **Historique** : l'overlay ne bloque que la **souris** — il couvre la page. Le clavier passait au travers : `Tab` entrait dans les contrôles derrière et `Espace` les activait. Corrigé avec l'attribut natif `inert` (retire un sous-arbre du focus, du hit-testing et de l'arbre d'accessibilité), posé sur chaque enfant de `<body>` **sauf** l'overlay.
- **`inert` ne couvre pas les raccourcis** : ils sont écoutés sur le **document**, donc ils ne traversent aucun élément et rien dans l'arbre ne peut les arrêter. Il a fallu un garde `isBusy()` explicite sur chacun — même famille que les hotkeys Win32, qui arrivent eux de l'extérieur de la fenêtre.
- **Point de vigilance** : `hideBusy()` retire l'inertie **inconditionnellement**. Une opération qui échouerait sans le rétablir laisserait une application qui semble gelée.
- **`inert` ne suffit pas seul** : il retire les contrôles du focus, mais **pas** le défilement. `Espace`, `Page haut/bas`, `Début/Fin` et les flèches font défiler le document **sans aucun élément focalisé** — la page bougeait encore sous l'overlay. Ces touches sont donc avalées en phase de capture tant que l'opération tourne.
- **Vérifier aussi le défilement** : pendant l'overlay, `Espace` et `Page bas` ne doivent rien bouger ; après, le défilement clavier doit refonctionner.
+12
View File
@@ -2741,6 +2741,9 @@ function isPasswordAged(e) {
// Stays inert when any input/textarea has focus, when a modal is open,
// when an open slideover would steal Enter, or the user is mid-edit.
function handleCardCursorKey(e) {
// Document-level like the other shortcuts, so `inert` does not stop it:
// j/k would still move the cursor and scroll the grid under the overlay.
if (isBusy()) return;
if (e.ctrlKey || e.altKey || e.metaKey) return;
const tag = (e.target && e.target.tagName || '').toLowerCase();
if (tag === 'input' || tag === 'textarea' || tag === 'select') return;
@@ -7316,6 +7319,10 @@ async function init() {
// batch bar does, just via keyboard.
document.addEventListener('keydown', e => {
if (e.key !== 'Delete' && e.key !== 'Backspace') return;
// Destructive, and listened for on the document — `inert` does not
// reach it. Deleting entries while a sync or a rotation is mid-flight
// is the worst thing this overlay is supposed to prevent.
if (isBusy()) return;
if (e.ctrlKey || e.altKey || e.metaKey) return;
const tag = (e.target && e.target.tagName || '').toLowerCase();
if (tag === 'input' || tag === 'textarea' || tag === 'select') return;
@@ -8267,6 +8274,11 @@ async function init() {
// Command palette + bulk shortcuts
document.addEventListener('keydown', e => {
// Shortcuts are listened for on the DOCUMENT, so `inert` on the page
// does not stop them — it only removes elements from focus and hit
// testing. Ctrl+A still selected every entry under the busy overlay,
// and Ctrl+K would have opened the palette behind it.
if (isBusy()) return;
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
openPalette();