All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 30s
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>
218 lines
10 KiB
Markdown
218 lines
10 KiB
Markdown
# Post-Review Roadmap 2026-04-19
|
||
|
||
> **Quelle:** `docs/superpowers/review/OPEN-ISSUES-NEXT.md` (Items A–I) + 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 | 1–2 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 1–2 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,97–102,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 (4–6 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:** 1–2 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`).
|