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
- First version
- Mockup-first rule
- Live; her first changes without me
- Her first requests live
- Six weeks: she runs it alone
Chapters
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
- Schedule blockWeek grid or day list, per course, teacher and location
- Teacher gridAnd course-teacher cards
- Course panelPrices, sign-up, trial lesson, waitlist
- JSON-LDCourse, Event and Person schema from the same records
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.
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 job | What the plugin does | Benefit to her | Live since |
|---|---|---|---|
| A place opens up in a full class | Everyone on that date's waiting list gets a mail at once; whoever books first gets the place | No ringing round a waiting list | At launch · |
| A teacher is ill | One 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 them | One click instead of ringing round everyone booked | At launch · |
| A class fills up | Places are counted per date. "Only a few places", "Fully booked" and the booking deadline switch themselves on every page | No overbooking; no "is there space?" calls | At launch · |
| Personal data she should not keep | Names, e-mail addresses and phone numbers delete themselves 90 days after the date, 97 at most; only the count stays | Deletion happens without her remembering | At launch · |
| A price changes | One price table. Every page and every sentence that shows a price follows it | One edit changes the price on all 37 pages that show it | At launch · |
| Holidays and closures | A closure period blocks bookings on those dates and flags the ones already made. A notice banner appears and expires on the dates she sets | No bookings for closed days; nothing to refund | 1.11.19 · |
| Sign-ups into her customer software | A list of every sign-up, newest first, and a spreadsheet export of everything since the last export. Her request, ruled 25 Sep | Export instead of retyping names | 1.11.23 · |
| Too many trial lessons in one class | A separate cap on trial lessons per date. When it is reached, the class page offers a regular sign-up instead. Her request, ruled 30 Sep | Regulars keep their places | 1.11.25 · |
| A sign-up removed by mistake | Removing 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 meanwhile | Undo, instead of asking the parent to book again | 1.11.34 · |
| A parent wants to try one class first | The 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 mockup | One button, one price, no explaining | 1.11.35 · |
| A one-off workshop | A 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 mockups | Same booking path as classes; no separate form | 1.11.47 · |
| How many people signed up last month | Monthly 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 Oct | Trends survive the deletion of the names | 1.11.52 · |
| Where personal data sits | One 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 mockup | One screen answers "what do you store about my child?" | 1.11.53 · |
“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, KayJaysTranslated from German

What it does for search and AI answers.
The same records that run the studio also feed search engines and assistants. Nothing here is claimed as a ranking or citation effect yet; that measurement is in section 08.
| Feature | What it does | Why it matters | Live since |
|---|---|---|---|
| One source for text and structured data | Course, class, workshop and teacher facts render the visible page and the JSON-LD from the same record. A changed time or teacher changes both at once | Schema that contradicts the page is the commonest way to lose rich results and to feed an AI answer a wrong fact. Here it cannot drift | At launch · |
| Prices inside answers | One price table; every "Was kostet …?" FAQ entry and every inline mention renders from it | The highest-intent question on a course page gets an exact, current answer instead of "ab …" or a stale figure. 37 pages carry it | At launch · |
| Live availability in the text | "Fully booked", "few places" and the booking deadline switch themselves | Fresh, specific state signals; nothing on the page says "contact us for availability" | At launch · |
| Booking as a measured event | When the setting is on, each sign-up, trial lesson and waiting-list entry sends its own GA4 event with class, date and location; no names, e-mail addresses or class code. Off by default. The Zahlen screen keeps monthly counts per class (1.11.52, 21 Oct) | Organic traffic can be tied to bookings per class, not sessions, so the SEO work is judged on sign-ups | 1.11.25 · |
| Workshops as Events | A dated workshop emits an EducationEvent with start, end and place, and appears on the schedule and its style page | Dated events are the one schema Google still surfaces as a rich result for small local businesses; the plugin writes it from the record, nobody hand-codes dates | 1.11.47 · |
| Level and age as data | Level on the course, age range on the class, both in CourseInstance | Audience is the filter people and assistants use; "ab 10 Jahren" as data beats "Teens" in a title | 1.12.0 · |
| Answer-first fact strip | A course page opens with up to six labelled facts, such as Wann / Für wen / Dauer / Preis, kept as fields on the course record and rendered by the plugin. Live on one course page (Dancehall) as the pilot; the other courses move once it is checked | The four facts a "hip hop kurs münchen erwachsene" query wants sit in the first screen as one labelled list, which is what AI Overviews and assistants lift | 1.14.0 · |
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.




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.
- Spec with rulings
- Build
- Independent review
- Zip
- Design audit
- Database backup
- Install on staging
- My read-back of every changed screen
- Live
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.
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.


