openvibers/openvibe.community Commits

Render every Community page through openvibe-shared/shell v2.6.0

OP OpenVibers committed 4 Oct 2026 · 452ab04ebb · 6 files, +72 −48

Asked: Part of a cross-repository initiative (init-t11-step-2-first-batch-every-1004T0752): T11 step 2 (first batch): every server-rendered page in OpenVibe.Tips, OpenVibe.VIP, OpenVibe.Codes, OpenVibe.Search and OpenVibe.Community renders its document through openvibe-shared/shell (shell.page), released …
Harness job init-t11-step-2-first-0752-s5-085400 (plan T11).

Changes

ddiff --git a/package-lock.json b/package-lock.json
iindex ae35a1d..883b035 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -19,7 +19,7 @@
"multer": "^2.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": {
@@@ -1236,9 +1236,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"
ddiff --git a/package.json b/package.json
iindex d8667ec..1e99bac 100644
---- a/package.json
++++ b/package.json
@@@ -30,7 +30,7 @@
"multer": "^2.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": {
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex 3ab56bf..728dd5a 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -1,15 +1,16 @@
'use strict';
/**
-- * Page shell — every page on the site is server-rendered through this: full <head> SEO from
-- * openvibe-shared/seo (description, canonical, robots, Open Graph, Twitter card, JSON-LD and the
-- * ai-summary page summary), the shared
-- * OpenVibe Frame (theme-loader first so there is no flash, navbar.js + footer.js from the
-- * Network), this site's small stylesheet and its progressive script.
++ * Page shell — every page on the site is server-rendered through openvibe-shared/shell: full <head>
++ * SEO (description, canonical, robots, Open Graph, Twitter card, JSON-LD and the ai-summary page
++ * summary), the shared OpenVibe Frame (theme-loader first so there is no flash, navbar.js +
++ * footer.js from the Network, the SSR footer), plus this site's small stylesheet and its
++ * progressive script.
*/
const crypto = require('crypto');
const ovServe = require('openvibe-shared/serve');
--const seo = require('openvibe-shared/seo');
++const shell = require('openvibe-shared/shell');
++const appIcon = require('openvibe-shared/app-icon');
const fs = require('fs');
const path = require('path');
const config = require('../config');
@@@ -110,52 +111,51 @@ function footerInit(opts) {
* published/modified (ISO, for article:*), noFrame (error pages during outages)
*/
function renderPage(o) {
-- const title = o.title ? `${o.title} · ${SITE_NAME}` : `${SITE_NAME} — the people of OpenVibe`;
const description = fitDescription((o.description || DEFAULT_DESCRIPTION).replace(/\s+/g, ' ').trim().slice(0, 300));
const canonical = abs(o.canonicalPath || '/');
-- const robots = o.robots || 'index,follow';
-- const ogType = o.ogType || 'website';
-- const ogImage = o.ogImage || DEFAULT_OG_IMAGE;
const nav = navbarInit(o);
const foot = footerInit(o);
++ const feeds = o.feeds || [{ title: `${SITE_NAME} — latest pastes`, href: '/feed.xml' }];
-- return `<!DOCTYPE html>
--<html lang="en" data-page="${escapeHtml(o.active || 'page')}">
--<head>
--<meta charset="utf-8">
--<meta name="viewport" content="width=device-width, initial-scale=1">
--<title>${escapeHtml(title)}</title>
--${seo.headTags({ title: o.title || SITE_NAME, noTitle: true, siteName: SITE_NAME, description, canonical, robots, type: ogType, image: ogImage, imageAlt: o.imageAlt, largeImage: !!o.ogImage, jsonLd: o.jsonLd, alternates: o.alternates })}
--${o.published ? `<meta property="article:published_time" content="${escapeHtml(o.published)}">` : ''}
--${o.modified ? `<meta property="article:modified_time" content="${escapeHtml(o.modified)}">` : ''}
--${seo.pageSummary({ title: o.title || SITE_NAME, summary: description, url: canonical, updated: o.modified })}
--${require('openvibe-shared/app-icon').headTags({ site: 'community', iconBase: '/assets' })}
--${(o.feeds || [{ title: `${SITE_NAME} — latest pastes`, href: '/feed.xml' }]).map((f) => `<link rel="alternate" type="application/rss+xml" title="${escapeHtml(f.title)}" href="${escapeHtml(f.href)}">`).join('\n')}
--<script src="${ovServe.url('theme-loader.js')}" defer></script>
--${(o.styles || []).map((name) => `<link rel="stylesheet" href="${ovServe.url(name)}">`).join('\n')}
--<link rel="stylesheet" href="${asset('css/community.css')}">
--<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer">
--<script src="${ovServe.url('navbar.js')}" defer></script>
--<script src="${ovServe.url('footer.js')}" defer></script>
--<script src="${asset('js/community.js')}" defer></script>
--<meta name="ov-boost" content="community@${escapeHtml(RELEASE)}">
--<script src="${ovServe.url('boost.js')}" data-main="#main" defer></script>
--</head>
--<body class="${escapeHtml(o.bodyClass || '')}">
--<div id="navbar-mount"></div>
++ // shell.page writes the document, the SEO head and ai-summary, the theme-loader, navbar.js/footer.js
++ // with the navbar init, the noscript nav and the SSR footer; the rest of the head is this site's own.
++ const html = shell.page({
++ name: SITE_NAME, service: 'community', lang: 'en',
++ title: o.title || `${SITE_NAME} — the people of OpenVibe`,
++ titleSuffix: o.title ? ` · ${SITE_NAME}` : undefined,
++ siteName: SITE_NAME, description, canonical, robots: o.robots || 'index,follow',
++ type: o.ogType || 'website', image: o.ogImage || DEFAULT_OG_IMAGE, imageAlt: o.imageAlt,
++ jsonLd: o.jsonLd, alternates: o.alternates,
++ summary: description, url: canonical, updated: o.modified,
++ navbar: nav, home: '/', navLinks: nav.links.map(({ label, href }) => ({ label, href })),
++ footer: { service: 'community', variant: 'full', updates: '/updates' },
++ head: [
++ o.published ? `<meta property="article:published_time" content="${escapeHtml(o.published)}">` : '',
++ o.modified ? `<meta property="article:modified_time" content="${escapeHtml(o.modified)}">` : '',
++ appIcon.headTags({ site: 'community', iconBase: '/assets' }),
++ ...feeds.map((f) => `<link rel="alternate" type="application/rss+xml" title="${escapeHtml(f.title)}" href="${escapeHtml(f.href)}">`),
++ ...(o.styles || []).map((name) => `<link rel="stylesheet" href="${ovServe.url(name)}">`),
++ `<link rel="stylesheet" href="${asset('css/community.css')}">`,
++ '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer">',
++ `<script src="${asset('js/community.js')}" defer></script>`,
++ `<meta name="ov-boost" content="community@${escapeHtml(RELEASE)}">`,
++ `<script src="${ovServe.url('boost.js')}" data-main="#main" defer></script>`,
++ ].filter(Boolean).join('\n'),
++ bodyClass: o.bodyClass,
++ bodyAttributes: { 'data-page': o.active || 'page' },
++ body: `<div id="navbar-mount"></div>
<main id="main" class="page">
${o.body || ''}
</main>
--${require('openvibe-shared/frame').footer({ service: 'community', variant: 'full', updates: '/updates' })}
<script>
window.__OV_PAGE = ${JSON.stringify({ navbar: nav, footer: foot }).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 SSR footer stays */ }
});
--</script>
--</body>
--</html>`;
++</script>`,
++ });
++ // The default share image is a small card: only a page's own image gets the large Twitter card.
++ return o.ogImage ? html : html.replace('<meta name="twitter:card" content="summary_large_image">', '<meta name="twitter:card" content="summary">');
}
module.exports = { renderPage, asset, assetVersion, abs, setRelease, SITE_NAME, DEFAULT_DESCRIPTION, DEFAULT_OG_IMAGE };
ddiff --git a/test/seo.test.js b/test/seo.test.js
iindex 17106cf..92a0e14 100644
---- a/test/seo.test.js
++++ b/test/seo.test.js
@@@ -97,10 +97,10 @@ const { boot, check, done } = require('./helpers/app');
const head = r.text.split('</head>')[0];
assert.ok(head.includes('<link rel="canonical" href="https://openvibe.community/p/amber-fox-42">'));
assert.ok(head.includes('<meta property="og:url" content="https://openvibe.community/p/amber-fox-42">'));
-- assert.ok(head.includes('<meta property="og:title" content="Hello world in JavaScript">'));
++ assert.ok(head.includes('<meta property="og:title" content="Hello world in JavaScript · OpenVibe.Community">'));
assert.ok(head.includes('<meta property="og:site_name" content="OpenVibe.Community">'));
assert.match(head, /<meta property="og:description" content="[^"]+">/);
-- assert.match(head, /<meta name="twitter:title" content="Hello world in JavaScript">/);
++ assert.match(head, /<meta name="twitter:title" content="Hello world in JavaScript · OpenVibe.Community">/);
assert.match(head, /<meta name="ai-summary" content="[^"]+">/);
assert.match(head, /<meta property="article:published_time" content="2026-09-15T10:00:00.000Z">/);
assert.ok(head.includes('"@type":"BreadcrumbList"') && head.includes('"@type":"WebPage"'));
ddiff --git a/test/ssr.test.js b/test/ssr.test.js
iindex 9fc94e0..d3b1964 100644
---- a/test/ssr.test.js
++++ b/test/ssr.test.js
@@@ -57,6 +57,30 @@ const { boot, check, done } = require('./helpers/app');
assert.ok(!/\bfree\b(?! speech)/i.test(r.text.replace(/<script[\s\S]*?<\/script>/g, '')), 'no "free" claims outside "free speech"');
});
++ await check('the home head comes whole from openvibe-shared/shell plus this site\'s extras', async () => {
++ const r = await t.get('/');
++ assert.ok(r.text.startsWith('<!doctype html>'));
++ const head = r.text.split('</head>')[0];
++ assert.strictEqual((head.match(/<title>/g) || []).length, 1, 'one title');
++ has(head, '<title>OpenVibe.Community — the people of OpenVibe</title>');
++ assert.strictEqual((head.match(/<link rel="canonical" href="https:\/\/openvibe\.community\/">/g) || []).length, 1, 'one canonical');
++ has(head, '<meta name="robots" content="index,follow');
++ has(head, '<script type="application/ld+json">');
++ has(head, '"@type":"WebSite"');
++ has(head, '<meta name="ai-summary" content="');
++ has(head, '<link rel="alternate" type="application/rss+xml" title="OpenVibe.Community — latest pastes" href="/feed.xml">');
++ has(head, '/css/community.css?v=');
++ has(head, '/js/community.js?v=');
++ has(head, 'font-awesome');
++ has(head, '<link rel="icon"');
++ assert.match(head, /<meta name="ov-boost" content="community@[^"]+">/);
++ for (const js of ['theme-loader.js', 'navbar.js', 'footer.js', 'boost.js']) has(head, `/shared/${js}?v=`);
++ assert.match(r.text, /<body[^>]* data-page="home">/);
++ has(r.text, '<div id="navbar-mount"></div>');
++ has(r.text, 'id="ov-footer"');
++ has(r.text, 'OpenVibeFooter.init(window.__OV_PAGE.footer)');
++ });
++
await check('every page carries the boost marker and script, and the navbar signs in back to the current page', async () => {
for (const url of ['/', '/pastes', '/new', '/p/amber-fox-42']) {
const r = await t.get(url);
ddiff --git a/test/submissions.test.js b/test/submissions.test.js
iindex 181cff3..c8eef4f 100644
---- a/test/submissions.test.js
++++ b/test/submissions.test.js
@@@ -110,7 +110,7 @@ const has = (html, s, msg) => assert.ok(html.includes(s), msg || `expected to fi
assert.strictEqual(pub.status, 200);
has(pub.text, `<link rel="canonical" href="https://openvibe.community/submissions/${slug}">`);
has(pub.text, '<meta property="og:type" content="article">');
-- has(pub.text, '<meta property="og:title" content="Best play of the week">');
++ has(pub.text, '<meta property="og:title" content="Best play of the week · OpenVibe.Community">');
has(pub.text, 'index,follow');
assert.ok(!pub.text.includes('Great one'), 'the review note is for the author and moderators');
assert.ok(!pub.text.includes(`/submissions/${slug}/withdraw`) && !pub.text.includes(`/submissions/${slug}/review"`));