docs(04): create phase plan
Phase 04: Enrichment - 2 plan(s) in 2 wave(s) - Wave 1: backend UnsplashService + Settings + pipeline/CSV integration - Wave 2: frontend Settings UI + PostCard thumbnail + OutputReview hint - Ready for execution Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
211
.planning/phases/04-enrichment/04-01-PLAN.md
Normal file
211
.planning/phases/04-enrichment/04-01-PLAN.md
Normal file
@@ -0,0 +1,211 @@
|
||||
---
|
||||
phase: 04-enrichment
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- backend/services/unsplash_service.py
|
||||
- backend/schemas/settings.py
|
||||
- backend/routers/settings.py
|
||||
- backend/services/csv_builder.py
|
||||
- backend/services/generation_pipeline.py
|
||||
- backend/routers/generate.py
|
||||
autonomous: true
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Se unsplash_api_key e' configurata nelle Settings, il CSV contiene URL immagini reali (urls.regular da Unsplash) nelle colonne _image_keyword"
|
||||
- "Se unsplash_api_key non e' configurata, il CSV contiene le keyword testuali originali senza errori"
|
||||
- "Keyword identiche nella stessa batch producono lo stesso URL (cache hit)"
|
||||
- "La cache Unsplash persiste su disco in data/unsplash_cache.json e sopravvive ai riavvii container"
|
||||
- "Se Unsplash ritorna errore o rate limit, il CSV usa la keyword testuale come fallback senza bloccare l'export"
|
||||
artifacts:
|
||||
- path: "backend/services/unsplash_service.py"
|
||||
provides: "UnsplashService con search, cache disco, fallback"
|
||||
min_lines: 80
|
||||
- path: "backend/schemas/settings.py"
|
||||
provides: "Campo unsplash_api_key nel modello Settings"
|
||||
contains: "unsplash_api_key"
|
||||
- path: "backend/services/csv_builder.py"
|
||||
provides: "Risoluzione keyword -> URL Unsplash nelle colonne _image_keyword"
|
||||
contains: "resolve"
|
||||
key_links:
|
||||
- from: "backend/services/generation_pipeline.py"
|
||||
to: "backend/services/unsplash_service.py"
|
||||
via: "Chiamata resolve_keywords dopo generazione LLM"
|
||||
pattern: "unsplash.*resolve"
|
||||
- from: "backend/services/csv_builder.py"
|
||||
to: "image_url_map"
|
||||
via: "Mappa keyword->URL passata a _build_rows"
|
||||
pattern: "image_url_map|image_urls"
|
||||
- from: "backend/routers/settings.py"
|
||||
to: "unsplash_api_key"
|
||||
via: "GET/PUT includono il nuovo campo"
|
||||
pattern: "unsplash"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Integrare Unsplash API nel backend per risolvere le keyword immagine in URL reali nel CSV.
|
||||
|
||||
Purpose: Quando l'utente configura un'API key Unsplash nelle Impostazioni, il CSV esportato contiene URL di immagini reali (~1080px) al posto delle keyword testuali, rendendo il template Canva pronto con immagini di alta qualita'. Se Unsplash non e' configurato o non disponibile, il sistema continua a funzionare con le keyword originali senza interruzioni.
|
||||
|
||||
Output: UnsplashService funzionante con cache disco, Settings esteso con unsplash_api_key, pipeline che risolve keyword dopo la generazione LLM, CSVBuilder che scrive URL quando disponibili.
|
||||
</objective>
|
||||
|
||||
<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>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/04-enrichment/04-CONTEXT.md
|
||||
|
||||
# File da modificare — leggi PRIMA di implementare
|
||||
@backend/schemas/settings.py
|
||||
@backend/routers/settings.py
|
||||
@backend/services/csv_builder.py
|
||||
@backend/services/generation_pipeline.py
|
||||
@backend/routers/generate.py
|
||||
@backend/config.py
|
||||
@backend/constants.py
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: UnsplashService + Settings unsplash_api_key</name>
|
||||
<files>
|
||||
backend/services/unsplash_service.py
|
||||
backend/schemas/settings.py
|
||||
backend/routers/settings.py
|
||||
</files>
|
||||
<action>
|
||||
**1. Crea `backend/services/unsplash_service.py`:**
|
||||
|
||||
Classe `UnsplashService` con:
|
||||
- `__init__(self, api_key: str, cache_path: Path)` — httpx.AsyncClient con base_url `https://api.unsplash.com`, header `Authorization: Client-ID {api_key}`
|
||||
- `async def search_photo(self, keyword: str) -> Optional[str]` — Cerca 1 foto per keyword, ritorna `urls.regular` (formato ~1080px come da CONTEXT.md). Parametri query: `query={keyword_in_english}`, `per_page=1`, `orientation=landscape`, `content_filter=low`. Se nessun risultato, ritorna None.
|
||||
- `async def resolve_keywords(self, keywords: list[str]) -> dict[str, str]` — Data una lista di keyword (con possibili duplicati), risolve ognuna in un URL Unsplash. Usa la cache per evitare chiamate duplicate. Ritorna `{keyword: url}` per le keyword risolte. Le keyword non risolvibili NON sono nel dizionario (il caller usa la keyword originale come fallback).
|
||||
- Cache interna in-memory `self._cache: dict[str, str]` + persistenza su disco in `cache_path` (JSON file, caricato all'init, salvato dopo ogni batch). La cache mappa `keyword -> url`.
|
||||
- Metodo privato `_translate_keyword(self, keyword: str) -> str` — traduce keyword italiane in inglese per query Unsplash. Approccio pragmatico: usa un dizionario statico di ~30 parole comuni B2B italiane (studio, ufficio, riunione, professionista, dentista, avvocato, imprenditore, cliente, analisi, crescita, successo, team, computer, scrivania, grafici, strategia, contratto, sorriso, stretta di mano, presentazione, azienda, consulenza, marketing, dati, risultati, innovazione, tecnologia, formazione, collaborazione, obiettivo) con relative traduzioni. Per keyword composte, traduce ogni parola individualmente e concatena. Parole non trovate nel dizionario restano invariate (molte keyword di contesto come nomi propri o termini tecnici sono gia' in inglese o comprensibili per Unsplash).
|
||||
- Retry: 1 tentativo se errore di rete, poi fallback (keyword non risolta). Non ritentare su 401/403 (api key invalida).
|
||||
- Rate limiting awareness: Legge header `X-Ratelimit-Remaining` dalla response. Se remaining < 5, logga warning e smette di fare richieste per il batch corrente (le keyword restanti tornano non risolte).
|
||||
- Logging: ogni search logga keyword + risultato (hit cache/miss/errore/rate-limited).
|
||||
- `async def close(self)` — chiude httpx.AsyncClient.
|
||||
|
||||
**2. Aggiungi `unsplash_api_key` a `backend/schemas/settings.py`:**
|
||||
|
||||
Aggiungi campo al modello Settings:
|
||||
```python
|
||||
unsplash_api_key: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Chiave API Unsplash. Se configurata, le keyword immagine vengono risolte in URL reali nel CSV.",
|
||||
)
|
||||
```
|
||||
|
||||
**3. Aggiorna `backend/routers/settings.py`:**
|
||||
|
||||
- Aggiungi `unsplash_api_key_masked: Optional[str]` a `SettingsResponse` — usa la stessa logica di mascheramento di api_key (ultimi 4 caratteri).
|
||||
- Nel `GET /` ritorna anche `unsplash_api_key_masked=_mask_api_key(settings.unsplash_api_key)`.
|
||||
- Aggiungi `unsplash_api_key_configured: bool` a `SettingsStatusResponse`.
|
||||
- Nel `GET /status` ritorna anche `unsplash_api_key_configured=bool(settings.unsplash_api_key)`.
|
||||
- Nel `PUT /` applica la stessa logica di merge None-preserving: se `new_settings.unsplash_api_key is None`, mantieni quella esistente.
|
||||
</action>
|
||||
<verify>
|
||||
- `python -c "from backend.services.unsplash_service import UnsplashService; print('OK')"` non da errori di import
|
||||
- `python -c "from backend.schemas.settings import Settings; s = Settings(); print(s.unsplash_api_key)"` stampa `None`
|
||||
- Verifica che il router settings compili: `python -c "from backend.routers.settings import router; print('OK')"`
|
||||
</verify>
|
||||
<done>
|
||||
UnsplashService creato con search, cache disco, traduzione keyword IT->EN, retry, rate limit awareness. Settings ha campo unsplash_api_key. Router settings espone il nuovo campo mascherato con merge None-preserving.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Integrazione pipeline + CSV con risoluzione Unsplash</name>
|
||||
<files>
|
||||
backend/services/generation_pipeline.py
|
||||
backend/services/csv_builder.py
|
||||
backend/routers/generate.py
|
||||
backend/routers/export.py
|
||||
</files>
|
||||
<action>
|
||||
**1. Aggiorna `backend/services/csv_builder.py`:**
|
||||
|
||||
Modifica `build_csv()` e `build_csv_content()` per accettare un parametro opzionale `image_url_map: Optional[dict[str, str]] = None`.
|
||||
|
||||
In `_build_rows()`, aggiungi parametro `image_url_map: Optional[dict[str, str]] = None`. Nelle righe dove scrive `*_image_keyword`, se `image_url_map` e' presente e la keyword ha un mapping, usa l'URL; altrimenti usa la keyword originale:
|
||||
```python
|
||||
def _resolve_image(self, keyword: str, image_url_map: Optional[dict[str, str]]) -> str:
|
||||
if image_url_map and keyword in image_url_map:
|
||||
return image_url_map[keyword]
|
||||
return keyword
|
||||
```
|
||||
Applica `_resolve_image()` a: `cover_image_keyword`, ogni `slide.image_keyword`, `cta_image_keyword`.
|
||||
|
||||
**2. Aggiorna `backend/services/generation_pipeline.py`:**
|
||||
|
||||
Dopo che il loop di generazione ha completato tutti gli slot (dopo il for loop, prima di `self._csv.build_csv()`):
|
||||
- Carica settings da disco per verificare se `unsplash_api_key` e' configurata
|
||||
- Se presente, crea UnsplashService con `cache_path=DATA_PATH / "unsplash_cache.json"`
|
||||
- Estrai tutte le keyword uniche dai PostResult success: `cover_image_keyword`, ogni `slide.image_keyword`, `cta_image_keyword`
|
||||
- Chiama `await unsplash.resolve_keywords(unique_keywords)` dentro asyncio.to_thread (se necessario) o direttamente se gia' async
|
||||
- Passa `image_url_map` a `self._csv.build_csv()`
|
||||
- Chiudi UnsplashService
|
||||
|
||||
Salva `image_url_map` nel JobStatus (aggiungere campo opzionale `image_url_map: Optional[dict[str, str]] = None` al dataclass `JobStatus`) per poterlo passare anche all'export con edits.
|
||||
|
||||
Includi `image_url_map` nella serializzazione/deserializzazione su disco (`_save_job_to_disk` / `_load_job_from_disk`).
|
||||
|
||||
**3. Aggiorna `backend/routers/generate.py`:**
|
||||
|
||||
In `_get_pipeline()` e `_get_or_create_pipeline()`, nessuna modifica necessaria — UnsplashService viene creato dentro la pipeline, non iniettato.
|
||||
|
||||
**4. Aggiorna `backend/routers/export.py`:**
|
||||
|
||||
In `download_csv_with_edits()`, dopo aver caricato il job JSON da disco, recupera `image_url_map` dal JSON (se presente) e passalo a `_csv_builder.build_csv_content()`. Questo garantisce che anche il CSV esportato con edits inline contenga gli URL Unsplash.
|
||||
|
||||
**ATTENZIONE**: La risoluzione Unsplash avviene UNA SOLA VOLTA dopo la generazione batch, NON ad ogni download CSV. L'`image_url_map` viene salvato nel job JSON e riutilizzato.
|
||||
|
||||
**NOTA**: Per `generate_single()` (rigenerazione singola), NON risolvere Unsplash. La rigenerazione e' veloce e deve restare tale. Gli URL verranno risolti al momento del download CSV dal `image_url_map` del job originale (le keyword nuove che non hanno mapping useranno il fallback keyword).
|
||||
</action>
|
||||
<verify>
|
||||
- `python -c "from backend.services.csv_builder import CSVBuilder; print('OK')"` compila senza errori
|
||||
- `python -c "from backend.services.generation_pipeline import GenerationPipeline, JobStatus; j = JobStatus(job_id='x', status='running', total=0, completed=0, current_post=0); print(j.image_url_map)"` stampa `None`
|
||||
- `python -c "from backend.routers.export import router; print('OK')"` compila senza errori
|
||||
- Verifica che il campo `image_url_map` sia incluso nella serializzazione: `python -c "from backend.services.generation_pipeline import JobStatus; import json; j = JobStatus(job_id='t', status='completed', total=1, completed=1, current_post=0, image_url_map={'test': 'https://example.com/img.jpg'}); print(j.image_url_map)"`
|
||||
</verify>
|
||||
<done>
|
||||
CSVBuilder risolve keyword in URL quando image_url_map e' disponibile. GenerationPipeline risolve keyword via UnsplashService dopo il batch LLM e salva la mappa nel job JSON. Export con edits riutilizza la mappa salvata. generate_single non tocca Unsplash.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
Verifica complessiva backend Phase 4 Plan 01:
|
||||
|
||||
1. **Import chain**: `python -c "from backend.main import app; print('FastAPI app OK')"` — nessun errore di import circolare
|
||||
2. **Settings roundtrip**: Il campo unsplash_api_key viene salvato e caricato correttamente da settings.json
|
||||
3. **CSV senza Unsplash**: Con unsplash_api_key=None, il CSV contiene le keyword testuali originali (nessuna regressione)
|
||||
4. **UnsplashService cache**: La cache in-memory evita chiamate duplicate nella stessa sessione
|
||||
5. **Serializzazione job**: image_url_map viene serializzato in JSON e deserializzato correttamente da _load_job_from_disk
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- UnsplashService creato e importabile senza errori
|
||||
- Settings ha campo unsplash_api_key con masking nel router
|
||||
- CSVBuilder accetta image_url_map opzionale e risolve keyword -> URL
|
||||
- GenerationPipeline integra UnsplashService dopo il batch LLM
|
||||
- JobStatus include image_url_map con persistenza su disco
|
||||
- Export con edits riutilizza image_url_map dal job originale
|
||||
- Nessuna regressione: senza API key Unsplash, tutto funziona come prima
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/04-enrichment/04-01-SUMMARY.md`
|
||||
</output>
|
||||
224
.planning/phases/04-enrichment/04-02-PLAN.md
Normal file
224
.planning/phases/04-enrichment/04-02-PLAN.md
Normal file
@@ -0,0 +1,224 @@
|
||||
---
|
||||
phase: 04-enrichment
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: ["04-01"]
|
||||
files_modified:
|
||||
- frontend/src/types.ts
|
||||
- frontend/src/pages/Settings.tsx
|
||||
- frontend/src/pages/OutputReview.tsx
|
||||
- frontend/src/components/PostCard.tsx
|
||||
- frontend/src/api/hooks.ts
|
||||
autonomous: true
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "La pagina Settings mostra un campo per l'API key Unsplash nella sezione appropriata"
|
||||
- "Se Unsplash non e' configurato, OutputReview mostra un avviso discreto che suggerisce di configurarlo"
|
||||
- "Se Unsplash e' configurato e le keyword sono state risolte in URL, la PostCard mostra un thumbnail della cover image"
|
||||
- "Il thumbnail e' visibile solo per post con URL immagine reali (non per keyword testuali)"
|
||||
- "SettingsStatus include unsplash_api_key_configured per controllo frontend"
|
||||
artifacts:
|
||||
- path: "frontend/src/types.ts"
|
||||
provides: "Tipo Settings con unsplash_api_key, SettingsStatus con unsplash_api_key_configured"
|
||||
contains: "unsplash_api_key"
|
||||
- path: "frontend/src/pages/Settings.tsx"
|
||||
provides: "Campo input per Unsplash API key"
|
||||
contains: "unsplash"
|
||||
- path: "frontend/src/components/PostCard.tsx"
|
||||
provides: "Thumbnail cover image quando URL disponibile"
|
||||
contains: "img"
|
||||
key_links:
|
||||
- from: "frontend/src/pages/Settings.tsx"
|
||||
to: "Settings type"
|
||||
via: "Campo unsplash_api_key nel form state"
|
||||
pattern: "unsplash_api_key"
|
||||
- from: "frontend/src/pages/OutputReview.tsx"
|
||||
to: "useSettingsStatus"
|
||||
via: "Controlla unsplash_api_key_configured per avviso"
|
||||
pattern: "unsplash"
|
||||
- from: "frontend/src/components/PostCard.tsx"
|
||||
to: "cover_image_keyword"
|
||||
via: "Controlla se inizia con http per decidere se mostrare thumbnail"
|
||||
pattern: "http|img|thumbnail"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Aggiornare il frontend per supportare la configurazione Unsplash e mostrare thumbnail delle immagini nell'anteprima.
|
||||
|
||||
Purpose: L'utente puo' configurare la propria API key Unsplash dalla pagina Impostazioni. Nell'Output Review, se le immagini sono state risolte in URL reali, ogni PostCard mostra un piccolo thumbnail della cover image. Se Unsplash non e' configurato, un avviso discreto suggerisce di configurarlo per ottenere immagini reali nel CSV.
|
||||
|
||||
Output: Frontend aggiornato con campo Unsplash in Settings, thumbnail preview in PostCard, hint discreto in OutputReview.
|
||||
</objective>
|
||||
|
||||
<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>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/04-enrichment/04-CONTEXT.md
|
||||
@.planning/phases/04-enrichment/04-01-SUMMARY.md
|
||||
|
||||
# File da modificare — leggi PRIMA di implementare
|
||||
@frontend/src/types.ts
|
||||
@frontend/src/pages/Settings.tsx
|
||||
@frontend/src/pages/OutputReview.tsx
|
||||
@frontend/src/components/PostCard.tsx
|
||||
@frontend/src/api/hooks.ts
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Types + Settings page + hooks per Unsplash</name>
|
||||
<files>
|
||||
frontend/src/types.ts
|
||||
frontend/src/pages/Settings.tsx
|
||||
frontend/src/api/hooks.ts
|
||||
</files>
|
||||
<action>
|
||||
**1. Aggiorna `frontend/src/types.ts`:**
|
||||
|
||||
Aggiungi a `Settings`:
|
||||
```typescript
|
||||
unsplash_api_key?: string | null
|
||||
```
|
||||
|
||||
Aggiungi a `SettingsStatus`:
|
||||
```typescript
|
||||
unsplash_api_key_configured: boolean
|
||||
```
|
||||
|
||||
**2. Aggiorna `frontend/src/pages/Settings.tsx`:**
|
||||
|
||||
Aggiungi una nuova sezione "Immagini" DOPO la sezione "Anthropic" e PRIMA di "Brand". La sezione contiene:
|
||||
|
||||
- Titolo sezione: `<h2>` con stile `text-xs font-semibold text-stone-500 uppercase tracking-wider` e testo "Immagini"
|
||||
- Campo API Key Unsplash: input password con toggle visibilita' (stessa struttura dell'API Key Claude). Placeholder: se gia' configurata mostra `"••••••••••••••••"`, se non configurata mostra `"Incolla la tua Access Key Unsplash"`.
|
||||
- Testo helper sotto: se configurata `"API key Unsplash configurata. Le keyword verranno risolte in URL immagini reali nel CSV."`, se non configurata `"Opzionale. Registrati su unsplash.com/developers per ottenere una Access Key gratuita (50 req/h)."`.
|
||||
|
||||
Aggiorna `useEffect` di inizializzazione form per includere `unsplash_api_key: ''` (come per api_key, non pre-popolare).
|
||||
|
||||
Nel `handleSubmit`, applica la stessa logica di api_key: se `unsplash_api_key` e' vuota, non inviarla (evita sovrascrittura). Reset dopo salvataggio.
|
||||
|
||||
Gestione stato visibilita': aggiungi `showUnsplashKey` state separato (non condividere con `showApiKey`).
|
||||
|
||||
**3. Hooks — nessuna modifica necessaria:**
|
||||
|
||||
`useSettings()`, `useUpdateSettings()` e `useSettingsStatus()` gia' funzionano genericamente con i tipi Settings/SettingsStatus. L'aggiunta di nuovi campi ai tipi TypeScript e' sufficiente.
|
||||
</action>
|
||||
<verify>
|
||||
- `cd frontend && npx tsc --noEmit` compila senza errori TypeScript
|
||||
- Verifica visivamente che Settings.tsx abbia la sezione Immagini con input Unsplash
|
||||
</verify>
|
||||
<done>
|
||||
Types aggiornati con unsplash_api_key. Settings page ha sezione "Immagini" con campo API Key Unsplash, toggle visibilita', helper text condizionale, e logica di submit identica a API Key Claude.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Thumbnail PostCard + hint OutputReview</name>
|
||||
<files>
|
||||
frontend/src/components/PostCard.tsx
|
||||
frontend/src/pages/OutputReview.tsx
|
||||
</files>
|
||||
<action>
|
||||
**1. Aggiorna `frontend/src/components/PostCard.tsx`:**
|
||||
|
||||
Nel rendering della sezione SUCCESS, DOPO il cover_title e PRIMA dei metadati secondari (formato/nicchia/data), aggiungi un thumbnail condizionale.
|
||||
|
||||
Logica di rilevamento URL: `const coverIsUrl = post.cover_image_keyword.startsWith('http')`. Se `coverIsUrl` e' true, mostra un thumbnail `<img>`:
|
||||
```tsx
|
||||
{coverIsUrl && (
|
||||
<div className="mt-2 mb-1">
|
||||
<img
|
||||
src={post.cover_image_keyword}
|
||||
alt="Cover preview"
|
||||
loading="lazy"
|
||||
className="w-20 h-14 object-cover rounded-md border border-stone-700"
|
||||
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
Il thumbnail e':
|
||||
- Piccolo: `w-20 h-14` (80x56px) — sufficiente per anteprima senza appesantire la pagina
|
||||
- `object-cover` per riempire senza distorsione
|
||||
- `loading="lazy"` per performance (13 immagini nella pagina)
|
||||
- `onError` nasconde l'immagine se il caricamento fallisce (URL scaduto o invalido)
|
||||
- `rounded-md border border-stone-700` per coerenza con il design stone/amber
|
||||
|
||||
**2. Aggiorna `frontend/src/pages/OutputReview.tsx`:**
|
||||
|
||||
Aggiungi un hint discreto DOPO il box "Info edit inline" e PRIMA della griglia post. L'hint appare SOLO quando Unsplash NON e' configurato.
|
||||
|
||||
Usa `useSettingsStatus()` per controllare `unsplash_api_key_configured`:
|
||||
|
||||
```tsx
|
||||
import { useSettingsStatus } from '../api/hooks'
|
||||
|
||||
// Nel componente, dopo le altre hook calls
|
||||
const { data: settingsStatus } = useSettingsStatus()
|
||||
|
||||
// Nel JSX, DOPO il box "Info edit inline"
|
||||
{settingsStatus && !settingsStatus.unsplash_api_key_configured && (
|
||||
<div className="px-4 py-2 rounded-lg bg-stone-800/30 border border-stone-700/50 text-xs text-stone-600 flex items-center gap-2">
|
||||
<span>Le colonne immagine contengono keyword testuali.</span>
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => { e.preventDefault(); navigate('/settings') }}
|
||||
className="text-amber-500/70 hover:text-amber-400 underline underline-offset-2"
|
||||
>
|
||||
Configura Unsplash
|
||||
</a>
|
||||
<span>per URL immagini reali.</span>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
Importa `useNavigate` da `react-router-dom` (verificare se gia' importato) e `useSettingsStatus` da hooks.
|
||||
|
||||
Stile dell'hint: volutamente discreto (`text-stone-600`, bordo sottile) — non intrusivo, non un warning aggressivo. Scompare quando Unsplash e' configurato.
|
||||
|
||||
**NOTA IMPORTANTE**: Se il progetto usa `<Link>` di react-router invece di `navigate()`, usa `<Link to="/settings">` per coerenza. Verificare il pattern usato nel codebase.
|
||||
</action>
|
||||
<verify>
|
||||
- `cd frontend && npx tsc --noEmit` compila senza errori TypeScript
|
||||
- Verifica che PostCard mostri thumbnail quando cover_image_keyword e' un URL
|
||||
- Verifica che OutputReview mostri hint quando Unsplash non e' configurato
|
||||
</verify>
|
||||
<done>
|
||||
PostCard mostra thumbnail 80x56px della cover image quando la keyword e' un URL Unsplash. OutputReview mostra hint discreto con link a Settings quando Unsplash non e' configurato. L'hint scompare quando l'API key e' presente.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
Verifica complessiva frontend Phase 4 Plan 02:
|
||||
|
||||
1. **TypeScript build**: `cd frontend && npx tsc --noEmit` — zero errori
|
||||
2. **Settings Unsplash**: La sezione "Immagini" appare nella pagina Settings con campo API key, toggle visibilita', helper text
|
||||
3. **PostCard thumbnail**: Se cover_image_keyword inizia con "http", il thumbnail e' visibile; se e' una keyword testuale, nessun thumbnail
|
||||
4. **OutputReview hint**: Senza Unsplash configurato, l'hint suggerisce di configurarlo; con Unsplash configurato, l'hint non appare
|
||||
5. **Nessuna regressione**: Tutte le funzionalita' esistenti (edit inline, rigenerazione, download CSV, badge PN/Schwartz) funzionano come prima
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Types TypeScript aggiornati con unsplash_api_key in Settings e unsplash_api_key_configured in SettingsStatus
|
||||
- Settings page ha sezione "Immagini" funzionante con campo Unsplash API key
|
||||
- PostCard mostra thumbnail condizionale per URL immagini
|
||||
- OutputReview mostra hint discreto quando Unsplash non configurato
|
||||
- TypeScript compila senza errori
|
||||
- Nessuna regressione sulle funzionalita' esistenti
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/04-enrichment/04-02-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user