Updates
What has been built, and on which day.
This is a work log, not a release history. Nothing here is running in production, nobody outside the people building it has used any of it, and there is no customer waiting on any of these changes. It is here because a dated list is the cheapest honest evidence that somebody is still at work on this, and because several of the entries are things we would rather you knew than found out.
Before the list
How to read this list
Every entry is a real change with a real date on it, taken from the repository’s own history. About a third of them are defects we found in our own work, including two that would have put the wrong page number under a quotation from a customer’s manual. Those are in the list because leaving them out would make it a different kind of document.
Four working days, and no release. The first line of code in this repository is dated 31 July 2026, which makes this company younger than your newest instructor, and there is no useful way to dress that up. There is deliberately no running total of commits on this page: a number like that is wrong the day after it is written, and a page that keeps a stale count is worse than one that keeps none.
We are not going to publish a roadmap with dates against it. A date we cannot keep is a worse thing to have written down than nothing.
5 August 2026
The public site, and two claims taken off it
- Codes on the wall came off the page
- The site said you could print one code for each branch and give each instructor a code of their own. That feature is not built. The words for it are reserved in the database and nothing reads them, so the claim came off the page. It goes back the day the thing exists and not before — it is the most checkable claim we had, and it would not have survived the first conversation.
- The front page shows the thing rather than describing it
- A page of a manual on the left, the lesson it became on the right, the same sentence and the same page number in both. It had been rewritten four times before that as a description of a service, which is a different product from the one being sold.
- A mock exam promise was removed
- The page described timed mock exams. Nothing in the product implements them.
- The positioning and the sitemap were written down
- Including the diagnosis of why those four rewrites all failed in exactly the same way, so that the next person tempted by a fifth has the argument in front of him instead of rediscovering it.
4 August 2026
The database stopped trusting the application
- 1
Every table now carries the rule itself
A database connection that has not said which school it is acting for now sees nothing rather than everything. It is the difference between a mistake in our code being a wrong page and a mistake in our code being somebody else’s material. - 2
The test that proved this was proving nothing
The suite connected as the database superuser, which is exempt from those rules, so the same test passed with the rules switched on and with them switched off. There is now a separate unprivileged role, and the test opens a second connection as that role. - 3
A check that fails the build when a new table has no rule
It reads the list of tables from the database rather than from a list somebody maintains by hand, because a list with an exceptions section is a list people add themselves to. - 4
Eleven defects, in work that had already passed its tests
The one that explains the others: the application was never loading its browser code at all, so every control that was not a plain link silently did nothing. Twenty-six browser tests had been running against that and passing. - 5
The document pipeline: upload, check, read, split
A file goes straight to storage on a one-off signed link, is identified by its first bytes rather than by its file name, is read with a PDF library, and where the pages are scans, goes through a page-reading service. Then it is split into segments that keep a stable identifier. - 6
The page numbers were coming back wrong
A long document is read in shards, and every shard was being counted from page one. A three-shard document came back as pages 1, 2 / 1, 2 / 1, 2 — individually plausible, collectively wrong, and it would have put a wrong page number under a quotation from a customer’s manual undetectably. It now reconstructs the numbering and refuses the document out loud if it cannot. - 7
Empty screens, error screens and a first-run guide
The console used to answer a school with nothing in it yet with a blank page. - 8
Errors are recorded with the values stripped out
A failed contact form had been writing the sender’s name, address and entire message into an error store with a long retention and no relationship to how long we said we would keep it.
3 August 2026
The rules that hold whatever the software does
- A published course
- Can no longer be edited underneath a student sitting an exam. The guard existed when a version was created and not afterwards, so a lesson or a question could still be added to the version somebody was being examined against. It is now enforced by the database rather than by the code that happens to be calling.
- Your school always has an owner
- The database refuses to remove or demote the last active one. An admin runs your people and your branches and cannot hand ownership out sideways or remove the person who holds it.
- Suspending somebody
- Ends their session immediately, rather than whenever it happens to expire. Reinstating them does not bring the old session back, so they sign in again — which looks broken unless the screen says so, and the screen now says so.
- Staff invitations
- Are a single-use link an admin copies and hands over, because nothing in this product sends email. The token is stored hashed and it expires.
- Email addresses
- Are no longer taken on trust from a sign-in token. Until this landed, any invitation matched on an email address would have been a way into somebody else’s school.
- Page references
- Got a stable identifier for each piece of your book, so that a citation survives the second edition. A new foreword that pushes every page number down by two is not a change to the material, and it is no longer treated as one.
- Serbian Cyrillic
- Every heading in a Cyrillic document was producing an empty identifier, which quietly made the identifier depend on the heading’s position in the file instead of on its words. Found while writing the test vectors for the change above, and fixed in the same pass.
- One school’s documents
- A uniqueness rule on uploaded documents did not name the school, so one school could plant a document inside another school’s family of revisions. Closed, and a guessed identifier now returns nothing rather than a collision, which is the same hole in a different shape.
- The contact form
- Writes into our own database instead of opening your mail program. There is no screen anywhere that reads those messages: somebody runs a script. A screen would have meant inventing a platform-wide administrator account, which is a change to the most sensitive code in the system in exchange for reading a handful of rows.
- Two colours nobody could see
- A button measured 1.18 to 1 against its own background in dark mode, and a focus outline was drawn in the school’s own colour, so it vanished on a bar filled with that colour. Neither was visible to the check that was running, so the check was changed as well as the button.
- The first deployment
- Would have crash-looped. The infrastructure was passing the document worker two settings under names it does not accept, so the process gave up at startup; separately, other settings were being ignored in silence, leaving it configured for a local disk that is refused in a deployed environment. Two independent reasons, one loud and one silent, both found before anything was deployed.
- Upper and lower case
- Are kept when text is normalised for matching. Folding case changes the length of a string in German — ß becomes ss — and the position of a citation is stored against that string.
31 July 2026
The first week, and the thing existed
- 1
The first working slice
A monorepo, a multi-tenant database, an API, a web application and a document worker. Every table that belongs to a school carries the school’s identifier from the first migration rather than from a later one. - 2
A security pass before there was anything to secure
Cross-site request forgery on the session routes, an open redirect after sign-in, and missing timestamp triggers, all closed in week one. - 3
Signing out signs you out
Token revocation, account status and rate limits on the sign-in routes. It is the thing everybody assumes already works, and it usually does not. - 4
Tests that drive a real browser
The first run found two elements sharing one identifier on the same page. - 5
Ten architecture decisions, with the rejected options
Postgres rather than Firestore, a separate API rather than one application doing everything, TypeScript for the API and Python for the document work. The options that lost are written down too, because the next person to have the idea deserves to find the argument rather than repeat the work. - 6
A colour check that runs in both schemes
Every foreground and background pair in the design system, measured against the accessibility minimum in light and in dark. It is the check that later caught the 1.18 to 1 button.
The question this list does not answer is what happens to your book once you send it. That one has its own page: where your material sits
Send one chapter.
Not a demo of somebody else’s course. A chapter of yours, converted properly, so you can look at your own questions and your own page numbers and decide whether it is any good. Pick the chapter you know best. If it is no good, you have lost an email.
We will ask who owns the copyright before we convert anything. If you do not hold the rights to the material, we will say so rather than take the work.