openvibers/openvibe.live Commits

Dashboard on a phone: Bench moves to the Chat tab, cards fit the screen (#709)

OP OpenVibers committed 4 days ago · 9d7681f8a4 · 4 files, +80 −26

Bench on stream was static markup in the Home grid, so it came first on every streamer's dashboard, above the
profile; it is a round chat votes on, so it now lives in the Chat tab after Scenes, with a line saying what a round
is. The phone grid was 1fr, which grows to its widest content: one long line made every Chat-tab card 466 px wide
on a 390 px phone and the page zoomed out; it is minmax(0, 1fr). The chat overlay and scene address rows showed
two unlabelled full-width grey bars on a phone; they read Copy and Preview, side by side.

Changes

ddiff --git a/public/css/features/dashboard.css b/public/css/features/dashboard.css
iindex f94b16d8..ba227765 100644
---- a/public/css/features/dashboard.css
++++ b/public/css/features/dashboard.css
@@@ -111,7 +111,15 @@
}
@media (max-width: 768px) {
-- .dash-grid { grid-template-columns: 1fr; }
++ /* minmax(0, 1fr), not 1fr: a 1fr track grows to its widest content, and one long line in the Chat tab made every
++ card there 466 px wide on a 390 px phone (the page zoomed out; signed-in sweep, 11-09). */
++ .dash-grid { grid-template-columns: minmax(0, 1fr); }
++ .dash-grid > * { min-width: 0; }
++ /* A card's address rows (the chat overlay, the scenes): the address on its own line, then Copy and Preview side by
++ side (they were two unlabeled full-width grey bars). */
++ .dash-card .key-display { flex-direction: row; }
++ .dash-card .key-display .form-input { flex: 1 1 100%; min-width: 0; }
++ .dash-card .key-display .btn { flex: 1 1 0; width: auto; min-height: 40px; justify-content: center; }
}
@media (max-width: 360px) {
.dash-card { padding: 14px; border-radius: 14px; }
@@@ -516,3 +524,9 @@
.dash-schedule-list a { color: var(--text-secondary); text-decoration: none; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-schedule-list a:hover { color: var(--text-primary); text-decoration: underline; }
.dash-schedule-actions { display: flex; flex-wrap: wrap; gap: 8px; }
++
++/* Bench on stream (Chat tab): labels over their fields, the button at the start of the row. */
++.dash-bench-form { display: grid; gap: 6px; }
++.dash-bench-form label { margin-top: 6px; font-size: 0.85rem; font-weight: 600; color: var(--text-secondary); }
++.dash-bench-form .btn { justify-self: start; min-height: 40px; margin-top: 10px; }
++#dash-bench-status:empty { display: none; }
ddiff --git a/public/fragments/dashboard.html b/public/fragments/dashboard.html
iindex 01e98fc0..86b5c7c9 100644
---- a/public/fragments/dashboard.html
++++ b/public/fragments/dashboard.html
@@@ -15,26 +15,6 @@
</div>
<div class="ch-tab-panel dash-tab-panel active" id="dash-panel-home" role="tabpanel"><div class="dash-grid" id="dash-grid-home">
-- <section class="dash-card" id="dash-bench-stream" aria-labelledby="dash-bench-title">
-- <h3 id="dash-bench-title">Bench on stream</h3>
-- <form id="dash-bench-form">
-- <label for="dash-bench-kind">Kind</label>
-- <select class="form-input" id="dash-bench-kind" name="kind" required>
-- <option value="design.page">Design page</option><option value="design.component">Design component</option>
-- <option value="meme.caption">Meme caption</option><option value="chat.reply">Chat reply</option>
-- <option value="games.fun">Games fun</option><option value="vision.pair">Vision pair</option>
-- <option value="codes.review">Codes review</option><option value="surf.desk">Surf desk</option>
-- <option value="speech.tts">Speech TTS</option><option value="translate.ja">Translate Japanese</option>
-- <option value="search.answer">Search answer</option>
-- </select>
-- <label for="dash-bench-prompt">Prompt</label>
-- <textarea class="form-input" id="dash-bench-prompt" name="prompt" maxlength="4000" required></textarea>
-- <label for="dash-bench-duration">Vote time (seconds)</label>
-- <input class="form-input" id="dash-bench-duration" name="duration" type="number" min="15" max="600" value="60" required>
-- <button class="btn" type="submit">Start round</button>
-- </form>
-- <p id="dash-bench-status" role="status"></p>
-- </section>
</div></div>
<div class="ch-tab-panel dash-tab-panel" id="dash-panel-chatai" role="tabpanel"><div class="dash-grid" id="dash-grid-chatai"></div></div>
<div class="ch-tab-panel dash-tab-panel" id="dash-panel-moderation" role="tabpanel"><div class="dash-grid" id="dash-grid-moderation"></div></div>
@@@ -65,10 +45,10 @@
<div class="key-display">
<input type="text" id="dash-obs-chat-overlay-url" readonly class="form-input">
<button class="btn btn-small" onclick="copyDashObsOverlayUrl()" title="Copy overlay URL">
-- <i class="fa-solid fa-copy"></i>
++ <i class="fa-solid fa-copy" aria-hidden="true"></i> Copy
</button>
<button class="btn btn-small" onclick="openDashObsOverlay()" title="Open overlay">
-- <i class="fa-solid fa-arrow-up-right-from-square"></i>
++ <i class="fa-solid fa-arrow-up-right-from-square" aria-hidden="true"></i> Preview
</button>
</div>
</div>
@@@ -86,6 +66,30 @@
<ul class="dash-scenes" id="dash-scenes"></ul>
</div>
++ <!-- Bench on stream (bench-stream-dashboard.js): a round chat votes on, so it sits in the Chat tab beside the
++ scenes. It was static markup in the Home grid, so it came first on every dashboard, above the profile. -->
++ <section class="dash-card" id="dash-bench-stream" aria-labelledby="dash-bench-title">
++ <h3 id="dash-bench-title"><i class="fa-solid fa-scale-balanced" aria-hidden="true"></i> Bench on stream</h3>
++ <p class="muted" style="margin-bottom:12px">Two AI models answer your prompt on stream, and chat votes for the better one with !1, !2 or !tie.</p>
++ <form id="dash-bench-form" class="dash-bench-form">
++ <label for="dash-bench-kind">Kind</label>
++ <select class="form-input" id="dash-bench-kind" name="kind" required>
++ <option value="design.page">Design page</option><option value="design.component">Design component</option>
++ <option value="meme.caption">Meme caption</option><option value="chat.reply">Chat reply</option>
++ <option value="games.fun">Games fun</option><option value="vision.pair">Vision pair</option>
++ <option value="codes.review">Codes review</option><option value="surf.desk">Surf desk</option>
++ <option value="speech.tts">Speech TTS</option><option value="translate.ja">Translate Japanese</option>
++ <option value="search.answer">Search answer</option>
++ </select>
++ <label for="dash-bench-prompt">Prompt</label>
++ <textarea class="form-input" id="dash-bench-prompt" name="prompt" maxlength="4000" rows="3" placeholder="A landing page for a neighbourhood bakery" required></textarea>
++ <label for="dash-bench-duration">Vote time (seconds)</label>
++ <input class="form-input" id="dash-bench-duration" name="duration" type="number" min="15" max="600" value="60" required>
++ <button class="btn btn-primary" type="submit">Start round</button>
++ </form>
++ <p id="dash-bench-status" role="status"></p>
++ </section>
++
<!-- API Tokens (Bot / Integration) -->
<div class="dash-card" id="dash-card-tokens">
<h3><i class="fa-solid fa-key"></i> API Tokens</h3>
ddiff --git a/public/js/dashboard.js b/public/js/dashboard.js
iindex e2305907..0eff78fa 100644
---- a/public/js/dashboard.js
++++ b/public/js/dashboard.js
@@@ -12,7 +12,7 @@ let activeStreams = [];
modules (e.g. moderation) register their loader on window._dashTabLoaders. */
const _DASH_TAB_CARDS = {
home: ['dash-card-profile', 'dash-card-schedule', 'dash-card-ai-derivation', 'dash-card-offline'],
-- chatai: ['dash-card-vibe', 'dash-aibot-card', 'dash-card-overlay', 'dash-card-scenes', 'dash-card-chatlogs'],
++ chatai: ['dash-card-vibe', 'dash-aibot-card', 'dash-card-overlay', 'dash-card-scenes', 'dash-bench-stream', 'dash-card-chatlogs'],
moderation: [], // injected by dashboard-moderation.js into #dash-grid-moderation
controls: ['dash-card-bot-embed', 'dash-card-tokens'],
money: ['dash-card-bucks', 'dash-card-goals', 'dash-card-tips-page', 'dash-powerchat-card'],
@@@ -245,9 +245,9 @@ function updateDashScenes() {
name.append(i, ` ${label}`);
const row = document.createElement('div'); row.className = 'key-display';
const input = document.createElement('input'); input.type = 'text'; input.readOnly = true; input.className = 'form-input'; input.value = dashSceneUrl(kind); input.setAttribute('aria-label', `${label} scene URL`);
-- const copy = document.createElement('button'); copy.className = 'btn btn-small'; copy.title = `Copy the ${label} URL`; copy.innerHTML = '<i class="fa-solid fa-copy"></i>';
++ const copy = document.createElement('button'); copy.className = 'btn btn-small'; copy.title = `Copy the ${label} URL`; copy.innerHTML = '<i class="fa-solid fa-copy" aria-hidden="true"></i> Copy';
copy.addEventListener('click', () => navigator.clipboard.writeText(input.value).then(() => toast(`${label} URL copied`, 'success')));
-- const open = document.createElement('a'); open.className = 'btn btn-small'; open.href = input.value; open.target = '_blank'; open.rel = 'noopener'; open.title = `Preview ${label}`; open.innerHTML = '<i class="fa-solid fa-arrow-up-right-from-square"></i>';
++ const open = document.createElement('a'); open.className = 'btn btn-small'; open.href = input.value; open.target = '_blank'; open.rel = 'noopener'; open.title = `Preview ${label}`; open.innerHTML = '<i class="fa-solid fa-arrow-up-right-from-square" aria-hidden="true"></i> Preview';
row.append(input, copy, open);
li.append(name, row);
return li;
ddiff --git a/test/dashboard-phone-layout.test.js b/test/dashboard-phone-layout.test.js
nnew file mode 100644
iindex 00000000..737c043c
---- /dev/null
++++ b/test/dashboard-phone-layout.test.js
@@@ -0,0 +1,36 @@
++'use strict';
++/**
++ * The streamer dashboard on a phone (signed-in sweep, 2026-11-09):
++ * - "Bench on stream" was static markup in the Home grid, so it came first on every dashboard, above the profile. It is
++ * a round chat votes on: it now lives in the Chat tab, after Scenes, relocated like every other card.
++ * - The phone grid was `1fr`, which grows to its widest content: one long line made every Chat-tab card 466 px wide on a
++ * 390 px phone and the page zoomed out. It is `minmax(0, 1fr)` now.
++ * - Address rows (chat overlay, scenes) showed two unlabeled full-width grey bars; they read Copy and Preview, side by side.
++ *
++ * node test/dashboard-phone-layout.test.js
++ */
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++
++const root = path.join(__dirname, '..');
++const html = fs.readFileSync(path.join(root, 'public/fragments/dashboard.html'), 'utf8');
++const js = fs.readFileSync(path.join(root, 'public/js/dashboard.js'), 'utf8');
++const css = fs.readFileSync(path.join(root, 'public/css/features/dashboard.css'), 'utf8');
++
++const home = html.slice(html.indexOf('id="dash-grid-home"'), html.indexOf('id="dash-panel-chatai"'));
++assert.ok(!home.includes('dash-bench-stream'), 'Bench is not static markup in the Home grid');
++const staging = html.slice(html.indexOf('id="dash-staging"'));
++assert.ok(staging.includes('id="dash-bench-stream"'), 'Bench is authored in the staging grid like the other cards');
++assert.ok(/chatai:\s*\[[^\]]*'dash-card-scenes', 'dash-bench-stream'/.test(js), 'Bench is relocated into the Chat tab after Scenes');
++assert.ok(/home:\s*\['dash-card-profile'/.test(js), 'the profile leads Home');
++assert.ok(/chat votes for the better one with !1, !2 or !tie/.test(staging), 'the card says what a round is');
++
++const phone = css.slice(css.indexOf('@media (max-width: 768px)'));
++assert.ok(/\.dash-grid \{ grid-template-columns: minmax\(0, 1fr\); \}/.test(phone), 'the phone column can shrink below its content');
++assert.ok(/\.dash-card \.key-display \{ flex-direction: row; \}/.test(phone), 'Copy and Preview share a row on a phone');
++
++assert.ok(/fa-copy" aria-hidden="true"><\/i> Copy'/.test(js) && /aria-hidden="true"><\/i> Preview'/.test(js), 'scene buttons are labelled');
++assert.ok(/copyDashObsOverlayUrl\(\)[^>]*>\s*<i class="fa-solid fa-copy" aria-hidden="true"><\/i> Copy/.test(html), 'the overlay copy button is labelled');
++
++console.log('dashboard on a phone: Bench in the Chat tab, columns that shrink, labelled address buttons');