openvibers/openvibe.search Commits
Search moves between its pages without a reload (plan T11): openvibe-shared v2.2.0 boost swaps <main> styles-first with the shared transition and does a normal load across a deploy (the ov-boost marker carries the release); page scripts that wire <main> also run on ov:boost:load. Tests pin the marker and the boost tag (verified).
Changes
@@@ -15,7 +15,7 @@"express": "^4.21.2","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.23.1",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.27.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0","pg": "^8.23.0"},"devDependencies": {@@@ -1069,9 +1069,9 @@}},"node_modules/openvibe-shared": {-- "version": "1.27.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.27.0",-- "integrity": "sha512-/H7dyFw+cURcdjyfbpyY7mNc6F+Pcb9TwkVwZHR+9d3M62q70KDhZLo/gZi71eaYxUjD8lO8tmbXu70EAa9paQ==",++ "version": "2.2.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",++ "integrity": "sha512-1q+iUuUxzPeylTYmithW4jR++Jccvn5GEOrB1B3D18Dgn6OXQhPpvhPQAa1D1k1p+dWIa1/SmggyJwSGFnTNtw==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -27,7 +27,7 @@"express": "^4.21.2","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.23.1",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.27.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0","pg": "^8.23.0"},"engines": {@@@ -20,6 +20,7 @@ function createApp({ config, db, store, engine, auth, outbox, relay, purges, purapp.set('trust proxy', 'loopback');app.set('query parser', 'extended');const release = createRelease({ service: 'search', root: path.join(__dirname, '..') });++ require('./web/page').setRelease(release.release);// HTTP golden signals by route template, process metrics, release_info and the Search gauges;// GET /metrics answers direct loopback callers only (Track O).const metrics = instrument(app, { service: 'search', release: release.release });@@@ -21,6 +21,11 @@ const { QueryError, one } = require('../api/query');const SITE_NAME = 'OpenVibe.Search';const SITE_DESCRIPTION = "Search what the OpenVibe network's services have published.";++// The deployed release (app.js sets it from openvibe-shared/release): openvibe-shared/boost swaps a page in++// place only between pages of the same release, and does a normal load across a deploy.++let RELEASE = 'dev';++function setRelease(id) { if (id) RELEASE = String(id); }++// The OpenVibe Frame (navbar, footer, "shipped" views, themes) comes from openvibe.network; its init is// /frame-init.js (same origin, no inline script), reading the JSON config in #ov-frame-config.const NETWORK = 'https://openvibe.network';@@@ -156,6 +161,8 @@ ${jsonLd ? jsonLd + '\n' : ''}${require('openvibe-shared/app-icon').headTags({ s<script src="${ovServe.url('navbar.js')}" defer></script><script src="${ovServe.url('footer.js')}" defer></script><script src="/frame-init.js" defer></script>++<meta name="ov-boost" content="search@${esc(RELEASE)}">++<script src="${ovServe.url('boost.js')}" data-main="#main" defer></script><style>/* The network theme (the Frame's theme loader sets these tokens on <html>) wins; the values here are thedefaults when it does not load. Search's own names follow them, so the page and the shared widgets@@@ -187,7 +194,7 @@ mark { background: var(--mark); color: inherit; }<body><div id="navbar-mount"></div>${frame.noscriptNav({ name: 'OpenVibe.Search', links: [{ label: 'Search', href: '/' }, { label: 'Updates', href: '/updates' }] })}--<main>++<main id="main"><h1><a href="/" style="color:inherit;text-decoration:none">OpenVibe.Search</a></h1><p class="lede">Search what the OpenVibe network's services have published. Alpha: the index holds only what they have sent so far.</p><form method="get" action="/" role="search">@@@ -310,4 +317,4 @@ function pageRouter({ searcher, auth, baseUrl = 'https://search.openvibe.networkreturn router;}--module.exports = { pageRouter, TEXT_INDEX, robotsTxt, llmsTxt, sitemapXml, homeJsonLd };++module.exports = { pageRouter, setRelease, TEXT_INDEX, robotsTxt, llmsTxt, sitemapXml, homeJsonLd };@@@ -56,6 +56,14 @@ t('the front page is a search form, indexable, no-store, with a strict CSP', asyassert.ok(ld[0].url.endsWith('/') && ld[1].url.endsWith('/'));assert.ok(r.text.includes('<div id="navbar-mount"></div>') && r.text.includes('id="ov-footer"'), 'the OpenVibe Frame');assert.ok(r.text.includes('data-ov-shipped="latest" data-service="search" href="/updates"'), 'what shipped');++ // Boost (openvibe-shared 2.2.0): every page carries the release marker and the swap script, and <main id="main">++ // is the element that changes, so a same-release move happens in place (any doubt is a normal load).++ assert.match(r.text, /<meta name="ov-boost" content="search@[^"]+">/);++ assert.match(r.text, /<script src="\/shared\/boost\.js\?v=[^"]+" data-main="#main" defer><\/script>/);++ assert.ok(r.text.includes('<main id="main">'), 'the changing part');++ // Sign-in must not bake the current path in: a {path} template returns to whatever page is showing.++ const frameCfg = JSON.parse(/<script type="application\/json" id="ov-frame-config">([\s\S]*?)<\/script>/.exec(r.text)[1]);++ assert.ok(!frameCfg.navbar.loginUrl || frameCfg.navbar.loginUrl.includes('{path}'), 'sign-in follows the current page');});t('the Frame init is a same-origin script and /updates is the shared log', async () => {@@@ -67,6 +75,7 @@ t('the Frame init is a same-origin script and /updates is the shared log', asyncassert.strictEqual(r.status, 200);assert.ok(r.text.includes('What shipped on OpenVibe.Search') && r.text.includes('data-ov-shipped="log" data-service="search"'));assert.match(r.text, /<link rel="canonical" href="https?:\/\/[^"/]+\/updates">/);++ assert.match(r.text, /<meta name="ov-boost" content="search@[^"]+">/);assert.match(r.headers.get('content-security-policy'), /default-src 'none'/);});
@@ -15,7 +15,7 @@
@@ -15,7 +15,7 @@
"express": "^4.21.2",
"express": "^4.21.2",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.23.1",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.23.1",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.27.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"devDependencies": {
"devDependencies": {
@@ -1069,9 +1069,9 @@
@@ -1069,9 +1069,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "1.27.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.27.0",
- "integrity": "sha512-/H7dyFw+cURcdjyfbpyY7mNc6F+Pcb9TwkVwZHR+9d3M62q70KDhZLo/gZi71eaYxUjD8lO8tmbXu70EAa9paQ==",
+ "version": "2.2.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",
+ "integrity": "sha512-1q+iUuUxzPeylTYmithW4jR++Jccvn5GEOrB1B3D18Dgn6OXQhPpvhPQAa1D1k1p+dWIa1/SmggyJwSGFnTNtw==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -27,7 +27,7 @@
@@ -27,7 +27,7 @@
"express": "^4.21.2",
"express": "^4.21.2",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.23.1",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.23.1",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.27.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"engines": {
"engines": {
@@ -20,6 +20,7 @@ function createApp({ config, db, store, engine, auth, outbox, relay, purges, pur
@@ -20,6 +20,7 @@ function createApp({ config, db, store, engine, auth, outbox, relay, purges, pur
app.set('trust proxy', 'loopback');
app.set('trust proxy', 'loopback');
app.set('query parser', 'extended');
app.set('query parser', 'extended');
const release = createRelease({ service: 'search', root: path.join(__dirname, '..') });
const release = createRelease({ service: 'search', root: path.join(__dirname, '..') });
+ require('./web/page').setRelease(release.release);
// HTTP golden signals by route template, process metrics, release_info and the Search gauges;
// HTTP golden signals by route template, process metrics, release_info and the Search gauges;
// GET /metrics answers direct loopback callers only (Track O).
// GET /metrics answers direct loopback callers only (Track O).
const metrics = instrument(app, { service: 'search', release: release.release });
const metrics = instrument(app, { service: 'search', release: release.release });
@@ -21,6 +21,11 @@ const { QueryError, one } = require('../api/query');
@@ -21,6 +21,11 @@ const { QueryError, one } = require('../api/query');
const SITE_NAME = 'OpenVibe.Search';
const SITE_NAME = 'OpenVibe.Search';
const SITE_DESCRIPTION = "Search what the OpenVibe network's services have published.";
const SITE_DESCRIPTION = "Search what the OpenVibe network's services have published.";
+// The deployed release (app.js sets it from openvibe-shared/release): openvibe-shared/boost swaps a page in
+// place only between pages of the same release, and does a normal load across a deploy.
+let RELEASE = 'dev';
+function setRelease(id) { if (id) RELEASE = String(id); }
+
// The OpenVibe Frame (navbar, footer, "shipped" views, themes) comes from openvibe.network; its init is
// The OpenVibe Frame (navbar, footer, "shipped" views, themes) comes from openvibe.network; its init is
// /frame-init.js (same origin, no inline script), reading the JSON config in #ov-frame-config.
// /frame-init.js (same origin, no inline script), reading the JSON config in #ov-frame-config.
const NETWORK = 'https://openvibe.network';
const NETWORK = 'https://openvibe.network';
@@ -156,6 +161,8 @@ ${jsonLd ? jsonLd + '\n' : ''}${require('openvibe-shared/app-icon').headTags({ s
@@ -156,6 +161,8 @@ ${jsonLd ? jsonLd + '\n' : ''}${require('openvibe-shared/app-icon').headTags({ s
<script src="${ovServe.url('navbar.js')}" defer></script>
<script src="${ovServe.url('navbar.js')}" defer></script>
<script src="${ovServe.url('footer.js')}" defer></script>
<script src="${ovServe.url('footer.js')}" defer></script>
<script src="/frame-init.js" defer></script>
<script src="/frame-init.js" defer></script>
+<meta name="ov-boost" content="search@${esc(RELEASE)}">
+<script src="${ovServe.url('boost.js')}" data-main="#main" defer></script>
<style>
<style>
/* The network theme (the Frame's theme loader sets these tokens on <html>) wins; the values here are the
/* The network theme (the Frame's theme loader sets these tokens on <html>) wins; the values here are the
defaults when it does not load. Search's own names follow them, so the page and the shared widgets
defaults when it does not load. Search's own names follow them, so the page and the shared widgets
@@ -187,7 +194,7 @@ mark { background: var(--mark); color: inherit; }
@@ -187,7 +194,7 @@ mark { background: var(--mark); color: inherit; }
<body>
<body>
<div id="navbar-mount"></div>
<div id="navbar-mount"></div>
${frame.noscriptNav({ name: 'OpenVibe.Search', links: [{ label: 'Search', href: '/' }, { label: 'Updates', href: '/updates' }] })}
${frame.noscriptNav({ name: 'OpenVibe.Search', links: [{ label: 'Search', href: '/' }, { label: 'Updates', href: '/updates' }] })}
-<main>
+<main id="main">
<h1><a href="/" style="color:inherit;text-decoration:none">OpenVibe.Search</a></h1>
<h1><a href="/" style="color:inherit;text-decoration:none">OpenVibe.Search</a></h1>
<p class="lede">Search what the OpenVibe network's services have published. Alpha: the index holds only what they have sent so far.</p>
<p class="lede">Search what the OpenVibe network's services have published. Alpha: the index holds only what they have sent so far.</p>
<form method="get" action="/" role="search">
<form method="get" action="/" role="search">
@@ -310,4 +317,4 @@ function pageRouter({ searcher, auth, baseUrl = 'https://search.openvibe.network
@@ -310,4 +317,4 @@ function pageRouter({ searcher, auth, baseUrl = 'https://search.openvibe.network
return router;
return router;
}
}
-module.exports = { pageRouter, TEXT_INDEX, robotsTxt, llmsTxt, sitemapXml, homeJsonLd };
+module.exports = { pageRouter, setRelease, TEXT_INDEX, robotsTxt, llmsTxt, sitemapXml, homeJsonLd };
@@ -56,6 +56,14 @@ t('the front page is a search form, indexable, no-store, with a strict CSP', asy
@@ -56,6 +56,14 @@ t('the front page is a search form, indexable, no-store, with a strict CSP', asy
assert.ok(ld[0].url.endsWith('/') && ld[1].url.endsWith('/'));
assert.ok(ld[0].url.endsWith('/') && ld[1].url.endsWith('/'));
assert.ok(r.text.includes('<div id="navbar-mount"></div>') && r.text.includes('id="ov-footer"'), 'the OpenVibe Frame');
assert.ok(r.text.includes('<div id="navbar-mount"></div>') && r.text.includes('id="ov-footer"'), 'the OpenVibe Frame');
assert.ok(r.text.includes('data-ov-shipped="latest" data-service="search" href="/updates"'), 'what shipped');
assert.ok(r.text.includes('data-ov-shipped="latest" data-service="search" href="/updates"'), 'what shipped');
+ // Boost (openvibe-shared 2.2.0): every page carries the release marker and the swap script, and <main id="main">
+ // is the element that changes, so a same-release move happens in place (any doubt is a normal load).
+ assert.match(r.text, /<meta name="ov-boost" content="search@[^"]+">/);
+ assert.match(r.text, /<script src="\/shared\/boost\.js\?v=[^"]+" data-main="#main" defer><\/script>/);
+ assert.ok(r.text.includes('<main id="main">'), 'the changing part');
+ // Sign-in must not bake the current path in: a {path} template returns to whatever page is showing.
+ const frameCfg = JSON.parse(/<script type="application\/json" id="ov-frame-config">([\s\S]*?)<\/script>/.exec(r.text)[1]);
+ assert.ok(!frameCfg.navbar.loginUrl || frameCfg.navbar.loginUrl.includes('{path}'), 'sign-in follows the current page');
});
});
t('the Frame init is a same-origin script and /updates is the shared log', async () => {
t('the Frame init is a same-origin script and /updates is the shared log', async () => {
@@ -67,6 +75,7 @@ t('the Frame init is a same-origin script and /updates is the shared log', async
@@ -67,6 +75,7 @@ t('the Frame init is a same-origin script and /updates is the shared log', async
assert.strictEqual(r.status, 200);
assert.strictEqual(r.status, 200);
assert.ok(r.text.includes('What shipped on OpenVibe.Search') && r.text.includes('data-ov-shipped="log" data-service="search"'));
assert.ok(r.text.includes('What shipped on OpenVibe.Search') && r.text.includes('data-ov-shipped="log" data-service="search"'));
assert.match(r.text, /<link rel="canonical" href="https?:\/\/[^"/]+\/updates">/);
assert.match(r.text, /<link rel="canonical" href="https?:\/\/[^"/]+\/updates">/);
+ assert.match(r.text, /<meta name="ov-boost" content="search@[^"]+">/);
assert.match(r.headers.get('content-security-policy'), /default-src 'none'/);
assert.match(r.headers.get('content-security-policy'), /default-src 'none'/);
});
});