openvibers/openvibe.shared Commits
4.145.1: the Dock stays open when a tap on the page opens it, on a phone (#335)
Changes
@@@ -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,@@@ -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 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.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)",@@@ -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();@@@ -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">.@@@ -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": {@@@ -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');
@@ -1,5 +1,12 @@
@@ -1,5 +1,12 @@
# Changelog
# 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
## 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,
- **ov-canvas.js: an agent's live panel, drawn the same on every site** (`actor.canvas@1`, Contracts 0.340.0, ADR-059,
@@ -2,7 +2,7 @@
@@ -2,7 +2,7 @@
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
-**Status:** alpha, v4.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
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.145.0",
+ "version": "4.145.1",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"deployed": false,
"deployed": false,
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
@@ -311,6 +311,9 @@
@@ -311,6 +311,9 @@
// chat) leaves its target detached, and must not read as outside.
// chat) leaves its target detached, and must not read as outside.
document.addEventListener('click', (e) => {
document.addEventListener('click', (e) => {
if (!isOpen() || !phone()) return;
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];
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;
if (path.includes(el) || (pill && path.includes(pill)) || (e.target && e.target.isConnected === false)) return;
close();
close();
@@ -687,7 +690,7 @@
@@ -687,7 +690,7 @@
}
}
// ── The pill ─────────────────────────────────────────────
// ── The pill ─────────────────────────────────────────────
- let pill = null, readT = 0, bound = false;
+ let pill = null, readT = 0, bound = false, openedAt = 0;
function store(v) {
function store(v) {
try { if (v === undefined) return JSON.parse(localStorage.getItem('ov-dock') || 'null'); localStorage.setItem('ov-dock', JSON.stringify(v)); } catch { /* */ }
try { if (v === undefined) return JSON.parse(localStorage.getItem('ov-dock') || 'null'); localStorage.setItem('ov-dock', JSON.stringify(v)); } catch { /* */ }
return null;
return null;
@@ -844,6 +847,7 @@
@@ -844,6 +847,7 @@
if (t) { chats.draft = { text: t, title: String(title || '').trim().slice(0, 140) }; chats.open = null; open({ tab: 'chats' }); }
if (t) { chats.draft = { text: t, title: String(title || '').trim().slice(0, 140) }; chats.open = null; open({ tab: 'chats' }); }
}
}
function open(opts = {}) {
function open(opts = {}) {
+ openedAt = typeof performance !== 'undefined' && performance.now ? performance.now() : 0;
o = { ...o, ...opts };
o = { ...o, ...opts };
if (opts.doc) doc = opts.doc;
if (opts.doc) doc = opts.doc;
if (!el) build();
if (!el) build();
@@ -513,7 +513,7 @@
@@ -513,7 +513,7 @@
const NAV_ICONS_REV = 'd9a4febbbad7'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)
const NAV_ICONS_REV = 'd9a4febbbad7'; // 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': '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
// 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">.
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"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",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"main": "index.js",
"bin": {
"bin": {
@@ -319,6 +319,18 @@ async function drag(p, from, to) {
@@ -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');
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(started, [[4]], 'the conversation is started with Mia, by her id');
assert.deepStrictEqual(said, [], 'still nothing sent');
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.
// Cancel: back to the list, nothing waiting.
await share();
await share();
await until(p, "!!document.querySelector('.ovdk .ovdk-share')", 'the bar');
await until(p, "!!document.querySelector('.ovdk .ovdk-share')", 'the bar');