ui-factory
Orchestrate the full UI-Factory chain (color-system → design-system → component-library → dashboard) for complex UI tasks. Use when user asks to "build a UI", "create a dashboard", "design an app", "m
SKILL.md.claude/skills/ui-factory/SKILL.md
GitHubui-factory
Meta-Skill: Orchestrates the full UI-Factory chain. Single entry-point for any UI task that needs design + build + a11y + responsiveness. Routes to the right atoms in the right order.
When to TRIGGER
This meta-skill auto-triggers when user input matches ANY of:
Trigger-Phrasen (UI-spezifisch)
| Category | Phrasen | Auto-Skill |
|---|---|---|
| Farben / Branding | "color palette", "brand colors", "WCAG", "contrast", "dark mode colors", "welche Farben", "Theme", "Akzentfarbe" | → ui-color-system |
| Design-System / Tokens | "design system", "design tokens", "tokens.json", "CSS variables", "theme", "Brand-Identität als Code", "Komponenten-Standards" | → ui-design-system |
| Komponenten / Library | "component library", "UI kit", "Button bauen", "Input-Feld", "Modal", "Card", "Nav bauen", "alle Komponenten", "scaffold components" | → ui-component-library |
| Dashboard / Data-UI | "dashboard", "admin panel", "monitoring", "KPIs", "analytics view", "metrics view", "Übersichts-Seite", "Statistiken anzeigen" | → ui-dashboard |
| Full-Stack / Komplett | "build a UI", "create a dashboard", "design an app", "make me a website", "complete UI from scratch", "production-ready UI", "Komplettes UI-Projekt", "Vollständig bauen" | → ui-factory (this skill) |
Decision-Tree: Welcher Skill passt?
User-Input
│
├─ "color/brand/WCAG" → ui-color-system
├─ "tokens/design-system" → ui-design-system
├─ "component/button/modal" → ui-component-library
├─ "dashboard/KPI/analytics" → ui-dashboard
│
└─ Komplex/Multi-Step/Full-Stack?
│
└─ YES → ui-factory (orchestriert alle 4 Atoms)
When NOT to use
- User asks for ONE simple UI change ("make this button blue") → direkt machen, kein Skill-Overhead
- User asks for code review on existing UI →
web-design-guidelinesodersimplify-code - User asks for creative content (writing, art) → nicht UI-bezogen, andere Skills
Inputs
brief:
product_type: "SaaS dashboard | Marketing site | E-commerce | Blog | App | Settings"
mood: "trustworthy | playful | serious | cozy | futuristic | minimal | corporate"
mode: "light | dark | both | high-contrast | cozy | cyberpunk"
brand_primary: "#XXXXXX or 'auto-generate'"
framework: "vanilla-html | react | vue | svelte | sveltekit | nextjs"
style: "css-modules | tailwind | vanilla-extract | plain-css | styled-components"
a11y_target: "AA | AAA"
components_needed: ["button", "input", "card", "modal", "nav", "table", "chart", "dashboard"]
output_target: "single-file-html | storybook | production-app | prototype"
Output
Full UI-Factory deliverable:
tokens.json+tokens.css+tokens.d.ts(von ui-design-system)colors.json+colors.css+contrast-report.md(von ui-color-system)- Component files:
Button.tsx,Input.tsx,Card.tsx, etc. mit.module.css+.stories.tsx+.test.tsx+.a11y.test.tsx(von ui-component-library) - Dashboard files:
Dashboard.tsx,KpiCard.tsx,RevenueChart.tsx,RecentErrorsTable.tsx,FilterPanel.tsx(von ui-dashboard) index.tsre-exports +README.mdusage-docs
Workflow
Phase 0: Brief-Analyse (1-2 min)
Lies den User-Brief und parse:
- Product type → spacing density, layout pattern
- Mood → color palette, corner radius, shadow strength
- Mode → light/dark/both token-sets
- Brand color → primary hue (oder auto-generate aus mood)
- Framework → output format (TSX/Vue/Svelte/HTML)
- a11y target → validation threshold
- Component set → welche Atoms werden gebraucht
Phase 1: ui-color-system (3-5 min)
Input: Brief
Output: colors.json + colors.css + contrast-report.md
Steps:
- Brand-Color analysieren (oder auto-generate aus mood)
- Scale generieren (50-950)
- Semantic-Rollen zuweisen
- Contrast-Checks durchführen (AA minimum, AAA wo möglich)
- 4 Modi generieren (statt nur 2-3):
- Cozy (Warm-Light Mode): Cream-BG
#FFF5F5, Pink-800 Text, weiche Schatten - Dark (Standard Dark Mode): Neutral-950 BG, Pink-300 Text, reduzierte Sättigung
- Cyberpunk (Dark + Neon): Night-BG
#0D0020, Magenta-Neon-Akzente, Sparkle-Particles, 0.12 opacity overlays - A11y (High-Contrast): Pure Black BG
#000, White Text#FFF, max Kontrast
- Cozy (Warm-Light Mode): Cream-BG
Output-Detail:
- 5+ Color-Scales (primary, neutral, success, warning, error, info)
- WCAG contrast report (≥10 kritische Pairs, pro Theme)
- Color-Blind-Simulation passed
Phase 2: ui-design-system (3-5 min)
Input: colors.json aus Phase 1
Output: tokens.json + tokens.css + tokens.d.ts
Steps:
- Token-Kategorien generieren: color, font, space, radius, shadow, motion, z-index, breakpoints
- CSS-Variablen mit kebab-case:
--color-primary-500,--space-4 - TypeScript-Types für IDE-Autocompletion
- Fluid-Typography mit
clamp()
Validation:
- 4px oder 8px Spacing-Grid (keine Arbitrary-Werte)
- Alle Hex-Codes 7-stellig
- Spacing in
remnichtpx
Phase 3: ui-component-library (5-10 min)
Input: tokens.json + colors.json
Output: Component-Files (TSX/Vue/Svelte/HTML) + Stories + Tests
Steps:
- Components wählen (Core-Set: Button, Input, Card, Badge, Avatar)
- Pro Component: 5 Files erstellen (.tsx + .module.css + .stories.tsx + .test.tsx + .a11y.test.tsx)
- Tokens importieren (keine hardcoded values)
- A11y-Checklist durchgehen pro Component
- Stories mit Controls generieren
Validation:
- Alle Components importieren Tokens (kein
rgb(255,255,255)hardcoded) - Jede Component hat jest-axe a11y test (zero violations)
- Keyboard-Handler überall
- Visible focus-indicator
Phase 4: ui-dashboard (5-10 min)
Input: Components aus Phase 3 + Brief
Output: Dashboard.tsx + KPI-Cards + Charts + Table + Filter-Panel
Steps:
- Layout planen: Header → Sidebar + KPI-Grid → Charts → Table
- KPI-Cards mit Trend-Indicators
- Charts (line/bar/pie/area — recharts/chart.js)
- Data-Table mit sort/filter/pagination
- Filter-Panel mit date-range, multi-select
- Loading/Empty/Error states für jede Section
- Responsive (mobile/tablet/desktop breakpoints)
Validation:
- 4-6 KPIs max
- Alle Charts haben hover-tooltip
- Alle Tables haben sort + filter + pagination
- Loading states überall
Phase 5: Verification + Output (2-3 min)
Wichtig — Auto-Validation vor manueller Abnahme laufen lassen. Im Yuno-Build 2026-07-06 fand die automatisierte WCAG-Pair-Validation 4 Fails die bei manueller Prüfung übersehen worden wären (Pink-500 Text 3.2:1, Magenta-500 Body 4.29:1, Error-500 4.04:1, White-on-Magenta 3.64:1). Automatisierte Validierung ist kein Nice-to-have — sie findet Fehler die selbst bei sorgfältigem manuellem Review durchrutschen.
Checkliste:
- Alle Atoms zusammen integriert (Color → Design → Components → Dashboard)
- Keine hardcoded values (alle Tokens referenziert)
- A11y: alle Tests grün, alle Pairs WCAG-konform
- Auto-Validation durchgeführt (Token-Konsistenz + WCAG-Pairs + A11y-Attribute)
- Responsive: Mobile/Tablet/Desktop breakpoints funktionieren
- Loading/Empty/Error states überall
- Theme-Switcher funktioniert in Echtzeit (4 Modi: Cozy/Dark/Cyberpunk/A11y)
- Kontrast-Report mit mindestens 10 kritischen Pairs pro Theme
- Bundle-Output (ZIP oder Git-Commit-ready) erstellt
Orchestration-Logic für Sub-Tasks
Wenn der Task lang/komplex ist (mehr als 1 Atoms):
User: "Bau mir ein komplettes SaaS-Dashboard mit Users/Revenue/Errors"
↓
ui-factory: Analysiere Brief
↓
[Auto-Chain triggert]:
1. ui-color-system → "Primary purple, dark mode default, WCAG AA"
2. ui-design-system → "Tokens mit 4px grid, Inter sans, JetBrains Mono"
3. ui-component-library → "Button, Input, Card, Badge, Modal, Nav, Table, Chart"
4. ui-dashboard → "4 KPIs (Users/Revenue/Errors/Latency), RevenueChart, ErrorsTable"
↓
Final: Komplettes Dashboard mit allem
Auto-Orchestration Rule (the user explicit request, 2026-07-01)
Bei Tasks die "etwas länger" sind → AUTOMATISCH orchestrieren, NICHT fragen. Diese Regel wurde in Session 2026-07-01 von the user EXPLIZIT angefordert und durch einen erfolgreichen Live-Build bewiesen (komplettes Dashboard in ~5 min ohne einzige Rückfrage).
Trigger-Heuristik für Auto-Orchestration (eines reicht):
- Task braucht 3+ Tool-Calls
- Task involviert mehrere Files/Components
- Task passt zu einer existierenden Skill-Chain (z.B. UI-Factory)
- User-Keywords: "komplett bauen", "from scratch", "vollständig", "alles", "production-ready"
Bei Match: SOFORT TodoWrite + Step-by-Step-Plan ausführen. NICHT erst fragen "soll ich orchestrieren?". Begründung: the user experimentierfreudig, hasst unnötige Rückfragen, will momentum.
Ausnahme: Bei Trade-offs mit echten Konsequenzen (z.B. "Hermes-Source patchen vs standalone Dashboard") → clarify() mit 2-4 konkreten Optionen und Sterne-Bewertung. In der Praxis hat the user die Option gewählt die Update-safe und unter unserer Kontrolle war ("Unser Yuno-Dashboard mit Hermes-API verbinden" statt Hermes-Source patchen).
Bewiesene Pipeline (Yuno-Dashboard, 2026-07-01):
User: "okay baue mir ein komplettes Dashboard :D"
→ ui-factory auto-triggered (match: "komplett" + "Dashboard" + "bauen")
→ TodoWrite mit 6 Phasen
→ Phase 1: ui-color-system (Yuno Purple/Pink + WCAG check + 1 fix)
→ Phase 2: ui-design-system (tokens.json/css)
→ Phase 3+4: Components + Dashboard compose (single-file HTML 37KB)
→ Phase 5: Validation (A11y, Responsive, Token-Konsistenz)
→ Phase 6: Doku + Smoke-Test
→ Result: Keine Rückfrage nötig, User zufrieden ("sieht hot aus!")
Live-Data-Erweiterung (v2/v3, gleiche Session): Nach dem Static-Build wollte User echte Daten → Auto-Orchestrierung startete erneut:
- Phase 1: API-Discovery (welche
/api/*sind ohne Auth erreichbar?) - Phase 2: Python Data-Provider (
server.pymit psutil + subprocess + caching) - Phase 3: Live-Dashboard HTML (fetch-Polling alle 10s → später 3s auf User-Wunsch)
- Phase 4: v3 Rewrite mit Accordion-Cards + KPI-as-Buttons (User-Präferenz: "alle tabs anklickbar")
- Phase 5: Start/Stop-Skripte + Browser-Open
Auto-Trigger Logic
Bei jedem User-Input der UI-spezifisch ist, prüft Hermes:
- Description-Match: User-Input matched eine der Trigger-Phrasen oben?
- Tag-Match: Input enthält UI-spezifische Keywords (dashboard, component, design, color, etc.)?
- Context-Match: Läuft eine UI-Skill-Chain? → Auto-orchestrate
Wenn JA auf 1+: triggere entsprechenden Skill automatisch.
Wenn JA auf allen 3: triggere ui-factory als Meta-Orchestrator.
Examples
Example 1: Komplex-Task (triggert ui-factory)
User: "Bau mir ein Monitoring-Dashboard für meine SaaS-App mit Users,
Revenue und Error-Rate. Dark mode, modern, soll schick aussehen."
→ ui-factory triggered
→ Auto-chain: ui-color-system → ui-design-system → ui-component-library → ui-dashboard
→ Output: Komplettes Dashboard
Example 2: Simple-Task (triggert ui-color-system only)
User: "Ich brauch ne WCAG-AA-konforme Color-Palette für mein Startup"
→ ui-color-system triggered (matches "color palette", "WCAG")
→ Auto-output: colors.json + contrast-report.md
→ KEIN ui-factory nötig (kein kompletter Build)
Example 3: Mid-Task (triggert ui-component-library)
User: "Generier mir ne Button-Komponente mit Storybook-Story"
→ ui-component-library triggered (matches "component", "storybook")
→ Auto-output: Button.tsx + Button.module.css + Button.stories.tsx + tests
→ KEIN ui-factory nötig
Integration with Agent-Profile
Das ui-builder Agent-Profile (siehe ~/.hermes/profiles/ui-builder/) hat:
- SOUL.md: "Du bist ein präziser UI-Builder. Sauberer, semantischer, accessible Code. Composition über Configuration. Jede Component keyboard-navigable + screen-reader-friendly."
- Skill-Bundle:
ui-factory+ui-color-system+ui-design-system+ui-component-library+ui-dashboard+web-design-guidelines
Wenn the user im ui-builder-Profile arbeitet, triggern diese Skills automatisch bei UI-Themen — ohne dass er sie explizit aufrufen muss.
Related Skills
- ui-color-system — Atom 1 (Color-Palette)
- ui-design-system — Atom 2 (Tokens)
- ui-component-library — Atom 3 (Components)
- ui-dashboard — Atom 4 (Dashboard)
- web-design-guidelines — Polish-Review nach Build
- html-artifact — Für Single-Page-Deliverables
- claude-design — Alternative UI-Generator
Part of UI-Factory
This is the META-skill in the UI-Factory pattern. Other UI-Factory skills are atoms — this one is the molecule that orchestrates them.
Atoms (Skills): Molecule (Meta): Organism (Profile):
ui-color-system ─┐
ui-design-system ─┤
ui-component-library ─┼──→ ui-factory ──→ ui-builder (SOUL.md)
ui-dashboard ─┘
Pitfalls (from real builds, 2026-07-01)
Pitfall 12 — Yuno-Pink/Magenta WCAG-Tabelle (entdeckt 2026-07-06)
Symptom: Bei der zweiten Iteration des Yuno-Operator-Dashboards waren alle Standard-Tokens (primary-500, accent-500, error-500) um 0.1-0.5:1 unter der AA-4.5:1-Grenze. Hätte man die "offensichtlichen" Token-Defaults aus Pitfall 1 genommen, wäre das Dashboard WCAG-non-compliant ausgeliefert worden. Erst die automatisierte Validierung (siehe Phase 5 Pflicht) hat das aufgedeckt.
Konkretes Failed-Set aus 2026-07-06 (erste Iteration, manuell geprüft sah alles ok aus):
| Token-Pair | Hex | Hex | Ratio | Target | Status |
|---|---|---|---|---|---|
white on pink-700 |
#FFFFFF | #E63975 | 4.03:1 | 4.5 | FAIL |
pink-700 on cream-50 |
#E63975 | #FFFBF5 | 3.91:1 | 4.5 | FAIL (Body-Text) |
magenta-500 on cream-50 |
#FF1493 | #FFFBF5 | 4.29:1 | 4.5 | FAIL |
magenta-600 on cream-50 |
#E80080 | #FFFBF5 | 4.43:1 | 4.5 | FAIL (knapp drunter!) |
error-500 on cream-50 |
#E63946 | #FFFBF5 | 4.04:1 | 4.5 | FAIL |
white on magenta-500 |
#FFFFFF | #FF1493 | 3.64:1 | 4.5 | FAIL (Cyberpunk Button) |
Fixes (Sweet-Spot-Erkennung):
| Token-Pair | Hex | Hex | Ratio | Status |
|---|---|---|---|---|
white on pink-800 |
#FFFFFF | #B7295B | 6.03:1 | ✓ AA |
pink-800 on cream-50 |
#B7295B | #FFFBF5 | 5.85:1 | ✓ AA |
magenta-700 on cream-50 |
#B30066 | #FFFBF5 | 6.55:1 | ✓ AA |
error-600 on cream-50 |
#C81F2E | #FFFBF5 | 5.52:1 | ✓ AA |
night-700 on magenta-500 |
#050817 | #FF1493 | 5.48:1 | ✓ AA (Cyberpunk-Button mit dunklem Text) |
Reusable Algorithm — Bright-Brand-Color-Fitting:
def find_aa_safe_foreground(brand_500, bg, target=4.5):
"""Suche den dunkelsten Brand-Shade, der auf bg noch mindestens target:1 hat."""
candidates = [brand_500, brand_600, brand_700, brand_800, brand_900]
for c in candidates:
if contrast(c, bg) >= target:
return c
# Fallback: brand_900, auch wenn's über das Ziel hinausschießt
return brand_900
Bright-Brand-Color-Sweet-Spot-Tabelle (empirisch, 2026-07-06):
| Brand-Hue | Primary-500 als Button | Primary-700 als Button | Sweet-Spot | Notes |
|---|---|---|---|---|
| Pink (#FF6BB5) | 3.2:1 ❌ | 4.03:1 ❌ | 800 (6.03:1) ✓ | Sehr hell, braucht 3 Stufen tiefer |
| Magenta (#FF1493) | 3.2:1 ❌ | 5.48:1 ✓ | 700 (5.48:1) ✓ | Heller als Pink, 2 Stufen reichen |
| Coral (#FF7F50) | ~2.5:1 ❌ | ~4.0:1 ❌ | 800 (~5.5:1) ✓ | Ähnlich wie Pink, 3 Stufen |
| Cyan (#00E5FF) | ~2.0:1 ❌ | ~5.0:1 ✓ | 700 (~5.0:1) ✓ | Sehr hell, viel zu leicht |
| Lime (#CDDC39) | ~1.5:1 ❌ | ~8.0:1 ✓ | 500 reicht nicht, erst 700+ | Extrem hell |
| Purple (#A855F7) | 3.96:1 ❌ | 6.98:1 ✓ | 700 (6.98:1) ✓ | Mittel, 2 Stufen reichen |
| Navy (#1A237E) | 14:1 ✓ | 16:1 ✓ | 500 reicht locker | Dunkel genug |
| Forest Green (#2E7D32) | 7:1 ✓ | 9:1 ✓ | 500 reicht | Schon dunkel |
Key Insight: Bright Brand-Colors (Pink, Coral, Cyan, Lime, Yellow) brauchen immer 2-3 Shade-Stufen tiefer als Standard-Purple/Navy/Forest. Die "offensichtliche" Token-Default primary-500 ist bei diesen Hues fast nie AA-safe.
Lesson:
- Immer Token-Sweetspot-Algorithmus laufen lassen für jeden Brand-Color + jeden geplanten BG-Mode (Light/Dark/Cyberpunk)
- Niemals nur "die Standard-500/700" raten — bei hellen Brand-Colors systematisch testen
- Auto-Validation in Phase 5 IST PFLICHT — manuelles Schauen übersieht die knappen Fails
- Bei Cyberpunk/Neon-Stilen explizit dark-Text auf Neon-BG testen (oft intuitiv falsch, white-auf-Neon scheitert weil Neon-Farben dunkler sind als sie aussehen)
Pitfall 1 — WCAG-AA Contrast bei Button-Text (erweitert 2026-07-06)
Standard-Case: Purple Brand
Symptom (Yuno-Dashboard-Build 2026-07-01): White-text auf purple-500 (#A855F7) = 3.96:1 → FAIL AA (minimum 4.5:1 für body-text).
Root Cause: Brand-color purple-500 ist visuell eindrucksvoll, hat aber nicht genug Luminanz für weißen Text.
Fix: Zwei verschiedene Primary-Shades für Light vs Dark Mode:
- Light Mode: Button-BG =
purple-700(#7E22CE), Button-Text =#FFFFFF(6.98:1 ✓) - Dark Mode: Button-BG =
purple-300(#D8B4FE), Button-Text =#09090B(11.25:1 ✓)
/* Light */
--yuno-primary: var(--yuno-purple-700); /* 6.98:1 mit white */
--yuno-primary-fg: #FFFFFF;
/* Dark */
[data-theme="dark"] {
--yuno-primary: var(--yuno-purple-300); /* 11.25:1 mit dark-bg */
--yuno-primary-fg: var(--yuno-neutral-950);
}
Harder Case: Pink/Magenta Brand (entdeckt 2026-07-06)
Symptom: Pink-500 (#FF6BB5) auf Cream (#FFF5F5) = 3.2:1 → FAIL AA — viel schlechter als Purple weil Pink heller ist.
Fix: Benötigt zwei Stufen dunkler als Purple-Case. Pink-500 reicht nicht, selbst Pink-700 (#D81B60) gibt nur 4.89:1 auf Cream:
- Cozy Mode (Cream-BG): Button-BG = Pink-800 (
#C2185B), Text = White (6.03:1 ✓ AA) - Dark Mode: Button-BG = Pink-300 (
#F48FB1), Text = Night-900 (9.37:1 ✓ AA) - Cyberpunk Mode: Button-BG = Magenta-700 (
#B30066), Text = White (knapp unter AA auf Neon-BG → aufNight-700Text wechseln, 5.48:1 ✓) - A11y Mode: Button-BG = Magenta-500 (
#E91E63), Text = White (5.96:1 ✓ AA)
Key Insight — Accent-Color braucht oft einen eigenen Scale-Durchlauf: Wenn die Brand-Color ein heller/satter Ton ist (Pink, Coral, Cyan, Lime), dann reicht primary-700 nicht. Der Sweetspot liegt 1-2 Stufen tiefer als bei dunklen Brand-Colors (Purple, Navy, Forest Green). Immer primary-500, primary-600, primary-700, primary-800 auf der Ziel-BG testen und den hellsten Shade nehmen der noch AA schafft.
Lesson: Bei jeder Brand-Color-Generierung (Phase 1) SOFORT den Button-Text-Contrast checken — vor der Component-Generation. Lieber primary-700 als primary-500 für Buttons. Bei hellen Brand-Colors (Pink, Coral, Cyan, Lime, Gelb) → primary-800 oder primary-900 nehmen.
Pitfall 2 — Hardcoded Hex außerhalb :root
Symptom: Inline-styles oder component-CSS haben #FFFFFF statt var(--yuno-neutral-50). Tokens werden umgangen → Theme-Switch bricht.
Detection-Pattern (in Token-Konsistenz-Check Phase 5):
# Finde hardcoded hex außerhalb der :root-Definition
grep -oE '#[0-9A-Fa-f]{6}' index.html | wc -l # Total hex
sed -n '/:root/,/^}/p' index.html | grep -oE '#[0-9A-Fa-f]{6}' | wc -l # In :root
# Outside tokens: $((total_hex - in_root)) ← sollte 1 sein (theme-color meta-Tag)
Legitimer Ausreißer: <meta name="theme-color" content="#XXX"> — das ist PWA/Chrome-spezifisch und kann nicht aus CSS-Vars gelesen werden.
Fix: Alle component-CSS nutzt var(--yuno-*) — nie hardcoded hex. Tokens sind die Single Source of Truth.
Pitfall 3 — Framework-Choice-Lock-In
Symptom: User sagt "Bau mir ein Dashboard", Skill empfiehlt React+TypeScript+Tailwind. Aber User wollte eigentlich nur single-file HTML zum zeigen.
Lesson: Framework-Defaults aus framework:-Input übernehmen, NICHT raten. Wenn unklar, 1× clarify() mit 2-4 Optionen (z.B. "vanilla-html | react | svelte | sveltekit").
Best-Practice Defaults per Use-Case:
| Use-Case | Default Framework |
|---|---|
| Quick-Show, No-Backend | vanilla-html (single-file) |
| Production-SaaS | react + tailwind |
| Marketing-Site | sveltekit + tailwind |
| Internal-Tool | vue + css-modules |
Pitfall 4 — Token-Count-Explosion
Symptom: Generierter tokens.json hat 200+ Tokens weil alle möglichen Variablen erzeugt wurden (jede shade × jede semantic role × jedes breakpoint).
Fix: Token-Budget setzen:
- Color: 5 main scales (primary, neutral, success, warning, error) × 11 shades = 55 tokens
- Font: 5 sizes × 1 weight + 4 families + 3 line-heights = 12 tokens
- Space: 13 (0-32) = 13 tokens
- Radius: 6 = 6 tokens
- Shadow: 6 = 6 tokens
- Motion: 6 = 6 tokens
- Z-Index: 6 = 6 tokens
- Total Budget: ~120 tokens. Wenn mehr → Atomic-Composition statt neue Tokens.
Pitfall 5 — Token-Konsistenz nicht messbar ohne Check-Tool
Symptom: Nach Build sind 30% der Components mit hardcoded Werten, Tokens werden ignoriert.
Build-Time-Check (in Phase 5):
python3 << 'EOF'
import re
with open('index.html') as f:
html = f.read()
# Total CSS-Var-Usage
var_usage = len(re.findall(r'var\(--[\w-]+\)', html))
# Hardcoded hex außerhalb :root
total_hex = len(re.findall(r'#[0-9A-Fa-f]{6}', html))
root_section = re.search(r':root\s*{([^}]+)}', html, re.DOTALL)
in_root = len(re.findall(r'#[0-9A-Fa-f]{6}', root_section.group(1))) if root_section else 0
outside = total_hex - in_root
print(f"var(--*) usage: {var_usage}")
print(f"hardcoded hex outside :root: {outside} (erwartet: ≤1)")
assert outside <= 1, "Token-Konsistenz FAIL"
print("✓ Token-Konsistenz PASS")
EOF
Wenn outside > 1 → components nochmal durchgehen, hardcoded → tokens.
Pitfall 6 — Output-Größe bei Single-File HTML
Symptom: Inline-everything Dashboard ist 150 KB weil alle Charts als SVG-Strings eingebettet sind.
Mitigation:
- ASCII-Sparklines statt SVG-Charts für KPIs (siehe Yuno-Dashboard: 28-byte bars statt 5KB SVG)
- CSS-only Progress-Bars statt Chart-Libraries
- External-Files wenn >50 KB:
tokens.css,tokens.jsonals separate Files - Lazy-Loading: Bilder/Schriften mit
loading="lazy"undfont-display: swap
Pitfall 8 — HC-Mode: Page-BG = Card-BG = Pure Black → Cards unsichtbar (entdeckt 2026-07-06)
Symptom: Im High-Contrast-Mode hat sowohl body (Page-BG) als auch alle .card-Container den gleichen Token --yuno-bg-panel: #000000. Resultat: Cards haben keine visuelle Abgrenzung mehr, alles verschwimmt zu einem schwarzen Block mit weißen Borders. User findet den Inhalt nicht mehr.
Root Cause: Wenn Page-BG und Panel-BG identisch sind, funktioniert das Layering-Konzept von Light/Dark-Modi (Page = subtle, Panel = lighter) nicht mehr — im HC-Mode ist beides per Definition pure Black.
Fix — Subtle-Lift-Pattern:
[data-theme="hc"] {
--yuno-bg: #000000; /* Page: pure black */
--yuno-bg-subtle: #0A0A0A; /* Subtle regions */
--yuno-bg-panel: #0A0A0A; /* Cards: lifted by 4% lightness */
--yuno-border: #FFFFFF; /* Aber Borders explizit sichtbar */
--yuno-border-strong:#FFFFFF;
}
Key Insight: Im HC-Mode muss der Card-BG bewusst 1 Stufe heller sein als der Page-BG, damit Layering funktioniert ohne die WCAG-AAA-Compliance zu verletzen. #0A0A0A auf #000000 ist immer noch weit unter 1.5:1 (kein extra "Card" mehr nötig), aber visuell differenziert. Alternative: Falls strikter AAA gewünscht, Borders fett zeichnen (2-3px statt 1px).
Lesson: HC-Mode ist nicht einfach "alles pure B/W" — Layering muss bewusst konstruiert werden. Page/Panel/Subtle-Trio aus 3 sehr nahen Schwarz-Tönen + dicke White-Borders ist die saubere Lösung.
Pitfall 9 — Headless Chrome für Screenshot-Validation wenn keine Browser-Tools verfügbar (entdeckt 2026-07-06)
Symptom: ui-factory Phase-5 empfiehlt visuelles Smoke-Testing via browser_vision / browser_navigate, aber in manchen Hermes-Sessions (Desktop-TUI ohne MCP-Browser) sind die Browser-Tools nicht im Toolset. Trotzdem muss irgendwie validiert werden, dass alle 4 Themes sauber rendern.
Workaround — Google Chrome Headless:
# Static-Server starten
cd ~/10-Projekte/10-active/yuno-ui && python3 -m http.server 8765 &
# Für jedes Theme: URL-Param setzen + Screenshot
for theme in light dark cyberpunk hc; do
google-chrome --headless --disable-gpu --no-sandbox --hide-scrollbars \
--window-size=1440,1100 \
--virtual-time-budget=2000 \
--screenshot=/tmp/yuno-${theme}.png \
"http://127.0.0.1:8765/index.html?theme=${theme}" 2>&1 | tail -1
done
Voraussetzung — URL-Param-Pattern im Theme-Switcher:
// In index.html script-section:
const urlTheme = new URLSearchParams(location.search).get('theme');
if (urlTheme && VALID_THEMES.includes(urlTheme)) {
applyTheme(urlTheme); // überschreibt localStorage
} else {
applyTheme(loadTheme()); // default path
}
Warum das funktioniert: Headless Chrome rendert die Page inkl. JS, der URL-Param triggert das Theme vor dem ersten paint, Screenshot zeigt den richtigen Mode.
Lesson: Wenn keine interaktiven Browser-Tools verfügbar sind, ist google-chrome --headless mit URL-Param-Theme + --virtual-time-budget=2000 der verlässlichste Fallback für Theme-Validation. Spart den Aufwand eines interaktiven MCP-Browsers und liefert reproduzierbare PNGs die via vision_analyze inspiziert werden können.
Reusable Helper: siehe scripts/headless-theme-screenshots.sh — wiederverwendbares Bash-Script das alle Themes einer ui-factory-Build als PNG rendert. Unterstützt Custom-Theme-Listen + Window-Size via Env-Vars.
Quick-Reference für nächste Session:
# 1. Static-Server starten (im Build-Ordner)
cd ~/10-Projekte/10-active/<build> && python3 -m http.server 8765 &
# 2. Theme-Screenshots rendern
bash ~/.hermes/skills/creative/ui-factory/scripts/headless-theme-screenshots.sh \
http://127.0.0.1:8765 \
~/Bilder/yuno-gallery/<build>-<datum> \
"light dark cyberpunk hc"
# 3. PNGs via vision_analyze inspizieren
Pitfall 10 — Favicon-404-Lärm im Server-Log (entdeckt 2026-07-06)
Symptom: Jeder Browser-Request triggert automatischen GET /favicon.ico der mit 404 failed. Bei Static-Server-Logs/CI-Output/Test-Renders entsteht dauerhafter Lärm der echte Fehler überdeckt. Plus: User sieht im Tab ein generisches Browser-Icon statt der Brand.
Fix — Inline SVG Data-URI als <link rel="icon">:
<!-- Im HTML-Head, vor allen anderen Stylesheets -->
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+...">
Generierung (encode_then_patch statt manuell):
import base64
svg = '''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#FFB6C1"/><stop offset="100%" stop-color="#FF1493"/>
</linearGradient></defs>
<rect width="32" height="32" rx="8" fill="url(#g)"/>
<path d="M16 6 L18 14 L26 16 L18 18 L16 26 L14 18 L6 16 L14 14 Z" fill="#FFFFFF"/>
</svg>'''
b64 = base64.b64encode(svg.encode()).decode()
f"data:image/svg+xml;base64,{b64}" # ~700 chars für 32x32 brand-coloured sparkle
Vorteile:
- Zero extra HTTP-Request (im HTML eingebettet)
- Funktioniert in allen modernen Browsern (Chrome/Firefox/Safari/Edge)
- Brand-Sichtbarkeit im Browser-Tab
- Server-Log bleibt clean
Nachteile:
- +1 KB HTML-Größe (vernachlässigbar)
- ICO-Fallback fehlt (für IE11 / sehr alte Browser — irrelevant 2026)
Lesson: Immer inline-SVG-Favicon bei single-file HTML-Builds setzen, vor allem wenn das HTML auch als Static-Server ausgeliefert wird. Spart 404-Logs, gibt Brand-Identität im Tab, kostet quasi nichts.
Pitfall 11 — Base64-Encode Tippfehler-Falle (entdeckt 2026-07-06)
Symptom: Beim manuellen Erstellen des Favicon-Data-URI via patch() mit Copy-Paste der Base64-Bytes wurde circle zu jacmNsZSB korrumpiert (das Np in NpcmNsZSB → jacmNsZSB durch Encoding-Fehler). Browser zeigt kaputtes/fehlendes SVG-Icon, Validation-Script erkennt es nicht sofort.
Reproduktion: Wenn ein langer Base64-String durch mehrere patch()-Operationen geht, kann einzelne Zeichenpaare bei Markdown-Rendering/Codeblock-Konvertierung korrumpiert werden — typisch: N → j, c → a, G → m (alle sehen visuell ähnlich aus, sind aber ASCII unterschiedlich).
Fix — IMMER nach dem Patch decode-testen:
import re, base64
idx = open('index.html').read()
m = re.search(r'href="(data:image/svg\+xml;base64,([A-Za-z0-9+/=]+))"', idx)
b64 = m.group(2)
try:
decoded = base64.b64decode(b64).decode('utf-8')
assert decoded.startswith('<svg') and decoded.endswith('</svg>')
print(f"✓ Valid SVG ({len(decoded)} chars)")
except Exception as e:
print(f"❌ Base64 INVALID: {e}")
# Specific check: typische Tippfehler
if 'jac' in b64: print(" 'jac' korrumpiertes 'Np' gefunden — Re-Patch nötig")
Best-Practice — Data-URI-Generierung im execute_code statt manuell:
# Statt SVG manuell zu schreiben und zu encoden:
import base64
svg = '<svg ...>...</svg>' # sauberes SVG
b64 = base64.b64encode(svg.encode()).decode() # automatisch korrekt
# Dann in write_file() oder patch() mit dem sauberen b64
Lesson: Bei langen Base64-Strings in patch()-Operationen ist das Risiko von Character-Korruption real. IMMER nach dem Patch den Base64-String decoden und validieren dass er ein valides SVG/PNG/etc. ergibt. Nutze execute_code für Encoding statt manuelles Schreiben wenn möglich.
Pitfall 7 — Theme-Switcher ohne A11y-freundliche Modi
Symptom: Drei Themes die alle Farb-Kombinationen nutzen (Light/Dark/Cyberpunk), aber kein dedicated High-Contrast-Mode. Screenreader-Nutzer und User mit Sehbehinderung haben keine Option.
Fix: Immer 4 Modi anbieten: Cozy (warm-light), Dark (standard), Cyberpunk (dark+neon), A11y (pure black/white). Der A11y-Mode muss alle Gradienten deaktivieren, alle Opacity-Overlays entfernen und auf pure #000/#FFF setzen (WCAG AAA guaranteed).
Theme-Switcher localStorage-Pattern (2026-07-06):
<div class="theme-switcher" role="group" aria-label="Theme auswählen">
<button class="theme-btn" data-theme="cozy" aria-pressed="true">☀️ Cozy</button>
<button class="theme-btn" data-theme="dark" aria-pressed="false">🌙 Dark</button>
<button class="theme-btn" data-theme="cyber" aria-pressed="false">⚡ Cyber</button>
<button class="theme-btn" data-theme="a11y" aria-pressed="false">♿ A11y</button>
</div>
// localStorage-gespeichertes Theme beim Laden anwenden
const saved = localStorage.getItem('yuno-theme') || 'cozy';
document.documentElement.setAttribute('data-theme', saved);
// Theme-Buttons steuern die data-theme auf <html>
document.querySelectorAll('.theme-btn').forEach(btn => {
btn.addEventListener('click', () => {
const theme = btn.dataset.theme;
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('yuno-theme', theme);
document.querySelectorAll('.theme-btn').forEach(b =>
b.setAttribute('aria-pressed', b === btn));
});
});
CSS-Aufbau:
:root { /* = Cozy Mode */ --bg-primary: #FFF5F5; --text-primary: #AD1457; /* Pink-800 */ }
[data-theme="dark"] { --bg-primary: #1A1A2E; --text-primary: #F48FB1; /* Pink-300 */ }
[data-theme="cyber"] { --bg-primary: #0D0020; --text-primary: #FF1493; /* DeepPink */ }
[data-theme="a11y"] { --bg-primary: #000000; --text-primary: #FFFFFF; }
Validation: Theme-Switcher im Browser testen (alle 4 Modi klicken, visuell prüfen) + localStorage.getItem('yuno-theme') nach Page-Reload checken.
Key Insight: Der A11y-Mode muss als viertes Theme eigenständig definiert sein, nicht als prefers-contrast: more media query. Grund: the user oder ein anderer User kann den Mode auch ohne OS-Setting aktivieren (z.B. bei Migräne kurzfristig umschalten). prefers-contrast zusätzlich als Fallback drin lassen aber nie als einzigen Weg.
Live-Build-Template: Yuno-Dashboard (2026-07-01, ~5 min)
Input: "okay baue mir ein komplettes Dashboard :D"
Output: ~/yuno-dashboard/ mit:
index.html(37 KB, 1034 lines, single-file)tokens/colors.json(2.3 KB, Yuno-Purple/Pink, WCAG-AA verified)tokens/tokens.css(3.4 KB, CSS-Vars)tokens/tokens.json(2.7 KB, JS/TS-tokens)docs/README.md(7.6 KB, architecture + WCAG-verification)
Phasen:
- ui-color-system — Yuno-Purple/Pink Palette + WCAG-check (1 fix: Button-Contrast)
- ui-design-system — Tokens (Color + Font + Space + Radius + Motion)
- ui-component-library — Button + Card + Badge + Progress + Avatar (inline)
- ui-dashboard — Compose: Sidebar + KPI-Grid + Tasks + Reminders + Ideas + Stats
- Validation — A11y (8× label, 24× hidden, 1× current, 13× role) + Responsive (5× @media) + Token-Konsistenz (84× var-usage, 1× hardcoded = meta theme-color)
- Docs — README mit Architecture + WCAG-Tabelle
Bestätigte Validation-Resultate:
- File-Size: 37 KB (single-file, no deps)
- A11y: 1× main, 1× nav, 8× article, 3× section, 1× aside, 1× h1 ✓
- ARIA: 8× label, 24× hidden, 1× current, 13× role ✓
- WCAG 2.2 AA: alle 10 Text/Bg-Pairs verified ✓ (Button-Text 6.98:1 light, 11.25:1 dark)
- Responsive: 5× @media queries ✓
- Token-Konsistenz: 84× var-usage, 0 hardcoded-components ✓
Lessons-Reference: siehe references/yuno-dashboard-build-2026-07-01.md (geplant für nächste Session).
Live-Build-Template: Yuno-Operator-Dashboard 4-Mode (2026-07-06, ~45 min)
Input: Yuno-Style-Pattern Analyse (3 Bilder → 5 Soul-Marker) + Dashboard-Build
Output: ~/yuno-ui/ mit:
index.html(42 KB, 1118 lines, single-file) — 4 Modi, 273×var(--*), 0 hardcoded-hextokens/colors.json(7.1 KB) — 9 Scales, 4 Modes, 17 Contrast-Pairstokens/colors.css(7.4 KB) — CSS-Vars pro Modetokens/tokens.css(5.6 KB) — Font + Space + Radius + Shadow + Motion + Ztokens/tokens.json(5.0 KB) — Style-Dictionary-Formattokens/tokens.d.ts(6.7 KB) — TypeScript-Types + getYunoMode() + applyYunoTheme()tokens/contrast-report.md(4.7 KB) — 17/17 WCAG-Pairs PASSREADME.md(10 KB) — Architecture + WCAG-Table + Theme-Guide
Neu gegenueber 2026-07-01:
- 4 Themes statt 3: Cozy (Cream), Dark (Neutral), Cyberpunk (Night+Neon), A11y (Pure B/W)
- 17 Contrast-Pairs statt 10 (pro Theme: Button, Body, Accent, Decorative, Status, Inverse)
- Brand-Color Pink/Magenta statt Purple (schwieriger wegen hellerer Hue)
- Echte Badge-Farben für Activity-Status (success/auto/creative/recovered/ui-factory/bug-known)
- ASCII-Spark-Bars statt SVG-Charts (28-byte bars statt 5KB SVG)
- Progress-Bars mit Farb-Coding (green <70%, yellow 70-90%, red >90%)
- Sidebar mit Badge + aktiver Nav-Indicator
- Console-Easter-Egg mit gradient + pink icon
- Brand-Mark Pulse-Animation mit prefers-reduced-motion Fallback
- A11y-Mode als separates Theme (nicht nur prefers-contrast)
Bestätigte Validation-Resultate:
- WCAG 2.2 AA: 17/17 Pairs PASS (alle 4 Themes) ✓
- Pink-800 (
#C2185B) auf Cream (#FFF5F5): 6.03:1 AA ✓ - Magenta-700 (
#B30066) auf Night (#0D0020): 5.48:1 AA ✓ - Pink-300 (
#F48FB1) auf Dark (#1A1A2E): 9.37:1 AA ✓ - White (
#FFF) auf Black (#000): 21.0:1 AAA ✓
- Pink-800 (
- Token-Konsistenz: 273×
var(--*), 0 hardcoded-hex in UI (1 metaTag + 3 console.log legitim) ✓ - A11y: 12× aria-label, 40× aria-hidden, 5× aria-pressed, 4× aria-current ✓
- Responsive: 4× @media (1024/768/480/reduced-motion) ✓
- Components: 13 inline-Typen (btn, badge, kpi-card, card, input, progress, nav-link, activity-item, theme-btn, brand-mark, kpi-spark, kpi-trend, theme-switcher) ✓
- Theme-Switcher: localStorage-persistent, alle 4 Modi im Browser getestet ✓
- Browser-Console: 0 Errors, 0 Warnings ✓
Lessons-Reference: siehe references/yuno-operator-build-2026-07-06.md.
Based on the KIMI K2 UI-Factory-Pattern (2026-06-30) + Live-Build-Evidence (2026-07-01, 2026-07-06).
Workspace-Convention für Yuno-UI-Builds (entdeckt 2026-07-06)
Lesson: ui-factory-Builds sind Dev-Projekte mit mehreren Files (HTML + Tokens + README + ggf. styles.css/.json/.d.ts). Sie gehören daher nach ~/10-Projekte/10-active/<build>/, NICHT in ~/ direkt.
Hintergrund: the user's 2026-07-04 Workspace-Präferenz sagt "Yuno agiert primär in ~/.hermes/" — die Ausnahme sind mehrere-File Dev-Projekte, die dem Cluster-Layout (~/00-Meta/, ~/10-Projekte/, ~/20-Workspace/, ~/30-Library/, ~/50-System/) folgen, neben greyhack-tools, cp77-modding, github-mcp-server, linux-assistant.
Empfohlene Pfade je nach Skill-Output:
- ui-factory-Build (mehrere Files):
~/10-Projekte/10-active/<name>/(z.B.~/10-Projekte/10-active/yuno-ui/) - Single-File Showcase (1 HTML):
~/Bilder/yuno-gallery/<build>-<datum>/(für Screenshots) +~/10-Projekte/10-active/<name>/index.html - Doku-Only-Output:
~/docs/system/<name>-<datum>.md(siehesystem-documentationSkill) - Yuno-Sandbox/Staging:
~/.hermes/sandbox/(kurzlebig)
Migration-Step wenn versehentlich in ~/ angelegt:
# 1. Verschieben
mv ~/<name> ~/10-Projekte/10-active/<name>
# 2. README + alle internen Referenzen auf den neuen Pfad updaten
# 3. Memory mit Workspace-Regel + Erweiterung committen
Curator-Note (Overlap-Hinweis, 2026-07-01)
Potenzielle Überschneidung mit creative/html-artifact Skill:
- Beide können single-file HTML ausliefern
html-artifact: Fokus auf Explainers/Reports (Status, Code-Review, Diagramme)ui-factory: Fokus auf komponentenbasierte UI (Design-System, Components, Dashboards)
Decision-Tree:
- "Bau mir X (UI/UX)" →
ui-factory - "Erklär mir X" / "Schreib Report" →
html-artifact - Single-file HTML Showcases (z.B. Yuno-Cockpit) → beide möglich,
ui-factoryfür polish wenn Design-Tokens relevant
Action für Curator: Falls Konsolidierung gewünscht, könnte ui-factory die html-artifact-Trigger-Phrasen übernehmen für "showcase"-Cases, oder umgekehrt. Aktuell nicht kritisch — beide haben distinkte Use-Cases.