Files
nova-control/PLAN.md
T
2026-06-28 23:33:35 +02:00

199 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.