Skip to content

One record, every surface.

A scheduling plugin for small studios: a class is entered once and drives the timetable, teacher cards, course panel, sign-ups and structured data. Built so a Munich dance school's owner can run her own website without touching a page.

Built
Four record types, four public surfaces, sign-ups with a trial-lesson path, cancellation mail, Course, Event and Person JSON-LD from the same records as the display.
Method
Participatory design: one clickable mockup per screen before any spec, tested by the owner. AI wrote the code from my specs; a second model reviewed it; I verified every version on staging.
Proven once
On launch day the owner opened every class for sign-up and set the capacities herself. Versions 1.11.18 to 1.14.0 have gone live since. She has run the schedule and the sign-ups alone the whole time; I ship versions. Nine requests from her so far: eight live, one declined with the reason.

Headline result

21
Sign-ups across 9 classes through the public forms, first two days live
Anmeldungen screen · 24.09.2026

WordPress plugin · designed and owned by meLive on kayjays.de since 23.09.2026

  1. First version
  2. Mockup-first rule
  3. Live; her first changes without me
  4. Her first requests live
  5. Six weeks: she runs it alone
0
Class facts typed on a page, across 27 classes, 12 teachers and 8 course types
Staging renders · Sep 2026
9
Defects caught in design review before code existed
Release records
10 → 3
Simultaneous requests for three places, exactly three bookings
Acceptance test
Chapters
  1. What it is
  2. What it does for her
  3. For search and AI
  4. How it was decided
  5. How it was built
  6. Deliberately not built
  7. At launch
  8. What is next
  9. Sources

Teacher, course and time live in the record, never on a page.

That is the governing rule, verbatim. The old site kept its timetable as one image and its teachers as hand-typed pages. Every change meant three edits and a chance to miss one. The plugin holds four record types (course, class, teacher, location) and renders everything a visitor sees from them. The page itself carries no facts.

One record, four surfaces

Source recordClass · teacher · location · priceEntered once in the plugin backend. Nothing downstream is typed by hand.
  1. Schedule blockWeek grid or day list, per course, teacher and location
  2. Teacher gridAnd course-teacher cards
  3. Course panelPrices, sign-up, trial lesson, waitlist
  4. JSON-LDCourse, Event and Person schema from the same records
27 classes a week, 12 teachers, 8 course types. Staging renders, September 2026.

A class time or an address lives on the timetable, a teacher card, the course panel and in the JSON-LD. Copies kept by hand drift; here all four are rendered from one record, so there is nothing to drift. What the records emit for search is listed under the jobs table.

The old KayJays weekly timetable, published as a single image
Before · the timetable as one image
The week grid rendered by the plugin: five day columns of class cards with time, age, teacher, duration and booking buttons
After · the same week, rendered from records

Thirteen jobs the plugin does for her now.

A schedule that renders itself is half the point. The other half is the work around a class that the owner did by hand: telling families a place opened up, telling everyone a date is off, counting who is coming, deleting what she should not keep. These are the jobs the plugin does on kayjays.de today, in the order they went live. Only live features are listed.

Her jobWhat the plugin doesBenefit to herLive since
A place opens up in a full classEveryone on that date's waiting list gets a mail at once; whoever books first gets the placeNo ringing round a waiting listAt launch · 23 Sep
A teacher is illOne click cancels the date and mails everyone booked on it. If the date is reinstated, a "takes place after all" mail follows. A bounced mail names the person so she can phone themOne click instead of ringing round everyone bookedAt launch · 23 Sep
A class fills upPlaces are counted per date. "Only a few places", "Fully booked" and the booking deadline switch themselves on every pageNo overbooking; no "is there space?" callsAt launch · 23 Sep
Personal data she should not keepNames, e-mail addresses and phone numbers delete themselves 90 days after the date, 97 at most; only the count staysDeletion happens without her rememberingAt launch · 23 Sep
A price changesOne price table. Every page and every sentence that shows a price follows itOne edit changes the price on all 37 pages that show itAt launch · 23 Sep
Holidays and closuresA closure period blocks bookings on those dates and flags the ones already made. A notice banner appears and expires on the dates she setsNo bookings for closed days; nothing to refund1.11.19 · 24 Sep
Sign-ups into her customer softwareA list of every sign-up, newest first, and a spreadsheet export of everything since the last export. Her request, ruled 25 SepExport instead of retyping names1.11.23 · 28 Sep
Too many trial lessons in one classA separate cap on trial lessons per date. When it is reached, the class page offers a regular sign-up instead. Her request, ruled 30 SepRegulars keep their places1.11.25 · 1 Oct
A sign-up removed by mistakeRemoving takes one confirmation in the list; the entry waits 14 days under "Recently deleted" and can be restored, unless the place has gone to someone else meanwhileUndo, instead of asking the parent to book again1.11.34 · 3 Oct
A parent wants to try one class firstThe trial-lesson page offers one 9 € trial button per class and nothing else; a full class shows "Fully booked" and no button. Ruled 2 and 3 Oct from a mockupOne button, one price, no explaining1.11.35 · 4 Oct
A one-off workshopA dated workshop sits in the weekly schedule and on its style's course page, takes bookings like a class, and is listed on one workshop page. Each one is an Event in the structured data. Her request, ruled 7 and 11 Oct from mockupsSame booking path as classes; no separate form1.11.47 · 12 Oct
How many people signed up last monthMonthly counts of sign-ups, trial lessons, workshops and cancellations per class, kept as numbers after the names are gone, with a spreadsheet export. No names leave the plugin. Ruled 15 OctTrends survive the deletion of the names1.11.52 · 21 Oct
Where personal data sitsOne read-only screen lists every place the site stores personal data: sign-ups, waiting list, forms, mail log, newsletter, server backups, with who deletes it and when. A backup older than 30 days turns red. Ruled 21 Oct from a mockupOne screen answers "what do you store about my child?"1.11.53 · 26 Oct

“What's especially practical is that I can update the schedule, changes or new classes myself, any time, without any hassle. My customers can sign up for the classes they want directly on the website. That saves me an incredible amount of time, and above all a lot of email writing.”

Jessy, owner of KayJaysJessy, owner, KayJaysTranslated from German
Mockup of the Zuletzt gelöscht tab: a removed booking whose restore is refused because the date is now full, with the reason and a next step, and a second removed booking that can be restored
Removing a sign-up, built as 1.11.30 and live since 3 Oct. Two people on the waiting list were mailed when the place opened, so the removal cannot be undone onto a full date; the screen says why and what to do instead. Mockup with invented names.
The plugin's Anmeldungen und Termine screen in wp-admin as of 1.11.34: tabs Nach Wochentag, Liste und Export, Warteliste with a count of 2, Zuletzt gelöscht; two people waiting for Thursday 8 October with e-mail, sign-up time and a remove link
The waiting-list tab as it is in 1.11.34. I could not find who was waiting, so the list got its own tab with a count (ruled 28 Sep, live 1 Oct); the remove link and the "Recently deleted" tab beside it came with 1.11.30. Mockup with invented names; the live screen holds real families.
Mockup of the public trial-lesson page with the style filter on the left, Hip-Hop selected: twelve classes across the week, each with one button, Probestunde 9 Euro; badges for booking deadlines and few places; a full class shows Ausgebucht and no button
The trial-lesson page as it went live in 1.11.35: style and day filters, one button per class, "Bookable until" on the deadline, no button on a full class. Mockup with invented teacher names.

The screen is drawn before the spec is written.

From 8 September, nothing went to build without one clickable mockup of the whole screen it touches, drawn with real staging data and every open request on it at once. The mockup measures itself (widths, tap heights, line breaks) and the decision is taken on the page. Only then is the spec written.

Both timetable layouts side by side at 375 px: the week grid scrolls 646 px sideways and shows 1.7 of 5 days; the day list shows all five. Controls under 44 px are outlined with their height
The public timetable, 11 Sep. Same 27 classes in both frames; the page measured the difference itself: 1.7 of 5 days visible in the grid, 646 px of sideways scroll, every control under 44 px outlined. Ruled 12 Sep: day list below 1120 px. Built 24 September as 1.11.21, twelve days after the ruling, live on kayjays.de by 28 September; why it waited is in the detail of the next chapter.
Mockup of the public schedule row with four ruling cards: deadline line, two buttons, tap size, duplicated age
The schedule row, 18 Sep. Two rules contradicted each other on the deadline line; the mockup measured both (16 vs 12 px, 10 of 10 rows wrapping) and the ruling took ten minutes. Shipped 22 Sep as 1.11.16.
Mockup of the cancellation flow in wp-admin at step 4: one mail bounced, the notice names the person, the mail preview beside it
Cancelling a date, 21 Sep. Five clickable steps in the real wp-admin screen, the mail drawn beside it, five open questions answered by looking. Ruled: no automatic retry; a bounced mail names the person so she can phone them.
Mockup of the wp-admin Anmeldungen screen, variant B: bookings and trial lessons counted separately
The owner's sign-up screen, 17 Sep, in WordPress's own skin because she already knows WordPress. A is the screen as it was; B separates trial lessons from bookings, which A silently added together.

Tested with the person who will use it.

The mockups decide how a screen looks; the owner decides whether it works. I sat next to her while she used the backend without instructions, noted where she hesitated and what she reached for, and asked what she wanted it to do next. Her requests are on the backlog in her words, not mine.

The dance school owner, on staging · 18 Sep 2026

She completed every task without help. Then she asked what happens when a teacher is ill: the date could be cancelled, but nobody booked on it was told. The mail had been on a to-do list since early September and never built. She caught it; it became the cancellation mail, built as 1.11.17 and live at the cutover.

The art school owner, on the backend tour · 18 Sep 2026

Her courses run by semester, so a waiting list there is for a place in the course, not a date. The waitlist storage was changed before it shipped to accept an entry without a date, with a test proving it. Her other asks (who has stopped coming, whether it holds 700 students, a grid for five locations) were ruled into scope or out of it, with the reason, and queued for her rollout.

The detail: what a mockup has to carry, and what it refuses

One per surface, never one per request. A screen collects every open request that touches it (the schedule row carried six), so a decision on one is taken knowing the others. The inspector side is drawn as Gutenberg draws it, same controls, same German labels, so the owner can tell the mockup from the editor.

Real data, measured on the page. Class names, times, teachers and booking states are read from staging, not retyped. Widths, heights and line counts are computed live in the mockup, so a "fits / wraps" verdict is a measurement, not an opinion.

A "not on this surface" list. Every mockup says what it deliberately leaves out and why: the class code stays in wp-admin, cancellations stay off the week row, the mobile accordion is its own surface after launch. The list stops a spec from growing on the way to the build.

Audited before it is shown. Each mockup gets a design audit (accessibility, responsiveness, theming, implementation integrity) before it is published; the audit report travels with it. The mockup's CSS is a decision aid. The spec wins on any value that was not ruled, and records the difference.

AI wrote the PHP. I own everything it was written from.

The code was generated by AI models from my specs, in a fixed seat order: architect, plan reviewer, adversary, builder, reviewer. Builder and reviewer are always different models, and every fix round gets an independent review.

Mine

Record types and what each field means · every ruling, recorded with the date · the mockups · the "deliberately not built" list · the acceptance tests · what counts as done · the staging verification of every version · the cutover.

The models'

The PHP, JS and CSS · migrations under a version number I assign · the plan review and the adversarial pass against each spec · the code review of each build · the test suite, about 1,400 tests, run on PHP 7.4 and 8.3 before every release · a security re-audit by an outside model in October, fixes shipped as 1.11.51.

The gate a version passes

In this order, every version. Filled steps are mine.

  1. Spec with rulings
  2. Build
  3. Independent review
  4. Zip
  5. Design audit
  6. Database backup
  7. Install on staging
  8. My read-back of every changed screen
  9. Live
A version that changes stored data answers four questions first: what if it fails halfway, what is deliberately not built, who does what on ship day, how it is undone and what that loses. So far: 6 schema versions, each with a backup and a rehearsed restore; 48 versions built since launch, shipped to the live site in 17 recorded installs.
The detail: the failures that shaped the process

The silent data-loss bug. Quick Edit on a teacher wrote its id as 0 and the class lost its teacher without an error. The fix touched the exact code path that failed, so it did not ship the night it was written: all acceptance tests were re-run the next morning first.

The wrong reviewer claim. A review reported the test site's sign-up tables missing. It had checked plural table names; the plugin's are singular. Since then a claim about the database is verified with a query before it goes into a spec, and a reviewer is never the model that built the thing.

The ruling that was not carried. A mobile layout for the timetable was ruled on 12 September, given a version number and left unbuilt for twelve days; the ruling lived in a document nobody re-read. It shipped as 1.11.21 on 24 September. Rule since: a reserved version with a parked spec gets one line on the active backlog the same day.

Code literacy, still being built. I specify, verify and operate the plugin; reading its PHP fluently is a skill I am building on my own codebase rather than assuming. Until then every version's report has to prove itself on the screen, which is a better rule anyway.

The plugin generates facts, never prose.

Judged as much by what it refuses as by what it does. Each of these was ruled out with its reason, so a future request meets a decision rather than a gap.

  • Payments, SEPA, member contracts. Liability the owner does not want on her website. Contracts may come later; money never.
  • Participant records, attendance, audit log. A membership system is a different product. Sign-ups are kept 90 days and deleted outright.
  • Newsletter sending. The plugin feeds the mail provider's list; it does not send campaigns. One less thing to secure.
  • Its own admin design. The owner already knows WordPress. Admin screens look like WordPress; the public side never overrides the theme's type, colour or spacing.
  • Automatic retries for failed mail. A bounced cancellation mail names the person instead. She phones them. Simpler, and it fails visibly.

Built in because most of the students are children.

Collect as little as possible, keep it no longer than needed, and make every step that touches personal data a visible action rather than a side effect.

  • Sign-up names and addresses delete themselves a set number of days after the date; only the count remains.
  • The attendance sheet left in the studio prints shortened names, not a list of children's full names.
  • No external fonts or requests from any block unless the site owner switches them on.
  • "Delete one person's data" removes every sign-up tied to an address, in one step.
  • Teachers get a signed link, not an account; renewing the secret revokes every link at once.

Mechanisms, not compliance. The legal basis, the privacy notice and the processing agreements stay with the school.

What is true at launch.

Measured on 23 September 2026. The plugin's claim is that a studio owner can change a class, a teacher or a price without editing a page. That day the owner opened every class for sign-up and set the capacities herself, without me in the room, then asked the parents to book through the public forms as a test. What she asked for afterwards (a cancel function, a phone number on trial-lesson bookings) went onto the backlog as rows, not as chat. A removal function for sign-ups was built as 1.11.30 (2 October) and is live since 3 October.

15 mincutover, rehearsed twice, rolled back never
52 → 0open pre-publish checks across 45 pages
26redirects, every sampled one a single hop
23 Sepfirst changes the owner made without me

The last figure is the one that matters. Search results for the relaunch are on the kayjays.de case study, with the 2025 baseline and readouts on 4 November and 16 December. Nothing about traffic is claimed for the plugin. What the plugin adds on its own is being tested separately: its schedule block goes onto other dance schools' sites one change at a time, with kayjays.de left untouched as the reference.

What I would change, and what is next.

  • Mockups from day one. The first eleven versions were specced from text. The mockup-first rule arrived on 8 September and the defect rate per version fell after it; I would not build without it again.
  • Teacher pages from the record. Twelve teacher pages were hand-built for launch because the generator was not ready. They are the last hand-typed facts on the site and the first thing to move this week.
  • Course pages from the record. Nine course pages still carry hand-typed text: intro, what the style is, FAQ. That text moves into course fields next, so the last prose on the site has one source too. Ruled 2 November from a mockup of the live page.
  • Measuring the plugin on its own. The relaunch and the plugin went live on the same day, so kayjays.de cannot say what the plugin alone did for search. The test is a staggered rollout on other schools' sites, three changes six weeks apart: schedule block, answer-first course pages, generated teacher pages.

Sources and limits.

Measurements in this study come from the plugin's own release records, staging verification logs and the cutover log of 23 September 2026. Counts (27 classes, 12 teachers, 45 pages, 26 redirects) were read from the site on the dates given. The sign-up count was read from the plugin's Anmeldungen screen on the evening of 24 September. The concurrency figure is from the plugin's acceptance test. Versions and live dates in the timeline and the ledger come from the release records and the idea board, re-counted on the publish date. Screenshots marked as mockups use invented names. Nothing here is estimated.