Watch
0
0
Fork
You've already forked hyperhive
0

dashboard: the SW4RM tab is the agents tab

The swarm lives in the swarm UI; this tab lists the hive's agents. The tab
is 4G3NTS at #agents, its pane and count pill follow, and the module that
renders the containers is agents.js. Comments and docs that named SW4RM or
swarm.js follow.
This commit is contained in:
iris 2026-10-14 20:23:14 +02:00 • committed by mara
commit e455964cb3
16 changed files with 70 additions and 69 deletions

View file

@ -127,7 +127,7 @@ Two things live in the `agent-configs` Forgejo organization:
[How a hive reads the forge](#how-a-hive-reads-the-forge)).
- The dashboard links each container's deployed config commit to this
config repo on the swarm's forge (`swarm.forge.publicUrl`; `null` means no
link), so operators can go straight from the SW4RM tab into
link), so operators can go straight from the 4G3NTS tab into
the rendered repo without an extra `git` step.
The `hive-forge` CLI (separate workspace crate, see

View file

@ -18,7 +18,7 @@ never more than one step from the hub.
The **dashboard** itself (`/dashboard.html`) is where you'll spend most
of your time. It's a single page with five tabs:
- **SW4RM** — every agent, live. This is the default tab and the one
- **4G3NTS** — every agent, live. This is the default tab and the one
you'll check most.
- **R3BU1LD QU3U3**, **M3T4 1NPUTS** and **BUILD L0GS** — the build
lifecycle: the rebuild queue with a live log of whatever is building,
@ -37,7 +37,7 @@ if the gateway isn't in front).
## The things you'll actually do
**Check on an agent.** SW4RM shows every container as a row: name,
**Check on an agent.** 4G3NTS shows every container as a row: name,
whether it's running, what it's currently doing (a live status pill —
`rebuilding…`, `starting…`, or similar — while something's in flight),
and quick links (stats, screen, forge profile). Select the name to open
@ -49,7 +49,7 @@ repo. Review and merge them on the forge; the merge deploys the change.
See [`config-changes.md`](../agent-lifecycle/config-changes.md#config-changes).
**Start, stop, restart, or rebuild an agent.** Select one or more
agents on SW4RM (select the icon) and use the selection bar, or use the
agents on 4G3NTS (select the icon) and use the selection bar, or use the
per-agent `⋮` menu on a single row. Rebuilding re-applies that agent's
current config; use it whenever an agent
shows as "needs update."

View file

@ -10,7 +10,7 @@ matrix accounts, the controller's job graph — are on the
hive-c0re serves the dashboard at `/dashboard.html` (with the home page at `/`).
It has a fixed chrome header at the top and a `<main>` that shows exactly
one tab pane at a time. The URL hash (`#swarm`, `#rebuild`, `#meta`,
one tab pane at a time. The URL hash (`#agents`, `#rebuild`, `#meta`,
`#buildlogs`, `#permissions`) drives which pane is
active; hash changes don't reload the page. FL0W and the other
standalone pages are reachable from the H0M3 hub at `/`, not
@ -19,12 +19,12 @@ from the dashboard tab strip.
**Chrome header** (fixed, overlays the active tab pane):
- **← home back-link**: top-left of the chrome, links to the H0M3 hub
at `/`. Every surface links back to the hub rather than to each other.
- **Tab strip**: `◆ SW4RM ◆`, `◆ R3BU1LD QU3U3 ◆`, `◆ M3T4 1NPUTS ◆`,
- **Tab strip**: `◆ 4G3NTS ◆`, `◆ R3BU1LD QU3U3 ◆`, `◆ M3T4 1NPUTS ◆`,
`◆ BUILD L0GS ◆`, `◆ P3RM1SS10NS ◆`. In-page tabs only —
the SYST3M panels live on the
standalone **C0R3** page (`/core.html`), and FL0W / ST4TS
live on their own pages too, all reachable from the
H0M3 hub (not the tab strip). Count pills on SW4RM
H0M3 hub (not the tab strip). Count pills on 4G3NTS
(stale containers) and R3BU1LD QU3U3 (rebuilds in flight).
- **Banner-thin** (`░▒▓█▓▒░ HYPERHIVE / HIVE-C0RE / WE ARE THE WIRED ░▒▓█▓▒░`)
— sits below the tab strip.
@ -56,7 +56,7 @@ The FL0W page uses a slim header (a `← home` back-link + the page
title) rather than the dashboard tab strip — it's a standalone surface,
not a tab pane.
## SW4RM tab
## 4G3NTS tab
**C0NTAINERS** — live containers as a flat list, one row per
container, sorted alphabetically by agent name; every container the
@ -123,7 +123,7 @@ dashboard's one tab strip, `/api/state` snapshot and
`rebuild_queue_changed`, `meta_inputs_changed` and `meta_update_running`
on the stream alongside the dashboard's other kinds. The count pill on
R3BU1LD QU3U3 is the number of rebuilds in flight.
The SW4RM tab does **not** read the graph endpoint, by design — the swarm
The 4G3NTS tab does **not** read the graph endpoint, by design — the swarm
view stays independent of job-queue internals. Its per-agent pending
badges are transient-only, and its queue-summary banner reads the much
narrower `GET /api/jobq/rollup` instead (see Container row, below) — a
@ -414,7 +414,7 @@ the tooltip says "select … for bulk actions" or "deselect … (or press
Esc to clear all)." The `<img>` points at `<url>/icon`; load failure
falls back to the dimmed hyperhive mark (`/favicon.svg`). The card
body sits to the right with three stacked lines
(`assets/swarm.js::renderContainers`).
(`assets/agents.js::renderContainers`).
<!-- vale write-good.Passive = NO -->
**Icon layout + load strategy:** the `<img>` is absolutely
@ -586,7 +586,7 @@ queue — N running · M queued — view queue →` (the link goes to the
R3BU1LD QU3U3 tab). The shared `JobqRollup` Preact component
(`@hive/shared/jobq-rollup.js` — the same one swarm-ui's `/jobs` page
mounts, pointed at swarm-controller's own rollup endpoint instead),
mounted once into `#jobq-rollup-section` by `swarm.js::initJobqRollup`
mounted once into `#jobq-rollup-section` by `agents.js::initJobqRollup`
and refreshed via its own handle rather than being re-rendered by
`renderContainers`. Reads `GET /api/jobq/rollup`
(`hive-jobq-wire::state_rollup`) — `Vec<{ state, nodes, roots }>`,
@ -648,7 +648,7 @@ Bulk actions (`R3ST4RT` / `ST0P` / `ST4RT` / `P4US3` / `R3SUM3` /
buttons — see **Container row** above. Selecting an agent's icon
toggles its selection (an in-memory `Set<name>`); `Esc` or the bar's
`✕ clear` button drops everything. The selection persists across tab
switches in-memory — the bar just hides on non-SW4RM tabs since other
switches in-memory — the bar just hides on non-4G3NTS tabs since other
tabs don't show the agent cards needed to cross-reference.
When the operator selects one or more agents (via the icon), a sticky

View file

@ -208,7 +208,7 @@ re-render), call `refreshState()`. State shapes live in
`hive-c0re/src/dashboard/state_snapshot.rs::StateSnapshot` and
`web_ui/state.rs::StateSnapshot` — when
adding state fields, plumb through the snapshot struct and the
relevant domain module (`swarm.js`, `permissions.js`, etc.) render function.
relevant domain module (`agents.js`, `permissions.js`, etc.) render function.
**Focus preservation:** `refreshState` checks whether
`document.activeElement` sits inside one of the managed sections

View file

@ -30,7 +30,7 @@ layers per-agent additions on top of the default agent dist.
output is reproducible without network access at build time.
- **esbuild**: `agent` and `swarm-ui` are Preact apps; `dashboard` is a
vanilla-JS + custom-element MPA with a few Preact-rendered pages
(`builds.js`, `swarm.js`, imported by the dashboard entry); `shared` holds both kinds of module. No
(`builds.js`, `agents.js`, imported by the dashboard entry); `shared` holds both kinds of module. No
framework runtime beyond Preact where it's used. Each workspace's
`build.mjs` is a short esbuild script.
- **Single-PR migration**: see the tracked design proposal and

View file

@ -1,6 +1,6 @@
// hive-agent-menu.js — <hive-agent-menu>, the per-agent "⋮" context-menu
// custom element. One instance per agent card, appended after
// .card-body in the container row's flex layout (swarm.js's
// .card-body in the container row's flex layout (agents.js's
// buildContainerLi via buildAgentMenu).
//
// Deliberately a *light-DOM* element with no shadow root of its own: all
@ -66,7 +66,7 @@ async function agentMenuPost(actionPath, name, body, flags) {
class HiveAgentMenu extends HTMLElement {
connectedCallback() {
// Same reconnect-without-detach hazard as <hive-menu> (see its
// connectedCallback comment) — swarm.js's row cache can move an
// connectedCallback comment) — agents.js's row cache can move an
// already-built <li> subtree without a real detach. Without this
// guard a reconnect would append a second <hive-menu> on top of the
// first, doubling the dropdown.

View file

@ -1,4 +1,4 @@
// SW4RM (containers) domain — extracted from tabs.js.
// 4G3NTS (containers) domain — extracted from tabs.js.
// Agent topology, container-row rendering, selection bar, and all
// live-update apply handlers for container-state, transient, and
// job-queue-rollup ops. See docs/web-ui/dashboard.md::Container row for the
@ -52,7 +52,7 @@ const selectionState = new Set();
// ─── job queue rollup (queue-summary banner only) ──────────────────────────
//
// The ONE piece of job-queue-derived state on this page, and deliberately
// narrow — mara: "swarm.js should not need to pull in the jobq to do its
// narrow — mara: "agents.js should not need to pull in the jobq to do its
// job." Rendering is owned by the shared `JobqRollup` Preact component
// (same one swarm-ui's /jobs page mounts, self-fetching
// GET /api/jobq/rollup); this file just calls `render(h(...))` and bumps
@ -752,7 +752,7 @@ export function renderContainers(s) {
}
// ─── selection bar ───────────────────────────────────────────────────────────
// Sticky-bottom strip; visible when >=1 agent selected on the SW4RM
// Sticky-bottom strip; visible when >=1 agent selected on the 4G3NTS
// tab. See docs/web-ui/dashboard.md::Selection bar for the interaction model
// and the per-action availability rules (disabled-with-tooltip for
// actions that don't apply to the full selection). Actions POST per
@ -767,11 +767,12 @@ export function renderSelectionBar(containers) {
if (!countSpan || !namesSpan || !actions) return;
const selected = containers.filter((c) => selectionState.has(c.name));
// Tab-gate: bar only renders on SW4RM (the only tab with agent
// Tab-gate: bar only renders on 4G3NTS (the only tab with agent
// cards to cross-reference). Selection state lives in-memory and
// the bar reappears on return to SW4RM if still non-empty.
const onSwarmTab = (document.body.dataset.activeTab || "swarm") === "swarm";
if (!selected.length || !onSwarmTab) {
// the bar reappears on return to 4G3NTS if still non-empty.
const onAgentsTab =
(document.body.dataset.activeTab || "agents") === "agents";
if (!selected.length || !onAgentsTab) {
bar.hidden = true;
document.body.classList.remove("has-selection");
return;

View file

@ -84,7 +84,7 @@ code {
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
(agents.js/core.js/builds.js). Used on container rows and build-log
rows. */
.badge-warn {
color: var(--amber);

View file

@ -127,14 +127,14 @@ body.dashboard-shell {
font-weight: bold;
}
/* `display` above beats the UA's `[hidden]` rule, so an empty count (the
SW4RM and R3BU1LD QU3U3 pills at zero) would otherwise draw a "0". */
4G3NTS and R3BU1LD QU3U3 pills at zero) would otherwise draw a "0". */
.tab-count[hidden] {
display: none;
}
/* Tab pane visibility — createTabStrip (@hive/shared/tabs.js) sets the
`hidden` attribute on every inactive pane (resolved from the URL hash;
default — no hash — is SW4RM). Only the active pane stays visible. */
default — no hash — is 4G3NTS). Only the active pane stays visible. */
.tab-pane[hidden] {
display: none;
}
@ -322,7 +322,7 @@ hive-agent-menu {
--menu-btn-opacity: 1;
}
/* Card actions dim while a row has any pending-state badge (transient-
driven only now — see swarm.js's pending-badge derivation comment;
driven only now — see agents.js's pending-badge derivation comment;
there is no separate queued-but-not-running row state to tell apart
from this one anymore). */
.container-row.pending-running .actions {
@ -443,12 +443,12 @@ hive-agent-menu {
shared <hive-warn> component — amber/no-pulse for
the standing caveat (a pulsing banner that's always present just
teaches you to stop seeing it), `severity="red" pulse` for the port
collision (an incident, not a caveat). See core.js/swarm.js and
collision (an incident, not a caveat). See core.js/agents.js and
hive-warn.css for the actual styling now. */
/* Build-queue summary banner on the SW4RM tab moved to the shared
/* Build-queue summary banner on the 4G3NTS tab moved to the shared
`JobqRollup` Preact component (`.jqr-*` classes, imported above) —
mounted by swarm.js::initJobqRollup into #jobq-rollup-section. */
mounted by agents.js::initJobqRollup into #jobq-rollup-section. */
.pending-state {
color: var(--amber);

View file

@ -26,14 +26,14 @@
<nav class="tabbar" id="tabbar" role="tablist">
<a
class="tab"
id="tab-swarm"
href="#swarm"
id="tab-agents"
href="#agents"
role="tab"
aria-controls="tab-pane-swarm"
data-tab="swarm"
aria-controls="tab-pane-agents"
data-tab="agents"
>
<span class="tab-label">◆ SW4RM ◆</span>
<span class="tab-count" id="tab-count-swarm" hidden></span>
<span class="tab-label">◆ 4G3NTS ◆</span>
<span class="tab-count" id="tab-count-agents" hidden></span>
</a>
<!-- SYST3M moved to its own standalone page (/core.html, "C0R3"),
reached from the H0M3 hub — keeps the dashboard tab strip lean.
@ -89,7 +89,7 @@
view, not part of the operational tab strip. -->
<!-- (Peer hives are no longer a tab — they render as a headline
under SW4RM. See tab-pane-swarm.) -->
under 4G3NTS. See tab-pane-agents.) -->
<!-- FL0W and the other standalone pages are reachable from the
H0M3 hub (served at /), not from the dashboard tab strip —
@ -99,33 +99,33 @@
<!-- Tab panes. createTabStrip (@hive/shared/tabs.js) keeps exactly one
visible by toggling the `hidden` attribute, resolved from the URL
hash (default SW4RM). Panes start `hidden` to avoid a flash before
hash (default 4G3NTS). Panes start `hidden` to avoid a flash before
the script runs. -->
<!-- Padded content wrapper: carries the 1.5em side gutter
(.page-content, common.css) while <body> stays full-bleed so the
sticky chrome above + the footer below span the full width. -->
<div class="page-content">
<main class="dashboard-main">
<!-- SW4RM: the swarm itself. Container cards (the central thing
<!-- 4G3NTS: the agents. Container cards (the central thing
the operator looks at) and rebuild queue / cascade visualisation
that drives them. No inline `C0NTAINERS` h2 heading + divider
— the tab label SW4RM already says it. -->
— the tab label 4G3NTS already says it. -->
<section
class="tab-pane"
id="tab-pane-swarm"
data-tab-pane="swarm"
id="tab-pane-agents"
data-tab-pane="agents"
hidden
role="tabpanel"
aria-labelledby="tab-swarm"
aria-labelledby="tab-agents"
>
<!-- Swarm / hive identity headline. Populated by refreshState from
hive_name + swarm_name; stays hidden when neither is set. -->
<h2 id="swarm-identity" hidden></h2>
<!-- JobqRollup mount point, kept outside #containers-section
deliberately — that section is wiped + rebuilt on every
container-list render (see swarm.js::renderContainers), which
container-list render (see agents.js::renderContainers), which
would tear down and remount the Preact tree on every
container-state tick. Mounted once by swarm.js::initJobqRollup,
container-state tick. Mounted once by agents.js::initJobqRollup,
refreshed via its own handle rather than by re-rendering. -->
<div id="jobq-rollup-section"></div>
<div id="containers-section">

View file

@ -8,7 +8,7 @@
// live in `./common.js`; the flow-page surface (broker
// terminal, @-mention composer) lives in `./flow.js` — `flow.html` and
// `dashboard.html` each load their own bundle.
// SW4RM (containers) domain lives in `./swarm.js`.
// 4G3NTS (containers) domain lives in `./agents.js`.
import { marked } from "marked";
import { $, openStream, renderServerWarnings } from "./common.js";
@ -33,7 +33,7 @@ import {
applyTransientCleared,
renderContainers,
renderSelectionBar,
} from "./swarm.js";
} from "./agents.js";
import {
initBuilds,
onBuildsTabShown,
@ -126,7 +126,7 @@ window.marked = marked;
window.__hyperhive_state = s;
renderServerWarnings(s.server_warnings);
// Hive identity: render the swarm/hive name as a headline at the
// top of the SW4RM pane + update the page title once the
// top of the 4G3NTS pane + update the page title once the
// server-side display names are known. `hive_name` / `swarm_name`
// come from HYPERHIVE_HIVE_NAME / HYPERHIVE_SWARM_NAME env vars
// (set by services.hyperhive.{hiveName,swarmName} nix options).
@ -148,8 +148,8 @@ window.marked = marked;
// `transientsState` + `containersState`, not from `s.*`).
syncTransientsFromSnapshot(s);
syncContainersFromSnapshot(s);
// Job-queue rollup feeds only the SW4RM queue-summary banner
// (per-agent card badges are transient-only — see swarm.js). Its
// Job-queue rollup feeds only the 4G3NTS queue-summary banner
// (per-agent card badges are transient-only — see agents.js). Its
// own `JobqRollup` mount self-fetches GET /api/jobq/rollup — not
// read off `s` (this page's snapshot carries no jobq field).
// initJobqRollup mounts once and is a no-op on later calls; the
@ -182,7 +182,7 @@ window.marked = marked;
// ─── live updates: dashboard event stream ──────────────────────────────
// The dashboard subscribes to /dashboard/stream for live mutation
// events so the SW4RM / SYST3M panes update without an
// events so the 4G3NTS / SYST3M panes update without an
// operator action triggering a refreshState.
//
// Bare `EventSource` (no terminal infrastructure needed — the
@ -202,7 +202,7 @@ window.marked = marked;
container_state_changed: applyContainerStateChanged,
container_removed: applyContainerRemoved,
// rebuild_queue_changed: a payload-less push trigger. Refreshes the
// SW4RM queue-summary banner (see swarm.js) and the R3BU1LD QU3U3
// 4G3NTS queue-summary banner (see agents.js) and the R3BU1LD QU3U3
// tab's JobqGraph (see builds.js).
rebuild_queue_changed: () => {
applyRebuildQueueChanged();
@ -260,9 +260,9 @@ window.marked = marked;
})();
// ─── tab routing ───────────────────────────────────────────────────────
// Hash-based: `#swarm` / `#rebuild` / `#meta` / `#buildlogs` /
// Hash-based: `#agents` / `#rebuild` / `#meta` / `#buildlogs` /
// `#permissions` activate the matching pane on the dashboard. Empty
// hash defaults to SW4RM. FL0W and ST4TS are NOT tabs —
// hash defaults to 4G3NTS. FL0W and ST4TS are NOT tabs —
// they're separate pages (`/flow.html`, `/stats.html`) reached from
// the H0M3 hub. Tab routing only
// applies when the tab DOM is present (e.g. not on the flow page
@ -275,7 +275,7 @@ window.marked = marked;
// to their own pages rather than hide them in a dropdown.
function activateTab(target) {
// Track active tab on the body so renderSelectionBar can gate
// visibility (bar only belongs on SW4RM where agent cards live).
// visibility (bar only belongs on 4G3NTS where agent cards live).
document.body.dataset.activeTab = target;
renderSelectionBar(Array.from(containersState.values()));
// Permissions tables: SSE covers worker-applied changes
@ -292,9 +292,9 @@ window.marked = marked;
// Wire the shared tab strip now that activateTab + the lazy-load fns it
// calls are defined. The strip resolves the active tab from the hash
// (default SW4RM), toggles the active tab/pane + aria-selected, and
// (default 4G3NTS), toggles the active tab/pane + aria-selected, and
// fires activateTab for the per-tab side-effects on every change.
createTabStrip($("tabbar"), { defaultId: "swarm", onShow: activateTab });
createTabStrip($("tabbar"), { defaultId: "agents", onShow: activateTab });
// The rebuild queue mounts now, not on first show, so its tab's count pill
// is live from the start.
initBuilds();
@ -313,14 +313,14 @@ window.marked = marked;
/** Recompute every tab's count from the current state. Called on
* every renderXxx that's tab-relevant. */
function refreshTabCounts() {
// SW4RM — flag any container that's stale (needs_update). Empty
// 4G3NTS — flag any container that's stale (needs_update). Empty
// when everyone's current. Container-row pulse signals state
// transitions; the pill catches "deploy-pending" specifically.
let swarm = 0;
let stale = 0;
for (const c of containersState.values()) {
if (c.needs_update) swarm++;
if (c.needs_update) stale++;
}
setTabCount("swarm", swarm);
setTabCount("agents", stale);
// R3BU1LD QU3U3 — rebuilds in flight.
setTabCount("rebuild", rebuildCount());
}

View file

@ -21,7 +21,7 @@
// widening from the old per-agent-menu-only coordination) so
// `closeAll()` can close each via its own `.close()`. `closeAllMenus()`
// force-closes everything before tearing down DOM one might anchor off
// of (e.g. swarm.js before replacing the container tree).
// of (e.g. agents.js before replacing the container tree).
//
// The document-level listeners below, registered once: shadow-DOM
// retargeting means `e.target` for a click inside a shadow tree gets
@ -58,7 +58,7 @@ document.addEventListener(
true,
);
// Close on Escape. stopImmediatePropagation so a caller's own
// selection-clear Escape handler (e.g. swarm.js's) doesn't also fire
// selection-clear Escape handler (e.g. agents.js's) doesn't also fire
// while a menu is open.
document.addEventListener(
"keydown",
@ -76,7 +76,7 @@ class HiveMenu extends HTMLElement {
// A custom element's connectedCallback fires again on a same-document
// *move* (insertBefore/append repositioning an already-connected node
// runs the removal + insertion steps for its whole subtree), not just
// on a fresh mount — swarm.js's row-fingerprint cache reuses/reorders
// on a fresh mount — agents.js's row-fingerprint cache reuses/reorders
// existing <li> subtrees, which can move an already-initialised
// <hive-menu> without ever detaching it from the document. Guard
// against re-running setup (attachShadow throws if the host already

View file

@ -1,7 +1,7 @@
// hive-warn.js — <hive-warn>, the shared inline warning-banner
// component. Consolidates what were independently-written instances of
// the same thing, among them `.tombstone-warn` (core.js, JS-built) and
// `.port-conflict` (swarm.js, JS-built) — some differed only by an
// `.port-conflict` (agents.js, JS-built) — some differed only by an
// undeliberate 10% vs 8% tint, the strongest evidence this was drift,
// not genuinely different needs.
//

View file

@ -1,7 +1,7 @@
// JobqRollup.tsx — <JobqRollup>, a compact one-line banner over
// GET .../api/jobq/rollup's pre-tallied `Vec<hive_jobq_wire::StateCount>`
// (any endpoint serving that shape works — hive-c0re and swarm-controller
// both do). Extracted out of dashboard/src/swarm.js's hand-rolled
// both do). Extracted out of dashboard/src/agents.js's hand-rolled
// queue-summary banner, which this component now replaces there too —
// same "one shared component, not two parallel implementations" pattern
// `JobqGraph` already established for the rebuild-queue tree view.
@ -24,7 +24,7 @@
//
// JSX (swarm-ui) — `<JobqRollup endpoint="..." queueHref="..." />`. Or
// plain `render(h(JobqRollup, props), container)` (dashboard/src/
// swarm.js, no JSX pragma needed) — call again with a bumped
// agents.js, no JSX pragma needed) — call again with a bumped
// `refreshToken` to force a refetch. No mount wrapper: `render` is
// already the re-render/diff entry point.

View file

@ -9,7 +9,7 @@
// swarm-level job (e.g. CreateAgent) lands.
//
// JobqRollup sits above the graph, same "N running / M queued" banner
// the dashboard's SW4RM tab shows — no `queueHref`, since a "view
// the dashboard's 4G3NTS tab shows — no `queueHref`, since a "view
// queue →" link back to this same page would be noise.
//
// Auto-refresh reuses `HivesPage`'s exact pattern (`RefreshIntervalPicker`

View file

@ -14,7 +14,7 @@ use super::AppState;
/// option) when present, otherwise **404** — there is no bundled
/// server-side default any more (that was `hive_sh4re::assets::
/// branding_svg`, now removed). Consumers fall back client-side: the
/// dashboard's `swarm.js` and this agent's own `app.js` both swap an
/// dashboard's `agents.js` and this agent's own `app.js` both swap an
/// `/icon` load failure to the frontend-bundled `/favicon.svg` rather
/// than probing first, so a 404 here is the expected "unconfigured"
/// signal, not an error case to work around.