Files
postgenerator/.planning/phases/03-organization-layer/03-01-PLAN.md
Michele 05988f4be5 docs(03): create phase plan
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>
2026-03-08 23:21:41 +01:00

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
backend/services/swipe_service.py
backend/schemas/swipe.py
backend/routers/swipe.py
backend/main.py
frontend/src/types.ts
frontend/src/api/hooks.ts
frontend/src/pages/SwipeFile.tsx
frontend/src/components/Sidebar.tsx
frontend/src/App.tsx
true
truths artifacts key_links
L'utente accede alla pagina Swipe File dalla sidebar e vede la lista delle idee salvate (o un messaggio vuoto)
L'utente aggiunge un'idea con topic e opzionalmente nicchia e note; l'idea appare immediatamente nella lista
L'utente clicca su un'idea e la modifica inline; il salvataggio persiste
L'utente elimina un'idea con conferma; la lista si aggiorna
Le idee persistono al riavvio del container (file JSON su disco)
La lista e' ordinata cronologicamente (piu' recenti prima) e filtrabile per nicchia
path provides contains
backend/services/swipe_service.py CRUD per swipe_file.json con load/save/add/update/delete class SwipeService
path provides contains
backend/schemas/swipe.py Pydantic models per SwipeItem, SwipeItemCreate, SwipeItemUpdate class SwipeItem
path provides contains
backend/routers/swipe.py REST API endpoints GET/POST/PUT/DELETE per swipe file router = APIRouter
path provides contains
frontend/src/pages/SwipeFile.tsx Pagina SwipeFile con lista, form aggiunta, modifica inline, eliminazione con conferma export function SwipeFile
from to via pattern
backend/routers/swipe.py backend/services/swipe_service.py SwipeService instance SwipeService
from to via pattern
frontend/src/pages/SwipeFile.tsx /api/swipe TanStack Query hooks useSwipeItems|useAddSwipeItem|useUpdateSwipeItem|useDeleteSwipeItem
from to via pattern
backend/main.py backend/routers/swipe.py app.include_router swipe.router
from to via pattern
frontend/src/App.tsx frontend/src/pages/SwipeFile.tsx React Router Route swipe-file
SwipeService backend (CRUD JSON file-based), endpoint FastAPI REST, e pagina Web UI completa per gestione Swipe File con aggiunta rapida, modifica inline, eliminazione con conferma, filtro per nicchia.

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=2
  • list_items() -> list[SwipeItem]: carica, ordina per created_at descending (piu' recenti prima), ritorna come SwipeItem
  • add_item(data: SwipeItemCreate) -> SwipeItem: genera UUID, datetime now ISO, salva, ritorna item creato
  • update_item(item_id: str, data: SwipeItemUpdate) -> SwipeItem: trova per id, aggiorna solo campi non-None, aggiorna updated_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: setta used=True, aggiorna updated_at. Raise ValueError se non trovato.
  • Usa uuid.uuid4().hex[:12] per ID brevi. Usa datetime.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 con DATA_PATH
  • Endpoint:
    • GET / -> SwipeListResponse — lista tutte le idee
    • POST / -> SwipeItem (status 201) — aggiunge idea
    • PUT /{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_PATH al lifespan mkdir se 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')" passa
  • python -c "from backend.services.swipe_service import SwipeService; print('Service OK')" passa
  • python -c "from backend.routers.swipe import router; print('Router OK')" passa
  • python -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
Task 2: Frontend — TypeScript types + TanStack Query hooks + pagina SwipeFile + navigazione frontend/src/types.ts frontend/src/api/hooks.ts frontend/src/pages/SwipeFile.tsx frontend/src/components/Sidebar.tsx frontend/src/App.tsx **1. Aggiungi types in `frontend/src/types.ts`** — Alla fine del file, sezione "Swipe File":
// ---------------------------------------------------------------------------
// 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' edit
  • editForm: { topic, nicchia, note } — valori temporanei durante editing
  • deleteConfirmId: string | null — quale idea mostra il dialog di conferma
  • filterNicchia: string | null — nicchia selezionata per filtro (null = tutte)
  • newItem: { topic, nicchia, note } — form di aggiunta

4. Aggiungi route in frontend/src/App.tsx:

  • Importa SwipeFile da ./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 Lightbulb da lucide-react
  • cd frontend && npx tsc --noEmit — nessun errore TypeScript
  • cd 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-file esiste 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-file registrata in App.tsx
  • TypeScript compila senza errori
1. Backend: `python -c "from backend.main import app; print('OK')"` — app importa senza errori 2. Frontend: `cd frontend && npx tsc --noEmit && npm run build` — build pulita 3. Sidebar mostra 6 link di navigazione con Swipe File presente 4. Route `/swipe-file` registrata e componente SwipeFile renderizza 5. types.ts contiene le 4 interfacce SwipeItem, SwipeItemCreate, SwipeItemUpdate, SwipeListResponse 6. hooks.ts contiene 4 nuovi hook per CRUD swipe 7. Router swipe registrato in main.py con 5 endpoint

<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>
After completion, create `.planning/phases/03-organization-layer/03-01-SUMMARY.md`