openvibers/openvibe.news Commits
News home: with no editor's story, the AI briefs are the latest stories (no "no stories have been published yet") (#70)
Changes
@@@ -45,18 +45,21 @@ ${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 || 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))}++${items.length || !showcase ? h`<section id="stories" aria-label="Stories">${storyList(items, 'No stories on this page.')}</section>++${raw(ssr.paginationHtml(pager))}` : ''}++${raw(briefsSection(briefs, { lead: showcase && !items.length }))}++${showcase && !items.length && !briefs.length && !wire ? h`<p class="empty">The first reports are on their way: the AI briefs and the wire fill in as the sources report.</p>` : ''}${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) {++// With no editor's story yet (`lead`), the briefs are the front page's latest stories, under its "Latest stories"++// heading: still labelled AI, and an editor's story takes the top when one is published.++function briefsSection(briefs, { lead = false } = {}) {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>++ return String(h`<section class="home-briefs" aria-labelledby="briefs-h">${lead ? '' : h`<h2 id="briefs-h">AI briefs</h2>`}++<p class="meta"${lead ? raw(' id="briefs-h"') : ''}>What the reports on each subject say, summarized by OpenVibe.AI with every paragraph linked to its reports. Written by AI, not by an editor${lead ? '; stories by editors appear here first when they are published' : ''}.</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>`);@@@ -46,8 +46,10 @@ const SAME = { 'sec-fetch-site': 'same-origin' };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'));++ // With no editor's story, the briefs are the front page's latest stories: under "Latest stories", labelled AI.++ assert.ok(home.text.includes('<h2>Latest stories</h2>') && home.text.includes(`href="/briefs/${clusterId}"`), 'the front page leads with the latest briefs');++ assert.ok(home.text.includes('Written by AI, not by an editor; stories by editors appear here first') && home.text.includes('<span class="badge">AI</span>'));++ assert.ok(!home.text.includes('No stories have been published yet'));});await check('a reader asks for another tone with a POST from this site; a GET never runs AI', async () => {@@@ -16,7 +16,7 @@ const { boot, check, done, launchReports } = require('./helpers/boot');assert.ok(await count('news_topics') >= 10);for (const table of ['news_stories', 'news_source_items', 'news_story_clusters', 'news_story_revisions', 'news_editorial_flags']) assert.strictEqual(await count(table), 0, table);const home = await t.get('/');-- assert.match(home.text, /No stories have been published yet/);++ assert.match(home.text, /The first reports are on their way/, 'an empty front page says what comes, not that nothing was published');});let reports;@@@ -29,9 +29,9 @@ const { boot, check, done, launchReports } = require('./helpers/boot');assert.match(r.headers.get('cache-control') || '', /public/);});-- await check('the front page keeps "no stories yet", and shows the latest clusters from the wire', async () => {++ await check('the front page shows the latest clusters from the wire, and no "no stories yet" apology', async () => {const r = await t.get('/');-- assert.ok(r.text.includes('No stories have been published yet'));++ assert.ok(!r.text.includes('No stories have been published yet'));assert.ok(r.text.includes('From the wire') && r.text.includes('href="/wire"'));assert.ok(r.text.includes('Europa Clipper'));assert.ok(!/noindex/.test(r.headers.get('x-robots-tag') || ''), 'with the wire, the front page is not empty: it is indexed');
@@ -45,18 +45,21 @@ ${raw(feedLinksHtml(feeds))}
@@ -45,18 +45,21 @@ ${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>
<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))}
${raw(feedLinksHtml(feeds))}
</header>`}
</header>`}
-<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))}
+${items.length || !showcase ? h`<section id="stories" aria-label="Stories">${storyList(items, 'No stories on this page.')}</section>
+${raw(ssr.paginationHtml(pager))}` : ''}
+${raw(briefsSection(briefs, { lead: showcase && !items.length }))}
+${showcase && !items.length && !briefs.length && !wire ? h`<p class="empty">The first reports are on their way: the AI briefs and the wire fill in as the sources report.</p>` : ''}
${raw(wire)}
${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>` : ''}`);
${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. */
/** The front page's latest AI briefs (server/domain/briefs.js), labelled as AI, each opening on its first paragraph. */
-function briefsSection(briefs) {
+// With no editor's story yet (`lead`), the briefs are the front page's latest stories, under its "Latest stories"
+// heading: still labelled AI, and an editor's story takes the top when one is published.
+function briefsSection(briefs, { lead = false } = {}) {
if (!briefs.length) return '';
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>
+ return String(h`<section class="home-briefs" aria-labelledby="briefs-h">${lead ? '' : h`<h2 id="briefs-h">AI briefs</h2>`}
+<p class="meta"${lead ? raw(' id="briefs-h"') : ''}>What the reports on each subject say, summarized by OpenVibe.AI with every paragraph linked to its reports. Written by AI, not by an editor${lead ? '; stories by editors appear here first when they are published' : ''}.</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>
<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 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>`);
<p><a href="/briefs">All AI briefs</a></p></section>`);
@@ -46,8 +46,10 @@ const SAME = { 'sec-fetch-site': 'same-origin' };
@@ -46,8 +46,10 @@ const SAME = { 'sec-fetch-site': 'same-origin' };
const list = await t.get('/briefs');
const list = await t.get('/briefs');
assert.ok(list.text.includes(`/briefs/${clusterId}`));
assert.ok(list.text.includes(`/briefs/${clusterId}`));
const home = await t.get('/');
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'));
+ // With no editor's story, the briefs are the front page's latest stories: under "Latest stories", labelled AI.
+ assert.ok(home.text.includes('<h2>Latest stories</h2>') && home.text.includes(`href="/briefs/${clusterId}"`), 'the front page leads with the latest briefs');
+ assert.ok(home.text.includes('Written by AI, not by an editor; stories by editors appear here first') && home.text.includes('<span class="badge">AI</span>'));
+ assert.ok(!home.text.includes('No stories have been published yet'));
});
});
await check('a reader asks for another tone with a POST from this site; a GET never runs AI', async () => {
await check('a reader asks for another tone with a POST from this site; a GET never runs AI', async () => {
@@ -16,7 +16,7 @@ const { boot, check, done, launchReports } = require('./helpers/boot');
@@ -16,7 +16,7 @@ const { boot, check, done, launchReports } = require('./helpers/boot');
assert.ok(await count('news_topics') >= 10);
assert.ok(await count('news_topics') >= 10);
for (const table of ['news_stories', 'news_source_items', 'news_story_clusters', 'news_story_revisions', 'news_editorial_flags']) assert.strictEqual(await count(table), 0, table);
for (const table of ['news_stories', 'news_source_items', 'news_story_clusters', 'news_story_revisions', 'news_editorial_flags']) assert.strictEqual(await count(table), 0, table);
const home = await t.get('/');
const home = await t.get('/');
- assert.match(home.text, /No stories have been published yet/);
+ assert.match(home.text, /The first reports are on their way/, 'an empty front page says what comes, not that nothing was published');
});
});
let reports;
let reports;
@@ -29,9 +29,9 @@ const { boot, check, done, launchReports } = require('./helpers/boot');
@@ -29,9 +29,9 @@ const { boot, check, done, launchReports } = require('./helpers/boot');
assert.match(r.headers.get('cache-control') || '', /public/);
assert.match(r.headers.get('cache-control') || '', /public/);
});
});
- await check('the front page keeps "no stories yet", and shows the latest clusters from the wire', async () => {
+ await check('the front page shows the latest clusters from the wire, and no "no stories yet" apology', async () => {
const r = await t.get('/');
const r = await t.get('/');
- assert.ok(r.text.includes('No stories have been published yet'));
+ assert.ok(!r.text.includes('No stories have been published yet'));
assert.ok(r.text.includes('From the wire') && r.text.includes('href="/wire"'));
assert.ok(r.text.includes('From the wire') && r.text.includes('href="/wire"'));
assert.ok(r.text.includes('Europa Clipper'));
assert.ok(r.text.includes('Europa Clipper'));
assert.ok(!/noindex/.test(r.headers.get('x-robots-tag') || ''), 'with the wire, the front page is not empty: it is indexed');
assert.ok(!/noindex/.test(r.headers.get('x-robots-tag') || ''), 'with the wire, the front page is not empty: it is indexed');