Require a name and birth year for every student #154

Merged
thatguygriff merged 1 commits from feature/148-required-name-and-birth-year into main 2026-07-30 00:04:52 +00:00
Owner

Closes #148.

A student's name and birth year are now required, marked in their labels the same way a required registration question is, and enforced on the server whichever form they arrive from.

Server-side is the real check

GuardianService::createChild() and updateChild() refuse a blank name or an unusable birth year, and the signup form checks the same rule up front — before it creates a single user — so a bad block never leaves a half-registered family behind. normaliseBirthYear() became public and static so both paths share one definition of a usable year rather than keeping two copies in step.

Why the signup form can't lean on the browser

Its child blocks are hidden until the parent/guardian box is ticked, and a required field inside a hidden container makes the whole form unsubmittable — the browser refuses with "an invalid form control is not focusable" and there is nothing the user can click to fix it. It's the same trap the guardian's own question panel already sidesteps by disabling rather than hiding (register.js:108-110).

So register.js puts required on and takes it off along with the block itself, keyed off [data-us-child-required], and the server is what makes the rule hold with JavaScript off. The profile screen has no such problem — its forms are always visible — so the attribute is static there.

One behaviour change beyond the requirement

A child block with anything typed into it is now reported back instead of dropped. Previously any block without a name was silently discarded; with birth year in the mix that would mean throwing away a year the guardian had filled in and saying nothing. A wholly untouched spare block — the one the form always renders for "add another" — is still ignored, so the common case is unchanged.

Verification

composer test (782 tests, up from 778), composer lint, composer cs all pass. New coverage: the half-filled-block rejection, three rejected birth years through the signup path, and create/update refusing seven unusable years apiece without touching the database.

The required-toggling is the part that could break signup outright, so I drove it in a headless browser rather than trusting it:

check
unticked → block hidden, nothing required, form submits ok
ticked → both fields required, empty block blocks submit ok
ticked + filled → form valid ok
"add another" → clone required, value cleared, label re-pointed ok
re-unticked → form submittable again ok

That last row is the one that matters: no orphan required left behind to block someone registering only for themselves.

I also tightened the current_time stub in RegistrationPageTest — it returned a datetime string that only cast to the right year by luck, and the birth-year check now depends on current_time('Y') specifically.

Closes #148. A student's **name and birth year are now required**, marked in their labels the same way a required registration question is, and enforced on the server whichever form they arrive from. ## Server-side is the real check `GuardianService::createChild()` and `updateChild()` refuse a blank name or an unusable birth year, and the signup form checks the same rule **up front** — before it creates a single user — so a bad block never leaves a half-registered family behind. `normaliseBirthYear()` became public and static so both paths share one definition of a usable year rather than keeping two copies in step. ## Why the signup form can't lean on the browser Its child blocks are `hidden` until the parent/guardian box is ticked, and **a `required` field inside a hidden container makes the whole form unsubmittable** — the browser refuses with "an invalid form control is not focusable" and there is nothing the user can click to fix it. It's the same trap the guardian's own question panel already sidesteps by disabling rather than hiding ([register.js:108-110](assets/js/register.js#L108-L110)). So `register.js` puts `required` on and takes it off along with the block itself, keyed off `[data-us-child-required]`, and the server is what makes the rule hold with JavaScript off. The profile screen has no such problem — its forms are always visible — so the attribute is static there. ## One behaviour change beyond the requirement A child block with **anything** typed into it is now reported back instead of dropped. Previously any block without a name was silently discarded; with birth year in the mix that would mean throwing away a year the guardian had filled in and saying nothing. A wholly untouched spare block — the one the form always renders for "add another" — is still ignored, so the common case is unchanged. ## Verification `composer test` (782 tests, up from 778), `composer lint`, `composer cs` all pass. New coverage: the half-filled-block rejection, three rejected birth years through the signup path, and `create`/`update` refusing seven unusable years apiece without touching the database. The `required`-toggling is the part that could break signup outright, so I drove it in a headless browser rather than trusting it: | check | | |---|---| | unticked → block hidden, nothing required, form submits | ok | | ticked → both fields required, empty block blocks submit | ok | | ticked + filled → form valid | ok | | "add another" → clone required, value cleared, label re-pointed | ok | | **re-unticked → form submittable again** | ok | That last row is the one that matters: no orphan `required` left behind to block someone registering only for themselves. I also tightened the `current_time` stub in `RegistrationPageTest` — it returned a datetime string that only cast to the right year by luck, and the birth-year check now depends on `current_time('Y')` specifically.
thatguygriff added 1 commit 2026-07-30 00:01:10 +00:00
Require a name and birth year for every student
CI / Tests (PHP 8.1) (pull_request) Successful in 42s
CI / Coding Standards (pull_request) Successful in 2m56s
CI / PHPStan (pull_request) Successful in 2m56s
CI / Tests (PHP 8.2) (pull_request) Successful in 50s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m45s
CI / Build Plugin Zip (pull_request) Skipped
1d2f95d388
Both fields are marked in their labels the same way a required registration
question is, and enforced on the server whichever form they arrive from:
GuardianService::createChild() and updateChild() now refuse a blank name or
an unusable birth year, and the signup form checks the same rule up front,
before it creates a single user, so a bad block never leaves a
half-registered family behind. normaliseBirthYear() became public and static
so both paths share one definition of what a usable year is.

The signup form cannot lean on the browser here. Its child blocks are hidden
until the parent/guardian box is ticked, and a `required` field inside a
hidden container makes the whole form unsubmittable with no control the user
can reach to fix — the same trap the guardian's own question panel already
sidesteps by disabling rather than hiding. So register.js puts `required` on
and takes it off along with the block itself, and the server is what makes
the rule hold with JavaScript off. The profile screen has no such problem:
its forms are always visible, so the attribute is static there.

One behaviour change beyond the requirement: a child block with anything
typed into it is now reported back instead of dropped. Previously any block
without a name was silently discarded, which would now mean losing a birth
year the guardian had filled in. A wholly untouched spare block — the one
the form always renders for "add another" — is still ignored.

Verified the required-toggling in a headless browser: unticked submits,
ticked blocks an empty block, a cloned block inherits the requirement, and
re-unticking leaves nothing behind to block a non-guardian signup.

Closes #148

Co-Authored-By: Claude Opus 5 <[email protected]>
thatguygriff merged commit f0149042cc into main 2026-07-30 00:04:52 +00:00
thatguygriff deleted branch feature/148-required-name-and-birth-year 2026-07-30 00:04:52 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Unsupervised/unsupervised-scheduler#154