openvibers/openvibe.live Commits
The Rundown's front page stops building the old river nobody reads: home() no longer queries the latest feed (its keys stay, empty, until the N-1 fixture is re-recorded) and the unused /api/rundown/latest route goes (#266)
Changes
@@@ -67,9 +67,8 @@ Speech-to-text hears a stream's whole audio track. On 2026-10-18 the Arena's "qu| Method | Path | Notes ||---|---|---|--| GET | `/` | `on_air`, `lead`, `streams` (+ `streams_next`), `trending`, `voices`, `counts`; `latest` (+ `next`) for the previous release's open tabs |++| GET | `/` | `on_air`, `lead`, `streams` (+ `streams_next`), `trending`, `voices`, `counts` || GET | `/streams?before=` | the next page of stream by stream |--| GET | `/latest?before=` | the previous release's feed (episodes and takes scored 4 and up, newest first) || GET | `/topics?sort=recent\|most&kind=` | every topic || GET | `/t/:slug` | `topic`, `takes`, `related` || GET | `/e/:streamId` | `episode`, `stream`, `segments`, `more` |@@@ -113,18 +113,6 @@ async function voices(days = 7) {return rows.map((r) => ({ user: ppl.get(Number(r.user_id)) || null, segments: Number(r.segments), topics: Number(r.topics), last_seen: r.last_seen, top_topics: byUser.get(Number(r.user_id)) || [] })).filter((v) => v.user);}--/** The latest: episodes and the best segments (score 3+, or naming someone), newest first, 30 a page. */--async function latest({ before = null, limit = 30, viewer = null } = {}) {-- const cut = before ? String(before).slice(0, 19) : '9999-12-31 00:00:00';-- const segs = await db.all(`SELECT ${SEGMENT_COLS} FROM rundown_segments WHERE status = 'live' AND said_at < ? AND (score >= ? OR cardinality(mentions) > 0)-- ORDER BY said_at DESC, id DESC LIMIT ?`, [cut, FEED_MIN_SCORE, limit]);-- const eps = await db.all(`SELECT ${EPISODE_COLS} FROM rundown_episodes WHERE status = 'live' AND aired_at < ? ORDER BY aired_at DESC LIMIT ?`, [cut, Math.ceil(limit / 3)]);-- const items = [...await segmentsOut(segs, viewer), ...await episodesOut(eps, viewer)]-- .sort((a, b) => String(b.said_at || b.aired_at).localeCompare(String(a.said_at || a.aired_at))).slice(0, limit);-- const last = items[items.length - 1];-- return { items, next: items.length >= limit && last ? (last.said_at || last.aired_at) : null };--}--/** The lead: the best take of the last day (else the week), a quoted one first. */async function lead({ viewer = null } = {}) {for (const days of [1, 7]) {@@@ -173,12 +161,12 @@ async function streamsFeed({ before = null, limit = STREAMS_PAGE, viewer = null,async function home({ viewer = null } = {}) {const top = await lead({ viewer });-- const [air, feed, topics, talk, old] = await Promise.all([onAir(), streamsFeed({ viewer, skip: top ? top.id : null }), trending(), voices(),-- // The previous release's front page reads latest and next; they go once no open tab runs it.-- latest({ viewer })]);++ const [air, feed, topics, talk] = await Promise.all([onAir(), streamsFeed({ viewer, skip: top ? top.id : null }), trending(), voices()]);const counts = await db.get(`SELECT (SELECT COUNT(*) FROM rundown_topics WHERE status = 'live') AS topics,(SELECT COUNT(*) FROM rundown_episodes WHERE status = 'live') AS episodes, (SELECT COUNT(*) FROM rundown_segments WHERE status = 'live') AS takes`);-- return { on_air: air, lead: top, streams: feed.streams, streams_next: feed.next, trending: topics, voices: talk, latest: old.items, next: old.next,++ return { on_air: air, lead: top, streams: feed.streams, streams_next: feed.next, trending: topics, voices: talk,++ // Empty, with no query behind them: the N-1 fixture's reply still has the old river's keys (gone once it is re-recorded).++ latest: [], next: null,counts: { topics: Number(counts.topics), episodes: Number(counts.episodes), takes: Number(counts.takes) } };}@@@ -271,4 +259,4 @@ async function setOptOut(userId, out) {return { opted_out: !!out };}--module.exports = { home, latest, lead, streamsFeed, topics, topic, episode, streamer, react, hide, optedOut, setOptOut, trending, voices, onAir, REACTIONS, KINDS, person };++module.exports = { home, lead, streamsFeed, topics, topic, episode, streamer, react, hide, optedOut, setOptOut, trending, voices, onAir, REACTIONS, KINDS, person };@@@ -42,9 +42,6 @@ router.get('/', optionalAuth, async (req, res) => {router.get('/streams', optionalAuth, async (req, res) => {try { fresh(res); res.json(await read.streamsFeed({ before: req.query.before ? String(req.query.before) : null, viewer: await voterKey(req) })); } catch (err) { fail(res, err, 'Failed to load the streams'); }});--router.get('/latest', optionalAuth, async (req, res) => {-- try { fresh(res); res.json(await read.latest({ before: req.query.before ? String(req.query.before) : null, viewer: await voterKey(req) })); } catch (err) { fail(res, err, 'Failed to load the latest'); }--});router.get('/topics', async (req, res) => {try {res.set('Cache-Control', 'public, max-age=60');@@@ -149,13 +149,11 @@ console.log('✅ desk: live streams wait for talk; topics gather streamers and g// ── Reads ──const home = await read.home({ viewer: 'anon:t' });--assert.ok(home.latest.some((x) => x.type === 'episode' && x.title === 'The Fridge Files'), 'the episode is in the latest');--assert.ok(home.latest.some((x) => x.type === 'segment' && x.title === 'French fridges get hacked'), 'a score-4 take is in the latest');++assert.ok([home.lead, ...home.streams.flatMap((x) => x.takes)].some((k) => k && k.title === 'French fridges get hacked'), 'a score-4 take is on the front page');assert.ok(!await db.get("SELECT 1 FROM rundown_segments WHERE title = 'Filler bit'"), 'a score-2 segment is filler: not kept');--assert.ok(!home.latest.some((x) => x.title === 'Ranked is pain'), 'a score-3 take without a name is not on the front page');assert.strictEqual(home.trending[0].slug, 'supermarket-fridges-hacked-in-france');assert.strictEqual(home.trending[0].streamers, 2);--assert.deepStrictEqual(Object.keys(home.latest[0].user).sort(), ['avatar_url', 'display_name', 'id', 'profile_color', 'username'], 'a person is five fields, never a users row');++assert.deepStrictEqual(Object.keys(home.streams[0].user).sort(), ['avatar_url', 'display_name', 'id', 'profile_color', 'username'], 'a person is five fields, never a users row');assert.ok(home.lead && home.lead.score >= 4, 'the lead is a take scored 4 or 5');const fridgeNight = home.streams.find((x) => x.stream_id === s1);assert.ok(fridgeNight && fridgeNight.episode && fridgeNight.episode.title === 'The Fridge Files', 'stream by stream: a stream carries its episode');@@@ -189,7 +187,6 @@ console.log('✅ reads: front page, topic, episode, streamer, reactions');await read.hide('segment', segs[1].id);await read.setOptOut(goose, true);assert.strictEqual((await read.streamer('goosely')).opted_out, true);--assert.ok(!(await read.home({})).latest.some((x) => x.user && x.user.username === 'goosely'), 'out: nothing about them on the front page');const outHome = await read.home({});assert.ok(!outHome.streams.some((x) => x.user.username === 'goosely') && !(outHome.lead && outHome.lead.user.username === 'goosely'), 'out: not in stream by stream or the lead');await read.setOptOut(goose, false);@@@ -212,7 +209,7 @@ await new Promise((res) => server.listen(0, '127.0.0.1', res));const base = `http://127.0.0.1:${server.address().port}/api/rundown`;try {const front = await (await fetch(`${base}/`)).json();-- assert.ok(Array.isArray(front.latest) && front.trending.length >= 1 && front.counts.episodes === 1);++ assert.ok(Array.isArray(front.latest) && !front.latest.length && front.trending.length >= 1 && front.counts.episodes === 1, 'the old river is empty');assert.ok(Array.isArray(front.streams) && front.streams.length >= 1, 'stream by stream');const page2 = await (await fetch(`${base}/streams?before=${encodeURIComponent(front.streams[0].last_at)}`)).json();assert.ok(Array.isArray(page2.streams) && 'next' in page2);
@@ -67,9 +67,8 @@ Speech-to-text hears a stream's whole audio track. On 2026-10-18 the Arena's "qu
@@ -67,9 +67,8 @@ Speech-to-text hears a stream's whole audio track. On 2026-10-18 the Arena's "qu
| Method | Path | Notes |
| Method | Path | Notes |
|---|---|---|
|---|---|---|
-| GET | `/` | `on_air`, `lead`, `streams` (+ `streams_next`), `trending`, `voices`, `counts`; `latest` (+ `next`) for the previous release's open tabs |
+| GET | `/` | `on_air`, `lead`, `streams` (+ `streams_next`), `trending`, `voices`, `counts` |
| GET | `/streams?before=` | the next page of stream by stream |
| GET | `/streams?before=` | the next page of stream by stream |
-| GET | `/latest?before=` | the previous release's feed (episodes and takes scored 4 and up, newest first) |
| GET | `/topics?sort=recent\|most&kind=` | every topic |
| GET | `/topics?sort=recent\|most&kind=` | every topic |
| GET | `/t/:slug` | `topic`, `takes`, `related` |
| GET | `/t/:slug` | `topic`, `takes`, `related` |
| GET | `/e/:streamId` | `episode`, `stream`, `segments`, `more` |
| GET | `/e/:streamId` | `episode`, `stream`, `segments`, `more` |
@@ -113,18 +113,6 @@ async function voices(days = 7) {
@@ -113,18 +113,6 @@ async function voices(days = 7) {
return rows.map((r) => ({ user: ppl.get(Number(r.user_id)) || null, segments: Number(r.segments), topics: Number(r.topics), last_seen: r.last_seen, top_topics: byUser.get(Number(r.user_id)) || [] })).filter((v) => v.user);
return rows.map((r) => ({ user: ppl.get(Number(r.user_id)) || null, segments: Number(r.segments), topics: Number(r.topics), last_seen: r.last_seen, top_topics: byUser.get(Number(r.user_id)) || [] })).filter((v) => v.user);
}
}
-/** The latest: episodes and the best segments (score 3+, or naming someone), newest first, 30 a page. */
-async function latest({ before = null, limit = 30, viewer = null } = {}) {
- const cut = before ? String(before).slice(0, 19) : '9999-12-31 00:00:00';
- const segs = await db.all(`SELECT ${SEGMENT_COLS} FROM rundown_segments WHERE status = 'live' AND said_at < ? AND (score >= ? OR cardinality(mentions) > 0)
- ORDER BY said_at DESC, id DESC LIMIT ?`, [cut, FEED_MIN_SCORE, limit]);
- const eps = await db.all(`SELECT ${EPISODE_COLS} FROM rundown_episodes WHERE status = 'live' AND aired_at < ? ORDER BY aired_at DESC LIMIT ?`, [cut, Math.ceil(limit / 3)]);
- const items = [...await segmentsOut(segs, viewer), ...await episodesOut(eps, viewer)]
- .sort((a, b) => String(b.said_at || b.aired_at).localeCompare(String(a.said_at || a.aired_at))).slice(0, limit);
- const last = items[items.length - 1];
- return { items, next: items.length >= limit && last ? (last.said_at || last.aired_at) : null };
-}
-
/** The lead: the best take of the last day (else the week), a quoted one first. */
/** The lead: the best take of the last day (else the week), a quoted one first. */
async function lead({ viewer = null } = {}) {
async function lead({ viewer = null } = {}) {
for (const days of [1, 7]) {
for (const days of [1, 7]) {
@@ -173,12 +161,12 @@ async function streamsFeed({ before = null, limit = STREAMS_PAGE, viewer = null,
@@ -173,12 +161,12 @@ async function streamsFeed({ before = null, limit = STREAMS_PAGE, viewer = null,
async function home({ viewer = null } = {}) {
async function home({ viewer = null } = {}) {
const top = await lead({ viewer });
const top = await lead({ viewer });
- const [air, feed, topics, talk, old] = await Promise.all([onAir(), streamsFeed({ viewer, skip: top ? top.id : null }), trending(), voices(),
- // The previous release's front page reads latest and next; they go once no open tab runs it.
- latest({ viewer })]);
+ const [air, feed, topics, talk] = await Promise.all([onAir(), streamsFeed({ viewer, skip: top ? top.id : null }), trending(), voices()]);
const counts = await db.get(`SELECT (SELECT COUNT(*) FROM rundown_topics WHERE status = 'live') AS topics,
const counts = await db.get(`SELECT (SELECT COUNT(*) FROM rundown_topics WHERE status = 'live') AS topics,
(SELECT COUNT(*) FROM rundown_episodes WHERE status = 'live') AS episodes, (SELECT COUNT(*) FROM rundown_segments WHERE status = 'live') AS takes`);
(SELECT COUNT(*) FROM rundown_episodes WHERE status = 'live') AS episodes, (SELECT COUNT(*) FROM rundown_segments WHERE status = 'live') AS takes`);
- return { on_air: air, lead: top, streams: feed.streams, streams_next: feed.next, trending: topics, voices: talk, latest: old.items, next: old.next,
+ return { on_air: air, lead: top, streams: feed.streams, streams_next: feed.next, trending: topics, voices: talk,
+ // Empty, with no query behind them: the N-1 fixture's reply still has the old river's keys (gone once it is re-recorded).
+ latest: [], next: null,
counts: { topics: Number(counts.topics), episodes: Number(counts.episodes), takes: Number(counts.takes) } };
counts: { topics: Number(counts.topics), episodes: Number(counts.episodes), takes: Number(counts.takes) } };
}
}
@@ -271,4 +259,4 @@ async function setOptOut(userId, out) {
@@ -271,4 +259,4 @@ async function setOptOut(userId, out) {
return { opted_out: !!out };
return { opted_out: !!out };
}
}
-module.exports = { home, latest, lead, streamsFeed, topics, topic, episode, streamer, react, hide, optedOut, setOptOut, trending, voices, onAir, REACTIONS, KINDS, person };
+module.exports = { home, lead, streamsFeed, topics, topic, episode, streamer, react, hide, optedOut, setOptOut, trending, voices, onAir, REACTIONS, KINDS, person };
@@ -42,9 +42,6 @@ router.get('/', optionalAuth, async (req, res) => {
@@ -42,9 +42,6 @@ router.get('/', optionalAuth, async (req, res) => {
router.get('/streams', optionalAuth, async (req, res) => {
router.get('/streams', optionalAuth, async (req, res) => {
try { fresh(res); res.json(await read.streamsFeed({ before: req.query.before ? String(req.query.before) : null, viewer: await voterKey(req) })); } catch (err) { fail(res, err, 'Failed to load the streams'); }
try { fresh(res); res.json(await read.streamsFeed({ before: req.query.before ? String(req.query.before) : null, viewer: await voterKey(req) })); } catch (err) { fail(res, err, 'Failed to load the streams'); }
});
});
-router.get('/latest', optionalAuth, async (req, res) => {
- try { fresh(res); res.json(await read.latest({ before: req.query.before ? String(req.query.before) : null, viewer: await voterKey(req) })); } catch (err) { fail(res, err, 'Failed to load the latest'); }
-});
router.get('/topics', async (req, res) => {
router.get('/topics', async (req, res) => {
try {
try {
res.set('Cache-Control', 'public, max-age=60');
res.set('Cache-Control', 'public, max-age=60');
@@ -149,13 +149,11 @@ console.log('✅ desk: live streams wait for talk; topics gather streamers and g
@@ -149,13 +149,11 @@ console.log('✅ desk: live streams wait for talk; topics gather streamers and g
// ── Reads ──
// ── Reads ──
const home = await read.home({ viewer: 'anon:t' });
const home = await read.home({ viewer: 'anon:t' });
-assert.ok(home.latest.some((x) => x.type === 'episode' && x.title === 'The Fridge Files'), 'the episode is in the latest');
-assert.ok(home.latest.some((x) => x.type === 'segment' && x.title === 'French fridges get hacked'), 'a score-4 take is in the latest');
+assert.ok([home.lead, ...home.streams.flatMap((x) => x.takes)].some((k) => k && k.title === 'French fridges get hacked'), 'a score-4 take is on the front page');
assert.ok(!await db.get("SELECT 1 FROM rundown_segments WHERE title = 'Filler bit'"), 'a score-2 segment is filler: not kept');
assert.ok(!await db.get("SELECT 1 FROM rundown_segments WHERE title = 'Filler bit'"), 'a score-2 segment is filler: not kept');
-assert.ok(!home.latest.some((x) => x.title === 'Ranked is pain'), 'a score-3 take without a name is not on the front page');
assert.strictEqual(home.trending[0].slug, 'supermarket-fridges-hacked-in-france');
assert.strictEqual(home.trending[0].slug, 'supermarket-fridges-hacked-in-france');
assert.strictEqual(home.trending[0].streamers, 2);
assert.strictEqual(home.trending[0].streamers, 2);
-assert.deepStrictEqual(Object.keys(home.latest[0].user).sort(), ['avatar_url', 'display_name', 'id', 'profile_color', 'username'], 'a person is five fields, never a users row');
+assert.deepStrictEqual(Object.keys(home.streams[0].user).sort(), ['avatar_url', 'display_name', 'id', 'profile_color', 'username'], 'a person is five fields, never a users row');
assert.ok(home.lead && home.lead.score >= 4, 'the lead is a take scored 4 or 5');
assert.ok(home.lead && home.lead.score >= 4, 'the lead is a take scored 4 or 5');
const fridgeNight = home.streams.find((x) => x.stream_id === s1);
const fridgeNight = home.streams.find((x) => x.stream_id === s1);
assert.ok(fridgeNight && fridgeNight.episode && fridgeNight.episode.title === 'The Fridge Files', 'stream by stream: a stream carries its episode');
assert.ok(fridgeNight && fridgeNight.episode && fridgeNight.episode.title === 'The Fridge Files', 'stream by stream: a stream carries its episode');
@@ -189,7 +187,6 @@ console.log('✅ reads: front page, topic, episode, streamer, reactions');
@@ -189,7 +187,6 @@ console.log('✅ reads: front page, topic, episode, streamer, reactions');
await read.hide('segment', segs[1].id);
await read.hide('segment', segs[1].id);
await read.setOptOut(goose, true);
await read.setOptOut(goose, true);
assert.strictEqual((await read.streamer('goosely')).opted_out, true);
assert.strictEqual((await read.streamer('goosely')).opted_out, true);
-assert.ok(!(await read.home({})).latest.some((x) => x.user && x.user.username === 'goosely'), 'out: nothing about them on the front page');
const outHome = await read.home({});
const outHome = await read.home({});
assert.ok(!outHome.streams.some((x) => x.user.username === 'goosely') && !(outHome.lead && outHome.lead.user.username === 'goosely'), 'out: not in stream by stream or the lead');
assert.ok(!outHome.streams.some((x) => x.user.username === 'goosely') && !(outHome.lead && outHome.lead.user.username === 'goosely'), 'out: not in stream by stream or the lead');
await read.setOptOut(goose, false);
await read.setOptOut(goose, false);
@@ -212,7 +209,7 @@ await new Promise((res) => server.listen(0, '127.0.0.1', res));
@@ -212,7 +209,7 @@ await new Promise((res) => server.listen(0, '127.0.0.1', res));
const base = `http://127.0.0.1:${server.address().port}/api/rundown`;
const base = `http://127.0.0.1:${server.address().port}/api/rundown`;
try {
try {
const front = await (await fetch(`${base}/`)).json();
const front = await (await fetch(`${base}/`)).json();
- assert.ok(Array.isArray(front.latest) && front.trending.length >= 1 && front.counts.episodes === 1);
+ assert.ok(Array.isArray(front.latest) && !front.latest.length && front.trending.length >= 1 && front.counts.episodes === 1, 'the old river is empty');
assert.ok(Array.isArray(front.streams) && front.streams.length >= 1, 'stream by stream');
assert.ok(Array.isArray(front.streams) && front.streams.length >= 1, 'stream by stream');
const page2 = await (await fetch(`${base}/streams?before=${encodeURIComponent(front.streams[0].last_at)}`)).json();
const page2 = await (await fetch(`${base}/streams?before=${encodeURIComponent(front.streams[0].last_at)}`)).json();
assert.ok(Array.isArray(page2.streams) && 'next' in page2);
assert.ok(Array.isArray(page2.streams) && 'next' in page2);