A reader who swaps a story away should keep that swap after a refresh; before,
the server re-served the original brief.
- localStorage now persists seen / dismissed / history (loadJSON/saveJSON).
- /api/brief accepts an exclude list; dismissed (replaced-away) ids are dropped
and the highlights refill around them, so swaps stick and stay full.
- Replace records the swap to dismissed+seen and persists; the seen-set
(persisted) keeps Replace from recycling across refreshes too.
- History panel survives refresh and gains 'Clear what I've seen (start fresh)'
so it never feels suffocating. Saved history/favorites still come with sign-in.
Tests: brief exclude + refill (90 total).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Hero blur fix: brief enrichment now prefers a page's og:image even when a
feed thumbnail exists (feed thumbs are often tiny; the hero is shown large).
Verified: BBC hero upgrades to the 1024px share image, ScienceDaily to 1920px.
- Today is now 'Highlights from Today' — hero + 6 (brief size 7), which also
makes the secondary grid a balanced 3+3 instead of an orphaned 3+1.
- Replace now excludes every article seen this session (a client-side seen-set),
so it never cycles back to something already shown.
- New session History panel (this tab only, no account): lists everything seen,
including swapped-away stories, so they stay recoverable. Persistent
history/favorites are tabled for sign-in later.
Tests: og:image upgrade of an existing feed image (86 total).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- paywall.py: conservative domain-level paywall detection (New Scientist,
Nature, and common hard/soft paywalls). Never fetches pages — an honest hint.
- API: Article gains a 'paywalled' flag; the brief now leads with a gentle AND
readable story (paywalled/charged stories stay in the five, just not first).
- New GET /api/replacement returns the next-best readable, unshown article
(honors mood+prefs via the merged prefs param; gentle=true for hero swaps).
- UI: paywalled cards show 'May need a subscription'; a Replace / 'Find one I
can read' action (always visible, while tuning actions stay tucked) swaps the
card for a readable alternative, with a gentle notice when none remain.
- Tests: paywall detection + replacement behavior (77 total).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Today is just the day's five highlights (hero + four) — the preview lanes are
gone; other categories appear only when their mood is selected.
- Each view leads with a large serif heading (Today / Wonder / ...) and a
subtitle, with a quiet sage rule — switching moods retitles the page.
- Drop the now-unused Lane usage from the home.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- New BoundariesPanel.svelte: gentle, device-local controls. Avoid words/phrases
first (the trust-critical piece), then 'Paused for now' and 'Always hidden',
each with easy remove. Reassures 'nothing leaves this device'; adding a term
refreshes the brief/feed immediately.
- Quiet 'Boundaries' toggle (active indicator) replaces the old calm bar, keeping
the first viewport calm.
- Wording stays gentle throughout: avoid / pause / hide / boundaries — never
blocked/banned/blacklist.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- New frontend/ SvelteKit static SPA (Svelte 5), served by FastAPI from
frontend/build (falls back to the legacy page if unbuilt).
- Calm design system: cream/sage palette, serif headlines, generous space,
no urgency colors, gentle motion (respects prefers-reduced-motion).
- Home screen: mood-mode nav (Today/Wonder/People Helping/Solutions/Light
Only/Grounded), the daily brief as a hero + remaining four, browsable mood
lanes, an explicit calm end-state, inline Not today / Less like this / Hide
affordances, and device-local Calm Filters mirroring goodnews/filters.py.
- Backend: moods.py + GET /api/moods (single source of truth for the modes);
FilterPrefs gains max_cortisol/max_ragebait ceilings (for Light Only).
- Push categorical filters (include/mute topics+flavors, ceilings) into SQL in
queries.feed so low-ranked-but-matching items (e.g. discovery for Wonder)
are not truncated by ranking; only avoid-terms stay a Python pass.
- PWA manifest + icon (installable; offline deferred per plan).
- Multi-stage Dockerfile builds the site then serves it from the API.
- Tests: queries.feed categorical filters (63 total). README updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>