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.
This commit is contained in:
iris 2026-06-01 19:39:17 +02:00
commit e9d22ef2d3
3 changed files with 271 additions and 14 deletions

View file

@ -71,14 +71,6 @@
<span class="tab-label">◆ M4TR1X ◆ →</span>
</a>
<!-- L0GS is its own page (`/logs.html`). Links to the dedicated
log viewer with three sub-tabs (build history, agent journal,
system services). Same nav pattern as FL0W. -->
<a class="tab tab-link" id="tab-logs" href="/logs.html"
title="open the log viewer">
<span class="tab-label">◆ L0GS ◆ →</span>
</a>
<!-- FL0W is its own page (`/flow.html`), not a tab. The link
lives in the tab strip so it reads as a peer surface;
clicking navigates rather than swapping panes in place.
@ -90,15 +82,33 @@
<span class="tab-count" id="tab-count-flow" hidden></span>
</a>
<!-- S3TT1NGS: operator-local preferences (browser notification
toggle today; future: theme, density). Sits at the end of
the strip because it's rarely-touched and not a primary
surface. See docs/web-ui.md::S3TT1NGS tab. -->
<!-- L0GS and S3TT1NGS: default-overflow — always live in the ⋮
dropdown, never in the main strip. `data-overflow="default"`
marks them so JS keeps them out of the bar regardless of
available width. -->
<a class="tab tab-link" id="tab-logs" href="/logs.html"
data-overflow="default"
title="open the log viewer">
<span class="tab-label">◆ L0GS ◆ →</span>
</a>
<a class="tab" id="tab-settings" href="#settings" role="tab"
aria-controls="tab-pane-settings"
data-tab="settings">
data-tab="settings"
data-overflow="default">
<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>