flow: keep the dashboard tab strip on the flow page (#383)

Operator wanted the tab header visible on /flow.html so switching
tabs doesn't require navigating back to / first.

The flow page now reuses the same `<header class="dashboard-chrome">`
markup the dashboard renders, with a few tweaks:

- The SW4RM / Y3R C4LL / SYST3M tabs are cross-page links
  (`href="/#swarm"` etc.) — clicking lands on the dashboard with the
  destination tab pre-active via the hash router.
- The FL0W tab is rendered `.active.tab-link` + `aria-current="page"`
  so it reads as the current view (no clickable arrow / "go here"
  affordance — you're already here).
- Banner-thin echoes the dashboard for visual continuity.
- Notif controls cohabit with the tabs (same IDs the dashboard uses,
  so app.js's NOTIF binding picks them up unchanged).

Layout glue:
- `body.flow-shell .dashboard-chrome.flow-chrome` overrides the
  dashboard's `position: sticky` with `position: fixed` so the
  chrome stays put under flow-shell's `overflow: hidden` body
  layout, keeping the terminal full-viewport behind/beneath.
- New rule for the active FL0W tab — the `.tab-link` styling on the
  dashboard otherwise reads as a passive cross-page link; here we
  need it lit-up like a regular active tab.
- `--flow-header-h` bumped from 4.2em → 4.7em to match the natural
  height of the tab strip + banner combo. Terminal padding +
  inbox-pill top offset both derive from this variable, so they
  follow automatically.

Removed:
- Legacy `.flow-title`, `.flow-hint`, `.flow-back` CSS rules (their
  HTML counterparts are gone — the tab strip carries the
  identity now).
- The `<a class="flow-back">← d4shb04rd</a>` link and the
  `<h2 class="flow-title">` from flow.html.

## Validation

`npm run build` clean.
  dashboard.css: 38kb → 37kb (legacy rules removed, new shared-
                  chrome rules are smaller)
  flow.html:    4.4kb → 4.7kb (tab strip replaces title bar)
  app.js:       unchanged (no JS changes — the tab navigation is
                pure HTML href + cross-page hash)

Closes #383.
This commit is contained in:
iris 2026-05-24 14:23:34 +02:00 committed by Mara
parent 3abd0ba711
commit ba669d2d6c
2 changed files with 66 additions and 56 deletions

View file

@ -1290,7 +1290,12 @@ footer a { color: var(--purple); }
ergonomics without stealing terminal real estate. */ ergonomics without stealing terminal real estate. */
:root { :root {
--flow-header-h: 4.2em; /* Approximate height of the flow chrome (banner-thin + tabbar +
dashboard-chrome padding). Bumped slightly above the natural
content height so the terminal padding-top clears the bottom
border. Tweaked once after #383 took over the chrome from the
simpler title bar. */
--flow-header-h: 4.7em;
--flow-composer-h: 3.6em; --flow-composer-h: 3.6em;
--flow-frost-bg: rgba(30, 30, 46, 0.74); --flow-frost-bg: rgba(30, 30, 46, 0.74);
--flow-frost-blur: blur(12px) saturate(140%); --flow-frost-blur: blur(12px) saturate(140%);
@ -1311,58 +1316,40 @@ body.flow-shell {
var(--bg); var(--bg);
} }
.flow-header { /* Flow chrome (#383): reuses the dashboard's `.dashboard-chrome` +
tabbar so the operator can switch tabs from the flow page without
navigating back first. The chrome must be fixed-position (vs
sticky on the dashboard) since flow-shell has `overflow: hidden`
on body and the main area absolute-positions the terminal. */
body.flow-shell .dashboard-chrome.flow-chrome {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
z-index: 30; z-index: 30;
min-height: var(--flow-header-h); margin: 0;
display: flex; padding: 0.4em 0 0;
align-items: center;
gap: 1em;
padding: 0.55em 1em;
background: var(--flow-frost-bg); background: var(--flow-frost-bg);
-webkit-backdrop-filter: var(--flow-frost-blur); -webkit-backdrop-filter: var(--flow-frost-blur);
backdrop-filter: var(--flow-frost-blur); backdrop-filter: var(--flow-frost-blur);
border-bottom: 1px solid var(--purple-dim); border-bottom: 1px solid var(--purple-dim);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35); box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
flex-wrap: wrap;
} }
.flow-back { /* Active tab on the flow page is FL0W. Its `.tab-link` styling (the
color: var(--cyan); tab strip's right-most entry on the dashboard, where it represents
text-decoration: none; the *link out* to /flow.html) needs to read as the active tab here.
font-size: 0.85em; `aria-current="page"` flags it semantically. */
letter-spacing: 0.1em; body.flow-shell .tabbar .tab.active.tab-link {
padding: 0.25em 0.6em;
border: 1px solid var(--purple-dim);
border-radius: 4px;
flex: 0 0 auto;
transition: border-color 0.15s ease, color 0.15s ease;
}
.flow-back:hover {
border-color: var(--cyan);
text-shadow: 0 0 8px rgba(137, 220, 235, 0.6);
}
.flow-title {
margin: 0;
font-size: 1.2em;
color: var(--purple); color: var(--purple);
text-transform: uppercase; background: var(--bg);
letter-spacing: 0.15em; border-color: var(--purple-dim);
} box-shadow: 0 -2px 12px -4px rgba(203, 166, 247, 0.4);
.flow-hint {
margin: 0;
color: var(--muted);
font-size: 0.82em;
flex: 1 1 auto;
min-width: 0;
}
.flow-header .notif-row {
margin-left: auto;
display: flex;
gap: 0.4em;
} }
/* Legacy `.flow-title` / `.flow-hint` / `.flow-back` rules were
removed in #383 the flow page now uses the shared chrome with
the dashboard tab strip, no need for FL0W-specific title/hint
elements. The `.notif-row` styling lives under the shared
`.tabbar #notif-row` selector earlier in the file. */
/* Inbox pill operator inbox flyout trigger. Sits right under the /* Inbox pill operator inbox flyout trigger. Sits right under the
header so it stays in the operator's gaze without crowding the header so it stays in the operator's gaze without crowding the

View file

@ -8,22 +8,45 @@
</head> </head>
<body class="flow-shell"> <body class="flow-shell">
<!-- Fixed-overlay header. Frosted glass over the message flow — <!-- Fixed-overlay chrome with the same tab strip as the dashboard
backdrop-filter blur shows the scrolled chat text behind. Mirrors (#383) — operator never has to navigate back to the dashboard
the agent live page's overlay layout (#362). --> to switch tabs. FL0W is the current page, the SW4RM / Y3R C4LL
<header class="flow-header" id="flow-header"> / SYST3M tabs cross-link to the dashboard with the matching
<a href="/" class="flow-back" title="back to dashboard">← d4shb04rd</a> hash so the destination tab is pre-active. Banner-thin echoes
<h2 class="flow-title">◆ FL0W ◆</h2> the dashboard for visual continuity. -->
<p class="flow-hint">live broker tail · newest at the top · <code>@name</code> picks a recipient (sticky); <code>tab</code> completes</p> <header class="dashboard-chrome flow-chrome" id="flow-header">
<!-- Notif controls cohabit here (no other chrome on this page). <pre class="banner banner-thin">░▒▓█▓▒░ HYPERHIVE / HIVE-C0RE / WE ARE THE WIRED ░▒▓█▓▒░</pre>
Same IDs as on the dashboard so app.js's NOTIF binding picks
them up unchanged. --> <nav class="tabbar" id="tabbar" role="tablist">
<div id="notif-row" class="notif-row"> <a class="tab" href="/#swarm" role="tab" data-tab="swarm">
<button type="button" id="notif-enable" class="btn btn-notif" hidden>🔔 enable notifications</button> <span class="tab-label">◆ SW4RM ◆</span>
<button type="button" id="notif-mute" class="btn btn-notif" hidden>🔕 mute</button> <span class="tab-count" id="tab-count-swarm" hidden></span>
<button type="button" id="notif-unmute" class="btn btn-notif" hidden>🔔 unmute</button> </a>
<span id="notif-status" class="meta" hidden></span> <a class="tab" href="/#call" role="tab" data-tab="call">
</div> <span class="tab-label">◆ Y3R C4LL ◆</span>
<span class="tab-count tab-count-attn" id="tab-count-call" hidden></span>
</a>
<a class="tab" href="/#system" role="tab" data-tab="system">
<span class="tab-label">◆ SYST3M ◆</span>
<span class="tab-count" id="tab-count-system" hidden></span>
</a>
<a class="tab tab-link active" id="tab-flow" href="/flow.html"
aria-current="page"
title="all-agents chat — you are here">
<span class="tab-label">◆ FL0W ◆</span>
<span class="tab-count" id="tab-count-flow" hidden></span>
</a>
<!-- Notif controls cohabit with the tabs (always-on chrome).
Same IDs as on the dashboard so app.js's NOTIF binding
picks them up unchanged. -->
<div id="notif-row" class="notif-row">
<button type="button" id="notif-enable" class="btn btn-notif" hidden>🔔 enable notifications</button>
<button type="button" id="notif-mute" class="btn btn-notif" hidden>🔕 mute</button>
<button type="button" id="notif-unmute" class="btn btn-notif" hidden>🔔 unmute</button>
<span id="notif-status" class="meta" hidden></span>
</div>
</nav>
</header> </header>
<!-- Operator inbox flyout trigger — count + click → side panel <!-- Operator inbox flyout trigger — count + click → side panel