Before we write a line of an app for a personal trainer, we spent a day deciding what already exists and what has to be new. This is the log of that day, because most of the useful decisions were made by arguing with ourselves.
The trainer is a real first client. The app is not built yet. What is built, and tested, is the foundation underneath it: three small packs that any business dealing with customers would need.
Step one: survey what we already have
RapScalYon apps are assembled from packs, each one an optional, removable feature with its own tables, security rules, tests and a rollback. So the first move was to read every pack we have and every app we have built, and write down what a trainer needs that is missing.
Two gaps were obvious: there was no calendar, and no consistent way to answer the question every messaging feature asks first, which is whether we are allowed to contact this person at all.
Step two: a challenge about consent
I asked why consent should be a special pack at all. It is a table. The honest answer was that it is separate for two reasons only. Every feature must ask the same question, so one shared check beats a flag in each feature. And any feature that sends something has to be able to ask it without depending on a private roster of clients.
Looking closely turned up a real flaw in the first design: consent was keyed by a free-text phone number or email, so the same person could appear three ways. The fix was a separate base pack, people, with one row per person the business deals with, and consent pointing at it. The normalised phone or email stays on the consent as well, because an inbound STOP message knows only a phone number.
Consent is an append-only ledger. You can add rows and read them, never edit or delete. Withdrawing consent is a new row, and the latest row wins.
Step three: no special type for staff
My next idea was a type of person that reports to someone, for clients who also get a login. We rejected it. A login is just an optional link from the person to a user account, set only through a single-use invitation. Who looks after a client is one column. Staff access already exists in the core. And the behaviour I actually wanted, a client who signs in sees only their own records, turned out to be one rule that other packs can reuse: staff see everything in the business, a person sees the row that is theirs.
Step four: what if they already use a CRM?
I asked whether the people pack could simply be a stub for an outside CRM when a business has one. In effect yes, but not as a column that exists only for some customers. A pack must have the same schema everywhere, so there is one migration, one rollback, one test suite and one export for a buyer. So the people pack always carries a small generic table of references to outside systems. A business with a CRM gets rows in it, written by a separate connector pack. A business without one has none. The data stays ours and the CRM holds a copy. (Since reversed: the master contact record stays in the CRM.)
What got built
Three packs: people, consents and calendar. Calendar uses an open-source calendar component, loaded only on its own page. Other features feed it events through a service-only function, so it never reads another pack's tables. Consents and calendar can point at a person, but if the person is erased the consent or event stays.
They send nothing, know no messaging provider and hold no payment data. Sending, syncing and anything specific to an industry are other packs that sit on top of these.
Afterwards: noticing who is drifting
You write rules like "3 missed meals in a rolling week", "4 missed events of any kind in a month", or a quincena (15 day) version. An evaluation function raises a flag for each person who crosses a rule. The flag stores the count and the events behind it.
A cooldown, 3 days by default, stops the same person being flagged twice in a row. Staff can snooze or resolve flags. Only the database function can create a flag or change its counts and evidence. It needs the people and calendar packs, and it sends nothing itself.
The test suite passed on its first run. It checks that people with too few misses are not flagged, that the cooldown works, and that users cannot forge flags. Other users see nothing, and anonymous access is denied. The packs were removed from the local database afterwards.
What is verified and what is not
Verified on 2026-10-12: each pack installs, passes its tests and removes without leaving anything behind; the full test suite passes with all three installed; the app type-checks and builds.
Not verified: the calendar page in a browser with real data on a phone; anything against a hosted project. Not built: a page for managing people, and the messaging itself, the sync with an outside CRM, and a schedule that runs the check described above. The consent design has not had a legal review, and rules about texting clients differ by country, so treat it as engineering, not legal advice.
Why write this down
The next step is twenty interviews with trainers, to learn which of the planned features they would actually pay for. If the interviews say the plan is wrong, the three packs still stand, because none of them knows anything about training. That was the point of the day.
If you want an app built like this for your own business, book a build.