Compute form: progressive validation, cluster cards, presets and a live estimate - #6
Compute form: progressive validation, cluster cards, presets and a live estimate#6timurbazhirov wants to merge 4 commits into
Conversation
A new job opened the Compute tab on four red 'The field is required' errors - complaints about things the reader had not been given a chance to do yet. The form validates live, so every required field reported itself on first paint. Errors are now shown only for fields the reader has actually edited, tracked from the field id RJSF passes to onChange. A new showAllErrors prop (default off) reveals everything at once for the cases where the whole form must answer for itself - submit, and the preflight check coming in phase 2. Note the RJSForm remount keyed on showAllErrors: RJSF only re-validates when its schema or form data change, so without it the toggle left the previous validation result on screen. Demo: 'Start empty' reproduces the state a new job starts in, and the 'Show all errors' switch flips between the two modes. LOAD JSON now remounts the form too - ComputeForm derives its data in the constructor, so loading JSON previously did nothing to the form. SOF-8023 phase 1.1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
dist/ is tracked in this repo and the published package ships it ("files":
["/dist", "/src"]). There is no prepublishOnly build here, so a stale dist
is what consumers would get - and dist/utils/touchedFields.js was missing
entirely, which ComputeForm.js imports.
The husky pre-commit hook does this automatically (npm run transpile &&
git add dist/), but only once hooks are installed; this repo has no
'prepare: husky install' script, so a fresh clone commits without it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
The hook that regenerates dist has never run: husky was not a dependency and no prepare script installed it, so a fresh clone had .husky/pre-commit sitting inert. That is why four packages landed src changes with a stale or entirely missing dist - including new modules whose emitted code imported files that were never built. Adds husky + 'prepare: husky install' (matching cove, the one repo where this works), and drops 'npx lint-staged' from the hook: lint-staged is neither a dependency nor configured here, so arming the hook with that line would have failed and blocked every commit. Linting is enforced in CI regardless. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
The compute step asked for a cluster, a queue, nodes, cores and a walltime as five schema fields — a dropdown listing cluster names and nothing to choose between them on, four required fields empty on arrival, and no statement anywhere of what the answers add up to. A reader could configure 4 × 32 × 12 h without being told that is 1536 core-hours, three times the quota, until the run failed. Opt-in via `useComputeCards`, which moves four fields out of the schema form and into their own surface above it: - ClusterCards — each cluster as a card carrying price, max walltime and typical queue wait, so the choice is a comparison rather than a guess. - ComputeResources — presets (Debug / Standard / Production) clamped to the cluster's published limits, so the button meant to avoid an invalid configuration cannot produce one; bounded steppers for nodes and cores; walltime as the HH:MM:SS the scheduler takes. Typed values past a limit are allowed and flagged inline, on the same touched-field rule as the rest of the form. - ComputeEstimatePanel — core-hours, cost and queue wait as metric tiles over a quota meter. Each degrades on its own: core-hours need only the job, cost needs a published price, the meter needs an injected allowance, and a tile with nothing behind it shows an em dash, never a zero. utils/computeEstimate.ts is the canonical home for the arithmetic — job-designer currently carries a copy for its context strip and submit preflight, and should import this once ive publishes. Writes go through the same handleFormUpdate path as a keystroke in the schema form, so touched-field bookkeeping and the validate-then-onUpdate gate are unchanged. The hidden fields stay in the schema and keep validating. Espresso's advanced options are untouched; the cluster group left holding only two links is retitled Documentation rather than pointing at a picker that has moved. Verified in the demo: presets fill the fields and the estimate follows synchronously; Production on a 2-node cluster applies as 2 × 16 × 6 h; the nodes stepper disables at the cluster's limit while a typed 24 h walltime reports "Over the 6 h queue limit" and the meter shows "over by 268 core·h"; toggling the surface off brings all four schema fields back. 44 unit tests pass. Note: requires a @mat3ra/cove release carrying SelectableCard, MetricTile, SegmentedMeter and NumericStepperInput. Refs SOF-8023. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8
|
The whole failure is The two I'm deliberately not fixing this here. The available workarounds are all worse than a red check that tells the truth: an ambient module declaration would shadow cove's real types once it does publish, and vendoring the primitives into ive would duplicate what cove#97 exists to own. Against a local cove build the typecheck is clean, all 44 tests pass, and the behaviour in the PR description was measured in the running demo. Sequence: merge and publish cove#97, bump the pin here, re-run. If you would rather have a mergeable PR today, say so and I'll split the branch — 1.1 (progressive validation) has no cove dependency and is green on its own. Generated by Claude Code |
Summary
Two phases of the Job Designer UX update (SOF-8023, plan), both in the compute form: 1.1 stops the form complaining before the reader has done anything, and 2.3 gives it a cluster choice worth making and says what the answer costs.
1.1 Progressive validation
A new job opens the Compute tab on four red "The field is required" errors — complaints about things the reader has not been given a chance to do yet. The form validates live, so every required field reports itself on first paint. Errors are now shown only for fields the reader has actually edited.
Touched fields are tracked from the field id RJSF already passes as
onChange's second argument — no diffing, no dependency on widget internals.customValidatethen skipsaddErrorfor fields not in that set. The decision logic is a pure module (src/utils/touchedFields.ts) with unit tests.A new
showAllErrorsprop (defaultfalse) reveals everything at once for the cases where the whole form must answer for itself — submit, and the job designer's preflight.One subtlety worth flagging: the
RJSFormis keyed onshowAllErrors. RJSF only re-validates when its schema or form data change, so flipping the prop alone left the previous validation result on screen — the toggle silently did nothing. The remount forces a fresh pass. It happens on submit, not while typing, so the cost isn't felt.Time Limit,Queue,Nodes,Coresall requiredshowAllErrorsThe last row is the point of the table:
showAllErrorsreproduces today's behavior exactly, so nothing is lost — only deferred. I confirmed the "before" column by stashing the change and re-running the same script against the original code.2.3 Cluster cards, presets and a live estimate
The step asked for a cluster, a queue, nodes, cores and a walltime as five schema fields — a dropdown listing cluster names and nothing to choose between them on, four required fields empty on arrival, and no statement anywhere of what the answers add up to. A reader could configure 4 × 32 × 12 h without being told that is 1536 core-hours, three times their quota, until the run failed.
Behind
useComputeCards(default off), four fields move out of the schema form into their own surface above it:ClusterCards— each cluster as a card carrying price, max walltime and typical queue wait, so the choice is a comparison rather than a guess. Selecting one also picks that cluster's first real queue, since a queue chosen on the previous cluster is meaningless on this one.ComputeResources— presets (Debug / Standard / Production) clamped to the cluster's published limits, so the button that exists to avoid an invalid configuration cannot produce one; bounded steppers for nodes and cores; walltime as theHH:MM:SSthe scheduler takes. Typed values past a limit are allowed and flagged inline, on the same touched-field rule as 1.1.ComputeEstimatePanel— core-hours, cost and queue wait as metric tiles over a quota meter. Each degrades on its own: core-hours need only the job, cost needs a published price, the meter needs an injected allowance. A tile with nothing behind it shows an em dash, never a zero.How it stays out of trouble
handleFormUpdatepath as a keystroke in the schema form — same touched-field bookkeeping, same validate-then-onUpdategate.utils/computeEstimateis public API on purposeThree surfaces need the same answer to "what does this job cost" — the panel here, job-designer's context-strip chip, and its submit preflight's budget check — and three implementations would be worse than none. It lives here, beside the form that holds the live values, and is exported. job-designer carries an identical copy with a
TODOto delete it once this ships.Verification
npm testgreen (44 tests),tscclean, lint clean. Measured in the running demo rather than assumed:Demo changes
LOAD JSONnow remounts the form.ComputeFormderives its form data in its constructor, so loading JSON previously updated the state but left the form untouched — a pre-existing demo bug.🤖 Generated with Claude Code
https://claude.ai/code/session_01DK8KomMescJvMQNSEfeRR8