openvibers/openvibe.shared Commits

4.195.0: the You tab's own row, and the menu grows out of the pressed button (#424)

OP OpenVibers committed 3 days ago · aa057bc499 · 11 files, +87 −36

* The You tab: one slim row of your own, and the old account block is not set in the menu

The owner, 2026-11-10, with the block crossed out: "When I said integrate the user drop down into this menu, I didn't mean like this … get rid of this thing … We didn't really need the actual [block] here, other than like adding your account … making a new design for that." The row: your face and name, add or switch account, Display, Settings, Sign out. Under it only what has no other home in the menu: this site's own items, your status, Admin for an admin. The bar hands the menu three small things (the add-account address, sign out, those items) instead of its dropdown.

* The menu grows out of the button that was pressed

On a computer it unfolded from its left corner, though its button is at the right since 4.193.0. Its origin is now the pressed button's centre, wherever the sheet was clamped to, on the dropdown's own curve. The owner, 2026-11-10: "go … to the proper element … what you clicked on, and be more aware … of positioning".

* 4.195.0: the You tab's own row, and the menu grows out of the pressed button

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex aeacf00..ea69253 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -1,5 +1,25 @@
# Changelog
++## 4.195.0 — 2026-11-10
++
++**The You tab gets a row of its own, and the menu grows out of the button that was pressed.** From the owner's notes of
++2026-11-10.
++
++- **The You tab: one slim row, and the old account block is gone from the menu.** The owner, with the block crossed
++ out: "When I said integrate the user drop down into this menu, I didn't mean like this … get rid of this thing … We
++ didn't really need the actual [block] here, other than like adding your account … making a new design for that."
++ The row: your face and name, add or switch account, Display, Settings, Sign out (each a full touch target, with a
++ tooltip). Under it, small, only what has no other home in the menu: this site's own items, your status, and Admin
++ for an admin. History, Ask Vibe, Work, the Dock and the wallet are not repeated: they are tiles, the search box, the
++ row of places and the balances pill. `navbar.js` hands the menu three small things (`me: { add, out, own }`) in
++ place of its dropdown; `ov-home.js` draws the row.
++- **The menu grows out of the pressed button** ("go … to the proper element … what you clicked on, and be more aware …
++ of positioning"). On a computer it unfolded from its left corner, though its button has been at the right since
++ 4.193.0: its origin is now the pressed button's centre, wherever the sheet was clamped to, on the dropdown's own
++ curve.
++- Tests: `menu-home-screen-chrome` and `navbar-ask-vibe` read the row (the old block and its grid are not in the
++ menu); `menu-places-chrome` checks the sheet's origin against the pressed button. `navbar.js` is under its budget.
++
## 4.194.0 — 2026-11-10
**One row of places in the menu, a wallet that is there at once, and a Sign in that says so.** From the owner's notes
ddiff --git a/README.md b/README.md
iindex 3cbb61b..b5c4f84 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.194.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
++**Status:** alpha, v4.195.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.
ddiff --git a/STATUS.json b/STATUS.json
iindex b5a43d0..2caa023 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -4,7 +4,7 @@
"code": true,
"wave": "W2",
"package": "openvibe-shared",
-- "version": "4.194.0",
++ "version": "4.195.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 1b737b3..f96f1d0 100644
---- a/navbar.js
++++ b/navbar.js
@@@ -11,6 +11,7 @@
history: null, silentLogin: null, fedcm: 'optional', fedcmLogin: null, // README.md, "The navbar"
};
const _runtimeMenu = { before: [], after: [] };
++ let _own = []; // the You tab's small items
let _runtimeLinks = null;
let _navEl = null;
// A site's own buttons (lead before the brand, actions before the bell): nav-slots.js.
@@@ -565,7 +566,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': '3174ce995380', 'dock.js': '1e2c189c1b1e', 'presence.js': '5f195d41f8c5', 'panels.js': 'd90bfe987d81', 'surfaces.js': 'e72f5a304f2d', 'notification-ui.js': 'b64ea0c39d34', 'history.js': 'f9fb9e096c65', 'appbar.js': 'dfc8e4da0869', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': '8e5047755456', 'sso-client.js': '52439265b980', 'overlay.js': '336b30eae325', 'ov-home.js': 'c38f5859fff6', 'nav-slots.js': '1db19edaeac4', 'ov-wallet.js': 'b750eacee14b', 'ov-wallet.css': 'da51db3269db', 'purse.js': '749120bfd24b', 'ov-drop.js': '0132897495f0', 'ov-tip.js': '1d918e7de69d' };
++ 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': '810a6665e79c', 'dock.js': '1e2c189c1b1e', 'presence.js': '5f195d41f8c5', 'panels.js': 'd90bfe987d81', 'surfaces.js': 'e72f5a304f2d', 'notification-ui.js': 'b64ea0c39d34', 'history.js': 'f9fb9e096c65', 'appbar.js': 'dfc8e4da0869', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': '8e5047755456', 'sso-client.js': '52439265b980', 'overlay.js': '336b30eae325', 'ov-home.js': '5fdb2b84221d', 'nav-slots.js': '1db19edaeac4', 'ov-wallet.js': 'b750eacee14b', 'ov-wallet.css': 'da51db3269db', 'purse.js': '749120bfd24b', 'ov-drop.js': '0132897495f0', 'ov-tip.js': '1d918e7de69d' };
// 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">.
@@@ -2153,9 +2154,6 @@
const barWallet = !!_config.wallet || (Array.isArray(_config.chips) && _config.chips.some(c => c && (c.id === 'coins' || c.id === 'vibes')));
const admin = u.role === 'admin' || !!(u.capabilities && (u.capabilities.is_owner || u.capabilities.admin_panel));
-- // The menu (4.23.0, the owner 2026-10-28: "more compactness… data-driven"): you, with + and the gear; your
-- // places as icons in the order you use them (eight, then More); the site's sections; one row for light/dark,
-- // text size, motion, themes and sign out. Notifications are the bell's; history and Ask Vibe, search's.
const bell = _config.notifications !== false && !!_config.token && !isAnon;
const places = before.concat(after).filter((it) => it && !it.sep).concat(menuCfg.defaults === false ? [] : [
!isAnon && u.username ? { id: 'ov-channel', label: 'My channel', short: 'Channel', drawn: navIcon('fa-tower-broadcast'), href: `https://openvibe.live/@${encodeURIComponent(u.username)}` } : null,
@@@ -2168,6 +2166,7 @@
!isAnon ? { id: 'ov-wallet', label: 'Wallet', drawn: navIcon('fa-wallet'), href: 'https://openvibe.network/my#wallet' } : null,
!isAnon && u.username ? { id: 'ov-profile', label: 'Profile', drawn: navIcon('fa-id-card'), href: `https://openvibe.network/@${encodeURIComponent(u.username)}` } : null,
].filter(Boolean));
++ _own = places.filter((p) => !/^ov-(hi|no|as|wo|do|wa|pr)/.test(p.id || ''));
const ranked = byUse(places), grid = ranked.slice(0, 8), more = ranked.slice(8);
const others = otherAccounts.filter(a => switcher || !a.is_anon);
const iconBtn = (cls, drawn, label, extra) => `<button type="button" class="ud-ib${cls}" title="${label}" aria-label="${label}"${extra || ''}><span class="icon">${drawn}</span></button>`;
@@@ -2326,8 +2325,7 @@
display: (el) => { const box = document.createElement('div'); box.className = 'ovnav-display ovs-display'; el.appendChild(box); bindDisplayControls(box); },
look: bindLook,
dock: _config.dock !== false && _config.user && !_config.user.is_anon && _config.token ? (t) => { if (root.OpenVibeSearch) root.OpenVibeSearch.close(); openDock(t); } : null,
-- // 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; },
++ me: _accountEl ? { add: _config.accounts === false ? '' : addAccountHref(), out: () => { const b = _accountEl.querySelector('#openvibe-logout-btn'); if (b) b.click(); }, own: _own } : null,
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');
};
ddiff --git a/ov-home.js b/ov-home.js
iindex f83a138..28bbc2c 100644
---- a/ov-home.js
++++ b/ov-home.js
@@@ -215,6 +215,13 @@ html[data-ov-motion=reduced] .ovh.is-edit .ovh-grid:not(.ovh-recent) .ovh-ic { a
.ovh-ib:focus-visible, .ovh-who:focus-visible, .ovh-card:focus-visible, .ovh-key:focus-visible { outline: 2px solid var(--accent, #3b82f6); outline-offset: 2px; }
@media (hover: hover) { .ovh-ib:hover { background: color-mix(in srgb, var(--text-primary, #e8ecf3) 14%, transparent); } .ovh-ib[aria-expanded="true"]:hover { background: var(--accent-strong, var(--accent, #2563eb)); } }
.ovh-me-look { margin: -2px 4px 8px; }
++.ovh-me .ovh-ib { width: 40px; }
++.ovh-out:hover { color: var(--danger, #f87171); }
++.ovh-own { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 4px 8px; }
++.ovh .ovh-own > .ovh-own-i { font-size: 13px; font-weight: 600; line-height: 1; }
++.ovh-own-i { display: inline-flex; align-items: center; min-height: 36px; padding: 0 13px; border: 0; border-radius: 999px; background: color-mix(in srgb, var(--text-primary, #e8ecf3) 6%, transparent); color: var(--text-secondary, #a8b3c4); font: 600 13px/1 inherit; text-decoration: none; cursor: pointer; transition: background-color .15s, color .15s; }
++.ovh-own-i:hover, .ovh-own-i:focus-visible { background: color-mix(in srgb, var(--accent, #3b82f6) 16%, transparent); color: var(--text-primary, #e8ecf3); }
++@media (pointer: coarse) { .ovh-own-i { min-height: 44px; } }
.ovh-rail { display: flex; gap: 8px; margin: 0 -2px; padding: 2px 6px 8px; overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; scroll-padding: 0 6px; scrollbar-width: none;
-webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent); mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent); }
.ovh-rail::-webkit-scrollbar { display: none; }
@@@ -335,6 +342,8 @@ html[data-ov-motion=reduced] .ovh-card { animation-name: ovh-fade; animation-dur
const ICON = {
pencil: [['path', { d: 'M4 20h4L19 9a2.8 2.8 0 0 0-4-4L4 16z' }], ['path', { d: 'M13.5 6.5l4 4' }]],
check: [['path', { d: 'M5 12.5l4.5 4.5L19 7.5' }]],
++ plus: [['circle', { cx: 9.5, cy: 8.5, r: 3.2 }], ['path', { d: 'M3.5 18.5c1-2.900 3.300-4.400 6-4.400 1.300 0 2.500.300 3.500 1' }], ['path', { d: 'M18 13.500v6M15 16.500h6' }]],
++ out: [['path', { d: 'M13.500 4.500h4a1.500 1.500 0 0 1 1.500 1.500v12a1.500 1.500 0 0 1-1.500 1.500h-4' }], ['path', { d: 'M9.500 8l-4 4 4 4M5.500 12h9.500' }]],
grid: [['rect', { x: 4, y: 4, width: 6.5, height: 6.5, rx: 1.6 }], ['rect', { x: 13.5, y: 4, width: 6.5, height: 6.5, rx: 1.6 }], ['rect', { x: 4, y: 13.5, width: 6.5, height: 6.5, rx: 1.6 }], ['rect', { x: 13.5, y: 13.5, width: 6.5, height: 6.5, rx: 1.6 }]],
look: [['path', { d: 'M12 3a9 9 0 1 0 0 18c1 0 1.7-.8 1.7-1.7 0-.5-.2-.9-.5-1.2-.3-.3-.4-.7-.4-1.1 0-1 .8-1.7 1.7-1.7h2.1A4.4 4.4 0 0 0 21 11c0-4.4-4-8-9-8Z' }],
['circle', { cx: 7.6, cy: 11.4, r: 1.1, fill: 'currentColor', stroke: 'none' }], ['circle', { cx: 10.4, cy: 7.4, r: 1.1, fill: 'currentColor', stroke: 'none' }], ['circle', { cx: 15, cy: 7.6, r: 1.1, fill: 'currentColor', stroke: 'none' }]],
@@@ -409,16 +418,9 @@ 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;
-- }
++ // Your row (4.195.0): the old account block is not set here any more (the owner: "get rid of this thing … we
++ // didn't really need [it] here, other than like adding your account"). The row has the five things a person needs of
++ // an account; under it, only what has no other home in the menu.
function me() {
if (meBox) return meBox;
const u = o.user || {};
@@@ -434,9 +436,25 @@ html[data-ov-motion=reduced] .ovh-card { animation-name: ovh-fade; animation-dur
const look = h('a', 'ovh-ib'); look.append(svgIcon('look')); look.href = 'https://openvibe.design/look'; look.setAttribute('data-ov-look-open', '');
look.setAttribute('aria-expanded', 'false'); look.setAttribute('aria-label', 'Display: theme, text size, motion'); look.title = 'Display';
const gear = h('a', 'ovh-ib'); gear.append(svgIcon('gear')); gear.href = 'https://openvibe.network/my'; gear.setAttribute('aria-label', 'Account and settings'); gear.title = 'Account and settings';
-- bar.append(who, look, gear);
++ bar.append(who);
++ const m = o.me || {};
++ if (m.add && safe(m.add)) { const add = h('a', 'ovh-ib'); add.append(svgIcon('plus')); add.href = m.add; add.setAttribute('aria-label', 'Add or switch account'); add.setAttribute('data-ov-tip', 'Add or switch account'); bar.append(add); }
++ bar.append(look, gear);
++ if (typeof m.out === 'function') { const out = h('button', 'ovh-ib ovh-out'); out.type = 'button'; out.append(svgIcon('out')); out.setAttribute('aria-label', 'Sign out'); out.setAttribute('data-ov-tip', 'Sign out'); out.addEventListener('click', () => { try { m.out(); } catch { /* the bar signs out */ } }); bar.append(out); }
const panel = h('div', 'ud-look ovh-me-look'); panel.hidden = true;
meBox.append(bar, panel);
++ // This site's own items, your status, Admin: small, in one line that wraps; nothing that the menu has elsewhere.
++ const own = (Array.isArray(m.own) ? m.own : []).filter((it) => it && it.label && (typeof it.onClick === 'function' || (it.href && safe(it.href)))).slice(0, 8);
++ if (own.length) {
++ const row = h('div', 'ovh-own'); row.setAttribute('role', 'group'); row.setAttribute('aria-label', 'Yours on this site');
++ own.forEach((it) => {
++ const el2 = it.href ? h('a', 'ovh-own-i') : h('button', 'ovh-own-i');
++ if (it.href) el2.href = it.href; else { el2.type = 'button'; el2.addEventListener('click', () => { try { it.onClick(); } catch { /* the item is the site's */ } }); }
++ el2.textContent = it.short || it.label;
++ row.append(el2);
++ });
++ meBox.append(row);
++ }
return meBox;
}
@@@ -510,7 +528,7 @@ html[data-ov-motion=reduced] .ovh-card { animation-name: ovh-fade; animation-dur
const calm = (x) => { x.classList.add('ovh-calm'); x.inert = state.edit; return x; };
// 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 ((o.user && !o.user.is_anon) || 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 c7d6980..01a2eec 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-shared",
-- "version": "4.194.0",
++ "version": "4.195.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-shared",
-- "version": "4.194.0",
++ "version": "4.195.0",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0",
ddiff --git a/package.json b/package.json
iindex 0e6df81..9b7bd75 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-shared",
-- "version": "4.194.0",
++ "version": "4.195.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 11a9f3e..e309a9e 100644
---- a/search.js
++++ b/search.js
@@@ -371,8 +371,11 @@
}
/* Dropped from the navbar's orb (4.25.0): under the bar, from the orb's side, the bar still showing. */
.ovs.ovs-anchored { top: var(--ovs-top, 0px); background: rgba(3, 6, 12, .45); }
--.ovs.ovs-anchored .ovs-panel { top: 8px; left: clamp(8px, var(--ovs-left, 12px), calc(100vw - min(720px, calc(100vw - 16px)) - 8px)); transform: none; transform-origin: 24px 0; max-height: calc(100dvh - var(--ovs-top, 0px) - 24px); animation: ovs-drop .22s cubic-bezier(.2,1.15,.3,1); }
--@keyframes ovs-drop { from { opacity: 0; transform: translateY(-8px) scale(.97); } to { opacity: 1; transform: none; } }
++/* It grows out of the button that was pressed (4.195.0; the owner: "go … to the proper element … what you clicked on"): its
++ origin is that button's centre, wherever the panel was clamped to (it unfolded from its left corner, though the button is at
++ the right since 4.193.0), on the dropdown's own curve. */
++.ovs.ovs-anchored .ovs-panel { --ovs-pl: clamp(8px, var(--ovs-left, 12px), calc(100vw - min(720px, calc(100vw - 16px)) - 8px)); top: 8px; left: var(--ovs-pl); transform: none; transform-origin: calc(var(--ovs-cx, 40px) - var(--ovs-pl)) 0; max-height: calc(100dvh - var(--ovs-top, 0px) - 24px); animation: ovs-drop .26s cubic-bezier(.2, 1.25, .3, 1); }
++@keyframes ovs-drop { from { opacity: 0; transform: translateY(-6px) scale(.9); } to { opacity: 1; transform: none; } }
@media (max-width: 640px) {
/* The whole screen on a phone (the owner, 11-07: "make full use of the height, not compensate for the navbar"),
opening as a circle that grows from the button that opened it. */
@@@ -1281,7 +1284,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, account: ctx.account, mountIcons: (scope) => { if (typeof ctx.icons === 'function') ctx.icons(scope); }, close,
++ token: ctx.token, apiBase: ctx.apiBase, user: ctx.user, look: ctx.look, me: ctx.me, 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/menu-home-screen-chrome.test.js b/test/menu-home-screen-chrome.test.js
iindex 09e758f..5316cff 100644
---- a/test/menu-home-screen-chrome.test.js
++++ b/test/menu-home-screen-chrome.test.js
@@@ -180,10 +180,12 @@ 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.
-- // 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');
++ // 4.195.0 (the owner, the old block crossed out: "get rid of this thing … we didn't really need [it] here, other than
++ // like adding your account"): one slim row of its own, and the old account block is not set in the menu at all.
++ 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('.ovs .ovh-account, .ovs .openvibe-navbar-dropdown, .ovs .ud-grid')`), 'the old account block and its grid of tiles are not in the menu');
++ assert.deepStrictEqual(await s.evaluate(`[...document.querySelectorAll('.ovh-me > .ovh-ib')].map((b) => b.getAttribute('aria-label'))`), ['Add or switch account', 'Display: theme, text size, motion', 'Account and settings', 'Sign out'], 'add or switch account, Display, Settings, Sign out, in that order');
++ assert.ok(await s.evaluate(`/[?&]add_account=1/.test(document.querySelector('.ovh-me > a.ovh-ib').getAttribute('href')) && !!document.querySelector('.ovh-me [data-ov-look-open]') && !!document.querySelector('.ovh-me a[href="https://openvibe.network/my"]')`), 'the add-account address is the bar\'s; Display and Settings are in the row');
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']);
@@@ -193,9 +195,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-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.ok(await s.evaluate(`document.querySelector('.ovh-me').getBoundingClientRect().height <= 64`), 'the row with your name is one short row');
++ assert.ok(await s.evaluate(`[...document.querySelectorAll('.ovh-me > .ovh-ib')].every((b) => { const r = b.getBoundingClientRect(); return r.height >= 44 && r.width >= 40; })`), 'and its buttons are full touch targets');
++ assert.ok(await s.evaluate(`(() => { const m = document.querySelector('.ovh-me').getBoundingClientRect(); return m.right <= innerWidth && [...document.querySelectorAll('.ovh-me > *')].every((x) => x.getBoundingClientRect().right <= m.right + 1); })()`), 'all of it on the screen');
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/menu-places-chrome.test.js b/test/menu-places-chrome.test.js
iindex ceafc72..823c4bc 100644
---- a/test/menu-places-chrome.test.js
++++ b/test/menu-places-chrome.test.js
@@@ -55,6 +55,16 @@ const ROW = `Array.from(document.querySelectorAll('.ovs-starttabs .ovs-tab')).ma
await page.evaluate(`document.querySelector('.ovs-starttabs [data-tab="feed"]').click()`);
assert.ok(await until(`(document.querySelector('.ovs-starttabs [aria-selected="true"]') || {}).textContent.trim().startsWith('Feed')`), `${name}: Feed is chosen in place`);
await page.evaluate('OpenVibeSearch.close()'); await wait(300);
++ // Opened from a button: the sheet grows out of that button's centre, wherever the sheet itself was placed.
++ if (width > 900) {
++ await page.evaluate(OPEN(true).replace("frame: {},", `frame: {}, anchor: { top: 52, left: ${width - 150}, cx: ${width - 130}, cy: 26 },`));
++ assert.ok(await until(`!!document.querySelector('.ovs.is-open.ovs-anchored .ovs-panel')`), 'opened from a button');
++ await wait(420);
++ const o = await page.evaluate(`(() => { const p = document.querySelector('.ovs-panel'), r = p.getBoundingClientRect(); return { origin: parseFloat(getComputedStyle(p).transformOrigin), left: r.left, right: r.right }; })()`);
++ assert.ok(Math.abs(o.origin - (width - 130 - o.left)) <= 2, `the sheet's origin is the pressed button's centre (${JSON.stringify(o)})`);
++ assert.ok(o.right <= width && o.left >= 0, `and the sheet is wholly on the screen (${JSON.stringify(o)})`);
++ await page.evaluate('OpenVibeSearch.close()'); await wait(300);
++ }
// A visitor: three places, as before.
await page.evaluate(OPEN(false));
assert.ok(await until(`document.querySelectorAll('.ovs.is-open .ovs-starttabs .ovs-tab').length === 3`), `${name}: a visitor has this site, Feed and You`);
ddiff --git a/test/navbar-ask-vibe.test.js b/test/navbar-ask-vibe.test.js
iindex 8459b4b..8deb69d 100644
---- a/test/navbar-ask-vibe.test.js
++++ b/test/navbar-ask-vibe.test.js
@@@ -88,10 +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');
-- // 4.188.0: the picture opens the menu on You, and the account's block (with Ask Vibe in it) is there.
++ // The picture opens the menu on You, with your own row in it (4.195.0: the old account block is not set 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');
++ assert.ok(await until(`!!document.querySelector('.ovs.is-open .ovh-me .ovh-out')`), "the picture opened the menu's You tab, with your row and Sign out in it");
++ assert.strictEqual(await p.evaluate(`!!document.querySelector('.ovs .openvibe-navbar-dropdown')`), false, 'and the old account block is not in the menu');
console.log('navbar-ask-vibe: browser ok');
} finally {
await browser.close();