2026-08-15 22:42:40 +00:00
<!doctype html>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< title > swactor fleet< / title >
< style >
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
:root {
color-scheme: dark;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
/* dark: Bloomberg night housing */
--bg: #000000; --panel: #001220; --panel-hover: #001c38; --inset: #00060c;
--border: #14406a; --divider: #0d2c4a;
--text: #ffffff; --muted: #9db2c4;
--amber: #ff9900; --ghost: rgba(255, 153, 0, .16);
--ok: #00d400; --bad: #ff4141; --cyan: #5cd5ff;
--primary-fill: #ff9900; --primary-ink: #000000;
--danger-fill: #7a1010; --danger-border: #ff4141; --danger-ink: #ffd7d7;
--selected: rgba(255, 153, 0, .10); --selected-edge: #ff9900;
--card-hover: #00263f; --row-hover: #002a47;
--r: 2px; --t: 120ms;
background: var(--bg); color: var(--text);
}
:root[data-theme="light"] {
color-scheme: light;
/* light: Atom One Light bench card */
--bg: #fafafa; --panel: #ffffff; --panel-hover: #f0f0f0; --inset: #f5f5f5;
--border: #dcdcE0; --divider: #eaeaea;
--text: #383a42; --muted: #696c77;
--amber: #986801; --ghost: rgba(152, 104, 1, .18);
--ok: #50a14f; --bad: #e45649; --cyan: #0184bc;
--primary-fill: #0184bc; --primary-ink: #ffffff;
--danger-fill: #cc3a2f; --danger-border: #cc3a2f; --danger-ink: #ffffff;
--selected: rgba(64, 120, 242, .08); --selected-edge: #4078f2;
--card-hover: #f0f4fb; --row-hover: #eef1f8;
}
@media (prefers-color-scheme: light) {
:root:not([data-theme]) {
color-scheme: light;
--bg: #fafafa; --panel: #ffffff; --panel-hover: #f0f0f0; --inset: #f5f5f5;
--border: #dcdcE0; --divider: #eaeaea;
--text: #383a42; --muted: #696c77;
--amber: #986801; --ghost: rgba(152, 104, 1, .18);
--ok: #50a14f; --bad: #e45649; --cyan: #0184bc;
--primary-fill: #0184bc; --primary-ink: #ffffff;
--danger-fill: #cc3a2f; --danger-border: #cc3a2f; --danger-ink: #ffffff;
--selected: rgba(64, 120, 242, .08); --selected-edge: #4078f2;
--card-hover: #f0f4fb; --row-hover: #eef1f8;
}
}
2026-08-15 22:42:40 +00:00
* { box-sizing: border-box; }
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
body { margin: 0; padding: 20px; }
header { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; margin-bottom: 16px; }
h1 { margin: 0; font-size: 20px; }
h2 { margin: 20px 0 10px; font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--amber); }
.muted { color: var(--muted); }
.mono { font-family: var(--mono); font-size: 12px; }
#status { font-family: var(--mono); font-size: 13px; color: var(--amber); }
a.node-card { color: inherit; text-decoration: none; display: block; }
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
.node-card.orchestrator { grid-column: 1 / -1; border-color: var(--amber); background: var(--ghost); box-shadow: inset 3px 0 0 var(--amber); padding-left: 17px; }
2026-08-18 10:17:04 +00:00
.node-card.orchestrator .role { margin-bottom: 6px; color: var(--muted); font: 600 9px/1.2 var(--mono); text-transform: uppercase; letter-spacing: .12em; }
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
:is(a, button, input, summary):focus-visible, tr[data-addr]:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
2026-08-15 22:42:40 +00:00
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 12px; }
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
.node-card { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r); padding: 14px; cursor: pointer; transition: border-color var(--t), background var(--t); }
.node-card:hover { border-color: var(--amber); background: var(--card-hover); }
2026-08-15 22:42:40 +00:00
.node-card[data-live="false"] { opacity: .55; }
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
.node-card h3 { margin: 0 0 2px; font: 700 13px/1.4 var(--mono); word-break: break-all; }
.node-card .meta { color: var(--muted); font-size: 12px; margin-bottom: 8px; }
2026-08-15 22:42:40 +00:00
.bars { display: grid; gap: 5px; }
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
.bar-row { display: flex; align-items: center; gap: 8px; font-family: var(--mono); font-size: 12px; }
.bar-row .name { width: 92px; color: var(--muted); }
.bar-track { flex: 1; height: 8px; background: var(--inset); border-radius: var(--r); overflow: hidden; }
.bar-fill { height: 100%; }
.bar-fill.ok { background: var(--ok); } .bar-fill.warn { background: var(--amber); } .bar-fill.bad { background: var(--bad); }
2026-08-15 22:42:40 +00:00
.bar-row .num { width: 64px; text-align: right; font-variant-numeric: tabular-nums; }
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
.pill { display: inline-flex; padding: 3px 6px; border: 1px solid var(--muted); border-radius: var(--r); font: 600 10px/1.4 var(--mono); text-transform: uppercase; letter-spacing: .06em; color: var(--text); }
.pill.live { color: var(--ok); border-color: var(--ok); }
.pill.stale { color: var(--bad); border-color: var(--bad); }
.pill.poisoned { color: var(--bad); border-color: var(--bad); font-weight: 700; }
2026-08-15 22:42:40 +00:00
details.stale { margin-top: 18px; }
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
details.stale summary { cursor: pointer; color: var(--muted); padding: 8px 0; font-family: var(--mono); font-size: 12px; }
.focus-back { display: inline-block; margin-bottom: 12px; color: var(--cyan); text-decoration: none; font-family: var(--mono); font-size: 12px; }
.focus-back:hover { color: var(--amber); }
2026-08-15 22:42:40 +00:00
.node-view { display: grid; gap: 14px; }
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
.panel { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r); padding: 14px; }
table { width: 100%; border-collapse: collapse; font-family: var(--mono); font-size: 12px; }
th, td { text-align: left; padding: 7px 8px; border-bottom: 1px solid var(--divider); font-variant-numeric: tabular-nums; }
th { color: var(--amber); font-weight: 600; font-size: 10px; text-transform: uppercase; letter-spacing: .08em; }
tbody tr[data-addr] { cursor: pointer; transition: background var(--t); }
tbody tr[data-addr]:hover { background: var(--row-hover); }
tbody tr[data-selected="true"] { background: var(--selected); box-shadow: inset 2px 0 0 var(--selected-edge); }
input[type=search] { background: var(--inset); color: var(--text); border: 1px solid var(--border); border-radius: var(--r); padding: 7px 10px; width: 240px; max-width: 100%; font: 12px var(--mono); }
input[type=search]:hover { border-color: var(--cyan); }
2026-08-15 22:42:40 +00:00
.layout { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; }
@media (min-width: 1100px) { .layout.with-dossier { grid-template-columns: minmax(0, 1fr) 420px; } }
.dossier { position: sticky; top: 12px; align-self: start; max-height: calc(100vh - 24px); overflow: auto; }
.kv { display: grid; grid-template-columns: 120px 1fr; gap: 4px 10px; font-size: 13px; }
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
.kv .k { color: var(--muted); font-family: var(--mono); font-size: 12px; }
2026-08-15 22:42:40 +00:00
.diet .bar-row .name { width: 48%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
.receipts td { font-size: 11px; }
.empty { padding: 24px; border: 1px dashed var(--border); border-radius: var(--r); color: var(--muted); font-family: var(--mono); font-size: 12px; }
.notice { font-size: 12px; color: var(--muted); margin: 6px 0; }
canvas { width: 100%; height: 64px; background: var(--inset); border: 1px solid var(--border); border-radius: var(--r); }
.err { color: var(--bad); }
@media (prefers-reduced-motion: reduce) { * { transition-duration: 0.01ms !important; animation: none !important; } }
2026-08-15 22:42:40 +00:00
< / style >
< / head >
< body >
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
<!--
swactor dashboard — direction contract (impeccable seed 8b5f2278)
THESIS: a numeric instrument bank for a live actor fleet; Bloomberg night housing (dark) / Atom One Light bench card (light); absence drawn as designed negative space, never blank.
OWN-WORLD: black-on-navy or paper-on-white; amber owns the data register, green/red are law, cyan is the only interactive voice; 2px corners, monospace data, machined caps labels, controls that depress.
FIRST VIEWPORT: the fleet as an instrument rack — orchestrator module pinned with an amber edge, node cards as module slots with state tags.
SIGNATURE: state as instrument grammar — lit tag for live, struck dim for dead, no skeleton bars for silent channels.
RISK: terminal cosplay if decoration creeps past data; held by the palette law.
-->
2026-08-15 22:42:40 +00:00
<!-- swactor:nav -->
< header >
< h1 > Fleet< / h1 >
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
< span class = "muted" id = "status" role = "status" aria-live = "polite" > loading…< / span >
2026-08-15 22:42:40 +00:00
< / header >
< div id = "page" >
< div class = "empty" > Waiting for telemetry frames…< / div >
< / div >
< script >
'use strict';
const POLL_MS = 1000;
const ROSTER_RENDER_CAP = 200;
const params = new URLSearchParams(window.location.search);
let selectedStream = params.get('stream') || null;
let selectedActor = params.get('actor') || null;
let rosterFilter = '';
let lastSnapshot = null;
let detailTimer = null;
2026-08-17 22:51:08 +00:00
// A1: last-rendered HTML per region. A poll that yields identical markup
// must not swap innerHTML — the swap destroyed hover/selection/presses and
// restarted animations every second even in a fully converged steady state.
let lastPageHtml = null;
let lastRosterHtml = null;
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
let lastMachineHtml = null;
2026-08-17 22:51:08 +00:00
let lastDossierHtml = null;
// Receipt ages (dossier) render as empty spans carrying an absolute epoch —
// stable across polls — and a 1 Hz pass rewrites their textContent. All other
// ticking text (card seen/stats) is written per-poll by updateCardTexts.
function agoSpan(epochMs) {
return `< span data-ago-at = "${Math.round(epochMs)}" > < / span > `;
}
function updateAgo() {
const now = Date.now();
for (const span of document.querySelectorAll('[data-ago-at]')) {
span.textContent = ago(now - Number(span.dataset.agoAt));
}
}
2026-08-15 22:42:40 +00:00
function esc(value) {
return String(value ?? '').replace(/[& < >"']/g, c => ({'&':'& ','< ':'< ','>':'> ','"':'" ','\'':'' '}[c]));
}
function fmt(value, digits = 0) {
const n = Number(value);
if (!Number.isFinite(n)) return '—';
return n.toLocaleString(undefined, { maximumFractionDigits: digits });
}
function fmtRate(value) {
const n = Number(value);
if (!Number.isFinite(n)) return '—';
if (n >= 1e9) return (n / 1e9).toFixed(1) + ' G';
if (n >= 1e6) return (n / 1e6).toFixed(1) + ' M';
if (n >= 1e3) return (n / 1e3).toFixed(1) + ' k';
return n.toFixed(n < 10 ? 1 : 0 ) ;
}
function typeShort(name) {
if (!name) return '—';
const parts = String(name).split('::');
return esc(parts[parts.length - 1] || name);
}
function ago(ms) {
if (ms == null) return '—';
if (ms < 1000 ) return ms + ' ms ' ;
if (ms < 60000 ) return Math . round ( ms / 1000 ) + ' s ' ;
if (ms < 3600000 ) return Math . round ( ms / 60000 ) + ' m ' ;
return Math.round(ms / 3600000) + 'h';
}
function bar(percent, max = 100) {
if (percent == null) return '< div class = "bar-track" > < / div > ';
const clamped = Math.max(0, Math.min(100, percent / max * 100));
const cls = clamped >= 90 ? 'bad' : clamped >= 70 ? 'warn' : 'ok';
return `< div class = "bar-track" > < div class = "bar-fill ${cls}" style = "width:${clamped}%" > < / div > < / div > `;
}
function pushUrl() {
const next = new URL(window.location);
if (selectedStream) next.searchParams.set('stream', selectedStream); else next.searchParams.delete('stream');
if (selectedActor) next.searchParams.set('actor', selectedActor); else next.searchParams.delete('actor');
window.history.replaceState(null, '', next);
}
async function refresh() {
let data;
try {
const response = await fetch('/api/view/fleet');
data = await response.json();
} catch { return; }
lastSnapshot = data;
render();
}
function render() {
const page = document.getElementById('page');
const status = document.getElementById('status');
const data = lastSnapshot;
const live = data.live || [];
const stale = data.stale || [];
const totals = data.totals || {};
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
status.textContent = `${fmt(totals.live_nodes)} live · ${fmt(totals.stale_nodes)} stale`;
2026-08-15 22:42:40 +00:00
const node = live.concat(stale).find(n => n.stream.key === selectedStream)
|| live.find(n => n.stream.key === selectedStream);
if (node) {
renderNode(page, node, live, stale);
} else {
renderFleet(page, live, stale, totals);
}
2026-08-17 22:51:08 +00:00
updateAgo();
2026-08-15 22:42:40 +00:00
}
function renderFleet(page, live, stale, totals) {
stopDetailPolling();
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
const orchestrators = live.filter(n => (n.stream.origin || '') === 'orchestrator');
const workers = live.filter(n => (n.stream.origin || '') !== 'orchestrator');
2026-08-15 22:42:40 +00:00
const parts = [];
if (!live.length & & !stale.length) {
parts.push('< div class = "empty" > No telemetry streams yet. A live swactor runtime or host publisher will populate this page.< / div > ');
} else {
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
parts.push('< div class = "grid" > ' + orchestrators.concat(workers).map(nodeCard).join('') + '< / div > ');
2026-08-15 22:42:40 +00:00
if (stale.length) {
parts.push(`< details class = "stale" >
< summary > Stale nodes (${stale.length}) — silent beyond the liveness window< / summary >
< div class = "grid" > ${stale.map(nodeCard).join('')}< / div >
< / details > `);
}
}
2026-08-17 22:51:08 +00:00
const html = parts.join('');
if (html !== lastPageHtml) {
page.innerHTML = html;
lastPageHtml = html;
}
updateCardTexts(orchestrators.concat(workers, stale));
2026-08-15 22:42:40 +00:00
}
function nodeCard(node) {
const summary = node.actor_summary || {};
const proc = node.process ? `< span class = "pill" > ${esc(node.process.state)}< / span > ` : '';
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
const origin = node.stream.origin || '';
const isOrchestrator = origin === 'orchestrator';
const role = isOrchestrator ? '< span class = "role" > orchestrator< / span > ' : '';
const label = node.stream.label ? ` · ${esc(node.stream.label)}` : '';
2026-08-17 22:51:08 +00:00
// The stats line (actors · msg/s · uptime) jitters every frame on busy
// supervisors; it renders as a pass-filled span so the card markup — and
// with it the whole grid — stays byte-identical between polls.
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
return `< a class = "node-card${isOrchestrator ? ' orchestrator' : ''}" data-stream = "${esc(node.stream.key)}" data-live = "${node.live ? 'true' : 'false'}" href = "?stream=${encodeURIComponent(node.stream.key)}" >
2026-08-18 10:17:04 +00:00
${role}
< h3 > ${esc(node.stream.node)} < span class = "muted" style = "font-size:12px" > life ${fmt(node.stream.life)}< / span > < / h3 >
2026-08-17 22:51:08 +00:00
< div class = "meta" > ${proc}seen < span data-seen > < / span > ago · < span data-stats > < / span > ${label}< / div >
2026-08-15 22:42:40 +00:00
${summary.poisoned ? `< div class = "notice err" > ${fmt(summary.poisoned)} poisoned actor(s)< / div > ` : ''}
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
< / a > `;
2026-08-15 22:42:40 +00:00
}
2026-08-17 22:51:08 +00:00
function cardStats(node) {
const summary = node.actor_summary || {};
return [
summary.actors ? fmt(summary.actors) + ' actors' : null,
summary.msg_per_sec ? fmtRate(summary.msg_per_sec) + ' msg/s' : null,
summary.num_workers != null ? fmt(summary.num_workers) + ' workers' : null,
summary.uptime_ms != null ? 'up ' + ago(summary.uptime_ms) : null,
].filter(Boolean).join(' · ') || 'no runtime stats';
}
function updateCardTexts(nodes) {
const byStream = new Map(nodes.map(node => [node.stream.key, node]));
for (const card of document.querySelectorAll('a.node-card')) {
const node = byStream.get(card.getAttribute('data-stream'));
const seen = card.querySelector('[data-seen]');
if (seen) seen.textContent = node ? ago(node.last_seen_ms_ago) : '—';
const stats = card.querySelector('[data-stats]');
if (stats) stats.textContent = node ? cardStats(node) : 'no runtime stats';
}
}
2026-08-15 22:42:40 +00:00
function renderNode(page, node, live, stale) {
const summary = node.actor_summary || {};
2026-08-17 22:51:08 +00:00
const active = document.activeElement;
const refocus = active & & active.id === 'roster-filter' & & page.contains(active)
? { start: active.selectionStart, end: active.selectionEnd, dir: active.selectionDirection }
: null;
const html = `
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
< a class = "focus-back" href = "/" > ← Fleet (< span data-live-count > < / span > live, < span data-stale-count > < / span > stale)< / a >
2026-08-15 22:42:40 +00:00
< div class = "layout ${selectedActor ? 'with-dossier' : ''}" >
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
< div class = "node-view" data-node = "${esc(node.stream.node)}" >
2026-08-15 22:42:40 +00:00
< section class = "panel" >
2026-08-17 22:51:08 +00:00
< h2 style = "margin:0 0 8px" > ${esc(node.stream.node)}${node.stream.label ? ` < span class = "muted" style = "font-size:13px" > ${esc(node.stream.label)}< / span > ` : ''} < span class = "muted" style = "font-size:13px" > life ${fmt(node.stream.life)} · < span data-seen > < / span > < / span > < / h2 >
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
< div id = "machine-detail" > < / div >
2026-08-15 22:42:40 +00:00
< / section >
< section class = "panel" >
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
< h2 style = "margin:0 0 8px" > Actors (< span data-actor-count > < / span > )< / h2 >
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
< input type = "search" id = "roster-filter" aria-label = "Filter actors" placeholder = "filter by type, address, worker…" value = "${esc(rosterFilter)}" >
2026-08-15 22:42:40 +00:00
< div id = "roster-wrap" style = "margin-top:10px" > < / div >
< / section >
< / div >
< div id = "dossier-slot" > < / div >
< / div > `;
2026-08-17 22:51:08 +00:00
if (html !== lastPageHtml) {
page.innerHTML = html;
lastPageHtml = html;
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
lastMachineHtml = null;
lastRosterHtml = null;
2026-08-17 22:51:08 +00:00
const filter = document.getElementById('roster-filter');
filter.addEventListener('input', e => { rosterFilter = e.target.value; renderRoster(node); });
if (refocus) {
filter.focus();
try { filter.setSelectionRange(refocus.start, refocus.end, refocus.dir); } catch { /* caret shape changed */ }
}
}
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
const liveCount = page.querySelector('[data-live-count]');
if (liveCount) liveCount.textContent = fmt(live.length);
const staleCount = page.querySelector('[data-stale-count]');
if (staleCount) staleCount.textContent = fmt(stale.length);
const actorCount = page.querySelector('[data-actor-count]');
if (actorCount) actorCount.textContent = fmt(summary.actors);
const seen = page.querySelector('h2 [data-seen]');
2026-08-17 22:51:08 +00:00
if (seen) seen.textContent = node.live ? 'live' : 'stale ' + ago(node.last_seen_ms_ago) + ' ago';
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
const machine = machineDetail(node);
const machineSlot = document.getElementById('machine-detail');
if (machineSlot & & machine !== lastMachineHtml) {
machineSlot.innerHTML = machine;
lastMachineHtml = machine;
}
2026-08-15 22:42:40 +00:00
renderRoster(node);
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
if (selectedActor) {
if (!detailTimer) startDetailPolling();
} else {
stopDetailPolling();
}
2026-08-15 22:42:40 +00:00
}
function machineDetail(node) {
const cpu = node.cpu, gpu = node.gpu, net = node.net;
const rows = [];
const cpuTotal = cpu & & cpu.host ? cpu.host.total_percent : null;
rows.push(`< div class = "bar-row" > < span class = "name" > CPU< / span > ${bar(cpuTotal)}< span class = "num" > ${cpuTotal == null ? '—' : fmt(cpuTotal, 1) + '%'}< / span > < / div > `);
if (gpu & & gpu.gpus) {
gpu.gpus.forEach((device, index) => {
const util = device.utilization_gpu_percent;
const used = device.memory_used_mib || 0, total = device.memory_total_mib || 0;
rows.push(`< div class = "bar-row" > < span class = "name" > GPU ${fmt(index)}< / span > ${bar(util)}< span class = "num" > ${util == null ? '—' : fmt(util) + '%'}< / span > < / div > `);
if (total) rows.push(`< div class = "bar-row" > < span class = "name" > < / span > ${bar(used, total)}< span class = "num" > ${fmt(used)}/${fmt(total)} MiB< / span > < / div > `);
});
}
if (net & & net.interfaces) {
net.interfaces.slice(0, 4).forEach(nic => {
rows.push(`< div class = "bar-row" > < span class = "name" title = "${esc(nic.name)}" > ${esc(nic.name.slice(0, 12))}< / span > < span class = "num" style = "width:auto;flex:1" > ${fmtRate(nic.rx_bps)}↓ ${fmtRate(nic.tx_bps)}↑< / span > < / div > `);
});
}
if (node.history & & node.history.length) drawMachineHistory(node.history);
const errors = (node.errors || []).map(e => `< div class = "notice err" > ${esc(e)}< / div > `).join('');
return `< div class = "bars" > ${rows.join('')}< / div > < canvas id = "machine-spark" width = "600" height = "64" > < / canvas > ${errors}`;
}
function drawMachineHistory(history) {
requestAnimationFrame(() => {
const canvas = document.getElementById('machine-spark');
if (!canvas) return;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
const points = history.map(h => ({ cpu: h.cpu_total_percent, gpu: h.gpu_max_percent }));
drawLine(ctx, points.map(p => p.gpu), '#fbbf24');
drawLine(ctx, points.map(p => p.cpu), '#34d399');
});
}
function drawLine(ctx, values, color) {
const valid = values.filter(v => v != null);
if (valid.length < 2 ) return ;
const max = Math.max(100, ...valid);
const step = ctx.canvas.width / (values.length - 1 || 1);
ctx.strokeStyle = color;
ctx.lineWidth = 1.5;
ctx.beginPath();
let started = false;
values.forEach((value, index) => {
if (value == null) return;
const x = index * step;
const y = ctx.canvas.height - (value / max) * (ctx.canvas.height - 6) - 3;
if (!started) { ctx.moveTo(x, y); started = true; } else ctx.lineTo(x, y);
});
ctx.stroke();
}
function renderRoster(node) {
const wrap = document.getElementById('roster-wrap');
if (!wrap) return;
let rows = node.roster || [];
if (rosterFilter) {
const q = rosterFilter.toLowerCase();
rows = rows.filter(a => `${a.name || ''} ${a.actor_type || ''} ${a.address} ${a.worker_id ?? ''}`.toLowerCase().includes(q));
}
const capped = rows.slice(0, ROSTER_RENDER_CAP);
const notice = rows.length > capped.length
? `< div class = "notice" > showing ${fmt(capped.length)} of ${fmt(rows.length)} — refine the filter to see more< / div > `
: '';
2026-08-17 22:51:08 +00:00
let html;
2026-08-15 22:42:40 +00:00
if (!rows.length) {
2026-08-17 22:51:08 +00:00
html = '< div class = "empty" > No actors on this stream (or none match the filter).< / div > ';
} else {
html = notice + `< table >
2026-08-15 22:42:40 +00:00
< thead > < tr > < th > Actor< / th > < th > Type< / th > < th > State< / th > < th > Mailbox< / th > < th > Msg/s< / th > < th > Processed< / th > < th > Worker< / th > < th > Last message< / th > < / tr > < / thead >
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
< tbody > ${capped.map(a => `< tr data-addr = "${esc(a.address)}" tabindex = "0" $ { a . address = == selectedActor ? ' data-selected = "true" ' : ' ' } >
2026-08-15 22:42:40 +00:00
< td class = "mono" > ${esc(shortAddr(a.address))}${a.name ? `< br > < span class = "muted" > ${esc(a.name)}< / span > ` : ''}< / td >
< td > < span class = "muted" title = "${esc(a.actor_type || '')}" > ${typeShort(a.actor_type)}< / span > < / td >
< td > ${a.poisoned ? '< span class = "pill poisoned" > poisoned< / span > ' : '< span class = "muted" > running< / span > '}< / td >
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
< td data-mailbox > < / td >
< td data-rate > < / td >
< td data-processed > < / td >
2026-08-15 22:42:40 +00:00
< td > ${a.worker_id == null ? '—' : fmt(a.worker_id)}< / td >
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
< td > < span class = "muted" data-last-message > < / span > < / td >
2026-08-15 22:42:40 +00:00
< / tr > `).join('')}< / tbody >
< / table > `;
2026-08-17 22:51:08 +00:00
}
Rework Myelin control and runtime integration
Add actor-backed manual node provisioning, control-plane endpoints, and fleet UI assets with durable provider lifecycle handling.
Simplify Myelin orchestration, node runtime, staging, and telemetry paths while removing obsolete engine-builder, dashboard-view, and local-mock implementations.
Align runtime delivery, data-plane, distribution, job-runner, process, telemetry, dashboard, Vast.ai integrations, and their tests with the revised actor and transport contracts.
2026-08-19 10:20:01 +00:00
if (html !== lastRosterHtml) {
wrap.innerHTML = html;
lastRosterHtml = html;
}
updateRosterTexts(wrap, capped);
}
function updateRosterTexts(wrap, actors) {
const byAddress = new Map(actors.map(actor => [actor.address, actor]));
for (const row of wrap.querySelectorAll('tr[data-addr]')) {
const actor = byAddress.get(row.getAttribute('data-addr'));
if (!actor) continue;
row.querySelector('[data-mailbox]').textContent = fmt(actor.mailbox_depth);
row.querySelector('[data-rate]').textContent = fmtRate(actor.msg_per_sec);
row.querySelector('[data-processed]').textContent = fmt(actor.messages_processed);
const last = row.querySelector('[data-last-message]');
const messageType = actor.last_msg_type || '';
last.title = messageType;
const parts = String(messageType).split('::');
last.textContent = messageType ? parts[parts.length - 1] || messageType : '—';
}
2026-08-15 22:42:40 +00:00
}
function shortAddr(address) {
const s = String(address || '');
return s.length > 14 ? s.slice(0, 8) + '…' + s.slice(-4) : s;
}
function stopDetailPolling() {
if (detailTimer) { clearInterval(detailTimer); detailTimer = null; }
}
function startDetailPolling() {
stopDetailPolling();
const poll = async () => {
if (!selectedStream || !selectedActor) return;
let detail;
try {
const response = await fetch(`/api/view/fleet/detail?stream=${encodeURIComponent(selectedStream)}&actor=${encodeURIComponent(selectedActor)}`);
if (!response.ok) { stopDetailPolling(); return; }
detail = await response.json();
} catch { return; }
renderDossier(detail);
};
poll();
detailTimer = setInterval(poll, POLL_MS);
}
function renderDossier(d) {
const slot = document.getElementById('dossier-slot');
if (!slot) return;
2026-08-17 22:51:08 +00:00
2026-08-15 22:42:40 +00:00
const diet = (d.message_type_counts || []).slice().sort((a, b) => b.count - a.count);
const dietMax = Math.max(1, ...diet.map(x => x.count));
const lastName = d.last_msg_type;
const dietHtml = diet.length ? diet.map(x => `
< div class = "bar-row" > < span class = "name" title = "${esc(x.message_type)}" > ${esc(x.message_type)}< / span >
< div class = "bar-track" > < div class = "bar-fill" style = "width:${(x.count / dietMax * 100).toFixed(1)}%" > < / div > < / div >
< span class = "num" > ${fmt(x.count)}< / span > < / div > `).join('')
: '< div class = "empty" > no message-type counts on frame< / div > ';
const receipts = (d.receipts || []).slice().reverse();
const receiptsHtml = receipts.length ? `< table class = "receipts" >
< thead > < tr > < th > When< / th > < th > Message type< / th > < th > Folded< / th > < / tr > < / thead >
< tbody > ${receipts.map(r => `< tr >
2026-08-17 22:51:08 +00:00
< td class = "muted" > ${agoSpan(Date.now() - r.ms_ago)} ago< / td >
2026-08-15 22:42:40 +00:00
< td > < span class = "mono" title = "${esc(r.ty)}" > ${typeShort(r.ty)}< / span > < / td >
< td class = "muted" > ${r.folded ? '+' + fmt(r.folded) + ' sampled' : '—'}< / td >
< / tr > `).join('')}< / tbody >
< / table >
< div class = "notice" > showing last ${fmt(receipts.length)} receipts · ${fmt(d.sampled_out)} messages sampled out · ${fmt(d.messages_processed)} processed total< / div > `
: '< div class = "empty" > no message receipts folded yet< / div > ';
2026-08-17 22:51:08 +00:00
const html = `< section class = "panel dossier" >
2026-08-15 22:42:40 +00:00
< div style = "display:flex;justify-content:space-between;align-items:baseline;gap:8px" >
< h2 style = "margin:0" > ${typeShort(d.actor_type)} < span class = "muted mono" style = "font-size:12px" > ${esc(shortAddr(d.address))}< / span > < / h2 >
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
< button id = "dossier-close" aria-label = "Close dossier" style = "background:none;border:none;color:var(--muted);cursor:pointer;font-size:16px" > × < / button >
2026-08-15 22:42:40 +00:00
< / div >
< div class = "kv" style = "margin-top:10px" >
< span class = "k" > node< / span > < span > ${esc(d.stream.node)}< / span >
< span class = "k" > address< / span > < span class = "mono" > ${esc(d.address)}< / span >
${d.name ? `< span class = "k" > name< / span > < span > ${esc(d.name)}< / span > ` : ''}
< span class = "k" > actor type< / span > < span class = "mono" title = "${esc(d.actor_type || '')}" > ${esc(d.actor_type || '—')}< / span >
< span class = "k" > message type< / span > < span class = "mono" title = "${esc(d.message_type || '')}" > ${esc(d.message_type || '—')}< / span >
< span class = "k" > state< / span > < span > ${d.poisoned ? '< span class = "pill poisoned" > poisoned< / span > ' : 'running'}< / span >
< span class = "k" > worker< / span > < span > ${d.worker_id == null ? '—' : fmt(d.worker_id)}< / span >
< span class = "k" > mailbox< / span > < span > ${fmt(d.mailbox_depth)} (${d.mailbox_growth >= 0 ? '+' : ''}${fmtRate(d.mailbox_growth)}/s)< / span >
< span class = "k" > throughput< / span > < span > ${fmtRate(d.msg_per_sec)} msg/s< / span >
< span class = "k" > processed< / span > < span > ${fmt(d.messages_processed)}< / span >
< span class = "k" > last message< / span > < span class = "mono" title = "${esc(d.last_msg_type || '')}" > ${esc(d.last_msg_type || '—')}< / span >
< / div >
< h2 > Throughput< / h2 >
< canvas id = "dossier-spark" width = "380" height = "64" > < / canvas >
< h2 > Message diet< / h2 >
< div class = "diet" > ${dietHtml}< / div >
< h2 > Message history< / h2 >
${receiptsHtml}
< / section > `;
2026-08-17 22:51:08 +00:00
if (html !== lastDossierHtml) {
slot.innerHTML = html;
lastDossierHtml = html;
}
2026-08-15 22:42:40 +00:00
const history = d.history || [];
const canvas = document.getElementById('dossier-spark');
if (canvas & & history.length > 1) {
const ctx = canvas.getContext('2d');
drawLine(ctx, history.map(h => h.msg_per_sec), '#60a5fa');
}
2026-08-17 22:51:08 +00:00
updateAgo();
2026-08-15 22:42:40 +00:00
}
2026-08-17 22:51:08 +00:00
// A2: delegation on the persistent #page container. Cards, roster rows and
// the dossier close button are rebuilt by render passes; listeners bound at
// startup mean a press straddling a rebuild still lands on the right target.
const pageEl = document.getElementById('page');
pageEl.addEventListener('click', e => {
if (e.target.closest('#dossier-close')) {
selectedActor = null;
pushUrl();
render();
return;
}
const rosterRow = e.target.closest('tr[data-addr]');
if (rosterRow) {
selectedActor = rosterRow.getAttribute('data-addr');
pushUrl();
render();
return;
}
const card = e.target.closest('a.node-card');
if (card) {
e.preventDefault();
selectedStream = card.getAttribute('data-stream');
selectedActor = null;
pushUrl();
render();
}
});
pageEl.addEventListener('keydown', e => {
if (e.key !== 'Enter') return;
const rosterRow = e.target.closest('tr[data-addr]');
if (rosterRow) {
selectedActor = rosterRow.getAttribute('data-addr');
pushUrl();
render();
}
});
2026-08-15 22:42:40 +00:00
refresh();
setInterval(refresh, POLL_MS);
< / script >
< / body >
< / html >