Compare commits

..
9 changed files with 39 additions and 107 deletions

View file

@ -6,7 +6,6 @@
import { linkify as termLinkify } from '@hive/shared/terminal.js'; import { linkify as termLinkify } from '@hive/shared/terminal.js';
import { el } from '@hive/shared/dom.js'; import { el } from '@hive/shared/dom.js';
import '@hive/shared/side-panel.js'; // registers <hive-side-panel> — side-effect import import '@hive/shared/side-panel.js'; // registers <hive-side-panel> — side-effect import
import '@hive/shared/hive-warn.js'; // registers <hive-warn> — side-effect import
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
// ─── helpers ──────────────────────────────────────────────────────────── // ─── helpers ────────────────────────────────────────────────────────────

View file

@ -50,7 +50,7 @@ function renderTombstones(s) {
// Wording is deliberately about what the list *is* rather than what it // Wording is deliberately about what the list *is* rather than what it
// isn't: nothing records a destroy, so "container absent" is the only thing // isn't: nothing records a destroy, so "container absent" is the only thing
// the backend can actually tell. // the backend can actually tell.
const warn = el('hive-warn', { level: 'warning' }); const warn = el('p', { class: 'tombstone-warn' });
warn.append( warn.append(
el('strong', {}, 'shows every agent whose container is absent'), el('strong', {}, 'shows every agent whose container is absent'),
' — not only destroyed ones. An agent part-way through being spawned ' + ' — not only destroyed ones. An agent part-way through being spawned ' +

View file

@ -27,6 +27,15 @@ body.cred-shell {
.cred-pane[hidden] { display: none; } .cred-pane[hidden] { display: none; }
.cred-warning {
border: 1px solid var(--amber);
border-radius: 4px;
padding: 0.7rem 0.9rem;
margin: 0.6rem 0 1.1rem;
font-size: 0.9rem;
background: color-mix(in srgb, var(--amber) 10%, transparent);
}
.gh-status { margin: 0.5rem 0 1.2rem; } .gh-status { margin: 0.5rem 0 1.2rem; }
.gh-status-line { .gh-status-line {
display: flex; display: flex;

View file

@ -90,7 +90,7 @@
Security-warning banner + a link to generate a PAT. --> Security-warning banner + a link to generate a PAT. -->
<section class="cred-pane" id="cred-pane-github" data-tab-pane="github" <section class="cred-pane" id="cred-pane-github" data-tab-pane="github"
role="tabpanel" aria-labelledby="cred-tab-github" hidden> role="tabpanel" aria-labelledby="cred-tab-github" hidden>
<hive-warn level="warning"> <div class="cred-warning">
⚠ use a <strong>dedicated bot account</strong>, not a human's &mdash; ⚠ use a <strong>dedicated bot account</strong>, not a human's &mdash;
and a <strong>minimally-scoped</strong> personal access token (only and a <strong>minimally-scoped</strong> personal access token (only
the repos/scopes the agent actually needs, e.g. <code>repo</code> + the repos/scopes the agent actually needs, e.g. <code>repo</code> +
@ -98,7 +98,7 @@
anything within the token's scopes is reachable if the agent is anything within the token's scopes is reachable if the agent is
ever compromised. the token is injected into the agent's state dir ever compromised. the token is injected into the agent's state dir
and is <strong>never displayed back</strong> on this page. and is <strong>never displayed back</strong> on this page.
</hive-warn> </div>
<h3>◇ status</h3> <h3>◇ status</h3>
<div id="gh-status" class="gh-status"><p class="meta">select an agent to see its github credential status.</p></div> <div id="gh-status" class="gh-status"><p class="meta">select an agent to see its github credential status.</p></div>

View file

@ -444,14 +444,33 @@ hive-agent-menu {
} }
.container-row.tombstone .name { color: var(--muted); } .container-row.tombstone .name { color: var(--muted); }
/* K3PT ST4T3 caveat and the port-collision banner both moved to the /* K3PT ST4T3 caveat. Amber, not red like `.port-conflict`, and no pulse: this
shared <hive-warn> component amber/no-pulse for is a standing "read the list this way" note, not an incident a pulsing
the standing caveat (a pulsing banner that's always present just banner that is always present just teaches you to stop seeing it.
teaches you to stop seeing it), `severity="red" pulse` for the port Nothing records a destroy, so this list is every agent whose container is
collision (an incident, not a caveat). See core.js/swarm.js and absent which includes one being spawned right now. */
hive-warn.css for the actual styling now. .tombstone-warn {
background: color-mix(in srgb, var(--amber) 8%, transparent);
border: 1px solid var(--amber);
color: var(--amber);
padding: 0.5em 0.8em;
margin-bottom: 0.6em;
border-radius: 4px;
}
.tombstone-warn strong { color: var(--amber); }
Notification controls below sit between the banner and the containers. */ /* Notification controls — sit between the banner and the containers. */
.port-conflict {
background: color-mix(in srgb, var(--red) 8%, transparent);
border: 1px solid var(--red);
color: var(--red);
padding: 0.5em 0.8em;
margin-bottom: 0.6em;
border-radius: 4px;
text-shadow: 0 0 6px color-mix(in srgb, var(--red) 40%, transparent);
animation: questions-pulse 2.4s ease-in-out infinite;
}
.port-conflict strong { color: var(--red); }
/* Build-queue summary banner on the SW4RM tab: one compact line /* Build-queue summary banner on the SW4RM tab: one compact line
when the rebuild queue has active work, with a link to the full queue on when the rebuild queue has active work, with a link to the full queue on

View file

@ -582,7 +582,7 @@ export function renderContainers(s) {
// rebuild. The banner sits above the agent list so it's the // rebuild. The banner sits above the agent list so it's the
// first thing the operator sees when something's wedged. // first thing the operator sees when something's wedged.
if (portConflicts.length) { if (portConflicts.length) {
const banner = el('hive-warn', { level: 'error' }, const banner = el('div', { class: 'port-conflict' },
el('strong', {}, '⚠ port collision'), ' — '); el('strong', {}, '⚠ port collision'), ' — ');
const groups = portConflicts.map((c) => const groups = portConflicts.map((c) =>
`:${c.port} (${c.agents.join(' + ')})`).join('; '); `:${c.port} (${c.agents.join(' + ')})`).join('; ');

View file

@ -22,7 +22,6 @@
"./modal.js": "./src/modal.js", "./modal.js": "./src/modal.js",
"./shadow-css.js": "./src/shadow-css.js", "./shadow-css.js": "./src/shadow-css.js",
"./hive-menu.js": "./src/hive-menu/hive-menu.js", "./hive-menu.js": "./src/hive-menu/hive-menu.js",
"./hive-warn.js": "./src/hive-warn/hive-warn.js",
"./side-panel.js": "./src/side-panel/hive-side-panel.js", "./side-panel.js": "./src/side-panel/hive-side-panel.js",
"./jobq-graph.js": "./src/jobq-graph/hive-jobq-graph.js" "./jobq-graph.js": "./src/jobq-graph/hive-jobq-graph.js"
}, },

View file

@ -1,53 +0,0 @@
/* hive-warn.css shadow-DOM stylesheet for the <hive-warn> autonomous
custom element (hive-warn.js). `level` is an attribute on the HOST
(how callers set it, `hive-warn.js`'s own header explains why)
`:host([level="…"])` sets `color`/`border-color`/`background`/
whether it pulses, same pattern as hive-btn.css's `[variant]`.
Three levels, not an open-ended severity+modifier combination (mara,
on review: "there should be distinction between info, warning, error
all warnings should be styled identically"). Colours reuse
theme.css's own documented semantics rather than inventing new
ones: --cyan is already "info accents", --amber already "warnings",
--red already "errors, fail state" (see theme.css's :root comments).
Pulse is baked into `error` specifically, not a separate opt-in
knob the one call site that wants attention-grabbing (an active
incident) is also the one call site that's semantically `error`, so
tying the two together removes a combination that shouldn't exist
independently of the tier. */
:host {
display: block;
border: 1px solid var(--amber);
border-radius: 4px;
padding: 0.5em 0.8em;
margin-bottom: 0.6em;
color: var(--amber);
background: color-mix(in srgb, var(--amber) 8%, transparent);
}
:host([level="info"]) {
border-color: var(--cyan);
color: var(--cyan);
background: color-mix(in srgb, var(--cyan) 8%, transparent);
}
:host([level="error"]) {
border-color: var(--red);
color: var(--red);
background: color-mix(in srgb, var(--red) 8%, transparent);
/* Only `error` pulses an active incident, not a standing caveat.
A banner that's always present and always pulsing just trains you
to stop seeing it, so `info`/`warning` deliberately stay still.
`currentColor` so the glow matches without a second colour switch. */
text-shadow: 0 0 6px color-mix(in srgb, currentColor 40%, transparent);
animation: hive-warn-pulse 2.4s ease-in-out infinite;
}
@keyframes hive-warn-pulse {
0%, 100% { box-shadow: 0 0 12px -4px color-mix(in srgb, currentColor 55%, transparent); }
50% { box-shadow: 0 0 22px -2px color-mix(in srgb, currentColor 95%, transparent); }
}
/* Explicit rather than relying on plain inheritance matches what
every one of the three original rules declared, kept for parity. */
::slotted(strong) {
color: inherit;
}

View file

@ -1,41 +0,0 @@
// hive-warn.js — <hive-warn>, the shared inline warning-banner
// component. Consolidates three independently-written instances of the
// same thing: `.cred-warning` (credentials.html, static
// markup), `.tombstone-warn` (core.js, JS-built), `.port-conflict`
// (swarm.js, JS-built) — the first two differed only by an
// undeliberate 10% vs 8% tint, the strongest evidence this was drift,
// not three genuinely different needs.
//
// Purely presentational — no internal state, no lifecycle beyond
// attaching its shadow root once. `level` ('info' | 'warning' | 'error',
// default 'warning') is read directly by hive-warn.css's
// `:host([level="…"])` selectors — a fixed three-tier semantic ladder,
// not an open severity+modifier combination (mara, on review: "there
// should be distinction between info, warning, error — all warnings
// should be styled identically"). Unlike `<hive-btn>`'s
// `variant`/`disabled` there's no inner native element that needs the
// attribute mirrored onto it, so no `attributeChangedCallback` is
// needed at all — the CSS reads the host attribute directly.
//
// Content is arbitrary light-DOM children through the shadow tree's
// single default `<slot>` — same reuse of the existing `<strong>`/
// `<code>` markup every call site already writes, no caller-side
// rewrite of their message content needed.
//
// Usage:
// `<hive-warn level="warning">...</hive-warn>` (static HTML)
// `el('hive-warn', { level: 'warning' }, ...)` (JS-built)
// `el('hive-warn', { level: 'error' }, ...)` (active incident, pulses)
import { attachShadowCss } from '../shadow-css.js';
import hiveWarnCss from './hive-warn.css';
class HiveWarn extends HTMLElement {
connectedCallback() {
if (this._built) return; // re-parenting re-fires connectedCallback
const root = attachShadowCss(this, hiveWarnCss);
root.append(document.createElement('slot'));
this._built = true;
}
}
customElements.define('hive-warn', HiveWarn);