openvibers/openvibe.wiki Commits
The Wiki home opens with what OpenVibe.Wiki is for (plan T11, D92; openvibe-shared v2.3.1 showcase): 'Knowledge, with sources', where to start (start a space, or sign in to), and the six things a space gets today (page trees, revision history, citations, Community discussion, roles and visibility, search and feeds); the spaces and recent pages follow. The kit's stylesheet is linked on the home only. Home budgets raised as a decision (html 27.5 KB, css 18 KB). Suites green.
Changes
@@@ -278,7 +278,7 @@ Production: `/opt/openvibe.wiki`, env `/etc/openvibe/wiki.env`, unit- PostgreSQL 18 and Valkey 9 (OpenVibe.Host `roles/data/`, ADR-035): every read and write is async through`openvibe-sdk/db`; Valkey holds the per-actor limit counters (optional: without `VALKEY_URL` they count per process).--- `openvibe-publishing` v1.0.0 (async PostgreSQL stores), `openvibe-contracts` v0.76.0, `openvibe-shared` v2.2.0++- `openvibe-publishing` v1.0.0 (async PostgreSQL stores), `openvibe-contracts` v0.76.0, `openvibe-shared` v2.3.1(Frame, release, metrics, readiness, SEO helpers, legal pages), `openvibe-sdk` v0.19.0 (db, auth, PostgreSQLevents outbox, per-actor limits, testing) — pinned release tarballs.- OpenVibe.Network (SSO, JWKS, service principal `wiki`), OpenVibe.Events, OpenVibe.Community,@@@ -10,7 +10,7 @@"contracts": "openvibe-contracts v0.76.0","packages": ["openvibe-publishing v1.0.0",-- "openvibe-shared v2.2.0",++ "openvibe-shared v2.3.1","openvibe-sdk v0.19.0"],"capabilities": [@@@ -17,7 +17,7 @@"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0","openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.0.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.19.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.3.1","pg": "^8.23.0"},"devDependencies": {@@@ -1196,9 +1196,9 @@}},"node_modules/openvibe-shared": {-- "version": "2.2.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",-- "integrity": "sha512-1q+iUuUxzPeylTYmithW4jR++Jccvn5GEOrB1B3D18Dgn6OXQhPpvhPQAa1D1k1p+dWIa1/SmggyJwSGFnTNtw==",++ "version": "2.3.1",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.3.1",++ "integrity": "sha512-K+MXnB4O+8xIFi+NQGC2SDTvDq3RkzfxQ3bTVx76Y/Rbuq7kC/L5kL3c/06xr9sPYcjyzF3Bhpv5yH2YNfR4Zw==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -28,7 +28,7 @@"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0","openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.0.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.19.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.3.1","pg": "^8.23.0"},"engines": {@@@ -72,3 +72,11 @@ table { border-collapse: collapse; }h1 { font-size: 1.5rem; }}@media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }++++/* The home's showcase sections (openvibe-shared/showcase.css) inside the page column, which has its own gutter. */++.wk-main .sc-hero, .wk-main .sc-sec, main .sc-hero, main .sc-sec { padding-left: 0; padding-right: 0; }++main .sc-hero { padding-top: 16px; }++main .sc-hero h1 { font-size: clamp(30px, 4.6vw, 46px); }++main .sc-sec { margin-top: 36px; }++main .sc-ic .ov-icon { --ovi-size: 28px !important; }++#spaces { display: block; height: 8px; }@@@ -33,6 +33,7 @@ const ssr = require('openvibe-publishing/ssr');const { renderPage } = require('../render/layout');const views = require('../render/views');const frame = require('openvibe-shared/frame');++const showcase = require('openvibe-shared/showcase');const content = require('../wiki/content');const { actorMiddleware, resolveCitations, resolveBundleCitations, citationsFromForm } = require('./common');const importer = require('../wiki/import');@@@ -122,7 +123,7 @@ function createPages({ svc, viewers, platform, config, log = console, limits })if (req.actor.subject) await svc.access.prepareVip(req.actor, (await svc.vipSpaces()).map((space) => ({ space }))); // VIP spaces the viewer may seeconst [spaces, recent] = await Promise.all([svc.listSpaces(req.actor), svc.recentChanges(10)]);send(req, res, 200, views.home({ spaces, recent, actor: req.actor }) + frame.shipped({ service: 'wiki', title: 'Recently shipped on OpenVibe.Wiki' }), {-- robots: 'index, follow', cache: 'public', active: 'home', path: '/',++ robots: 'index, follow', cache: 'public', active: 'home', path: '/', styles: [showcase.STYLESHEET],jsonLd: seo.structuredData.webPage({ url: `${config.baseUrl}/`, name: 'OpenVibe.Wiki', description: 'Wiki spaces of the OpenVibe network.' }),});}));@@@ -84,6 +84,7 @@ ${head}${appIcon.headTags({ site: 'network' })}${feeds.map((f) => `<link rel="alternate" type="${f.type}" title="${esc(f.title)}" href="${f.href}">`).join('\n')}<link rel="stylesheet" href="${asset('css/wiki.css')}">++${(o.styles || []).map((name) => `<link rel="stylesheet" href="${esc(ovServe.url(name))}">`).join('\n')}<script src="${ovServe.url('theme-loader.js')}" defer></script><script src="${ovServe.url('navbar.js')}" defer></script><script src="${ovServe.url('footer.js')}" defer></script>@@@ -4,6 +4,7 @@* unless raw()). Forms are plain HTML posts; nothing here needs JavaScript.*/const ssr = require('openvibe-publishing/ssr');++const showcase = require('openvibe-shared/showcase');const { figureHtml } = require('openvibe-publishing/media');const { infoboxToText } = require('../wiki/content');@@@ -35,9 +36,25 @@ function home({ spaces, recent, actor }) {const official = spaces.filter((s) => s.kind === 'official');const user = spaces.filter((s) => s.kind === 'user');const list = (items) => items.length ? html`<ul class="wk-spaces">${items.map((s) => html`<li><a href="/s/${e(s.slug)}"><strong>${s.name}</strong></a>${s.visibility !== 'public' ? html` <span class="wk-tag">${s.visibility}</span>` : ''}${s.description ? html`<br><span class="wk-muted">${s.description}</span>` : ''}</li>`)}</ul>` : html`<p class="wk-muted">None yet.</p>`;-- return html`<section class="wk-intro"><h1>OpenVibe.Wiki</h1>--<p>Wiki spaces with page trees, revision history, citations and discussion. Official spaces are edited by OpenVibe staff; anyone signed in with an OpenVibe account can start a space of their own.</p>--${actor && actor.subject ? html`<p><a class="wk-button" href="/new-space">Start a space</a></p>` : html`<p><a href="/auth/login?next=%2Fnew-space">Sign in</a> to start a space.</p>`}</section>++ const start = actor && actor.subject ? { label: 'Start a space', href: '/new-space', primary: true } : { label: 'Sign in to start a space', href: '/auth/login?next=%2Fnew-space', primary: true };++ // What OpenVibe.Wiki is for (openvibe-shared/showcase), then the spaces and what was published.++ return html`${raw(showcase.hero({++ eyebrow: 'OpenVibe.Wiki',++ title: 'Knowledge,', accent: 'with sources',++ lede: 'Wiki spaces with page trees, revision history, citations and discussion. Official spaces are edited by OpenVibe staff; anyone signed in with an OpenVibe account can start a space of their own.',++ actions: [start, { label: 'Browse the spaces', href: '#spaces' }],++ }) + showcase.features({++ title: 'What a space gets',++ items: [++ { icon: 'ov:wiki', title: 'Page trees', text: 'Pages nest under pages, so a space reads like a book.' },++ { icon: 'ov:history', title: 'Revision history', text: 'Every published revision is kept; compare or restore any of them.' },++ { icon: 'ov:check', title: 'Citations', text: 'Claims carry their sources, listed with every page.' },++ { icon: 'ov:community', title: 'Discussion', text: 'Each page has a thread on OpenVibe.Community.' },++ { icon: 'ov:account', title: 'Roles and visibility', text: 'Public, members-only, VIP or private spaces, with editors you choose.' },++ { icon: 'ov:search', title: 'Search and feeds', text: 'Full-text search across the spaces you can see, and Atom and JSON feeds.' },++ ],++ }))}++<div id="spaces"></div><section><h2>Official spaces</h2>${list(official)}</section><section><h2>Community spaces</h2>${list(user)}</section><section><h2>Recently published</h2>${recent.length ? html`<ul class="wk-recent">${recent.map((r) => html`<li><a href="${wpath(r.space, r.page)}">${r.page.title}</a> <span class="wk-muted">in ${r.space.name} · ${t(r.page.revision_published_at)}</span></li>`)}</ul>` : html`<p class="wk-muted">Nothing published yet.</p>`}@@@ -12,14 +12,14 @@ const { spawn } = require('child_process');const { measure, check, format } = require('openvibe-shared/perf-budget');const BUDGETS = {-- htmlRawKB: 22, // measured 17.3 (fresh database)-- htmlBrotliKB: 5.5, // 4.4++ htmlRawKB: 27.5, // measured 24.9 (fresh database; 2026-09-29 the showcase hero and features with six inline ring icons)++ htmlBrotliKB: 7, // 6.3jsFiles: 4, // 3jsRawKB: 230, // 197.7jsBrotliKB: 53, // 45.6cssFiles: 2, // 1-- cssRawKB: 7, // 5.5-- cssBrotliKB: 2, // 1.5++ cssRawKB: 18, // 16.3 (wiki.css + the cached /shared/showcase.css)++ cssBrotliKB: 4.5, // 4.1externalFiles: 1, // 0};
@@ -278,7 +278,7 @@ Production: `/opt/openvibe.wiki`, env `/etc/openvibe/wiki.env`, unit
@@ -278,7 +278,7 @@ Production: `/opt/openvibe.wiki`, env `/etc/openvibe/wiki.env`, unit
- PostgreSQL 18 and Valkey 9 (OpenVibe.Host `roles/data/`, ADR-035): every read and write is async through
- PostgreSQL 18 and Valkey 9 (OpenVibe.Host `roles/data/`, ADR-035): every read and write is async through
`openvibe-sdk/db`; Valkey holds the per-actor limit counters (optional: without `VALKEY_URL` they count per process).
`openvibe-sdk/db`; Valkey holds the per-actor limit counters (optional: without `VALKEY_URL` they count per process).
-- `openvibe-publishing` v1.0.0 (async PostgreSQL stores), `openvibe-contracts` v0.76.0, `openvibe-shared` v2.2.0
+- `openvibe-publishing` v1.0.0 (async PostgreSQL stores), `openvibe-contracts` v0.76.0, `openvibe-shared` v2.3.1
(Frame, release, metrics, readiness, SEO helpers, legal pages), `openvibe-sdk` v0.19.0 (db, auth, PostgreSQL
(Frame, release, metrics, readiness, SEO helpers, legal pages), `openvibe-sdk` v0.19.0 (db, auth, PostgreSQL
events outbox, per-actor limits, testing) — pinned release tarballs.
events outbox, per-actor limits, testing) — pinned release tarballs.
- OpenVibe.Network (SSO, JWKS, service principal `wiki`), OpenVibe.Events, OpenVibe.Community,
- OpenVibe.Network (SSO, JWKS, service principal `wiki`), OpenVibe.Events, OpenVibe.Community,
@@ -10,7 +10,7 @@
@@ -10,7 +10,7 @@
"contracts": "openvibe-contracts v0.76.0",
"contracts": "openvibe-contracts v0.76.0",
"packages": [
"packages": [
"openvibe-publishing v1.0.0",
"openvibe-publishing v1.0.0",
- "openvibe-shared v2.2.0",
+ "openvibe-shared v2.3.1",
"openvibe-sdk v0.19.0"
"openvibe-sdk v0.19.0"
],
],
"capabilities": [
"capabilities": [
@@ -17,7 +17,7 @@
@@ -17,7 +17,7 @@
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.0.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.0.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.19.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.19.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.3.1",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"devDependencies": {
"devDependencies": {
@@ -1196,9 +1196,9 @@
@@ -1196,9 +1196,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "2.2.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",
- "integrity": "sha512-1q+iUuUxzPeylTYmithW4jR++Jccvn5GEOrB1B3D18Dgn6OXQhPpvhPQAa1D1k1p+dWIa1/SmggyJwSGFnTNtw==",
+ "version": "2.3.1",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.3.1",
+ "integrity": "sha512-K+MXnB4O+8xIFi+NQGC2SDTvDq3RkzfxQ3bTVx76Y/Rbuq7kC/L5kL3c/06xr9sPYcjyzF3Bhpv5yH2YNfR4Zw==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -28,7 +28,7 @@
@@ -28,7 +28,7 @@
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.0.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.0.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.19.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.19.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.3.1",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"engines": {
"engines": {
@@ -72,3 +72,11 @@ table { border-collapse: collapse; }
@@ -72,3 +72,11 @@ table { border-collapse: collapse; }
h1 { font-size: 1.5rem; }
h1 { font-size: 1.5rem; }
}
}
@media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }
@media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }
+
+/* The home's showcase sections (openvibe-shared/showcase.css) inside the page column, which has its own gutter. */
+.wk-main .sc-hero, .wk-main .sc-sec, main .sc-hero, main .sc-sec { padding-left: 0; padding-right: 0; }
+main .sc-hero { padding-top: 16px; }
+main .sc-hero h1 { font-size: clamp(30px, 4.6vw, 46px); }
+main .sc-sec { margin-top: 36px; }
+main .sc-ic .ov-icon { --ovi-size: 28px !important; }
+#spaces { display: block; height: 8px; }
@@ -33,6 +33,7 @@ const ssr = require('openvibe-publishing/ssr');
@@ -33,6 +33,7 @@ const ssr = require('openvibe-publishing/ssr');
const { renderPage } = require('../render/layout');
const { renderPage } = require('../render/layout');
const views = require('../render/views');
const views = require('../render/views');
const frame = require('openvibe-shared/frame');
const frame = require('openvibe-shared/frame');
+const showcase = require('openvibe-shared/showcase');
const content = require('../wiki/content');
const content = require('../wiki/content');
const { actorMiddleware, resolveCitations, resolveBundleCitations, citationsFromForm } = require('./common');
const { actorMiddleware, resolveCitations, resolveBundleCitations, citationsFromForm } = require('./common');
const importer = require('../wiki/import');
const importer = require('../wiki/import');
@@ -122,7 +123,7 @@ function createPages({ svc, viewers, platform, config, log = console, limits })
@@ -122,7 +123,7 @@ function createPages({ svc, viewers, platform, config, log = console, limits })
if (req.actor.subject) await svc.access.prepareVip(req.actor, (await svc.vipSpaces()).map((space) => ({ space }))); // VIP spaces the viewer may see
if (req.actor.subject) await svc.access.prepareVip(req.actor, (await svc.vipSpaces()).map((space) => ({ space }))); // VIP spaces the viewer may see
const [spaces, recent] = await Promise.all([svc.listSpaces(req.actor), svc.recentChanges(10)]);
const [spaces, recent] = await Promise.all([svc.listSpaces(req.actor), svc.recentChanges(10)]);
send(req, res, 200, views.home({ spaces, recent, actor: req.actor }) + frame.shipped({ service: 'wiki', title: 'Recently shipped on OpenVibe.Wiki' }), {
send(req, res, 200, views.home({ spaces, recent, actor: req.actor }) + frame.shipped({ service: 'wiki', title: 'Recently shipped on OpenVibe.Wiki' }), {
- robots: 'index, follow', cache: 'public', active: 'home', path: '/',
+ robots: 'index, follow', cache: 'public', active: 'home', path: '/', styles: [showcase.STYLESHEET],
jsonLd: seo.structuredData.webPage({ url: `${config.baseUrl}/`, name: 'OpenVibe.Wiki', description: 'Wiki spaces of the OpenVibe network.' }),
jsonLd: seo.structuredData.webPage({ url: `${config.baseUrl}/`, name: 'OpenVibe.Wiki', description: 'Wiki spaces of the OpenVibe network.' }),
});
});
}));
}));
@@ -84,6 +84,7 @@ ${head}
@@ -84,6 +84,7 @@ ${head}
${appIcon.headTags({ site: 'network' })}
${appIcon.headTags({ site: 'network' })}
${feeds.map((f) => `<link rel="alternate" type="${f.type}" title="${esc(f.title)}" href="${f.href}">`).join('\n')}
${feeds.map((f) => `<link rel="alternate" type="${f.type}" title="${esc(f.title)}" href="${f.href}">`).join('\n')}
<link rel="stylesheet" href="${asset('css/wiki.css')}">
<link rel="stylesheet" href="${asset('css/wiki.css')}">
+${(o.styles || []).map((name) => `<link rel="stylesheet" href="${esc(ovServe.url(name))}">`).join('\n')}
<script src="${ovServe.url('theme-loader.js')}" defer></script>
<script src="${ovServe.url('theme-loader.js')}" defer></script>
<script src="${ovServe.url('navbar.js')}" defer></script>
<script src="${ovServe.url('navbar.js')}" defer></script>
<script src="${ovServe.url('footer.js')}" defer></script>
<script src="${ovServe.url('footer.js')}" defer></script>
@@ -4,6 +4,7 @@
@@ -4,6 +4,7 @@
* unless raw()). Forms are plain HTML posts; nothing here needs JavaScript.
* unless raw()). Forms are plain HTML posts; nothing here needs JavaScript.
*/
*/
const ssr = require('openvibe-publishing/ssr');
const ssr = require('openvibe-publishing/ssr');
+const showcase = require('openvibe-shared/showcase');
const { figureHtml } = require('openvibe-publishing/media');
const { figureHtml } = require('openvibe-publishing/media');
const { infoboxToText } = require('../wiki/content');
const { infoboxToText } = require('../wiki/content');
@@ -35,9 +36,25 @@ function home({ spaces, recent, actor }) {
@@ -35,9 +36,25 @@ function home({ spaces, recent, actor }) {
const official = spaces.filter((s) => s.kind === 'official');
const official = spaces.filter((s) => s.kind === 'official');
const user = spaces.filter((s) => s.kind === 'user');
const user = spaces.filter((s) => s.kind === 'user');
const list = (items) => items.length ? html`<ul class="wk-spaces">${items.map((s) => html`<li><a href="/s/${e(s.slug)}"><strong>${s.name}</strong></a>${s.visibility !== 'public' ? html` <span class="wk-tag">${s.visibility}</span>` : ''}${s.description ? html`<br><span class="wk-muted">${s.description}</span>` : ''}</li>`)}</ul>` : html`<p class="wk-muted">None yet.</p>`;
const list = (items) => items.length ? html`<ul class="wk-spaces">${items.map((s) => html`<li><a href="/s/${e(s.slug)}"><strong>${s.name}</strong></a>${s.visibility !== 'public' ? html` <span class="wk-tag">${s.visibility}</span>` : ''}${s.description ? html`<br><span class="wk-muted">${s.description}</span>` : ''}</li>`)}</ul>` : html`<p class="wk-muted">None yet.</p>`;
- return html`<section class="wk-intro"><h1>OpenVibe.Wiki</h1>
-<p>Wiki spaces with page trees, revision history, citations and discussion. Official spaces are edited by OpenVibe staff; anyone signed in with an OpenVibe account can start a space of their own.</p>
-${actor && actor.subject ? html`<p><a class="wk-button" href="/new-space">Start a space</a></p>` : html`<p><a href="/auth/login?next=%2Fnew-space">Sign in</a> to start a space.</p>`}</section>
+ const start = actor && actor.subject ? { label: 'Start a space', href: '/new-space', primary: true } : { label: 'Sign in to start a space', href: '/auth/login?next=%2Fnew-space', primary: true };
+ // What OpenVibe.Wiki is for (openvibe-shared/showcase), then the spaces and what was published.
+ return html`${raw(showcase.hero({
+ eyebrow: 'OpenVibe.Wiki',
+ title: 'Knowledge,', accent: 'with sources',
+ lede: 'Wiki spaces with page trees, revision history, citations and discussion. Official spaces are edited by OpenVibe staff; anyone signed in with an OpenVibe account can start a space of their own.',
+ actions: [start, { label: 'Browse the spaces', href: '#spaces' }],
+ }) + showcase.features({
+ title: 'What a space gets',
+ items: [
+ { icon: 'ov:wiki', title: 'Page trees', text: 'Pages nest under pages, so a space reads like a book.' },
+ { icon: 'ov:history', title: 'Revision history', text: 'Every published revision is kept; compare or restore any of them.' },
+ { icon: 'ov:check', title: 'Citations', text: 'Claims carry their sources, listed with every page.' },
+ { icon: 'ov:community', title: 'Discussion', text: 'Each page has a thread on OpenVibe.Community.' },
+ { icon: 'ov:account', title: 'Roles and visibility', text: 'Public, members-only, VIP or private spaces, with editors you choose.' },
+ { icon: 'ov:search', title: 'Search and feeds', text: 'Full-text search across the spaces you can see, and Atom and JSON feeds.' },
+ ],
+ }))}
+<div id="spaces"></div>
<section><h2>Official spaces</h2>${list(official)}</section>
<section><h2>Official spaces</h2>${list(official)}</section>
<section><h2>Community spaces</h2>${list(user)}</section>
<section><h2>Community spaces</h2>${list(user)}</section>
<section><h2>Recently published</h2>${recent.length ? html`<ul class="wk-recent">${recent.map((r) => html`<li><a href="${wpath(r.space, r.page)}">${r.page.title}</a> <span class="wk-muted">in ${r.space.name} · ${t(r.page.revision_published_at)}</span></li>`)}</ul>` : html`<p class="wk-muted">Nothing published yet.</p>`}
<section><h2>Recently published</h2>${recent.length ? html`<ul class="wk-recent">${recent.map((r) => html`<li><a href="${wpath(r.space, r.page)}">${r.page.title}</a> <span class="wk-muted">in ${r.space.name} · ${t(r.page.revision_published_at)}</span></li>`)}</ul>` : html`<p class="wk-muted">Nothing published yet.</p>`}
@@ -12,14 +12,14 @@ const { spawn } = require('child_process');
@@ -12,14 +12,14 @@ const { spawn } = require('child_process');
const { measure, check, format } = require('openvibe-shared/perf-budget');
const { measure, check, format } = require('openvibe-shared/perf-budget');
const BUDGETS = {
const BUDGETS = {
- htmlRawKB: 22, // measured 17.3 (fresh database)
- htmlBrotliKB: 5.5, // 4.4
+ htmlRawKB: 27.5, // measured 24.9 (fresh database; 2026-09-29 the showcase hero and features with six inline ring icons)
+ htmlBrotliKB: 7, // 6.3
jsFiles: 4, // 3
jsFiles: 4, // 3
jsRawKB: 230, // 197.7
jsRawKB: 230, // 197.7
jsBrotliKB: 53, // 45.6
jsBrotliKB: 53, // 45.6
cssFiles: 2, // 1
cssFiles: 2, // 1
- cssRawKB: 7, // 5.5
- cssBrotliKB: 2, // 1.5
+ cssRawKB: 18, // 16.3 (wiki.css + the cached /shared/showcase.css)
+ cssBrotliKB: 4.5, // 4.1
externalFiles: 1, // 0
externalFiles: 1, // 0
};
};