From ea845336d6650cae471a608a9a29fefc1c503814 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Sun, 28 Jun 2026 23:33:35 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20Fix-Plan=20f=C3=BCr=2026=20Gates=20(P0+?= =?UTF-8?q?P1+P2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PLAN.md | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 PLAN.md diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..4b0391b --- /dev/null +++ b/PLAN.md @@ -0,0 +1,198 @@ +# NOVA Control Fix-Plan — 26 Gates + +> **Basis:** `index.html` (461 Zeilen, eine Datei, Vanilla JS, offline) +> **Ziel:** Alle 26 binären Gates schließen. P0+P1 vollständig, P2 nach Spec. + +--- + +## Architektur-Entscheidungen + +- **Eine Datei** bleibt — kein Splitting in CSS/JS +- **Event-Delegation** statt pro-Render-Listener (fix P1.7) +- **State-getriebenes Rendering**: `rv()` rendert NUR den aktiven View, kein Voll-Neuaufbau +- **CSS-Variablen** für Dark Mode (existiert bereits via `:root`, braucht `[data-theme="dark"]`-Block) + +--- + +## P0 — Blocker (3 Gates) + +### P0.1 — Datei-Dialog öffnet NICHT beim Laden +**File:** `index.html` +**Change:** Zeile `document.getElementById('imp').click();` **löschen** +**Verify:** Seite laden → kein Datei-Dialog. Manueller Import per Command-Palette funktioniert. + +### P0.2 — Command-Palette mit ≥8 Befehlen +**File:** `index.html` +**Change:** +1. `rp()` erweitern auf ≥8 Commands: Board, Streaks, Insights, Neues Task, Import, Export, Reset/Demo, 500 Tasks laden +2. `ld()` am Ende der Init aufrufen → `rp()` ausführen +3. Tasks als anspringbare Einträge in `inipal()` dynamisch hinzufügen +**Verify:** Cmd+K → Liste zeigt ≥8 Einträge. Pfeiltasten navigieren, Enter führt aus. + +### P0.3 — Dark Mode (realer Farbwechsel + Persistenz) +**File:** `index.html` +**Change:** +1. `[data-theme="dark"]` CSS-Block mit min `--bg`, `--surface`, `--text`, `--border` +2. `prefers-color-scheme: dark` Media Query in `at()` korrekt verdrahtet +3. System-Modus reagiert auf `matchMedia('(prefers-color-scheme:dark)').addEventListener('change', ...)` +4. `at()` validieren: `data-theme` auf `` korrekt gesetzt? CSS-Selektoren existent? +**Verify:** 🌙 → Hintergrund dunkel, Text hell. Reload → bleibt. System-Umschaltung in OS-Einstellungen → reagiert live. + +--- + +## P1 — Kern-Features (7 Gates) + +### P1.1 — Drag & Drop (Maus), volle Implementierung +**File:** `index.html` +**Change:** HTML5 DnD API: +- `draggable="true"` auf `.task` +- `dragstart`: `dataTransfer.setData('text/plain', taskId)` +- `dragover`: `e.preventDefault()` + Drop-Indikator (gestrichelte Linie/Platzhalter) +- `drop`: Task verschieben (Spalte + Position), `st` updaten, `sv()`, `rv()` +- Intra-Spalte (Reihenfolge) + Inter-Spalte (Statuswechsel) +**Verify:** Task mit Maus von Backlog → Aktiv ziehen. Position innerhalb Spalte ändern. Persistiert nach Reload. + +### P1.2 — Tastatur-DnD +**File:** `index.html` +**Change:** Bei fokussiertem Task: +- Ctrl+Alt+← → Spalte nach links (Status rückwärts) +- Ctrl+Alt+→ → Spalte nach rechts (Status vorwärts) +- Ctrl+Alt+↑/↓ → Position in Spalte ändern +- `aria-live="polite"` Region für Status-Ansagen +**Verify:** Task per Tastatur durch alle Spalten bewegen. Screenreader-kompatibel. + +### P1.3 — Feld-Schema-Drift behoben +**File:** `index.html` +**Change:** +1. `op()`-Create: `du` → `d` (Zeile ~260 im Original-Fix-Brief-Kontext) +2. `ldDemo()`: prüfen ob schon `d` verwendet wird → ja, aber `op()`-Create schreibt `du` +3. Task-Render im Board: liest `d` → bleibt korrekt +4. Chart: liest `df` für Abschlussdatum → ok +5. `dt` (created) und `df` (finished) konsistent halten +**Verify:** Neues Task mit Fälligkeitsdatum → nach Reload noch da. Keine `du`-Felder im localStorage. + +### P1.4 — Streak-Berechnung +**File:** `index.html` +**Change:** +1. Pro Habit: aktuelle Serie + längste Serie berechnen +2. Algorithmus: lokale Kalendertage, Wert > 0 = erfüllt +3. Lücke (Tag = 0) bricht aktuelle Serie +4. Heute noch nicht erfüllt → bricht NICHT (gestern zählt weiter) +5. Anzeige im Streak-View: "Aktuell: X Tage | Best: Y Tage" +**Verify:** Serie bricht korrekt. Gestern 1, heute 0 → Serie läuft weiter. Gestern 0, heute 1 → Serie = 1. + +### P1.5 — Export → verlustfreier Round-Trip +**File:** `index.html` +**Change:** +1. `exportData()`: JSON-Blob mit `{version: 1, tasks, habits, theme}` → Download +2. Command-Palette-Eintrag: "Export" +3. Import validiert `version`-Feld + `tasks` als Array +4. Round-Trip-Test: Export → Import → gleicher Zustand +**Verify:** 40 Tasks exportieren, Seite löschen, importieren → 40 Tasks zurück, gleiche Daten. + +### P1.6 — Zweiter Chart + Trend-Granularität +**File:** `index.html` +**Change:** +1. `` unter dem Trend-Chart +2. Verteilungs-Chart: Donut nach Priorität (Hoch/Mittel/Tief) +3. Trend-Chart auf **pro Woche** umstellen (nicht pro Monat) +4. Datenbasis für Trend: Abschlussdatum (`df`) +5. Beide Charts mit Achsen/Labels/Legende, Empty-State bei 0 Daten +**Verify:** Insights zeigt zwei Charts. Trend zeigt Wochen-Balken. Donut zeigt Prio-Verteilung. + +### P1.7 — Listener-Leak + Suchfeld-Fokusverlust +**File:** `index.html` +**Change:** +1. `rb()`: KEINE `innerHTML`-Neusetzung mehr — nur Board-Grid updaten +2. Toolbar (Suche, Prioritätsfilter) wird **einmalig** im HTML gerendert, nicht pro `rb()` +3. Event-Delegation: `click`/`keydown` auf `#board-section` **einmalig** im Init binden +4. Suche filtert per CSS-Klasse `display:none` statt Neuaufbau → kein Fokusverlust +**Verify:** Tippen in Suche → Fokus bleibt. 10× zwischen Views wechseln → Klick öffnet Modal genau 1×. + +--- + +## P2 — Spec-Konformität & Politur (10 Gates) + +### P2.1 — Heatmap: 26-Wochen-Grid +**File:** `index.html` +**Change:** `rs()` → Heatmap als Wochen-Grid (Spalten=Wochen, Zeilen=Mo..So), 26 Wochen zurück. +Hover-Tooltip: `
` mit Datum + Wert. +**Verify:** Kein 28-Tage-Streifen mehr. Wochen-Grid sichtbar. Tooltip zeigt korrektes Datum. + +### P2.2 — Responsive / kein Horizontal-Scroll +**File:** `index.html` +**Change:** Media Queries: +- `@media (max-width: 768px)`: Layout einspaltig, Sidebar kollabiert +- `@media (max-width: 480px)`: Board-Spalten untereinander +- Board bei schmalen Screens: `overflow-x: auto` NUR auf `.board`, nicht `body` +**Verify:** 360px Breite → kein horizontaler Body-Scroll. Board scrollt innerhalb. + +### P2.3 — `prefers-reduced-motion` +**File:** `index.html` +**Change:** +```css +@media (prefers-reduced-motion: reduce) { + .toast { animation: none; } + .task { transition: none; } +} +``` +**Verify:** OS-Einstellung "Reduzierte Bewegung" → keine Animationen. Toast erscheint sofort. + +### P2.4 — Focus-Trap (Modal + Palette) +**File:** `index.html` +**Change:** +1. Modal: Fokusbare Elemente sammeln, Tab/Shift+Tab zyklisch +2. Palette: dito +3. ESC schließt + Fokus zurück auf Auslöser +4. Palette: `clp()` setzt Fokus zurück (aktuell nicht) +**Verify:** Modal offen → Tab zyklisch. ESC → Fokus auf vorherigem Element. + +### P2.5 — Palette-Tastatur korrekt +**File:** `index.html` +**Change:** Keydown-Handler auf `#pal-q` (Eingabefeld) statt `#pal-list`: +- ↑/↓ navigieren Liste (Index-Update + visuelles Highlight) +- Enter führt markierten Befehl aus +- Fokus bleibt im Eingabefeld +**Verify:** Pfeiltasten navigieren bei Fokus im Suchfeld. Enter führt aus. + +### P2.6 — Hash-Routing +**File:** `index.html` +**Change:** +1. Navigation setzt `location.hash = '#/' + view` +2. `window.addEventListener('hashchange', ...)` schaltet View +3. Beim Laden: `if (location.hash)` → View aus Hash setzen +4. Default: `#/board` +**Verify:** URL `#/insights` → Insights-View. Reload → bleibt. Browser-Zurück funktioniert. + +### P2.7 — Stress-Aktion "500 Tasks laden" +**File:** `index.html` +**Change:** +1. `gen500()`: 500 Tasks generieren (Batch-Insert, kein 500× `push` in Render-Loop) +2. Command-Palette-Eintrag "500 Tasks laden" +3. Performance: `requestAnimationFrame` für Batch-Rendering, keine 500 Listener +**Verify:** 500 Tasks → Board flüssig, Suche filtert in Echtzeit, Drag&Drop ruckelfrei. + +### P2.8 — Robustheit +**File:** `index.html` +**Change:** +1. `ld()`: `nv_t` validieren → `Array.isArray()`? JSON.parse-Fehler? → Fallback `[]` +2. Import: Jeden Task auf `id`, `tl`, `st` prüfen → fehlerhafte Einträge → Fehlermeldung ohne Crash +3. Kein Datenverlust am bestehenden Zustand bei fehlerhaftem Import +**Verify:** Kaputtes JSON importieren → klare Fehlermeldung, bestehende Tasks unverändert. `nv_t` = `"nicht ein array"` → App startet mit Seed-Daten. + +--- + +## Phasen-Plan + +| Phase | Gates | Aufwand | +|-------|-------|---------| +| **Phase 1: Blocker** | P0.1–P0.3 | ~15 min | +| **Phase 2: Kern** | P1.1–P1.7 | ~90 min | +| **Phase 3: Politur** | P2.1–P2.8 | ~45 min | +| **Phase 4: Gate-Check** | Alle 26 | ~15 min | + +**Gesamt:** ~2.5h, 26 binäre Gates. + +--- + +> **Nach Freigabe:** Implementierung in 4 Phasen, nach jeder Phase Commit + Gate-Check.