dashboard: rename src/app.js → src/tabs.js (closes #406)
#406 was the JS-split refactor: pull shared helpers into common.js (step 1), pull the flow-only IIFEs into flow.js (step 2), then rename the legacy combined entry from app.js to tabs.js (step 3 — this commit) to reflect that the bundle now owns the dashboard tabs surface only. What moved: - `frontend/packages/dashboard/src/app.js` → `tabs.js` - `build.mjs` entry: `src('app.js')` → `src('tabs.js')`; output is now `static/tabs.js` - `index.html` `<script>` src: `/static/app.js` → `/static/tabs.js`; the FL0W-section comment retouched - `flow.html` reference from `/static/app.js`-as-tabs to `/static/tabs.js`; notif + offscreen-inbox comments rewired to point at the correct owners (common.js for NOTIF, flow.js for renderInbox) - `common.js`, `flow.js`, `tabs.js` headers: scrub stale "app.js" references; document that #406 steps 2 + 3 are done and both pages import directly from common.js - `dashboard.css` comments: every "by app.js" → "by tabs.js" - `docs/web-ui.md`, `docs/conventions.md`: legacy `assets/app.js` → `assets/tabs.js` (the path prefix is itself stale from a much-earlier rename, but consistent with the rest of those docs) - `CLAUDE.md` file map: refresh the dashboard package's src/ and dist/ layouts to reflect the post-split shape (tabs.js, flow.js, common.js, stream-worker.js) What did NOT move: - `frontend/packages/agent/src/app.js` (per-agent UI entry) stays named `app.js` — it's a separate package, has only one entry point, no split happened there - The two "no-op when target absent" guards in renderContainers and renderQuestions are kept as belt-and-suspenders for any future page that adds tabs.js without the corresponding sections; comments updated to note this rather than the pre-split flow.html reason Functional behaviour identical; this is a pure rename + comment sweep.
This commit is contained in:
parent
3dbef583a7
commit
a2c5348ca4
10 changed files with 69 additions and 57 deletions
|
|
@ -118,9 +118,11 @@ frontend/ npm workspaces (esbuild → static dist). Built
|
|||
+ base typography (was hive-fr0nt). ES module
|
||||
exporting { create, linkify }; pure JS, no IIFE
|
||||
globals; consumed by dashboard + agent.
|
||||
packages/dashboard/ @hive/dashboard SPA: src/{index.html, app.js,
|
||||
dashboard.css} + build.mjs → dist/{index.html,
|
||||
static/{app.js, dashboard.css}}.
|
||||
packages/dashboard/ @hive/dashboard SPA: src/{index.html, tabs.js,
|
||||
flow.html, flow.js, common.js, dashboard.css} +
|
||||
build.mjs → dist/{index.html, flow.html,
|
||||
static/{tabs.js, flow.js, dashboard.css,
|
||||
stream-worker.js}}.
|
||||
packages/agent/ @hive/agent default per-container UI: src/
|
||||
{index, stats, screen}.html + {app, stats}.js
|
||||
+ agent.css → dist/{*.html, static/*}.
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ in `hive-c0re::dashboard_events::DashboardEvent`.
|
|||
## Async forms
|
||||
|
||||
Dashboard + per-agent mutating forms carry `data-async`; a delegated
|
||||
`submit` listener in `assets/app.js` intercepts, shows a spinner,
|
||||
`submit` listener in `assets/tabs.js` intercepts, shows a spinner,
|
||||
POSTs `application/x-www-form-urlencoded` (axum's `Form` extractor
|
||||
rejects multipart), calls `refreshState()` on success. New mutating
|
||||
forms should add `data-async` and optionally `data-confirm` (for a
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ listener: read `data-confirm`, swap the button to a spinner, POST
|
|||
re-render), call `refreshState()`. State shapes live in
|
||||
`dashboard.rs::StateSnapshot` and `web_ui.rs::StateSnapshot` — when
|
||||
adding state fields, plumb through the snapshot struct and the
|
||||
relevant `assets/app.js` render function.
|
||||
relevant `assets/tabs.js` render function.
|
||||
|
||||
**Focus preservation:** `refreshState` checks whether
|
||||
`document.activeElement` sits inside one of the managed sections
|
||||
|
|
@ -298,7 +298,7 @@ the tooltip says "select … for bulk actions" or "deselect … (or press
|
|||
Esc to clear all)". The `<img>` points at `<url>/icon`; load failure
|
||||
falls back to the dimmed hyperhive mark (`/favicon.svg`). The card
|
||||
body sits to the right with three stacked lines
|
||||
(`assets/app.js::renderContainers`).
|
||||
(`assets/tabs.js::renderContainers`).
|
||||
|
||||
- Line 1: agent name (link → new tab), m1nd/ag3nt chip, an
|
||||
**icon-only nav strip** populated async from the agent backend
|
||||
|
|
@ -382,7 +382,7 @@ every render before the bar appears.
|
|||
|
||||
### Approval card
|
||||
|
||||
Each pending approval renders as a card (`assets/app.js::
|
||||
Each pending approval renders as a card (`assets/tabs.js::
|
||||
renderApprovals`) with three stacked sections:
|
||||
|
||||
- **identity header** — glyph, `#id`, agent, kind chip, (for
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
//
|
||||
// dist/index.html served by the Rust router at GET /
|
||||
// dist/flow.html served at GET /flow.html
|
||||
// dist/static/app.js /index.html entry — tab renderers +
|
||||
// dist/static/tabs.js /index.html entry — tab renderers +
|
||||
// tab routing + refreshState
|
||||
// dist/static/flow.js /flow.html entry — broker terminal +
|
||||
// operator inbox + @-mention composer
|
||||
// dist/static/{app,flow}.js.map source map siblings
|
||||
// dist/static/{tabs,flow}.js.map source map siblings
|
||||
// dist/static/dashboard.css served at /static/dashboard.css
|
||||
// (@import resolved from @hive/shared)
|
||||
//
|
||||
|
|
@ -35,7 +35,7 @@ mkdirSync(staticDir(''), { recursive: true });
|
|||
// follow-up once asset sizes warrant it). esbuild writes each entry
|
||||
// to `static/<name>.js` based on the entryPoint basename.
|
||||
await build({
|
||||
entryPoints: [src('app.js'), src('flow.js')],
|
||||
entryPoints: [src('tabs.js'), src('flow.js')],
|
||||
outdir: staticDir(''),
|
||||
bundle: true,
|
||||
format: 'esm',
|
||||
|
|
@ -47,7 +47,7 @@ await build({
|
|||
|
||||
// Stream-worker entry (#448). Lives in a separate bundle: SharedWorker
|
||||
// scripts run in a different global (`self` is the worker scope, no
|
||||
// `window`) so they can't be inlined into app.js / flow.js. Output is
|
||||
// `window`) so they can't be inlined into tabs.js / flow.js. Output is
|
||||
// at `static/stream-worker.js`; common.js's `openStream` references
|
||||
// `/static/stream-worker.js` as the SharedWorker URL. `format: 'iife'`
|
||||
// matches the classic-script load (`new SharedWorker(url, name)` with
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
// Shared dashboard helpers — extracted from app.js as step 1 of the
|
||||
// #406 split. These bits are used by both the tab dashboard
|
||||
// (index.html) and the flow page (flow.html): pure DOM helpers, the
|
||||
// side-panel singleton, the OS-notification module, and the
|
||||
// path-link / file-preview infrastructure for the side panel.
|
||||
// Shared dashboard helpers — extracted from the original monolithic
|
||||
// dashboard JS as step 1 of the #406 split. These bits are used by
|
||||
// both the tab dashboard (index.html) and the flow page (flow.html):
|
||||
// pure DOM helpers, the side-panel singleton, the OS-notification
|
||||
// module, and the path-link / file-preview infrastructure for the
|
||||
// side panel.
|
||||
//
|
||||
// Both pages currently still load `app.js` as their single entry
|
||||
// point; this module is bundled inline by esbuild via the import
|
||||
// below. The follow-up step splits app.js into per-page entry
|
||||
// points (tabs.js + flow.js), at which point both will import from
|
||||
// here directly.
|
||||
// Each page now has its own entry point — `./tabs.js` for index.html,
|
||||
// `./flow.js` for flow.html — and both import from here directly
|
||||
// (#406 steps 2 + 3 complete; #406 closed).
|
||||
|
||||
import { linkify as termLinkify } from '@hive/shared/terminal.js';
|
||||
|
||||
|
|
@ -53,7 +52,7 @@ export const form = (action, btnClass, btnLabel, confirmMsg, extra = {}, opts =
|
|||
return f;
|
||||
};
|
||||
|
||||
// `truncate`, `fmtAgo`, `fmtElapsed`, `fmtDuration` stay in app.js
|
||||
// `truncate`, `fmtAgo`, `fmtElapsed`, `fmtDuration` stay in tabs.js
|
||||
// for now — each has display-specific phrasing ("X running", "X ago")
|
||||
// tied to its caller, so they don't generalise cleanly. We can lift
|
||||
// them when a second consumer needs the same shape.
|
||||
|
|
@ -62,7 +61,7 @@ export const form = (action, btnClass, btnLabel, confirmMsg, extra = {}, opts =
|
|||
// Returns an EventSource-shaped object backed by a SharedWorker that
|
||||
// holds ONE upstream `new EventSource(url)` and fans events out to
|
||||
// every connected tab. Replaces direct `new EventSource(url)` at the
|
||||
// dashboard's two consumer sites (app.js inline + flow.js via
|
||||
// dashboard's two consumer sites (tabs.js inline + flow.js via
|
||||
// terminal.js's `streamFactory` option) so N hyperhive tabs share
|
||||
// ONE backend connection — way under the browser's per-host
|
||||
// connection cap, immune to per-tab throttling that drops the SSE
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ body.dashboard-shell {
|
|||
}
|
||||
|
||||
/* Tab pane visibility — show only the active one. The .tab-pane-active
|
||||
class is set by app.js based on the URL hash; default (no hash)
|
||||
class is set by tabs.js based on the URL hash; default (no hash)
|
||||
resolves to SW4RM. */
|
||||
.tab-pane { display: none; }
|
||||
.tab-pane.tab-pane-active { display: block; }
|
||||
|
|
@ -190,7 +190,7 @@ a:hover {
|
|||
transition: opacity 200ms ease, border-color 200ms ease;
|
||||
}
|
||||
/* Topology indent (#363). Each depth level shifts the row right by one
|
||||
step; the .tree-prefix span (drawn by app.js::treePrefix) carries
|
||||
step; the .tree-prefix span (drawn by tabs.js::treePrefix) carries
|
||||
the ├─ / └─ glyph and any continuation lines that thread through
|
||||
ancestor columns. When every container has parent=null (pre-#361
|
||||
state) `[data-depth]` is absent on every row and these rules are
|
||||
|
|
@ -1592,7 +1592,7 @@ body.flow-shell .tabbar .tab.active.tab-link {
|
|||
}
|
||||
/* Tail pill (↓ N new): bottom offset clears the floating composer.
|
||||
z-index escapes the stacking context the .terminal-wrap's
|
||||
backdrop-filter creates (issue #375) — app.js anchors the pill
|
||||
backdrop-filter creates (issue #375) — tabs.js anchors the pill
|
||||
on .flow-main now (not .terminal-wrap), so this z-index reaches
|
||||
the root stacking context and properly floats above the
|
||||
composer at z-index 30. */
|
||||
|
|
@ -1861,6 +1861,6 @@ body.flow-shell .tabbar .tab.active.tab-link {
|
|||
/* Pad the dashboard body so the sticky bar doesn't cover the
|
||||
bottom of the agent list. ~3.4em covers the bar's vertical
|
||||
footprint with breathing room; only applies when the bar is
|
||||
visible (`body.has-selection`, toggled by app.js when the
|
||||
visible (`body.has-selection`, toggled by tabs.js when the
|
||||
selection set is non-empty). */
|
||||
body.dashboard-shell.has-selection { padding-bottom: 4.5em; }
|
||||
|
|
|
|||
|
|
@ -41,7 +41,8 @@
|
|||
</a>
|
||||
|
||||
<!-- Notif controls cohabit with the tabs (always-on chrome).
|
||||
Same IDs as on the dashboard so app.js's NOTIF binding
|
||||
Same IDs as on the dashboard so the shared NOTIF binding
|
||||
(from common.js, imported by both tabs.js and flow.js)
|
||||
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>
|
||||
|
|
@ -84,7 +85,7 @@
|
|||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Inbox rendered offscreen — kept in the DOM so app.js's
|
||||
<!-- Inbox rendered offscreen — kept in the DOM so flow.js's
|
||||
renderInbox keeps working unchanged. The pill click handler
|
||||
opens the side panel which displays a clone of the list. The
|
||||
legacy section heading would otherwise be visible; hidden
|
||||
|
|
@ -113,7 +114,7 @@
|
|||
<!-- Flow-specific bundle (#406 step 2). Contains the broker
|
||||
terminal init, the operator-inbox derived store, the inbox
|
||||
pill flyout, and the @-mention composer. Tab renderers etc.
|
||||
live in `/static/app.js` which /flow.html doesn't need. -->
|
||||
live in `/static/tabs.js` which /flow.html doesn't load. -->
|
||||
<script type="module" src="/static/flow.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
// /flow.html entry point (#406 step 2 — flow-specific split from app.js).
|
||||
// /flow.html entry point (#406 step 2 — flow-specific split from the
|
||||
// previous combined entry; #406 step 3 renamed that combined entry
|
||||
// from `app.js` to `tabs.js`).
|
||||
//
|
||||
// Owns the full-page broker terminal, the operator-inbox derived store
|
||||
// (populated from the broker stream), the inbox pill flyout, and the
|
||||
|
|
@ -6,10 +8,10 @@
|
|||
// panel, OS notifications, path linkification) from `./common.js`.
|
||||
//
|
||||
// Does NOT contain the dashboard's tab renderers, mutation-event
|
||||
// dispatchers, or refreshState — that's `./app.js` (the legacy entry
|
||||
// kept until step 3 renames it to `tabs.js`). For now the flow page
|
||||
// runs purely on the broker stream + an initial /api/state fetch
|
||||
// (compose autocomplete needs the live container list).
|
||||
// dispatchers, or refreshState — that's `./tabs.js`, loaded only by
|
||||
// /index.html. The flow page runs purely on the broker stream + an
|
||||
// initial /api/state fetch (compose autocomplete needs the live
|
||||
// container list).
|
||||
|
||||
import { create as termCreate } from '@hive/shared/terminal.js';
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@
|
|||
|
||||
<!-- FL0W: lives on its own page now (`/flow.html`). The
|
||||
message-flow + inbox + compose DOM only exists there — when
|
||||
app.js boots on this page the corresponding renderers
|
||||
tabs.js boots on this page the corresponding renderers
|
||||
no-op silently (each guard is `if (!el) return`). -->
|
||||
|
||||
</main>
|
||||
|
|
@ -216,9 +216,11 @@
|
|||
title="clear selection (esc)">✕ clear</button>
|
||||
</div>
|
||||
|
||||
<!-- Single bundled entry. esbuild folds @hive/shared/terminal.js and
|
||||
the marked npm package into app.js; load order is preserved by
|
||||
the module bundler. -->
|
||||
<script type="module" src="/static/app.js" defer></script>
|
||||
<!-- Single bundled entry (#406 step 3 — renamed from app.js to
|
||||
tabs.js since this bundle is the dashboard *tabs* surface only;
|
||||
flow.html has its own flow.js bundle). esbuild folds
|
||||
@hive/shared/terminal.js and the marked npm package into
|
||||
tabs.js; load order is preserved by the module bundler. -->
|
||||
<script type="module" src="/static/tabs.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
// /index.html entry point: tab renderers + tab routing + refreshState
|
||||
// + notification deltas. Reads /api/state on cold load and after every
|
||||
// async-form submit; live updates run through `applyXxx` mutation
|
||||
// handlers triggered by the dashboard event stream. (Live SSE
|
||||
// subscription on /index.html is still missing — see #406 step 3 / #408.)
|
||||
// handlers triggered by the dashboard event stream.
|
||||
//
|
||||
// #406 step 1: pure helpers + side panel + OS notifications + path
|
||||
// linkification moved to `./common.js`.
|
||||
// #406 step 2: the flow-only IIFEs (operator inbox, inbox-pill, broker
|
||||
// terminal, @-mention composer) moved to `./flow.js`. /flow.html now
|
||||
// loads `flow.js` as its own bundle entry; this file (still named
|
||||
// `app.js` for the moment; rename to `tabs.js` is the last step
|
||||
// of #406) is loaded only by /index.html.
|
||||
// terminal, @-mention composer) moved to `./flow.js`. /flow.html loads
|
||||
// `flow.js` as its own bundle entry; this file is loaded only by
|
||||
// /index.html.
|
||||
// #406 step 3: file renamed from `app.js` → `tabs.js` since it owns
|
||||
// the dashboard *tabs* surface only (the FL0W page has its own bundle).
|
||||
// Live SSE subscription is wired through `openStream` from common.js
|
||||
// (#406 step 3 hookup; see also #408 for stream-side filtering).
|
||||
|
||||
import { marked } from 'marked';
|
||||
import {
|
||||
|
|
@ -446,14 +448,17 @@ window.marked = marked;
|
|||
|
||||
function renderContainers(s) {
|
||||
const root = $('containers-section');
|
||||
// #containers-section only exists on /index.html. The same
|
||||
// bundled app.js runs on /flow.html (which has the broker
|
||||
// terminal but no container list) and on any future pages —
|
||||
// `container_state_changed` SSE events arrive on every page and
|
||||
// route through `applyContainerStateChanged → renderContainersFromState`,
|
||||
// so without this guard the renderer throws `root is null` on
|
||||
// every event (#399). Matches the no-op-when-target-absent
|
||||
// convention the other renderers (renderTombstones, etc.) follow.
|
||||
// #containers-section only exists on /index.html. tabs.js is the
|
||||
// bundle for that page only (#406 step 3 — /flow.html loads
|
||||
// flow.js instead), but historical context: pre-split the
|
||||
// `container_state_changed` SSE handler routed through
|
||||
// `applyContainerStateChanged → renderContainersFromState` on
|
||||
// every page that loaded the single combined bundle, and the
|
||||
// guard prevented a `root is null` throw on /flow.html (#399).
|
||||
// Today it's belt-and-suspenders for any future page that adds
|
||||
// tabs.js without a #containers-section. Matches the
|
||||
// no-op-when-target-absent convention the other renderers
|
||||
// (renderTombstones, etc.) follow.
|
||||
if (!root) return;
|
||||
root.innerHTML = '';
|
||||
|
||||
|
|
@ -1102,9 +1107,10 @@ window.marked = marked;
|
|||
function renderQuestions() {
|
||||
const root = $('questions-section');
|
||||
// #questions-section only lives on /index.html (Y3R C4LL tab);
|
||||
// no-op on /flow.html etc. — the bundled app.js runs everywhere
|
||||
// and `question_added` / `question_resolved` SSE events route
|
||||
// through here (#399).
|
||||
// no-op when missing (#406 step 3 — only /index.html loads
|
||||
// tabs.js, but kept as belt-and-suspenders for any future page
|
||||
// adding it without that section). `question_added` /
|
||||
// `question_resolved` SSE events route through here (#399).
|
||||
if (!root) return;
|
||||
root.innerHTML = '';
|
||||
const fmt = (n) => new Date(n * 1000).toISOString().replace('T', ' ').slice(0, 19);
|
||||
Loading…
Reference in a new issue