diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js
index f134ed21..a8678c72 100644
--- a/frontend/eslint.config.js
+++ b/frontend/eslint.config.js
@@ -221,6 +221,18 @@ export default tseslint.config(
// The canonical "FE control" row (CLAUDE.md): a short fixed set is a SegmentedToggle, a
// longer one or a user's own list a FilterSelect. Six native selects had drifted in; the
// 2026-09 debt burn replaced them, and this keeps the next one out.
+ // `enabled:` sees only the native attribute; a FocusSafeButton that holds the focus is
+ // disabled by aria-disabled, so a bare `enabled:hover:` lit it up on hover as if it worked.
+ {
+ selector: "Literal[value=/(^|\\s)enabled:hover:/]",
+ message:
+ "`enabled:hover:` misses a focused FocusSafeButton (aria-disabled): use `enabled:not-aria-disabled:hover:`.",
+ },
+ {
+ selector: "TemplateElement[value.raw=/(^|\\s)enabled:hover:/]",
+ message:
+ "`enabled:hover:` misses a focused FocusSafeButton (aria-disabled): use `enabled:not-aria-disabled:hover:`.",
+ },
{
selector: "JSXOpeningElement[name.name='select']",
message:
diff --git a/frontend/src/components/PersonalViewsModal.tsx b/frontend/src/components/PersonalViewsModal.tsx
index e87d6b1b..e0520207 100644
--- a/frontend/src/components/PersonalViewsModal.tsx
+++ b/frontend/src/components/PersonalViewsModal.tsx
@@ -240,7 +240,7 @@ export default function PersonalViewsModal() {
disabled={!nameChanged}
title={t("views.rename")}
aria-label={t("views.rename")}
- className="inline-flex shrink-0 items-center rounded-lg border border-border p-2 text-muted transition enabled:hover:border-accent enabled:hover:text-accent disabled:opacity-30"
+ className="inline-flex shrink-0 items-center rounded-lg border border-border p-2 text-muted transition enabled:not-aria-disabled:hover:border-accent enabled:not-aria-disabled:hover:text-accent disabled:opacity-30"
>
@@ -302,7 +302,7 @@ export default function PersonalViewsModal() {
title={
saveCurrentDisabled ? t("views.saveCurrentUpToDate") : t("views.saveCurrentHint")
}
- className="inline-flex items-center gap-1.5 rounded-lg border border-border px-3 py-1.5 text-sm text-muted transition enabled:hover:border-accent enabled:hover:text-accent disabled:opacity-40"
+ className="inline-flex items-center gap-1.5 rounded-lg border border-border px-3 py-1.5 text-sm text-muted transition enabled:not-aria-disabled:hover:border-accent enabled:not-aria-disabled:hover:text-accent disabled:opacity-40"
>
{t("views.saveCurrent")}
diff --git a/frontend/src/components/PlaylistLimitsHint.tsx b/frontend/src/components/PlaylistLimitsHint.tsx
index e9682ce3..64ca4cfb 100644
--- a/frontend/src/components/PlaylistLimitsHint.tsx
+++ b/frontend/src/components/PlaylistLimitsHint.tsx
@@ -21,7 +21,12 @@ export default function PlaylistLimitsHint() {
offset: 8,
});
return (
-
+ // The panel is placed off the whole row, not the "Why?" button: off the button it opened
+ // over the right third of the rail and covered the lists (measured on localdev).
+