Rebuild the operator console on the ethos shell
Nine screens against orchestra-ui-spec.md and the accepted mockups, on the ethos design system: signal violet #8F7AE5, the routing fork motif, the 64px rail and 56px top bar, mono for every machine value and sans for every human one. Each screen was built by its own agent against a fixed foundation, so the shell, tokens and primitives have one author and the screens cannot drift into nine dialects. Real data only. Where no endpoint exists the screen says which one it needs instead of inventing a value. That is most of what was learned here: Steer / Correct is disabled because nothing records a human decision from the web, take-control is disabled because nothing forwards keystrokes to a pane, context occupancy is missing from three screens, and projects can show no repo, remote, quality gate or verification policy because those live only in config.jsonc. Three bugs the render caught that no computed value would have: The previous stylesheet fought every shared class name and leaked properties the new rules never mention, which is how position:fixed survived on .topbar. It is now scoped under .legacy and applies only to the login route, which also stops its green accent and its backdrop-filter from reaching the console. Go marshals a zero time.Time as "0001-01-01T00:00:00Z" and omitempty does not omit a struct, so absent timestamps arrived populated-looking and rendered as "739855d ago". Stripped once at the API boundary every screen reads through, with a test. Long machine ids overflowed their cards and painted under the next one. Verified by rendering: chromium screenshots of the dashboard, tasks, task detail, terminal, workers and review at 1440px, and the dashboard at 390px. Geist is still not on disk, so both stacks fall back to the system faces. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVbaKucEYBjMqVeUgJUsc1
This commit is contained in:
@@ -0,0 +1,679 @@
|
||||
/* Orchestra operator console — the ethos shell, applied.
|
||||
Tokens come from ethos.tokens.css and are never redefined here. This file
|
||||
holds the shared shell geometry and the primitives every screen reuses, so
|
||||
nine screens cannot drift into nine dialects. */
|
||||
|
||||
@import './ethos.tokens.css';
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg-0);
|
||||
color: var(--text-hi);
|
||||
font-family: var(--sans);
|
||||
font-size: 14px;
|
||||
line-height: 1.45;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
/* Law 1. Anything the machine owns is mono, with tabular figures so columns
|
||||
of ids, durations and percentages line up instead of shimmering. */
|
||||
.mono,
|
||||
code,
|
||||
kbd,
|
||||
pre {
|
||||
font-family: var(--mono);
|
||||
font-feature-settings: 'tnum' 1, 'zero' 1;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--text-machine);
|
||||
}
|
||||
|
||||
/* Machine ids are long and unbreakable by default, so a pane id ran out of
|
||||
its card and under the next one. Ids wrap; tables keep their own scroll. */
|
||||
.console .mono {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.console .table .mono,
|
||||
.console .topbar-readouts .mono {
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
|
||||
.console .label {
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--text-lo);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.console h1 {
|
||||
font-size: 28px;
|
||||
line-height: 1.15;
|
||||
letter-spacing: -0.02em;
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.console h2 {
|
||||
font-size: 17px;
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.console a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.console button {
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* ── shell ───────────────────────────────────────────────────────────── */
|
||||
|
||||
.console.shell {
|
||||
display: grid;
|
||||
grid-template-columns: 64px 1fr;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.console .rail {
|
||||
background: var(--bg-1);
|
||||
border-right: 1px solid var(--line);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 14px 0 10px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.console .rail-mark {
|
||||
color: var(--accent);
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.console .rail-item {
|
||||
position: relative;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: var(--r-sm);
|
||||
color: var(--text-lo);
|
||||
background: none;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
transition: color var(--fast) var(--ease), background var(--fast) var(--ease);
|
||||
}
|
||||
|
||||
.console .rail-item:hover {
|
||||
color: var(--text-mid);
|
||||
background: var(--bg-2);
|
||||
}
|
||||
|
||||
/* Accent marks the one active thing. It never fills the rail. */
|
||||
.console .rail-item[aria-current='page'] {
|
||||
color: var(--accent);
|
||||
background: var(--accent-dim);
|
||||
}
|
||||
|
||||
.console .rail-item[aria-current='page']::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -10px;
|
||||
top: 10px;
|
||||
bottom: 10px;
|
||||
width: 3px;
|
||||
border-radius: 0 2px 2px 0;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.console .rail-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.console .topbar {
|
||||
height: 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 0 20px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
background: var(--bg-1);
|
||||
}
|
||||
|
||||
.console .topbar-title {
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.console .topbar-readouts {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
color: var(--text-machine);
|
||||
}
|
||||
|
||||
.console .main {
|
||||
display: grid;
|
||||
grid-template-rows: 56px 1fr;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.console .page {
|
||||
overflow-y: auto;
|
||||
padding: 22px 24px 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.console .page-head {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 14px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.console .page-head p {
|
||||
margin: 0;
|
||||
color: var(--text-mid);
|
||||
}
|
||||
|
||||
/* ── command surface ─────────────────────────────────────────────────── */
|
||||
|
||||
.console .cmdk-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
height: 34px;
|
||||
min-width: 260px;
|
||||
max-width: 520px;
|
||||
flex: 1;
|
||||
padding: 0 12px;
|
||||
background: var(--bg-2);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--r-sm);
|
||||
color: var(--text-lo);
|
||||
cursor: pointer;
|
||||
transition: border-color var(--fast) var(--ease);
|
||||
}
|
||||
|
||||
.console .cmdk-trigger:hover {
|
||||
border-color: var(--line-hi);
|
||||
}
|
||||
|
||||
kbd {
|
||||
font-size: 11px;
|
||||
padding: 2px 6px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 5px;
|
||||
background: var(--bg-1);
|
||||
}
|
||||
|
||||
.console .cmdk-scrim {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(10, 8, 6, 0.62);
|
||||
display: grid;
|
||||
place-items: start center;
|
||||
padding-top: 12vh;
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.console .cmdk-panel {
|
||||
width: min(620px, 92vw);
|
||||
background: var(--bg-1);
|
||||
border: 1px solid var(--line-hi);
|
||||
border-radius: var(--r-md);
|
||||
box-shadow: var(--shadow-soft);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.console .cmdk-input {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
padding: 0 16px;
|
||||
background: none;
|
||||
border: 0;
|
||||
border-bottom: 1px solid var(--line);
|
||||
color: var(--text-hi);
|
||||
font: inherit;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.console .cmdk-list {
|
||||
max-height: 46vh;
|
||||
overflow-y: auto;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.console .cmdk-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
padding: 9px 10px;
|
||||
border: 0;
|
||||
border-radius: var(--r-sm);
|
||||
background: none;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.console .cmdk-row[data-active='true'] {
|
||||
background: var(--bg-3);
|
||||
}
|
||||
|
||||
/* ── panels and tables ───────────────────────────────────────────────── */
|
||||
|
||||
.console .panel {
|
||||
background: var(--bg-1);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--r-md);
|
||||
}
|
||||
|
||||
.console .panel-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.console .panel-head .count {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
padding: 1px 7px;
|
||||
border-radius: 999px;
|
||||
background: var(--bg-3);
|
||||
color: var(--text-machine);
|
||||
}
|
||||
|
||||
.console .panel-head .trailing {
|
||||
margin-left: auto;
|
||||
color: var(--text-lo);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.console .panel-body {
|
||||
padding: 14px 18px;
|
||||
}
|
||||
|
||||
.console .rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.console .row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 12px 18px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
text-align: left;
|
||||
background: none;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-top: 0;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
transition: background var(--fast) var(--ease);
|
||||
}
|
||||
|
||||
.console .row:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.console .row:hover {
|
||||
background: var(--bg-2);
|
||||
}
|
||||
|
||||
.console .row-main {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.console .row-title {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.console .row-sub {
|
||||
color: var(--text-mid);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.console .table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.console .table th {
|
||||
text-align: left;
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--text-lo);
|
||||
font-weight: 500;
|
||||
padding: 10px 14px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.console .table td {
|
||||
padding: 12px 14px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.console .table tbody tr:last-child td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.console .table tbody tr:hover {
|
||||
background: var(--bg-2);
|
||||
}
|
||||
|
||||
.console .table-scroll {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* ── status, phases, values ──────────────────────────────────────────── */
|
||||
|
||||
.console .dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.console .dot[data-health='healthy'] {
|
||||
background: #5fbf82;
|
||||
}
|
||||
|
||||
.console .dot[data-health='degraded'] {
|
||||
background: #d8a657;
|
||||
}
|
||||
|
||||
.console .dot[data-health='error'] {
|
||||
background: #e06c60;
|
||||
}
|
||||
|
||||
.console .dot[data-health='unknown'] {
|
||||
background: var(--text-lo);
|
||||
}
|
||||
|
||||
.console .chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 3px 9px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--bg-2);
|
||||
color: var(--text-mid);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.console .chip[data-tone='accent'] {
|
||||
border-color: var(--accent-line);
|
||||
background: var(--accent-dim);
|
||||
color: var(--accent-hi);
|
||||
}
|
||||
|
||||
.console .chip[data-tone='warn'] {
|
||||
border-color: rgba(216, 166, 87, 0.32);
|
||||
background: rgba(216, 166, 87, 0.12);
|
||||
color: #d8a657;
|
||||
}
|
||||
|
||||
.console .chip[data-tone='fault'] {
|
||||
border-color: rgba(224, 108, 96, 0.32);
|
||||
background: rgba(224, 108, 96, 0.12);
|
||||
color: #e06c60;
|
||||
}
|
||||
|
||||
.console .chip[data-tone='done'] {
|
||||
border-color: rgba(95, 191, 130, 0.28);
|
||||
background: rgba(95, 191, 130, 0.1);
|
||||
color: #5fbf82;
|
||||
}
|
||||
|
||||
/* The routing fork, rendered as the phase path. Completed is quiet green,
|
||||
the current phase carries the accent, the future stays neutral. */
|
||||
.console .phases {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.console .phase-step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
min-width: 46px;
|
||||
}
|
||||
|
||||
.console .phase-node {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 50%;
|
||||
border: 1.6px solid var(--text-lo);
|
||||
}
|
||||
|
||||
.console .phase-step[data-state='done'] .phase-node {
|
||||
border-color: #5fbf82;
|
||||
background: rgba(95, 191, 130, 0.35);
|
||||
}
|
||||
|
||||
.console .phase-step[data-state='current'] .phase-node {
|
||||
border-color: var(--accent);
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 0 3px var(--accent-dim);
|
||||
}
|
||||
|
||||
.console .phase-step .phase-name {
|
||||
font-size: 9px;
|
||||
color: var(--text-lo);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.console .phase-step[data-state='current'] .phase-name {
|
||||
color: var(--accent-hi);
|
||||
}
|
||||
|
||||
.console .phase-link {
|
||||
height: 1.6px;
|
||||
width: 18px;
|
||||
background: var(--line-hi);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.console .phase-link[data-state='done'] {
|
||||
background: rgba(95, 191, 130, 0.4);
|
||||
}
|
||||
|
||||
.console .meter {
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
background: var(--bg-3);
|
||||
overflow: hidden;
|
||||
min-width: 64px;
|
||||
}
|
||||
|
||||
.console .meter > span {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
/* ── actions ─────────────────────────────────────────────────────────── */
|
||||
|
||||
.console .btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
height: 32px;
|
||||
padding: 0 13px;
|
||||
border-radius: var(--r-sm);
|
||||
border: 1px solid var(--line);
|
||||
background: var(--bg-2);
|
||||
color: var(--text-hi);
|
||||
cursor: pointer;
|
||||
transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease);
|
||||
}
|
||||
|
||||
.console .btn:hover {
|
||||
background: var(--bg-3);
|
||||
border-color: var(--line-hi);
|
||||
}
|
||||
|
||||
.console .btn[data-variant='primary'] {
|
||||
background: var(--accent-dim);
|
||||
border-color: var(--accent-line);
|
||||
color: var(--accent-hi);
|
||||
}
|
||||
|
||||
.console .btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* ── empty, error, gap ───────────────────────────────────────────────── */
|
||||
|
||||
.console .empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 44px 20px;
|
||||
text-align: center;
|
||||
color: var(--text-mid);
|
||||
}
|
||||
|
||||
.console .empty .motif {
|
||||
color: var(--accent);
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.console .empty h3 {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
color: var(--text-hi);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* A screen with no endpoint says so, with the path it would call. Law 5: an
|
||||
interface never implies data it does not have. */
|
||||
.console .gap-note {
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
color: var(--text-machine);
|
||||
background: var(--bg-2);
|
||||
border: 1px dashed var(--line-hi);
|
||||
border-radius: var(--r-sm);
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.console .grid {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.console .stats {
|
||||
grid-template-columns: repeat(auto-fit, minmax(184px, 1fr));
|
||||
}
|
||||
|
||||
.console .stat {
|
||||
background: var(--bg-1);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--r-md);
|
||||
padding: 14px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.console .stat-value {
|
||||
font-family: var(--mono);
|
||||
font-size: 26px;
|
||||
color: var(--text-hi);
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
/* ── mobile: rail becomes a bottom tab bar, readouts relocate ────────── */
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.console.shell {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr auto;
|
||||
}
|
||||
|
||||
.console .rail {
|
||||
order: 2;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
padding: 4px 0;
|
||||
border-right: 0;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.rail-mark,
|
||||
.rail-spacer,
|
||||
.console .rail-item[data-secondary='true'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.console .rail-item[aria-current='page']::before {
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
top: -4px;
|
||||
bottom: auto;
|
||||
width: auto;
|
||||
height: 3px;
|
||||
border-radius: 0 0 2px 2px;
|
||||
}
|
||||
|
||||
.console .topbar-readouts {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.console .page {
|
||||
padding: 16px 14px 28px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
transition: none !important;
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user