openvibers/openvibe.shared Commits
Shared 4.1.2: Recently shipped passes over library pin bumps (the full log keeps them) (#68)
Changes
@@@ -4,6 +4,14 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semvebreaking 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@@@ -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": {@@@ -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 };}));@@@ -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); });
@@ -4,6 +4,14 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
@@ -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.
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).
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
## 4.1.1 — 2026-10-12
- **`release.js` reads the commit time on release-layout services.** A release directory belongs to the deploying
- **`release.js` reads the commit time on release-layout services.** A release directory belongs to the deploying
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"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",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"main": "index.js",
"bin": {
"bin": {
@@ -168,11 +168,23 @@
@@ -168,11 +168,23 @@
if (_ticker.unref) _ticker.unref();
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 ───────────────────────────────────
// ── latest: the one-liner ───────────────────────────────────
/** Render the newest entry into `node` (exported for tests). A node that is an <a> keeps its own href. */
/** Render the newest entry into `node` (exported for tests). A node that is an <a> keeps its own href. */
function renderLatest(node, data, opts) {
function renderLatest(node, data, opts) {
const o = 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 = '';
node.textContent = '';
if (!e || !e.subject) { node.hidden = true; return node; }
if (!e || !e.subject) { node.hidden = true; return node; }
node.classList.add('ov-shipped-latest');
node.classList.add('ov-shipped-latest');
@@ -195,14 +207,15 @@
@@ -195,14 +207,15 @@
if (!node) return null;
if (!node) return null;
o.service = resolveService(o.service);
o.service = resolveService(o.service);
style();
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 ────────────────────────────────────
// ── list: the newest few ────────────────────────────────────
/** Render a changelog answer into `node` (exported for tests). */
/** Render a changelog answer into `node` (exported for tests). */
function render(node, data, opts) {
function render(node, data, opts) {
const o = 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 = '';
node.textContent = '';
if (!entries.length) { node.hidden = true; return node; }
if (!entries.length) { node.hidden = true; return node; }
node.classList.add('ov-shipped');
node.classList.add('ov-shipped');
@@ -235,7 +248,7 @@
@@ -235,7 +248,7 @@
if (!node) return null;
if (!node) return null;
o.service = resolveService(o.service);
o.service = resolveService(o.service);
style();
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 ───────────────────────────────────
// ── log: the updates page ───────────────────────────────────
@@ -382,5 +395,5 @@
@@ -382,5 +395,5 @@
return out;
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 };
}));
}));
@@ -45,7 +45,7 @@ const data = {
@@ -45,7 +45,7 @@ const data = {
let asked = null;
let asked = null;
globalThis.fetch = async (url) => { asked = url; return { ok: true, json: async () => data }; };
globalThis.fetch = async (url) => { asked = url; return { ok: true, json: async () => data }; };
await shipped.mount('#s', { service: 'live', limit: 3 });
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'); };
globalThis.fetch = async () => { throw new Error('offline'); };
const failed = await shipped.mount('#n', {});
const failed = await shipped.mount('#n', {});
assert.strictEqual(failed.hidden, true);
assert.strictEqual(failed.hidden, true);
@@ -136,5 +136,19 @@ const data = {
@@ -136,5 +136,19 @@ const data = {
assert.ok(auto.hasAttribute('data-ov-shipped-mounted'));
assert.ok(auto.hasAttribute('data-ov-shipped-mounted'));
assert.strictEqual(auto.querySelectorAll('li').length, 2);
assert.strictEqual(auto.querySelectorAll('li').length, 2);
assert.strictEqual(shipped.scan(document).length, 0, 'already mounted: not again');
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');
console.log('shipped: all checks passed');
})().catch((e) => { console.error(e); process.exit(1); });
})().catch((e) => { console.error(e); process.exit(1); });