diff --git a/docs/specs/3-katalog-suche.md b/docs/specs/3-katalog-suche.md index 065e626..cee1d5f 100644 --- a/docs/specs/3-katalog-suche.md +++ b/docs/specs/3-katalog-suche.md @@ -159,7 +159,17 @@ Hinweise: Das Anlegen einer gespeicherten Spalte schreibt `games` einmal neu (un **6. RPC `public.search_catalog_games`** – Vertrag in § 4.2. -### 3.3 Doku zum Datenmodell +### 3.3 Reihenfolge zu 2b und 2c (Spec #2) + +**Merge-Reihenfolge: 2b → 2c → 3a.** 2b (`admin_merge_games`, `repoint_game_references`, Branch `feature/2b-katalog-zusammenfuehren`) und 2c (`game_reports`, Erweiterung des Fremdschlüssel-Wächters) sind in Arbeit; 3a wird danach gemergt. Folgen für 3a: + +- **Migration:** Zeitstempel von `_catalog_search.sql` liegt nach den Migrationen von 2b und 2c. B1 erstellt bzw. aktualisiert den Branch von `main` nach dem Merge von 2c (bei früherem Start: vor dem PR auf `main` aktualisieren und den Zeitstempel bei Bedarf neu vergeben, solange 3a nicht gemergt ist). +- **`manual_fields`-Check:** vollständige Liste von `main` übernehmen, `alt_titles` ergänzen (3.2 Nr. 5). +- **Fremdschlüssel-Wächter in `07_catalog_merge.test.sql`:** Die neue FK `game_alt_titles.game_id` macht den Wächter rot. 3a ergänzt sie im `set_eq` in der Gruppe **„bewusst ausgelassen: bleiben am ausgeblendeten Quell-Eintrag“** (neben `game_genres`, `game_mechanics`, `game_categories`, `game_publishers`), gemäß Spec-F6 A. `admin_merge_games`/`repoint_game_references` bleiben unverändert. Die Einträge von 2c (`game_reports.target_game_id` behandelt, `game_reports.provisional_game_id` ausgelassen) bleiben erhalten. +- **`00_schema.test.sql`:** Tabellenzahl von `main` (nach 2c) + 1. +- **pgTAP-Nummer:** `09_catalog_search.test.sql` (07 = 2b, 08 = 2c). + +### 3.4 Doku zum Datenmodell `docs/datenmodell-r1.md`: neue Tabelle `game_alt_titles`, generierte Spalten, Funktion `search_normalize`, RPC `search_catalog_games`, Erweiterungen, neuer `manual_fields`-Schlüssel; Abschnitt „API für das Frontend“ um die RPC ergänzen. `supabase/README.md`: Tabellenzahl, Hinweis „erste RPC mit `execute` für `anon`“. `docs/katalog-pflege.md`: Abschnitt „Alternative Titel pflegen“ (Studio-SQL und CSV-Spalte `alt_titles`). @@ -380,14 +390,18 @@ Werkzeuge: pgTAP (`supabase test db`), Deno-Tests (Startbestand-Werkzeug), `flut **pgTAP-Datei `supabase/tests/database/09_catalog_search.test.sql`** (3a). Die Nummern `07` und `08` sind für Spec #2 (2b, 2c) reserviert. Die Tests laufen mit eingespieltem Startbestand: Fixtures mit festen Test-UUIDs und einem eindeutigen Kunstwort in allen Titeln und Verlagen (z. B. `qxz`), sodass Suchanfragen nie Startbestandsspiele treffen; bei Abfragen ohne Suchbegriff wird nur auf die Fixture-IDs geprüft. -**`00_schema.test.sql`:** Tabellenzahl + 1 gegenüber dem Stand von `main` beim Merge (2c erhöht ebenfalls; wer später merged, gleicht ab). +**Auswirkung auf bestehende Tests** (geprüft gegen `main` und `feature/2b-katalog-zusammenfuehren`): + +- **`00_schema.test.sql`:** nur die Tabellenzahl (+ 1 gegenüber dem Stand von `main` beim Merge von 3a, also nach 2b und 2c, siehe 3.3). Es gibt keine `columns_are`-Prüfungen; die neuen generierten Spalten `title_search`/`name_search` brechen dort nichts. +- **`06_catalog_foundation.test.sql`:** Die Spaltenrechte-Prüfungen (Z. 293–302) arbeiten mit festen Spaltenlisten und sind von den generierten Spalten nicht betroffen. **Aber Z. 532–535 prüft `manual_fields` von „Testspiel A“ exakt.** Bekäme Testspiel A alternative Titel, käme `alt_titles` hinzu und der Test würde rot. Vorgabe für B3: die alternativen Titel in `supabase/catalog/testdata/startbestand.csv` an **Testspiel B** hängen (Testspiel A bleibt unverändert); nur falls das nicht geht, passt B2 die erwartete Liste in Z. 532–535 an. Testspiel B hat bereits zwei Verlage und eignet sich auch für Krit. 3. +- **`07_catalog_merge.test.sql`** (2b): Fremdschlüssel-Wächter um `game_alt_titles.game_id` erweitern (3.3). ### 7.1 Abdeckungsmatrix | AK | Unit | Widget | Integration/E2E | RLS/pgTAP | Task | |---|---|---|---|---|---| | Krit. 1 | Repository: Parameter nur aus gesetzten Werten, Suchbegriff < 2 Zeichen wird nicht gesendet; DTO-Parsing (alle Felder nullable, `publisher_names` leer); Controller: Entprellung 300 ms, Enter sofort, nur letzte Antwort zählt | Suchfeld: Eingabe → Liste; Löschen-Schaltfläche; Treffer-Kachel mit Kurzangaben, fehlende Angaben weggelassen | Web: Teil eines Titels in anderer Schreibweise und mit Sonderzeichen findet das Spiel | `search_normalize`-Pflichtfälle (3.2); Teilzeichenkette, Groß/Klein, Sonderzeichen, Umlaut/ß; mehrere Wörter (UND); Schreibweise ohne Leerzeichen (`qc`); Relevanzstufen 0–4 in erwarteter Reihenfolge; gleiche Stufe → Ähnlichkeit, Titel, ID; `'!!!'` → 0 Treffer; `null`/Leerzeichen → kein Suchbegriff | B1, B2, F1, F2, T1, T2 | -| Krit. 2 | DTO: `matched_alt_title` | Kachel zeigt „auch: …“ | Web: Suche nach deutschem und englischem alternativen Titel findet dasselbe Spiel | Treffer über `de`- und `en`-Titel; Spiel nur einmal, auch wenn Haupt- und alternativer Titel treffen; `matched_alt_title` nur bei besserer Stufe; Tabelle: Checks (Sprache, Länge, nur Sonderzeichen → 23514), Unique (Schreibweise egal → 23505); RLS: `anon` liest Titel von Katalogspielen, nicht die eines fremden privaten Spiels; `anon`/`authenticated` können nicht schreiben (42501); Einfügen als `postgres` markiert `alt_titles` in `manual_fields`; Check akzeptiert `alt_titles`, lehnt unbekannte Schlüssel ab. Deno: Format, Sprache, Länge, Duplikat, gleich Haupttitel, `:` im Titel; erzeugte SQL; Idempotenz (zweites Einspielen ändert nichts, `updated_at` stabil; entfernter alternativer Titel wird gelöscht) | B1, B2, B3, T1, T2 | +| Krit. 2 | DTO: `matched_alt_title` | Kachel zeigt „auch: …“ | Web: Suche nach deutschem und englischem alternativen Titel findet dasselbe Spiel | Treffer über `de`- und `en`-Titel; Spiel nur einmal, auch wenn Haupt- und alternativer Titel treffen; `matched_alt_title` nur bei besserer Stufe; Tabelle: Checks (Sprache, Länge, nur Sonderzeichen → 23514), Unique (Schreibweise egal → 23505); RLS: `anon` liest Titel von Katalogspielen, nicht die eines fremden privaten Spiels; `anon`/`authenticated` können nicht schreiben (42501); Einfügen als `postgres` markiert `alt_titles` in `manual_fields`; Check akzeptiert `alt_titles`, lehnt unbekannte Schlüssel ab. **Idempotenz in pgTAP** über die neu erzeugten `.inc`-Fixtures (`startbestand_fixture.inc` zweimal einspielen, wie in `06`): zweites Einspielen ändert keine Zeile von `game_alt_titles` (Vergleich ohne `updated_at`, dazu `updated_at` unverändert); ein in der CSV entfernter alternativer Titel wird gelöscht; geänderte Schreibweise aktualisiert nur `title`. Deno (nur das Skript, ohne Datenbank): Format, Sprache, Länge, Duplikat, gleich Haupttitel, `:` im Titel; erzeugte SQL (Snapshot) | B1, B2, B3, T1, T2 | | Krit. 3 | – | Kachel: Verlage (bis zwei, „+n“) | Web: Verlagsname findet dessen Spiele | Verlag findet alle seine Spiele; Spiel mit zwei Verlagen über beide; Wort aus Titel + Wort aus Verlag (UND); `publisher_names` sortiert, vollständig; Stufe 4 hinter Titeltreffern | B1, B2, T2 | | Krit. 4 | `CatalogSearchQuery`: Grenzen, `activeFilterCount`; URL-Parameter lesen/schreiben (Rundreise, ungültige Werte ignoriert); Controller: Filteränderung startet ab Versatz 0 | Filterbereich: jede Filterart setzt den Wert; Zurücksetzen; Bottom Sheet (kompakt) und Seitenleiste (≥ 1024 dp); Begriffe nach Sprache sortiert und benannt; Fehler beim Laden der Begriffe | Web: Filter Genre + Spielerzahl mit Suchbegriff | je Filter allein; Kombination aller Filter (UND); zwei Genres → nur Spiele mit beiden (Spec-F1); fehlende Angaben werden ausgeschlossen (Spec-F2); unbekannte Genre-ID → 0 Treffer; Duplikate im Array zählen einfach | B1, B2, F1, F2, U1, T1, T2 | | Krit. 5 | Controller: `loadMore` hängt an, verwirft doppelte IDs, nicht doppelt gleichzeitig; Fehler beim Nachladen behält Treffer; `retry` | Listenfuß: Ladeanzeige, „Weitere laden“, Fehler + „Erneut versuchen“; automatisches Nachladen beim Scrollen | Web: ≥ 31 Fixture-Treffer, Scrollen lädt die zweite Seite; „Weitere laden“ per Tastatur | Seiten ohne Überschneidung, Vereinigung = Gesamtmenge, Reihenfolge stabil über Aufrufe; `total_count` gleich in allen Zeilen und Seiten | B2, F2, T1, T2 | @@ -428,9 +442,9 @@ Werkzeuge: pgTAP (`supabase test db`), Deno-Tests (Startbestand-Werkzeug), `flut | # | Agent | Aufgabe | abhängig von | |---|---|---|---| | B1 | backend-dev | Migration `_catalog_search.sql` nach 3.2: Erweiterungen, `search_normalize`, generierte Spalten + Trigramm-Indizes, `game_alt_titles` (Checks, Unique, Trigger `set_updated_at` und `game_links_mark_manual('alt_titles')`, RLS, Grants), `manual_fields`-Check mit vollständiger Liste + `alt_titles`, RPC `search_catalog_games` nach 4.2 (Prüfungen, Treffer-Regel, Relevanz, Filter, Sortierung, Rechte: `revoke` von allen, dann `grant execute` an `anon`, `authenticated`) | – | -| B2 | backend-dev | pgTAP `09_catalog_search.test.sql` nach 7.1; `00_schema.test.sql` Tabellenzahl | B1 | -| B3 | backend-dev | Startbestand-Werkzeug nach 4.3: `alt_titles` parsen, prüfen, SQL erzeugen; Deno-Tests (neu und geänderter Test Z. 253); Testdaten-CSV um alternative Titel ergänzen; alle erzeugten Dateien neu erzeugen | B1 | -| B4 | backend-dev | Doku nach 3.3: `docs/datenmodell-r1.md`, `supabase/README.md`, `docs/katalog-pflege.md` (Abschnitt „Alternative Titel pflegen“, SQL-Bausteine wie in pgTAP ausgeführt) | B1, B3 | +| B2 | backend-dev | pgTAP `09_catalog_search.test.sql` nach 7.1 (inkl. Idempotenz der alternativen Titel über die `.inc`-Fixtures); `00_schema.test.sql` Tabellenzahl; Fremdschlüssel-Wächter in `07_catalog_merge.test.sql` um `game_alt_titles.game_id` („bewusst ausgelassen“) ergänzen (3.3); `06` Z. 532–535 nur anpassen, falls B3 Testspiel A ändern muss | B1, B3 | +| B3 | backend-dev | Startbestand-Werkzeug nach 4.3: `alt_titles` parsen, prüfen, SQL erzeugen; Deno-Tests (neu und geänderter Test Z. 253); Testdaten-CSV: alternative Titel (de und en) an **Testspiel B**, nicht an Testspiel A (§ 7, Auswirkung auf `06`); alle erzeugten Dateien neu erzeugen | B1 | +| B4 | backend-dev | Doku nach 3.4: `docs/datenmodell-r1.md`, `supabase/README.md`, `docs/katalog-pflege.md` (Abschnitt „Alternative Titel pflegen“, SQL-Bausteine wie in pgTAP ausgeführt) | B1, B3 | | B5 | backend-dev | Annahmen prüfen und messen: Erweiterungen lokal verfügbar (und mit devops auf Staging, D1); `statement_timeout` von `anon`/`authenticated`; Skript `supabase/tests/perf/catalog_search_perf.sql` mit synthetischen Daten (nur lokal, in einer zurückgerollten Transaktion), `explain (analyze, buffers)` und p95 im PR dokumentieren | B1 | | T1a | test-engineer | Review der pgTAP- und Deno-Abdeckung gegen 7.1, Lücken ergänzen | B2, B3 | @@ -457,7 +471,8 @@ Werkzeuge: pgTAP (`supabase test db`), Deno-Tests (Startbestand-Werkzeug), `flut ### 8.4 Reihenfolge und Parallelität ``` -3a B1 → B2, B3, B5 (parallel) → B4 → T1a → Merge (Chris) +#2 2b gemergt → 2c gemergt (Voraussetzung für den Merge von 3a, 3.3) +3a B1 → B3, B5 (parallel) → B2 → B4 → T1a → Merge (Chris) 3b U1 ‖ F1 → F2 → F3 → T1, T2 (startet parallel zu 3a, gegen den Vertrag § 4.2) └─ Integration gegen die echte RPC, sobald 3a gemergt ist → Merge (Chris) 3c D1 (nach 3a) → T4, T3 (nach 3b) → C1 → Merge (Chris), Issue #3 geschlossen @@ -465,6 +480,7 @@ Werkzeuge: pgTAP (`supabase test db`), Deno-Tests (Startbestand-Werkzeug), `flut - Der Vertrag in § 4.2 ist verbindlich für 3a und 3b; Änderungen daran nur über diese Spec. - 3b wird erst nach 3a gemergt (sonst ruft die App eine fehlende RPC auf). +- Merge-Reihenfolge im Backend: **2b → 2c → 3a** (3.3). Arbeiten an 3a und 3b dürfen vorher beginnen; vor dem PR von 3a wird auf `main` aktualisiert. - Merge nur durch Chris. Kein ADR (9.4). ## 9. Risiken & offene Fragen @@ -479,7 +495,7 @@ Werkzeuge: pgTAP (`supabase test db`), Deno-Tests (Startbestand-Werkzeug), `flut | R4 | `search_catalog_games` ist ohne Anmeldung aufrufbar | Last durch automatisierte Aufrufe | Eingabegrenzen, `statement_timeout` (B5), nur lesend; Mengenbegrenzung am Gateway erst bei Bedarf (nicht in R1) | | R5 | Nachladen per `offset` bei gleichzeitiger Katalogänderung | Ein Spiel doppelt oder übersprungen | Doppelte IDs werden verworfen; Katalog ändert sich selten; akzeptiert | | R6 | Änderung von `search_normalize` später | Generierte Spalten veralten nicht automatisch | Kommentar an der Funktion; Neuanlage der Spalten per Migration | -| R7 | Parallelarbeit mit 2b/2c und #12 | Konflikte bei `manual_fields`-Check, Tabellenzahl in `00_schema`, Fremdschlüssel-Wächter | Regel „vollständige Liste“ (3.2 Nr. 5); Tabellenzahl beim Merge abgleichen; Wächter-Einordnung von `game_alt_titles` (9.5) | +| R7 | Parallelarbeit mit 2b/2c und #12 | Konflikte bei `manual_fields`-Check, Tabellenzahl in `00_schema`, Fremdschlüssel-Wächter | Merge-Reihenfolge 2b → 2c → 3a (3.3); Regel „vollständige Liste“ (3.2 Nr. 5); Tabellenzahl beim Merge abgleichen; Wächter-Einordnung von `game_alt_titles` durch 3a (3.3) | | R8 | Relevanz wirkt mit echten Daten unpassend | Nutzer finden das erwartete Spiel weiter unten | Stufen sind einfach anpassbar (nur RPC); Plausibilisierung durch Chris (C1) | | R9 | Schreibweise „ae“/„oe“/„ue“ findet Umlaut-Titel nicht | „Kaese“ findet „Käse“ nicht | Spec-F5; spätere Erweiterung ohne Datenmigration der Clients möglich (nur Funktion + Spalten) | | R10 | Krit. 7 wird im Web gemessen, gilt aber für mobile Apps | Abweichung der Messbedingung | Web als maßgeblicher Nachweis, mobile Stichprobe (7.3), Begründung im PR | @@ -517,7 +533,7 @@ Werkzeuge: pgTAP (`supabase test db`), Deno-Tests (Startbestand-Werkzeug), `flut ### 9.5 Hinweise für andere Stories, nicht Teil von #3 -- **KAT-05 2b (#2):** Der Fremdschlüssel-Wächter (Spec #2 § 7) muss `game_alt_titles.game_id` einordnen: Empfehlung „bewusst ausgelassen“ (bleibt am Quell-Eintrag, Spec-F6 A); Runbook-Schritt „vor dem Zusammenführen relevante Titel der Quelle als alternative Titel am Ziel anlegen“. Der `manual_fields`-Check in 2b/2c muss `alt_titles` enthalten (Regel 3.2 Nr. 5). Tabellenzahl in `00_schema` abgleichen. +- **KAT-05 2b/2c (#2):** Keine Änderung an 2b oder 2c nötig, weil beide vor 3a gemergt werden (3.3). 3a ordnet `game_alt_titles.game_id` im Fremdschlüssel-Wächter von `07_catalog_merge.test.sql` als „bewusst ausgelassen“ ein (bleibt am Quell-Eintrag, passt zu `admin_merge_games`, das die Zuordnungen ebenfalls nicht umhängt; Spec-F6 A). Für das Runbook (Abschnitt Zusammenführen) ergänzt 3a (B4) den Schritt „vor dem Zusammenführen relevante Titel der Quelle als alternative Titel am Ziel anlegen“. - **KAT-02 (#4):** Route `/catalog/games/:id`, `onTap` der Treffer-Kachel verdrahten; Domänenmodell `CatalogGame` (Detail) neu, `CatalogGameSummary` bleibt für Listen. Alternative Titel sind in KAT-02 Krit. 1 nicht gefordert. - **KAT-04 (#13):** `onReportGame`-Callback im Leerzustand setzen (Suchbegriff als vorbelegter Titel, Anmeldung über `from`). „Gleicher normalisierter Titel“ (Klärung 15) auf Basis von `search_normalize` plus Entfernen von Artikeln (eigene Funktion, nicht in `search_normalize`, sonst ändert sich die Suche); Trigramm-Ähnlichkeit über `extensions.similarity` auf `title_search`. Krit. 6 von KAT-01 wird mit #13 vollständig (Spec-F4). - **QUE-09 (#29):** Startseite ohne Anmeldung bettet `CatalogSearchView` ein. @@ -530,3 +546,9 @@ Werkzeuge: pgTAP (`supabase test db`), Deno-Tests (Startbestand-Werkzeug), `flut ### 2026-11-03 – Entwurf Erste Fassung durch architect, wartet auf Freigabe und Antworten zu Spec-F1 bis Spec-F7. + +### 2026-11-03 – Korrekturen am Entwurf + +- § 7.1 Krit. 2: Idempotenz der alternativen Titel nach pgTAP verschoben (über die erzeugten `.inc`-Fixtures); Deno prüft nur das Skript. +- § 7: Auswirkung auf bestehende Tests: `00_schema` nur Tabellenzahl; `06` Z. 532–535 prüft `manual_fields` von Testspiel A exakt → alternative Titel an Testspiel B (B3), sonst Anpassung in B2. +- Neuer Abschnitt 3.3: Merge-Reihenfolge 2b → 2c → 3a; 3a erweitert den Fremdschlüssel-Wächter in `07_catalog_merge.test.sql` um `game_alt_titles.game_id` („bewusst ausgelassen“, Spec-F6 A). Abschnitt „Doku zum Datenmodell“ wird 3.4. B2, B3, § 8.4, R7, 9.5 angepasst.