Skip to content

fix(admin): a cleared poll-interval part no longer emits "null" - #889

Merged
ajslater merged 1 commit into
developfrom
fix/validation-riders
Sep 21, 2026
Merged

ajslater merged 1 commit into
developfrom
fix/validation-riders

Conversation

@ajslater

Copy link
Copy Markdown
Owner

Implements P3f of tasks/followups-implementation-plan.md §5.6 — the last item in the plan.

Stacked on #888#887#886#885. Retarget as each merges.

The bug

duration-input.vue's three v-number-inputs each call update(), which emits fieldsToDjangoDuration. Clearing a part sets its model to null, and:

String(null)              // "null"  -- four characters
"null".padStart(3, "0")   // "null"  -- already longer than 3, so untouched

…so the emit became "null 01:00:00". Its consumer is library-create-update-inputs.vue for Library.poll_every, a DurationField, which rejects it with a 400 — the same 400 the dialog rendered as raw JSON, because nothing parsed the envelope's error shape (#888).

Reproduced: reverting only the composition guard makes the test fail with expected 'null 01:00:00' not to contain 'null'.

Two halves, because rules alone are not enough

Per-part rules, bound to the :min/:max already on each control ([0, 365] / [0, 23] / [0, 59]). Vuetify's $required alias carves out v === 0, so zero — a legitimate value here — still passes, and the parts sit inside the dialog's <v-form ref="form"> so form.validate() disables Submit.

A composition guard, because rules do not stop the emit. A cleared, negative or unparseable part coerces to zero. Coerced rather than refusing to emit: the field still reads empty to the user, the rule still says so, and Submit stays disabled — but whatever does get emitted is a valid duration.

The rule arrays are frozen module constants read from data, not computed — they are static.

Tests

frontend/tests/unit/duration-input.test.js, 7 cases — none existed. Initial parsing, normal composition, a cleared part never emitting "null", every part cleared still composing "000 00:00:00", the coercion table, zero being a legitimate value rather than an empty one, and the bounds matching the controls.

make fix && make lint && make ty clean. Full make test green: 1283 pytest, 602 vitest (+7).

NEWS, under Fixes: "Clearing part of a poll interval no longer produces an invalid value."

Note on the other rider

§5.6 also carried the password-message wording (five sites, one wording). That shipped with #876, which already touched the one line that disagreed — §5.6 explicitly permits folding it there if that PR was still open, and it was.

This completes the plan

All thirteen items are now open as PRs: #876 #877 #878 #879 #880 #881 #882 #883 #884 #885 #886 #887 #888 and this one.

🤖 Generated with Claude Code

@ajslater
ajslater added this pull request to stack #890 September 21, 2026 16:16
Base automatically changed from fix/validation-server-field-errors to develop September 21, 2026 16:17
Clearing one of the three v-number-inputs sets its model to `null`, and
`String(null)` is `"null"` -- four characters, so `padStart(3, "0")`
leaves it alone and the emitted duration was `"null 01:00:00"`. The
server rejects that with a 400 the dialog then rendered as raw JSON,
because nothing parsed the envelope's error shape.

Per-part rules bound each field to the `:min`/`:max` already on its
control. Vuetify's `$required` alias carves out `v === 0`, so zero --
a legitimate value here -- still passes, and the parts sit inside the
dialog's v-form so `form.validate()` disables Submit.

Rules do not stop the emit, though, so the composition is guarded as
well: a cleared, negative or unparseable part coerces to zero. The
field still reads empty to the user and the rule still says so; what
gets emitted is just always a valid duration.

The test reproduces "null 01:00:00" against the unguarded composition.

This was the last item of the validation-parity plan; the password
message wording it was also meant to carry shipped with the
change-password fix, which already touched that line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ajslater
ajslater force-pushed the fix/validation-riders branch from 5f01f03 to bf146c8 Compare September 21, 2026 16:17
@ajslater
ajslater merged commit 47b609d into develop Sep 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant