openvibers/openvibe.network Commits

Registry flip: chat is live on openvibe.chat (exposure live/service); the Frame's site switcher opens it (its host now comes from the manifest's publicOrigin)

OP OpenVibers committed 24 Sept 2026 · cfb112be12 · 4 files, +10 −10


Changes

ddiff --git a/server/frame/sites.js b/server/frame/sites.js
iindex e9681fde..bfc4dfb9 100644
---- a/server/frame/sites.js
++++ b/server/frame/sites.js
@@@ -11,7 +11,7 @@ const { exposureOf } = require('../registry/exposure');
// Network's exposure overlay (server/registry/exposure.js); every other site is 'soon' and says why
// (state: internal = runs on loopback only, placeholder = planned). The two lists cannot disagree.
// host comes from the service's manifest (openvibe-contracts publicOrigin); an entry names its host only
--// while its manifest has no public origin (chat). TODO(plan §5.1/§5.3): name, icon, tagline, what and the
++// while its manifest has no public origin. TODO(plan §5.1/§5.3): name, icon, tagline, what and the
// legal profile are presentation with no manifest field yet; they move when the manifest schema has them.
const RAW_SITES = [
{ id: 'live', name: 'Live', icon: 'live', service: 'live', profile: 'streaming', tagline: 'Streams, clips and chat', what: 'Live streaming from a browser, OBS or a phone, with restreaming, clips, VODs, chat games and channel points.' },
@@@ -20,7 +20,7 @@ const RAW_SITES = [
{ id: 'games', name: 'Games', icon: 'games', service: 'games', profile: 'games', tagline: 'Browser games', what: 'Scraplandia, a shared pixel canvas and other games that run in a tab and know your OpenVibe account.' },
{ id: 'media', name: 'Media', icon: 'media', service: 'media', profile: 'hosting', tagline: 'VODs, clips, files', what: 'The public media library behind every site: recorded streams, clips, images and uploads.' },
{ id: 'network', name: 'Network', icon: 'network', service: 'network', profile: 'account', tagline: 'Account and themes', what: 'The account behind every site: one sign-in, themes, notifications, history and linked services.' },
-- { id: 'chat', name: 'Chat', host: 'openvibe.chat', icon: 'chat', service: 'chat', profile: 'ugc', tagline: 'One chat for the network', what: 'Rooms, stream chat, direct messages, calls and text-to-speech under one identity.' },
++ { id: 'chat', name: 'Chat', icon: 'chat', service: 'chat', profile: 'ugc', tagline: 'One chat for the network', what: 'Rooms, stream chat, direct messages, calls and text-to-speech under one identity.' },
{ id: 'codes', name: 'Codes', icon: 'codes', service: 'codes', profile: 'ugc', tagline: 'Build on OpenVibe', what: 'The developer portal: API and SDK docs, the service and capability registry, event schemas, credentials, playgrounds and mod publishing.' },
{ id: 'blog', name: 'Blog', icon: 'blog', service: 'blog', profile: 'info', tagline: 'Long-form, yours to keep', what: 'The official OpenVibe blog and a blog for every member: drafts, scheduling, media, comments and feeds.' },
{ id: 'wiki', name: 'Wiki', icon: 'wiki', service: 'wiki', profile: 'ugc', tagline: 'Knowledge, with sources', what: 'Wiki spaces with page trees, revision history, citations and discussion, editable together.' },
ddiff --git a/server/registry/exposure.js b/server/registry/exposure.js
iindex 90267189..881e8806 100644
---- a/server/registry/exposure.js
++++ b/server/registry/exposure.js
@@@ -82,7 +82,7 @@ const EXPOSURE = {
openre: { state: 'internal', public_site: 'placeholder', note: SITE_PLACEHOLDER },
search: { state: 'live', public_site: 'service', note: 'public search page and query API at search.openvibe.network (since 2026-09-23)' },
sources: { state: 'internal', public_site: null, note: 'internal ingestion service; sources.openvibe.network says so (its API is loopback-only)' },
-- chat: { state: 'internal', public_site: null, note: 'serves Live\'s chat through Live; openvibe.chat serves the OpenVibe.Sites placeholder page' },
++ chat: { state: 'live', public_site: 'service', note: 'openvibe.chat since 2026-09-24 (global chat, messages, settings); also serves Live\'s chat paths' },
ai: { state: 'internal', public_site: null, note: 'called by other services at 127.0.0.1:4700; ai.openvibe.network serves the OpenVibe.Sites placeholder page' },
sdk: library('openvibe-sdk', 'OpenVibe.SDK'),
ddiff --git a/test/frame.test.js b/test/frame.test.js
iindex 8f52f8a6..5464b074 100644
---- a/test/frame.test.js
++++ b/test/frame.test.js
@@@ -14,10 +14,10 @@ assert.equal(siteForHost('yt.openvibe.tools').id, 'tools');
assert.equal(siteForHost('evil.example'), null);
assert.strictEqual(svc.ranking().at, null, 'cold start: no use counted yet (the registry\'s featured list says so)');
let p = svc.payloadFor('openvibe.media');
--assert.deepEqual(p.nav.map(n => n.id), ['live', 'tools', 'community', 'games', 'media', 'network', 'codes', 'blog', 'wiki'], 'cold start follows the base order');
++assert.deepEqual(p.nav.map(n => n.id), ['live', 'tools', 'community', 'games', 'media', 'network', 'chat', 'codes', 'blog', 'wiki'], 'cold start follows the base order');
assert.equal(p.footer.legal.dmca, 'https://openvibe.media/dmca', 'legal links stay on the site\'s own domain');
assert.ok(!p.footer.discover.some(l => l.url === 'https://openvibe.media/'), 'a site never recommends itself');
--assert.ok(p.soon.length >= 10);
++assert.ok(p.soon.length >= 9, 'every planned site still listed as soon (chat launched 2026-09-24)');
// Real use reorders: community gets the history, so it rises above the cold-start order.
const ins = db.prepare("INSERT INTO user_history (user_id, service, sub, type, title, url) VALUES (?, ?, ?, 'page', 't', 'u')");
ddiff --git a/test/registry-exposure.test.js b/test/registry-exposure.test.js
iindex 2e25744a..ebec0b76 100644
---- a/test/registry-exposure.test.js
++++ b/test/registry-exposure.test.js
@@@ -18,7 +18,7 @@ const PINNED = {
network: 'live', live: 'live', tools: 'live', media: 'live', games: 'live', community: 'live', events: 'live',
billing: 'live', codes: 'live', blog: 'live', wiki: 'live', sites: 'live',
news: 'internal', reviews: 'internal', deals: 'internal', coupons: 'internal', trade: 'internal', host: 'internal',
-- tips: 'internal', vip: 'internal', openre: 'internal', search: 'live', sources: 'internal', chat: 'internal', ai: 'internal',
++ tips: 'internal', vip: 'internal', openre: 'internal', search: 'live', sources: 'internal', chat: 'live', ai: 'internal',
sdk: 'library', shared: 'library', contracts: 'library', publishing: 'library',
examples: 'repository',
realtime: 'placeholder',
@@@ -101,11 +101,11 @@ const ready = [200, { ready: true, status: 'ready', failed: [], degraded: [], ch
assert.strictEqual(sBy.live.label, 'up');
assert.strictEqual(sBy.live.origin, 'https://openvibe.live');
assert.strictEqual(sBy.shared.label, `not running (library, released v${require('openvibe-shared/package.json').version})`);
-- assert.deepStrictEqual(st.exposure_summary, { live: 13, internal: 12, library: 4, repository: 1, placeholder: 1 });
++ assert.deepStrictEqual(st.exposure_summary, { live: 14, internal: 11, library: 4, repository: 1, placeholder: 1 });
// The page: no row for a service whose domain serves a placeholder shows a bare "Up".
const html = await fetch(base + '/status').then(x => x.text());
-- for (const id of PLACEHOLDER_DOMAINS.concat(['chat', 'ai'])) {
++ for (const id of PLACEHOLDER_DOMAINS.concat(['ai'])) {
const row = html.match(new RegExp(`<tr id="svc-${id}">[\\s\\S]*?</tr>`))[0];
assert.ok(!/>Up<\/span>/.test(row), `${id} row must not read a bare Up`);
assert.ok(row.includes('Up · loopback only'), `${id} row says loopback only`);
@@@ -116,12 +116,12 @@ const ready = [200, { ready: true, status: 'ready', failed: [], degraded: [], ch
// 3. The Frame: the nav lists only sites whose domain serves the service; the rest are soon with a reason.
const open = SITES.filter(s => s.status === 'open').map(s => s.id);
-- assert.deepStrictEqual(open, ['live', 'tools', 'community', 'games', 'media', 'network', 'codes', 'blog', 'wiki']);
++ assert.deepStrictEqual(open, ['live', 'tools', 'community', 'games', 'media', 'network', 'chat', 'codes', 'blog', 'wiki']);
for (const s of SITES) {
assert.ok(s.service && PINNED[s.service], `${s.id} names its service`);
assert.strictEqual(s.status === 'open', PINNED[s.service] === 'live', `${s.id} nav status follows exposure`);
}
-- for (const id of ['news', 'reviews', 'deals', 'coupons', 'trade', 'host', 'tips', 'vip', 'stream', 'chat']) {
++ for (const id of ['news', 'reviews', 'deals', 'coupons', 'trade', 'host', 'tips', 'vip', 'stream']) {
const s = SITES.find(x => x.id === id);
assert.strictEqual(s.status, 'soon'); assert.strictEqual(s.state, 'internal');
}