openvibers/openvibe.space Commits
openvibe-shared v4.228.0 (was v4.227.0) (#308)
Changes
@@@ -154,5 +154,5 @@ AGPL-3.0-only. See [LICENSE](LICENSE).<!-- versions:start -->- openvibe-contracts: v0.417.0- openvibe-sdk: v0.161.0--- openvibe-shared: v4.227.0++- openvibe-shared: v4.228.0<!-- versions:end -->@@@ -8,7 +8,7 @@"domain": "openvibe.space","contracts": "openvibe-contracts v0.417.0","sdk": "openvibe-sdk v0.161.0",-- "shared": "openvibe-shared v4.227.0",++ "shared": "openvibe-shared v4.228.0","capabilities": ["space.site.manage","space.site.config",@@@ -44,7 +44,7 @@"packages": ["openvibe-contracts v0.417.0","openvibe-sdk v0.161.0",-- "openvibe-shared v4.227.0"++ "openvibe-shared v4.228.0"],"updated": "2026-11-12"}@@@ -17,7 +17,7 @@"helmet": "^8.1.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.417.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.161.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.227.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.228.0","pg": "^8.23.0","quickjs-emscripten": "0.32.0"},@@@ -1247,9 +1247,9 @@}},"node_modules/openvibe-shared": {-- "version": "4.227.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.227.0",-- "integrity": "sha512-/PqSqCC/vw/iwvkhDipLyixPyrtTcMpPhZ8UQ35fGDfAEJt43du2MYmZetu/TUysz+iJ9/Z7+VCFsFI1XzuF3Q==",++ "version": "4.228.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.228.0",++ "integrity": "sha512-HR0Hac/K+qKmwMf2QZWCFxW+uVja36nyAtxVbNFgPGUDFdmWhmb6RMoP/CkU0+GSCPsC50k6aTS+oPx2czDgGg==","license": "MIT","dependencies": {"clean-css": "^5.3.3",@@@ -29,7 +29,7 @@"helmet": "^8.1.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.417.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.161.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.227.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.228.0","pg": "^8.23.0","quickjs-emscripten": "0.32.0"},@@@ -2,9 +2,9 @@/*** The home on a phone (the owner's short-pages rule, 2026-10-30): one or two screens, app-like.*-- * pickBlock() several blocks of the same kind, one at a time on a phone, picked with a row of chips. Radio-- * inputs and :has(), no script: every block stays in the page for crawlers, and a wider screen (or a-- * browser without :has()) shows them all and no chips.++ * pickBlock() several blocks of the same kind, one at a time on a phone as tabs (showcase.parts): radios and++ * :has(), no script; every block stays in the page for crawlers, and a wider screen (or a browser++ * without :has()) shows them all.* foldBlock() a part a reader rarely needs, closed on a phone and open where there is room. The page's own markup* keeps it open, so a browser without the script shows it as before; the script closes it on a narrow* screen and remembers the reader's choice.@@@ -12,21 +12,19 @@* The CSS for both lives in public/css/space.css; the rule that shows the picked block is written for the page,* because there is one per block.*/++const { pick } = require('openvibe-shared/showcase');const esc = (s) => String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');/**-- * A pick block. The panes are the page's own blocks; each one keeps its heading on a wide screen, and on a phone the-- * heading gives way to the chip that names it.++ * A pick block: the page's own blocks, one at a time as tabs on a phone (openvibe-shared showcase.parts, the same tabs++ * as every OpenVibe page; the owner, 2026-11-11: "self-contained tabs, so the page isn't so freaking huge … at least on++ * mobile"). Every pane stays in the page for crawlers, a wider screen stacks them, and a link to a pane's id (an++ * anchor in the hero) opens its tab. The old chips only jumped to a pane, so a phone still showed all of them.** pickBlock({ key, label, head, panes }) panes: [{ id, label, html }]*/function pickBlock({ key, label, head = '', panes }) {-- if (!panes.length) return '';-- const chips = panes.map((p) => `<a class="pick-chip" href="#${esc(p.id)}">${esc(p.label)}</a>`).join('');-- // The pane carries the id the page already links to (an anchor in the hero, for instance), so a link to it still-- // opens that pane on a phone (the CSS shows the pane a link points at).-- const body = panes.map((p) => `<div class="pick-pane" id="${esc(p.id)}">${p.html}</div>`).join('');-- return `<div class="pick" id="pick-${esc(key)}"><div class="pick-bar"><div class="pick-head"><h2>${esc(head || label)}</h2></div><nav class="pick-row" data-ovk-scroll aria-label="${esc(label)}">${chips}</nav></div><div class="pick-body">${body}</div></div>`;++ return pick({ key, label, head, panes });}@@@ -12,31 +12,28 @@ const { boot, check, done } = require('./helpers/app');(async () => {const t = await boot();try {-- await check('the home is one pick block: a chip per part, the first picked, every part in the page', async () => {++ await check('the home is one pick block: on a phone its parts are tabs, the first open, every part in the page', async () => {const r = await t.get('/');assert.strictEqual(r.status, 200, r.text);const page = r.text;// From the block to the end of the page: its panes grew past a fixed window when section heads gained an icon (Shared 4.160.0).const block = page.slice(page.indexOf('<div class="pick" id="pick-parts">'));assert.ok(block.startsWith('<div class="pick" id="pick-parts">'), 'the pick block is on the home');-- assert.match(block, /<nav class="pick-row" data-ovk-scroll aria-label="What Space does">/);-- const chips = [...block.matchAll(/<a class="pick-chip" href="#(?:pp-parts-)?([a-z]+)">()([^<]+)<\/a>/g)];-- assert.ok(chips.length >= 5, `a chip per part (${chips.length})`);-- assert.ok(!/type="radio"/.test(block), 'no chip is a radio: nothing is picked, every pane shows');-- // Every part stays in the page, with the id its chip picks.++ // The owner, 11-11: "self-contained tabs, so the page isn't so freaking huge … at least on mobile" (Shared's++ // showcase.parts, a front page: tabs at 600 px and under; the old chips only jumped, so every pane showed).++ assert.match(block, /<div class="ov-parts ov-parts--front" data-parts="\d">/);++ const tabs = [...block.matchAll(/<label for="ovp-parts-([a-z]+)">([^<]+)<\/label>/g)];++ assert.ok(tabs.length >= 5, `a tab per part (${tabs.length})`);++ assert.ok(block.includes(`id="ovp-parts-${tabs[0][1]}" value="${tabs[0][1]}" checked>`), 'the first part is open');++ assert.ok(!block.includes('class="pick-chip"'), 'no jump chips beside the tabs');++ // Every part stays in the page, with the id a link opens it by.const panes = [...block.matchAll(/<div class="pick-pane" id="([a-z]+)">/g)].map((m) => m[1]);-- assert.deepStrictEqual(panes, chips.map((m) => m[1]), 'every part is in the page, one per chip');++ assert.deepStrictEqual(panes, tabs.map((m) => m[1]), 'every part is in the page, one per tab');// The parts themselves are all there: nothing was removed.for (const title of ['Start from a template', 'How a space comes together', 'How a deploy works', 'What works now', 'The same over the API', 'Quotas per project']) {assert.ok(page.includes(title), `${title} is on the page`);}-- });---- await check('the CSS guard: the chips are hidden and the parts all show until 640 px and where :has() is understood', async () => {-- const r = await t.get('/');-- const page = r.text;-- const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'css', 'space.css'), 'utf8');-- assert.match(css, /\.pick-row \{ display: none; \}/, 'the chips are hidden by default');++ assert.match(page, /<link rel="stylesheet" href="\/shared\/ov-parts\.css[^"]*">/, 'the tabs\' look is linked before the page draws');});await check('the parts a reader rarely needs are folds, open in the page and closed on a phone', async () => {
@@ -154,5 +154,5 @@ AGPL-3.0-only. See [LICENSE](LICENSE).
@@ -154,5 +154,5 @@ AGPL-3.0-only. See [LICENSE](LICENSE).
<!-- versions:start -->
<!-- versions:start -->
- openvibe-contracts: v0.417.0
- openvibe-contracts: v0.417.0
- openvibe-sdk: v0.161.0
- openvibe-sdk: v0.161.0
-- openvibe-shared: v4.227.0
+- openvibe-shared: v4.228.0
<!-- versions:end -->
<!-- versions:end -->
@@ -8,7 +8,7 @@
@@ -8,7 +8,7 @@
"domain": "openvibe.space",
"domain": "openvibe.space",
"contracts": "openvibe-contracts v0.417.0",
"contracts": "openvibe-contracts v0.417.0",
"sdk": "openvibe-sdk v0.161.0",
"sdk": "openvibe-sdk v0.161.0",
- "shared": "openvibe-shared v4.227.0",
+ "shared": "openvibe-shared v4.228.0",
"capabilities": [
"capabilities": [
"space.site.manage",
"space.site.manage",
"space.site.config",
"space.site.config",
@@ -44,7 +44,7 @@
@@ -44,7 +44,7 @@
"packages": [
"packages": [
"openvibe-contracts v0.417.0",
"openvibe-contracts v0.417.0",
"openvibe-sdk v0.161.0",
"openvibe-sdk v0.161.0",
- "openvibe-shared v4.227.0"
+ "openvibe-shared v4.228.0"
],
],
"updated": "2026-11-12"
"updated": "2026-11-12"
}
}
@@ -17,7 +17,7 @@
@@ -17,7 +17,7 @@
"helmet": "^8.1.0",
"helmet": "^8.1.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.417.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.417.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.161.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.161.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.227.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.228.0",
"pg": "^8.23.0",
"pg": "^8.23.0",
"quickjs-emscripten": "0.32.0"
"quickjs-emscripten": "0.32.0"
},
},
@@ -1247,9 +1247,9 @@
@@ -1247,9 +1247,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "4.227.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.227.0",
- "integrity": "sha512-/PqSqCC/vw/iwvkhDipLyixPyrtTcMpPhZ8UQ35fGDfAEJt43du2MYmZetu/TUysz+iJ9/Z7+VCFsFI1XzuF3Q==",
+ "version": "4.228.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.228.0",
+ "integrity": "sha512-HR0Hac/K+qKmwMf2QZWCFxW+uVja36nyAtxVbNFgPGUDFdmWhmb6RMoP/CkU0+GSCPsC50k6aTS+oPx2czDgGg==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"clean-css": "^5.3.3",
"clean-css": "^5.3.3",
@@ -29,7 +29,7 @@
@@ -29,7 +29,7 @@
"helmet": "^8.1.0",
"helmet": "^8.1.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.417.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.417.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.161.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.161.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.227.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.228.0",
"pg": "^8.23.0",
"pg": "^8.23.0",
"quickjs-emscripten": "0.32.0"
"quickjs-emscripten": "0.32.0"
},
},
@@ -2,9 +2,9 @@
@@ -2,9 +2,9 @@
/**
/**
* The home on a phone (the owner's short-pages rule, 2026-10-30): one or two screens, app-like.
* The home on a phone (the owner's short-pages rule, 2026-10-30): one or two screens, app-like.
*
*
- * pickBlock() several blocks of the same kind, one at a time on a phone, picked with a row of chips. Radio
- * inputs and :has(), no script: every block stays in the page for crawlers, and a wider screen (or a
- * browser without :has()) shows them all and no chips.
+ * pickBlock() several blocks of the same kind, one at a time on a phone as tabs (showcase.parts): radios and
+ * :has(), no script; every block stays in the page for crawlers, and a wider screen (or a browser
+ * without :has()) shows them all.
* foldBlock() a part a reader rarely needs, closed on a phone and open where there is room. The page's own markup
* foldBlock() a part a reader rarely needs, closed on a phone and open where there is room. The page's own markup
* keeps it open, so a browser without the script shows it as before; the script closes it on a narrow
* keeps it open, so a browser without the script shows it as before; the script closes it on a narrow
* screen and remembers the reader's choice.
* screen and remembers the reader's choice.
@@ -12,21 +12,19 @@
@@ -12,21 +12,19 @@
* The CSS for both lives in public/css/space.css; the rule that shows the picked block is written for the page,
* The CSS for both lives in public/css/space.css; the rule that shows the picked block is written for the page,
* because there is one per block.
* because there is one per block.
*/
*/
+const { pick } = require('openvibe-shared/showcase');
const esc = (s) => String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
const esc = (s) => String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
/**
/**
- * A pick block. The panes are the page's own blocks; each one keeps its heading on a wide screen, and on a phone the
- * heading gives way to the chip that names it.
+ * A pick block: the page's own blocks, one at a time as tabs on a phone (openvibe-shared showcase.parts, the same tabs
+ * as every OpenVibe page; the owner, 2026-11-11: "self-contained tabs, so the page isn't so freaking huge … at least on
+ * mobile"). Every pane stays in the page for crawlers, a wider screen stacks them, and a link to a pane's id (an
+ * anchor in the hero) opens its tab. The old chips only jumped to a pane, so a phone still showed all of them.
*
*
* pickBlock({ key, label, head, panes }) panes: [{ id, label, html }]
* pickBlock({ key, label, head, panes }) panes: [{ id, label, html }]
*/
*/
function pickBlock({ key, label, head = '', panes }) {
function pickBlock({ key, label, head = '', panes }) {
- if (!panes.length) return '';
- const chips = panes.map((p) => `<a class="pick-chip" href="#${esc(p.id)}">${esc(p.label)}</a>`).join('');
- // The pane carries the id the page already links to (an anchor in the hero, for instance), so a link to it still
- // opens that pane on a phone (the CSS shows the pane a link points at).
- const body = panes.map((p) => `<div class="pick-pane" id="${esc(p.id)}">${p.html}</div>`).join('');
- return `<div class="pick" id="pick-${esc(key)}"><div class="pick-bar"><div class="pick-head"><h2>${esc(head || label)}</h2></div><nav class="pick-row" data-ovk-scroll aria-label="${esc(label)}">${chips}</nav></div><div class="pick-body">${body}</div></div>`;
+ return pick({ key, label, head, panes });
}
}
@@ -12,31 +12,28 @@ const { boot, check, done } = require('./helpers/app');
@@ -12,31 +12,28 @@ const { boot, check, done } = require('./helpers/app');
(async () => {
(async () => {
const t = await boot();
const t = await boot();
try {
try {
- await check('the home is one pick block: a chip per part, the first picked, every part in the page', async () => {
+ await check('the home is one pick block: on a phone its parts are tabs, the first open, every part in the page', async () => {
const r = await t.get('/');
const r = await t.get('/');
assert.strictEqual(r.status, 200, r.text);
assert.strictEqual(r.status, 200, r.text);
const page = r.text;
const page = r.text;
// From the block to the end of the page: its panes grew past a fixed window when section heads gained an icon (Shared 4.160.0).
// From the block to the end of the page: its panes grew past a fixed window when section heads gained an icon (Shared 4.160.0).
const block = page.slice(page.indexOf('<div class="pick" id="pick-parts">'));
const block = page.slice(page.indexOf('<div class="pick" id="pick-parts">'));
assert.ok(block.startsWith('<div class="pick" id="pick-parts">'), 'the pick block is on the home');
assert.ok(block.startsWith('<div class="pick" id="pick-parts">'), 'the pick block is on the home');
- assert.match(block, /<nav class="pick-row" data-ovk-scroll aria-label="What Space does">/);
- const chips = [...block.matchAll(/<a class="pick-chip" href="#(?:pp-parts-)?([a-z]+)">()([^<]+)<\/a>/g)];
- assert.ok(chips.length >= 5, `a chip per part (${chips.length})`);
- assert.ok(!/type="radio"/.test(block), 'no chip is a radio: nothing is picked, every pane shows');
- // Every part stays in the page, with the id its chip picks.
+ // The owner, 11-11: "self-contained tabs, so the page isn't so freaking huge … at least on mobile" (Shared's
+ // showcase.parts, a front page: tabs at 600 px and under; the old chips only jumped, so every pane showed).
+ assert.match(block, /<div class="ov-parts ov-parts--front" data-parts="\d">/);
+ const tabs = [...block.matchAll(/<label for="ovp-parts-([a-z]+)">([^<]+)<\/label>/g)];
+ assert.ok(tabs.length >= 5, `a tab per part (${tabs.length})`);
+ assert.ok(block.includes(`id="ovp-parts-${tabs[0][1]}" value="${tabs[0][1]}" checked>`), 'the first part is open');
+ assert.ok(!block.includes('class="pick-chip"'), 'no jump chips beside the tabs');
+ // Every part stays in the page, with the id a link opens it by.
const panes = [...block.matchAll(/<div class="pick-pane" id="([a-z]+)">/g)].map((m) => m[1]);
const panes = [...block.matchAll(/<div class="pick-pane" id="([a-z]+)">/g)].map((m) => m[1]);
- assert.deepStrictEqual(panes, chips.map((m) => m[1]), 'every part is in the page, one per chip');
+ assert.deepStrictEqual(panes, tabs.map((m) => m[1]), 'every part is in the page, one per tab');
// The parts themselves are all there: nothing was removed.
// The parts themselves are all there: nothing was removed.
for (const title of ['Start from a template', 'How a space comes together', 'How a deploy works', 'What works now', 'The same over the API', 'Quotas per project']) {
for (const title of ['Start from a template', 'How a space comes together', 'How a deploy works', 'What works now', 'The same over the API', 'Quotas per project']) {
assert.ok(page.includes(title), `${title} is on the page`);
assert.ok(page.includes(title), `${title} is on the page`);
}
}
- });
-
- await check('the CSS guard: the chips are hidden and the parts all show until 640 px and where :has() is understood', async () => {
- const r = await t.get('/');
- const page = r.text;
- const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'css', 'space.css'), 'utf8');
- assert.match(css, /\.pick-row \{ display: none; \}/, 'the chips are hidden by default');
+ assert.match(page, /<link rel="stylesheet" href="\/shared\/ov-parts\.css[^"]*">/, 'the tabs\' look is linked before the page draws');
});
});
await check('the parts a reader rarely needs are folds, open in the page and closed on a phone', async () => {
await check('the parts a reader rarely needs are folds, open in the page and closed on a phone', async () => {