openvibers/openvibe.reviews Commits

Reviews: the advertised Atom feed is a valid empty feed before anything is published (was 404) (#45)

OP OpenVibers committed 12 Oct 2026 · efda098441 · 2 files, +16 −2

A broken-link survey of every OpenVibe home page found one real 404: openvibe.reviews names `/feed.atom` in every page's head (`<link rel="alternate" type="application/atom+xml">`), and that feed answered **404 "No summary has been published yet."** until a summary is published, which is today's state. Feed readers and crawlers that follow the link got an error.

An Atom feed needs a real `<updated>` time, which is why the empty feed answered 404. It now answers 200 with a valid, empty feed whose `<updated>` is the release's commit time: the `releaseLastmod` the sitemap already uses. This is how News, Trade and Coupons already serve their empty feeds. It answers 404 only when there is no release time. Once a summary is published nothing changes: the newest entry dates the feed.

Test: `nojs.test.js` adds "the Atom feed every page names is a valid, empty feed before anything is published" (200, the Atom namespace, a real `<updated>`, no entries). All of `nojs.test.js` passes.

Changes

ddiff --git a/server/http/machine.js b/server/http/machine.js
iindex ae13a19..f18ad4f 100644
---- a/server/http/machine.js
++++ b/server/http/machine.js
@@@ -78,8 +78,11 @@ function createMachine({ svc, config, releaseLastmod }) {
router.get('/feed.atom', async (_req, res) => {
const items = await feedItems();
-- if (!items.some((i) => i.decision.listable)) return res.status(404).set('Cache-Control', cache.htmlHeaders({ maxAge: 60 })).type('text/plain').send('No summary has been published yet.');
-- setCache(res).type('application/atom+xml').send(seo.atomFeed({ title: 'OpenVibe.Reviews: published summaries', link: `${origin}/`, feedUrl: `${origin}/feed.atom`, id: `${origin}/feed.atom` }, items));
++ // Every page names this feed, so an empty one is still a valid feed (as News, Trade and Coupons serve): its
++ // <updated> is the release's commit time, a real time. Only without one does it answer 404.
++ const empty = !items.some((i) => i.decision.listable);
++ if (empty && !releaseLastmod) return res.status(404).set('Cache-Control', cache.htmlHeaders({ maxAge: 60 })).type('text/plain').send('No summary has been published yet.');
++ setCache(res).type('application/atom+xml').send(seo.atomFeed({ title: 'OpenVibe.Reviews: published summaries', link: `${origin}/`, feedUrl: `${origin}/feed.atom`, id: `${origin}/feed.atom`, ...(empty ? { updated: releaseLastmod } : {}) }, items));
});
router.get('/feed.json', async (_req, res) => {
setCache(res).type('application/feed+json').send(JSON.stringify(seo.jsonFeed({ title: 'OpenVibe.Reviews: published summaries', link: `${origin}/`, feedUrl: `${origin}/feed.json`, description: 'Editor-reviewed summaries, by the time their current revision was published.' }, await feedItems())));
ddiff --git a/test/nojs.test.js b/test/nojs.test.js
iindex 94964c5..e4da177 100644
---- a/test/nojs.test.js
++++ b/test/nojs.test.js
@@@ -26,6 +26,17 @@ const noScripts = (html) => html.replace(/<script[\s\S]*?<\/script>/g, '');
const jsonLd = (html) => [...html.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)].map((m) => JSON.parse(m[1]));
const robots = (html) => (html.match(/<meta name="robots" content="([^"]+)"/) || [])[1];
++t('the Atom feed every page names is a valid, empty feed before anything is published', async () => {
++ const h = await boot();
++ try {
++ const atom = await req(h, 'GET', '/feed.atom');
++ assert.strictEqual(atom.status, 200, atom.text);
++ assert.match(atom.text, /<feed xmlns="http:\/\/www\.w3\.org\/2005\/Atom"/);
++ assert.match(atom.text, /<updated>\d{4}-\d{2}-\d{2}T[^<]+<\/updated>/, 'a real <updated>: the release commit time');
++ assert.ok(!/<entry>/.test(atom.text), 'no entries');
++ } finally { await h.stop(); }
++});
++
t('AggregateRating JSON-LD is absent without signals, present only while signals back it', async () => {
const h = await boot();
try {