Commit graph hyperhive/docs/web-ui/dashboard.md
Author SHA1 Message Date
iris
ec433a07dd docs: fix Microsoft.UIVerbs hits in dashboard.md (click -> select)
Two genuine instruction-verb hits: the container-row selection toggle
and the schedule-table per-target cancel button both said 'click' where
the action also works via keyboard (Enter/Space) or is otherwise
input-device-neutral.

Left the other 4 hits found in the same #4041 sweep alone (false
positives, not fixed here): agent.md's 'ctrl/middle-click' and
'clicks' (real mouse-button-specific browser/VNC behavior, not reader
instructions), shape.md's 'swipes in' (describes the drawer's own
animation, not a user gesture), and process/gotchas.md's
'click-to-unlock' (weston's actual lock-screen feature name).
2026-09-08 16:05:33 +02:00
iris
acdd9e884b docs: fix genuine passive-voice hits in docs/web-ui/dashboard.md (part 2/2)
Second half (lines 762-1389) of dashboard.md's write-good.Passive backlog --
22 rewrites, reusing an actor already named nearby wherever one exists
(the dashboard, hive-c0re, the harness, the operator, the endpoint, the
broker forwarder, CSS, Client, the manual fan-out, ...). Companion to
part 1 (PR #4117, lines 1-697).

10 left alone in this half: five established idioms (config-state
conditionals, negative-capability "nothing is X", state descriptors
"is untouched"/"is gone"), and one literal quoted UI string
("container will be created") that documents exact displayed text
rather than the doc's own voice. One rewrite (naming "the dashboard"
for the old L762 'is known stopped') incidentally leaves behind a new
instance of the same 'is stopped' idiom already accepted at L797 --
21 net fewer hits from this half, not 22, though 22 edits were made.

Together with part 1 (31 rewrites, 7 left alone), this closes out
dashboard.md's 70-hit Passive backlog: 53 edits total, 52 net fewer
hits, 18 left alone.
2026-09-08 15:54:39 +02:00
iris
a52d344600 docs: fix genuine passive-voice hits in docs/web-ui/dashboard.md (part 1/2)
First half (lines 1-697) of dashboard.md's write-good.Passive backlog --
31 of the file's 70 hits rewritten to name an actor (hive-c0re, the page,
the operator, the table, the filter, builds.js, Provision, the matrix
daemon, ...), reusing an actor already named nearby wherever one exists.
7 left alone in this half: a fixed idiom ("not to be confused with"),
two false positives ("named buckets" / compound adjective; a
field-is-set data-state conditional), a negative-capability idiom
("nothing is written"), and two established state-descriptor idioms
("is unchanged", "is tooltipped").

Part 2 (lines 762-1389, ~32 more hits) is a separate follow-up, not
started -- dashboard.md is too big for one PR.
2026-09-08 15:54:31 +02:00
atlas
d6f23ff371 swarm: move the matrix packages to deploy, where their enable already lives
Which build a service runs is a decision of the host that runs it, so
`*.package` belongs with `enable` rather than in the namespace every hive
must agree on byte for byte.

The `gui` pair was the sharpest case and the reason this starts here:
`gui.enable` moved to `deploy.*` in an earlier slice while `gui.package`
stayed behind, so one web client's two halves sat in two namespaces —
and hive-matrix.nix said so, in a comment directly above the option.
The diff removes a stated inconsistency rather than introducing a rule.

Both old paths keep working through `mkRenamedOptionModule`; both are
settable (neither is `readOnly`), so both get a shim. The `matrixOldPath`
fixture in module-eval.nix now sets eight options through their
pre-rename paths instead of six, so dropping either new shim fails the
eval rather than silently dropping a definition.

Prose swept in the same commit: three comments claimed the package was
swarm-wide or named the pre-rename path, and docs/web-ui/dashboard.md
named `swarm.matrix.gui.package` two lines above
`deploy.matrix.gui.enable` — the same asymmetry this commit exists to
remove, displayed in the docs. A shim keeps the old spelling valid, so
no build, lint or broken-reference search would have found that line.

Refs #3772.
2026-09-07 20:46:37 +02:00
iris
a8fb08a7e8 docs: fix Microsoft.UIVerbs findings (click -> select)
Part of #4041's forgotten remainder (UIVerbs was scoped as pure
execution work, no decision needed -- see comment 71586).

29 genuine input-device-neutral rewrites ("click"/"clicking"/"clicked"
-> "select"/"selecting"/"selected", a few reworded where a direct verb
swap didn't read naturally: "one click from the hub" -> "one step from
the hub", "click straight from X into Y" -> "go straight from X into
Y", "the operator never clicked" -> "the operator never triggered").

Left 5 hits unfixed, each read in context and genuinely not a
device-assumption problem:
- docs/process/gotchas.md:439 -- "click-to-unlock screen" names a
  specific OS lock-screen pattern, not an instruction to the reader.
- docs/web-ui/agent.md:377 -- "clicks" describes literal RFB pointer
  events forwarded by hive-screen-mcp's sendPointer, genuinely
  mouse-specific.
- docs/web-ui/agent.md:78 -- "ctrl/middle-click" names a specific
  keyboard-modifier + mouse-button combination with no "select"
  equivalent.
- docs/web-ui/dashboard.md:741 -- explicitly contrasts "click (or
  Enter/Space)" as two named input methods; replacing "click" with
  "select" would conflate the two the sentence is distinguishing.
- docs/web-ui/shape.md:253 -- "swipes in from the right" describes the
  panel's own slide animation, not a user touch gesture.

Verified: fresh `vale --filter '.Name == "Microsoft.UIVerbs"' docs`
after the fix shows exactly those 5, `nix fmt` reports 0 changes.
2026-09-07 18:46:16 +02:00
iris
e82a735745 docs: fix write-good.So/ThereIs/Weasel lint findings
Fixes the "obvious ones first" slice of #4042 (mara: do the obvious
ones first) -- 81 hits across write-good.So, write-good.ThereIs, and
write-good.Weasel, all in docs/. Each is a genuine sentence rewrite
(lead with the real subject instead of "There is/are", drop a
sentence-initial "So ", replace a vague intensifier), not a blind
regex substitution -- read every hit in its real file context before
touching it.

3 of the 81 hits were in CI-generated CLI docs (docs/tools/{hivectl,
swarmctl,forge}-cli.md) -- fixed at the clap #[arg(...)]/doc-comment
source in hivectl/src/cli.rs, swarmctl/src/main.rs, and
hive-forge/src/verbs/repo_add_collaborator.rs, then regenerated via
each crate's `markdown-docs` subcommand so CI's freshness check stays
green.

Verified: fresh vale re-run shows 0 remaining So/ThereIs/Weasel hits
and no new hits introduced (983->982, exactly the one incidental fix
this pass also picked up at docs/scheduler/observability.md:48).
cargo fmt --check and clippy clean on the three touched crates.

Remaining write-good backlog (Passive: 726, TooWordy: 207) is
judgment-heavy and left for a follow-up slice of #4042, not bulk-
rewritten here.
2026-09-07 17:49:27 +02:00
iris
3f41e7a09c docs: drop redundant hyphens from -ly adverb + adjective/participle compounds
Fixes 22 of 23 Microsoft.Hyphens hits: an adverb ending in -ly doesn't
need a hyphen to the word it modifies (minimally scoped, currently
running, deliberately stopped, ...) -- the -ly ending already
unambiguously signals it's modifying the following word, so the
hyphen is redundant per Microsoft style.

Left one hit alone: docs/web-ui/dashboard.md's 'checked-not-originally-
active' is a single 4-word compound designator (parallel structure
with 'unchecked-was-active' right before it in the same sentence,
describing a diffing algorithm's two possible states), not an
ordinary -ly-adverb modifier -- dropping just the one hyphen vale
flagged would have broken the compound into a dangling
'checked-not-originally active' fragment. Also rewrote
docs/scheduler/coordinator.md's 'still-fully-queued' as a predicate
('DAGs that are still fully queued') rather than leaving a similar
broken partial-hyphen chain, since 'still' and 'fully' are both plain
adverbs with no reason to hyphenate at all once dehyphenated
consistently.

Verified via vale: only the one deliberately-left hit remains.
2026-09-07 16:28:06 +02:00
iris
c20496604c fix Microsoft.Quotes vale findings - move punctuation inside quotes
Moved 27 instances of periods and commas from outside closing quote marks
to inside, following American-style punctuation placement. Changes span
across ~15 documentation files including integration, process, scheduler,
trust-boundary, and web-ui docs.
2026-09-07 16:28:06 +02:00
iris
05cf8e8276 docs: remove hyphens from auto-X compounds per Microsoft.Auto style
Fixed 72 hyphenated 'auto-X' compounds across the docs by deleting
the hyphen to match Microsoft style guide (autogenerated, autoregistration,
autodiscovery, etc). Preserved original capitalization throughout.

Covered 26 documentation files in docs/, including README.md, agent-lifecycle,
integrations, scheduler, networking, process, tools, trust-boundary,
turn-loop, and web-ui sections.
2026-09-07 16:28:06 +02:00
iris
4f2d1d281a docs: fix Microsoft.Foreign hits (e.g./i.e. -> for example/that is)
Substitutes 'for example' for 'e.g.' and 'that is' for 'i.e.' per
Microsoft style, with no comma after the replacement -- this repo
never puts a comma after e.g./i.e. either, so the substituted phrase
matches that house style instead of vale's literal 'for example,'
suggestion.

Covers all 77 real hits (72 from the initial vale pass + 5 more of the
same pattern found on a follow-up vale run). None of the flagged
instances were sentence-initial, so there's no capitalization concern
here.

Verified via a fresh vale run: 0 remaining Microsoft.Foreign hits.
Full accounting in /agents/iris/state/haiku-batches/foreign-report.md.
2026-09-07 16:28:06 +02:00
iris
ff377b0dee docs: fix capitalization losses + one text corruption from the contractions pass
Review pass on top of the previous commit (85a94671) turned up three
real issues its own vale-only verification didn't catch:

- 'has not' -> 'hasn't' fired inside 'has nothing', corrupting
  docs/swarm/README.md into 'hasn'thing to say upward'. Restored
  'has nothing'.
- ~44 hits where the original text was genuinely sentence-initial
  ('It is', 'That is', 'They are', 'What is', 'Do not', ...) got
  contracted in lowercase, silently de-capitalizing the start of a
  sentence. Checked each one's real surrounding text and capitalized
  the ones that needed it.
- Confirmed (by reading the actual text, not just trusting vale) that
  the 3 remaining 'is not'/'has not'/'it is' hits after those fixes are
  vale's own false positives: it matches these as a bare substring with
  no trailing word boundary, so it also fires on 'is nothing', 'has
  nothing', and the already-correct 'it isn't'. Left those alone --
  there's no real issue in the text.

Re-verified with a fresh vale run: the remaining 17 hits are exactly
the union of the 14 legitimate ALL-CAPS/bold-emphasis skips from the
first commit and these 3 false positives. Full accounting in
/agents/iris/state/haiku-batches/contractions-report.md.
2026-09-07 16:28:06 +02:00
iris
78021ce982 docs: fix 329 Microsoft.Contractions hits with proper safety checks
Apply contraction fixes across ~40 doc files (setup, integrations, lifecycle,
networking, scheduler, swarm, tools, trust-boundary, UI, etc.). Skipped 14 hits:
- 10 where words appear in ALL CAPS for deliberate emphasis (is NOT, do NOT, etc.)
- 4 where text could not be safely located due to markdown formatting or column position

Applied via systematic scan with checks for fenced code blocks, inline code spans,
and intentional caps. Preserves sentence-initial capitalization throughout.
2026-09-07 16:28:06 +02:00
iris
77296aff35 docs: retire the stray top-level web-ui.md, fold it into web-ui/README.md
docs/web-ui.md duplicated the web-ui/ directory name at the top level --
the only such collision in docs/ (every other subsystem has just a
directory, no sibling <dir>.md file). That's exactly why it rendered
outside the directory structure in the docs site nav (mara's report,
hyperhive#4054): the site build walks docs/ generically with no
special-casing, so a loose top-level file next to a same-named
directory shows up as its own flat top-level entry instead of nesting
under that directory's section.

web-ui.md's own first paragraph already said as much -- 'This doc has
been split for readability... start at web-ui/README.md instead.' It
was a leftover pointer from before the split, not a page carrying
unique content on its own merit.

Folded its two sections web-ui/README.md didn't already have (the
swarm-ui design-guide link, and the task-oriented 'reading paths'
quick-lookup list) into web-ui/README.md's existing 'More depth'
section, then deleted the stray file and repointed every real
reference at it: 3 in-tree doc cross-links, 3 doc prose mentions
(retargeted to the more specific dashboard.md/shape.md sub-page each
one was actually about), and ~28 frontend source comments
(dashboard/agent/shared packages) that cited it as
'docs/web-ui.md::<heading>' for implementation context -- retargeted
each to whichever of dashboard.md/shape.md/agent.md actually carries
that heading now, verified against each file's real heading list
rather than guessed.

Verified via scripts/check-doc-refs.sh (the same lint CI runs): 0 dead
pointers, both before write (confirming the tree was clean beforehand)
and after (confirming nothing broke).
2026-09-07 15:49:52 +02:00
atlas
7003d14d2c deploy: split the homeserver's host decisions out of swarm.matrix
`swarm.*` is what a hive needs to be a *client* of the swarm. For the
homeserver that is what it IS from anywhere: its package, the name it
answers to, the ports and URLs it is reached on, and the client id it is
registered under. Whether it is exposed, which peers it trusts, how large
a request it accepts and where its host-local secrets sit are decisions
of the machine running it, so openFirewall, trustedServers,
maxRequestSize, registrationTokenFile, gui.enable and
sso.clientSecretFile move to `deploy.matrix.*`.

Two sub-blocks split rather than moving whole, on their own evidence.
`gui.enable` is whether THIS host serves the web client; `gui.package` is
which client, an artifact identity, and stays. `sso.clientSecretFile` is a
path on one host; `clientId` must match the id in authelia's register, so
it is swarm-wide. Each half now points at the other, because the rendered
docs put them on separate pages.

hive-gateway passed the whole `swarm.matrix` attrset into vhosts.nix, so
that file read a moving option through an argument with no option path
anywhere in it. It now takes `matrixDeployCfg` beside `matrixCfg` — the
only shape that carries a split namespace across that boundary.

While there: vhosts.nix read `matrixCfg.enable`, which has been a rename
alias for `deploy.matrix.enable` since the enable moved. Reading it made
the module system print `Obsolete option services.hyperhive.swarm.matrix.
enable is used` on EVERY evaluation of every host — a deprecation warning
no operator could silence, because the config tripping it was ours. That
shim lives in hive-matrix.nix rather than in this file's table, which is
why deploy.nix's header claim to be their single home is now qualified
in the new block's comment.

glue-matrix-bao-token.nix read the registration token through its own
`matrixCfg` alias; with that read repointed, the binding had no reader
left, so it goes, and the comment naming it is reworded.

module-eval gains a case configuring a hive through all six OLD paths and
asserting two rendered effects — the host firewall's port list and the
container's bind-mount table — because the new paths evaluate fine
without the shims. `gui.enable` is set to the opposite of its default so
the definition has to land rather than agreeing with it by accident.
2026-09-07 14:24:52 +02:00
atlas
9e76589be3 docs: 10 cited nix options that do not exist, two in pasteable blocks
An option name that is absent reads exactly like one that is present —
the reader has no way to tell, and the failure surfaces as a nixos eval
error at deploy time. Two namespace reorgs the prose never followed:
`forge.*` moved under `swarm.`, and several flat paths under `c0re.`.

  docs/networking/gateway.md:335   services.hyperhive.forge = { … }
  docs/networking/gateway.md:367   forge.rootUrl        → swarm.forge.rootUrl
  docs/networking/gateway.md:389   frontend             → c0re.frontend
  docs/tools/hivectl.md:193        agentCpuQuota        → c0re.agentCpuQuota
  docs/tools/hivectl.md:317        forge.behindGateway  → swarm.forge.behindGateway
  docs/agent-lifecycle/persistence.md:404   agentCpuQuota → c0re.agentCpuQuota
  docs/web-ui/dashboard.md:610     modelPrices          → c0re.modelPrices
  docs/web-ui/dashboard.md:789     forge.publicUrl      → swarm.forge.publicUrl

Both `gateway.md` blocks are fenced nix an operator is meant to paste.

Two sites needed more than a rename:

  dashboard.md:781 cited `gateway.enable` as a live opt-in. It was
  removed; `gateway.md:269` already says the gateway is unconditional,
  and `hive-c0re/environment.nix:208` sets HIVE_GATEWAY_ENABLED = "1"
  with no condition. Rewritten to say so, keeping the flag-absent
  fallback the dashboard still implements.

  turn-loop/config.md:4 attributed the page's knobs to
  `services.hyperhive.agents.<name>`. No such option exists — every
  knob the page documents is agent-side `hyperhive.*`, set in the
  agent's own agent.nix.

Measured against the module system rather than a grep: `nix build
.#docs` renders nixosOptionsDoc over the evaluated trees (279 option
paths). That arm alone is wrong — it omits `internal = true` options
and called `gateway.localNames` and `swarm.peerHives` dead, so a second
arm requires the literal path to appear in nix/ and rescues exactly
those. Four remaining unresolved citations are correct prose saying
`gateway.enable` / `network.{enable,isolateContainers,upstreamDns}`
were removed, and are left alone.

Closes #3948
2026-09-02 17:17:09 +02:00
iris
07b62612b0 docs: restructure into topic subdirectories, collapse duplicated index
Per mara's go-ahead on hyperhive#3902 ("getting started is good, but
terminal rendering does not go in there i think"):

Moved 21 top-level docs/*.md files into 7 new topic subdirectories
(existing web-ui/, turn-loop/, swarm/, tools/, crates/ untouched):
  getting-started/  setup.md
  agent-lifecycle/  agent-hierarchy.md, approvals.md, persistence.md
  trust-boundary/   boundary.md, security.md
  integrations/     forge.md, matrix.md, github.md, knowledge.md
  networking/       gateway.md, network.md, snapshot-store.md
  scheduler/        jobq.md, coordinator.md, ci.md, observability.md
  process/          conventions.md, gotchas.md, pr-review-gate.md
  web-ui/           terminal-rendering.md (moved into the EXISTING dir,
                    per mara's correction to the original getting-started
                    guess -- it's UI implementation detail, not onboarding)

The physical layout now matches docs/README.md's own topical headers,
which already amounted to this taxonomy -- see the scoping comment on
the issue for the two findings that motivated this (a genuine
duplication between CLAUDE.md's old "Reading paths" list and
docs/README.md's grouped one, since drifted out of sync with each
other; and the flat layout not matching the grouping we already had).

Fixed every cross-reference this moved across the whole repo (~120
files: docs/ internal links at every depth, Rust doc comments, nix
module option docs, crate READMEs) -- verified two ways: a grep sweep
confirming zero remaining references to any old path, and a script
that resolves every markdown link in docs/**/*.md + CLAUDE.md +
README.md against the filesystem and reports anything that doesn't
exist (zero broken links).

Collapsed CLAUDE.md's "Reading paths" section (the duplicate) down to
a pointer at docs/README.md, now the single index. Rewrote
docs/README.md itself to use the new subdirectory paths and added the
one doc it was missing that CLAUDE.md's old copy had (pr-review-gate.md).

Classified all 22 docs/*.md files first via a haiku subagent (mara's
suggestion) on two axes -- proposed grouping and operator-vs-
implementation focus -- before finalizing the taxonomy; spot-checked
the report and found internal inconsistencies (its classification
table disagreed with its own summary section for a few files), so this
taxonomy is my original proposal + the one correction mara gave
directly, not a blind application of the subagent's table. The
operator-focus data it gathered is still useful for a follow-up
content pass (docs skewing 'mixed' rather than pure operator-facing),
not addressed in this PR -- structure only.

nix fmt clean, both pre-push lints clean.
2026-09-02 01:55:37 +02:00
iris
d9937ee834 dashboard: also remove the tombstone revive trigger
mara asked on PR #3854 to remove the revive action alongside the
blank-name spawn form. Both posted to /api/request-spawn as the last
two frontend callers; with this the dashboard has no UI path left that
hits that endpoint (backend removal/decision stays damocles's call).

Drops the K3PT ST4T3 tombstone row's ⊕ R3V1V3 form, leaving PURG3 as
the only per-tombstone action. Updated the two doc/comment spots that
described the now-gone button.
2026-08-31 19:35:45 +02:00
iris
8475d9921f dashboard: remove the spawn-new-agent trigger from the Y3R C4LL tab
Frontend half of removing hive-level agent spawning (swarm-level
creation now covers it, and its forge-works confirmation just landed
in hive/hive-chat). Removes call.js's blank-name spawn-request form
(R3QU3ST SP4WN) and its now-orphaned .spawnform CSS.

Deliberately keeps core.js's tombstone-list revive action (R3V1V3) --
a different feature (respawn an *existing* agent, reusing its kept
state), not agent creation, and not what this issue asked to remove.
It happens to post to the same /api/request-spawn action with the
tombstone's own name pre-filled rather than a blank input.

Backend endpoint + wiring is a separate slice (damocles).
2026-08-31 19:30:38 +02:00
iris
b33bb7a3d3 Move dashboard settings into Y3R C4LL tab, drop standalone S3TT1NGS page
mara (issue #3817): remove the home link + page, put the setting toggle into the Y3R C4LL tab. Deleted settings.html/.js/.css; the browser-notification toggle (the only content there) now renders as a ◆ PR3F3R3NC3S ◆ section under Y3R C4LL's approvals/inbox, wired the same way (NOTIF.bind()/NOTIF.show() in common.js — no behavior change, just a new mount point). Updated build.mjs's entry lists and every doc/comment that pointed at the old page.
2026-08-31 12:57:31 +02:00
iris
146db58bab docs: trim M4TR1X-page LinksMenu paragraph to the pointer, not the impl
mara: strip impl details from docs. Dropped the nix-module name, struct literal, and gating-condition restatement — docs/swarm/ui.md::Quick links already covers that; this section only needs to say where the link lives now.
2026-08-31 12:55:40 +02:00
iris
19ed400cdd docs: M4TR1X page is reached via swarm-ui's LinksMenu, not opened directly
mara (PR#3820): the operator doesn't type /matrix/ into the browser — it's a swarm-level link (hive-matrix.nix registers it into services.hyperhive.swarm.controller.links, rendered by swarm-ui's LinksMenu). Corrected the M4TR1X page doc, and fixed the hive-matrix.nix comment that still described the removed H0M3 tile in present tense while I was in there.
2026-08-31 12:55:40 +02:00
iris
40be8222b8 docs: fix stale Matrix-tile reference in M4TR1X page section
argus (PR#3820 review): the M4TR1X page section still said the operator opens /matrix/ from the H0M3 hub's Matrix tile — that tile no longer exists after this PR removed it. Fixed to say the page is opened directly.
2026-08-31 12:55:40 +02:00
iris
2490181055 H0M3: remove Forge/Matrix tiles from hive home
mara (issue #3816): remove the swarm-service links (forge, matrix) from the hive home hub. Both surfaces are still reachable directly (forge's own public URL, /matrix/); they just no longer get a tile on H0M3. Dropped the now-dead reveal/href-fill logic in home.js and updated the two docs that described the old gating.
2026-08-31 12:55:40 +02:00
damocles
22adfd1451 remove the 1NFR4 dashboard panel and the now-writer-less audit log 2026-08-31 00:18:21 +02:00
damocles
7516a4e10e remove hive-level infra-container restart from web ui and agents 2026-08-31 00:18:21 +02:00
iris
f14056996e terminal: catch the remaining stale ask/answer references
argus, reviewing PR#3793: a third stale  ask mention survived in the
same file/table the first pass touched (docs/terminal-rendering.md's
icon legend) — tool_icon() has no ask/answer arm at all, confirmed by
reading the function directly. Swept the whole tree this time instead
of trusting the earlier narrow grep: found three more —
docs/web-ui/dashboard.md's S3TT1NGS section still documented the
expand-tool-output toggle as living on the dashboard, which moved to
the per-agent page's own SettingsMenu popover in #3780 and was never
followed up here; markdown.ts/streamRow.ts/terminal.css had the same
send/ask/answer/recv phrasing as the original two comments.
2026-08-30 20:13:49 +02:00
damocles
46183795dd hive-c0re, docs: sweep remaining stale ask/answer-dashboard references 2026-08-29 23:28:32 +02:00
damocles
ff0a6660aa dashboard: remove the Y3R C4LL questions pane (ask/answer removal, frontend) 2026-08-29 20:35:27 +02:00
iris
4af890f01d hive dashboard: bulk pause + resume in the selection bar
Mechanical addition to the existing bulk-action framework
(renderSelectionBar/addBulkButton, swarm.js) -- two per-agent actions
already exist (POST /api/pause/, /api/resume/, see hive-agent-menu.js),
this just gives the selection bar the same all-or-nothing enablement
rule the other six bulk buttons already use (pause enabled only when
none of the selection is already paused, and vice versa for resume).

.btn-pause reuses .badge-paused's yellow so the trigger and the
resulting state pill read as one colour; .btn-resume is green like
.btn-start (both are "go" actions). Docs updated to list both in the
Selection bar reference.
2026-08-24 11:37:04 +02:00
iris
1111577c91 extract build-queue rollup as a shared Preact component
New @hive/shared/jobq-rollup.js (JobqRollup.tsx + jobq-rollup.css),
mirroring JobqGraph's shape exactly: JSX use plus an imperative
mountJobqRollup(container, props) for a plain-.js call site. Fetches
Vec<hive_jobq_wire::StateCount> off `endpoint`, sums Running+Finishing
roots as "running" and Pending roots as "queued", renders nothing when
both are zero. Optional `queueHref` adds a "view queue -> " link.

Swapped dashboard's hand-rolled queue-summary banner (swarm.js) over to
this component instead of keeping two parallel implementations - same
"one shared component" pattern JobqGraph already set for the rebuild
queue tree view. Mounted once into a new #jobq-rollup-section, kept as
a sibling of (not inside) #containers-section since that section gets
replaceChildren()-wiped on every container-state render, which would
tear down and remount a Preact tree on every tick. Refreshed via the
mount handle's .refresh() on rebuild_queue_changed, same as builds.js's
JobqGraph handle.

Also mounted in swarm-ui's /jobs page, above JobqGraph, with no
queueHref (a link back to the page you're already on is noise) - the
literal ask on hyperhive#3364.

Verified: npm run build (whole workspace) and swarm-ui typecheck both
clean, comment-block + issue-ref lints run manually, headless-chromium
screenshots of both the dashboard SW4RM tab and swarm-ui's /jobs page
against mocked /api/jobq/rollup payloads - banner renders identically
in both, with and without the queue link as expected.
2026-08-16 22:08:38 +02:00
iris
ecf01f5e87 docs: remove historic framing, state what JobqGraph is now 2026-08-16 15:18:25 +02:00
iris
37161cd136 dashboard: replace <hive-jobq-graph> with a shared Preact component
Ports the shadow-DOM <hive-jobq-graph> custom element
(frontend/packages/shared/src/jobq-graph/) to a Preact component
(JobqGraph.js) shared by the dashboard and swarm-ui, per hyperhive#3310.

- JobqGraph.js: written with plain h() calls (no JSX) so the same file
  compiles unmodified under both the dashboard's text-loader CSS config
  and swarm-ui's JSX config. Exports `JobqGraph` for JSX use and
  `mountJobqGraph(container, props)` for the dashboard's non-JSX
  imperative mount, returning a `{refresh(), update()}` handle matching
  the old custom element's public surface. Same rendering contract as
  before: indented state tree, payload.label verbatim, payload.data as
  a generic key/value list, "waits on: <label>" text for Node-kind deps,
  per-state filter checkboxes, optional cancel button.
- jobq-graph.css: light-DOM adaptation of the old shadow-scoped
  stylesheet (:host -> .jg-root, otherwise unchanged).
- dashboard/src/builds.js: local mountJobqGraph() renamed to
  mountRebuildQueue() to avoid colliding with the newly-imported shared
  mountJobqGraph; cancel handling is now a plain onCancel callback
  instead of a DOM CustomEvent listener (no shadow boundary to cross
  anymore).
- dashboard + shared package.json: added preact as a dependency (matches
  swarm-ui's existing pin, 10.29.8) - the dashboard was a vanilla-JS MPA
  with no Preact/JSX pipeline before this.
- Removed the old hive-jobq-graph.js/.css entirely (confirmed via grep
  it had exactly one consumer, dashboard/src/builds.js, so this is a
  clean swap, not parallel maintenance of two implementations).
- Updated stale doc-comment references to the old element name in
  builds.html, tabs.js, swarm.js, docs/web-ui/dashboard.md, and
  hive-c0re/src/job_queue/mod.rs.

Verified: npm run build (whole frontend workspace) and npm run
typecheck (swarm-ui) both clean; cargo build/clippy/test -p hive-c0re
all clean (331 tests, 0 failures); headless-chromium screenshot of
/builds.html against a mock GET /api/jobq/graph payload confirms full
visual/behavioral parity with the old custom element (tree, filter
checkboxes, cancel buttons, error text, waits-on line, data list, live
build log panel).

This covers the dashboard-replacement half of hyperhive#3310 only. The
swarm-ui half (rendering the CreateAgent DAG on the agent-creation page)
is downstream of hyperhive#3306/#3124 landing - no swarm-ui page exists
yet to mount it in.
2026-08-16 15:18:25 +02:00
iris
8c8f0e2758 docs+nix: fix stale peer-hives-dashboard references (hyperhive#3294)
Follow-up to the peer-hives removal: docs/web-ui/dashboard.md drops
the whole "P33R H1V3S" section + its stale #peers hash and tab-strip
mentions; docs/swarm/README.md's runtime-effects list and
cross-references point at swarm-ui's roster page instead. Nix comments
in hive-c0re/environment.nix, swarm.nix and swarm-wireguard.nix that
described HYPERHIVE_PEERS/certFingerprint as dashboard-consumed are
updated to say what's actually still true (agent identity only).

Deliberately NOT removed: the `certFingerprint` nix option itself.
It has no remaining Rust consumer, but deleting a documented,
potentially already-set option is a bigger, breaking change than
removing dead display code — flagged in docs/swarm/README.md with a
note instead of silently dropped, so an operator or reviewer can make
that call explicitly rather than have it made for them here.
2026-08-15 19:55:29 +02:00
iris
3e9f2f2fd5 docs(dashboard): fix stale facts found in source-verification sweep
- server-warnings banner: was described as disk-pressure-only; it's now
  a push-based registry with multiple producers (forge/CI boot
  failures, agent-state warnings), and shown on more standalone pages
  than listed
- browser tab title: actual default/format is "hyperhive // h1ve-c0re",
  not "hive / c0re"
- SW4RM container tree: ContainerView.parent comes from
  agent_config/topology.rs, not the ambiguous dashboard/topology.rs
  (which only holds set-parent endpoints)
- T00L GR0UPS: tool-group enumeration was missing "forge"; root agent's
  default (MANAGER_DEFAULT) is not literally "all groups" -- it excludes
  forge and web_tools
- ST4TS moved to its own /stats.html page a while back; section still
  called it a dashboard tab
- P33RS is a headline block within the SW4RM tab, not its own tab --
  contradicted the doc's own chrome-header description
- active model badge: reads hyperhive-harness.json's active_model
  field, not a bare harness/hyperhive-model file
- themed dialogs: modal.js lives in the shared package
  (@hive/shared/modal.js), and themedToast takes (message, opts) not
  a single options object
- graceful stop: backend uses a Signal/Drain job-queue node pair, not
  a "GracefulStop" transient kind; hivectl flag is `stop --graceful`,
  not a bare `--graceful`
- container_state_changed event: named a nonexistent
  auto_update::rebuild_agent as an emit site; actual sites are
  actions::approve and the job queue's own node execution

Verified against hive-c0re/src (dashboard/, stats/, job_queue/,
container_view.rs), hive-sh4re/src/permissions.rs, and the dashboard
frontend package (tabs.js, stats.js, swarm.js, builds.js, modal.js).

No structural/prose changes beyond what the fact corrections required.
2026-08-15 12:45:22 +02:00
iris
dd8b5db7ab docs: fix remaining stale internal references from the #3281 sweep
Two follow-ups the per-file sweep workers flagged but couldn't fix
themselves (either not their assignment, or not visible from their
worktree):

- web-ui/dashboard.md's P33RS tab named dashboard.rs, which no longer
  exists (hive-c0re/src/dashboard/ is a module dir now); the function
  moved to state_snapshot.rs.
- approvals.md and conventions.md used hive_ag3nt:: as a Rust
  module-path prefix. hive-agent is a bin-only crate (no lib target),
  so that prefix never resolved to anything; also fixed the
  hive-ag3nt/prompts/system.md path to the real hive-agent/prompts/.

All three verified directly against the current source tree, not
guessed.
2026-08-15 12:45:22 +02:00
iris
785b59a4bb docs(dashboard): fix misplaced CR3D3NTIALS content, dedupe, trim history 2026-08-15 12:45:22 +02:00
atlas
07852cabc1 feat(3088): move the gateway's nginx + dnsmasq onto the host
The gateway's nginx + dnsmasq no longer run in their own nspawn container.
`nix/host-modules/hive-gateway/default.nix` loses the
`containers.hive-gateway` wrapper and everything that existed only to punch
holes in it: `privateNetwork = false`, `CAP_NET_ADMIN`, five bind mounts,
its own `stateVersion`, `networking.firewall.enable = false`,
`networking.resolvconf.enable = false`, and the `hive-gateway-resolv`
path+service pair. 465 -> 303 lines.

The container never bought isolation here. It shared the host netns by
necessity — nginx binds the host's :80/:443, dnsmasq answers on the bridge —
so each of those settings was undoing a boundary the gateway could not
afford in the first place.

Four things made it more than a deletion, none of them visible in the nix
diff:

- The self-signed cert service also imports the hive CA leaf, so removing it
  with the container would have left nginx naming a missing cert file, which
  it refuses to load at all.
- The nginx reload is a hive-priv verb. It still needs root, but no longer
  for the reason its doc gave, and `--machine=` was both transport and
  scope — so the unit name is now hard-coded in the helper as the
  containment.
- The lifecycle verb named a container that stops existing.
- `journalctl -M hive-gateway` had no machine to enter.

Per the operator's ruling, the operator verb keeps working and agents lose
it. `InfraContainer` answered three questions that used to share an answer;
it now splits into `name()` (identity), `target()` (Container vs HostUnit),
`service_unit()` (the systemd unit), and `agent_restartable()`, which the
MCP restart path checks before the capability so the refusal cannot read as
"ask for infra_admin". `SIBLING_CONTAINERS` drops the gateway — it gates the
requests that name a container as a string — while `FromStr` still accepts
it, because that answers what a name is, not who may act on it. The
dashboard's gateway journal reads host journald filtered to `nginx.service`.

Prose was corrected where it only named a location, and re-argued where the
container was doing security work: a `0666` per-agent socket was safe
because only the gateway container had the directory bind-mounted. There is
no mount now, so the directory permissions are the whole of the access
control — the constraint holds, its mechanism doesn't.

Gate: nix fmt / clippy --all-targets -D warnings / cargo test all clean (710
tests); hivectl-cli.md regenerated from the clap tree. The nix eval was run
in both TLS shapes at this commit: every delta in the rendered
virtualHosts is one of the three intended path moves, dnsmasq settings are
byte-identical, and the absence probe flips true -> false with bindMounts
emptied.
2026-08-11 18:01:03 +02:00
iris
b785f96d30 dashboard: distinct "gave up" badge for a crash-looped container
Frontend half of the two-PR split on the systemd restart-bound work
(clause 2): once a container's unit hits systemd's bounded restart
limit and stops on its own, that read exactly like a deliberate stop
("not running") — no way to tell "gave up" from "off on purpose".

Renders ContainerView.failed as a distinct red "gave up" badge on the
container row, in place of the muted "not running" badge. Both states
have running: false; failed is the new orthogonal fact that tells them
apart, same "independent flags, no state machine" shape as
paused/needs_update/needs_login. An older backend without the field
serves failed: undefined, which reads falsy, so this degrades cleanly
to the existing single "not running" badge.

Frontend-only — no Rust changes. Safe to merge in either order
relative to the backend PR carrying ContainerView.failed itself.
2026-08-10 23:14:36 +02:00
iris
1e13b88c8c jobq graph: state filter on /api/jobq/graph + multi-select checkboxes
GET /api/jobq/graph gains a states query param (comma-separated
hive_jobq::State names): narrows the served root groups to the named
states, keeping a group whole (filtering by a root's own state, which
is already its subtree's rolled-up answer). Absent, empty, or fully
unrecognised is the identity filter, matching prior behaviour.

hive-jobq-graph.js gains a row of per-state checkboxes above the tree,
re-fetching the endpoint with the selection on toggle. Default
selection hides Done and Skipped.

Server-side filtering (not client-side hiding) so hive-jobq-graph-update's
node list, and everything downstream of it in builds.js (count pill,
live-log panel), only ever sees what's actually shown.
2026-08-10 23:05:03 +02:00
iris
aa14339be7 jobq-graph: re-add per-node cancel button
Fixes #3067.

<hive-jobq-graph> gains a `cancellable` attribute: any non-terminal
node (Pending/Running/Finishing) gets a small cancel button, and a
click dispatches `hive-jobq-graph-cancel` (`detail: { id }`) rather
than POSTing anything itself -- which endpoint actually cancels a
node is the host's domain concept, same "push data out, host decides"
shape `hive-jobq-graph-update` already uses.

builds.js turns it on for R3BU1LD QU3U3, confirms via themedConfirm,
then POSTs the existing `/api/rebuild-queue/{id}/cancel` endpoint.
No manual refresh needed -- cancelling flips node state, which
already fires rebuild_queue_changed over SSE, and the page's existing
handler for that tick already calls jobqGraphEl.refresh().

Also removed ~130 lines of dead `.rqe-*` CSS in system-sections.css
left over from the bespoke pre-<hive-jobq-graph> queue renderer
(confirmed zero JS references before deleting each rule; kept the
still-used `.rqe-kind`/`.rqe-agent`/`.rqe-source*`).

docs/web-ui/dashboard.md's R3BU1LD QU3U3 section updated to match
current behaviour (cancel button, waits-on text instead of the old
"no per-node actions" note, sibling order no longer implies anything
since deps render as text not a reordered rail).
2026-08-09 17:40:39 +02:00
atlas
a1243fe04a docs: follow the swarm service names to the swarm domain
The three moves above falsify prose in five files, and none of it is
caught by a gate: clippy, cargo test and nix flake check read exactly
zero markdown.

Corrected where a doc stated a DEFAULT or enumerated the vhosts --
gateway (ACME requirements + the header table + the two Forgejo
reachability lines), matrix (gatewayHost, firewall rationale, agent
reachability), network (container table + the HTTP resolution bullet),
turn-loop config, dashboard.

Two mentions are deliberately left alone. `docs/tools/hivectl.md` says
the URL is read from the daemon "instead of assuming forge.<domain>" --
that sentence is about not assuming a shape and stays true. And
matrix.md's breaking-change note describes what `serverName` USED to
default to; it is history, correctly labelled, and rewriting it would
erase the record of an earlier migration.

The ACME line gained a requirement rather than a rename: with the
service names under the swarm domain, every one of those names has to
resolve to this host, not just the hive's own.
2026-08-09 17:32:44 +02:00
atlas
433b294099 refactor(nix): swarm.peers becomes swarm.hives, a directory of every hive
One attrset describing every hive in the swarm including this one,
identical on every host, with hiveName selecting which entry is us.
"My peers" is derived (swarm.peerHives) rather than declared.

Every field in the old per-host peer list was intrinsic to the hive it
described, never to the pair -- so the list was a directory each host
kept its own copy of. Beyond the deduplication it removes a bug class:
two hosts could hold different endpoints for the same third hive with
nothing to detect the disagreement.

Drops the per-hive caCert. Trust inside a swarm derives from the swarm
root, which every hive chains to. What that genuinely removes is
trusting a hive whose root this swarm does not own -- a cross-swarm
problem that wants a mechanism of its own, not a field that happened to
work.

The matrix container's certificateFiles block goes with it and could
NOT be migrated: that list is read at build time and the swarm root is
a runtime file (its key must never enter the store), so there is no
build-time name to put there. caCert being a nix path was precisely
what made it the build-time distribution channel. Agents are unaffected
-- hive-tls folds the root into the hive trust bundle and the meta
renderer embeds that one file. Tracked separately.

Migration is an assertion plus warnings, not a rename: hives is peers
union {self}, and the set gains a member no existing config has written
down. A rename migrates a name and a default can re-root a meaning;
neither can conjure a new member. The warning explains, the self-entry
assertion stops the build.
2026-08-05 20:44:16 +02:00
atlas
5235166bb3 docs: name the matrix host options by their new path
Every prose and error-message reference to the moved options, including
two in hive-c0re/src/matrix.rs that omitted the services. prefix and so
read as the per-agent option. An error that names an option the operator
cannot find is worse than no hint.
2026-08-05 13:45:09 +02:00
atlas
730c923a97 docs: stop asserting DagView/NodeView after their deletion
The deletion PR removed the types but left ~10 sites still describing
them. Two are real breakage rather than staleness: rustdoc intra-doc
links to deleted items ([NodeView::kind] and [Self::snapshot] in
job_queue/mod.rs). Neither clippy --all-targets -D warnings nor cargo
test resolves intra-doc links, so the tree was green with both already
dangling.

The rest reassert facts the deletion made false: docs/coordinator.md
documented the event as RebuildQueueChanged { seq, queue: [DagView...] }
with a per-node field list, and three sites pointed at the removed
/api/state.rebuild_queue endpoint.

One is pointer rot rather than a rename, and no grep for a deleted name
finds it: SchedulesChanged justified itself as "same snapshot-shape
rationale as RebuildQueueChanged" -- which the deletion turned into the
one event that is not a snapshot. Repointed at TombstonesChanged /
MetaInputsChanged, in both the Rust doc and the dashboard doc.

Two are pre-existing and strictly out of scope, swept under the
pfadfinderregel because the same grep surfaced them: hive-sh4re/README
advertised a jobs module that crate has not had since the host-sock
split, and hive-host-sock/README claimed its own payload types live in
hive-sh4re.

Docs and comments only -- no behaviour, no API, no test changes.
2026-08-03 21:47:58 +02:00
iris
4ec1c61d52 swarm.js: restore the queue-summary banner on GET /api/jobq/rollup
hyperhive#3033 (jobq rollup endpoint) merged, unblocking hyperhive#3036.

The banner (removed on PR#3031 rather than ship it on an interim
GET /api/jobq/graph client-side derivation) is back, now reading
GET /api/jobq/rollup — hive-jobq-wire::state_rollup's pre-tallied
Vec<StateCount>, not the full graph. running sums the Running and
Finishing entries' roots (Finishing = own work done, subtree still
going); queued reads the Pending entry's roots. roots specifically,
matching the banner's established "N whole operations" meaning, not
the endpoint's parallel nodes count (~7 nodes per rebuild, 1 root).

Re-adds the rebuild_queue_changed SSE subscription dropped alongside
the banner, wired as a payload-less refetch trigger — confirmed with
atlas on the DagView-deletion tracker that this is the intended final
shape (keep the event, drop the payload) rather than deleting it and
falling back to polling.

Verified the running/queued derivation against constructed
StateCount[] fixtures (running+queued mix, Finishing-counts-as-running,
settled states never contribute, multi-state sums) before touching
swarm.js — 6/6 checks passed. npm run build clean, tracker-tag +
comment-block pre-push lints clean. docs/web-ui/dashboard.md updated
to describe the restored banner + the two prior shapes it went
through.
2026-08-03 21:07:53 +02:00
iris
40cc115a0a swarm.js: drop the queue-summary banner rather than ship it on an interim jobq fetch
mara, on the already-approved PR: "dont replace one legacy thing with
another. then we will have to either wait with this pr or split it
into what can and cannot be done now."

Splitting: the transients-only per-agent badge fix is real, correct,
and fixes a live regression (the old DagView fields it read no longer
exist) — nothing about it depends on job-queue data at all, so it
ships as-is. The queue-summary banner is the part that doesn't belong
in this shape: it was reading GET /api/jobq/graph directly and
deriving counts client-side as an interim stand-in for the dedicated
rollup endpoint mara separately asked for — exactly the kind of
stopgap-on-a-stopgap her comment is calling out, since the endpoint
that should serve it doesn't exist on main yet.

Removes jobqNodesState, refreshJobqGraph(), the rebuild_queue_changed
SSE subscription, and the banner's render block from swarm.js/tabs.js
entirely — swarm.js now reads no job-queue state of any kind, fully
satisfying "swarm.js should not need to pull in the jobq to do its
job." The banner comes back once the rollup endpoint
(hyperhive#2985's follow-up) exists, reading that directly instead of
the full graph. Until then the per-agent transient pills still show
what's actually running on each card; only the hive-wide "N running /
M queued" summary line is temporarily gone.

CSS classes for the banner (.queue-summary/.queue-summary-link) kept
in dashboard.css rather than deleted-then-restored — commented as
currently unused, expected to come back unchanged.

docs/web-ui/dashboard.md updated to match (Container-row pending-
badge section, the removed Build-queue-summary-banner section, and
the BU1LDS-page note that used to describe SW4RM's now-removed
parallel fetch).
2026-08-03 20:03:49 +02:00
iris
17f61d1da6 swarm.js: drop per-agent pending-badge fallback, transients-only now
mara, on review: "swarm.js should not need to pull in the jobq to do
its job" followed by "remove the per agent pending stuff - only show
what is running."

Deletes queuedOpsByAgent() entirely — no more per-agent badge derived
from Pending-state job-queue nodes. A card's pending badges are now
driven exclusively by transientsState (i.e. actually-running work);
queued-but-not-started work shows nothing on the card until a node
starts. jobqNodesState + refreshJobqGraph() stay, now feeding only
the queue-summary banner (a separate, still-open question — mara
separately asked for a dedicated rollup endpoint for that, tracked
apart from this PR).

Collapses the now-always-coincident `pending`/`pending-running` row
classes into one (`pending-running`) — there's no more queued-only
row state to visually distinguish it from.

docs/web-ui/dashboard.md's Container-row section rewritten to match:
the two-store priority-fallback description is gone, replaced with
"transients only."
2026-08-03 20:03:49 +02:00
iris
45710ab739 swarm.js: migrate pending-row fallback + queue-summary banner off DagView
hyperhive#2822/PR#3026 moved swarm.js's per-agent in-flight status off
the rebuild queue. Two other reads of the same rebuild_queue field
survived that PR by design (a different feature, atlas flagged it on
#2985) and are the last DagView/NodeView consumers on the frontend:
queuedOpsByAgent()'s pending-row fallback and the SW4RM queue-summary
banner. Both now read GET /api/jobq/graph (hive-jobq-wire's generic
GraphNode shape) instead, matching the pattern builds.js already
established for <hive-jobq-graph>.

Along the way: DagView no longer carries state/kind fields (removed
in an earlier refactor that pushed roll-up derivation client-side),
so both migrated functions were silently reading undefined fields and
had become permanent no-ops — the pending-badge fallback never lit
and the queue-summary banner never rendered. This restores real
behavior rather than porting broken logic forward.

The queue-summary banner's node-count-vs-group-count question (flagged
on hyperhive#3028 as needing a decision) resolves cleanly: a GraphNode
group root (parent: null) is an ordinary node whose own state already
IS the group's roll-up per hive-jobq-wire's contract, so counting
roots by state is a direct filter, not a parent-chain walk or a
client-side rollup calculation.

Verified the derivation logic against constructed GraphNode fixtures
(multi-step chains, settled history that must not count, Finishing
roots, multi-agent single-DAG groups) before wiring it in — 13/13
checks passed.

docs/web-ui/dashboard.md's Container-row + BU1LDS sections updated to
match.
2026-08-03 20:03:49 +02:00
iris
e269af7882 frontend: drop the queued-badge label lookup, show the raw kind
mara, PR #3026 review: "drop queuedLabelFor - just show what the
backend sends".

`queuedLabelFor` translated the rebuild-queue entry's `kind` field
through a curated English-phrase table ("meta_update" -> "meta-update
queued", "graceful_stop" -> "stop queued", etc.) for the one fallback
case where no transient exists for an agent. Removed the lookup
entirely -- `queuedOpsByAgent()` now returns the raw `kind` string
directly, and the render loop uses it as-is, the same opaque-string
treatment a transient's own `kind` already got (never run through a
lookup, per docs/web-ui/dashboard.md's existing "treat it as an opaque
display string" note). The queued-vs-running visual distinction still
comes entirely from the row's CSS classes (no ring/tint for queued,
amber ring + tint for running) -- the text was never carrying that
signal on its own, so nothing is lost by not reformatting it.

Updated docs/web-ui/dashboard.md's Container-row section to match.

npm run build clean, standalone verification re-run (17/17 checks,
one updated for the new raw-string expectation).
2026-08-03 18:47:51 +02:00
iris
d1f82e725e frontend: swarm.js off rebuild-queue-derived in-flight status onto transients
Fixes #2822.

`swarm.js` had two independent per-agent "is this in flight" sources:
`transientsState` (operator/worker-initiated ops the backend chose to
flag) and `inFlightOpsByAgent()`, a separate derivation straight from
`rebuildQueueState` covering everything else. Since #3010/#3016,
`running_transients()` is a status-only test — any `Running` job-queue
node naming a non-empty agent lights a transient pill, not just a
curated subset — so the second source's Running-state handling is now
provably redundant: a Running node with an agent always already has a
transient by the time `queuedOpsByAgent()` (renamed from
`inFlightOpsByAgent`) would be consulted.

## What changed

- `transientsState`: `Map<name, {kind, since_unix}>` (one pill per
  agent) -> `Map<name, Map<kind, since_unix>>` (several pills per
  agent). `applyTransientSet`/`applyTransientCleared` now add/remove
  by `(name, kind)` rather than overwrite/delete by name alone, using
  `TransientCleared`'s `transient_kind` field (landed in #3016) to
  know which pill cleared. `syncTransientsFromSnapshot` groups the
  now-flat `TransientView` list by name instead of assuming one row
  per agent.
- `inFlightOpsByAgent()` -> `queuedOpsByAgent()`: trimmed to the
  `Pending` (queued, not yet started) case only. The `Running` branch
  and its "running beats queued" priority logic are gone entirely —
  dead weight now that transients cover every running case
  unconditionally.
- Render loop: an agent's transients win outright whenever any exist
  (rendered as **one badge per pill**, not collapsed into one label —
  mara: "show all running nodes that name the agent"); the queued
  fallback only applies when a agent has zero transients. `opRunning`
  simplifies to "does this agent have at least one transient".
- `docs/web-ui/dashboard.md`'s Container-row section rewritten to
  match — it described a "transient, then in-flight-queue, in
  priority order" model that's no longer accurate now that the second
  source only ever fires for the one case the first can't represent.

## Verification

`npm run build` clean for both packages (dashboard + agent). Standalone
re-derivation of the transient-map + queued-fallback logic
(`/tmp/verify-swarm-transients.mjs`, not part of this diff) run against
constructed event sequences: single-pill lifecycle, two simultaneous
pills on one agent with independent clear-by-kind, clearing an unknown
kind is a safe no-op, a flat snapshot with duplicate agent names groups
correctly, the queued fallback only fires when no transient exists and
steps aside the instant one arrives, and a Running-state rebuild-queue
entry produces no queued badge (confirming the Pending-only trim is
correct, not just assumed). All 17 checks passed.

Verified directly against the merged backend rather than trusting
summaries: `job_queue/mod.rs::running_transients()` filters
`State::Running` only (not Pending — an earlier note of mine claiming
otherwise was imprecise paraphrasing), and `NodeView.agent` /
`running_transients()`'s agent both resolve through the same
`payload.agent()`, so a Running node's presence in `rebuild_queue`
and its presence as a transient are guaranteed consistent, not just
usually so.

#2985 (DagView/NodeView deletion) unblocks once this merges — atlas is
waiting on a ping.
2026-08-03 18:47:51 +02:00