Replaces the Go server with a Rust one (axum, tokio, sqlx, feed-rs,
ammonia, reqwest) serving the same REST/WebSocket API, so the React
frontend is unchanged.
- Same database: migrations keep the schema_migrations table and file
names, Argon2id hashes and hashed session tokens created by the Go
server keep working (users stay logged in), and feed item GUIDs are
computed the same way, so existing articles are not re-ingested.
- Same security model: SSRF-safe fetching (public addresses only,
checked at resolution time and on every redirect), HTML sanitization,
CSP and security headers, same-origin guard, WebSocket origin check,
per-IP / per-user rate limits, trusted-proxy aware client IPs.
- Fix: articles whose publication date lies in the future (ingested
before the clamp existed) pinned the top of the list. The maintenance
job now pulls such dates back at startup and daily.
- Relative links and images in feed content are resolved against the
article URL instead of being dropped.
- Deploy: static musl binary in an Alpine image; the Unraid compose file
uses pull_policy: always so the new image is applied on the next
`docker compose up -d`. CI runs fmt, clippy and tests (including a
PostgreSQL integration test) before building the image.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fc1UFpB4V1MGWLxHAu3BMK
The grid adds columns as long as 20rem fits; on wide screens that meant
6+ cards per row. The column minimum now grows with the width once 5
columns fit, so a 6th never appears.
Dates outside the current year now show the year: a feed item dated
2027 was displayed as "5 oct." and looked like an old article stuck on
top of the list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fc1UFpB4V1MGWLxHAu3BMK
- Login: the brand-panel headline inherited the ink heading colour and was
near-invisible on the dark green; it is white again.
- Focus mode: sentence-case labels instead of spaced capitals, like the
rest of the app.
- Article placeholder covers (grid and focus) use a soft green tint rather
than a saturated block, which was glaring in dark mode.
- Reader header: "date · 3 min" fits on phones ("de lecture" from sm up).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsXUyMEGFFGVRcHNVfcBLh
- Drop the service worker's image cache: its cross-origin fetch() is bound
by the CSP's connect-src 'self', so every cover failed and fell back to
the green placeholder (in the grid and the reader).
- Grid fills the width with as many ~20rem columns as fit (max 2400px),
now that the sidebar is gone.
- Covers fade in once loaded and get a light bottom shade.
- Placeholder cover shows a centered initial instead of the feed name
that overlapped the feed chip.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsXUyMEGFFGVRcHNVfcBLh
The row of feed chips is gone. The title in the top bar ("Tous les
articles ▾") now says what is shown and opens the feed list: all articles,
favorites, each feed with its unread count, add a feed, manage feeds.
Desktop shows an anchored menu, phones a bottom sheet; arrow keys, Escape
and focus return are supported.
The dashboard keeps a single quiet toolbar (unread count, Non lus/Tous,
refresh, mark all read). Theme, OPML export, keyboard shortcuts and logout
live in the ⋯ menu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- One top bar for every screen size: feeds as chips with unread counts,
Focus mode, theme, add feed, and a menu for managing feeds, OPML export
and logout. The article grid now uses the full page width.
- New "Mes flux" dialog to rename or delete feeds (was hidden behind
hover actions in the sidebar); feeds that fail to update are flagged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Service worker activates new versions immediately (skipWaiting +
clientsClaim). In prompt mode a waiting worker kept serving the previous
build to tabs that never send SKIP_WAITING, so the old UI hit the new API
and showed "Aucun contenu disponible" on every article. Pages already
controlled get a reload toast; lazy chunks missing after a deploy reload
once.
- Reader opens as a large card above the dimmed grid: close with ✕, Escape
or a click beside it; previous/next arrows on the sides (desktop) and a
bottom bar with the next title (all sizes).
- Remove search, the focus banner and decorative headers; plain sentence-
case labels instead of tracked capitals; quieter dashboard header.
- Fix horizontal overflow of cards and reader navigation on phones.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Dockerfile: Go 1.26, npm ci, go mod verify, non-root user, alpine 3.22.
- Compose: Postgres no longer published on the host, password and new
security settings read from .env, GOMEMLIMIT.
- README: document new environment variables and reading shortcuts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- New Reader (desktop + mobile): loads full content on open, prefetches
the next article, reading progress and time, "Aa" settings (light /
sepia / dark / auto theme, serif / sans / Atkinson Hyperlegible font,
size, line height, measure), Space-to-next, keyboard shortcuts, focus
management, inert background, URL-driven (?a=) so Back closes it.
- Dashboard: cursor pagination, unread/all filter, CSS grid (no masonry),
no re-sorting under the reader, optimistic cache patches instead of
refetch storms, "new articles" banner, error/offline states, j/k/o/m/s/v
shortcuts and help dialog.
- Cards: keyboard-openable, lazy <img>, memoized, touch-only swipe.
- Focus mode: framer drag (drop @use-gesture), Read button, undo restores
unread state, arrow keys.
- WebSocket: single connection, backoff, cache patching, no console logs.
- Contrast fixes (on-nature tokens), 44px targets, lang="fr", reduced
motion, self-hosted fonts, theme applied before first paint.
- Code splitting + vendor chunk, prompt-based PWA updates, smaller
precache, offline cache for opened articles and images, caches cleared
on logout; upgrade react-router (CVE), drop unused deps and assets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Security:
- WebSocket events are routed to their owner only (no cross-user leak);
hub close is idempotent (fixes double-close panic), adds ping/pong and
write deadlines.
- Session tokens stored as SHA-256 (migration 008 keeps sessions valid);
single-query auth middleware puts the user in the request context.
- Client IP only trusts X-Forwarded-For from TRUSTED_PROXIES; rate limiter
map is bounded; per-user limit on AI summaries.
- Argon2id at OWASP minimum with a concurrency cap; constant-time login
for unknown emails; atomic first-admin bootstrap; REGISTRATION_ENABLED.
- CSP/HSTS/COOP headers, same-origin guard on mutations, body size limits,
wider SSRF denylist, bounded feed/page/AI response reads, generic errors.
- Upgrade chi, pgx, x/net, x/text, x/crypto (known CVEs); commit go.sum.
Performance:
- List endpoints return a plain-text excerpt and reading time instead of
full HTML; content is sanitized once at ingest (legacy rows backfilled).
- Keyset pagination on (sort_at, id) with matching partial indexes;
redundant indexes dropped (migration 007).
- Fetcher: bounded worker pool, conditional GET (ETag/Last-Modified),
exponential backoff, dedupe before insert, column-safe truncation,
retention-aware ingest, per-user refresh coalescing.
- Read/favorite/read-all are single ownership-scoped statements.
- gzip compression, immutable caching for hashed assets, path-safe SPA
handler, server timeouts; expired sessions purged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Le petit point vert peu visible est remplacé par le bandeau d'angle
d'origine (vert nature, texte blanc), découpé dans le coin de la couverture.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- XSS stocké (critique) : sanitisation bluemonday sur tous les endpoints
d'articles (List, ListByFeed, Favorites, Search, Get), pas seulement Get
- SSRF (élevé) : nouveau utils/safehttp.go (ValidateExternalURL + client durci
via Dialer.Control, bloque IP privées/loopback/link-local/metadata,
anti-DNS-rebinding et limite de redirections) appliqué à l'extracteur et au parser
- WebSocket CSWSH (élevé) : politique same-origin + override WS_ALLOWED_ORIGINS
- rate-limiting (moyen) : token-bucket en mémoire sur /auth/*
- token de session retiré du corps JSON (json:"-"), livré uniquement par le
cookie HttpOnly
- cookie Secure correct derrière un reverse-proxy (X-Forwarded-Proto +
override COOKIE_SECURE)
- admin : interdiction de supprimer son propre compte
- en-têtes de sécurité (nosniff, X-Frame-Options, Referrer-Policy,
Permissions-Policy)
Note : backend non compilé localement (pas de toolchain Go) ; à valider via Docker.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Refonte de toutes les vues en conservant l'identité magazine/vert nature,
avec un système de design cohérent et accessible.
- index.css : tokens sémantiques, échelle radius/ombres, classes réutilisables
(btn-*, chip, surface-card, magazine-content), :focus-visible,
prefers-reduced-motion, grain papier affiné
- nouveaux composants AuthShell (auth split-screen) et MobileTopBar
(navigation mobile, auparavant absente)
- framer-motion sur toutes les vues ; AddFeedModal expose l'import OPML ;
Sidebar ajoute déconnexion + identité utilisateur
- corrections : text-gold cassé -> nature, arithmétique Date -> getTime(),
suppression de App.css mort, ProtectedRoute en Tailwind,
snapshot du deck Focus conforme aux règles React
Frontend : npm run lint + build OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>