Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 175 additions & 41 deletions apps/decodex/src/orchestrator/operator_dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -2409,6 +2409,9 @@
width: var(--bucket-width, 0%);
background: currentColor;
opacity: 0.75;
transition:
width var(--slow) var(--ease),
opacity var(--fast) var(--ease);
}

.status-label {
Expand Down Expand Up @@ -4621,6 +4624,10 @@ <h2 id="recent-title">Run History</h2>
return `run:${run.run_id}:more-fields`;
}

function activeRunRenderKey(run) {
return `active-run:${run.run_id || issueDisplayKey(run)}`;
}

function detailsOpenAttribute(detailKey) {
return detailDisclosureState.get(detailKey) ? ' open data-detail-state="open"' : "";
}
Expand Down Expand Up @@ -4786,6 +4793,129 @@ <h2 id="recent-title">Run History</h2>
: renderEmptyState(COPY.waitingSnapshot, waitingCopy);
}

function keyedPatchNodeKey(node) {
if (!(node instanceof Element)) {
return "";
}

return node.dataset.renderKey || node.dataset.detailKey || "";
}

function syncElementAttributes(current, next) {
for (const attribute of [...current.attributes]) {
if (!next.hasAttribute(attribute.name)) {
current.removeAttribute(attribute.name);
}
}

for (const attribute of [...next.attributes]) {
if (current.getAttribute(attribute.name) !== attribute.value) {
current.setAttribute(attribute.name, attribute.value);
}
}
}

function patchNode(current, next) {
if (
current.nodeType !== next.nodeType ||
current.nodeName !== next.nodeName
) {
current.replaceWith(next.cloneNode(true));
return;
}

if (current.nodeType === Node.TEXT_NODE) {
if (current.nodeValue !== next.nodeValue) {
current.nodeValue = next.nodeValue;
}
return;
}

if (!(current instanceof Element) || !(next instanceof Element)) {
return;
}

// Preserve active accordion animation styles until their timer clears them.
if (
current.closest("details.is-animating") &&
!(current instanceof HTMLDetailsElement)
) {
return;
}

if (current instanceof HTMLDetailsElement) {
const detailKey = detailStateKey(current);
if (detailKey && detailDisclosureState.has(detailKey)) {
const shouldOpen = detailDisclosureState.get(detailKey);
if (shouldOpen) {
next.setAttribute("open", "");
next.dataset.detailState = "open";
} else {
next.removeAttribute("open");
delete next.dataset.detailState;
}
}
}

syncElementAttributes(current, next);
patchChildNodes(current, next);
}

function patchChildNodes(current, next) {
const currentChildren = [...current.childNodes];
const nextChildren = [...next.childNodes];
const keyedCurrent = new Map();

for (const child of currentChildren) {
const key = keyedPatchNodeKey(child);
if (key && !keyedCurrent.has(key)) {
keyedCurrent.set(key, child);
}
}

let cursor = current.firstChild;
const used = new Set();

for (const nextChild of nextChildren) {
const key = keyedPatchNodeKey(nextChild);
let currentChild = key ? keyedCurrent.get(key) : null;

while (cursor && used.has(cursor)) {
cursor = cursor.nextSibling;
}

if (!currentChild && cursor && !keyedPatchNodeKey(cursor)) {
currentChild = cursor;
}

if (currentChild) {
used.add(currentChild);
patchNode(currentChild, nextChild);
if (currentChild !== cursor) {
current.insertBefore(currentChild, cursor);
}
cursor = currentChild.nextSibling;
} else {
const clone = nextChild.cloneNode(true);
current.insertBefore(clone, cursor);
used.add(clone);
}
}

for (const child of [...current.childNodes]) {
if (!used.has(child)) {
child.remove();
}
}
}

function renderStableList(container, html) {
const template = document.createElement("template");
template.innerHTML = html.trim();

patchChildNodes(container, template.content);
}

function pluralLabel(count, singular, plural = `${singular}s`) {
return count === 1 ? singular : plural;
}
Expand Down Expand Up @@ -7901,46 +8031,49 @@ <h4>${escapeHtml(item.title)}</h4>
return;
}

nodes.activeRuns.innerHTML = runs
.map((run) => {
const tone = toneForRun(run);
const statusBits = [statusLabel(runStageLabel(run), tone)];
const detailKey = runDetailKey(run);
const issueKey = issueDisplayKey(run);
const issueTitle = runIssueTitle(run, derived);
const summary = activeRunSummary(run);

if (run.wait_reason && !runWaitReasonShowsExecutionProgress(run)) {
statusBits.push(inlineStatusFact("Wait", humanizeToken(run.wait_reason)));
}
if (runTelemetryMissing(run)) {
statusBits.push(
runHasChildAgentActivity(run)
? inlineStatusFact("Metadata", "Pending")
: inlineStatusFact("Telemetry", "Missing"),
);
}
if (
!runStoppedProcessNeedsAttention(run) &&
runProcessStoppedWhileActive(run)
) {
statusBits.push(inlineStatusFact("Agent", "Done"));
}
if (run.interactive_requested && !runStoppedProcessNeedsAttention(run)) {
statusBits.push(inlineStatusFact("Operator", "Input"));
}
if (run.continuation_pending) {
statusBits.push(inlineStatusFact("Continuation", "Pending"));
}
const attemptNumber = attemptNumberFromRun(run);
const stopControl = renderRunStopControl(run);
const statusLineParts = [...statusBits];
if (stopControl) {
statusLineParts.splice(1, 0, stopControl);
}
renderStableList(
nodes.activeRuns,
runs
.map((run) => {
const tone = toneForRun(run);
const statusBits = [statusLabel(runStageLabel(run), tone)];
const detailKey = runDetailKey(run);
const renderKey = activeRunRenderKey(run);
const issueKey = issueDisplayKey(run);
const issueTitle = runIssueTitle(run, derived);
const summary = activeRunSummary(run);

if (run.wait_reason && !runWaitReasonShowsExecutionProgress(run)) {
statusBits.push(inlineStatusFact("Wait", humanizeToken(run.wait_reason)));
}
if (runTelemetryMissing(run)) {
statusBits.push(
runHasChildAgentActivity(run)
? inlineStatusFact("Metadata", "Pending")
: inlineStatusFact("Telemetry", "Missing"),
);
}
if (
!runStoppedProcessNeedsAttention(run) &&
runProcessStoppedWhileActive(run)
) {
statusBits.push(inlineStatusFact("Agent", "Done"));
}
if (run.interactive_requested && !runStoppedProcessNeedsAttention(run)) {
statusBits.push(inlineStatusFact("Operator", "Input"));
}
if (run.continuation_pending) {
statusBits.push(inlineStatusFact("Continuation", "Pending"));
}
const attemptNumber = attemptNumberFromRun(run);
const stopControl = renderRunStopControl(run);
const statusLineParts = [...statusBits];
if (stopControl) {
statusLineParts.splice(1, 0, stopControl);
}

return `
<article class="run-card ${tone}">
return `
<article class="run-card ${tone}" data-render-key="${escapeHtml(renderKey)}">
<div class="row-head">
<div class="run-title-stack">
<div class="run-subtitle">
Expand Down Expand Up @@ -7987,8 +8120,9 @@ <h3 class="run-title">${escapeHtml(issueTitle)}</h3>
</details>
</article>
`;
})
.join("");
})
.join(""),
);
}

function renderAttemptTimeline(lane) {
Expand Down
15 changes: 15 additions & 0 deletions apps/decodex/src/orchestrator/tests/operator/status/dashboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,21 @@ fn operator_dashboard_cards_and_accounts_share_running_lane_typography() {
assert!(!response.contains(".account-use-label"));
}

#[test]
fn operator_dashboard_patches_active_run_cards_without_replacing_the_list() {
let response = dashboard_response();

assert!(response.contains("function renderStableList(container, html)"));
assert!(response.contains("function patchChildNodes(current, next)"));
assert!(response.contains("function activeRunRenderKey(run)"));
assert!(response.contains("data-render-key=\"${escapeHtml(renderKey)}\""));
assert!(response.contains("renderStableList(\n\t\t\t\t\tnodes.activeRuns,"));
assert!(!response.contains("nodes.activeRuns.innerHTML = runs"));
assert!(response.contains("return node.dataset.renderKey || node.dataset.detailKey || \"\";"));
assert!(response.contains("current.closest(\"details.is-animating\")"));
assert!(response.contains("width var(--slow) var(--ease),"));
}

#[test]
fn operator_dashboard_child_bucket_rows_split_time_bars_from_event_diagnostics() {
let response = String::from_utf8(
Expand Down