openvibers/openvibe.shared Commits

Shared 4.1.2: Recently shipped passes over library pin bumps (the full log keeps them) (#68)

OP OpenVibers committed 12 Oct 2026 · 581578dcc4 · 4 files, +42 −7

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 7fc506c..71a5bda 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,14 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
breaking change to any exported module, browser global or served file name is a new major.
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
++## 4.1.2 — 2026-10-12
++
++- **"Recently shipped" tells visitors what changed, not which library was bumped.** The one-liner and the short
++ list (`shipped.js` latest and list) pass over library pin bumps ("openvibe-shared v4.1.0 (was v4.0.0)", opened by
++ pin-bump.js on every repo at once): they read three times as many entries and show the newest real changes; when
++ only pin bumps shipped, those are shown rather than an empty block. The full updates log keeps every deployed commit.
++ `isHousekeeping(entry)` is exported.
++
## 4.1.1 — 2026-10-12
- **`release.js` reads the commit time on release-layout services.** A release directory belongs to the deploying
ddiff --git a/package.json b/package.json
iindex 2b7beb0..ebfadb2 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-shared",
-- "version": "4.1.1",
++ "version": "4.1.2",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"bin": {
ddiff --git a/shipped.js b/shipped.js
iindex 2f6cae0..63e4052 100644
---- a/shipped.js
++++ b/shipped.js
@@@ -168,11 +168,23 @@
if (_ticker.unref) _ticker.unref();
}
++ // ── what a visitor reads ────────────────────────────────────
++ // A library pin bump ("openvibe-shared v4.1.0 (was v4.0.0) (#34)", opened by pin-bump.js on every repo at once)
++ // says nothing to a visitor: the one-liner and the short list pass over them, while the full log keeps every
++ // deployed commit. If nothing else shipped, the newest entry is shown anyway, so a site never looks dead.
++ const PIN_BUMP = /^openvibe-[a-z][a-z0-9-]* v\d+\.\d+\.\d+ \(was v\d+\.\d+\.\d+\)/;
++ function isHousekeeping(e) { return Boolean(e && PIN_BUMP.test(String(e.subject || ''))); }
++ function forVisitors(entries, n) {
++ const all = Array.isArray(entries) ? entries : [];
++ const told = all.filter((e) => !isHousekeeping(e));
++ return (told.length ? told : all).slice(0, n);
++ }
++
// ── latest: the one-liner ───────────────────────────────────
/** Render the newest entry into `node` (exported for tests). A node that is an <a> keeps its own href. */
function renderLatest(node, data, opts) {
const o = opts || {};
-- const e = data && Array.isArray(data.entries) ? data.entries[0] : null;
++ const e = forVisitors(data && data.entries, 1)[0] || null;
node.textContent = '';
if (!e || !e.subject) { node.hidden = true; return node; }
node.classList.add('ov-shipped-latest');
@@@ -195,14 +207,15 @@
if (!node) return null;
o.service = resolveService(o.service);
style();
-- return renderLatest(node, await feed({ service: o.service, limit: 1, api: o.api }), o);
++ // A few more than one, so a run of pin bumps does not hide the newest real change.
++ return renderLatest(node, await feed({ service: o.service, limit: 12, api: o.api }), o);
}
// ── list: the newest few ────────────────────────────────────
/** Render a changelog answer into `node` (exported for tests). */
function render(node, data, opts) {
const o = opts || {};
-- const entries = (data && Array.isArray(data.entries) ? data.entries : []).slice(0, clamp(o.limit, 1, 20, 5));
++ const entries = forVisitors(data && data.entries, clamp(o.limit, 1, 20, 5));
node.textContent = '';
if (!entries.length) { node.hidden = true; return node; }
node.classList.add('ov-shipped');
@@@ -235,7 +248,7 @@
if (!node) return null;
o.service = resolveService(o.service);
style();
-- return render(node, await feed({ service: o.service, limit: clamp(o.limit, 1, 20, 5), api: o.api }), o);
++ return render(node, await feed({ service: o.service, limit: Math.min(50, clamp(o.limit, 1, 20, 5) * 3), api: o.api }), o);
}
// ── log: the updates page ───────────────────────────────────
@@@ -382,5 +395,5 @@
return out;
}
-- return { latest, list, mount: list, log, scan, render, renderLatest, appendDays, serviceFor, ago, API, SITE_NAMES, siteName };
++ return { latest, list, mount: list, log, scan, render, renderLatest, appendDays, serviceFor, ago, API, SITE_NAMES, siteName, isHousekeeping };
}));
ddiff --git a/test/shipped.test.js b/test/shipped.test.js
iindex 8fe55a2..0e37595 100644
---- a/test/shipped.test.js
++++ b/test/shipped.test.js
@@@ -45,7 +45,7 @@ const data = {
let asked = null;
globalThis.fetch = async (url) => { asked = url; return { ok: true, json: async () => data }; };
await shipped.mount('#s', { service: 'live', limit: 3 });
-- assert.strictEqual(asked, 'https://openvibe.network/api/v1/changelog?service=live&limit=3', 'the Network proxy every site\'s CSP already allows');
++ assert.strictEqual(asked, 'https://openvibe.network/api/v1/changelog?service=live&limit=9', 'the Network proxy every site\'s CSP already allows (three times the list: pin bumps are passed over)');
globalThis.fetch = async () => { throw new Error('offline'); };
const failed = await shipped.mount('#n', {});
assert.strictEqual(failed.hidden, true);
@@@ -136,5 +136,19 @@ const data = {
assert.ok(auto.hasAttribute('data-ov-shipped-mounted'));
assert.strictEqual(auto.querySelectorAll('li').length, 2);
assert.strictEqual(shipped.scan(document).length, 0, 'already mounted: not again');
++ // Library pin bumps say nothing to a visitor: the one-liner and the list pass over them (the log keeps them).
++ const bump = (n, subject) => ({ service: 'wiki', subject, url: `https://github.com/OpenVibers/OpenVibe.Wiki/commit/${n}`, deployed_at: new Date(Date.now() - n * 60e3).toISOString() });
++ const mixed = { entries: [bump(1, 'openvibe-shared v4.1.0 (was v4.0.0) (#44)'), bump(2, 'openvibe-contracts v0.152.0 (was v0.143.0) (#43)'), bump(3, 'Wiki: page history shows who changed what'), bump(4, 'Sitemaps: every entry carries an honest lastmod')] };
++ assert.ok(shipped.isHousekeeping(mixed.entries[0]) && shipped.isHousekeeping(mixed.entries[1]) && !shipped.isHousekeeping(mixed.entries[2]));
++ const pill2 = document.createElement('a');
++ shipped.renderLatest(pill2, mixed, { service: 'wiki' });
++ assert.ok(pill2.textContent.includes('Wiki: page history shows who changed what'), 'the newest real change, not a pin bump');
++ const list2 = document.createElement('section');
++ shipped.render(list2, mixed, { service: 'wiki', limit: 5 });
++ assert.deepStrictEqual([...list2.querySelectorAll('li .ov-shipped-subject')].map((a) => a.textContent), ['Wiki: page history shows who changed what', 'Sitemaps: every entry carries an honest lastmod']);
++ const onlyBumps = { entries: [mixed.entries[0], mixed.entries[1]] };
++ const list3 = document.createElement('section');
++ shipped.render(list3, onlyBumps, { service: 'wiki', limit: 5 });
++ assert.strictEqual(list3.querySelectorAll('li').length, 2, 'only pin bumps shipped: shown anyway, never an empty block');
console.log('shipped: all checks passed');
})().catch((e) => { console.error(e); process.exit(1); });