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 SEEN_KEY = 'goodnews:seen';
|
||||||
const DISMISSED_KEY = 'goodnews:dismissed';
|
const DISMISSED_KEY = 'goodnews:dismissed';
|
||||||
const HISTORY_KEY = 'goodnews:history';
|
const HISTORY_KEY = 'goodnews:history';
|
||||||
|
const BRIEF_VIEW_KEY = 'goodnews:brief_view'; // {date, items} — the reader's curated brief
|
||||||
const HISTORY_CAP = 200;
|
const HISTORY_CAP = 200;
|
||||||
|
|
||||||
let seenIds = new Set();
|
let seenIds = new Set();
|
||||||
@@ -53,8 +54,9 @@
|
|||||||
dismissed = new Set();
|
dismissed = new Set();
|
||||||
history = [];
|
history = [];
|
||||||
persistSession();
|
persistSession();
|
||||||
|
P.saveJSON(BRIEF_VIEW_KEY, null); // drop the pinned brief so it re-composes fresh
|
||||||
showHistory = false;
|
showHistory = false;
|
||||||
select(selected);
|
select(selected, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
let filtersOn = $derived(P.active(userPrefs));
|
let filtersOn = $derived(P.active(userPrefs));
|
||||||
@@ -69,17 +71,31 @@
|
|||||||
return P.param(merged);
|
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;
|
selected = key;
|
||||||
error = '';
|
error = '';
|
||||||
try {
|
try {
|
||||||
// Today = the day's highlights (hero + six). Other moods reveal that
|
// Today = the day's highlights (hero + six). Other moods reveal that
|
||||||
// category only when chosen.
|
// category only when chosen.
|
||||||
if (key === 'today') {
|
if (key === 'today') {
|
||||||
const q = P.param(userPrefs);
|
await loadToday(fresh);
|
||||||
const ex = Array.from(dismissed).join(',');
|
|
||||||
brief = await getJSON(`/api/brief?limit=7${q ? '&' + q : ''}${ex ? '&exclude=' + ex : ''}`);
|
|
||||||
remember(brief.items);
|
|
||||||
} else {
|
} else {
|
||||||
const mood = moods.find((m) => m.key === key);
|
const mood = moods.find((m) => m.key === key);
|
||||||
const q = P.param(P.merge(userPrefs, mood?.filter ?? {}));
|
const q = P.param(P.merge(userPrefs, mood?.filter ?? {}));
|
||||||
@@ -96,7 +112,7 @@
|
|||||||
function refreshPrefs() {
|
function refreshPrefs() {
|
||||||
userPrefs = { ...userPrefs };
|
userPrefs = { ...userPrefs };
|
||||||
P.save(userPrefs);
|
P.save(userPrefs);
|
||||||
select(selected);
|
select(selected, true); // boundaries changed — re-fetch so they apply
|
||||||
}
|
}
|
||||||
function applyAction(kind, value) {
|
function applyAction(kind, value) {
|
||||||
P[kind]?.(userPrefs, value);
|
P[kind]?.(userPrefs, value);
|
||||||
@@ -139,6 +155,8 @@
|
|||||||
if (i >= 0) {
|
if (i >= 0) {
|
||||||
brief.items[i] = repl;
|
brief.items[i] = repl;
|
||||||
brief = { ...brief, items: [...brief.items] };
|
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 {
|
} else {
|
||||||
const i = feed.findIndex((a) => a.id === article.id);
|
const i = feed.findIndex((a) => a.id === article.id);
|
||||||
|
|||||||
Reference in New Issue
Block a user