openvibers/openvibe.report Commits

openvibe-shared v4.228.0 (was v4.225.0) (#208)

OP OpenVibers committed 15 h ago · 478439f1ae · 8 files, +33 −24

* openvibe-shared v4.228.0 (was v4.225.0)

Opened by scripts/pin-bump.js in OpenVibers/OpenVibe.Shared after the v4.228.0 release.

* Bench: the leaderboard's parts as tabs on a phone

* Home: the pick block is Shared's showcase.pick(), tabs on a phone; the old chips only jumped, so every pane showed

* Tests: an unversioned asset revalidates after five minutes (openvibe-shared 4.227.0 cache-policy)

Changes

ddiff --git a/README.md b/README.md
iindex 4249ca3..0806957 100644
---- a/README.md
++++ b/README.md
@@@ -262,7 +262,7 @@ HMAC. See [SECURITY.md](SECURITY.md).
<!-- versions:start -->
- openvibe-contracts: v0.417.0
- openvibe-sdk: v0.161.0
--- openvibe-shared: v4.225.0
++- openvibe-shared: v4.228.0
- openvibe-publishing: v1.7.0
<!-- versions:end -->
ddiff --git a/STATUS.json b/STATUS.json
iindex d19b073..d479faf 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -10,12 +10,12 @@
"domain": "openvibe.report",
"contracts": "openvibe-contracts v0.417.0",
"sdk": "openvibe-sdk v0.161.0",
-- "shared": "openvibe-shared v4.225.0",
++ "shared": "openvibe-shared v4.228.0",
"packages": [
"openvibe-contracts v0.417.0",
"openvibe-publishing v1.7.0",
"openvibe-sdk v0.161.0",
-- "openvibe-shared v4.225.0",
++ "openvibe-shared v4.228.0",
"pg v8.23.1",
"pglite v0.5.13 (dev)"
],
ddiff --git a/package-lock.json b/package-lock.json
iindex 3dcb2fb..1657cc0 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -16,7 +16,7 @@
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.417.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.7.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.161.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.225.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.228.0",
"pg": "^8.23.1"
},
"devDependencies": {
@@@ -1200,9 +1200,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "4.225.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.225.0",
-- "integrity": "sha512-zuZIgYODwJJ7UpDBTVnT+czEI5l6YFWqt1nbfbPeEsW6/NlmDZHJWPSGpBv223jRpvhJYH7Duea6nuRr8KZ3sg==",
++ "version": "4.228.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.228.0",
++ "integrity": "sha512-HR0Hac/K+qKmwMf2QZWCFxW+uVja36nyAtxVbNFgPGUDFdmWhmb6RMoP/CkU0+GSCPsC50k6aTS+oPx2czDgGg==",
"license": "MIT",
"dependencies": {
"clean-css": "^5.3.3",
ddiff --git a/package.json b/package.json
iindex 28056f0..0cfc5d9 100644
---- a/package.json
++++ b/package.json
@@@ -22,7 +22,7 @@
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.417.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.7.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.161.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.225.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.228.0",
"pg": "^8.23.1"
},
"engines": {
ddiff --git a/server/views/ai-comparison.js b/server/views/ai-comparison.js
iindex bb53a9a..49dfe93 100644
---- a/server/views/ai-comparison.js
++++ b/server/views/ai-comparison.js
@@@ -8,6 +8,7 @@
// - speed and uptime are measured on the Exchange's real traffic, shown only where traffic went.
// A column or section earns its place: it shows when some row has a value, and a missing value reads in words.
const kit = require('openvibe-shared/kit');
++const { parts, PARTS_CSS } = require('openvibe-shared/showcase');
const marks = require('openvibe-shared/ai-marks');
const { asset } = require('../assets');
const { layout } = require('./index');
@@@ -110,7 +111,7 @@ function board(rows, { full = false } = {}) {
}
function page(title, description, body, path) {
-- return layout({ title, description, body: `<link rel="stylesheet" href="${asset('css/ai-comparison.css')}"><style>${marks.CSS}</style><div class="ai-page">${body}</div>`, active: '/ai', path });
++ return layout({ title, description, body: `<link rel="stylesheet" href="${asset('css/ai-comparison.css')}"><style>${marks.CSS}\n${PARTS_CSS}</style><div class="ai-page">${body}</div>`, active: '/ai', path });
}
const dataLinks = () => `<p class="ai-data">Data: <a href="/api/v1/ai/leaderboard">JSON</a> · <a href="/api/v1/ai/leaderboard?format=csv&amp;limit=100">CSV</a> · <a href="/ai/method">How it is measured</a></p>`;
@@@ -146,13 +147,17 @@ function hub({ rows = [], picks = [], providers = [], others = 0, month = new Da
+ kit.stats([[ratedRows.length, 'rated'], [rows.length, 'models listed'], [providerNames.size, 'providers'], [measuredOffers.length, 'measured endpoints']], { label: 'The leaderboard in numbers' })
+ `</header>`
+ (pickList || pending.length ? kit.section({ title: 'Top picks', body: `${pickList ? `<ul class="ai-picks">${pickList}</ul>` : ''}${pending.length ? `<p class="ai-fine">Not enough data yet for ${esc(pending.join(', '))}.</p>` : ''}` }) : '')
-- + kit.section({ title: 'Leaderboard', count: rows.length, actions: [act('/ai/leaderboard', rows.length > 25 ? `All ${rows.length} models` : 'Filter and sort')], body: board(rows.slice(0, 25)) + (others ? `<p class="ai-fine">Chat models only. ${others} embedding and speech ${others === 1 ? 'model is' : 'models are'} in the <a href="/ai/leaderboard?type=all">full list</a>.</p>` : '') + dataLinks() })
-- + (arena && (arena.answers || []).length ? kit.section({ title: 'One prompt, many answers', body: `<p class="ai-prompt">${esc(arena.prompt || '')}</p><div class="ai-answers">${arena.answers.slice(0, 3).map((a) => `<article>${modelCell({ id: a.model || 'Model', maker: a.maker })}<p>${esc(a.text || '')}</p></article>`).join('')}</div>` }) : '')
-- + (tasks.length ? kit.section({ title: 'Boards by task', body: `<ul class="ai-chips">${tasks.slice(0, 16).map((x) => `<li><a href="/ai/${encodeURIComponent(x.id)}">${esc(x.name || x.id)}</a></li>`).join('')}</ul>` }) : '')
-- + (measuredOffers.length ? kit.section({ title: 'Providers', actions: [act('/ai/providers', 'Every provider')], body: providerTable(measuredOffers.slice(0, 6)) }) : '')
-- + (pairs.length ? kit.section({ title: 'Popular comparisons', body: `<ul class="ai-chips">${pairs.map(([a, b]) => `<li><a href="/ai/compare/${encodeURIComponent(a.id)}-vs-${encodeURIComponent(b.id)}">${esc(shortName(a.id))} vs ${esc(shortName(b.id))}</a></li>`).join('')}</ul>` }) : '')
-- + (changed.length ? kit.section({ title: 'What changed this month', body: `<ul>${changed.slice(0, 8).map((x) => `<li>${esc(x)}</li>`).join('')}</ul>` }) : '')
-- + kit.section({ title: 'Questions', body: faq.map(([q, a]) => kit.fold({ title: q, body: `<p>${esc(a)}</p>` })).join('') })
++ // A phone shows the parts below the top picks one at a time as tabs (the owner, 11-11: "self-contained tabs, so
++ // the page isn't so freaking huge … at least on mobile"); every part stays in the HTML, a wide screen stacks them.
++ + parts({ name: 'board', label: 'The leaderboard\'s parts', items: [
++ { id: 'leaderboard', label: 'Leaderboard', count: rows.length, html: kit.section({ title: 'Leaderboard', count: rows.length, actions: [act('/ai/leaderboard', rows.length > 25 ? `All ${rows.length} models` : 'Filter and sort')], body: board(rows.slice(0, 25)) + (others ? `<p class="ai-fine">Chat models only. ${others} embedding and speech ${others === 1 ? 'model is' : 'models are'} in the <a href="/ai/leaderboard?type=all">full list</a>.</p>` : '') + dataLinks() }) },
++ { id: 'answers', label: 'Answers', html: (arena && (arena.answers || []).length ? kit.section({ title: 'One prompt, many answers', body: `<p class="ai-prompt">${esc(arena.prompt || '')}</p><div class="ai-answers">${arena.answers.slice(0, 3).map((a) => `<article>${modelCell({ id: a.model || 'Model', maker: a.maker })}<p>${esc(a.text || '')}</p></article>`).join('')}</div>` }) : '') },
++ { id: 'tasks', label: 'By task', count: tasks.length || '', html: (tasks.length ? kit.section({ title: 'Boards by task', body: `<ul class="ai-chips">${tasks.slice(0, 16).map((x) => `<li><a href="/ai/${encodeURIComponent(x.id)}">${esc(x.name || x.id)}</a></li>`).join('')}</ul>` }) : '') },
++ { id: 'providers', label: 'Providers', count: measuredOffers.length || '', html: (measuredOffers.length ? kit.section({ title: 'Providers', actions: [act('/ai/providers', 'Every provider')], body: providerTable(measuredOffers.slice(0, 6)) }) : '') },
++ { id: 'compare', label: 'Compare', html: (pairs.length ? kit.section({ title: 'Popular comparisons', body: `<ul class="ai-chips">${pairs.map(([a, b]) => `<li><a href="/ai/compare/${encodeURIComponent(a.id)}-vs-${encodeURIComponent(b.id)}">${esc(shortName(a.id))} vs ${esc(shortName(b.id))}</a></li>`).join('')}</ul>` }) : '') },
++ { id: 'changes', label: 'Changes', html: (changed.length ? kit.section({ title: 'What changed this month', body: `<ul>${changed.slice(0, 8).map((x) => `<li>${esc(x)}</li>`).join('')}</ul>` }) : '') },
++ { id: 'questions', label: 'Questions', html: kit.section({ title: 'Questions', body: faq.map(([q, a]) => kit.fold({ title: q, body: `<p>${esc(a)}</p>` })).join('') }) },
++ ] })
+ `<script type="application/ld+json">${JSON.stringify({ '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq.map(([q, a]) => ({ '@type': 'Question', name: q, acceptedAnswer: { '@type': 'Answer', text: a } })) }).replace(/</g, '\\u003c')}</script>`
+ `<script type="application/ld+json">${JSON.stringify({ '@context': 'https://schema.org', '@type': 'Dataset', name: 'OpenVibe AI model leaderboard', url: 'https://openvibe.report/ai', variableMeasured: ['Bench rating', 'Provider price', 'Provider reply time', 'Provider uptime'], distribution: { '@type': 'DataDownload', contentUrl: 'https://openvibe.report/api/v1/ai/leaderboard', encodingFormat: 'application/json' } }).replace(/</g, '\\u003c')}</script>`;
return page(`AI model leaderboard — ${month}`, `${rows.length} AI models compared: ratings from blind votes, provider prices and measured speed.`, body, '/ai');
ddiff --git a/server/views/short.js b/server/views/short.js
iindex 3d62884..22c4933 100644
---- a/server/views/short.js
++++ b/server/views/short.js
@@@ -1,4 +1,5 @@
'use strict';
++const { pick } = require('openvibe-shared/showcase');
/**
* The home on a phone (the owner's short-pages rule, 2026-10-30): one or two screens, app-like.
*
@@@ -21,13 +22,11 @@ const esc = (s) => String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</
*
* pickBlock({ key, label, head, panes }) panes: [{ id, label, html }]
*/
--function pickBlock({ key, label, head = '', panes }) {
-- // A pane with nothing to show is left out, so the chips and the rules that show them match the page.
-- panes = (panes || []).filter((p) => p.html);
-- if (!panes.length) return '';
-- const chips = panes.map((p, i) => '<input type="radio" name="pick-' + esc(key) + '" id="pk-' + esc(key) + '-' + esc(p.id) + '" value="' + esc(p.id) + '"' + (i === 0 ? ' checked' : '') + '><label for="pk-' + esc(key) + '-' + esc(p.id) + '">' + esc(p.label) + '</label>').join('');
-- const body = panes.map((p) => '<div class="pick-pane" id="' + esc(p.id) + '">' + p.html + '</div>').join('');
-- return '<div class="pick" id="pick-' + esc(key) + '"><div class="pick-bar"><div class="pick-head"><h2>' + esc(head || label) + '</h2></div><div class="pick-row" role="radiogroup" aria-label="' + esc(label) + '">' + chips + '</div></div><div class="pick-body">' + body + '</div></div>';
++// 4.228.0: Shared's showcase.pick() draws the block (tabs on a phone, every pane in the page, a link to a pane opens
++// it); this copy only jumped to a pane with chips, so a phone showed them all (the owner, 11-12). The ids are this
++// site's own, so its links keep working.
++function pickBlock({ key, label, head = '', headId = '', bodyClass = '', panes, checked = '' }) {
++ return pick({ key, label, head, headId, bodyClass, checked: checked ? checked : '', panes: (panes || []).map((p) => ({ id: p.id, label: p.label, html: p.html })) });
}
/** The rule that shows the picked block of a pick block: one per block, so the CSS is written for the page. */
ddiff --git a/test/ai-pages.test.js b/test/ai-pages.test.js
iindex c9a6844..788cf6f 100644
---- a/test/ai-pages.test.js
++++ b/test/ai-pages.test.js
@@@ -42,6 +42,11 @@ const { boot, check, done, stubFetch, jsonResponse } = require('./helpers/boot')
assert.strictEqual(r.status, 200);
for (const s of ['AI model leaderboard', 'openai/gpt-4o', 'Best overall', 'Dataset', 'FAQPage']) assert.ok(r.text.includes(s), s);
assert.ok(!r.text.includes('Page <design>'));
++ // A phone shows the parts below the top picks as tabs, the leaderboard first; every part stays in the HTML.
++ assert.ok(r.text.includes('<div class="ov-parts" data-parts='), 'the parts are tabs on a phone');
++ assert.ok(r.text.includes('<label for="ovp-board-leaderboard">Leaderboard <span class="ov-parts-n">'), 'the leaderboard tab names its count');
++ assert.ok(r.text.indexOf('ovp-board-leaderboard') < r.text.indexOf('ovp-board-questions'), 'the leaderboard first, the questions last');
++ assert.ok(r.text.includes('.ov-parts-bar{display:flex}'), 'its look is in the page, so nothing jumps');
});
await check('Shared Elo shows ranks, intervals and win rates', async () => {
ddiff --git a/test/health.test.js b/test/health.test.js
iindex a13ff80..744fba4 100644
---- a/test/health.test.js
++++ b/test/health.test.js
@@@ -46,7 +46,7 @@ const { boot, check, done } = require('./helpers/boot');
for (const rel of ['css/app.css', 'js/app.js']) {
const plain = await healthy.get(`/${rel}`);
assert.strictEqual(plain.status, 200);
-- assert.strictEqual(plain.headers.get('cache-control'), 'public, max-age=300, stale-while-revalidate=86400');
++ assert.strictEqual(plain.headers.get('cache-control'), 'public, max-age=300, stale-while-revalidate=300');
const pinned = await healthy.get(`/${rel}?v=${assetVersion(rel)}`);
assert.strictEqual(pinned.status, 200);
assert.strictEqual(pinned.headers.get('cache-control'), 'public, max-age=31536000, immutable');