openvibers/openvibe.sites Commits
openvibe-shared v4.1.3 (was v4.1.1) (#53)
Changes
@@@ -72,7 +72,7 @@ The pinned `openvibe-contracts` release supplies the catalog `sites.json` mirror## Depends on- `openvibe-contracts` v0.143.0 — `scripts/sync-catalog.js` reads `openvibe-contracts/lib/products` (`catalog()`) to generate `sites.json`.--- `openvibe-shared` v4.1.1 — `build.js` uses `legal` for the terms, privacy and dmca pages, `files` for the `BROWSER` list copied to `dist/_shared/`, and `app-icon` for the manifest and head tags; `test/run.js` uses `test-runner`.++- `openvibe-shared` v4.1.3 — `build.js` uses `legal` for the terms, privacy and dmca pages, `files` for the `BROWSER` list copied to `dist/_shared/`, and `app-icon` for the manifest and head tags; `test/run.js` uses `test-runner`.- OpenVibe.Host — the deploy wrapper calls `ovhost deploy sites --restart`, or `ovhost plan sites --restart` with `DRY_RUN=1`.- OpenVibe.Network at `https://openvibe.network` — the frozen pages and shared browser files load its assets and API in the browser; `NET.networkUrl` in `build.js` is that base.@@@ -108,5 +108,5 @@ Report security issues through [SECURITY.md](SECURITY.md).<!-- versions:start -->- openvibe-contracts: v0.143.0--- openvibe-shared: v4.1.1++- openvibe-shared: v4.1.3<!-- versions:end -->@@@ -23,7 +23,7 @@"status.openvibe.network (status notice)"],"contracts": "openvibe-contracts v0.143.0: sites.json mirrors products.catalog() (scripts/sync-catalog.js)",-- "shared": "openvibe-shared v4.1.1",++ "shared": "openvibe-shared v4.1.3","deployed": true,"deployment": "Deployed with `sudo ovhost deploy sites --restart` (OpenVibe.Host strategy static-build since the 2026-09-27 cutover): /opt/openvibe.sites, nginx serves dist/<domain>/ through deploy/nginx/<domain>.conf; no unit, no port, no env file","features": [@@@ -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 ───────────────────────────────────@@@ -287,7 +300,10 @@const days = el('div', { class: 'ov-shipped-days' });days.style.cssText = 'display:flex;flex-direction:column;gap:24px;min-width:0';const more = el('button', { type: 'button', class: 'ov-shipped-more', hidden: '' }, 'Load more');-- node.append(bar, posts, days, more);++ // The first load shows one "Loading…" line on its own (and a site's own chips, known up front), so the chips,++ // the patch notes and the days arrive together instead of pushing a visible line down the page (layout shift).++ const loading = el('p', { class: 'ov-shipped-empty' }, 'Loading…');++ node.append(bar, posts, loading, days, more);let next = null;let seq = 0;@@@ -329,7 +345,7 @@if (reset) {next = null;days.textContent = '';-- days.appendChild(el('p', { class: 'ov-shipped-empty' }, 'Loading…'));++ if (!loading.parentNode) days.appendChild(el('p', { class: 'ov-shipped-empty' }, 'Loading…'));for (const b of bar.querySelectorAll('.ov-shipped-chip')) b.setAttribute('aria-pressed', 'false');}more.disabled = true;@@@ -337,6 +353,7 @@if (mine !== seq) return;if (reset) {days.textContent = '';++ loading.remove();if (data && data.sites) sitesSeen = data.sites;if (!own && !sitesSeen) {const all = await feed({ service: null, limit: 1, api: o.api });@@@ -360,6 +377,7 @@tick();}more.addEventListener('click', () => load(false));++ if (own) drawBar(null);await load(true);return node;}@@@ -382,5 +400,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 };}));@@@ -7,7 +7,7 @@"name": "openvibe-sites","dependencies": {"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.143.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.1"++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.3"},"engines": {"node": ">=20"@@@ -197,9 +197,9 @@}},"node_modules/openvibe-shared": {-- "version": "4.1.1",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.1",-- "integrity": "sha512-jP+3EtStuhHarDCpoQ0+5oAblx3LjoplZtgYn2CxbE/j388nEWFofiGPY/ORUKp7zBrHad2qv8YOL6kJP/MVEA==",++ "version": "4.1.3",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.3",++ "integrity": "sha512-mXLBDTfoFWqnpHQdgpMvSx02v6m9vFy7k3RpKvltK7q4SSM4WQxa2jXzRGpHk0yVfIcoxDH6QqDWx0wNKobulA==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -8,7 +8,7 @@},"dependencies": {"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.143.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.1"++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.3"},"engines": {"node": ">=20"
@@ -72,7 +72,7 @@ The pinned `openvibe-contracts` release supplies the catalog `sites.json` mirror
@@ -72,7 +72,7 @@ The pinned `openvibe-contracts` release supplies the catalog `sites.json` mirror
## Depends on
## Depends on
- `openvibe-contracts` v0.143.0 — `scripts/sync-catalog.js` reads `openvibe-contracts/lib/products` (`catalog()`) to generate `sites.json`.
- `openvibe-contracts` v0.143.0 — `scripts/sync-catalog.js` reads `openvibe-contracts/lib/products` (`catalog()`) to generate `sites.json`.
-- `openvibe-shared` v4.1.1 — `build.js` uses `legal` for the terms, privacy and dmca pages, `files` for the `BROWSER` list copied to `dist/_shared/`, and `app-icon` for the manifest and head tags; `test/run.js` uses `test-runner`.
+- `openvibe-shared` v4.1.3 — `build.js` uses `legal` for the terms, privacy and dmca pages, `files` for the `BROWSER` list copied to `dist/_shared/`, and `app-icon` for the manifest and head tags; `test/run.js` uses `test-runner`.
- OpenVibe.Host — the deploy wrapper calls `ovhost deploy sites --restart`, or `ovhost plan sites --restart` with `DRY_RUN=1`.
- OpenVibe.Host — the deploy wrapper calls `ovhost deploy sites --restart`, or `ovhost plan sites --restart` with `DRY_RUN=1`.
- OpenVibe.Network at `https://openvibe.network` — the frozen pages and shared browser files load its assets and API in the browser; `NET.networkUrl` in `build.js` is that base.
- OpenVibe.Network at `https://openvibe.network` — the frozen pages and shared browser files load its assets and API in the browser; `NET.networkUrl` in `build.js` is that base.
@@ -108,5 +108,5 @@ Report security issues through [SECURITY.md](SECURITY.md).
@@ -108,5 +108,5 @@ Report security issues through [SECURITY.md](SECURITY.md).
<!-- versions:start -->
<!-- versions:start -->
- openvibe-contracts: v0.143.0
- openvibe-contracts: v0.143.0
-- openvibe-shared: v4.1.1
+- openvibe-shared: v4.1.3
<!-- versions:end -->
<!-- versions:end -->
@@ -23,7 +23,7 @@
@@ -23,7 +23,7 @@
"status.openvibe.network (status notice)"
"status.openvibe.network (status notice)"
],
],
"contracts": "openvibe-contracts v0.143.0: sites.json mirrors products.catalog() (scripts/sync-catalog.js)",
"contracts": "openvibe-contracts v0.143.0: sites.json mirrors products.catalog() (scripts/sync-catalog.js)",
- "shared": "openvibe-shared v4.1.1",
+ "shared": "openvibe-shared v4.1.3",
"deployed": true,
"deployed": true,
"deployment": "Deployed with `sudo ovhost deploy sites --restart` (OpenVibe.Host strategy static-build since the 2026-09-27 cutover): /opt/openvibe.sites, nginx serves dist/<domain>/ through deploy/nginx/<domain>.conf; no unit, no port, no env file",
"deployment": "Deployed with `sudo ovhost deploy sites --restart` (OpenVibe.Host strategy static-build since the 2026-09-27 cutover): /opt/openvibe.sites, nginx serves dist/<domain>/ through deploy/nginx/<domain>.conf; no unit, no port, no env file",
"features": [
"features": [
@@ -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 ───────────────────────────────────
@@ -287,7 +300,10 @@
@@ -287,7 +300,10 @@
const days = el('div', { class: 'ov-shipped-days' });
const days = el('div', { class: 'ov-shipped-days' });
days.style.cssText = 'display:flex;flex-direction:column;gap:24px;min-width:0';
days.style.cssText = 'display:flex;flex-direction:column;gap:24px;min-width:0';
const more = el('button', { type: 'button', class: 'ov-shipped-more', hidden: '' }, 'Load more');
const more = el('button', { type: 'button', class: 'ov-shipped-more', hidden: '' }, 'Load more');
- node.append(bar, posts, days, more);
+ // The first load shows one "Loading…" line on its own (and a site's own chips, known up front), so the chips,
+ // the patch notes and the days arrive together instead of pushing a visible line down the page (layout shift).
+ const loading = el('p', { class: 'ov-shipped-empty' }, 'Loading…');
+ node.append(bar, posts, loading, days, more);
let next = null;
let next = null;
let seq = 0;
let seq = 0;
@@ -329,7 +345,7 @@
@@ -329,7 +345,7 @@
if (reset) {
if (reset) {
next = null;
next = null;
days.textContent = '';
days.textContent = '';
- days.appendChild(el('p', { class: 'ov-shipped-empty' }, 'Loading…'));
+ if (!loading.parentNode) days.appendChild(el('p', { class: 'ov-shipped-empty' }, 'Loading…'));
for (const b of bar.querySelectorAll('.ov-shipped-chip')) b.setAttribute('aria-pressed', 'false');
for (const b of bar.querySelectorAll('.ov-shipped-chip')) b.setAttribute('aria-pressed', 'false');
}
}
more.disabled = true;
more.disabled = true;
@@ -337,6 +353,7 @@
@@ -337,6 +353,7 @@
if (mine !== seq) return;
if (mine !== seq) return;
if (reset) {
if (reset) {
days.textContent = '';
days.textContent = '';
+ loading.remove();
if (data && data.sites) sitesSeen = data.sites;
if (data && data.sites) sitesSeen = data.sites;
if (!own && !sitesSeen) {
if (!own && !sitesSeen) {
const all = await feed({ service: null, limit: 1, api: o.api });
const all = await feed({ service: null, limit: 1, api: o.api });
@@ -360,6 +377,7 @@
@@ -360,6 +377,7 @@
tick();
tick();
}
}
more.addEventListener('click', () => load(false));
more.addEventListener('click', () => load(false));
+ if (own) drawBar(null);
await load(true);
await load(true);
return node;
return node;
}
}
@@ -382,5 +400,5 @@
@@ -382,5 +400,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 };
}));
}));
@@ -7,7 +7,7 @@
@@ -7,7 +7,7 @@
"name": "openvibe-sites",
"name": "openvibe-sites",
"dependencies": {
"dependencies": {
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.143.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.143.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.1"
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.3"
},
},
"engines": {
"engines": {
"node": ">=20"
"node": ">=20"
@@ -197,9 +197,9 @@
@@ -197,9 +197,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "4.1.1",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.1",
- "integrity": "sha512-jP+3EtStuhHarDCpoQ0+5oAblx3LjoplZtgYn2CxbE/j388nEWFofiGPY/ORUKp7zBrHad2qv8YOL6kJP/MVEA==",
+ "version": "4.1.3",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.3",
+ "integrity": "sha512-mXLBDTfoFWqnpHQdgpMvSx02v6m9vFy7k3RpKvltK7q4SSM4WQxa2jXzRGpHk0yVfIcoxDH6QqDWx0wNKobulA==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -8,7 +8,7 @@
@@ -8,7 +8,7 @@
},
},
"dependencies": {
"dependencies": {
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.143.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.143.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.1"
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.3"
},
},
"engines": {
"engines": {
"node": ">=20"
"node": ">=20"