refactor(frontend): migrate dashboard tabbar to createTabStrip + drop overflow (#1464 step 1)

The dashboard's tab strip now runs on the shared createTabStrip
(@hive/shared/tabs.js) — the third and final consumer of #1464 step 1
(after logs + the agent window selector).

- activateTab is reduced to the per-tab side-effects only
  (body.dataset.activeTab, the selection-bar gate, the lazy-loads);
  createTabStrip owns the active tab/pane toggle + aria-selected + hash
  routing, calling activateTab via onShow. Panes switch via the `hidden`
  attribute (was a `.tab-pane-active` class) — the markup carries `hidden`
  so there's no flash before the script runs.
- the responsive overflow menu is DROPPED per the design call (move panes
  to their own pages rather than hide them in a dropdown). Removes ~150
  lines: updateTabbarOverflow, closeOverflowMenu, the click/keydown
  handlers, the ResizeObserver + MutationObserver, and syncTabFromHash.
  S3TT1NGS is a regular strip tab now (no data-overflow).
- CSS: .tab.active becomes .tab.hive-tab--active; pane visibility is now
  .tab-pane[hidden] { display: none }; the overflow dropdown styles are
  gone. Count pills (setTabCount/refreshTabCounts) are unchanged.

Behaviour-preserving except the deliberate overflow removal; aria-selected
is now standardised on the tabs.
This commit is contained in:
iris 2026-06-08 22:47:12 +02:00 committed by mara
commit 2ec06f07ed
3 changed files with 37 additions and 320 deletions

View file

@ -78,39 +78,29 @@
into the log viewer (an agent's logs, a build entry's log)
still live inside the relevant dashboard content. -->
<!-- S3TT1NGS: default-overflow — always lives in the ⋮ dropdown,
never in the main strip. `data-overflow="default"` marks it so
JS keeps it out of the bar regardless of available width. -->
<!-- S3TT1NGS: lock-bump + matrix-gui config. A regular tab in the
strip now (the responsive overflow ⋮ menu was dropped — if the
strip ever runs out of room we move panes to their own pages
rather than hide them). -->
<a class="tab" id="tab-settings" href="#settings" role="tab"
aria-controls="tab-pane-settings"
data-tab="settings"
data-overflow="default">
data-tab="settings">
<span class="tab-label">◆ S3TT1NGS ◆</span>
</a>
<!-- Overflow ⋮ button + dropdown. JS populates the dropdown with
tab clones and keeps the button hidden when the dropdown would
be empty. Gets `.tabbar-overflow-active` when the active tab
is inside. -->
<div class="tabbar-overflow" id="tabbar-overflow">
<button type="button" class="tabbar-overflow-btn" id="tabbar-overflow-btn"
aria-haspopup="menu" aria-expanded="false"
title="more tabs" hidden>⋮</button>
<ul class="tabbar-overflow-dropdown" id="tabbar-overflow-dropdown"
role="menu" hidden></ul>
</div>
</nav>
</header>
<!-- Tab panes. Exactly one is `.tab-pane-active` at a time;
JS toggles based on the URL hash + `hashchange` events. -->
<!-- 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
the script runs. -->
<main class="dashboard-main">
<!-- SW4RM: the swarm itself. 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. -->
<section class="tab-pane" id="tab-pane-swarm"
<section class="tab-pane" id="tab-pane-swarm" data-tab-pane="swarm" hidden
role="tabpanel" aria-labelledby="tab-swarm">
<!-- Swarm / hive identity headline. Populated by refreshState from
hive_name + swarm_name; stays hidden when neither is set. -->
@ -133,7 +123,7 @@
questions read as the same concept ("something is waiting on
you"); both surface their full bodies inline so the operator
can decide without leaving the pane. -->
<section class="tab-pane" id="tab-pane-call"
<section class="tab-pane" id="tab-pane-call" data-tab-pane="call" hidden
role="tabpanel" aria-labelledby="tab-call">
<!-- 1NB0X: unread agent→operator messages (#1469). Fetched on
tab activation + cold load, appended live from the broker
@ -162,7 +152,7 @@
bumps), rebuild queue (watch only), kept state from previous
tombstoned agents. Per-section content auto-compresses to a
one-line summary when empty (JS toggle). -->
<section class="tab-pane" id="tab-pane-system"
<section class="tab-pane" id="tab-pane-system" data-tab-pane="system" hidden
role="tabpanel" aria-labelledby="tab-system">
<h2>◆ M3T4 1NPUTS ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
@ -201,7 +191,7 @@
(GET /api/capabilities, GET /api/tool-groups) so new entries
require no UI change. Saving POSTs to the respective
/{agent} endpoint and queues a rebuild. -->
<section class="tab-pane" id="tab-pane-permissions"
<section class="tab-pane" id="tab-pane-permissions" data-tab-pane="permissions" hidden
role="tabpanel" aria-labelledby="tab-permissions">
<h2>◆ C4P4B1L1T13S ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
@ -233,7 +223,7 @@
`schedules_changed` SSE; tab activation re-fetches as a
safety net for disconnect windows.
See docs/web-ui.md::SCH3DUL3S tab. -->
<section class="tab-pane" id="tab-pane-schedules"
<section class="tab-pane" id="tab-pane-schedules" data-tab-pane="schedules" hidden
role="tabpanel" aria-labelledby="tab-schedules">
<h2>◆ SCH3DUL3S ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
@ -262,7 +252,7 @@
/api/stats-hive?window=, fetched on tab activation + on
window change. Per-agent trend charts stay on each agent's
own /stats page. -->
<section class="tab-pane" id="tab-pane-stats"
<section class="tab-pane" id="tab-pane-stats" data-tab-pane="stats" hidden
role="tabpanel" aria-labelledby="tab-stats">
<h2>◆ ST4TS ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
@ -297,7 +287,7 @@
The same IDs that used to live in the tab strip migrated
here unchanged so `NOTIF.bind()` in common.js wires them
without any JS change. -->
<section class="tab-pane" id="tab-pane-settings"
<section class="tab-pane" id="tab-pane-settings" data-tab-pane="settings" hidden
role="tabpanel" aria-labelledby="tab-settings">
<h2>◆ S3TT1NGS ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>