openvibers/openvibe.news Commits

News: empty briefs and live lists draw the kit's empty state (#268)

OP OpenVibers committed 3 days ago · a941fe771a · 3 files, +12 −4

The briefs index and the live page said 'No briefs yet' and 'Nothing new
from the sources yet' in a bare line with no way forward. Both now use the
shared kit's empty state and its button ('See the newest reports', 'Read
the wire'), and both pages load kit.css. test/briefs.test.js checks the
empty briefs page and its button.

Changes

ddiff --git a/server/http/public.js b/server/http/public.js
iindex abf87bb..9177c9f 100644
---- a/server/http/public.js
++++ b/server/http/public.js
@@@ -172,7 +172,7 @@ function createPublicRoutes(ctx) {
const canMake = () => Boolean(briefs && config.briefs.enabled && ctx.ai && ctx.ai.enabled);
router.get('/briefs', wrap(async (req, res) => {
-- send(req, res, 200, { title: 'AI briefs', description: 'What the reports on a subject say, summarized by AI with every paragraph linked to its reports.', decision: noindex('/briefs'), canonical: `${config.baseUrl}/briefs`, localStyles: ['css/briefs.css'], body: briefPages.briefsIndex({ briefs: briefs ? await briefs.latest(40) : [] }) }, { cacheable: true });
++ send(req, res, 200, { title: 'AI briefs', description: 'What the reports on a subject say, summarized by AI with every paragraph linked to its reports.', decision: noindex('/briefs'), canonical: `${config.baseUrl}/briefs`, localStyles: ['css/briefs.css'], styles: ['kit.css'], body: briefPages.briefsIndex({ briefs: briefs ? await briefs.latest(40) : [] }) }, { cacheable: true });
}));
router.get('/briefs/:id', wrap(async (req, res) => {
@@@ -227,7 +227,7 @@ function createPublicRoutes(ctx) {
const breaking = new Set(list.filter((c) => c.items.filter((i) => i.published_at && now - Date.parse(i.published_at) <= 2 * 3_600_000).length >= 3).map((c) => c.id));
const briefIds = new Set(briefs ? (await briefs.latest(200)).map((b) => b.cluster_id) : []);
res.set('Refresh', '60');
-- send(req, res, 200, { title: 'Live', description: 'The newest reports from the sources OpenVibe.News follows, as they arrive, with what is breaking.', decision: noindex('/live'), canonical: `${config.baseUrl}/live`, localStyles: ['css/briefs.css'], body: briefPages.livePage({ clusters: list, briefIds, breaking }) }, { cacheable: true });
++ send(req, res, 200, { title: 'Live', description: 'The newest reports from the sources OpenVibe.News follows, as they arrive, with what is breaking.', decision: noindex('/live'), canonical: `${config.baseUrl}/live`, localStyles: ['css/briefs.css'], styles: ['kit.css'], body: briefPages.livePage({ clusters: list, briefIds, breaking }) }, { cacheable: true });
}));
router.get('/topics', wrap(async (req, res) => {
ddiff --git a/server/render/briefs.js b/server/render/briefs.js
iindex b56f724..f5e3a22 100644
---- a/server/render/briefs.js
++++ b/server/render/briefs.js
@@@ -6,6 +6,7 @@
* reports' own headlines and links are the outlets'.
*/
const ssr = require('openvibe-publishing/ssr');
++const kit = require('openvibe-shared/kit');
const { time, httpHref } = require('./pages');
const { html: h, raw } = ssr;
@@@ -89,7 +90,7 @@ function briefsIndex({ briefs }) {
return String(h`<header class="site-header"><h1>AI briefs</h1><p class="lede">What the reports on a subject say, summarized by OpenVibe.AI with every paragraph linked to its reports, and labelled as AI. Editors' stories are on <a href="/">the front page</a>.</p></header>
${briefs.length ? h`<ol class="story-list">${briefs.map((b) => h`<li class="story-item"><h2 class="story-item-title"><a href="/briefs/${b.cluster_id}">${b.headline || b.label}</a> <span class="badge badge-ai">AI</span></h2>
<p class="meta">${time(b.window_end)} · from ${b.item_count} reports${b.outlets ? ` by ${b.outlets} outlets` : ''}</p>${b.paragraphs[0] ? h`<p class="summary">${b.paragraphs[0].text.slice(0, 280)}${b.paragraphs[0].text.length > 280 ? '…' : ''}</p>` : ''}</li>`)}</ol>`
-- : h`<p class="empty">No briefs yet. One is written once two or more independent outlets have reported a subject.</p>`}`);
++ : raw(kit.empty({ icon: 'book', title: 'No briefs yet', text: 'One is written once two or more independent outlets have reported a subject.', action: { href: '/live', label: 'See the newest reports', icon: 'live' } }))}`);
}
/** /live: the newest reports as they arrive, grouped, with what is breaking. */
@@@ -98,7 +99,7 @@ function livePage({ clusters, briefIds, breaking }) {
${clusters.length ? h`<ol class="wire-list wide live-list">${clusters.map((c) => h`<li class="wire-cluster${breaking.has(c.id) ? ' is-breaking' : ''}">
<p class="wire-lead">${breaking.has(c.id) ? h`<span class="badge badge-breaking">Breaking</span> ` : ''}<strong>${c.label}</strong>${briefIds.has(c.id) ? h` · <a href="/briefs/${c.id}">AI brief</a>` : ''}</p>
<ul class="wire-items">${c.items.slice(0, 5).map((i) => h`<li>${httpHref(i.url) ? h`<a href="${httpHref(i.url)}" rel="nofollow noopener" target="_blank">${i.headline}</a>` : i.headline} <span class="meta">${i.outlet} · ${time(i.published_at ? Date.parse(i.published_at) : null)}</span></li>`)}</ul></li>`)}</ol>`
-- : h`<p class="empty">Nothing new from the sources yet.</p>`}`);
++ : raw(kit.empty({ icon: 'live', title: 'Nothing new from the sources yet', text: 'New reports appear here as the sources publish them.', action: { href: '/wire', label: 'Read the wire', icon: 'list' } }))}`);
}
module.exports = { briefPage, briefsIndex, livePage, AI_NOTE, TONES };
ddiff --git a/test/briefs.test.js b/test/briefs.test.js
iindex a58846a..93ee539 100644
---- a/test/briefs.test.js
++++ b/test/briefs.test.js
@@@ -18,6 +18,13 @@ const SAME = { 'sec-fetch-site': 'same-origin' };
try {
launchReports(t.sources);
await t.pull();
++ await check('an empty briefs list draws the kit empty state and one button', async () => {
++ const r = await t.get('/briefs');
++ assert.strictEqual(r.status, 200);
++ assert.match(r.text, /class="ovk-empty"/, 'the kit empty state is used, not a bare sentence');
++ assert.match(r.text, /No briefs yet/);
++ assert.match(r.text, /class="ovk-btn[^"]*" href="\/live"/, 'the empty state offers one button forward');
++ });
const clusterId = (await t.db().prepare("SELECT cluster_id, count(*) AS n FROM news_source_items WHERE status = 'active' GROUP BY cluster_id ORDER BY n DESC").get()).cluster_id;
await check('the sweep writes a cited summary for a cluster with two or more reports; uncited claims are dropped', async () => {