openvibers/openvibe.design Commits

Changes: month folds with counts, a release per row (#266)

OP OpenVibers committed 2 days ago · ab0d571638 · 3 files, +30 −4

Changes

ddiff --git a/public/css/changes.css b/public/css/changes.css
iindex d16c5e7..55fb761 100644
---- a/public/css/changes.css
++++ b/public/css/changes.css
@@@ -53,3 +53,27 @@
.chg-shot-row { grid-template-columns: 1fr 1fr; gap: 8px; }
.chg-shot figcaption { display: block; }
}
++
++/* The months after the newest twelve (2026-11-11): each a fold with its count, each release one row. */
++.chg-month { margin: 0 0 8px; border: 1px solid var(--border, #2c2c38); border-radius: 16px; background: color-mix(in srgb, var(--text-primary, #e6edf7) 2%, transparent); overflow: hidden; }
++.chg-month > summary { display: flex; align-items: center; gap: 10px; min-height: 52px; padding: 0 18px; cursor: pointer; list-style: none; }
++.chg-month > summary::-webkit-details-marker { display: none; }
++.chg-month > summary b { font-size: 16px; font-weight: 750; }
++.chg-month-n { margin-left: auto; color: var(--text-muted, #8b93ad); font-size: 13px; font-variant-numeric: tabular-nums; }
++.chg-month > summary::after { content: ""; width: 8px; height: 8px; margin: 0 2px 4px 6px; border: solid var(--text-muted, #8b93ad); border-width: 0 2px 2px 0; transform: rotate(45deg); transition: transform .2s; }
++.chg-month[open] > summary::after { transform: translateY(3px) rotate(225deg); }
++.chg-month > summary:hover { background: color-mix(in srgb, var(--accent, #3b82f6) 7%, transparent); }
++.chg-month > summary:focus-visible { outline: 2px solid var(--accent, #3b82f6); outline-offset: -2px; }
++.chg-month-rows { display: grid; padding: 4px 8px 10px; border-top: 1px solid var(--border, #2c2c38); }
++.chg-month-rows > a { display: grid; grid-template-columns: 5.6em minmax(0, 1fr) auto 10px; align-items: center; gap: 12px; min-height: 44px; padding: 6px 10px; border-radius: 10px; color: var(--text-primary, #e6edf7); text-decoration: none; }
++.chg-month-rows > a:hover, .chg-month-rows > a:focus-visible { background: color-mix(in srgb, var(--accent, #3b82f6) 9%, transparent); }
++.chg-month-rows > a > b { font: 650 13px/1 ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--accent, #5b9dff); font-variant-numeric: tabular-nums; }
++.chg-month-rows > a > span { color: var(--text-primary, #e6edf7); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14.5px; }
++.chg-month-rows > a > small { color: var(--text-muted, #8b93ad); font-size: 12.5px; font-variant-numeric: tabular-nums; }
++.chg-month-rows > a > i { width: 8px; height: 8px; border-radius: 50%; background: var(--accent, #5b9dff); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, #3b82f6) 20%, transparent); }
++@media (max-width: 640px) {
++ .chg-month-rows > a { grid-template-columns: 5em minmax(0, 1fr) 10px; grid-template-rows: auto auto; row-gap: 2px; }
++ .chg-month-rows > a > span { grid-column: 2; white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
++ .chg-month-rows > a > small { grid-column: 2; grid-row: 2; }
++ .chg-month-rows > a > i { grid-column: 3; grid-row: 1; }
++}
ddiff --git a/server/views/changes.js b/server/views/changes.js
iindex 0c2ff60..5c67f7c 100644
---- a/server/views/changes.js
++++ b/server/views/changes.js
@@@ -47,7 +47,8 @@ function releaseCard(r) {
function releaseRow(r) {
const hasShots = screenshots(r.version).length > 0;
-- return `<a href="/changes/${esc(r.version)}">${esc(r.version)} · ${esc(r.title)} · ${esc(r.date)}${hasShots ? ' ●' : ''}</a>`;
++ // Lean on purpose (328 rows): the fold's styles read the parts by element, so no class or attribute per row.
++ return `<a href="/changes/${esc(r.version)}"><b>${esc(r.version)}</b><span>${esc(r.title)}</span><small>${esc(r.date)}</small>${hasShots ? '<i title="Has screenshots"></i>' : ''}</a>`;
}
function monthLabel(date) {
@@@ -90,7 +91,7 @@ ${kit.stats([[all.length, 'releases'], [counted, 'with screenshots'], [pin.versi
if (!monthGroups.has(key)) monthGroups.set(key, { label: monthLabel(r.date), releases: [] });
monthGroups.get(key).releases.push(r);
}
-- const folds = [...monthGroups.values()].map((group) => `<details class="chg-month"><summary>${esc(group.label)} · ${group.releases.length} ${group.releases.length === 1 ? 'release' : 'releases'}</summary>${group.releases.map(releaseRow).join('')}</details>`).join('');
++ const folds = [...monthGroups.values()].map((group) => `<details class="chg-month"><summary><b>${esc(group.label)}</b><span class="chg-month-n">${group.releases.length} ${group.releases.length === 1 ? 'release' : 'releases'}</span></summary><div class="chg-month-rows">${group.releases.map(releaseRow).join('')}</div></details>`).join('');
return layout({
title: `Changes: ${all.length} OpenVibe.Shared releases`,
description: `Every OpenVibe.Shared release in the pinned changelog, newest first: what changed, the component pages it touches, the rules it carries out, and its screenshots.`,
ddiff --git a/test/changes.test.js b/test/changes.test.js
iindex 5b28fcc..76ce4c3 100644
---- a/test/changes.test.js
++++ b/test/changes.test.js
@@@ -22,11 +22,12 @@ const { PUBLIC_DIR } = require('../server/assets');
await check('/changes lists every release in the pinned CHANGELOG, newest first, in its own words', async () => {
const r = await t.get('/changes');
assert.strictEqual(r.status, 200);
-- assert.ok(Buffer.byteLength(r.text) < 120 * 1024, `${Buffer.byteLength(r.text)} bytes`);
++ // 136 KB (2026-11-11, was 120): each folded release is a row with its version, title and date as parts.
++ assert.ok(Buffer.byteLength(r.text) < 136 * 1024, `${Buffer.byteLength(r.text)} bytes`);
assert.match(r.text, /Every shared release, and where it shows/);
assert.match(r.text, new RegExp(`openvibe-shared@${changes.SHARED_VERSION.replace(/\./g, '\\.')}`));
assert.strictEqual((r.text.match(/data-release="/g) || []).length, 12, 'exactly twelve full releases');
-- const folded = [...r.text.matchAll(/<summary>([^<]+) · (\d+) releases?<\/summary>/g)];
++ const folded = [...r.text.matchAll(/<summary><b>([^<]+)<\/b><span class="chg-month-n">(\d+) releases?<\/span><\/summary>/g)];
assert.strictEqual(folded.reduce((n, m) => n + Number(m[2]), 0), all.length - 12, 'fold counts cover every remaining release');
assert.strictEqual((r.text.match(/<details class="chg-month">/g) || []).length, folded.length);
let last = -1;