Previously 3 failed attempts at 06:00 (e.g. llamaswap model cold/starting)
permanently blocked generation until the next day. Now the retry loop
pauses SLOW_RETRY_MINUTES (default 60) after the burst budget is spent,
then resumes — one attempt per hour until jokes exist.
- Middleware sets CSP (default-src 'none'; style-src 'self'), HSTS,
X-Content-Type-Options, X-Frame-Options, Referrer-Policy,
Permissions-Policy on every response
- Inline <style> moved to /styles.css so CSP needs no unsafe-inline
- /favicon.ico returns a 1x1 GIF (kills 404 noise)
- Escape LLM jokes, article title, and URL before HTML rendering (XSS fix)
halogen-qwen3.8-flash-next sometimes uses typographic quotes (“ ”) as JSON
string delimiters, which breaks strict json.loads. Add a scanner that
normalizes curly-delimited strings to straight quotes while preserving
curly quotes used as content inside straight-quoted strings.
- FastAPI + APScheduler + SQLite, single container
- Daily generation at 06:00 Europe/Copenhagen + cold-start generation
- Retry with backoff (3 attempts/15 min), stale fallback with banner
- OpenAI-compatible endpoint via env (OPENAI_BASE_URL/MODEL/API_KEY)
- docker-compose with named volume for joke persistence