openvibers/openvibe.network Commits
openvibe-shared v4.47.2 (was v4.43.0) (#429)
Changes
@@@ -785,5 +785,5 @@ MIT (`license` in [package.json](package.json)).<!-- versions:start -->- openvibe-contracts: v0.238.0- openvibe-sdk: v0.67.0--- openvibe-shared: v4.43.0++- openvibe-shared: v4.47.2<!-- versions:end -->@@@ -8,7 +8,7 @@"domain": "openvibe.network (with auth., api., admin., my. and themes.openvibe.network in its manifest)","contracts": "openvibe-contracts v0.238.0","sdk": "openvibe-sdk v0.67.0",-- "shared": "openvibe-shared v4.43.0",++ "shared": "openvibe-shared v4.47.2","capabilities": ["identity.subject.resolve","mods.grant.manage",@@@ -48,5 +48,5 @@"per-actor limits (openvibe-sdk/limits)","universal telemetry: per-route aggregated platform.telemetry-sample@1 plus per-flush HTTP autoscaling gauges (openvibe-sdk/telemetry)"],-- "updated": "2026-10-29"++ "updated": "2026-10-30"}@@@ -22,7 +22,7 @@"multer": "^1.4.5-lts.1","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.238.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.43.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.47.2","pg": "^8.23.0","uuid": "^11.1.0","web-push": "^3.6.7"@@@ -1639,9 +1639,9 @@}},"node_modules/openvibe-shared": {-- "version": "4.43.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.43.0",-- "integrity": "sha512-OANEzCEB2QPgbkb9Yt0O67k6DxcK2pHMO1nhKYCjTI7OhUF8LW8EpLf6FJmpHmDTmpFTUq4pJ3lr3u2hPXMDYQ==",++ "version": "4.47.2",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.47.2",++ "integrity": "sha512-zsvjjCI3CxkjDiKlC4HHeiiE+W7DHb0tJTRoCe4aqCffN1SQVkwtoJf7UKWYP4jHCMjwHjjOAT+xhxAfXSCDuQ==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0",@@@ -26,7 +26,7 @@"multer": "^1.4.5-lts.1","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.238.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.43.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.47.2","pg": "^8.23.0","uuid": "^11.1.0","web-push": "^3.6.7"@@@ -12,6 +12,7 @@ const cache = require('openvibe-shared/cache-policy');*/const express = require('express');const boost = require('../boost');++const shipped = require('openvibe-shared/shipped');const TTL_MS = 60 * 1000;const MAX_KEYS = 300;@@@ -96,7 +97,19 @@ function renderPage({ site, filtered, body, releaseId = '' }) {if (!days.length || days[days.length - 1].day !== d) days.push({ day: d, list: [] });days[days.length - 1].list.push(e);}-- 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');++ // 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>`;++ // 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) => {++ if (g.entries.length === 1) return row(g.entries[0], !site);++ const names = [...new Set(g.entries.map((e) => e.service))].map(nameOf);++ const notes = shipped.groupNotes(g);++ const first = hhmm(g.entries[g.entries.length - 1]), last = hhmm(g.entries[0]);++ return `<div class="ov-shipped-entry ov-shipped-group"><span class="ov-shipped-count">${names.length}</span><span class="ov-shipped-text">${esc(shipped.groupTitle(g))}${notes ? ` <a class="ov-shipped-notes" href="${esc(notes)}" rel="noopener">What's in it</a>` : ''}</span><span class="ov-shipped-meta">${esc(first === last ? last : `${first}–${last}`)} UTC</span><details class="ov-shipped-fold"><summary>${esc(names.length > 7 ? `${names.slice(0, 6).join(', ')} and ${names.length - 6} more` : names.join(', '))}</summary><div class="ov-shipped-entries">${g.entries.map((e) => row(e, true)).join('')}</div></details></div>`;++ };++ const dayHtml = days.map((g) => `<section class="ov-shipped-day"><h3>${esc(g.day)}</h3><div class="ov-shipped-entries">${shipped.groupEntries(g.list).map(groupHtml).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.@@@ -128,6 +141,10 @@ a{color:var(--accent)}.ov-shipped-entry{display:flex;gap:10px;align-items:baseline;padding:8px 12px;flex-wrap:wrap}.ov-shipped-text{flex:1;min-width:0;overflow-wrap:anywhere}.ov-shipped-meta{font-size:.75rem;color:var(--text-secondary)}.ov-shipped-hash{font-family:ui-monospace,monospace;font-size:.8rem}.ov-shipped-site{font-size:.72rem;font-weight:700;padding:.05rem .45rem;border-radius:999px;background:rgba(59,130,246,.16)}++.ov-shipped-count{flex:none;min-width:2em;text-align:center;font-size:.72rem;font-weight:800;padding:.12rem .45rem;border-radius:999px;background:rgba(59,130,246,.22)}++.ov-shipped-notes{font-size:.82rem;font-weight:600;text-decoration:none;white-space:nowrap}++.ov-shipped-fold{flex-basis:100%;min-width:0;font-size:.82rem;color:var(--text-secondary)}.ov-shipped-fold summary{cursor:pointer;padding:3px 0}++.ov-shipped-fold .ov-shipped-entries{margin:4px 0 2px;padding-left:10px;border-left:2px solid var(--border)}</style></head><body>@@@ -15,8 +15,8 @@ const BUDGETS = {htmlRawKB: 188, // 188 with the agent layer's rel=alternate/describedby link tags on every public page (SEO-1/SEO-2, 2026-10-29: +0.2 KB); 187 with Bench open (contracts 0.238.0, 2026-10-28); 186 with Cam open (contracts 0.237.0, 2026-10-28); 184.4 with Fun open (contracts 0.236.0, 2026-10-28); 182.7 with Vision and Exchange open (contracts 0.235.0, 2026-10-28); 179 with Meme open (contracts 0.234.0, 2026-10-28); 174.1 with Domains, Report and Design open (contracts 0.226-0.228, 2026-10-28); 168.1 the hero's orbit and rich cards for live streams, recordings and screenshots (2026-10-28: the owner asked for animated visuals and YouTube-style cards); 152.4 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 147.9 with the home's FAQ (six questions and their FAQPage JSON-LD), the numbers panel and the service map's group headings (2026-10-22); 141.2 with Tips and VIP open (contracts 0.200.0, 37 sites; the Opening next section is gone); 134 with Play (contracts 0.172.1); 128.3 with Events, AI and Search added (31 sites, contracts 0.135.0); 122.3 (shared 2.21.0: a real glyph for every product card, where ten drew the 'V'); was 110.6 with 33 live sites (contracts 0.119.0). The home page lists every site, so it grows with the networkhtmlBrotliKB: 34, // 33.1 with Games granted its Bench match capabilities (BENCH-7, 2026-10-29: the home lists what each service may do); 32.0 with Fun, Vision and Exchange open (contracts 0.236.0, 2026-10-28); 31.2 with Domains, Report and Design open (2026-10-28); 30.1 with the Organization saying who OpenVibe is (description, other names, what it is not, 15 sites; 2026-10-28); 28.5 with the FAQ, numbers and groups (2026-10-22); 26.1 with Play; 25.1 (31 sites, contracts 0.135.0); 24.1 (shared 2.21.0, the same glyphs); 20.5 (contracts 0.116.0)jsFiles: 8, // 7 with boost.js, was 6 (2026-10-13, plan T11 step 3: one more script on every boosted page; it loads route-transition.js on the first move only)-- jsRawKB: 314, // 313.7 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 306.4 openvibe-shared 4.25.0: the menu's orb beside the brand, both balances on phones, messages, the account sheet on phones and the bar stepping away on phones; 298.9 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 293.1 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 279.1 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 269.3 with shared 4.11.0's seasonal navbar (Pumpkin Night and the season badge, +2.6); 249.8 with boost.js (13.1); 228.3-- jsBrotliKB: 79, // 78.9 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 77 openvibe-shared 4.25.0: the menu's orb beside the brand, both balances on phones, messages, the account sheet on phones and the bar stepping away on phones; 73.3 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 70.7 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 68.0 with shared 4.12.0 (light/dark switch, seasonal mark, theme pairs); 64.5 with shared 4.11.0's navbar; 60.3 with boost.js (3.8); 53.9++ jsRawKB: 324, // 323.2 with openvibe-shared 4.47.2 (the comfort settings in theme-loader 4.45, touch targets 4.46, the grouped updates log 4.47.2); 313.7 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 306.4 openvibe-shared 4.25.0: the menu's orb beside the brand, both balances on phones, messages, the account sheet on phones and the bar stepping away on phones; 298.9 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 293.1 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 279.1 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 269.3 with shared 4.11.0's seasonal navbar (Pumpkin Night and the season badge, +2.6); 249.8 with boost.js (13.1); 228.3++ jsBrotliKB: 82, // 81.4 with openvibe-shared 4.47.2 (comfort settings, touch targets, the grouped updates log); 78.9 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 77 openvibe-shared 4.25.0: the menu's orb beside the brand, both balances on phones, messages, the account sheet on phones and the bar stepping away on phones; 73.3 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 70.7 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 68.0 with shared 4.12.0 (light/dark switch, seasonal mark, theme pairs); 64.5 with shared 4.11.0's navbar; 60.3 with boost.js (3.8); 53.9cssFiles: 2, // 1 (Font Awesome, cross-origin)externalFiles: 3, // 1 locally; production adds Cloudflare's beacon};@@@ -5,7 +5,7 @@const assert = require('assert');const http = require('http');const express = require('express');--const { createUpdatesRoutes, serviceFor } = require('../server/updates/routes');++const { createUpdatesRoutes, serviceFor, renderPage } = require('../server/updates/routes');const publicCors = require('../server/public-cors');(async () => {@@@ -55,6 +55,15 @@ const publicCors = require('../server/public-cors');assert.strictEqual(serviceFor('search.openvibe.network'), 'search');assert.strictEqual(serviceFor('evil.example'), null);assert.ok(publicCors.isPublicDiscoveryPath('/api/v1/changelog'), 'readable from any origin');++++ // The first page groups the same change across sites, as openvibe-shared/shipped.js (4.47.2) does once it runs.++ const at = '2026-10-29T17:40:00.000Z';++ const pin = (svc, n) => ({ service: svc, sha: String(n).repeat(40).slice(0, 40), subject: `openvibe-shared v4.45.0 (was v4.43.0) (#${n})`, author: 'OpenVibers', deployed_at: at, url: `https://github.com/OpenVibers/x/commit/${n}` });++ const grouped = renderPage({ site: null, filtered: false, body: { entries: [pin('fun', 9), pin('chat', 252), pin('cam', 7), { service: 'trade', sha: 'c'.repeat(40), subject: 'Touch targets on phones (fleet audit) (#141)', author: 'OpenVibers', deployed_at: at, url: 'https://github.com/OpenVibers/x/commit/c' }] } });++ assert.ok(grouped.includes('<span class="ov-shipped-count">3</span><span class="ov-shipped-text">Shared 4.45.0 reached 3 sites'), 'one row for a pin round');++ assert.ok(grouped.includes('https://github.com/OpenVibers/OpenVibe.Shared/blob/v4.45.0/CHANGELOG.md'), "what's in that release");++ assert.strictEqual(grouped.split('class="ov-shipped-entry"').length - 1, 4, 'every commit still listed in its fold, plus the single one');++ assert.ok(!grouped.includes('OpenVibers · '), 'the shared account is not named on each row');srv.close();console.log('updates: all checks passed');})().catch((e) => { console.error(e); process.exit(1); });
@@ -785,5 +785,5 @@ MIT (`license` in [package.json](package.json)).
@@ -785,5 +785,5 @@ MIT (`license` in [package.json](package.json)).
<!-- versions:start -->
<!-- versions:start -->
- openvibe-contracts: v0.238.0
- openvibe-contracts: v0.238.0
- openvibe-sdk: v0.67.0
- openvibe-sdk: v0.67.0
-- openvibe-shared: v4.43.0
+- openvibe-shared: v4.47.2
<!-- versions:end -->
<!-- versions:end -->
@@ -8,7 +8,7 @@
@@ -8,7 +8,7 @@
"domain": "openvibe.network (with auth., api., admin., my. and themes.openvibe.network in its manifest)",
"domain": "openvibe.network (with auth., api., admin., my. and themes.openvibe.network in its manifest)",
"contracts": "openvibe-contracts v0.238.0",
"contracts": "openvibe-contracts v0.238.0",
"sdk": "openvibe-sdk v0.67.0",
"sdk": "openvibe-sdk v0.67.0",
- "shared": "openvibe-shared v4.43.0",
+ "shared": "openvibe-shared v4.47.2",
"capabilities": [
"capabilities": [
"identity.subject.resolve",
"identity.subject.resolve",
"mods.grant.manage",
"mods.grant.manage",
@@ -48,5 +48,5 @@
@@ -48,5 +48,5 @@
"per-actor limits (openvibe-sdk/limits)",
"per-actor limits (openvibe-sdk/limits)",
"universal telemetry: per-route aggregated platform.telemetry-sample@1 plus per-flush HTTP autoscaling gauges (openvibe-sdk/telemetry)"
"universal telemetry: per-route aggregated platform.telemetry-sample@1 plus per-flush HTTP autoscaling gauges (openvibe-sdk/telemetry)"
],
],
- "updated": "2026-10-29"
+ "updated": "2026-10-30"
}
}
@@ -22,7 +22,7 @@
@@ -22,7 +22,7 @@
"multer": "^1.4.5-lts.1",
"multer": "^1.4.5-lts.1",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.238.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.238.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.43.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.47.2",
"pg": "^8.23.0",
"pg": "^8.23.0",
"uuid": "^11.1.0",
"uuid": "^11.1.0",
"web-push": "^3.6.7"
"web-push": "^3.6.7"
@@ -1639,9 +1639,9 @@
@@ -1639,9 +1639,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "4.43.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.43.0",
- "integrity": "sha512-OANEzCEB2QPgbkb9Yt0O67k6DxcK2pHMO1nhKYCjTI7OhUF8LW8EpLf6FJmpHmDTmpFTUq4pJ3lr3u2hPXMDYQ==",
+ "version": "4.47.2",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.47.2",
+ "integrity": "sha512-zsvjjCI3CxkjDiKlC4HHeiiE+W7DHb0tJTRoCe4aqCffN1SQVkwtoJf7UKWYP4jHCMjwHjjOAT+xhxAfXSCDuQ==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
@@ -26,7 +26,7 @@
@@ -26,7 +26,7 @@
"multer": "^1.4.5-lts.1",
"multer": "^1.4.5-lts.1",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.238.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.238.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.43.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.47.2",
"pg": "^8.23.0",
"pg": "^8.23.0",
"uuid": "^11.1.0",
"uuid": "^11.1.0",
"web-push": "^3.6.7"
"web-push": "^3.6.7"
@@ -12,6 +12,7 @@ const cache = require('openvibe-shared/cache-policy');
@@ -12,6 +12,7 @@ const cache = require('openvibe-shared/cache-policy');
*/
*/
const express = require('express');
const express = require('express');
const boost = require('../boost');
const boost = require('../boost');
+const shipped = require('openvibe-shared/shipped');
const TTL_MS = 60 * 1000;
const TTL_MS = 60 * 1000;
const MAX_KEYS = 300;
const MAX_KEYS = 300;
@@ -96,7 +97,19 @@ function renderPage({ site, filtered, body, releaseId = '' }) {
@@ -96,7 +97,19 @@ function renderPage({ site, filtered, body, releaseId = '' }) {
if (!days.length || days[days.length - 1].day !== d) days.push({ day: d, list: [] });
if (!days.length || days[days.length - 1].day !== d) days.push({ day: d, list: [] });
days[days.length - 1].list.push(e);
days[days.length - 1].list.push(e);
}
}
- 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');
+ // 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>`;
+ // 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) => {
+ if (g.entries.length === 1) return row(g.entries[0], !site);
+ const names = [...new Set(g.entries.map((e) => e.service))].map(nameOf);
+ const notes = shipped.groupNotes(g);
+ const first = hhmm(g.entries[g.entries.length - 1]), last = hhmm(g.entries[0]);
+ return `<div class="ov-shipped-entry ov-shipped-group"><span class="ov-shipped-count">${names.length}</span><span class="ov-shipped-text">${esc(shipped.groupTitle(g))}${notes ? ` <a class="ov-shipped-notes" href="${esc(notes)}" rel="noopener">What's in it</a>` : ''}</span><span class="ov-shipped-meta">${esc(first === last ? last : `${first}–${last}`)} UTC</span><details class="ov-shipped-fold"><summary>${esc(names.length > 7 ? `${names.slice(0, 6).join(', ')} and ${names.length - 6} more` : names.join(', '))}</summary><div class="ov-shipped-entries">${g.entries.map((e) => row(e, true)).join('')}</div></details></div>`;
+ };
+ const dayHtml = days.map((g) => `<section class="ov-shipped-day"><h3>${esc(g.day)}</h3><div class="ov-shipped-entries">${shipped.groupEntries(g.list).map(groupHtml).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>` : '';
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
// 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.
// adding a second row.
@@ -128,6 +141,10 @@ a{color:var(--accent)}
@@ -128,6 +141,10 @@ a{color:var(--accent)}
.ov-shipped-entry{display:flex;gap:10px;align-items:baseline;padding:8px 12px;flex-wrap:wrap}.ov-shipped-text{flex:1;min-width:0;overflow-wrap:anywhere}
.ov-shipped-entry{display:flex;gap:10px;align-items:baseline;padding:8px 12px;flex-wrap:wrap}.ov-shipped-text{flex:1;min-width:0;overflow-wrap:anywhere}
.ov-shipped-meta{font-size:.75rem;color:var(--text-secondary)}.ov-shipped-hash{font-family:ui-monospace,monospace;font-size:.8rem}
.ov-shipped-meta{font-size:.75rem;color:var(--text-secondary)}.ov-shipped-hash{font-family:ui-monospace,monospace;font-size:.8rem}
.ov-shipped-site{font-size:.72rem;font-weight:700;padding:.05rem .45rem;border-radius:999px;background:rgba(59,130,246,.16)}
.ov-shipped-site{font-size:.72rem;font-weight:700;padding:.05rem .45rem;border-radius:999px;background:rgba(59,130,246,.16)}
+.ov-shipped-count{flex:none;min-width:2em;text-align:center;font-size:.72rem;font-weight:800;padding:.12rem .45rem;border-radius:999px;background:rgba(59,130,246,.22)}
+.ov-shipped-notes{font-size:.82rem;font-weight:600;text-decoration:none;white-space:nowrap}
+.ov-shipped-fold{flex-basis:100%;min-width:0;font-size:.82rem;color:var(--text-secondary)}.ov-shipped-fold summary{cursor:pointer;padding:3px 0}
+.ov-shipped-fold .ov-shipped-entries{margin:4px 0 2px;padding-left:10px;border-left:2px solid var(--border)}
</style>
</style>
</head>
</head>
<body>
<body>
@@ -15,8 +15,8 @@ const BUDGETS = {
@@ -15,8 +15,8 @@ const BUDGETS = {
htmlRawKB: 188, // 188 with the agent layer's rel=alternate/describedby link tags on every public page (SEO-1/SEO-2, 2026-10-29: +0.2 KB); 187 with Bench open (contracts 0.238.0, 2026-10-28); 186 with Cam open (contracts 0.237.0, 2026-10-28); 184.4 with Fun open (contracts 0.236.0, 2026-10-28); 182.7 with Vision and Exchange open (contracts 0.235.0, 2026-10-28); 179 with Meme open (contracts 0.234.0, 2026-10-28); 174.1 with Domains, Report and Design open (contracts 0.226-0.228, 2026-10-28); 168.1 the hero's orbit and rich cards for live streams, recordings and screenshots (2026-10-28: the owner asked for animated visuals and YouTube-style cards); 152.4 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 147.9 with the home's FAQ (six questions and their FAQPage JSON-LD), the numbers panel and the service map's group headings (2026-10-22); 141.2 with Tips and VIP open (contracts 0.200.0, 37 sites; the Opening next section is gone); 134 with Play (contracts 0.172.1); 128.3 with Events, AI and Search added (31 sites, contracts 0.135.0); 122.3 (shared 2.21.0: a real glyph for every product card, where ten drew the 'V'); was 110.6 with 33 live sites (contracts 0.119.0). The home page lists every site, so it grows with the network
htmlRawKB: 188, // 188 with the agent layer's rel=alternate/describedby link tags on every public page (SEO-1/SEO-2, 2026-10-29: +0.2 KB); 187 with Bench open (contracts 0.238.0, 2026-10-28); 186 with Cam open (contracts 0.237.0, 2026-10-28); 184.4 with Fun open (contracts 0.236.0, 2026-10-28); 182.7 with Vision and Exchange open (contracts 0.235.0, 2026-10-28); 179 with Meme open (contracts 0.234.0, 2026-10-28); 174.1 with Domains, Report and Design open (contracts 0.226-0.228, 2026-10-28); 168.1 the hero's orbit and rich cards for live streams, recordings and screenshots (2026-10-28: the owner asked for animated visuals and YouTube-style cards); 152.4 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 147.9 with the home's FAQ (six questions and their FAQPage JSON-LD), the numbers panel and the service map's group headings (2026-10-22); 141.2 with Tips and VIP open (contracts 0.200.0, 37 sites; the Opening next section is gone); 134 with Play (contracts 0.172.1); 128.3 with Events, AI and Search added (31 sites, contracts 0.135.0); 122.3 (shared 2.21.0: a real glyph for every product card, where ten drew the 'V'); was 110.6 with 33 live sites (contracts 0.119.0). The home page lists every site, so it grows with the network
htmlBrotliKB: 34, // 33.1 with Games granted its Bench match capabilities (BENCH-7, 2026-10-29: the home lists what each service may do); 32.0 with Fun, Vision and Exchange open (contracts 0.236.0, 2026-10-28); 31.2 with Domains, Report and Design open (2026-10-28); 30.1 with the Organization saying who OpenVibe is (description, other names, what it is not, 15 sites; 2026-10-28); 28.5 with the FAQ, numbers and groups (2026-10-22); 26.1 with Play; 25.1 (31 sites, contracts 0.135.0); 24.1 (shared 2.21.0, the same glyphs); 20.5 (contracts 0.116.0)
htmlBrotliKB: 34, // 33.1 with Games granted its Bench match capabilities (BENCH-7, 2026-10-29: the home lists what each service may do); 32.0 with Fun, Vision and Exchange open (contracts 0.236.0, 2026-10-28); 31.2 with Domains, Report and Design open (2026-10-28); 30.1 with the Organization saying who OpenVibe is (description, other names, what it is not, 15 sites; 2026-10-28); 28.5 with the FAQ, numbers and groups (2026-10-22); 26.1 with Play; 25.1 (31 sites, contracts 0.135.0); 24.1 (shared 2.21.0, the same glyphs); 20.5 (contracts 0.116.0)
jsFiles: 8, // 7 with boost.js, was 6 (2026-10-13, plan T11 step 3: one more script on every boosted page; it loads route-transition.js on the first move only)
jsFiles: 8, // 7 with boost.js, was 6 (2026-10-13, plan T11 step 3: one more script on every boosted page; it loads route-transition.js on the first move only)
- jsRawKB: 314, // 313.7 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 306.4 openvibe-shared 4.25.0: the menu's orb beside the brand, both balances on phones, messages, the account sheet on phones and the bar stepping away on phones; 298.9 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 293.1 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 279.1 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 269.3 with shared 4.11.0's seasonal navbar (Pumpkin Night and the season badge, +2.6); 249.8 with boost.js (13.1); 228.3
- jsBrotliKB: 79, // 78.9 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 77 openvibe-shared 4.25.0: the menu's orb beside the brand, both balances on phones, messages, the account sheet on phones and the bar stepping away on phones; 73.3 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 70.7 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 68.0 with shared 4.12.0 (light/dark switch, seasonal mark, theme pairs); 64.5 with shared 4.11.0's navbar; 60.3 with boost.js (3.8); 53.9
+ jsRawKB: 324, // 323.2 with openvibe-shared 4.47.2 (the comfort settings in theme-loader 4.45, touch targets 4.46, the grouped updates log 4.47.2); 313.7 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 306.4 openvibe-shared 4.25.0: the menu's orb beside the brand, both balances on phones, messages, the account sheet on phones and the bar stepping away on phones; 298.9 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 293.1 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 279.1 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 269.3 with shared 4.11.0's seasonal navbar (Pumpkin Night and the season badge, +2.6); 249.8 with boost.js (13.1); 228.3
+ jsBrotliKB: 82, // 81.4 with openvibe-shared 4.47.2 (comfort settings, touch targets, the grouped updates log); 78.9 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 77 openvibe-shared 4.25.0: the menu's orb beside the brand, both balances on phones, messages, the account sheet on phones and the bar stepping away on phones; 73.3 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 70.7 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 68.0 with shared 4.12.0 (light/dark switch, seasonal mark, theme pairs); 64.5 with shared 4.11.0's navbar; 60.3 with boost.js (3.8); 53.9
cssFiles: 2, // 1 (Font Awesome, cross-origin)
cssFiles: 2, // 1 (Font Awesome, cross-origin)
externalFiles: 3, // 1 locally; production adds Cloudflare's beacon
externalFiles: 3, // 1 locally; production adds Cloudflare's beacon
};
};
@@ -5,7 +5,7 @@
@@ -5,7 +5,7 @@
const assert = require('assert');
const assert = require('assert');
const http = require('http');
const http = require('http');
const express = require('express');
const express = require('express');
-const { createUpdatesRoutes, serviceFor } = require('../server/updates/routes');
+const { createUpdatesRoutes, serviceFor, renderPage } = require('../server/updates/routes');
const publicCors = require('../server/public-cors');
const publicCors = require('../server/public-cors');
(async () => {
(async () => {
@@ -55,6 +55,15 @@ const publicCors = require('../server/public-cors');
@@ -55,6 +55,15 @@ const publicCors = require('../server/public-cors');
assert.strictEqual(serviceFor('search.openvibe.network'), 'search');
assert.strictEqual(serviceFor('search.openvibe.network'), 'search');
assert.strictEqual(serviceFor('evil.example'), null);
assert.strictEqual(serviceFor('evil.example'), null);
assert.ok(publicCors.isPublicDiscoveryPath('/api/v1/changelog'), 'readable from any origin');
assert.ok(publicCors.isPublicDiscoveryPath('/api/v1/changelog'), 'readable from any origin');
+
+ // The first page groups the same change across sites, as openvibe-shared/shipped.js (4.47.2) does once it runs.
+ const at = '2026-10-29T17:40:00.000Z';
+ const pin = (svc, n) => ({ service: svc, sha: String(n).repeat(40).slice(0, 40), subject: `openvibe-shared v4.45.0 (was v4.43.0) (#${n})`, author: 'OpenVibers', deployed_at: at, url: `https://github.com/OpenVibers/x/commit/${n}` });
+ const grouped = renderPage({ site: null, filtered: false, body: { entries: [pin('fun', 9), pin('chat', 252), pin('cam', 7), { service: 'trade', sha: 'c'.repeat(40), subject: 'Touch targets on phones (fleet audit) (#141)', author: 'OpenVibers', deployed_at: at, url: 'https://github.com/OpenVibers/x/commit/c' }] } });
+ assert.ok(grouped.includes('<span class="ov-shipped-count">3</span><span class="ov-shipped-text">Shared 4.45.0 reached 3 sites'), 'one row for a pin round');
+ assert.ok(grouped.includes('https://github.com/OpenVibers/OpenVibe.Shared/blob/v4.45.0/CHANGELOG.md'), "what's in that release");
+ assert.strictEqual(grouped.split('class="ov-shipped-entry"').length - 1, 4, 'every commit still listed in its fold, plus the single one');
+ assert.ok(!grouped.includes('OpenVibers · '), 'the shared account is not named on each row');
srv.close();
srv.close();
console.log('updates: all checks passed');
console.log('updates: all checks passed');
})().catch((e) => { console.error(e); process.exit(1); });
})().catch((e) => { console.error(e); process.exit(1); });