openvibers/openvibe.blog Commits
The shared update system and one account UI: the home shows what shipped, /updates is the shared log, the footer (now initialised in the browser too) links it; the shared navbar handles sign in/out with logoutUrl ending this site's session, and the page's own 'Write · Sign out' bar is only for no-JS visitors. openvibe-shared 1.10.0, openvibe-publishing 0.3.1
Changes
@@@ -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.3.0",++ "openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.3.1","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.10.0"},"devDependencies": {"jsonwebtoken": "^9.0.2"@@@ -1031,12 +1031,12 @@}},"node_modules/openvibe-publishing": {-- "version": "0.3.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.3.0",-- "integrity": "sha512-zekpQwHyxkhVW5CZf3ccDPkOniV1hjyw9VlVajA+4bLRCsLUo+jcxamtFIVM2IJ0i8pDlF/MpUfj4wSSv9H/iw==",++ "version": "0.3.1",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.3.1",++ "integrity": "sha512-fK7BqaLKLmTwpWTPL+1cQt13tbG6Tp407hIooVWJ8Hi5QvZIzJxQPWb3lnzdCDjAOUCCgxmNNQ0o+fQbohbUJg==","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.10.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.10.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.10.0",++ "integrity": "sha512-d2GkNuJkx6as4NKiryIBAGuff+7kY+eDMmGxK3AR91yLcgW8UYugML0foas/vdhqRNowE2I8JRVEnD01ZLhnrQ==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -22,9 +22,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.3.0",++ "openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.3.1","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.10.0"},"devDependencies": {"jsonwebtoken": "^9.0.2"@@@ -17,6 +17,7 @@* members/VIP and private posts, refusals, 404/410 — is `private, no-store` + X-Robots-Tag.*/const express = require('express');++const chromeSsr = require('openvibe-shared/chrome-ssr');const seo = require('openvibe-publishing/seo');const ssr = require('openvibe-publishing/ssr');const authorship = require('openvibe-publishing/authorship');@@@ -77,7 +78,7 @@ function createPublicRoutes(ctx) {// ── Blog front pages ────────────────────────────────────-- async function blogFront(req, res, blog) {++ async function blogFront(req, res, blog, { home = false } = {}) {const page = pageNumber(req);const restricted = isMember(blog, req.viewer);const path = publication.blogPath(blog);@@@ -109,11 +110,21 @@ function createPublicRoutes(ctx) {blog, blogUrl: `/@${blog.handle}`, items, pager, feeds,series: blogs.series(blog).filter((sr) => shows({ seriesId: sr.id })), categories: prune(reading.categoriesTree(blog)),canWrite: access.canWrite(store, req.viewer, blog, 'create'),-- }),++ }) + (home && pager.page === 1 ? chromeSsr.shipped({ service: 'blog', title: 'Recently shipped on OpenVibe.Blog' }) : ''),}, { cacheable: !restricted });}-- router.get('/', wrap(async (req, res) => blogFront(req, res, blogs.official())));++ router.get('/', wrap(async (req, res) => blogFront(req, res, blogs.official(), { home: true })));++++ // What shipped on OpenVibe.Blog: the shared update log (openvibe-shared shipped.js, fed by the++ // network changelog this service keeps), like every OpenVibe site's /updates.++ router.get('/updates', wrap(async (req, res) => send(req, res, 200, {++ title: 'What shipped on OpenVibe.Blog',++ description: 'Every change deployed to OpenVibe.Blog, newest first, with the Patch notes that gather them.',++ decision: pageDecision('/updates'),++ canonical: seo.canonicalUrl(config.baseUrl, '/updates'),++ body: chromeSsr.updatesBody({ service: 'blog', siteName: 'OpenVibe.Blog' }) + chromeSsr.shippedScript(),++ }, { cacheable: true })));router.get('/@:handle', wrap(async (req, res) => {const blog = activeBlog(req);@@@ -72,7 +72,9 @@ 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};++ const footer = { service: 'blog', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME, updates: '/updates' };const style = o.theme ? ` style="${esc(themeStyle(o.theme.vars))}" data-blog-theme="${esc(o.theme.slug)}"` : '';const account = signedIn? `<a href="/write">Write</a> · <a href="/auth/logout?next=${loginNext}">Sign out</a>`@@@ -90,20 +92,22 @@ ${appIcon.headTags({ site: 'blog' })}<link rel="stylesheet" href="${asset('css/blog.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: 'Blog', href: '/' }, { label: 'Write', href: '/write' }] })}--<div class="account-bar" role="navigation" aria-label="Account">${account}</div>++<noscript><div class="account-bar" role="navigation" aria-label="Account">${account}</div></noscript><main id="main" class="page blog-surface"${style}>${o.body || ''}</main>--${chrome.footer({ service: 'blog', variant: 'full' })}++${chrome.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.OpenVibeFooter) OpenVibeFooter.init(window.__OV_PAGE.footer); } catch (e) { /* */ }});</script></body>nnew file mode 100644@@@ -0,0 +1,32 @@++'use strict';++/**++ * The shared update system on openvibe.blog: the home shows what shipped, /updates is the log, the++ * footer carries the "shipped" line and an Updates link, and the shared navbar (not a second account++ * bar) handles sign in and out, with Sign out ending this site's session.++ */++const assert = require('assert');++const { boot, check, done } = require('./helpers/boot');++++(async () => {++ const t = await boot();++ await check('the home shows the shipped block; /updates is the shared log', async () => {++ const home = await t.get('/');++ assert.strictEqual(home.status, 200);++ assert.ok(home.text.includes('data-ov-shipped="latest" data-service="blog" href="/updates"'));++ assert.ok(home.text.includes('data-ov-shipped="list" data-service="blog"'));++ const up = await t.get('/updates');++ assert.strictEqual(up.status, 200);++ assert.ok(up.text.includes('What shipped on OpenVibe.Blog') && up.text.includes('data-ov-shipped="log" data-service="blog"'));++ assert.ok(up.text.includes('https://openvibe.network/shared/shipped.js'));++ assert.ok(up.text.includes('https://openvibe.network/updates?site=blog'), 'no-JavaScript fallback');++ });++ await check('one account UI: the shared navbar signs out through this site; the old bar is only for no-JS', async () => {++ const home = await t.get('/');++ assert.ok(home.text.includes('"logoutUrl":"/auth/logout?next={path}"'));++ assert.ok(/<noscript><div class="account-bar"/.test(home.text), 'the site account bar only without JavaScript');++ assert.ok(home.text.includes('https://openvibe.network/shared/footer.js') && home.text.includes('"updates":"/updates"'));++ assert.ok(home.text.includes('href="/updates"'), 'the footer links the update log');++ });++ await t.close();++ done();++})();
@@ -16,9 +16,9 @@
@@ -16,9 +16,9 @@
"express-rate-limit": "^7.5.0",
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"helmet": "^8.1.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.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.3.0",
+ "openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.3.1",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.7.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.10.0"
},
},
"devDependencies": {
"devDependencies": {
"jsonwebtoken": "^9.0.2"
"jsonwebtoken": "^9.0.2"
@@ -1031,12 +1031,12 @@
@@ -1031,12 +1031,12 @@
}
}
},
},
"node_modules/openvibe-publishing": {
"node_modules/openvibe-publishing": {
- "version": "0.3.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.3.0",
- "integrity": "sha512-zekpQwHyxkhVW5CZf3ccDPkOniV1hjyw9VlVajA+4bLRCsLUo+jcxamtFIVM2IJ0i8pDlF/MpUfj4wSSv9H/iw==",
+ "version": "0.3.1",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.3.1",
+ "integrity": "sha512-fK7BqaLKLmTwpWTPL+1cQt13tbG6Tp407hIooVWJ8Hi5QvZIzJxQPWb3lnzdCDjAOUCCgxmNNQ0o+fQbohbUJg==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"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.10.0"
},
},
"engines": {
"engines": {
"node": ">=20"
"node": ">=20"
@@ -1060,9 +1060,9 @@
@@ -1060,9 +1060,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"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.10.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.10.0",
+ "integrity": "sha512-d2GkNuJkx6as4NKiryIBAGuff+7kY+eDMmGxK3AR91yLcgW8UYugML0foas/vdhqRNowE2I8JRVEnD01ZLhnrQ==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -22,9 +22,9 @@
@@ -22,9 +22,9 @@
"express-rate-limit": "^7.5.0",
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"helmet": "^8.1.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.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.3.0",
+ "openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.3.1",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.7.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.10.0"
},
},
"devDependencies": {
"devDependencies": {
"jsonwebtoken": "^9.0.2"
"jsonwebtoken": "^9.0.2"
@@ -17,6 +17,7 @@
@@ -17,6 +17,7 @@
* members/VIP and private posts, refusals, 404/410 — is `private, no-store` + X-Robots-Tag.
* members/VIP and private posts, refusals, 404/410 — is `private, no-store` + X-Robots-Tag.
*/
*/
const express = require('express');
const express = require('express');
+const chromeSsr = require('openvibe-shared/chrome-ssr');
const seo = require('openvibe-publishing/seo');
const seo = require('openvibe-publishing/seo');
const ssr = require('openvibe-publishing/ssr');
const ssr = require('openvibe-publishing/ssr');
const authorship = require('openvibe-publishing/authorship');
const authorship = require('openvibe-publishing/authorship');
@@ -77,7 +78,7 @@ function createPublicRoutes(ctx) {
@@ -77,7 +78,7 @@ function createPublicRoutes(ctx) {
// ── Blog front pages ────────────────────────────────────
// ── Blog front pages ────────────────────────────────────
- async function blogFront(req, res, blog) {
+ async function blogFront(req, res, blog, { home = false } = {}) {
const page = pageNumber(req);
const page = pageNumber(req);
const restricted = isMember(blog, req.viewer);
const restricted = isMember(blog, req.viewer);
const path = publication.blogPath(blog);
const path = publication.blogPath(blog);
@@ -109,11 +110,21 @@ function createPublicRoutes(ctx) {
@@ -109,11 +110,21 @@ function createPublicRoutes(ctx) {
blog, blogUrl: `/@${blog.handle}`, items, pager, feeds,
blog, blogUrl: `/@${blog.handle}`, items, pager, feeds,
series: blogs.series(blog).filter((sr) => shows({ seriesId: sr.id })), categories: prune(reading.categoriesTree(blog)),
series: blogs.series(blog).filter((sr) => shows({ seriesId: sr.id })), categories: prune(reading.categoriesTree(blog)),
canWrite: access.canWrite(store, req.viewer, blog, 'create'),
canWrite: access.canWrite(store, req.viewer, blog, 'create'),
- }),
+ }) + (home && pager.page === 1 ? chromeSsr.shipped({ service: 'blog', title: 'Recently shipped on OpenVibe.Blog' }) : ''),
}, { cacheable: !restricted });
}, { cacheable: !restricted });
}
}
- router.get('/', wrap(async (req, res) => blogFront(req, res, blogs.official())));
+ router.get('/', wrap(async (req, res) => blogFront(req, res, blogs.official(), { home: true })));
+
+ // What shipped on OpenVibe.Blog: the shared update log (openvibe-shared shipped.js, fed by the
+ // network changelog this service keeps), like every OpenVibe site's /updates.
+ router.get('/updates', wrap(async (req, res) => send(req, res, 200, {
+ title: 'What shipped on OpenVibe.Blog',
+ description: 'Every change deployed to OpenVibe.Blog, newest first, with the Patch notes that gather them.',
+ decision: pageDecision('/updates'),
+ canonical: seo.canonicalUrl(config.baseUrl, '/updates'),
+ body: chromeSsr.updatesBody({ service: 'blog', siteName: 'OpenVibe.Blog' }) + chromeSsr.shippedScript(),
+ }, { cacheable: true })));
router.get('/@:handle', wrap(async (req, res) => {
router.get('/@:handle', wrap(async (req, res) => {
const blog = activeBlog(req);
const blog = activeBlog(req);
@@ -72,7 +72,9 @@ function renderPage(o) {
@@ -72,7 +72,9 @@ function renderPage(o) {
silentLogin: `${o.config.baseUrl}/auth/login?silent=1&next={url}`,
silentLogin: `${o.config.baseUrl}/auth/login?silent=1&next={url}`,
sessionUrl: '/auth/me',
sessionUrl: '/auth/me',
loginUrl: `/auth/login?next=${loginNext}`,
loginUrl: `/auth/login?next=${loginNext}`,
+ logoutUrl: '/auth/logout?next={path}', // Sign out in the shared navbar ends this site's session too
};
};
+ const footer = { service: 'blog', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME, updates: '/updates' };
const style = o.theme ? ` style="${esc(themeStyle(o.theme.vars))}" data-blog-theme="${esc(o.theme.slug)}"` : '';
const style = o.theme ? ` style="${esc(themeStyle(o.theme.vars))}" data-blog-theme="${esc(o.theme.slug)}"` : '';
const account = signedIn
const account = signedIn
? `<a href="/write">Write</a> · <a href="/auth/logout?next=${loginNext}">Sign out</a>`
? `<a href="/write">Write</a> · <a href="/auth/logout?next=${loginNext}">Sign out</a>`
@@ -90,20 +92,22 @@ ${appIcon.headTags({ site: 'blog' })}
@@ -90,20 +92,22 @@ ${appIcon.headTags({ site: 'blog' })}
<link rel="stylesheet" href="${asset('css/blog.css')}">
<link rel="stylesheet" href="${asset('css/blog.css')}">
<script src="${NETWORK_URL}/shared/theme-loader.js" defer></script>
<script src="${NETWORK_URL}/shared/theme-loader.js" defer></script>
<script src="${NETWORK_URL}/shared/navbar.js" defer></script>
<script src="${NETWORK_URL}/shared/navbar.js" defer></script>
+<script src="${NETWORK_URL}/shared/footer.js" defer></script>
</head>
</head>
<body class="${esc(o.bodyClass || '')}">
<body class="${esc(o.bodyClass || '')}">
<a class="skip" href="#main">Skip to content</a>
<a class="skip" href="#main">Skip to content</a>
<div id="navbar-mount"></div>
<div id="navbar-mount"></div>
${chrome.noscriptNav({ name: SITE_NAME, home: '/', links: [{ label: 'Blog', href: '/' }, { label: 'Write', href: '/write' }] })}
${chrome.noscriptNav({ name: SITE_NAME, home: '/', links: [{ label: 'Blog', href: '/' }, { label: 'Write', href: '/write' }] })}
-<div class="account-bar" role="navigation" aria-label="Account">${account}</div>
+<noscript><div class="account-bar" role="navigation" aria-label="Account">${account}</div></noscript>
<main id="main" class="page blog-surface"${style}>
<main id="main" class="page blog-surface"${style}>
${o.body || ''}
${o.body || ''}
</main>
</main>
-${chrome.footer({ service: 'blog', variant: 'full' })}
+${chrome.footer(footer)}
<script>
<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 () {
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 chrome is optional */ }
+ try { if (window.OpenVibeFooter) OpenVibeFooter.init(window.__OV_PAGE.footer); } catch (e) { /* */ }
});
});
</script>
</script>
</body>
</body>
new file mode 100644
new file mode 100644
@@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
+'use strict';
+/**
+ * The shared update system on openvibe.blog: the home shows what shipped, /updates is the log, the
+ * footer carries the "shipped" line and an Updates link, and the shared navbar (not a second account
+ * bar) handles sign in and out, with Sign out ending this site's session.
+ */
+const assert = require('assert');
+const { boot, check, done } = require('./helpers/boot');
+
+(async () => {
+ const t = await boot();
+ await check('the home shows the shipped block; /updates is the shared log', async () => {
+ const home = await t.get('/');
+ assert.strictEqual(home.status, 200);
+ assert.ok(home.text.includes('data-ov-shipped="latest" data-service="blog" href="/updates"'));
+ assert.ok(home.text.includes('data-ov-shipped="list" data-service="blog"'));
+ const up = await t.get('/updates');
+ assert.strictEqual(up.status, 200);
+ assert.ok(up.text.includes('What shipped on OpenVibe.Blog') && up.text.includes('data-ov-shipped="log" data-service="blog"'));
+ assert.ok(up.text.includes('https://openvibe.network/shared/shipped.js'));
+ assert.ok(up.text.includes('https://openvibe.network/updates?site=blog'), 'no-JavaScript fallback');
+ });
+ await check('one account UI: the shared navbar signs out through this site; the old bar is only for no-JS', async () => {
+ const home = await t.get('/');
+ assert.ok(home.text.includes('"logoutUrl":"/auth/logout?next={path}"'));
+ assert.ok(/<noscript><div class="account-bar"/.test(home.text), 'the site account bar only without JavaScript');
+ assert.ok(home.text.includes('https://openvibe.network/shared/footer.js') && home.text.includes('"updates":"/updates"'));
+ assert.ok(home.text.includes('href="/updates"'), 'the footer links the update log');
+ });
+ await t.close();
+ done();
+})();