openvibers/openvibe.shared Commits
shipped.js: the update log's selected filter chip is --on-accent-strong on --accent-strong (4.5:1 in every theme) instead of white on --accent (3.67:1, axe color-contrast serious on every site's /updates, found by the browser harness); test/login-contrast.test.js checks it
Changes
@@@ -8,6 +8,8 @@ A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).**`openvibe-shared/browser-harness`** (roadmap WS-Q task 3, WS-T tasks 3 and 4): Live's browser smoke suite, generalised for any site. It drives headless Chrome over the DevTools protocol with Node's own WebSocket (Node 22+, no dependency), one browser context per route. `run({ base, routes, widths, checks, navigation, … })` checks every route at 390, 768 and 1280 px: the HTTP status (a route can expect 404), console errors (exceptions, `console.error`, failed loads, CSP), horizontal overflow (naming the elements) and scripts requested twice. With JavaScript off it checks the visible text (200 characters), exactly one absolute canonical link, and that JSON-LD parses and its headline or name is in the visible text. axe-core runs at the widest width: serious and critical violations fail, moderate is reported. Once per site, A → B → A navigation runs five times, clicking a link when there is one so a single-page app navigates in place. After a forced GC it samples heap, DOM nodes, listeners and documents (`Performance.getMetrics`), plus live intervals, pending timeouts and open WebSockets (a probe installed before page scripts). A measure fails when it grows past its budget from lap 2 to the last lap and is still growing (`BUDGETS`). CPU, requests and bytes over 5 idle seconds are reported, not failed. `ignoreErrors` rules are counted per label, never dropped silently. `format()` gives text or Markdown, and `summarize()` gives per-check counts. axe-core is not vendored: an installed `axe-core` is used, else 4.13.0 comes from jsDelivr, checked against a pinned sha384 (the same bytes as the npm tarball) and cached. `test/browser-harness-chrome.test.js` runs it in real Chrome against a fixture and skips without Chrome. Additive.++Fix: the selected filter chip of the shared update log (`shipped.js`, every site's `/updates`) was white on `--accent` (3.67:1). It now uses `--accent-strong` / `--on-accent-strong` (4.5:1 or better in every theme), like the navbar's Sign In button. The harness found it.++## 1.15.0 — 2026-09-26**`openvibe-shared/perf-budget`** (roadmap WS-T task 1): size budgets for a page's first load, measured from the running server with no browser. `measure({ base, path })` fetches the page and every same-origin script and stylesheet it names. It reports the files and their raw and brotli KB; cross-origin assets are listed as `external` and not weighed. `check(m, budgets)` lists the budgets exceeded, and `format()` is the failure message.@@@ -56,7 +56,7 @@'.ov-shipped-log{display:flex;flex-direction:column;gap:22px;min-width:0}','.ov-shipped-bar{display:flex;flex-wrap:wrap;gap:6px;align-items:center}',`.ov-shipped-chip{font:inherit;font-size:.8rem;padding:4px 12px;border-radius:999px;border:1px solid ${BORDER};background:transparent;color:${MUTED};cursor:pointer;text-decoration:none}`,-- `.ov-shipped-chip:hover{color:${TEXT};border-color:var(--accent,#3b82f6)}.ov-shipped-chip[aria-pressed="true"]{background:var(--accent,#3b82f6);border-color:var(--accent,#3b82f6);color:var(--on-accent,#fff)}`,++ `.ov-shipped-chip:hover{color:${TEXT};border-color:var(--accent,#3b82f6)}.ov-shipped-chip[aria-pressed="true"]{background:var(--accent-strong,#1d4ed8);border-color:var(--accent-strong,#1d4ed8);color:var(--on-accent-strong,#fff)}`,`.ov-shipped-day h3{font-size:.95rem;font-weight:600;color:${MUTED};padding-bottom:8px;border-bottom:1px solid ${BORDER};margin:0 0 4px}`,'.ov-shipped-entries{display:flex;flex-direction:column;gap:2px}','.ov-shipped-entry{display:flex;align-items:baseline;gap:10px;padding:8px 12px;border-radius:6px;min-width:0}',@@@ -1,5 +1,6 @@'use strict';--// The navbar's Sign In button (navbar.js .openvibe-navbar-login) is --on-accent-strong on --accent-strong:++// The navbar's Sign In button (navbar.js .openvibe-navbar-login) and the update log's pressed filter chip++// (shipped.js) are --on-accent-strong on --accent-strong:// that pair must read at 4.5:1 or better (WCAG AA, normal text) in every built-in theme. An axe survey// found white on the accent (#3b82f6) at 3.67:1 on nine sites (roadmap WS-T task 3).const assert = require('assert');@@@ -17,6 +18,10 @@ const ratio = (a, b) => { const [x, y] = [lum(a), lum(b)].sort((p, q) => q - p);const css = fs.readFileSync(path.join(__dirname, '..', 'navbar.js'), 'utf8');assert.match(css, /\.openvibe-navbar-login \{[^}]*background: var\(--accent-strong, #1d4ed8\); color: var\(--on-accent-strong, #fff\)/, 'the button uses --accent-strong and --on-accent-strong');++// The same pair on the selected filter chip of the shared update log (shipped.js), which the browser check++// (openvibe-shared/browser-harness) found at 3.67:1 on every site's /updates page.++const shipped = fs.readFileSync(path.join(__dirname, '..', 'shipped.js'), 'utf8');++assert.match(shipped, /\.ov-shipped-chip\[aria-pressed="true"\]\{background:var\(--accent-strong,#1d4ed8\);border-color:var\(--accent-strong,#1d4ed8\);color:var\(--on-accent-strong,#fff\)\}/, 'the pressed chip uses the pair');const low = [];for (const t of BUILTIN_THEMES) {const bg = t.variables['--accent-strong'], fg = t.variables['--on-accent-strong'];
@@ -8,6 +8,8 @@ A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
@@ -8,6 +8,8 @@ A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
**`openvibe-shared/browser-harness`** (roadmap WS-Q task 3, WS-T tasks 3 and 4): Live's browser smoke suite, generalised for any site. It drives headless Chrome over the DevTools protocol with Node's own WebSocket (Node 22+, no dependency), one browser context per route. `run({ base, routes, widths, checks, navigation, … })` checks every route at 390, 768 and 1280 px: the HTTP status (a route can expect 404), console errors (exceptions, `console.error`, failed loads, CSP), horizontal overflow (naming the elements) and scripts requested twice. With JavaScript off it checks the visible text (200 characters), exactly one absolute canonical link, and that JSON-LD parses and its headline or name is in the visible text. axe-core runs at the widest width: serious and critical violations fail, moderate is reported. Once per site, A → B → A navigation runs five times, clicking a link when there is one so a single-page app navigates in place. After a forced GC it samples heap, DOM nodes, listeners and documents (`Performance.getMetrics`), plus live intervals, pending timeouts and open WebSockets (a probe installed before page scripts). A measure fails when it grows past its budget from lap 2 to the last lap and is still growing (`BUDGETS`). CPU, requests and bytes over 5 idle seconds are reported, not failed. `ignoreErrors` rules are counted per label, never dropped silently. `format()` gives text or Markdown, and `summarize()` gives per-check counts. axe-core is not vendored: an installed `axe-core` is used, else 4.13.0 comes from jsDelivr, checked against a pinned sha384 (the same bytes as the npm tarball) and cached. `test/browser-harness-chrome.test.js` runs it in real Chrome against a fixture and skips without Chrome. Additive.
**`openvibe-shared/browser-harness`** (roadmap WS-Q task 3, WS-T tasks 3 and 4): Live's browser smoke suite, generalised for any site. It drives headless Chrome over the DevTools protocol with Node's own WebSocket (Node 22+, no dependency), one browser context per route. `run({ base, routes, widths, checks, navigation, … })` checks every route at 390, 768 and 1280 px: the HTTP status (a route can expect 404), console errors (exceptions, `console.error`, failed loads, CSP), horizontal overflow (naming the elements) and scripts requested twice. With JavaScript off it checks the visible text (200 characters), exactly one absolute canonical link, and that JSON-LD parses and its headline or name is in the visible text. axe-core runs at the widest width: serious and critical violations fail, moderate is reported. Once per site, A → B → A navigation runs five times, clicking a link when there is one so a single-page app navigates in place. After a forced GC it samples heap, DOM nodes, listeners and documents (`Performance.getMetrics`), plus live intervals, pending timeouts and open WebSockets (a probe installed before page scripts). A measure fails when it grows past its budget from lap 2 to the last lap and is still growing (`BUDGETS`). CPU, requests and bytes over 5 idle seconds are reported, not failed. `ignoreErrors` rules are counted per label, never dropped silently. `format()` gives text or Markdown, and `summarize()` gives per-check counts. axe-core is not vendored: an installed `axe-core` is used, else 4.13.0 comes from jsDelivr, checked against a pinned sha384 (the same bytes as the npm tarball) and cached. `test/browser-harness-chrome.test.js` runs it in real Chrome against a fixture and skips without Chrome. Additive.
+Fix: the selected filter chip of the shared update log (`shipped.js`, every site's `/updates`) was white on `--accent` (3.67:1). It now uses `--accent-strong` / `--on-accent-strong` (4.5:1 or better in every theme), like the navbar's Sign In button. The harness found it.
+
## 1.15.0 — 2026-09-26
## 1.15.0 — 2026-09-26
**`openvibe-shared/perf-budget`** (roadmap WS-T task 1): size budgets for a page's first load, measured from the running server with no browser. `measure({ base, path })` fetches the page and every same-origin script and stylesheet it names. It reports the files and their raw and brotli KB; cross-origin assets are listed as `external` and not weighed. `check(m, budgets)` lists the budgets exceeded, and `format()` is the failure message.
**`openvibe-shared/perf-budget`** (roadmap WS-T task 1): size budgets for a page's first load, measured from the running server with no browser. `measure({ base, path })` fetches the page and every same-origin script and stylesheet it names. It reports the files and their raw and brotli KB; cross-origin assets are listed as `external` and not weighed. `check(m, budgets)` lists the budgets exceeded, and `format()` is the failure message.
@@ -56,7 +56,7 @@
@@ -56,7 +56,7 @@
'.ov-shipped-log{display:flex;flex-direction:column;gap:22px;min-width:0}',
'.ov-shipped-log{display:flex;flex-direction:column;gap:22px;min-width:0}',
'.ov-shipped-bar{display:flex;flex-wrap:wrap;gap:6px;align-items:center}',
'.ov-shipped-bar{display:flex;flex-wrap:wrap;gap:6px;align-items:center}',
`.ov-shipped-chip{font:inherit;font-size:.8rem;padding:4px 12px;border-radius:999px;border:1px solid ${BORDER};background:transparent;color:${MUTED};cursor:pointer;text-decoration:none}`,
`.ov-shipped-chip{font:inherit;font-size:.8rem;padding:4px 12px;border-radius:999px;border:1px solid ${BORDER};background:transparent;color:${MUTED};cursor:pointer;text-decoration:none}`,
- `.ov-shipped-chip:hover{color:${TEXT};border-color:var(--accent,#3b82f6)}.ov-shipped-chip[aria-pressed="true"]{background:var(--accent,#3b82f6);border-color:var(--accent,#3b82f6);color:var(--on-accent,#fff)}`,
+ `.ov-shipped-chip:hover{color:${TEXT};border-color:var(--accent,#3b82f6)}.ov-shipped-chip[aria-pressed="true"]{background:var(--accent-strong,#1d4ed8);border-color:var(--accent-strong,#1d4ed8);color:var(--on-accent-strong,#fff)}`,
`.ov-shipped-day h3{font-size:.95rem;font-weight:600;color:${MUTED};padding-bottom:8px;border-bottom:1px solid ${BORDER};margin:0 0 4px}`,
`.ov-shipped-day h3{font-size:.95rem;font-weight:600;color:${MUTED};padding-bottom:8px;border-bottom:1px solid ${BORDER};margin:0 0 4px}`,
'.ov-shipped-entries{display:flex;flex-direction:column;gap:2px}',
'.ov-shipped-entries{display:flex;flex-direction:column;gap:2px}',
'.ov-shipped-entry{display:flex;align-items:baseline;gap:10px;padding:8px 12px;border-radius:6px;min-width:0}',
'.ov-shipped-entry{display:flex;align-items:baseline;gap:10px;padding:8px 12px;border-radius:6px;min-width:0}',
@@ -1,5 +1,6 @@
@@ -1,5 +1,6 @@
'use strict';
'use strict';
-// The navbar's Sign In button (navbar.js .openvibe-navbar-login) is --on-accent-strong on --accent-strong:
+// The navbar's Sign In button (navbar.js .openvibe-navbar-login) and the update log's pressed filter chip
+// (shipped.js) are --on-accent-strong on --accent-strong:
// that pair must read at 4.5:1 or better (WCAG AA, normal text) in every built-in theme. An axe survey
// that pair must read at 4.5:1 or better (WCAG AA, normal text) in every built-in theme. An axe survey
// found white on the accent (#3b82f6) at 3.67:1 on nine sites (roadmap WS-T task 3).
// found white on the accent (#3b82f6) at 3.67:1 on nine sites (roadmap WS-T task 3).
const assert = require('assert');
const assert = require('assert');
@@ -17,6 +18,10 @@ const ratio = (a, b) => { const [x, y] = [lum(a), lum(b)].sort((p, q) => q - p);
@@ -17,6 +18,10 @@ const ratio = (a, b) => { const [x, y] = [lum(a), lum(b)].sort((p, q) => q - p);
const css = fs.readFileSync(path.join(__dirname, '..', 'navbar.js'), 'utf8');
const css = fs.readFileSync(path.join(__dirname, '..', 'navbar.js'), 'utf8');
assert.match(css, /\.openvibe-navbar-login \{[^}]*background: var\(--accent-strong, #1d4ed8\); color: var\(--on-accent-strong, #fff\)/, 'the button uses --accent-strong and --on-accent-strong');
assert.match(css, /\.openvibe-navbar-login \{[^}]*background: var\(--accent-strong, #1d4ed8\); color: var\(--on-accent-strong, #fff\)/, 'the button uses --accent-strong and --on-accent-strong');
+// The same pair on the selected filter chip of the shared update log (shipped.js), which the browser check
+// (openvibe-shared/browser-harness) found at 3.67:1 on every site's /updates page.
+const shipped = fs.readFileSync(path.join(__dirname, '..', 'shipped.js'), 'utf8');
+assert.match(shipped, /\.ov-shipped-chip\[aria-pressed="true"\]\{background:var\(--accent-strong,#1d4ed8\);border-color:var\(--accent-strong,#1d4ed8\);color:var\(--on-accent-strong,#fff\)\}/, 'the pressed chip uses the pair');
const low = [];
const low = [];
for (const t of BUILTIN_THEMES) {
for (const t of BUILTIN_THEMES) {
const bg = t.variables['--accent-strong'], fg = t.variables['--on-accent-strong'];
const bg = t.variables['--accent-strong'], fg = t.variables['--on-accent-strong'];