- PostCard: thumbnail 80x56px della cover image quando keyword e' URL
- Rilevamento con startsWith('http')
- object-cover, loading=lazy, onError nasconde se URL non valido
- Posizionato dopo cover_title e prima dei metadati secondari
- OutputReview: hint discreto Unsplash sotto il box info edit inline
- Visibile solo se unsplash_api_key_configured === false
- Link a /impostazioni con stile amber discreto
- Scompare automaticamente dopo configurazione Unsplash
- Usa Link da react-router-dom (pattern codebase)
- Aggiunto unsplash_api_key a interface Settings
- Aggiunto unsplash_api_key_configured a interface SettingsStatus
- Aggiunta sezione 'Immagini' in Settings con campo API Key Unsplash
- Toggle visibilita' showUnsplashKey separato da showApiKey
- Helper text condizionale: messaggio diverso se key configurata o no
- Logica submit: unsplash_api_key vuota non sovrascrive quella esistente
- Reset campo dopo salvataggio come per api_key Claude
- CSVBuilder.build_csv() e build_csv_content() accettano image_url_map opzionale
- _resolve_image() risolve keyword->URL Unsplash con fallback keyword originale
- _build_rows() chiama _resolve_image per cover, slides e cta image keywords
- JobStatus ha campo image_url_map con persistenza su disco JSON
- GenerationPipeline._resolve_unsplash_keywords() chiamato dopo batch LLM
- Carica unsplash_api_key da settings.json, crea UnsplashService, chiama resolve_keywords
- image_url_map salvato nel job JSON per riuso in export con edits
- Export router recupera image_url_map dal job JSON e passa a build_csv_content
- generate_single NON risolve Unsplash (velocità e riuso map job originale)
- Crea UnsplashService con search, cache disco, traduzione IT->EN
- ~30 keyword B2B italiane tradotte in dizionario statico
- Cache in-memory + persistenza su disco (unsplash_cache.json)
- Retry automatico su errori di rete, no-retry su 401/403
- Rate limiting awareness via X-Ratelimit-Remaining header
- Aggiunge campo unsplash_api_key a Settings schema
- Router settings espone unsplash_api_key_masked + configured
- Merge None-preserving per unsplash_api_key nel PUT
- CalendarRequest in types.ts: aggiunto topic_overrides?: Record<number, string>
- hooks.ts: aggiunto useMarkSwipeUsed hook (POST /swipe/{id}/mark-used)
- GenerateCalendar.tsx: sezione Topic Override con griglia 13 slot
- Bottone "Da Swipe File" per aprire picker inline per ogni slot
- Picker mostra lista idee con nicchia badge e badge Usato
- Selezione assegna topic allo slot e chiama mark-used
- Bottone X per rimuovere override da uno slot
- Override inclusi in CalendarRequest.topic_overrides al submit
- Riepilogo counter override selezionati
- Aggiunto campo topic_overrides: Optional[dict[int, str]] a CalendarRequest
- GenerationPipeline._run_generation ora controlla request.topic_overrides
prima di chiamare LLM per generare il topic
- Slot con override saltano la chiamata LLM per il topic
- Log informativo quando un override viene applicato
- Slot senza override continuano a funzionare come prima
- Add regeneratedSlots Set to track regenerated post indices
- Add summary counter showing: N post, X generati, Y rigenerati, Z modificati
- Counter updates in real-time after each regeneration or inline edit
- Pass isRegenerated prop to PostCard for visual badge
- Update info box to mention regen feature with RefreshCw icon
- Use JSON.stringify comparison for manual edit detection
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add handleRegen function with topic/notes override support
- Add inline popover form with optional topic and notes fields
- Add isRegenerated prop with amber RefreshCw badge for regenerated posts
- Regen button positioned in card header next to expand/collapse chevron
- Form appears below header with Rigenera/Annulla actions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- GET /api/prompts — list all prompts with modified/default flag
- GET /api/prompts/{name} — read prompt content + required variables
- PUT /api/prompts/{name} — save modified prompt with validation
- POST /api/prompts/{name}/reset — restore prompt to default
- Lazy PromptService init to handle lifespan directory creation
- Router registered in main.py before SPA catch-all
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GenerateResponse now includes calendar field from backend.
OutputReview merges CalendarSlot into PostResult via slot_index,
enabling BadgePN, BadgeSchwartz rendering and Retry button.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- GenerateCalendar.tsx: form con obiettivo+settimane+brand+tono+nicchie
- Pulsante Genera disabilitato senza API key con banner link a Impostazioni
- Async submit: mutation ritorna job_id, mostra ProgressIndicator
- Auto-navigate a /risultati/:jobId quando job completato
- OutputReview.tsx: carica job results via useJobResults(jobId)
- Griglia PostCard responsive con conteggio success/failed
- Stato locale per edit inline (aggiornato da PostCard.onEdit)
- Download CSV via useDownloadEditedCsv (POST con edits) con due pulsanti (header + footer)
- GenerateSingle.tsx: form con tipo PN, livello Schwartz, nicchia, formato narrativo
- Topic opzionale (altrimenti generato dall'AI)
- Anteprima risultato con PostCard+SlideViewer e download CSV singolo
- SlideViewer.tsx: navigazione frecce sinistra/destra tra 8 slide (Cover + 6 centrali + CTA)
- EditableField component: click-to-edit per ogni campo (input/textarea)
- Dot indicators + keyboard navigation (frecce sinistra/destra)
- Caption Instagram editabile con contatore caratteri
- Callback onEdit propaga modifiche al parent
- ProgressIndicator.tsx: polling real-time via useJobStatus(jobId)
- Barra progresso visuale con percentuale
- Lista post con icone stato: pending/running/success/failed
- onComplete(jobId) chiamato quando status diventa 'completed'
- Poll ogni 2s (condizionale nel hook, si ferma quando completed/failed)
- schemas/settings.py: Settings pydantic model con api_key, llm_model, nicchie_attive, tono
- routers/calendar.py: POST /api/calendar/generate, GET /api/calendar/formats
- routers/generate.py: POST /api/generate/bulk (202 + job_id), GET /job/{job_id}/status (polling), GET /job/{job_id}, POST /single
- routers/export.py: GET /api/export/{job_id}/csv (originale), POST /api/export/{job_id}/csv (modifiche inline)
- routers/settings.py: GET /api/settings/status (api_key_configured), GET /api/settings, PUT /api/settings
- main.py: include_router x4 PRIMA di SPAStaticFiles, copia prompt default al primo avvio
- backend/services/calendar_service.py: genera 13 slot con distribuzione PN (4v+2s+2n+3r+1c+1p) e Schwartz (L5=3,L4=3,L3=4,L2=2,L1=1), ordina per funnel, ruota nicchie, calcola date
- backend/services/prompt_service.py: carica/compila/elenca prompt {{variabile}}, ValueError per variabili mancanti
- backend/data/prompts/system_prompt.txt: sistema prompt esperto content marketing B2B italiano
- backend/data/prompts/topic_generator.txt: generazione topic per slot calendario
- backend/data/prompts/pas_valore.txt: formato PAS per post valore educativo
- backend/data/prompts/listicle_valore.txt: formato Listicle per post valore
- backend/data/prompts/bab_storytelling.txt: formato BAB per post storytelling
- backend/data/prompts/aida_promozione.txt: formato AIDA per post promozionale
- backend/data/prompts/dato_news.txt: formato Dato+Implicazione per post news
- FastAPI app with SPAStaticFiles catch-all (root_path NOT in constructor)
- config.py with DATA_PATH, PROMPTS_PATH, OUTPUTS_PATH, CAMPAIGNS_PATH, CONFIG_PATH
- Startup lifespan creates data directories automatically
- Health endpoint: GET /api/health -> {"status": "ok"}
- Dockerfile multi-stage: node:22-slim builds React, python:3.12-slim serves API+SPA
- --root-path /postgenerator set in Uvicorn CMD only (avoids Pitfall #4)
- docker-compose.yml: lab-postgenerator-app, proxy_net, named volume for data persistence
- requirements.txt with pinned versions: fastapi[standard]==0.135.1, anthropic==0.84.0
- Fix CSV-01 column count: 32 -> 33 (8 meta + 24 slide + 1 caption)
- Add TopicResult Pydantic model + topic_generator.txt prompt
- Make bulk generation async with background task + polling endpoint
- Add POST /api/export/{job_id}/csv for inline edit CSV download
- Split Plan 01-04 Task 2 into 2a/2b/2c (badges, slideviewer, pages)
- Update ProgressIndicator to use polling on /status endpoint
- Add --yes flag and frontend/ prerequisite note to Plan 01-01
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instagram carousel automation system for B2B content marketing - strategic content engine with Persuasion Nurturing, Schwartz levels, and niche rotation producing Canva Bulk Create CSV
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>