Files
Michele fe6cd4d516 docs: complete project research
Files:
- STACK.md
- FEATURES.md
- ARCHITECTURE.md
- PITFALLS.md
- SUMMARY.md

Key findings:
- Stack: FastAPI 0.135.1 + React 19 + Vite 7 + Tailwind v4, single-container deploy
- Architecture: FastAPI serves React SPA via catch-all, file-based storage (Docker volume), LLMService with retry/backoff
- Critical pitfall: All 9 pitfalls map to Phase 1 — Italian prompts, Canva field constants, UTF-8 BOM, root_path config, per-item bulk isolation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 14:06:44 +01:00

416 lines
17 KiB
Markdown

# Stack Research
**Domain:** Python content automation backend + React SPA frontend (bulk social media content generation)
**Researched:** 2026-03-07
**Confidence:** HIGH (all versions verified against PyPI and npm registries as of March 2026)
---
## Recommended Stack
### Core Technologies
| Technology | Version | Purpose | Why Recommended |
|------------|---------|---------|-----------------|
| Python | 3.12+ | Runtime | LTS, full async support, matches Docker slim images |
| FastAPI | 0.135.1 | API framework + static file serving | Native async, Pydantic v2 integration, StaticFiles for SPA, OpenAPI auto-docs. Current standard for Python REST APIs. |
| Uvicorn | 0.41.0 | ASGI server | Production-grade async server for FastAPI. "Standard" install includes uvicorn + extras. |
| Pydantic | 2.12.5 | Data validation + settings | FastAPI depends on it. v2 is 5-50x faster than v1. Required for request/response schemas. |
| anthropic (SDK) | 0.84.0 | Claude API client | Official Anthropic Python SDK. Supports sync/async, streaming via `client.messages.stream()`. |
| React | 19.2.4 | Frontend SPA | Latest stable. New concurrent features, improved hooks. Ecosystem standard. |
| Vite | 7.x (latest stable) | Frontend build tool | Faster than Webpack/CRA. Native ES modules in dev. First-party React plugin. Single-command build for production. |
| Tailwind CSS | 4.2.1 | Utility CSS | v4 is major rewrite: 5x faster builds, no tailwind.config.js required, native @tailwindcss/vite plugin. |
### Supporting Libraries
#### Python Backend
| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| python-dotenv | 1.2.2 | Environment variable loading | Load `.env` in local dev. In Docker, use compose `environment:` directly. |
| python-multipart | 0.0.22 | Form/file upload parsing | Required by FastAPI for `UploadFile` and `Form` parameters. |
| httpx | 0.28.1 | Async HTTP client | Calling Unsplash API. Native async, HTTP/2 support, replaces requests for async code. |
| aiofiles | 24.x | Async file I/O | Reading/writing prompt files and generated CSVs without blocking the event loop. |
**CSV generation:** Use Python's built-in `csv` module (stdlib). No external dependency needed. This is the correct choice for structured CSV output to Canva. Pandas/Polars are overkill — the project generates structured rows, not analyzes datasets.
**Prompt file management:** Use Python's built-in `pathlib.Path` + `json`/`yaml` stdlib. No external library needed for file-based prompt storage.
#### React Frontend
| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| @tailwindcss/vite | 4.x | Tailwind v4 Vite plugin | Required for Tailwind v4 integration. Replaces PostCSS config. |
| shadcn/ui | latest CLI | UI component collection | Copy-owned components built on Radix UI + Tailwind. Use for forms, tables, modals, progress indicators. Not an npm dep — CLI copies source. |
| @radix-ui/react-* | latest | Accessible primitives | Pulled in automatically by shadcn/ui. Headless, accessible. |
| react-router-dom | 7.x | Client-side routing | SPA navigation between generation wizard, history, settings pages. |
| @tanstack/react-query | 5.x | Server state + async data | Handles API call lifecycle (loading, error, cache). Essential for polling long-running generation jobs. |
| lucide-react | latest | Icons | shadcn/ui default icon set. Consistent with component library. |
### Development Tools
| Tool | Purpose | Notes |
|------|---------|-------|
| Docker | Container runtime | Multi-stage build: Node stage builds React, Python stage serves everything |
| docker compose | Local dev + VPS deploy | Single `docker-compose.yml` for both environments with env overrides |
| Vite dev proxy | Dev CORS bypass | `vite.config.ts` proxy `/api` to `localhost:8000` during development |
| TypeScript | Frontend type safety | Enabled by default in Vite React template. Catches API contract errors at compile time. |
| ESLint + prettier | Code quality | Vite template includes eslint. Add prettier for formatting. |
---
## Installation
### Backend
```bash
# Create and activate venv
python -m venv .venv
source .venv/bin/activate # Linux/Mac
# .venv\Scripts\activate # Windows
# Core
pip install fastapi[standard]==0.135.1
pip install anthropic==0.84.0
pip install httpx==0.28.1
pip install python-dotenv==1.2.2
pip install aiofiles
# python-multipart is included in fastapi[standard]
# pydantic is included in fastapi[standard]
# uvicorn is included in fastapi[standard]
# Generate requirements.txt
pip freeze > requirements.txt
```
**Note:** `fastapi[standard]` installs FastAPI + uvicorn[standard] + python-multipart + pydantic + email-validator. This is the recommended install per FastAPI docs as of v0.100+. Do NOT install `fastapi-slim` — it was deprecated.
### Frontend
```bash
# Create Vite + React + TypeScript project
npm create vite@latest frontend -- --template react-ts
cd frontend
# Install Tailwind v4 with Vite plugin
npm install tailwindcss @tailwindcss/vite
# Install routing and data fetching
npm install react-router-dom @tanstack/react-query
# Install shadcn/ui (interactive CLI)
npx shadcn@latest init
# Add components as needed
npx shadcn@latest add button input table progress select
# Dev dependencies
npm install -D typescript @types/react @types/react-dom
```
### Tailwind v4 Configuration
In `vite.config.ts`:
```typescript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
react(),
tailwindcss(),
],
})
```
In `src/index.css` (no `tailwind.config.js` needed in v4):
```css
@import "tailwindcss";
```
---
## Architecture: Single-Container Deploy
FastAPI serves both the API and the built React SPA. This eliminates CORS configuration and simplifies Docker deployment on the VPS.
**Pattern:**
```python
# main.py
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
import os
app = FastAPI()
# API routes first
app.include_router(api_router, prefix="/api")
# SPA catch-all: serve index.html for any non-API route
@app.get("/{full_path:path}")
async def serve_spa(full_path: str):
static_dir = "static"
file_path = os.path.join(static_dir, full_path)
if os.path.isfile(file_path):
return FileResponse(file_path)
return FileResponse(os.path.join(static_dir, "index.html"))
# Mount static files (CSS, JS, assets)
app.mount("/assets", StaticFiles(directory="static/assets"), name="assets")
```
**Multi-stage Dockerfile:**
```dockerfile
# Stage 1: Build React frontend
FROM node:22-slim AS frontend-builder
WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ .
RUN npm run build
# Stage 2: Python runtime
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY backend/ .
# Copy built frontend into FastAPI static directory
COPY --from=frontend-builder /app/frontend/dist ./static
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "3000"]
```
---
## Claude API Integration
**Recommended model:** `claude-sonnet-4-5` for content generation (best cost/quality ratio for structured text output at volume).
**Sync pattern** (for bulk generation — simpler, no streaming needed for batch jobs):
```python
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from env
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2000,
messages=[
{"role": "user", "content": prompt}
]
)
content = response.content[0].text
```
**Streaming pattern** (for real-time UI feedback during generation):
```python
with client.messages.stream(
model="claude-sonnet-4-5",
max_tokens=2000,
messages=[{"role": "user", "content": prompt}]
) as stream:
for text in stream.text_stream:
yield text # SSE to frontend
```
**Async pattern** (for FastAPI async endpoints):
```python
async_client = anthropic.AsyncAnthropic()
async def generate_content(prompt: str) -> str:
response = await async_client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2000,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
```
---
## Unsplash API Integration
**Rate limits:**
- Demo (free) tier: **50 requests/hour**
- Production (after manual approval): 5,000 requests/hour
**Implication for bulk generation:** With 50 req/hour limit and bulk carousel generation, implement:
1. Local image cache (save URLs/download images locally after first fetch)
2. Deduplication (don't re-fetch same search term if cached)
3. Fallback to cached results when limit approached
**Python integration via httpx** (no dedicated library needed — Unsplash REST API is simple):
```python
import httpx
async def search_unsplash(query: str, count: int = 1) -> list[dict]:
async with httpx.AsyncClient() as client:
response = await client.get(
"https://api.unsplash.com/search/photos",
params={"query": query, "per_page": count},
headers={"Authorization": f"Client-ID {UNSPLASH_ACCESS_KEY}"}
)
response.raise_for_status()
return response.json()["results"]
```
**Why not python-unsplash library:** `python-unsplash` (v1.2.5, last updated Nov 2023) and `pyunsplash` (v2020) are both poorly maintained. The Unsplash REST API is simple enough that `httpx` direct calls are cleaner, more maintainable, and don't add a stale dependency.
---
## CSV Generation for Canva Bulk Create
Use Python's stdlib `csv` module — no additional dependency:
```python
import csv
import io
def generate_canva_csv(rows: list[dict], fields: list[str]) -> str:
"""Generate CSV string formatted for Canva Bulk Create."""
output = io.StringIO()
writer = csv.DictWriter(output, fieldnames=fields)
writer.writeheader()
writer.writerows(rows)
return output.getvalue()
```
**FastAPI endpoint for CSV download:**
```python
from fastapi.responses import StreamingResponse
@app.get("/api/export/{job_id}/csv")
async def export_csv(job_id: str):
csv_content = get_job_csv(job_id) # retrieve generated CSV
return StreamingResponse(
io.StringIO(csv_content),
media_type="text/csv",
headers={"Content-Disposition": f"attachment; filename=carousels_{job_id}.csv"}
)
```
---
## File-Based Prompt Management
No database. Prompt templates stored as files:
```
backend/
prompts/
frameworks/
persuasion_nurturing.txt
schwartz_awareness.txt
niches/
tech_saas.txt
finance.txt
system_prompt.txt
```
**Access pattern:**
```python
from pathlib import Path
PROMPTS_DIR = Path(__file__).parent / "prompts"
def load_prompt(category: str, name: str) -> str:
prompt_path = PROMPTS_DIR / category / f"{name}.txt"
return prompt_path.read_text(encoding="utf-8")
def list_prompts(category: str) -> list[str]:
return [p.stem for p in (PROMPTS_DIR / category).glob("*.txt")]
```
**Advantages over DB:** Zero setup, version-controlled with git, easy to edit, diff-friendly. Appropriate for this scale (tens of prompt files, not millions of records).
---
## Alternatives Considered
| Recommended | Alternative | When to Use Alternative |
|-------------|-------------|-------------------------|
| FastAPI | Django REST Framework | When you need Django ORM, admin panel, or large team with Django expertise |
| FastAPI | Flask | Flask is simpler but lacks native async, auto OpenAPI docs, and Pydantic integration |
| React + Vite | Next.js | When you need SSR, SEO, or ISR. Overkill for an internal B2B tool served on a subpath. |
| Tailwind v4 | Tailwind v3 | Only if targeting environments where v4's new CSS syntax causes issues. v4 is stable as of 2025. |
| shadcn/ui | MUI / Chakra UI | When you need a full pre-styled design system without customization. shadcn is better for owned components. |
| stdlib csv | pandas | When doing data analysis, not just writing structured rows. pandas adds ~30MB to container. |
| httpx direct | python-unsplash | Only if Unsplash API complexity grows significantly (pagination, upload, etc.) |
| Single container | Separate nginx + Python | When frontend and backend scale independently, or need different update cadences. Overkill for this project. |
---
## What NOT to Use
| Avoid | Why | Use Instead |
|-------|-----|-------------|
| `fastapi-slim` | Deprecated as of recent FastAPI versions. Missing extras. | `fastapi[standard]` |
| `create-react-app` | Unmaintained since 2023. No Vite, slow builds, outdated webpack. | `npm create vite@latest` |
| `requests` library | Sync-only. Blocks FastAPI async event loop when called from async endpoints. | `httpx` with `AsyncClient` |
| `openai` library for Claude | Wrong SDK. Anthropic has official `anthropic` SDK with better type safety and streaming helpers. | `anthropic` SDK |
| Tailwind v3 PostCSS config | v3 config is still valid but v4 drops `tailwind.config.js` requirement and is significantly faster. | Tailwind v4 + `@tailwindcss/vite` |
| `python-unsplash` / `pyunsplash` | Both stale (2020-2023), no async support, thin wrappers adding maintenance risk. | `httpx` direct calls |
| `uvicorn` without `[standard]` | Base uvicorn lacks websocket support and other extensions. | `uvicorn[standard]` (included in `fastapi[standard]`) |
| Separate nginx container | Adds operational complexity without benefit for single-service subpath deploy. | FastAPI serves static files directly. |
---
## Stack Patterns by Variant
**For development (local):**
- Run FastAPI with `uvicorn main:app --reload` on port 8000
- Run Vite dev server on port 5173 with proxy: `vite.config.ts``proxy: { '/api': 'http://localhost:8000' }`
- No Docker needed locally
**For VPS subpath deployment (`lab.mlhub.it/postgenerator/`):**
- React must be built with `base: '/postgenerator/'` in `vite.config.ts`
- FastAPI must handle requests prefixed with `/postgenerator/` (via reverse proxy or app root_path)
- Set `root_path="/postgenerator"` in uvicorn or via `--root-path /postgenerator` flag
- lab-router nginx handles the subpath routing
**For bulk generation (many carousels in one job):**
- Use FastAPI `BackgroundTasks` for async job processing
- Store job state in a JSON file (file-based, no DB)
- Frontend polls `/api/jobs/{id}/status` with react-query
- Streaming SSE optional for real-time token display
---
## Version Compatibility
| Package | Compatible With | Notes |
|---------|-----------------|-------|
| fastapi==0.135.1 | pydantic>=2.0, starlette>=0.46.0 | v0.100+ requires pydantic v2. pydantic v1 not supported. |
| anthropic==0.84.0 | Python>=3.8 | Fully supports async via `AsyncAnthropic()`. |
| tailwindcss==4.2.1 | @tailwindcss/vite==4.x | v4 requires the Vite plugin, NOT PostCSS plugin (different from v3). |
| react==19.2.4 | react-router-dom>=7, @tanstack/react-query>=5 | React 19 has breaking changes from 18. Verify shadcn/ui component compatibility. |
| shadcn/ui | react>=18, tailwindcss>=3 | shadcn/ui officially supports Tailwind v4 since early 2025. Use `npx shadcn@latest init`. |
| python-multipart==0.0.22 | Python>=3.10 | Required for FastAPI file uploads. Requires py3.10+. |
---
## Sources
- [PyPI - fastapi](https://pypi.org/project/fastapi/) — v0.135.1 verified March 1, 2026 (HIGH confidence)
- [PyPI - anthropic](https://pypi.org/project/anthropic/) — v0.84.0 verified February 25, 2026 (HIGH confidence)
- [PyPI - uvicorn](https://pypi.org/project/uvicorn/) — v0.41.0 verified February 16, 2026 (HIGH confidence)
- [PyPI - pydantic](https://pypi.org/project/pydantic/) — v2.12.5 verified (HIGH confidence)
- [PyPI - python-dotenv](https://pypi.org/project/python-dotenv/) — v1.2.2 verified March 1, 2026 (HIGH confidence)
- [PyPI - httpx](https://pypi.org/project/httpx/) — v0.28.1 verified December 6, 2024 (HIGH confidence)
- [PyPI - python-multipart](https://pypi.org/project/python-multipart/) — v0.0.22 verified January 25, 2026 (HIGH confidence)
- [npm - react](https://www.npmjs.com/package/react) — v19.2.4 (HIGH confidence, per WebSearch)
- [npm - tailwindcss](https://www.npmjs.com/package/tailwindcss) — v4.2.1 verified ~February 2026 (HIGH confidence)
- [npm - vite](https://vite.dev/releases) — v7.x confirmed stable 2026 (MEDIUM confidence — WebSearch shows 7.3.1, v8 beta in progress)
- [Anthropic Streaming Docs](https://platform.claude.com/docs/en/api/messages-streaming) — Streaming patterns verified directly (HIGH confidence)
- [Unsplash API Docs](https://unsplash.com/documentation) — Rate limits 50 req/hr demo, 5000 prod (HIGH confidence)
- [FastAPI Static Files Docs](https://fastapi.tiangolo.com/tutorial/static-files/) — SPA serving pattern (HIGH confidence)
- [Tailwind CSS v4 Announcement](https://tailwindcss.com/blog/tailwindcss-v4) — v4 features and migration (HIGH confidence)
---
*Stack research for: PostGenerator — Instagram carousel automation (Python + FastAPI + React + Claude API)*
*Researched: 2026-03-07*