Editor: dynamic input bounds + full hierarchy validation, layout polish
Bounds (new src/editor/bounds.js):
- Sets HTML5 min/max on every level + percent input each redraw,
derived from the current values of related inputs so the spinner
stops at the basin hierarchy:
0 < outflowLevel < dryRunLevel < startLevel ≤ inflowLevel
≤ shiftLevel ≤ maxLevel ≤ overflowLevel ≤ basinHeight
- dryRunPercent capped so dryRunLevel ≤ startLevel given current outflow.
- shiftArmPercent ∈ [1, 100]; highVolumeSafety% ∈ [1, 100].
Validation:
- New visible ribbon above the basin diagram (#ps-basin-validation)
listing every hierarchy violation. The in-SVG warning text is now a
small reminder ("⚠ N ordering issues").
- basin-diagram.js owns hierarchy issues; mode-preview.js trimmed to
only own shift-specific issues (shift > start, shift ≤ max,
shiftArmPercent range, shiftLevel required-when-enabled).
- oneditsave blocks Deploy on the union of _psBasinValidationIssues
and _psModeValidationIssues with a RED.notify listing all problems.
Layout polish:
- Side panel widened to 220 px with minmax(0, 1fr) first column so long
labels can no longer push the rows past the panel edge.
- Basin SVG max-width 380 → 360, gap between side panel and SVG bumped
14 → 28 px. Tank shifted right (x=145 width=110) so the inlet
"bottom of pipe" sub-label is no longer clipped on the left edge.
- "0 m (datum)" moved below the tank (y=395, centred) so it can't
collide with "Outlet / top of pipe" when outflowLevel is near floor.
- Zone labels shortened (Spare / Sewage + buffer / Buffer / Dead vol)
and only show when the bracketing thresholds are ≥ 28 px apart, so
they never sit on a threshold label.
- Mode preview axis labels under the chart removed — line colour +
side-panel labels + hover-couple already identify each line. Stub
<text> elements left hidden to keep the redraw loop simple. Arm-%
line + label trimmed in 10 px on the right so they're not clipped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:10:22 +02:00
|
|
|
// PumpingStation editor — dynamic input bounds.
|
|
|
|
|
// Sets HTML5 min/max attributes on every level and percent input based on
|
|
|
|
|
// the current values of related inputs, so the up/down arrows stop at
|
|
|
|
|
// values that respect the basin hierarchy:
|
|
|
|
|
//
|
|
|
|
|
// 0 < outflowLevel < dryRunLevel < startLevel ≤ inflowLevel
|
|
|
|
|
// ≤ shiftLevel ≤ maxLevel ≤ overflowLevel ≤ basinHeight
|
|
|
|
|
//
|
|
|
|
|
// The user can still type out-of-range values via the keyboard (HTML5
|
|
|
|
|
// min/max only constrain the spinner). The validation ribbons in
|
|
|
|
|
// basin-diagram.js and mode-preview.js catch typed violations and the
|
|
|
|
|
// oneditsave handler blocks Deploy until they're resolved.
|
|
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
|
const ns = window.PSEditor = window.PSEditor || {};
|
|
|
|
|
const fNum = (id) => ns.fNum(id);
|
|
|
|
|
const EPS = 0.001; // smallest meaningful step (mm-precision)
|
|
|
|
|
|
|
|
|
|
const setBounds = (id, min, max) => {
|
|
|
|
|
const el = document.getElementById(`node-input-${id}`);
|
|
|
|
|
if (!el) return;
|
|
|
|
|
if (Number.isFinite(min)) el.setAttribute('min', String(min));
|
|
|
|
|
else el.removeAttribute('min');
|
|
|
|
|
if (Number.isFinite(max)) el.setAttribute('max', String(max));
|
|
|
|
|
else el.removeAttribute('max');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ns.bounds = {
|
|
|
|
|
apply() {
|
|
|
|
|
const basinHeight = fNum('basinHeight');
|
|
|
|
|
const outflow = fNum('outflowLevel');
|
|
|
|
|
const dryPct = fNum('dryRunThresholdPercent');
|
|
|
|
|
const start = fNum('startLevel');
|
|
|
|
|
const inlet = fNum('inflowLevel');
|
|
|
|
|
const max = fNum('maxLevel');
|
|
|
|
|
const overflow = fNum('overflowLevel');
|
|
|
|
|
const shiftEnabled = !!document.getElementById('node-input-enableShiftedRamp')?.checked;
|
|
|
|
|
|
|
|
|
|
// Derived dryRunLevel (lower bound for startLevel).
|
|
|
|
|
const dryRun = (Number.isFinite(outflow) && Number.isFinite(dryPct))
|
|
|
|
|
? outflow * (1 + dryPct / 100) : null;
|
|
|
|
|
|
|
|
|
|
// Geometry — basin envelope.
|
|
|
|
|
setBounds('basinHeight', EPS, undefined);
|
|
|
|
|
setBounds('basinVolume', EPS, undefined);
|
|
|
|
|
|
|
|
|
|
// Levels (each capped by the next-higher level if defined).
|
|
|
|
|
setBounds('outflowLevel', EPS,
|
|
|
|
|
Number.isFinite(start) && Number.isFinite(dryPct)
|
|
|
|
|
? start / (1 + dryPct / 100) - EPS // keep dryRun < start
|
|
|
|
|
: (start ?? inlet ?? max ?? overflow ?? basinHeight));
|
|
|
|
|
|
|
|
|
|
setBounds('startLevel',
|
|
|
|
|
Number.isFinite(dryRun) ? dryRun + EPS : EPS,
|
|
|
|
|
inlet ?? max ?? overflow ?? basinHeight);
|
|
|
|
|
|
|
|
|
|
setBounds('inflowLevel',
|
|
|
|
|
start ?? EPS,
|
|
|
|
|
max ?? overflow ?? basinHeight);
|
|
|
|
|
|
|
|
|
|
setBounds('maxLevel',
|
|
|
|
|
inlet ?? start ?? EPS,
|
|
|
|
|
overflow ?? basinHeight);
|
|
|
|
|
|
|
|
|
|
setBounds('overflowLevel',
|
|
|
|
|
max ?? inlet ?? start ?? EPS,
|
|
|
|
|
basinHeight);
|
|
|
|
|
|
2026-05-08 11:20:36 +02:00
|
|
|
// stopLevel — explicit pump-off threshold. Must sit between
|
|
|
|
|
// dryRunLevel and startLevel (so it can be reached during draining
|
|
|
|
|
// before pumps re-engage).
|
|
|
|
|
setBounds('stopLevel',
|
|
|
|
|
Number.isFinite(dryRun) ? dryRun + EPS : EPS,
|
|
|
|
|
start ?? inlet ?? max ?? overflow ?? basinHeight);
|
|
|
|
|
|
Editor: dynamic input bounds + full hierarchy validation, layout polish
Bounds (new src/editor/bounds.js):
- Sets HTML5 min/max on every level + percent input each redraw,
derived from the current values of related inputs so the spinner
stops at the basin hierarchy:
0 < outflowLevel < dryRunLevel < startLevel ≤ inflowLevel
≤ shiftLevel ≤ maxLevel ≤ overflowLevel ≤ basinHeight
- dryRunPercent capped so dryRunLevel ≤ startLevel given current outflow.
- shiftArmPercent ∈ [1, 100]; highVolumeSafety% ∈ [1, 100].
Validation:
- New visible ribbon above the basin diagram (#ps-basin-validation)
listing every hierarchy violation. The in-SVG warning text is now a
small reminder ("⚠ N ordering issues").
- basin-diagram.js owns hierarchy issues; mode-preview.js trimmed to
only own shift-specific issues (shift > start, shift ≤ max,
shiftArmPercent range, shiftLevel required-when-enabled).
- oneditsave blocks Deploy on the union of _psBasinValidationIssues
and _psModeValidationIssues with a RED.notify listing all problems.
Layout polish:
- Side panel widened to 220 px with minmax(0, 1fr) first column so long
labels can no longer push the rows past the panel edge.
- Basin SVG max-width 380 → 360, gap between side panel and SVG bumped
14 → 28 px. Tank shifted right (x=145 width=110) so the inlet
"bottom of pipe" sub-label is no longer clipped on the left edge.
- "0 m (datum)" moved below the tank (y=395, centred) so it can't
collide with "Outlet / top of pipe" when outflowLevel is near floor.
- Zone labels shortened (Spare / Sewage + buffer / Buffer / Dead vol)
and only show when the bracketing thresholds are ≥ 28 px apart, so
they never sit on a threshold label.
- Mode preview axis labels under the chart removed — line colour +
side-panel labels + hover-couple already identify each line. Stub
<text> elements left hidden to keep the redraw loop simple. Arm-%
line + label trimmed in 10 px on the right so they're not clipped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:10:22 +02:00
|
|
|
// Shift inputs (only relevant when shifted ramp enabled).
|
|
|
|
|
if (shiftEnabled) {
|
|
|
|
|
setBounds('shiftLevel',
|
|
|
|
|
Number.isFinite(start) ? start : EPS,
|
|
|
|
|
max ?? overflow ?? basinHeight);
|
|
|
|
|
setBounds('shiftArmPercent', 1, 100);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Percentages.
|
|
|
|
|
// dryRun% capped so dryRunLevel ≤ startLevel.
|
|
|
|
|
let dryMax = 99;
|
|
|
|
|
if (Number.isFinite(start) && Number.isFinite(outflow) && outflow > 0) {
|
|
|
|
|
dryMax = Math.max(0, Math.min(99, ((start / outflow) - 1) * 100));
|
|
|
|
|
}
|
|
|
|
|
setBounds('dryRunThresholdPercent', 0, dryMax);
|
|
|
|
|
|
|
|
|
|
// highVol% bounded (1, 100). Equal to 100 means no margin to overflow.
|
|
|
|
|
setBounds('highVolumeSafetyThresholdPercent', 1, 100);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
})();
|