openvibers/openvibe.network Commits
Forward changelog kind filters (#789)
Changes
@@@ -54,6 +54,7 @@ function createUpdatesRoutes({ blogUrl = 'http://127.0.0.1:4810', release = nullconst out = new URLSearchParams();const service = serviceOf(q);if (service) out.set('service', service);++ if (['new', 'better', 'fixed'].includes(String(q.kind || ''))) out.set('kind', String(q.kind));const n = parseInt(q.limit, 10);out.set('limit', String(Number.isFinite(n) ? Math.min(Math.max(n, 1), 100) : 20));if (q.before && CURSOR_RE.test(String(q.before))) out.set('before', String(q.before));@@@ -149,7 +150,7 @@ function renderPage({ site, filtered, body, releaseId = '' }) {}// One commit; the OpenVibers account every commit shares is not named on each row.const hhmm = (e) => String(e.deployed_at || '').slice(11, 16);-- const row = (e, showSite) => `<div class="ov-shipped-entry">${/^https:\/\//.test(e.url || '') ? `<a class="ov-shipped-hash" href="${esc(e.url)}" rel="noopener">${esc(e.short || String(e.sha || '').slice(0, 7))}</a>` : ''}${showSite ? `<span class="ov-shipped-site">${esc(nameOf(e.service))}</span>` : ''}<span class="ov-shipped-text">${esc(e.subject)}</span><span class="ov-shipped-meta">${esc(e.author && e.author !== 'OpenVibers' ? `${e.author} · ` : '')}${esc(hhmm(e))} UTC</span></div>`;++ const row = (e, showSite) => { const repo = String(e.repo || '').split('/').pop(); const sha = String(e.sha || ''); const commit = repo && /^[A-Za-z0-9_.-]+$/.test(repo) && /^[0-9a-f]{7,40}$/i.test(sha) ? `https://git.openvibe.codes/OpenVibers/${repo}/commit/${sha}` : e.url; return `<div class="ov-shipped-entry">${/^https:\/\//.test(commit || '') ? `<a class="ov-shipped-hash" href="${esc(commit)}" rel="noopener">${esc(e.short || sha.slice(0, 7))}</a>` : ''}${showSite ? `<span class="ov-shipped-site">${esc(nameOf(e.service))}</span>` : ''}<span class="ov-shipped-text">${esc(e.subject)}</span><span class="ov-shipped-meta">${esc(e.author && e.author !== 'OpenVibers' ? `${e.author} · ` : '')}${esc(hhmm(e))} UTC</span></div>`; };// The same change on many sites is one row (a pin round was thirty identical lines), grouped exactly as// openvibe-shared/shipped.js groups the page once its script runs.const groupHtml = (g) => {@@@ -203,7 +204,7 @@ a{color:var(--accent)}${require('openvibe-shared/frame').noscriptNav({ name: 'OpenVibe.Network', links: [{ label: 'Updates', href: '/updates' }, { label: 'Status', href: '/status' }] })}<main class="up" id="main"><h1>${esc(title)}</h1>--<p class="lede">Every change deployed to OpenVibe, newest first. Each line is a commit from the OpenVibers repositories, linked to the change itself. When enough have gathered, or a large feature lands, they are written up as <a href="https://openvibe.blog/@openvibe">Patch notes on openvibe.blog</a>. JSON: <a href="/api/v1/changelog"><code>/api/v1/changelog</code></a>.</p>++<p class="lede">Every change deployed to OpenVibe, newest first. Each line is a commit from the <a href="https://git.openvibe.codes/OpenVibers">OpenVibers repositories</a>, linked to the change itself. When enough have gathered, or a large feature lands, they are written up as <a href="https://openvibe.blog/@openvibe">Patch notes on openvibe.blog</a>. JSON: <a href="/api/v1/changelog"><code>/api/v1/changelog</code></a>.</p><div data-ov-shipped="log" data-service="${esc(site || '')}" data-limit="50">${chips}${postsHtml}@@@ -35,12 +35,14 @@ const publicCors = require('../server/public-cors');assert.strictEqual(calls[0], 'http://blog.test/api/v1/changelog?service=wiki&limit=100', 'limit clamped, a bad cursor dropped');assert.strictEqual(calls[1], 'http://blog.test/api/v1/devblogs?service=wiki&limit=1', 'the lead is the newest devblog for the requested service');let body = await r.json();++ await fetch(`${base}/api/v1/changelog?service=wiki&kind=new&limit=5&before=abc123`);++ assert.strictEqual(calls[2], 'http://blog.test/api/v1/changelog?service=wiki&kind=new&limit=5&before=abc123', 'kind and cursor pass through to Blog');assert.deepStrictEqual(body.latest_devblog, {title: 'What shipped on OpenVibe.Wiki', summary: 'A week of pages.',figure: 'https://openvibe.media/o/m1', url: 'https://openvibe.blog/@openvibe/what-shipped-wiki', published_at: '2026-09-24T06:00:00.000Z',}, 'the devblog as the card lead, its art as an https URL');await fetch(`${base}/api/v1/changelog?service=wiki&limit=500`);-- assert.strictEqual(calls.length, 2, 'changelog and devblog cached for 60 s');++ assert.strictEqual(calls.length, 3, 'all, New, and devblog requests are cached for 60 s');t += 61 * 1000;devblogs = { total: 1, posts: [{ id: 'p2', title: 'T'.repeat(260), summary: 'S'.repeat(500), figure: 'http://insecure.test/a.png', url: 'https://openvibe.blog/@openvibe/second', published_at: '2026-09-25T06:00:00.000Z' }] };body = await (await fetch(`${base}/api/v1/changelog?service=wiki`)).json();
@@ -54,6 +54,7 @@ function createUpdatesRoutes({ blogUrl = 'http://127.0.0.1:4810', release = null
@@ -54,6 +54,7 @@ function createUpdatesRoutes({ blogUrl = 'http://127.0.0.1:4810', release = null
const out = new URLSearchParams();
const out = new URLSearchParams();
const service = serviceOf(q);
const service = serviceOf(q);
if (service) out.set('service', service);
if (service) out.set('service', service);
+ if (['new', 'better', 'fixed'].includes(String(q.kind || ''))) out.set('kind', String(q.kind));
const n = parseInt(q.limit, 10);
const n = parseInt(q.limit, 10);
out.set('limit', String(Number.isFinite(n) ? Math.min(Math.max(n, 1), 100) : 20));
out.set('limit', String(Number.isFinite(n) ? Math.min(Math.max(n, 1), 100) : 20));
if (q.before && CURSOR_RE.test(String(q.before))) out.set('before', String(q.before));
if (q.before && CURSOR_RE.test(String(q.before))) out.set('before', String(q.before));
@@ -149,7 +150,7 @@ function renderPage({ site, filtered, body, releaseId = '' }) {
@@ -149,7 +150,7 @@ function renderPage({ site, filtered, body, releaseId = '' }) {
}
}
// One commit; the OpenVibers account every commit shares is not named on each row.
// One commit; the OpenVibers account every commit shares is not named on each row.
const hhmm = (e) => String(e.deployed_at || '').slice(11, 16);
const hhmm = (e) => String(e.deployed_at || '').slice(11, 16);
- const row = (e, showSite) => `<div class="ov-shipped-entry">${/^https:\/\//.test(e.url || '') ? `<a class="ov-shipped-hash" href="${esc(e.url)}" rel="noopener">${esc(e.short || String(e.sha || '').slice(0, 7))}</a>` : ''}${showSite ? `<span class="ov-shipped-site">${esc(nameOf(e.service))}</span>` : ''}<span class="ov-shipped-text">${esc(e.subject)}</span><span class="ov-shipped-meta">${esc(e.author && e.author !== 'OpenVibers' ? `${e.author} · ` : '')}${esc(hhmm(e))} UTC</span></div>`;
+ const row = (e, showSite) => { const repo = String(e.repo || '').split('/').pop(); const sha = String(e.sha || ''); const commit = repo && /^[A-Za-z0-9_.-]+$/.test(repo) && /^[0-9a-f]{7,40}$/i.test(sha) ? `https://git.openvibe.codes/OpenVibers/${repo}/commit/${sha}` : e.url; return `<div class="ov-shipped-entry">${/^https:\/\//.test(commit || '') ? `<a class="ov-shipped-hash" href="${esc(commit)}" rel="noopener">${esc(e.short || sha.slice(0, 7))}</a>` : ''}${showSite ? `<span class="ov-shipped-site">${esc(nameOf(e.service))}</span>` : ''}<span class="ov-shipped-text">${esc(e.subject)}</span><span class="ov-shipped-meta">${esc(e.author && e.author !== 'OpenVibers' ? `${e.author} · ` : '')}${esc(hhmm(e))} UTC</span></div>`; };
// The same change on many sites is one row (a pin round was thirty identical lines), grouped exactly as
// The same change on many sites is one row (a pin round was thirty identical lines), grouped exactly as
// openvibe-shared/shipped.js groups the page once its script runs.
// openvibe-shared/shipped.js groups the page once its script runs.
const groupHtml = (g) => {
const groupHtml = (g) => {
@@ -203,7 +204,7 @@ a{color:var(--accent)}
@@ -203,7 +204,7 @@ a{color:var(--accent)}
${require('openvibe-shared/frame').noscriptNav({ name: 'OpenVibe.Network', links: [{ label: 'Updates', href: '/updates' }, { label: 'Status', href: '/status' }] })}
${require('openvibe-shared/frame').noscriptNav({ name: 'OpenVibe.Network', links: [{ label: 'Updates', href: '/updates' }, { label: 'Status', href: '/status' }] })}
<main class="up" id="main">
<main class="up" id="main">
<h1>${esc(title)}</h1>
<h1>${esc(title)}</h1>
-<p class="lede">Every change deployed to OpenVibe, newest first. Each line is a commit from the OpenVibers repositories, linked to the change itself. When enough have gathered, or a large feature lands, they are written up as <a href="https://openvibe.blog/@openvibe">Patch notes on openvibe.blog</a>. JSON: <a href="/api/v1/changelog"><code>/api/v1/changelog</code></a>.</p>
+<p class="lede">Every change deployed to OpenVibe, newest first. Each line is a commit from the <a href="https://git.openvibe.codes/OpenVibers">OpenVibers repositories</a>, linked to the change itself. When enough have gathered, or a large feature lands, they are written up as <a href="https://openvibe.blog/@openvibe">Patch notes on openvibe.blog</a>. JSON: <a href="/api/v1/changelog"><code>/api/v1/changelog</code></a>.</p>
<div data-ov-shipped="log" data-service="${esc(site || '')}" data-limit="50">
<div data-ov-shipped="log" data-service="${esc(site || '')}" data-limit="50">
${chips}
${chips}
${postsHtml}
${postsHtml}
@@ -35,12 +35,14 @@ const publicCors = require('../server/public-cors');
@@ -35,12 +35,14 @@ const publicCors = require('../server/public-cors');
assert.strictEqual(calls[0], 'http://blog.test/api/v1/changelog?service=wiki&limit=100', 'limit clamped, a bad cursor dropped');
assert.strictEqual(calls[0], 'http://blog.test/api/v1/changelog?service=wiki&limit=100', 'limit clamped, a bad cursor dropped');
assert.strictEqual(calls[1], 'http://blog.test/api/v1/devblogs?service=wiki&limit=1', 'the lead is the newest devblog for the requested service');
assert.strictEqual(calls[1], 'http://blog.test/api/v1/devblogs?service=wiki&limit=1', 'the lead is the newest devblog for the requested service');
let body = await r.json();
let body = await r.json();
+ await fetch(`${base}/api/v1/changelog?service=wiki&kind=new&limit=5&before=abc123`);
+ assert.strictEqual(calls[2], 'http://blog.test/api/v1/changelog?service=wiki&kind=new&limit=5&before=abc123', 'kind and cursor pass through to Blog');
assert.deepStrictEqual(body.latest_devblog, {
assert.deepStrictEqual(body.latest_devblog, {
title: 'What shipped on OpenVibe.Wiki', summary: 'A week of pages.',
title: 'What shipped on OpenVibe.Wiki', summary: 'A week of pages.',
figure: 'https://openvibe.media/o/m1', url: 'https://openvibe.blog/@openvibe/what-shipped-wiki', published_at: '2026-09-24T06:00:00.000Z',
figure: 'https://openvibe.media/o/m1', url: 'https://openvibe.blog/@openvibe/what-shipped-wiki', published_at: '2026-09-24T06:00:00.000Z',
}, 'the devblog as the card lead, its art as an https URL');
}, 'the devblog as the card lead, its art as an https URL');
await fetch(`${base}/api/v1/changelog?service=wiki&limit=500`);
await fetch(`${base}/api/v1/changelog?service=wiki&limit=500`);
- assert.strictEqual(calls.length, 2, 'changelog and devblog cached for 60 s');
+ assert.strictEqual(calls.length, 3, 'all, New, and devblog requests are cached for 60 s');
t += 61 * 1000;
t += 61 * 1000;
devblogs = { total: 1, posts: [{ id: 'p2', title: 'T'.repeat(260), summary: 'S'.repeat(500), figure: 'http://insecure.test/a.png', url: 'https://openvibe.blog/@openvibe/second', published_at: '2026-09-25T06:00:00.000Z' }] };
devblogs = { total: 1, posts: [{ id: 'p2', title: 'T'.repeat(260), summary: 'S'.repeat(500), figure: 'http://insecure.test/a.png', url: 'https://openvibe.blog/@openvibe/second', published_at: '2026-09-25T06:00:00.000Z' }] };
body = await (await fetch(`${base}/api/v1/changelog?service=wiki`)).json();
body = await (await fetch(`${base}/api/v1/changelog?service=wiki`)).json();