Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ jobs:
- name: Root runner tests
run: python3 -m unittest discover -s tests -t .
working-directory: additional/python
# MLA-Next #91: GTK token gate — tokens.css/tokens-dark.css consistent
# with docs/mla-next/TOKENS.md, WCAG-AA text pairs, no hardcoded colors
# in the shell. Plain Python, no GTK toolchain needed.
- name: GTK token gate
run: python3 -m unittest discover -s prototype/gtk/tests
# MLA-Next: the pure-Dart core package (#59/#60) runs its own gates with
# plain `dart` from the same SDK — no Flutter, no GTK toolchain, no
# display (issue #59 requires the compiled probe to run headless in CI).
Expand Down
165 changes: 165 additions & 0 deletions docs/mla-next/TOKENS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# MLA-Next Tokens — GTK4/libadwaita (Issue #91, A1)

> Stand: 2026-09-30 · Branch `feature/mla-91-tokens` · Plan:
> `docs/superpowers/plans/2026-09-30-mla-91-tokens.md`
>
> **Quelle der Wahrheit für Farbwerte:** `lib/layouts/hermes_tokens.dart`
> (light `:98-132`, dark `:135-162`). Diese Datei portiert sie 1:1 in den
> GTK-Track; Abweichungen sind nur dokumentiert, nie zurückgeportet.
> Umsetzung: `prototype/gtk/tokens.css` (Light + Klassen) +
> `prototype/gtk/tokens-dark.css` (Dark-Overrides), geprüft durch
> `prototype/gtk/tests/test_tokens.py` (Konsistenz, Kontrast, Hardcode-Gate).

## 1. Farb-Tokens (26, hell und dunkel)

Schreibweise: opake Farben als `#RRGGBB`, transparente als `rgba(r,g,b,a)`
(die Alpha-Stufen entsprechen `Color(0xAARRGGBB)` aus HermesTokens:
`0x0D`→0.051, `0x08`→0.031, `0x0A`→0.039, `0x0F`→0.059, `0x59`→0.349).

| Token | CSS-Name | Light | Dark | Verwendung |
|---|---|---|---|---|
| bg | `bg` | #FEFCF7 | #0D0D1A | Fensterhintergrund |
| sidebar | `sidebar` | #FAF7F0 | #141425 | Navigationsseitenleiste |
| surface | `surface` | #F3EEE3 | #1A1A2E | Karten, angehobene Flächen |
| surfaceSubtle | `surface-subtle` | #F7F4EC | #16162A | sekundäre Flächen, Badges (Neutral) |
| surfaceSubtleHover | `surface-subtle-hover` | #EFEADF | #1F1F35 | Hover-Zustand dazu |
| border | `border` | #E0D8C8 | #2A2A45 | Hairline-Border (1px) |
| borderMuted | `border-muted` | #D0C6B2 | #3A3A58 | stärkere Trennlinien |
| borderSubtle | `border-subtle` | #EAE4D8 | #20203A | zarte Trennlinien |
| text | `text` | #1A1610 | #FFF8DC | Fließtext |
| strong | `strong` | #0F0D08 | #FFFFFF | betonte Werte, Titel |
| muted | `muted` | #5C5344 | #C0C0C0 | Metadaten, Sekundärtext |
| accent | `accent` | #B8860B | #FFD700 | Golden — Akzentflächen, aktive Markierung |
| accentHover | `accent-hover` | #996F08 | #FFBF00 | Akzent-Hover |
| accentText | `accent-text` | #7F5C08 | #FFD700 | Akzentfarbe als Text (light 2 Stufen dunkler für AA) |
| accentBg | `accent-bg` | #F8F2E4 | #201D18 | Akzent-Fläche 8 % (vorgeblendet) |
| accentBgStrong | `accent-bg-strong` | #F1E7CE | #322D1D | Akzent-Fläche 15 % (vorgeblendet) |
| onAccent | `on-accent` | #1A1610 | #0D0D1A | Text/Icons auf Akzent (bewusst dunkle Tinte, nicht Weiß) |
| error | `error` | #C62828 | #EF5350 | Fehler, crit/failed |
| success | `success` | #2E7D32 | #4CAF50 | Erfolg, ok |
| warning | `warning` | #B45309 | #FFA726 | Warnung, warn |
| info | `info` | #05748F | #4DD0E1 | Information, running |
| hoverBg | `hover-bg` | rgba(0,0,0,0.051) | rgba(255,255,255,0.059) | generische Hover-Überlagerung |
| inputBg | `input-bg` | rgba(0,0,0,0.031) | rgba(255,255,255,0.039) | Eingabefelder |
| focusRing | `focus-ring` | rgba(184,134,11,0.349) | rgba(255,215,0,0.349) | sichtbarer Fokusring (35 % Akzent) |
| codeBg | `code-bg` | #F5F0E5 | #1A1A2E | Code-Blöcke |
| codeText | `code-text` | #8B4513 | #F0C27F | Code-Inhalt |

**Dark-Regeln (wie Hermes):** Semantische Farben (error/success/warning/info)
**hellen im Dunkelmodus auf** statt fix zu bleiben; der Akzent wechselt von
Golden `#B8860B` (light) zu `#FFD700` (dark); `onAccent` bleibt in beiden
Schemata dunkle Tinte. Elevation entsteht aus 1px-Border, nicht aus Schatten.

## 2. Struktur-Tokens

| Token | Wert | Verwendung |
|---|---|---|
| space1 | 4 | Innenabstände kompakt |
| space2 | 8 | Innenabstände Standard |
| space3 | 12 | Abstände Gruppen ↔ Elemente |
| space4 | 16 | Außenabstände Standard |
| space5 | 24 | **GTK-Zusatz** (kein Hermes-Pendant): Flächen-Rahmenabstand großer Fenster |
| radiusSm / radiusMd / radiusLg / radiusPill | 4 / 8 / 12 / 999 | Ecken; Pill für Badges |
| borderWidth | 1 | Hairline überall, keine Schatten |
| spineWidth | 2 | Akzent-Rücken aktiver Nav-Items |
| opacityFaint / opacityMuted / opacityStrong | 0.42 / 0.56 / 0.75 | Entwertung von Metadaten über Opacity, nie über andere Farbe |
| fontMono | `monospace` | generischer Alias (kein gebundelter Font) |
| layoutSidebarMin | 280 | Mindestbreite rechte Detail-Leiste |
| layoutPanePos | 850 | Startposition des Trenners (Gtk.Paned) |
| layoutWindow | 1200 × 780 | Fenster-Defaultgröße |

## 3. Typografie (Adw-Klassen als Typo-Tokens)

GTK nutzt die libadwaita-Stilklassen; Pixel-Parität zur Flutter-App ist kein
Ziel, **Rangstufen-Parität** schon. Adw-Größen sind Punkt-basiert und folgen
der System-Skalierung (100/125/150 %).

| Rang | GTK (Adw-Klasse) | Flutter (MintY/Hermes) |
|---|---|---|
| 1 | `title-1` | heading1, 32 px w500 |
| 2 | `title-2` | heading2, 24 px |
| 3 | `title-3` | heading3, 20 px |
| 4 | `title-4` | heading4, 17 px |
| Fließtext | `body` | paragraph, 15 px |
| Betont/Support | `heading` / `caption` | 12–13 px Support |
| Metadaten | `caption` (klein) + `opacityMuted` | 11 px uppercase w600 (Hermes-Stat-Tile-Stil) |
| Code/Werte | `fontMono` | `HermesTokens.fontMono` |

## 4. Status-/Ampel-Tokens (Tone-Mapping, keine neuen Farben)

Zustandsvokabular aus IPC_CONTRACT (`ok|warn|crit|unknown`) und ProbeState
(`unknown|running|ok|stale|failed`). Tone-Formel wie Hermes-Badges
(`hermes_badge.dart:25-52`): **fg = Basisfarbe solid, bg = Basisfarbe 10 %
auf `bg` vorgeblendet, border = Basisfarbe 28 % auf `bg` vorgeblendet.**

| Zustand | Basisfarbe | Struktur |
|---|---|---|
| ok | success | solide Border |
| warn | warning | solide Border |
| crit / failed | error | solide Border |
| running | info | solide Border |
| unknown | muted (Neutral-Tone: fg `muted`, bg `surfaceSubtle`, border `border`) | solide Border |
| **stale** | wie unknown | **gestrichelte Border** — Struktur- statt Farbunterschied |

**stale ≠ ok ist Strukturregel:** `stale` darf niemals wie `ok` aussehen —
der Unterschied ist formgebunden (gestrichelt) und damit farbenblindsicher
(«stale ist UI-/Transportstatus, nicht stillschweigend ok»,
IPC_CONTRACT.md:16).

## 5. Kontrast-Ziele (vom Task-Spec gesetzt, ISSUES.md #91 Abnahme 2)

- **Text-Paare: WCAG AA ≥ 4,5:1** — Paarliste analog
`test/hermes_tokens_test.dart:11-28`, je Schema (light/dark) 16 Paare:
text/bg, text/surface, text/surfaceSubtle, text/sidebar, strong/bg,
muted/bg, muted/surfaceSubtle, accentText/bg, accentText/accentBg,
accentText/accentBgStrong, onAccent/accent, error/bg, success/bg,
warning/bg, info/bg, codeText/codeBg.
- **focusRing (Non-Text): bekannte, dokumentierte Schwäche mit Hermes-Parität**
— kompositiert (35 % Akzent auf bg) ≈ 1,4:1 (light) bzw. ≈ 2,5:1 (dark),
also unter dem 3:1-Wert von WCAG 2.4.11 (Focus Appearance, AA erst ab
WCAG 2.2 gefordert). Der Wert kommt 1:1 aus `hermes_tokens.dart`
(Werte-Parität geht vor); Kompensation: 2-px-Outline mit 2-px-Offset, und
die Sichtbarkeit am lebenden System ist Teil der manuellen Gate-0-Prüfung
(§6, Basti). `test_tokens.py` rechnet beide Verhältnisse nach und pinnt
sie unter 3:1 fest — die Doku behauptet nichts, was der Rechner widerlegt.
- Geprüft maschinell in `prototype/gtk/tests/test_tokens.py` (WCAG-2.2-
Luminanz/Formel wie `hermes_tokens.dart:204-224`); Alpha-Farben werden vor
der Prüfung auf bg kompositiert.

## 6. Fokus-Regeln

- Jeder fokussierbare Bereich (Sidebar-Rows, Buttons, Eingaben, Details)
zeigt einen **sichtbaren Fokusring**: `:focus-visible`-Outline in
`focus-ring` (35 % Akzent), 2 px, Abstand 2 px.
- Vollständige Tab-/Pfeiltasten-Reihenfolge; der Durchgang am lebenden System
ist Teil der manuellen Gate-0-Checks (BASELINE §3 Punkt 6, Basti).

## 7. Screenshot-Regeln

- Screenshots nur als `/tmp`-Artefakte, **nie ins Repo**; ohne Secrets
(Fixtures/Demo-Daten only).
- Hell/Dunkel erzwingbar über `MLA_FORCE_COLOR_SCHEME=light|dark`
(Test-Affordance, wirkt nur innerhalb der App über
`Adw.StyleManager.set_color_scheme`; keine Systemeinstellung).
- X11 maschinell (xdotool+import, Rezept BASELINE §2); **Wayland-Screenshot
bleibt manuell** (gnome-screenshot fehlt, D-Bus verweigert; BASELINE §6.2).

## 8. Anwendung in der Shell

**Mechanismus:** `tokens.css` (Light-Werte + CSS-Klassen) läuft immer mit;
`tokens-dark.css` (nur Dark-`@define-color`-Overrides) wird über einen
zweiten `Gtk.CssProvider` mit höherer Priorität zugeschaltet bzw. entfernt,
wenn `Adw.StyleManager` `notify::dark` meldet. Grund: GTK 4.14.5 unterstützt
kein `@media` in provider-geladenem CSS (Parser: «Unknown @ rule», verifiziert
2026-09-30) — der Plan-Fallback (Provider-Tausch) ist damit der Mechanismus.

CSS-Klassen mit Präfix `mla-` — semantische Klassen, deren Padding-/Border-Werte die Struktur-Tokens mit px-Kommentar anwenden (z. B. `.mla-nav-label` = 12/16 px = space3/space4; keine generischen `.mla-space-*`-Utility-Klassen):
`.mla-screen` (Seitenrahmen), `.mla-details` (rechte Leiste),
`.mla-chip` + `.mla-tone-ok` … `.mla-tone-stale` (Status; stale mit
gestrichelter Border), Fokus-Outline global über `:focus-visible`. Für
Abstände gelten padding/margin-Klassen; `Gtk.Box`-`spacing`, Paned-Position
und Fenstergröße sind dokumentierte Tokens (§2), die im Code Anwendung finden
(GTK bietet dafür kein CSS-Äquivalent). Keine Hex-Farben außerhalb der
Token-Dateien — durchgesetzt per Regex-Gate in `test_tokens.py`
(vormaliger Verstoß `#b8860b` in `mla_app.py:84` ist mit diesem Paket
beseitigt).
40 changes: 40 additions & 0 deletions docs/mla-next/VERIFY.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,46 @@ unabhängig reproduziert).

**Grenzen (bewusst offen).** Die #92-Abnahme bleibt formal offen (GTK-Datenadapter-Rest) und ist im §8-„Nicht verglichen“ benannt, nicht weggebügelt; die Flutter-vs-GTK-Entscheidung ist 0.4.x-Aufgabe (#105 nimmt diese Basis auf).

## Handoff #91 — A1 Tokens & UI-Design (GTK-Shell)

**Status:** Automatisierbarer Teil umgesetzt auf `feature/mla-91-tokens` (Basis-SHA `506eb88`; Plan-Commit `f8a080e`, Token-Spec `04a0c7d`, Shell-Umstellung `cfff063`, Token-Gate + CI `134b7a7`, Fenster-bg-Fix `eb81fbf`, dieser Abschnitt). Plan: `docs/superpowers/plans/2026-09-30-mla-91-tokens.md`. Manuelle Abnahmekriterien von #91 (Fokus-Durchgang, Skalierung, Wayland-Screenshot, visuelle Hell/Dunkel-Prüfung) bleiben Basti vorbehalten — unten benannt. GitHub-#91 bleibt bis Freigabe offen; kein Push erfolgt.

**Failing-Test/Fixture.** Token-Gate `prototype/gtk/tests/test_tokens.py` als neuer Gate-Typ (kein klassisches RED/GREEN am Produktionscode — Doku+CSS+Test entstehen gemeinsam): Schärfe-Nachweise als RED-Äquivalent eingebaut, Muster wie der Leak-Check (`test_checker_rejects_known_bad_pair`: Weiß auf Cream muss abgewiesen werden; `test_gate_regex_flags_known_bad_snippet`: das Regex-Gate muss den vormals realen Verstoß `#B8860B` finden). Tone-Werte und Kontrastpaare werden aus `tokens.css`/`tokens-dark.css` geparst und gegen TOKENS.md/Formel nachgerechnet.

**Basis-Entscheidungen.** Token-Namen 1:1 von `HermesTokens` übernommen (Werte `hermes_tokens.dart:98-162`; Naming-Frage aus ISSUES.md #71 dem User gestellt, unbeantwortet geblieben → Empfehlung getroffen, im Plan als revidierbar markiert). Kontrast-Ziel AA 4,5:1 für 16 Text-Paare je Schema. Status-Tones ohne neue Farben (fg solid / bg 10 % / border 28 % auf bg vorgeblendet), `stale` strukturell von `ok` getrennt (gestrichelte Border, farbenblindsicher).

**Mechanismus-Befunde (2026-09-30, GTK 4.14.5 / libadwaita 1.5.0):**
1. **`@media` wird in provider-geladenem CSS nicht unterstützt** (Parser: «Unknown @ rule», isoliert verifiziert — `load_from_data` warnt nur, wirft nicht). Plan-Fallback umgesetzt: `tokens.css` (Light+Klassen) + `tokens-dark.css` (nur `@define-color`-Overrides) über zweiten Provider mit `PRIORITY_APPLICATION + 1`, umgeschaltet am `Adw.StyleManager`-Signal `notify::dark`.
2. **GTK4 zieht Wayland vor**, wenn `WAYLAND_DISPLAY` gesetzt ist — `DISPLAY=:1` allein erzwingt KEINEN X11-Lauf. X11-Läufe brauchen `GDK_BACKEND=x11` (Lesson für künftige Start-Gates; BASELINE-§2-X11-Belege beruhten auf Backend-Erzwingung).
3. **GApplication-Primärinstanz-Verhalten:** eine überlebende alte Instanz lässt neue Läufe still (Exit 0, stderr leer) beenden. Beim Aufräumen die echte python-PID killen (`pgrep -f ^python3 mla_app.py`), nicht die Wrapper-Subshell.
4. **`@bg` war definiert, aber nicht angewandt** (libadwaita zeigt eigenes `window_bg`) — durch Pixel-Probe der Screenshots gefunden und mit `window { background-color: @bg; }` geschlossen (`eb81fbf`).
5. **focusRing < 3:1** (kompositiert ≈ 1,4:1 light / ≈ 2,5:1 dark): Plan-Prämisse «Non-Text ≥ 3:1» korrigiert — als dokumentierte Schwäche mit Hermes-Parität ausgewiesen (TOKENS.md §5) und vom Token-Gate unter 3:1 gepinnt statt behauptet.

**Belege (X11-Screenshots, nur /tmp-Artefakte, ohne Secrets).** `/tmp/mla91-x11-light.png` und `/tmp/mla91-x11-dark.png` (je 1294×874, `MLA_FORCE_COLOR_SCHEME` + `GDK_BACKEND=x11`, xdotool-`--pid`-Suche + `import -window`). Pixel-Probe: Inhalt hell (254,252,247) = `#FEFCF7` = `@bg` light, dunkel (13,13,26) = `#0D0D1A` = `@bg` dark; Sidebar (250,247,240)/(20,20,37) = `@sidebar` je Schema — die Dark-Umschaltung (Provider-Tausch) ist damit pixelgenau belegt. Durchschnittshelligkeit 84 % vs. 12 %.

**Gates — tatsächlich ausgeführt (2026-09-30 auf `eb81fbf`):**

| Gate | Ausgabe |
|---|---|
| `python3 -m py_compile prototype/gtk/mla_app.py` | Exit 0 |
| Start-Gate Wayland light/dark (`GDK_BACKEND=wayland`, `timeout 6`) | je Exit 124 (≥ 6 s am Leben), stderr 0 Bytes |
| Start-Gate X11 light/dark (`DISPLAY=:1 GDK_BACKEND=x11`, `timeout 6`) | je Exit 124, stderr 0 Bytes |
| `python3 -m unittest discover -s prototype/gtk/tests` | `Ran 10 tests` / `OK` |
| `python3 -m unittest discover -s tests -t .` (additional/python, unberührt) | `Ran 53 tests` / `OK` |

**Rote/übersprungene Gates.** Rot: eine — der erste Screenshot-Versuch schlug fehl (xdotool fand kein Fenster; Ursache Befund 2+3, kein Produktionsfehler); nach Backend-Erzwingung und PID-Aufräumen grün. Übersprungen: `build-deb.sh` (kein Paketbezug; CI baut beim späteren PR inkl. neuem GTK-token-gate-Schritt), Root-Flutter-Gates und la_core in Task 4 (laufen frisch in Task 5 als Frischlauf-Sicherung — keine Flutter-/la_core-Dateien im Scope), Wayland-Screenshot (bleibt manuell, BASELINE §6.2).

**Manuelle Zorin-Prüfung (Basti, offen).** Vollständiger Fokus-/Tastaturdurchgang (sichtbarer Fokusring in allen Bereichen — der Ring liegt rechnerisch unter 3:1, Befund 5), Hell/Dunkel-Umschaltung am lebenden System, Skalierung 100/125/150 %, Wayland-Screenshot; entspricht BASELINE §3 Punkten 6–8 plus ISSUES.md #91 Abnahmen 3–5.

**Reviewer (Final-Whole-Branch-Review 2026-09-30, `506eb88..5e308ad`, 6 Commits).**
- **A (Korrektheit): APPROVED** — Werte-Parität 26/26 je Schema in eigener Nachrechnung direkt gegen `hermes_tokens.dart` (nicht dem Test vertrauend); Tone-Formel 24/24 selbst nachgerechnet; Mutations-Test real durchgeführt (`#FEFCF7`→`#FEFCF6` ⇒ 8 Failures — das Gate fängt selbst 1/255-Drift); WCAG-Mathematik über alle 256 Kanalwerte als identisch mit der Dart-Implementierung verifiziert (Dart-Schwelle 0.03928 vs. 0.04045 ohne Auswirkung auf 8-bit-Werte); Provider-Mechanismus sauber (Initialisierung vor Fenster, kein Doppel-Add, keine Provider-Lecks, Prioritäten unkollidiert); Handoff-Pixelwerte gegen die /tmp-Screenshots reproduziert.
- **B (Sicherheit/Spec): READY_FOR_PR** — Scope exakt die 8 erlaubten Dateien, Trinitäts-Diff leer, keine Secrets/Hostnamen/IPs, kein PNG committet, stale≠ok durchgängig (Doc + CSS + Test), Doku ohne 3:1-Überbehauptung, CI-Schritt YAML-valid und korrekt platziert, Abnahme-Mapping der 5 #91-Kriterien korrekt und ohne criterion-Washing.
- **Gesamt: READY_FOR_PR.** Minors: focusRing-Dark-Rundung ≈2,6→≈2,5 und Test-Kommentar 1.06→1.03 im Verdict-Commit korrigiert; Plan-Erratum nachgetragen. **FOLLOW-UP (bewusst offen):** (a) Die Parität TOKENS.md↔`hermes_tokens.dart` ist nur manuell geprüft — das Token-Gate liest die Dart-Quelle nicht (Kandidat für den Follow-up-Pool, analog Issue #110); (b) `self.title`-Schattierung in `mla_app.py` (seit Basis vorhanden, harmlos — für #92 merken); (c) `spineWidth`/`opacity*`-Tokens dokumentiert, aber in der Shell noch ohne Anwendung (Andockpunkt A2/#92).

**Task-5-Frischlauf (2026-09-30, auf Handoff-Stand):** `check-versions.sh` ok · `dart format` 123 Dateien 0 geändert · `flutter analyze` 0 Findings · `flutter test` +208 · additional/python 53 OK · GTK-Token-Gate 10 OK · la_core format 0 geändert / analyze clean / +61.

**Rückfallplan.** `git revert` der #91-Commits (`f8a080e` … Handoff-Commit) genügt: neue Dateien (TOKENS.md, tokens.css, tokens-dark.css, test_tokens.py, Plan-Datei) plus kleine Änderungen (mla_app.py, build.yml, VERIFY.md); kein Datenpfad, keine Unit, kein Packaging, polkit-Trinität unberührt.

## Agenten-Handoff

Je Aufgabe: Basis-SHA, Pfade, Scope, Failing-Test/Fixture, Umsetzung, Ergebnis von Reviewer 1 (Funktion/UX) und Reviewer 2 (Sicherheit), **wirklich ausgeführte** Gates mit Ausgaben, rote/übersprungene Gates, manuelle Zorin-Prüfung, Rückfallplan. Kein Merge/Release/Policy-Update ohne gesonderte Freigabe.
Loading
Loading