feat(pwa): als App installierbar
Web-Manifest mit eigenen Symbolen; auf dem Handy laesst sich Nebula zum Startbildschirm hinzufuegen und laeuft dann ohne Adressleiste. Das Manifest entsteht wie die uebrigen Metadaten aus einer Funktion, nicht aus einer Konstanten: nur so wird APP_NAME zur Laufzeit gelesen und das fertige Docker-Image bleibt umbenennbar. Manifest und Symbole muessen an der Zugangsschranke vorbei - der Browser holt sie, bevor sich jemand angemeldet hat. Sonst landeten sie auf der Anmeldeseite und die Anwendung waere nicht installierbar. Die maskierbare Fassung hat mehr Rand: Android schneidet Symbole je nach Geraet rund, eckig oder als Tropfen zu. Die PNGs sind aus dem vorhandenen SVG mit Chrome erzeugt - das spart eine Bildbibliothek als Abhaengigkeit. Beim Ergaenzen des Apple-Symbols ist mir aufgefallen, dass ein eigener icons-Eintrag den Verweis ersetzt, den Next aus app/icon.svg von selbst erzeugt: die Seite hatte danach gar kein Symbol mehr und der Browser fiel auf /favicon.ico zurueck, das es hier nicht gibt. Beide Eintraege stehen jetzt ausdruecklich da, ein Test prueft den Verweis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+16
-1
@@ -1,4 +1,5 @@
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { ChangelogButton } from "@/components/ChangelogButton";
|
||||
import { CommandPalette } from "@/components/CommandPalette";
|
||||
import { TopNav } from "@/components/TopNav";
|
||||
import { getSessionUser } from "@/lib/auth/session";
|
||||
@@ -20,6 +21,20 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
"Uebersicht ueber alle Vibe-Coding Projekte: Status, Notizen und Live-Daten aus GitHub, GitLab und Gitea.",
|
||||
applicationName: APP_NAME,
|
||||
robots: { index: false, follow: false },
|
||||
manifest: "/manifest.webmanifest",
|
||||
// iOS liest das Manifest nicht; dort braucht es diese Angaben einzeln.
|
||||
appleWebApp: { capable: true, title: APP_NAME, statusBarStyle: "black-translucent" },
|
||||
// Beide Eintraege ausdruecklich: sobald hier etwas steht, ersetzt es den
|
||||
// Verweis, den Next aus app/icon.svg von selbst erzeugt. Ohne die erste
|
||||
// Zeile haette die Seite gar kein Symbol mehr und der Browser fiele auf
|
||||
// /favicon.ico zurueck – das es hier nicht gibt.
|
||||
icons: {
|
||||
icon: [
|
||||
{ url: "/icon.svg", type: "image/svg+xml" },
|
||||
{ url: "/icon-192.png", sizes: "192x192", type: "image/png" },
|
||||
],
|
||||
apple: "/apple-touch-icon.png",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -56,7 +71,7 @@ export default async function RootLayout({
|
||||
<footer className="mx-auto w-full max-w-[1400px] px-4 pb-10 text-xs text-slate-500 sm:px-6 lg:px-8">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 border-t border-white/5 pt-6">
|
||||
<span>{APP_NAME} · lokal betrieben, Daten bleiben in deiner PostgreSQL.</span>
|
||||
<span className="font-mono text-slate-600">v{APP_VERSION}</span>
|
||||
<ChangelogButton version={APP_VERSION} />
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
import { APP_NAME, APP_TAGLINE } from "@/lib/config";
|
||||
|
||||
/**
|
||||
* Web-App-Manifest: macht Nebula auf dem Handy installierbar.
|
||||
*
|
||||
* Als Funktion und nicht als Konstante, aus demselben Grund wie bei
|
||||
* generateMetadata: nur so wird APP_NAME zur Laufzeit gelesen. Ein
|
||||
* konstanter Export waere beim Build eingefroren und liesse sich im
|
||||
* fertigen Docker-Image nicht mehr aendern.
|
||||
*/
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default function manifest(): MetadataRoute.Manifest {
|
||||
return {
|
||||
name: `${APP_NAME} – ${APP_TAGLINE} Dashboard`,
|
||||
short_name: APP_NAME,
|
||||
description:
|
||||
"Uebersicht ueber alle Vibe-Coding-Projekte: Status, Notizen und Live-Daten aus GitHub, GitLab und Gitea.",
|
||||
// Ohne Adressleiste, aber mit den Systemleisten – die Anwendung braucht
|
||||
// keinen Vollbildmodus, wohl aber die Uhrzeit und den Akkustand.
|
||||
display: "standalone",
|
||||
start_url: "/",
|
||||
scope: "/",
|
||||
background_color: "#05060f",
|
||||
theme_color: "#05060f",
|
||||
lang: "de",
|
||||
orientation: "any",
|
||||
icons: [
|
||||
{ src: "/icon-192.png", sizes: "192x192", type: "image/png", purpose: "any" },
|
||||
{ src: "/icon-512.png", sizes: "512x512", type: "image/png", purpose: "any" },
|
||||
// Getrennte Fassung mit mehr Rand: Android schneidet maskierbare
|
||||
// Symbole je nach Geraet rund, eckig oder als Tropfen zu.
|
||||
{ src: "/icon-maskable-512.png", sizes: "512x512", type: "image/png", purpose: "maskable" },
|
||||
],
|
||||
shortcuts: [
|
||||
{ name: "Dashboard", url: "/" },
|
||||
{ name: "Mein Konto", url: "/account" },
|
||||
],
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user