lcms[2024 — now]
Master Courses and Child Templates
A lesson and course authoring platform for a US online school, with 23,000 students and over a thousand teachers. Content designers build the courses, teachers deliver them. Two years on it, as the only designer across two teams of around eight engineers each. I run the weekly sessions with the client.
Every variant of a course was a separate course
One course, duplicated for each variant. The copies were independent, so they drifted apart, and a correction had to be made in every one of them by hand.
The main work is done once, in a master. Children are derived from it and inherit everything. A child can be set to differ in specific places. Teachers get the children.
Editing an element in a child makes it local. The child stays attached, and a sync overwrites what is still inherited and skips what was edited. So the sync has to report its own coverage.
The model has a bill. Everything had to be migrated, and the content designers had to learn a way of working they did not ask for. Assigning a calendar stopped being shared and is set per child now.
The warning would have fired on every course
A missing element in a child was shown as a warning.
A skipped element is marked on its own row, in its own colour, and the sync still reports success.
Children are supposed to differ, so a warning fires on the normal case. I took that to a session before it was built. Once the school year started every course would sit in warnings and nobody would read the panel. The client agreed.

Three children skipped this element, in the left column. The synchronisation at the top is still ticked.
The parent row stopped having a status
A flat list of changes, each row carrying a breadcrumb saying where in the course that element lived.
Then a hierarchy where a parent row carried its own change and status as well as the ones nested under it.
A parent row carries the path and a count. Every change is a line underneath, including the parent’s own.
A breadcrumb works on one row. On twenty, the reader rebuilds the same tree once per row, so the changes became a tree. My first hierarchy was worse. The parent row carried its own status, and nobody could tell whether that belonged to it or summed up the lines below. The pattern is recent, and nothing else in the platform uses it yet.

Five rows, five repetitions of Unit 1 / Topic 1 / Day 1.

The chip on the Day 1 row reads Modified Settings. A tick sits beside it.

Day 1 now carries (3) and nothing else. Settings has moved down beside the lessons.
Designing both answers while the argument ran
A retry re-ran the entire set of updates, because that is what the system supported.
Retry the batch from the top, or open one failure and retry only that update.
I raised this one. A failed batch blocked the next sync for the children that had already succeeded. One error held up work that had nothing to do with it. The client agreed. The system only allowed a whole-batch retry, so it became a question of what engineering could be persuaded to build. Individual retry shipped. I designed both outcomes while it was open.

19/20, and the one that failed is opened underneath with its own error.
Two things I have not fixed
Component editing happens in a column that turned out too narrow. That one is mine, and fixing it needs nobody's permission. A sync still locks editing while it runs, so edits cannot corrupt a run in progress. Lifting the lock is a backend question where I have little influence. I keep proposing it.
What I am not claiming
The client is not named. Adoption and time-on-task numbers are being collected and are not in yet. AI-assisted authoring was scoped and has not been built.