openvibers/openvibe.shared Commits
4.93.1: an ov: icon the ring set lacks draws as its glyph in a ring (#250)
Changes
@@@ -4,6 +4,12 @@ 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.93.1 — 2026-11-02++++- **An `ov:` icon the ring set lacks draws as its glyph** (`showcase.icon`). Before, it drew the bare OV mark: Today's++ "Your agenda" (`ov:calendar`) showed a "V". When the ring icons do not know a name and the glyph set does, the glyph++ is drawn in a ring of its own (the accent, a soft fill and a thin ring), the same size as the ring icons.++## 4.93.0 — 2026-11-02- **Content labels, each viewer's own choice** (plan L1, `common.content-labels@1`, `network.label-prefs@1`).@@@ -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.93.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed++**Status:** alpha, v4.93.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.93.0",++ "version": "4.93.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)",@@@ -1,6 +1,6 @@{"name": "openvibe-shared",-- "version": "4.93.0",++ "version": "4.93.1","description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site","main": "index.js","bin": {@@@ -61,6 +61,7 @@ html[data-ov-motion=reduced] .sc-ring,html[data-ov-motion=reduced] .sc-aura-glowa.sc-card:hover,a.sc-card:focus-visible{border-color:var(--accent,#3b82f6);transform:translateY(-2px);outline:0}.sc-ic{width:40px;height:40px;flex:none;display:grid;place-items:center;border-radius:12px;font-size:17px;color:var(--accent-light,#60a5fa);background:color-mix(in srgb,var(--accent,#3b82f6) 14%,transparent)}.sc-ic.sc-ic-ov{background:none;border-radius:50%}++.sc-ovi--glyph{border-radius:50%;color:var(--accent,#3b82f6);background:color-mix(in srgb,var(--accent,#3b82f6) 13%,transparent);box-shadow:inset 0 0 0 1.5px color-mix(in srgb,var(--accent,#3b82f6) 42%,transparent)}.sc-ovi--glyph svg{width:54%!important;height:54%!important}.sc-card b{display:block;font-size:15.5px}.sc-card small{display:block;margin-top:3px;color:var(--text-secondary,#96a7c2);font-size:13.5px;line-height:1.45}.sc-steps{list-style:none;margin:0;padding:0;counter-reset:sc;display:grid;gap:14px 24px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}@container (min-width:584px) and (max-width:1191px){.sc-steps:has(>:nth-child(4):last-child){grid-template-columns:repeat(2,minmax(0,1fr))}}@@@ -33,12 +33,20 @@const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));const has = (a) => Array.isArray(a) && a.length > 0;let ovIcons = null;++let glyphSet = null;function icon(name, size = 22) {if (!name) return '';const n = String(name);if (n.startsWith('ov:')) {ovIcons = ovIcons || require('./ov-icons');-- return `<span class="ov-icon sc-ovi" data-icon="${esc(n.slice(3))}" style="--ovi-size:${size}px" aria-hidden="true">${ovIcons.svg(n.slice(3))}</span>`;++ const key = n.slice(3);++ // A name the ring set lacks drew as the bare OV mark (Today's "Your agenda", ov:calendar, 2026-11-02): the plain++ // glyph of that name, in a ring of its own, when the glyph set has it.++ if (key !== 'ov' && ovIcons.resolve(key) === 'ov') {++ glyphSet = glyphSet || require('./glyphs');++ if (glyphSet.NAMES.includes(key)) return `<span class="ov-icon sc-ovi sc-ovi--glyph" data-icon="${esc(key)}" style="--ovi-size:${size}px" aria-hidden="true">${glyphSet.svg(key)}</span>`;++ }++ return `<span class="ov-icon sc-ovi" data-icon="${esc(key)}" style="--ovi-size:${size}px" aria-hidden="true">${ovIcons.svg(key)}</span>`;}return `<i class="fa-solid ${esc(n.replace(/[^a-z0-9 -]/gi, ''))}" aria-hidden="true"></i>`;}@@@ -481,6 +489,7 @@ html[data-ov-motion=reduced] .sc-ring,html[data-ov-motion=reduced] .sc-aura-glowa.sc-card:hover,a.sc-card:focus-visible{border-color:var(--accent,#3b82f6);transform:translateY(-2px);outline:0}.sc-ic{width:40px;height:40px;flex:none;display:grid;place-items:center;border-radius:12px;font-size:17px;color:var(--accent-light,#60a5fa);background:color-mix(in srgb,var(--accent,#3b82f6) 14%,transparent)}.sc-ic.sc-ic-ov{background:none;border-radius:50%}++.sc-ovi--glyph{border-radius:50%;color:var(--accent,#3b82f6);background:color-mix(in srgb,var(--accent,#3b82f6) 13%,transparent);box-shadow:inset 0 0 0 1.5px color-mix(in srgb,var(--accent,#3b82f6) 42%,transparent)}.sc-ovi--glyph svg{width:54%!important;height:54%!important}.sc-card b{display:block;font-size:15.5px}.sc-card small{display:block;margin-top:3px;color:var(--text-secondary,#96a7c2);font-size:13.5px;line-height:1.45}.sc-steps{list-style:none;margin:0;padding:0;counter-reset:sc;display:grid;gap:14px 24px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}@container (min-width:584px) and (max-width:1191px){.sc-steps:has(>:nth-child(4):last-child){grid-template-columns:repeat(2,minmax(0,1fr))}}@@@ -67,6 +67,10 @@ assert.ok(/@container \(min-width:520px\) and \(max-width:1035px\)\{\.sc-grid:ha{const ring = sc.features({ items: [{ icon: 'ov:live', title: 'Live' }, { icon: 'fa-code', title: 'Code' }] });assert.ok(ring.includes('<span class="sc-ic sc-ic-ov"><span class="ov-icon sc-ovi" data-icon="live" style="--ovi-size:40px"'), 'the ring icon is 40 px in an untinted slot');++ // A name the ring set lacks is its glyph in a ring, never the bare OV mark (2026-11-02, Today's ov:calendar).++ const cal = sc.features({ title: 'More', items: [{ icon: 'ov:calendar', title: 'Your agenda', text: 'x' }] });++ assert.ok(cal.includes('<span class="ov-icon sc-ovi sc-ovi--glyph" data-icon="calendar"'), 'a glyph in a ring');++ assert.ok(!/data-icon="calendar"[^>]*>[^<]*<svg viewBox="0 0 48 48"/.test(cal), 'not the ring set\'s fallback');assert.ok(ring.includes('<span class="sc-ic"><i class="fa-solid fa-code"'), 'a Font Awesome icon keeps the plain slot');assert.ok(sc.CSS.includes('.sc-ic.sc-ic-ov{background:none'), 'the slot drops its tile for a ring icon');}
@@ -4,6 +4,12 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
@@ -4,6 +4,12 @@ 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.93.1 — 2026-11-02
+
+- **An `ov:` icon the ring set lacks draws as its glyph** (`showcase.icon`). Before, it drew the bare OV mark: Today's
+ "Your agenda" (`ov:calendar`) showed a "V". When the ring icons do not know a name and the glyph set does, the glyph
+ is drawn in a ring of its own (the accent, a soft fill and a thin ring), the same size as the ring icons.
+
## 4.93.0 — 2026-11-02
## 4.93.0 — 2026-11-02
- **Content labels, each viewer's own choice** (plan L1, `common.content-labels@1`, `network.label-prefs@1`).
- **Content labels, each viewer's own choice** (plan L1, `common.content-labels@1`, `network.label-prefs@1`).
@@ -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.93.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
+**Status:** alpha, v4.93.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.93.0",
+ "version": "4.93.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)",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.93.0",
+ "version": "4.93.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": {
@@ -61,6 +61,7 @@ html[data-ov-motion=reduced] .sc-ring,html[data-ov-motion=reduced] .sc-aura-glow
@@ -61,6 +61,7 @@ html[data-ov-motion=reduced] .sc-ring,html[data-ov-motion=reduced] .sc-aura-glow
a.sc-card:hover,a.sc-card:focus-visible{border-color:var(--accent,#3b82f6);transform:translateY(-2px);outline:0}
a.sc-card:hover,a.sc-card:focus-visible{border-color:var(--accent,#3b82f6);transform:translateY(-2px);outline:0}
.sc-ic{width:40px;height:40px;flex:none;display:grid;place-items:center;border-radius:12px;font-size:17px;color:var(--accent-light,#60a5fa);background:color-mix(in srgb,var(--accent,#3b82f6) 14%,transparent)}
.sc-ic{width:40px;height:40px;flex:none;display:grid;place-items:center;border-radius:12px;font-size:17px;color:var(--accent-light,#60a5fa);background:color-mix(in srgb,var(--accent,#3b82f6) 14%,transparent)}
.sc-ic.sc-ic-ov{background:none;border-radius:50%}
.sc-ic.sc-ic-ov{background:none;border-radius:50%}
+.sc-ovi--glyph{border-radius:50%;color:var(--accent,#3b82f6);background:color-mix(in srgb,var(--accent,#3b82f6) 13%,transparent);box-shadow:inset 0 0 0 1.5px color-mix(in srgb,var(--accent,#3b82f6) 42%,transparent)}.sc-ovi--glyph svg{width:54%!important;height:54%!important}
.sc-card b{display:block;font-size:15.5px}.sc-card small{display:block;margin-top:3px;color:var(--text-secondary,#96a7c2);font-size:13.5px;line-height:1.45}
.sc-card b{display:block;font-size:15.5px}.sc-card small{display:block;margin-top:3px;color:var(--text-secondary,#96a7c2);font-size:13.5px;line-height:1.45}
.sc-steps{list-style:none;margin:0;padding:0;counter-reset:sc;display:grid;gap:14px 24px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
.sc-steps{list-style:none;margin:0;padding:0;counter-reset:sc;display:grid;gap:14px 24px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
@container (min-width:584px) and (max-width:1191px){.sc-steps:has(>:nth-child(4):last-child){grid-template-columns:repeat(2,minmax(0,1fr))}}
@container (min-width:584px) and (max-width:1191px){.sc-steps:has(>:nth-child(4):last-child){grid-template-columns:repeat(2,minmax(0,1fr))}}
@@ -33,12 +33,20 @@
@@ -33,12 +33,20 @@
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
const has = (a) => Array.isArray(a) && a.length > 0;
const has = (a) => Array.isArray(a) && a.length > 0;
let ovIcons = null;
let ovIcons = null;
+let glyphSet = null;
function icon(name, size = 22) {
function icon(name, size = 22) {
if (!name) return '';
if (!name) return '';
const n = String(name);
const n = String(name);
if (n.startsWith('ov:')) {
if (n.startsWith('ov:')) {
ovIcons = ovIcons || require('./ov-icons');
ovIcons = ovIcons || require('./ov-icons');
- return `<span class="ov-icon sc-ovi" data-icon="${esc(n.slice(3))}" style="--ovi-size:${size}px" aria-hidden="true">${ovIcons.svg(n.slice(3))}</span>`;
+ const key = n.slice(3);
+ // A name the ring set lacks drew as the bare OV mark (Today's "Your agenda", ov:calendar, 2026-11-02): the plain
+ // glyph of that name, in a ring of its own, when the glyph set has it.
+ if (key !== 'ov' && ovIcons.resolve(key) === 'ov') {
+ glyphSet = glyphSet || require('./glyphs');
+ if (glyphSet.NAMES.includes(key)) return `<span class="ov-icon sc-ovi sc-ovi--glyph" data-icon="${esc(key)}" style="--ovi-size:${size}px" aria-hidden="true">${glyphSet.svg(key)}</span>`;
+ }
+ return `<span class="ov-icon sc-ovi" data-icon="${esc(key)}" style="--ovi-size:${size}px" aria-hidden="true">${ovIcons.svg(key)}</span>`;
}
}
return `<i class="fa-solid ${esc(n.replace(/[^a-z0-9 -]/gi, ''))}" aria-hidden="true"></i>`;
return `<i class="fa-solid ${esc(n.replace(/[^a-z0-9 -]/gi, ''))}" aria-hidden="true"></i>`;
}
}
@@ -481,6 +489,7 @@ html[data-ov-motion=reduced] .sc-ring,html[data-ov-motion=reduced] .sc-aura-glow
@@ -481,6 +489,7 @@ html[data-ov-motion=reduced] .sc-ring,html[data-ov-motion=reduced] .sc-aura-glow
a.sc-card:hover,a.sc-card:focus-visible{border-color:var(--accent,#3b82f6);transform:translateY(-2px);outline:0}
a.sc-card:hover,a.sc-card:focus-visible{border-color:var(--accent,#3b82f6);transform:translateY(-2px);outline:0}
.sc-ic{width:40px;height:40px;flex:none;display:grid;place-items:center;border-radius:12px;font-size:17px;color:var(--accent-light,#60a5fa);background:color-mix(in srgb,var(--accent,#3b82f6) 14%,transparent)}
.sc-ic{width:40px;height:40px;flex:none;display:grid;place-items:center;border-radius:12px;font-size:17px;color:var(--accent-light,#60a5fa);background:color-mix(in srgb,var(--accent,#3b82f6) 14%,transparent)}
.sc-ic.sc-ic-ov{background:none;border-radius:50%}
.sc-ic.sc-ic-ov{background:none;border-radius:50%}
+.sc-ovi--glyph{border-radius:50%;color:var(--accent,#3b82f6);background:color-mix(in srgb,var(--accent,#3b82f6) 13%,transparent);box-shadow:inset 0 0 0 1.5px color-mix(in srgb,var(--accent,#3b82f6) 42%,transparent)}.sc-ovi--glyph svg{width:54%!important;height:54%!important}
.sc-card b{display:block;font-size:15.5px}.sc-card small{display:block;margin-top:3px;color:var(--text-secondary,#96a7c2);font-size:13.5px;line-height:1.45}
.sc-card b{display:block;font-size:15.5px}.sc-card small{display:block;margin-top:3px;color:var(--text-secondary,#96a7c2);font-size:13.5px;line-height:1.45}
.sc-steps{list-style:none;margin:0;padding:0;counter-reset:sc;display:grid;gap:14px 24px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
.sc-steps{list-style:none;margin:0;padding:0;counter-reset:sc;display:grid;gap:14px 24px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
@container (min-width:584px) and (max-width:1191px){.sc-steps:has(>:nth-child(4):last-child){grid-template-columns:repeat(2,minmax(0,1fr))}}
@container (min-width:584px) and (max-width:1191px){.sc-steps:has(>:nth-child(4):last-child){grid-template-columns:repeat(2,minmax(0,1fr))}}
@@ -67,6 +67,10 @@ assert.ok(/@container \(min-width:520px\) and \(max-width:1035px\)\{\.sc-grid:ha
@@ -67,6 +67,10 @@ assert.ok(/@container \(min-width:520px\) and \(max-width:1035px\)\{\.sc-grid:ha
{
{
const ring = sc.features({ items: [{ icon: 'ov:live', title: 'Live' }, { icon: 'fa-code', title: 'Code' }] });
const ring = sc.features({ items: [{ icon: 'ov:live', title: 'Live' }, { icon: 'fa-code', title: 'Code' }] });
assert.ok(ring.includes('<span class="sc-ic sc-ic-ov"><span class="ov-icon sc-ovi" data-icon="live" style="--ovi-size:40px"'), 'the ring icon is 40 px in an untinted slot');
assert.ok(ring.includes('<span class="sc-ic sc-ic-ov"><span class="ov-icon sc-ovi" data-icon="live" style="--ovi-size:40px"'), 'the ring icon is 40 px in an untinted slot');
+ // A name the ring set lacks is its glyph in a ring, never the bare OV mark (2026-11-02, Today's ov:calendar).
+ const cal = sc.features({ title: 'More', items: [{ icon: 'ov:calendar', title: 'Your agenda', text: 'x' }] });
+ assert.ok(cal.includes('<span class="ov-icon sc-ovi sc-ovi--glyph" data-icon="calendar"'), 'a glyph in a ring');
+ assert.ok(!/data-icon="calendar"[^>]*>[^<]*<svg viewBox="0 0 48 48"/.test(cal), 'not the ring set\'s fallback');
assert.ok(ring.includes('<span class="sc-ic"><i class="fa-solid fa-code"'), 'a Font Awesome icon keeps the plain slot');
assert.ok(ring.includes('<span class="sc-ic"><i class="fa-solid fa-code"'), 'a Font Awesome icon keeps the plain slot');
assert.ok(sc.CSS.includes('.sc-ic.sc-ic-ov{background:none'), 'the slot drops its tile for a ring icon');
assert.ok(sc.CSS.includes('.sc-ic.sc-ic-ov{background:none'), 'the slot drops its tile for a ring icon');
}
}