openvibers/openvibe.shared Commits

4.188.0: the account is in the menu (no dropdown under the picture), and the mark is larger in the bar (#411)

OP OpenVibers committed 3 days ago · 0ee0ec5cf2 · 12 files, +82 −46

* 4.188.0: the account is in the menu (no dropdown under the picture), and the mark is larger in the bar

The picture opens the OpenVibe menu on You, where the block the dropdown drew now lives: who you are, add another
account, settings, sign out, your other accounts, the site own items, light or dark and Display. The popup and its
panel registration are gone; the block rests in the bar, out of sight, while the menu is closed, so a site that looks
for its sign-out button or its own item still finds it.

The mark is 42 in the bar (was 34), 34 on a small phone (was 28).

* The brand-face check expects the bar mark at 42

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex eb5ca72..24a75af 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -1,5 +1,23 @@
# Changelog
++## 4.188.0 — 2026-11-10
++
++**The account's things are in the menu.** The owner, 2026-11-10, with the dropdown under the picture marked "Replace →
++to You tab": "this user drop down is just redundant … move it into the You tab of the menu … add account switching
++and adding accounts to that … consolidate the two into one menu."
++
++- **The picture opens the OpenVibe menu on You.** The block the dropdown drew (who you are, add another account,
++ settings, sign out, your other accounts, the site's own items from `addMenuItem`, light or dark and Display) is a
++ block of that tab, at its top, where a shorter bar with the name was. It is there wherever the bar has a person, with
++ or without a token for the network's reads.
++- **No dropdown.** The popup, its position, its height rules and its place among the panels (`user-menu`) are gone.
++ The block rests in the bar, out of sight, while the menu is closed: a site that looks for `#openvibe-logout-btn`,
++ `[data-ov-ask]` or its own item still finds it.
++- **The mark is larger in the bar** (42, was 34; 34 on a small phone, was 28). The owner: "make the OV branding take
++ up most of its area … stand out a little more … compared to the My." Nothing overlaps from 320 px
++ (`navbar-phone-fit-chrome`).
++- In the You tab, "All 65" keeps its own case (it was drawn in the heading's capitals).
++
## 4.187.0 — 2026-11-10
**The bar the owner chose.** 2026-11-10, in the Arena: "here's the new menu button and the logo … these two, but
ddiff --git a/README.md b/README.md
iindex 9251f74..b84000d 100644
---- a/README.md
++++ b/README.md
@@@ -5,7 +5,7 @@
**Docs:** https://openvibe.services · **For AI:** https://openvibe.network/llms.txt
**Install and test:** `npm ci && npm test`
--**Status:** alpha, v4.187.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
++**Status:** alpha, v4.188.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
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
the tag it pins.
@@@ -831,6 +831,12 @@ One menu (4.187): the OpenVibe mark is a button that opens the OpenVibe menu
on its first tab, as the menu button does (there is no separate site
switcher). The site's name stays the link home; switcher: false keeps the
mark as a home link.
++The account's things (4.188): the person's picture opens the OpenVibe menu
++ on You, where the account's block is (who you are, add account, settings,
++ sign out, your other accounts, this site's own items from addMenuItem,
++ Display). There is no dropdown under the picture; the block rests in the bar,
++ out of sight, while the menu is closed, so #openvibe-logout-btn and a
++ site's own items are always in the page.
The bar's order (4.187): balances, the bell, the menu button, then the
person's picture (or Sign In), last. New messages are counted on the bell
(setMessages(count)); there is no Messages button.
ddiff --git a/STATUS.json b/STATUS.json
iindex 055dea2..c94fe55 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -4,7 +4,7 @@
"code": true,
"wave": "W2",
"package": "openvibe-shared",
-- "version": "4.187.0",
++ "version": "4.188.0",
"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)",
ddiff --git a/navbar.js b/navbar.js
iindex 41f3e92..8e145b5 100644
---- a/navbar.js
++++ b/navbar.js
@@@ -66,7 +66,7 @@
.openvibe-navbar .openvibe-navbar-brand button.flame:focus-visible { outline: 2px solid var(--accent, #3b82f6); outline-offset: 1px; }
[data-ov-scroll-focus]:focus-visible { outline: 2px solid var(--accent, #3b82f6); outline-offset: 2px; }
.openvibe-navbar .openvibe-navbar-brand { gap: 6px; padding: 2px; border-radius: 12px; margin-right: 0; }
-- .openvibe-navbar .openvibe-navbar-brand .flame { display: inline-grid; place-items: center; width: auto; min-width: 30px; height: 30px; padding: 0 4px; box-sizing: border-box; border-radius: 9px; transition: background .15s; }
++ .openvibe-navbar .openvibe-navbar-brand .flame { display: inline-grid; place-items: center; width: auto; min-width: 30px; height: 34px; padding: 0 4px; box-sizing: border-box; border-radius: 9px; transition: background .15s; }
.openvibe-navbar .openvibe-navbar-brand .flame:hover { background: var(--accent-glow, rgba(59,130,246,.14)); }
.openvibe-navbar .openvibe-navbar-brand .name { align-items: center; gap: 0; font-family: 'OV Brand', Nunito, 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; font-size: 19px; letter-spacing: -.25px; }
.openvibe-navbar .openvibe-navbar-brand .name a { padding: 3px 1px; border-radius: 6px; transition: color .15s, background .15s; }
@@@ -207,7 +207,7 @@
@media (max-width: 440px) { .ovnav-chip-mini { padding: 0 5px; gap: 3px; } .openvibe-navbar .ovnav-chip.ovnav-chip--all { font-size: 12px; } .ovnav-chip-mini .ovnav-ic { width: 13px; height: 13px; } }
@media (max-width: 400px) { .openvibe-navbar .openvibe-navbar-right { gap: 1px; } .ovnav-chip-mini { padding: 0 3px; gap: 2px; } .openvibe-navbar .ovnav-chip.ovnav-chip--all { font-size: 11.5px; letter-spacing: -.2px; } .openvibe-navbar .openvibe-navbar-brand { gap: 4px; }
.openvibe-navbar { padding-left: 8px; padding-right: 6px; gap: 2px; } .openvibe-navbar .ovnav-burger { width: 34px; } .openvibe-navbar .ovnav-featured-phone { width: 34px; height: 34px; } .openvibe-navbar .openvibe-navbar-avatar { width: 32px; height: 32px; box-sizing: border-box; }
-- .openvibe-navbar .openvibe-navbar-brand .name { font-size: 16.5px; letter-spacing: -.25px; } .openvibe-navbar .openvibe-navbar-brand .flame { min-width: 28px; height: 28px; padding: 0 2px; } .openvibe-navbar .openvibe-navbar-brand .flame .ov-mark { width: 28px !important; } }
++ .openvibe-navbar .openvibe-navbar-brand .name { font-size: 16.5px; letter-spacing: -.25px; } .openvibe-navbar .openvibe-navbar-brand .flame { min-width: 28px; height: 28px; padding: 0 2px; } .openvibe-navbar .openvibe-navbar-brand .flame .ov-mark { width: 34px !important; } }
/* Under 350px the mark alone names the site, so both balances keep their icons (a number alone says nothing). */
@media (max-width: 350px) { .openvibe-navbar .openvibe-navbar-brand .name { display: none; } }
/* The site's name reads whole on a signed-in phone (the owner, 2026-11-02: "the label or the branding of the
@@@ -379,26 +379,13 @@
.openvibe-navbar .openvibe-navbar-login { padding: 0 13px; }
}
-- .openvibe-navbar-dropdown {
-- position: absolute; top: 48px; right: 8px;
-- width: min(320px, calc(100vw - 16px)); background: var(--bg-card, #22222c);
-- border: 1px solid var(--border, #333340); border-radius: 10px;
-- box-shadow: var(--shadow-lg, 0 8px 32px rgba(0,0,0,0.5));
-- display: none; flex-direction: column; overflow: hidden;
-- animation: openvibe-slide-down .2s ease;
-- }
-- .openvibe-navbar-dropdown.open { display: flex; }
-- /* The panel scrolls as a whole; its sections keep their height. A flex column lets children shrink by
-- default, which squeezed the accounts block into a sliver with its own scrollbar when the panel was
-- height-limited (phones). */
++ /* The account's block (4.188.0): not a popup. It rests in the bar out of sight and is a block of the menu's
++ You tab while the menu is open: no position, no shadow, no height of its own. */
++ .openvibe-navbar-dropdown { display: none; flex-direction: column; border: 1px solid var(--border, #333340); }
.openvibe-navbar-dropdown > * { flex-shrink: 0; }
-- /* Panels never leave the screen: dvh is the VISIBLE height on phones (100vh reaches behind the address
-- bar), the panel scrolls inside itself, and it is never wider than the viewport. */
-- .openvibe-navbar-dropdown {
-- max-width: calc(100vw - 16px);
-- max-height: calc(100vh - 64px); max-height: calc(100dvh - 64px - env(safe-area-inset-bottom, 0px));
-- overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; scrollbar-width: thin;
-- }
++ .ovh-account > .openvibe-navbar-dropdown.ovnav-account .openvibe-navbar-dropdown-header .ud-hbtn { min-width: 44px; min-height: 44px; }
++ .ovh-account > .openvibe-navbar-dropdown.ovnav-account { display: flex; overflow: hidden; margin: 2px 4px 10px; border-radius: 16px;
++ background: color-mix(in srgb, var(--text-primary, #e6edf7) 4%, transparent); border-color: color-mix(in srgb, var(--text-primary, #e6edf7) 10%, transparent); }
@keyframes openvibe-slide-down { from { transform: translateY(-8px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
.ov-pop { transform-origin: var(--ov-pop-x, 50%) var(--ov-pop-y, 0); animation: ov-pop .34s cubic-bezier(.2, 1.1, .3, 1) both !important; }
.ov-pop-out { transform-origin: var(--ov-pop-x, 50%) var(--ov-pop-y, 0); animation: ov-pop-out .16s ease-in both !important; pointer-events: none; }
@@@ -571,7 +558,7 @@
const NAV_ICONS_REV = '148a0eda9ca8'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)
// END generated
// The files loaded from beside this one, by content (scripts/sibling-revs.js): a release changes their URLs.
-- const SIBLING_REV = { 'ov-mark.js': '01cf79e36af5', 'ov-glyphs.js': '97224763226b', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '7ef00feeb07c', 'ov-fx.js': 'bb5afd571c72', 'ov-labels.js': 'd40f7ca14bc4', 'ov-forms.js': '2eb9e2604c3e', 'ov-forms.css': '06208fe89eac', 'ov-share.js': 'd419508a797e', 'ov-share.css': 'd609a71f611a', 'search.js': 'bb502f67c4d5', 'dock.js': '00e20b4b2895', 'presence.js': '5f195d41f8c5', 'panels.js': 'd90bfe987d81', 'surfaces.js': 'e72f5a304f2d', 'notification-ui.js': 'b64ea0c39d34', 'history.js': '1e6598f78ba4', 'appbar.js': '4e34b7d60372', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': '8e5047755456', 'sso-client.js': '52439265b980', 'overlay.js': '336b30eae325', 'ov-home.js': 'db1d79b7b932', 'nav-slots.js': '1db19edaeac4', 'ov-wallet.js': '5c4d7fdb448d', 'ov-wallet.css': 'e46f653fa59e' };
++ const SIBLING_REV = { 'ov-mark.js': '01cf79e36af5', 'ov-glyphs.js': '97224763226b', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '7ef00feeb07c', 'ov-fx.js': 'bb5afd571c72', 'ov-labels.js': 'd40f7ca14bc4', 'ov-forms.js': '2eb9e2604c3e', 'ov-forms.css': '06208fe89eac', 'ov-share.js': 'd419508a797e', 'ov-share.css': 'd609a71f611a', 'search.js': '9c0b9621ca9f', 'dock.js': '00e20b4b2895', 'presence.js': '5f195d41f8c5', 'panels.js': 'd90bfe987d81', 'surfaces.js': 'e72f5a304f2d', 'notification-ui.js': 'b64ea0c39d34', 'history.js': '1e6598f78ba4', 'appbar.js': '4e34b7d60372', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': '8e5047755456', 'sso-client.js': '52439265b980', 'overlay.js': '336b30eae325', 'ov-home.js': '135d730d03fd', 'nav-slots.js': '1db19edaeac4', 'ov-wallet.js': '5c4d7fdb448d', 'ov-wallet.css': 'e46f653fa59e' };
// The navbar's icons are filled paths (no Font Awesome CSS needed): the ones above ship here, the rest
// (nav-icons.js) load after first paint when a link, chip or menu row names one, into an empty <svg> of their
// size. Anything unknown, or nav-icons.js failing, falls back to <i class="fa-solid">.
@@@ -927,7 +914,7 @@
: seg('b-core', brand.core, to.core, brand.name) + join + seg('b-tld', brand.tldText, to.tld, brand.name);
const mark = brand.icon
? `<i class="fa-solid ${escapeAttr(brand.icon)}"></i>`
-- : `<span class="ov-mark" data-size="34" data-wide data-variant="${escapeAttr(brand.variant)}"></span>`;
++ : `<span class="ov-mark" data-size="42" data-wide data-variant="${escapeAttr(brand.variant)}"></span>`; // 42 since 4.188.0 (was 34: beside a short name it read as smaller than the word)
return `<div class="openvibe-navbar-brand${brand.subText ? ' has-sub' : ''}${brand.joined ? ' joined' : ''}">
${_config.switcher === false
? `<a class="flame" href="${escapeAttr(brand.href)}" aria-label="${escapeAttr(brand.name)} home">${mark}</a>`
@@@ -1073,6 +1060,7 @@
/** The chips folded into one (shown instead of them on a phone): each chip's icon and short value. */
// New messages are counted on the bell (4.187.0; the owner, 2026-11-10: "instead of having messaging and
// notifications, just have the notifications … it'll show you new messages as well"): there is no Messages button.
++ let _accountEl = null; // the account's block, built at render, hosted by the menu's You tab
let _msgCount = 0;
// New messages, for the bell's count: Chat's GET /api/dm/unread, at most once a minute (README, "The navbar").
let _unreadAt = 0;
@@@ -2198,26 +2186,30 @@
</div>
`;
dropdown.addEventListener('click', (e) => { const t = e.target.closest && e.target.closest('[data-use]'); if (t) countUse(t.getAttribute('data-use')); });
++ // 4.188.0: not a popup under the picture. It rests in the bar, out of sight (so a site that looks for its
++ // sign-out button or its own item still finds it), and the menu's You tab hosts it while the menu is open
++ // (openSearch's account(); onClose brings it back).
++ dropdown.classList.add('ovnav-account');
nav.appendChild(dropdown);
++ _accountEl = dropdown;
bindMenuItems(dropdown, before.concat(after, sectionItems(menuCfg.sections)));
dropdown.addEventListener('click', (e) => { if (!e.target.closest || !e.target.closest('[data-ov-dock]')) return; dropdown.classList.remove('open'); openDock(); });
bindChips(dropdown);
if (typeof _config.onNavigate === 'function') dropdown.addEventListener('click', (e) => { const a = e.target.closest && e.target.closest('a[href]'); if (!a || e.defaultPrevented || a.target === '_blank' || e.metaKey || e.ctrlKey) return; const path = sitePath(a); if (path != null) { if (_config.onNavigate(path, e) === false) e.preventDefault(); dropdown.classList.remove('open'); } });
bindDisplayRows(dropdown);
bindLook(dropdown);
-- regPanel(dropdown, 'user-menu', () => dropdown.classList.remove('open'), nav.querySelector('#openvibe-avatar-btn'));
-- // Avatar click toggles dropdown (the selection for Ask Vibe is read on pointerdown: the press can clear it)
++ // The picture opens the menu on You, where the account's things are (the selection for Ask Vibe is read on
++ // pointerdown: the press can clear it).
const avatarBtn = nav.querySelector('#openvibe-avatar-btn'); let sel = null;
avatarBtn.addEventListener('pointerdown', () => { sel = selectedText(); });
avatarBtn.addEventListener('click', () => {
-- if (dropdown.classList.contains('open') && !dropdown.classList.contains('ov-pop-out')) { unpop(dropdown, () => { if (!dropdown.classList.contains('ov-pop')) dropdown.classList.remove('open'); }); return; }
-- dropdown.classList.add('open'); popFrom(dropdown, avatarBtn);
const ask = dropdown.querySelector('[data-ov-ask]'); if (ask) ask.href = askVibeHref(sel ?? selectedText()); sel = null;
-- if (dropdown.classList.contains('open')) { loadWallet(dropdown); dropdown.scrollTop = 0; loadOvIcons(dropdown); }
++ if (menuOpen() && root.OpenVibeSearch) { root.OpenVibeSearch.close(); return; }
++ const orbs = nav.querySelectorAll ? [...nav.querySelectorAll('#openvibe-burger.ovnav-orb, #openvibe-search-btn')] : [];
++ openSearch('', 'you', orbs.find((o) => o.getBoundingClientRect && o.getBoundingClientRect().width > 0) || null);
});
-- // panels.js closes this on an outside pointerdown, including another control in the navbar.
dropdown.querySelectorAll('[data-account-id]').forEach(el => {
el.addEventListener('click', () => {
@@@ -2317,7 +2309,9 @@
// Display (theme, text size, motion) at the menu's foot, drawn by the same controls the drawer had.
display: (el) => { const box = document.createElement('div'); box.className = 'ovnav-display ovs-display'; el.appendChild(box); bindDisplayControls(box); },
look: bindLook,
-- onClose: () => { const b = _navEl && _navEl.querySelector('#openvibe-search-btn'); if (b) b.setAttribute('aria-expanded', 'false'); const g = _navEl && _navEl.querySelector('#openvibe-burger'); if (g) g.setAttribute('aria-expanded', 'false'); } });
++ // The account's own block (who you are, your accounts, sign out, this site's items): the You tab hosts it.
++ account: (host) => { if (!_accountEl || !host) return false; host.appendChild(_accountEl); loadWallet(_accountEl); loadOvIcons(_accountEl); return true; },
++ onClose: () => { if (_accountEl && _navEl && _accountEl.parentNode !== _navEl) _navEl.appendChild(_accountEl); const b = _navEl && _navEl.querySelector('#openvibe-search-btn'); if (b) b.setAttribute('aria-expanded', 'false'); const g = _navEl && _navEl.querySelector('#openvibe-burger'); if (g) g.setAttribute('aria-expanded', 'false'); } });
const b = fromEl || (_navEl && _navEl.querySelector('#openvibe-search-btn')); if (b) b.setAttribute('aria-expanded', 'true');
};
if (root.OpenVibeSearch) return go();
ddiff --git a/ov-home.js b/ov-home.js
iindex 9a0c310..1d52465 100644
---- a/ov-home.js
++++ b/ov-home.js
@@@ -114,7 +114,7 @@ html[data-ov-motion=reduced] .ovh.is-edit .ovh-grid:not(.ovh-recent) .ovh-ic { a
.ovh-calm { transition: opacity .2s; }
.ovh.is-edit .ovh-calm { opacity: .38; pointer-events: none; }
.ovh-headtools { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; }
--.ovh-sec .ovh-allbtn { all: unset; box-sizing: border-box; display: inline-flex; align-items: center; gap: 6px; min-height: 36px; padding: 0 11px 0 10px; border-radius: 999px; cursor: pointer; font-size: 13px; font-weight: 700;
++.ovh-sec .ovh-allbtn { all: unset; box-sizing: border-box; text-transform: none; letter-spacing: 0; display: inline-flex; align-items: center; gap: 6px; min-height: 36px; padding: 0 11px 0 10px; border-radius: 999px; cursor: pointer; font-size: 13px; font-weight: 700;
color: var(--text-secondary, #a8b3c4); background: color-mix(in srgb, var(--text-primary, #e8ecf3) 7%, transparent); transition: color .15s, background-color .15s, transform .12s; }
.ovh-sec .ovh-allbtn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.ovh-sec .ovh-allbtn b { font-weight: 800; font-variant-numeric: tabular-nums; color: var(--text-primary, #e8ecf3); }
@@@ -409,6 +409,16 @@ html[data-ov-motion=reduced] .ovh-card { animation-name: ovh-fade; animation-dur
/** You: your picture (online), your name, and Display and Settings beside it. Made once per opening, so an open
* Look panel survives the repaints that come as history and the catalogue arrive. */
++ // The account's block (navbar.js builds it: who you are, add account, settings, sign out, your other accounts,
++ // this site's own items): the You tab is where it lives since 4.188.0. Without a bar to give it, the short bar below.
++ let accountBox = null;
++ function account() {
++ if (typeof o.account !== 'function') return null;
++ accountBox = accountBox || h('div', 'ovh-account');
++ let ok = false;
++ try { ok = o.account(accountBox) !== false; } catch { ok = false; }
++ return ok ? accountBox : null;
++ }
function me() {
if (meBox) return meBox;
const u = o.user || {};
@@@ -496,7 +506,9 @@ html[data-ov-motion=reduced] .ovh-card { animation-name: ovh-fade; animation-dur
// nothing above the grid may come or go (the macOS run of #394 dropped onto the wrong place when the hint
// pushed the grid down mid-drag). The cards above stay, quiet and inert; the hint goes under the grid.
const calm = (x) => { x.classList.add('ovh-calm'); x.inert = state.edit; return x; };
-- if (signedIn(o)) el.append(calm(me()));
++ // The account's block wherever the bar has a person (a site with its own session may have no token for
++ // the network's reads yet: sign out and the rest must still be there); else the short bar.
++ { const a = account(); if (a) el.append(calm(a)); else if (signedIn(o)) el.append(calm(me())); }
if (signedIn(o)) { actorBox = actorBox || h('div'); el.append(actorBox); }
const head = sec(L.arranged || signedIn(o) ? 'Your home' : 'Popular on OpenVibe');
const edit = h('button', state.edit ? 'ovh-edit is-done' : 'ovh-edit');
ddiff --git a/package-lock.json b/package-lock.json
iindex a362f10..43dedd7 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-shared",
-- "version": "4.187.0",
++ "version": "4.188.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-shared",
-- "version": "4.187.0",
++ "version": "4.188.0",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0",
ddiff --git a/package.json b/package.json
iindex b8f2a6c..9e59fef 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-shared",
-- "version": "4.187.0",
++ "version": "4.188.0",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"bin": {
ddiff --git a/search.js b/search.js
iindex e299c37..1811096 100644
---- a/search.js
++++ b/search.js
@@@ -1236,7 +1236,7 @@
const back = mine ? mine.items.filter((x) => { if (seenB.has(x.url)) return false; seenB.add(x.url); return true; }).slice(0, 8).map(backRow) : [];
const usage = mine ? mine.services.filter((x) => x && x.count > 0).map((x) => ({ id: x.service === 'media' ? 'media-hub' : x.service, count: Number(x.count) || 0 })) : [];
return { apps, yours, tools, toolCount: catalog ? catalog.tools.length : 0, recent: recentIds, back, usage, groups: f && Array.isArray(f.groups) ? f.groups : [],
-- token: ctx.token, apiBase: ctx.apiBase, user: ctx.user, look: ctx.look, mountIcons: (scope) => { if (typeof ctx.icons === 'function') ctx.icons(scope); }, close,
++ token: ctx.token, apiBase: ctx.apiBase, user: ctx.user, look: ctx.look, account: ctx.account, mountIcons: (scope) => { if (typeof ctx.icons === 'function') ctx.icons(scope); }, close,
pulseReady: pulseReady || (pulseReady = root.OpenVibeHome.loadPulse({ token: ctx.token, user: ctx.user })),
// The feed answered: the Feed tab is the stream (its sections would sit after an endless list); it did not: as before.
feedLive: () => { if (!feedV2) { feedV2 = true; if (el && el.classList.contains('is-open') && !input.value && tab === 'feed') render(''); } } };
ddiff --git a/test/brand-face.test.js b/test/brand-face.test.js
iindex 0e928d2..e51980b 100644
---- a/test/brand-face.test.js
++++ b/test/brand-face.test.js
@@@ -38,7 +38,7 @@ assert.strictEqual(files.path('ov-brand.woff2'), path.join(__dirname, '..', 'ov-
assert.ok(/function brandFace\(\)[\s\S]*ov-brand\.woff2/.test(cards) && /\.name\{[^}]*font-family:'OV Brand'/.test(cards), 'the share cards, with the face embedded');
// The mark sits beside the name in the wide box: level with the words, not a square with space above and below.
-- assert.match(nav, /<span class="ov-mark" data-size="34" data-wide data-variant=/, 'navbar: the wide mark');
++ assert.match(nav, /<span class="ov-mark" data-size="42" data-wide data-variant=/, 'navbar: the wide mark');
assert.match(read('footer.js'), /<span class="ov-mark" data-size="38" data-wide><\/span>/, 'footer: the wide mark');
console.log('brand face: served as a font with CORS, declared by the navbar, used for the names with a fallback; the wide mark beside them');
})().catch((e) => { console.error(e); process.exit(1); });
ddiff --git a/test/menu-home-screen-chrome.test.js b/test/menu-home-screen-chrome.test.js
iindex de66808..09e758f 100644
---- a/test/menu-home-screen-chrome.test.js
++++ b/test/menu-home-screen-chrome.test.js
@@@ -180,8 +180,10 @@ const server = http.createServer((req, res) => {
assert.ok(await until(s, `!!document.querySelector('.ovh-actor')`), 'the Actor strip is on top');
assert.match(await s.evaluate(`document.querySelector('.ovh-actor').textContent`), /Juniper.*Working.*Reading job posts/, 'it names the Actor, its state and its step');
// You on top: the name, Display (the Look panel) and Settings beside it; then your home, Most used and Jump back in.
-- assert.strictEqual(await s.evaluate(`document.querySelector('.ovh-me .ovh-who-txt b').textContent`), 'Ada', 'your name on top');
-- assert.ok(await s.evaluate(`!!document.querySelector('.ovh-me [data-ov-look-open]') && !!document.querySelector('.ovh-me a[href="https://openvibe.network/my"]')`), 'Display and Settings beside it');
++ // 4.188.0: the account's block is here (it was a popup under the picture): who you are, add account, settings, sign out.
++ assert.strictEqual(await s.evaluate(`document.querySelector('.ovh-account .openvibe-navbar-dropdown-header .name').textContent`), 'Ada', 'your name on top');
++ assert.ok(await s.evaluate(`!!document.querySelector('.ovh-account #openvibe-logout-btn') && !document.querySelector('.ovh-me')`), 'with Sign out, and no second bar with your name');
++ assert.ok(await s.evaluate(`!!document.querySelector('.ovh-account [data-ov-look-open]') && !!document.querySelector('.ovh-account a[href="https://openvibe.network/my"]')`), 'Display and Settings in it');
assert.ok(await s.evaluate(`!document.querySelector('.ovs-displayrow')`), 'Display is not at the foot any more');
assert.ok(await until(s, `document.querySelectorAll('.ovh-rail .ovh-card').length === 2`), 'Jump back in shows the places opened lately');
assert.deepStrictEqual(await s.evaluate(`[...document.querySelectorAll('.ovh-rail .ovh-card')].map((a) => a.getAttribute('href'))`), ['https://openvibe.live/ada', 'https://openvibe.pics/1']);
@@@ -191,8 +193,9 @@ const server = http.createServer((req, res) => {
// to whatever the user uses the most". Your home comes first, then what you use most, then where you were last;
// the bar with your name is short; each most-used app is a way in (its icon, a full touch target), not a legend.
assert.deepStrictEqual(await s.evaluate(`[...document.querySelectorAll('.ovh-sec > span')].map((x) => x.textContent)`), ['Your home', 'Most used', 'Jump back in'], 'your home, then most used, then where you were');
-- assert.ok(await s.evaluate(`document.querySelector('.ovh-me').getBoundingClientRect().height <= 56`), 'the bar with your name is short');
-- assert.ok(await s.evaluate(`[...document.querySelectorAll('.ovh-me .ovh-ib')].every((b) => b.getBoundingClientRect().height >= 44)`), 'and its two buttons are still full touch targets');
++ assert.ok(await s.evaluate(`document.querySelector('.ovh-account .openvibe-navbar-dropdown-header').getBoundingClientRect().height <= 96`), 'the row with your name is short');
++ assert.ok(await s.evaluate(`[...document.querySelectorAll('.ovh-account .ud-hbtn')].every((b) => { const r = b.getBoundingClientRect(); return r.height >= 44 && r.width >= 44; })`), 'and its buttons are full touch targets');
++ assert.strictEqual(await s.evaluate(`getComputedStyle(document.querySelector('.ovh-account > .openvibe-navbar-dropdown')).position`), 'static', 'a block of the page, not a popup');
assert.deepStrictEqual(await s.evaluate(`[...document.querySelectorAll('.ovh-use .ovh-key')].map((k) => [k.getAttribute('href'), k.getAttribute('aria-label'), !!k.querySelector('.ovh-ic > *')].join('|'))`),
['https://openvibe.live/|Live, opened 5 times|true', 'https://openvibe.pics/|Pics, opened 2 times|true'], 'each most-used app is a link with its icon and says how often');
assert.ok(await s.evaluate(`[...document.querySelectorAll('.ovh-use .ovh-key')].every((k) => { const r = k.getBoundingClientRect(); return r.height >= 44 && r.width >= 44; })`), 'each a full touch target');
ddiff --git a/test/navbar-ask-vibe.test.js b/test/navbar-ask-vibe.test.js
iindex 9777834..8459b4b 100644
---- a/test/navbar-ask-vibe.test.js
++++ b/test/navbar-ask-vibe.test.js
@@@ -88,7 +88,10 @@ const server = http.createServer((req, res) => {
const u = new URL(await askHref());
assert.strictEqual(u.searchParams.get('ask'), 'The rover drives to the door.');
assert.strictEqual(u.searchParams.get('from'), null, 'an http test page is not named');
-- assert.strictEqual(await p.evaluate(`document.querySelector('.openvibe-navbar-dropdown').classList.contains('open')`), true, 'the menu opened');
++ // 4.188.0: the picture opens the menu on You, and the account's block (with Ask Vibe in it) is there.
++ const until = async (expr, ms = 6000) => { const end = Date.now() + ms; while (Date.now() < end) { if (await p.evaluate(expr)) return true; await new Promise((r) => setTimeout(r, 80)); } return false; };
++ assert.ok(await until(`!!document.querySelector('.ovs.is-open .ovh-account > .openvibe-navbar-dropdown [data-ov-ask]')`), "the picture opened the menu's You tab, with the account's things in it");
++ assert.strictEqual(await p.evaluate(`getComputedStyle(document.querySelector('.ovh-account > .openvibe-navbar-dropdown')).position`), 'static', 'as a block of that page, not a popup');
console.log('navbar-ask-vibe: browser ok');
} finally {
await browser.close();
ddiff --git a/test/navbar-brand.test.js b/test/navbar-brand.test.js
iindex 433c745..e0b99bf 100644
---- a/test/navbar-brand.test.js
++++ b/test/navbar-brand.test.js
@@@ -192,7 +192,7 @@ function load(hostname, opts = {}) {
global.addEventListener = (type, fn, opts) => winListeners.push({ type, opts });
navbar.init({ service: 'space', user: { id: 1, username: 'x' } });
const first = regs.slice(), firstDisplay = winListeners.filter((l) => l.type === 'ov:display');
-- assert.deepStrictEqual(first.map((r) => r.id).sort(), ['nav-drawer', 'user-menu'], 'the drawer and the account menu are registered');
++ assert.deepStrictEqual(first.map((r) => r.id).sort(), ['nav-drawer'], 'the drawer is registered (the account\'s things are in the menu since 4.188.0: no popup of their own)');
assert.ok(first.every((r) => r.signal && !r.signal.aborted), 'each registration carries the render\'s signal');
assert.ok(firstDisplay.length === 1 && firstDisplay[0].opts && firstDisplay[0].opts.signal, 'the display rows\' window listener carries it too');
navbar.init({ service: 'space', user: { id: 1, username: 'x' } });
@@@ -211,7 +211,7 @@ function load(hostname, opts = {}) {
global.OpenVibePanels = { register: (o) => { regs.push(o); } };
loaders[loaders.length - 1].onload();
delete global.OpenVibePanels;
-- assert.deepStrictEqual(regs.map((r) => r.id).sort(), ['nav-drawer', 'user-menu'], 'only the live render\'s panels are registered');
++ assert.deepStrictEqual(regs.map((r) => r.id).sort(), ['nav-drawer'], 'only the live render\'s panels are registered');
assert.ok(regs.every((r) => !r.signal.aborted));
}
// ── balance chips fold into one on a phone (owner, 2026-10-27): the folded chip, its short values, and setChip ──