mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
Consolidated hotfix landing the findings from a six-axis audit
covering mobile security, UX + accessibility, code quality + docs,
README + discoverability, web performance, and mobile-deep responsive
behaviour. None of the changes break the public API or the existing
data shape; every fix is additive and SAFE under the v1.4.34.x web
freeze. Full audit reports live in `.planning/round-audit-*.md`.
Security
- Password rotation now wipes API tokens + refresh tokens in the
same `$transaction` as session revocation. A stolen iOS Bearer no
longer survives a user-initiated password change or an admin reset.
- `/.well-known/` proxy bypass tightened to an explicit-allowlist
`Set`. Stale `/api/auth/codex/callback` PUBLIC_PATHS entry dropped.
- HSTS gains `preload`. Withings host removed from the global CSP
and gated to the integration's own routes.
- `getClientIp` warns once per process on `TRUST_PROXY_HOPS`/XFF
mismatch + exposes a tagged helper for tighter universal buckets.
- Service worker rejects off-origin notification-click URLs. Offline
fallback body switched to a locale-neutral icon. Manifest gains
`scope`, `id`, `display_override`.
UX + accessibility
- i18n leaks closed on Dialog/Sheet close-X, ChartSkeleton's loading
announcement, and the notifications-section breadcrumb.
- Focus rings on bottom-nav, the More overflow button, and the
mobile top-bar user menu.
- Tap-target floor: Input / NativeSelect / Select / DateTimeInput
switch to `h-11 sm:h-10`.
- Admin chip-strip auto-scroll-into-view (matches v1.4.33 settings).
- List-page subtitles visible on mobile.
- Insights empty-state CTA vocabulary normalised so the link opens
the right measurement form (was silently failing for
BODY_TEMPERATURE / BLOOD_GLUCOSE / PULSE).
Mobile-deep
- `viewport.themeColor` reacts to light/dark mode.
- TopBar + Coach FAB reserve safe-area-inset-top / -bottom for
notched iPhones.
- Sonner toaster switches to `theme="system"`.
Code quality
- `SESSION_SECRET` dropped — the env var was documented as required
but never read by code. Three secrets, not four. Self-hosters
upgrading can delete the existing line.
- `toJson<T>()` helper consolidates seven Prisma JSON-cast sites.
- `db-compat.ts` gains a header docstring naming it the
schema-bootstrap path.
- Passkey response types adopted from `@simplewebauthn/server`;
`any` + eslint-disable pragmas drop out.
- Zero-TODO CI gate added at `.github/workflows/no-todo-markers.yml`.
Documentation + discoverability
- README rework: How-it-compares matrix, Apple Health + AI Coach
promoted to standalone Key-Features bullets, status block, latest
release + GHCR badges, OpenAPI pointer, three-secret Quick Start.
- openGraph + twitter cards now carry `images` and a
`summary_large_image` Twitter card.
- GitHub repo metadata: description tightened from 290 to 156
chars + keyword-front, `homepageUrl` flipped from a personal
tenant to the demo site, six generic / PII-adjacent topics
swapped for six high-intent ones.
- Five new user-facing docs: `docs/self-hosting/getting-started.md`,
`docs/self-hosting/reverse-proxy.md`,
`docs/integrations/apple-health.md`,
`docs/integrations/withings.md`,
`docs/integrations/ai-providers.md`.
- `docs/README.md` clarifies the docs tree as internal playbooks
vs the user-facing `docs.healthlog.dev` site.
126 lines
4.3 KiB
TypeScript
126 lines
4.3 KiB
TypeScript
import type { Metadata, Viewport } from "next";
|
||
import { Inter } from "next/font/google";
|
||
import { headers, cookies } from "next/headers";
|
||
import "./globals.css";
|
||
import { Providers } from "@/components/providers";
|
||
import { AuthShell } from "@/components/layout/auth-shell";
|
||
import { MonitoringBootstrap } from "@/components/monitoring/bootstrap";
|
||
import { WebVitalsReporter } from "@/components/monitoring/web-vitals-reporter";
|
||
import { parseLocaleFromAcceptLanguage } from "@/lib/format-locale";
|
||
import { locales, type Locale } from "@/lib/i18n/config";
|
||
|
||
async function resolveInitialLocale(): Promise<Locale> {
|
||
// Both cookies() and headers() can throw (DynamicServerError, etc.) —
|
||
// fall back to the default so a locale hiccup never crashes the root
|
||
// layout into global-error.tsx.
|
||
try {
|
||
const cookieStore = await cookies();
|
||
const cookieLocale = cookieStore.get("healthlog-locale")?.value;
|
||
if (cookieLocale && (locales as readonly string[]).includes(cookieLocale)) {
|
||
return cookieLocale as Locale;
|
||
}
|
||
const headerList = await headers();
|
||
return parseLocaleFromAcceptLanguage(headerList.get("accept-language"));
|
||
} catch {
|
||
return "en";
|
||
}
|
||
}
|
||
|
||
const inter = Inter({
|
||
variable: "--font-sans",
|
||
subsets: ["latin"],
|
||
weight: ["400", "500", "600", "700"],
|
||
});
|
||
|
||
export const metadata: Metadata = {
|
||
title: "HealthLog",
|
||
description:
|
||
"Personal health tracking — weight, blood pressure, pulse, mood, medication compliance",
|
||
manifest: "/manifest.json",
|
||
icons: {
|
||
icon: "/favicon.svg",
|
||
apple: "/logo-192.png",
|
||
},
|
||
appleWebApp: {
|
||
capable: true,
|
||
statusBarStyle: "black-translucent",
|
||
title: "HealthLog",
|
||
},
|
||
openGraph: {
|
||
title: "HealthLog",
|
||
description:
|
||
"Self-hosted health tracker. Weight, blood pressure, glucose, mood, medications. Withings + Apple Health sync. AI Insights you own.",
|
||
type: "website",
|
||
locale: "en_US",
|
||
alternateLocale: ["de_DE", "fr_FR", "es_ES", "it_IT", "pl_PL"],
|
||
siteName: "HealthLog",
|
||
// Drop-in OG asset. Replace with a 1200×630 dashboard-screenshot
|
||
// capture when an official one ships; the logo render keeps the
|
||
// unfurl from rendering a blank tile in the meantime.
|
||
images: [
|
||
{
|
||
url: "/logo-readme.png",
|
||
width: 1000,
|
||
height: 1000,
|
||
alt: "HealthLog — your health data, your server",
|
||
},
|
||
],
|
||
},
|
||
twitter: {
|
||
card: "summary_large_image",
|
||
title: "HealthLog",
|
||
description:
|
||
"Self-hosted health tracker. Weight, blood pressure, glucose, mood, medications. Withings + Apple Health sync. AI Insights you own.",
|
||
images: ["/logo-readme.png"],
|
||
},
|
||
};
|
||
|
||
export const viewport: Viewport = {
|
||
width: "device-width",
|
||
initialScale: 1,
|
||
viewportFit: "cover",
|
||
// Match the browser chrome (Android URL bar, iOS PWA status bar) to
|
||
// the active palette. The hex values are the resolved background of
|
||
// `--background` from `app/globals.css` for each theme so the bar
|
||
// edge never seams against the page on cold paint.
|
||
themeColor: [
|
||
{ media: "(prefers-color-scheme: light)", color: "#ffffff" },
|
||
{ media: "(prefers-color-scheme: dark)", color: "#282a36" },
|
||
],
|
||
};
|
||
|
||
// Inline script to apply theme before first paint (prevents FOUC)
|
||
const themeScript = `(function(){try{var t=localStorage.getItem("healthlog-theme");var c=(t==="light"||t==="dark")?t:(window.matchMedia("(prefers-color-scheme:dark)").matches?"dark":"light");document.documentElement.classList.add(c)}catch(e){document.documentElement.classList.add("dark")}})()`;
|
||
|
||
export default async function RootLayout({
|
||
children,
|
||
}: Readonly<{
|
||
children: React.ReactNode;
|
||
}>) {
|
||
const nonce =
|
||
process.env.NODE_ENV === "production"
|
||
? ((await headers()).get("x-nonce") ?? undefined)
|
||
: undefined;
|
||
|
||
const initialLocale = await resolveInitialLocale();
|
||
|
||
return (
|
||
<html lang={initialLocale} suppressHydrationWarning>
|
||
<head>
|
||
<script
|
||
suppressHydrationWarning
|
||
nonce={nonce}
|
||
dangerouslySetInnerHTML={{ __html: themeScript }}
|
||
/>
|
||
</head>
|
||
<body className={`${inter.variable} font-sans antialiased`}>
|
||
<Providers initialLocale={initialLocale}>
|
||
<MonitoringBootstrap />
|
||
<WebVitalsReporter />
|
||
<AuthShell>{children}</AuthShell>
|
||
</Providers>
|
||
</body>
|
||
</html>
|
||
);
|
||
}
|