fix(ui): actionable toasts linger, and a dead hotkey shows in the sidebar
Every toast auto-dismissed after 2.8 s — fine for a success receipt, too short to read AND act on an error. Errors and warnings now stay 10 s and can be clicked away; success is unchanged. The hotkey-conflict warning also needed a trace outside the toast: the ⚠ in Settings only helps someone who opens the panel. A dot on the sidebar Settings entry (plus a tooltip) is now the cue in the main UI that a combo is dead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -310,7 +310,7 @@ l'éditeur slideover : créer + éditer une entrée, ajouter/retirer des tags,
|
|||||||
|
|
||||||
### H7 · combo déjà pris par une autre application ✅
|
### H7 · combo déjà pris par une autre application ✅
|
||||||
- **ACTION** : assigner à un hotkey un combo qu'une autre app tient déjà en global (ex. `Ctrl+Shift+Q` si Quick Assist / un lanceur l'occupe ; sinon, tester en assignant le même combo dans deux apps).
|
- **ACTION** : assigner à un hotkey un combo qu'une autre app tient déjà en global (ex. `Ctrl+Shift+Q` si Quick Assist / un lanceur l'occupe ; sinon, tester en assignant le même combo dans deux apps).
|
||||||
- **ATTENDU** : toast **nommant le combo refusé** — « Already used by another app: 2FA code — pick another combo » — **et** un ⚠ ambre persistant sur cette ligne de Settings (visible longtemps après le toast). Les **autres** hotkeys restent fonctionnels (enregistrement best-effort, pas de tout-ou-rien).
|
- **ATTENDU** : toast **nommant le combo refusé** — « Already used by another app: 2FA code — pick another combo » — **et** un ⚠ ambre persistant sur cette ligne de Settings, **et** une pastille ambre sur l'entrée *Settings* de la sidebar (seul indice hors du panneau). Le toast d'avertissement reste ~10 s et se ferme au clic. Les **autres** hotkeys restent fonctionnels (enregistrement best-effort, pas de tout-ou-rien).
|
||||||
- **ACTION 2** : fermer l'app concurrente → *Reset to defaults* (ou re-capturer le combo) → le hotkey redevient actif **et le ⚠ disparaît**.
|
- **ACTION 2** : fermer l'app concurrente → *Reset to defaults* (ou re-capturer le combo) → le hotkey redevient actif **et le ⚠ disparaît**.
|
||||||
- **Piège de test** : une app qui capture via un *hook* bas niveau (et non `RegisterHotKey`) avale la frappe sans faire échouer notre enregistrement → hotkey inerte **sans** ⚠. Utiliser AutoHotkey (`^+t::MsgBox`) ou Lightshot pour un vrai conflit.
|
- **Piège de test** : une app qui capture via un *hook* bas niveau (et non `RegisterHotKey`) avale la frappe sans faire échouer notre enregistrement → hotkey inerte **sans** ⚠. Utiliser AutoHotkey (`^+t::MsgBox`) ou Lightshot pour un vrai conflit.
|
||||||
- **Historique** : le message disait « one or both hotkeys » (faux depuis qu'il y en a 3) sans dire lequel ; et un échec sur **Quick search** ou **New entry** n'était écrit que dans le panel debug — invisible en prod, le hotkey semblait juste cassé.
|
- **Historique** : le message disait « one or both hotkeys » (faux depuis qu'il y en a 3) sans dire lequel ; et un échec sur **Quick search** ou **New entry** n'était écrit que dans le panel debug — invisible en prod, le hotkey semblait juste cassé.
|
||||||
|
|||||||
@@ -2551,6 +2551,19 @@ body[data-editor-position="center"]:has(#settingsPanel.is-open)::before {
|
|||||||
min-width: 240px;
|
min-width: 240px;
|
||||||
animation: toastIn var(--t-base);
|
animation: toastIn var(--t-base);
|
||||||
}
|
}
|
||||||
|
/* Errors / warnings linger — let the user clear them instead of waiting. */
|
||||||
|
.toast.is-dismissable { cursor: pointer; }
|
||||||
|
/* Settings entry point when a hotkey is dead: a dot the user meets in the
|
||||||
|
normal UI, not only inside the panel they may never open. */
|
||||||
|
#settingsBtn.has-alert { position: relative; }
|
||||||
|
#settingsBtn.has-alert::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 6px; right: 8px;
|
||||||
|
width: 7px; height: 7px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--warning, #e0a800);
|
||||||
|
}
|
||||||
.toast.is-success { border-left-color: var(--success); }
|
.toast.is-success { border-left-color: var(--success); }
|
||||||
.toast.is-error { border-left-color: var(--danger); }
|
.toast.is-error { border-left-color: var(--danger); }
|
||||||
.toast.is-warning { border-left-color: var(--warning); }
|
.toast.is-warning { border-left-color: var(--warning); }
|
||||||
|
|||||||
@@ -36,6 +36,17 @@ function renderHotkeyConflicts() {
|
|||||||
'the hotkey does nothing. Pick another one.';
|
'the hotkey does nothing. Pick another one.';
|
||||||
else btn.removeAttribute('title');
|
else btn.removeAttribute('title');
|
||||||
}
|
}
|
||||||
|
// Dot on the Settings button: the ⚠ inside the panel only helps someone who
|
||||||
|
// opens it, and the toast may well have been missed. This is the only cue
|
||||||
|
// left in the main UI that a hotkey is silently dead.
|
||||||
|
const gear = document.getElementById('settingsBtn');
|
||||||
|
if (gear) {
|
||||||
|
gear.classList.toggle('has-alert', bad.size > 0);
|
||||||
|
if (bad.size > 0)
|
||||||
|
gear.title = 'A hotkey combo is already used by another app — see Autofill settings';
|
||||||
|
else if (gear.title.startsWith('A hotkey combo'))
|
||||||
|
gear.title = 'Settings';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format a combo for human display: "Ctrl+Shift+L".
|
// Format a combo for human display: "Ctrl+Shift+L".
|
||||||
|
|||||||
@@ -1016,7 +1016,16 @@ function toast(msg, type) {
|
|||||||
t.className = 'toast is-' + type;
|
t.className = 'toast is-' + type;
|
||||||
t.textContent = msg;
|
t.textContent = msg;
|
||||||
container.appendChild(t);
|
container.appendChild(t);
|
||||||
setTimeout(() => t.remove(), 2800);
|
// A success toast is a receipt — 2.8 s is plenty. An error or warning is
|
||||||
|
// something the user has to READ and usually act on, and 2.8 s isn't
|
||||||
|
// enough for either, so those linger and can be clicked away early.
|
||||||
|
const actionable = (type === 'error' || type === 'warning');
|
||||||
|
if (actionable) {
|
||||||
|
t.classList.add('is-dismissable');
|
||||||
|
t.title = 'Click to dismiss';
|
||||||
|
t.addEventListener('click', () => t.remove());
|
||||||
|
}
|
||||||
|
setTimeout(() => t.remove(), actionable ? 10000 : 2800);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user