openvibers/openvibe.community Commits

The OpenVibe Frame and the shared update system on openvibe.community: the home shows what shipped and /updates is the shared log (openvibe-shared/frame 1.11.1), the footer links it, and the shared navbar signs out through this site (logoutUrl)

OP OpenVibers committed 24 Sept 2026 · 58f1b3b58e · 6 files, +43 −11


Changes

ddiff --git a/package-lock.json b/package-lock.json
iindex a5ac7e1..64d07fc 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.35.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.7.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.11.1"
},
"engines": {
"node": ">=20.0.0"
@@@ -1082,9 +1082,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "1.5.1",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
-- "integrity": "sha512-1XWRFwN8br3iiy2Z8fmnXQMgR76VlfMpHjP1xr5uKLCJmtvtNSWO1BgA1kQmVtyBY7g+1n6YFIjahaD4qWbD/Q==",
++ "version": "1.11.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.11.1",
++ "integrity": "sha512-hBaZlZRVZjkGfsPQE16rQIou2lUuusADea3Evx1tVjB84FTHGHS7ZALfcXaNOk7dR53HOVTJrQ0njmScdreUiw==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/package.json b/package.json
iindex e1bff24..042f8dd 100644
---- a/package.json
++++ b/package.json
@@@ -28,7 +28,7 @@
"multer": "^2.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.35.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.7.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.11.1"
},
"engines": {
"node": ">=20.0.0"
ddiff --git a/server/app.js b/server/app.js
iindex 3623d5d..2822770 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -211,6 +211,8 @@ function createApp(opts = {}) {
html(res, pages.homePage({ latest, trending, languages, user: req.user }));
}));
++ app.get('/updates', (req, res) => html(res, pages.updatesPage()));
++
app.get('/pastes', withUser, wrap(async (req, res) => {
const [result, languages] = await Promise.all([catalog.browse(req.query), catalog.languages()]);
html(res, pages.browsePage(result, languages));
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex 4c5da70..25232e3 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -3,7 +3,7 @@
/**
* Page shell — every page on the site is server-rendered through this: full <head> SEO
* (title, description, canonical, robots, Open Graph, Twitter card, JSON-LD), the shared
-- * OpenVibe chrome (theme-loader first so there is no flash, navbar.js + footer.js from the
++ * 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.
*/
const crypto = require('crypto');
@@@ -54,6 +54,7 @@ function navbarInit(opts) {
silentLogin: `${config.baseUrl}/auth/login?silent=1&next={url}`,
sessionUrl: '/auth/me',
loginUrl: `/auth/login?next=${encodeURIComponent(opts.canonicalPath || '/')}`,
++ logoutUrl: '/auth/logout?next={path}', // Sign out in the shared navbar ends this site's session too
};
return cfg;
}
@@@ -64,6 +65,7 @@ function footerInit(opts) {
variant: opts.footerVariant || 'full',
mount: '#ov-footer',
brandName: SITE_NAME,
++ updates: '/updates', // the footer's "shipped X ago" line and Updates link open this site's log
tagline: 'Community-run and open source. Free speech within the rules — the people of OpenVibe.',
legalBase: config.liveUrl,
links: [{
@@@ -87,7 +89,7 @@ function footerInit(opts) {
* ogImage, jsonLd (array), body (main HTML), active ('home'|'pastes'|'spaces'|'pulse'|'new'|'my'),
* feeds ([{ title, href }] RSS alternates; default: the latest-pastes feed),
* historyType ('page'|'paste'), historyTitle, footerVariant ('full'|'compact'), bodyClass,
-- * published/modified (ISO, for article:*), noChrome (error pages during outages)
++ * 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`;
@@@ -135,11 +137,11 @@ ${jsonLdScript(o.jsonLd)}
<main id="main" class="page">
${o.body || ''}
</main>
--${require('openvibe-shared/footer').ssr({ service: 'community', variant: 'full' })}
++${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) { /* chrome is optional */ }
++ 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) { /* */ }
});
</script>
ddiff --git a/server/render/pages.js b/server/render/pages.js
iindex 2d65b67..6430f07 100644
---- a/server/render/pages.js
++++ b/server/render/pages.js
@@@ -9,6 +9,7 @@ const config = require('../config');
const live = require('../live-client');
const seo = require('../seo');
const { renderPage, SITE_NAME, DEFAULT_OG_IMAGE } = require('./layout');
++const frame = require('openvibe-shared/frame');
const { highlight, escapeHtml: esc, languageLabel, extensionFor, LANGUAGES } = require('./highlight');
const NETWORK_URL = 'https://openvibe.network';
@@@ -135,7 +136,8 @@ function homePage({ latest, trending, languages, user }) {
<div class="coming-item"><i class="fa-solid fa-comments" aria-hidden="true"></i><h3><a href="/s/general">Threads</a></h3><p>Long-form discussion that outlives a chat scrollback: start one in any open space.</p></div>
<div class="coming-item"><i class="fa-solid fa-wave-square" aria-hidden="true"></i><h3><a href="/pulse">Pulse</a></h3><p>What is happening across the OpenVibe network, in one feed.</p></div>
</div>
--</section>`;
++</section>
++<section class="section">${frame.shipped({ service: 'community', title: 'Recently shipped on OpenVibe.Community' })}</section>`;
return renderPage({
title: null,
description: 'The people of OpenVibe. A community-run, open source home for pastes — code, logs and screenshots with a link — spaces and threads, and soon submissions. Free speech within the rules.',
@@@ -146,6 +148,16 @@ function homePage({ latest, trending, languages, user }) {
});
}
++// ── What shipped: the shared update log every OpenVibe site has ──
++function updatesPage() {
++ return renderPage({
++ title: 'What shipped on OpenVibe.Community',
++ description: 'Every change deployed to OpenVibe.Community, newest first, with the Patch notes that gather them.',
++ canonicalPath: '/updates',
++ body: frame.updatesBody({ service: 'community', siteName: 'OpenVibe.Community' }) + frame.shippedScript(),
++ });
++}
++
// ── Browse ───────────────────────────────────────────────────
function browsePage(result, languages) {
const { pastes, total, page, pages, q, sort, lang, type = 'pastes', windowed, windowSize } = result;
@@@ -384,4 +396,4 @@ function errorPage({ status = 500, title = 'Something went wrong', message = '',
return renderPage({ title, description: message || title, canonicalPath: '/', robots: 'noindex,nofollow', footerVariant: 'compact', body });
}
--module.exports = { homePage, browsePage, pastePage, newPage, myPage, errorPage, pasteCard, cardGrid, timeAgo, timeTag, fmtDate, num, authorHtml };
++module.exports = { homePage, updatesPage, browsePage, pastePage, newPage, myPage, errorPage, pasteCard, cardGrid, timeAgo, timeTag, fmtDate, num, authorHtml };
ddiff --git a/test/shipped-pages.test.js b/test/shipped-pages.test.js
nnew file mode 100644
iindex 0000000..b52c6fd
---- /dev/null
++++ b/test/shipped-pages.test.js
@@@ -0,0 +1,16 @@
++'use strict';
++// The shared update system on openvibe.community: the home shows what shipped, /updates is the log
++// (openvibe-shared/frame markup, the same on every OpenVibe site), the footer links it, and the
++// shared navbar signs out through this site (logoutUrl).
++const assert = require('assert');
++const pages = require('../server/render/pages');
++
++const home = pages.homePage({ latest: [], trending: [], languages: [], user: null });
++assert.ok(home.includes('data-ov-shipped="latest" data-service="community" href="/updates"'), 'home pill');
++assert.ok(home.includes('data-ov-shipped="list" data-service="community"'), 'home recent list');
++assert.ok(home.includes('"logoutUrl":"/auth/logout?next={path}"'));
++assert.ok(home.includes('"updates":"/updates"'));
++const up = pages.updatesPage();
++assert.ok(up.includes('What shipped on OpenVibe.Community') && up.includes('data-ov-shipped="log" data-service="community"'));
++assert.ok(/<link rel="canonical" href="[^"]*\/updates">/.test(up));
++console.log('shipped pages: all checks passed');