openvibers/openvibe.shared Commits
The Dock's Agent panel reads the timeline again, and can open at a step (4.134.0) (#322)
Changes
@@@ -1,5 +1,10 @@# Changelog++## 4.134.0 — 2026-11-07++++- **Fixed: the Dock's Agent panel could not read the timeline.** The navbar hands the Dock the person's token as a function (it may change when they sign in again); the panel used it as a string, so every call went out as `Bearer function…` and the panel said the agent was not reachable. The token is now resolved when a call is made.++- The Dock can open at a step: `OpenVibeNavbar.openDock('agent', { task: 'tsk_…' })` marks that entry in the timeline and brings it into view, so a site can send the person to the exact task it started for them.++## 4.133.0 — 2026-11-07- The menu's network cards draw an entry's picture when the site sent one (`frame.menu-card@1`'s optional item `image`, openvibe-contracts 0.320.0): a 64×36 rounded thumbnail, `object-fit: cover`, `loading="lazy"`, `decoding="async"`, empty alt, before the title and after the site's icon. The `src` is checked again in the menu (https only); a card or entry without a picture renders exactly as before.@@@ -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.133.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed++**Status:** alpha, v4.134.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,13 +4,14 @@"code": true,"wave": "W2","package": "openvibe-shared",-- "version": "4.133.0",++ "version": "4.134.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)","contracts": "openvibe-contracts v0.312.0 (devDependency: the release manifest contract)","sdk": "openvibe-sdk v0.94.0","features": [++ "the Dock's Agent panel reads the timeline again (the token is resolved when a call is made), and the Dock can open at a step: OpenVibeNavbar.openDock('agent', { task }) marks it and brings it into view (4.134.0)","the OpenVibe Frame: navbar, footer, panels, account switcher, notification UI with realtime","the launcher beside the brand lists every site of the network, in groups; Ask Actor and the Vibes chip in the account menu","themes, design tokens, icons and ring icons",@@@ -1,11 +1,15 @@// ═══════════════════════════════════════════════════════════════// OpenVibe — the Dock's Agent panel (design 2026-11-07: one agent, one timeline behind the Dock), Shared 4.126.0//--// OpenVibeDockTimeline.mount(container, { token, base, active }) → { refresh(), destroy() }++// OpenVibeDockTimeline.mount(container, { token, base, active, focus }) → { refresh(), destroy() }//++// `token` is the person's own Network token: a string, or the function the navbar hands over (which++// resolves it when it is needed).// `base` is OpenVibe.Actor's API: GET /api/v1/agent/timeline (the person's own steps, newest first, with the cursor// and the mode and needs-you filters), POST /api/v1/agent/approvals/:product/:id/decide (an approval is answered in// the mode that owns it) and POST /api/v1/agent/kill-switch (stop what is open and pause the agent).++// `focus` is the id of the step to point at (the task id a site opened the Dock with): the entry that++// carries it is marked and brought into view.//// The panel is what the person reads:// Now one card for what the agent is doing this moment: the mode's word, the step in plain words, how long,@@@ -90,6 +94,7 @@'.ovdt-chips{display:flex;gap:8px;overflow-x:auto;margin:4px -12px 0;padding:6px 12px 10px;scrollbar-width:none;-webkit-overflow-scrolling:touch}','.ovdt-chips::-webkit-scrollbar{display:none}','.ovdt-fold{width:100%;text-align:left;min-height:44px;margin:0 0 8px;padding:0 14px;border-radius:12px;border:1px dashed var(--border,rgba(255,255,255,.16));background:transparent;color:var(--text-secondary,#a8b3c4);font:inherit;font-weight:650;cursor:pointer}',++ '.ovdt-card.ovdt-focus{border-color:color-mix(in srgb,var(--accent,#3b82f6) 55%,transparent)}','.ovdt-empty{color:var(--text-muted,#93a0b4);padding:4px 2px 6px}','.ovdt-card.ovdt-empty{padding:12px 14px}','.ovdt-link{color:color-mix(in srgb,var(--accent,#3b82f6) 72%,var(--text-primary,#ececf1));font-weight:650;text-decoration:none}',@@@ -103,12 +108,20 @@const doc = container.ownerDocument || document;styles(doc);const base = String(opts.base || 'https://openvibe.actor').replace(/\/+$/, '');-- const token = opts.token;++ const rawToken = opts.token;const active = typeof opts.active === 'function' ? opts.active : () => true;++ const focus = typeof opts.focus === 'function' ? opts.focus : () => opts.focus || null;const filters = new Set();let timer = null;let destroyed = false;++ // The navbar hands the Dock a function (the token may change when the person signs in again),++ // so it is resolved when a call is made, never read once at mount.++ async function bearer() {++ const t = typeof rawToken === 'function' ? await rawToken() : rawToken;++ return t ? 'Bearer ' + t : null;++ }++const wrap = el(doc, 'div', 'ovdt');const nowHead = el(doc, 'h3', null, 'Now');const nowBox = el(doc, 'div', 'ovdt-now');@@@ -120,7 +133,8 @@container.appendChild(wrap);async function api(path, options = {}) {-- const res = await fetch(base + path, Object.assign({ headers: { Accept: 'application/json', Authorization: 'Bearer ' + token } }, options));++ const auth = await bearer();++ const res = await fetch(base + path, Object.assign({ headers: { Accept: 'application/json', ...(auth ? { Authorization: auth } : {}) } }, options));const body = await res.json().catch(() => ({}));if (!res.ok) throw new Error((body && (body.detail || body.title)) || ('HTTP ' + res.status));return body;@@@ -165,7 +179,8 @@stop.type = 'button';stop.addEventListener('click', async () => {stop.disabled = true;-- try { await api('/api/v1/agent/kill-switch', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token }, body: JSON.stringify({ action: 'stop' }) }); } catch (err) { /* the panel refreshes either way */ }++ const auth = await bearer();++ try { await api('/api/v1/agent/kill-switch', { method: 'POST', headers: { 'Content-Type': 'application/json', ...(auth ? { Authorization: auth } : {}) }, body: JSON.stringify({ action: 'stop' }) }); } catch (err) { /* the panel refreshes either way */ }refresh();});buttons.appendChild(stop);@@@ -191,10 +206,11 @@b.addEventListener('click', async () => {b.disabled = true;const approval = item.approval || {};++ const auth = await bearer();try {await api('/api/v1/agent/approvals/' + encodeURIComponent(approval.product || item.product) + '/' + encodeURIComponent(approval.id) + '/decide', {method: 'POST',-- headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token },++ headers: { 'Content-Type': 'application/json', ...(auth ? { Authorization: auth } : {}) },body: JSON.stringify({ decision }),});} catch (err) { /* the panel refreshes either way */ }@@@ -229,9 +245,26 @@a.target = '_blank';card.appendChild(a);}++ // The step a site opened the Dock at (its task id): it is marked, so the person sees where they landed.++ const wanted = focusId();++ if (wanted && (entry.id === wanted || entry.ref === wanted)) card.classList.add('ovdt-focus');return card;}++ /** The step to point at: the id (a task id) a site opened the Dock with, if it is still there. */++ function focusId() {++ try { return String(focus() || '').trim() || null; } catch (err) { return null; }++ }++++ /** Bring the focused step into view (the Dock's panel is short; the step may be below the fold). */++ function scrollToFocus() {++ const wanted = focusId();++ if (!wanted) return;++ const node = list.querySelector('.ovdt-focus');++ if (!node || !node.scrollIntoView) return;++ try { node.scrollIntoView({ block: 'nearest' }); } catch (err) { node.scrollIntoView(); }++ }++function renderList(entries) {list.textContent = '';if (!entries.length) {@@@ -322,6 +355,7 @@renderNeeds(data.needs_you || []);renderChips(data.counts || {});renderList(data.entries || []);++ scrollToFocus();writeUrl();}@@@ -454,7 +454,8 @@return;}if (id === 'chats') { chats.panel = panel; mounts.chats = mounts.chats || { refresh: null }; return state().chat && chats.open !== state().chat ? openChat(state().chat, false) : chats.open ? openChat(chats.open, false) : chatList(); }-- if (mounts[id]) { if (mounts[id].refresh) mounts[id].refresh(); return; }++ // The Agent panel reads the step.++ if (mounts[id]) { if (mounts[id].refresh) mounts[id].refresh(); if (mounts[id + 'Timeline']) mounts[id + 'Timeline'].refresh(); return; }mounts[id] = { refresh: null };panel.innerHTML = '<div class="ovdk-wait"><span></span></div>';if (id === 'agent') {@@@ -465,7 +466,7 @@// The Agent panel: Now, Needs you and the one timeline (design 2026-11-07). It is first in the tab, so on a// phone Now and Needs you are on screen before anything else.load('dock-timeline.js', () => !!(root.OpenVibeDockTimeline && root.OpenVibeDockTimeline.mount)).then((on) => {-- if (on && !mounts[id + 'Timeline'] && panel.isConnected) mounts[id + 'Timeline'] = root.OpenVibeDockTimeline.mount(panel.querySelector('.ovdk-timeline'), { token: o.token, base: actorBase(), active: () => isOpen() && tab === 'agent' });++ if (on && !mounts[id + 'Timeline'] && panel.isConnected) mounts[id + 'Timeline'] = root.OpenVibeDockTimeline.mount(panel.querySelector('.ovdk-timeline'), { token: o.token, base: actorBase(), active: () => isOpen() && tab === 'agent', focus: () => o.task || null });});load('dock-agent.js', () => !!(root.OpenVibeDockAgent && root.OpenVibeDockAgent.mount)).then((on) => {if (on && !mounts[id + 'View'] && panel.isConnected) mounts[id + 'View'] = root.OpenVibeDockAgent.mount(panel.firstChild, { token: o.token, base: agentBase(), active: () => isOpen() && tab === 'agent' });@@@ -1235,8 +1235,11 @@if (document.getElementById('ov-presence-loader')) return;const sc = document.createElement('script'); sc.id = 'ov-presence-loader'; sc.src = sibling('presence.js'); sc.async = true; sc.onload = go; document.head.appendChild(sc);}-- /** Open the Dock on a tab: 'agent' | 'activity'. */-- function openDock(tab) { loadDock(() => { root.OpenVibeDock.mount(dockOpts()); root.OpenVibeDock.open({ tab }); }); }++ /**++ * Open the Dock on a tab: 'agent' | 'activity'. opts may name the step to point at (opts.task): the++ * Agent panel marks that entry and brings it into view, so a site can open the Dock at its own task.++ */++ function openDock(tab, opts) { loadDock(() => { root.OpenVibeDock.mount(dockOpts()); root.OpenVibeDock.open(Object.assign({ tab }, opts || {})); }); }/** Custom sections for the user menu: [{ label, items: [{ label, icon, href, id, elId, hidden, danger, value, onClick }] }]. */function sectionsHTML(sections) {@@@ -2396,8 +2399,12 @@* settles; a page that draws such a box later may call this). Returns how many it marked. */focusScrollers() { try { return focusScrollers(); } catch { return 0; } },-- /** Open the Dock (your agents and activity), on a tab: 'agent' | 'activity'. Signed-in people only. */-- openDock(tab) { if (_config.token && _config.dock !== false) openDock(tab); },++ /**++ * Open the Dock (your agents and activity), on a tab: 'agent' | 'activity'. Signed-in people only.++ * opts may name the step to point at (opts.task): the Agent panel marks that entry and brings it++ * into view, so a site can open the Dock at its own task.++ */++ openDock(tab, opts) { if (_config.token && _config.dock !== false) openDock(tab, opts); },/** Get the bell mount point for OpenVibeNotifications. */getBellMount() {@@@ -1,6 +1,6 @@{"name": "openvibe-shared",-- "version": "4.133.0",++ "version": "4.134.0","description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site","main": "index.js","bin": {@@@ -49,5 +49,18 @@ assert.ok(dock.includes("panel.querySelector('.ovdk-timeline')"), 'the panel hasassert.ok(dock.indexOf('ovdk-timeline') < dock.indexOf('ovdk-agent'), 'the panel comes before the desk view');assert.ok(dock.includes('https://openvibe.actor'), 'the panel reads OpenVibe.Actor');++// The person's token: the navbar hands the Dock a function, so the panel resolves it when it calls.++assert.ok(/typeof rawToken === 'function' \? await rawToken\(\)/.test(panel), 'a token given as a function is resolved at the call');++assert.ok(!panel.includes("'Bearer ' + token }"), 'the panel never sends a function as a bearer');++assert.ok(panel.includes('async function bearer()'), 'the bearer is resolved once per call');++++// Opening the Dock at a task: the entry that carries it is marked and brought into view.++assert.ok(panel.includes('ovdt-focus'), 'the focused entry is marked');++assert.ok(panel.includes('scrollIntoView'), 'the focused entry is brought into view');++assert.ok(panel.includes('entry.ref === wanted'), 'the step is found by the id the site opened the Dock with');++assert.ok(dock.includes('focus: () => o.task || null'), 'the Dock tells the panel which step to point at');++const navbar = fs.readFileSync(path.join(__dirname, '..', 'navbar.js'), 'utf8');++assert.ok(/function openDock\(tab, opts\)/.test(navbar), 'the navbar opens the Dock with the step to point at');++console.log('dock timeline: now, needs you, the timeline, the folds and the filters');console.log('all passed');
@@ -1,5 +1,10 @@
@@ -1,5 +1,10 @@
# Changelog
# Changelog
+## 4.134.0 — 2026-11-07
+
+- **Fixed: the Dock's Agent panel could not read the timeline.** The navbar hands the Dock the person's token as a function (it may change when they sign in again); the panel used it as a string, so every call went out as `Bearer function…` and the panel said the agent was not reachable. The token is now resolved when a call is made.
+- The Dock can open at a step: `OpenVibeNavbar.openDock('agent', { task: 'tsk_…' })` marks that entry in the timeline and brings it into view, so a site can send the person to the exact task it started for them.
+
## 4.133.0 — 2026-11-07
## 4.133.0 — 2026-11-07
- The menu's network cards draw an entry's picture when the site sent one (`frame.menu-card@1`'s optional item `image`, openvibe-contracts 0.320.0): a 64×36 rounded thumbnail, `object-fit: cover`, `loading="lazy"`, `decoding="async"`, empty alt, before the title and after the site's icon. The `src` is checked again in the menu (https only); a card or entry without a picture renders exactly as before.
- The menu's network cards draw an entry's picture when the site sent one (`frame.menu-card@1`'s optional item `image`, openvibe-contracts 0.320.0): a 64×36 rounded thumbnail, `object-fit: cover`, `loading="lazy"`, `decoding="async"`, empty alt, before the title and after the site's icon. The `src` is checked again in the menu (https only); a card or entry without a picture renders exactly as before.
@@ -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.133.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
+**Status:** alpha, v4.134.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,13 +4,14 @@
@@ -4,13 +4,14 @@
"code": true,
"code": true,
"wave": "W2",
"wave": "W2",
"package": "openvibe-shared",
"package": "openvibe-shared",
- "version": "4.133.0",
+ "version": "4.134.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)",
"contracts": "openvibe-contracts v0.312.0 (devDependency: the release manifest contract)",
"contracts": "openvibe-contracts v0.312.0 (devDependency: the release manifest contract)",
"sdk": "openvibe-sdk v0.94.0",
"sdk": "openvibe-sdk v0.94.0",
"features": [
"features": [
+ "the Dock's Agent panel reads the timeline again (the token is resolved when a call is made), and the Dock can open at a step: OpenVibeNavbar.openDock('agent', { task }) marks it and brings it into view (4.134.0)",
"the OpenVibe Frame: navbar, footer, panels, account switcher, notification UI with realtime",
"the OpenVibe Frame: navbar, footer, panels, account switcher, notification UI with realtime",
"the launcher beside the brand lists every site of the network, in groups; Ask Actor and the Vibes chip in the account menu",
"the launcher beside the brand lists every site of the network, in groups; Ask Actor and the Vibes chip in the account menu",
"themes, design tokens, icons and ring icons",
"themes, design tokens, icons and ring icons",
@@ -1,11 +1,15 @@
@@ -1,11 +1,15 @@
// ═══════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════
// OpenVibe — the Dock's Agent panel (design 2026-11-07: one agent, one timeline behind the Dock), Shared 4.126.0
// OpenVibe — the Dock's Agent panel (design 2026-11-07: one agent, one timeline behind the Dock), Shared 4.126.0
//
//
-// OpenVibeDockTimeline.mount(container, { token, base, active }) → { refresh(), destroy() }
+// OpenVibeDockTimeline.mount(container, { token, base, active, focus }) → { refresh(), destroy() }
//
//
+// `token` is the person's own Network token: a string, or the function the navbar hands over (which
+// resolves it when it is needed).
// `base` is OpenVibe.Actor's API: GET /api/v1/agent/timeline (the person's own steps, newest first, with the cursor
// `base` is OpenVibe.Actor's API: GET /api/v1/agent/timeline (the person's own steps, newest first, with the cursor
// and the mode and needs-you filters), POST /api/v1/agent/approvals/:product/:id/decide (an approval is answered in
// and the mode and needs-you filters), POST /api/v1/agent/approvals/:product/:id/decide (an approval is answered in
// the mode that owns it) and POST /api/v1/agent/kill-switch (stop what is open and pause the agent).
// the mode that owns it) and POST /api/v1/agent/kill-switch (stop what is open and pause the agent).
+// `focus` is the id of the step to point at (the task id a site opened the Dock with): the entry that
+// carries it is marked and brought into view.
//
//
// The panel is what the person reads:
// The panel is what the person reads:
// Now one card for what the agent is doing this moment: the mode's word, the step in plain words, how long,
// Now one card for what the agent is doing this moment: the mode's word, the step in plain words, how long,
@@ -90,6 +94,7 @@
@@ -90,6 +94,7 @@
'.ovdt-chips{display:flex;gap:8px;overflow-x:auto;margin:4px -12px 0;padding:6px 12px 10px;scrollbar-width:none;-webkit-overflow-scrolling:touch}',
'.ovdt-chips{display:flex;gap:8px;overflow-x:auto;margin:4px -12px 0;padding:6px 12px 10px;scrollbar-width:none;-webkit-overflow-scrolling:touch}',
'.ovdt-chips::-webkit-scrollbar{display:none}',
'.ovdt-chips::-webkit-scrollbar{display:none}',
'.ovdt-fold{width:100%;text-align:left;min-height:44px;margin:0 0 8px;padding:0 14px;border-radius:12px;border:1px dashed var(--border,rgba(255,255,255,.16));background:transparent;color:var(--text-secondary,#a8b3c4);font:inherit;font-weight:650;cursor:pointer}',
'.ovdt-fold{width:100%;text-align:left;min-height:44px;margin:0 0 8px;padding:0 14px;border-radius:12px;border:1px dashed var(--border,rgba(255,255,255,.16));background:transparent;color:var(--text-secondary,#a8b3c4);font:inherit;font-weight:650;cursor:pointer}',
+ '.ovdt-card.ovdt-focus{border-color:color-mix(in srgb,var(--accent,#3b82f6) 55%,transparent)}',
'.ovdt-empty{color:var(--text-muted,#93a0b4);padding:4px 2px 6px}',
'.ovdt-empty{color:var(--text-muted,#93a0b4);padding:4px 2px 6px}',
'.ovdt-card.ovdt-empty{padding:12px 14px}',
'.ovdt-card.ovdt-empty{padding:12px 14px}',
'.ovdt-link{color:color-mix(in srgb,var(--accent,#3b82f6) 72%,var(--text-primary,#ececf1));font-weight:650;text-decoration:none}',
'.ovdt-link{color:color-mix(in srgb,var(--accent,#3b82f6) 72%,var(--text-primary,#ececf1));font-weight:650;text-decoration:none}',
@@ -103,12 +108,20 @@
@@ -103,12 +108,20 @@
const doc = container.ownerDocument || document;
const doc = container.ownerDocument || document;
styles(doc);
styles(doc);
const base = String(opts.base || 'https://openvibe.actor').replace(/\/+$/, '');
const base = String(opts.base || 'https://openvibe.actor').replace(/\/+$/, '');
- const token = opts.token;
+ const rawToken = opts.token;
const active = typeof opts.active === 'function' ? opts.active : () => true;
const active = typeof opts.active === 'function' ? opts.active : () => true;
+ const focus = typeof opts.focus === 'function' ? opts.focus : () => opts.focus || null;
const filters = new Set();
const filters = new Set();
let timer = null;
let timer = null;
let destroyed = false;
let destroyed = false;
+ // The navbar hands the Dock a function (the token may change when the person signs in again),
+ // so it is resolved when a call is made, never read once at mount.
+ async function bearer() {
+ const t = typeof rawToken === 'function' ? await rawToken() : rawToken;
+ return t ? 'Bearer ' + t : null;
+ }
+
const wrap = el(doc, 'div', 'ovdt');
const wrap = el(doc, 'div', 'ovdt');
const nowHead = el(doc, 'h3', null, 'Now');
const nowHead = el(doc, 'h3', null, 'Now');
const nowBox = el(doc, 'div', 'ovdt-now');
const nowBox = el(doc, 'div', 'ovdt-now');
@@ -120,7 +133,8 @@
@@ -120,7 +133,8 @@
container.appendChild(wrap);
container.appendChild(wrap);
async function api(path, options = {}) {
async function api(path, options = {}) {
- const res = await fetch(base + path, Object.assign({ headers: { Accept: 'application/json', Authorization: 'Bearer ' + token } }, options));
+ const auth = await bearer();
+ const res = await fetch(base + path, Object.assign({ headers: { Accept: 'application/json', ...(auth ? { Authorization: auth } : {}) } }, options));
const body = await res.json().catch(() => ({}));
const body = await res.json().catch(() => ({}));
if (!res.ok) throw new Error((body && (body.detail || body.title)) || ('HTTP ' + res.status));
if (!res.ok) throw new Error((body && (body.detail || body.title)) || ('HTTP ' + res.status));
return body;
return body;
@@ -165,7 +179,8 @@
@@ -165,7 +179,8 @@
stop.type = 'button';
stop.type = 'button';
stop.addEventListener('click', async () => {
stop.addEventListener('click', async () => {
stop.disabled = true;
stop.disabled = true;
- try { await api('/api/v1/agent/kill-switch', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token }, body: JSON.stringify({ action: 'stop' }) }); } catch (err) { /* the panel refreshes either way */ }
+ const auth = await bearer();
+ try { await api('/api/v1/agent/kill-switch', { method: 'POST', headers: { 'Content-Type': 'application/json', ...(auth ? { Authorization: auth } : {}) }, body: JSON.stringify({ action: 'stop' }) }); } catch (err) { /* the panel refreshes either way */ }
refresh();
refresh();
});
});
buttons.appendChild(stop);
buttons.appendChild(stop);
@@ -191,10 +206,11 @@
@@ -191,10 +206,11 @@
b.addEventListener('click', async () => {
b.addEventListener('click', async () => {
b.disabled = true;
b.disabled = true;
const approval = item.approval || {};
const approval = item.approval || {};
+ const auth = await bearer();
try {
try {
await api('/api/v1/agent/approvals/' + encodeURIComponent(approval.product || item.product) + '/' + encodeURIComponent(approval.id) + '/decide', {
await api('/api/v1/agent/approvals/' + encodeURIComponent(approval.product || item.product) + '/' + encodeURIComponent(approval.id) + '/decide', {
method: 'POST',
method: 'POST',
- headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token },
+ headers: { 'Content-Type': 'application/json', ...(auth ? { Authorization: auth } : {}) },
body: JSON.stringify({ decision }),
body: JSON.stringify({ decision }),
});
});
} catch (err) { /* the panel refreshes either way */ }
} catch (err) { /* the panel refreshes either way */ }
@@ -229,9 +245,26 @@
@@ -229,9 +245,26 @@
a.target = '_blank';
a.target = '_blank';
card.appendChild(a);
card.appendChild(a);
}
}
+ // The step a site opened the Dock at (its task id): it is marked, so the person sees where they landed.
+ const wanted = focusId();
+ if (wanted && (entry.id === wanted || entry.ref === wanted)) card.classList.add('ovdt-focus');
return card;
return card;
}
}
+ /** The step to point at: the id (a task id) a site opened the Dock with, if it is still there. */
+ function focusId() {
+ try { return String(focus() || '').trim() || null; } catch (err) { return null; }
+ }
+
+ /** Bring the focused step into view (the Dock's panel is short; the step may be below the fold). */
+ function scrollToFocus() {
+ const wanted = focusId();
+ if (!wanted) return;
+ const node = list.querySelector('.ovdt-focus');
+ if (!node || !node.scrollIntoView) return;
+ try { node.scrollIntoView({ block: 'nearest' }); } catch (err) { node.scrollIntoView(); }
+ }
+
function renderList(entries) {
function renderList(entries) {
list.textContent = '';
list.textContent = '';
if (!entries.length) {
if (!entries.length) {
@@ -322,6 +355,7 @@
@@ -322,6 +355,7 @@
renderNeeds(data.needs_you || []);
renderNeeds(data.needs_you || []);
renderChips(data.counts || {});
renderChips(data.counts || {});
renderList(data.entries || []);
renderList(data.entries || []);
+ scrollToFocus();
writeUrl();
writeUrl();
}
}
@@ -454,7 +454,8 @@
@@ -454,7 +454,8 @@
return;
return;
}
}
if (id === 'chats') { chats.panel = panel; mounts.chats = mounts.chats || { refresh: null }; return state().chat && chats.open !== state().chat ? openChat(state().chat, false) : chats.open ? openChat(chats.open, false) : chatList(); }
if (id === 'chats') { chats.panel = panel; mounts.chats = mounts.chats || { refresh: null }; return state().chat && chats.open !== state().chat ? openChat(state().chat, false) : chats.open ? openChat(chats.open, false) : chatList(); }
- if (mounts[id]) { if (mounts[id].refresh) mounts[id].refresh(); return; }
+ // The Agent panel reads the step.
+ if (mounts[id]) { if (mounts[id].refresh) mounts[id].refresh(); if (mounts[id + 'Timeline']) mounts[id + 'Timeline'].refresh(); return; }
mounts[id] = { refresh: null };
mounts[id] = { refresh: null };
panel.innerHTML = '<div class="ovdk-wait"><span></span></div>';
panel.innerHTML = '<div class="ovdk-wait"><span></span></div>';
if (id === 'agent') {
if (id === 'agent') {
@@ -465,7 +466,7 @@
@@ -465,7 +466,7 @@
// The Agent panel: Now, Needs you and the one timeline (design 2026-11-07). It is first in the tab, so on a
// The Agent panel: Now, Needs you and the one timeline (design 2026-11-07). It is first in the tab, so on a
// phone Now and Needs you are on screen before anything else.
// phone Now and Needs you are on screen before anything else.
load('dock-timeline.js', () => !!(root.OpenVibeDockTimeline && root.OpenVibeDockTimeline.mount)).then((on) => {
load('dock-timeline.js', () => !!(root.OpenVibeDockTimeline && root.OpenVibeDockTimeline.mount)).then((on) => {
- if (on && !mounts[id + 'Timeline'] && panel.isConnected) mounts[id + 'Timeline'] = root.OpenVibeDockTimeline.mount(panel.querySelector('.ovdk-timeline'), { token: o.token, base: actorBase(), active: () => isOpen() && tab === 'agent' });
+ if (on && !mounts[id + 'Timeline'] && panel.isConnected) mounts[id + 'Timeline'] = root.OpenVibeDockTimeline.mount(panel.querySelector('.ovdk-timeline'), { token: o.token, base: actorBase(), active: () => isOpen() && tab === 'agent', focus: () => o.task || null });
});
});
load('dock-agent.js', () => !!(root.OpenVibeDockAgent && root.OpenVibeDockAgent.mount)).then((on) => {
load('dock-agent.js', () => !!(root.OpenVibeDockAgent && root.OpenVibeDockAgent.mount)).then((on) => {
if (on && !mounts[id + 'View'] && panel.isConnected) mounts[id + 'View'] = root.OpenVibeDockAgent.mount(panel.firstChild, { token: o.token, base: agentBase(), active: () => isOpen() && tab === 'agent' });
if (on && !mounts[id + 'View'] && panel.isConnected) mounts[id + 'View'] = root.OpenVibeDockAgent.mount(panel.firstChild, { token: o.token, base: agentBase(), active: () => isOpen() && tab === 'agent' });
@@ -1235,8 +1235,11 @@
@@ -1235,8 +1235,11 @@
if (document.getElementById('ov-presence-loader')) return;
if (document.getElementById('ov-presence-loader')) return;
const sc = document.createElement('script'); sc.id = 'ov-presence-loader'; sc.src = sibling('presence.js'); sc.async = true; sc.onload = go; document.head.appendChild(sc);
const sc = document.createElement('script'); sc.id = 'ov-presence-loader'; sc.src = sibling('presence.js'); sc.async = true; sc.onload = go; document.head.appendChild(sc);
}
}
- /** Open the Dock on a tab: 'agent' | 'activity'. */
- function openDock(tab) { loadDock(() => { root.OpenVibeDock.mount(dockOpts()); root.OpenVibeDock.open({ tab }); }); }
+ /**
+ * Open the Dock on a tab: 'agent' | 'activity'. opts may name the step to point at (opts.task): the
+ * Agent panel marks that entry and brings it into view, so a site can open the Dock at its own task.
+ */
+ function openDock(tab, opts) { loadDock(() => { root.OpenVibeDock.mount(dockOpts()); root.OpenVibeDock.open(Object.assign({ tab }, opts || {})); }); }
/** Custom sections for the user menu: [{ label, items: [{ label, icon, href, id, elId, hidden, danger, value, onClick }] }]. */
/** Custom sections for the user menu: [{ label, items: [{ label, icon, href, id, elId, hidden, danger, value, onClick }] }]. */
function sectionsHTML(sections) {
function sectionsHTML(sections) {
@@ -2396,8 +2399,12 @@
@@ -2396,8 +2399,12 @@
* settles; a page that draws such a box later may call this). Returns how many it marked. */
* settles; a page that draws such a box later may call this). Returns how many it marked. */
focusScrollers() { try { return focusScrollers(); } catch { return 0; } },
focusScrollers() { try { return focusScrollers(); } catch { return 0; } },
- /** Open the Dock (your agents and activity), on a tab: 'agent' | 'activity'. Signed-in people only. */
- openDock(tab) { if (_config.token && _config.dock !== false) openDock(tab); },
+ /**
+ * Open the Dock (your agents and activity), on a tab: 'agent' | 'activity'. Signed-in people only.
+ * opts may name the step to point at (opts.task): the Agent panel marks that entry and brings it
+ * into view, so a site can open the Dock at its own task.
+ */
+ openDock(tab, opts) { if (_config.token && _config.dock !== false) openDock(tab, opts); },
/** Get the bell mount point for OpenVibeNotifications. */
/** Get the bell mount point for OpenVibeNotifications. */
getBellMount() {
getBellMount() {
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.133.0",
+ "version": "4.134.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": {
@@ -49,5 +49,18 @@ assert.ok(dock.includes("panel.querySelector('.ovdk-timeline')"), 'the panel has
@@ -49,5 +49,18 @@ assert.ok(dock.includes("panel.querySelector('.ovdk-timeline')"), 'the panel has
assert.ok(dock.indexOf('ovdk-timeline') < dock.indexOf('ovdk-agent'), 'the panel comes before the desk view');
assert.ok(dock.indexOf('ovdk-timeline') < dock.indexOf('ovdk-agent'), 'the panel comes before the desk view');
assert.ok(dock.includes('https://openvibe.actor'), 'the panel reads OpenVibe.Actor');
assert.ok(dock.includes('https://openvibe.actor'), 'the panel reads OpenVibe.Actor');
+// The person's token: the navbar hands the Dock a function, so the panel resolves it when it calls.
+assert.ok(/typeof rawToken === 'function' \? await rawToken\(\)/.test(panel), 'a token given as a function is resolved at the call');
+assert.ok(!panel.includes("'Bearer ' + token }"), 'the panel never sends a function as a bearer');
+assert.ok(panel.includes('async function bearer()'), 'the bearer is resolved once per call');
+
+// Opening the Dock at a task: the entry that carries it is marked and brought into view.
+assert.ok(panel.includes('ovdt-focus'), 'the focused entry is marked');
+assert.ok(panel.includes('scrollIntoView'), 'the focused entry is brought into view');
+assert.ok(panel.includes('entry.ref === wanted'), 'the step is found by the id the site opened the Dock with');
+assert.ok(dock.includes('focus: () => o.task || null'), 'the Dock tells the panel which step to point at');
+const navbar = fs.readFileSync(path.join(__dirname, '..', 'navbar.js'), 'utf8');
+assert.ok(/function openDock\(tab, opts\)/.test(navbar), 'the navbar opens the Dock with the step to point at');
+
console.log('dock timeline: now, needs you, the timeline, the folds and the filters');
console.log('dock timeline: now, needs you, the timeline, the folds and the filters');
console.log('all passed');
console.log('all passed');