Phase 03: Organization Layer - 2 plan(s) in 2 wave(s) - Wave 1: SwipeService CRUD + API + UI (parallel-ready) - Wave 2: Swipe-to-calendar integration (depends on 03-01) - Ready for execution Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
14 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 03-organization-layer | 01 | execute | 1 |
|
true |
|
Purpose: Dare all'utente uno strumento di cattura veloce per idee e topic interessanti, consultabile e persistente, prima dell'integrazione con il form Genera Calendario (Piano 02). Output: Backend CRUD funzionante su data/swipe_file.json + pagina Swipe File nella Web UI con tutte le operazioni CRUD.
<execution_context> @C:\Users\miche.claude/get-shit-done/workflows/execute-plan.md @C:\Users\miche.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/03-organization-layer/03-CONTEXT.md @.planning/phases/01-core-generation-pipeline/01-04-SUMMARY.md Task 1: Backend — SwipeService + Pydantic schemas + FastAPI router backend/schemas/swipe.py backend/services/swipe_service.py backend/routers/swipe.py backend/main.py **1. Crea `backend/schemas/swipe.py`** — Pydantic models:class SwipeItem(BaseModel):
id: str # UUID generato dal backend
topic: str # Obbligatorio, min 3 chars
nicchia: Optional[str] = None # Opzionale (es. "dentisti", "ecommerce")
note: Optional[str] = None # Note libere opzionali
created_at: str # ISO datetime
updated_at: str # ISO datetime
used: bool = False # True quando usato come override in calendario
class SwipeItemCreate(BaseModel):
topic: str = Field(..., min_length=3, max_length=200)
nicchia: Optional[str] = None
note: Optional[str] = None
class SwipeItemUpdate(BaseModel):
topic: Optional[str] = Field(None, min_length=3, max_length=200)
nicchia: Optional[str] = None
note: Optional[str] = None
class SwipeListResponse(BaseModel):
items: list[SwipeItem]
total: int
2. Crea backend/services/swipe_service.py — SwipeService class:
__init__(self, data_path: Path): riceve la directory DATA_PATH, file =data_path / "swipe_file.json"_load() -> list[dict]: legge il file JSON, ritorna lista vuota se non esiste_save(items: list[dict]): scrive il file JSON con encoding utf-8, indent=2list_items() -> list[SwipeItem]: carica, ordina percreated_atdescending (piu' recenti prima), ritorna come SwipeItemadd_item(data: SwipeItemCreate) -> SwipeItem: genera UUID, datetime now ISO, salva, ritorna item creatoupdate_item(item_id: str, data: SwipeItemUpdate) -> SwipeItem: trova per id, aggiorna solo campi non-None, aggiornaupdated_at, salva. Raise ValueError se non trovato.delete_item(item_id: str) -> bool: trova per id, rimuove, salva. Raise ValueError se non trovato.mark_used(item_id: str) -> SwipeItem: settaused=True, aggiornaupdated_at. Raise ValueError se non trovato.- Usa
uuid.uuid4().hex[:12]per ID brevi. Usadatetime.now(timezone.utc).isoformat()per timestamp.
3. Crea backend/routers/swipe.py — FastAPI router:
router = APIRouter(prefix="/api/swipe", tags=["swipe"])- Lazy init pattern identico a prompts.py:
_swipe_service: SwipeService | None = None+_get_swipe_service()che crea l'istanza conDATA_PATH - Endpoint:
GET /->SwipeListResponse— lista tutte le ideePOST /->SwipeItem(status 201) — aggiunge ideaPUT /{item_id}->SwipeItem— modifica idea (ValueError -> 404)DELETE /{item_id}->{"deleted": True}(status 200) — elimina idea (ValueError -> 404)POST /{item_id}/mark-used->SwipeItem— segna come usata (ValueError -> 404)
4. Registra router in backend/main.py:
- Importa
from backend.routers import ... swipe - Aggiungi
app.include_router(swipe.router)PRIMA del SPA catch-all mount, nella sezione con gli altri router. Ordine: calendar, generate, export, settings, prompts, swipe. - Aggiungi
DATA_PATHal lifespanmkdirse non gia' incluso (verificare).
Pattern da seguire: Il router prompts.py e' il template di riferimento per lo stile, lazy init, error handling.
python -c "from backend.schemas.swipe import SwipeItem, SwipeItemCreate, SwipeItemUpdate, SwipeListResponse; print('Schemas OK')"passapython -c "from backend.services.swipe_service import SwipeService; print('Service OK')"passapython -c "from backend.routers.swipe import router; print('Router OK')"passapython -c "from backend.main import app; print([r.path for r in app.routes])"include/api/swipe- SwipeService legge/scrive data/swipe_file.json con CRUD completo
- Router espone 5 endpoint REST: list, add, update, delete, mark-used
- Router registrato in main.py prima del SPA catch-all
// ---------------------------------------------------------------------------
// Swipe File
// ---------------------------------------------------------------------------
export interface SwipeItem {
id: string
topic: string
nicchia?: string | null
note?: string | null
created_at: string
updated_at: string
used: boolean
}
export interface SwipeItemCreate {
topic: string
nicchia?: string | null
note?: string | null
}
export interface SwipeItemUpdate {
topic?: string | null
nicchia?: string | null
note?: string | null
}
export interface SwipeListResponse {
items: SwipeItem[]
total: number
}
2. Aggiungi hooks in frontend/src/api/hooks.ts — Alla fine del file, sezione "Swipe File":
// ---------------------------------------------------------------------------
// Swipe File
// ---------------------------------------------------------------------------
export function useSwipeItems() {
return useQuery<SwipeListResponse>({
queryKey: ['swipe'],
queryFn: () => apiGet<SwipeListResponse>('/swipe'),
staleTime: 30_000,
})
}
export function useAddSwipeItem() {
const queryClient = useQueryClient()
return useMutation<SwipeItem, Error, SwipeItemCreate>({
mutationFn: (item) => apiPost<SwipeItem>('/swipe', item),
onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['swipe'] }) },
})
}
export function useUpdateSwipeItem() {
const queryClient = useQueryClient()
return useMutation<SwipeItem, Error, { id: string; data: SwipeItemUpdate }>({
mutationFn: ({ id, data }) => apiPut<SwipeItem>(`/swipe/${id}`, data),
onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['swipe'] }) },
})
}
export function useDeleteSwipeItem() {
const queryClient = useQueryClient()
return useMutation<{ deleted: boolean }, Error, string>({
mutationFn: (id) => apiFetch<{ deleted: boolean }>(`/swipe/${id}`, { method: 'DELETE' }),
onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['swipe'] }) },
})
}
Importare i nuovi tipi (SwipeItem, SwipeItemCreate, SwipeItemUpdate, SwipeListResponse) e apiFetch dove necessario.
3. Crea frontend/src/pages/SwipeFile.tsx — Pagina completa:
Layout: stone/amber palette coerente con il resto dell'app.
Struttura pagina:
- Header: titolo "Swipe File", sottotitolo breve, contatore totale idee
- Form aggiunta rapida (inline, sempre visibile sopra la lista):
- Input topic (obbligatorio, placeholder "Scrivi un'idea o topic...")
- Input nicchia (opzionale, placeholder "Es. dentisti")
- Input note (opzionale, placeholder "Note aggiuntive...")
- Bottone "Aggiungi" (amber, con icona Plus)
- Reset form dopo successo
- Filtro nicchia: chip/pill cliccabili sopra la lista. Mostra tutte le nicchie uniche trovate nelle idee + chip "Tutte". Click filtra la lista. Chip attivo evidenziato in amber.
- Lista idee (cronologico inverso): card stone-800 per ogni idea con:
- Topic in bold come titolo
- Badge nicchia (se presente) — pill colorata
- Badge "Usato" se
used === true— amber badge con icona CheckCircle - Note se presenti — testo secondario stone-400
- Data relativa ("3 giorni fa", "1 ora fa") — usa logica semplice basata su differenza date, non libreria
- Bottone modifica (icona Pencil) — click attiva editing inline:
- I campi della card diventano input editabili
- Mostra bottoni Salva (Check) e Annulla (X)
- Salva chiama
useUpdateSwipeItem, Annulla ripristina valori originali
- Bottone elimina (icona Trash2) — click mostra dialog conferma:
- Testo: "Eliminare questa idea? L'azione e' irreversibile."
- Bottoni: "Annulla" e "Elimina" (rosso)
- Conferma chiama
useDeleteSwipeItem
- Stato vuoto: messaggio "Nessuna idea salvata" con invito a usare il form sopra
Pattern UI da seguire: Stile card identico a PostCard (stone-800, border stone-700, rounded-xl). Palette amber per accent. Lucide icons coerenti.
Icone lucide da usare: Plus, Pencil, Trash2, CheckCircle, X, Check, Lightbulb, Filter
State management:
editingId: string | null— quale idea e' in modalita' editeditForm: { topic, nicchia, note }— valori temporanei durante editingdeleteConfirmId: string | null— quale idea mostra il dialog di confermafilterNicchia: string | null— nicchia selezionata per filtro (null = tutte)newItem: { topic, nicchia, note }— form di aggiunta
4. Aggiungi route in frontend/src/App.tsx:
- Importa
SwipeFileda./pages/SwipeFile - Aggiungi
<Route path="/swipe-file" element={<SwipeFile />} />dopo la route/prompt-editor
5. Aggiungi voce navigazione in frontend/src/components/Sidebar.tsx:
- Aggiungi nella lista
navItems, DOPO "Prompt Editor" e PRIMA di "Impostazioni":{ to: '/swipe-file', label: 'Swipe File', icon: <Lightbulb size={18} /> } - Importare
Lightbulbda lucide-react cd frontend && npx tsc --noEmit— nessun errore TypeScriptcd frontend && npm run build— build pulita senza errori- Sidebar contiene 6 link di navigazione (Dashboard, Genera Calendario, Genera Singolo Post, Prompt Editor, Swipe File, Impostazioni)
- Route
/swipe-fileesiste in App.tsx - hooks.ts contiene 4 nuovi hook: useSwipeItems, useAddSwipeItem, useUpdateSwipeItem, useDeleteSwipeItem
- Pagina SwipeFile completa con form aggiunta, lista con filtro nicchia, modifica inline, eliminazione con conferma
- 4 TanStack Query hooks che cablano tutti gli endpoint swipe
- Navigazione sidebar con link "Swipe File" con icona Lightbulb
- Route
/swipe-fileregistrata in App.tsx - TypeScript compila senza errori
<success_criteria>
- SwipeService legge e scrive data/swipe_file.json con CRUD completo (add, update, delete, mark_used, list con ordinamento cronologico inverso)
- 5 endpoint REST funzionanti: GET /api/swipe, POST /api/swipe, PUT /api/swipe/{id}, DELETE /api/swipe/{id}, POST /api/swipe/{id}/mark-used
- Pagina SwipeFile accessibile dalla sidebar con form aggiunta inline, lista filtrata per nicchia, modifica inline, eliminazione con conferma dialog
- TypeScript e build frontend compilano senza errori </success_criteria>