- 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>
- API endpoints (feed, brief, category-counts) accept a 'prefs' JSON query
param, parsed tolerantly into FilterPrefs (bad blobs never break the feed).
- Feed over-fetches then applies word-boundary filters in Python and slices to
the page; brief is filtered down (no refill); counts are computed over the
same filtered set so browse numbers match the feed exactly.
- Pause.active() coerces naive datetimes to UTC; FilterPrefs.from_dict skips
malformed pauses and non-string list entries.
- Static site adds the humane ladder (Not today / Less like this / Always hide)
plus a Calm filters panel managing pauses, mutes, and avoid-terms in
localStorage. Nothing leaves the device.
- Tests now 38 (added forgiving-parse and naive-now cases). README documents it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add pytest suite (34 tests) covering scoring thresholds, dedup clustering +
representative selection + time window, brief source/category diversity,
avoid-term phrase matching, and text canonicalization/truncation.
- Rewrite _select_diverse with an explicit, tested contract (best-first, one
per source, backfill, then inject a second category by evicting the
lowest-ranked pick).
- classify_articles now returns attempted/succeeded/skipped (ClassifyReport) so
silent model failures are visible in both the cycle and classify output.
- Fix clean_text truncation to stay within max_len (ellipsis no longer
overshoots).
- New filters.py: canonical FilterPrefs shape (include/mute topics+flavors,
avoid_terms, pauses) and pure word/phrase-boundary matching engine seeding
Calm Filters. Not yet wired into the API.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>