Commit graph hyperhive/frontend/packages/dashboard/src/tabs.js
Author SHA1 Message Date
damocles
7ec0a36d7a fix(#1218): bulk topology move uses one git commit via new set-parent-bulk endpoint 2026-06-03 23:24:55 +02:00
damocles
79265e04f8 cleanup(#1014): remove manager pinning from frontend — no special treatment for ruth 2026-06-03 16:44:29 +02:00
damocles
7a035911ef fix: use empty string as initial managerNameState default 2026-06-03 16:44:29 +02:00
damocles
6feab54882 cleanup(#1014): remove is_manager from ContainerView, add manager_name to StateSnapshot 2026-06-03 16:44:29 +02:00
iris
b13a6911b7 feat(#1098): replace deployed:hash chip with config link in three-dot menu
Remove the deployed sha plain-text badge from the card head.
Add a forge-linked menu item to the three-dot menu instead:
'deployed:abc123def012 ↗' opens agent-configs/<name>/commit/<sha>
on the hive forge in a new tab.

Only shown when both c.deployed_sha and forgeBase are present.
buildAgentMenu now takes forgeBase as second arg; call site passes
the same forgeBase computed for nav-strip links.

CSS: add text-decoration: none + box-sizing to .agent-menu-item
so <a> link items render identically to the <button> items.
2026-06-03 01:11:56 +02:00
iris
5da7f6cd3a feat(#1097): overhaul schedule table style and layout
- Wrap table in .schedules-table-wrap (overflow-x: auto) so it
  never exceeds the page width on narrow viewports
- Add .schedules-table-next-col (width: 8em) and
  .schedules-table-every-col (width: 7em) with white-space: nowrap
  to stop those columns from stealing space from the body column
- Align visual style with the perm page tables: unified border on
  all cells (was: border-top row separator only), bg-elev thead,
  font-size 0.82em (was 0.9em), min-width: 100% on the table itself
2026-06-02 22:50:25 +02:00
iris
a389ead6de fix(#1096): remove container name/port chip from dashboard card
Drop the 'h-sock :8421' meta span from the agent card head.
ContainerView.container is now #[serde(skip)] since no frontend
code reads it; the field stays in Rust for internal use.
c.port remains serialized — used for fallback agent UI URLs.
2026-06-02 22:48:41 +02:00
damocles
89665b94de feat(#1014): rename manager agent root→ruth across all crates + frontend 2026-06-02 18:18:06 +02:00
iris
2368bec634 feat(#1082): add description() to ToolGroup + Capability; expose in API + UI tooltips
- hive-sh4re: ToolGroup::description() and Capability::description() return
  short human-readable strings for each variant
- hive-c0re: ToolGroupsSnapshot and CapabilitiesSnapshot now include a
  `descriptions` map (name → description); get_capabilities now iterates
  Capability::ALL instead of hardcoding the list
- tabs.js: renderToolGroups + renderCapabilities use descriptions[name] as
  the column header title attribute (native browser tooltip on hover)
2026-06-02 12:53:50 +02:00
iris
c7f02993d0 chore(#1055): address argus review nits
- rename cap-cap-col → cap-col; add CSS rules for .cap-col and .cap-save-col
- drop zero-width-space replace in capability header cells (nowrap makes it a no-op)
- add Capability::ALL to hive-sh4re; validate incoming cap strings in post_capabilities
2026-06-02 11:15:33 +02:00
iris
013e8740bd feat(#1053): permissions tab — capabilities UI + move tool-groups
Add a new P3RM1SS10NS tab to the dashboard that consolidates all
per-agent permission configuration:

Backend:
- GET /api/capabilities returns { caps: [...], assignments: {...} }
  driven by Capability::ALL variants (manage_root_agent,
  read_host_journal, query_agent_state)
- POST /api/capabilities/{agent} writes capabilities.json and queues
  a rebuild so HIVE_CAPABILITIES takes effect

Frontend:
- New 'permissions' entry in TABS, placed after 'system'
- P3RM1SS10NS tab pane with two sections:
  C4P4B1L1T13S — agents × capabilities checkbox matrix (.cap-*)
  T00L GR0UPS — agents × tool-groups checkbox matrix (.tg-*) moved
    from SYST3M tab
- activateTab('permissions') fetches both tables; neither has an SSE
  channel so they re-fetch on each activation to stay fresh
- CSS for .cap-* mirrors the .tg-* layout (scrollable, Catppuccin)
2026-06-02 01:11:07 +02:00
iris
07e729ae24 chore: last frontend style nits — http case in common.js, setTimeout wrappers
- common.js fetchStateFile: 'HTTP' -> 'http' — last uppercase instance
  in the frontend (tabs.js, app.js, logs.js already fixed in prior PRs)
- tabs.js: two redundant arrow wrappers in setTimeout dropped —
  setTimeout(() => f(), N) -> setTimeout(f, N) where f takes no args
2026-06-01 23:34:52 +02:00
iris
2a62a561c4 chore: frontend cleanup — replaceChildren, missing source badge, http case
- Replace innerHTML = '' with replaceChildren() throughout tabs.js and
  app.js (12 + 7 sites). paintAtomic already used replaceChildren; now
  the direct-clear sites are consistent with it.
- Add missing .rqe-source-startup_sweep CSS rule (startup_sweep is a
  valid QueueSource variant but had no badge style, falling through to
  the base muted appearance with no explicit intent).
- Lowercase the one uppercase 'HTTP ' in the fetchAndRenderToolGroups
  error path to match every other fetch error in the file.
2026-06-01 22:48:21 +02:00
iris
6c75030420 fix(#1015,#1016): declare overflow vars before syncTabFromHash() call
overflowBtn/overflowDrop/overflowWrap were declared after syncTabFromHash()
was invoked. activateTab() calls updateTabbarOverflow() which closes over
these consts — hitting them in the TDZ threw ReferenceError on every page
load, also preventing fetchAndRenderToolGroups() from running (tool-groups
section stuck at loading).
2026-06-01 20:42:42 +02:00
iris
912f9c5ed2 feat(#1007): select-as-action — picking a parent directly triggers M0V3
Selecting any option in the M0V3 dropdown now fires confirm+POST
immediately; no separate button needed. On cancel or after the
request completes, the select resets to the placeholder. Removes
the now-redundant btn-move button and its CSS rule.
2026-06-01 20:31:35 +02:00
iris
1f5197a0a5 feat(#1007): unify M0V3 → ROOT and reparent into single picker
Remove the separate '⇡ M0V3 → ROOT' button from the selection bar.
Add '(no parent)' as the first real option in the existing M0V3
dropdown — selecting it submits an empty new_parent, which the backend
already treats as 'promote to root'.

The select placeholder label changes to '⇢ M0V3 →' so the combined
control reads naturally without the old standalone button. The submit
button enables as soon as any option past the placeholder is chosen
(selectedIndex > 0), which correctly covers both '(no parent)' and
named-parent selections.
2026-06-01 20:31:35 +02:00
iris
a7d31046a6 refactor(#1005): rename capabilities → tool-groups throughout UI
Section heading, element id, CSS classes, and JS functions all renamed
from 'capabilities'/'cap-*' to 'tool-groups'/'tg-*' to accurately
describe what the UI manages (tool-group permissions, not a capabilities
system).
2026-06-01 20:27:28 +02:00
iris
86a1591cfc feat(#1005): capabilities UI — per-agent tool-group table in SYST3M tab
Backend (hive-c0re/src/dashboard.rs):
  GET /api/tool-groups  — returns { groups: [...], assignments: {...} };
    groups list comes from ToolGroup::ALL so the UI needs no change when
    a new group is added (satisfies the 'no extend ui' requirement)
  POST /api/tool-groups/{agent} — accepts { groups: [...] }, calls
    set_groups() then enqueues a rebuild so the new HIVE_TOOL_GROUPS
    env var takes effect immediately

hive-sh4re/src/lib.rs:
  Added ToolGroup::ALL const (ordered slice of every group)
  Added ToolGroup::as_str() — snake_case wire name, matches serde

Frontend:
  SYST3M tab: new C4P4B1L1T13S section above K3PT ST4T3 with
    #capabilities-section placeholder
  tabs.js: fetchAndRenderCapabilities() + renderCapabilities() —
    columns are built from the groups array returned by the API;
    each row has one checkbox per group and a save button that POSTs
    and re-fetches after 800ms; agents without explicit assignments
    show a (default) label; triggered on each SYST3M tab activation
  dashboard.css: .cap-table-wrap/.cap-table/.cap-row/.cap-agent-*
    styles for the scrollable matrix table
2026-06-01 20:19:11 +02:00
iris
e045a29508 fix(#994,#996): replace undefined CSS vars; guard overflow rebuild when open
dashboard.css used var(--surface0/1/2) and var(--text) throughout the
agent context menu, tabbar overflow dropdown, and logs toolbar — none
of which are declared in base.css (the palette only defines --bg-elev,
--border, --purple-dim, --fg etc). This caused all three dropdowns to
render with a transparent background (issue #996).

Replacements:
  --surface0  → --bg-elev   (dropdown background)
  --surface1  → --border    (hover / active state)
  --surface2  → --purple-dim (border / separator)
  --text      → --fg        (label colour)

Also addresses two argus yellows from the PR #997 review:
- Skip updateTabbarOverflow() early when the dropdown is open; avoids
  DOM flicker from the 1s badge-update tick while the menu is visible.
- Fall back to 32px when overflowBtn.offsetWidth === 0 (hidden on the
  first render) so the initial width measurement is not off.
2026-06-01 19:49:43 +02:00
iris
e9d22ef2d3 feat(#994): tabbar overflow menu — settings and logs in ⋮ by default
Add a ⋮ overflow button at the right end of the dashboard tab strip.

SETTINGS and LOGS always live in the overflow dropdown (marked
data-overflow="default" in index.html — never rendered in the main bar).

Dynamic overflow: when the bar is too narrow to fit all remaining tabs,
rightmost tabs spill into the dropdown right-to-left. Implemented via
JS measurement + ResizeObserver; no CSS-only relayout trick needed.

The ⋮ button:
- hidden when the dropdown is empty (wide screens with only the default
  tabs overflowed, which are always there anyway → button always shown)
- gets .tabbar-overflow-active when the current hash tab is inside
- dropdown items clone the tab label + count badge from the original
- closes on outside click / Escape

MutationObserver on the tabbar catches P33RS/M4TR1X hidden-attribute
changes so the overflow recalculates when those tabs are shown/hidden.
2026-06-01 19:39:17 +02:00
iris
0b15cad93f feat(#986): dedicated logs page with build/agent/system sub-tabs
Add /logs.html as a standalone page (same back-link pattern as flow.html):
- BUILD tab: all-agents build log history via new GET /api/build-logs endpoint
- AGENT tab: per-container journald viewer with agent selector + unit filter
- SYSTEM tab: host-side hive-c0re.service logs via new GET /api/journal-host endpoint

Remove inline log drill-ins from SW4RM container rows (buildJournalTrigger
and buildBuildLogsTrigger) — log viewing now lives on the dedicated page.

flow.html: strip the full dashboard tabbar, replace with a simple back link
matching the new logs page chrome.

index.html: add L0GS tab link to /logs.html in the tab strip.

Backend additions:
- build_logs::list_recent_all — cross-agent query (newest first, cap 100)
- GET /api/build-logs — all-agents variant backed by list_recent_all
- GET /api/journal-host — host journald (no -M container flag), restricted
  to allow-listed units (hive-c0re.service)
2026-06-01 19:20:54 +02:00
iris
ba43b5f869 fix(988): hide inapplicable actions in agent context menu
Per mara feedback: don't show disabled actions at all.
- restart/stop only rendered when agent is running
- start only rendered when agent is stopped
- removed .agent-menu-item:disabled CSS rule (no longer needed)

Also switch Escape handler to stopImmediatePropagation so the
selection-clear keydown listener doesn't co-fire when closing a menu.
2026-06-01 19:18:18 +02:00
iris
28e0d1b7fa feat(987): add per-agent three-dot context menu on SW4RM tab
Each agent card in the SW4RM tab now has a ⋮ button that reveals a
dropdown with single-agent lifecycle actions: R3ST4RT, ST0P, ST4RT,
R3BU1LD, DESTR0Y, PURG3. Items are state-aware (disabled when action
doesn't apply). The button is CSS-invisible until the row is hovered
or the menu is open — quiet rows stay clean. DESTR0Y and PURG3 are
hidden for the manager container.

Click-outside and Escape close the menu. A tree-rebuild (on any SSE
state update) also closes it to avoid stale DOM references.
2026-06-01 19:18:18 +02:00
iris
f39015e41d fix(961): remove stale manager/hm1nd references from dashboard tabs.js
Three post-rename artifacts:

1. journalUnit: was 'hive-m1nd.service' for the manager branch.
   Both roles use 'hive-ag3nt.service' since the harness unification —
   always use 'hive-ag3nt.service'.

2. buildTargetChips: hardcoded ['operator', 'manager'] candidates +
   filter by n !== 'manager'. After the rename the manager's c.name
   is 'root', so it was excluded from the filter but not from the
   hardcoded initial list — the manager could appear twice or be listed
   as 'manager' when its real broker name is 'root'. Fix: derive
   the manager's name from c.is_manager on containersState; filter
   sub-agents by !c.is_manager rather than by hardcoded string.

3. schedulesTableAgentSet: same hardcoded 'manager' + filter pattern
   as buildTargetChips. Same fix applied.
2026-06-01 17:55:15 +02:00
damocles
aa96d76e73 build-logs: extract compute_runtime_secs helper; clamp runtime in js 2026-06-01 17:42:35 +02:00
damocles
26c9c13cf5 build-logs: surface runtime_secs in header; show duration in dashboard row 2026-06-01 17:36:29 +02:00
iris
188f3ea5ec chore: scrub #NNN issue references from code comments and nix descriptions
Part of the docs-migration chore (issue #708). Remove GitHub issue
numbers from inline comments, option descriptions, and rustdoc —
these are contextless noise for anyone reading the code without
access to the original discussions. Replace with prose that captures
the same rationale directly.

No functional change. Build still clean (cargo check passes).
2026-06-01 13:30:52 +02:00
iris
c353dc1893 fix: emit relative container link URLs from harness, update dashboard join
Container link URLs (/screen.html, /stats.html) were root-relative,
which works when the agent is accessed directly at :port/ but resolves
to the gateway root when accessed via /agent/<name>/.

Fix at the source: harness emits relative URLs (screen.html, stats.html)
and the dashboard joins containerBase + '/' + url. The agent page uses
the URL directly and relative resolution is now correct in both paths.
2026-05-31 23:24:39 +02:00
iris
7a8b8fe266 feat: P33RS tab — peer hive link cards in dashboard
Adds the P33RS tab to the dashboard. The tab is hidden by default and
only shown when at least one peer hive is configured in
`services.hyperhive.peers`. When visible, it renders a card per peer
with a link to that hive's dashboard.

Backend (HYPERHIVE_PEERS env var, PeerHiveView in StateSnapshot) is
wired by a separate PR. This commit is frontend only:
- index.html: P33RS tab strip entry + tab-pane-peers section
- tabs.js: renderPeerHives(), gate logic, 'peers' added to TABS array
2026-05-31 23:14:15 +02:00
iris
d3ca857659 fix(#888): use gateway forge URL in dashboard links
Dashboard links to the forge were hardcoded as http://<hostname>:3000,
which breaks when the operator accesses the dashboard through
hive-gateway (forge is served at forge.<domain> with no port).

- nix/modules/hive-c0re.nix: inject HIVE_FORGE_PUBLIC_URL when
  forge.behindGateway = true (e.g. http://forge.pr1ma.darkest.space)
- hive-c0re/src/dashboard.rs: expose forge_url: Option<String> in
  StateSnapshot, reading from HIVE_FORGE_PUBLIC_URL
- frontend/packages/dashboard/src/tabs.js: use state.forge_url when
  present; fall back to http://<hostname>:3000 for gateway-off /
  local-dev deploys
2026-05-31 22:04:52 +02:00
iris
12985ade6d fix(#726): handle broadcast Lagged + clear on SSE reconnect
Distinguish RecvError::Lagged from Closed in the build-log stream
loop: Lagged continues (next recv delivers the full accumulated delta
via saved cursors), Closed returns (shutdown path).

Clear pre.textContent and reset cursor lengths in es.onerror when
readyState is CONNECTING so auto-reconnect doesn't double-append the
replay from cursor=0.
2026-05-31 21:48:31 +02:00
iris
050e130eba build-logs: SSE live streaming + raw download (#726 phase 4)
Backend (hive-c0re):
- build_logs.rs: add tokio::sync::broadcast::Sender<i64> to BuildLogs;
  append() and finish() notify subscribers after each sqlite write.
  Add BuildLogProgress struct and get_progress(id, stdout_cursor,
  stderr_cursor) for incremental delta reads.
- dashboard.rs: two new endpoints —
    GET /api/build-logs/id/{id}/stream  SSE; streams BuildLogFrame
      {stdout_append, stderr_append, status?, done} deltas until the
      build finishes or the browser disconnects. Backed by an mpsc
      channel task that watches the per-build broadcast notifications.
    GET /api/build-logs/id/{id}/raw    text/plain download with
      Content-Disposition: attachment; filename build-log-{agent}-{id}.txt

Frontend (dashboard):
- tabs.js: running builds (status === null) connect an EventSource to
  /stream and append lines live; "live" badge pulses amber while active,
  flips to ok/fail on done. Finished builds still use the JSON fetch path.
  Collapsing a running panel closes the EventSource; re-expanding
  reconnects. Adds a "⬇ raw" download link to every expanded row.
- dashboard.css: .build-logs-dl inline download link; .build-logs-live
  live pulse @keyframes animation.

Docs: web-ui.md updated for all three new endpoints + behaviour.
2026-05-31 21:48:31 +02:00
iris
c4ce91b61f build-logs viewer: add --crust var + aria-expanded on row buttons (argus nits) 2026-05-31 21:48:31 +02:00
iris
cc6227ccb5 dashboard: build-log side-panel viewer in agent card (#726 phase 3)
Adds a `↳ build logs · {agent}` drill-in to every agent card that
opens the side panel and fetches the last 10 build-log headers from
GET /api/build-logs/{agent}. Each row shows a status chip (ok/fail/
running), kind, age, and truncated cmdline. Clicking a row lazily
fetches the full stdout+stderr from GET /api/build-logs/id/{id} and
expands it inline as a scrollable pre.

CSS additions: .build-logs-{panel,toolbar,list,item,row-btn,...} plus
.badge-ok/.badge-fail/.badge-running status chips shared with future
uses. docs/web-ui.md updated with the new drill-in and the new badge
classes.
2026-05-31 21:48:31 +02:00
iris
e1e5195081 dashboard + agent UI: surface hive_name + swarm_name in chrome (#701)
Damocles's backend (c41bf1b) landed HYPERHIVE_HIVE_NAME / _SWARM_NAME
env vars and identity.rs accessors. This commit wires them into the two
web surfaces:

hive-c0re/dashboard.rs:
  StateSnapshot gains `hive_name` + `swarm_name` (Option<String>),
  populated from the env vars the c0re NixOS module injects.

hive-ag3nt/web_ui.rs:
  Per-agent StateSnapshot gains the same two fields, populated from
  crate::identity::hive_name() / swarm_name().

Dashboard frontend (index.html + tabs.js):
  A `<p class="banner-thin" id="hive-identity">` sits above the tab
  strip in the chrome. refreshState() populates it with "swarm / hive"
  (or just "hive") when the fields are non-null, and updates
  document.title to "<swarm>/<hive> // h1ve-c0re". No change to
  chrome when both fields are null (backward compat).

Per-agent frontend (index.html + app.js + agent.css):
  A hidden `.agent-hive-label` span under the title row shows the
  swarm/hive label once setHeader() receives non-null names.
  document.title is updated to "<label> // <hive_name>" when hive_name
  is set, enabling browser-tab disambiguation when multiple hives are
  open in parallel. Styled as a small uppercase purple-dim sub-label.
2026-05-31 21:09:08 +02:00
iris
6cf169d324 dashboard: new S3TT1NGS tab housing the notification toggle (closes #763)
Adds a fifth tab (◆ S3TT1NGS ◆) at the end of the dashboard tab
strip; moves the browser-notification controls out of the chrome
header into the new pane.

### Why

The chrome was carrying the notification controls as 'always-on
ergonomics' but the surface they belong on is operator preferences,
not navigation. With #589 / #701 likely adding more per-browser
preferences later (theme / density / etc.), having a dedicated
S3TT1NGS pane gives them a home so the chrome strip doesn't keep
accreting controls.

### Shape

- index.html: new `#tab-pane-settings` section with H2 + intro +
  H3 'browser notifications' group; same notif-row DOM (same IDs)
  migrated from the chrome
- flow.html: drops the duplicated notif-row from its chrome (FL0W
  reused the dashboard's strip); adds a `◆ S3TT1NGS ◆ →`
  cross-page link to `/#settings`. Notifications still fire on
  FL0W when enabled — NOTIF.show() reads
  Notification.permission + the hyperhive.notify.muted localStorage
  key directly, no buttons needed in the page DOM.
- tabs.js: adds 'settings' to TABS array so hash routing activates
  the pane
- dashboard.css: drops the chrome-anchoring `.tabbar #notif-row`
  rule and the flow-page reuse comment, both obsolete; the general
  `.notif-row` styling stays
- docs/web-ui.md: new `### S3TT1NGS tab` section; Chrome-header
  bullet drops 'Notification controls' + adds S3TT1NGS; Browser
  notifications section pointers at the new pane

### No JS or backend changes

Same IDs (notif-row, notif-enable, notif-mute, notif-unmute,
notif-status) so `NOTIF.bind()` in common.js wires the new pane
unchanged. NOTIF.show() dispatch is DOM-independent.

### Validation

`npm run --workspace @hive/dashboard build` clean (tabs.js 167.8kb,
flow.js 37.4kb, dashboard.css 50.2kb).
2026-05-31 17:55:30 +02:00
iris
fbc41d42a6 dashboard: build same-origin /agent/<name>/ links when gateway is up (closes #842)
When `services.hyperhive.gateway.enable` is on (default), the c0re
NixOS module now sets `HIVE_GATEWAY_ENABLED=1` on the service env.
`/api/state` exposes the flag as `gateway_enabled`; the SW4RM tab's
container-row renderer flips three link sites (primary agent-name
link, favicon fetch, `container`-kind nav-strip links) from the
legacy `http://<host>:<port>/` direct TCP shape to same-origin
`/agent/<name>/` URLs — the gateway proxies them to the per-agent
harness via `agent-ports.json` or `agent-sockets.json` (#784 / #815).
Gateway-off deploys keep the direct TCP fallback so local-dev /
operator opt-out keeps working.

`forge`-kind nav-strip links still resolve against `:3000` (separate
sub-domain transition, tracked by `forge.behindGateway`);
`external`-kind links are already absolute.

Mirrors the `HIVE_MATRIX_GUI_ENABLED` env→snapshot-flag pattern.
Docs updated: `docs/web-ui.md::Container row` + new
`docs/gateway.md::Dashboard link shape` section.
2026-05-31 17:16:53 +02:00
iris
b15c534e67 tabs.js: final cookie scrub — 0 issue refs (#712 batch 14)
Final pass on tabs.js. All 6 remaining cookies were either
attribution refs to closed issues / past reviews or pure-history
mile-markers:

- #259 disabled meta-update state → drop attribution
- dashboard.rs#2170 backend handler line ref → drop the line
  number (handler still lives in dashboard.rs, line drifts)
- mara on #695 M0V3 picker any-selection → drop attribution
  (substance already captured in docs/web-ui.md::Selection bar)
- #541 journald scroll surface → drop the migration history
  framing, keep present-state behaviour
- argus on #471 textContent vs innerHTML review → drop the review
  attribution, keep the safer-pattern rationale inline
- #369 tab routing section header → drop the cookie, keep the
  section divider

tabs.js: 6 → 0 issue-ref cookies (100% reduction). Across
batches 1-14 (#712): 73 → 0 issue-ref cookies, with substantive
prose (Topology tree CSS geometry, Container row icon layout,
Pending-state derivation, Selection bar, R3BU1LD QU3U3 cancel +
step annotations, Approval card requested-at, SSE multiplexing
worker-death self-heal, FL0W page MESS4GE FL0W body layout)
migrated to docs/web-ui.md across the batches.

dashboard SPA files all at 0 cookies now (PR #795 covers the
other dashboard files; this PR covers the tabs.js residual).
2026-05-31 15:14:53 +02:00
iris
ea1c6e4d05 tabs.js + docs: migrate icon + pending-state prose to docs (#712 batch 11)
Mara on #774: previous batches were cookie-stripping rather than
prose migration. This batch actually moves substantive comment
prose from tabs.js into docs/web-ui.md.

Moved to docs/web-ui.md:
- Container row → **Icon layout + load strategy** sub-paragraph
  (new): absolute-positioning rationale (so img load state can't
  reflow row), fire-and-forget load pattern (no pre-check
  reachability), favicon fallback chain, icon-unreachable class,
  the immediate-fallback-when-stopped optimisation.
- Container row → **Pending-state derivation** paragraph (new):
  three-source priority order (transient → in-flight queue → none),
  why ContainerStateChanged isn't enough, the opRunning flag's role
  in driving the pending-running class + spinner.

Collapsed in tabs.js:
- Icon block (~14 lines of WHY comments + pointer to docs) →
  4-line pointer + behavioural one-liner. Drops #177 / #195 / #202
  cookies en passant since their substance now lives in docs.
- Pending-state block (~22 lines split across two paragraphs) →
  4-line pointer. Drops #769 self-cookie (the queued vs running
  split lives in docs::Container row now).
- SharedWorker EventSource (~7 lines) → 5-line pointer. Drops
  #448 cookie (the SSE multiplexing paragraph in docs already
  has the design + Firefox throttling rationale; the in-code
  comment was duplicating).
- M4TR1X tab gating (~4 lines) → 2-line pointer. Drops #607 cookie
  in both tabs.js + docs/web-ui.md::Tab strip (the substance was
  already in docs, just had the cookie attached).

tabs.js: 11 → 6 refs (92% reduction from baseline 73). Net effect:
~47 lines of substantive prose moved out of tabs.js into
docs/web-ui.md, where it belongs.
2026-05-31 14:23:49 +02:00
iris
68c2b3284e tabs.js + docs: drop rebuild-queue cookies (#712 batch 10)
#437 (multi-step progress on rebuild_queue entries — closed) ×2:
in tabs.js current-step comment + in docs/web-ui.md prose
referencing the phase annotation.

#501 (PR — merged for #437) ×1: paired with #437 in the same
tabs.js current-step comment.

#575 (X button to cancel pending builds — closed) ×2: in tabs.js
cancel-form comment + in docs/web-ui.md cancel-button prose.

#436 (route approval execution through rebuild_queue — closed)
×1 in docs/web-ui.md: scrubbed the parenthetical "; #436" next
to the approval source-chip description (small freebie since I
was editing that paragraph anyway). The approval-as-source-chip
is the implementation of #436; the cookie was just history.

Pre-scanned docs for matching cookies; caught all 3 doc cookies
in the same PR.
2026-05-31 14:16:30 +02:00
iris
c527f739ec tabs.js: drop scheduled-prompts cookies (#712 batch 9)
#459 (dashboard tab + creation form for scheduled prompts —
closed) ×2 (section header + tab-activation re-fetch comment) +
#444 (make it possible to schedule prompts — closed) ×1 (Backend
endpoint enumeration preamble) + #535 (schedules-as-table —
closed) ×1 (renderSchedulesList layout rationale). The
section-header comment now reads as a pure mile-marker; the
backend-endpoints preamble enumerates what the API surface is
today; the table-layout comment describes the rendered shape.
No docs/web-ui.md matches for any of #444 / #459 / #535.
2026-05-31 14:12:44 +02:00
iris
272d8af620 dashboard: split queued vs running rebuild visual (#769)
Container row's pending highlight fired for both queued + running
rebuild-queue entries, so a long queue painted half the SW4RM tab
amber. Mara on #769: don't highlight while queued, show running as
spinner on agent icon.

- tabs.js: derive opRunning (transient OR op.state === 'running')
  separately from pending; add a pending-running row class.
  Operator-initiated transients still count as running so the
  rebuild-now-clicked → backend-picks-it-up window stays visually
  consistent.
- dashboard.css: move the amber border + tint from .pending to
  .pending-running (queued rows now keep their normal look, with
  the badge text still saying "rebuild queued"). Add a 1s linear
  rotating amber arc as .pending-running > .container-icon::after,
  with overflow:visible so the ring sits just outside the icon and
  composes with the mauve selected ring.
- docs/web-ui.md::Container row: describe the queued vs running
  split in prose. Drop the orphaned (#398) cookie while I'm at it
  — small #712 freebie.
2026-05-31 14:10:37 +02:00
iris
4ab4d99340 tabs.js + docs: drop #272 + #275 cookies (#712 batch 8)
#272 (show approval requested-at — closed) ×2 in tabs.js (live
ApprovalAdded fallback note + amber stale chip comment) + ×1 in
docs/web-ui.md (Approval card identity header). #275 (select all
for meta inputs — closed) ×1 in tabs.js (bulk-select rationale)
+ ×1 in docs/web-ui.md (M3T4 1NPUTS tree control note). The
amber-stale chip line in tabs.js now points at
docs/web-ui.md::Approval card for the rendered spec instead of
the closed issue.
2026-05-31 14:01:57 +02:00
iris
3c2fffabdf tabs.js: drop #406 module-split + #408 forward-looking cookies (#712 batch 7)
#406 (split app.js → tabs/flow/common) closed; the migration is
done — the 'moved to ./flow.js' / 'pre-step-2 this wiring lived
in the broker-terminal IIFE' commentary is git-history, not
present-state docs. #408 (split flow messages from main sse) also
closed; the 'will give /index.html its own stream' line was
forward-looking design that didn't ship that way. Comments
tightened to describe what the code does today; the section
headers, behaviour notes, and reconnect-rationale all stay
intact.
2026-05-31 13:35:18 +02:00
iris
8134b955e2 tabs.js: scrub #262/#486/#163 cookies from agent links + topology move + SSE resync (#712 batch 6)
Three small clusters of cookies removed:

- #262 (×2): agent-declared dashboard links via /api/agent/<name>/links.
  Both comment blocks describe the same architectural decision
  (agent backend = source of truth, DOM-built so link strings can't
  reach the HTML parser). The cookies just attributed the decision
  to a PR; the prose stands without them.
- #486 (×2): M0V3 topology affordances + addBulkButton perAgentBodyFor
  hook. Substantive prose moved to docs/web-ui.md::Selection bar in
  #695 already; in-code comments now reference the docs.
- #163 (×3): snapshot re-sync + SSE catchup idempotence. The cookies
  flagged 'this is why the guard exists'; replaced with the present-
  tense 'post-disconnect SSE catchup can carry duplicate rows' which
  reads as the actual reason without needing the issue context.

tabs.js: 31 → 24 #NNN refs (-7). Net +1 line (the rewrites are
sometimes slightly longer when 'issue #N' is replaced with the
substantive description; net is still ahead). 67% reduction since
milestone start.

refs #712
2026-05-31 13:13:07 +02:00
iris
9d9cac2f61 tabs.js: drop schedule + flow-split + TTL-ticker cookies (#712 batch 5)
Four clusters of cookies in tabs.js, all pointing at prose that's
either already in docs/web-ui.md::SCH3DUL3S tab or self-evident
from the code:

- #564 (×4): inline-create row + carry-state for schedules. Layout
  rationale is in the docs; the in-code comments describe what the
  state does, which stands on its own.
- #474 (×3): inline edit form + targets multi-select for
  schedules. Same pattern — docs cover the UX, code comments
  describe the implementation pieces.
- #399 (×4): null-guard for tabs.js sections that may not exist on
  /flow.html. The 'pre-split this code lived in flow.js' historical
  paragraph dropped entirely; replaced with the present-tense
  no-op-when-target-absent convention statement.
- #335 (×3): question-TTL chip ticker. Cookies dropped; substantive
  text stays.

Plus #466 (interval composer shared use) and some adjacent cleanup
from the same passes — small additional drops folded in.

tabs.js: 48 → 31 #NNN refs (down from 73 at start of milestone).
Net -8 lines. Functional code unchanged; build clean.

refs #712
2026-05-31 13:03:48 +02:00
iris
4c30b52e48 tabs.js: scrub #398/#432 cookies from container-card render path (#712 batch 4)
Seven cookies removed across the container-card rendering code (4
#398, 3 #432). All point at existing docs/web-ui.md::Container row
prose (rebuild_queue building badge, stopped-container state
clearing). Each comment shrinks to either a short docs ref or just
drops the cookie where the substantive content already explains
itself.

Net ±0 lines in tabs.js (rewrites are similar length); the win is
each comment now reads as 'what this code does' rather than 'why
PR #N decided this'. tabs.js is now at 48 #NNN refs (down from
55 at start of this batch, 73 at start of the milestone).

refs #712, builds on #722 + #724 + #728
2026-05-31 12:35:08 +02:00
iris
10097bb9b6 tabs.js: collapse stale dangling sentence in per-card actions comment (argus #728)
When I rewrote the original 'Per-card action buttons … moved into
the sticky #selection-bar (see renderSelectionBar) which appears
when the operator has at least one agent selected via the icon
click' comment to use the docs ref, I shrunk part of it but left
the second sentence as a dangling fragment glued onto the new
short ref. Collapse cleanly: docs ref + the still-useful
`needs update ↻` chip note.
2026-05-31 12:16:27 +02:00
iris
69debe6b7d tabs.js + web-ui.md: drop 'no special-case manager' framing (mara on #728)
mara's review nit: explaining the absence of a manager special-case
implicitly endorses the idea that special cases would be normal.
Default is no special case; the doc shouldn't dwell on it.

- ST0P comment block removed entirely (the bulk-button line above
  is self-explanatory; the substantive 'c0re survives manager-down'
  rationale lives in the host docs, not here).
- M0V3 helper comment reframed: trim the 'no special-case' framing
  but keep the substantive note that the backend refuses moves it
  can't satisfy and the failure surfaces in the bulk roll-up.
- docs/web-ui.md::Selection bar bullet list: drop the 'manager
  included; no special-case' qualifiers; just describe the action.
  M0V3→ROOT keeps the rationale that backend refusals surface in
  the roll-up but stops calling out manager specifically.
2026-05-31 12:16:27 +02:00
iris
a9422518e5 docs: scrub selection-bar #443/#596 cookies in tabs.js, add interaction-model preface (#712 batch 3)
selection + selection-bar in tabs.js carried six #443 cookies + two
#596 cookies — most marking the bulk-bar architectural decision
('actions live in bar, not on per-card buttons; manager not
special-cased') or the SW4RM-tab gate. All scrubbed; the canonical
docs/web-ui.md::Selection bar section already covered the bulk
actions in detail but was missing a preface for the **interaction
model** itself (how selection enters/exits, why per-card buttons
moved). Added that as the opening paragraph of the section so the
in-code refs have something to point at.

Net in this batch:
- tabs.js: -8 #NNN refs (#443 ×6, #596 ×2)
- docs/web-ui.md: +9 lines (interaction-model preface)
- functional code unchanged; build clean

refs #712
2026-05-31 12:16:27 +02:00