Progress pane: ramp/hold countdown with green digits; header ramp+hold rows

Step plates now show "Ramp h:mm:ss" (ramp time only) during ramp phase
and "Hold h:mm:ss" (hold remaining) during hold phase, both in the same
digit area in green (#4ade80). Inactive steps show planned ramp duration.

Header countdown gains a "hold" row alongside the existing "ramp" (renamed
from "step") and "total" rows. All three use the same h:mm:ss format.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SH2D4LRuCnhLSzoYerAfJX
This commit is contained in:
2026-06-28 12:30:23 +02:00
co-authored by Claude Sonnet 4.6
parent 6d481d7764
commit fd4cede19b
3 changed files with 33 additions and 9 deletions
+27 -6
View File
@@ -239,8 +239,16 @@ function updateStepPlates() {
endT = forecastFinalT; endT = forecastFinalT;
} }
if (startT !== undefined && endT !== undefined) { if (startT !== undefined && endT !== undefined) {
const total = Math.max(0, endT - startT); const stepTotal = Math.max(0, endT - startT);
plate.remaining.textContent = formatDuration(Math.max(0, Math.min(total, endT - currentElapsed))); const holdDurS = (sudSchedule[i] && sudSchedule[i].hold)
? (sudSchedule[i].hold.duration || 0) * 60 : 0;
const rampTotal = Math.max(0, stepTotal - holdDurS);
const rampVal = formatDuration(
Math.max(0, Math.min(rampTotal, endT - currentElapsed - holdDurS)));
// Active step in ramp phase gets the "ramp" prefix; hold branch
// overwrites remaining below with hold time instead.
plate.remaining.textContent =
(sudStepIndex === i && sudStepType === 'ramp') ? 'Ramp ' + rampVal : rampVal;
} else { } else {
plate.remaining.textContent = '-:--:--'; plate.remaining.textContent = '-:--:--';
} }
@@ -260,6 +268,11 @@ function updateStepPlates() {
plate.actual.textContent = `Actual ${plotTempIst !== null ? plotTempIst.toFixed(1) + ' °C' : '—'}`; plate.actual.textContent = `Actual ${plotTempIst !== null ? plotTempIst.toFixed(1) + ' °C' : '—'}`;
plate.stirrer.textContent = `Stirrer ${stirrerSpeedIst.toFixed(0)} rpm`; plate.stirrer.textContent = `Stirrer ${stirrerSpeedIst.toFixed(0)} rpm`;
plate.energy.textContent = `Energy ${energyCurrent.toFixed(0)} Wh`; plate.energy.textContent = `Energy ${energyCurrent.toFixed(0)} Wh`;
// Active step: ramp prefix when ramping; bare hold time when holding
// (reuses the same digit area). PAUSED/WAIT_USER keep the last phase.
if (sudStepType === 'hold') {
plate.remaining.textContent = 'Hold ' + formatDuration(Math.max(0, sudHoldRemaining));
}
} else if (i in stepActualFrozen) { } else if (i in stepActualFrozen) {
plate.actual.textContent = `Actual ${stepActualFrozen[i].toFixed(1)} °C`; plate.actual.textContent = `Actual ${stepActualFrozen[i].toFixed(1)} °C`;
plate.stirrer.textContent = `Stirrer ${plate.configuredSpeed.toFixed(0)} rpm (cfg)`; plate.stirrer.textContent = `Stirrer ${plate.configuredSpeed.toFixed(0)} rpm (cfg)`;
@@ -276,24 +289,32 @@ function updateStepPlates() {
function updateHeaderCountdown() { function updateHeaderCountdown() {
const currentElapsed = (elapsed !== null) ? elapsed : 0; const currentElapsed = (elapsed !== null) ? elapsed : 0;
const n = sudSchedule.length; const n = sudSchedule.length;
let stepText = '-:--:--'; let rampText = '-:--:--';
let totalText = '-:--:--'; let totalText = '-:--:--';
let visible = false; let visible = false;
if (n > 0 && sudStepIndex !== null) { if (n > 0 && sudStepIndex !== null) {
visible = true; visible = true;
const startT = forecastStepStarts[sudStepIndex];
let endT = forecastStepStarts[sudStepIndex + 1]; let endT = forecastStepStarts[sudStepIndex + 1];
if (endT === undefined && sudStepIndex === n - 1 && forecastFinished) { if (endT === undefined && sudStepIndex === n - 1 && forecastFinished) {
endT = forecastFinalT; endT = forecastFinalT;
} }
if (endT !== undefined) { if (startT !== undefined && endT !== undefined) {
stepText = formatDuration(Math.max(0, endT - currentElapsed)); const stepTotal = Math.max(0, endT - startT);
const step = sudSchedule[sudStepIndex];
const holdDurS = (step && step.hold) ? (step.hold.duration || 0) * 60 : 0;
const rampTotal = Math.max(0, stepTotal - holdDurS);
rampText = formatDuration(Math.max(0, Math.min(rampTotal, endT - currentElapsed - holdDurS)));
} }
if (forecastFinalT !== null && elapsed !== null) { if (forecastFinalT !== null && elapsed !== null) {
totalText = formatDuration(Math.max(0, forecastFinalT - currentElapsed)); totalText = formatDuration(Math.max(0, forecastFinalT - currentElapsed));
} }
} }
document.getElementById('hdr-step-remaining').textContent = stepText; const holdText = (sudStepType === 'hold' && sudHoldRemaining > 0)
? formatDuration(Math.max(0, sudHoldRemaining)) : '-:--:--';
document.getElementById('hdr-step-remaining').textContent = rampText;
document.getElementById('hdr-total-remaining').textContent = totalText; document.getElementById('hdr-total-remaining').textContent = totalText;
document.getElementById('hdr-hold-remaining').textContent = holdText;
document.getElementById('header-countdown').classList.toggle('hidden', !visible); document.getElementById('header-countdown').classList.toggle('hidden', !visible);
} }
+2 -1
View File
@@ -21,7 +21,8 @@
<button id="btn-sud-stop" class="transport-btn transport-stop" title="Stop">&#9632;</button> <button id="btn-sud-stop" class="transport-btn transport-stop" title="Stop">&#9632;</button>
<span id="header-countdown" class="hidden"> <span id="header-countdown" class="hidden">
<span class="hdr-cdown-row"><span class="hdr-cdown-lbl">total</span><span id="hdr-total-remaining">-:--:--</span></span> <span class="hdr-cdown-row"><span class="hdr-cdown-lbl">total</span><span id="hdr-total-remaining">-:--:--</span></span>
<span class="hdr-cdown-row"><span class="hdr-cdown-lbl">step</span><span id="hdr-step-remaining">-:--:--</span></span> <span class="hdr-cdown-row"><span class="hdr-cdown-lbl">ramp</span><span id="hdr-step-remaining">-:--:--</span></span>
<span class="hdr-cdown-row"><span class="hdr-cdown-lbl">hold</span><span id="hdr-hold-remaining">-:--:--</span></span>
</span> </span>
</span> </span>
</header> </header>
+4 -2
View File
@@ -106,13 +106,15 @@ header#connection-bar {
width: 2.8em; width: 2.8em;
} }
#hdr-step-remaining, #hdr-step-remaining,
#hdr-total-remaining { #hdr-total-remaining,
#hdr-hold-remaining {
font-family: 'Courier New', 'Lucida Console', monospace; font-family: 'Courier New', 'Lucida Console', monospace;
font-weight: bold; font-weight: bold;
font-size: 1.35em; font-size: 1.35em;
color: var(--accent); color: var(--accent);
} }
/* --- Transport buttons (circular, coloured) --- */ /* --- Transport buttons (circular, coloured) --- */
.transport-btn { .transport-btn {
@@ -327,7 +329,7 @@ header#connection-bar {
font-weight: bold; font-weight: bold;
text-align: right; text-align: right;
font-family: 'Courier New', monospace; font-family: 'Courier New', monospace;
color: var(--accent); color: #4ade80;
} }
.step-plate .field { .step-plate .field {
color: var(--text-muted); color: var(--text-muted);