Technische Spezifikation des Endnutzer-Frontends aus Vision §1 (Einstieg),
§2 (Lineatur-Konfigurator), §3 (Animation), §4 (Lesen üben), §5 (Lese-Hilfe
inkl. Lupe), §6 (Stil-Analyse-Upload + Hände-Vergleich), §7 (Open-Data) und
dem Zweisprachig-Leitprinzip (DE/EN). Ergänzt
architektur.md §16.
Kernprinzip: anyplot-Stil — das gleiche Setup wie
~/projects/anyplot/app/, das anyplot.ai auf Cloud Run trägt. Eine SPA,
gemeinsam für Endnutzer und Admin, mit Auth-Gate für sensible Routen.
| Komponente | Version | Zweck |
|---|---|---|
| React | 19.x | UI-Framework. Server Components nicht genutzt — wir bleiben Client-Only. |
Vite (mit @vitejs/plugin-react-swc) |
8.x | Build-Tool, schneller HMR. |
| MUI + Emotion | 9.x | Komponenten-Bibliothek. |
| React Router | 7.x | Client-Side-Routing. |
react-helmet-async |
geplant (P1) | SEO-Meta-Tags pro Route (Title, Description, Open Graph). Noch nicht installiert. |
react-i18next |
geplant (P1) | Internationalisierung DE/EN. Noch nicht installiert (siehe i18n unten). |
| TypeScript | 6.x | Typsicherheit. |
vite-plugin-compression2 |
geplant (P1) | Gzip + Brotli-Pre-Compression. Noch nicht installiert. |
Package Manager: npm (wie heute im Repo — app/package-lock.json ist
checked in; anyplot nutzt yarn, wir bewusst nicht).
Begründung gegen andere Stacks (Verworfen-Sektion):
- Astro mit Islands-Architektur — wäre für die SEO-Inhaltsseite ein
besserer Fit, aber das jetzige
/app/ist schon Vite+React+MUI. Ein zweiter Stack wäre Pflegeaufwand. SEO ist mitreact-helmet-async+ Googles JS-Rendering tragbar. - Next.js — Vercel-zentriert, Cloud Run möglich aber Reibung. Größerer Footprint. Für uns keine RSC-Bedarfsfall.
- Reine SSG — Inhalt ist teilweise dynamisch (User-Renders, Stil-Analyse, HTR-Job-Status). SSG-only ohne Client-State wäre Brokerei.
So liegen die Routen heute im Code (app/src/routes/paths.ts +
routes/sections/public.tsx; die IA mit den drei Bereichen und den zwei
Hub-Seiten ist in design-system.md §6
festgelegt). Die Pfade sind ohne Sprachpräfix notiert; im Ziel-Design
(P1, siehe i18n unten) wandern sie unter /de/… (Default) bzw. /en/… —
die englischen Slug-Varianten werden mit dem locales/en/-Bundle
definiert (P1-Arbeit).
| Pfad | Inhalt | Bereich |
|---|---|---|
/ |
Landing (der Hero schreibt das Markenwort font-first: GLKurrent + Clip-Path-Reveal, Engine-Naht offen — §7) | Einstieg |
/schriftkunde |
Überblick der deutschen Schreibschriften (der umbenannte frühere /lehrbuch) |
Schriftkunde |
/lesen |
Hub → Quiz, Tafel | Lesen |
/quiz |
Lese-Quiz (Buchstaben + ganze Wörter) | Lesen |
/tafel |
Schreibtafel (Vorlage) | Lesen |
/schreiben |
Hub → Übungsblatt, Federprobe | Schreiben |
/schreiben/uebungsblatt |
Übungsblatt-Generator (Lineatur-Konfigurator, PDF) | Schreiben |
/federprobe |
Live-Schreiber (Sütterlin-Synthese mit generierten Übergängen) | Schreiben |
/impressum |
Impressum, Datenschutz, Quellen | Footer |
/lehrbuch |
Redirect → /schriftkunde (alter Name) |
— |
Noch nicht gebaut — Ziel-Routen aus der Vision, kommen mit den Post-MVP-Phasen (architektur.md §10):
| Pfad | Inhalt | Vision-Bezug | Status |
|---|---|---|---|
/lernen |
Einstieg (Geschichte, Alphabet-Tafel, Lese-Regeln) | §1 | geplant (P1+) |
/animation |
Animierte Buchstaben-Tafel | §3 | geplant |
/lesen-ueben |
Beliebiger Text → Kurrent-Rendering | §4 | geplant (P2) |
/lese-hilfe |
Upload historischer Brief → HTR-Job | §5 | geplant (P1) |
/lese-lupe/:job |
Lese-Lupe für transkribierten Brief | §5 | geplant (P1+) |
/stil-analyse |
Upload Schrift-Probe → Statistik-Report | §6 | geplant (P3) |
/vergleich |
Hände vergleichen mit Heatmaps | §6 | geplant (P4) |
/open-data |
Daten-Export-Seite mit DOI-Verweis | §7 | zurückgestellt (Open-Core, architektur.md §17) |
/glossar |
Erklärungen (Rund-s, Ligaturen, Schwellzug…) | §1, §5 | geplant |
| Pfad | Inhalt | Status |
|---|---|---|
/admin/chart |
Bbox-Editor auf Source-Chart; enthält den Einrichtungs-Wizard (Dialog, einzige Autoren-Fläche inkl. Stylus-Trace) und das Diagnose-Modal (3-Spalten + M4-Fit) | existiert |
/admin/vergleich |
Vergleichsansicht: jedes autorisierte Zeichen als Chart-Crop vs. „wie geschrieben", nebeneinander oder überlagert (sections/admin/compare/GlyphComparison.tsx) |
existiert |
/admin/sources |
Source-Verwaltung | post-MVP |
/admin/jobs |
HTR-Job-Monitor (Quote-Übersicht) | post-MVP |
Die Routen-Migration ist erfolgt (Restructure 2026-06): / ist die
öffentliche Landing, der Chart-Editor liegt unter /admin/chart
(routes/paths.ts). Eine eigene /admin/edit/:glyphKey-Route gibt es
nicht — die frühere EditorPage wurde durch Wizard + Diagnose-Modal
innerhalb von /admin/chart ersetzt.
Status: Ziel-Design für P1, noch nicht eingebaut. Ist-Stand:
react-i18next ist nicht installiert; alle deutschen UI-Strings liegen
als Pre-i18n-TS-Namespaces unter app/src/locales/de/, das Routing kennt
keine Sprachpräfixe. Der Rest dieses Abschnitts beschreibt das Soll.
react-i18nextals Library.- URL-Präfix:
/de/...(Deutscher Default) und/en/...(Englisch). Verwerfen: Cookie-basiert (schlechte UX bei Link-Sharing), Domain-Switch (Hosting-Komplexität). - Lazy-Loaded Locale-Bundles pro Sprache — JSON-Dateien unter
app/src/locales/{de,en}/....
// app/src/i18n.ts (Skizze)
i18n
.use(LanguageDetector)
.use(HttpBackend) // JSON-Files lazy laden
.use(initReactI18next)
.init({
fallbackLng: 'de',
supportedLngs: ['de', 'en'],
detection: { order: ['path', 'htmlTag', 'navigator'] },
interpolation: { escapeValue: false },
});- MVP (DE only, Ist-Stand): alle Strings nur in
locales/de/(als TS-Namespaces, noch ohne i18n-Library); keine Sprachpräfixe im Routing. - P1+ (EN folgt):
locales/en/füllen. Reihenfolge: Lese-Hilfe-UI zuerst (Genealogie-Zielgruppe), dann Inhalts-Seiten (Einstieg, Glossar). - Hilfetexte und Pitch-Texte bleiben in den Page-Komponenten als
Trans-Keys; technische Strings (Button-Labels, Validierungen) kommen in
ein gemeinsames
common.json.
Jede Page-Komponente setzt eigene Meta-Tags:
<Helmet>
<title>Lese-Hilfe — kurrentschrift.ink</title>
<meta name="description" content="Historische Briefe transkribieren …" />
<meta property="og:image" content="/og/lesehilfe.png" />
<link rel="alternate" hreflang="de" href="https://kurrentschrift.ink/de/lese-hilfe" />
<link rel="alternate" hreflang="en" href="https://kurrentschrift.ink/en/reading-help" />
</Helmet>- Google: rendert JavaScript, indexiert SPAs mit aktualisierten
Meta-Tags zuverlässig. Reichweite für unsere Hauptzielgruppen (Genealogie
- Lernende) gegeben.
- Bing, DuckDuckGo, andere: lesen Meta-Tags ohne JS-Rendering — daher
ist
react-helmet-asyncPflicht, nicht Kür. - Social-Sharing (Open Graph): wird über
react-helmet-asyncgleich miterledigt.
public/sitemap.xmlstatisch generieren beim Build (Vite-Pluginvite-plugin-sitemapoder eigenes Script).- Aufnehmen: alle öffentlichen Routen × beide Sprachen.
Sollte sich später herausstellen, dass SEO-Indexierung doch zu schwach ist (z.B. niedrige Rankings bei langem Inhalt), Migration möglich auf:
- vite-ssg (statische Pre-Renderung der Inhalts-Routen) — kleine Migration, gleicher React-Code.
- Astro mit React-Islands — größere Migration, eigentliche SEO-First-Lösung.
Beide bleiben als Optionen dokumentiert, sind aber kein MVP-Pfad.
Wie anyplot (anyplot/api/routers/debug.py:require_admin):
- Cloudflare Access verifiziert Google-Identity am Edge.
- Cloudflare leitet Request mit
Cf-Access-Jwt-Assertion-Header an Cloud Run weiter. - FastAPI verifiziert das JWT (Issuer, Audience, Email-Allowlist).
- Bei Erfolg: Endpoint freigegeben.
Vorteile:
- Auth-Komplexität an die Edge ausgelagert.
- Keine Cookies, kein Session-Management im Backend.
- Google-Login Out-of-the-Box.
api/auth.py:require_admin akzeptiert neben dem CF-Access-JWT einen
X-Admin-Token-Header als Shared-Secret-Fallback (lokale Entwicklung /
CI / Break-Glass): ADMIN_TOKEN im API-Env, das passende
VITE_ADMIN_TOKEN im SPA-Env. Ist keiner der beiden Pfade konfiguriert,
beantwortet das Gate jeden geschützten Request mit 503 — ein
fehlkonfiguriertes Prod-Deploy schlägt geschlossen fehl statt offen.
Wenn wir Cloudflare gar nicht im Stack haben wollen, ist GCP IAP die äquivalente Lösung — IAP-Header werden statt CF-Access-Header gelesen. Funktionsweise identisch.
- Admin-Routen sind im Router-Tree als geschützt markiert.
- Auf 401/403 zeigt die App eine „Sign-in"-Seite mit Redirect zum Auth-Provider.
- Existierende
DebugPage.tsx-Logik in anyplot als Vorlage.
cd app && npm install && npm run build→ statischesdist/mit JS-Chunks + Assets.- Geplant (P1, noch nicht in
vite.config.ts):- Manual-Chunks wie anyplot —
mui-iconsseparat (large, oft gecached),mui(MUI + Emotion),vendor(React + Router). - Gzip + Brotli pre-compressed via
vite-plugin-compression2.
- Manual-Chunks wie anyplot —
Zwei Services, live seit 2026-05:
kurrentschrift-api— FastAPI (api/Dockerfile);api/cloudbuild.yamlfährt vor dem Rollout einen Alembic-Migrate-Job (kurrentschrift-migrate).kurrentschrift-app— statisches Vite-Build hinter nginx-unprivileged (app/Dockerfile+app/cloudbuild.yaml).- CI/CD: Cloud Build, je ein Trigger pro Service (deploy-api /
deploy-app), deployt aus
main. - Region: europe-west4 — niedrige Latenz für deutschsprachige Hauptzielgruppe.
- Min instances: 0 (Cold-Start akzeptabel für eine Lern-Webseite).
- Memory: API 1 Gi (für FastAPI + WeasyPrint + ggf. TrOCR später 2–4 Gi), App 512 Mi.
/api/*→ der Cloudflare-Worker vor dem App-Service leitet aufapi.kurrentschrift.ink(FastAPI) um; nginx im App-Container kennt kein/api(siehe Kopfkommentarapp/nginx.conf)./admin/*→ React-SPA (Auth-Gate am Edge via Cloudflare Access, §5).- alles andere → React-SPA mit Fallback
index.html(nginx).
Struktur seit dem Restructure (2026-06): routes/ (Pfad-Konstanten +
lazy Public/Admin-Sections) · pages/ (dünne Route-Mounts) · sections/
(Feature-Views mit Logik) · components/ (wiederverwendbar) ·
layouts/admin/ · theme/ (Farbwahrheit in styles/paper.ts) ·
lib/api/ (Fetch-Client mit Cold-Start-Retry + typisiertem ApiError,
Wire-Typen handsynchron zu api/schemas.py) · domain/glyphs.ts
(Alphabet-Registry + Lock/Split-Helfer) · context/AdminContext.tsx ·
locales/de/ (alle deutschen UI-Strings als Pre-i18n-Namespaces) ·
hooks/.
routes/index.tsx— Router-Assembly (Suspense-Fallback, errorElement);routes/paths.tsist die einzige Quelle der URLs.sections/landing/—LandingView+HeroWritten(GLKurrent-Schreib- Animation, Font-first mit offener Engine-Naht) +Reveal(Scroll-Reveal).sections/schriftkunde/— der/schriftkunde-Überblick (Grundbegriffe, drei Ausgangsschriften mit Specimen, Federn, Tinte, Chronologie).sections/hub/—HubView(die/lesen- und/schreiben-Bereichs-Hubs).sections/worksheet/—WorksheetView+ConfigPanel+PreviewSvg(Lineatur-Konfigurator,/schreiben/uebungsblatt).sections/scribe/— der/federprobe-Live-Schreiber (Text → serverseitig komponiertes Wort,WrittenWord).sections/tafel/— die/tafel-Schreibtafel (Vorlage-Zeilen „wie geschrieben").sections/quiz/—QuizView+useQuizEngine(gesamte Quiz-Logik ohne JSX) + Setup/Play/Results-Panels +QuestionVisual.sections/impressum/— Impressum/Datenschutz/Quellen als Dokumentspalte.sections/admin/chart/—ChartView(Pointer-Routing) +useChartViewport(Zoom/Pan/Pinch) +useBboxEditing(Bbox-Commits, Lock) +BboxOverlay/ChartToolbar+ purebboxGeometry.sections/admin/setup-wizard/—SetupWizard(Dialog-Shell) +useWizard(State + Server-Mutationen) +useCropView(Crop-Viewport) +WizardCanvassteps/{Mask,Lineatur,Slant,Trace,Overview}Step. Einzige Autoren-Fläche.
sections/admin/diagnostics/—DiagnosticDialog(3-Spalten + M4-Fit),DiagnosticView/FitView.sections/admin/compare/—GlyphComparison(/admin/vergleich): jedes autorisierte Zeichen als Chart-Crop vs. „wie geschrieben", nebeneinander oder überlagert.sections/admin/sidebar/GlyphSidebar.tsx— Buchstaben-Grid ausdomain/glyphs.ts.components/—PaperBackground(Papier-Atmosphäre),PublicHeader(3-Bereiche-Nav),PublicFooter,PageContainer(eine Inhaltsspalte, drei Breiten 760/1152/1280),Prose(Lesemaß ~66 Zeichen),PageHeader(einheitlicher Seitenkopf: Bereichs-Eyebrow + Playfair-Titel + Intro),CategoryHeading(Abschnittstitel mit Viridian-Kurrent-Initiale),InfoHint(Kurrent-„i"-Popover, die eine Info-Affordanz app-weit),inkReveal/(geteilte „wie geschrieben"-Primitiven: Silhouette, maskiert von einer gesweepten Centerline + Ink-Bleed/Settle),WrittenGlyph(ein Glyph als Duktus-Animation),WrittenWord(ganzes Wort/Zeile, serverseitig komponiert viaGET /write/word),BootStatus(Boot/Fehler-Screens). Vollinventar mit Kern-APIs:design-system.md§7.
Bereits gebaut (siehe oben): Landing (sections/landing/),
Schriftkunde (sections/schriftkunde/), die Bereichs-Hubs
(sections/hub/), Lineatur-Konfigurator (sections/worksheet/,
/schreiben/uebungsblatt), Federprobe (sections/scribe/), Schreibtafel
(sections/tafel/), Lese-Quiz (sections/quiz/). Neue Features
kommen als je eine
sections/<feature>/-View + dünner pages/-Mount + Eintrag in
routes/paths.ts:
sections/learn/— Einstieg (P1+).sections/animation/— Animierte Tafel (P1+).sections/render/— Text → Kurrent (P2).sections/htr/— Upload + Job-Polling (P1) und Lese-Lupe (P1+).sections/style-analysis/— Stil-Analyse-Upload (P3).sections/hand-compare/— Heatmaps Side-by-Side (P4).sections/open-data/— Daten-Export-Seite (P5).components/GlyphAnimation— abgespeckte MVP-Animation (heute schon alsWrittenGlyphim Quiz).components/KurrentRenderer— Text → SVG-Render.components/HeatmapView— D3.js-Heatmap-Komponente.components/IiifViewer— Annotorious + OpenSeadragon wrapper.
- Keine eigene Komponenten-Bibliothek. MUI 9 deckt unsere
UI-Bedürfnisse. Custom-Komponenten nur dort, wo es unvermeidbar ist
(Animation, Lineatur, Heatmap). Die verbindliche Bauvorschrift
(Tokens, Typo-Leiter, Flächen) ist
design-system.md— das ist ein Regelwerk ÜBER MUI, keine eigene Bibliothek. - Kein State-Management-Framework (Redux/Zustand/Recoil). React-Context
- lokaler Component-State reichen für unsere Use-Cases.
- Keine GraphQL-Schicht. REST über FastAPI ist genug.
- Kein Service Worker / PWA-Modus im MVP. Kann später als progressive Erweiterung kommen.
- React 19 Release Notes
- Vite Docs
- MUI v9
- React Router 7
- react-helmet-async
- react-i18next
- Astro vs Next.js 2026 — alexbobes.com (Vergleich, nicht verwendet)
- Cloudflare Access
- GCP Identity-Aware Proxy
- Cloud Run
- anyplot.ai-Repo (interner Maintainer; bewusst gleicher Stack)