openvibers/openvibe.shared Commits

4.145.1: the Dock stays open when a tap on the page opens it, on a phone (#335)

OP OpenVibers committed 5 days ago · 7abe3bf0d9 · 7 files, +28 −5

The tap that opened the sheet (OpenVibe Chat in the share sheet, Ask your agent, a page button) was still bubbling when the
sheet opened and read as a tap outside, so the sheet closed at once. Found in the production proof of a party's Invite.

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 321fc49..500abd1 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -1,5 +1,12 @@
# Changelog
++## 4.145.1 — 2026-11-08
++
++- **The Dock stays open when a tap on the page opens it, on a phone.** The phone sheet closes on a tap outside it, and
++ the tap that opened it (the share sheet's OpenVibe Chat, Ask your agent, any page button calling the Dock) was still
++ on its way up when the sheet opened, so it read as outside and the sheet closed at once. A click made before the
++ sheet opened is never "a tap outside" now. Found in the production proof of a party's Invite → OpenVibe Chat.
++
## 4.145.0 — 2026-11-08
- **ov-canvas.js: an agent's live panel, drawn the same on every site** (`actor.canvas@1`, Contracts 0.340.0, ADR-059,
ddiff --git a/README.md b/README.md
iindex 9970c5b..c2dc606 100644
---- a/README.md
++++ b/README.md
@@@ -2,7 +2,7 @@
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
--**Status:** alpha, v4.145.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
++**Status:** alpha, v4.145.1 (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 4ad18b0..066a552 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -4,7 +4,7 @@
"code": true,
"wave": "W2",
"package": "openvibe-shared",
-- "version": "4.145.0",
++ "version": "4.145.1",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"deployed": false,
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
ddiff --git a/dock.js b/dock.js
iindex 30ca610..f7a6070 100644
---- a/dock.js
++++ b/dock.js
@@@ -311,6 +311,9 @@
// chat) leaves its target detached, and must not read as outside.
document.addEventListener('click', (e) => {
if (!isOpen() || !phone()) return;
++ // The tap that opened the sheet (a page's button, the share sheet's OpenVibe Chat, Ask your agent) is still on
++ // its way up when the sheet opens: an event made before the sheet opened is never "a tap outside" (4.145.1).
++ if (e.timeStamp && e.timeStamp <= openedAt) return;
const path = typeof e.composedPath === 'function' ? e.composedPath() : [e.target];
if (path.includes(el) || (pill && path.includes(pill)) || (e.target && e.target.isConnected === false)) return;
close();
@@@ -687,7 +690,7 @@
}
// ── The pill ─────────────────────────────────────────────
-- let pill = null, readT = 0, bound = false;
++ let pill = null, readT = 0, bound = false, openedAt = 0;
function store(v) {
try { if (v === undefined) return JSON.parse(localStorage.getItem('ov-dock') || 'null'); localStorage.setItem('ov-dock', JSON.stringify(v)); } catch { /* */ }
return null;
@@@ -844,6 +847,7 @@
if (t) { chats.draft = { text: t, title: String(title || '').trim().slice(0, 140) }; chats.open = null; open({ tab: 'chats' }); }
}
function open(opts = {}) {
++ openedAt = typeof performance !== 'undefined' && performance.now ? performance.now() : 0;
o = { ...o, ...opts };
if (opts.doc) doc = opts.doc;
if (!el) build();
ddiff --git a/navbar.js b/navbar.js
iindex 4f4e3ff..4ade83b 100644
---- a/navbar.js
++++ b/navbar.js
@@@ -513,7 +513,7 @@
const NAV_ICONS_REV = 'd9a4febbbad7'; // 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': '1e91c9c0f347', 'ov-glyphs.js': '1a0d8ce6909f', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2a6c53a7b176', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': '068ce1b1c4d5', 'ov-forms.css': '98cf0f28bb68', 'ov-share.js': 'fa4ded2928f4', 'ov-share.css': 'cbd18e6ce991', 'search.js': '295b2fffb4e1', 'dock.js': '1db802150958', 'presence.js': '1b675c37d888', 'panels.js': '7d8267ca8035', 'notification-ui.js': '3ceb5266e7b8', 'history.js': '1e6598f78ba4', 'site-switcher.js': '292bea111aa8', 'appbar.js': '1edf7532f228', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': 'ba3981246bc0', 'sso-client.js': '52439265b980' };
++ const SIBLING_REV = { 'ov-mark.js': '1e91c9c0f347', 'ov-glyphs.js': '1a0d8ce6909f', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2a6c53a7b176', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': '068ce1b1c4d5', 'ov-forms.css': '98cf0f28bb68', 'ov-share.js': 'fa4ded2928f4', 'ov-share.css': 'cbd18e6ce991', 'search.js': '295b2fffb4e1', 'dock.js': 'fc4a350badec', 'presence.js': '1b675c37d888', 'panels.js': '7d8267ca8035', 'notification-ui.js': '3ceb5266e7b8', 'history.js': '1e6598f78ba4', 'site-switcher.js': '292bea111aa8', 'appbar.js': '1edf7532f228', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': 'ba3981246bc0', 'sso-client.js': '52439265b980' };
// 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">.
ddiff --git a/package.json b/package.json
iindex 2311dc9..45b93ea 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-shared",
-- "version": "4.145.0",
++ "version": "4.145.1",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"bin": {
ddiff --git a/test/dock-chrome.test.js b/test/dock-chrome.test.js
iindex 117fd4c..7c90aa7 100644
---- a/test/dock-chrome.test.js
++++ b/test/dock-chrome.test.js
@@@ -319,6 +319,18 @@ async function drag(p, from, to) {
await until(p, `document.querySelector('.ovdk-send textarea') && document.querySelector('.ovdk-send textarea').value === ${JSON.stringify(TEXT)}`, 'a new conversation with the message in the box');
assert.deepStrictEqual(started, [[4]], 'the conversation is started with Mia, by her id');
assert.deepStrictEqual(said, [], 'still nothing sent');
++ // On a phone, a page button that sends something opens the sheet and it stays open (the opening tap is not
++ // "a tap outside"); a later tap outside still closes it.
++ await p.evaluate("(() => { OpenVibeDock.close(); const b = document.createElement('button'); b.id = 'pagesend'; b.textContent = 'Send it'; b.addEventListener('click', () => OpenVibeDock.share({ text: 'From a page button' })); document.body.prepend(b); })()");
++ await p.evaluate("document.getElementById('pagesend').click()");
++ await until(p, "!!document.querySelector('.ovdk .ovdk-share')", 'the bar, opened by a tap on the page');
++ await wait(300);
++ assert.strictEqual(await p.evaluate("!!document.querySelector('.ovdk:not([hidden])')"), true, 'the opening tap does not close the sheet');
++ await p.evaluate("document.body.click()");
++ await wait(150);
++ assert.strictEqual(await p.evaluate("!!document.querySelector('.ovdk:not([hidden])')"), false, 'a later tap outside closes it');
++ await share();
++ await until(p, "!!document.querySelector('.ovdk .ovdk-share')", 'the bar again');
// Cancel: back to the list, nothing waiting.
await share();
await until(p, "!!document.querySelector('.ovdk .ovdk-share')", 'the bar');