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