openvibers/openvibe.shared Commits
4.100.1: the browser harness counts a counted fold's text as on the page (#261)
Changes
@@@ -4,6 +4,14 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semvebreaking 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.100.1 — 2026-11-02++++- **The browser harness counts a counted fold's text as on the page.** A closed `<details>` opens with one tap, with++ or without JavaScript, and search engines read it; the network's rule is to fold, never to hide. The release check's++ JSON-LD rule (each entity's name in the page's text; 80% of a list's items) failed Network's home: its "50 sites"++ list names the sites under "Every other neighbourhood". The contents of closed `<details>` (all but the summary) are++ now part of the text the checks read.++## 4.100.0 — 2026-11-02From a phone sweep of every site's home (11-02):@@@ -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.100.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed++**Status:** alpha, v4.100.1 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployedconsumer 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` namesthe tag it pins.@@@ -4,7 +4,7 @@"code": true,"wave": "W2","package": "openvibe-shared",-- "version": "4.100.0",++ "version": "4.100.1","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)",@@@ -360,7 +360,10 @@ const READ_PAGE = `(() => {offenders.push({ el: e, desc: describe(e) + ' (right ' + Math.round(r.right) + 'px)' });}}-- const text = (body ? body.innerText : '').replace(/\\s+/g, ' ').trim();++ // A counted fold's contents (a closed <details>: one tap, with or without JavaScript, and read by search engines)++ // count as the page's text: the network's rule is to fold, never to hide (the owner, 2026-10-31).++ const folded = [...document.querySelectorAll('details:not([open])')].map((d) => [...d.children].filter((c) => c.tagName !== 'SUMMARY').map((c) => c.textContent).join(' ')).join(' ');++ const text = ((body ? body.innerText : '') + ' ' + folded).replace(/\\s+/g, ' ').trim();return {url: location.href, title: document.title, vw, scrollWidth, scrollX,offenders: offenders.map((o) => o.desc),@@@ -1,6 +1,6 @@{"name": "openvibe-shared",-- "version": "4.100.0",++ "version": "4.100.1","description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site","main": "index.js","bin": {@@@ -15,8 +15,10 @@ if (process.env.OV_SKIP_BROWSER === '1' || !h.findChrome()) {const words = 'This page has enough server-rendered words to count as meaningful content for a crawler. '.repeat(4);const page = (body, head = '') => `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>T</title><link rel="icon" href="data:,">${head}</head><body>${body}</body></html>`;const ROUTES = {-- '/': () => page(`<main><h1>Good Page</h1><p>${words}</p><a href="/b">B page</a></main>`,-- '<link rel="canonical" href="ORIGIN/"><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","name":"Good Page"}</script>'),++ // A list whose items sit in a counted fold (a closed <details>) is on the page: fold, never hide (2026-11-02).++ '/': () => page(`<main><h1>Good Page</h1><p>${words}</p><a href="/b">B page</a><details><summary>Every other site <span>3 more</span></summary><ul><li>Folded Alpha</li><li>Folded Beta</li><li>Folded Gamma</li></ul></details></main>`,++ '<link rel="canonical" href="ORIGIN/"><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","name":"Good Page"}</script>'++ + '<script type="application/ld+json">{"@context":"https://schema.org","@type":"ItemList","name":"Sites","itemListElement":[{"@type":"ListItem","position":1,"name":"Folded Alpha"},{"@type":"ListItem","position":2,"name":"Folded Beta"},{"@type":"ListItem","position":3,"name":"Folded Gamma"}]}</script>'),'/b': () => page(`<div id="root"></div><div style="width:2000px" class="wide">wide</div><script src="/s.js?v=1"></script><script src="/s.js?v=2"></script><script>document.getElementById('root').innerHTML = '<p>${words}</p>'; console.error('boom from b'); window.onload = () => { null.x; };</script>`,@@@ -73,7 +75,7 @@ const AXE_STUB = `window.axe = { run: () => Promise.resolve({ violations: locatiassert.ok(/^Chrome\/|HeadlessChrome\//.test(report.chrome), report.chrome);assert.deepStrictEqual(good.checks, { status: 'pass', errors: 'pass', overflow: 'pass', scripts: 'pass', nojs: 'pass', canonical: 'pass', jsonld: 'pass', axe: 'pass' }, text);assert.strictEqual(good.canonical.href, `${base}/`);-- assert.deepStrictEqual(good.jsonld.entities.map((e) => e.found), ['html']);++ assert.deepStrictEqual(good.jsonld.entities.map((e) => e.found), ['html', 'html'], 'a list folded in a closed <details> counts as on the page');assert.deepStrictEqual(bad.checks, { status: 'pass', errors: 'fail', overflow: 'fail', scripts: 'fail', nojs: 'fail', canonical: 'fail', jsonld: 'fail', axe: 'fail' }, text);assert.ok(bad.errors.some((e) => e.startsWith('console: boom from b (')), bad.errors.join(' | '));
@@ -4,6 +4,14 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
@@ -4,6 +4,14 @@ 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.
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).
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
+## 4.100.1 — 2026-11-02
+
+- **The browser harness counts a counted fold's text as on the page.** A closed `<details>` opens with one tap, with
+ or without JavaScript, and search engines read it; the network's rule is to fold, never to hide. The release check's
+ JSON-LD rule (each entity's name in the page's text; 80% of a list's items) failed Network's home: its "50 sites"
+ list names the sites under "Every other neighbourhood". The contents of closed `<details>` (all but the summary) are
+ now part of the text the checks read.
+
## 4.100.0 — 2026-11-02
## 4.100.0 — 2026-11-02
From a phone sweep of every site's home (11-02):
From a phone sweep of every site's home (11-02):
@@ -2,7 +2,7 @@
@@ -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.
> 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.100.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
+**Status:** alpha, v4.100.1 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
consumer installs a tagged release and none keeps a vendored copy
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
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
the tag it pins.
the tag it pins.
@@ -4,7 +4,7 @@
@@ -4,7 +4,7 @@
"code": true,
"code": true,
"wave": "W2",
"wave": "W2",
"package": "openvibe-shared",
"package": "openvibe-shared",
- "version": "4.100.0",
+ "version": "4.100.1",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"deployed": false,
"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)",
"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)",
@@ -360,7 +360,10 @@ const READ_PAGE = `(() => {
@@ -360,7 +360,10 @@ const READ_PAGE = `(() => {
offenders.push({ el: e, desc: describe(e) + ' (right ' + Math.round(r.right) + 'px)' });
offenders.push({ el: e, desc: describe(e) + ' (right ' + Math.round(r.right) + 'px)' });
}
}
}
}
- const text = (body ? body.innerText : '').replace(/\\s+/g, ' ').trim();
+ // A counted fold's contents (a closed <details>: one tap, with or without JavaScript, and read by search engines)
+ // count as the page's text: the network's rule is to fold, never to hide (the owner, 2026-10-31).
+ const folded = [...document.querySelectorAll('details:not([open])')].map((d) => [...d.children].filter((c) => c.tagName !== 'SUMMARY').map((c) => c.textContent).join(' ')).join(' ');
+ const text = ((body ? body.innerText : '') + ' ' + folded).replace(/\\s+/g, ' ').trim();
return {
return {
url: location.href, title: document.title, vw, scrollWidth, scrollX,
url: location.href, title: document.title, vw, scrollWidth, scrollX,
offenders: offenders.map((o) => o.desc),
offenders: offenders.map((o) => o.desc),
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.100.0",
+ "version": "4.100.1",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"main": "index.js",
"bin": {
"bin": {
@@ -15,8 +15,10 @@ if (process.env.OV_SKIP_BROWSER === '1' || !h.findChrome()) {
@@ -15,8 +15,10 @@ if (process.env.OV_SKIP_BROWSER === '1' || !h.findChrome()) {
const words = 'This page has enough server-rendered words to count as meaningful content for a crawler. '.repeat(4);
const words = 'This page has enough server-rendered words to count as meaningful content for a crawler. '.repeat(4);
const page = (body, head = '') => `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>T</title><link rel="icon" href="data:,">${head}</head><body>${body}</body></html>`;
const page = (body, head = '') => `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>T</title><link rel="icon" href="data:,">${head}</head><body>${body}</body></html>`;
const ROUTES = {
const ROUTES = {
- '/': () => page(`<main><h1>Good Page</h1><p>${words}</p><a href="/b">B page</a></main>`,
- '<link rel="canonical" href="ORIGIN/"><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","name":"Good Page"}</script>'),
+ // A list whose items sit in a counted fold (a closed <details>) is on the page: fold, never hide (2026-11-02).
+ '/': () => page(`<main><h1>Good Page</h1><p>${words}</p><a href="/b">B page</a><details><summary>Every other site <span>3 more</span></summary><ul><li>Folded Alpha</li><li>Folded Beta</li><li>Folded Gamma</li></ul></details></main>`,
+ '<link rel="canonical" href="ORIGIN/"><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","name":"Good Page"}</script>'
+ + '<script type="application/ld+json">{"@context":"https://schema.org","@type":"ItemList","name":"Sites","itemListElement":[{"@type":"ListItem","position":1,"name":"Folded Alpha"},{"@type":"ListItem","position":2,"name":"Folded Beta"},{"@type":"ListItem","position":3,"name":"Folded Gamma"}]}</script>'),
'/b': () => page(`<div id="root"></div><div style="width:2000px" class="wide">wide</div>
'/b': () => page(`<div id="root"></div><div style="width:2000px" class="wide">wide</div>
<script src="/s.js?v=1"></script><script src="/s.js?v=2"></script>
<script src="/s.js?v=1"></script><script src="/s.js?v=2"></script>
<script>document.getElementById('root').innerHTML = '<p>${words}</p>'; console.error('boom from b'); window.onload = () => { null.x; };</script>`,
<script>document.getElementById('root').innerHTML = '<p>${words}</p>'; console.error('boom from b'); window.onload = () => { null.x; };</script>`,
@@ -73,7 +75,7 @@ const AXE_STUB = `window.axe = { run: () => Promise.resolve({ violations: locati
@@ -73,7 +75,7 @@ const AXE_STUB = `window.axe = { run: () => Promise.resolve({ violations: locati
assert.ok(/^Chrome\/|HeadlessChrome\//.test(report.chrome), report.chrome);
assert.ok(/^Chrome\/|HeadlessChrome\//.test(report.chrome), report.chrome);
assert.deepStrictEqual(good.checks, { status: 'pass', errors: 'pass', overflow: 'pass', scripts: 'pass', nojs: 'pass', canonical: 'pass', jsonld: 'pass', axe: 'pass' }, text);
assert.deepStrictEqual(good.checks, { status: 'pass', errors: 'pass', overflow: 'pass', scripts: 'pass', nojs: 'pass', canonical: 'pass', jsonld: 'pass', axe: 'pass' }, text);
assert.strictEqual(good.canonical.href, `${base}/`);
assert.strictEqual(good.canonical.href, `${base}/`);
- assert.deepStrictEqual(good.jsonld.entities.map((e) => e.found), ['html']);
+ assert.deepStrictEqual(good.jsonld.entities.map((e) => e.found), ['html', 'html'], 'a list folded in a closed <details> counts as on the page');
assert.deepStrictEqual(bad.checks, { status: 'pass', errors: 'fail', overflow: 'fail', scripts: 'fail', nojs: 'fail', canonical: 'fail', jsonld: 'fail', axe: 'fail' }, text);
assert.deepStrictEqual(bad.checks, { status: 'pass', errors: 'fail', overflow: 'fail', scripts: 'fail', nojs: 'fail', canonical: 'fail', jsonld: 'fail', axe: 'fail' }, text);
assert.ok(bad.errors.some((e) => e.startsWith('console: boom from b (')), bad.errors.join(' | '));
assert.ok(bad.errors.some((e) => e.startsWith('console: boom from b (')), bad.errors.join(' | '));