openvibers/openvibe.help Commits

Help: /sites names where its list came from (the provenance link was empty; axe link-name) (#40)

OP OpenVibers committed 29 days ago · e80fa9c3ee · 2 files, +10 −1

**openvibe.help/sites names where its list came from: the provenance link had no text.**

**Found by:** the host's browser check, which now watches openvibe.help (Host#102). Its axe check failed on `/sites`: `link-name` (serious), `.prov > a[rel="noopener nofollow"]`. The page read "Generated from " followed by an empty link to OpenVibe.Contracts.

**Cause:** `/sites` passes `catalog.source`, which is `{ name, url }`, to `provenance()`. That function reads `source`, which only the per-site and per-article provenance objects carry.

**Change (`server/http/pages.js`):** the link text is `p.source || p.name`, so `/sites` reads "Generated from OpenVibe.Contracts".

**Test (`help-articles.test.js`):** `/sites` and a site page each have a provenance line, and none of their links is empty. The 13 test files that reach the change pass.

Changes

ddiff --git a/server/http/pages.js b/server/http/pages.js
iindex 983414d..11fd519 100644
---- a/server/http/pages.js
++++ b/server/http/pages.js
@@@ -57,7 +57,7 @@ function createPageRoutes(ctx) {
/** Where a page's text came from: the manifests it was read from, and when. */
const provenance = (p, { compact = false } = {}) => {
const files = p.manifests || (p.manifest ? [p.manifest] : []);
-- return html`<p class="prov small muted">Generated from <a href="${p.url}" rel="noopener nofollow">${p.source}</a>${files.length ? html` · ${files.map((f) => html`<code>${f}</code>`)}` : ''}${compact ? '' : html` · read ${time(catalog.generatedAt)}`}</p>`;
++ return html`<p class="prov small muted">Generated from <a href="${p.url}" rel="noopener nofollow">${p.source || p.name}</a>${files.length ? html` · ${files.map((f) => html`<code>${f}</code>`)}` : ''}${compact ? '' : html` · read ${time(catalog.generatedAt)}`}</p>`;
};
const signInPrompt = (what, next) => html`<div class="notice">${what} <a href="/auth/login?next=${encodeURIComponent(next)}">Sign in with OpenVibe</a>.</div>`;
ddiff --git a/test/help-articles.test.js b/test/help-articles.test.js
iindex b9d7d9e..090ad3d 100644
---- a/test/help-articles.test.js
++++ b/test/help-articles.test.js
@@@ -109,6 +109,15 @@ const WITH_FAQ = [
assert.ok(r.text.includes('href="https://openvibe.actor/updates"'), 'no link to its /updates');
});
++ await check('every provenance line names its source: no empty link (axe link-name on /sites, 2026-10-15)', async () => {
++ for (const path of ['/sites', '/sites/openvibe.actor']) {
++ const r = await t.get(path);
++ const links = [...r.text.matchAll(/Generated from <a [^>]*>([^<]*)<\/a>/g)].map((m) => m[1]);
++ assert.ok(links.length, `${path} has a provenance line`);
++ assert.ok(links.every((x) => x.trim()), `${path} has an empty provenance link`);
++ }
++ });
++
await check('a site the catalog does not know is a 404 page, and the URL it was asked for is escaped', async () => {
const r = await t.get('/sites/nope');
assert.strictEqual(r.status, 404);