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
|
agent SUB-pages (stats, screen) the same back-link nav the dashboard's
|
||||||
standalone pages use. The live terminal page keeps its own header. */
|
standalone pages use. The live terminal page keeps its own header. */
|
||||||
@import "@hive/shared/chrome.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";
|
||||||
/* Themed dialog/toast component CSS (modal.js) now lives in each
|
/* Themed dialog/toast component CSS (modal.js) now lives in each
|
||||||
component's own shadow root (adoptedStyleSheets), not a global
|
component's own shadow root (adoptedStyleSheets), not a global
|
||||||
stylesheet — nothing to @import here any more. */
|
stylesheet — nothing to @import here any more. */
|
||||||
|
|
@ -300,16 +304,16 @@ h2, h3 {
|
||||||
background: color-mix(in srgb, var(--purple) 6%, transparent);
|
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 {
|
.header-pill {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 1px solid var(--purple-dim);
|
border-color: var(--purple-dim);
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 0.85em;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
border-radius: 999px;
|
|
||||||
padding: 0.25em 0.7em;
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.4em;
|
gap: 0.4em;
|
||||||
|
|
@ -602,14 +606,11 @@ pre.diff {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
letter-spacing: 0.05em;
|
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,
|
.model-chip,
|
||||||
.effort-chip {
|
.effort-chip {
|
||||||
display: inline-block;
|
border-color: var(--purple-dim);
|
||||||
padding: 0.1em 0.6em;
|
|
||||||
border: 1px solid var(--purple-dim);
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 0.78em;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
}
|
}
|
||||||
.model-chip { color: var(--cyan); }
|
.model-chip { color: var(--cyan); }
|
||||||
.effort-chip { color: var(--amber); }
|
.effort-chip { color: var(--amber); }
|
||||||
|
|
@ -618,28 +619,15 @@ pre.diff {
|
||||||
breakdown on hover. Sized/coloured like a peer of model-chip so
|
breakdown on hover. Sized/coloured like a peer of model-chip so
|
||||||
the state row reads as one row of chrome. */
|
the state row reads as one row of chrome. */
|
||||||
.ctx-badge {
|
.ctx-badge {
|
||||||
display: inline-block;
|
border-color: var(--purple-dim);
|
||||||
padding: 0.1em 0.6em;
|
|
||||||
border: 1px solid var(--purple-dim);
|
|
||||||
border-radius: 999px;
|
|
||||||
color: var(--green);
|
color: var(--green);
|
||||||
font-size: 0.78em;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
cursor: default;
|
cursor: default;
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Harness reachability badge. Same chip shape + sizing as
|
/* Harness reachability badge. Markup carries `hive-pill` alongside
|
||||||
`.state-badge` / `.model-chip` so the state row stays visually
|
`status-badge` (shared pill.css supplies the shape); colour
|
||||||
uniform; colour communicates the actual reachability state. */
|
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;
|
|
||||||
}
|
|
||||||
.status-badge.status-loading { color: var(--muted); border-color: var(--purple-dim); }
|
.status-badge.status-loading { color: var(--muted); border-color: var(--purple-dim); }
|
||||||
.status-badge.status-online { color: var(--green); border-color: var(--green);
|
.status-badge.status-online { color: var(--green); border-color: var(--green);
|
||||||
text-shadow: 0 0 6px color-mix(in srgb, var(--green) 55%, transparent); }
|
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
|
the overflow menu (`.overflow-item-new-session` covers it; the
|
||||||
`:disabled` opacity treatment lives on the shared
|
`:disabled` opacity treatment lives on the shared
|
||||||
`.overflow-item:disabled` rule). */
|
`.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 {
|
.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,
|
transition: color 280ms ease, border-color 280ms ease,
|
||||||
box-shadow 280ms ease, background 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 def = STATE_LABELS[stateName] || STATE_LABELS.loading;
|
||||||
const age = fmtAge(Date.now() - stateSince);
|
const age = fmtAge(Date.now() - stateSince);
|
||||||
badge.textContent = def.glyph + ' ' + def.text + ' · ' + age;
|
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;
|
badge.title = (STATE_TOOLTIPS[stateName] || '') + '\nin this state for ' + age;
|
||||||
const cancelBtn = $('cancel-btn');
|
const cancelBtn = $('cancel-btn');
|
||||||
if (cancelBtn) cancelBtn.hidden = stateName !== 'thinking';
|
if (cancelBtn) cancelBtn.hidden = stateName !== 'thinking';
|
||||||
|
|
@ -1057,7 +1057,7 @@ window.marked = marked;
|
||||||
if (!el_) return;
|
if (!el_) return;
|
||||||
const def = ALIVE_LABELS[status] || ALIVE_LABELS.loading;
|
const def = ALIVE_LABELS[status] || ALIVE_LABELS.loading;
|
||||||
el_.textContent = def.glyph + ' ' + def.text;
|
el_.textContent = def.glyph + ' ' + def.text;
|
||||||
el_.className = 'status-badge ' + def.cls;
|
el_.className = 'hive-pill status-badge ' + def.cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderModelChip(model) {
|
function renderModelChip(model) {
|
||||||
|
|
|
||||||
|
|
@ -35,12 +35,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="state-row" class="agent-state-row agent-header-row">
|
<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="alive-badge" class="hive-pill status-badge status-loading" title="harness reachability">…</span>
|
||||||
<span id="state-badge" class="state-badge state-loading">… booting</span>
|
<span id="state-badge" class="hive-pill state-badge state-loading">… booting</span>
|
||||||
<span id="model-chip" class="model-chip" hidden></span>
|
<span id="model-chip" class="hive-pill-sm model-chip" hidden></span>
|
||||||
<span id="effort-chip" class="effort-chip" hidden></span>
|
<span id="effort-chip" class="hive-pill-sm effort-chip" hidden></span>
|
||||||
<span id="ctx-badge" class="ctx-badge" hidden title="tokens used in the current context window"></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="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="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>
|
<span id="last-turn" class="last-turn" hidden></span>
|
||||||
<button type="button" id="cancel-btn" class="btn-cancel-turn" hidden>■ cancel turn</button>
|
<button type="button" id="cancel-btn" class="btn-cancel-turn" hidden>■ cancel turn</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -52,13 +52,13 @@
|
||||||
it — see docs/web-ui.md::Per-agent page (Overflow button)
|
it — see docs/web-ui.md::Per-agent page (Overflow button)
|
||||||
for the rare-destructive-extra-click rationale. -->
|
for the rare-destructive-extra-click rationale. -->
|
||||||
<div class="agent-header-pills">
|
<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">
|
title="open inbox flyout">
|
||||||
<span class="header-pill-icon" aria-hidden="true">📬</span>
|
<span class="header-pill-icon" aria-hidden="true">📬</span>
|
||||||
<span class="header-pill-label">inbox</span>
|
<span class="header-pill-label">inbox</span>
|
||||||
<span class="header-pill-count" id="inbox-count">0</span>
|
<span class="header-pill-count" id="inbox-count">0</span>
|
||||||
</button>
|
</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">
|
title="open todos flyout">
|
||||||
<span class="header-pill-icon" aria-hidden="true">📋</span>
|
<span class="header-pill-icon" aria-hidden="true">📋</span>
|
||||||
<span class="header-pill-label">todos</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 li = el('li', { class: 'build-logs-item', 'data-log-id': String(h.id) });
|
||||||
const live = !h.status;
|
const live = !h.status;
|
||||||
const ok = h.status === 'ok';
|
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 statusLabel = live ? 'live' : ok ? 'ok' : 'fail';
|
||||||
const age = h.finished_at ? fmtTs(h.finished_at) : (live ? '' : fmtTs(h.started_at));
|
const age = h.finished_at ? fmtTs(h.finished_at) : (live ? '' : fmtTs(h.started_at));
|
||||||
const runtime = h.runtime_secs != null
|
const runtime = h.runtime_secs != null
|
||||||
|
|
@ -398,7 +398,7 @@ async function fetchBuild() {
|
||||||
);
|
);
|
||||||
if (live) {
|
if (live) {
|
||||||
const pre = el('pre', { class: 'build-logs-output build-logs-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);
|
detail.append(badge, pre);
|
||||||
|
|
||||||
streamEs = openBuildLogStream(h.id, pre, {
|
streamEs = openBuildLogStream(h.id, pre, {
|
||||||
|
|
@ -408,7 +408,7 @@ async function fetchBuild() {
|
||||||
const elapsed = Math.floor(Date.now() / 1000) - h.started_at;
|
const elapsed = Math.floor(Date.now() / 1000) - h.started_at;
|
||||||
runtime.textContent = fmtDuration(Math.max(0, elapsed));
|
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;
|
badge.textContent = status;
|
||||||
streamEs = null;
|
streamEs = null;
|
||||||
detail.dataset.loaded = '1';
|
detail.dataset.loaded = '1';
|
||||||
|
|
@ -416,7 +416,7 @@ async function fetchBuild() {
|
||||||
onError: () => {
|
onError: () => {
|
||||||
if (durTimer) { clearInterval(durTimer); durTimer = null; }
|
if (durTimer) { clearInterval(durTimer); durTimer = null; }
|
||||||
badge.textContent = 'stream error';
|
badge.textContent = 'stream error';
|
||||||
badge.className = 'badge badge-fail';
|
badge.className = 'hive-pill-sm badge-fail';
|
||||||
streamEs = null;
|
streamEs = null;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@
|
||||||
@import "@hive/shared/terminal.css";
|
@import "@hive/shared/terminal.css";
|
||||||
@import "@hive/shared/tabs.css";
|
@import "@hive/shared/tabs.css";
|
||||||
@import "@hive/shared/chrome.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 ─────────────────────────────────────────────
|
/* ─── global typography ─────────────────────────────────────────────
|
||||||
Element-level rules shared across all three pages (index, flow,
|
Element-level rules shared across all three pages (index, flow,
|
||||||
|
|
@ -53,17 +57,11 @@ code {
|
||||||
.empty { color: var(--muted); font-style: italic; }
|
.empty { color: var(--muted); font-style: italic; }
|
||||||
|
|
||||||
/* ─── status badges ─────────────────────────────────────────────────
|
/* ─── status badges ─────────────────────────────────────────────────
|
||||||
.badge base + semantic colour variants. Used on container rows
|
Semantic colour variants only — shape comes from `hive-pill-sm`
|
||||||
(tabs.js) and build-log rows (logs.js). */
|
(shared with the agent UI's pill/chip family, @hive/shared/pill.css),
|
||||||
.badge {
|
carried directly in the markup alongside each of these classnames
|
||||||
display: inline-block;
|
(swarm.js/core.js/builds.js). Used on container rows and build-log
|
||||||
padding: 0.05em 0.5em;
|
rows. */
|
||||||
border: 1px solid;
|
|
||||||
border-radius: 2px;
|
|
||||||
font-size: 0.75em;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
.badge-warn {
|
.badge-warn {
|
||||||
color: var(--amber); border-color: var(--amber);
|
color: var(--amber); border-color: var(--amber);
|
||||||
text-shadow: 0 0 6px color-mix(in srgb, var(--amber) 50%, transparent);
|
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),
|
el('span', { class: 'name' }, t.name),
|
||||||
// Was `destroyed`, which the backend cannot actually know — see the
|
// Was `destroyed`, which the backend cannot actually know — see the
|
||||||
// caveat above. `offline` is what the absence of a container proves.
|
// 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' },
|
head.append(el('span', { class: 'meta' },
|
||||||
`${fmtBytes(t.state_bytes)} · ${fmtAgeDays(t.last_seen)}`));
|
`${fmtBytes(t.state_bytes)} · ${fmtAgeDays(t.last_seen)}`));
|
||||||
li.append(head);
|
li.append(head);
|
||||||
|
|
@ -117,7 +117,7 @@ function renderStalePerms(root, ghosts) {
|
||||||
for (const name of ghosts) {
|
for (const name of ghosts) {
|
||||||
const li = el('li', { class: 'tombstones-stale-row' });
|
const li = el('li', { class: 'tombstones-stale-row' });
|
||||||
li.append(el('span', { class: 'tombstones-stale-name' }, name));
|
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', {
|
const btn = el('button', {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
class: 'btn btn-destroy',
|
class: 'btn btn-destroy',
|
||||||
|
|
@ -398,7 +398,7 @@ function renderInfraContainers(rows) {
|
||||||
const head = el('div', { class: 'head' });
|
const head = el('div', { class: 'head' });
|
||||||
head.append(
|
head.append(
|
||||||
el('span', { class: 'name' }, c.name),
|
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'),
|
c.running ? 'running' : 'stopped'),
|
||||||
);
|
);
|
||||||
li.append(head);
|
li.append(head);
|
||||||
|
|
|
||||||
|
|
@ -448,7 +448,7 @@ function buildContainerLi(c, node, opts) {
|
||||||
// already shown (pending / not-running already handled sync).
|
// already shown (pending / not-running already handled sync).
|
||||||
if (ds.rate_limited) {
|
if (ds.rate_limited) {
|
||||||
head.append(el('span',
|
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'));
|
'⊘ rate limited'));
|
||||||
}
|
}
|
||||||
// ctx-window badge
|
// ctx-window badge
|
||||||
|
|
@ -464,7 +464,7 @@ function buildContainerLi(c, node, opts) {
|
||||||
? `last turn context: ${ds.ctx_tokens.toLocaleString()} / ${win.toLocaleString()} `
|
? `last turn context: ${ds.ctx_tokens.toLocaleString()} / ${win.toLocaleString()} `
|
||||||
+ `tokens (${Math.round((ds.ctx_tokens / win) * 100)}% of the window)`
|
+ `tokens (${Math.round((ds.ctx_tokens / win) * 100)}% of the window)`
|
||||||
: `last turn context size: ${ds.ctx_tokens.toLocaleString()} tokens`;
|
: `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) ---------
|
// -- agent status text (self-reported via set_status) ---------
|
||||||
if (ds.status_text) {
|
if (ds.status_text) {
|
||||||
|
|
@ -516,32 +516,32 @@ function buildContainerLi(c, node, opts) {
|
||||||
} else if (!c.running && c.failed) {
|
} else if (!c.running && c.failed) {
|
||||||
head.append(el('span',
|
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 '
|
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',
|
+ 'on its own, not on operator request; start it to bring the harness back up',
|
||||||
},
|
},
|
||||||
'✖ gave up'));
|
'✖ gave up'));
|
||||||
} else if (!c.running) {
|
} else if (!c.running) {
|
||||||
head.append(el('span',
|
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'));
|
'■ not running'));
|
||||||
} else if (c.needs_login) {
|
} else if (c.needs_login) {
|
||||||
head.append(el('a',
|
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 →'));
|
'needs login →'));
|
||||||
}
|
}
|
||||||
if (c.paused) {
|
if (c.paused) {
|
||||||
// Paused badge is also a resume button: clicking POSTs /api/resume/{name}
|
// 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.
|
// which removes the marker and flips the badge off via the SSE rescan.
|
||||||
head.append(form(
|
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.`,
|
`resume ${c.name}? the turn loop restarts and drains queued messages.`,
|
||||||
{}, { noRefresh: true },
|
{}, { noRefresh: true },
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if (c.needs_update) {
|
if (c.needs_update) {
|
||||||
head.append(form(
|
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.',
|
'rebuild ' + c.name + '? hot-reloads the container.',
|
||||||
{}, { noRefresh: true },
|
{}, { noRefresh: true },
|
||||||
));
|
));
|
||||||
|
|
@ -549,7 +549,7 @@ function buildContainerLi(c, node, opts) {
|
||||||
|
|
||||||
if (c.active_model) {
|
if (c.active_model) {
|
||||||
head.append(el('span',
|
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));
|
c.active_model));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -563,7 +563,7 @@ function buildContainerLi(c, node, opts) {
|
||||||
if (targetCount > 0) parts.push(`${targetCount} to answer`);
|
if (targetCount > 0) parts.push(`${targetCount} to answer`);
|
||||||
head.append(el('span',
|
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`,
|
title: `pending questions: ${parts.join(', ')} — see the Q33R1ES tab`,
|
||||||
},
|
},
|
||||||
`❓ ${agentQCount}`));
|
`❓ ${agentQCount}`));
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
"./base.css": "./src/base.css",
|
"./base.css": "./src/base.css",
|
||||||
"./terminal.css": "./src/terminal/terminal.css",
|
"./terminal.css": "./src/terminal/terminal.css",
|
||||||
"./chrome.css": "./src/chrome.css",
|
"./chrome.css": "./src/chrome.css",
|
||||||
|
"./pill.css": "./src/pill.css",
|
||||||
"./forms.js": "./src/forms.js",
|
"./forms.js": "./src/forms.js",
|
||||||
"./prefs.js": "./src/prefs.js",
|
"./prefs.js": "./src/prefs.js",
|
||||||
"./dom.js": "./src/dom.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