Estudio Shine, Systems Architecture

Operations architecture · Barcelona

A music school that runs on software it owns.

Three businesses share one platform: a school, a rehearsal-room rental, and an instrument rental. One control panel is the situation room; a phone app is the key to the buildings; the public sites, the forms, the payments and the door hardware all hang off the same core. This is how the pieces connect, and what is being taken out next.

Rule zero

Access is key and the key is access. The app's only load-bearing job is opening a door. Bookings, profiles, packs and languages are all decoration on top of it. Every architectural decision below is downstream of that sentence.

3 businesses 1 platform 21 panel pages 394 endpoints 130 schema migrations 5 languages at the edge

The shopfronts

Four public faces, one platform behind them

Three businesses and a shop, each with its own site and its own customers, all pricing from the same catalogue and all writing into the same records. Someone who rents a room, hires a cello and enrols a child is one account, not three strangers who share an email address.

Also live, deliberately quiet: a frozen static host still serving the internal teacher form, and a South African site that stays on the old form vendor until it is rebuilt.

The map 01

Everything, on one canvas

Drag to pan, scroll or pinch to zoom, click any box to read what it is. The Planned layer ghosts in the parts that do not exist yet, so you can see the shape the system is growing into rather than just where it stands.

Drag to pan · scroll to zoom
Teal what Shine built and runs
Amber access: the app, the grants, the hardware
Blue the outside world: web, payments, Google
Rose switched off, or being switched off
Violet planned, not built

Access 02

The door is the product. Everything else is a feature.

Teachers and renters get into the buildings with their phone. If that fails, nothing else about the app matters, so the access path is engineered to keep working when the network, the office and the cloud are all having a bad day.

A door opening, as it happens

This runs on a loop below. It is the real sequence, at roughly real speed: a teacher taps Open, the phone reaches the controller directly over Bluetooth, the relay fires, the strike releases, and the door relocks itself a few seconds later without anyone doing anything. No part of this touches the internet.

Phone tap Open Bluetooth · no network Door controller at the door relay opens, then relocks itself

Reachable

the server can see it

The controller checked in recently over the building network.

Can open

a teacher can actually get in

The controller says it is discoverable right now. This is the one that matters.

Both, separately

reachable, but dark
silent, but opening fine

Inside three days the panel showed both. They are independent facts and are now measured as two.

Illustrative, not a live feed: this page reads nothing from the school.

A booking becomes a key

The other half of access is not a person the school knows. Someone books a room from the website at midnight, and the building has to let them in on Saturday morning without anybody meeting them there.

customer

Books and pays

Website or app, priced by the server.
webhook

Booking recorded

Lands on the office board as a card.
office

Filed to a room

A human picks the building and the room.
automatic

Grant minted

Their account may open that door, for that slot only.
saturday

The door opens

They tap Open. Nobody had to be there.
after

Grant expires

Move the booking and it is torn down and re-issued.

The shape of the network underneath

Bluetooth is the real path. The phone talks straight to the controller at the door, so an unlock needs no internet, no office, and no working WiFi at the building. The panel can also queue a remote open, which the controller collects on its next outbound check-in.

Phone teacher / renter Door controller at the door Control panel Amsterdam unlocks over Bluetooth no network needed checks in, outbound only office queues a remote open collected on next check-in Office
The controllers never accept a connection from the internet. They reach out, report what happened, and take instructions back — so there is no inbound door port to find.

What the design has to survive

offline is not locked out

A door with no internet still opens. Unlocks queue in the controller's memory and flush when it reconnects, so the log catches up even though entry never depended on it.

reachable ≠ usable

"The server can reach this door" and "a teacher can get in" are separate facts and are now measured separately. The controller reports whether it is actually discoverable, not just whether it is online.

never a dead end

A failed unlock says which thing failed and what to do next, in the reader's own language. A generic "could not open" turned a two-second diagnosis into a two-day one, once.

login is part of access

Someone who cannot log in cannot open a door, so sign-in is held to the same standard as the lock itself. Session state is shared, so either server can answer.

Sign-in
Six-digit email codeno password, long-lived session
Authorisation
Time-bounded grantsseparate from the login itself
Hardware
Custom controllersown firmware, no lock vendor
Buildings
Two independent networksplus one site never fitted
App
iOS and Androidone codebase, five languages
Booking link
Filing a booking grants the doorrevoked when it moves

A room booking is not finished when it is paid. It is finished when the customer can physically get in — so the booking pipeline ends at the door, not at the receipt.

Control panel 03

The situation room

One web app that the whole staff lives in. Its design brief is momentum: show the one thing to do next, then the next, and never leave someone staring at a card with no obvious move. It reshapes itself around whoever signed in.

Backend
47 routers · 394 endpointsPython, async, one process
Frontend
21 pages · 54 componentsserved from the same origin
Roles
Seven, fenced in middlewarea role cannot widen by accident
Tests
200 test files, three enginesrun against the real stack too

What the staff actually work

daily board

Panel

Signups, bookings, the lifecycle lane, follow-ups and each person's own queue.

rooms

Reservas de sala

Booking cards. File one and the door grant fires for that building.

intake

Formularios

Every public form submission, as a sortable table with charts.

money in

Domiciliaciones

Direct-debit mandates and the monthly run that becomes the bank file.

matching

Profesores

Roster, instruments, availability, and each teacher's private form link.

work

Tareas

The task board: threads, attachments, hand-offs, snoozes.

access

Puertas

Who may open what, when, and the audit of every entry.

rental

Alquiler Exprés

Walk-in instrument rentals, priced from the shop catalogue.

rental

Alquileres largo plazo

Long-term contracts, month by month, off the spreadsheet.

field

Tareas de Alquiler

Delivery and collection agenda, run one-handed from a van.

field

Calculador de Entregas

Route, tolls and live diesel price for a delivery quote.

money out

Cobros

The debt ladder, with invoice drafts and one-tap messaging.

lifecycle

Gestión

Pauses, withdrawals, teacher changes, and the return-to-lessons chase.

classes

Combos y Grupos

Group classes and combos with fill and gap signals.

exams

Exámenes

External music-exam candidates, tracked step by step.

config

Catálogo · Tarifas

Courses, instruments and room rates: the one place a price lives.

insight

Estadísticas

Intake, conversion, retention and where students are lost.

plumbing

Sincronización

Imports, reconciliations and the manual levers behind them.

Running while nobody is looking

every 60s

Intake sync

Pulls signups, vouchers, bookings and payments in, and retries any confirmation email that did not land.

nightly

Lifecycle crons

Resurfaces pauses due back, chases returns, ages exam records, files what has gone quiet.

every 30m

Fuel prices

Caches the national diesel price so a delivery quote never waits on a government server.

nightly

Off-site backup

A full database dump leaves the platform every night and is verified on arrival.

What it actually looks like

These are the real screens, photographed from a running copy of the panel. Every person, family, mandate and booking in them was invented for this page.

The Panel screen of the Shine control panel, showing invented demonstration data.
PanelThe daily board. Lane counts across the top, then whoever is logged in gets one card: the single next thing to do, the reason it surfaced, and the buttons to finish it, postpone it or hand it on.
The Reservas de sala screen of the Shine control panel, showing invented demonstration data.
Reservas de salaEvery room booking in one place regardless of how it arrived: website, app, the office, or cash at the desk. Filing one is what grants the door for that slot.
The Formularios screen of the Shine control panel, showing invented demonstration data.
FormulariosThe submission journal. Every public form in one sortable table, with the colour of an instrument coming from the shared catalogue so the same value is the same hue on every screen.
The Domiciliaciones screen of the Shine control panel, showing invented demonstration data.
DomiciliacionesThe direct-debit book and the monthly run that becomes the bank file. Note the IBAN column: it never shows more than the country and a mask, on any screen, to anyone.
The Profesores screen of the Shine control panel, showing invented demonstration data.
ProfesoresThe teacher roster: instruments, levels, availability, and the private link each teacher uses to update their own hours.
The Puertas screen of the Shine control panel, showing invented demonstration data.
PuertasAccess. Which doors exist, who may open them, standing grants against a booking's two-hour window, and the log of every entry.

None of this is real data. The screenshots come from a throwaway copy of the system, seeded with invented people and cut off from the network, so no student, teacher, family or bank detail could reach this page even by accident. The interface is exactly the production one.

Two servers run at once, but only one may run the background work. They settle it with a lock held in the database, so a deploy hands over cleanly and split-brain is structurally impossible.

Data 04

Append, derive, never clobber

The database is one schema, built by 130 numbered migrations that are only ever added to. The shape of it is deliberate: durable identities at the bottom, an immutable event log above them, and current state calculated rather than stored.

Account usually a family Persons Roles Lifecycle events typed · append-only signed up placed with a teacher paused · resumed · paid Derived state active / paused / withdrawn computed, never stored
Nothing is edited in place, so two people acting at once cannot overwrite each other's version of the truth. History is the record; status is a calculation over it.

Rules that hold the schema together

migrations

A change is a new numbered step, never an edit to an old one. A machine-generated copy of the resulting schema is committed alongside, and the build fails if the two ever disagree.

three test runs

The suite runs on a lightweight engine for speed, then twice more on the real database — once through the exact connection stack production uses. Behaviour that only works on one of them is caught before it ships.

money writes

The connection layer counts commit attempts before sending them, so a request that might already have committed is never retried automatically. Ambiguity surfaces as an error instead of a double charge.

files

Documents, photos and generated PDFs live in object storage, not in the database. Only the metadata is a row.

Network 05

Where every piece physically sits

Four separate environments, joined only by requests that cross them deliberately. The school buildings are their own island: nothing on the internet can reach into them.

HOSTING · AMSTERDAM App server api + panel App server identical Managed Postgres same region, private network Object storage · files & PDFs PUBLIC WEB School & rental websites Edge worker · checkout holds the payment key, the websites never do prices quote SCHOOL BUILDINGS Network A 2 controllers Network B 1 controller outbound only · no open ports THIRD PARTIES Payments Google · sheets, mail Routing & fuel prices Document AI
The app servers and the database sit in the same city on a private network. Everything else is reached by the platform going out to it — and the door hardware only ever dials out, so the buildings expose nothing.

Under the hood

The stack, in detail

Everything below is measured from the running system rather than described from memory. The numbers are the actual constants, timeouts and byte counts in the code. If you only read one thing, read the scars at the end: most of these decisions are somebody's bad night, written down.

The door controller

ESP32-S3 · C++ · three in the field

Custom firmware on a relay board at each door. It has one job, and the whole boot order is arranged around it: the Bluetooth radio comes up before the network, because Bluetooth is the only thing that physically opens the door and it must never wait on a router.

Boot order
serial → random boot id → relay driven locked → BLE up → WiFi → HTTP → OTA
WiFi wait
bounded at ~15s, then continues without a network. It never reboots on failure
Reconnect
backoff 5s doubling to a 60s ceiling · 12 scans an hour instead of 720
Check-in
outbound every 3,000 ms, 4,000 ms timeout, carrying telemetry on the query string
Relay
duration clamped to 300–15,000 ms; auto-relock is a hardware one-shot timer and a redundant check in the main loop
Offline log
16-entry ring buffer, 12 bytes each — 192 bytes of RAM — guarded by a spinlock because the radio callback runs on another task
Image
1,324,768 bytes, 67.4% of one of two OTA slots · 54,708 bytes static RAM, 16.7% of budget
Reports
13 fields including ble_advertising — the field that distinguishes "reachable" from "can actually let someone in"

Two details worth the detour. The device has no clock, so a buffered unlock is sent as an age in milliseconds rather than a timestamp, computed with unsigned arithmetic so it survives the counter wrapping, and clamped to an hour so a wrapped value can never claim to be more recent than it is. And the flush is by sequence number, not by clearing the buffer: events queued while the request was in flight survive it, and the server de-duplicates on boot id plus sequence, so a lost acknowledgement replays harmlessly.

The backend

FastAPI · Python 3.12 · 47 routers, 394 endpoints

One uvicorn process per machine, deliberately with no worker flag — which is what makes the rest of the design coherent, because it means exactly one event loop, one connection pool and exactly two leader candidates cluster-wide.

Machines
2, always on, 1 vCPU / 2 GB each · one region, chosen to sit beside the database
Health
every 10s, 5s timeout. It reads a real row from a real table, and publishes the age of the last successful backup
Leader
a Postgres session advisory lock on a dedicated connection outside the pool, because pool recycling would silently drop it
Failover
instant on deploy; on a crash, bounded by TCP keepalive at 30 + 3×10 = 60s. No lease, no clock comparison, no split brain
Boot
pool → advisory lock → migrate → unlock, so two machines starting together cannot race the schema
Loops
five, staggered 10 / 20 / 30 / 45s at startup, four of them leader-gated
Connections
floor 8, ceiling 22, every session capped at a 5s statement timeout and a 2s lock timeout
why the health check reads a row

A bare SELECT 1 needs no table and can be answered from a constant. On one June morning that let a dead database report itself healthy for about forty minutes while every real query failed. The probe now touches storage, and a saturated pool fails it inside the platform's own timeout rather than queueing behind real traffic.

The data layer

Postgres 16 · psycopg3 · 130 migrations

A connection pool with a lease taken per request, and an unusual constraint: the entire codebase was written against sqlite's implicit transaction model, so the layer reproduces those semantics on Postgres rather than rewriting a thousand call sites. Reads run autocommit; the first write opens a transaction.

Pool
min 3, max 10 · 5s checkout · 30 min max lifetime · 10 min max idle
Money safety
commit attempts are counted before the commit is sent, so a request that might have committed is never auto-retried. Ambiguity surfaces as an error, never as a double charge
Migrations
append-only, never edited once applied. A machine-generated copy of the resulting schema is committed beside them and CI fails on drift
Tests
200 files across three engines — fast sqlite, real Postgres, and Postgres through the exact production connection stack

The panel

React 19.2 · Vite 7.3 · Tailwind 4.2

21 pages, of which 9 are lazily loaded — and the split is drawn on role, not on file size. A secretary, a rental driver or a debt chaser should not download code for a page their token can never open.

Payload
12 chunks, 1.97 MB raw. The charting library alone is 32% of all shipped JavaScript and never reaches a non-admin login
Layouts
resolved from the union of roles, not one primary role, so a second grant can never silently narrow somebody's access
Server fence
200 role dependencies across the routers, plus three middlewares that restrict by HTTP method and by path before any route runs
Allowlists
matched on path segments, not string prefixes, so granting one path cannot accidentally grant a longer one that starts the same way
Timeouts
30s globally, 120s for uploads, with three endpoints deliberately exempt because their server work cannot be cancelled
a timeout that refuses to lie

About fifty call sites in the app catch errors with a bare catch that throws the message away and prints a flat "could not save". So a timed-out write does two things: it throws a message saying it may have saved, and it fires a global event — because a catch block can swallow an error, but it cannot swallow an event.

The app

Expo 54 · React Native 0.81 · both stores
Languages
229 keys × 5 languages, typed so that a key missing any one of them fails the build. Not a lint rule, not a runtime fallback
Bluetooth
one radio manager for the process lifetime, a 6s wait for the radio to settle, an 8s scan, a 5s connect
Failure
seven distinct reason codes, each translated into all five languages, plus a diagnostic line a maintainer can read and a teacher can ignore
Releases
a version-code ledger kept by hand, because the store burns a number permanently the moment a build is uploaded
the one-line dependency that broke every launch

Naming an icon package the SDK already bundles hoisted it, its open-ended peer range pulled a far-future version of the font module, and the platform's autolinker constructs every module during startup — so it crashed before a single line of JavaScript ran. No red box, no stack trace, no error. The fix is one pinned line, and the reasoning is written above it so nobody unpins it.

Money

Stripe · SEPA · hand-verified webhooks

There is no payment SDK in the repository. The webhook signature is verified by hand, and the rule after that is absolute: once a signature verifies, the handler may never answer with an error, because a non-2xx makes the payment processor retry and eventually drop a booking somebody genuinely paid for. Anything wrong becomes a flag a human reads, not a rejection.

Pricing
computed in exactly one place. The edge function that takes the payment holds no rates at all and asks the server for the total
Underpayment
recomputed from the signed payload; if it is short, the booking is flagged for a secretary, never refused
Idempotency
keyed on the payment session, enforced twice: a check before insert, and a database constraint that catches a concurrent retry racing past it
Bank details
AES-256-GCM, with the row's own identity bound into the encryption, so a ciphertext cannot be moved to another row or another table
Refunds
exactly one module in the whole repository may issue one, for exactly one case, gated by an allowlist every caller re-checks
Bank file
a byte-level contract reverse-engineered from nine files the bank actually accepted — encoding, line endings, delimiter, decimal comma, quoting
the spreadsheet that could have run code

Names on the bank sheet come from a public form anybody can fill in. Written the default way, a name beginning with = stops being text and becomes a live formula in the file that goes to the bank — capable of pulling in another sheet, or of recalculating an amount. Every write is now forced to raw text. It is one parameter, and it is the difference between a spreadsheet and an execution surface.

A pattern runs through all of it: prefer the failure that a human will notice. Flag the underpaid booking rather than reject it, report "this may have saved" rather than a clean lie, let the audit log lose a row rather than block a door, and never let a green tick mean anything other than the thing actually happened.

Money 06

Two ways in, and the server picks every price

Cards for anything immediate, direct debit for anything monthly. Nothing a browser or a phone says about an amount is trusted: the price is recomputed on the server at the moment of payment, every time.

Card: a room booking, end to end

one source

Rates

Edited once in the panel.
preview

Site & app

Show the price, cannot set it.
authoritative

Re-quote

Server prices it again at pay time.
checkout

Edge worker

The only holder of the payment key.
confirmed

Booking card

Recorded, customer and office emailed.
rule zero

Door grant

Access opens for that slot.

Direct debit, the Spanish way

Most tuition is collected by domiciliación. A customer signs a mandate on a form the platform serves itself; the bank details are encrypted the moment they arrive and are never stored or displayed in the clear. Each month a remesa is prepared, frozen, and handed to the bank as a file in the exact format it accepts.

around 500 mandates

Every run is frozen at the moment it is stamped, with the whole book snapshotted beside it, so any past charge can be reconstructed exactly as it went out.

bounces

A returned debit is a named adjustment line with a reason, not a silently edited amount — and it expires by itself the following month.

Two payment accounts, on purpose

The school and the rental business bank separately, so the platform talks to two payment accounts and never mixes them. Registration fees and gift vouchers land on the school side; room packs and instrument rentals land on the rental side.

refunds

Cancel a room 48 hours ahead and the refund is automatic. Inside 48 hours it can be moved but not cancelled. Pack sessions are never refundable — the room was held either way.

reschedules

A customer asks; a human answers. The office places the new slot in the real calendar, and the door grant is torn down and rebuilt to match. The clock is measured when they asked, never when the office got round to it.

Every price in the school — a lesson, a room, an instrument, a pack — comes from one catalogue. Change it once and the website, the app and the calculator all follow on the next load, with nothing redeployed anywhere.

Forms & paper 07

Every form the public can reach, and the paper each one leaves

These were rented from a form vendor until 2026. They are now first-party: the platform serves the sensitive ones itself, on its own locked-down pages, so a signature and a bank detail never pass through a third party.

school

Student registration

The main intake. Pays the enrolment fee by card, transfer or instant mobile payment.

school

Gift voucher

Buy lessons as a present. Pays first, then the voucher PDF is generated and emailed.

school

Voucher redemption

Turns a paid voucher into a real student record, once, in a single transaction.

self-served

Direct-debit mandate

A private link, not on any website. Signature is a ticked box and a typed name, recorded with the exact terms shown.

self-served

Instrument rental agreement

The full contract, with ID photos and either a card subscription or a debit mandate.

rooms

Room booking & packs

Hourly bookings and monthly practice-room packs, priced live from the panel.

internal

Teacher availability

Reached only by a teacher's own private link — no shared code exists any more.

The journal

Every public submission writes one row to a single journal, inside the same transaction as the thing it created. If the journal write fails, the submission fails: there is no such thing as an action with no record.

What goes into that row is a whitelist, not a copy. Bank details, identity documents, addresses and network fingerprints are never journalled, and a column holding a fragment of one can never be charted — a bar chart of account fragments is still a leak with a bar next to it.

Paper the system produces

PDF

Generated on the server: gift vouchers on the branded background, invoices for the debt ladder, payroll sheets, rental contracts. Rebuilt from the record each time and never stored half-made.

email

Sent from the school's own mailbox. Most go out as a draft for a human to read and send; only receipts, codes and confirmations send themselves. Drafts are idempotent, so pressing the button twice does not send twice.

messaging

WhatsApp is deep links, not a bot. The panel writes the message, in the recipient's language, and a person presses send. Nothing auto-messages a customer.

archive

7,469 historical submissions and 1,425 PDFs were pulled out of the old form vendor and kept, so nothing was lost when the subscription ended.

Students 08

From a form to a first lesson, and everything after it

The oldest part of the platform and still the busiest. A signup arrives, a teacher is found, the class is placed and confirmed, and from then on the system's job is noticing what quietly stopped.

arrives

Signup

From the website, the office, or a voucher.
engine

Matching

Ranks teachers on instrument, level, style, availability and history.
outreach

Offer

Messaged in their own language; accept or decline is recorded.
placed

First class

Date set, confirmation drafted, contact created.
watch

Follow-ups

Trial check-ins and a conversion check weeks later.
lifecycle

Pause · return · leave

Each a tracked chase, never a dead end.
the hard part

Coming back is a designed flow, not a hope

A pause is not an ending. It carries an expected return date, chases the family up to three times, and resurfaces on the board the day it is due. If a chase dead-ends it is flagged rather than silently converted into a departure — a person decides that.

one click, not forty

Returning to the same teacher

Resuming a whole family's lessons used to be a checklist that took most of a minute and frequently ended with no date set. It is now a single action that places and finalises in one move.

what it measures

Intake, conversion, retention

Where signups come from, how many convert after a trial, how long students stay, which instruments hold people and which lose them, and how much is outstanding right now.

debt

Chasing, as a ladder

Each family's outstanding balance moves through defined steps with a drafted message at each one. It replaced a spreadsheet that nobody could tell you the state of.

Rentals 09

The other two businesses

Rehearsal rooms and instrument rental run on the same platform as the school, sharing its accounts, its payments and its door grants — but with their own catalogue, their own crew and their own vehicle.

rooms

Rehearsal rooms

Booked by the hour from the website or the app, or as a monthly pack. Every booking, however it arrives — site, app, office, or cash at the desk — lands in the same place, and ends in a door grant.

walk-in

Express rental

Same-day instrument hire at the counter, priced straight from the shop's public catalogue so the desk and the website never disagree.

contracts

Long-term rental

244 contracts across 3,709 billed months lifted out of a spreadsheet. The rule that makes it honest: a month is paid, failed, or not recorded yet — never two of those at once. Payments are appended as events, so a bounce can never erase a payment that happened.

field

Deliveries

The crew works a phone-shaped agenda: tap to call, tap to message, tap to navigate. A delivery is quoted from real routing, real tolls and the live national diesel price rather than a guess.

The rental crew's accounts can reach their own work and nothing else. The restriction is enforced in the database query itself, not just hidden in the menu — a hand-crafted request for someone else's data comes back empty, not forbidden.

Switched off 10

What the platform replaced

Each of these was a subscription doing one job badly across a boundary the school could not see into. The point was never only the money — it was owning the data — but the money is real and it is worth stating plainly.

Form vendor
All public forms: registration, vouchers, mandates, rental contracts. Now first-party, with the full history exported and kept.
~€42 / month
Switched off
Third-party door app
The building-access app renters were told to install. Every customer-facing route now points at Shine's own app instead; a staff-only path remains until the last account is moved.
~€60 / month
Nearly out
Previous database
Replaced with managed Postgres beside the app servers, ending a long run of stability incidents. Not a saving — a reliability decision.
Switched off
Automation glue
Still the one path that writes into the scheduling tool. It goes when that tool does.
~€20 / month
Goes with the next one
Scheduling & billing tool
The big one, and the last. Still holds the real calendar and issues the invoices. Replacing it is the whole of the next phase.
~€250 / month
Planned
Target once the last two are gone €4,460 per year · about €372 a month

Roughly a quarter of that is already out. The remainder is not a licence cancellation — it is a calendar and an invoicing system that have to be built and proven first.

Where it goes 11

The half that is not built yet

Turn on the Planned layer in the map above to see these in place. The order is deliberate: the money has to be right before the calendar can be trusted, and the catalogue has to be shared before the shop and the rental can be one business.

the big one

Billing that bills the contract

Invoicing, balances and receipts move in-house. The principle behind it: a family is billed for the enrolment they agreed to, not for a calendar teachers keep imperfectly. The current tool conflates the two, which is the root of most billing disputes. One balance per family, lines attributed per person, integer cents, append-only, prices frozen at the moment of enrolment.

the seam

A calendar, but only as far as money needs it

A lean attendance record, touching money in exactly two places: depleting a pack, and paying a teacher. Everything else about a lesson stays descriptive. This is what finally retires the scheduling subscription and the automation glue with it.

one catalogue

Every instrument in one database

Rental and the shop currently keep separate ideas of what an instrument is. They become one record with one history: bought, listed, sold, or rented out, to whom and for how long. A serial number stops being three different rows in three different places.

the third shopfront

The shop joins the platform

Instrument sales come onto the same catalogue and the same customer records as rentals, so a person who rents, then buys, then books a room is one customer with one history — not three strangers who happen to share an email address.

the app grows up

Lessons and rentals in your pocket

The app already opens doors and books rooms. Next it carries your lessons, your rental, your packs and your payments. The thing everyone installed to get through a door becomes the thing they manage everything in — which is exactly why access came first.

new kind of user

Teachers log in as themselves

Teachers get their own side: the students they actually teach, marking who turned up, their availability, and their own public profile. Today every one of those reaches the school through the office or a form, which means the school's picture of its own teaching is always slightly out of date.

new kind of user

Families sign themselves up

Students and parents enrol in lessons, see what they are booked into, pay, and request a change without any of it being a message a secretary retypes. The office stops being the only path and becomes the exception handler — which is the point at which the platform scales past the number of people answering the phone.

later

Noticing before a human does

Nightly rules that spot a student who quietly stopped coming, or a rental month nobody recorded, and raise it as a card with a drafted message — before the school bills someone for lessons that are not happening.

The through-line, from the first line of code to everything above: always show the next action, never a dead end — and never, ever break the door.