fix(news): the browser keeps Local news an hour at most; docs say what the fixes do

Local kept World News API's rows listed after the server had deleted them.
README names News among the routes refused behind a proxy; a timed-out
request is said to count; the cockpit cached state is near the same place.

Co-Authored-By: Claude Fable 5.2 <[email protected]>
This commit is contained in:
Mazeyar Moeini FeizabadiandClaude Fable 5.2 committed 2026-11-10 21:41:29 +01:00
1 parent 671a256a6a
commit caa6f6f812
5 files changed
+49 -8

No files matched your search

+3 -1
View File
@@ -1083,6 +1083,7 @@ export function createNewsLayer({
minor: page.reports.filter((r) =>
withinNewsWindow(r, now(), maxWindowMs),
),
readAt: now(),
loading: false,
error: null,
};
@@ -1094,7 +1095,8 @@ export function createNewsLayer({
...inView,
// A missing key is read again with the next refresh, not each pan.
readKey: need ? readKey : inView.readKey,
minor: need ? [] : inView.minor,
// World News API's news is kept an hour at most, as the server does.
minor: need || now() - inView.readAt >= 3_600_000 ? [] : inView.minor,
loading: false,
error: need ? null : cause?.message || FEED_UNAVAILABLE,
};
+39 -1
View File
@@ -45,6 +45,7 @@ function fixture(
reduced = false,
centre = () => ({ lat: 49, lon: 36 }),
credits = [],
now = () => NOW,
} = {},
) {
const calls = { feed: [], hotspots: 0, outline: 0 };
@@ -110,7 +111,7 @@ function fixture(
const layer = createNewsLayer({
source,
overlayHost,
now: () => NOW,
now,
matchMedia: () => ({ matches: reduced }),
getViewCentre: () => centre(),
registerCredit: (_viewer, credit) => {
@@ -840,6 +841,43 @@ test('a failed Conflicts read leaves Local reading with each refresh', async (t)
assert.equal(view.error, 'News feed unavailable');
});
test('Local reports are listed an hour at most while they cannot be read again', async (t) => {
t.mock.timers.enable({ apis: ['setTimeout'] });
let at = NOW;
let failing = false;
const { layer } = fixture(t, {
height: 1_000_000,
now: () => at,
source: {
getFeed: async (query) => {
if (query.at && failing) throw new Error('News feed unavailable');
return {
reports: [report('inside', { relevance: 'minor' })],
nextBefore: null,
};
},
getAreaOutline: async () => ukraine,
},
});
const listed = () =>
layer.getRowControls().feed.inView.items.map((item) => item.id);
layer.enable();
await layer.update();
layer.setParams({ filters: { section: 'local' } });
await flush();
assert.deepEqual(listed(), ['inside']);
// World News API's terms: its news is kept an hour at most, here too.
failing = true;
at += 59 * 60_000;
await layer.update();
await flush();
assert.deepEqual(listed(), ['inside']);
at += 2 * 60_000;
await layer.update();
await flush();
assert.deepEqual(listed(), []);
});
test('a failed read keeps the layer on with an error; disable clears the overlay', async (t) => {
const { layer, entries } = fixture(t, {
source: { getFeed: async () => Promise.reject(new Error('boom')) },