openvibers/openvibe.media Commits
Home on openvibe-shared 4.39.1: the numbers are tiles and the cards keep their corners; the rail wraps through the kit (#222)
Changes
@@@ -129,7 +129,7 @@ server/storage.orphans.scan (monthly storage orphan report, service-wide, report only),storage.move.cleanup (the alert for placement moves whose delete keeps failing, service-wide)client-ip.js trust proxy = loopback; req.ip is the only client address--(openvibe-shared v4.39.0, openvibe-contracts v0.222.0, openvibe-sdk v0.67.0: pinned release tarballs, installed by npm)++(openvibe-shared v4.39.1, openvibe-contracts v0.222.0, openvibe-sdk v0.67.0: pinned release tarballs, installed by npm)scripts/smoke-test.sh end-to-end smoke test (boots a temp instance)scripts/reconcile-objects.js / object-invariant.js / no-good-copy-report.js object-model operator toolsscripts/media-jobs.js list jobs, run the size-invariant scan (dry run by default), approve/cancel proposals@@@ -757,5 +757,5 @@ restart; afterwards `sudo ovhost rollback media --to <sha>`.<!-- versions:start -->- openvibe-contracts: v0.222.0- openvibe-sdk: v0.67.0--- openvibe-shared: v4.39.0++- openvibe-shared: v4.39.1<!-- versions:end -->@@@ -8,7 +8,7 @@"domain": "openvibe.media","contracts": "openvibe-contracts v0.222.0","sdk": "openvibe-sdk v0.67.0",-- "shared": "openvibe-shared v4.39.0",++ "shared": "openvibe-shared v4.39.1","capabilities": ["media.object.upload","media.object.read",@@@ -18,7 +18,7 @@"multer": "^1.4.5-lts.1","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.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.39.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.39.1","pg": "^8.23.0","sharp": "^0.33.4"},@@@ -2166,9 +2166,9 @@}},"node_modules/openvibe-shared": {-- "version": "4.39.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.39.0",-- "integrity": "sha512-xmEqLmhC+njwb2icgzOX4HibrgpXjHI7m4cI9I7sdYseKWH0xEFpaqlhxAsdKm2ZEbzaKkVo+F5rYn5HUPdwZA==",++ "version": "4.39.1",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.39.1",++ "integrity": "sha512-tuvvMaCFeMejUCu/eq0udJaTf3uH+vPOKkmZQYEimfZh/NnX1RDakZ48cZXG6o9S+M7iAf9ObYr9nIQ/ZCMxxA==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0",@@@ -22,7 +22,7 @@"multer": "^1.4.5-lts.1","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.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.39.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.39.1","pg": "^8.23.0","sharp": "^0.33.4"},@@@ -231,8 +231,11 @@ const TABS = [['all', 'All'], ['videos', 'Videos'], ['clips', 'Clips'], ['emotesconst BROWSE_CSS = `.sc-kinds{display:flex;flex-wrap:wrap;gap:8px;margin:4px 0 0}.sc-kinds .ovk-btn{display:inline-flex;align-items:center;min-height:36px;padding:6px 13px;border:1px solid var(--line);border-radius:999px;background:var(--panel);color:inherit;font-weight:700;font-size:.88rem;text-decoration:none}.sc-kinds .ovk-btn:hover{border-color:var(--acc)}--.sc-media .ovk-i{width:1.15em;height:1.15em;flex:none;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:-.2em}--@media (min-width:760px){.sc-media .ovk-rail-track{grid-auto-flow:row;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));grid-auto-columns:auto;overflow:visible;margin:0;padding:2px 0 10px}.sc-media .ovk-rail-btn{display:none}}++/* :where(): the inline icons' size, without beating the kit's own (a card's fallback icon is 30% of its media). */++:where(.sc-media) .ovk-i{width:1.15em;height:1.15em;flex:none;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:-.2em}++@media (pointer:coarse),(max-width:759px){.sc-kinds .ovk-btn{min-height:44px}}++/* The hero's four numbers sit two by two beside the title, never three and one. */++@media (min-width:600px){.sc-hero-aside .ovk-stats,.sc-hero-vis .ovk-stats{grid-template-columns:repeat(2,minmax(0,1fr))}}header.intro{display:flex;align-items:baseline;gap:.2rem .8rem;flex-wrap:wrap;padding:0 0 .4rem}header.intro h1{font-size:1.15rem;margin:0}header.intro .sub{color:var(--muted);font-size:.85rem}@@@ -357,7 +360,7 @@ function renderPage(tab, page, data, counts, { home = false } = {}) {${data.cards.length ? `<div class="grid">${data.cards.map(renderCard).join('')}</div>` : '<div class="empty">Nothing here yet.</div>'}<div class="pager">${nav(page - 1, '‹ Prev', page <= 1)}<span>Page ${page} / ${pages} · ${data.total.toLocaleString()} items</span>${nav(page + 1, 'Next ›', page >= pages)}</div>`;const body = `${heading}--${first ? `<section class="sc-sec sc-media">${kit.rail({ id: 'media', title: 'Latest public media', more: { href: '/?tab=all', label: `See all ${data.total.toLocaleString('en-US')}` }, items: data.cards.slice(0, 12).map(asCard) })}<p class="sc-kinds" aria-label="Browse by kind">${TABS.filter(([k]) => k !== 'all' && (counts[k] ?? 0) > 0).map(([k, label]) => kit.button({ href: `/?tab=${k}`, label: `${label} ${(counts[k] ?? 0).toLocaleString('en-US')}`, kind: 'quiet', small: true })).join(' ')}</p></section>` : list}++${first ? `<section class="sc-sec sc-media">${kit.rail({ id: 'media', wrap: true, title: 'Latest public media', more: { href: '/?tab=all', label: `See all ${data.total.toLocaleString('en-US')}` }, items: data.cards.slice(0, 12).map(asCard) })}<p class="sc-kinds" aria-label="Browse by kind">${TABS.filter(([k]) => k !== 'all' && (counts[k] ?? 0) > 0).map(([k, label]) => kit.button({ href: `/?tab=${k}`, label: `${label} ${(counts[k] ?? 0).toLocaleString('en-US')}`, kind: 'quiet', small: true })).join(' ')}</p></section>` : list}${first ? developerSections() : ''}${first ? frame.shipped({ service: 'media', title: 'Recently shipped on OpenVibe.Media' }) : ''}`;return pageFrame.page({@@@ -23,7 +23,7 @@ const { measure, check, format } = require('openvibe-shared/perf-budget');jsRawKB: 290, // 286 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; 279.9 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 274.1 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 260.1 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 248.9 Shared 4.12.0: the light/dark switch, the seasonal mark and the theme pairs; 241 with openvibe-shared 4.9.0 (status text twins in every theme, 2026-10-23); 230.9 (was 197.7)jsBrotliKB: 72, // 70.1 Shared 4.33.0 navbar glyph widths; 68.2 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 66.7 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 64.1 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 61.5 Shared 4.12.0: the light/dark switch, the seasonal mark and the theme pairs; 56.1 with openvibe-shared 4.10.0 (Report a problem in the footer, 2026-10-24); 53.7 (was 45.6)cssFiles: 2, // 1 (showcase.css)-- cssRawKB: 44, // 38.6 the front page's latest media as a rail of kit cards (room.css, 15.7 KB raw): the page shed a 48-card grid, 17 of its 23 phone screens; 23.3 openvibe-shared 4.26.1 (hero visual); 17.8 with Shared 4.13.0 (2026-10-26); 11.6 same-origin (showcase.css; was 0)++ cssRawKB: 47, // 45.4 with openvibe-shared 4.39.1: room.css carries the kit's tokens and numbers strip (+2.8 KB), so this page's cards keep their corners and colours and its numbers are tiles without loading kit.css (25.6 KB); 38.6 the front page's latest media as a rail of kit cards (room.css, 15.7 KB raw): the page shed a 48-card grid, 17 of its 23 phone screens; 23.3 openvibe-shared 4.26.1 (hero visual); 17.8 with Shared 4.13.0 (2026-10-26); 11.6 same-origin (showcase.css; was 0)externalFiles: 3, // 2};@@@ -171,7 +171,7 @@ const http = require('http');assert.ok(!r.body.includes('class="pager"'), 'no pager on the front page');const index = await get('/?tab=all', nav);for (const k of ['all', 'videos', 'clips', 'emotes', 'sounds', 'thumbnails', 'files']) assert.ok(index.body.includes(`href="/?tab=${k}"`), `tab ${k}`);-- assert.ok(index.body.includes('class="pager"') && index.body.includes('Page 1 /') && !index.body.includes('sc-hero'), 'the index: every tab, the pager, no hero');++ assert.ok(index.body.includes('class="pager"') && index.body.includes('Page 1 /') && !index.body.includes('class="sc-hero'), 'the index: every tab, the pager, no hero');assert.ok(r.body.includes('<meta name="robots" content="index, follow">'));// The front page is also Media's public home for developers: what it does, how an app uses it, the limits it// enforces (limitsOf, the same numbers as /limits.json) and the questions, which are FAQPage data too.@@@ -182,11 +182,11 @@ const http = require('http');assert.ok(faq && faq.mainEntity.length >= 5, 'FAQPage data');for (const q of faq.mainEntity) assert.ok(r.body.includes(`<summary>${q.name.replace(/'/g, ''')}</summary>`) || r.body.includes(`<summary>${q.name}</summary>`), `the page asks "${q.name}"`);r = await get('/?tab=videos', nav);-- assert.ok(!r.body.includes('sc-hero') && h1s(r.body) === 1, 'the hero is the front page\'s only');++ assert.ok(!r.body.includes('class="sc-hero') && h1s(r.body) === 1, 'the hero is the front page\'s only');assert.ok(!r.body.includes('id="questions"') && !r.body.includes('FAQPage'), 'the developer half is the front page\'s only');r = await get('/?page=2', nav);assert.ok(!r.body.includes('application/ld+json'), 'a later (noindex) page carries none');-- assert.ok(!r.body.includes('sc-hero') && h1s(r.body) === 1, 'no hero after page 1');++ assert.ok(!r.body.includes('class="sc-hero') && h1s(r.body) === 1, 'no hero after page 1');assert.ok(r.body.includes('<meta name="robots" content="noindex, follow">'));assert.ok(r.body.includes('Page 2 /') && r.body.includes('href="/?tab=all&page=1"'), 'the pager links back');r = await get('/og-image.png', {});
@@ -129,7 +129,7 @@ server/
@@ -129,7 +129,7 @@ server/
storage.orphans.scan (monthly storage orphan report, service-wide, report only),
storage.orphans.scan (monthly storage orphan report, service-wide, report only),
storage.move.cleanup (the alert for placement moves whose delete keeps failing, service-wide)
storage.move.cleanup (the alert for placement moves whose delete keeps failing, service-wide)
client-ip.js trust proxy = loopback; req.ip is the only client address
client-ip.js trust proxy = loopback; req.ip is the only client address
-(openvibe-shared v4.39.0, openvibe-contracts v0.222.0, openvibe-sdk v0.67.0: pinned release tarballs, installed by npm)
+(openvibe-shared v4.39.1, openvibe-contracts v0.222.0, openvibe-sdk v0.67.0: pinned release tarballs, installed by npm)
scripts/smoke-test.sh end-to-end smoke test (boots a temp instance)
scripts/smoke-test.sh end-to-end smoke test (boots a temp instance)
scripts/reconcile-objects.js / object-invariant.js / no-good-copy-report.js object-model operator tools
scripts/reconcile-objects.js / object-invariant.js / no-good-copy-report.js object-model operator tools
scripts/media-jobs.js list jobs, run the size-invariant scan (dry run by default), approve/cancel proposals
scripts/media-jobs.js list jobs, run the size-invariant scan (dry run by default), approve/cancel proposals
@@ -757,5 +757,5 @@ restart; afterwards `sudo ovhost rollback media --to <sha>`.
@@ -757,5 +757,5 @@ restart; afterwards `sudo ovhost rollback media --to <sha>`.
<!-- versions:start -->
<!-- versions:start -->
- openvibe-contracts: v0.222.0
- openvibe-contracts: v0.222.0
- openvibe-sdk: v0.67.0
- openvibe-sdk: v0.67.0
-- openvibe-shared: v4.39.0
+- openvibe-shared: v4.39.1
<!-- versions:end -->
<!-- versions:end -->
@@ -8,7 +8,7 @@
@@ -8,7 +8,7 @@
"domain": "openvibe.media",
"domain": "openvibe.media",
"contracts": "openvibe-contracts v0.222.0",
"contracts": "openvibe-contracts v0.222.0",
"sdk": "openvibe-sdk v0.67.0",
"sdk": "openvibe-sdk v0.67.0",
- "shared": "openvibe-shared v4.39.0",
+ "shared": "openvibe-shared v4.39.1",
"capabilities": [
"capabilities": [
"media.object.upload",
"media.object.upload",
"media.object.read",
"media.object.read",
@@ -18,7 +18,7 @@
@@ -18,7 +18,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.222.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.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.39.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.39.1",
"pg": "^8.23.0",
"pg": "^8.23.0",
"sharp": "^0.33.4"
"sharp": "^0.33.4"
},
},
@@ -2166,9 +2166,9 @@
@@ -2166,9 +2166,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "4.39.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.39.0",
- "integrity": "sha512-xmEqLmhC+njwb2icgzOX4HibrgpXjHI7m4cI9I7sdYseKWH0xEFpaqlhxAsdKm2ZEbzaKkVo+F5rYn5HUPdwZA==",
+ "version": "4.39.1",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.39.1",
+ "integrity": "sha512-tuvvMaCFeMejUCu/eq0udJaTf3uH+vPOKkmZQYEimfZh/NnX1RDakZ48cZXG6o9S+M7iAf9ObYr9nIQ/ZCMxxA==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
@@ -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.222.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.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.39.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.39.1",
"pg": "^8.23.0",
"pg": "^8.23.0",
"sharp": "^0.33.4"
"sharp": "^0.33.4"
},
},
@@ -231,8 +231,11 @@ const TABS = [['all', 'All'], ['videos', 'Videos'], ['clips', 'Clips'], ['emotes
@@ -231,8 +231,11 @@ const TABS = [['all', 'All'], ['videos', 'Videos'], ['clips', 'Clips'], ['emotes
const BROWSE_CSS = `
const BROWSE_CSS = `
.sc-kinds{display:flex;flex-wrap:wrap;gap:8px;margin:4px 0 0}
.sc-kinds{display:flex;flex-wrap:wrap;gap:8px;margin:4px 0 0}
.sc-kinds .ovk-btn{display:inline-flex;align-items:center;min-height:36px;padding:6px 13px;border:1px solid var(--line);border-radius:999px;background:var(--panel);color:inherit;font-weight:700;font-size:.88rem;text-decoration:none}.sc-kinds .ovk-btn:hover{border-color:var(--acc)}
.sc-kinds .ovk-btn{display:inline-flex;align-items:center;min-height:36px;padding:6px 13px;border:1px solid var(--line);border-radius:999px;background:var(--panel);color:inherit;font-weight:700;font-size:.88rem;text-decoration:none}.sc-kinds .ovk-btn:hover{border-color:var(--acc)}
-.sc-media .ovk-i{width:1.15em;height:1.15em;flex:none;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:-.2em}
-@media (min-width:760px){.sc-media .ovk-rail-track{grid-auto-flow:row;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));grid-auto-columns:auto;overflow:visible;margin:0;padding:2px 0 10px}.sc-media .ovk-rail-btn{display:none}}
+/* :where(): the inline icons' size, without beating the kit's own (a card's fallback icon is 30% of its media). */
+:where(.sc-media) .ovk-i{width:1.15em;height:1.15em;flex:none;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:-.2em}
+@media (pointer:coarse),(max-width:759px){.sc-kinds .ovk-btn{min-height:44px}}
+/* The hero's four numbers sit two by two beside the title, never three and one. */
+@media (min-width:600px){.sc-hero-aside .ovk-stats,.sc-hero-vis .ovk-stats{grid-template-columns:repeat(2,minmax(0,1fr))}}
header.intro{display:flex;align-items:baseline;gap:.2rem .8rem;flex-wrap:wrap;padding:0 0 .4rem}
header.intro{display:flex;align-items:baseline;gap:.2rem .8rem;flex-wrap:wrap;padding:0 0 .4rem}
header.intro h1{font-size:1.15rem;margin:0}
header.intro h1{font-size:1.15rem;margin:0}
header.intro .sub{color:var(--muted);font-size:.85rem}
header.intro .sub{color:var(--muted);font-size:.85rem}
@@ -357,7 +360,7 @@ function renderPage(tab, page, data, counts, { home = false } = {}) {
@@ -357,7 +360,7 @@ function renderPage(tab, page, data, counts, { home = false } = {}) {
${data.cards.length ? `<div class="grid">${data.cards.map(renderCard).join('')}</div>` : '<div class="empty">Nothing here yet.</div>'}
${data.cards.length ? `<div class="grid">${data.cards.map(renderCard).join('')}</div>` : '<div class="empty">Nothing here yet.</div>'}
<div class="pager">${nav(page - 1, '‹ Prev', page <= 1)}<span>Page ${page} / ${pages} · ${data.total.toLocaleString()} items</span>${nav(page + 1, 'Next ›', page >= pages)}</div>`;
<div class="pager">${nav(page - 1, '‹ Prev', page <= 1)}<span>Page ${page} / ${pages} · ${data.total.toLocaleString()} items</span>${nav(page + 1, 'Next ›', page >= pages)}</div>`;
const body = `${heading}
const body = `${heading}
-${first ? `<section class="sc-sec sc-media">${kit.rail({ id: 'media', title: 'Latest public media', more: { href: '/?tab=all', label: `See all ${data.total.toLocaleString('en-US')}` }, items: data.cards.slice(0, 12).map(asCard) })}<p class="sc-kinds" aria-label="Browse by kind">${TABS.filter(([k]) => k !== 'all' && (counts[k] ?? 0) > 0).map(([k, label]) => kit.button({ href: `/?tab=${k}`, label: `${label} ${(counts[k] ?? 0).toLocaleString('en-US')}`, kind: 'quiet', small: true })).join(' ')}</p></section>` : list}
+${first ? `<section class="sc-sec sc-media">${kit.rail({ id: 'media', wrap: true, title: 'Latest public media', more: { href: '/?tab=all', label: `See all ${data.total.toLocaleString('en-US')}` }, items: data.cards.slice(0, 12).map(asCard) })}<p class="sc-kinds" aria-label="Browse by kind">${TABS.filter(([k]) => k !== 'all' && (counts[k] ?? 0) > 0).map(([k, label]) => kit.button({ href: `/?tab=${k}`, label: `${label} ${(counts[k] ?? 0).toLocaleString('en-US')}`, kind: 'quiet', small: true })).join(' ')}</p></section>` : list}
${first ? developerSections() : ''}
${first ? developerSections() : ''}
${first ? frame.shipped({ service: 'media', title: 'Recently shipped on OpenVibe.Media' }) : ''}`;
${first ? frame.shipped({ service: 'media', title: 'Recently shipped on OpenVibe.Media' }) : ''}`;
return pageFrame.page({
return pageFrame.page({
@@ -23,7 +23,7 @@ const { measure, check, format } = require('openvibe-shared/perf-budget');
@@ -23,7 +23,7 @@ const { measure, check, format } = require('openvibe-shared/perf-budget');
jsRawKB: 290, // 286 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; 279.9 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 274.1 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 260.1 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 248.9 Shared 4.12.0: the light/dark switch, the seasonal mark and the theme pairs; 241 with openvibe-shared 4.9.0 (status text twins in every theme, 2026-10-23); 230.9 (was 197.7)
jsRawKB: 290, // 286 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; 279.9 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 274.1 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 260.1 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 248.9 Shared 4.12.0: the light/dark switch, the seasonal mark and the theme pairs; 241 with openvibe-shared 4.9.0 (status text twins in every theme, 2026-10-23); 230.9 (was 197.7)
jsBrotliKB: 72, // 70.1 Shared 4.33.0 navbar glyph widths; 68.2 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 66.7 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 64.1 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 61.5 Shared 4.12.0: the light/dark switch, the seasonal mark and the theme pairs; 56.1 with openvibe-shared 4.10.0 (Report a problem in the footer, 2026-10-24); 53.7 (was 45.6)
jsBrotliKB: 72, // 70.1 Shared 4.33.0 navbar glyph widths; 68.2 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 66.7 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 64.1 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 61.5 Shared 4.12.0: the light/dark switch, the seasonal mark and the theme pairs; 56.1 with openvibe-shared 4.10.0 (Report a problem in the footer, 2026-10-24); 53.7 (was 45.6)
cssFiles: 2, // 1 (showcase.css)
cssFiles: 2, // 1 (showcase.css)
- cssRawKB: 44, // 38.6 the front page's latest media as a rail of kit cards (room.css, 15.7 KB raw): the page shed a 48-card grid, 17 of its 23 phone screens; 23.3 openvibe-shared 4.26.1 (hero visual); 17.8 with Shared 4.13.0 (2026-10-26); 11.6 same-origin (showcase.css; was 0)
+ cssRawKB: 47, // 45.4 with openvibe-shared 4.39.1: room.css carries the kit's tokens and numbers strip (+2.8 KB), so this page's cards keep their corners and colours and its numbers are tiles without loading kit.css (25.6 KB); 38.6 the front page's latest media as a rail of kit cards (room.css, 15.7 KB raw): the page shed a 48-card grid, 17 of its 23 phone screens; 23.3 openvibe-shared 4.26.1 (hero visual); 17.8 with Shared 4.13.0 (2026-10-26); 11.6 same-origin (showcase.css; was 0)
externalFiles: 3, // 2
externalFiles: 3, // 2
};
};
@@ -171,7 +171,7 @@ const http = require('http');
@@ -171,7 +171,7 @@ const http = require('http');
assert.ok(!r.body.includes('class="pager"'), 'no pager on the front page');
assert.ok(!r.body.includes('class="pager"'), 'no pager on the front page');
const index = await get('/?tab=all', nav);
const index = await get('/?tab=all', nav);
for (const k of ['all', 'videos', 'clips', 'emotes', 'sounds', 'thumbnails', 'files']) assert.ok(index.body.includes(`href="/?tab=${k}"`), `tab ${k}`);
for (const k of ['all', 'videos', 'clips', 'emotes', 'sounds', 'thumbnails', 'files']) assert.ok(index.body.includes(`href="/?tab=${k}"`), `tab ${k}`);
- assert.ok(index.body.includes('class="pager"') && index.body.includes('Page 1 /') && !index.body.includes('sc-hero'), 'the index: every tab, the pager, no hero');
+ assert.ok(index.body.includes('class="pager"') && index.body.includes('Page 1 /') && !index.body.includes('class="sc-hero'), 'the index: every tab, the pager, no hero');
assert.ok(r.body.includes('<meta name="robots" content="index, follow">'));
assert.ok(r.body.includes('<meta name="robots" content="index, follow">'));
// The front page is also Media's public home for developers: what it does, how an app uses it, the limits it
// The front page is also Media's public home for developers: what it does, how an app uses it, the limits it
// enforces (limitsOf, the same numbers as /limits.json) and the questions, which are FAQPage data too.
// enforces (limitsOf, the same numbers as /limits.json) and the questions, which are FAQPage data too.
@@ -182,11 +182,11 @@ const http = require('http');
@@ -182,11 +182,11 @@ const http = require('http');
assert.ok(faq && faq.mainEntity.length >= 5, 'FAQPage data');
assert.ok(faq && faq.mainEntity.length >= 5, 'FAQPage data');
for (const q of faq.mainEntity) assert.ok(r.body.includes(`<summary>${q.name.replace(/'/g, ''')}</summary>`) || r.body.includes(`<summary>${q.name}</summary>`), `the page asks "${q.name}"`);
for (const q of faq.mainEntity) assert.ok(r.body.includes(`<summary>${q.name.replace(/'/g, ''')}</summary>`) || r.body.includes(`<summary>${q.name}</summary>`), `the page asks "${q.name}"`);
r = await get('/?tab=videos', nav);
r = await get('/?tab=videos', nav);
- assert.ok(!r.body.includes('sc-hero') && h1s(r.body) === 1, 'the hero is the front page\'s only');
+ assert.ok(!r.body.includes('class="sc-hero') && h1s(r.body) === 1, 'the hero is the front page\'s only');
assert.ok(!r.body.includes('id="questions"') && !r.body.includes('FAQPage'), 'the developer half is the front page\'s only');
assert.ok(!r.body.includes('id="questions"') && !r.body.includes('FAQPage'), 'the developer half is the front page\'s only');
r = await get('/?page=2', nav);
r = await get('/?page=2', nav);
assert.ok(!r.body.includes('application/ld+json'), 'a later (noindex) page carries none');
assert.ok(!r.body.includes('application/ld+json'), 'a later (noindex) page carries none');
- assert.ok(!r.body.includes('sc-hero') && h1s(r.body) === 1, 'no hero after page 1');
+ assert.ok(!r.body.includes('class="sc-hero') && h1s(r.body) === 1, 'no hero after page 1');
assert.ok(r.body.includes('<meta name="robots" content="noindex, follow">'));
assert.ok(r.body.includes('<meta name="robots" content="noindex, follow">'));
assert.ok(r.body.includes('Page 2 /') && r.body.includes('href="/?tab=all&page=1"'), 'the pager links back');
assert.ok(r.body.includes('Page 2 /') && r.body.includes('href="/?tab=all&page=1"'), 'the pager links back');
r = await get('/og-image.png', {});
r = await get('/og-image.png', {});