Files
bricktrack/src/app/layout.tsx
T
unknownandClaude Opus 5 39271f8af7 feat(pwa): installierbar mit manifest, icons und offline-katalog
Manifest, Icons (auch maskable und Apple-Touch) und ein Service Worker.
Auf dem Handy laesst sich Bricktrack damit auf den Startbildschirm legen
und startet ohne Browserleiste; die Verknuepfungen springen direkt zu
"Fehlende Sets" und zur Sammlung.

Der Service Worker liefert Seiten nach dem Muster "erst Netz, dann
Cache": mit Verbindung ist alles aktuell, ohne Verbindung bleibt lesbar,
was schon einmal geladen war - genau der Katalog, den man im Keller oder
im Laden braucht. Statische Build-Dateien und Set-Bilder kommen aus dem
Cache, weil ihre Namen ohnehin unveraenderlich sind.

Bewusst nie aus dem Cache: /api/auth, /api/export und der Healthcheck,
und nichts ausser GET - eine offline "reservierte" Wunschliste waere
eine Luege. Beim Abmelden raeumt die App den Cache, damit auf einem
geteilten Geraet keine lesbaren Seiten zurueckbleiben.

Icons entstehen reproduzierbar aus einer SVG-Quelle
(node scripts/generate-icons.mjs).

Refs Phase 2

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 07:27:12 +02:00

49 lines
1.5 KiB
TypeScript

import type { Metadata, Viewport } from 'next';
import { ServiceWorkerRegistration } from '@/components/layout/service-worker';
import { Toaster } from '@/components/ui/sonner';
import './globals.css';
export const metadata: Metadata = {
title: {
default: 'Bricktrack',
template: '%s · Bricktrack',
},
description: 'Sammlung und Wunschlisten für Spielzeug-Sets',
applicationName: 'Bricktrack',
manifest: '/manifest.webmanifest',
icons: {
icon: [{ url: '/favicon.png', type: 'image/png', sizes: '32x32' }],
apple: [{ url: '/icons/apple-touch-icon.png', sizes: '180x180' }],
},
appleWebApp: {
capable: true,
title: 'Bricktrack',
statusBarStyle: 'default',
},
// A private, self-hosted instance never belongs in a search index - and the
// public wishlist links are meant to be shared, not found.
robots: { index: false, follow: false },
};
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
viewportFit: 'cover',
themeColor: [
{ media: '(prefers-color-scheme: light)', color: '#ffffff' },
{ media: '(prefers-color-scheme: dark)', color: '#0a0a0a' },
],
};
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="de" suppressHydrationWarning>
<body className="min-h-dvh bg-background text-foreground antialiased">
{children}
<Toaster position="top-center" richColors closeButton />
<ServiceWorkerRegistration />
</body>
</html>
);
}