diff --git a/web/js/api.js b/web/js/api.js
index a818ba7..9e2e88e 100644
--- a/web/js/api.js
+++ b/web/js/api.js
@@ -25,6 +25,17 @@ async function populateStudyList() {
if (study === activeStudy) opt.selected = true;
select.appendChild(opt);
});
+ if (!activeStudy && studiesToShow.length > 0) {
+ select.value = studiesToShow[0];
+ window.HPOState.session.studyName = studiesToShow[0];
+ const url = new URL(window.location);
+ url.searchParams.set('study', studiesToShow[0]);
+ window.history.pushState({}, '', url);
+ window.fetchStudyDetails();
+ window.fetchHpoConfig();
+ window.fetchSearchSpace();
+ window.fetchFanova();
+ }
}
}
} catch (err) {
@@ -33,6 +44,7 @@ async function populateStudyList() {
}
async function fetchStudyDetails(throwOnError = false) {
+ if (!window.HPOState.session.studyName) return;
try {
const res = await fetch(`/api/study_details?study_name=${window.HPOState.session.studyName}`);
if (!res.ok) {
@@ -42,11 +54,7 @@ async function fetchStudyDetails(throwOnError = false) {
const data = await res.json();
window.HPOState.data.latestStudyData = data;
- const completedCount = data.trials.filter(t => t.state === "COMPLETE" || t.state === "FAIL" || t.state === "PRUNED").length;
- if (window.HPOState.telemetry.lastCompletedCount !== null && completedCount !== window.HPOState.telemetry.lastCompletedCount) {
- window.HPOState.ui.reviewPillDismissed = false;
- }
- window.HPOState.telemetry.lastCompletedCount = completedCount;
+ window.HPOState.telemetry.lastCompletedCount = data.trials.filter(t => t.state === "COMPLETE" || t.state === "FAIL" || t.state === "PRUNED").length;
const select = document.getElementById("study-select");
if (select) select.value = data.study_name;
@@ -58,8 +66,6 @@ async function fetchStudyDetails(throwOnError = false) {
window.HPOState.data.review = data.review || null;
const nComplete = data.completed_count ?? data.trials.filter((t) => t.state === "COMPLETE").length;
updateStatConfidenceBanner(data.statistical_confidence || "low", nComplete);
- renderPastReviews(data.past_reviews || []);
- applyReviewUi();
updateAnalysisStatusTicker();
syncTelemetryFromTrials([...data.trials].sort((a, b) => b.number - a.number));
@@ -76,6 +82,7 @@ async function fetchStudyDetails(throwOnError = false) {
}
async function fetchHpoConfig() {
+ if (!window.HPOState.session.studyName) return;
const studyName = window.HPOState.session.studyName || '';
try {
const res = await fetch(`/api/hpo_config?study_name=${encodeURIComponent(studyName)}`);
@@ -89,6 +96,7 @@ async function fetchHpoConfig() {
}
async function fetchSearchSpace() {
+ if (!window.HPOState.session.studyName) return;
const studyName = window.HPOState.session.studyName || '';
try {
const res = await fetch(`/api/search_space?study_name=${encodeURIComponent(studyName)}`);
@@ -96,7 +104,6 @@ async function fetchSearchSpace() {
window.HPOState.data.activeSearchSpace = await res.json();
renderSearchSpace();
renderDashboardSearchSpaceSummary();
- fetchPendingChanges();
if (window.HPOState.data.latestStudyData) {
renderStudyDetails(window.HPOState.data.latestStudyData);
}
@@ -106,6 +113,7 @@ async function fetchSearchSpace() {
}
async function fetchFanova() {
+ if (!window.HPOState.session.studyName) return;
try {
const res = await fetch(`/api/fanova?study_name=${window.HPOState.session.studyName}`);
if (!res.ok) return;
diff --git a/web/js/charts_modal.js b/web/js/charts_modal.js
index c3d0112..ea4a630 100644
--- a/web/js/charts_modal.js
+++ b/web/js/charts_modal.js
@@ -1,3 +1,72 @@
+function openTrialDetails(trialNumber) {
+ trialNumber = Number(trialNumber);
+ const trials = window.HPOState.data.trials || [];
+ const trial = trials.find(t => t.number === trialNumber);
+ if (!trial) return;
+
+ document.getElementById("modal-trial-title").textContent = `Trial #${trial.number}`;
+
+ const paramsEl = document.getElementById("modal-params");
+ if (paramsEl) {
+ const params = trial.params_display || trial.params || {};
+ paramsEl.innerHTML = Object.entries(params)
+ .map(([k, v]) => `
`)
+ .join("");
+ }
+
+ const oomSection = document.getElementById("modal-oom-section");
+ if (oomSection) {
+ oomSection.style.display = trial.oom_triggered ? "block" : "none";
+ }
+
+ const state = trial.state || "";
+ const titleEl = document.getElementById("modal-rationale-title");
+ if (titleEl) {
+ titleEl.textContent = state === "COMPLETE" ? "Result" : "Status";
+ }
+
+ const reasoningEl = document.getElementById("modal-reasoning");
+ if (reasoningEl) {
+ reasoningEl.textContent = state === "COMPLETE"
+ ? `Completed with ${trial.loss != null ? `loss ${Number(trial.loss).toFixed(4)}` : "unknown loss"} and ${trial.score != null ? `score ${Number(trial.score).toFixed(4)}` : "unknown score"}.`
+ : state === "PRUNED" ? "Pruned by median-rule early stopping." :
+ state === "FAIL" ? `Failed${trial.oom_triggered ? " (Out of Memory)" : ""}.` :
+ "Awaiting results.";
+ }
+
+ const history = trial.history || [];
+ updateModalChart(history);
+
+ const modal = document.getElementById("detail-modal");
+ if (modal) modal.classList.add("active");
+ window.HPOState.ui.isModalOpen = true;
+}
+
+function closeModal(event) {
+ if (event && event.target !== event.currentTarget) return;
+ closeModalDirect();
+}
+
+function closeModalDirect() {
+ const modal = document.getElementById("detail-modal");
+ if (modal) modal.classList.remove("active");
+ window.HPOState.ui.isModalOpen = false;
+ if (window.HPOState.charts.modalHistory.instance) {
+ window.HPOState.charts.modalHistory.instance.destroy();
+ window.HPOState.charts.modalHistory.instance = null;
+ }
+ if (window.HPOState.render.pendingRender) {
+ window.HPOState.render.pendingRender = false;
+ if (window.renderStudyDetails && window.HPOState.data.latestStudyData) {
+ window.renderStudyDetails(window.HPOState.data.latestStudyData);
+ }
+ }
+}
+
+window.openTrialDetails = openTrialDetails;
+window.closeModal = closeModal;
+window.closeModalDirect = closeModalDirect;
+
function updateModalChart(history) {
const ctx = document.getElementById("modal-history-chart")?.getContext("2d");
if (!ctx) return;
diff --git a/web/js/charts_pareto.js b/web/js/charts_pareto.js
index 85a24c0..10dc68b 100644
--- a/web/js/charts_pareto.js
+++ b/web/js/charts_pareto.js
@@ -45,9 +45,13 @@ function updateChart(trials, paretoSet, directions) {
const trialNum = parseInt(match[1], 10);
const trial = trials.find(t => t.number === trialNum);
if (trial) {
- const paramsList = Object.entries(trial.params)
- .map(([k, v]) => `${k.replace(/_/g, " ")}: ${typeof v === 'number' && v % 1 !== 0 ? v.toFixed(4) : v}`)
+ const paramKeys = Object.keys(trial.params);
+ const first3 = paramKeys.slice(0, 3)
+ .map(k => `${k.replace(/_/g, " ")}: ${typeof trial.params[k] === 'number' && trial.params[k] % 1 !== 0 ? trial.params[k].toFixed(4) : trial.params[k]}`)
.join(", ");
+ const paramsList = paramKeys.length > 3
+ ? `${first3}, +${paramKeys.length - 3} more`
+ : first3;
let labelText = `${rawPoint.label} | `;
if (isSingleObj) {
diff --git a/web/js/health.js b/web/js/health.js
index b14ed29..698651f 100644
--- a/web/js/health.js
+++ b/web/js/health.js
@@ -1,66 +1,15 @@
-function renderHealthLatestAction() {
- const body = document.getElementById("health-action-body");
- if (!body) return;
- body.replaceChildren();
- const action = window.getLatestCoordinatorAction(window.HPOState.data.pastReviews, window.HPOState.data.pendingChanges);
- if (!action) {
- body.appendChild(Object.assign(document.createElement("div"), { className: "health-action-line health-action-muted", textContent: "No coordinator action recorded" }));
- return;
- }
- body.appendChild(Object.assign(document.createElement("div"), { className: "health-action-line", textContent: action.label }));
- body.appendChild(Object.assign(document.createElement("div"), { className: "health-action-target", textContent: action.detail }));
- if (action.timestamp) {
- body.appendChild(Object.assign(document.createElement("div"), { className: "health-action-time", textContent: window.formatReviewTimestamp(action.timestamp) }));
- }
- if (action.kind === "pending") {
- body.appendChild(Object.assign(document.createElement("a"), { className: "health-action-link", href: "#search-space", textContent: "Review in Search Space →" }));
- }
-}
-
function renderHealthPanelExtras() {
- const tier = window.HPOState.data.healthTier || "healthy", latestReview = (window.HPOState.data.pastReviews || [])[0], rating = latestReview?.health_rating;
- const tierLine = document.getElementById("health-tier-line"), blocksEl = document.getElementById("health-rating-blocks");
+ const tier = window.HPOState.data.healthTier || "healthy";
+ const tierLine = document.getElementById("health-tier-line");
if (tierLine) tierLine.textContent = tier.charAt(0).toUpperCase() + tier.slice(1);
- if (blocksEl) {
- const blocks = window.buildHealthBlocks(rating);
- blocksEl.textContent = blocks ? (blocks + (rating ? ` ${rating}/5` : "")) : "— — — — —";
- blocksEl.classList.toggle("health-blocks-empty", !blocks);
- }
- renderHealthLatestAction();
-}
-
-function renderPastReviews(reviews) {
- const details = document.getElementById("audit-history-details"), summary = document.getElementById("audit-history-summary"), list = document.getElementById("past-reviews-list");
- if (!list) return;
- window.HPOState.data.pastReviews = reviews || [];
- list.replaceChildren();
- const count = (reviews || []).length;
- if (summary) summary.textContent = count ? `Show audit history (${count} logs)` : "No audit history";
- if (details) details.style.display = count ? "" : "none";
- if (!count) { renderHealthPanelExtras(); return; }
- reviews.forEach((r) => {
- const row = Object.assign(document.createElement("div"), { className: "audit-history-row" + (r.policy_action === "no_change" ? " no-change" : "") });
- const parts = [`#${r.id}`, window.policyActionLabel(r.policy_action), window.formatReviewDate(r.created_at), (r.summary || "").slice(0, 80)].filter(Boolean);
- row.appendChild(Object.assign(document.createElement("div"), { textContent: parts.join(" · ") }));
- if (r.policy_action && r.policy_action !== "no_change") {
- const btn = Object.assign(document.createElement("button"), { type: "button", className: "audit-flag-btn", textContent: r.quality_flagged ? "Unflag" : "Flag" });
- btn.addEventListener("click", () => toggleReviewFlag(r.id, !r.quality_flagged));
- row.appendChild(btn);
- }
- list.appendChild(row);
- });
- renderHealthPanelExtras();
}
function updateAnalysisStatusTicker() {
const ticker = document.getElementById("analysis-status-ticker"), track = document.getElementById("analysis-ticker-track");
if (!ticker || !track) return;
- const tier = window.HPOState.data.healthTier || "healthy", reason = window.HPOState.data.healthReason || window.HPOState.data.studyHealthReason || "", review = window.HPOState.data.review, confidence = window.HPOState.data.statisticalConfidence || "low", nComplete = window.HPOState.data.completedCount || 0;
+ const tier = window.HPOState.data.healthTier || "healthy", reason = window.HPOState.data.healthReason || window.HPOState.data.studyHealthReason || "", confidence = window.HPOState.data.statisticalConfidence || "low", nComplete = window.HPOState.data.completedCount || 0;
const messages = [];
if (tier === "watch" || tier === "intervene") messages.push(`${tier.charAt(0).toUpperCase() + tier.slice(1)}: ${reason}`);
- if (review?.review_recommended && (review.reasons || []).length && !window.HPOState.ui.reviewPillDismissed) {
- messages.push(`Coordinator review recommended — ` + review.reasons.map((r) => r.message || r.code).join(" · "));
- }
if (confidence === "low") messages.push(`${nComplete} complete trial${nComplete === 1 ? "" : "s"}. Early signal; treat rankings as indicative.`);
track.replaceChildren();
if (!messages.length) {
@@ -68,7 +17,7 @@ function updateAnalysisStatusTicker() {
ticker.classList.remove("tier-healthy", "tier-watch", "tier-intervene");
return;
}
- let displayTier = tier === "intervene" ? "intervene" : (tier === "watch" || (review?.review_recommended && (review.reasons || []).length && !window.HPOState.ui.reviewPillDismissed) || confidence === "low" ? "watch" : "healthy");
+ let displayTier = tier === "intervene" ? "intervene" : (tier === "watch" || confidence === "low" ? "watch" : "healthy");
let separator = " ▲ ";
if (displayTier === "watch") {
separator = " ◆ ";
@@ -82,121 +31,32 @@ function updateAnalysisStatusTicker() {
track.style.setProperty("--ticker-duration", `${Math.max(20, Math.min(45, text.length * 0.28))}s`);
}
-async function toggleReviewFlag(reviewId, flagged) {
- try {
- await fetch(`/api/flag_review?review_id=${reviewId}&flagged=${flagged}`, { method: "POST" });
- fetchStudyDetails();
- } catch (err) { console.error("Failed to flag review:", err); }
-}
-
-function applyReviewUi() {
- const recommended = window.HPOState.data.review?.review_recommended, reasons = window.HPOState.data.review?.reasons || [];
- const show = !!(recommended && reasons.length) && !window.HPOState.ui.reviewPillDismissed;
- const pill = document.getElementById("dashboard-review-pill"), pillReasons = document.getElementById("dashboard-review-reasons");
- if (pill) {
- pill.classList.toggle("hidden", !show);
- if (pillReasons) pillReasons.textContent = show ? reasons.map((r) => r.message || r.code).join(" • ") : "";
- }
- updateAnalysisStatusTicker();
-}
-
-async function copyReviewPrompt() {
- const pillAction = document.querySelector("#dashboard-review-pill .review-pill-action");
- try {
- const res = await fetch(`/api/review_packet?study_name=${window.HPOState.session.studyName}`);
- const data = await res.json();
- await navigator.clipboard.writeText(data.review_prompt || "");
- if (pillAction) {
- const prev = pillAction.textContent; pillAction.textContent = "Copied!";
- setTimeout(() => { pillAction.textContent = prev; }, 1600);
- }
- fetch(`/api/dismiss_coordinator_nudge?study_name=${window.HPOState.session.studyName}`, { method: "POST" }).catch(e => {});
- const mark = document.querySelector('.brand-mark'); if (mark) mark.classList.remove("ping-twice");
- if (window.HPOState.ui.reviewPillDismissTimeout) clearTimeout(window.HPOState.ui.reviewPillDismissTimeout);
- window.HPOState.ui.reviewPillDismissTimeout = setTimeout(() => { dismissReviewPill(); }, 120000);
- } catch (err) { console.error("Could not copy review prompt:", err); }
-}
-
async function checkStudyHealth(throwOnError = false) {
+ if (!window.HPOState.session.studyName) return;
try {
const res = await fetch(`/api/study_health?study_name=${window.HPOState.session.studyName}`);
if (!res.ok) { if (throwOnError) throw new Error("HTTP error " + res.status); return; }
const data = await res.json(), card = document.getElementById("study-health-card"), badge = document.getElementById("health-badge");
- const msg = document.getElementById("health-message"), actionBtn = document.getElementById("health-action-btn"), dismissBtn = document.getElementById("health-dismiss-btn"), mark = document.querySelector('.brand-mark');
+ const msg = document.getElementById("health-message"), mark = document.querySelector('.brand-mark');
if (!card) return;
window.HPOState.data.studyHealthReason = window.HPOState.data.healthReason = data.health_reason || "";
window.HPOState.data.healthTier = data.health_tier || "healthy";
- card.className = "card health-card " + (data.health_tier || "healthy");
+ const tier = data.health_tier || "healthy";
+ card.className = "card health-card";
+ const orb = document.getElementById("health-orb");
+ if (orb) orb.className = "status-orb " + tier;
if (mark) mark.classList.remove("ping-watch", "ping-intervene", "ping-twice");
if (badge) { badge.className = `badge ${data.health_tier || 'healthy'}`; badge.textContent = (data.health_tier || 'healthy').toUpperCase(); }
- const isDismissed = !!data.is_dismissed;
if (data.health_tier === "healthy") {
msg.textContent = "No anomalies detected. Search space is healthy.";
- if (actionBtn) actionBtn.style.display = "none";
- if (dismissBtn) dismissBtn.style.display = "none";
} else {
msg.textContent = data.health_reason || (data.health_tier === "watch" ? "Watch condition detected." : "Intervention recommended.");
- if (actionBtn) actionBtn.style.display = data.health_tier === "intervene" ? "inline-block" : "none";
- if (dismissBtn) dismissBtn.style.display = isDismissed ? "none" : "inline-block";
- if (mark && !isDismissed) mark.classList.add(data.health_tier === "watch" ? "ping-watch" : "ping-intervene");
+ if (mark) mark.classList.add(data.health_tier === "watch" ? "ping-watch" : "ping-intervene");
}
renderHealthPanelExtras(); updateAnalysisStatusTicker();
} catch (err) { console.error("Error checking study health:", err); if (throwOnError) throw err; }
}
-async function dismissHealthAlert() {
- try {
- const res = await fetch(`/api/dismiss_coordinator_nudge?study_name=${window.HPOState.session.studyName}`, { method: "POST" });
- if (res.ok) checkStudyHealth();
- } catch (err) { console.error("Error dismissing health alert:", err); }
-}
-
-function copyDiagnosticPrompt() {
- const diagnosticPayload = {
- anomaly: window.HPOState.data.studyHealthReason || "unknown anomaly",
- active_search_space: window.HPOState.data.activeSearchSpace || {},
- recent_trials: window.HPOState.data.trials.slice(0, 3).map(t => ({
- trial_id: t.number, state: t.state, params: t.params, score: t.score, loss: t.loss
- }))
- };
- const promptText = `I need help debugging my Pathfinder study because it is failing health checks:
-ANOMALY: ${diagnosticPayload.anomaly}
-
-ACTIVE SEARCH SPACE:
-${JSON.stringify(diagnosticPayload.active_search_space, null, 2)}
-
-RECENT TRIALS:
-${JSON.stringify(diagnosticPayload.recent_trials, null, 2)}
-
-Please analyze the failure and suggest one policy action (update_search_space or enqueue_one_manual_trial).`;
-
- navigator.clipboard.writeText(promptText).then(() => {
- const btn = document.getElementById("health-action-btn");
- if (btn) {
- const orig = btn.textContent; btn.textContent = "Copied!";
- setTimeout(() => btn.textContent = orig, 2000);
- }
- }).catch(err => console.error("Could not copy diagnostic prompt:", err));
-}
-
-function dismissReviewPill() {
- window.HPOState.ui.reviewPillDismissed = true;
- const pill = document.getElementById("dashboard-review-pill");
- if (pill) pill.classList.add("hidden");
-
- // Clear double ping on dismiss via backend DB
- fetch(`/api/dismiss_coordinator_nudge?study_name=${window.HPOState.session.studyName}`, { method: "POST" })
- .catch(err => console.error("Error dismissing nudge:", err));
-
- const brandMark = document.querySelector('.brand-mark');
- if (brandMark) brandMark.classList.remove("ping-twice");
-
- if (window.HPOState.ui.reviewPillDismissTimeout) {
- clearTimeout(window.HPOState.ui.reviewPillDismissTimeout);
- window.HPOState.ui.reviewPillDismissTimeout = null;
- }
-}
-
function updateStatConfidenceBanner(confidence, nComplete) {
window.HPOState.data.statisticalConfidence = confidence || "low";
window.HPOState.data.completedCount = Number.isFinite(nComplete) ? nComplete : 0;
@@ -227,12 +87,10 @@ function syncTelemetryFromTrials(trials) {
return;
}
+ window.HPOState.telemetry.trialNumber = running.number;
const epochSuffix =
running.latest_epoch != null ? ` · E${running.latest_epoch}` : "";
const label = `Trial #${running.number}${epochSuffix}`;
- if (window.HPOState.telemetry.trialNumber !== running.number) {
- window.HPOState.telemetry.trialNumber = running.number;
- }
setWorkerRunIndicator(true, label);
}
@@ -247,17 +105,9 @@ function setWorkerRunIndicator(isLive, label) {
if (wrap) wrap.title = label;
}
-window.renderHealthLatestAction = renderHealthLatestAction;
window.renderHealthPanelExtras = renderHealthPanelExtras;
-window.renderPastReviews = renderPastReviews;
window.updateAnalysisStatusTicker = updateAnalysisStatusTicker;
-window.toggleReviewFlag = toggleReviewFlag;
-window.applyReviewUi = applyReviewUi;
-window.copyReviewPrompt = copyReviewPrompt;
window.checkStudyHealth = checkStudyHealth;
-window.dismissHealthAlert = dismissHealthAlert;
-window.copyDiagnosticPrompt = copyDiagnosticPrompt;
-window.dismissReviewPill = dismissReviewPill;
window.updateStatConfidenceBanner = updateStatConfidenceBanner;
window.syncTelemetryFromTrials = syncTelemetryFromTrials;
window.setWorkerRunIndicator = setWorkerRunIndicator;
diff --git a/web/js/main.js b/web/js/main.js
index 8735d1c..21382f3 100644
--- a/web/js/main.js
+++ b/web/js/main.js
@@ -1,5 +1,5 @@
document.addEventListener("DOMContentLoaded", () => {
- window.initHpoMarks();
+ window.populateStudyList();
window.syncTopPerformersFilterCheckboxes();
const savedAccentName = localStorage.getItem("hpo_accent_name") || "cyan";
diff --git a/web/js/settings.js b/web/js/settings.js
index 1e6a519..1ee1077 100644
--- a/web/js/settings.js
+++ b/web/js/settings.js
@@ -70,15 +70,7 @@ async function saveEvalProtocol() {
}
function togglePill(param, option, btn) {
- const list = window.HPOState.data.activeSearchSpace[param].active;
- const val = isNaN(option) ? option : Number(option);
- const index = list.indexOf(val);
- if (index > -1) {
- if (list.length > 1) { list.splice(index, 1); btn.classList.remove("active"); }
- } else {
- list.push(val);
- btn.classList.add("active");
- }
+ btn.classList.toggle("active");
markParamModified(param);
}
@@ -87,6 +79,10 @@ async function applySingleParamConstraints(param) {
if (val.type === "float" || val.type === "float_log" || val.type === "int") {
const minEl = document.getElementById(`${param}-min`), maxEl = document.getElementById(`${param}-max`);
if (minEl && maxEl) { val.min = Number(minEl.value); val.max = Number(maxEl.value); }
+ } else if (val.type === "categorical") {
+ const pills = [...document.querySelectorAll(`#group-${param} .pill-btn.active`)]
+ .map(b => { const v = b.textContent; return isNaN(v) ? v : Number(v); });
+ val.active = pills;
}
try {
const res = await fetch(`/api/update_search_space?study_name=${window.HPOState.session.studyName}`, {
@@ -193,6 +189,37 @@ function renderDashboardSearchSpaceSummary() {
}
function markParamModified(param) {
+ const currentSpace = window.HPOState.data.activeSearchSpace || {};
+ const cfg = currentSpace[param];
+ if (!cfg) return clearParamModified(param);
+
+ if (cfg.type === "categorical") {
+ const activePills = [...document.querySelectorAll(`#group-${param} .pill-btn.active`)]
+ .map(b => { const v = b.textContent; return isNaN(v) ? v : Number(v); });
+ const original = (cfg.active || []).slice().sort();
+ const current = activePills.slice().sort();
+ if (original.length === current.length && original.every((v, i) => v === current[i])) {
+ clearParamModified(param);
+ return;
+ }
+ window.HPOState.ui.modifiedParams.add(param);
+ const indicator = document.getElementById(`indicator-${param}`);
+ const btn = document.getElementById(`apply-btn-${param}`);
+ if (indicator) indicator.style.opacity = 1;
+ if (btn) btn.style.display = "inline-block";
+ return;
+ }
+
+ const minInput = document.getElementById(`${param}-min`);
+ const maxInput = document.getElementById(`${param}-max`);
+ const minChanged = minInput && parseFloat(minInput.value) !== parseFloat(cfg.min);
+ const maxChanged = maxInput && parseFloat(maxInput.value) !== parseFloat(cfg.max);
+
+ if (!minChanged && !maxChanged) {
+ clearParamModified(param);
+ return;
+ }
+
window.HPOState.ui.modifiedParams.add(param);
const indicator = document.getElementById(`indicator-${param}`);
const btn = document.getElementById(`apply-btn-${param}`);
diff --git a/web/js/state.js b/web/js/state.js
index ba7273d..caa9a06 100644
--- a/web/js/state.js
+++ b/web/js/state.js
@@ -2,7 +2,7 @@ window.HPOState = {
session: {
studyName: new URLSearchParams(window.location.search).get("study")
|| new URLSearchParams(window.location.search).get("study_name")
- || "bridge_crack_study",
+ || null,
},
data: {
trials: [],
@@ -15,8 +15,6 @@ window.HPOState = {
studyHealthReason: "",
healthTier: "healthy",
healthReason: "",
- pastReviews: [],
- pendingChanges: null,
statisticalConfidence: "low",
completedCount: 0,
},
@@ -24,8 +22,6 @@ window.HPOState = {
accentColorHex: "#06b6d4",
filterTopPerformersOnly: false,
isModalOpen: false,
- reviewPillDismissed: false,
- reviewPillDismissTimeout: null,
toastTimeout: null,
columnWidths: {},
modifiedParams: new Set(),
@@ -59,24 +55,6 @@ window.HPOState = {
constants: {
HPO_FILTER_TOP_KEY: "hpo_filter_top",
DASHBOARD_TABLE_COLS: 9,
- HPO_MARK_SVG: `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
`,
},
};
window.HPOState.ui.filterTopPerformersOnly =
diff --git a/web/js/table_render.js b/web/js/table_render.js
index 0725fb6..311e9b7 100644
--- a/web/js/table_render.js
+++ b/web/js/table_render.js
@@ -49,11 +49,7 @@ function renderDashboardTableBody(displayTrials, paretoSet, data) {
const tbody = document.getElementById("trials-table-body");
if (!tbody) return;
const ev = data.hpo_config?.eval_protocol || {};
- let paramKeys = [];
- data.trials.forEach((t) => Object.keys(t.params).forEach((k) => {
- if (!paramKeys.includes(k)) paramKeys.push(k);
- }));
- paramKeys.sort();
+ const paramKeys = getParamKeys();
const piped = applyTablePipeline(displayTrials, "dashboard");
if (!piped.length) {
@@ -101,7 +97,7 @@ function renderDashboardTableBody(displayTrials, paretoSet, data) {
syncTrialColumnWidth(document.getElementById("dashboard-trial-table"), piped);
}
-function getAnalysisParamKeys() {
+function getParamKeys() {
const activeSpace = window.HPOState.data.activeSearchSpace || {};
let keys = Object.keys(activeSpace).filter(k => !k.startsWith("_") && typeof activeSpace[k] === "object");
if (keys.length === 0 && window.HPOState.data.trials && window.HPOState.data.trials.length > 0) {
@@ -122,7 +118,7 @@ function renderAnalysisTableBody(displayTrials) {
if (!tbody) return;
applyAnalysisTableHeaders();
const ev = window.HPOState.data.hpoConfig?.eval_protocol || {};
- const paramKeys = getAnalysisParamKeys();
+ const paramKeys = getParamKeys();
const piped = applyTablePipeline(displayTrials, "analysis");
if (!piped.length) {
const totalCols = 4 + (ev.enabled ? 1 : 0) + paramKeys.length + 1;
@@ -172,7 +168,7 @@ function renderAnalysisTrialsTable(trials) {
function applyAnalysisTableHeaders() {
const ev = window.HPOState.data.hpoConfig?.eval_protocol || {};
const paramLabels = window.HPOState.data.hpoConfig?.param_labels || {};
- const paramKeys = getAnalysisParamKeys();
+ const paramKeys = getParamKeys();
const lossLabel = window.HPOState.data.hpoConfig?.metric_loss_label || "Loss";
const scoreLabel = window.HPOState.data.hpoConfig?.metric_score_label || "Score";
@@ -197,7 +193,7 @@ function applyAnalysisTableHeaders() {
const label = paramLabels[k] || k.replace(/_/g, " ");
thHtml += buildSortableTh(label, `param:${k}`, "analysis");
});
- thHtml += `
Actions `;
+ thHtml += `
Actions `;
tableHeader.innerHTML = thHtml;
const table = tableHeader.closest("table");
const baseTrials = window.HPOState.data.trials || [];
@@ -212,25 +208,9 @@ function renderStudyDetails(data) {
if (data.hpo_config) applyAnalysisTableHeaders();
applyEvalInsightsUi();
- applyReviewUi();
- // Find all parameters defined in trials or search space dynamically
- let paramKeys = [];
const paramLabels = data.hpo_config?.param_labels || {};
-
- if (data.trials && data.trials.length > 0) {
- data.trials.forEach(t => {
- Object.keys(t.params).forEach(k => {
- if (!paramKeys.includes(k)) paramKeys.push(k);
- });
- });
- } else if (window.HPOState.data.activeSearchSpace) {
- Object.keys(window.HPOState.data.activeSearchSpace).forEach(k => {
- if (!k.startsWith("_") && !paramKeys.includes(k)) paramKeys.push(k);
- });
- }
- paramKeys.sort();
-
+ const paramKeys = getParamKeys();
const ev = data.hpo_config?.eval_protocol || {};
// Render live monitor table headers dynamically
@@ -256,7 +236,7 @@ function renderStudyDetails(data) {
thHtml += buildSortableTh(label, `param:${k}`, "dashboard");
});
thHtml += `
★ `;
- thHtml += `
Actions `;
+ thHtml += `
Actions `;
tableHeader.innerHTML = thHtml;
const table = tableHeader.closest("table");
syncTrialColumnWidth(table, data.trials || []);
diff --git a/web/js/utils.js b/web/js/utils.js
index 1f896f9..be8d0ab 100644
--- a/web/js/utils.js
+++ b/web/js/utils.js
@@ -4,8 +4,7 @@ function paramLabel(key) {
}
function fanovaParamLabel(param) {
- const legacy = { encoder_name: "model_capacity" };
- return paramLabel(legacy[param] || param);
+ return paramLabel(param);
}
function formatTrainResolution(trial) {
@@ -70,85 +69,6 @@ function copyTrialsToJson() {
});
}
-function policyActionLabel(action) {
- const labels = {
- no_change: "No change",
- update_search_space: "Search space adjusted",
- enqueue_one_manual_trial: "Manual trial queued",
- };
- return labels[action] || action || "Unknown action";
-}
-
-function formatReviewDate(createdAt) {
- if (!createdAt) return "";
- const d = new Date(createdAt);
- if (Number.isNaN(d.getTime())) return String(createdAt);
- return d.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" });
-}
-
-function formatOutcomeStatus(status) {
- const labels = {
- measured: "Outcome measured",
- pending: "Outcome pending",
- not_applicable: "No change applied",
- inconclusive: "Outcome inconclusive",
- };
- return labels[status] || status || "";
-}
-
-function buildHealthBlocks(rating) {
- if (rating == null || rating <= 0) return null;
- const n = Math.max(0, Math.min(5, Math.round(rating)));
- const filled = "■ ".repeat(n).trim();
- const empty = "□ ".repeat(5 - n).trim();
- return (filled + (filled && empty ? " " : "") + empty).trim();
-}
-
-function formatReviewTimestamp(createdAt) {
- if (!createdAt) return "";
- const d = new Date(createdAt);
- if (Number.isNaN(d.getTime())) return String(createdAt);
- return d.toISOString().replace("T", " ").slice(0, 16) + " UTC";
-}
-
-function getLatestCoordinatorAction(pastReviews, pendingChanges) {
- if (pendingChanges && Object.keys(pendingChanges).length > 0) {
- const keys = Object.keys(pendingChanges).map((k) => window.paramLabel(k)).join(", ");
- return {
- kind: "pending",
- label: "Pending approval",
- detail: `Search space adjustment (${keys})`,
- timestamp: null,
- };
- }
- const actionable = (pastReviews || []).find((r) => r.policy_action && r.policy_action !== "no_change");
- if (actionable) {
- return {
- kind: "review",
- label: policyActionLabel(actionable.policy_action),
- detail: (actionable.summary || "").slice(0, 100),
- timestamp: actionable.created_at,
- reviewId: actionable.id,
- };
- }
- return null;
-}
-
-window.paramLabel = paramLabel;
-window.fanovaParamLabel = fanovaParamLabel;
-window.formatTrainResolution = formatTrainResolution;
-window.formatMetric = formatMetric;
-window.formatLR = formatLR;
-window.formatTime = formatTime;
-window.copyTrialConfigToClipboard = copyTrialConfigToClipboard;
-window.copyTrialsToJson = copyTrialsToJson;
-window.policyActionLabel = policyActionLabel;
-window.formatReviewDate = formatReviewDate;
-window.formatOutcomeStatus = formatOutcomeStatus;
-window.buildHealthBlocks = buildHealthBlocks;
-window.formatReviewTimestamp = formatReviewTimestamp;
-window.getLatestCoordinatorAction = getLatestCoordinatorAction;
-
function showEmptyState(containerId, message) {
const container = document.getElementById(containerId);
if (!container) return;
@@ -244,15 +164,6 @@ function hideEmptyState(containerId, recreateCanvas = false) {
}
}
-function initHpoMarks() {
- const svg = window.HPOState?.constants?.HPO_MARK_SVG;
- if (!svg) return;
- document.querySelectorAll(".brand-mark").forEach((el) => {
- el.innerHTML = svg;
- });
-}
-
window.showEmptyState = showEmptyState;
window.hideEmptyState = hideEmptyState;
-window.initHpoMarks = initHpoMarks;
diff --git a/web/styles.css b/web/styles.css
index 325edd8..d7904d7 100644
--- a/web/styles.css
+++ b/web/styles.css
@@ -155,6 +155,7 @@
margin-bottom: 18px;
overflow: hidden;
width: 100%;
+ padding-left: 12px;
}
.brand-logo-group {
@@ -165,12 +166,11 @@
.brand-mark {
flex-shrink: 0;
- width: 3px;
- height: 20px;
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
background-color: var(--accent-color);
- border-radius: 0;
- transition: background-color 0.3s ease, transform 0.2s cubic-bezier(0.25, 1, 0.5, 1), box-shadow 0.2s ease;
- transform-origin: center;
+ transition: background-color 0.3s ease, opacity 0.3s ease;
}
.brand-mark.collision-flash {
@@ -484,7 +484,6 @@
.sidebar-footer {
margin-top: auto;
border-top: 1px solid var(--border-color);
- padding-top: 12px;
display: flex;
flex-direction: column;
gap: 0;
@@ -495,9 +494,10 @@
align-items: center;
gap: 10px;
width: 100%;
- padding: 8px 4px;
+ padding: 12px 12px;
background: none;
border: none;
+ border-bottom: 1px solid var(--border-color);
color: var(--text-muted);
font-family: var(--font-main);
font-size: 0.8rem;
@@ -506,6 +506,7 @@
letter-spacing: 0.05em;
cursor: pointer;
transition: color 0.2s ease;
+ line-height: 1;
}
.accent-toggle:hover {
@@ -514,7 +515,7 @@
.accent-preview {
width: 14px;
- height: 10px;
+ height: 12px;
border-radius: 0;
background: var(--accent-color);
border: 2px solid rgba(255, 255, 255, 0.25);
@@ -575,6 +576,7 @@
cursor: pointer;
padding: 5px 2px;
text-align: center;
+ line-height: 1;
transition: all 0.15s ease;
}
@@ -593,8 +595,6 @@
align-items: center;
gap: 10px;
padding: 12px 4px 4px;
- border-top: 1px solid var(--border-color);
- margin-top: 4px;
}
.worker-run-dot {
@@ -736,7 +736,6 @@
#dashboard-view > .dashboard-sidebar {
height: 100%;
min-height: 0;
- overflow: hidden;
}
/* Left column: trial table + pareto (original layout) */
@@ -919,11 +918,11 @@
}
.section-label {
- font-size: 0.68rem;
+ font-size: 0.75rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.06em;
- color: var(--text-muted);
+ color: #94a3b8;
margin: 0 0 10px 0;
}
@@ -1005,6 +1004,7 @@
/* Live Monitor Table Styling */
.trial-table {
width: 100%;
+ min-width: 100%;
border-collapse: collapse;
font-family: var(--font-mono);
font-size: 0.8rem;
@@ -1026,6 +1026,7 @@
.table-scroll-wrapper {
overflow: auto;
+ box-shadow: inset 0 -1px 0 var(--border-color), inset -1px 0 0 var(--border-color);
}
.card-content--table {
@@ -1153,7 +1154,7 @@
position: absolute;
top: 0;
right: 0;
- width: 4px;
+ width: 8px;
cursor: col-resize;
user-select: none;
height: 100%;
@@ -1276,7 +1277,7 @@
}
.trial-table .col-trial { width: 60px; min-width: 60px; }
- .trial-table .col-state { width: 100px; }
+ .trial-table .col-state { width: 120px; }
.trial-table .col-metric { width: 72px; }
.trial-table .col-score-fixed { width: 72px; }
.trial-table .col-lr { width: 64px; }
@@ -1395,6 +1396,7 @@
min-height: 0;
flex: 1;
background-color: transparent;
+ height: 100%;
}
.dashboard-sidebar > .card {
@@ -1840,13 +1842,13 @@
cursor: pointer;
transition: all 0.2s ease;
font-family: var(--font-mono);
+ line-height: 1;
}
.pill-btn.active {
background-color: rgba(6, 182, 212, 0.12);
border-color: var(--accent-color);
color: var(--accent-color);
- box-shadow: 0 0 10px var(--accent-glow);
}
.apply-btn {
@@ -2198,67 +2200,21 @@
}
.brand-mark.ping-watch {
- animation: brand-mark-ping-watch-anim 3s infinite ease-in-out;
+ animation: brand-mark-ping-watch-anim 2s infinite ease-in-out;
}
@keyframes brand-mark-ping-watch-anim {
- 0%, 100% {
- transform: scale(1);
- box-shadow: none;
- background-color: var(--accent-color);
- }
- 10% {
- transform: scale(1.2);
- box-shadow: 0 0 10px #ffffff;
- background-color: #ffffff;
- }
- 20% {
- transform: scale(1);
- box-shadow: none;
- background-color: var(--accent-color);
- }
- 30% {
- transform: scale(1.2);
- box-shadow: 0 0 10px #ffffff;
- background-color: #ffffff;
- }
- 40%, 90% {
- transform: scale(1);
- box-shadow: none;
- background-color: var(--accent-color);
- }
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.35; }
}
.brand-mark.ping-intervene {
- animation: brand-mark-ping-intervene-anim 3s infinite ease-in-out;
+ animation: brand-mark-ping-intervene-anim 2s infinite ease-in-out;
}
@keyframes brand-mark-ping-intervene-anim {
- 0%, 100% {
- transform: scale(1);
- box-shadow: none;
- background-color: var(--accent-color);
- }
- 10% {
- transform: scale(1.2);
- box-shadow: 0 0 10px #ff3b30;
- background-color: #ff3b30;
- }
- 20% {
- transform: scale(1);
- box-shadow: none;
- background-color: var(--accent-color);
- }
- 30% {
- transform: scale(1.2);
- box-shadow: 0 0 10px #ff3b30;
- background-color: #ff3b30;
- }
- 40%, 90% {
- transform: scale(1);
- box-shadow: none;
- background-color: var(--accent-color);
- }
+ 0%, 100% { opacity: 1; background-color: #ff3b30; }
+ 50% { opacity: 0.2; background-color: #ff3b30; }
}
.pending-banner {
@@ -2368,21 +2324,11 @@
.health-card {
flex: 0 0 auto;
transition: all 0.2s ease;
- border-left: 3px solid #10B981 !important;
- }
- .health-card.healthy {
- border-left-color: #10B981 !important;
- }
- .health-card.watch {
- border-left-color: #f59e0b !important;
- }
- .health-card.intervene {
- border-left-color: #ef4444 !important;
}
.status-orb {
- width: 16px;
- height: 6px;
- border-radius: 0;
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
display: inline-block;
transition: all 0.2s ease;
flex-shrink: 0;
@@ -2688,7 +2634,6 @@
.control-group {
display: flex;
align-items: center;
- justify-content: space-between;
gap: 12px;
padding: 10px 12px;
border-bottom: 1px solid var(--border-color);
@@ -2700,21 +2645,23 @@
}
.control-label {
- flex: 0 0 200px;
- margin-right: 24px;
+ flex: 0 0 180px;
+ margin-right: 12px;
font-weight: 500;
font-size: 0.85rem;
color: var(--text-color);
+ line-height: 1.2;
}
.eval-text-input {
width: 220px;
- padding: 4px 8px;
+ padding: 6px 8px;
font-family: var(--font-mono);
- font-size: 0.82rem;
+ font-size: 0.78rem;
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border-color);
border-radius: 0;
+ color: var(--text-color);
color: #fff;
}
@@ -2726,9 +2673,16 @@
.number-input--compact {
flex: 0 0 auto;
width: 110px;
- padding: 4px 8px;
+ padding: 6px 8px;
border-radius: 0;
- font-size: 0.82rem;
+ font-size: 0.78rem;
+ -moz-appearance: textfield;
+ }
+
+ .number-input--compact::-webkit-inner-spin-button,
+ .number-input--compact::-webkit-outer-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
}
#settings-tab-eval .hpo-toggle--eval .hpo-toggle-box {
@@ -2749,7 +2703,7 @@
.eval-form-section .section-label {
padding: 0 12px;
- margin-bottom: 0;
+ margin-bottom: 8px;
}
.eval-form-section .control-group:last-child {
@@ -2762,6 +2716,11 @@
margin-top: 14px;
}
+ .eval-form-actions .toolbar-btn {
+ font-size: 0.8rem;
+ padding: 8px 16px;
+ }
+
.page-header {
border-bottom: 1px solid var(--border-color);
padding: 14px 24px;
@@ -2830,3 +2789,16 @@
color: #000000;
}
+ /* Chart.js tooltip constraints */
+ #chartjs-tooltip {
+ max-width: 280px;
+ white-space: normal;
+ word-break: break-word;
+ border-radius: 6px;
+ pointer-events: none;
+ }
+
+ #chartjs-tooltip table {
+ margin: 0;
+ }
+