openvibers/openvibe.reviews Commits
Reviews moves between its pages without a reload (plan T11): openvibe-shared v2.2.0 boost swaps <main> styles-first with the shared transition, prefetching on intent, and does a normal load across a deploy (the ov-boost marker carries the release). The navbar's sign-in returns to the current page. Tests pin the marker, the boost tag and the login template (verified).
Changes
@@@ -226,7 +226,7 @@ by editors; signals arrive only from Sources.- OpenVibe.AI — optional, proposes summaries through `reviews.summary.propose`- OpenVibe.Search — consumes the index events- PostgreSQL 18 and Valkey 9 (OpenVibe.Host `roles/data/`, ADR-035): every read and write is async through `openvibe-sdk/db`; Valkey holds the per-actor limit counters (optional)--- packages: openvibe-publishing v1.0.0, openvibe-contracts v0.76.0, openvibe-shared v1.27.0, openvibe-sdk v0.20.0++- packages: openvibe-publishing v1.0.0, openvibe-contracts v0.76.0, openvibe-shared v2.2.0, openvibe-sdk v0.20.0## Capabilities@@@ -10,7 +10,7 @@"contracts": "openvibe-contracts v0.76.0","packages": ["openvibe-publishing v1.0.0",-- "openvibe-shared v1.27.0",++ "openvibe-shared v2.2.0","openvibe-sdk v0.20.0"],"capabilities": [@@@ -17,7 +17,7 @@"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0","openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.0.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.20.0",-- "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": {@@@ -1196,9 +1196,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 @@"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0","openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.0.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.20.0",-- "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": {@@@ -49,6 +49,7 @@ function createApp({ config, svc, viewers, platform, sync, keys, db, log = consorequire('openvibe-shared/trace').install(app);const release = require('openvibe-shared/release').createRelease({ service: 'reviews', root: path.join(__dirname, '..') });++ require('./render/layout').setRelease(release.release);// Valkey (ADR-035): shared, never-authoritative state (per-actor limit counters). Optional.const valkey = config.valkey.url ? require('openvibe-sdk/valkey').createValkey({ url: config.valkey.url, prefix: config.valkey.prefix, log }) : null;const metrics = require('openvibe-shared/metrics').instrument(app, { service: 'reviews', release: release.release });@@@ -32,6 +32,11 @@ function asset(rel) {/** The ?v= this process renders for a public/ file (the static route caches only that one as immutable). */function assetVersion(rel) { asset(rel); return hashes.get(rel); }++// 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); }++const LINKS = [{ label: 'Entities', href: '/', key: 'home' },{ label: 'How it works', href: '/about', key: 'about' },@@@ -45,7 +50,7 @@ function navConfig(o, config) {history: { type: 'page', title: o.title || SITE_NAME },silentLogin: `${config.baseUrl}/auth/login?silent=1&next={url}`,sessionUrl: '/auth/me',-- loginUrl: `/auth/login?next=${encodeURIComponent(o.path || '/')}`,++ loginUrl: '/auth/login?next={path}', // filled from the current page (boost moves between pages)logoutUrl: '/auth/logout?next={path}', // Sign out in the shared navbar ends this site's session toonotificationsRealtime: true, // the bell hears new notifications over OpenVibe.Events (Shared 1.22.0)};@@@ -83,6 +88,8 @@ ${feeds.map((f) => `<link rel="alternate" type="${f.type}" title="${esc(f.title)<script src="${ovServe.url('theme-loader.js')}" defer></script><script src="${ovServe.url('navbar.js')}" defer></script><script src="${ovServe.url('footer.js')}" defer></script>++<meta name="ov-boost" content="reviews@${esc(RELEASE)}">++<script src="${ovServe.url('boost.js')}" data-main="#main" defer></script></head><body><a class="rv-skip" href="#main">Skip to content</a>@@@ -105,4 +112,4 @@ document.addEventListener('DOMContentLoaded', function () {</html>`;}--module.exports = { renderPage, asset, SITE_NAME, DEFAULT_DESCRIPTION, NETWORK_URL, assetVersion };++module.exports = { renderPage, asset, assetVersion, setRelease, SITE_NAME, DEFAULT_DESCRIPTION, NETWORK_URL };nnew file mode 100644@@@ -0,0 +1,33 @@++'use strict';++/**++ * The page shell carries what openvibe-shared/boost needs to move between pages in place (plan T11): an++ * <meta name="ov-boost"> release marker on every page and the boost script with data-main="#main"; the shared++ * navbar's sign-in returns to whatever page is showing ({path} is filled per page).++ */++const assert = require('assert');++const { boot, req, suite } = require('./helpers');++++const t = suite('layout');++++t('every rendered page carries the boost marker and the boost script (data-main)', async () => {++ const h = await boot();++ try {++ for (const p of ['/', '/about', '/search']) {++ const r = await req(h, 'GET', p);++ assert.strictEqual(r.status, 200);++ assert.match(r.text, /<meta name="ov-boost" content="reviews@[^"]+">/, `${p}: the release marker`);++ assert.match(r.text, /<script src="\/shared\/boost\.js\?v=[0-9a-f]+" data-main="#main" defer><\/script>/, `${p}: the boost script`);++ assert.match(r.text, /<main id="main"/, `${p}: the swapped element is <main id="main">`);++ }++ } finally { await h.stop(); }++});++++t('the shared navbar config uses the {path} sign-in template', async () => {++ const h = await boot();++ try {++ const r = await req(h, 'GET', '/about');++ assert.match(r.text, /"loginUrl":"\/auth\/login\?next=\{path\}"/);++ const page = JSON.parse((r.text.match(/window\.__OV_PAGE = (\{[\s\S]*?\});\n/) || [])[1]);++ assert.strictEqual(page.navbar.loginUrl, '/auth/login?next={path}');++ } finally { await h.stop(); }++});
@@ -226,7 +226,7 @@ by editors; signals arrive only from Sources.
@@ -226,7 +226,7 @@ by editors; signals arrive only from Sources.
- OpenVibe.AI — optional, proposes summaries through `reviews.summary.propose`
- OpenVibe.AI — optional, proposes summaries through `reviews.summary.propose`
- OpenVibe.Search — consumes the index events
- OpenVibe.Search — consumes the index events
- PostgreSQL 18 and Valkey 9 (OpenVibe.Host `roles/data/`, ADR-035): every read and write is async through `openvibe-sdk/db`; Valkey holds the per-actor limit counters (optional)
- PostgreSQL 18 and Valkey 9 (OpenVibe.Host `roles/data/`, ADR-035): every read and write is async through `openvibe-sdk/db`; Valkey holds the per-actor limit counters (optional)
-- packages: openvibe-publishing v1.0.0, openvibe-contracts v0.76.0, openvibe-shared v1.27.0, openvibe-sdk v0.20.0
+- packages: openvibe-publishing v1.0.0, openvibe-contracts v0.76.0, openvibe-shared v2.2.0, openvibe-sdk v0.20.0
## Capabilities
## Capabilities
@@ -10,7 +10,7 @@
@@ -10,7 +10,7 @@
"contracts": "openvibe-contracts v0.76.0",
"contracts": "openvibe-contracts v0.76.0",
"packages": [
"packages": [
"openvibe-publishing v1.0.0",
"openvibe-publishing v1.0.0",
- "openvibe-shared v1.27.0",
+ "openvibe-shared v2.2.0",
"openvibe-sdk v0.20.0"
"openvibe-sdk v0.20.0"
],
],
"capabilities": [
"capabilities": [
@@ -17,7 +17,7 @@
@@ -17,7 +17,7 @@
"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-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.0.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.0.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.20.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.20.0",
- "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": {
@@ -1196,9 +1196,9 @@
@@ -1196,9 +1196,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 @@
"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-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.0.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.0.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.20.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.20.0",
- "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": {
@@ -49,6 +49,7 @@ function createApp({ config, svc, viewers, platform, sync, keys, db, log = conso
@@ -49,6 +49,7 @@ function createApp({ config, svc, viewers, platform, sync, keys, db, log = conso
require('openvibe-shared/trace').install(app);
require('openvibe-shared/trace').install(app);
const release = require('openvibe-shared/release').createRelease({ service: 'reviews', root: path.join(__dirname, '..') });
const release = require('openvibe-shared/release').createRelease({ service: 'reviews', root: path.join(__dirname, '..') });
+ require('./render/layout').setRelease(release.release);
// Valkey (ADR-035): shared, never-authoritative state (per-actor limit counters). Optional.
// Valkey (ADR-035): shared, never-authoritative state (per-actor limit counters). Optional.
const valkey = config.valkey.url ? require('openvibe-sdk/valkey').createValkey({ url: config.valkey.url, prefix: config.valkey.prefix, log }) : null;
const valkey = config.valkey.url ? require('openvibe-sdk/valkey').createValkey({ url: config.valkey.url, prefix: config.valkey.prefix, log }) : null;
const metrics = require('openvibe-shared/metrics').instrument(app, { service: 'reviews', release: release.release });
const metrics = require('openvibe-shared/metrics').instrument(app, { service: 'reviews', release: release.release });
@@ -32,6 +32,11 @@ function asset(rel) {
@@ -32,6 +32,11 @@ function asset(rel) {
/** The ?v= this process renders for a public/ file (the static route caches only that one as immutable). */
/** The ?v= this process renders for a public/ file (the static route caches only that one as immutable). */
function assetVersion(rel) { asset(rel); return hashes.get(rel); }
function assetVersion(rel) { asset(rel); return hashes.get(rel); }
+// 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); }
+
const LINKS = [
const LINKS = [
{ label: 'Entities', href: '/', key: 'home' },
{ label: 'Entities', href: '/', key: 'home' },
{ label: 'How it works', href: '/about', key: 'about' },
{ label: 'How it works', href: '/about', key: 'about' },
@@ -45,7 +50,7 @@ function navConfig(o, config) {
@@ -45,7 +50,7 @@ function navConfig(o, config) {
history: { type: 'page', title: o.title || SITE_NAME },
history: { type: 'page', title: o.title || SITE_NAME },
silentLogin: `${config.baseUrl}/auth/login?silent=1&next={url}`,
silentLogin: `${config.baseUrl}/auth/login?silent=1&next={url}`,
sessionUrl: '/auth/me',
sessionUrl: '/auth/me',
- loginUrl: `/auth/login?next=${encodeURIComponent(o.path || '/')}`,
+ loginUrl: '/auth/login?next={path}', // filled from the current page (boost moves between pages)
logoutUrl: '/auth/logout?next={path}', // Sign out in the shared navbar ends this site's session too
logoutUrl: '/auth/logout?next={path}', // Sign out in the shared navbar ends this site's session too
notificationsRealtime: true, // the bell hears new notifications over OpenVibe.Events (Shared 1.22.0)
notificationsRealtime: true, // the bell hears new notifications over OpenVibe.Events (Shared 1.22.0)
};
};
@@ -83,6 +88,8 @@ ${feeds.map((f) => `<link rel="alternate" type="${f.type}" title="${esc(f.title)
@@ -83,6 +88,8 @@ ${feeds.map((f) => `<link rel="alternate" type="${f.type}" title="${esc(f.title)
<script src="${ovServe.url('theme-loader.js')}" defer></script>
<script src="${ovServe.url('theme-loader.js')}" defer></script>
<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>
+<meta name="ov-boost" content="reviews@${esc(RELEASE)}">
+<script src="${ovServe.url('boost.js')}" data-main="#main" defer></script>
</head>
</head>
<body>
<body>
<a class="rv-skip" href="#main">Skip to content</a>
<a class="rv-skip" href="#main">Skip to content</a>
@@ -105,4 +112,4 @@ document.addEventListener('DOMContentLoaded', function () {
@@ -105,4 +112,4 @@ document.addEventListener('DOMContentLoaded', function () {
</html>`;
</html>`;
}
}
-module.exports = { renderPage, asset, SITE_NAME, DEFAULT_DESCRIPTION, NETWORK_URL, assetVersion };
+module.exports = { renderPage, asset, assetVersion, setRelease, SITE_NAME, DEFAULT_DESCRIPTION, NETWORK_URL };
new file mode 100644
new file mode 100644
@@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
+'use strict';
+/**
+ * The page shell carries what openvibe-shared/boost needs to move between pages in place (plan T11): an
+ * <meta name="ov-boost"> release marker on every page and the boost script with data-main="#main"; the shared
+ * navbar's sign-in returns to whatever page is showing ({path} is filled per page).
+ */
+const assert = require('assert');
+const { boot, req, suite } = require('./helpers');
+
+const t = suite('layout');
+
+t('every rendered page carries the boost marker and the boost script (data-main)', async () => {
+ const h = await boot();
+ try {
+ for (const p of ['/', '/about', '/search']) {
+ const r = await req(h, 'GET', p);
+ assert.strictEqual(r.status, 200);
+ assert.match(r.text, /<meta name="ov-boost" content="reviews@[^"]+">/, `${p}: the release marker`);
+ assert.match(r.text, /<script src="\/shared\/boost\.js\?v=[0-9a-f]+" data-main="#main" defer><\/script>/, `${p}: the boost script`);
+ assert.match(r.text, /<main id="main"/, `${p}: the swapped element is <main id="main">`);
+ }
+ } finally { await h.stop(); }
+});
+
+t('the shared navbar config uses the {path} sign-in template', async () => {
+ const h = await boot();
+ try {
+ const r = await req(h, 'GET', '/about');
+ assert.match(r.text, /"loginUrl":"\/auth\/login\?next=\{path\}"/);
+ const page = JSON.parse((r.text.match(/window\.__OV_PAGE = (\{[\s\S]*?\});\n/) || [])[1]);
+ assert.strictEqual(page.navbar.loginUrl, '/auth/login?next={path}');
+ } finally { await h.stop(); }
+});