Files
kochwas/docs/superpowers/plans/2026-04-19-post-review-roadmap.md
hsiegeln 2c1fd29003
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 30s
docs(plan): Post-Review-Roadmap fuer Items A-I
Sequenziert die nach review-fixes-2026-04-18 offenen Punkte aus
OPEN-ISSUES-NEXT.md in 5 Tiers: Cleanup-Batch (I+H+F+G) direkt
nach Merge, Search-State-Store als eigene Phase, SearXNG-Recovery
reaktiv, Rest trigger-basiert.

Jedes Item hat Scope, Files, Gate und Aufwand — tief genug fuer
/gsd-plan-phase als naechsten Schritt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 11:34:19 +02:00

218 lines
10 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.
# Post-Review Roadmap 2026-04-19
> **Quelle:** `docs/superpowers/review/OPEN-ISSUES-NEXT.md` (Items AI) + UAT `kochwas-dev.siegeln.net` (Branch `review-fixes-2026-04-18`, 2026-04-19).
> **Branch-Status:** Merge-ready — 8 atomare Commits, 184/184 Tests grün, svelte-check 0 Errors, UAT durchgeklickt (Profil, Suche, Rezept-Actions, Wunschliste, Preview, Admin, API-Shapes).
> **Goal:** Die nach dem Review-Branch offenen 9 Items in priorisierte Phasen übersetzen, damit jede einzeln via `/gsd-plan-phase` → `/gsd-execute-phase` abgearbeitet werden kann.
> **Architecture:** Keine Groß-Refactor-Phase, sondern getaktete Einzel-Phasen mit klarem Gate. Reihenfolge folgt Risiko × Wert: erst kleine Wins, dann eine strukturelle Phase (A), dann opportunistische.
> **Tech-Stack:** SvelteKit, TypeScript-strict, Zod, Vitest, Playwright-UAT, better-sqlite3, Service-Worker.
---
## Merge-Entscheidung
**Jetzt mergen.** Der Branch-UAT auf `kochwas-dev` war clean (siehe Session-Log 2026-04-19). Findings aus dem UAT:
- Kommentar-Delete hat keinen UI-Button (MINOR, kein Branch-Regress — Zustand schon vor Refactor so).
- `/preview` ohne `?url=` bleibt im Dauer-Lader (MINOR, harmlos — niemand ruft die Route blank auf).
Beide werden als LOW-Items unten aufgenommen, sind aber **kein Merge-Blocker**.
---
## Tier-Zuordnung
| Tier | Items | Wann | Aufwand total |
|------|-------|------|---------------|
| 1 — Schneller Cleanup-Batch | F, G, H, I | Direkt nach Merge | ~2 h |
| 2 — Phase Search-State-Store | A | Nächster größerer Slot | halber Tag |
| 3 — Phase SearXNG-Recovery | C | Wenn Rate-Limit-Schmerz konkret auftaucht | 12 h |
| 4 — Opportunistisch | B, D, E, + Kommentar-Delete, Preview-Guard | Trigger-basiert | reaktiv |
| 5 — Geparkt | yauzl / Phase 5b | Nur bei explizitem Bedarf | nicht geplant |
---
## Tier 1 — Cleanup-Batch (1 Phase, 4 Items)
**Phasenname-Vorschlag:** `Phase Cleanup-Batch nach Review-Fixes` (via `/gsd-new-phase` oder `/gsd-add-phase`).
Alle vier Items touchen wenige Zeilen, sind LOW/MEDIUM, und lassen sich in 12 Commits pro Item sauber atomar committen. **Gebündelt statt einzeln**, weil Kontext-Overhead pro Einzelphase größer wäre als der Fix.
### Item I — RecipeEditor auf `$derived` umstellen
**Files:** `src/lib/components/RecipeEditor.svelte:28,97102,113,121`, `src/routes/recipes/[id]/+page.svelte:43`
Pattern aktuell: `let foo = recipe.bar` → Svelte-5-Warning, Snapshot-only, bricht bei In-Place-Mutation des Rezepts.
**Plan pro Warnung:**
- [ ] Warning-Site auslesen, beurteilen: soll `foo` Mutations am `recipe` tracken oder bewusst ein Snapshot bleiben?
- [ ] Track-Fall: `let foo = $derived(recipe.bar)`.
- [ ] Snapshot-Fall: Variable umbenennen (z. B. `initialFoo`) und als `$state` deklarieren mit Kommentar `// intentional snapshot`.
- [ ] `npm run check` — 0 Warnings erwartet.
- [ ] `npm test` — grün.
- [ ] Commit: `refactor(editor): RecipeEditor auf $derived umstellen`.
**Gate:** svelte-check 0 Warnings, alle Editor-Flows (Titel, Zutaten, Schritte) per Hand getestet — In-Place-PATCH zeigt aktualisierten Wert.
### Item H — RecipeEditor Bild-Upload/Delete auf `asyncFetch`
**Files:** `src/lib/components/RecipeEditor.svelte:54,83`
**Warum zusammen mit I:** Gleiche Datei, gleicher Touch.
- [ ] Zeile 54 (Upload): `const res = await fetch(...); if (!res.ok) alertAction(...)``await asyncFetch(...)`.
- [ ] Zeile 83 (Delete): dito.
- [ ] Error-Messages beibehalten.
- [ ] Test manuell: Bild hochladen + löschen in einem Test-Rezept.
- [ ] Commit: `refactor(editor): Bild-Upload/Delete auf asyncFetch`.
**Gate:** Bild-Upload + Delete-Flow grün in manuellem Smoke; `npm run check` clean.
### Item F — Inline UI-Constants in `src/lib/theme.ts`
**Files:** Neu `src/lib/theme.ts`, Modify `ConfirmDialog.svelte`, `ProfileSwitcher.svelte`, weitere Call-Sites via `grep`.
- [ ] `grep -rn "z-index:\|border-radius: 999\|setTimeout.*[0-9]{3,4}" src/lib/components src/routes` — Call-Sites auflisten.
- [ ] `src/lib/theme.ts` anlegen mit: `MODAL_Z_INDEX = 1000`, `POPOVER_Z_INDEX = 900`, `PILL_RADIUS = '999px'` (nur Werte, die wirklich mehrfach vorkommen — YAGNI).
- [ ] Call-Sites durchgehen, Inline-Werte durch Import ersetzen.
- [ ] `npm run check` + `npm test`.
- [ ] Commit: `refactor(ui): shared theme constants fuer z-index/radius`.
**Gate:** Keine visuellen Änderungen beim Durchklicken (Confirm-Dialog, Profile-Switcher, Toast, Menü).
### Item G — `requireProfile()` mit optionaler Message
**Files:** `src/lib/client/confirm.svelte.ts` (oder wo `requireProfile` liegt), `src/routes/wishlist/+page.svelte:38`
**Option A — minimal invasiv:** `wishlist/+page.svelte` belassen, Custom-Message-Konstante in der Datei. Dann **nur dokumentieren** im Kommentar der `requireProfile`-Funktion, dass die Wunschliste bewusst eigenständig ist.
**Option B — DRY:** `requireProfile(message?: string): Profile | null` mit Fallback auf Default.
- [ ] **Entscheidung zuerst** — Option A sparsamer, Option B konsistent. Ich empfehle **A**, weil die Custom-Message in der Wunschliste wirklich Kontext ist („um mitzuwünschen"), nicht nur Deko. Aber: wenn B, dann sauber mit Unit-Test.
- [ ] Commit: `refactor(client): requireProfile Custom-Message entscheiden` (je nach Entscheidung).
**Gate:** Wunschliste zeigt beim Klick ohne Profil die korrekte Message; keine anderen Sites verhalten sich anders.
---
## Tier 2 — Phase Search-State-Store (Item A)
**Empfohlener Einstieg:** `/gsd-discuss-phase Search-State-Store` (per OPEN-ISSUES Empfehlung), nicht direkt `/gsd-plan-phase`.
**Warum eigene Phase:** Touch `+page.svelte` (808 L) + `+layout.svelte` (678 L), Reactive-Glue zwischen Header-Search-Dropdown und Home-Search muss 1:1 übernommen werden. **UAT-pflichtig**, weil es keine UI-Tests gibt.
**Scope-Sketch (für die Discuss-Phase):**
- Neu: `src/lib/client/search.svelte.ts` — reaktiver Store mit `query`, `hits`, `loading`, `error`, `hasMore`, `search(q)`, `loadMore()`, `clear()`.
- Debounce (aktuell in `+page.svelte`) in den Store migrieren.
- Web-Fallback-Logik (lokal leer → Web-Suche) beibehalten — Store muss beide Modi kennen (`mode: 'local' | 'web'`).
- `+layout.svelte` Header-Dropdown zuerst migrieren (kleineres Surface), dann `+page.svelte`.
- Duplizierten `$state`-Block entfernen.
**Verifikation pro Wave:**
1. Nach Store-Anlegen: Vitest-Unit-Tests für Store (mocked fetch).
2. Nach Layout-Migration: Browser-UAT Header-Dropdown auf Rezept-Seite + Startseite.
3. Nach Page-Migration: Browser-UAT Live-Suche (lokaler Treffer, Web-Fallback, Empty-State), inkl. Deep-Link `?q=xyz`.
4. Playwright-Script wiederholen (existiert aus 2026-04-19 UAT).
**Gate:** Alle 3 UAT-Pfade clean; `+page.svelte` unter 700 L; `+layout.svelte` unter 600 L; `npm test` + `npm run check` grün.
**Aufwand:** halber Tag (46 h).
---
## Tier 3 — Phase SearXNG-Rate-Limit-Recovery (Item C)
**Trigger:** Wenn konkreter Schmerz (User merkt „Suche liefert komische alte Sachen" oder SearXNG logt 429/403 gehäuft).
**Scope:**
- `src/lib/server/search/searxng.ts`: `lastFailureAt: Map<string, number>` pro Domain.
- Exponentieller Backoff: bei wiederholtem 429/403 → 1 min → 5 min → 30 min (Cap).
- Response-Shape erweitern: `isStale?: boolean` wenn aus Cache nach Fail.
- UI: `src/routes/+page.svelte` Such-Ergebnisheader zeigt „Ergebnisse evtl. veraltet" wenn `isStale`.
**Tests (TDD, Vitest):**
- Simulierter 429 → nächster Call innerhalb 1 min geht nicht raus, Response aus Cache mit `isStale: true`.
- Nach 1 min Wartezeit → Call geht wieder raus.
- Nach erfolgreichem Call → Backoff-Zähler resettet.
**Gate:** Tests grün; manuell: Fake-429 injizieren (z. B. über ENV-Toggle im Dev), UI zeigt Hinweis.
**Aufwand:** 12 h.
---
## Tier 4 — Opportunistisch (Trigger-gesteuert)
Alle Items hier werden **nicht proaktiv** geplant. Sie warten auf ihren Trigger.
### Item B — RecipeEditor/RecipeView in Sub-Components
**Trigger:** Zweite Person arbeitet am Projekt mit, ODER Editor-Bug-Hunt wird unübersichtlich.
**Scope-Sketch:** `IngredientRow.svelte`, `StepList.svelte`, `TimeDisplay.svelte`, `ImageUploadBox.svelte`.
**Vorbedingung:** Item I muss zuerst durch sein (die pre-existing Warnings würden sonst in die Sub-Components wandern).
### Item D — SW Zombie-Cleanup unter Drosselung
**Trigger:** Nächster Service-Worker-Touch (z. B. neue Cache-Strategy oder Chunks-Manifest-Änderung).
**Scope:** Mit DevTools-Throttling-Profil „Slow 3G" durchgehen, prüfen ob der 1500ms-Timeout in `pwa.svelte.ts` False-Positives triggert. Falls ja: Timeout konfigurierbar oder Heuristik verfeinern.
### Item E — JSON-LD Parser Locale-Edge-Cases
**Trigger:** Echter Import-Bug aus dem Alltag.
**Scope:** Gezielter Test für die Fail-URL + Fix. Kein Vorab-Sprint.
### Kommentar-Delete-UI (UAT 2026-04-19)
**Status:** Kommentar-DELETE-Endpoint existiert, aber keine UI-Exposition.
**Vorschlag:** In `src/routes/recipes/[id]/+page.svelte` Kommentar-Liste pro Eintrag ein 🗑-Button für den Autor (`comment.profile_id === profileStore.active?.id`). Mit `confirmAction`-Dialog.
**Trigger:** Erster Wunsch, einen Kommentar loszuwerden.
**Aufwand:** ~30 min.
### Preview-ohne-URL-Guard (UAT 2026-04-19)
**Status:** `/preview` ohne `?url=` bleibt im Dauer-Lader.
**Vorschlag:** `src/routes/preview/+page.svelte` Zeile 33ff.: wenn `u` leer, `errored = 'Kein URL-Parameter gesetzt'` oder Redirect auf `/`. **2-Zeilen-Fix.**
**Trigger:** Bevor jemand die Route bookmarked.
**Aufwand:** 5 min — könnte man auch sofort in Tier 1 reinnehmen, ist aber so trivial, dass es ohne Phase geht.
---
## Tier 5 — Geparkt
### Phase 5b — ZIP-Backup-Restore via `yauzl`
**Status:** Dokumentiert in `ARCHITECTURE.md:33` und `session-handoff-2026-04-17.md`. Dependency bleibt installiert.
**Kein Plan.** Wird erst aktiviert, wenn jemand wirklich ein Backup-ZIP zurückspielen will. Dann: `/gsd-plan-phase Phase-5b-ZIP-Restore`.
---
## Empfohlene Ausführungs-Reihenfolge
1. **Merge** `review-fixes-2026-04-18``main`.
2. **Neuen Branch** `cleanup-batch-post-review` → Tier 1 (Items I + H zusammen in einem Wave, dann F, dann G).
3. **Merge** → Tier 2 Discuss: `/gsd-discuss-phase Search-State-Store`.
4. Tier 2 execution.
5. Tier 3 erst wenn der Trigger da ist, sonst Tier 4 abwarten.
---
## Commit-Stil für alle Phasen
- Deutsch, kleinteilig, eine Idee pro Commit.
- Body erklärt das *Warum* (Reference auf Item-Nummer aus diesem Doc).
- Nach jedem Commit `npm test` + `npm run check` grün.
- Push direkt nach Commit (CI baut Branch-Tag, siehe `docker.yml`).