openvibers/openvibe.codes Commits

Codes C9b: the conductor's pages join the workspace navigation (#185)

OP OpenVibers committed 14 days ago · 2f501c051f · 3 files, +4 −3

* Codes C9b: /conductor and /approvals are workspace pages, and Conductor is one of its sections

The Frame still drew its phone app bar on both new pages, so a phone had two
bottom bars and the workspace one was cut in half behind the other. They join
the workspace pages, so the Frame's app bar stays away and the compact footer
applies.

Conductor is also listed with the workspace sections in the Frame's navbar,
next to Tasks, so it can be reached from the web without going through Home.

* Codes C9b: the phone's tab bar holds every section in one row

It was a six-column grid with a fixed height, so the seventh section (Conductor,
beside Tasks) fell into a second row that the height cut off — Settings was
unreachable on a phone. The row now makes one column per section, and a long
label is ellipsised instead of wrapping.

Changes

ddiff --git a/public/css/c4.css b/public/css/c4.css
iindex f1084ad..1ebbdd5 100644
---- a/public/css/c4.css
++++ b/public/css/c4.css
@@@ -38,7 +38,7 @@
.c4-settings{display:grid;gap:12px;font-size:13px}.c4-safety{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:16px}.c4-safety-card{display:flex;align-items:flex-start;gap:10px;border:1px solid var(--c4-line);border-radius:10px;padding:14px;cursor:pointer;min-height:90px}.c4-safety-card:has(input:checked){border-color:var(--c4-accent);box-shadow:0 0 0 1px var(--c4-accent)}.c4-safety-card input{margin-top:3px;accent-color:var(--c4-accent)}.c4-safety-card span{display:grid;gap:6px}.c4-safety-card small{color:var(--c4-muted);font-size:12px;line-height:1.4}
.c4 a:focus-visible,.c4 button:focus-visible,.c4 input:focus-visible,.c4 textarea:focus-visible,.c4 select:focus-visible{outline:2px solid #188c75;outline-offset:2px}
@media(max-width:900px){.c4{padding-left:22px;padding-right:22px}}
--@media(max-width:760px){.c4{padding:0 15px 24px}.c4-main{padding-top:18px}.c4-title{margin-bottom:16px}.c4-card{padding:16px;border-radius:11px}.c4-grid{grid-template-columns:1fr;gap:0}.c4-controls{display:grid;grid-template-columns:1fr 1fr}.c4-controls label{min-width:0}.c4-controls select,.c4-controls input{width:100%}.c4-controls .c4-button{grid-column:1/-1}.c4-task-detail{padding:17px}.c4-task-heading{gap:8px}.c4-task-heading>.c4-status{flex:none}.c4-chips{gap:6px}.c4-chip{font-size:11px;padding:5px 8px}.c4-outcome{grid-template-columns:30px minmax(0,1fr);gap:8px}.c4-outcome-actions{grid-column:1/-1;justify-content:flex-start;padding-left:38px}.c4-opill{min-height:44px;padding:0 11px;font-size:12px}.c4-mobile{display:grid;position:fixed;z-index:20;bottom:0;left:0;right:0;grid-template-columns:repeat(6,1fr);height:var(--c4-tabs-h);box-sizing:border-box;padding:0 max(6px,env(safe-area-inset-left)) env(safe-area-inset-bottom,0px);background:var(--c4-surface);border-top:1px solid var(--c4-line)}.c4-mobile a{display:flex;min-height:44px;flex-direction:column;align-items:center;justify-content:center;gap:2px;text-align:center;font-size:10px;padding:4px 2px;border-radius:8px;color:var(--c4-muted);text-decoration:none}.c4-mobile svg{width:19px;height:19px}.c4-search{align-items:stretch;flex-wrap:wrap}.c4-search label{width:100%}.c4-safety{grid-template-columns:1fr}.c4-safety-card{min-height:68px}.c4-progress-toggle{flex-wrap:wrap}.c4-progress-meta{margin-left:0}.c4-task-meta{font-size:10px}}
++@media(max-width:760px){.c4{padding:0 15px 24px}.c4-main{padding-top:18px}.c4-title{margin-bottom:16px}.c4-card{padding:16px;border-radius:11px}.c4-grid{grid-template-columns:1fr;gap:0}.c4-controls{display:grid;grid-template-columns:1fr 1fr}.c4-controls label{min-width:0}.c4-controls select,.c4-controls input{width:100%}.c4-controls .c4-button{grid-column:1/-1}.c4-task-detail{padding:17px}.c4-task-heading{gap:8px}.c4-task-heading>.c4-status{flex:none}.c4-chips{gap:6px}.c4-chip{font-size:11px;padding:5px 8px}.c4-outcome{grid-template-columns:30px minmax(0,1fr);gap:8px}.c4-outcome-actions{grid-column:1/-1;justify-content:flex-start;padding-left:38px}.c4-opill{min-height:44px;padding:0 11px;font-size:12px}.c4-mobile{display:grid;position:fixed;z-index:20;bottom:0;left:0;right:0;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);height:var(--c4-tabs-h);box-sizing:border-box;padding:0 max(6px,env(safe-area-inset-left)) env(safe-area-inset-bottom,0px);background:var(--c4-surface);border-top:1px solid var(--c4-line)}.c4-mobile a{display:flex;min-width:0;min-height:44px;flex-direction:column;align-items:center;justify-content:center;gap:2px;text-align:center;font-size:10px;padding:4px 2px;border-radius:8px;color:var(--c4-muted);text-decoration:none}.c4-mobile a span{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c4-mobile svg{width:19px;height:19px}.c4-search{align-items:stretch;flex-wrap:wrap}.c4-search label{width:100%}.c4-safety{grid-template-columns:1fr}.c4-safety-card{min-height:68px}.c4-progress-toggle{flex-wrap:wrap}.c4-progress-meta{margin-left:0}.c4-task-meta{font-size:10px}}
:root[data-theme-mode="dark"] .c4{--c4-bg:#101518;--c4-surface:#171f23;--c4-ink:#eaf0f1;--c4-muted:#a3b0b4;--c4-line:#2d393d;--c4-accent:#75d0ba;--c4-dot:#5b6870;--c4-dot-ok:#afd39c;--c4-dot-bad:#f0b18c;--c4-dot-skip:#5b6870;--c4-dot-open:#75d0ba;--c4-ok-bg:#243423;--c4-ok-ink:#afd39c;--c4-bad-bg:#39291f;--c4-bad-ink:#f0b18c;--c4-bad-line:#5a4034;--c4-skip-bg:#263034;--c4-skip-ink:#cad4d6;--c4-open-bg:#1d3831;--c4-open-ink:#75d0ba;--c4-quiet-bg:#20292d;--c4-quiet-ink:#cad4d6;--c4-quiet:#20292d;--c4-ghost:#11181b}
:root[data-theme-mode="dark"] .c4-composer{background:linear-gradient(140deg,#171f23,#182722);border-color:#315148}:root[data-theme-mode="dark"] .c4 textarea,:root[data-theme-mode="dark"] .c4 select,:root[data-theme-mode="dark"] .c4 input{background:#11181b;color:var(--c4-ink)}
:root[data-theme-mode="dark"] .c4-mobile a[aria-current]{background:#1d3831}:root[data-theme-mode="dark"] .c4-log,:root[data-theme-mode="dark"] .c4-code{background:#11181b;color:#d3dfdf}:root[data-theme-mode="dark"] .c4-button-light{background:#20292d;color:var(--c4-ink)}:root[data-theme-mode="dark"] .c4-obadge{background:#39291f;color:#f0b18c}:root[data-theme-mode="dark"] .c4-progress-toggle{background:#1d3831;color:var(--c4-ink)}
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex 5a3005d..c0e60c8 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -38,6 +38,7 @@ const NAV = [
const WORKSPACE_NAV = [
{ label: 'Home', href: '/', icon: 'fa-house' },
{ label: 'Tasks', href: '/tasks', icon: 'fa-list' },
++ { label: 'Conductor', href: '/conductor', icon: 'fa-chart-simple' },
{ label: 'Agents', href: '/agents', icon: 'fa-robot' },
{ label: 'Machines', href: '/machines', icon: 'fa-display' },
{ label: 'Usage', href: '/usage', icon: 'fa-chart-line' },
@@@ -70,7 +71,7 @@ function renderPage(o) {
const pathNow = o.path || '/';
// The workspace pages share the compact Frame: no second app bar under the Frame's own (a query must not change it).
const pathOnly = pathNow.split('?')[0];
-- const c4Workspace = pathOnly === '/' || /^\/(tasks|agents|machines|usage|settings)(\/|$)/.test(pathOnly);
++ const c4Workspace = pathOnly === '/' || /^\/(tasks|conductor|approvals|agents|machines|usage|settings)(\/|$)/.test(pathOnly);
const loginNext = encodeURIComponent(pathNow);
const canonical = `${o.config.baseUrl}${pathNow.split('?')[0]}`;
// One navigation per page. On the workspace pages a signed-in person's navbar carries the workspace sections, with
ddiff --git a/test/c4c.test.js b/test/c4c.test.js
iindex 91034dd..f71ae45 100644
---- a/test/c4c.test.js
++++ b/test/c4c.test.js
@@@ -35,7 +35,7 @@ const split = (links) => ({
assert.strictEqual(page.status, 200);
const links = navbarOf(page.text).links;
const { top, more } = split(links);
-- assert.deepStrictEqual(top, ['Home', 'Tasks', 'Agents', 'Machines', 'Usage', 'Settings'], 'the sections are the navbar');
++ assert.deepStrictEqual(top, ['Home', 'Tasks', 'Conductor', 'Agents', 'Machines', 'Usage', 'Settings'], 'the sections are the navbar');
assert.ok(more.includes('Get started') && more.includes('Run on your machine'), 'the product’s places fold into More');
assert.ok(!top.includes('Agents') || top.filter((l) => l === 'Agents').length === 1, 'Agents is in one place only');
const tasks = links.find((l) => l.href === '/tasks');