openvibers/openvibe.shared Commits
4.195.0: the You tab's own row, and the menu grows out of the pressed button (#424)
Changes
@@@ -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@@@ -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 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.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)",@@@ -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 itemslet _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');};@@@ -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-durconst 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-durconst 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-durconst 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');@@@ -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",@@@ -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": {@@@ -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(''); } } };@@@ -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');@@@ -55,6 +55,16 @@ const ROW = `Array.from(document.querySelectorAll('.ovs-starttabs .ovs-tab')).maawait 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`);@@@ -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();
@@ -1,5 +1,25 @@
@@ -1,5 +1,25 @@
# Changelog
# 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
## 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
**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
@@ -5,7 +5,7 @@
@@ -5,7 +5,7 @@
**Docs:** https://openvibe.services · **For AI:** https://openvibe.network/llms.txt
**Docs:** https://openvibe.services · **For AI:** https://openvibe.network/llms.txt
**Install and test:** `npm ci && npm test`
**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
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.194.0",
+ "version": "4.195.0",
"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)",
@@ -11,6 +11,7 @@
@@ -11,6 +11,7 @@
history: null, silentLogin: null, fedcm: 'optional', fedcmLogin: null, // README.md, "The navbar"
history: null, silentLogin: null, fedcm: 'optional', fedcmLogin: null, // README.md, "The navbar"
};
};
const _runtimeMenu = { before: [], after: [] };
const _runtimeMenu = { before: [], after: [] };
+ let _own = []; // the You tab's small items
let _runtimeLinks = null;
let _runtimeLinks = null;
let _navEl = null;
let _navEl = null;
// A site's own buttons (lead before the brand, actions before the bell): nav-slots.js.
// A site's own buttons (lead before the brand, actions before the bell): nav-slots.js.
@@ -565,7 +566,7 @@
@@ -565,7 +566,7 @@
const NAV_ICONS_REV = '148a0eda9ca8'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)
const NAV_ICONS_REV = '148a0eda9ca8'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)
// END generated
// END generated
// The files loaded from beside this one, by content (scripts/sibling-revs.js): a release changes their URLs.
// 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
// 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
// (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">.
// size. Anything unknown, or nav-icons.js failing, falls back to <i class="fa-solid">.
@@ -2153,9 +2154,6 @@
@@ -2153,9 +2154,6 @@
const barWallet = !!_config.wallet || (Array.isArray(_config.chips) && _config.chips.some(c => c && (c.id === 'coins' || c.id === 'vibes')));
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));
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 bell = _config.notifications !== false && !!_config.token && !isAnon;
const places = before.concat(after).filter((it) => it && !it.sep).concat(menuCfg.defaults === false ? [] : [
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,
!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 @@
@@ -2168,6 +2166,7 @@
!isAnon ? { id: 'ov-wallet', label: 'Wallet', drawn: navIcon('fa-wallet'), href: 'https://openvibe.network/my#wallet' } : null,
!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,
!isAnon && u.username ? { id: 'ov-profile', label: 'Profile', drawn: navIcon('fa-id-card'), href: `https://openvibe.network/@${encodeURIComponent(u.username)}` } : null,
].filter(Boolean));
].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 ranked = byUse(places), grid = ranked.slice(0, 8), more = ranked.slice(8);
const others = otherAccounts.filter(a => switcher || !a.is_anon);
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>`;
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 @@
@@ -2326,8 +2325,7 @@
display: (el) => { const box = document.createElement('div'); box.className = 'ovnav-display ovs-display'; el.appendChild(box); bindDisplayControls(box); },
display: (el) => { const box = document.createElement('div'); box.className = 'ovnav-display ovs-display'; el.appendChild(box); bindDisplayControls(box); },
look: bindLook,
look: bindLook,
dock: _config.dock !== false && _config.user && !_config.user.is_anon && _config.token ? (t) => { if (root.OpenVibeSearch) root.OpenVibeSearch.close(); openDock(t); } : null,
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'); } });
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');
const b = fromEl || (_navEl && _navEl.querySelector('#openvibe-search-btn')); if (b) b.setAttribute('aria-expanded', 'true');
};
};
@@ -215,6 +215,13 @@ html[data-ov-motion=reduced] .ovh.is-edit .ovh-grid:not(.ovh-recent) .ovh-ic { a
@@ -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; }
.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)); } }
@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-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;
.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); }
-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; }
.ovh-rail::-webkit-scrollbar { display: none; }
@@ -335,6 +342,8 @@ html[data-ov-motion=reduced] .ovh-card { animation-name: ovh-fade; animation-dur
@@ -335,6 +342,8 @@ html[data-ov-motion=reduced] .ovh-card { animation-name: ovh-fade; animation-dur
const ICON = {
const ICON = {
pencil: [['path', { d: 'M4 20h4L19 9a2.8 2.8 0 0 0-4-4L4 16z' }], ['path', { d: 'M13.5 6.5l4 4' }]],
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' }]],
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 }]],
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' }],
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' }]],
['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
@@ -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
/** 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. */
* 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() {
function me() {
if (meBox) return meBox;
if (meBox) return meBox;
const u = o.user || {};
const u = o.user || {};
@@ -434,9 +436,25 @@ html[data-ov-motion=reduced] .ovh-card { animation-name: ovh-fade; animation-dur
@@ -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', '');
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';
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';
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;
const panel = h('div', 'ud-look ovh-me-look'); panel.hidden = true;
meBox.append(bar, panel);
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;
return meBox;
}
}
@@ -510,7 +528,7 @@ html[data-ov-motion=reduced] .ovh-card { animation-name: ovh-fade; animation-dur
@@ -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; };
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 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.
// 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); }
if (signedIn(o)) { actorBox = actorBox || h('div'); el.append(actorBox); }
const head = sec(L.arranged || signedIn(o) ? 'Your home' : 'Popular on OpenVibe');
const head = sec(L.arranged || signedIn(o) ? 'Your home' : 'Popular on OpenVibe');
const edit = h('button', state.edit ? 'ovh-edit is-done' : 'ovh-edit');
const edit = h('button', state.edit ? 'ovh-edit is-done' : 'ovh-edit');
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.194.0",
+ "version": "4.195.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.194.0",
+ "version": "4.195.0",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"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",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"main": "index.js",
"bin": {
"bin": {
@@ -371,8 +371,11 @@
@@ -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. */
/* 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 { 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) {
@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"),
/* 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. */
opening as a circle that grows from the button that opened it. */
@@ -1281,7 +1284,7 @@
@@ -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 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 })) : [];
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 : [],
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 })),
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.
// 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(''); } } };
feedLive: () => { if (!feedV2) { feedV2 = true; if (el && el.classList.contains('is-open') && !input.value && tab === 'feed') render(''); } } };
@@ -180,10 +180,12 @@ const server = http.createServer((req, res) => {
@@ -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.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');
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.
// 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 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.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']);
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) => {
@@ -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;
// 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.
// 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.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('|'))`),
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');
['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');
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');
@@ -55,6 +55,16 @@ const ROW = `Array.from(document.querySelectorAll('.ovs-starttabs .ovs-tab')).ma
@@ -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()`);
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`);
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);
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.
// A visitor: three places, as before.
await page.evaluate(OPEN(false));
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`);
assert.ok(await until(`document.querySelectorAll('.ovs.is-open .ovs-starttabs .ovs-tab').length === 3`), `${name}: a visitor has this site, Feed and You`);
@@ -88,10 +88,10 @@ const server = http.createServer((req, res) => {
@@ -88,10 +88,10 @@ const server = http.createServer((req, res) => {
const u = new URL(await askHref());
const u = new URL(await askHref());
assert.strictEqual(u.searchParams.get('ask'), 'The rover drives to the door.');
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(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; };
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');
console.log('navbar-ask-vibe: browser ok');
} finally {
} finally {
await browser.close();
await browser.close();