fix(mobile): touch-target floor, reduced-motion backstop, manifest categories, trivy ignore for npm-bundled picomatch

This commit is contained in:
Marc-André Bombeck committed 2026-06-18 15:16:23 +02:00
commit ba89f97176
10 files changed
+48 -14

No files matched your search

+14
View File
@@ -0,0 +1,14 @@
# Trivy vulnerability ignore list.
# One CVE id per line. Only suppress a finding that has NO upstream fix we
# can apply; never suppress one with a fixed version available — bump instead.
# Each entry names what it covers and the condition to re-check / remove it.
# CVE-2026-33671 (picomatch ReDoS, HIGH) — flagged only in the npm CLI's
# OWN bundled copy that ships inside the Node base image
# (usr/local/lib/node_modules/npm/node_modules/picomatch, v4.0.3), used at
# image-build time by npm, not by our runtime. Our application dependency
# tree pins picomatch >= 4.0.4 via a pnpm override, so the running app is
# not affected and the path is not reachable at runtime. Re-check when the
# Node base image ships an npm build whose bundled picomatch is >= 4.0.4,
# then drop this entry.
CVE-2026-33671
+2 -1
View File
@@ -6,7 +6,8 @@
"scope": "/",
"start_url": "/",
"display": "standalone",
"display_override": ["standalone"],
"display_override": ["standalone", "minimal-ui"],
"categories": ["health", "medical", "lifestyle"],
"theme_color": "#282a36",
"background_color": "#282a36",
"icons": [
+2 -1
View File
@@ -85,7 +85,8 @@ describe("public/manifest.json PWA identity hygiene (F-7, 2026-05-16)", () => {
const manifest = JSON.parse(raw) as Record<string, unknown>;
expect(manifest.scope).toBe("/");
expect(manifest.id).toBe("/?source=pwa");
expect(manifest.display_override).toEqual(["standalone"]);
expect(manifest.display_override).toEqual(["standalone", "minimal-ui"]);
expect(manifest.categories).toEqual(["health", "medical", "lifestyle"]);
});
it("keeps the existing display/start_url contract", () => {
+18
View File
@@ -889,3 +889,21 @@
animation: none;
}
}
/* ── Global reduced-motion backstop ──────────────────────────────────
* Defensive catch-all so any animation/transition we forget to gate
* per-component still collapses to near-instant when the user has asked
* for reduced motion. The per-component blocks above stay — they carry
* tailored static fallbacks (static dots, full-height bars). This is the
* floor, not a replacement. Near-zero (not 0) keeps JS animation-end /
* transition-end listeners firing so state machines don't stall. */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
+1 -1
View File
@@ -32,7 +32,7 @@ export function SortableHead({
<button
type="button"
onClick={() => onSort(column)}
className="hover:text-foreground inline-flex items-center gap-1 transition-colors"
className="hover:text-foreground focus-visible:ring-ring/50 inline-flex min-h-11 items-center gap-1 rounded-sm transition-colors focus-visible:ring-2 focus-visible:outline-none sm:min-h-0"
>
{label}
{isActive ? (
@@ -63,7 +63,7 @@ export function GlucoseAdvancedDisclosure({
aria-expanded={open}
aria-controls={regionId}
data-slot="glucose-advanced-toggle"
className="text-muted-foreground hover:text-foreground flex min-h-11 w-full items-center justify-between gap-2 text-sm font-medium transition-colors"
className="text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 flex min-h-11 w-full items-center justify-between gap-2 rounded-sm text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none"
>
<span>{t("insights.bloodGlucose.clinical.advanced.toggle")}</span>
<ChevronDown
@@ -85,14 +85,14 @@ export function SuggestedPrompts({
type="button"
onClick={() => onPick(prompt)}
data-slot="insights-suggested-prompts-chip"
// v1.8.5 W4a — denser chips. The tap target stays comfortable
// (`min-h-9` ≈ 36 px, the WCAG/iOS floor) but the horizontal
// padding + type size drop a notch so the two prompts pack into
// fewer rows and the left column stops towering over the
// Health-Score card.
// v1.8.5 W4a — denser chips. The horizontal padding + type size
// drop a notch so the two prompts pack into fewer rows and the
// left column stops towering over the Health-Score card. The tap
// target clears the 44 px iOS/WCAG floor on phones (`min-h-11`)
// and relaxes to the denser 36 px from `sm` up.
className={cn(
"border-dracula-purple/18 hover:border-dracula-purple/40 hover:text-foreground",
"text-muted-foreground inline-flex min-h-9 items-center gap-1.5",
"text-muted-foreground inline-flex min-h-11 items-center gap-1.5 sm:min-h-9",
"rounded-full border bg-transparent px-3 py-1.5 text-xs",
"transition-colors focus-visible:ring-2 focus-visible:outline-none",
"focus-visible:ring-dracula-purple/50",
@@ -669,7 +669,7 @@ function LedgerRowItem({
<Button
variant="ghost"
size="icon-sm"
className="min-h-9 min-w-9"
className="min-h-11 min-w-11 sm:min-h-9 sm:min-w-9"
aria-label={t("medications.detail.verlauf.rowActionsLabel", {
time: timeLabel,
})}
@@ -238,7 +238,7 @@ export function TimesOfDayChips({
disabled={disabled}
aria-label={`${t(`${i18nPrefix}.remove`)} ${time}`}
onClick={() => onRemove(time)}
className="focus-visible:ring-ring hover:bg-background inline-flex h-7 w-7 items-center justify-center rounded-sm focus-visible:ring-2 focus-visible:outline-none disabled:opacity-50"
className="focus-visible:ring-ring hover:bg-background relative inline-flex h-7 w-7 items-center justify-center rounded-sm before:absolute before:inset-[-8px] before:content-[''] focus-visible:ring-2 focus-visible:outline-none disabled:opacity-50"
data-slot="times-of-day-chip-remove"
>
<X className="h-3.5 w-3.5" aria-hidden="true" />
@@ -223,7 +223,7 @@ export function ApiTokensRow({
onClick={() =>
void copy(endpoint, t("medications.detail.api.urlCopied"))
}
className="h-7 w-7 shrink-0"
className="relative h-7 w-7 shrink-0 before:absolute before:inset-[-8px] before:content-['']"
aria-label={t("medications.detail.api.copyUrl")}
data-slot="api-tokens-copy-url"
>
@@ -328,7 +328,7 @@ export function ApiTokensRow({
<Button
variant="ghost"
size="icon"
className="absolute top-1 right-1 h-7 w-7"
className="absolute top-1 right-1 h-7 w-7 before:absolute before:inset-[-8px] before:content-['']"
onClick={() =>
void copy(selectedExample.value, t("common.copied"))
}