Adds detect_and_parse_feed which orchestrates feed caching/freshness logic:
uses cached feed URL directly if fresh (< 30 days), otherwise re-discovers
from source URL via discover_feed. Returns FeedResult::Found or NotFound.
Includes 4 new tests covering fresh cache, no cache, no feed, and stale cache cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add nullable rss_url (TEXT) and rss_discovered_at (TIMESTAMPTZ) columns
to the sources table for RSS feed URL caching. Update the Source struct,
all query_as SELECT/RETURNING queries, and add update_source_rss db function.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7-task plan covering: feed-rs dependency, DB migration, feed_parser
service (parse, discover, orchestrate), pipeline integration, and tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Spec for adding RSS/Atom feed support to personalized sources in Phase 1
of the synthesis pipeline — auto-discovery, persistence with 30-day
re-check, and fallback to HTML extraction.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The API client's 401 handler was intercepting responses from /auth/*
endpoints (login, register, me), throwing "Session expired" before the
actual response could reach the caller. This prevented the login form
from working — the AuthProvider's me() call returned 401, threw, and
the error propagated into the login flow.
Now the 401 redirect only triggers for non-auth API calls (where it
genuinely indicates an expired session). Auth endpoints handle their
own error responses normally.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The API client redirected to /login on any 401 response, including the
GET /auth/me call made by AuthProvider on the login page itself. This
caused an infinite hard-navigation reload loop.
Skip the redirect when already on /login or /register — the AuthContext
route guards handle unauthenticated routing for those pages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The CSP had connect-src 'self' which blocked Cloudflare Turnstile's
internal fetch requests to challenges.cloudflare.com, causing them to
hang indefinitely and triggering a page reload loop.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ServeDir::not_found_service serves index.html but preserves the 404
status code. Switch to ServeDir::fallback which returns 200, fixing
client-side routes like /login returning 404 to the browser.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Turnstile tokens are single-use. The resend flow reused the consumed token,
causing "timeout-or-duplicate" errors from Cloudflare.
Frontend:
- Add Turnstile widget to resend view on Login and Register pages
- Add resetSignal prop to Turnstile component to re-solve after each resend
- Clear token after each successful API call, guard resend against null token
- Add test for resetSignal behavior
Backend:
- Add entry log when magic link email sending begins
- Add explicit warning when rate limit prevents sending
- Add error log with rollback context when email delivery fails
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Turnstile tokens are single-use. The resend flow was reusing the consumed
token from the initial submission, causing "timeout-or-duplicate" errors.
- Add Turnstile widget to the resend view so a fresh token is obtained
- Add resetSignal prop to Turnstile component to re-solve after each resend
- Clear token after each successful API call to prevent stale reuse
- Guard handleResend against null token
- Add test for resetSignal behavior
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the Cloudflare Turnstile script fails to load (e.g., 503 from CDN),
the polling interval ran forever, causing the page to appear stuck in a
refresh loop. Now stops after 100 attempts (10s) and calls onError.
Also adds dedicated unit tests for the Turnstile component covering
immediate render, delayed load, timeout, and cleanup-during-polling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The frontend Vite build was not receiving VITE_TURNSTILE_SITE_KEY during
Docker builds, causing the production bundle to fall back to the Cloudflare
test sitekey (1x00000000000000000000AA) which returns 503 in production.
- Add ARG/ENV for VITE_TURNSTILE_SITE_KEY in Dockerfile frontend stage
(placed after npm ci to preserve dependency cache)
- Pass TURNSTILE_SITE_KEY from .env as build arg in docker-compose.yml
- Add post-change workflow section to CLAUDE.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Host port changed from 8080 to 8005 and bound to 127.0.0.1 only
so traffic goes through Caddy (HTTPS) instead of being exposed directly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Many modern sites (Hugo, WordPress, Next.js) load articles via JavaScript
but include full article URLs in JSON-LD schema.org markup in the <head>.
The scraper now extracts these first (highest quality), then falls back
to <a href> heuristic extraction. Supports ItemList, BlogPosting,
NewsArticle, @graph arrays, and mainEntity wrappers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Date parser now supports: 25/03/2026, 25-03-2026, March 25 2026,
25 mars 2026, 15 février 2026, and short month variants.
Articles without dates are no longer routed to a separate category —
they stay in their LLM-classified category with date shown as empty.
This prevents losing good articles in a catch-all section.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Date aligned left, source URL aligned right. URL stripped of protocol
and truncated to 40 chars with "..." if too long. Full URL on hover.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The API client expects empty responses to use 204, not 200.
Returning 200 with no body caused JSON parse error in the frontend.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract content settings card and sources card into dedicated components,
reducing ThemeManager from 938 to 233 lines while keeping theme list CRUD
and selector in the parent.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace raw fetch in handleStop with synthesesApi.stop()
- Add stop() method to synthesesApi
- Replace raw <button> elements in GenerateSynthesis with Button component (generate, retry, stop)
- Remove deprecated LLM link extraction schema reference from technical_specs.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- export_csv now accepts optional theme_id query param and filters accordingly
- Add UpdateThemeRequest::validate() with bounds checking; call it in the update handler
- Verify theme ownership in sources::create when theme_id is provided
- Update STATUS_OPTIONS (add filtered_too_old, filtered_not_article; remove filtered_duplicate) and SOURCE_TYPE_OPTIONS (add brave_search; remove overflow) in ArticleHistory
- Replace hardcoded French strings ('Confirmer', 'Erreur inconnue') with t() calls; add settings.apiKeys.unknownError key to fr.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Bulk/CSV import now passes theme_id through to DB
- Preferred source update scoped by theme_id (no cross-theme reset)
- Theme creation sends sensible defaults from frontend
- Scheduler wraps generation in 15-minute timeout
- Job store cleanup runs every 5 minutes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Trim architecture.md significantly (section 1 overview, technology stack, deployment topology,
module inventory lists, LLM trait block, pipeline details, data model table, full API tables,
background task list). Replace section 5 API tables with a one-liner. Requirements.md sections
3.1/3.5/3.6/3.7/3.8 and 4.2 condensed with cross-references. deployment.md security feature
list replaced by cross-reference to architecture.md Section 6. functional_specs.md Section 3
gains a cross-reference to technical_specs.md Section 5.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The full pipeline algorithm is now in technical_specs.md with added
details: preferred source ordering, windowed waves, is_article filter,
date fallback, "Articles sans date" category, cancellation support.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaced by 7 consolidated docs: requirements, functional_specs,
architecture, technical_specs, dev_guidelines, qa_guidelines, deployment.
algorithm.md retained as pipeline reference.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Updated project structure, features list, documentation links, database
tables, testing commands, and environment variables to reflect the
current state (multi-theme, scheduling, preferred sources, etc.)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace "Lire la suite" and "Reduire" with t('synthesis.readMore')
and t('synthesis.collapse'). Adds both keys to fr.ts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Moves JobEntry, JobStore, ProgressEvent, JOB_TTL, and emit_progress
to a dedicated module. Updates imports in synthesis.rs, generation.rs,
scheduler.rs, and app_state.rs. synthesis.rs re-exports for backward
compatibility.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces ~200 duplicated lines in Phase 1 (personalized sources) and
Phase 2 (Brave Search) with a shared scrape_and_classify_batch function.
Uses ScrapeClassifyCtx to bundle shared parameters. Also prepares
synthesis.rs for JobStore extraction by re-exporting from job_store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
normalizeUrl and isValidUrl are now in ~/utils/url. ThemeManager
and sources-utils tests import from the new location. The /sources
route redirect to /themes is preserved in App.tsx.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Settings tests now reference fields that exist in the current UI
(max articles per source instead of removed theme field). Generate
tests now mock the themes API and use current pipeline step names.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>