openvibers/openvibe.news Commits

News: the front page shows the latest AI briefs (labelled AI) above the wire (#69)

OP OpenVibers committed 29 days ago · 5a9a0f98e8 · 3 files, +17 −3

Changes

ddiff --git a/server/http/public.js b/server/http/public.js
iindex 3716a5e..ab81525 100644
---- a/server/http/public.js
++++ b/server/http/public.js
@@@ -116,7 +116,7 @@ function createPublicRoutes(ctx) {
// The front page opens with what OpenVibe.News is for (openvibe-shared/showcase), then the stories.
...(!topic && pager.page === 1 ? { styles: [showcase.STYLESHEET] } : {}),
body: topic ? pages.topicPage({ topic, items, pager, feeds, breadcrumbs: crumbs })
-- : (pager.page === 1 ? newsShowcase() : '') + pages.home({ items, pager, topics: await topics.list(), feeds, showcase: pager.page === 1, wire: pages.wireSection(wire) }),
++ : (pager.page === 1 ? newsShowcase() : '') + pages.home({ items, pager, topics: await topics.list(), feeds, showcase: pager.page === 1, wire: pages.wireSection(wire), briefs: pager.page === 1 && briefs ? (await briefs.latest(12)).filter((b) => b.kind === 'summary' && !b.stub && b.paragraphs.length).slice(0, 4) : [] }),
}, { cacheable: true });
}
ddiff --git a/server/render/pages.js b/server/render/pages.js
iindex 4bf1b16..3edf504 100644
---- a/server/render/pages.js
++++ b/server/render/pages.js
@@@ -37,7 +37,7 @@ function feedLinksHtml(feeds) {
// With the showcase above it (page 1), the showcase hero is the page's h1 and says what the site is,
// so the list header is a plain "Latest stories" h2; later pages keep the site's own h1 and lede.
--function home({ items, pager, topics, feeds, showcase = false, wire = '' }) {
++function home({ items, pager, topics, feeds, showcase = false, wire = '', briefs = [] }) {
return String(h`${showcase ? h`<header class="site-header"><h2>Latest stories</h2>
${raw(feedLinksHtml(feeds))}
</header>` : h`<header class="site-header">
@@@ -45,12 +45,23 @@ ${raw(feedLinksHtml(feeds))}
<p class="lede">Source-backed stories. Every paragraph cites the reports it rests on; corrections and retractions stay on the record.</p>
${raw(feedLinksHtml(feeds))}
</header>`}
--<section id="stories" aria-label="Stories">${storyList(items, wire ? 'No stories have been published yet: editors write them from the sources, and every paragraph cites them. Until then, the wire below shows what the sources report.' : 'No stories have been published yet. OpenVibe.News publishes only what an editor has written from its sources.')}</section>
++<section id="stories" aria-label="Stories">${storyList(items, wire || briefs.length ? `No stories have been published yet: editors write them from the sources, and every paragraph cites them. Until then, ${briefs.length ? 'the AI briefs and the wire below show' : 'the wire below shows'} what the sources report.` : 'No stories have been published yet. OpenVibe.News publishes only what an editor has written from its sources.')}</section>
${raw(ssr.paginationHtml(pager))}
++${raw(briefsSection(briefs))}
${raw(wire)}
${topics.length ? h`<section class="side" aria-label="Topics"><h2>Topics</h2><ul class="topic-list">${topics.map((t) => h`<li><a href="/topics/${t.slug}">${t.name}</a></li>`)}</ul></section>` : ''}`);
}
++/** The front page's latest AI briefs (server/domain/briefs.js), labelled as AI, each opening on its first paragraph. */
++function briefsSection(briefs) {
++ if (!briefs.length) return '';
++ return String(h`<section class="home-briefs" aria-labelledby="briefs-h"><h2 id="briefs-h">AI briefs</h2>
++<p class="meta">What the reports on a subject say, summarized by OpenVibe.AI with every paragraph linked to its reports. Written by AI, not by an editor.</p>
++<ol class="story-list">${briefs.map((b) => h`<li class="story-item"><h3 class="story-item-title"><a href="/briefs/${b.cluster_id}">${b.headline || b.label}</a> <span class="badge">AI</span></h3>
++<p class="meta">${time(b.window_end)} · from ${b.item_count} reports</p>${b.paragraphs[0] ? h`<p class="summary">${b.paragraphs[0].text.length > 240 ? `${b.paragraphs[0].text.slice(0, 237)}…` : b.paragraphs[0].text}</p>` : ''}</li>`)}</ol>
++<p><a href="/briefs">All AI briefs</a></p></section>`);
++}
++
// The wire: what the sources report, grouped, before (or without) an editor's story. Each line is the outlet's own
// headline and link; nothing here is written by News.
const WIRE_NOTE = 'Grouped automatically from the reports OpenVibe.Sources collects. Nothing here is written by OpenVibe.News: each line is the outlet\'s own headline and links to its report.';
ddiff --git a/test/briefs.test.js b/test/briefs.test.js
iindex ec0f9e4..df7f0a6 100644
---- a/test/briefs.test.js
++++ b/test/briefs.test.js
@@@ -45,6 +45,9 @@ const SAME = { 'sec-fetch-site': 'same-origin' };
assert.ok(!r.text.includes('A claim nobody reported') || r.text.includes('What the AI left out'));
const list = await t.get('/briefs');
assert.ok(list.text.includes(`/briefs/${clusterId}`));
++ const home = await t.get('/');
++ assert.ok(home.text.includes('<h2 id="briefs-h">AI briefs</h2>') && home.text.includes(`href="/briefs/${clusterId}"`), 'the front page shows the latest briefs');
++ assert.ok(home.text.includes('Written by AI, not by an editor.') && home.text.includes('the AI briefs and the wire below show'));
});
await check('a reader asks for another tone with a POST from this site; a GET never runs AI', async () => {