Commit Graph
286 Commits
Author SHA1 Message Date
thatguygriffandClaude Opus 5 85c7a01939 Cache the .debs php-builder installs, to measure whether it helps
CI / Tests (PHP 8.2) (pull_request) Successful in 54s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.1) (pull_request) Successful in 1m7s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m56s
CI / Coding Standards & Static Analysis (pull_request) Successful in 3m35s
CI / Tests (PHP 8.5) (pull_request) Successful in 12m53s
CI / Build Plugin Zip (pull_request) Skipped
Experiment for #178. On self-hosted runners setup-php installs PHP 8.3+
through php-builder, whose install.sh apt-installs ~70 -dev packages
before unpacking the build. The build tarball itself is only 19MB, so
that apt work is the whole cost, not the download.

Ubuntu's image drops the .debs after install, so every job fetches them
from the archive again. This keeps them and restores them through the
cache server, which lives in the cluster, so a WAN download becomes a
local one. The dependency list does not vary by PHP version, so a single
key serves 8.3 and 8.5 and the quality and build jobs alike.

Only the .debs are cached. /var/lib/apt/lists is deliberately left alone,
since a stale index is how apt starts 404ing mid-install, and reducing
flakiness is the entire point.

The Report restored .debs step is temporary instrumentation to show
whether the cache is actually being read. Baseline to beat, from run 526:
8.1 30s, 8.2 53s, 8.3 454s, quality 151s, 8.5 733s and a hard failure.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Uw545F1vveNJKjzLxdi2ks
2026-08-20 14:44:54 -03:00
thatguygriffandClaude Opus 5 bc046ec2a1 Fold PHPCS and PHPStan into one job and test PHP 8.5
CI / Tests (PHP 8.1) (pull_request) Successful in 57s
CI / Tests (PHP 8.2) (pull_request) Successful in 1m17s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards & Static Analysis (pull_request) Successful in 3m26s
CI / Tests (PHP 8.3) (pull_request) Successful in 13m1s
CI / Build Plugin Zip (pull_request) Skipped
CI / Tests (PHP 8.5) (pull_request) Failing after 12m16s
The two jobs were identical up to their final step, each paying for its own
Setup PHP. That step is the flaky one (#178), so running it twice to reach
two short commands was two chances for a run to fall over instead of one.
They are now steps in a single Coding Standards & Static Analysis job.

The one thing given up is that PHPCS failing now stops the job before
PHPStan reports, where before the two ran in parallel and both spoke. That
seemed a fair trade for halving the exposure, and the fix for a PHPCS
failure rarely depends on knowing PHPStan's verdict at the same time.

PHP 8.5 joins the test matrix. composer.json already allows it at >=8.1
and the suite passes on 8.5.9 locally: 915 tests, PHPStan and PHPCS clean,
check-platform-reqs satisfied. 8.4 is deliberately not added, only 8.5.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Uw545F1vveNJKjzLxdi2ks
2026-08-20 14:29:30 -03:00
thatguygriff b814ae34b4 Merge pull request 'Stop authenticating setup-php against the GitHub API' (#177) from ci/drop-github-token-from-setup-php into main
CI / Coding Standards (push) Successful in 3m7s
CI / Tests (PHP 8.1) (push) Successful in 2m11s
CI / Tests (PHP 8.2) (push) Successful in 1m6s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 6m42s
CI / Tests (PHP 8.3) (push) Successful in 12m55s
CI / Build Plugin Zip (push) Successful in 2m45s
Reviewed-on: #177
2026-08-20 17:25:22 +00:00
thatguygriffandClaude Opus 5 9071a3f70f Stop authenticating setup-php against the GitHub API
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.1) (pull_request) Successful in 56s
CI / Tests (PHP 8.2) (pull_request) Successful in 57s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m56s
CI / Coding Standards (pull_request) Successful in 7m5s
CI / Build Plugin Zip (pull_request) Skipped
CI / PHPStan (pull_request) Successful in 8m5s
The GitHub API rate limit was the wrong diagnosis, so this reverts the
1Password-backed token added in #175 along with the mirrored composite
action, leaving the workflows as they were.

Timing every Setup PHP step across runs 454-523 rules the rate limit out.
PHP 8.1 and 8.2 install in 26-41 seconds, 12 for 12, never once failing.
PHP 8.3 has never finished in under 143 seconds and ranges up to 1273,
with two outright failures. Those jobs share a fan-out, and so an egress
address and a rate limit bucket, with the 8.1 and 8.2 jobs that are never
touched. A throttle could not sort itself by PHP version that way.

Run 523, the first to carry the token, is the direct refutation: the
token resolved and verified, and Setup PHP still took 749 seconds on
kallone and 408 on eris. The 8.3 penalty also predates the whole story,
sitting at ~145 seconds back on 30 July.

What is left is a slow path specific to 8.3 on these arm64 runners, whose
long tail sometimes crosses the step timeout and reports the unhelpful
"Could not setup PHP 8.3". That is worth fixing on its own terms rather
than behind a token that was never in the path.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Uw545F1vveNJKjzLxdi2ks
2026-08-20 14:22:52 -03:00
thatguygriff 170d7e6c21 Merge pull request 'Authenticate setup-php against the GitHub API' (#175) from ci/github-token-for-setup-php into main
CI / Tests (PHP 8.2) (push) Successful in 57s
CI / Tests (PHP 8.1) (push) Successful in 1m0s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 3m10s
CI / PHPStan (push) Successful in 7m19s
CI / Tests (PHP 8.3) (push) Successful in 12m53s
CI / Build Plugin Zip (push) Successful in 2m45s
Reviewed-on: #175
2026-08-20 16:56:38 +00:00
thatguygriff aa3dd13775 Merge pull request 'Bump version to 1.5.3' (#176) from release/bump-1.5.3 into main
CI / PHPStan (push) Successful in 3m6s
CI / Tests (PHP 8.1) (push) Successful in 1m9s
CI / Tests (PHP 8.2) (push) Successful in 1m10s
CI / Tests (PHP 8.3) (push) Successful in 7m52s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Failing after 12m16s
CI / Build Plugin Zip (push) Skipped
Reviewed-on: #176
2026-08-20 16:37:58 +00:00
Release Bot 164c8ebf97 Bump version to 1.5.3 and open changelog section 2026-08-20 16:28:20 +00:00
thatguygriffandClaude Opus 5 1291af0b72 Authenticate setup-php against the GitHub API
CI / PHPStan (pull_request) Successful in 6m52s
CI / Tests (PHP 8.1) (pull_request) Successful in 6m0s
CI / Tests (PHP 8.2) (pull_request) Successful in 1m11s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m55s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 7m6s
CI / Build Plugin Zip (pull_request) Skipped
setup-php resolves its tools through the GitHub API, unauthenticated at 60
requests an hour per source address. A CI fan-out across the fleet exhausts
that bucket, and the step then retries for several minutes before reporting
only "Could not setup PHP 8.3". It reads as a hang rather than a throttle,
and it took out both a main CI run and a release build.

Each cluster has its own egress address and so its own bucket, which is why
the same job passed on one runner and failed on another in the same minute.

The token comes from 1Password through the Connect instance in whichever
cluster picked up the job, matching the pattern in thatguygriff/infra. That
repository's composite action is not reachable from here, so it is mirrored
locally. It stays a step output rather than being exported to the job
environment, to keep it away from the package scripts composer install runs.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0133tYSQoZhoKebKZV8o2GPs
2026-08-20 13:27:22 -03:00
thatguygriff 78083fc96c Merge pull request 'Let a studio choose its default payment method, and disconnect Stripe again' (#174) from feature/173-default-payment-method into main
CI / PHPStan (push) Successful in 8m6s
CI / Tests (PHP 8.1) (push) Successful in 1m6s
CI / Tests (PHP 8.2) (push) Successful in 54s
CI / Tests (PHP 8.3) (push) Successful in 6m48s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 5m55s
CI / Build Plugin Zip (push) Successful in 2m51s
Release / Build and Publish Release (push) Successful in 16m35s
Release / Open next-version bump PR (push) Successful in 4s
Reviewed-on: #174
v1.5.2
2026-08-20 15:53:02 +00:00
thatguygriffandClaude Opus 5 c077a653fb Let a studio pick its default payment method and disconnect Stripe
CI / No Debug Code (pull_request) Successful in 5s
CI / Tests (PHP 8.2) (pull_request) Successful in 54s
CI / Tests (PHP 8.1) (pull_request) Successful in 56s
CI / Coding Standards (pull_request) Successful in 2m55s
CI / PHPStan (pull_request) Successful in 3m48s
CI / Tests (PHP 8.3) (pull_request) Successful in 5m31s
CI / Build Plugin Zip (pull_request) Skipped
Stripe configuration was a one-way door. Keys could be entered but never
removed, and entering them moved every student onto card billing at once,
so there was no way to have Stripe live and satisfy yourself that card
payments worked before committing the studio to them.

Two settings-page changes open both directions:

Default payment method (`us_default_payment_method`) is now an explicit
choice between card and e-transfer for students with no per-student
override, rather than something inferred from whether keys exist. Card
remains the default, so a site that adds keys and changes nothing else
behaves as before. BillingMethodResolver still degrades a card default to
e-transfer while Stripe is unconfigured — there is nothing to charge a
card with — and `comp` is deliberately not offerable studio-wide, since
it would silently stop billing everybody; an unrecognised stored value
reads back as card. Holding the default on e-transfer with Stripe live is
the staged-rollout path: move individual students to card on their detail
page, watch real charges land, then flip the studio over.

Clear Stripe configuration deletes the publishable key, secret key and
webhook signing secret and returns the mode to test, so a re-configuration
later cannot inherit live. Currency, HST, e-transfer and registration
settings are untouched, as are recorded payments. The button only appears
when some Stripe value is stored, and reuses the page's existing nonce and
`manage_billing` check.

`composer test` (915), `composer lint` and `composer cs` all pass. Options
only — no schema change, so no version bump.

Closes #173

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WyktWmwNRgMYuwe5eBuPZm
2026-08-20 12:43:29 -03:00
thatguygriff f9e222be29 Merge pull request 'Bump version to 1.5.2' (#172) from release/bump-1.5.2 into main
CI / No Debug Code (push) Successful in 3s
CI / Tests (PHP 8.1) (push) Successful in 46s
CI / Tests (PHP 8.2) (push) Successful in 1m4s
CI / Tests (PHP 8.3) (push) Successful in 2m43s
CI / Coding Standards (push) Successful in 3m40s
CI / PHPStan (push) Successful in 3m43s
CI / Build Plugin Zip (push) Successful in 2m51s
Reviewed-on: #172
2026-08-11 22:06:41 +00:00
Release Bot b950e35e5a Bump version to 1.5.2 and open changelog section 2026-08-11 22:06:18 +00:00
thatguygriff b220de48c5 Merge pull request 'Let a guardian enrol every child in the same group class' (#171) from fix/group-multi-student-enrolment into main
CI / No Debug Code (push) Successful in 4s
CI / Tests (PHP 8.1) (push) Successful in 1m4s
CI / Tests (PHP 8.2) (push) Successful in 1m10s
CI / Coding Standards (push) Successful in 3m35s
CI / PHPStan (push) Successful in 3m52s
CI / Tests (PHP 8.3) (push) Successful in 21m42s
CI / Build Plugin Zip (push) Successful in 14m57s
Release / Build and Publish Release (push) Successful in 3m11s
Release / Open next-version bump PR (push) Successful in 5s
Reviewed-on: #171
v1.5.1
2026-08-11 20:44:29 +00:00
thatguygriffandClaude Opus 5 8017dbb9ff Let a guardian enrol every child in the same group class
CI / PHPStan (pull_request) Successful in 2m55s
CI / No Debug Code (pull_request) Successful in 26s
CI / Tests (PHP 8.2) (pull_request) Successful in 48s
CI / Tests (PHP 8.1) (pull_request) Successful in 1m5s
CI / Coding Standards (pull_request) Successful in 3m49s
CI / Tests (PHP 8.3) (pull_request) Successful in 6m9s
CI / Build Plugin Zip (pull_request) Skipped
The group-class page matched enrolments to the account instead of to the
student: the lookup it built from GET /enrollments was keyed by offering id
alone, so the first household enrolment marked the class as "yours" and took
the Enrol button away from everyone else on the account. A parent could enrol
one child and was then offered nothing but Withdraw.

The server was never the constraint — hasActiveEnrollment() checks the
(offering, student) pair and GET /enrollments deliberately returns the whole
household — so the fix is to stop discarding student_id on the way in. Active
enrolments are now grouped per class as a list, each student gets their own
"… is enrolled in this class." line and their own named Withdraw button, and
the Enrol button stays (as "Enrol another student") while anyone the account
may enrol is still out.

The enrolment form offers only the students not yet enrolled. When exactly one
of them is left the picker collapses, and that case needed care: an omitted
student_id reads as "enrol the account holder" server-side, so a hidden field
carries the id rather than posting nothing and signing up the parent instead of
the last child.

Closes #170

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Jy9UPhmpLUAfimsyecHN2Z
2026-08-11 17:36:01 -03:00
thatguygriff c73b10d779 Merge pull request 'Bump version to 1.5.1' (#169) from release/bump-1.5.1 into main
CI / Tests (PHP 8.1) (push) Successful in 47s
CI / Tests (PHP 8.2) (push) Successful in 55s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m52s
CI / Coding Standards (push) Successful in 2m57s
CI / Tests (PHP 8.3) (push) Successful in 2m47s
CI / Build Plugin Zip (push) Successful in 2m51s
Reviewed-on: #169
2026-07-30 19:48:12 +00:00
Release Bot 7ea8d653ee Bump version to 1.5.1 and open changelog section 2026-07-30 19:46:05 +00:00
thatguygriff 1e4e21e8d3 Merge pull request 'Show a recurring lesson's policy acceptances and intake answers on every week of it' (#168) from fix/167-series-policy-acceptances into main
CI / Tests (PHP 8.2) (push) Successful in 44s
CI / Tests (PHP 8.1) (push) Successful in 54s
CI / No Debug Code (push) Successful in 1s
CI / PHPStan (push) Successful in 2m54s
CI / Coding Standards (push) Successful in 2m58s
CI / Tests (PHP 8.3) (push) Successful in 2m43s
Release / Build and Publish Release (push) Successful in 2m58s
Release / Open next-version bump PR (push) Successful in 4s
CI / Build Plugin Zip (push) Successful in 2m46s
Reviewed-on: #168
v1.5.0
2026-07-30 19:41:03 +00:00
thatguygriffandClaude Opus 5 df3462a8b3 Show a series' policy acceptances on every occurrence
CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / Tests (PHP 8.1) (pull_request) Successful in 54s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m54s
CI / Coding Standards (pull_request) Successful in 2m59s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m43s
CI / Build Plugin Zip (pull_request) Skipped
A weekly booking reserves a series of lessons, but the student answers
the intake and ticks the policy boxes once — so BookingEndpoint records
both against the anchor lesson alone. The admin detail view looked them
up by whichever lesson id was being viewed, so every occurrence after
the first showed no answers and no acceptances at all.

LessonDetail now takes the Lesson rather than a bare id and resolves the
registration to `series_id ?? id`, so each occurrence reads the anchor's
records. This is the same seam PaymentService already uses to find a
series lesson's payment on the anchor.

Nothing was ever missing from the database, so existing bookings read
correctly with no migration and no schema change.

Closes #167

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-30 16:35:26 -03:00
thatguygriff 748478f2f1 Merge pull request 'Let the account holder edit their own details on the profile page' (#166) from feature/165-editable-own-profile into main
CI / Tests (PHP 8.1) (push) Successful in 46s
CI / Tests (PHP 8.2) (push) Successful in 55s
CI / No Debug Code (push) Successful in 3s
CI / PHPStan (push) Successful in 2m57s
CI / Coding Standards (push) Successful in 3m3s
CI / Tests (PHP 8.3) (push) Successful in 2m48s
CI / Build Plugin Zip (push) Successful in 2m50s
Reviewed-on: #166
2026-07-30 19:29:21 +00:00
thatguygriffandClaude Opus 5 f97b8a4576 Let the account holder edit their own profile details
CI / Tests (PHP 8.1) (pull_request) Successful in 45s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 55s
CI / PHPStan (pull_request) Successful in 2m57s
CI / Coding Standards (pull_request) Successful in 3m3s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / Build Plugin Zip (pull_request) Skipped
The Profile block is headed "Your profile", but the one person on it you
could not change was yourself: your name, your birth year, and whether you
take lessons yourself were fixed at whatever signup recorded, and correcting
any of them meant asking a studio admin.

A "Your details" section now opens the page, saved through the same
nonce-checked template_redirect post/redirect/get path the child rows use:

- Your name, written to display_name and nickname together, for the reason
  updateChild() does — UserName reads the nickname first, and leaving it
  behind would put the account's email address back on every screen that
  names a person.
- "I take lessons myself", the positive of us_guardian_only. This makes good
  on the claim already in bookableStudents() and the feature doc that a
  guardian-only account can put itself right from the profile page.
- Your birth year, held to the same normaliseBirthYear() rule as every other
  student.

The email is shown but not editable: it is the account's user_login as well
as its address, so changing it stays a studio-side job.

The birth-year field deliberately carries no `required` attribute. It is
asked of a student only, and this page loads no JavaScript, so a
browser-enforced `required` would leave a guardian who books solely for
other people unable to submit the form at all; handleSelf() enforces it
against the checkbox instead. Unticking the box does not clear a stored
birth year — it says who books, not "forget what is on file".

Closes #165

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-30 15:08:05 -03:00
thatguygriff 325a86f247 Merge pull request 'Ask some registration questions of students only, and require them of students without requiring them of the account holder' (#164) from feature/student-scoped-registration-questions into main
CI / Tests (PHP 8.2) (push) Successful in 57s
CI / Build Plugin Zip (push) Successful in 2m49s
CI / Tests (PHP 8.1) (push) Successful in 58s
CI / No Debug Code (push) Successful in 3s
CI / Coding Standards (push) Successful in 2m50s
CI / PHPStan (push) Successful in 2m59s
CI / Tests (PHP 8.3) (push) Successful in 2m42s
Reviewed-on: #164
2026-07-30 16:56:33 +00:00
thatguygriffandClaude Opus 5 434fe801ba Ask some registration questions of students only
CI / Tests (PHP 8.2) (pull_request) Successful in 58s
CI / Tests (PHP 8.1) (pull_request) Successful in 58s
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards (pull_request) Successful in 2m53s
CI / PHPStan (pull_request) Successful in 3m0s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / Build Plugin Zip (pull_request) Skipped
Every account-signup question was asked of everybody who registered, on the
same terms: "school and grade" had to be put to an adult signing themselves
up, and a question a studio needed answered for each student could only be
made required by demanding it of everyone.

A question now carries an audience — everyone, or only the students someone
registers on behalf of — and its own required flag for each side, so optional
for you and required for every student you enrol is expressible. Both settings
are account-scope only: an offering asks its questions once, about the student
being booked, so there is no second audience to differ from, and an offering
question mirrors its single "required" into both columns.

Every caller reads askedOfSelf()/isRequiredForSelf()/isRequiredForChild()
rather than the raw flags, so a students-only question can neither block the
account holder nor have an answer filed against them by a crafted post. The
family screen, which only ever adds a student, is held to the students' rule.

is_required_child arrives from dbDelta defaulting to 0, which would quietly
stop every existing required question being required of the students a
guardian registers — the case it most likely existed for. A one-time backfill
copies is_required across, guarded by its own option so a question later made
optional for students stays that way.

Closes #163

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-30 13:51:52 -03:00
thatguygriff 84378e856b Merge pull request 'Bump version to 1.4.2' (#162) from release/bump-1.4.2 into main
CI / Tests (PHP 8.2) (push) Successful in 51s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m53s
CI / Coding Standards (push) Successful in 2m58s
CI / Build Plugin Zip (push) Successful in 2m45s
CI / Tests (PHP 8.1) (push) Successful in 45s
CI / Tests (PHP 8.3) (push) Successful in 2m42s
Reviewed-on: #162
2026-07-30 15:55:06 +00:00
Release Bot a2cece750b Bump version to 1.4.2 and open changelog section 2026-07-30 15:34:07 +00:00
thatguygriff 5d98aedfa5 Merge pull request 'Editable policy name, one-page signup with adult birth year, group classes in upcoming lessons, and booking cleanup on user delete' (#161) from feature/demo-followups-2026-07-30 into main
CI / PHPStan (push) Successful in 2m53s
CI / Coding Standards (push) Successful in 3m2s
Release / Build and Publish Release (push) Successful in 2m59s
Release / Open next-version bump PR (push) Successful in 4s
CI / Tests (PHP 8.3) (push) Successful in 2m42s
CI / Build Plugin Zip (push) Successful in 2m46s
CI / Tests (PHP 8.1) (push) Successful in 44s
CI / Tests (PHP 8.2) (push) Successful in 51s
CI / No Debug Code (push) Successful in 2s
Reviewed-on: #161
v1.4.1
2026-07-30 15:28:12 +00:00
thatguygriffandClaude Opus 5 8fd7bf983d Name people by their name, not their email address
CI / Tests (PHP 8.2) (pull_request) Successful in 58s
CI / Tests (PHP 8.1) (pull_request) Successful in 59s
CI / Coding Standards (pull_request) Successful in 2m51s
CI / PHPStan (pull_request) Successful in 3m1s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / No Debug Code (pull_request) Successful in 3s
CI / Build Plugin Zip (pull_request) Skipped
Anywhere the plugin named a person it could show their email instead —
"Managed by [email protected]" in the students table, the same under
Booked by, instructor names on the class pages.

WordPress defaults a new account's `nickname` to its `user_login`, and
signup uses the email address as the login. So every self-registered
account carried its own address as its nickname, and UserName::format()
fell straight through to it. The name they typed was in `display_name`
all along. Accounts created by a guardian were never affected —
GuardianService::createChild() sets `nickname` outright, which is exactly
why children read correctly and their parents did not.

UserName::format() now walks nickname then display name, skipping either
when it is really the login or the email, so existing accounts read
correctly with nothing to migrate. An identifier still never reaches the
screen: an account with nothing but its address on file falls back to the
id, as before. Signup also sets `nickname` at insert, so new accounts are
right at the source rather than relying on the fallback.

Tests: composer test (866), composer lint, composer cs all pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-30 12:24:04 -03:00
thatguygriffandClaude Opus 5 122f7a0f53 Show "Booked by" in the Account section of a student's detail page
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m48s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / Tests (PHP 8.2) (pull_request) Successful in 57s
CI / Tests (PHP 8.1) (pull_request) Successful in 59s
CI / Coding Standards (pull_request) Successful in 3m1s
CI / Build Plugin Zip (pull_request) Skipped
The parent/guardian was only named further down under Profile, where it
reads as background rather than as an account fact, and only when there
was one — so a page with no such line was ambiguous between "books for
themselves" and "the lookup found nothing".

It now sits in the Account table beside display name and email, as the
guardian's name linked to their own detail page, and always renders: a
student who books for themselves says so outright. No email address —
theirs is one click away on their own page, and repeating it here only
makes the row harder to scan. The Profile section keeps only the note
explaining the placeholder email, which is a different point.

Tests: composer test (863), composer lint, composer cs all pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-30 12:18:42 -03:00
thatguygriffandClaude Opus 5 c9a1205fc0 Never drop an enrolled class from upcoming lessons; delete a guardian's children with them; pin the panel's line spacing
CI / Tests (PHP 8.2) (pull_request) Successful in 58s
CI / PHPStan (pull_request) Successful in 2m53s
CI / Coding Standards (pull_request) Successful in 2m58s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m47s
CI / Build Plugin Zip (pull_request) Skipped
CI / Tests (PHP 8.1) (pull_request) Successful in 1m1s
CI / No Debug Code (pull_request) Successful in 3s
Three fixes from testing the branch.

A group class was only listed when its schedule resolved to exact
datetimes, which needs a class time *and* a duration — both optional on
the offering form, and the schedule note exists precisely so a studio can
write "Tuesdays 4:00pm" instead. A class configured that way vanished
from the list, which is the one thing this feature must never do. So
Offering::sessionStarts() splits "when does it meet" from "how long does
it run" (sessionWindows() is that plus the duration, unchanged), and
SessionSchedule degrades instead of disappearing: dated rows with an open
end when there is no duration, and a single row carrying
Offering::scheduleLabel() when there is no time to derive dates from.
Only a class whose last day has passed drops out.

Deleting a guardian now deletes the children linked to them, releasing
each one's lessons and enrolments first. A child account is login-less
and exists only so the guardian has somebody to book for; without the
guardian nobody can reach it, book for it, or be billed for it, so it was
left stranded on the roster still holding slots. A `handled` set makes
the re-entrant delete_user each child deletion fires a no-op, and stops a
circular link recursing.

The upcoming panel never stated its own line-height, so a theme setting
line-height: 0 above it — the usual icon-font reset — was inherited
straight through. Below 1 that produces both reported symptoms at once:
stacked lines overlap, and the status pill's background is shorter than
the text in it. Pinned at the same id-level specificity as the rest.

Tests: composer test (863), composer lint, composer cs all pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-30 12:10:43 -03:00
thatguygriffandClaude Opus 5 cb347ffca0 Demo follow-ups: editable policy name, one-page signup, group classes in upcoming lessons, deletion cleanup
CI / Tests (PHP 8.1) (pull_request) Successful in 1m0s
CI / Tests (PHP 8.2) (pull_request) Successful in 1m0s
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards (pull_request) Successful in 3m8s
CI / Build Plugin Zip (pull_request) Skipped
CI / PHPStan (pull_request) Successful in 2m49s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m44s
Five items from the latest demo pass:

- A policy's title can be edited from the Policies screen. Only the title
  moves; the slug is what the gates resolve policies by, so a rename can
  never detach a policy from acceptances already recorded against it.
- Signup is one page again. The studio's registration questions move from
  a second step behind "Next" onto the main form, in an "About you" panel
  above the students being added, and that panel also asks an adult
  student for their birth year (the same us_birth_year meta a child's
  uses). register.js disables and hides the whole panel for a pure
  guardian, since the questions describe a student.
- The password is re-scored on submit, not only as it is typed. zxcvbn's
  dictionary arrives after page load, so a password typed straight away
  was never scored at all and the first the student heard of it was the
  server rejecting the whole form.
- Group-class sessions appear alongside lessons wherever upcoming lessons
  are listed: the [us_scheduler] panel (students and instructors) and the
  admin student detail page. GroupClass\SessionSchedule derives them from
  Offering::sessionWindows(), the same derivation the billing scan uses.
  They carry kind = 'group_class' and no Cancel action - a session is one
  date in a term, not a booked slot.
- Deleting a user releases what the account was holding: each upcoming
  lesson is cancelled, its slot freed for rebooking, its pending payment
  voided, and active class enrolments cancelled. Past lessons and paid
  history are left alone.

Tests: composer test (851), composer lint, composer cs all pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-30 11:45:04 -03:00
thatguygriff 258468093b Merge pull request 'Bump version to 1.4.1' (#160) from release/bump-1.4.1 into main
CI / PHPStan (push) Successful in 2m47s
CI / Coding Standards (push) Successful in 2m57s
CI / Tests (PHP 8.1) (push) Successful in 48s
CI / Tests (PHP 8.2) (push) Successful in 45s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.3) (push) Successful in 2m40s
CI / Build Plugin Zip (push) Successful in 2m48s
Reviewed-on: #160
2026-07-30 02:30:31 +00:00
Release Bot 3a4b25a711 Bump version to 1.4.1 and open changelog section 2026-07-30 02:30:20 +00:00
thatguygriff 969d864106 Merge pull request 'Ask who the signup is for, and ask each student the studio's questions' (#159) from feature/145-registering-for-choice into main
CI / Tests (PHP 8.2) (push) Successful in 45s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 2m54s
CI / PHPStan (push) Successful in 2m55s
CI / Tests (PHP 8.3) (push) Successful in 2m44s
CI / Tests (PHP 8.1) (push) Successful in 53s
Release / Build and Publish Release (push) Successful in 3m1s
Release / Open next-version bump PR (push) Successful in 4s
CI / Build Plugin Zip (push) Successful in 2m53s
Reviewed-on: #159
v1.4.0
2026-07-30 02:24:52 +00:00
thatguygriffandClaude Opus 5 69179b75c9 Ask the account holder the studio's questions when they are a student too
CI / Tests (PHP 8.2) (pull_request) Successful in 43s
CI / Tests (PHP 8.1) (pull_request) Successful in 52s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m50s
CI / Coding Standards (pull_request) Successful in 3m19s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m47s
CI / Build Plugin Zip (pull_request) Skipped
Under "both" the questions were collected per student only, so someone
registering themselves alongside their children was never asked their own
instrument, level or anything else — despite being able to book lessons. The
account-scope questions describe a student, and under "both" the account
holder is one.

Their answers are recorded against their own user id, not shared with a
child's, and recorded after the children so a rollback that deletes the
account cannot leave answers pointing at a user that no longer exists. A
pure guardian is unchanged: they are not a student, so anything posted for
them is still ignored.

Validation became two passes rather than one so the message can say whose
answers are missing — with one pass, "both" had to blame "each student" for
the account holder's own blank field.

In the form, the two questions turn out to be independent: whether student
blocks are in play, and whether the account holder answers for themselves.
"Both" is the case that needs its own answer to each, so sync() now tracks
them separately, and step two comes back into play under "both".

Verified in a headless browser: 21 checks across all three choices, now
including that "both" enables the account holder's own question panel and
offers Next rather than the early submit.

Closes #146

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-29 23:15:47 -03:00
thatguygriffandClaude Opus 5 4e5382e259 Ask who the signup is for as a three-way choice
CI / Tests (PHP 8.2) (pull_request) Successful in 50s
CI / PHPStan (pull_request) Successful in 2m53s
CI / Coding Standards (pull_request) Successful in 2m57s
CI / Tests (PHP 8.1) (pull_request) Successful in 45s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m40s
CI / Build Plugin Zip (pull_request) Skipped
Replaces the single "I'm registering as a parent or guardian" tick with
"Just myself" / "On behalf of one or more students" / "Both".

Radios, not checkboxes as the feedback put it: the three answers are
mutually exclusive, and "both" only means anything as a third choice
alongside the other two.

The tick could only ever say whether there were children to add. It could
not say whether the account holder was a student, so bookableStudents()
always offered them their own name and any guardian could book themselves a
lesson nobody meant to sell. "On behalf of" now records us_guardian_only and
leaves them out of the picker.

That flag is stored as the negative on purpose. Every account predating this
choice is a bookable student, and absence has to keep meaning exactly that,
or the picker would quietly stop offering people themselves on upgrade.
setGuardianOnly() clears the key rather than writing 0, so "not set" stays
the single spelling of "yes, a student". A guardian-only account with nobody
linked to it is still offered itself — an empty picker is no way to book at
all, and they can put the account right from the profile page.

An unrecognised or absent value reads as "just myself": the choice that
collects the least and grants the least. A missing radio must never be taken
as "register these children".

Bumps to 1.4.0.

The account holder's own questions stay out of play whenever students are
being added, "both" included — asking them there is #146.

Verified the form in a headless browser across all three choices: which
blocks show, which fields carry `required`, whether the account holder's
question panel is disabled, which submit is offered, and that switching back
to "just myself" leaves no hidden required field blocking submit.

Closes #145

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-29 23:09:47 -03:00
thatguygriff 28046e0fd1 Merge pull request 'Return to a bookable calendar after a booking is confirmed' (#158) from fix/143-return-to-bookable into main
CI / Tests (PHP 8.1) (push) Successful in 44s
CI / No Debug Code (push) Successful in 1s
CI / Build Plugin Zip (push) Successful in 2m49s
CI / Tests (PHP 8.2) (push) Successful in 51s
CI / PHPStan (push) Successful in 2m55s
CI / Coding Standards (push) Successful in 2m59s
CI / Tests (PHP 8.3) (push) Successful in 2m41s
Reviewed-on: #158
2026-07-30 02:02:51 +00:00
thatguygriffandClaude Opus 5 699e479805 Return to a bookable calendar after a booking is confirmed
CI / Tests (PHP 8.3) (pull_request) Successful in 2m44s
CI / Build Plugin Zip (pull_request) Skipped
CI / Coding Standards (pull_request) Successful in 3m0s
CI / Tests (PHP 8.1) (pull_request) Successful in 42s
CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m46s
showConfirmation() hid the slot list and put the confirmation in its place,
which is a dead end: a student wanting a second lesson had nothing to click
and no way back short of reloading the page. Enrolling in a group class did
the same thing.

The confirmation is now a dismissible notice above the calendar. The
calendar is reloaded first — so the slot just taken is already gone and the
upcoming-lessons panel is current — and the notice is shown over it, which
is why loadSlots() had to start returning its promise. "It worked" and "book
another" are the same screen.

The notice clears when dismissed, when another slot's form is opened, and on
any reload of the calendar. group-classes.js gets the identical treatment.

It is built from DOM nodes rather than innerHTML because the message can
carry a studio's e-transfer address, and it is toggled with the `hidden`
attribute rather than an inline display — an inline style would outrank the
stylesheet's display:flex and stack the notice's parts. `hidden` needs the
!important guard for the same reason the upcoming-lessons panel does: the
div{display:block} theme reset outranks the UA sheet.

The slotList/list `display = 'block'` lines went with it. Nothing hides
those any more, so restoring them each load only implied otherwise.

Verified in a headless browser against a stubbed REST API: booking twice in
a row without a reload, the booked slot leaving the calendar, the upcoming
panel updating, dismissal, the notice clearing when the next form opens, and
the notice staying hidden under div{display:block}.

Closes #143

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-29 22:57:16 -03:00
thatguygriff 1b42d20541 Merge pull request 'Add an account block showing who is signed in' (#156) from feature/142-account-block into main
CI / Build Plugin Zip (push) Successful in 2m48s
CI / Tests (PHP 8.2) (push) Successful in 43s
CI / Tests (PHP 8.1) (push) Successful in 52s
CI / PHPStan (push) Successful in 2m53s
CI / Coding Standards (push) Successful in 2m57s
CI / Tests (PHP 8.3) (push) Successful in 2m44s
CI / No Debug Code (push) Successful in 2s
Reviewed-on: #156
2026-07-30 01:53:31 +00:00
thatguygriffandClaude Opus 5 ab5212282d Show only the name and email, not who the account books for
CI / Tests (PHP 8.2) (pull_request) Successful in 41s
CI / Tests (PHP 8.1) (pull_request) Successful in 42s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m48s
CI / Coding Standards (pull_request) Successful in 3m1s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m44s
CI / Build Plugin Zip (pull_request) Skipped
The block reports who is signed in and nothing more. Dropping the "Booking
for …" line takes GuardianService with it — it was the only reason the page
had a dependency at all, so AccountPage now constructs with no arguments.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-29 22:42:25 -03:00
thatguygriffandClaude Opus 5 6e3affb1cb Add an account block showing who is signed in
[us_account], or the Account block: the signed-in visitor's name, their
email, a Sign out link, and — only when the account books for someone
besides itself — the students it books for. A parent's first question on
seeing "signed in as Grace" is whether this is the account their children's
lessons are on.

Two decisions worth naming.

Signed out with no login page chosen, the block renders nothing. Its whole
subject is the person signed in, which a stranger is not, and a bare "you
are not signed in" in a site header is noise with no way to act on it. With
a login page chosen it offers a Sign in link instead. The editor preview is
populated regardless, so the block is never an invisible box to the person
placing it.

Signing out returns to the chosen login page, or to the current page when
there is none. A block meant for a header should not also navigate someone
somewhere when they use it; the login page wins when configured, because the
page they were on may well be members-only.

The name comes from UserName::format(), so the block never exposes a
username the way display_name can.

Also brings docs/features/editor-blocks.md back in step: it still described
"four shortcodes" and had never listed the family block.

Closes #142

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-29 22:42:25 -03:00
thatguygriff 7875cb1bf7 Merge pull request 'Fix main: two signup fixtures use a now-rejected password' (#157) from fix/green-main into main
CI / Coding Standards (push) Successful in 2m57s
CI / Tests (PHP 8.1) (push) Successful in 57s
CI / Tests (PHP 8.2) (push) Successful in 45s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m51s
CI / Tests (PHP 8.3) (push) Successful in 2m46s
CI / Build Plugin Zip (push) Successful in 2m50s
Reviewed-on: #157
2026-07-30 01:41:29 +00:00
thatguygriffandClaude Opus 5 04cba9702c Fix main: two signup fixtures use a now-rejected password
CI / Tests (PHP 8.2) (pull_request) Successful in 52s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m46s
CI / Tests (PHP 8.1) (pull_request) Successful in 54s
CI / Coding Standards (pull_request) Successful in 3m6s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m49s
CI / Build Plugin Zip (pull_request) Skipped
#154 and #155 each passed on their own branch and broke on landing
together. #154 added two guardian-signup tests using 'password123' as
their fixture; #155 then added PasswordPolicy, which rejects exactly
that. Neither branch ever saw the other's change, because #155 was cut
from main before #154 merged.

Both tests now use the same policy-clearing fixture as the rest of the
file. The deliberate 'password123' in the rejected-passwords provider
stays — that one is the point.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-29 22:40:51 -03:00
thatguygriff d554e35d80 Merge pull request 'Validate signup email and password strength' (#155) from feature/150-signup-credential-validation into main
CI / Tests (PHP 8.2) (push) Failing after 43s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m55s
CI / Coding Standards (push) Successful in 2m56s
CI / Tests (PHP 8.3) (push) Failing after 2m44s
CI / Build Plugin Zip (push) Skipped
CI / Tests (PHP 8.1) (push) Failing after 50s
Reviewed-on: #155
2026-07-30 01:27:05 +00:00
thatguygriffandClaude Opus 5 b5b9a7ac54 Validate signup email and password strength
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.2) (pull_request) Successful in 42s
CI / Tests (PHP 8.1) (pull_request) Successful in 53s
CI / PHPStan (pull_request) Successful in 2m53s
CI / Coding Standards (pull_request) Successful in 2m57s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m44s
CI / Build Plugin Zip (pull_request) Skipped
The password was only ever checked for length. It is now checked on both
sides, with each side doing the job it can actually do.

The browser scores it with zxcvbn, through WordPress's own
password-strength-meter script rather than a second opinion of our own, and
refuses to submit below "medium". That is the nuanced test — it knows
Tr0ub4dor&3 is weaker than it looks — but it is advice a client can decline
to take.

Auth\PasswordPolicy runs on the server and is the rule that holds. It does
not try to reproduce a strength score in PHP; it rejects the categorically
bad, which is what a server can check without shipping a dictionary: too
short, a well-known leaked password, fewer than four distinct characters, or
the user's own name or email inside it. No composition rules — NIST advises
against them, and they mostly produce predictable substitutions.

Both thresholds come from the same two constants, handed to JavaScript by
wp_localize_script, so the sides cannot drift into disagreeing about what
was accepted.

The verdict is attached to the field with setCustomValidity() rather than by
disabling a button. The form has up to three submits plus a "Next" that
already gates on checkValidity(), and an invalid field stops all of them
without any of them needing to know why.

Email validation moved ahead of the password check, since the password is
now checked against the email. A blank form therefore reports the email
first, which also matches the order the fields appear in.

Verified the browser half against a controllable scorer: each score band
blocks or allows as intended, the identity list reaches the meter, and the
gate stays open while zxcvbn's dictionary is still loading — the server
covers that window.

Closes #150

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-29 22:13:31 -03:00
thatguygriff f0149042cc Merge pull request 'Require a name and birth year for every student' (#154) from feature/148-required-name-and-birth-year into main
CI / Tests (PHP 8.2) (push) Successful in 43s
CI / Tests (PHP 8.1) (push) Successful in 51s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m53s
CI / Coding Standards (push) Successful in 2m59s
CI / Tests (PHP 8.3) (push) Successful in 2m45s
CI / Build Plugin Zip (push) Successful in 2m45s
Reviewed-on: #154
2026-07-30 00:04:51 +00:00
thatguygriffandClaude Opus 5 1d2f95d388 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
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]>
2026-07-29 21:00:45 -03:00
thatguygriff 2878beb221 Merge pull request 'Collect a birth year instead of a full date of birth' (#153) from feature/147-birth-year into main
CI / Tests (PHP 8.2) (push) Successful in 50s
CI / Tests (PHP 8.1) (push) Successful in 52s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m47s
CI / Build Plugin Zip (push) Successful in 2m35s
CI / Coding Standards (push) Successful in 2m58s
CI / Tests (PHP 8.3) (push) Successful in 2m44s
Reviewed-on: #153
2026-07-29 23:53:37 +00:00
thatguygriffandClaude Opus 5 7e2bba79fe Collect a birth year instead of a full date of birth
CI / Tests (PHP 8.1) (pull_request) Successful in 43s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 50s
CI / PHPStan (pull_request) Successful in 2m55s
CI / Coding Standards (pull_request) Successful in 3m0s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m40s
CI / Build Plugin Zip (pull_request) Skipped
Signup and the profile page now ask for a four-digit year between 1900 and
the current year. Anything else — a short year, a full date, a year in the
future — is discarded rather than stored, so a typo cannot leave a nonsense
age on the record.

The year lives in a new us_birth_year user meta rather than reusing
us_date_of_birth, which would have left one key holding two formats. The old
key is not migrated in bulk. Instead GuardianService handles it in two
halves: birthYear() falls back to the year of the old date when the new key
is absent, so a student added before this change still shows one, and
setBirthYear() deletes the old date on every save.

That deletion is what makes the fallback safe rather than merely tidy.
Without it, clearing the birth year on a student who predates the change
would leave the old date behind for the fallback to read straight back, and
the year could never be cleared at all.

Stored in user meta, so no Schema.php change and no USC_VERSION bump.

Closes #147

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-29 20:47:56 -03:00
thatguygriff 3a83decc82 Merge pull request 'Say "student" and "profile" in the UI, not "child" and "family"' (#152) from refactor/144-student-profile-copy into main
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.1) (push) Successful in 52s
CI / Tests (PHP 8.2) (push) Successful in 51s
CI / PHPStan (push) Successful in 2m47s
CI / Coding Standards (push) Successful in 2m54s
CI / Tests (PHP 8.3) (push) Successful in 2m39s
CI / Build Plugin Zip (push) Successful in 2m50s
Reviewed-on: #152
2026-07-29 23:41:55 +00:00
thatguygriffandClaude Opus 5 76caf178f0 Say "student" and "profile" in the UI, not "child" and "family"
CI / Tests (PHP 8.1) (pull_request) Successful in 41s
CI / Tests (PHP 8.2) (pull_request) Successful in 40s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m55s
CI / PHPStan (pull_request) Successful in 3m1s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m43s
CI / Build Plugin Zip (pull_request) Skipped
Sweep the translatable strings across the frontend templates, the admin
screens, the editor previews and the block inserter entry. Nothing else
moves: the database columns, request parameters, form field names, CSS
classes, the us_family shortcode and the us-scheduler/family block name are
contracts with existing installs and with post content people have already
saved, so renaming them would break sites for no user-visible gain.

Two strings are reworded rather than swapped, because the direct
substitution reads wrong:

- The students list said "Child of Jane" and now says "Managed by Jane".
  "Student of Jane" would read as a teacher's pupil, which is exactly the
  wrong idea in a music studio.
- A managed account is now "a managed student account" rather than "a
  student account", which would not distinguish it from the account holder.

The guardian feature doc gains a short section on the split, so the next
person to work on it does not read the mismatch as drift and "fix" it.

Closes #144

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-29 20:34:55 -03:00
thatguygriff 8013d05d68 Merge pull request 'Stop upcoming lesson rows rendering on top of each other' (#151) from fix/149-upcoming-lesson-overlap into main
CI / Coding Standards (push) Successful in 2m56s
CI / Tests (PHP 8.1) (push) Successful in 51s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.2) (push) Successful in 43s
CI / PHPStan (push) Successful in 2m50s
CI / Tests (PHP 8.3) (push) Successful in 2m40s
CI / Build Plugin Zip (push) Successful in 2m51s
Reviewed-on: #151
2026-07-29 23:29:18 +00:00