Pin the curated brief across refresh (stable, not dynamic)
Persisting only 'dismissed' kept swapped-away stories out but let the brief recompose on refresh — so a chosen replacement (and the hero) could change unexpectedly. Now the reader's actual brief view is persisted per day: - loadToday keeps the saved view for the same brief_date (swaps and hero hold steady); re-fetches fresh on a new day or when forced. - A boundary change forces a fresh re-fetch (and re-pins); Replace pins the new view; Clear-session drops the pin so it re-composes fresh. Frontend only — rebuild + refresh (no server restart needed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user