chore(release): v1.4.27

v1.4.27 — Mobile capability and maintainer-finding cleanup.

ResponsiveSheet primitive (bottom-sheet on narrow viewports, centred dialog on desktop)
applied to BD, Weight, Glucose, Mood, Activity forms. Maintainer-finding cleanups across
Coach, Insights, Reports, Privacy. GeoLite2 offline geocoding with resilience layer
(graceful fallback when EULA not yet propagated). E2E gap-fill coverage. 450-line
release notes attached to the GitHub Release.
This commit is contained in:
Marc-André Bombeck authored and GitHub committed 2026-05-15 22:59:10 +02:00
1 parent d98e85cd03
commit 4f09c3f862
314 files changed
+26801 -4419

No files matched your search

+28 -5
View File
@@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useId, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { KeyRound, Lock, Loader2 } from "lucide-react";
@@ -23,6 +23,12 @@ export default function LoginPage() {
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
// v1.4.27 MB3 — explicit error-region id so the email + password
// inputs reference the banner via `aria-describedby`. Screen readers
// pair the error with the field instead of announcing it as a
// standalone alert detached from the form.
const errorId = useId();
const errorDescriptor = error ? errorId : undefined;
const { data: registrationEnabled } = useQuery({
queryKey: ["auth", "registration-status"],
queryFn: async () => {
@@ -118,7 +124,11 @@ export default function LoginPage() {
return (
<div className="flex w-full max-w-sm flex-col gap-4">
<div className="border-border bg-card rounded-xl border p-8 shadow-lg shadow-black/20">
{/* v1.4.27 MB7 / CF-61 — drop `p-8` to `p-6 sm:p-8` so the
auth card breathes on Galaxy Fold / Pixel 5 without stealing
half the visible viewport with padding. Tablet+ keeps the
generous 32 px padding. */}
<div className="border-border bg-card rounded-xl border p-6 shadow-lg shadow-black/20 sm:p-8">
<div className="flex flex-col items-center gap-3 text-center">
<div className="bg-primary/10 flex h-12 w-12 items-center justify-center rounded-lg">
<Logo className="text-primary" size={28} />
@@ -140,7 +150,7 @@ export default function LoginPage() {
disabled={loading}
>
{loading && mode === "passkey" ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
) : (
<KeyRound className="mr-2 h-4 w-4" />
)}
@@ -176,6 +186,13 @@ export default function LoginPage() {
onChange={(e) => setEmail(e.target.value)}
required
autoComplete="username"
inputMode="email"
enterKeyHint="next"
autoCapitalize="none"
spellCheck={false}
aria-required="true"
aria-invalid={!!error || undefined}
aria-describedby={errorDescriptor}
placeholder={t("auth.emailOrUsernamePlaceholder")}
/>
</div>
@@ -188,6 +205,10 @@ export default function LoginPage() {
onChange={(e) => setPassword(e.target.value)}
required
autoComplete="current-password"
enterKeyHint="go"
aria-required="true"
aria-invalid={!!error || undefined}
aria-describedby={errorDescriptor}
placeholder="********"
/>
</div>
@@ -198,7 +219,7 @@ export default function LoginPage() {
disabled={loading}
>
{loading && mode === "password" ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
) : (
<Lock className="mr-2 h-4 w-4" />
)}
@@ -207,7 +228,7 @@ export default function LoginPage() {
<button
type="button"
onClick={() => setMode("passkey")}
className="text-muted-foreground hover:text-foreground w-full text-center text-xs"
className="text-muted-foreground hover:text-foreground inline-flex min-h-11 w-full items-center justify-center text-center text-xs"
>
{t("auth.backToPasskey")}
</button>
@@ -216,7 +237,9 @@ export default function LoginPage() {
{error && (
<div
id={errorId}
role="alert"
aria-live="polite"
className="bg-destructive/10 text-destructive rounded-lg p-3 text-sm"
>
{error}
+41 -4
View File
@@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useId, useState } from "react";
import { useRouter } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
import { Loader2, UserPlus } from "lucide-react";
@@ -22,6 +22,12 @@ export default function RegisterPage() {
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const { t } = useTranslations();
// v1.4.27 MB3 — explicit error-region id wired to every required
// input via `aria-describedby` so screen readers pair the validation
// failure with the offending field instead of announcing it as a
// standalone alert detached from the form.
const errorId = useId();
const errorDescriptor = error ? errorId : undefined;
async function handleRegister(e: React.FormEvent) {
e.preventDefault();
@@ -59,7 +65,11 @@ export default function RegisterPage() {
return (
<div className="w-full max-w-sm">
<div className="border-border bg-card rounded-xl border p-8 shadow-lg shadow-black/20">
{/* v1.4.27 MB7 / CF-61 — drop `p-8` to `p-6 sm:p-8` so the
auth card breathes on Galaxy Fold / Pixel 5 without stealing
half the visible viewport with padding. Tablet+ keeps the
generous 32 px padding. */}
<div className="border-border bg-card rounded-xl border p-6 shadow-lg shadow-black/20 sm:p-8">
<div className="flex flex-col items-center gap-3 text-center">
<div className="bg-primary/10 flex h-12 w-12 items-center justify-center rounded-lg">
<Logo className="text-primary" size={28} />
@@ -85,6 +95,12 @@ export default function RegisterPage() {
placeholder={t("auth.emailPlaceholder")}
required
autoComplete="email"
enterKeyHint="next"
autoCapitalize="none"
spellCheck={false}
aria-required="true"
aria-invalid={!!error || undefined}
aria-describedby={errorDescriptor}
/>
</div>
@@ -98,6 +114,12 @@ export default function RegisterPage() {
placeholder="user"
required
autoComplete="username"
enterKeyHint="next"
autoCapitalize="none"
spellCheck={false}
aria-required="true"
aria-invalid={!!error || undefined}
aria-describedby={errorDescriptor}
minLength={3}
maxLength={30}
/>
@@ -113,6 +135,10 @@ export default function RegisterPage() {
placeholder={t("auth.passwordMinLength")}
required
autoComplete="new-password"
enterKeyHint="go"
aria-required="true"
aria-invalid={!!error || undefined}
aria-describedby={errorDescriptor}
/>
<PasswordStrength password={password} />
<p className="text-muted-foreground text-xs">
@@ -122,16 +148,27 @@ export default function RegisterPage() {
{error && (
<div
id={errorId}
role="alert"
aria-live="polite"
className="bg-destructive/10 text-destructive rounded-lg p-3 text-sm"
>
{error}
</div>
)}
<Button type="submit" className="w-full" disabled={loading}>
{/* v1.4.27 MB6 — lift the submit to the 44 px tap-target
floor and the `lg` size so the primary action stays
reachable on a narrow viewport without falling under the
iOS keyboard's accessory bar. */}
<Button
type="submit"
size="lg"
className="min-h-11 w-full"
disabled={loading}
>
{loading ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
) : (
<UserPlus className="mr-2 h-4 w-4" />
)}