Files
juliaandClaude Sonnet 4.6 1070ba3ac1 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>
2026-06-16 11:53:24 +02:00

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.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.

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.


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