web: drive control limits from the served bounds and clamp, not 400
The store fetches {config, bounds} at startup and snaps values into
range everywhere they enter: typed fields, sliders, panel overrides,
and shared links (a ?edgesPerNode=150 link now runs at 8 instead of
showing a validation error). Shrinking numStudents pulls numEducated
and origin down with it. If the bounds fetch fails the app still
starts and the engine's validation backstops. Native min/max
attributes follow the same numbers, so spinners stop at the bounds.
This commit is contained in:
parent
bd890384e5
commit
268575ed6c
8 changed files with 337 additions and 18 deletions
|
|
@ -14,15 +14,46 @@ interface NumberFieldDef {
|
|||
field: keyof Config
|
||||
label: string
|
||||
min: number
|
||||
max: number | undefined
|
||||
step: number
|
||||
}
|
||||
|
||||
const numberFields: NumberFieldDef[] = [
|
||||
{ field: 'numStudents', label: CONFIG_FIELD_LABELS.numStudents, min: 2, step: 1 },
|
||||
{ field: 'edgesPerNode', label: CONFIG_FIELD_LABELS.edgesPerNode, min: 1, step: 1 },
|
||||
{ field: 'triangleProb', label: CONFIG_FIELD_LABELS.triangleProb, min: 0, step: 0.05 },
|
||||
{ field: 'origin', label: CONFIG_FIELD_LABELS.origin, min: 0, step: 1 },
|
||||
]
|
||||
// min/max come from the engine's served bounds; until they arrive the
|
||||
// inputs are unbounded and the store clamps nothing (the engine's own
|
||||
// validation backstops). origin's ceiling tracks numStudents.
|
||||
const numberFields = computed<NumberFieldDef[]>(() => {
|
||||
const bounds = store.state.bounds
|
||||
return [
|
||||
{
|
||||
field: 'numStudents',
|
||||
label: CONFIG_FIELD_LABELS.numStudents,
|
||||
min: bounds?.numStudents.min ?? 2,
|
||||
max: bounds?.numStudents.max,
|
||||
step: 1,
|
||||
},
|
||||
{
|
||||
field: 'edgesPerNode',
|
||||
label: CONFIG_FIELD_LABELS.edgesPerNode,
|
||||
min: bounds?.edgesPerNode.min ?? 1,
|
||||
max: bounds?.edgesPerNode.max,
|
||||
step: 1,
|
||||
},
|
||||
{
|
||||
field: 'triangleProb',
|
||||
label: CONFIG_FIELD_LABELS.triangleProb,
|
||||
min: bounds?.triangleProb.min ?? 0,
|
||||
max: bounds?.triangleProb.max,
|
||||
step: 0.05,
|
||||
},
|
||||
{
|
||||
field: 'origin',
|
||||
label: CONFIG_FIELD_LABELS.origin,
|
||||
min: 0,
|
||||
max: store.state.base.numStudents - 1,
|
||||
step: 1,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
interface SeedFieldDef {
|
||||
field: keyof Config
|
||||
|
|
@ -39,7 +70,7 @@ const seedFields: SeedFieldDef[] = [
|
|||
const offendingField = computed(() => {
|
||||
const validationError = store.state.validationError
|
||||
if (!validationError) return null
|
||||
const allFields = [...numberFields, ...seedFields].map(({ field }) => field)
|
||||
const allFields = [...numberFields.value, ...seedFields].map(({ field }) => field)
|
||||
return allFields.find((field) => validationError.includes(field)) ?? null
|
||||
})
|
||||
|
||||
|
|
@ -82,6 +113,7 @@ function rerollWorld() {
|
|||
<input
|
||||
type="number"
|
||||
:min="def.min"
|
||||
:max="def.max"
|
||||
:step="def.step"
|
||||
:value="store.state.base[def.field]"
|
||||
:aria-invalid="offendingField === def.field || undefined"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue