Ctrl K
ComunidadGuías…

docs/ui-system.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Kiwiki UI-System

Kiwiki nutzt eine ruhige, werkzeugartige Oberfläche: dunkle, warme Flächen, klare Typografie und Honig-Bernstein (`#e3a94f`) nur
für Fokus, Status und primäre Aktionen. Der Feinschliff orientiert sich an der Präzision moderner Developer-Tools,
bleibt aber eine eigenständige, vollständig selbst gehostete Kiwiki-Oberfläche ohne Astryx- oder React-Abhängigkeit.

## Grundregeln

- `app/static/kiwiki-polish.css` ist die kanonische Ergänzung für gemeinsame Primitive und komponentenübergreifende
  Zustände. Seitenspezifische Regeln dürfen dort ergänzt werden, wenn mindestens zwei Oberflächen davon profitieren.
- Abstände verwenden die `--space-*`-Skala; normale Controls sind 40 Pixel hoch, wichtige Touch-Ziele mindestens
  44 Pixel. Icon-Buttons zentrieren ihr SVG über Grid und verändern beim Hover nicht ihre Geometrie.
- Fokus ist immer sichtbar. Disabled-, Error-, Loading- und Empty-Zustände dürfen nicht allein über Farbe vermittelt
  werden. `prefers-reduced-motion` wird respektiert (Ausnahme mit Begründung: Neuronaler Atlas, siehe unten und
  `ui-accessibility.md`).
- Lesetext bleibt auf `--content-reading`, Arbeitsoberflächen auf `--content-workspace` begrenzt. Overlays verwenden
  die dokumentierten `--layer-*`-Tokens.

## Lokalisierung

Alle sichtbaren Texte, ARIA-Beschriftungen und Browsermeldungen liegen gleichzeitig in Deutsch und Englisch in
`app/i18n.py`. Templates erhalten `lang` und `t` über einen gemeinsamen Kontextprozessor; JavaScript liest den
Unterkatalog über `window.KIWIKI_I18N` und `kwText()`.

Die Reihenfolge der Sprachwahl ist: `?lang=de|en`, persistiertes `kiwiki_language`-Cookie, anschließend
`Accept-Language`. Jede neue UI-Funktion benötigt einen Test für beide Sprachen. Fest verdrahtete einsprachige
Produktoberfläche ist nicht zulässig.

## Komponentenvertrag

- Buttons: Primär nur für die wichtigste Aktion eines Bereichs, Ghost für ergänzende Aktionen, Danger ausschließlich
  für irreversible Änderungen.
- Sidebar und Menüs: explizite Open/Closed-Zustände, `aria-hidden` plus `inert`, robuste Layer und mindestens ein
  vollständiger Tastaturpfad.
- Formulare: sichtbares Label, klarer Fokus, Fehler direkt am Kontext; Placeholder ersetzt kein Label.
- Dialoge und Toasts: Fokusfalle und Escape im Dialog, lokalisierte Aktionstexte, Toast-Region mit passendem ARIA-Label.

## Verifikation

Änderungen am UI-System werden mindestens mit den Lokalisierungs- und Frontend-Vertragstests sowie
`tests/browser_smoke.py` geprüft. Visuelle Änderungen sind zusätzlich in Desktop- und Mobile-Viewport zu kontrollieren.

## Bewegung

Eine Sprache für die ganze App, als Tokens in `kiwiki-polish.css`:

| Token | Wert | Wofür |
|---|---|---|
| `--ease-spring` | `cubic-bezier(0.34, 1.36, 0.64, 1)` | Dinge, die ankommen: Menüs, Dialoge, Segment-Daumen, Ordner-Chevron |
| `--ease-calm` | `cubic-bezier(0.22, 1, 0.36, 1)` | Zustandswechsel und Dinge, die gehen: Sidebar, Einblenden |
| `--d-quick` / `--d-std` / `--d-slow` | 140 / 260 / 520 ms | Hover · Standard · Sidebar und Seitenaufbau |

- Animiert werden nur `transform`, `opacity` und die Sidebar-Breite. Gestaffelte Einläufe nutzen
  `animation-fill-mode: backwards`, damit nach dem Ende kein `transform` hängen bleibt.
- Der Block „4.3 Bewegung in der App“ am Ende von `kiwiki-polish.css` enthält Sidebar (Desktop:
  Breite + versetzter Inhalt, Mobil: Hereinschieben), Ordner-Aufklappen, Konto-Menü, Startseite und
  Atlas-Seitenleiste. Alles steht in `@media (prefers-reduced-motion: no-preference)`.
- Die Baumzeilen laufen beim Öffnen der Sidebar einmal ein: `kwPlaySidebarOpening()` in `kiwiki.js`
  setzt `.sidebar.kw-opening` für 900 ms. Ohne das Entfernen würde jede spätere Baum-Aktualisierung
  die Animation erneut abspielen.
- Beim Ziehen am Sidebar-Griff ist die Breiten-Transition aus (`body:has(.sidebar-resizer.dragging)`),
  sonst hinkt die Sidebar der Maus hinterher.

### Segment-Umschalter

`.recent-switch` (Startseite) hat Segmente in Textbreite. Der Daumen (`::before`) wird in
`kiwiki-recall.js` → `syncSegments()` auf das aktive Segment vermessen (`--seg-x`, `--seg-w`) und
bei Klick, Resize und nach dem Laden der Schriften neu gesetzt. Gleich breite Spalten sind keine
Lösung: Das kürzere Label bekommt dann mehr Rand als das längere und wirkt schief. `data-label`
(per JS aus dem Text gesetzt) reserviert über `::after` die Breite der fetten Variante, damit der
Text beim Umschalten nicht springt. Zwischen den Segmenten gibt es keine Lücke.

## Neuronaler Atlas: Bewegung

`app/static/knowledge-graph.js` folgt dem Hero von kiwiki.xyz (`kiwiki-website/hive.js`):

- **Ablauf bei jedem Besuch:** Aufbau (Knoten spiralen herein, Kanten wachsen, Lichtwelle), dann
  `SHOWCASE_MS` Datenfluss (7 s, mit `prefers-reduced-motion` 2,5 s), dann `CALM_MS` (1,8 s)
  Ausklingen: Pakete und Drehung blenden über `state.energy` aus, neue Pakete entstehen nicht mehr.
  Danach ruht der Atlas (`state.paused`), der Knopf zeigt ▶. ▶ ruft `resumeMotion()` auf und läuft,
  bis ⏸ (`beginCalm(…, CALM_ON_PAUSE_MS)`, 0,7 s) gedrückt wird. Die Wahl wird nicht gespeichert.
- **Synapsenfeld:** `buildField()` legt 72 dekorative Punkte (40 unter 760 px) als Schale um den
  echten Graphen (Reichweite `FIELD_REACH` × Graphradius, je zwei nächste Nachbarn). Es läuft mit
  derselben Kamera und demselben Aufbau, hat keine Beschriftung und keine Trefferprüfung, wird bei
  Auswahl gedimmt und entfällt über `MAX_PAIRWISE_NODES` (180).
- **Datenpakete:** `drawPacket()` zeichnet Schweif (`PULSE_TAIL`, Anteil der Kante) und hellen Kern,
  für Graph und Feld. Takt `PULSE_EVERY_MS`, Weiterleitung `PULSE_HOP`, Obergrenzen `MAX_PULSES`
  und `MAX_FIELD_PULSES`.
- **Abruf:** `maybeRecall()` wählt alle 4–7 s ein sichtbares vorderes Dokument mit Nachbarn: Ring,
  fettes Label (`RECALL_LABEL_MS`), hervorgehobene Kanten, heiße Pakete zu den Nachbarn.
- Bei großen Graphen (> 180 Knoten) gibt es weder Feld noch Umgebungs-Pakete.

Messen statt schätzen: Statische Screenshots sagen nichts über Timing. Für Änderungen eine
Frame-Folge aufnehmen und den Anteil sich ändernder Pixel zwischen zwei Aufnahmen sowie die hellen
Paket-Pixel zählen (aus echten Screenshots, nicht per `getImageData` vom Canvas, das mit
`desynchronized: true` unzuverlässig zurückliest). Referenzwerte bei 1440 × 900 mit 21 Knoten:
Datenfluss 4–11 % Pixeländerung je 250 ms und 50–300 Paket-Pixel, in Ruhe 0 %.

## Cache-Schlüssel

Statische Dateien hängen mit `?v=…` in `layout.html`, `login.html` und `knowledge.html`. Bei jeder
Änderung an JS/CSS den Schlüssel der betroffenen Datei erhöhen, sonst sehen Nutzer den alten Stand.
Die Tests in `tests/test_frontend_regressions.py` und `tests/test_knowledge_graph_ui.py` prüfen die
aktuellen Werte und müssen mitgezogen werden.