feat: Projektgerüst, gehärteten Betriebsstand und Prozess aufsetzen
Legt die Grundlage, auf der alle weiteren Meilensteine nur noch Fachlichkeit ergänzen: Next 16 mit App Router und TypeScript strict, Tailwind 4, Prisma 7, Vitest 5, Playwright, ESLint, Prettier und die Hooks für Lint und Commits. Zwei Dinge sind bewusst so gebaut und durch Tests festgehalten: Die Datenbank ist im Betriebsstand von außen nicht erreichbar. Sie hat kein ports:-Mapping und hängt allein im Netz "intern", das ohne Gateway läuft; nur "web" kommt zusätzlich nach draußen. tests/betriebsstand.test.ts schlägt an, falls ein Port-Mapping zurückkehrt. Geprüft, nicht behauptet: docker compose up startet den Stack, 5432 ist vom Host aus zu, aus "web" offen. pnpm pruefe:doku hält Dokumentation und Code zusammen — Version im Changelog, Issue-Dateien gegen ihre Übersicht, keine Verweise auf fehlende Dateien, keine fehlenden Bilder. Das Skript wurde gegen vier eingebaute Fehler gegengeprüft. Drei Versions-Obergrenzen sind nötig und in CLAUDE.md begründet: eslint bleibt auf 9.x, weil eslint-plugin-react keinen Nachfolger für eslint 10 hat und das Regelwerk sonst beim Laden abbricht; typescript bleibt auf 6.0.x, weil typescript-eslint TypeScript 7 noch nicht unterstützt; prisma ist exakt auf 7.10.0 gepinnt, weil sein latest-Tag auf einen 8.0-RC zeigt, der von @prisma/client aber nicht. Auth.js entfällt entgegen der ursprünglichen Vorgabe: v5 steht dauerhaft auf Beta, das Projekt ist im Wartungsmodus und sein Prisma-Adapter deckt Prisma 7 nicht ab. Stattdessen openid-client und jose, wie in ProxLedger. Dieser erste Commit geht direkt auf main, weil es noch keinen Branch gibt, von dem abzuzweigen wäre. Ab #006 läuft jedes Issue über einen eigenen Branch. Closes #5 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
import { prisma } from "@/lib/db";
|
||||
|
||||
/** Healthcheck fuer Docker und den Reverse Proxy. Prueft, ob die Datenbank antwortet. */
|
||||
export async function GET(): Promise<Response> {
|
||||
try {
|
||||
await prisma.$queryRaw`SELECT 1`;
|
||||
return Response.json({ status: "ok" });
|
||||
} catch (fehler) {
|
||||
const meldung = fehler instanceof Error ? fehler.message : "unbekannter Fehler";
|
||||
return Response.json({ status: "fehler", meldung }, { status: 503 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
/*
|
||||
* Laufzettel – Gestaltung.
|
||||
* Dunkel zuerst. Ein einziger Akzent (Signal-Lime), nur fuer Aktionen, Fortschritt,
|
||||
* Fokus und aktive Zustaende. Keine Verlaeufe, keine Schatten.
|
||||
* Druckansichten und Etiketten sind immer hell, siehe Abschnitt @media print.
|
||||
*/
|
||||
@theme {
|
||||
--color-grund: #0b0d10;
|
||||
--color-flaeche: #14171c;
|
||||
--color-flaeche-hoch: #1b1f26;
|
||||
--color-linie: #262b33;
|
||||
--color-text: #f3f5f7;
|
||||
--color-nebentext: #98a2b3;
|
||||
|
||||
--color-akzent: #c6f135;
|
||||
--color-auf-akzent: #0b0d10;
|
||||
--color-akzent-flaeche: rgb(198 241 53 / 0.12);
|
||||
|
||||
--color-erledigt: #3ddc97;
|
||||
--color-faellig: #f5b84a;
|
||||
--color-fehler: #f87171;
|
||||
|
||||
--font-titel: "Space Grotesk Variable", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-sans: "Manrope Variable", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-mono: "JetBrains Mono Variable", ui-monospace, monospace;
|
||||
|
||||
--radius-karte: 1rem;
|
||||
--radius-pille: 999px;
|
||||
}
|
||||
|
||||
/* Heller Modus ist optional und nur aktiv, wenn er ausdruecklich gewaehlt wurde. */
|
||||
:root[data-thema="hell"] {
|
||||
--color-grund: #f6f7f4;
|
||||
--color-flaeche: #ffffff;
|
||||
--color-flaeche-hoch: #ffffff;
|
||||
--color-linie: #d9ddd4;
|
||||
--color-text: #0b0d10;
|
||||
--color-nebentext: #5b6472;
|
||||
--color-akzent: #5e8a00;
|
||||
--color-auf-akzent: #ffffff;
|
||||
--color-akzent-flaeche: rgb(94 138 0 / 0.1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Vertrag von shadcn/ui, gespeist aus den Laufzettel-Token oben.
|
||||
* Dadurch sieht jede per `pnpm dlx shadcn@latest add <bauteil>` kopierte
|
||||
* Komponente sofort nach Laufzettel aus, ohne dass sie angefasst werden muss –
|
||||
* und der helle Modus greift automatisch mit.
|
||||
*/
|
||||
:root {
|
||||
--background: var(--color-grund);
|
||||
--foreground: var(--color-text);
|
||||
--card: var(--color-flaeche);
|
||||
--card-foreground: var(--color-text);
|
||||
--popover: var(--color-flaeche-hoch);
|
||||
--popover-foreground: var(--color-text);
|
||||
--primary: var(--color-akzent);
|
||||
--primary-foreground: var(--color-auf-akzent);
|
||||
--secondary: var(--color-flaeche-hoch);
|
||||
--secondary-foreground: var(--color-text);
|
||||
--muted: var(--color-flaeche-hoch);
|
||||
--muted-foreground: var(--color-nebentext);
|
||||
--accent: var(--color-akzent-flaeche);
|
||||
--accent-foreground: var(--color-akzent);
|
||||
--destructive: var(--color-fehler);
|
||||
--destructive-foreground: var(--color-grund);
|
||||
--border: var(--color-linie);
|
||||
--input: var(--color-linie);
|
||||
--ring: var(--color-akzent);
|
||||
--radius: 0.75rem;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--color-grund);
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-sans);
|
||||
/* Am Handy darf nie horizontal gescrollt werden. */
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
.zahl {
|
||||
font-family: var(--font-titel);
|
||||
}
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp,
|
||||
.mono {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* Fokus immer sichtbar und immer im Akzent. */
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-akzent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
background: #ffffff;
|
||||
color: #0b0d10;
|
||||
}
|
||||
|
||||
.nicht-drucken {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48">
|
||||
<rect width="48" height="48" rx="10" fill="#0B0D10"/>
|
||||
<g fill="none" stroke="#C6F135" stroke-width="5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M15 8H9v32h6"/><path d="M33 8h6v32h-6"/><path d="M17.5 24.5l5 5 9.5-11"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 340 B |
@@ -0,0 +1,24 @@
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import "@fontsource-variable/space-grotesk";
|
||||
import "@fontsource-variable/manrope";
|
||||
import "@fontsource-variable/jetbrains-mono";
|
||||
import "./globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "laufzettel",
|
||||
description: "Checklisten fuer wiederkehrende IT-Arbeiten",
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: "#0B0D10",
|
||||
width: "device-width",
|
||||
initialScale: 1,
|
||||
};
|
||||
|
||||
export default function WurzelLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="de">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
|
||||
export default function manifest(): MetadataRoute.Manifest {
|
||||
return {
|
||||
name: "laufzettel",
|
||||
short_name: "laufzettel",
|
||||
description: "Checklisten fuer wiederkehrende IT-Arbeiten",
|
||||
start_url: "/",
|
||||
display: "standalone",
|
||||
background_color: "#0B0D10",
|
||||
theme_color: "#0B0D10",
|
||||
lang: "de",
|
||||
icons: [
|
||||
{ src: "/icon-kachel.svg", sizes: "any", type: "image/svg+xml", purpose: "any" },
|
||||
{ src: "/icon-192.png", sizes: "192x192", type: "image/png", purpose: "any" },
|
||||
{ src: "/icon-512.png", sizes: "512x512", type: "image/png", purpose: "maskable" },
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Huelle } from "@/components/huelle";
|
||||
|
||||
export default function Startseite() {
|
||||
return (
|
||||
<Huelle aktiv="/">
|
||||
<h1 className="text-2xl font-bold">Übersicht</h1>
|
||||
<p className="mt-2 max-w-prose text-nebentext">
|
||||
Das Gerüst steht: Gestaltung, Betriebsstand, Qualitätsschranken und Prozess. Die Fachlichkeit kommt ab
|
||||
dem Datenmodell.
|
||||
</p>
|
||||
|
||||
<section className="mt-8 grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
|
||||
{[
|
||||
{ nummer: "01", titel: "Datenmodell", issue: "#006" },
|
||||
{ nummer: "02", titel: "Anmeldung und Rollen", issue: "#007" },
|
||||
{ nummer: "03", titel: "Betriebsmodus und Kunden", issue: "#008" },
|
||||
].map((karte) => (
|
||||
<article key={karte.nummer} className="rounded-karte border border-linie bg-flaeche p-5">
|
||||
<p className="mono text-xs text-nebentext">
|
||||
<span className="text-akzent">{karte.nummer}</span> · als Nächstes
|
||||
</p>
|
||||
<h2 className="mt-2 text-lg font-semibold">{karte.titel}</h2>
|
||||
<p className="mono mt-1 text-xs text-nebentext">{karte.issue}</p>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
</Huelle>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user