openvibers/openvibe.events Commits
Public events: the shared explanation said once; each card says only its own (#202)
Changes
@@@ -98,7 +98,9 @@ function busOrbit(stats) {}const nsOf = (type) => String(type).split('.')[0];const slug = (s) => String(s).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');--const eventTitle = (type) => TYPE_WORDS[type] || `An event of type ${type}`;++// A type with no words of its own reads as its publisher and what happened ("OpenVibe.Host: release published"), not++// "An event of type host.release.published" over the same name again.++const eventTitle = (type) => TYPE_WORDS[type] || `${serviceName(nsOf(type))}: ${String(type).split('.').slice(1).join(' ').replace(/_/g, ' ')}`;const eventDescription = (type) => `Published by ${serviceName(nsOf(type))} as ${type}. Every subscriber receives it as a signed webhook; any reader can pull it with a cursor.`;const isoDate = (ms) => { const n = Number(ms); return Number.isFinite(n) && n > 0 ? new Date(n).toISOString() : null; };const humanDate = (ms) => {@@@ -159,11 +161,12 @@ function eventCard(r, cat) {const url = serviceUrl(ns);const category = url ? `<a href="${esc(url)}">${esc(ns)}</a>` : esc(ns);const when = iso ? `<time datetime="${iso}">${esc(humanDate(r.last))}</time>` : '<span class="hm-event-undated">date not recorded</span>';++ // What every public event is (a signed webhook to each subscriber, a cursor for any reader) is said once, above the++ // list; each card says only its own: what happened, when it last arrived, who published it, how often, its name.++ void category;return `<li class="hm-event" id="event-${esc(slug(type))}" data-cat="${esc(cat)}">--<p class="hm-event-cat">Category: ${category}</p><h3 class="hm-event-title">${esc(eventTitle(type))}</h3>--<p class="hm-event-meta">${when} · Online, from ${serviceLink(ns)} · ${esc(fmt(r.n))} this week</p>--<p class="hm-event-desc">${esc(eventDescription(type))}</p>++<p class="hm-event-meta">${when} · from ${serviceLink(ns)} · ${esc(fmt(r.n))} this week</p><p class="hm-event-type"><code>${esc(type)}</code></p></li>`;}@@@ -193,7 +196,7 @@ function eventsSection(stats) {const firstOf = (ns) => list.find((r) => nsOf(r.type) === ns);const chips = cats.map((ns) => `<a class="pick-chip" href="#event-${esc(slug(String(firstOf(ns).type)))}">${esc(serviceName(ns))}</a>`).join('');const cards = list.map((r) => eventCard(r, String(cats.indexOf(nsOf(r.type))))).join('');-- inner = `<p class="sc-lede">The public event types anyone may read, newest arrival first: when each last arrived, who published it and what it means. Subscribe to a pattern such as <code>live.stream.*</code> to get them as signed webhooks.</p>++ inner = `<p class="sc-lede">The public event types anyone may read, newest arrival first: when each last arrived, who published it and what it means. Subscribe to a pattern such as <code>live.stream.*</code> to get them as signed webhooks; any reader can also pull them with a cursor.</p><div class="pick pick-events" id="pick-events"><nav class="pick-row" aria-label="Public events this week">${chips}</nav><ul class="hm-events">${cards}</ul></div>${publicTypes.length > list.length ? `<p class="hm-note">And ${esc(fmt(publicTypes.length - list.length))} more public event types this week.</p>` : ''}`;@@@ -49,7 +49,7 @@ t('published events show as counts by service, public types and the latest publiassert.match(fold(page, 'who'), /<a href="https:\/\/openvibe\.live">OpenVibe\.Live<\/a>/);assert.match(events, /A channel went live on OpenVibe\.Live/);assert.match(events, /<time datetime="\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z">/);-- assert.match(events, /Online, from <a href="https:\/\/openvibe\.live">OpenVibe\.Live<\/a> · 2 this week/);++ assert.match(events, / · from <a href="https:\/\/openvibe\.live">OpenVibe\.Live<\/a> · 2 this week/);assert.match(events, /<code>live\.stream\.started<\/code>/);assert.match(events, /A post was published on OpenVibe\.Blog/);assert.match(events, /<code>blog\.post\.published<\/code>/);@@@ -176,14 +176,19 @@ t('the server-rendered event list: title, real <time datetime>, place, descripticonst card = /<li class="hm-event"[\s\S]*?<\/li>/.exec(page)[0];assert.match(card, /A channel went live on OpenVibe\.Live/, 'the event title');assert.match(card, /<time datetime="2026-10-29T09:41:00\.000Z">/, 'a real <time datetime>');-- assert.match(card, /Online, from <a href="https:\/\/openvibe\.live">OpenVibe\.Live<\/a>/, 'the place and publisher');-- assert.match(card, /Published by OpenVibe\.Live as live\.stream\.started/, 'the short description');++ assert.match(card, / · from <a href="https:\/\/openvibe\.live">OpenVibe\.Live<\/a> · /, 'the publisher');assert.match(card, /3 this week/, 'the count');-- assert.match(card, /Category: <a href="https:\/\/openvibe\.live">live<\/a>/, 'a category link');++ // What every event is, said once above the list (2026-11-02: the same sentence on 24 cards made the page 9 screens).++ assert.ok(!card.includes('Every subscriber receives it') && !card.includes('Category:'), 'no boilerplate on the card');++ assert.match(page, /signed webhooks; any reader can also pull them with a cursor/, 'said once in the lede');++ // A type without its own words reads as its publisher and what happened.++ const odd = renderHome({ siteUrl: SITE, apiUrl: SITE, stats: { ...stats, public: [{ type: 'host.release_note.published', n: 1, last }] } });++ assert.match(odd, /<h3 class="hm-event-title">OpenVibe\.Host: release note published<\/h3>/, 'its publisher and what happened');++ assert.ok(!odd.includes('An event of type'), 'never "An event of type …"');// The category nav became the row of chips that picks one category at a time on a phone (the short-pages rule);// the links it held are still in the page, on the cards themselves.assert.match(page, /<a class="pick-chip" href="#event-[a-z0-9-]+">OpenVibe\.Live<\/a>/, 'the category chips jump to their first card');-- assert.match(page, /Online, from <a href="https:\/\/openvibe\.live">OpenVibe\.Live<\/a>/, 'the publisher link is still in the page');++ assert.match(page, /from <a href="https:\/\/openvibe\.live">OpenVibe\.Live<\/a>/, 'the publisher link is still in the page');assert.match(page, /<code>live\.stream\.started<\/code>/, 'the exact event type');// One JSON-LD script per page: every node lives in the page's @graph.
@@ -98,7 +98,9 @@ function busOrbit(stats) {
@@ -98,7 +98,9 @@ function busOrbit(stats) {
}
}
const nsOf = (type) => String(type).split('.')[0];
const nsOf = (type) => String(type).split('.')[0];
const slug = (s) => String(s).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
const slug = (s) => String(s).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
-const eventTitle = (type) => TYPE_WORDS[type] || `An event of type ${type}`;
+// A type with no words of its own reads as its publisher and what happened ("OpenVibe.Host: release published"), not
+// "An event of type host.release.published" over the same name again.
+const eventTitle = (type) => TYPE_WORDS[type] || `${serviceName(nsOf(type))}: ${String(type).split('.').slice(1).join(' ').replace(/_/g, ' ')}`;
const eventDescription = (type) => `Published by ${serviceName(nsOf(type))} as ${type}. Every subscriber receives it as a signed webhook; any reader can pull it with a cursor.`;
const eventDescription = (type) => `Published by ${serviceName(nsOf(type))} as ${type}. Every subscriber receives it as a signed webhook; any reader can pull it with a cursor.`;
const isoDate = (ms) => { const n = Number(ms); return Number.isFinite(n) && n > 0 ? new Date(n).toISOString() : null; };
const isoDate = (ms) => { const n = Number(ms); return Number.isFinite(n) && n > 0 ? new Date(n).toISOString() : null; };
const humanDate = (ms) => {
const humanDate = (ms) => {
@@ -159,11 +161,12 @@ function eventCard(r, cat) {
@@ -159,11 +161,12 @@ function eventCard(r, cat) {
const url = serviceUrl(ns);
const url = serviceUrl(ns);
const category = url ? `<a href="${esc(url)}">${esc(ns)}</a>` : esc(ns);
const category = url ? `<a href="${esc(url)}">${esc(ns)}</a>` : esc(ns);
const when = iso ? `<time datetime="${iso}">${esc(humanDate(r.last))}</time>` : '<span class="hm-event-undated">date not recorded</span>';
const when = iso ? `<time datetime="${iso}">${esc(humanDate(r.last))}</time>` : '<span class="hm-event-undated">date not recorded</span>';
+ // What every public event is (a signed webhook to each subscriber, a cursor for any reader) is said once, above the
+ // list; each card says only its own: what happened, when it last arrived, who published it, how often, its name.
+ void category;
return `<li class="hm-event" id="event-${esc(slug(type))}" data-cat="${esc(cat)}">
return `<li class="hm-event" id="event-${esc(slug(type))}" data-cat="${esc(cat)}">
-<p class="hm-event-cat">Category: ${category}</p>
<h3 class="hm-event-title">${esc(eventTitle(type))}</h3>
<h3 class="hm-event-title">${esc(eventTitle(type))}</h3>
-<p class="hm-event-meta">${when} · Online, from ${serviceLink(ns)} · ${esc(fmt(r.n))} this week</p>
-<p class="hm-event-desc">${esc(eventDescription(type))}</p>
+<p class="hm-event-meta">${when} · from ${serviceLink(ns)} · ${esc(fmt(r.n))} this week</p>
<p class="hm-event-type"><code>${esc(type)}</code></p>
<p class="hm-event-type"><code>${esc(type)}</code></p>
</li>`;
</li>`;
}
}
@@ -193,7 +196,7 @@ function eventsSection(stats) {
@@ -193,7 +196,7 @@ function eventsSection(stats) {
const firstOf = (ns) => list.find((r) => nsOf(r.type) === ns);
const firstOf = (ns) => list.find((r) => nsOf(r.type) === ns);
const chips = cats.map((ns) => `<a class="pick-chip" href="#event-${esc(slug(String(firstOf(ns).type)))}">${esc(serviceName(ns))}</a>`).join('');
const chips = cats.map((ns) => `<a class="pick-chip" href="#event-${esc(slug(String(firstOf(ns).type)))}">${esc(serviceName(ns))}</a>`).join('');
const cards = list.map((r) => eventCard(r, String(cats.indexOf(nsOf(r.type))))).join('');
const cards = list.map((r) => eventCard(r, String(cats.indexOf(nsOf(r.type))))).join('');
- inner = `<p class="sc-lede">The public event types anyone may read, newest arrival first: when each last arrived, who published it and what it means. Subscribe to a pattern such as <code>live.stream.*</code> to get them as signed webhooks.</p>
+ inner = `<p class="sc-lede">The public event types anyone may read, newest arrival first: when each last arrived, who published it and what it means. Subscribe to a pattern such as <code>live.stream.*</code> to get them as signed webhooks; any reader can also pull them with a cursor.</p>
<div class="pick pick-events" id="pick-events"><nav class="pick-row" aria-label="Public events this week">${chips}</nav>
<div class="pick pick-events" id="pick-events"><nav class="pick-row" aria-label="Public events this week">${chips}</nav>
<ul class="hm-events">${cards}</ul></div>
<ul class="hm-events">${cards}</ul></div>
${publicTypes.length > list.length ? `<p class="hm-note">And ${esc(fmt(publicTypes.length - list.length))} more public event types this week.</p>` : ''}`;
${publicTypes.length > list.length ? `<p class="hm-note">And ${esc(fmt(publicTypes.length - list.length))} more public event types this week.</p>` : ''}`;
@@ -49,7 +49,7 @@ t('published events show as counts by service, public types and the latest publi
@@ -49,7 +49,7 @@ t('published events show as counts by service, public types and the latest publi
assert.match(fold(page, 'who'), /<a href="https:\/\/openvibe\.live">OpenVibe\.Live<\/a>/);
assert.match(fold(page, 'who'), /<a href="https:\/\/openvibe\.live">OpenVibe\.Live<\/a>/);
assert.match(events, /A channel went live on OpenVibe\.Live/);
assert.match(events, /A channel went live on OpenVibe\.Live/);
assert.match(events, /<time datetime="\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z">/);
assert.match(events, /<time datetime="\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z">/);
- assert.match(events, /Online, from <a href="https:\/\/openvibe\.live">OpenVibe\.Live<\/a> · 2 this week/);
+ assert.match(events, / · from <a href="https:\/\/openvibe\.live">OpenVibe\.Live<\/a> · 2 this week/);
assert.match(events, /<code>live\.stream\.started<\/code>/);
assert.match(events, /<code>live\.stream\.started<\/code>/);
assert.match(events, /A post was published on OpenVibe\.Blog/);
assert.match(events, /A post was published on OpenVibe\.Blog/);
assert.match(events, /<code>blog\.post\.published<\/code>/);
assert.match(events, /<code>blog\.post\.published<\/code>/);
@@ -176,14 +176,19 @@ t('the server-rendered event list: title, real <time datetime>, place, descripti
@@ -176,14 +176,19 @@ t('the server-rendered event list: title, real <time datetime>, place, descripti
const card = /<li class="hm-event"[\s\S]*?<\/li>/.exec(page)[0];
const card = /<li class="hm-event"[\s\S]*?<\/li>/.exec(page)[0];
assert.match(card, /A channel went live on OpenVibe\.Live/, 'the event title');
assert.match(card, /A channel went live on OpenVibe\.Live/, 'the event title');
assert.match(card, /<time datetime="2026-10-29T09:41:00\.000Z">/, 'a real <time datetime>');
assert.match(card, /<time datetime="2026-10-29T09:41:00\.000Z">/, 'a real <time datetime>');
- assert.match(card, /Online, from <a href="https:\/\/openvibe\.live">OpenVibe\.Live<\/a>/, 'the place and publisher');
- assert.match(card, /Published by OpenVibe\.Live as live\.stream\.started/, 'the short description');
+ assert.match(card, / · from <a href="https:\/\/openvibe\.live">OpenVibe\.Live<\/a> · /, 'the publisher');
assert.match(card, /3 this week/, 'the count');
assert.match(card, /3 this week/, 'the count');
- assert.match(card, /Category: <a href="https:\/\/openvibe\.live">live<\/a>/, 'a category link');
+ // What every event is, said once above the list (2026-11-02: the same sentence on 24 cards made the page 9 screens).
+ assert.ok(!card.includes('Every subscriber receives it') && !card.includes('Category:'), 'no boilerplate on the card');
+ assert.match(page, /signed webhooks; any reader can also pull them with a cursor/, 'said once in the lede');
+ // A type without its own words reads as its publisher and what happened.
+ const odd = renderHome({ siteUrl: SITE, apiUrl: SITE, stats: { ...stats, public: [{ type: 'host.release_note.published', n: 1, last }] } });
+ assert.match(odd, /<h3 class="hm-event-title">OpenVibe\.Host: release note published<\/h3>/, 'its publisher and what happened');
+ assert.ok(!odd.includes('An event of type'), 'never "An event of type …"');
// The category nav became the row of chips that picks one category at a time on a phone (the short-pages rule);
// The category nav became the row of chips that picks one category at a time on a phone (the short-pages rule);
// the links it held are still in the page, on the cards themselves.
// the links it held are still in the page, on the cards themselves.
assert.match(page, /<a class="pick-chip" href="#event-[a-z0-9-]+">OpenVibe\.Live<\/a>/, 'the category chips jump to their first card');
assert.match(page, /<a class="pick-chip" href="#event-[a-z0-9-]+">OpenVibe\.Live<\/a>/, 'the category chips jump to their first card');
- assert.match(page, /Online, from <a href="https:\/\/openvibe\.live">OpenVibe\.Live<\/a>/, 'the publisher link is still in the page');
+ assert.match(page, /from <a href="https:\/\/openvibe\.live">OpenVibe\.Live<\/a>/, 'the publisher link is still in the page');
assert.match(page, /<code>live\.stream\.started<\/code>/, 'the exact event type');
assert.match(page, /<code>live\.stream\.started<\/code>/, 'the exact event type');
// One JSON-LD script per page: every node lives in the page's @graph.
// One JSON-LD script per page: every node lives in the page's @graph.