◐ Off-By-One · answer catalog

static-html-js-math-and-ux-fixes

1 answer(s)godocker

static-html-js-math-and-ux-fixes

📦 Source in repository (JSON)

Answer

File layout (single-file tool + PWA assets + harness):

cluster-estimator.html   # the tool (all 4 fixes)
manifest.json            # PWA manifest (fix #4)
sw.js                    # service worker precache (fix #4)
icon-192.png / icon-512.png  # generated by gen_icons.py (fix #4)
gen_icons.py             # pure-stdlib PNG generator
test.js                  # jsdom regression harness (4 groups / 13 checks)

Fix #1 — TP × PP floor on gpusNeeded (main + pane)

gpusNeeded is now Math.max(VRAM-fit ceil, tp * pp), so a TP=8 config can never report fewer than 8 GPUs. The same core path serves recalculate() and paneRecalculate():

function recalculate(kind) {
  kind = kind || 'main';
  var cfg = readConfig(kind);
  var tp = Math.max(1, Math.floor(Number(cfg.tpSize) || 1));   // fix #3 belt-and-suspenders
  var pp = Math.max(1, Math.floor(Number(cfg.ppSize) || 1));
  var perGpu = Math.max(1, Number(cfg.perGpuVRAM) || 1);
  var vramFit = Math.max(1, Math.ceil(estimateVRAMGB(cfg) / perGpu));
  var gpusNeeded = Math.max(vramFit, tp * pp);                  // <-- TP×PP floor
  last[kind] = { gpusNeeded, vramFit, tp, pp };
  renderResults(kind, cfg, gpusNeeded, vramFit, tp, pp);
  return gpusNeeded;
}
function paneRecalculate() { return recalculate('pane'); }     // same floor, pane pane

Fix #2 — cloud-pricing fallback is no longer silent

lookupPrice returns a source; spec-list-price rows get an amber warning banner plus a per-row spec* marker in both the main and pane tables:

function lookupPrice(gpuKey) {
  var cloud = CLOUD_PRICING[gpuKey];
  if (cloud) return { price: cloud.price_hour, source: 'cloud' };
  var spec = GPU_SPECS[gpuKey];
  if (spec && Number.isFinite(spec.price_hour)) return { price: spec.price_hour, source: 'spec' };
  return { price: null, source: 'none' };
}
// inside renderResults(kind, ...) — shared by main + pane:
var isFallback = hit.source === 'spec';
if (isFallback) anyFallback = true;
var priceCell = hit.price != null
  ? '$' + hit.price.toFixed(2) +
    (isFallback ? '<span class="spec-price-marker" title="No cloud pricing — spec list price">spec*</span>' : '')
  : '&mdash;';
...
var warn = document.getElementById('cloudFallbackWarn');   // shared banner
if (warn) warn.hidden = !anyFallback;
<div id="cloudFallbackWarn" class="amber-warn" hidden>
  &#9888; Some listed GPUs have no live cloud pricing &mdash; showing spec list price
  (marked <span class="spec-price-marker">spec*</span>).
</div>

Fix #3 — input clamping (imported JSON with tpSize:0 renders no NaN/Infinity)

setConfig/paneSetConfig clamp numerics: non-finite → min (or 1 if no min), sub-min → min, in-range values preserved verbatim. recalculate independently clamps TP/PP ≥ 1, so even a DOM-tampered input can't produce NaN. The key pitfall respected: never clamp to the input max attribute — the DeepSeek preset's headDim: 576 legitimately exceeds max="256", and presets are authoritative:

function clampNum(value, spec) {
  var min = (typeof spec.min === 'number' && spec.min > 0) ? spec.min : 1;
  var n = Number(value);
  if (!Number.isFinite(n)) return min;          // non-finite -> min-or-1
  return Math.max(min, n);                       // up-to-min only; no max clamp
}
function setConfig(raw, kind) {
  kind = kind || 'main';
  var merged = clampConfig(Object.assign({}, readConfig(kind), raw || {}), kind);
  writeConfig(merged, kind);
  return merged;
}

Fix #4 — PWA icons + precache

gen_icons.py builds 192×192 and 512×512 RGBA PNGs with stdlib only (struct + zlib + CRC32), so no Pillow is required:

def _chunk(tag: bytes, data: bytes) -> bytes:
    return (struct.pack(">I", len(data)) + tag + data
            + struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF))

def _render(size: int) -> bytes:
    raw = bytearray()
    for y in range(size):
        raw.append(0)  # filter None
        for x in range(size):
            ... # indigo→cyan gradient + white ring "C" monogram
            raw += bytes((r, g, b, 255))
    ihdr = struct.pack(">IIBBBBB", size, size, 8, 6, 0, 0, 0)
    return (b"\x89PNG\r\n\x1a\n" + _chunk(b"IHDR", ihdr)
            + _chunk(b"IDAT", zlib.compress(bytes(raw), 9)) + _chunk(b"IEND", b""))

manifest.json gains icons entries (192x192 + 512x512, any maskable) and sw.js precaches both icons plus the app shell:

const PRECACHE = ['./', './cluster-estimator.html', './manifest.json',
                  './icon-192.png', './icon-512.png'];

Evidence & signatures

Verified with the extended jsdom harness (`test.js`, run with Node 22 + jsdom): the new **5-check GAP-regression group** was added to the existing 3 groups — **4/4 harness groups PASS**:

```
[PASS] MATH: 2/2            # VRAM-fit math (8B→1 GPU; 700B→18 GPUs)
[PASS] TPxPP-FLOOR: 3/3     # TP=8→8; TP=4×PP=2→8; paneRecalculate TP=8→8
[PASS] CLAMP: 3/3           # tpSize:0→1; numParams:'abc'→0.1; pane NaN/-3→1
[PASS] GAP-REGRESSION: 5/5  # new group
ALL 13/13 checks PASS in 0.14s
4/4 harness groups PASS
```

GAP-regression checks: (1) TP=8 config never reports fewer than 8 GPUs; (2) amber `cloudFallbackWarn` visible; (3) per-row `spec-price-marker` present in **main and pane** tables; (4) imported JSON with `tpSize:0` renders **no NaN/Infinity**; (5) preset `headDim:576` (> input `max=256`) roundtrips losslessly in config **and** DOM.

**Edge battery (7/7 PASS):** DOM-tampered `tpSize='0'` then `recalculate()` → no NaN/Infinity, ≥1 GPU (recalc-level clamp works even bypassing `setConfig`); TP=8 summary text reads "8 GPUs"; pane import `tpSize:0` finite; RTX 4090 (spec-only) row shows `0.42` + `spec*` marker while H100 row uses cloud price with **no** marker; all `sw.js` precache files exist on disk.

**PWA assets:** `file` confirms valid `PNG image data, 192 x 192` / `512 x 512, 8-bit/color RGBA`; `manifest.json` parses as valid JSON; icons are `any maskable`.

**Pitfall confirmed live:** during harness bring-up, `getConfig()` without an argument returned `{}` (missing `kind || 'main'` default) — fixed; this is exactly the class of regression the roundtrip GAP-check guards.

---
{"model": "deepseek-v4-flash", "problem_class": "static-html-js-math-and-ux-fixes", "result": "passed", "tests": 13}
Generated from the verified corpus · MIT licensedBack to the catalog