Sidelore
A subscription app that helps A-level students build an intellectual life outside the syllabus, and remembers it for them. The parent pays, the student uses, and the product only survives if a seventeen-year-old keeps opening it without being told to. Live eight days after the first commit, with a minor’s data protected in the database rather than in the controllers.
The opening
From 2026 entry, UCAS replaced the free-text personal statement with three structured questions, each with a minimum length. The third asks what the applicant has done outside education to prepare, and why it was useful. Under the old format a thin record could hide inside a well-written whole. Now every applicant in the country has a visibly separate box to fill with specific, reflected-upon activity, which turns super-curricular work from an advantage some students seek into a field every student must complete.
The hard part is not doing the activity. It is remembering it. In the autumn of Year 13 a student has to recall eighteen months of reading, talks and courses, and say what each one taught them. They won’t. Sidelore suggests one genuinely interesting thing a week, chosen for the course they intend to apply for, and when they do it, captures a short reflection in their own words, at the time. When the form asks, the raw material already exists and is true.
The parent pays and the student uses. Every failure mode is a variant of “the student stops opening it, so the parent cancels”. A decision that makes the parent happier and the student less likely to open the app is wrong commercially as well as ethically.
Designing for the user who didn’t buy it
Most products in this market sell oversight to the parent and privacy to the student, and their two pages quietly contradict each other. Sidelore can’t, for a practical reason before an ethical one: the student will read the parent page, and read it looking for evidence of surveillance. So the product rests on a handful of decisions that favour the teenager, each of which is also what the ICO’s Children’s Code requires.
- The student owns the record; the parent is a linked payer. The student sets what the parent sees, across three tiers with a private override per activity, and a persistent indicator always shows them what that is.
- It never writes the statement. Reflections are the student’s own words, timestamped and never rewritten. A generated statement is worthless to an admissions tutor; a dated record of genuine curiosity is the one thing that can’t be produced at volume.
- No score. Coverage is shown across six dimensions that are never summed, and every gap comes paired with a next step. No progress rings, no percentages, nothing to game.
- The parent’s hero is a question, not a dashboard. The weekly digest leads with one good thing to ask at dinner. Its job is to stop the nagging, not to feed it.
Privacy enforced where it can’t be forgotten
Sharing tiers, per-activity overrides and an operator who must never read reflections are too many rules to hold in application code. Someone eventually writes a query that skips one, and the tests still pass. So the permission model lives in PostgreSQL row-level security, with policies applied to every table by default rather than to a list somebody maintains.
There are two database roles, and only one is reachable from the web. The elevated role that the weekly send needs runs in a separate process with its own credentials, and there is no code path from an HTTP request to it at all. That boundary cost a second deployable, and it was taken deliberately: a guarantee that is structural is worth more than a convention about which class opens which connection. The curation console reads items, themes and rates; reflection text is excluded from it by database policy, not by good intentions.
The governance was written alongside the build, not after it: a Data Protection Impact Assessment, a legitimate interests assessment, a processing record and the Children’s Code worked through standard by standard, with whole-account erasure that stops billing before it destroys anything.
A weekly batch job with a thin read surface
Almost all the work happens once a week per student: select the pick, assemble two tracks, render a student email and a parent digest, send. Scale is a non-problem and was deliberately not designed for; the genuine risk on a product this size is over-engineering. What matters instead is that a failed send means the week didn’t happen, so the send is idempotent per student per week on a database constraint, isolates one student’s bad data from the rest of the run, and carries a kill switch and a dry-run mode that renders everything without dispatching.
Selection stays in SQL, because it is the one piece of logic that has to be debuggable at seven in the morning when the send looks wrong. Behind it sits a curated library organised by theme rather than by subject, so one well-chosen piece on migration serves five subject clusters, with a scheduled ingestion pipeline, an operator console with review gates, and automated link-rot checking so a suggestion never arrives dead.
Offline, and the one thing that must never queue
The native apps work on a train with no signal. Taps and reflections queue locally and drain when the connection returns, under one rule: the app may lose a tap, but it never loses prose. Two devices editing the same activity resolve so that a reflection can’t be silently overwritten.
Permission changes are the exception, and so is erasure. Imagine a student who widens what their parent can see on a train, then thinks better of it and narrows it again once they are back online. If the first change had been queued, it would drain afterwards and quietly re-widen it: a minor’s reflections exposed by an intent they had already abandoned, with nothing anywhere broken. So those changes need a connection, and the app says so in one honest sentence.
The architecture
One web host serves the marketing site, the app and the console: Blazor static server rendering with interactive islands, so most pages are plain HTML and only the screens that need it ship a runtime. The iOS and Android apps are .NET MAUI over the same screens and an API that is the whole of the shared surface. Background work runs on Hangfire in its own process, against the same PostgreSQL database, so the infrastructure count stays at one while the security boundary holds.
- .NET 10 · ASP.NET Core
- Blazor static SSR + WebAssembly islands
- .NET MAUI (iOS & Android) with offline queue
- PostgreSQL row-level security + pgvector
- Hangfire in a separate, elevated process
- Amazon SES weekly send & digest
- Stripe subscriptions & billing portal
- Curation console & ingestion pipeline
- AWS Parameter Store secrets
Speed without skipping the evidence
Sidelore went from first commit to a live site, app and console in eight days, with a full subscription path behind it: sign-up, Stripe checkout, invitation and a signed-in student. The pace came from AI-native delivery, and so did the discipline that kept it honest: more than a thousand tests, with new ones checked red by breaking the code they guard, because a test that cannot fail is worse than none.
That habit earned its keep. Building the payment path end to end surfaced a family of defects that all had the same root, code that assumed instead of reading: a sibling product’s webhooks answered with an error on a shared account, a subscription recorded at one price whatever was actually charged, an index declared in configuration that had never existed in any database. None was a crash. Each would have been found by a customer.
Why it is here
Sidelore is product leadership where the buyer and the user want different things and the product has to be honest with both, and platform architecture where a minor’s privacy is a property of the database rather than a promise in a policy. It is also evidence of what a small, AI-native team can put into production in days, without trading away the governance that a product holding children’s data has to carry from the start. It is live at sidelore.co.uk.
Want work like this in your business?
Start a conversation