diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 7676f67..4242eb1 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -23,6 +23,7 @@ const SEEN_KEY = 'goodnews:seen'; const DISMISSED_KEY = 'goodnews:dismissed'; const HISTORY_KEY = 'goodnews:history'; + const BRIEF_VIEW_KEY = 'goodnews:brief_view'; // {date, items} — the reader's curated brief const HISTORY_CAP = 200; let seenIds = new Set(); @@ -53,8 +54,9 @@ dismissed = new Set(); history = []; persistSession(); + P.saveJSON(BRIEF_VIEW_KEY, null); // drop the pinned brief so it re-composes fresh showHistory = false; - select(selected); + select(selected, true); } let filtersOn = $derived(P.active(userPrefs)); @@ -69,17 +71,31 @@ return P.param(merged); } - async function select(key) { + async function loadToday(fresh) { + const q = P.param(userPrefs); + const ex = Array.from(dismissed).join(','); + const fetched = await getJSON(`/api/brief?limit=7${q ? '&' + q : ''}${ex ? '&exclude=' + ex : ''}`); + const view = P.loadJSON(BRIEF_VIEW_KEY, null); + // On a plain (re)load, keep the reader's curated view for the same day — so + // swaps and the hero hold steady. Re-fetch fresh on a new day's brief, or + // when forced (e.g. a boundary changed and must be re-applied). + if (!fresh && view && view.date === fetched.brief_date && Array.isArray(view.items) && view.items.length) { + brief = { brief_date: view.date, title: fetched.title, items: view.items }; + } else { + brief = fetched; + P.saveJSON(BRIEF_VIEW_KEY, { date: fetched.brief_date, items: fetched.items }); + } + remember(brief.items); + } + + async function select(key, fresh = false) { selected = key; error = ''; try { // Today = the day's highlights (hero + six). Other moods reveal that // category only when chosen. if (key === 'today') { - const q = P.param(userPrefs); - const ex = Array.from(dismissed).join(','); - brief = await getJSON(`/api/brief?limit=7${q ? '&' + q : ''}${ex ? '&exclude=' + ex : ''}`); - remember(brief.items); + await loadToday(fresh); } else { const mood = moods.find((m) => m.key === key); const q = P.param(P.merge(userPrefs, mood?.filter ?? {})); @@ -96,7 +112,7 @@ function refreshPrefs() { userPrefs = { ...userPrefs }; P.save(userPrefs); - select(selected); + select(selected, true); // boundaries changed — re-fetch so they apply } function applyAction(kind, value) { P[kind]?.(userPrefs, value); @@ -139,6 +155,8 @@ if (i >= 0) { brief.items[i] = repl; brief = { ...brief, items: [...brief.items] }; + // Pin the swap so this exact curated brief survives a refresh. + P.saveJSON(BRIEF_VIEW_KEY, { date: brief.brief_date, items: brief.items }); } } else { const i = feed.findIndex((a) => a.id === article.id);