openvibers/openvibe.shared Commits

4.166.0: the menu's Feed on every site (through Network) (#385)

OP OpenVibers committed 4 days ago · 4e50c5ac52 · 10 files, +27 −15

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 466d56e..0186ec4 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -1,5 +1,13 @@
# Changelog
++## 4.166.0 — 2026-11-09
++
++- **The Feed on every site** (the owner: "I still don't see the custom doom scrollable feed"): the menu's Feed, wall and
++ composer reach Community through Network's frame relay (`https://openvibe.network/api/frame/feed`, `/wall`,
++ `/status`; Network #714). Every site's Content-Security-Policy allows openvibe.network and refused
++ openvibe.community, so on every site but five the Feed fell back to its old sections. `o.frameBase` overrides the
++ relay (tests, staging); the menu no longer calls openvibe.community directly.
++
## 4.165.0 — 2026-11-09
- **Faster between pages and sites** (the owner, 11-09: "when you change sites… faster across sites… better caching on
ddiff --git a/README.md b/README.md
iindex accdacc..2f69d94 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.165.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
++**Status:** alpha, v4.166.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 c04343c..3f28b56 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -4,7 +4,7 @@
"code": true,
"wave": "W2",
"package": "openvibe-shared",
-- "version": "4.165.0",
++ "version": "4.166.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/navbar.js b/navbar.js
iindex 7a4429c..dcf6dbc 100644
---- a/navbar.js
++++ b/navbar.js
@@@ -572,7 +572,7 @@
const NAV_ICONS_REV = '1091daee9c7e'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)
// END generated
// The files loaded from beside this one, by content (scripts/sibling-revs.js): a release changes their URLs.
-- const SIBLING_REV = { 'ov-mark.js': 'd7e87def8fff', 'ov-glyphs.js': '6f8c8dd2a10d', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2d3a47d3535c', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': '20f4ef41d127', 'ov-forms.css': '98cf0f28bb68', 'ov-share.js': '8e55bfe93238', 'ov-share.css': 'cbd18e6ce991', 'search.js': '8d00547b413c', 'dock.js': '496a40c534ce', 'presence.js': '1b675c37d888', 'panels.js': 'd90bfe987d81', 'surfaces.js': 'e72f5a304f2d', 'notification-ui.js': 'a2d37237ba0b', 'history.js': '1e6598f78ba4', 'site-switcher.js': '292bea111aa8', 'appbar.js': '1edf7532f228', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': 'ba3981246bc0', 'sso-client.js': '52439265b980', 'overlay.js': 'd18308ab8a63', 'ov-home.js': '455179332bc2', 'nav-slots.js': 'a5af310d62e0' };
++ const SIBLING_REV = { 'ov-mark.js': 'd7e87def8fff', 'ov-glyphs.js': '6f8c8dd2a10d', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2d3a47d3535c', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': '20f4ef41d127', 'ov-forms.css': '98cf0f28bb68', 'ov-share.js': '8e55bfe93238', 'ov-share.css': 'cbd18e6ce991', 'search.js': '8d00547b413c', 'dock.js': '496a40c534ce', 'presence.js': '1b675c37d888', 'panels.js': 'd90bfe987d81', 'surfaces.js': 'e72f5a304f2d', 'notification-ui.js': 'a2d37237ba0b', 'history.js': '1e6598f78ba4', 'site-switcher.js': '292bea111aa8', 'appbar.js': '1edf7532f228', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': 'ba3981246bc0', 'sso-client.js': '52439265b980', 'overlay.js': 'd18308ab8a63', 'ov-home.js': '367f701e1b1a', 'nav-slots.js': 'a5af310d62e0' };
// The navbar's icons are filled paths (no Font Awesome CSS needed): the ones above ship here, the rest
// (nav-icons.js) load after first paint when a link, chip or menu row names one, into an empty <svg> of their
// size. Anything unknown, or nav-icons.js failing, falls back to <i class="fa-solid">.
ddiff --git a/ov-home.js b/ov-home.js
iindex d884bbb..e212f06 100644
---- a/ov-home.js
++++ b/ov-home.js
@@@ -26,7 +26,10 @@
const LONG_MS = 450;
const HOME_MAX = 40;
const ACTOR = 'https://openvibe.actor';
-- const COMMUNITY = 'https://openvibe.community';
++ // The Feed, the wall and the composer go through Network's frame relay (/api/frame/feed, /wall, /status): every
++ // site's connect-src lets the navbar reach openvibe.network and refuses openvibe.community (11-09: the Feed fell back
++ // to its old sections on every site but five). o.frameBase overrides it (tests, staging).
++ const FRAME = 'https://openvibe.network/api/frame';
const safe = (u) => { try { const x = new URL(String(u || ''), location.href); return x.protocol === 'https:' || x.origin === location.origin ? x.href : null; } catch { return null; } };
const h = (tag, cls, text) => { const n = document.createElement(tag); if (cls) n.className = cls; if (text != null) n.textContent = text; return n; };
@@@ -736,7 +739,7 @@ html[data-ov-motion=reduced] .ovh-card.is-live .ovh-ic { animation-duration: 4s;
const text = ta.value.trim(); if (!text) return;
post.disabled = true;
try {
-- const r = await fetch(`${o.communityBase || COMMUNITY}/api/v1/me/status`, { method: 'POST', credentials: 'omit', headers: { Authorization: `Bearer ${o.token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ text }) });
++ const r = await fetch(`${o.frameBase || FRAME}/status`, { method: 'POST', credentials: 'omit', headers: { Authorization: `Bearer ${o.token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ text }) });
const j = await r.json().catch(() => ({}));
if (!r.ok) throw new Error(j.detail || 'It did not post. Try again in a moment.');
ta.value = ''; note.hidden = false; note.textContent = 'Posted. It is on your profile and in your followers’ feeds.';
@@@ -778,7 +781,7 @@ html[data-ov-motion=reduced] .ovh-card.is-live .ovh-ic { animation-duration: 4s;
const p = new URLSearchParams({ limit: '20' });
if (signedIn(o)) p.set('for', 'me');
if (cursor) p.set('after', cursor);
-- return `${o.communityBase || COMMUNITY}/api/v1/pulse?${p}`;
++ return `${o.frameBase || FRAME}/wall?${p}`;
}
function loadPulse(o, cursor) {
return fetch(pulseUrl(o, cursor), { credentials: 'omit', headers: Object.assign({ Accept: 'application/json' }, signedIn(o) ? { Authorization: `Bearer ${o.token}` } : {}) })
@@@ -845,7 +848,7 @@ html[data-ov-motion=reduced] .ovh-card.is-live .ovh-ic { animation-duration: 4s;
const p = new URLSearchParams({ limit: '12' });
if (kind) p.set('kind', kind);
if (cursor) p.set('cursor', cursor);
-- return `${o.communityBase || COMMUNITY}/api/v1/feed?${p}`;
++ return `${o.frameBase || FRAME}/feed?${p}`;
}
function loadFeed(o, kind, cursor) {
return fetch(feedUrl(o, kind, cursor), { credentials: 'omit', headers: Object.assign({ Accept: 'application/json' }, signedIn(o) ? { Authorization: `Bearer ${o.token}` } : {}) })
ddiff --git a/package-lock.json b/package-lock.json
iindex 8534c76..42b69df 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-shared",
-- "version": "4.165.0",
++ "version": "4.166.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-shared",
-- "version": "4.165.0",
++ "version": "4.166.0",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0",
ddiff --git a/package.json b/package.json
iindex 0b80a66..058ca42 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-shared",
-- "version": "4.165.0",
++ "version": "4.166.0",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"bin": {
ddiff --git a/test/menu-feed-v2-chrome.test.js b/test/menu-feed-v2-chrome.test.js
iindex 409213a..2e898a1 100644
---- a/test/menu-feed-v2-chrome.test.js
++++ b/test/menu-feed-v2-chrome.test.js
@@@ -40,11 +40,11 @@ const page = (signed) => `<!doctype html><html lang="en"><head><meta charset="ut
{ service: 'video', subject: 'Clips play from where you left off.', deployed_at: new Date(Date.now() - 7200e3).toISOString() } ] });
if (s.startsWith('https://openvibe.network/api/history')) return J({ items: [{ title: 'A stream', url: 'https://openvibe.live/ada', service: 'live', updated_at: new Date().toISOString() }, { title: 'A pic', url: 'https://openvibe.pics/1', service: 'pics', updated_at: new Date(Date.now() - 1000).toISOString() }], services: [{ service: 'live', count: 5 }, { service: 'pics', count: 2 }] });
if (s.startsWith('https://openvibe.network/api/v1/me/home-screen')) return (o && o.method === 'PUT') ? J({ ok: true }) : J({}, false, 404);
-- if (s.startsWith('https://openvibe.community/api/v1/feed')) { window.__feedCalls = (window.__feedCalls || []).concat([s]); const u = new URL(s); const kind = u.searchParams.get('kind') || ''; const page = u.searchParams.get('cursor') ? 2 : 1;
++ if (s.startsWith('https://openvibe.network/api/frame/feed')) { window.__feedCalls = (window.__feedCalls || []).concat([s]); const u = new URL(s); const kind = u.searchParams.get('kind') || ''; const page = u.searchParams.get('cursor') ? 2 : 1;
const mk = (i, k) => ({ id: k + i + '-' + page, kind: k, live_now: k === 'live', url: 'https://openvibe.video/c/' + i, title: (k === 'post' ? '<b>bold</b> ' : '') + k + ' ' + i + ' page ' + page, text: 'words', at: new Date(Date.now() - i * 60000).toISOString(), site: k === 'pic' ? 'pics' : 'video', site_name: 'Video', actor: { name: 'ada' }, media: k === 'post' || k === 'update' ? {} : { thumb: 'https://openvibe.media/t/x.webp', ratio: 1.7778, length: '0:30' }, counts: { views: 1200, likes: 3 } });
const kinds = kind ? [kind] : ['live', 'clip', 'pic', 'post', 'update'];
return J({ items: Array.from({ length: 12 }, (_, i) => mk(i, kinds[i % kinds.length])), next_cursor: page === 1 ? 'c2' : null }); }
-- if (s.startsWith('https://openvibe.community/api/v1/pulse')) return J(Object.assign({}, ${JSON.stringify(PULSE)}, s.includes('after=c2') ? { items: [{ title: 'An older post', url: 'https://openvibe.community/p/0', occurred_at: new Date(Date.now() - 86400e3).toISOString(), ref: { service: 'community' } }], next_cursor: null } : {}, ${signed ? "{ viewer: { can_post: true } }" : '{}'}));
++ if (s.startsWith('https://openvibe.network/api/frame/wall')) return J(Object.assign({}, ${JSON.stringify(PULSE)}, s.includes('after=c2') ? { items: [{ title: 'An older post', url: 'https://openvibe.community/p/0', occurred_at: new Date(Date.now() - 86400e3).toISOString(), ref: { service: 'community' } }], next_cursor: null } : {}, ${signed ? "{ viewer: { can_post: true } }" : '{}'}));
if (s.startsWith('https://openvibe.actor/api/v1/me/actor')) return J({ id: 'act_1', name: 'Juniper', enabled: true, status: 'working', now: { current_step: 'Reading job posts' }, asks: [] });
if (s.startsWith('https://')) return J({}, false, 404);
return realFetch(u, o);
ddiff --git a/test/menu-home-screen-chrome.test.js b/test/menu-home-screen-chrome.test.js
iindex 533bb37..7120223 100644
---- a/test/menu-home-screen-chrome.test.js
++++ b/test/menu-home-screen-chrome.test.js
@@@ -50,7 +50,7 @@ const page = (signed) => `<!doctype html><html lang="en"><head><meta charset="ut
{ service: 'video', subject: 'Clips play from where you left off.', deployed_at: new Date(Date.now() - 7200e3).toISOString() } ] });
if (s.startsWith('https://openvibe.network/api/history')) return J({ items: [{ title: 'A stream', url: 'https://openvibe.live/ada', service: 'live', updated_at: new Date().toISOString() }, { title: 'A pic', url: 'https://openvibe.pics/1', service: 'pics', updated_at: new Date(Date.now() - 1000).toISOString() }], services: [{ service: 'live', count: 5 }, { service: 'pics', count: 2 }] });
if (s.startsWith('https://openvibe.network/api/v1/me/home-screen')) return (o && o.method === 'PUT') ? J({ ok: true }) : J({}, false, 404);
-- if (s.startsWith('https://openvibe.community/api/v1/pulse')) return J(Object.assign({}, ${JSON.stringify(PULSE)}, s.includes('after=c2') ? { items: [{ title: 'An older post', url: 'https://openvibe.community/p/0', occurred_at: new Date(Date.now() - 86400e3).toISOString(), ref: { service: 'community' } }], next_cursor: null } : {}, ${signed ? "{ viewer: { can_post: true } }" : '{}'}));
++ if (s.startsWith('https://openvibe.network/api/frame/wall')) return J(Object.assign({}, ${JSON.stringify(PULSE)}, s.includes('after=c2') ? { items: [{ title: 'An older post', url: 'https://openvibe.community/p/0', occurred_at: new Date(Date.now() - 86400e3).toISOString(), ref: { service: 'community' } }], next_cursor: null } : {}, ${signed ? "{ viewer: { can_post: true } }" : '{}'}));
if (s.startsWith('https://openvibe.actor/api/v1/me/actor')) return J({ id: 'act_1', name: 'Juniper', enabled: true, status: 'working', now: { current_step: 'Reading job posts' }, asks: [] });
if (s.startsWith('https://')) return J({}, false, 404);
return realFetch(u, o);
ddiff --git a/test/ov-home.test.js b/test/ov-home.test.js
iindex 95271ac..3ff00b2 100644
---- a/test/ov-home.test.js
++++ b/test/ov-home.test.js
@@@ -12,8 +12,9 @@ assert.ok(!/\.innerHTML\s*=/.test(src) && !/insertAdjacentHTML/.test(src), 'it n
assert.ok(/const safe = \(u\) =>/.test(src) && /x\.protocol === 'https:' \|\| x\.origin === location\.origin/.test(src), 'links are https or this site only');
assert.ok(/localStorage\.setItem\(HOME_KEY/.test(src) && /\/api\/v1\/me\/home-screen/.test(src), 'the arrangement is kept here and on Network');
assert.ok(/credentials: 'omit'/.test(src) && !/credentials: 'include'/.test(src), 'no cookies go with any call');
--assert.ok(/\/api\/v1\/me\/status/.test(src) && /viewer\.can_post/.test(src), 'the composer posts to Community only when it says the person can');
--assert.ok(/\/api\/v1\/pulse\?/.test(src) && /for', 'me'/.test(src), 'the wall reads the pulse, personal when signed in');
++assert.ok(/\/status`/.test(src) && /viewer\.can_post/.test(src), 'the composer posts (through Network to Community) only when Community says the person can');
++assert.ok(/\/wall\?/.test(src) && /for', 'me'/.test(src), 'the wall reads the pulse (through Network), personal when signed in');
++assert.ok(/const FRAME = 'https:\/\/openvibe\.network\/api\/frame'/.test(src) && !/openvibe\.community\/api/.test(src), 'the menu reaches Community only through Network (every site allows openvibe.network)');
assert.ok(/LONG_MS = 450/.test(src) && /ArrowLeft/.test(src) && /Delete/.test(src), 'long-press, and keys for people who cannot drag');
assert.ok(/prefers-reduced-motion: reduce/.test(src), 'the wiggle and the pulse stop for reduced motion');
assert.ok(!/@keyframes [a-z-]*spin|ovh-spin|rotate\(360deg\)/i.test(src), 'no spinners');