fix(settings): tile-padding parity, passkey breakpoint, scroll-into-view, F17 threshold toggle

Five small polish items from the v1.4.33 audit, plus the
maintainer's scroll-into-view item:

* Export-section card padding promoted from `p-5` to `p-6` so the
  Export grid rhythm matches every other Settings card.
* Passkey list flips from a desktop table to a mobile card list at
  `lg` instead of `md`. iPad portrait (768 px) used to sit on the
  `md` inflection and flip-flop between the two layouts on
  rotation; the card layout reads fine on iPad portrait, the table
  needs ~620 px of column width to show the destructive delete
  action without horizontal scroll.
* `<SettingsShell>` mobile chip strip now scrolls its active chip
  into view on route change (`block: "nearest", inline: "center",
  behavior: "smooth"`). On a 393 CSS px viewport the 11-chip strip
  pushes the rightmost four chips below the fold; without the
  scroll-into-view, tapping one of those chips left the strip
  pinned to the leftmost chip on the new page, which read as a
  broken nav. Smooth scroll honours the user's
  `prefers-reduced-motion` via the browser's own auto-detection.
* F17 — `thresholds-editor-section` toggle label anchors on the
  *action* it performs ("Eigene Werte" / "Custom range") instead
  of mirroring the current state ("Auto" / "Überschrieben"). The
  old label read as "Auto is off, why are there no inputs?" when
  the toggle was off and inputs were hidden; the new label
  unambiguously announces "flip me to enter a custom range". The
  `thresholds.sourceOverride` badge still surfaces when the
  override is the active source. Adds
  `thresholds.overrideToggleLabel`.

Locale parity across de, en, es, fr, it, pl.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-16 17:35:07 +02:00
1 parent a8a5887768
commit 73043d42d1
10 files changed
+57 -10

No files matched your search

+1
View File
@@ -1321,6 +1321,7 @@
"defaultLabel": "Default",
"autoModeLabel": "Auto",
"overrideModeLabel": "Überschrieben",
"overrideToggleLabel": "Eigene Werte",
"resetAction": "Auf Default zurücksetzen",
"resetAllAction": "Alle auf Default zurücksetzen",
"minLabel": "Min",
+1
View File
@@ -1321,6 +1321,7 @@
"defaultLabel": "Default",
"autoModeLabel": "Auto",
"overrideModeLabel": "Override",
"overrideToggleLabel": "Custom range",
"resetAction": "Reset to default",
"resetAllAction": "Reset all to default",
"minLabel": "Min",
+1
View File
@@ -1321,6 +1321,7 @@
"defaultLabel": "Default",
"autoModeLabel": "Auto",
"overrideModeLabel": "Override",
"overrideToggleLabel": "Custom range",
"resetAction": "Reset to default",
"resetAllAction": "Reset all to default",
"minLabel": "Mín",
+1
View File
@@ -1321,6 +1321,7 @@
"defaultLabel": "Default",
"autoModeLabel": "Auto",
"overrideModeLabel": "Override",
"overrideToggleLabel": "Custom range",
"resetAction": "Reset to default",
"resetAllAction": "Reset all to default",
"minLabel": "Min",
+1
View File
@@ -1321,6 +1321,7 @@
"defaultLabel": "Default",
"autoModeLabel": "Auto",
"overrideModeLabel": "Override",
"overrideToggleLabel": "Custom range",
"resetAction": "Reset to default",
"resetAllAction": "Reset all to default",
"minLabel": "Min",
+1
View File
@@ -1321,6 +1321,7 @@
"defaultLabel": "Default",
"autoModeLabel": "Auto",
"overrideModeLabel": "Override",
"overrideToggleLabel": "Custom range",
"resetAction": "Reset to default",
"resetAllAction": "Reset all to default",
"minLabel": "Min",
+15 -6
View File
@@ -740,7 +740,13 @@ function PasskeyListSection({ isAuthenticated }: { isAuthenticated: boolean }) {
table only at `≥ md`, and at `< md` paint a card-list where
every passkey's name, device type, backup status, created
date, and delete action are all visible without scrolling. */}
<div className="border-border mt-3 hidden overflow-x-auto rounded-lg border md:block">
{/* v1.4.33 — desktop table flips to card list at `lg` instead
of `md` so iPad portrait (768 px = exactly the `md`
inflection) lands on the card layout. The table needs
~620 px of column width to read; below that it scrolls
horizontally and the destructive delete column is the one
that disappears. */}
<div className="border-border mt-3 hidden overflow-x-auto rounded-lg border lg:block">
<table className="w-full text-sm">
<thead>
<tr className="bg-muted/40 text-muted-foreground border-b text-xs">
@@ -824,12 +830,15 @@ function PasskeyListSection({ isAuthenticated }: { isAuthenticated: boolean }) {
</table>
</div>
{/* Mobile card list — < md only. Each passkey gets its own
card so the delete action stays visible and tap-targetable
(44px) without the user having to horizontally scroll
through a wide table. */}
{/* Mobile + tablet card list — < lg only. Each passkey gets
its own card so the delete action stays visible and
tap-targetable (44 px) without the user having to
horizontally scroll through a wide table. v1.4.33 bumped
the breakpoint from `md` to `lg` so iPad portrait (768 px)
stays on the card layout instead of flipping between
layouts on rotation. */}
<ul
className="mt-3 space-y-2 md:hidden"
className="mt-3 space-y-2 lg:hidden"
data-testid="passkeys-mobile-list"
>
{passkeys.map((pk) => (
+1 -1
View File
@@ -122,7 +122,7 @@ function ExportCardShell({
return (
<div
data-testid={testId}
className={`bg-card border-border flex h-full flex-col rounded-xl border p-5${outerClassName ? ` ${outerClassName}` : ""}`}
className={`bg-card border-border flex h-full flex-col rounded-xl border p-6${outerClassName ? ` ${outerClassName}` : ""}`}
>
<div className="flex items-start justify-between gap-3">
<div className="flex items-center gap-2">
@@ -135,6 +135,29 @@ export function SettingsShell({ active, children }: SettingsShellProps) {
const { t } = useTranslations();
const activeSlug = deriveActiveSlug(pathname, active);
// v1.4.33 IW4 — scroll the active chip into view inside the
// horizontal mobile strip. On a 393 CSS px viewport the strip
// is 11 chips wide and the rightmost four sit below the fold; a
// user who tapped one of those chips lands on the new route
// with the strip still scrolled to the leftmost chip, which
// reads as if the navigation is broken. `inline: "center"`
// anchors the active chip in the visible middle of its scroll
// container so the user keeps spatial orientation between
// sections.
const mobileStripRef = React.useRef<HTMLElement | null>(null);
React.useEffect(() => {
if (typeof window === "undefined") return;
const strip = mobileStripRef.current;
if (!strip) return;
const active = strip.querySelector<HTMLElement>('[aria-current="page"]');
if (!active) return;
active.scrollIntoView({
block: "nearest",
inline: "center",
behavior: "smooth",
});
}, [activeSlug]);
// v1.4.25 W8 — AuthShell wraps the page in `px-4 py-6 md:px-6`
// already, so this inner shell only carries the wider max-width.
// Previously the duplicate `px-4 py-6 md:px-6 md:py-8` here was
@@ -150,6 +173,7 @@ export function SettingsShell({ active, children }: SettingsShellProps) {
page, which makes the page feel like every section card has
an overflow problem. */}
<nav
ref={mobileStripRef}
aria-label={t("settings.shell.sectionsNav")}
className="no-scrollbar -mx-4 mb-4 overflow-x-auto px-4 md:hidden"
>
@@ -250,10 +250,18 @@ function MetricRow({
</p>
</div>
<div className="flex shrink-0 items-center gap-2">
{/* v1.4.33 F17 — the switch label used to flip between
"Auto" and "Überschrieben" with the current state. The
maintainer's audit caught that as confusing: a user
looking at a row with "Auto" + the toggle off reads it
as "Auto is off, why are there no inputs?" instead of
"flip the switch to enter a custom range". Anchor the
label on the *action* ("Eigene Werte" / "Custom range")
so the affordance is unambiguous; the
`thresholds.sourceOverride` badge to the right still
announces when the override is active. */}
<Label htmlFor={`override-${metric}`} className="text-xs">
{overrideMode
? t("thresholds.overrideModeLabel")
: t("thresholds.autoModeLabel")}
{t("thresholds.overrideToggleLabel")}
</Label>
<Switch
id={`override-${metric}`}