215 Commits (1dad319e5edbf7b5aefcaad805f272e5d7570de2)
 

Author SHA1 Message Date
oabrivard da05965dde feat: add max_articles_per_source setting to frontend
Add max_articles_per_source field to UserSettings interface and DEFAULT_SETTINGS,
expose it as a number input on the Settings page, and add the French i18n label.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 6819c7193c feat: add limit_articles_per_source filter with unit tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 months ago
oabrivard c1ee79bcf6 feat: add max_articles_per_source setting (migration + model + DB)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 months ago
oabrivard a3f4c3b42f fix: always run scrape+rewrite pass to prevent hallucinated URLs
The adaptive pipeline skipped the scrape+rewrite pass when the LLM's search
results had URLs starting with "http". But LLMs hallucinate plausible URLs
(Wikipedia, corporate sites) that pass the http check but aren't actual source
articles. The scrape pass catches these by fetching each URL and validating
the content exists. Always running the full pipeline ensures URL integrity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 2b8f5236d5 fix: strip smart quotes and zero-width chars from pasted URLs
normalizeUrl now strips smart quotes, zero-width spaces, and other invisible
formatting characters that browsers inject when copy-pasting URLs from rich
text sources. Prevents false "URL invalid" errors for valid URLs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 45c9e71589 fix: enforce max_items_per_category in JSON schema and prompt
The LLM was returning only 1 article per category despite the user setting 4.
- Added minItems/maxItems to the category array schema (enforced by OpenAI strict mode)
- Changed prompt from "au maximum N actualites" to "exactement N actualites"
- Schema builder now takes max_items_per_category parameter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 0b0702de39 fix: strip null bytes from LLM output before saving to PostgreSQL JSONB
LLM output occasionally contains \u0000 null bytes (e.g., "annonc\u0000...")
which PostgreSQL rejects in JSONB columns. Added sanitize_json_null_bytes()
that recursively strips null bytes from all string values before DB insert.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 3fe667591d fix: LLM providers use own HTTP client with 120s timeout (was sharing scraper's 15s)
The scraper client (build_scraper_client) has a 15s timeout appropriate for web
scraping, but LLM API calls — especially with web search — take 30-60s. LLM
providers now build their own reqwest client with 120s timeout via build_llm_client().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 69e5f2257a fix: UAT test — ESM compat, correct status codes, idempotent source setup
- Use import.meta.url for ESM-compatible __dirname
- Source creation expects 201, not 200
- Clean up existing sources before adding to avoid unique constraint violation
- Fix E2E docker-compose build context to project root

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard fb604a408b Changed Claude code configuration 3 months ago
oabrivard 97cb58ff42 fix: improve type safety and error handling in generation UAT 3 months ago
oabrivard 02017db2e0 test: add live generation UAT with real OpenAI API key
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 months ago
oabrivard 6fe75d77e7 feat: add source file:line to WARN and ERROR log lines
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 months ago
oabrivard 004f08f385 fix: runtime bugs found during first Docker run + integration tests
Bugs fixed:
- resolve_model queried non-existent admin_provider_models table (use JSONB query on admin_providers)
- key_prefix VARCHAR(10) too short for 11-char prefix (migration to VARCHAR(12))
- API key test schema missing additionalProperties: false (OpenAI strict mode)
- CSP missing font-src data: directive (PDF font embedding blocked)
- Magic link URL not logged in test mode (can't verify without real email)
- Rust 1.85 Docker image too old for dependencies (bumped to 1.88)

Tests added to prevent recurrence:
- schema_meets_openai_strict_mode_requirements: validates additionalProperties on all objects
- key_prefix_full_length_stored_in_db: verifies 11-char prefix survives DB round-trip
- generate_pipeline_resolves_model_from_admin_config: exercises full generation pipeline

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard fa8f604407 fix: set STATIC_DIR in production docker-compose for frontend serving
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 069a4f2022 feat: graceful shutdown and frontend build in Docker
- Add SIGTERM/Ctrl+C signal handling with graceful connection draining
- Close database pool cleanly on shutdown
- Add frontend-builder stage to Dockerfile (node:22-alpine, npm ci + build)
- Move Docker build context to project root so both frontend/ and backend/ are accessible
- Frontend dist/ copied into container at ./static/ for the backend to serve

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard b961f82f01 refactor: add UserRateLimitEntry constructor and settings_changed method
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard c1f2f1456f refactor: simplify recent changes — extract helper, named struct, atomic entry, pre-alloc
- Extract auth::create_and_send_magic_link() to deduplicate token rollback logic
- Replace (i32, i32, RateLimiter) tuple with named UserRateLimitEntry struct
- Use DashMap entry API for atomic rate limiter lookup (fixes TOCTOU race)
- Pre-allocate scraper body Vec from Content-Length when available

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 87338af052 fix: align frontend API types and error handling with backend contracts
- updateRole return type matches backend's {id, role} instead of full AdminUser
- fetchFile error priority aligned with central client (message before error)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 54d54f2a06 fix: architect assessment remediation — 6 issues across backend, frontend, and infra
- Wire hardened scraper client into runtime (SSRF redirect validation was defined but unused)
- Stream scraper body with per-chunk size limit instead of post-download check (DoS/OOM)
- Persist user rate-limit overrides across generation jobs via AppState DashMap
- Roll back magic-link token on email send failure to prevent quota exhaustion
- Fix API error UX: prefer human message over machine error code in frontend
- Unwrap GET /syntheses { items } wrapper in frontend API layer (contract mismatch)
- Bind Postgres to localhost in docker-compose (was exposed on all interfaces)
- Fix CLAUDE.md: runtime queries not compile-time, 10 migrations not 9

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard b9dcf6e749 Updated architect assessment 3 months ago
oabrivard bcfff4d5d5 docs: add spec and plan for coverage assessment v2 update
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard a72ca14779 docs: update tech lead assessment with current test coverage and closed gaps 3 months ago
oabrivard 748606c287 test: shared typed fixtures to prevent mock drift from backend contracts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard ae01bc8e62 security: SSRF redirect validation per hop with custom reqwest policy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 months ago
oabrivard 5da9fad4ec fix: admin rate-limits API passes provider_name instead of id
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 months ago
oabrivard 1ca604401e fix: align SynthesisListItem with backend response (preview fields, not sections)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 months ago
oabrivard e1c37b520b Add architect remediation implementation plan
5 tasks: fix syntheses list contract (frontend aligns to backend preview
fields), fix admin rate-limits path (provider_name not id), SSRF redirect
per-hop validation with custom reqwest policy, shared typed test fixtures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard d20fa20ed2 Add architect remediation design spec
4 fixes: syntheses list contract alignment (frontend to backend),
admin rate-limits path fix (provider_name not id), SSRF redirect
per-hop validation with attempt.error(), shared test fixtures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 7769f56410 Added architect assessment 3 months ago
oabrivard 3b4fa28bb5 Updated Claude code config 3 months ago
oabrivard 3a59362acc docs: add tech lead assessment of test coverage and documentation 3 months ago
oabrivard 286dbbbcc8 test: add E2E infrastructure and 5 Playwright test flows
Add Playwright E2E testing setup with Docker Compose test environment,
database seed script, auth helpers, and five test scenarios covering
registration, admin providers, settings persistence, sources CRUD,
and settings export/import.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 6f3ff1e9a2 docs: add JSDoc to all frontend API modules, pages, components, utilities
Add English JSDoc documentation to 32 source files across the frontend:
- API layer (8 files): client CSRF strategy, credential handling, 401 redirect,
  and endpoint-level docs for auth, settings, sources, syntheses, admin, config, apiKeys
- Pages (11 files): Settings export/import, GenerateSynthesis SSE state machine,
  Home delete confirmation timer, Sources bulk import parsing, SynthesisDetail
  email/export flows, Login/Register Turnstile lifecycle, AuthVerify token flow,
  admin Providers/RateLimits/Users
- Components (8 files): ApiKeyManager CRUD, Turnstile polling init, Navbar/MobileMenu
  route detection, Layout/AdminLayout structure, ErrorBoundary retry, Button variants,
  Toast auto-dismiss timer, LoadingSpinner props
- Utilities (2 files): SSE reconnection backoff, dates locale config
- Context (1 file): AuthContext session check, isAdmin derived signal

No logic changes. TypeScript and vitest pass unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard fa346dc346 test: add frontend page interaction tests (Home, Settings, Sources, Login, Register, Generate)
Add test-utils.tsx with renderWithProviders (MemoryRouter + I18n + Toast),
mockFetch, and mockFetchRoutes helpers. Create 39 interaction-level tests
across 6 page components covering rendering, form validation, API calls,
delete confirmation flows, SSE progress, and file import/export.

Also add Blob.text() polyfill in test setup for jsdom compatibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard a4e618feda test: add unit tests for auth middleware cookie extraction
Extract cookie parsing into a standalone `extract_session_token` function
and add 5 unit tests covering the valid, missing, multi-cookie, whitespace,
and empty-header cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 months ago
oabrivard 7b3fc717eb Add test coverage and documentation implementation plan
12 tasks: backend auth middleware tests, frontend test utilities,
5 page test suites (Home, Settings, Sources, Login/Register, Generate),
JSDoc for API layer + pages/components, E2E infrastructure + 5 Playwright flows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 449edfcf59 Add test coverage and documentation improvement spec
Addresses all 5 recommendations from tech lead assessment:
- Frontend page tests (~39 tests for Settings, Home, Sources, Login, Generate)
- Frontend JSDoc documentation (~30 files)
- Backend test gaps (schema builder, auth middleware, token utils)
- E2E tests with Playwright (5 flows against real Docker stack)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 98528f51bd Fix rate limiter bug, simplify v2 code
Bug fix:
- Per-generation rate limiter was creating a new instance on every check,
  making user rate limit overrides non-functional. Fixed by creating the
  limiter once at pipeline start and reusing for both passes.

Simplifications:
- Extract spawn_task closure in scrape_articles (deduplicate spawn blocks)
- Use idiomatic if let Ok(...) instead of if let Some(..).ok() in scraper
- Replace manual loop with iterator chain in export_keys handler
- Simplify check_rate_limit to single boolean check
- Simplify handleImport settings merge (spread already provides defaults)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 0f66c28c38 v2: empty sections fallback in email template
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 months ago
oabrivard 8c7672105c v2: empty sections fallback in synthesis detail view
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 months ago
oabrivard c698f6e4a3 v2: dual model selection, rate limit overrides, settings export/import
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 7eb24cfd9a v2: API key export endpoint (POST, rate-limited)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 months ago
oabrivard 191e1c716b v2: enhanced scraper - title priority chain, broken link detection, noindex
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 9b994e0528 v2: pipeline user model selection, rate limiter, URL filter, original title, null-safe sections
- resolve_provider_and_key() now respects user ai_provider preference
- Dual model resolution: ai_model for search pass, ai_model_writing for rewrite pass
- Per-generation rate limiter with user override support
- Homepage URL filter removes domain-only URLs after search pass
- ScrapedNewsItem gains original_title field populated from page <title>
- SynthesisResponse::try_from handles null sections gracefully (returns empty vec)
- Search prompt warns LLM against returning homepage URLs
- Rewrite prompt instructs LLM to use originalTitle with language preservation rules

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard ed6b41fe52 v2: add settings migration, model expansion, DB queries (provider, models, rate limits)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 6b27a0f691 Add v2 changes implementation plan
10 tasks covering: migration, settings model expansion, DB queries,
pipeline updates (user model selection, rate limiter, URL filter),
prompts (deep-link, original title), scraper improvements, API key
export, frontend (dual models, rate limits, export/import), and
empty sections fallback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 74e2cb0273 Add v2 changes design spec
8 changes covering: settings export/import, dual model selection,
user rate limit overrides, original title preservation, enhanced
broken link detection, deep-link URL enforcement, settings schema
expansion, and empty sections fallback message.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago
oabrivard 62c2d959d3 Modified Claude settings 3 months ago
oabrivard 04819aa926 Simplify code: deduplicate patterns, fix captcha field name bug
Bug fix:
- Fix frontend sending captcha_token instead of turnstile_token in
  login/register requests (would cause 422 errors on auth)

Backend simplifications:
- Deduplicate VALID_PROVIDERS constant (provider.rs is now the single source)
- Extract validate_display_name/validate_models helpers in provider model
- Add From<UserSettings> for SettingsResponse, From<User> for AdminUserResponse
- Consolidate Resend API call pattern into shared send_via_resend()
- Extract do_bulk_import() for sources bulk/CSV import
- Use idiomatic range.contains() for rate limit validation

Frontend simplifications:
- Consolidate file download logic (exportCsv reuses shared fetchFile/triggerDownload)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 months ago