openvibers/openvibe.shared Commits

4.92.0: the page starts the footer it renders; the compact footer centres its links (#248)

OP OpenVibers committed 11 days ago · 6336a527cb · 7 files, +30 −4

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex c58e8ae..41830d3 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,17 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
breaking change to any exported module, browser global or served file name is a new major.
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
++## 4.92.0 — 2026-11-02
++
++- **The page starts the footer it renders** (`shell.page`, `shell.scripts`).
++ - Six sites rendered the shared footer and never started it: Video, Pics, Run, Events, AI and Website. On a phone
++ every fold stood open, and there was no shipped line.
++ - The page's boot now starts it a moment after the site's own scripts, with the page's footer config.
++ - A site that starts it itself (`OpenVibeFooter.init`) keeps its own: a started footer carries `data-ovf-live`, and
++ the shell leaves it.
++- **The compact footer centres its links** under its centred brand. On a phone the row sat at the left, and "Report a
++ problem" wrapped alone.
++
## 4.91.1 — 2026-11-01
- **A numbers strip can be reached by keyboard** (`kit.stats`): on a phone the strip scrolls sideways, and a strip with
ddiff --git a/README.md b/README.md
iindex 9df123b..ed61e98 100644
---- a/README.md
++++ b/README.md
@@@ -2,7 +2,7 @@
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
--**Status:** alpha, v4.91.1 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
++**Status:** alpha, v4.92.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
consumer installs a tagged release and none keeps a vendored copy
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
the tag it pins.
ddiff --git a/STATUS.json b/STATUS.json
iindex 63cefbc..c7f7a0d 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -4,7 +4,7 @@
"code": true,
"wave": "W2",
"package": "openvibe-shared",
-- "version": "4.91.1",
++ "version": "4.92.0",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"deployed": false,
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
ddiff --git a/footer.js b/footer.js
iindex 2829803..9cdc2be 100644
---- a/footer.js
++++ b/footer.js
@@@ -409,6 +409,7 @@
padding-inline:clamp(0px, calc(84px - (100vw - 1240px) / 2), 84px)}
.ovf-copy{color:var(--text-muted,#8b93ad);font-size:.78rem}
.ovf-legal-inline,.ovf-compact-links{display:flex;flex-wrap:wrap;gap:14px}
++.ovf-compact-links{justify-content:center}
.ovf [hidden],.ovf-account[hidden]{display:none!important}
.ovf .ovf-shipped.ov-shipped-latest{font-size:.76rem;padding:4px 12px;max-width:min(520px,100%);background:transparent}
.ovf-legal-inline a,.ovf-compact-links a{color:var(--text-muted,#8b93ad);font-size:.78rem;text-decoration:none;transition:color .15s}
@@@ -598,6 +599,8 @@
const el = typeof _cfg.mount === 'string' ? document.querySelector(_cfg.mount) : _cfg.mount;
if (!el) return null;
_mounted = el;
++ // Started: shell.page's own start (after the site's scripts) leaves a footer the site started alone.
++ try { el.setAttribute('data-ovf-live', ''); } catch { /* */ }
render();
// 'auto' on a single-page site follows its routes: back and forward re-read the path (a route the site pushes
// itself calls setVariant or render).
ddiff --git a/package.json b/package.json
iindex 8149511..d8034a8 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-shared",
-- "version": "4.91.1",
++ "version": "4.92.0",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"bin": {
ddiff --git a/shell.js b/shell.js
iindex 07392af..a96b64d 100644
---- a/shell.js
++++ b/shell.js
@@@ -58,6 +58,7 @@ function attrs(map) {
function scripts(o = {}) {
const name = o.name || o.service || 'OpenVibe';
const navbar = { service: name, ...(o.navbar || {}) };
++ const footer = o.footer || { service: name, variant: 'compact' };
const boot = { features: o.features || {}, routes: o.routes || [] };
if (o.versions) boot.versions = o.versions;
return [
@@@ -65,7 +66,9 @@ function scripts(o = {}) {
`<script src="${esc(serve.url('web-runtime.js'))}" defer></script>`,
`<script src="${esc(serve.url('navbar.js'))}" defer></script>`,
`<script src="${esc(serve.url('footer.js'))}" defer></script>`,
-- `<script>window.addEventListener('DOMContentLoaded', function () { OpenVibeNavbar.init(${json(navbar)}); OVWebRuntime.create(${json(boot)}).boot(); });</script>`,
++ // The footer this page renders (frame.footer) is started here too, a moment after the site's own scripts: a site
++ // that starts it itself (OpenVibeFooter.init) keeps its own; one that does not gets its phone folds and its line.
++ `<script>window.addEventListener('DOMContentLoaded', function () { OpenVibeNavbar.init(${json(navbar)}); OVWebRuntime.create(${json(boot)}).boot(); setTimeout(function () { var f = document.getElementById('ov-footer'); if (window.OpenVibeFooter && f && !f.hasAttribute('data-ovf-live')) OpenVibeFooter.init(${json(footer)}); }, 0); });</script>`,
].join('\n');
}
ddiff --git a/test/shell.test.js b/test/shell.test.js
iindex 06af92b..d345b11 100644
---- a/test/shell.test.js
++++ b/test/shell.test.js
@@@ -5,6 +5,8 @@
// escaped, the page summary appears only when asked for, and the no-JS nav + shared footer are there.
const assert = require('node:assert/strict');
const shell = require('openvibe-shared/shell');
++const fs = require('fs');
++const path = require('path');
// ── A whole document, in order ────────────────────────────────────────────────────────────────────
const html = shell.page({
@@@ -61,6 +63,13 @@ assert.deepEqual(JSON.parse(m[1]), {
versions: { '/js/chat.js': 'abc123' },
});
assert.match(bootSrc, /OpenVibeNavbar\.init\(\{"service":"example"\}\);/, 'navbar init carries the service');
++// The footer it renders is started too, unless the site started it (Video and five more never did: 2026-11-02).
++assert.match(bootSrc, /setTimeout\(function \(\) \{ var f = document\.getElementById\('ov-footer'\); if \(window\.OpenVibeFooter && f && !f\.hasAttribute\('data-ovf-live'\)\) OpenVibeFooter\.init\(\{"service":"example","variant":"compact"\}\); \}, 0\);/, 'the page starts its footer after the site\'s own scripts');
++const withFooter = shell.scripts({ name: 'v', footer: { service: 'media-hub', variant: 'full', brandName: 'OpenVibe.Video' } });
++assert.ok(withFooter.includes('OpenVibeFooter.init({"service":"media-hub","variant":"full","brandName":"OpenVibe.Video"})'), 'with the page\'s own footer config');
++const footerSrc = fs.readFileSync(path.join(__dirname, '..', 'footer.js'), 'utf8');
++assert.ok(/_mounted = el;\s*\/\/[^\n]*\n\s*try \{ el\.setAttribute\('data-ovf-live', ''\); \}/.test(footerSrc), 'a started footer says so');
++assert.ok(footerSrc.includes('.ovf-compact-links{justify-content:center}'), 'the compact row centres its links under the brand');
const evil = shell.page({ name: 'x', features: { a: '</script><script>alert(1)</script>' } });
assert.ok(!evil.includes('</script><script>alert(1)'), 'a feature name cannot break out of the boot script');
assert.ok(evil.includes('\\u003c/script>'), 'the boot escapes <');