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