openvibers/openvibe.codes Commits
Render every Codes page through openvibe-shared/shell v2.6.0
Changes
@@@ -48,7 +48,7 @@ Everything is server-rendered and usable without JavaScript. The only scripts of- **OpenVibe.Events** — the outbox relay publishes `codes.app.*` with Codes' service token (`events.event.publish`); the Events playground calls it with the app's token (`events.app.publish`); the project archive pulls the project's app events with a Network export token (`events.app.read`).- **OpenVibe.Media** — the Media playground uploads with the app's token into the project's namespace; the project archive lists the project's objects, namespaces and download URLs with a Network export token (`media.object.list`, `media.object.read`).- **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).--- **openvibe-contracts** v0.86.0, **openvibe-sdk v0.26.0**, **openvibe-shared v2.5.0** (pinned tag tarballs; the docs show the tag and the package version, and say so when they differ).++- **openvibe-contracts** v0.86.0, **openvibe-sdk v0.26.0**, **openvibe-shared v2.6.0** (pinned tag tarballs; the docs show the tag and the package version, and say so when they differ).No path in Codes sends or accepts a shared loopback key (tested by grep and at runtime).@@@ -11,7 +11,7 @@"packages": ["openvibe-contracts v0.86.0","openvibe-sdk v0.26.0",-- "openvibe-shared v2.5.0"++ "openvibe-shared v2.6.0"],"summary": "Deployed and public since 2026-09-23: https://openvibe.codes is served by this portal (deployed with `sudo ovhost deploy codes`, unit openvibe-codes on 127.0.0.1:4900 behind nginx), and the domain left OpenVibe.Sites. Sign-in uses the registered Network OAuth client `codes` (PKCE S256); the principal `codes` holds events.event.publish and the Events relay is configured. On 2026-09-23 production held 0 manifests, 0 releases and 0 playground runs. The external-developer path (account, project, sandbox app, grants, Media upload, app events) was run once on production on 2026-09-23 with public endpoints and curl, not through these playgrounds; that check is not committed to a repository or CI.","works": [@@@ -18,7 +18,7 @@"iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.86.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.5.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0","pg": "^8.23.0"},"devDependencies": {@@@ -1201,9 +1201,9 @@}},"node_modules/openvibe-shared": {-- "version": "2.5.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.5.0",-- "integrity": "sha512-fHuUS6SXM7ezatfAyqLm1V8B6puV9YXSnz6aG6XQbWx28qghRCCHeUF3lr4nxiCGdn9SyikYtIyLGL5r1ycp9Q==",++ "version": "2.6.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",++ "integrity": "sha512-IKKvWTM/9UonQ4msrzM4yir0QcTUNbhyqeN1r5ywWavU4VTsL1sI5QdHQlxge7BAsMrQ4OvMEPlHjuAfUJM3rg==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -30,7 +30,7 @@"iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.86.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.5.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0","pg": "^8.23.0"},"engines": {@@@ -1,10 +1,11 @@'use strict';/**-- * Page shell. Every page is server-rendered through this and is complete without JavaScript:-- * - <head>: title, description, canonical, robots (portal pages are noindex), the shared app icon-- * - the OpenVibe Frame: navbar.js and theme-loader.js from the Network (progressive), a <noscript>-- * navigation bar and the server-rendered shared footer (openvibe-shared)++ * Page shell. Every page is server-rendered through openvibe-shared/shell and is complete without JavaScript:++ * - <head>: title, description, canonical, robots (portal pages are noindex), Open Graph/Twitter,++ * JSON-LD (openvibe-shared/seo), plus Codes' extras: the shared app icon, stylesheets, the boost marker++ * - the OpenVibe Frame: theme-loader, web-runtime, navbar and footer scripts (the shell boots the++ * navbar; the footer is initialised below), a <noscript> navigation bar and the server-rendered footer* - Codes' own script only where a page offers an optional in-browser convenience (webhook* signature computed locally, copy buttons); every form works without it*/@@@ -14,7 +15,7 @@ const fs = require('fs');const path = require('path');const appIcon = require('openvibe-shared/app-icon');const frame = require('openvibe-shared/frame');--const seo = require('openvibe-shared/seo');++const shell = require('openvibe-shared/shell');const cache = require('openvibe-shared/cache-policy');const { html, raw, esc } = require('./html');@@@ -76,49 +77,43 @@ function renderPage(o) {const crumbs = (o.crumbs || []).length? html`<nav class="crumbs" aria-label="Breadcrumbs">${o.crumbs.map((c, i) => html`${i ? ' / ' : ''}${c.href ? html`<a href="${c.href}">${c.label}</a>` : c.label}`)}</nav>`: '';-- const title = o.title ? `${o.title} · ${SITE_NAME}` : `${SITE_NAME}: the OpenVibe developer portal`;const scripts = (o.scripts || []).map((rel) => html`<script src="${asset(rel)}" defer></script>`);-- return `<!DOCTYPE html>--<html lang="en">--<head>--<meta charset="utf-8">--<meta name="viewport" content="width=device-width, initial-scale=1">--<title>${esc(title)}</title>--<meta name="description" content="${esc(o.description || 'Build on OpenVibe: projects, apps and scoped credentials, generated contract and SDK docs, webhook and OAuth tools, and manifest validation.')}">--<meta name="robots" content="${o.index ? 'index, follow' : 'noindex, nofollow'}">--<link rel="canonical" href="${esc(canonical)}">--<meta name="referrer" content="${o.noReferrer ? 'no-referrer' : 'strict-origin-when-cross-origin'}">++ return shell.page({++ name: SITE_NAME,++ lang: 'en',++ title: o.title || `${SITE_NAME}: the OpenVibe developer portal`,++ titleSuffix: o.title ? ` · ${SITE_NAME}` : '',++ siteName: SITE_NAME,++ description: o.description || 'Build on OpenVibe: projects, apps and scoped credentials, generated contract and SDK docs, webhook and OAuth tools, and manifest validation.',++ canonical,++ robots: o.index ? 'index, follow' : 'noindex, nofollow',++ jsonLd: (o.jsonLd || []).filter(Boolean),++ home: '/',++ navLinks: NAV,++ navbar: nav,++ footer,++ head: `<meta name="referrer" content="${o.noReferrer ? 'no-referrer' : 'strict-origin-when-cross-origin'}">${appIcon.headTags({ site: 'codes' })}--${(o.jsonLd || []).filter(Boolean).map(seo.jsonLdTag).join('\n')}<link rel="stylesheet" href="${asset('css/codes.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>${render(scripts)}<meta name="ov-boost" content="codes@${esc(RELEASE)}">--<script src="${ovServe.url('boost.js')}" data-main="#main" defer></script>--</head>--<body>--<a class="skip" href="#main">Skip to content</a>++<script src="${ovServe.url('boost.js')}" data-main="#main" defer></script>`,++ body: `<a class="skip" href="#main">Skip to content</a><div id="navbar-mount"></div>--${frame.noscriptNav({ name: SITE_NAME, home: '/', links: NAV })}<noscript><div class="account-bar" role="navigation" aria-label="Account">${render(account)}</div></noscript><main id="main" class="page">${render(crumbs)}${render(o.body)}${o.path === '/' ? frame.shipped({ service: 'codes', title: `Recently shipped on ${SITE_NAME}` }) : ''}</main>--${frame.footer(footer)}<script>window.__OV_PAGE = ${JSON.stringify({ navbar: nav, footer }).replace(/</g, '\\u003c')};document.addEventListener('DOMContentLoaded', function () {-- try { if (window.OpenVibeNavbar) OpenVibeNavbar.init(window.__OV_PAGE.navbar); } catch (e) { /* the Frame is optional */ }-- try { if (window.OpenVibeFooter) OpenVibeFooter.init(window.__OV_PAGE.footer); } catch (e) { /* */ }++ try { if (window.OpenVibeFooter) OpenVibeFooter.init(window.__OV_PAGE.footer); } catch (e) { /* the Frame is optional */ }});--</script>--</body>--</html>`;++</script>`,++ });}function render(v) { return require('./html').render(v); }@@@ -35,6 +35,23 @@ const BOOST = /<script src="\/shared\/boost\.js\?v=[0-9a-f]{12}" data-main="#maiassert.match(signedIn.text, BOOST, '/projects: no boost script');});++ await check('the home page head comes from openvibe-shared/shell with the same title, canonical, robots and JSON-LD', async () => {++ const r = await t.get('/');++ assert.strictEqual(r.status, 200);++ const head = r.text.slice(0, r.text.indexOf('</head>'));++ assert.strictEqual((r.text.match(/<title>/g) || []).length, 1, 'exactly one <title>');++ assert.ok(head.includes('<title>OpenVibe.Codes: the OpenVibe developer portal</title>'), 'home title');++ assert.ok(head.includes('<link rel="canonical" href="https://openvibe.codes/">'), 'canonical');++ assert.ok(head.includes('<meta name="robots" content="index, follow">'), 'robots');++ const ld = [...head.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)].map((m) => JSON.parse(m[1])['@type']);++ assert.deepStrictEqual(ld, ['WebSite', 'WebApplication', 'WebPage'], 'the home JSON-LD');++ for (const tag of ['<meta property="og:title"', 'data-ov-icon="codes"', '/css/codes.css?v=', '/shared/theme-loader.js', '/shared/navbar.js', '/shared/footer.js', '<meta name="referrer"']) {++ assert.ok(head.includes(tag), `head has ${tag}`);++ }++ assert.ok(r.text.includes('id="ov-footer"'), 'the server-rendered footer');++ assert.ok(r.text.includes('OpenVibeFooter.init(window.__OV_PAGE.footer)'), 'the footer is initialised');++ });++await check('the navbar config uses the {path} login template, not the path baked in', async () => {const r = await t.get('/docs');assert.match(r.text, /"loginUrl":"\/auth\/login\?next=\{path\}"/);@@@ -14,9 +14,9 @@ const { measure, check, format } = require('openvibe-shared/perf-budget');const BUDGETS = {htmlRawKB: 30, // measured 27.0 (fresh database; the showcase home, 2026-09-29: sections + six inline ring icons)htmlBrotliKB: 7.5, // 6.7-- jsFiles: 5, // 4-- jsRawKB: 225, // 212.2-- jsBrotliKB: 53, // 49.9++ jsFiles: 5, // 5 (theme-loader, web-runtime, navbar, footer, boost: openvibe-shared/shell adds web-runtime, 2026-10-04)++ jsRawKB: 245, // 239.1 (212.2 before openvibe-shared/shell's web-runtime.js)++ jsBrotliKB: 59, // 56.3 (49.9 before web-runtime.js)cssFiles: 2, // 1cssRawKB: 19, // 17.3 (codes.css + the cached /shared/showcase.css)cssBrotliKB: 5, // 4.1
@@ -48,7 +48,7 @@ Everything is server-rendered and usable without JavaScript. The only scripts of
@@ -48,7 +48,7 @@ Everything is server-rendered and usable without JavaScript. The only scripts of
- **OpenVibe.Events** — the outbox relay publishes `codes.app.*` with Codes' service token (`events.event.publish`); the Events playground calls it with the app's token (`events.app.publish`); the project archive pulls the project's app events with a Network export token (`events.app.read`).
- **OpenVibe.Events** — the outbox relay publishes `codes.app.*` with Codes' service token (`events.event.publish`); the Events playground calls it with the app's token (`events.app.publish`); the project archive pulls the project's app events with a Network export token (`events.app.read`).
- **OpenVibe.Media** — the Media playground uploads with the app's token into the project's namespace; the project archive lists the project's objects, namespaces and download URLs with a Network export token (`media.object.list`, `media.object.read`).
- **OpenVibe.Media** — the Media playground uploads with the app's token into the project's namespace; the project archive lists the project's objects, namespaces and download URLs with a Network export token (`media.object.list`, `media.object.read`).
- **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).
- **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).
-- **openvibe-contracts** v0.86.0, **openvibe-sdk v0.26.0**, **openvibe-shared v2.5.0** (pinned tag tarballs; the docs show the tag and the package version, and say so when they differ).
+- **openvibe-contracts** v0.86.0, **openvibe-sdk v0.26.0**, **openvibe-shared v2.6.0** (pinned tag tarballs; the docs show the tag and the package version, and say so when they differ).
No path in Codes sends or accepts a shared loopback key (tested by grep and at runtime).
No path in Codes sends or accepts a shared loopback key (tested by grep and at runtime).
@@ -11,7 +11,7 @@
@@ -11,7 +11,7 @@
"packages": [
"packages": [
"openvibe-contracts v0.86.0",
"openvibe-contracts v0.86.0",
"openvibe-sdk v0.26.0",
"openvibe-sdk v0.26.0",
- "openvibe-shared v2.5.0"
+ "openvibe-shared v2.6.0"
],
],
"summary": "Deployed and public since 2026-09-23: https://openvibe.codes is served by this portal (deployed with `sudo ovhost deploy codes`, unit openvibe-codes on 127.0.0.1:4900 behind nginx), and the domain left OpenVibe.Sites. Sign-in uses the registered Network OAuth client `codes` (PKCE S256); the principal `codes` holds events.event.publish and the Events relay is configured. On 2026-09-23 production held 0 manifests, 0 releases and 0 playground runs. The external-developer path (account, project, sandbox app, grants, Media upload, app events) was run once on production on 2026-09-23 with public endpoints and curl, not through these playgrounds; that check is not committed to a repository or CI.",
"summary": "Deployed and public since 2026-09-23: https://openvibe.codes is served by this portal (deployed with `sudo ovhost deploy codes`, unit openvibe-codes on 127.0.0.1:4900 behind nginx), and the domain left OpenVibe.Sites. Sign-in uses the registered Network OAuth client `codes` (PKCE S256); the principal `codes` holds events.event.publish and the Events relay is configured. On 2026-09-23 production held 0 manifests, 0 releases and 0 playground runs. The external-developer path (account, project, sandbox app, grants, Media upload, app events) was run once on production on 2026-09-23 with public endpoints and curl, not through these playgrounds; that check is not committed to a repository or CI.",
"works": [
"works": [
@@ -18,7 +18,7 @@
@@ -18,7 +18,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.86.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.86.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.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.5.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"devDependencies": {
"devDependencies": {
@@ -1201,9 +1201,9 @@
@@ -1201,9 +1201,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "2.5.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.5.0",
- "integrity": "sha512-fHuUS6SXM7ezatfAyqLm1V8B6puV9YXSnz6aG6XQbWx28qghRCCHeUF3lr4nxiCGdn9SyikYtIyLGL5r1ycp9Q==",
+ "version": "2.6.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",
+ "integrity": "sha512-IKKvWTM/9UonQ4msrzM4yir0QcTUNbhyqeN1r5ywWavU4VTsL1sI5QdHQlxge7BAsMrQ4OvMEPlHjuAfUJM3rg==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -30,7 +30,7 @@
@@ -30,7 +30,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.86.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.86.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.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.5.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"engines": {
"engines": {
@@ -1,10 +1,11 @@
@@ -1,10 +1,11 @@
'use strict';
'use strict';
/**
/**
- * Page shell. Every page is server-rendered through this and is complete without JavaScript:
- * - <head>: title, description, canonical, robots (portal pages are noindex), the shared app icon
- * - the OpenVibe Frame: navbar.js and theme-loader.js from the Network (progressive), a <noscript>
- * navigation bar and the server-rendered shared footer (openvibe-shared)
+ * Page shell. Every page is server-rendered through openvibe-shared/shell and is complete without JavaScript:
+ * - <head>: title, description, canonical, robots (portal pages are noindex), Open Graph/Twitter,
+ * JSON-LD (openvibe-shared/seo), plus Codes' extras: the shared app icon, stylesheets, the boost marker
+ * - the OpenVibe Frame: theme-loader, web-runtime, navbar and footer scripts (the shell boots the
+ * navbar; the footer is initialised below), a <noscript> navigation bar and the server-rendered footer
* - Codes' own script only where a page offers an optional in-browser convenience (webhook
* - Codes' own script only where a page offers an optional in-browser convenience (webhook
* signature computed locally, copy buttons); every form works without it
* signature computed locally, copy buttons); every form works without it
*/
*/
@@ -14,7 +15,7 @@ const fs = require('fs');
@@ -14,7 +15,7 @@ const fs = require('fs');
const path = require('path');
const path = require('path');
const appIcon = require('openvibe-shared/app-icon');
const appIcon = require('openvibe-shared/app-icon');
const frame = require('openvibe-shared/frame');
const frame = require('openvibe-shared/frame');
-const seo = require('openvibe-shared/seo');
+const shell = require('openvibe-shared/shell');
const cache = require('openvibe-shared/cache-policy');
const cache = require('openvibe-shared/cache-policy');
const { html, raw, esc } = require('./html');
const { html, raw, esc } = require('./html');
@@ -76,49 +77,43 @@ function renderPage(o) {
@@ -76,49 +77,43 @@ function renderPage(o) {
const crumbs = (o.crumbs || []).length
const crumbs = (o.crumbs || []).length
? html`<nav class="crumbs" aria-label="Breadcrumbs">${o.crumbs.map((c, i) => html`${i ? ' / ' : ''}${c.href ? html`<a href="${c.href}">${c.label}</a>` : c.label}`)}</nav>`
? html`<nav class="crumbs" aria-label="Breadcrumbs">${o.crumbs.map((c, i) => html`${i ? ' / ' : ''}${c.href ? html`<a href="${c.href}">${c.label}</a>` : c.label}`)}</nav>`
: '';
: '';
- const title = o.title ? `${o.title} · ${SITE_NAME}` : `${SITE_NAME}: the OpenVibe developer portal`;
const scripts = (o.scripts || []).map((rel) => html`<script src="${asset(rel)}" defer></script>`);
const scripts = (o.scripts || []).map((rel) => html`<script src="${asset(rel)}" defer></script>`);
- return `<!DOCTYPE html>
-<html lang="en">
-<head>
-<meta charset="utf-8">
-<meta name="viewport" content="width=device-width, initial-scale=1">
-<title>${esc(title)}</title>
-<meta name="description" content="${esc(o.description || 'Build on OpenVibe: projects, apps and scoped credentials, generated contract and SDK docs, webhook and OAuth tools, and manifest validation.')}">
-<meta name="robots" content="${o.index ? 'index, follow' : 'noindex, nofollow'}">
-<link rel="canonical" href="${esc(canonical)}">
-<meta name="referrer" content="${o.noReferrer ? 'no-referrer' : 'strict-origin-when-cross-origin'}">
+ return shell.page({
+ name: SITE_NAME,
+ lang: 'en',
+ title: o.title || `${SITE_NAME}: the OpenVibe developer portal`,
+ titleSuffix: o.title ? ` · ${SITE_NAME}` : '',
+ siteName: SITE_NAME,
+ description: o.description || 'Build on OpenVibe: projects, apps and scoped credentials, generated contract and SDK docs, webhook and OAuth tools, and manifest validation.',
+ canonical,
+ robots: o.index ? 'index, follow' : 'noindex, nofollow',
+ jsonLd: (o.jsonLd || []).filter(Boolean),
+ home: '/',
+ navLinks: NAV,
+ navbar: nav,
+ footer,
+ head: `<meta name="referrer" content="${o.noReferrer ? 'no-referrer' : 'strict-origin-when-cross-origin'}">
${appIcon.headTags({ site: 'codes' })}
${appIcon.headTags({ site: 'codes' })}
-${(o.jsonLd || []).filter(Boolean).map(seo.jsonLdTag).join('\n')}
<link rel="stylesheet" href="${asset('css/codes.css')}">
<link rel="stylesheet" href="${asset('css/codes.css')}">
${(o.styles || []).map((name) => `<link rel="stylesheet" href="${esc(ovServe.url(name))}">`).join('\n')}
${(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>
${render(scripts)}
${render(scripts)}
<meta name="ov-boost" content="codes@${esc(RELEASE)}">
<meta name="ov-boost" content="codes@${esc(RELEASE)}">
-<script src="${ovServe.url('boost.js')}" data-main="#main" defer></script>
-</head>
-<body>
-<a class="skip" href="#main">Skip to content</a>
+<script src="${ovServe.url('boost.js')}" data-main="#main" defer></script>`,
+ body: `<a class="skip" href="#main">Skip to content</a>
<div id="navbar-mount"></div>
<div id="navbar-mount"></div>
-${frame.noscriptNav({ name: SITE_NAME, home: '/', links: NAV })}
<noscript><div class="account-bar" role="navigation" aria-label="Account">${render(account)}</div></noscript>
<noscript><div class="account-bar" role="navigation" aria-label="Account">${render(account)}</div></noscript>
<main id="main" class="page">
<main id="main" class="page">
${render(crumbs)}
${render(crumbs)}
${render(o.body)}
${render(o.body)}
${o.path === '/' ? frame.shipped({ service: 'codes', title: `Recently shipped on ${SITE_NAME}` }) : ''}
${o.path === '/' ? frame.shipped({ service: 'codes', title: `Recently shipped on ${SITE_NAME}` }) : ''}
</main>
</main>
-${frame.footer(footer)}
<script>
<script>
window.__OV_PAGE = ${JSON.stringify({ navbar: nav, footer }).replace(/</g, '\\u003c')};
window.__OV_PAGE = ${JSON.stringify({ navbar: nav, footer }).replace(/</g, '\\u003c')};
document.addEventListener('DOMContentLoaded', function () {
document.addEventListener('DOMContentLoaded', function () {
- try { if (window.OpenVibeNavbar) OpenVibeNavbar.init(window.__OV_PAGE.navbar); } catch (e) { /* the Frame is optional */ }
- try { if (window.OpenVibeFooter) OpenVibeFooter.init(window.__OV_PAGE.footer); } catch (e) { /* */ }
+ try { if (window.OpenVibeFooter) OpenVibeFooter.init(window.__OV_PAGE.footer); } catch (e) { /* the Frame is optional */ }
});
});
-</script>
-</body>
-</html>`;
+</script>`,
+ });
}
}
function render(v) { return require('./html').render(v); }
function render(v) { return require('./html').render(v); }
@@ -35,6 +35,23 @@ const BOOST = /<script src="\/shared\/boost\.js\?v=[0-9a-f]{12}" data-main="#mai
@@ -35,6 +35,23 @@ const BOOST = /<script src="\/shared\/boost\.js\?v=[0-9a-f]{12}" data-main="#mai
assert.match(signedIn.text, BOOST, '/projects: no boost script');
assert.match(signedIn.text, BOOST, '/projects: no boost script');
});
});
+ await check('the home page head comes from openvibe-shared/shell with the same title, canonical, robots and JSON-LD', async () => {
+ const r = await t.get('/');
+ assert.strictEqual(r.status, 200);
+ const head = r.text.slice(0, r.text.indexOf('</head>'));
+ assert.strictEqual((r.text.match(/<title>/g) || []).length, 1, 'exactly one <title>');
+ assert.ok(head.includes('<title>OpenVibe.Codes: the OpenVibe developer portal</title>'), 'home title');
+ assert.ok(head.includes('<link rel="canonical" href="https://openvibe.codes/">'), 'canonical');
+ assert.ok(head.includes('<meta name="robots" content="index, follow">'), 'robots');
+ const ld = [...head.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)].map((m) => JSON.parse(m[1])['@type']);
+ assert.deepStrictEqual(ld, ['WebSite', 'WebApplication', 'WebPage'], 'the home JSON-LD');
+ for (const tag of ['<meta property="og:title"', 'data-ov-icon="codes"', '/css/codes.css?v=', '/shared/theme-loader.js', '/shared/navbar.js', '/shared/footer.js', '<meta name="referrer"']) {
+ assert.ok(head.includes(tag), `head has ${tag}`);
+ }
+ assert.ok(r.text.includes('id="ov-footer"'), 'the server-rendered footer');
+ assert.ok(r.text.includes('OpenVibeFooter.init(window.__OV_PAGE.footer)'), 'the footer is initialised');
+ });
+
await check('the navbar config uses the {path} login template, not the path baked in', async () => {
await check('the navbar config uses the {path} login template, not the path baked in', async () => {
const r = await t.get('/docs');
const r = await t.get('/docs');
assert.match(r.text, /"loginUrl":"\/auth\/login\?next=\{path\}"/);
assert.match(r.text, /"loginUrl":"\/auth\/login\?next=\{path\}"/);
@@ -14,9 +14,9 @@ const { measure, check, format } = require('openvibe-shared/perf-budget');
@@ -14,9 +14,9 @@ const { measure, check, format } = require('openvibe-shared/perf-budget');
const BUDGETS = {
const BUDGETS = {
htmlRawKB: 30, // measured 27.0 (fresh database; the showcase home, 2026-09-29: sections + six inline ring icons)
htmlRawKB: 30, // measured 27.0 (fresh database; the showcase home, 2026-09-29: sections + six inline ring icons)
htmlBrotliKB: 7.5, // 6.7
htmlBrotliKB: 7.5, // 6.7
- jsFiles: 5, // 4
- jsRawKB: 225, // 212.2
- jsBrotliKB: 53, // 49.9
+ jsFiles: 5, // 5 (theme-loader, web-runtime, navbar, footer, boost: openvibe-shared/shell adds web-runtime, 2026-10-04)
+ jsRawKB: 245, // 239.1 (212.2 before openvibe-shared/shell's web-runtime.js)
+ jsBrotliKB: 59, // 56.3 (49.9 before web-runtime.js)
cssFiles: 2, // 1
cssFiles: 2, // 1
cssRawKB: 19, // 17.3 (codes.css + the cached /shared/showcase.css)
cssRawKB: 19, // 17.3 (codes.css + the cached /shared/showcase.css)
cssBrotliKB: 5, // 4.1
cssBrotliKB: 5, // 4.1