openvibers/openvibe.reviews Commits

Reviews: a product home built with the showcase kit above the entity list. (#11)

OP OpenVibers committed 7 Oct 2026 · ce29e840d3 · 6 files, +35 −16

The front page now opens with what OpenVibe.Reviews is for, built with the shared showcase kit (openvibe-shared v2.10.1), above the entity list it already had.

- server/render/views.js: home() renders a showcase hero ("Review scores you can trace": named sources, every number shows where it came from and when it was read, no score when no source said anything; actions Browse what is reviewed / How it works) and a "What every page shows" features grid (provenance, a checkable aggregate with listed inputs and reasoned exclusions, changes kept on the record, summaries by people that never carry a rating). The copy restates the README and the About page; nothing new is claimed. The entity list keeps its feeds and gains the anchor #entities; the Editor desk link moves beside the feeds.
- server/render/layout.js: renderPage forwards styles to openvibe-publishing renderDocument, so a page can ask for an openvibe-shared stylesheet.
- server/http/pages.js: / asks for showcase.css.
- test/perf-budget.test.js: budgets raised for the showcase (html 17.8 -> 23.7 KB raw, 4.6 -> 5.9 KB br; css 6.7 -> 18.2 KB raw, 1.7 -> 4.3 KB br for showcase.css). The same cost every product home that adopted the kit took.
- package.json / lockfile: openvibe-shared v2.10.1.

Tests: ov test test/layout.test.js test/perf-budget.test.js test/open-redirect.test.js: 3 passed.

Changes

ddiff --git a/package-lock.json b/package-lock.json
iindex 5b22d44..5d97524 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -17,7 +17,7 @@
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.96.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.3.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",
"pg": "^8.23.0"
},
"devDependencies": {
@@@ -922,9 +922,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "2.6.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",
-- "integrity": "sha512-IKKvWTM/9UonQ4msrzM4yir0QcTUNbhyqeN1r5ywWavU4VTsL1sI5QdHQlxge7BAsMrQ4OvMEPlHjuAfUJM3rg==",
++ "version": "2.10.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",
++ "integrity": "sha512-6fUOw22bSvxg0K33BefAyAkNRkETe9euLNT0CHs2mLIvzYR1q38KiHRUQIWj8FESbVANF0pXMojhBz5TeqpMtg==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/package.json b/package.json
iindex 7e1ae89..cd7ea75 100644
---- a/package.json
++++ b/package.json
@@@ -27,7 +27,7 @@
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.96.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.3.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",
"pg": "^8.23.0"
},
"engines": {
ddiff --git a/server/http/pages.js b/server/http/pages.js
iindex aac702f..cc2b9ec 100644
---- a/server/http/pages.js
++++ b/server/http/pages.js
@@@ -1,4 +1,5 @@
'use strict';
++const showcase = require('openvibe-shared/showcase');
/**
* Server-rendered pages. Everything a reader or an editor does works with plain links and HTML
* form posts; the shared navbar is progressive enhancement.
@@@ -146,7 +147,7 @@ function createPages({ svc, platform, sync, viewers, config, log = console, limi
router.get('/', async (req, res) => {
const all = await svc.listEntities({ limit: 1 });
send(req, res, 200, views.home({ entities: await svc.entitiesWithData(60), total: all.total, editor: isEditor(req) }), {
-- robots: 'index, follow', cache: 'public', active: 'home', path: '/', summary: SITE_SUMMARY,
++ robots: 'index, follow', cache: 'public', active: 'home', path: '/', summary: SITE_SUMMARY, styles: [showcase.STYLESHEET],
jsonLd: seo.structuredData.webPage({ url: `${config.baseUrl}/`, name: 'OpenVibe.Reviews', description: 'Review signals from named sources, with provenance and honest aggregates.' }),
});
});
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex 273fa62..f40d3e4 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -95,6 +95,7 @@ function renderPage(o) {
navLinks: LINKS.map((l) => ({ label: l.label, href: l.href })),
home: '/',
css: asset('css/reviews.css'),
++ styles: o.styles, // openvibe-shared stylesheet names (the home page's showcase.css)
release: RELEASE,
mainClass: 'rv-main',
header: `<header class="rv-bar"><a class="rv-brand" href="/">${SITE_NAME}</a><form class="rv-search" action="/search" method="get" role="search"><label for="rv-q" class="rv-sr">Search entities</label><input id="rv-q" name="q" type="search" placeholder="Search entities" value="${esc(o.query || '')}"><button type="submit">Search</button></form><noscript><span class="rv-account">${who}</span></noscript></header>`,
ddiff --git a/server/render/views.js b/server/render/views.js
iindex 8fabe1e..0d4f5ba 100644
---- a/server/render/views.js
++++ b/server/render/views.js
@@@ -8,6 +8,7 @@
* else, and no star glyphs: values are printed as the source stated them.
*/
const ssr = require('openvibe-publishing/ssr');
++const showcase = require('openvibe-shared/showcase');
const { html, raw } = ssr;
const t = (v) => raw(ssr.timeTag(v));
@@@ -71,14 +72,30 @@ function aggregateLine(agg) {
return html`${parts.join(' · ')}`;
}
++/** The front page opens with what OpenVibe.Reviews is for (openvibe-shared/showcase), then the entities. */
++function reviewsShowcase() {
++ return showcase.hero({
++ eyebrow: 'OpenVibe.Reviews',
++ title: 'Review scores you can', accent: 'trace',
++ lede: 'Ratings and recommendations gathered from named sources. Every number shows where it came from and when it was read, and when no source has said anything there is no score at all.',
++ actions: [{ label: 'Browse what is reviewed', href: '#entities', primary: true }, { label: 'How it works', href: '/about' }],
++ }) + showcase.features({
++ title: 'What every page shows',
++ items: [
++ { icon: 'ov:search', title: 'Where each number came from', text: 'Every signal keeps its source, the item it came from, and when it was read.' },
++ { icon: 'ov:check', title: 'An aggregate you can check', text: 'The inputs and the method are listed. An editor can leave a source out only with the reason shown.' },
++ { icon: 'ov:history', title: 'Changes on the record', text: 'Replaced and withdrawn signals stay in the history, beside every revision of the aggregate.' },
++ { icon: 'ov:docs', title: 'Summaries by people', text: 'Written or approved by an editor, citing the signals they rest on, and never carrying a rating.' },
++ ],
++ });
++}
++
function home({ entities, total, editor }) {
-- return html`<section class="rv-intro"><h1>OpenVibe.Reviews</h1>
--<p>Review signals gathered from named sources. Every number on an entity page shows where it came from and when it was read, the aggregate lists its inputs and how it was computed, and there is no aggregate at all when no source has stated anything. Summaries are written or checked by a person; AI drafts are never published on their own and never produce a rating.</p>
--<p><a href="/about">How it works</a>${editor ? html` · <a href="/editor">Editor desk</a>` : ''}</p></section>
--<section><h2>Entities with source data</h2>
++ return html`${raw(reviewsShowcase())}
++<section id="entities"><h2>Entities with source data</h2>
${entities.length ? html`<ul class="rv-entities">${entities.map((x) => html`<li><a href="${epath(x.entity)}"><strong>${x.entity.name}</strong></a> <span class="rv-tag">${KIND_LABEL[x.entity.kind]}</span><br><span class="rv-muted">${aggregateLine(x.aggregate)}</span></li>`)}</ul>`
: html`<p class="rv-muted">Nothing yet. Entities appear here once a source has stated something about them (or an editor has published a summary).</p>`}
--<p class="rv-muted">${n(total)} entit${total === 1 ? 'y' : 'ies'} in total. <a href="/feed.atom">Atom</a> · <a href="/feed.json">JSON Feed</a></p></section>`;
++<p class="rv-muted">${n(total)} entit${total === 1 ? 'y' : 'ies'} in total. <a href="/feed.atom">Atom</a> · <a href="/feed.json">JSON Feed</a>${editor ? html` · <a href="/editor">Editor desk</a>` : ''}</p></section>`;
}
function aboutPage() {
ddiff --git a/test/perf-budget.test.js b/test/perf-budget.test.js
iindex 35511bd..068c932 100644
---- a/test/perf-budget.test.js
++++ b/test/perf-budget.test.js
@@@ -12,14 +12,14 @@ const { spawn } = require('child_process');
const { measure, check, format } = require('openvibe-shared/perf-budget');
const BUDGETS = {
-- htmlRawKB: 20, // measured 17.8 (fresh database)
-- htmlBrotliKB: 5.5, // 4.6
++ htmlRawKB: 25, // measured 23.7 (fresh database; 17.8 before the showcase hero and features)
++ htmlBrotliKB: 6.5, // 5.9 (4.6 before the showcase)
jsFiles: 5, // 5 (theme-loader, web-runtime, navbar, footer, boost: openvibe-shared/shell adds web-runtime)
jsRawKB: 257, // 239.1 (212.2 before openvibe-shared/shell's web-runtime.js)
jsBrotliKB: 61.5, // 56.3 (49.9 before web-runtime.js)
-- cssFiles: 2, // 1
-- cssRawKB: 7.5, // 6.7
-- cssBrotliKB: 2, // 1.7
++ cssFiles: 2, // 2 (reviews.css, openvibe-shared showcase.css)
++ cssRawKB: 19.5, // 18.2 (6.7 before showcase.css)
++ cssBrotliKB: 4.8, // 4.3 (1.7 before showcase.css)
externalFiles: 1, // 0
};