Analyzes all Taiga stories, maps hard/soft dependencies, and writes full specs (Goal, Data Model, Components, Verification, Open Questions) for every story before any implementation begins. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.1 KiB
4.1 KiB
Spec: 06 · Fruit Search
Taiga Story: #6 · 06-fruit-search
Status: Ready
Branch: feature/06-fruit-search
Depends on: 02-manage-fruits (fruits + fruit_synonyms)
1. Goal
Improve the fruit list search: users can filter by name/synonym text and optionally by type. Combined-type aliases (e.g. "Birnen- und Quittensorten") match both constituent types.
2. Background / Scope / Context / Constraints
- Target component:
FruitList.vue(search bar already exists as placeholder from story #2). - Search is case-insensitive, substring match on
fruits.nameOR anyfruit_synonyms.synonym. - Type filter is optional; when set, filters to exactly that enum value OR to its alias group.
- Combined-type aliases (not in the enum — only in search UI):
| Alias | Matches enum values |
|---|---|
| Birnen- und Quittensorten | Birnensorten, Quittensorten |
| Aprikosen und Pfirsiche | Aprikosen, Pfirsiche |
| Mirabellen und Reineclauden | Mirabellen, Renekloden |
| Pflaumen und Zwetschen | Pflaumen, Zwetschen |
- Search is client-triggered (on input, debounced) or button-triggered — consistent with existing UX pattern.
- API already supports
?name=and?type=query params (placeholder from story #2); this story implements them correctly on the backend.
3. Necessary Refactorings
GET /api/v1/fruitshandler: add SQL filter logic forname(ILIKE on name + JOIN synonyms) andtype(exact enum match or multi-value IN for aliases).FruitList.vue: wire up search input + type dropdown to API query params.
4. Related Data Model
No new tables or migrations. Query pattern:
SELECT DISTINCT f.*
FROM fruits f
LEFT JOIN fruit_synonyms fs ON fs.fruit_id = f.id
WHERE
($name = '' OR f.name ILIKE '%' || $name || '%' OR fs.synonym ILIKE '%' || $name || '%')
AND ($types IS NULL OR f.fruit_type = ANY($types::fruit_type[]))
ORDER BY f.name;
$types is an array expanded from the selected alias or single type value.
5. Affected Places / Related Components
Backend
| File | Change |
|---|---|
backend/internal/repository/fruit_repo.go |
List(name, types []string) — parameterized SQL |
backend/internal/handler/fruit_handler.go |
Parse ?name= and ?type= query params; resolve alias → types array |
backend/internal/domain/fruit.go |
Add alias map constant |
Frontend
| File | Change |
|---|---|
frontend/src/views/FruitList.vue |
Search text input (debounced 300 ms) + type dropdown (enum values + aliases) |
frontend/src/api/fruits.ts |
listFruits({name?, type?}) passes query params |
frontend/src/stores/fruitStore.ts |
Store search state; trigger reload on change |
Type Dropdown Options
(All)
Apfelsorten
Birnensorten
Quittensorten
Birnen- und Quittensorten ← alias
Aprikosen
Pfirsiche
Aprikosen und Pfirsiche ← alias
Mirabellen
Renekloden
Mirabellen und Reineclauden ← alias
Pflaumen
Zwetschen
Pflaumen und Zwetschen ← alias
Sauerkirschen
Süßkirschen
Brombeeren
Erdbeeren
Himbeeren
Johannisbeeren
Stachelbeeren
Wein
6. Out of Scope
- Full-text search index (pg
tsvector) — ILIKE sufficient for expected dataset size - Search in comment field
- Sorting controls
- Pagination (defer to later if needed)
7. Verification
| Check | Expected |
|---|---|
| Search "Boskop" → results include fruit named "Boskop" | Pass |
| Search "boskop" (lowercase) | Same results (case-insensitive) |
| Search by synonym term | Fruits with matching synonym appear |
| Filter "Birnensorten" | Only Birnensorten fruits |
| Filter "Birnen- und Quittensorten" | Birnensorten AND Quittensorten fruits |
| Combined name + type filter | Intersection of both |
| Empty search + no type filter | All fruits |
go test ./... — repo list filter tests |
Green |
8. Open Questions
| # | Question | Impact |
|---|---|---|
| 1 | Debounce delay: 300 ms sufficient or user preference? | UX |
| 2 | Search on Enter only vs real-time? |
UX |
| 3 | Type dropdown: grouped by alias vs flat list? | UX clarity |