feat(dashboard): split monolithic CSS into common + per-page bundles
dashboard.css was 2500+ lines covering every page. Split into four
separate esbuild entry points:
- common.css — @hive/shared imports + shared typography, badges,
buttons, inbox/compose, side panel, diff panel,
path linkification, logs-header chrome (shared by
flow + logs)
- dashboard.css — dashboard-only styles (tabs, container rows,
approvals, questions, permissions, schedules, etc.)
- flow.css — flow page chrome (frosted header, composer, inbox pill)
- logs.css — log viewer sub-tabs (journal-*, build-logs-*)
Each HTML file now loads common.css + its own page-specific bundle.
build.mjs updated to produce all four CSS outputs. All builds pass.
Closes #1056
This commit is contained in:
parent
ed6f40ba40
commit
ea5115c3a6
8 changed files with 949 additions and 1136 deletions
510
frontend/packages/dashboard/src/common.css
Normal file
510
frontend/packages/dashboard/src/common.css
Normal file
|
|
@ -0,0 +1,510 @@
|
|||
/* Shared Catppuccin palette + body typography + terminal pane styles.
|
||||
Bundled in front of the page-specific rules via esbuild. */
|
||||
@import "@hive/shared/base.css";
|
||||
@import "@hive/shared/terminal.css";
|
||||
|
||||
/* ─── global typography ─────────────────────────────────────────────
|
||||
Element-level rules shared across all three pages (index, flow,
|
||||
logs). These must not reference page-specific chrome classes. */
|
||||
|
||||
h1, h2 {
|
||||
color: var(--purple);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.15em;
|
||||
margin-top: 2em;
|
||||
text-shadow: 0 0 8px rgba(203, 166, 247, 0.4);
|
||||
}
|
||||
.divider {
|
||||
color: var(--purple-dim);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
ul { list-style: none; padding-left: 0; }
|
||||
li { padding: 0.5em 0; }
|
||||
.glyph { color: var(--purple); margin-right: 0.5em; }
|
||||
a {
|
||||
color: var(--cyan);
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 0 4px rgba(137, 220, 235, 0.5);
|
||||
}
|
||||
a:hover {
|
||||
color: var(--fg);
|
||||
text-shadow: 0 0 12px rgba(137, 220, 235, 0.9);
|
||||
}
|
||||
code {
|
||||
color: var(--amber);
|
||||
background: var(--bg-elev);
|
||||
padding: 0.1em 0.4em;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 2px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* ─── shared inline labels ──────────────────────────────────────────
|
||||
.meta / .id / .agent / .empty appear in message rows, build-log
|
||||
toolbars, and side-panel content rendered by common.js. */
|
||||
.meta { color: var(--muted); font-size: 0.85em; margin-left: 0.4em; }
|
||||
.id { color: var(--pink); font-weight: bold; margin-right: 0.4em; }
|
||||
.agent { color: var(--amber); font-weight: bold; margin-right: 0.6em; }
|
||||
.empty { color: var(--muted); font-style: italic; }
|
||||
|
||||
/* ─── status badges ─────────────────────────────────────────────────
|
||||
.badge base + semantic colour variants. Used on container rows
|
||||
(tabs.js) and build-log rows (logs.js). */
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.05em 0.5em;
|
||||
border: 1px solid;
|
||||
border-radius: 2px;
|
||||
font-size: 0.75em;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.badge-warn {
|
||||
color: var(--amber); border-color: var(--amber);
|
||||
text-shadow: 0 0 6px rgba(250, 179, 135, 0.5);
|
||||
}
|
||||
.badge-rate-limited {
|
||||
color: var(--red); border-color: var(--red);
|
||||
text-shadow: 0 0 6px rgba(243, 139, 168, 0.5);
|
||||
}
|
||||
.badge-muted {
|
||||
color: var(--muted); border-color: var(--purple-dim);
|
||||
background: rgba(127, 132, 156, 0.08);
|
||||
}
|
||||
.badge-reminder {
|
||||
color: var(--cyan); border-color: var(--cyan);
|
||||
text-shadow: 0 0 6px rgba(137, 220, 235, 0.4);
|
||||
}
|
||||
/* Context-window usage badges on dashboard container rows. */
|
||||
.badge-ctx-ok {
|
||||
color: var(--green); border-color: var(--green);
|
||||
opacity: 0.85;
|
||||
}
|
||||
.badge-ctx-caution {
|
||||
color: var(--amber); border-color: var(--amber);
|
||||
text-shadow: 0 0 6px rgba(250, 179, 135, 0.5);
|
||||
}
|
||||
.badge-ctx-warn {
|
||||
color: var(--red); border-color: var(--red);
|
||||
text-shadow: 0 0 6px rgba(243, 139, 168, 0.5);
|
||||
}
|
||||
.badge-ok { background: rgba(166,227,161,0.12); color: var(--green); border-color: var(--green); }
|
||||
.badge-fail { background: rgba(243,139,168,0.12); color: var(--red); border-color: var(--red); }
|
||||
.badge-running { background: rgba(250,179,135,0.12); color: var(--amber); border-color: var(--amber); }
|
||||
|
||||
/* ─── buttons ───────────────────────────────────────────────────────
|
||||
.btn base + semantic colour/size modifiers. Logs page uses
|
||||
.btn-restart for the ↻ refresh buttons; dashboard uses all variants. */
|
||||
.btn {
|
||||
font-family: inherit;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
background: transparent;
|
||||
border: 1px solid;
|
||||
padding: 0.25em 0.8em;
|
||||
cursor: pointer;
|
||||
text-shadow: 0 0 4px currentColor;
|
||||
box-shadow: 0 0 0 0 currentColor;
|
||||
transition: box-shadow 0.15s ease;
|
||||
}
|
||||
.btn:hover {
|
||||
background: rgba(205, 214, 244, 0.06);
|
||||
text-shadow: 0 0 10px currentColor;
|
||||
box-shadow: 0 0 10px -2px currentColor;
|
||||
}
|
||||
.btn:disabled,
|
||||
.btn[disabled] {
|
||||
opacity: 0.32;
|
||||
cursor: not-allowed;
|
||||
text-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.btn:disabled:hover,
|
||||
.btn[disabled]:hover {
|
||||
background: transparent;
|
||||
text-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.btn-approve { color: var(--green); border-color: var(--green); }
|
||||
.btn-deny { color: var(--red); border-color: var(--red); }
|
||||
.btn-destroy { color: var(--red); border-color: var(--red); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; }
|
||||
.btn-rebuild { color: var(--amber); border-color: var(--amber); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; }
|
||||
.btn-restart { color: var(--cyan); border-color: var(--cyan); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; }
|
||||
.btn-stop { color: var(--pink); border-color: var(--pink); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; }
|
||||
.btn-start { color: var(--green); border-color: var(--green); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; }
|
||||
/* M0V3 affordance (selection bar) — mauve reads as "structural
|
||||
change" rather than the destructive red / amber chrome of
|
||||
destroy / rebuild. See docs/web-ui.md::Selection bar. */
|
||||
.btn-move { color: var(--purple); border-color: var(--purple); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; }
|
||||
.btn-talk { color: var(--cyan); border-color: var(--cyan); }
|
||||
.btn-spawn { color: var(--amber); border-color: var(--amber); }
|
||||
.btn-fire-now { color: var(--mauve, #cba6f7); border-color: var(--mauve, #cba6f7); }
|
||||
/* Post-fire flash: report renders directly on the button for ~1.5s
|
||||
so the operator sees ok/failed/missing/consumed counts inline
|
||||
without a modal. Green when at least one ok; muted otherwise. */
|
||||
.btn-fire-now-flashed {
|
||||
color: var(--green);
|
||||
border-color: var(--green);
|
||||
text-shadow: 0 0 6px currentColor;
|
||||
box-shadow: 0 0 8px -2px currentColor;
|
||||
}
|
||||
/* Inline edit button on each schedule row. Yellow reads as a
|
||||
parallel destructive-adjacent action (edit changes state, but
|
||||
isn't deletion). */
|
||||
.btn-edit-schedule { color: var(--yellow, #f9e2af); border-color: var(--yellow, #f9e2af); }
|
||||
|
||||
/* ─── file-preview / diff panel (common.js Panel) ───────────────────
|
||||
Side-panel content rendered by openFilePanel / buildTabbedPreview
|
||||
in common.js; all three pages load the Panel singleton. */
|
||||
.diff-panel { display: flex; flex-direction: column; gap: 0.6em; }
|
||||
.diff-base-tabs { display: flex; flex-wrap: wrap; gap: 0.4em; }
|
||||
.diff-base-tab {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
font: inherit;
|
||||
font-size: 0.85em;
|
||||
padding: 0.2em 0.7em;
|
||||
cursor: pointer;
|
||||
}
|
||||
.diff-base-tab:hover { color: var(--fg); }
|
||||
.diff-base-tab.active {
|
||||
color: var(--purple);
|
||||
border-color: var(--purple);
|
||||
background: rgba(203, 166, 247, 0.08);
|
||||
}
|
||||
/* Image / tabbed file preview */
|
||||
.preview-host { margin-top: 0.5em; }
|
||||
.img-preview {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin: 0 auto;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
/* checkerboard so transparent regions of the image read clearly */
|
||||
background: repeating-conic-gradient(#313244 0% 25%, #1e1e2e 0% 50%) 50% / 18px 18px;
|
||||
}
|
||||
|
||||
/* Path linkification — agents drop pointer strings into messages;
|
||||
clicking the anchor opens the file in the side panel. */
|
||||
.path-link {
|
||||
color: var(--blue, #89b4fa);
|
||||
text-decoration: underline dotted;
|
||||
cursor: pointer;
|
||||
}
|
||||
.path-link:hover { color: var(--amber); }
|
||||
/* File-preview body — rendered inside the side panel. */
|
||||
.path-preview-body {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
padding: 0.5em 0.7em;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-size: 0.85em;
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
/* ─── page chrome: header + back link (flow + logs pages) ───────────
|
||||
.logs-header / .logs-back / .logs-title are used by both
|
||||
/flow.html and /logs.html — the same frosted sticky bar pattern. */
|
||||
.logs-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 25;
|
||||
background: rgba(30, 30, 46, 0.92);
|
||||
-webkit-backdrop-filter: blur(8px) saturate(120%);
|
||||
backdrop-filter: blur(8px) saturate(120%);
|
||||
border-bottom: 1px solid var(--purple-dim);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5em;
|
||||
padding: 0.5em 1.5em;
|
||||
}
|
||||
|
||||
.logs-back {
|
||||
color: var(--purple);
|
||||
text-decoration: none;
|
||||
font-size: 0.88em;
|
||||
white-space: nowrap;
|
||||
flex: none;
|
||||
}
|
||||
.logs-back:hover { text-decoration: underline; }
|
||||
|
||||
.logs-title {
|
||||
color: var(--subtext0);
|
||||
font-size: 0.85em;
|
||||
letter-spacing: 0.05em;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
/* ─── operator inbox + message rows ────────────────────────────────
|
||||
.inbox and .msg-* are rendered by common.js (renderInbox) and
|
||||
flow.js — both the dashboard side panel and the /flow.html page
|
||||
display broker messages. */
|
||||
.inbox {
|
||||
background: var(--bg-elev);
|
||||
border: 1px solid var(--border);
|
||||
padding: 0.5em 0.8em;
|
||||
/* No max-height cap — let the inbox grow to fill the
|
||||
side-panel-body which already scrolls. */
|
||||
}
|
||||
.inbox li {
|
||||
padding: 0.25em 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto 1fr;
|
||||
gap: 0.5em;
|
||||
align-items: baseline;
|
||||
}
|
||||
.inbox li:last-child { border-bottom: 0; }
|
||||
.inbox .msg-ts { color: var(--muted); font-size: 0.85em; }
|
||||
.inbox .msg-from { color: var(--amber); }
|
||||
.inbox .msg-sep { color: var(--muted); }
|
||||
.inbox .msg-body { color: var(--fg); white-space: pre-wrap; word-break: break-word; }
|
||||
/* `#msgflow` is a shared `.live` pane inside `.terminal-wrap`. The
|
||||
msgrow / msg-* rules below power both the dashboard CALL tab and
|
||||
the /flow.html full-page terminal. */
|
||||
.live .msgrow {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 0.5em;
|
||||
padding: 0.1em 0;
|
||||
text-indent: 0;
|
||||
}
|
||||
.live .msgrow .msg-body {
|
||||
flex: 1 1 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
.live .msgrow.sent .msg-arrow { color: var(--cyan); }
|
||||
.live .msgrow.delivered .msg-arrow { color: var(--green); }
|
||||
/* Reply-thread rendering: indented border-left + muted reply tag. */
|
||||
.live .msgrow.msg-reply {
|
||||
padding-left: 1.2em;
|
||||
border-left: 2px solid var(--border);
|
||||
margin-left: 0.6em;
|
||||
}
|
||||
.msg-reply-tag {
|
||||
color: var(--muted);
|
||||
font-size: 0.8em;
|
||||
white-space: nowrap;
|
||||
order: -1;
|
||||
}
|
||||
.msg-reply-tag a {
|
||||
color: var(--muted);
|
||||
text-shadow: none;
|
||||
font-weight: normal;
|
||||
}
|
||||
.msg-reply-tag a:hover { color: var(--fg); }
|
||||
@keyframes msg-highlight-fade {
|
||||
from { background: rgba(203, 166, 247, 0.18); }
|
||||
to { background: transparent; }
|
||||
}
|
||||
.msg-highlight { animation: msg-highlight-fade 1.5s ease-out forwards; }
|
||||
.msg-ts { color: var(--muted); font-size: 0.85em; }
|
||||
.msg-arrow { font-weight: bold; }
|
||||
.msg-from { color: var(--amber); }
|
||||
.msg-sep { color: var(--muted); }
|
||||
.msg-to { color: var(--pink); }
|
||||
.msg-body { color: var(--fg); white-space: pre-wrap; word-break: break-word; }
|
||||
|
||||
/* ─── operator compose box ──────────────────────────────────────────
|
||||
Sits inside `.terminal-wrap` on both /flow.html and the dashboard
|
||||
CALL tab. Dashed top separator mirrors the agent terminal prompt. */
|
||||
.op-compose {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.6em;
|
||||
padding: 0.55em 0.8em;
|
||||
border-top: 1px dashed var(--purple-dim);
|
||||
}
|
||||
.op-compose-prompt {
|
||||
color: var(--purple);
|
||||
text-shadow: 0 0 4px currentColor;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
padding-top: 0.15em;
|
||||
}
|
||||
.op-compose-input {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
color: var(--fg);
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
font-size: 1em;
|
||||
line-height: 1.4;
|
||||
resize: none;
|
||||
overflow: hidden;
|
||||
max-height: 8em;
|
||||
padding: 0;
|
||||
}
|
||||
.op-compose-input:focus { outline: none; }
|
||||
.op-compose-input::placeholder { color: var(--muted); }
|
||||
.op-compose-suggest {
|
||||
position: absolute;
|
||||
left: 3em;
|
||||
bottom: 100%;
|
||||
z-index: 50;
|
||||
background: var(--bg-elev);
|
||||
border: 1px solid var(--purple-dim);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0.3em 0;
|
||||
min-width: 12em;
|
||||
max-height: 14em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.op-compose-suggest .item {
|
||||
padding: 0.35em 0.9em;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
color: var(--muted);
|
||||
}
|
||||
.op-compose-suggest .item.active,
|
||||
.op-compose-suggest .item:hover {
|
||||
background: var(--border);
|
||||
color: var(--purple);
|
||||
}
|
||||
|
||||
/* ─── side panel ─────────────────────────────────────────────────
|
||||
Long content (file previews, diffs, journald, applied config)
|
||||
opens in a drawer that swipes in from the right — used by both
|
||||
/flow.html and the dashboard. */
|
||||
.side-panel {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 200;
|
||||
pointer-events: none;
|
||||
}
|
||||
.side-panel-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(17, 17, 27, 0.55);
|
||||
opacity: 0;
|
||||
transition: opacity 200ms ease;
|
||||
}
|
||||
.side-panel-drawer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: min(42em, 95vw);
|
||||
background: var(--bg-elev);
|
||||
border-left: 1px solid var(--purple-dim);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transform: translateX(100%);
|
||||
transition: transform 220ms ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
.side-panel.open { pointer-events: auto; }
|
||||
.side-panel.open .side-panel-backdrop { opacity: 1; }
|
||||
.side-panel.open .side-panel-drawer { transform: translateX(0); }
|
||||
.side-panel-resize {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 6px;
|
||||
cursor: ew-resize;
|
||||
opacity: 0;
|
||||
transition: opacity 120ms;
|
||||
background: var(--purple-dim);
|
||||
}
|
||||
.side-panel-resize:hover,
|
||||
body.side-panel-resizing .side-panel-resize {
|
||||
opacity: 1;
|
||||
}
|
||||
body.side-panel-resizing .side-panel-resize {
|
||||
background: var(--purple);
|
||||
}
|
||||
body.side-panel-resizing {
|
||||
user-select: none;
|
||||
}
|
||||
body.side-panel-resizing * { cursor: ew-resize !important; }
|
||||
.side-panel-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6em;
|
||||
padding: 0.6em 1em;
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex: none;
|
||||
}
|
||||
.side-panel-title {
|
||||
flex: 1;
|
||||
font-size: 0.88em;
|
||||
color: var(--muted);
|
||||
letter-spacing: 0.04em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.side-panel-close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 3px;
|
||||
color: var(--muted);
|
||||
font-size: 1em;
|
||||
line-height: 1;
|
||||
padding: 0.15em 0.4em;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
.side-panel-close:hover { border-color: var(--red); color: var(--red); }
|
||||
.side-panel-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0.8em 1em;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
.side-panel-body .md { color: var(--fg); line-height: 1.5; }
|
||||
.side-panel-body .md > :first-child { margin-top: 0; }
|
||||
.side-panel-body .md > :last-child { margin-bottom: 0; }
|
||||
.side-panel-body .md p { margin: 0.5em 0; }
|
||||
.side-panel-body .md h1,
|
||||
.side-panel-body .md h2,
|
||||
.side-panel-body .md h3,
|
||||
.side-panel-body .md h4 { color: var(--purple); margin: 0.9em 0 0.4em; }
|
||||
.side-panel-body .md code {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 2px;
|
||||
padding: 0.1em 0.3em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.side-panel-body .md pre {
|
||||
background: var(--crust);
|
||||
border: 1px solid var(--border);
|
||||
padding: 0.5em 0.7em;
|
||||
overflow-x: auto;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.side-panel-body .md pre code { background: none; border: none; padding: 0; }
|
||||
.side-panel-body .md a { color: var(--cyan); }
|
||||
.side-panel-body .md ul,
|
||||
.side-panel-body .md ol { margin: 0.4em 0; padding-left: 1.5em; }
|
||||
.side-panel-body .md blockquote {
|
||||
border-left: 3px solid var(--purple-dim);
|
||||
padding-left: 0.8em;
|
||||
margin: 0.4em 0;
|
||||
color: var(--muted);
|
||||
}
|
||||
.side-panel-body .md table { border-collapse: collapse; margin: 0.5em 0; }
|
||||
.side-panel-body .md th,
|
||||
.side-panel-body .md td {
|
||||
border: 1px solid var(--border);
|
||||
padding: 0.2em 0.5em;
|
||||
}
|
||||
Loading…
Reference in a new issue