openvibers/openvibe.network Commits

One site filter on /updates: the no-JS links sit inside the shipped mount, which shipped.js replaces with its own bar; Bot is a site (#60)

OP OpenVibers committed 5 Oct 2026 · 2dc61bfaed · 2 files, +10 −2

The /updates page showed two rows of site filter chips: the server-rendered no-JS links (nav.up-sites) sat above the shipped mount, and openvibe-shared shipped.js log view then drew its own filter bar inside the mount.

- server/updates/routes.js: the no-JS site links now render inside the data-ov-shipped="log" mount. shipped.js empties the mount when it loads and draws its own bar, so there is one filter row with JavaScript and the links without it.
- Bot is a site on the page (SITES gains bot), so ?site=bot filters and its no-JS chip reads Bot.
- test/updates.test.js asserts the links are inside the mount, that there is exactly one up-sites nav, the Bot chip, and serviceFor("bot").

ov test test/updates.test.js test/page-checks.test.js: both pass.

Built in the main session (Opus).

Changes

ddiff --git a/server/updates/routes.js b/server/updates/routes.js
iindex 0169b441..27994680 100644
---- a/server/updates/routes.js
++++ b/server/updates/routes.js
@@@ -69,7 +69,7 @@ function createUpdatesRoutes({ blogUrl = 'http://127.0.0.1:4810', fetchImpl = gl
return { router: r, feed, queryOf };
}
--const SITES = ['live', 'network', 'tools', 'media', 'community', 'chat', 'games', 'blog', 'wiki', 'news', 'reviews', 'deals', 'coupons', 'trade', 'codes', 'host', 'ai', 'search', 'sources', 'events', 'billing', 'tips', 'vip', 'openre', 'sites'];
++const SITES = ['live', 'network', 'tools', 'media', 'community', 'chat', 'games', 'blog', 'wiki', 'news', 'reviews', 'deals', 'coupons', 'trade', 'codes', 'host', 'ai', 'search', 'sources', 'events', 'billing', 'tips', 'vip', 'openre', 'bot', 'sites'];
const NAMES = { ai: 'AI', vip: 'VIP', openre: 'OpenRe' };
const nameOf = (id) => NAMES[id] || (id ? id.charAt(0).toUpperCase() + id.slice(1) : '');
@@@ -97,6 +97,8 @@ function renderPage({ site, filtered, body }) {
}
const dayHtml = days.map((g) => `<section class="ov-shipped-day"><h3>${esc(g.day)}</h3><div class="ov-shipped-entries">${g.list.map((e) => `<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>` : ''}${site ? '' : `<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} · ` : '')}${esc(String(e.deployed_at || '').slice(11, 16))} UTC</span></div>`).join('')}</div></section>`).join('\n');
const postsHtml = posts.length ? `<aside class="ov-shipped-posts"><h3>Patch notes</h3><ul>${posts.map((p) => `<li><a href="${esc(p.url)}">${esc(p.title || 'Patch notes')}</a>${p.published_at ? ` <small>${esc(String(p.published_at).slice(0, 10))}</small>` : ''}</li>`).join('')}</ul></aside>` : '';
++ // The no-JS site filter (links): inside the shipped mount, so shipped.js's own filter bar replaces it rather than
++ // adding a second row.
const chips = `<nav class="up-sites" aria-label="Sites"><a href="/updates"${site ? '' : ' aria-current="page"'}>Everything</a>${SITES.filter((s) => s !== 'sites').map((s) => `<a href="/updates?site=${s}"${site === s ? ' aria-current="page"' : ''}>${esc(nameOf(s))}</a>`).join('')}</nav>`;
return `<!DOCTYPE html>
<html lang="en">
@@@ -132,8 +134,8 @@ ${require('openvibe-shared/frame').noscriptNav({ name: 'OpenVibe.Network', links
<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>
--${chips}
<div data-ov-shipped="log" data-service="${esc(site || '')}" data-limit="50">
++${chips}
${postsHtml}
${dayHtml || '<p class="ov-shipped-empty">The update history could not be loaded just now.</p>'}
</div>
ddiff --git a/test/updates.test.js b/test/updates.test.js
iindex 207653b4..46e3d35a 100644
---- a/test/updates.test.js
++++ b/test/updates.test.js
@@@ -39,6 +39,12 @@ const publicCors = require('../server/public-cors');
assert.ok(page.includes('Fix &lt;script&gt;x&lt;/script&gt; in pages') && !page.includes('<script>x</script>'), 'commit text escaped in the SSR log');
assert.ok(page.includes('https://openvibe.blog/@openvibe/x'), 'patch notes listed');
assert.ok(page.includes('<link rel="canonical" href="https://openvibe.network/updates">'));
++ // One filter row: the no-JS site links sit inside the shipped mount, which shipped.js empties and refills with its own bar.
++ const mount = page.indexOf('data-ov-shipped="log"'), nav = page.indexOf('<nav class="up-sites"');
++ assert.ok(nav > mount && page.indexOf('</div>', mount) > nav, 'the site links are inside the shipped mount');
++ assert.strictEqual(page.split('class="up-sites"').length - 1, 1, 'one site filter');
++ assert.ok(page.includes('<a href="/updates?site=bot">Bot</a>'), 'Bot is a site');
++ assert.strictEqual(serviceFor('bot'), 'bot');
const one = await fetch(`${base}/updates?site=openvibe.wiki`);
assert.strictEqual(one.headers.get('x-robots-tag'), 'noindex, follow');
assert.ok((await one.text()).includes('What shipped on OpenVibe.Wiki'));