openvibers/openvibe.run Commits
Every Run page sends the same page list, addressed from anywhere (#263)
Changes
@@@ -177,7 +177,15 @@ function createApp(opts = {}) {res.type('html').send(home.renderUpdates({ config }));});{ const legal = require('openvibe-shared/legal');-- app.get(legal.PATHS, (req, res, next) => { res.setHeader('Content-Security-Policy', home.HOME_CSP); next(); }, legal.handler({ id: 'run', service: 'run', host: 'openvibe.run', name: home.SITE_NAME, profile: 'hosting' })); }++ // Shared's legal template starts the navbar without links, so /terms and /privacy showed its fallback++ // (Home, My Account, Themes) instead of Run's own pages. Add Run's list to that init call.++ const navLinks = JSON.stringify(home.NAV_LINKS);++ app.get(legal.PATHS, (req, res, next) => {++ res.setHeader('Content-Security-Policy', home.HOME_CSP);++ const send = res.send.bind(res);++ res.send = (body) => send(String(body).replace('try{OpenVibeNavbar.init({service:', `try{OpenVibeNavbar.init({links:${navLinks},service:`));++ next();++ }, legal.handler({ id: 'run', service: 'run', host: 'openvibe.run', name: home.SITE_NAME, profile: 'hosting' })); }// The agent layer (openvibe-shared/agent-layer): /.well-known/agent.json, and the Markdown twins of the home// and the update log. Mounted after the page routes, so a browser keeps this site's own page (which names the// twin) and only a twin request reaches this renderer; before the fallback, so an unknown .md is a 404.@@@ -17,6 +17,9 @@ const appIcon = require('openvibe-shared/app-icon');const ovServe = require('openvibe-shared/serve');const SITE_NAME = 'OpenVibe.Run';++// The site's own places, the same on every page: home's sections, addressed from anywhere (on /updates a bare++// "#how" went nowhere). The first row is the home itself.++const NAV_LINKS = [{ label: 'Your machine', href: '/#machine' }, { label: 'How it works', href: '/#how' }, { label: 'API', href: '/#api' }];// The questions the catalog answers for Run (openvibe-contracts manifests/services/run.json, kept word for word): shown// on the home and given to search engines as FAQPage data.const FAQ = ((require('openvibe-contracts/manifests/services/run.json').site || {}).faq || [])@@@ -73,7 +76,7 @@ function renderHome({ config }) {const nav = {service: 'run',apiBase: NETWORK_URL, sessionUrl: '/auth/me', loginUrl: '/auth/login?next={path}', logoutUrl: '/auth/logout?next={url}', silentLogin: '/auth/login?silent=1&next={url}',-- links: [{ label: 'Your machine', href: '#machine' }, { label: 'How it works', href: '#how' }, { label: 'API', href: '#api' }],++ links: NAV_LINKS,history: { type: 'page', title: SITE_NAME },};return shell.page({@@@ -98,7 +101,7 @@ const UPDATES_FONT = "<style>:where(body){font-family:var(--ov-font,'Inter',-app/** /updates: what shipped on this site, the network's shared update log (openvibe-shared/frame updatesBody). */function renderUpdates({ config }) {-- const links = [{ label: 'How it works', href: '/#how' }, { label: 'API', href: '/#api' }];++ const links = NAV_LINKS;return shell.page({name: SITE_NAME, service: 'run', lang: 'en',title: `What shipped on ${SITE_NAME}`,@@@ -115,7 +118,7 @@ function renderUpdates({ config }) {/** The page a browser gets for a path nothing serves: the site's own Frame, one way back, and no clue about what was asked for. */function renderNotFound({ config }) {const host = String(config.baseUrl).replace(/^https?:\/\//, '').replace(/\/.*$/, '');-- const links = [{ label: 'How it works', href: '/#how' }, { label: 'API', href: '/#api' }];++ const links = NAV_LINKS;return shell.page({name: SITE_NAME, service: 'run', lang: 'en',title: `Page not found · ${SITE_NAME}`,@@@ -258,4 +261,4 @@ function body() {});}--module.exports = { renderHome, renderUpdates, renderNotFound, HOME_CSP, SITE_NAME, DESCRIPTION, FAQ };++module.exports = { renderHome, renderUpdates, renderNotFound, HOME_CSP, SITE_NAME, DESCRIPTION, FAQ, NAV_LINKS };nnew file mode 100644@@@ -0,0 +1,45 @@++'use strict';++// Every page of openvibe.run gives the shared navbar the site's own page list (the owner, 2026-11-12): the home,++// /updates, /terms, /privacy and /dmca all send Your machine / How it works / API, addressed from any page.++const assert = require('assert');++const { boot, check, done } = require('./helpers/app');++const { NAV_LINKS } = require('../server/web/home');++++function initLinks(html) {++ const at = html.indexOf('OpenVibeNavbar.init');++ if (at < 0) return null;++ const start = html.indexOf('links', at);++ const open = html.indexOf('[', start);++ let depth = 0, q = null, esc = false;++ for (let i = open; i < html.length; i += 1) {++ const c = html[i];++ if (q) { if (esc) esc = false; else if (c === '\\') esc = true; else if (c === q) q = null; continue; }++ if (c === '"' || c === "'") { q = c; continue; }++ if (c === '[') depth += 1; else if (c === ']') { depth -= 1; if (!depth) return JSON.parse(html.slice(open, i + 1)); }++ }++ return null;++}++++(async () => {++ const t = await boot();++ try {++ const own = NAV_LINKS.map(({ label, href }) => ({ label, href }));++ await check('every page sends Run\'s own list, not the shared fallback', async () => {++ for (const path of ['/', '/updates', '/terms', '/privacy', '/dmca']) {++ const r = await t.call('GET', path, { token: null });++ assert.strictEqual(r.status, 200, `${path} answered ${r.status}`);++ const links = (initLinks(r.text) || []).map(({ label, href }) => ({ label, href }));++ assert.deepStrictEqual(links, own, `${path} sends Run's list`);++ }++ });++ await check('every row is a route or a section of the home', async () => {++ for (const { href } of NAV_LINKS) {++ const [path, hash] = href.split('#');++ const r = await t.call('GET', path || '/', { token: null });++ assert.strictEqual(r.status, 200, `${path} is a route`);++ if (hash) assert.ok(r.text.includes(`id="${hash}"`), `${href} names a section on the home`);++ }++ });++ } finally { await t.close(); }++ done();++})().catch((e) => { console.error(e); process.exit(1); });
@@ -177,7 +177,15 @@ function createApp(opts = {}) {
@@ -177,7 +177,15 @@ function createApp(opts = {}) {
res.type('html').send(home.renderUpdates({ config }));
res.type('html').send(home.renderUpdates({ config }));
});
});
{ const legal = require('openvibe-shared/legal');
{ const legal = require('openvibe-shared/legal');
- app.get(legal.PATHS, (req, res, next) => { res.setHeader('Content-Security-Policy', home.HOME_CSP); next(); }, legal.handler({ id: 'run', service: 'run', host: 'openvibe.run', name: home.SITE_NAME, profile: 'hosting' })); }
+ // Shared's legal template starts the navbar without links, so /terms and /privacy showed its fallback
+ // (Home, My Account, Themes) instead of Run's own pages. Add Run's list to that init call.
+ const navLinks = JSON.stringify(home.NAV_LINKS);
+ app.get(legal.PATHS, (req, res, next) => {
+ res.setHeader('Content-Security-Policy', home.HOME_CSP);
+ const send = res.send.bind(res);
+ res.send = (body) => send(String(body).replace('try{OpenVibeNavbar.init({service:', `try{OpenVibeNavbar.init({links:${navLinks},service:`));
+ next();
+ }, legal.handler({ id: 'run', service: 'run', host: 'openvibe.run', name: home.SITE_NAME, profile: 'hosting' })); }
// The agent layer (openvibe-shared/agent-layer): /.well-known/agent.json, and the Markdown twins of the home
// The agent layer (openvibe-shared/agent-layer): /.well-known/agent.json, and the Markdown twins of the home
// and the update log. Mounted after the page routes, so a browser keeps this site's own page (which names the
// and the update log. Mounted after the page routes, so a browser keeps this site's own page (which names the
// twin) and only a twin request reaches this renderer; before the fallback, so an unknown .md is a 404.
// twin) and only a twin request reaches this renderer; before the fallback, so an unknown .md is a 404.
@@ -17,6 +17,9 @@ const appIcon = require('openvibe-shared/app-icon');
@@ -17,6 +17,9 @@ const appIcon = require('openvibe-shared/app-icon');
const ovServe = require('openvibe-shared/serve');
const ovServe = require('openvibe-shared/serve');
const SITE_NAME = 'OpenVibe.Run';
const SITE_NAME = 'OpenVibe.Run';
+// The site's own places, the same on every page: home's sections, addressed from anywhere (on /updates a bare
+// "#how" went nowhere). The first row is the home itself.
+const NAV_LINKS = [{ label: 'Your machine', href: '/#machine' }, { label: 'How it works', href: '/#how' }, { label: 'API', href: '/#api' }];
// The questions the catalog answers for Run (openvibe-contracts manifests/services/run.json, kept word for word): shown
// The questions the catalog answers for Run (openvibe-contracts manifests/services/run.json, kept word for word): shown
// on the home and given to search engines as FAQPage data.
// on the home and given to search engines as FAQPage data.
const FAQ = ((require('openvibe-contracts/manifests/services/run.json').site || {}).faq || [])
const FAQ = ((require('openvibe-contracts/manifests/services/run.json').site || {}).faq || [])
@@ -73,7 +76,7 @@ function renderHome({ config }) {
@@ -73,7 +76,7 @@ function renderHome({ config }) {
const nav = {
const nav = {
service: 'run',
service: 'run',
apiBase: NETWORK_URL, sessionUrl: '/auth/me', loginUrl: '/auth/login?next={path}', logoutUrl: '/auth/logout?next={url}', silentLogin: '/auth/login?silent=1&next={url}',
apiBase: NETWORK_URL, sessionUrl: '/auth/me', loginUrl: '/auth/login?next={path}', logoutUrl: '/auth/logout?next={url}', silentLogin: '/auth/login?silent=1&next={url}',
- links: [{ label: 'Your machine', href: '#machine' }, { label: 'How it works', href: '#how' }, { label: 'API', href: '#api' }],
+ links: NAV_LINKS,
history: { type: 'page', title: SITE_NAME },
history: { type: 'page', title: SITE_NAME },
};
};
return shell.page({
return shell.page({
@@ -98,7 +101,7 @@ const UPDATES_FONT = "<style>:where(body){font-family:var(--ov-font,'Inter',-app
@@ -98,7 +101,7 @@ const UPDATES_FONT = "<style>:where(body){font-family:var(--ov-font,'Inter',-app
/** /updates: what shipped on this site, the network's shared update log (openvibe-shared/frame updatesBody). */
/** /updates: what shipped on this site, the network's shared update log (openvibe-shared/frame updatesBody). */
function renderUpdates({ config }) {
function renderUpdates({ config }) {
- const links = [{ label: 'How it works', href: '/#how' }, { label: 'API', href: '/#api' }];
+ const links = NAV_LINKS;
return shell.page({
return shell.page({
name: SITE_NAME, service: 'run', lang: 'en',
name: SITE_NAME, service: 'run', lang: 'en',
title: `What shipped on ${SITE_NAME}`,
title: `What shipped on ${SITE_NAME}`,
@@ -115,7 +118,7 @@ function renderUpdates({ config }) {
@@ -115,7 +118,7 @@ function renderUpdates({ config }) {
/** The page a browser gets for a path nothing serves: the site's own Frame, one way back, and no clue about what was asked for. */
/** The page a browser gets for a path nothing serves: the site's own Frame, one way back, and no clue about what was asked for. */
function renderNotFound({ config }) {
function renderNotFound({ config }) {
const host = String(config.baseUrl).replace(/^https?:\/\//, '').replace(/\/.*$/, '');
const host = String(config.baseUrl).replace(/^https?:\/\//, '').replace(/\/.*$/, '');
- const links = [{ label: 'How it works', href: '/#how' }, { label: 'API', href: '/#api' }];
+ const links = NAV_LINKS;
return shell.page({
return shell.page({
name: SITE_NAME, service: 'run', lang: 'en',
name: SITE_NAME, service: 'run', lang: 'en',
title: `Page not found · ${SITE_NAME}`,
title: `Page not found · ${SITE_NAME}`,
@@ -258,4 +261,4 @@ function body() {
@@ -258,4 +261,4 @@ function body() {
});
});
}
}
-module.exports = { renderHome, renderUpdates, renderNotFound, HOME_CSP, SITE_NAME, DESCRIPTION, FAQ };
+module.exports = { renderHome, renderUpdates, renderNotFound, HOME_CSP, SITE_NAME, DESCRIPTION, FAQ, NAV_LINKS };
new file mode 100644
new file mode 100644
@@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
+'use strict';
+// Every page of openvibe.run gives the shared navbar the site's own page list (the owner, 2026-11-12): the home,
+// /updates, /terms, /privacy and /dmca all send Your machine / How it works / API, addressed from any page.
+const assert = require('assert');
+const { boot, check, done } = require('./helpers/app');
+const { NAV_LINKS } = require('../server/web/home');
+
+function initLinks(html) {
+ const at = html.indexOf('OpenVibeNavbar.init');
+ if (at < 0) return null;
+ const start = html.indexOf('links', at);
+ const open = html.indexOf('[', start);
+ let depth = 0, q = null, esc = false;
+ for (let i = open; i < html.length; i += 1) {
+ const c = html[i];
+ if (q) { if (esc) esc = false; else if (c === '\\') esc = true; else if (c === q) q = null; continue; }
+ if (c === '"' || c === "'") { q = c; continue; }
+ if (c === '[') depth += 1; else if (c === ']') { depth -= 1; if (!depth) return JSON.parse(html.slice(open, i + 1)); }
+ }
+ return null;
+}
+
+(async () => {
+ const t = await boot();
+ try {
+ const own = NAV_LINKS.map(({ label, href }) => ({ label, href }));
+ await check('every page sends Run\'s own list, not the shared fallback', async () => {
+ for (const path of ['/', '/updates', '/terms', '/privacy', '/dmca']) {
+ const r = await t.call('GET', path, { token: null });
+ assert.strictEqual(r.status, 200, `${path} answered ${r.status}`);
+ const links = (initLinks(r.text) || []).map(({ label, href }) => ({ label, href }));
+ assert.deepStrictEqual(links, own, `${path} sends Run's list`);
+ }
+ });
+ await check('every row is a route or a section of the home', async () => {
+ for (const { href } of NAV_LINKS) {
+ const [path, hash] = href.split('#');
+ const r = await t.call('GET', path || '/', { token: null });
+ assert.strictEqual(r.status, 200, `${path} is a route`);
+ if (hash) assert.ok(r.text.includes(`id="${hash}"`), `${href} names a section on the home`);
+ }
+ });
+ } finally { await t.close(); }
+ done();
+})().catch((e) => { console.error(e); process.exit(1); });