01 Überblick
n3 verwaltet Farbpaletten, Typografie-Scales und Shape-Scales zentral und leitet daraus vollständige Material 3-Themes ab — für Flutter- und Web-Apps gleichermaßen, aus derselben Quelle.
Kernprinzip: eine Spec, n Renderer. core/ ist plattformneutral (JSON-Schemas + gespiegelte Dart-/TS-Bindings); die Dart-Engine (n3_flutter) ist kanonisch, @n3/web spiegelt sie bit-identisch nach (Cross-Language-Golden-Tests, §03). Das N3 Lab ist die einzige Schreib-Oberfläche auf die zentrale Spec; Consumer-Apps ziehen sie versionsgepinnt und konsumieren die Engine nativ — nie über gebackene Hex-Werte.
Drei produktive Consumer beweisen das Modell an echten Apps: datada (Flutter + Web, Benchmark für R-6/R-9), PayMore (voller Engine-Swap, R-7) und brokay (Flutter, Livestyling-Pilot).
02 Stand — Roadmap
Neun große Phasen seit Projektstart (extrahiert aus konrektor). Acht sind erledigt, eine ist umformuliert statt abgehakt.
| Phase | Was | Status |
|---|---|---|
| R-1 | Repo + Monorepo-Skelett (core / web / flutter / lab) | erledigt |
| R-2 / R-2b | Flutter-Extraktion aus konrektor, konrektor auf n3_flutter-Dependency umgestellt | erledigt |
| R-3 | core/-Spec + Paletten-Migration + Extra-Token-Registry | erledigt |
| R-4 | Web-Version — TS-Port der Engine | erledigt |
| R-5 | N3 Lab — Verwaltungs-App mit Schreibrechten | erledigt |
| R-6 | datada-Integration (Benchmark: Engine + Panel in Web & Flutter) | erledigt |
| R-7 | PayMore-Migration — voller Engine-Swap, _kColor* → Extra-Tokens | erledigt |
| R-8 | Fiddle-Integration | umformuliert |
| R-9 | Typo- + Shape-Engine — die Dreieinigkeit komplettiert (a/b/c) | erledigt |
R-8 war als „harte Nuss" separat geplant. Erkenntnis unterwegs: Livestyling (Lab-Stage), Fiddle und Website-Tuning sind dieselbe Operation gegen unterschiedliche Render-Ziele — R-8 ist jetzt Teil der Livestyling-Arbeit (§03, LS-2), keine Extra-Baustelle mehr.
03 Fertig — Features
Gruppiert nach Arbeitsbereich. Jede Karte nennt die wichtigsten Dateien/Exports, damit sich Behauptung und Code direkt abgleichen lassen.
Engine & Parität
Multi-Seed-HCT-Ableitung (Primary/Secondary/Tertiary/Neutral) → vollständiges M3-ColorScheme. Optionaler tone pro Seed für originalgetreue Akzente, ohne die 34 Rollen zu verschieben.
Dart generiert Referenz-Hexes, TS leitet dieselben Fixtures ab und vergleicht jede geteilte Rolle. mcu-dart 0.13.0 ↔ mcu-npm 0.4.0 liefern identische Werte — nachdem der Seed-Round-Trip nachgezogen wurde (siehe Pitfalls, §06).
Offene, benannte Font-Registry + Style-Set (M3-15 vorbelegt, erweiterbar) und offene Radius-Scale (M3-7 vorbelegt). Keine Fallback-Stacks — fehlende Schrift ist ein sichtbarer Fehler, kein stiller Ersatz.
Palette-Werkstatt PW-1…PW-7
101 Presets neu aus Original-Hexes migriert (Seeds als {hue, chroma, tone}, 34 Rollen unverändert). Import beliebiger Sammlungen: Paste-Hex-Liste, Bild-Extrakt, Datei — ohne Zwang auf vier Seeds.
Konfigurierbares Regelwerk (Quelle → Ziel + Transform) rechnet eine rohe Farbliste in n Paletten um. „⚡ Live" streamt jede Änderung sofort auf die eingebettete App im Lab-Stage — Batch-Einstellungen lassen sich am echten Look durchklicken.
Freie Labels (App-IDs/Gruppen, z. B. paymore/all) auf Palette, Type- und Shape-Scale. Ein Projekt-Tag zieht das volle Bündel — kein separater Namespace pro App.
Lab-Sidebar „From app" liest app-getunte Themes per syncKey (Firestore REST, App-seitiges Firebase-Mirror), wandelt Color→N3 und promotet zur Auswahl. Nur das Lab schreibt zurück in core/spec.
Bild-Farb-Extraktion IMG-1 / IMG-2
Bild laden (Datei/Drag&Drop/Paste, nur in-memory Canvas, nie persistiert) + Pipette mit Lupe/Zoom. Median-Cut-Quantisierung extrahiert 3–8 dominante Farben automatisch, direkte Übergabe an die Palette-Werkstatt zur Zuweisung auf Seeds/Rollen.
Livestyling & Highlight LS-1 · LS-4 · LS-5 · LS-6
Der Lab-Stage bettet eine laufende App in einem iframe/Flutter-Fenster ein und streamt das komplette Theme bei jeder Änderung. Ein Consumer bindet sich mit wenigen Zeilen an (connectN3LiveStyle() web-seitig, N3LiveTheme/N3LiveScope Flutter-seitig).
n3:palette — Farbrollen3:shape — Corner-Radienn3:token — Extra-Token (App-scoped)n3:outline — Highlight-Kontur (opt-in)n3:ready — Handshake, damit nichts verloren gehtEine Rolle/einen Corner/einen Token im Lab hovern → nur dieser wird in der laufenden App auffällig. Für Farbe: umfärben. Für Shape: Radius aufblähen (Default, kostet den Consumer nichts) oder als Kontur (Opt-in, braucht einen dormanten CSS-Hook).
Nebeneffekt: zeigt ein Highlight nichts, ist die Primitive nicht an eine n3-Var gebunden — das macht hartkodierte Werte im Consumer sichtbar, ohne dass es je ein „Audit"-Feature war.
web/bundle/n3-web.mjs — Vite-Build mit material-color-utilities + @n3/core inlined. Löst den mcu-ESM-Gotcha (§06) für jeden rohen Worker/esbuild-Consumer, ohne App-Sonderweg.
Sonstiges
Server-side Write (R-11). Das Lab schreibt nicht mehr per Browser-Token — eine Cloudflare Pages Function hält den GitHub-Token serverseitig und verifiziert das Cloudflare-Access-JWT. Kein Token im Browser mehr.
Lab-Feedback-Runde 1. Konfigurierbarer Farbwähler (inline, an jeder Farbe — wie datadas „coors"), echte Google-Font-Vorschau, Rollen-Referenzen im Font-Dropdown, native Controls gestylt (Light + Dark). Einzig offen: Palettes-Tab wartet auf User-Design (§04).
04 Offen
Was n3 selbst noch schuldet — App-spezifische Anschluss-Arbeit (z. B. brokays Shape-Bindung) lebt bewusst in den jeweiligen App-Backlogs, nicht hier.
Fiddle als Livestyling gegen ein mitgeliefertes Mock-Ziel (statt einer echten App) — vereinheitlicht R-8. Dazu eine Theme-Scope-Liste, mit der eine App mehrere benannte Scopes fürs Lab meldet.
Ein benanntes Rollout-Default-Theme (NTBT_STD) für theme-lose Produkte. Consumer-Doku vervollständigen: Flutter-Beispiele für Typografie/Shapes/Extra-Tokens, N3ThemeScope in die API-Doku, eine Conformance-Checkliste vor Release.
Komplett-Überarbeitung des Palettes-Tabs — einzig legitim wartendes Item, bis das angekündigte User-Design vorliegt.
blockiert auf: User-Design
Geteilter Store über mehrere Apps & Geräte. Das Repo-Modell (ein gemeinsamer, asynchroner Schrank) trägt private Multi-Device-User-Daten nicht sauber — eher ein user-scoped Backend als git-Commits. Braucht eine Architektur-Entscheidung.
blockiert auf: Architektur-Entscheidung
Zwei Live-Verifikationen stehen noch aus: der Publish-Flow einmal komplett im Browser durchklicken; der App→Spec-Rückweg gegen einen echten syncKey (Fetch + CORS) statt nur gegen Mocks.
blockiert auf: Geräte-/Live-Test
Kleinere offene Design-Punkte: Highlight-Konsolidierung (Stage- vs. In-App-Implementierung), Konrektor*→N3*-Rename, Extra-Token-Rechte-Vergabe jenseits des Labs, Minimal-/Vollset-Flag-API.
05 Verworfen / abgelöst
Drei Ansätze, die es in den Code schafften und bewusst wieder verlassen wurden — nicht stillschweigend, sondern mit dokumentiertem Nachfolger.
m3-palette-lab.html abgelöst
Das alte, eigenständige Kurations-Tool (Quelle von konrektors preset_palettes.dart) existiert lokal nicht mehr. Seine Batch-Mechanik (Slot→Ziel-Zuordnung, Hue-Shift, „Fold in Neutral") lebt als Referenz weiter und wurde in die Palette-Werkstatt (PW-3/PW-5) überführt — mit echter Live-Vorschau statt statischem Export.
GitHub-PAT-Trigger für App-Promote
Ursprünglicher Plan für PW-7: ein Fine-grained-Token in jeder App hinterlegen, das per repository_dispatch einen Promote in n3 anstößt. Verworfen zugunsten eines token-losen Wegs — das Lab liest app-getunte Themes direkt aus dem bestehenden Firebase-Mirror der App (Firestore REST, öffentlicher Web-Key), kein App-seitiges Secret mehr nötig.
Browser-seitiger PAT fürs Lab-Schreiben
Vor R-11 hielt jeder Lab-Browser sein eigenes GitHub-Token (Contents-API ist CORS-fähig, der OAuth-Web-Flow aber nicht). Abgelöst durch eine Cloudflare-Pages-Function, die den Token serverseitig hält und nur ein Access-JWT verifiziert — kein Secret mehr im Browser, ein Login pro Gerät statt ein Token pro Gerät.
06 Pitfalls & Gotchas
Jeder Punkt hat einen konkreten Auslöser und eine gefundene Lösung.
mcu-npm ist invalides Node-ESM verifiziert
@material/material-color-utilities (0.4.0) nutzt extension-lose interne Imports → nacktes Node/esbuild scheitert, nur Vite/Rollup/vitest inlinen es. Deshalb: web/vitest.config.ts mit server.deps.inline, und ein eigenes self-contained Bundle (DH-1) für rohe Worker-Consumer. Nebenbefund: Variant.tonalSpot ist als Property undefined (numerisch 2 nutzen); mcu nennt eine Rolle inverseOnSurface, n3/Flutter onInverseSurface.
Seed-Round-Trip — bis zu 85/255 Kanal-Abweichung ohne ihn
Jedes {hue, chroma} muss erst zu Color@tone50 und zurück zu HCT, bevor es eine TonalPalette treibt (seedFromHC/hctOf) — sonst weicht die Web-Engine trotz identischer mcu-Werte pro Kanal um bis zu 85 von 255 ab. Der Golden-Test hat das aufgedeckt, nicht Review.
M3-Textfarbe folgt nicht automatisch onSurface
Flutters Standard-textTheme färbt Text aus der Typography-Tabelle (schwarz/weiß je Brightness), nicht aus colorScheme.onSurface. Effekt: Text ignoriert untypische Paletten, und der Lab-Highlight einer onSurface-Rolle ließ Text unberührt. Fix: textTheme.apply(bodyColor: scheme.onSurface, displayColor: scheme.onSurface) — Widgets mit expliziter anderer Rolle gewinnen weiter über copyWith.
Ein Shape-Corner hat keine Farbe
Highlight für Farbrollen funktioniert per Umfärben; ein Radius lässt sich nicht „anders färben". Deshalb zwei Mechanismen: Radius-Aufblähen (Default, funktioniert automatisch über die ohnehin nötige --n3-radius-*-Bindung) und Kontur (Opt-in, braucht einen zusätzlichen, aber im Ruhezustand kostenlosen CSS-Hook). Wer nur Ersteres will, bindet den zweiten Hook einfach nicht.
Tags sind Organisation, keine Zugriffsrechte
tags auf Palette/Type-/Shape-Scale steuern, welches Bündel ein Projekt lädt — sie sind kein Namespace und kein Schreibschutz. „App-getunt" heißt: Palette trägt den App-ID-Tag, nicht: sie liegt in einem App-Ordner. Schreibrechte laufen separat über das Lab-„acting as"-Modell.
07 Integration — Kurzfassung
Vollständige Anleitung: docs/INTEGRATION.md im Repo. Zwei Regeln tragen den ganzen Guide.
Jede Komponente referenziert eine semantische Rolle (var(--n3-primary)), nie einen Hex-Wert. Ändert sich die Palette, zieht alles automatisch nach.
Jede onX-Rolle ist der garantiert lesbare Vordergrund zu X. Kontrast wird nie geraten, immer als Paar verwendet.
| Stack | Weg | Beispiel |
|---|---|---|
| Flutter-App | n3_flutter nativ, schemeFromPalette | PayMore, brokay |
| Vite / Astro | @n3/web nativ, deriveScheme | datada, malemedia26 |
| Roher Worker / esbuild | self-contained Bundle (n3-web.mjs) | notbot-devhub |
| Plugin / Release-Page | Rollout-Default (NTBT_STD) | toolboxx26 |
Zugriffsmodell: n3 wird versionsgepinnt gezogen (Submodule/ref), nie gepusht — ein neuer Look ist ein Pin-Bump + Rebuild in der App, nicht ein Commit von n3 aus.
08 Arbeitsweise
Zwei Regeln, die mehr über den Projektverlauf aussagen als jedes Feature.
Jeder Consumer zieht die Engine selbst — keine vorgebackenen Hexe, keine App-spezifischen Sonderlösungen. Kann ein Stack die Engine nicht direkt ziehen (mcu-ESM-Gotcha, §06), ist die Antwort ein wiederverwendbarer Stack-Adapter für alle Consumer dieses Stacks (das self-contained Bundle), nie ein Einzelfall-Fix.
Von n3 zieht eine App nur Dokumentation, nie Aufgaben. Umgekehrt trackt n3s Backlog nur n3-eigene Liefergegenstände — nicht, was eine App noch an ihre eigene Bindung anschließen muss. Dieser Trennschnitt wurde im Projektverlauf einmal zu eng ausgelegt (ein trivialer 3-Zeilen-Consumer-Hook lief unnötig über mehrere Session-Übergaben) und korrigiert: Schnittstellen-Änderungen liefert die Owner-Session in einem Zug — Vertrag und die mechanische Anschluss-Verdrahtung —, statt sie App für App anzufragen. Die formale Doku dazu ist als offener, aktuell zurückgestellter Punkt im internen Task-Board vermerkt.
09 Auffälliges
Subjektive Beobachtungen, keine Statusaussagen.
Der Seed-Round-Trip-Fehler (85/255 Kanalabweichung) wäre bei reinem Review vermutlich durchgerutscht — erst der bit-genaue Cross-Language-Golden-Test hat ihn sichtbar gemacht. Golden-Tests als Vertrag statt als Beispiel zu behandeln, hat sich hier konkret ausgezahlt.
Das Highlight-System ist als Debugging-Hilfe entstanden, wirkt aber wie ein beiläufiger Hardcode-Detektor: zeigt es nichts, ist etwas fest verdrahtet statt gebunden. Kein Feature wurde extra dafür gebaut — es fällt aus dem Design ab.
Radius-Aufblähen vs. Kontur ist eine saubere API-Entscheidung: der kostenlose Default deckt den Regelfall, die teurere Variante ist explizit opt-in — statt einer Konfiguration, die beide Kosten immer verlangt.