Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dd6d05d6c | ||
|
|
92b9e67327 |
9 changed files with 97 additions and 73 deletions
|
|
@ -6,6 +6,10 @@
|
|||
agent SUB-pages (stats, screen) the same back-link nav the dashboard's
|
||||
standalone pages use. The live terminal page keeps its own header. */
|
||||
@import "@hive/shared/chrome.css";
|
||||
/* Shared pill/chip/badge shape (the "extract pill component" issue,
|
||||
resolved as shared CSS rather than a JS component) — see pill.css
|
||||
itself for the classnames it covers. */
|
||||
@import "@hive/shared/pill.css";
|
||||
/* Themed dialog/toast component CSS (modal.js) now lives in each
|
||||
component's own shadow root (adoptedStyleSheets), not a global
|
||||
stylesheet — nothing to @import here any more. */
|
||||
|
|
@ -300,16 +304,16 @@ h2, h3 {
|
|||
background: color-mix(in srgb, var(--purple) 6%, transparent);
|
||||
}
|
||||
|
||||
/* Header pill — inbox / loose-ends triggers. Compact, count-prominent. */
|
||||
/* Header pill — inbox / loose-ends triggers. Compact, count-prominent.
|
||||
Markup carries `hive-pill` alongside `header-pill` (shared pill.css
|
||||
supplies border/radius/padding/font-size/letter-spacing; `display`
|
||||
is the one thing overridden here, to `inline-flex`, for the icon+
|
||||
label+count row layout — see pill.css's own note on that). */
|
||||
.header-pill {
|
||||
background: transparent;
|
||||
border: 1px solid var(--purple-dim);
|
||||
border-color: var(--purple-dim);
|
||||
color: var(--fg);
|
||||
font-family: inherit;
|
||||
font-size: 0.85em;
|
||||
letter-spacing: 0.04em;
|
||||
border-radius: 999px;
|
||||
padding: 0.25em 0.7em;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4em;
|
||||
|
|
@ -602,14 +606,11 @@ pre.diff {
|
|||
font-size: 0.8em;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
/* Markup carries `hive-pill-sm` alongside each of these (shared
|
||||
pill.css supplies the shape); rules below are colour only. */
|
||||
.model-chip,
|
||||
.effort-chip {
|
||||
display: inline-block;
|
||||
padding: 0.1em 0.6em;
|
||||
border: 1px solid var(--purple-dim);
|
||||
border-radius: 999px;
|
||||
font-size: 0.78em;
|
||||
letter-spacing: 0.04em;
|
||||
border-color: var(--purple-dim);
|
||||
}
|
||||
.model-chip { color: var(--cyan); }
|
||||
.effort-chip { color: var(--amber); }
|
||||
|
|
@ -618,28 +619,15 @@ pre.diff {
|
|||
breakdown on hover. Sized/coloured like a peer of model-chip so
|
||||
the state row reads as one row of chrome. */
|
||||
.ctx-badge {
|
||||
display: inline-block;
|
||||
padding: 0.1em 0.6em;
|
||||
border: 1px solid var(--purple-dim);
|
||||
border-radius: 999px;
|
||||
border-color: var(--purple-dim);
|
||||
color: var(--green);
|
||||
font-size: 0.78em;
|
||||
letter-spacing: 0.04em;
|
||||
cursor: default;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
/* Harness reachability badge. Same chip shape + sizing as
|
||||
`.state-badge` / `.model-chip` so the state row stays visually
|
||||
uniform; colour communicates the actual reachability state. */
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 0.25em 0.8em;
|
||||
border: 1px solid;
|
||||
border-radius: 999px;
|
||||
font-size: 0.85em;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
/* Harness reachability badge. Markup carries `hive-pill` alongside
|
||||
`status-badge` (shared pill.css supplies the shape); colour
|
||||
communicates the actual reachability state. */
|
||||
.status-badge.status-loading { color: var(--muted); border-color: var(--purple-dim); }
|
||||
.status-badge.status-online { color: var(--green); border-color: var(--green);
|
||||
text-shadow: 0 0 6px color-mix(in srgb, var(--green) 55%, transparent); }
|
||||
|
|
@ -671,13 +659,10 @@ pre.diff {
|
|||
the overflow menu (`.overflow-item-new-session` covers it; the
|
||||
`:disabled` opacity treatment lives on the shared
|
||||
`.overflow-item:disabled` rule). */
|
||||
/* Markup carries `hive-pill` alongside `state-badge` (shared pill.css
|
||||
supplies the shape); this rule is only the transition + state-
|
||||
specific colour below. */
|
||||
.state-badge {
|
||||
display: inline-block;
|
||||
padding: 0.25em 0.8em;
|
||||
border: 1px solid;
|
||||
border-radius: 999px;
|
||||
font-size: 0.85em;
|
||||
letter-spacing: 0.05em;
|
||||
transition: color 280ms ease, border-color 280ms ease,
|
||||
box-shadow 280ms ease, background 280ms ease;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -793,7 +793,7 @@ window.marked = marked;
|
|||
const def = STATE_LABELS[stateName] || STATE_LABELS.loading;
|
||||
const age = fmtAge(Date.now() - stateSince);
|
||||
badge.textContent = def.glyph + ' ' + def.text + ' · ' + age;
|
||||
badge.className = 'state-badge state-' + stateName;
|
||||
badge.className = 'hive-pill state-badge state-' + stateName;
|
||||
badge.title = (STATE_TOOLTIPS[stateName] || '') + '\nin this state for ' + age;
|
||||
const cancelBtn = $('cancel-btn');
|
||||
if (cancelBtn) cancelBtn.hidden = stateName !== 'thinking';
|
||||
|
|
@ -1057,7 +1057,7 @@ window.marked = marked;
|
|||
if (!el_) return;
|
||||
const def = ALIVE_LABELS[status] || ALIVE_LABELS.loading;
|
||||
el_.textContent = def.glyph + ' ' + def.text;
|
||||
el_.className = 'status-badge ' + def.cls;
|
||||
el_.className = 'hive-pill status-badge ' + def.cls;
|
||||
}
|
||||
|
||||
function renderModelChip(model) {
|
||||
|
|
|
|||
|
|
@ -35,12 +35,12 @@
|
|||
</div>
|
||||
|
||||
<div id="state-row" class="agent-state-row agent-header-row">
|
||||
<span id="alive-badge" class="status-badge status-loading" title="harness reachability">…</span>
|
||||
<span id="state-badge" class="state-badge state-loading">… booting</span>
|
||||
<span id="model-chip" class="model-chip" hidden></span>
|
||||
<span id="effort-chip" class="effort-chip" hidden></span>
|
||||
<span id="ctx-badge" class="ctx-badge" hidden title="tokens used in the current context window"></span>
|
||||
<span id="cost-badge" class="ctx-badge" hidden title="cumulative tokens billed across the last turn (sum across every inference; tool-heavy turns rebill the cached prompt per call)"></span>
|
||||
<span id="alive-badge" class="hive-pill status-badge status-loading" title="harness reachability">…</span>
|
||||
<span id="state-badge" class="hive-pill state-badge state-loading">… booting</span>
|
||||
<span id="model-chip" class="hive-pill-sm model-chip" hidden></span>
|
||||
<span id="effort-chip" class="hive-pill-sm effort-chip" hidden></span>
|
||||
<span id="ctx-badge" class="hive-pill-sm ctx-badge" hidden title="tokens used in the current context window"></span>
|
||||
<span id="cost-badge" class="hive-pill-sm ctx-badge" hidden title="cumulative tokens billed across the last turn (sum across every inference; tool-heavy turns rebill the cached prompt per call)"></span>
|
||||
<span id="last-turn" class="last-turn" hidden></span>
|
||||
<button type="button" id="cancel-btn" class="btn-cancel-turn" hidden>■ cancel turn</button>
|
||||
</div>
|
||||
|
|
@ -52,13 +52,13 @@
|
|||
it — see docs/web-ui.md::Per-agent page (Overflow button)
|
||||
for the rare-destructive-extra-click rationale. -->
|
||||
<div class="agent-header-pills">
|
||||
<button type="button" id="inbox-pill" class="header-pill header-pill-inbox" hidden
|
||||
<button type="button" id="inbox-pill" class="hive-pill header-pill header-pill-inbox" hidden
|
||||
title="open inbox flyout">
|
||||
<span class="header-pill-icon" aria-hidden="true">📬</span>
|
||||
<span class="header-pill-label">inbox</span>
|
||||
<span class="header-pill-count" id="inbox-count">0</span>
|
||||
</button>
|
||||
<button type="button" id="todos-pill" class="header-pill header-pill-todos" hidden
|
||||
<button type="button" id="todos-pill" class="hive-pill header-pill header-pill-todos" hidden
|
||||
title="open todos flyout">
|
||||
<span class="header-pill-icon" aria-hidden="true">📋</span>
|
||||
<span class="header-pill-label">todos</span>
|
||||
|
|
|
|||
|
|
@ -344,7 +344,7 @@ async function fetchBuild() {
|
|||
const li = el('li', { class: 'build-logs-item', 'data-log-id': String(h.id) });
|
||||
const live = !h.status;
|
||||
const ok = h.status === 'ok';
|
||||
const statusClass = live ? 'badge badge-running' : ok ? 'badge badge-ok' : 'badge badge-fail';
|
||||
const statusClass = live ? 'hive-pill-sm badge-running' : ok ? 'hive-pill-sm badge-ok' : 'hive-pill-sm badge-fail';
|
||||
const statusLabel = live ? 'live' : ok ? 'ok' : 'fail';
|
||||
const age = h.finished_at ? fmtTs(h.finished_at) : (live ? '' : fmtTs(h.started_at));
|
||||
const runtime = h.runtime_secs != null
|
||||
|
|
@ -398,7 +398,7 @@ async function fetchBuild() {
|
|||
);
|
||||
if (live) {
|
||||
const pre = el('pre', { class: 'build-logs-output build-logs-live' }, '');
|
||||
const badge = el('span', { class: 'build-logs-live-badge badge badge-running' }, 'live');
|
||||
const badge = el('span', { class: 'build-logs-live-badge hive-pill-sm badge-running' }, 'live');
|
||||
detail.append(badge, pre);
|
||||
|
||||
streamEs = openBuildLogStream(h.id, pre, {
|
||||
|
|
@ -408,7 +408,7 @@ async function fetchBuild() {
|
|||
const elapsed = Math.floor(Date.now() / 1000) - h.started_at;
|
||||
runtime.textContent = fmtDuration(Math.max(0, elapsed));
|
||||
}
|
||||
badge.className = status === 'ok' ? 'badge badge-ok' : 'badge badge-fail';
|
||||
badge.className = status === 'ok' ? 'hive-pill-sm badge-ok' : 'hive-pill-sm badge-fail';
|
||||
badge.textContent = status;
|
||||
streamEs = null;
|
||||
detail.dataset.loaded = '1';
|
||||
|
|
@ -416,7 +416,7 @@ async function fetchBuild() {
|
|||
onError: () => {
|
||||
if (durTimer) { clearInterval(durTimer); durTimer = null; }
|
||||
badge.textContent = 'stream error';
|
||||
badge.className = 'badge badge-fail';
|
||||
badge.className = 'hive-pill-sm badge-fail';
|
||||
streamEs = null;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@
|
|||
@import "@hive/shared/terminal.css";
|
||||
@import "@hive/shared/tabs.css";
|
||||
@import "@hive/shared/chrome.css";
|
||||
/* Shared pill/chip/badge shape (the "extract pill component" issue,
|
||||
resolved as shared CSS rather than a JS component) — see pill.css
|
||||
itself for the classnames it covers. */
|
||||
@import "@hive/shared/pill.css";
|
||||
|
||||
/* ─── global typography ─────────────────────────────────────────────
|
||||
Element-level rules shared across all three pages (index, flow,
|
||||
|
|
@ -53,17 +57,11 @@ code {
|
|||
.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;
|
||||
}
|
||||
Semantic colour variants only — shape comes from `hive-pill-sm`
|
||||
(shared with the agent UI's pill/chip family, @hive/shared/pill.css),
|
||||
carried directly in the markup alongside each of these classnames
|
||||
(swarm.js/core.js/builds.js). Used on container rows and build-log
|
||||
rows. */
|
||||
.badge-warn {
|
||||
color: var(--amber); border-color: var(--amber);
|
||||
text-shadow: 0 0 6px color-mix(in srgb, var(--amber) 50%, transparent);
|
||||
|
|
|
|||
|
|
@ -66,9 +66,9 @@ function renderTombstones(s) {
|
|||
el('span', { class: 'name' }, t.name),
|
||||
// Was `destroyed`, which the backend cannot actually know — see the
|
||||
// caveat above. `offline` is what the absence of a container proves.
|
||||
el('span', { class: 'badge badge-muted' }, 'offline'),
|
||||
el('span', { class: 'hive-pill-sm badge-muted' }, 'offline'),
|
||||
);
|
||||
if (t.has_creds) head.append(el('span', { class: 'badge badge-muted' }, 'creds kept'));
|
||||
if (t.has_creds) head.append(el('span', { class: 'hive-pill-sm badge-muted' }, 'creds kept'));
|
||||
head.append(el('span', { class: 'meta' },
|
||||
`${fmtBytes(t.state_bytes)} · ${fmtAgeDays(t.last_seen)}`));
|
||||
li.append(head);
|
||||
|
|
@ -117,7 +117,7 @@ function renderStalePerms(root, ghosts) {
|
|||
for (const name of ghosts) {
|
||||
const li = el('li', { class: 'tombstones-stale-row' });
|
||||
li.append(el('span', { class: 'tombstones-stale-name' }, name));
|
||||
li.append(el('span', { class: 'badge badge-muted' }, 'stale perms'));
|
||||
li.append(el('span', { class: 'hive-pill-sm badge-muted' }, 'stale perms'));
|
||||
const btn = el('button', {
|
||||
type: 'button',
|
||||
class: 'btn btn-destroy',
|
||||
|
|
@ -398,7 +398,7 @@ function renderInfraContainers(rows) {
|
|||
const head = el('div', { class: 'head' });
|
||||
head.append(
|
||||
el('span', { class: 'name' }, c.name),
|
||||
el('span', { class: 'badge ' + (c.running ? 'badge-ok' : 'badge-fail') },
|
||||
el('span', { class: 'hive-pill-sm ' + (c.running ? 'badge-ok' : 'badge-fail') },
|
||||
c.running ? 'running' : 'stopped'),
|
||||
);
|
||||
li.append(head);
|
||||
|
|
|
|||
|
|
@ -448,7 +448,7 @@ function buildContainerLi(c, node, opts) {
|
|||
// already shown (pending / not-running already handled sync).
|
||||
if (ds.rate_limited) {
|
||||
head.append(el('span',
|
||||
{ class: 'badge badge-rate-limited', title: 'API rate-limited — harness is parked, will retry automatically' },
|
||||
{ class: 'hive-pill-sm badge-rate-limited', title: 'API rate-limited — harness is parked, will retry automatically' },
|
||||
'⊘ rate limited'));
|
||||
}
|
||||
// ctx-window badge
|
||||
|
|
@ -464,7 +464,7 @@ function buildContainerLi(c, node, opts) {
|
|||
? `last turn context: ${ds.ctx_tokens.toLocaleString()} / ${win.toLocaleString()} `
|
||||
+ `tokens (${Math.round((ds.ctx_tokens / win) * 100)}% of the window)`
|
||||
: `last turn context size: ${ds.ctx_tokens.toLocaleString()} tokens`;
|
||||
head.append(el('span', { class: `badge ${ctxClass}`, title }, `ctx·${k}k`));
|
||||
head.append(el('span', { class: `hive-pill-sm ${ctxClass}`, title }, `ctx·${k}k`));
|
||||
}
|
||||
// -- agent status text (self-reported via set_status) ---------
|
||||
if (ds.status_text) {
|
||||
|
|
@ -516,32 +516,32 @@ function buildContainerLi(c, node, opts) {
|
|||
} else if (!c.running && c.failed) {
|
||||
head.append(el('span',
|
||||
{
|
||||
class: 'badge badge-fail',
|
||||
class: 'hive-pill-sm badge-fail',
|
||||
title: 'container gave up — its unit hit systemd\'s bounded restart limit and stopped '
|
||||
+ 'on its own, not on operator request; start it to bring the harness back up',
|
||||
},
|
||||
'✖ gave up'));
|
||||
} else if (!c.running) {
|
||||
head.append(el('span',
|
||||
{ class: 'badge badge-muted', title: 'container is shut down — start it to bring the harness back up' },
|
||||
{ class: 'hive-pill-sm badge-muted', title: 'container is shut down — start it to bring the harness back up' },
|
||||
'■ not running'));
|
||||
} else if (c.needs_login) {
|
||||
head.append(el('a',
|
||||
{ class: 'badge badge-warn', href: url, target: '_blank', rel: 'noopener' },
|
||||
{ class: 'hive-pill-sm badge-warn', href: url, target: '_blank', rel: 'noopener' },
|
||||
'needs login →'));
|
||||
}
|
||||
if (c.paused) {
|
||||
// Paused badge is also a resume button: clicking POSTs /api/resume/{name}
|
||||
// which removes the marker and flips the badge off via the SSE rescan.
|
||||
head.append(form(
|
||||
'/api/resume/' + c.name, 'badge badge-paused btn-inline', '⏸ paused',
|
||||
'/api/resume/' + c.name, 'hive-pill-sm badge-paused btn-inline', '⏸ paused',
|
||||
`resume ${c.name}? the turn loop restarts and drains queued messages.`,
|
||||
{}, { noRefresh: true },
|
||||
));
|
||||
}
|
||||
if (c.needs_update) {
|
||||
head.append(form(
|
||||
'/api/rebuild/' + c.name, 'badge badge-warn btn-inline', 'needs update ↻',
|
||||
'/api/rebuild/' + c.name, 'hive-pill-sm badge-warn btn-inline', 'needs update ↻',
|
||||
'rebuild ' + c.name + '? hot-reloads the container.',
|
||||
{}, { noRefresh: true },
|
||||
));
|
||||
|
|
@ -549,7 +549,7 @@ function buildContainerLi(c, node, opts) {
|
|||
|
||||
if (c.active_model) {
|
||||
head.append(el('span',
|
||||
{ class: 'badge badge-model', title: `active claude model: ${c.active_model}` },
|
||||
{ class: 'hive-pill-sm badge-model', title: `active claude model: ${c.active_model}` },
|
||||
c.active_model));
|
||||
}
|
||||
|
||||
|
|
@ -563,7 +563,7 @@ function buildContainerLi(c, node, opts) {
|
|||
if (targetCount > 0) parts.push(`${targetCount} to answer`);
|
||||
head.append(el('span',
|
||||
{
|
||||
class: 'badge badge-loose-ends',
|
||||
class: 'hive-pill-sm badge-loose-ends',
|
||||
title: `pending questions: ${parts.join(', ')} — see the Q33R1ES tab`,
|
||||
},
|
||||
`❓ ${agentQCount}`));
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"./base.css": "./src/base.css",
|
||||
"./terminal.css": "./src/terminal/terminal.css",
|
||||
"./chrome.css": "./src/chrome.css",
|
||||
"./pill.css": "./src/pill.css",
|
||||
"./forms.js": "./src/forms.js",
|
||||
"./prefs.js": "./src/prefs.js",
|
||||
"./dom.js": "./src/dom.js",
|
||||
|
|
|
|||
40
frontend/packages/shared/src/pill.css
Normal file
40
frontend/packages/shared/src/pill.css
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/* Shared pill/chip/badge shape — the "extract pill component" issue,
|
||||
resolved as shared CSS rather than a JS component. `.hive-pill` and
|
||||
`.hive-pill-sm` are the ONLY two classnames this file names — every
|
||||
inline status/count chip across the dashboard + per-agent UI carries
|
||||
one of them (added alongside its own semantic colour modifier class,
|
||||
e.g. `class="hive-pill-sm badge-ok"`) instead of this file having to
|
||||
enumerate every call site's own base classname. Per mara's review on
|
||||
the first cut of this: naming every use here just relocates the
|
||||
duplication rather than removing it — the fix is unifying the
|
||||
classnames themselves, not fanning shared CSS out across old names.
|
||||
|
||||
Two size tiers, matching what was already organically in use:
|
||||
.hive-pill — primary state chips (agent status/state chips)
|
||||
.hive-pill-sm — secondary meta chips (agent model/effort/ctx chips,
|
||||
the dashboard's whole badge family)
|
||||
|
||||
`display: inline-block` is the default here since that covers every
|
||||
plain-text chip; the one exception (the agent header's icon+label+
|
||||
count flyout pills) overrides `display` locally to `inline-flex` —
|
||||
layout mode isn't part of the shape these two classes exist to
|
||||
share, so a single site overriding it is expected, not a gap. */
|
||||
|
||||
.hive-pill,
|
||||
.hive-pill-sm {
|
||||
display: inline-block;
|
||||
border: 1px solid;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.hive-pill {
|
||||
padding: 0.25em 0.8em;
|
||||
font-size: 0.85em;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.hive-pill-sm {
|
||||
padding: 0.1em 0.6em;
|
||||
font-size: 0.78em;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
Loading…
Reference in a new issue