openvibers/openvibe.news Commits

The shared update system and one account UI, on the OpenVibe Frame (openvibe-shared 1.11.0, openvibe/frame): the home shows what shipped, /updates is the shared log and the footer links it; the shared navbar handles sign in and out (logoutUrl ends this site's session) and carries this site's account links in its menu, while the page's own account bar is only for no-JS visitors

OP OpenVibers committed 24 Sept 2026 · b3e752beb9 · 5 files, +34 −19


Changes

ddiff --git a/package-lock.json b/package-lock.json
iindex 39a7c08..ee7b0d6 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -16,9 +16,9 @@
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
-- "openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.2.2",
++ "openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.3.2",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.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.0"
},
"devDependencies": {
"jsonwebtoken": "^9.0.2"
@@@ -1031,12 +1031,12 @@
}
},
"node_modules/openvibe-publishing": {
-- "version": "0.2.2",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.2.2",
-- "integrity": "sha512-+aX5lcXQfvfczCY4Ca89B5uYugCS/uncu4gYI7U/mQoOioXZMHk/mCu2f1/+qxM1Ehi6EA2eB/0TCRv7jbuHbA==",
++ "version": "0.3.2",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.3.2",
++ "integrity": "sha512-XNpM7JJHCWlk2SZRzRqpu2kmWF9kMj6+tS3433hRKdyAB3jubi7Xzeav0OGX1wtqlKk4k00R186XiFmx1igTDg==",
"license": "MIT",
"dependencies": {
-- "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.0"
},
"engines": {
"node": ">=20"
@@@ -1060,9 +1060,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.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.11.0",
++ "integrity": "sha512-edMW0I25HNFReLcfZ9ak+4q5sy2t7qq7EPG52tOerTBV2fU994C5we/02AD/9i2Vuq0+ayNBBisW0cg60L6DLQ==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/package.json b/package.json
iindex 7a5d054..abcaf63 100644
---- a/package.json
++++ b/package.json
@@@ -23,9 +23,9 @@
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
-- "openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.2.2",
++ "openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.3.2",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.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.0"
},
"devDependencies": {
"jsonwebtoken": "^9.0.2"
ddiff --git a/server/app.js b/server/app.js
iindex 23cae79..cb88921 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -84,7 +84,7 @@ function createApp(opts = {}) {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
-- // The shared chrome (theme-loader, navbar) comes from the Network; the inline init is ours.
++ // The OpenVibe Frame (theme-loader, navbar, footer) comes from the Network; the inline init is ours.
scriptSrc: ["'self'", "'unsafe-inline'", 'https://openvibe.network'],
styleSrc: ["'self'", "'unsafe-inline'", 'https://openvibe.network', 'https://fonts.googleapis.com', 'https://cdnjs.cloudflare.com'],
fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com', 'https://cdnjs.cloudflare.com'],
ddiff --git a/server/http/public.js b/server/http/public.js
iindex 862d085..3e9f36e 100644
---- a/server/http/public.js
++++ b/server/http/public.js
@@@ -18,6 +18,7 @@
* built for a viewer. Robots come from the gate: a retracted story is noindex with its reason.
*/
const express = require('express');
++const frame = require('openvibe-shared/frame');
const rateLimit = require('express-rate-limit');
const seo = require('openvibe-publishing/seo');
const ssr = require('openvibe-publishing/ssr');
@@@ -87,6 +88,12 @@ function createPublicRoutes(ctx) {
}, { cacheable: true });
}
++ // What shipped on OpenVibe.News: the shared update log every OpenVibe site has.
++ router.get('/updates', (req, res) => send(req, res, 200, {
++ title: 'What shipped on OpenVibe.News', description: 'Every change deployed to OpenVibe.News, newest first.',
++ decision: pageDecision('/updates'), canonical: `${config.baseUrl}/updates`,
++ body: frame.updatesBody({ service: 'news', siteName: 'OpenVibe.News' }) + frame.shippedScript(),
++ }, { cacheable: true }));
router.get('/', wrap((req, res) => listing(req, res, {})));
router.get('/topics', wrap(async (req, res) => {
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex 4fbe465..f880655 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -5,7 +5,7 @@
* - <head>: title, description, canonical and robots from the indexability gate's decision
* (openvibe-publishing/seo metaTags — there is no default that makes a page indexable),
* Open Graph/Twitter, JSON-LD, feed links, the shared app icon
-- * - the shared chrome: navbar.js and theme-loader.js from the Network (progressive), a
++ * - 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)
*/
const crypto = require('crypto');
@@@ -14,7 +14,7 @@ const path = require('path');
const seo = require('openvibe-publishing/seo');
const { escapeHtml: esc } = require('openvibe-publishing/ssr');
const appIcon = require('openvibe-shared/app-icon');
--const chrome = require('openvibe-shared/chrome-ssr');
++const frame = require('openvibe-shared/frame');
const NETWORK_URL = 'https://openvibe.network';
const SITE_NAME = 'OpenVibe.News';
@@@ -64,7 +64,12 @@ function renderPage(o) {
silentLogin: `${o.config.baseUrl}/auth/login?silent=1&next={url}`,
sessionUrl: '/auth/me',
loginUrl: `/auth/login?next=${loginNext}`,
++ logoutUrl: '/auth/logout?next={path}', // Sign out in the shared navbar ends this site's session too
};
++ // This site's own account links live in the shared navbar's account menu (the page's account
++ // bar below is only for visitors without JavaScript).
++ if (signedIn) nav.menu = { before: (o.editor ? [{ label: 'Editor desk', href: '/edit', icon: 'fa-pen' }] : []) };
++ const footer = { service: 'news', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME, updates: '/updates' };
const account = signedIn
? `${o.editor ? '<a href="/edit">Editor desk</a> · ' : ''}<a href="/auth/logout?next=${loginNext}">Sign out</a>`
: `<a href="/auth/login?next=${loginNext}">Sign in with OpenVibe</a>`;
@@@ -81,20 +86,23 @@ ${appIcon.headTags({ site: 'news' })}
<link rel="stylesheet" href="${asset('css/news.css')}">
<script src="${NETWORK_URL}/shared/theme-loader.js" defer></script>
<script src="${NETWORK_URL}/shared/navbar.js" defer></script>
++<script src="${NETWORK_URL}/shared/footer.js" defer></script>
</head>
<body class="${esc(o.bodyClass || '')}">
<a class="skip" href="#main">Skip to content</a>
<div id="navbar-mount"></div>
--${chrome.noscriptNav({ name: SITE_NAME, home: '/', links: [{ label: 'News', href: '/' }, { label: 'Topics', href: '/topics' }] })}
--<div class="account-bar" role="navigation" aria-label="Account">${account}</div>
++${frame.noscriptNav({ name: SITE_NAME, home: '/', links: [{ label: 'News', href: '/' }, { label: 'Topics', href: '/topics' }] })}
++<noscript><div class="account-bar" role="navigation" aria-label="Account">${account}</div></noscript>
<main id="main" class="page">
${o.body || ''}
++${o.path === '/' ? frame.shipped({ service: 'news', title: `Recently shipped on ${SITE_NAME}` }) : ''}
</main>
--${chrome.footer({ service: 'news', variant: 'full' })}
++${frame.footer(footer)}
<script>
--window.__OV_PAGE = ${JSON.stringify({ navbar: nav }).replace(/</g, '\\u003c')};
++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 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>
</body>