Stabilize GUI status metric row

This commit is contained in:
Meta Vens 2026-04-25 03:35:02 +01:00
commit 69a3d1f385
3 changed files with 198 additions and 144 deletions

View file

@ -417,6 +417,37 @@ function formatNumber(value: number | null, digits = 3): string {
return value.toFixed(digits);
}
type StatusMetricItem = {
label: string;
value: string;
};
function formatGridValue(width: number | null | undefined, height: number | null | undefined, fallback = "pending"): string {
if (!width || !height) {
return fallback;
}
return `${width} x ${height}`;
}
function formatPhaseValue(phaseX: number | null | undefined, phaseY: number | null | undefined, fallback = "pending"): string {
if (phaseX === null || phaseX === undefined || phaseY === null || phaseY === undefined) {
return fallback;
}
return `${formatNumber(phaseX, 2)}, ${formatNumber(phaseY, 2)}`;
}
function renderStatusMetricItems(items: StatusMetricItem[]): void {
statusMetrics.innerHTML = "";
statusMetrics.style.setProperty("--status-metric-count", String(Math.max(items.length, 1)));
for (const { label, value } of items) {
const node = document.createElement("div");
node.className = "status-metric";
node.title = `${label}: ${value}`;
node.innerHTML = `<strong>${label}</strong><span>${value}</span>`;
statusMetrics.appendChild(node);
}
}
function getSelectedFrame(): FramePayload | null {
if (state.frames.length === 0) {
return null;
@ -460,87 +491,85 @@ function renderInference(): void {
}
function renderStatusMetrics(): void {
statusMetrics.innerHTML = "";
statusMetrics.classList.toggle("status-metrics-fixed", state.stageKey === "solver");
const items: Array<[string, string]> = [];
const items: StatusMetricItem[] = [];
const frame = getSelectedFrame();
if (state.stageKey === "inference") {
if (state.latticeSearch) {
items.push(["Sizes", `${state.latticeSearch.completedCandidates} / ${state.latticeSearch.candidateCount}`]);
items.push(["Phase samples", String(state.latticeSearch.phaseSampleCount)]);
if (state.latticeSearch.currentTargetWidth && state.latticeSearch.currentTargetHeight) {
items.push(["Current grid", `${state.latticeSearch.currentTargetWidth} x ${state.latticeSearch.currentTargetHeight}`]);
}
if (state.latticeSearch.device) {
items.push(["Device", state.latticeSearch.device]);
}
if (state.latticeSearch.bestScore !== null) {
items.push(["Best score", formatNumber(state.latticeSearch.bestScore, 3)]);
}
items.push({ label: "Sizes", value: `${state.latticeSearch.completedCandidates} / ${state.latticeSearch.candidateCount}` });
items.push({ label: "Phases", value: String(state.latticeSearch.phaseSampleCount) });
items.push({
label: "Grid",
value: formatGridValue(state.latticeSearch.currentTargetWidth, state.latticeSearch.currentTargetHeight),
});
items.push({ label: "Device", value: state.latticeSearch.device ?? "pending" });
items.push({ label: "Best", value: formatNumber(state.latticeSearch.bestScore, 3) });
}
} else if (state.stageKey === "analysis" || state.stageKey === "selection") {
if (state.inference) {
items.push(["Mode", state.inferenceMode === "fixed" ? "pinned" : state.inferenceMode ?? "search"]);
items.push(["Grid", `${state.inference.target_width} x ${state.inference.target_height}`]);
items.push(["Phase", `${formatNumber(state.inference.phase_x, 2)}, ${formatNumber(state.inference.phase_y, 2)}`]);
items.push(["Confidence", formatNumber(state.inference.confidence, 3)]);
items.push({ label: "Mode", value: state.inferenceMode === "fixed" ? "pinned" : state.inferenceMode ?? "search" });
items.push({ label: "Grid", value: `${state.inference.target_width} x ${state.inference.target_height}` });
items.push({ label: "Phase", value: `${formatNumber(state.inference.phase_x, 2)}, ${formatNumber(state.inference.phase_y, 2)}` });
items.push({ label: "Confidence", value: formatNumber(state.inference.confidence, 3) });
}
} else if (state.stageKey === "rerank") {
if (state.phaseRerank) {
items.push(["Candidates", `${state.phaseRerank.completedCandidates} / ${state.phaseRerank.candidateCount}`]);
if (state.phaseRerank.currentCandidateIndex > 0) {
items.push(["Active candidate", `${state.phaseRerank.currentCandidateIndex} / ${state.phaseRerank.candidateCount}`]);
}
items.push([
"Preview step",
state.phaseRerank.currentTotalSteps > 0
? `${state.phaseRerank.currentStep} / ${state.phaseRerank.currentTotalSteps}`
: String(state.phaseRerank.previewSteps),
]);
if (state.phaseRerank.currentTargetWidth && state.phaseRerank.currentTargetHeight) {
items.push(["Grid", `${state.phaseRerank.currentTargetWidth} x ${state.phaseRerank.currentTargetHeight}`]);
}
if (state.phaseRerank.currentPhaseX !== null && state.phaseRerank.currentPhaseY !== null) {
items.push(["Phase", `${formatNumber(state.phaseRerank.currentPhaseX, 2)}, ${formatNumber(state.phaseRerank.currentPhaseY, 2)}`]);
}
if (state.phaseRerank.currentLoss !== null) {
items.push(["Loss", formatNumber(state.phaseRerank.currentLoss, 4)]);
}
items.push(["Confidence", formatNumber(state.phaseRerank.confidence, 3)]);
items.push({
label: "Done",
value: `${state.phaseRerank.completedCandidates} / ${state.phaseRerank.candidateCount}`,
});
items.push({
label: "Candidate",
value:
state.phaseRerank.currentCandidateIndex > 0
? `${state.phaseRerank.currentCandidateIndex} / ${state.phaseRerank.candidateCount}`
: `0 / ${state.phaseRerank.candidateCount}`,
});
items.push({
label: "Preview",
value:
state.phaseRerank.currentTotalSteps > 0
? `${state.phaseRerank.currentStep} / ${state.phaseRerank.currentTotalSteps}`
: state.phaseRerank.previewSteps > 0
? `0 / ${state.phaseRerank.previewSteps}`
: "skipped",
});
items.push({
label: "Grid",
value: formatGridValue(state.phaseRerank.currentTargetWidth, state.phaseRerank.currentTargetHeight),
});
items.push({
label: "Phase",
value: formatPhaseValue(state.phaseRerank.currentPhaseX, state.phaseRerank.currentPhaseY),
});
items.push({ label: "Loss", value: formatNumber(state.phaseRerank.currentLoss, 4) });
items.push({ label: "Confidence", value: formatNumber(state.phaseRerank.confidence, 3) });
}
} else if (state.stageKey === "solver") {
const solverTerms: Array<[string, number | null]> = [
["Coherence", frame?.terms.local_coherence ?? null],
["Edge", frame?.terms.local_edge ?? null],
["Smoothness", frame?.terms.smoothness ?? null],
["Collapse", frame?.terms.collapse ?? null],
["Magnitude", frame?.terms.magnitude ?? null],
const solverTerms: StatusMetricItem[] = [
{ label: "Coherence", value: formatNumber(frame?.terms.local_coherence ?? null, 4) },
{ label: "Edge", value: formatNumber(frame?.terms.local_edge ?? null, 4) },
{ label: "Smoothness", value: formatNumber(frame?.terms.smoothness ?? null, 4) },
{ label: "Collapse", value: formatNumber(frame?.terms.collapse ?? null, 4) },
{ label: "Magnitude", value: formatNumber(frame?.terms.magnitude ?? null, 4) },
];
items.push([
"Grid",
state.phaseFieldPrep ? `${state.phaseFieldPrep.targetWidth} x ${state.phaseFieldPrep.targetHeight}` : "pending",
]);
items.push([
"Step",
frame ? `${frame.step} / ${frame.totalSteps}` : `0 / ${Math.max(0, state.solverStepBudget)}`,
]);
items.push([
"Cell pitch",
state.phaseFieldPrep
? `${formatNumber(state.phaseFieldPrep.cellX, 1)} x ${formatNumber(state.phaseFieldPrep.cellY, 1)} px`
items.push({
label: "Grid",
value: state.phaseFieldPrep ? `${state.phaseFieldPrep.targetWidth} x ${state.phaseFieldPrep.targetHeight}` : "pending",
});
items.push({
label: "Step",
value: frame ? `${frame.step} / ${frame.totalSteps}` : `0 / ${Math.max(0, state.solverStepBudget)}`,
});
items.push({
label: "Cell pitch",
value: state.phaseFieldPrep
? `${formatNumber(state.phaseFieldPrep.cellX, 1)}x${formatNumber(state.phaseFieldPrep.cellY, 1)} px`
: "pending",
]);
items.push(["Loss", frame?.loss === null || frame?.loss === undefined ? "n/a" : formatNumber(frame.loss, 4)]);
for (const [label, value] of solverTerms) {
items.push([label, formatNumber(value, 4)]);
}
}
for (const [label, value] of items) {
const node = document.createElement("div");
node.className = "status-metric";
node.innerHTML = `<strong>${label}</strong><span>${value}</span>`;
statusMetrics.appendChild(node);
});
items.push({ label: "Loss", value: frame?.loss === null || frame?.loss === undefined ? "n/a" : formatNumber(frame.loss, 4) });
items.push(...solverTerms);
}
renderStatusMetricItems(items);
}
function renderSummary(): void {

View file

@ -282,6 +282,29 @@ function formatNumber(value, digits = 3) {
}
return value.toFixed(digits);
}
function formatGridValue(width, height, fallback = "pending") {
if (!width || !height) {
return fallback;
}
return `${width} x ${height}`;
}
function formatPhaseValue(phaseX, phaseY, fallback = "pending") {
if (phaseX === null || phaseX === undefined || phaseY === null || phaseY === undefined) {
return fallback;
}
return `${formatNumber(phaseX, 2)}, ${formatNumber(phaseY, 2)}`;
}
function renderStatusMetricItems(items) {
statusMetrics.innerHTML = "";
statusMetrics.style.setProperty("--status-metric-count", String(Math.max(items.length, 1)));
for (const { label, value } of items) {
const node = document.createElement("div");
node.className = "status-metric";
node.title = `${label}: ${value}`;
node.innerHTML = `<strong>${label}</strong><span>${value}</span>`;
statusMetrics.appendChild(node);
}
}
function getSelectedFrame() {
if (state.frames.length === 0) {
return null;
@ -323,90 +346,86 @@ function renderInference() {
}
}
function renderStatusMetrics() {
statusMetrics.innerHTML = "";
statusMetrics.classList.toggle("status-metrics-fixed", state.stageKey === "solver");
const items = [];
const frame = getSelectedFrame();
if (state.stageKey === "inference") {
if (state.latticeSearch) {
items.push(["Sizes", `${state.latticeSearch.completedCandidates} / ${state.latticeSearch.candidateCount}`]);
items.push(["Phase samples", String(state.latticeSearch.phaseSampleCount)]);
if (state.latticeSearch.currentTargetWidth && state.latticeSearch.currentTargetHeight) {
items.push(["Current grid", `${state.latticeSearch.currentTargetWidth} x ${state.latticeSearch.currentTargetHeight}`]);
}
if (state.latticeSearch.device) {
items.push(["Device", state.latticeSearch.device]);
}
if (state.latticeSearch.bestScore !== null) {
items.push(["Best score", formatNumber(state.latticeSearch.bestScore, 3)]);
}
items.push({ label: "Sizes", value: `${state.latticeSearch.completedCandidates} / ${state.latticeSearch.candidateCount}` });
items.push({ label: "Phases", value: String(state.latticeSearch.phaseSampleCount) });
items.push({
label: "Grid",
value: formatGridValue(state.latticeSearch.currentTargetWidth, state.latticeSearch.currentTargetHeight),
});
items.push({ label: "Device", value: state.latticeSearch.device ?? "pending" });
items.push({ label: "Best", value: formatNumber(state.latticeSearch.bestScore, 3) });
}
}
else if (state.stageKey === "analysis" || state.stageKey === "selection") {
if (state.inference) {
items.push(["Mode", state.inferenceMode === "fixed" ? "pinned" : state.inferenceMode ?? "search"]);
items.push(["Grid", `${state.inference.target_width} x ${state.inference.target_height}`]);
items.push(["Phase", `${formatNumber(state.inference.phase_x, 2)}, ${formatNumber(state.inference.phase_y, 2)}`]);
items.push(["Confidence", formatNumber(state.inference.confidence, 3)]);
items.push({ label: "Mode", value: state.inferenceMode === "fixed" ? "pinned" : state.inferenceMode ?? "search" });
items.push({ label: "Grid", value: `${state.inference.target_width} x ${state.inference.target_height}` });
items.push({ label: "Phase", value: `${formatNumber(state.inference.phase_x, 2)}, ${formatNumber(state.inference.phase_y, 2)}` });
items.push({ label: "Confidence", value: formatNumber(state.inference.confidence, 3) });
}
}
else if (state.stageKey === "rerank") {
if (state.phaseRerank) {
items.push(["Candidates", `${state.phaseRerank.completedCandidates} / ${state.phaseRerank.candidateCount}`]);
if (state.phaseRerank.currentCandidateIndex > 0) {
items.push(["Active candidate", `${state.phaseRerank.currentCandidateIndex} / ${state.phaseRerank.candidateCount}`]);
}
items.push([
"Preview step",
state.phaseRerank.currentTotalSteps > 0
items.push({
label: "Done",
value: `${state.phaseRerank.completedCandidates} / ${state.phaseRerank.candidateCount}`,
});
items.push({
label: "Candidate",
value: state.phaseRerank.currentCandidateIndex > 0
? `${state.phaseRerank.currentCandidateIndex} / ${state.phaseRerank.candidateCount}`
: `0 / ${state.phaseRerank.candidateCount}`,
});
items.push({
label: "Preview",
value: state.phaseRerank.currentTotalSteps > 0
? `${state.phaseRerank.currentStep} / ${state.phaseRerank.currentTotalSteps}`
: String(state.phaseRerank.previewSteps),
]);
if (state.phaseRerank.currentTargetWidth && state.phaseRerank.currentTargetHeight) {
items.push(["Grid", `${state.phaseRerank.currentTargetWidth} x ${state.phaseRerank.currentTargetHeight}`]);
}
if (state.phaseRerank.currentPhaseX !== null && state.phaseRerank.currentPhaseY !== null) {
items.push(["Phase", `${formatNumber(state.phaseRerank.currentPhaseX, 2)}, ${formatNumber(state.phaseRerank.currentPhaseY, 2)}`]);
}
if (state.phaseRerank.currentLoss !== null) {
items.push(["Loss", formatNumber(state.phaseRerank.currentLoss, 4)]);
}
items.push(["Confidence", formatNumber(state.phaseRerank.confidence, 3)]);
: state.phaseRerank.previewSteps > 0
? `0 / ${state.phaseRerank.previewSteps}`
: "skipped",
});
items.push({
label: "Grid",
value: formatGridValue(state.phaseRerank.currentTargetWidth, state.phaseRerank.currentTargetHeight),
});
items.push({
label: "Phase",
value: formatPhaseValue(state.phaseRerank.currentPhaseX, state.phaseRerank.currentPhaseY),
});
items.push({ label: "Loss", value: formatNumber(state.phaseRerank.currentLoss, 4) });
items.push({ label: "Confidence", value: formatNumber(state.phaseRerank.confidence, 3) });
}
}
else if (state.stageKey === "solver") {
const solverTerms = [
["Coherence", frame?.terms.local_coherence ?? null],
["Edge", frame?.terms.local_edge ?? null],
["Smoothness", frame?.terms.smoothness ?? null],
["Collapse", frame?.terms.collapse ?? null],
["Magnitude", frame?.terms.magnitude ?? null],
{ label: "Coherence", value: formatNumber(frame?.terms.local_coherence ?? null, 4) },
{ label: "Edge", value: formatNumber(frame?.terms.local_edge ?? null, 4) },
{ label: "Smoothness", value: formatNumber(frame?.terms.smoothness ?? null, 4) },
{ label: "Collapse", value: formatNumber(frame?.terms.collapse ?? null, 4) },
{ label: "Magnitude", value: formatNumber(frame?.terms.magnitude ?? null, 4) },
];
items.push([
"Grid",
state.phaseFieldPrep ? `${state.phaseFieldPrep.targetWidth} x ${state.phaseFieldPrep.targetHeight}` : "pending",
]);
items.push([
"Step",
frame ? `${frame.step} / ${frame.totalSteps}` : `0 / ${Math.max(0, state.solverStepBudget)}`,
]);
items.push([
"Cell pitch",
state.phaseFieldPrep
? `${formatNumber(state.phaseFieldPrep.cellX, 1)} x ${formatNumber(state.phaseFieldPrep.cellY, 1)} px`
items.push({
label: "Grid",
value: state.phaseFieldPrep ? `${state.phaseFieldPrep.targetWidth} x ${state.phaseFieldPrep.targetHeight}` : "pending",
});
items.push({
label: "Step",
value: frame ? `${frame.step} / ${frame.totalSteps}` : `0 / ${Math.max(0, state.solverStepBudget)}`,
});
items.push({
label: "Cell pitch",
value: state.phaseFieldPrep
? `${formatNumber(state.phaseFieldPrep.cellX, 1)}x${formatNumber(state.phaseFieldPrep.cellY, 1)} px`
: "pending",
]);
items.push(["Loss", frame?.loss === null || frame?.loss === undefined ? "n/a" : formatNumber(frame.loss, 4)]);
for (const [label, value] of solverTerms) {
items.push([label, formatNumber(value, 4)]);
}
}
for (const [label, value] of items) {
const node = document.createElement("div");
node.className = "status-metric";
node.innerHTML = `<strong>${label}</strong><span>${value}</span>`;
statusMetrics.appendChild(node);
});
items.push({ label: "Loss", value: frame?.loss === null || frame?.loss === undefined ? "n/a" : formatNumber(frame.loss, 4) });
items.push(...solverTerms);
}
renderStatusMetricItems(items);
}
function renderSummary() {
summaryPanel.innerHTML = "";

View file

@ -732,22 +732,27 @@ p {
}
.status-metrics {
--status-metric-count: 1;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
grid-template-columns: repeat(var(--status-metric-count), minmax(96px, 1fr));
grid-auto-rows: minmax(0, 1fr);
gap: 10px;
}
.status-metrics.status-metrics-fixed {
grid-template-columns: repeat(3, minmax(0, 1fr));
overflow-x: auto;
overscroll-behavior-x: contain;
scrollbar-width: thin;
}
.status-metric {
min-width: 0;
width: 100%;
min-height: 72px;
box-sizing: border-box;
padding: 8px 10px;
border: 2px solid rgba(8, 36, 62, 0.88);
background: linear-gradient(180deg, rgba(9, 27, 44, 0.96), rgba(5, 16, 28, 0.95));
display: grid;
grid-template-rows: auto minmax(0, 1fr);
align-content: start;
}
.status-metric strong {
@ -756,6 +761,9 @@ p {
font-size: 0.58rem;
color: var(--ui-accent);
text-transform: uppercase;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.status-metric span {
@ -763,8 +771,10 @@ p {
font-size: 1rem;
line-height: 1.12;
color: var(--ui-text);
overflow-wrap: anywhere;
word-break: break-word;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-variant-numeric: tabular-nums;
}
@keyframes status-sweep {
@ -950,10 +960,6 @@ select:focus-visible {
grid-template-columns: 1fr;
}
.status-metrics.status-metrics-fixed {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.card-head {
flex-wrap: wrap;
}
@ -1003,7 +1009,7 @@ select:focus-visible {
width: 100%;
}
.status-metrics.status-metrics-fixed {
grid-template-columns: 1fr;
.status-metrics {
grid-template-columns: repeat(var(--status-metric-count), minmax(88px, 1fr));
}
}