docs: Fix-Plan für 26 Gates (P0+P1+P2)

This commit is contained in:
Hermes Agent
2026-06-28 23:33:35 +02:00
parent 6a3fdfc65c
commit ea845336d6
+198
View File
@@ -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 `<html>` 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. `<canvas id="chart2">` 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: `<div class="heatmap-tip">` 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.1P0.3 | ~15 min |
| **Phase 2: Kern** | P1.1P1.7 | ~90 min |
| **Phase 3: Politur** | P2.1P2.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.