docs: Fix-Plan für 26 Gates (P0+P1+P2)
This commit is contained in:
@@ -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.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.
|
||||
Reference in New Issue
Block a user