docs: add sprint planning — dependency diagram + specs for all 8 stories
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>
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
# 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.name` OR any `fruit_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/fruits` handler: add SQL filter logic for `name` (ILIKE on name + JOIN synonyms) and `type` (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:
|
||||
|
||||
```sql
|
||||
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 |
|
||||
Reference in New Issue
Block a user