Statusbericht · N0TB0T/n3 · Stand 2026-07-06 · Tag v0.1.2

n3 — Farben · Fonts · Shapes

Ein Design-Token-Modul auf Material 3-Basis: eine plattformneutrale Spec, zwei bit-identische Renderer (Dart, TypeScript), eine Verwaltungs-App. Dieser Bericht fasst zusammen, was steht, was offen ist, was verworfen wurde und wo es hakt — ohne Marketing, mit Belegen.

→ kompakte Fassung (mobil)

5
Packages
95
Tests grün
103
Preset-Paletten
3
Produktiv-Consumer
v0.1.2
Aktueller Tag
~12.6k
LOC (Quelle)

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.

core/specJSON-Schemas · Paletten · Type-/Shape-Scales · Extra-Tokens
n3_flutterkanonische Dart-Engine
@n3/webTS-Spiegel, golden-getestet
N3 Labeinzige Schreib-Oberfläche

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.

PhaseWasStatus
R-1Repo + Monorepo-Skelett (core / web / flutter / lab)erledigt
R-2 / R-2bFlutter-Extraktion aus konrektor, konrektor auf n3_flutter-Dependency umgestellterledigt
R-3core/-Spec + Paletten-Migration + Extra-Token-Registryerledigt
R-4Web-Version — TS-Port der Engineerledigt
R-5N3 Lab — Verwaltungs-App mit Schreibrechtenerledigt
R-6datada-Integration (Benchmark: Engine + Panel in Web & Flutter)erledigt
R-7PayMore-Migration — voller Engine-Swap, _kColor* → Extra-Tokenserledigt
R-8Fiddle-Integrationumformuliert
R-9Typo- + 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

34-Rollen Color-Schemegolden-getestet

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 schemeFromPalette(palette, brightness)
ts deriveScheme(palette, isDark)
Cross-Language-Goldenbit-identisch

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).

Typografie- + Shape-EngineR-9

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.

ts deriveTypography · deriveShapes · typographyCss · shapesCss

Palette-Werkstatt PW-1…PW-7

Fidelity-Fix + Import

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.

Batch + Live-Test

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.

Tags statt Ordner

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.

App→Spec-Rückwegn3-Seite

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).

Lab →n3:palette — Farbrolle
Lab →n3:shape — Corner-Radien
Lab →n3:token — Extra-Token (App-scoped)
Lab →n3:outline — Highlight-Kontur (opt-in)
App →n3:ready — Handshake, damit nichts verloren geht
Highlight für alle drei Domänen

Eine 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.

Self-contained BundleDH-1

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.

LS-2 / LS-3

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.

CI-2 / CI-3

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.

F1-PA1

Komplett-Überarbeitung des Palettes-Tabs — einzig legitim wartendes Item, bis das angekündigte User-Design vorliegt.

blockiert auf: User-Design

R-5b

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

R-11 / PW-7

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.

Screenshot des alten m3-palette-lab-Tools: Batch-UI mit Seed-Zuordnung, Hue-Shift und Neutral-Threshold
Vorgänger-Tool (Screenshot aus dem Repo-Archiv) — die Batch-Logik ist die Vorlage für die heutige Palette-Werkstatt, die UI selbst wurde nicht übernommen.

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.

1 — Rolle, nie Hex.

Jede Komponente referenziert eine semantische Rolle (var(--n3-primary)), nie einen Hex-Wert. Ändert sich die Palette, zieht alles automatisch nach.

2 — Paar-Regel.

Jede onX-Rolle ist der garantiert lesbare Vordergrund zu X. Kontrast wird nie geraten, immer als Paar verwendet.

StackWegBeispiel
Flutter-Appn3_flutter nativ, schemeFromPalettePayMore, brokay
Vite / Astro@n3/web nativ, deriveSchemedatada, malemedia26
Roher Worker / esbuildself-contained Bundle (n3-web.mjs)notbot-devhub
Plugin / Release-PageRollout-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.

Native-only (Gesetz).

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.

App-Aufgaben leben im App-Backlog.

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.