common.js + flow.js + index.html + flow.html: cookie scrub + doc pointers (#712 batch 13 cont.)
Final dashboard frontend cleanup. Four files at 0 cookies each. common.js (11 → 0): - #406 module-split history → drop, present-state intro - #448 SharedWorker SSE pipe (×3) → docs/web-ui.md SSE multiplexing paragraph pointer - #515 worker-death self-heal (×3) → docs pointer + brief inline - #453 bfcache argus-nit (×2) → drop framing, keep inline why - #451 drag-to-resize → docs/web-ui.md::Side panel pointer - #233 marked autolinks new-tab → drop cookie flow.js (8 → 0): - #406 module-split history → drop - #389 banner-in-footer → drop framing - #375 stacking context → docs/web-ui.md::Per-agent page Terminal-wrap - #408 server-side filter, #499 backend allow-list → drop cookies, keep inline why - #448 SharedWorker per-URL keying → docs pointer - #163 SSE catchup drift → drop cookie, keep inline why index.html (13 → 0): - #389 chrome history → drop - #459 schedules, #607 matrix, #609 nginx-front, #15 → drop cookies, keep functional comments + docs/web-ui.md::Tab strip + gateway.md pointers for matrix - #385 dropped C0NTAINERS heading → drop framing - #460 reminders-moved-here → drop framing - #444 SchedulesChanged SSE absence → drop cookie, keep inline why - #443 selection bar → docs/web-ui.md::Selection bar pointer - #564 fold-create-into-table → drop framing - #369#issuecomment-3437 → drop attribution - #406 step 3 bundle rename → drop history flow.html (3 → 0): - #389 slug + #362 pill pattern + #406 step 2 bundle → drop cookies, preserve functional descriptions Total this batch (across all 4 files): **35 cookies scrubbed**. Net effect: the dashboard SPA's HTML + JS comments all point at docs/web-ui.md for substantive design context now, with inline comments only retaining present-state operational descriptions.
This commit is contained in:
parent
6c9b28903f
commit
f7e38c0b42
4 changed files with 123 additions and 182 deletions
|
|
@ -1,13 +1,7 @@
|
|||
// 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.
|
||||
//
|
||||
// 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).
|
||||
// Shared dashboard helpers used by both index.html (./tabs.js) and
|
||||
// flow.html (./flow.js): pure DOM helpers, the side-panel singleton,
|
||||
// the OS-notification module, and the path-link / file-preview
|
||||
// infrastructure for the side panel.
|
||||
|
||||
import { linkify as termLinkify } from '@hive/shared/terminal.js';
|
||||
|
||||
|
|
@ -57,31 +51,23 @@ export const form = (action, btnClass, btnLabel, confirmMsg, extra = {}, opts =
|
|||
// tied to its caller, so they don't generalise cleanly. We can lift
|
||||
// them when a second consumer needs the same shape.
|
||||
|
||||
// ─── shared-worker SSE pipe (#448) ──────────────────────────────────────
|
||||
// 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 (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
|
||||
// when Firefox suspends background tabs.
|
||||
// ─── shared-worker SSE pipe ─────────────────────────────────────────────
|
||||
// Returns an EventSource-shaped facade backed by a SharedWorker that
|
||||
// holds one upstream `new EventSource(url)` and fans events out to
|
||||
// every connected tab. See docs/web-ui.md (SSE multiplexing paragraph)
|
||||
// for the design + Firefox throttling motivation; graceful fallback to
|
||||
// direct EventSource on environments without SharedWorker.
|
||||
//
|
||||
// Graceful fallback to direct EventSource on environments without
|
||||
// SharedWorker (some embedded browsers, some Safari versions). The
|
||||
// per-tab connection cost is the same as today — no regression.
|
||||
//
|
||||
// The page consumer uses the returned object like a regular
|
||||
// EventSource: assign `onmessage` / `onopen` / `onerror`. `.close()`
|
||||
// tells the worker to drop the subscription; the worker closes the
|
||||
// upstream EventSource when the last subscriber leaves.
|
||||
// Consumer API: assign `onmessage` / `onopen` / `onerror`; `.close()`
|
||||
// drops the subscription (the worker closes the upstream when the last
|
||||
// subscriber leaves).
|
||||
const SHARED_WORKER_PATH = '/static/stream-worker.js';
|
||||
const SHARED_WORKER_NAME = 'hyperhive-stream';
|
||||
|
||||
// One SharedWorker port per page, reused by all openStream calls on
|
||||
// that page. Invalidated on `pagehide` so a bfcache restore picks up
|
||||
// a fresh port (the cached one may have been collected if all other
|
||||
// tabs closed while this page was frozen — argus nit on #453).
|
||||
// a fresh port — the cached port may be dead if all other tabs
|
||||
// closed while this page was frozen.
|
||||
let _sharedPort = null;
|
||||
function makeSharedPort() {
|
||||
if (typeof SharedWorker === 'undefined') return null;
|
||||
|
|
@ -99,30 +85,11 @@ function getSharedPort() {
|
|||
return _sharedPort;
|
||||
}
|
||||
|
||||
// #515: detect when the SharedWorker has been killed. Firefox aggressively
|
||||
// reclaims "idle" SharedWorkers under memory pressure (or just on tab
|
||||
// lifecycle quirks we don't fully understand), and there's no native
|
||||
// signal to the client when that happens — `postMessage` on a dead
|
||||
// port silently no-ops, and the page just stops receiving events. The
|
||||
// observable symptom is "dashboard never refreshes; F5 fixes it" (a
|
||||
// fresh page creates a fresh worker), which is mara's report on #515.
|
||||
//
|
||||
// Pipeline:
|
||||
// - The worker pings every connected port every WORKER_PING_INTERVAL_MS.
|
||||
// - `route` (every message arrival, including pings) bumps
|
||||
// `_lastWorkerActivityAt`.
|
||||
// - `startWorkerWatchdog` polls every WORKER_WATCHDOG_INTERVAL_MS.
|
||||
// If the page is visible AND we have active subs AND we haven't
|
||||
// heard from the worker in > WORKER_DEAD_THRESHOLD_MS, we presume
|
||||
// the worker is dead, log a warning, and re-subscribe on a fresh
|
||||
// port.
|
||||
//
|
||||
// Numbers picked so a real Firefox tab-suspend / unsuspend cycle (which
|
||||
// can pause the watchdog itself) doesn't false-positive: 90s without a
|
||||
// 30s ping means at least three pings missed. Visibility-gated so a
|
||||
// backgrounded tab — where Firefox throttles setInterval to 1Hz min and
|
||||
// our healthcheck wouldn't trigger reliably anyway — doesn't try to
|
||||
// reconnect uselessly.
|
||||
// SharedWorker death detection: pings from the worker bump the
|
||||
// activity clock; a visibility-gated watchdog polls and re-subscribes
|
||||
// on a fresh port if the page has been silent past the threshold.
|
||||
// See docs/web-ui.md (Worker-death self-heal paragraph) for the
|
||||
// timing rationale + Firefox reclaim symptom.
|
||||
const WORKER_DEAD_THRESHOLD_MS = 90_000;
|
||||
const WORKER_WATCHDOG_INTERVAL_MS = 15_000;
|
||||
let _lastWorkerActivityAt = 0;
|
||||
|
|
@ -164,25 +131,19 @@ function rebindOnFreshPort() {
|
|||
noteWorkerActivity();
|
||||
}
|
||||
|
||||
// Registry of live subscriptions on this page. Keyed by url so a
|
||||
// second openStream call for the same URL (would only happen on a
|
||||
// hypothetical multi-consumer page) attaches to the existing route
|
||||
// rather than overlapping. Each entry caches the route function so
|
||||
// bfcache-restore re-bind can re-attach it to the fresh port.
|
||||
//
|
||||
// Today's pages only call openStream once with one URL; the registry
|
||||
// shape just keeps the bfcache-restore path correct if that changes
|
||||
// (e.g. /index.html later subscribing to two streams).
|
||||
// Registry of live subscriptions on this page. Keyed by url; entries
|
||||
// cache the route function so bfcache-restore re-bind can re-attach
|
||||
// it to the fresh port. Today's pages only call openStream once with
|
||||
// one URL; the registry shape just keeps the bfcache path correct
|
||||
// if that changes.
|
||||
const _activeSubs = new Map();
|
||||
|
||||
// One-shot wiring of the page-wide lifecycle hooks: on bfcache
|
||||
// freeze (`pagehide { persisted: true }`) we unsubscribe so the
|
||||
// worker can close the upstream when the last live subscriber
|
||||
// leaves; on bfcache restore (`pageshow { persisted: true }`) we
|
||||
// invalidate the cached port (it may be dead if all other tabs
|
||||
// closed during the freeze) and re-attach every active subscription
|
||||
// to a fresh port. argus nit on #453: without this, the consumer's
|
||||
// onmessage stays bound but no events flow after a bfcache restore.
|
||||
// One-shot wiring of page-wide lifecycle hooks. On bfcache freeze
|
||||
// we unsubscribe so the worker can close the upstream when the last
|
||||
// live subscriber leaves; on bfcache restore we invalidate the cached
|
||||
// port (may be dead after the freeze) and re-attach every active
|
||||
// subscription to a fresh port. Without this, the consumer's
|
||||
// onmessage stays bound but no events flow after restore.
|
||||
let _lifecycleBound = false;
|
||||
function bindLifecycleOnce() {
|
||||
if (_lifecycleBound) return;
|
||||
|
|
@ -245,7 +206,7 @@ export function openStream(url) {
|
|||
},
|
||||
};
|
||||
const route = (e) => {
|
||||
// #515: any message from the worker is proof of life — note it
|
||||
// Any message from the worker is proof of life — note it
|
||||
// before the URL filter, since heartbeat pings carry no URL.
|
||||
noteWorkerActivity();
|
||||
const m = e.data;
|
||||
|
|
@ -272,8 +233,8 @@ export function openStream(url) {
|
|||
_activeSubs.set(url, { target, route });
|
||||
port.addEventListener('message', route);
|
||||
port.postMessage({ kind: 'subscribe', url });
|
||||
// #515: seed the activity clock so the watchdog has a baseline; it
|
||||
// would otherwise compare against 0 (epoch) and trigger immediately.
|
||||
// Seed the activity clock so the watchdog has a baseline (would
|
||||
// otherwise compare against 0 and trigger immediately).
|
||||
noteWorkerActivity();
|
||||
return target;
|
||||
}
|
||||
|
|
@ -330,13 +291,10 @@ export const Panel = (() => {
|
|||
root.classList.remove('open');
|
||||
root.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
// #451: drag-to-resize the drawer's width. Listens on a thin
|
||||
// hit-strip glued to the drawer's left edge; mousedown captures
|
||||
// pointermove + pointerup on the document so the drag continues
|
||||
// even if the cursor strays outside the 6px handle band. Width
|
||||
// persists to localStorage so it survives page reload. The CSS
|
||||
// clamps the value (min-width: 320px, max-width: 96vw) — drop
|
||||
// unparseable / out-of-range stored values silently.
|
||||
// Drag-to-resize the drawer's width. See docs/web-ui.md::Side panel
|
||||
// for the hit-strip + pointer-capture + localStorage persistence
|
||||
// model; CSS clamps the stored value to min 320px / max 96vw and
|
||||
// out-of-range stored values are dropped silently.
|
||||
const WIDTH_KEY = 'hyperhive:side-panel-width';
|
||||
const WIDTH_MIN = 320;
|
||||
function clampWidth(w) {
|
||||
|
|
@ -476,7 +434,7 @@ function mdNode(text) {
|
|||
window.marked.setOptions({ breaks: true, gfm: true });
|
||||
div.innerHTML = window.marked.parse(text);
|
||||
// marked autolinks URLs but leaves them same-tab — open externally
|
||||
// so a click never navigates away from the dashboard. (issue #233)
|
||||
// so a click never navigates away from the dashboard.
|
||||
div.querySelectorAll('a[href]').forEach((a) => {
|
||||
a.target = '_blank';
|
||||
a.rel = 'noopener noreferrer';
|
||||
|
|
|
|||
Loading…
Reference in a new issue