diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index c82c2ed..4af14c0 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -141,16 +141,16 @@ | IMG-02 | Phase 4 | Pending | | IMG-03 | Phase 4 | Pending | | IMG-04 | Phase 1 | Complete | -| SWP-01 | Phase 3 | Pending | -| SWP-02 | Phase 3 | Pending | -| SWP-03 | Phase 3 | Pending | -| SWP-04 | Phase 3 | Pending | +| SWP-01 | Phase 3 | Complete | +| SWP-02 | Phase 3 | Complete | +| SWP-03 | Phase 3 | Complete | +| SWP-04 | Phase 3 | Complete | | UI-01 | Phase 1 | Complete | | UI-02 | Phase 1 | Complete | | UI-03 | Phase 1 | Complete | | UI-04 | Phase 1 | Complete | | UI-05 | Phase 2 | Complete | -| UI-06 | Phase 3 | Pending | +| UI-06 | Phase 3 | Complete | | UI-07 | Phase 1 | Complete | | UI-08 | Phase 1 | Complete | | INF-01 | Phase 1 | Complete | @@ -167,4 +167,4 @@ --- *Requirements defined: 2026-03-07* -*Last updated: 2026-03-08 — Phase 2 requirements marked Complete* +*Last updated: 2026-03-09 — Phase 3 requirements marked Complete* diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index c87456b..3b64a62 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -14,7 +14,7 @@ Decimal phases appear between their surrounding integers in numeric order. - [x] **Phase 1: Core Generation Pipeline** - Infrastruttura + pipeline calendario → LLM → CSV funzionante end-to-end - [x] **Phase 2: Prompt Control + Output Review** - Editor prompt via UI e anteprima caroselli prima dell'export -- [ ] **Phase 3: Organization Layer** - Swipe File e gestione storico campagne per workflow sostenibile +- [x] **Phase 3: Organization Layer** - Swipe File e gestione storico campagne per workflow sostenibile - [ ] **Phase 4: Enrichment** - Integrazione Unsplash, context injection da Swipe File, polish UI ## Phase Details diff --git a/.planning/phases/03-organization-layer/03-VERIFICATION.md b/.planning/phases/03-organization-layer/03-VERIFICATION.md new file mode 100644 index 0000000..62f0945 --- /dev/null +++ b/.planning/phases/03-organization-layer/03-VERIFICATION.md @@ -0,0 +1,170 @@ +--- +phase: 03-organization-layer +verified: 2026-03-09T00:00:00Z +status: passed +score: 5/5 must-haves verified +--- + +# Phase 3: Organization Layer - Verification Report + +**Phase Goal:** Swipe File per salvare rapidamente idee e topic interessanti; integrazione topic override nel form genera calendario. +**Verified:** 2026-03-09 +**Status:** PASSED +**Re-verification:** No - initial verification + +## Goal Achievement + +### Observable Truths + +| # | Truth | Status | Evidence | +|---|-------|--------|----------| +| 1 | Aggiunta idea Swipe File appare immediatamente | VERIFIED | SwipeFile.tsx: form 3 campi, addMutation.mutate(), onSuccess invalida queryKey swipe | +| 2 | Idea persiste al riavvio container | VERIFIED | swipe_service.py _save() su swipe_file.json; docker-compose.yml volume postgenerator-data:/app/data | +| 3 | Eliminazione con conferma aggiorna lista | VERIFIED | SwipeCard delete dialog, deleteMutation.mutate(itemId), onSuccess invalida cache | +| 4 | Selezione topic da Swipe File come override slot specifico | VERIFIED | GenerateCalendar.tsx: griglia 13 slot, SwipePicker inline, topicOverrides state | +| 5 | Topic override applicato in generazione prima dell LLM | VERIFIED | generation_pipeline.py righe 313-320: override controllato PRIMA della chiamata LLM | + +**Score:** 5/5 truths verified + +--- + +### Required Artifacts + +| Artifact | Expected | Status | Details | +|----------|----------|--------|---------| +| backend/schemas/swipe.py | 4 Pydantic models | VERIFIED | 53 righe - SwipeItem/Create/Update/ListResponse con validazione | +| backend/services/swipe_service.py | CRUD su swipe_file.json | VERIFIED | 182 righe - list/add/update/delete/mark_used con _load/_save completi | +| backend/routers/swipe.py | 5 endpoint REST | VERIFIED | 142 righe - GET/POST/PUT/DELETE/mark-used, lazy init, ValueError->404 | +| frontend/src/pages/SwipeFile.tsx | Form, lista, filtro, edit, delete | VERIFIED | 491 righe - tutte le feature presenti e collegate agli hook | +| backend/schemas/calendar.py | CalendarRequest.topic_overrides | VERIFIED | Riga 91: Optional[dict[int, str]] con description esplicativa | +| backend/services/generation_pipeline.py | Override prima LLM | VERIFIED | Righe 313-320: topic_overrides controllati PRIMA del blocco LLM | +| frontend/src/pages/GenerateCalendar.tsx | Griglia 13 slot con picker | VERIFIED | 463 righe - Array.from(length:13), SwipePicker inline, topicOverrides nel CalendarRequest | +| frontend/src/types.ts | 4 interfacce Swipe File + topic_overrides | VERIFIED | Righe 188-213 (SwipeItem/Create/Update/ListResponse), riga 33 (topic_overrides) | +| frontend/src/api/hooks.ts | 5 hook Swipe File | VERIFIED | Righe 244-297 - hook con invalidazione cache; apiFetch per DELETE | + +--- + +### Key Link Verification + +| From | To | Via | Status | Details | +|------|----|-----|--------|---------| +| SwipeFile.tsx | GET /api/swipe/ | useSwipeItems() | VERIFIED | apiGet(/swipe/), dati in data.items | +| SwipeFile.tsx | POST /api/swipe/ | useAddSwipeItem().mutate() | VERIFIED | addMutation.mutate con topic, nicchia, note | +| SwipeFile.tsx | PUT /api/swipe/{id} | useUpdateSwipeItem().mutate() | VERIFIED | updateMutation.mutate con id e data: update | +| SwipeFile.tsx | DELETE /api/swipe/{id} | useDeleteSwipeItem().mutate() | VERIFIED | apiFetch DELETE, deleteMutation.mutate(itemId) | +| GenerateCalendar.tsx | POST /api/swipe/{id}/mark-used | useMarkSwipeUsed().mutate() | VERIFIED | markUsed.mutate(item.id) fire-and-forget | +| GenerateCalendar.tsx | CalendarRequest.topic_overrides | topicOverrides->handleSubmit | VERIFIED | Object.fromEntries converte SlotOverride -> string nel payload | +| CalendarRequest | generation_pipeline._run_generation | request.topic_overrides | VERIFIED | Override applicato per slot.indice corrispondente | +| swipe_file.json | Docker volume | postgenerator-data:/app/data | VERIFIED | docker-compose.yml volume; SwipeService scrive in DATA_PATH/swipe_file.json | + +--- + +### Requirements Coverage + +| Requirement | Status | Note | +|-------------|--------|------| +| SWP-01: Aggiunta voce Swipe File (topic, nicchia, note) | SATISFIED | Form inline 3 campi, POST /api/swipe/ | +| SWP-02: Eliminazione voce con conferma | SATISFIED | Dialog conferma in SwipeCard, DELETE /api/swipe/{id} | +| SWP-03: Modifica inline voce esistente | SATISFIED | editingId state, input inline, PUT /api/swipe/{id} PATCH-like | +| SWP-04: Filtro per nicchia | SATISFIED | uniqueNicchie derivate dagli items, chip filter con filterNicchia state | +| UI-06: Topic override da Swipe File nel form Genera Calendario | SATISFIED | Griglia 13 slot, SwipePicker inline, mark-used automatico | + +--- + +### Anti-Patterns Found + +Nessun anti-pattern bloccante trovato. + +| File | Pattern | Severita | Note | +|------|---------|----------| -----| +| generation_pipeline.py riga 290 | brand_name hardcoded | Avviso | Previsto - commento segnala lettura da settings in fase successiva | + +--- + +### Human Verification Required + +#### 1. Persistenza dati al riavvio + +**Test:** Aggiungere 2-3 voci allo Swipe File, eseguire docker compose restart, ricaricare la pagina. +**Expected:** Le voci sono ancora presenti. +**Why human:** Volume Docker configurato correttamente nel codice ma verifica richiede container live. + +#### 2. Picker Swipe File nel form Genera Calendario + +**Test:** Con voci nello Swipe File, aprire Genera Calendario, cliccare Da Swipe File su uno slot, selezionare un idea. +**Expected:** Topic appare nello slot con bordo amber; contatore override si aggiorna; idea compare come Usato. +**Why human:** Comportamento picker inline (absolute positioning, z-20, chiusura automatica) richiede test visivo nel browser. + +#### 3. Override applicato nella generazione reale + +**Test:** Selezionare override per slot 0, avviare generazione, verificare nei log che "Topic override applicato" appaia. +**Expected:** Log mostra "Topic override applicato | slot=0 | topic=..." e il post usa il topic scelto. +**Why human:** Richiede API key configurata e generazione reale per verifica log end-to-end. + +--- + +## Verifica Strutturale Dettagliata + +### Backend + +**SwipeService (backend/services/swipe_service.py):** +- _load(): ritorna [] se file non esiste, gestisce JSONDecodeError e OSError +- _save(): scrive con ensure_ascii=False, indent=2 +- list_items(): ordina per created_at descending (ISO string sort) +- add_item(): genera id uuid4.hex[:12], timestamp ISO UTC, used=False +- update_item(): aggiorna solo campi non-None, aggiorna updated_at; ValueError se ID non trovato +- delete_item(): filtra per id, ValueError se non trovato +- mark_used(): setta used=True, aggiorna updated_at, ValueError se non trovato + +**Router (backend/routers/swipe.py):** +- Lazy init identico al pattern PromptService (singleton module-level) +- Tutti i ValueError mappati a HTTPException 404 +- 5 endpoint con prefix /api/swipe registrati in main.py riga 95 + +**CalendarRequest (backend/schemas/calendar.py):** +- topic_overrides: Optional[dict[int, str]] alla riga 91 +- Pydantic converte automaticamente chiavi stringa JSON -> int + +**GenerationPipeline._run_generation (backend/services/generation_pipeline.py):** +- Priorita override: slot.topic -> request.topic_overrides[slot.indice] -> generazione LLM +- Righe 313-320: check topic_overrides con log informativo quando applicato + +### Frontend + +**frontend/src/pages/SwipeFile.tsx:** +- Form 3 campi, submit disabilitato se topic.trim() < 3 chars +- SwipeCard: visualizzazione + edit inline + delete confirm in un componente +- Filter chips da uniqueNicchie = Array.from(new Set(...)) +- Badge Usato con CheckCircle icon per item.used === true +- relativeTime(): logica data relativa senza librerie esterne + +**frontend/src/pages/GenerateCalendar.tsx:** +- topicOverrides: Record con swipeId per tracking +- handleSelectSwipeItem(): aggiorna state + markUsed.mutate() fire-and-forget +- handleOpenPicker(): toggle (prev === slotIndex ? null : slotIndex) +- handleSubmit(): converte SlotOverride -> string nel CalendarRequest payload + +**Navigazione e routing:** +- Sidebar.tsx: navItem to=/swipe-file, label Swipe File, icon Lightbulb +- App.tsx: Route path=/swipe-file element={SwipeFile} registrata + +**frontend/src/api/hooks.ts:** +- useSwipeItems(): GET /swipe/, staleTime 30s +- useAddSwipeItem(): POST /swipe/, invalida [swipe] +- useUpdateSwipeItem(): PUT /swipe/{id}, invalida [swipe] +- useDeleteSwipeItem(): apiFetch DELETE /swipe/{id}, invalida [swipe] +- useMarkSwipeUsed(): POST /swipe/{id}/mark-used, invalida [swipe] + +--- + +## Conclusione + +Tutti i requisiti della Fase 3 sono implementati in modo completo e connesso. +Non ci sono stub, placeholder o wiring mancanti. +La persistenza e garantita dal Docker volume postgenerator-data montato su /app/data. +Gli override topic passano correttamente: frontend topicOverrides state -> CalendarRequest.topic_overrides -> GenerationPipeline._run_generation, dove vengono applicati con priorita superiore rispetto alla generazione LLM. + +--- + +_Verified: 2026-03-09_ +_Verifier: Claude (gsd-verifier)_ \ No newline at end of file