diff --git a/frontend/src/app.ts b/frontend/src/app.ts index a527fd9..d68a939 100644 --- a/frontend/src/app.ts +++ b/frontend/src/app.ts @@ -458,6 +458,7 @@ function renderInference(): void { function renderStatusMetrics(): void { statusMetrics.innerHTML = ""; + statusMetrics.classList.toggle("status-metrics-fixed", state.stageKey === "solver"); const items: Array<[string, string]> = []; const frame = getSelectedFrame(); if (state.stageKey === "inference") { @@ -505,15 +506,30 @@ function renderStatusMetrics(): void { items.push(["Confidence", formatNumber(state.phaseRerank.confidence, 3)]); } } else if (state.stageKey === "solver") { - if (state.phaseFieldPrep) { - items.push(["Grid", `${state.phaseFieldPrep.targetWidth} x ${state.phaseFieldPrep.targetHeight}`]); - items.push(["Cell pitch", `${formatNumber(state.phaseFieldPrep.cellX, 1)} x ${formatNumber(state.phaseFieldPrep.cellY, 1)} px`]); - } - if (frame) { - items.push(["Loss", frame.loss === null ? "n/a" : formatNumber(frame.loss, 4)]); - for (const [key, value] of Object.entries(frame.terms).slice(0, 4)) { - items.push([key.replaceAll("_", " "), formatNumber(value, 4)]); - } + 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], + ]; + 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` + : "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) { diff --git a/src/repixelizer/gui_static/app.js b/src/repixelizer/gui_static/app.js index 96b3bc5..c18d4c9 100644 --- a/src/repixelizer/gui_static/app.js +++ b/src/repixelizer/gui_static/app.js @@ -322,6 +322,7 @@ function renderInference() { } function renderStatusMetrics() { statusMetrics.innerHTML = ""; + statusMetrics.classList.toggle("status-metrics-fixed", state.stageKey === "solver"); const items = []; const frame = getSelectedFrame(); if (state.stageKey === "inference") { @@ -372,15 +373,30 @@ function renderStatusMetrics() { } } else if (state.stageKey === "solver") { - if (state.phaseFieldPrep) { - items.push(["Grid", `${state.phaseFieldPrep.targetWidth} x ${state.phaseFieldPrep.targetHeight}`]); - items.push(["Cell pitch", `${formatNumber(state.phaseFieldPrep.cellX, 1)} x ${formatNumber(state.phaseFieldPrep.cellY, 1)} px`]); - } - if (frame) { - items.push(["Loss", frame.loss === null ? "n/a" : formatNumber(frame.loss, 4)]); - for (const [key, value] of Object.entries(frame.terms).slice(0, 4)) { - items.push([key.replaceAll("_", " "), formatNumber(value, 4)]); - } + 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], + ]; + 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` + : "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) { diff --git a/src/repixelizer/gui_static/styles.css b/src/repixelizer/gui_static/styles.css index 86e8358..6373ff9 100644 --- a/src/repixelizer/gui_static/styles.css +++ b/src/repixelizer/gui_static/styles.css @@ -201,6 +201,15 @@ p { gap: 10px; } +.sidebar, +.section-block, +.summary-panel, +.candidate-list, +.event-log, +.info-stack { + min-width: 0; +} + :is(.hero, .card, .metric-chip, .summary-card, .app-shell-header, .viz-card, .loss-canvas, .editor-toolbar, .editor-surface, .dropzone, .field input, .field select, .button, .toggle-row) { position: relative; overflow: hidden; @@ -728,8 +737,14 @@ p { gap: 10px; } +.status-metrics.status-metrics-fixed { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + .status-metric { min-width: 0; + width: 100%; + 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)); @@ -748,6 +763,8 @@ p { font-size: 1rem; line-height: 1.12; color: var(--ui-text); + overflow-wrap: anywhere; + word-break: break-word; } @keyframes status-sweep { @@ -764,6 +781,9 @@ p { .event-item, .metric-chip, .summary-card { + min-width: 0; + width: 100%; + box-sizing: border-box; border: 3px solid rgba(8, 36, 62, 0.88); background: linear-gradient(180deg, rgba(11, 32, 52, 0.96), rgba(6, 19, 35, 0.94)); @@ -781,6 +801,17 @@ p { font-size: 0.72rem; color: var(--ui-accent); text-transform: uppercase; + overflow-wrap: anywhere; + word-break: break-word; +} + +.candidate-card span, +.event-item span, +.summary-card span { + display: block; + min-width: 0; + overflow-wrap: anywhere; + word-break: break-word; } .editor-actions { @@ -919,6 +950,10 @@ 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; } @@ -967,4 +1002,8 @@ select:focus-visible { .button { width: 100%; } + + .status-metrics.status-metrics-fixed { + grid-template-columns: 1fr; + } }