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>
10 KiB
Post-Review Roadmap 2026-04-19
Quelle:
docs/superpowers/review/OPEN-ISSUES-NEXT.md(Items A–I) + UATkochwas-dev.siegeln.net(Branchreview-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-phaseabgearbeitet 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).
/previewohne?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
fooMutations amrecipetracken oder bewusst ein Snapshot bleiben? - Track-Fall:
let foo = $derived(recipe.bar). - Snapshot-Fall: Variable umbenennen (z. B.
initialFoo) und als$statedeklarieren 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.tsanlegen 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 mitquery,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.svelteHeader-Dropdown zuerst migrieren (kleineres Surface), dann+page.svelte.- Duplizierten
$state-Block entfernen.
Verifikation pro Wave:
- Nach Store-Anlegen: Vitest-Unit-Tests für Store (mocked fetch).
- Nach Layout-Migration: Browser-UAT Header-Dropdown auf Rezept-Seite + Startseite.
- Nach Page-Migration: Browser-UAT Live-Suche (lokaler Treffer, Web-Fallback, Empty-State), inkl. Deep-Link
?q=xyz. - 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?: booleanwenn aus Cache nach Fail. - UI:
src/routes/+page.svelteSuch-Ergebnisheader zeigt „Ergebnisse evtl. veraltet" wennisStale.
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
- Merge
review-fixes-2026-04-18→main. - Neuen Branch
cleanup-batch-post-review→ Tier 1 (Items I + H zusammen in einem Wave, dann F, dann G). - Merge → Tier 2 Discuss:
/gsd-discuss-phase Search-State-Store. - Tier 2 execution.
- 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 checkgrün. - Push direkt nach Commit (CI baut Branch-Tag, siehe
docker.yml).