openvibers/openvibe.live Commits

Live guides: '(optional)' stays beside its label in the go-live setup (#135)

OP OpenVibers committed 29 days ago · 33c4df1758 · 3 files, +16 −1

In the go-live setup (and every guide form), "(optional)" dropped onto a row of its own in heading weight under "One line about it". `.ovg-field` is a grid, and the label's text and its `<span class="muted">` note were separate grid items. Found in a signed-in screenshot of `/broadcast`.

`ui.field` now wraps the label in one `.ovg-field-text` span, so the note stays beside its label, and `.ovg-field-text .muted` makes the note lighter (500, muted colour). `test/guide-field.test.js` checks both. This is a static change, so it needs no restart.

Changes

ddiff --git a/public/css/ov-guide.css b/public/css/ov-guide.css
iindex ae51ceab..41c250a2 100644
---- a/public/css/ov-guide.css
++++ b/public/css/ov-guide.css
@@@ -117,6 +117,7 @@ body.ovg-lock { overflow: hidden; }
.ovg-error { padding: 12px; border-radius: 12px; background: color-mix(in srgb, #ff5a5f 12%, var(--bg-secondary)); border: 1px solid #ff5a5f; }
.ovg-field { display: grid; gap: 6px; font-size: 0.8rem; font-weight: 700; color: var(--text-secondary); margin-bottom: 10px; }
.ovg-field-label { font-size: 0.8rem; font-weight: 700; color: var(--text-secondary); margin: 2px 0 6px; }
++.ovg-field-text .muted { font-weight: 500; color: var(--text-muted); }
.ovg-prefix { display: flex; align-items: center; gap: 0; } .ovg-prefix span { padding: 9px 12px; border: 1px solid var(--border); border-right: 0; border-radius: 10px 0 0 10px; background: var(--bg-tertiary); font-weight: 900; } .ovg-prefix .form-input { border-radius: 0 10px 10px 0; }
.ovg-drop { display: flex; align-items: center; justify-content: center; gap: 10px; min-height: 96px; border: 2px dashed color-mix(in srgb, var(--accent) 45%, var(--border)); border-radius: 14px; background: var(--bg-secondary); cursor: pointer; color: var(--text-secondary); font-weight: 700; padding: 12px; transition: border-color .15s, background .15s; }
.ovg-drop:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, var(--bg-secondary)); }
ddiff --git a/public/js/ov-guide.js b/public/js/ov-guide.js
iindex c0f7e963..b1d3b6c7 100644
---- a/public/js/ov-guide.js
++++ b/public/js/ov-guide.js
@@@ -201,7 +201,8 @@
secret: (label, value) => `<div class="ovg-kv"><span>${cfg.esc(label)}</span><code class="ovg-secret" data-secret="${cfg.esc(value)}">••••••••••••••••</code><button type="button" class="ovg-copy" data-copy="${cfg.esc(value)}"><i class="fa-regular fa-copy"></i> Copy</button><button type="button" class="ovg-copy" data-reveal="1"><i class="fa-regular fa-eye"></i> Show</button></div>`,
steps: (items) => `<ol class="ovg-obs">${items.map(i => `<li>${i}</li>`).join('')}</ol>`,
chips: (items, name) => `<div class="ovg-chips" data-chips="${cfg.esc(name || '')}">${items.map(([v, l, i]) => `<button type="button" class="ovg-chip" data-chip="${cfg.esc(v)}">${i ? `<i class="fa-solid ${i}"></i> ` : ''}${cfg.esc(l)}</button>`).join('')}</div>`,
-- field: (label, inputHtml) => `<label class="ovg-field">${label}${inputHtml}</label>`,
++ // The label is one grid row (its text and any "(optional)" note together), the control the next.
++ field: (label, inputHtml) => `<label class="ovg-field"><span class="ovg-field-text">${label}</span>${inputHtml}</label>`,
wire(root) {
root.querySelectorAll('[data-copy]').forEach(b => b.addEventListener('click', async () => { try { await navigator.clipboard.writeText(b.dataset.copy); b.innerHTML = '<i class="fa-solid fa-check"></i> Copied'; setTimeout(() => { b.innerHTML = '<i class="fa-regular fa-copy"></i> Copy'; }, 1500); } catch { cfg.toast('Copy failed — long-press to copy', 'error'); } }));
root.querySelectorAll('[data-reveal]').forEach(b => b.addEventListener('click', () => { const c = b.parentElement.querySelector('.ovg-secret'); if (!c) return; const on = c.dataset.on === '1'; c.textContent = on ? '••••••••••••••••' : c.dataset.secret; c.dataset.on = on ? '' : '1'; b.innerHTML = on ? '<i class="fa-regular fa-eye"></i> Show' : '<i class="fa-regular fa-eye-slash"></i> Hide'; }));
ddiff --git a/test/guide-field.test.js b/test/guide-field.test.js
nnew file mode 100644
iindex 00000000..a4640ab4
---- /dev/null
++++ b/test/guide-field.test.js
@@@ -0,0 +1,13 @@
++'use strict';
++// The guides' form field (public/js/ov-guide.js ui.field): .ovg-field is a grid, so the label's text and its
++// "(optional)" note must be ONE grid item, or the note lands on a row of its own in heading weight (the go-live
++// setup's "One line about it", 2026-10-15). The note itself is lighter than the label.
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++
++const js = fs.readFileSync(path.join(__dirname, '..', 'public', 'js', 'ov-guide.js'), 'utf8');
++const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'css', 'ov-guide.css'), 'utf8');
++assert.ok(js.includes('field: (label, inputHtml) => `<label class="ovg-field"><span class="ovg-field-text">${label}</span>${inputHtml}</label>`'), 'the label text is wrapped as one grid item');
++assert.match(css, /\.ovg-field-text \.muted \{ font-weight: 500;/, 'the optional note is lighter than the label');
++console.log('guide field: the label and its note share one row');