openvibers/openvibe.chat Commits

Chat header: the room's AI overview sits among the tools, with its own mark (#529)

OP OpenVibers committed 2 days ago · d547186998 · 4 files, +19 −2

On a phone Global chat's header had a second row holding one sparkle: the AI overview's button, placed after the tools
and caught by .oc-more's grid rules (12 px top margin, grid columns). It is now among the header's tools, before Vibe
and settings, and its mark is a list, not the same sparkle that opens Vibe beside it. Test: insight-block-icon.

Changes

ddiff --git a/public/web/chat.css b/public/web/chat.css
iindex 7b70800..b16bbcf 100644
---- a/public/web/chat.css
++++ b/public/web/chat.css
@@@ -638,6 +638,10 @@ h2 { font-size: 1.15rem; margin: 1.4em 0 .5em; }
.oc-snip > pre { max-height: 50vh; overflow: auto; padding-right: 48px; }
/* A header's "more" menu (a conversation's Block): a small panel under its button. */
.oc-more { position: relative; }
++/* The room's AI overview sits in the header's tools (it wrapped onto a row of its own: .oc-more is also the grid of
++ "more" links above). Opus, 2026-11-11. */
++.oc-chan-tools .oc-more { display: inline-flex; margin: 0; grid-template-columns: none; }
++.oc-chan-tools .oc-insight-pop { width: min(340px, calc(100vw - 32px)); }
.oc-more > summary { list-style: none; }
.oc-more > summary::-webkit-details-marker { display: none; }
.oc-pop { position: absolute; right: 0; top: calc(100% + 4px); z-index: 20; min-width: 200px; padding: 6px; border-radius: 12px; border: 1px solid var(--oc-line); background: var(--oc-panel); box-shadow: 0 12px 28px rgba(0, 0, 0, .35); }
ddiff --git a/server/web/insight-block.js b/server/web/insight-block.js
iindex 309d781..e380441 100644
---- a/server/web/insight-block.js
++++ b/server/web/insight-block.js
@@@ -17,7 +17,7 @@ function insightBlock(insight) {
const off = insight && insight.state === 'off';
const when = o.updated_at ? ui.when(o.updated_at) : null;
const meta = [o.people ? `${o.people} ${o.people === 1 ? 'person' : 'people'}` : '', o.messages ? `${o.messages} messages` : '', when && when.text ? `updated ${when.text}` : ''].filter(Boolean);
-- return `<details class="oc-more oc-insight"><summary class="oc-icon-btn" aria-label="AI overview of this room" title="AI overview">${IC.spark}</summary><div class="oc-pop oc-insight-pop">`
++ return `<details class="oc-more oc-insight"><summary class="oc-icon-btn" aria-label="AI overview of this room" title="AI overview">${IC.steps}</summary><div class="oc-pop oc-insight-pop">`
+ (ready ? `<p>${esc(o.text)}</p>` : `<p class="oc-muted">${off ? 'AI insight is off for this room.' : 'Quiet right now: there is nothing new to summarise.'}</p>`)
+ (topics.length ? `<ul class="oc-insight-topics">${topics.map((t) => `<li>${esc(t.label)} <span class="oc-muted">${Number(t.count) || 0}</span></li>`).join('')}</ul>` : '')
+ (events.length ? `<ol class="oc-insight-timeline">${events.map((e) => `<li><time>${esc(ui.when(e.at).text || '')}</time> <strong>${esc(e.title)}</strong>${e.text ? ` <span class="oc-muted">${esc(e.text)}</span>` : ''}</li>`).join('')}</ol>` : '')
ddiff --git a/server/web/pages.js b/server/web/pages.js
iindex c9cf057..5fb0c89 100644
---- a/server/web/pages.js
++++ b/server/web/pages.js
@@@ -324,7 +324,7 @@ function createWebRoutes({ config }) {
rail: shell.rail({ global: true, dmUnread: dms.reduce((n, d) => n + d.unread, 0), servers: shell.railServers(mineServers) }),
side: shell.sideHome({ dms, friends: contacts.friends, requests: contacts.requests, you: { name: me.display_name || me.username, status: 'Online', avatarUrl: me.avatar_url || '' } }),
sideLabel: 'Home',
-- main: `<header class="oc-chan-head">${shell.toList}<h1 id="oc-h" class="oc-chan-title">Global chat</h1><span class="oc-muted oc-chan-topic">Everyone on OpenVibe${home.peopleLine(activity)}</span><span class="oc-chan-tools"><span class="oc-live" id="oc-live" hidden>● live</span><a class="oc-icon-btn" href="/vibe" aria-label="Chat with Vibe" title="Chat with Vibe">${IC.spark}</a><a class="oc-icon-btn" href="/settings" aria-label="Chat settings" title="Chat settings">${IC.gear}</a></span>${insightHtml}</header>
++ main: `<header class="oc-chan-head">${shell.toList}<h1 id="oc-h" class="oc-chan-title">Global chat</h1><span class="oc-muted oc-chan-topic">Everyone on OpenVibe${home.peopleLine(activity)}</span><span class="oc-chan-tools"><span class="oc-live" id="oc-live" hidden>● live</span>${insightHtml}<a class="oc-icon-btn" href="/vibe" aria-label="Chat with Vibe" title="Chat with Vibe">${IC.spark}</a><a class="oc-icon-btn" href="/settings" aria-label="Chat settings" title="Chat settings">${IC.gear}</a></span></header>
${error}
${feedList}
${composer}`,
ddiff --git a/test/insight-block-icon.test.js b/test/insight-block-icon.test.js
nnew file mode 100644
iindex 0000000..db52f7f
---- /dev/null
++++ b/test/insight-block-icon.test.js
@@@ -0,0 +1,13 @@
++'use strict';
++// The room's AI overview in a header (server/web/insight-block.js): its own mark (a list), not the sparkle that opens
++// Vibe beside it, and the page puts it among the header's tools rather than on a row of its own.
++const assert = require('node:assert/strict');
++const fs = require('node:fs');
++const path = require('node:path');
++const { insightBlock } = require('../server/web/insight-block');
++const html = insightBlock({ state: 'ready', overview: { text: 'People are planning a game night.', topics: [], people: 3, messages: 12 }, timeline: [] });
++assert.match(html, /aria-label="AI overview of this room"/);
++assert.ok(html.includes('M9 6h11M9 12h11M9 18h11'), 'the overview has the list mark');
++const pages = fs.readFileSync(path.join(__dirname, '../server/web/pages.js'), 'utf8');
++assert.match(pages, /<span class="oc-chan-tools"><span class="oc-live" id="oc-live" hidden>● live<\/span>\$\{insightHtml\}/, 'Global chat puts it among the tools');
++console.log('insight-block-icon: the overview has its own mark and sits among the header tools');