In short
- What it is. A 185-page workbook drilling the algebra and trigonometry underneath Calculus I. Thirty-nine lessons, five dollars, with a free five-lesson preview and no account to make.
- How it was written. For myself first. I worked every problem myself with no notes and rewrote the lessons that did not survive that.
- The harder half. Turning a course wired into my own study system into something a stranger could buy. The build strips my log lines, self-report widgets and scheduler vocabulary out, and refuses to ship if any of it survives.
- Why it was worth building. A placement diagnostic on 4 August exposed six topics the course did not cover. Adding them took one evening, and the build refused the book twice over defects I had not spotted. The pipeline is the part that compounds; the workbook is just its first output.
- The worst failure. The finished product sat at a guessable public URL for as long as the site existed. A static host serves every file in the published folder whether or not anything links to it.
- What is enforced now. The build fails if the paid file reappears in the deployed folder, and it generates the book's contents page from the storefront table so the two cannot drift. Five failures below.
The problem
Students do not fail calculus on the calculus. They fail on rusty algebra and half-remembered trigonometry. In a typical problem about 90% of the work is algebra and only one step is the genuinely new idea, so if the algebra is not automatic, the new idea never gets a fair hearing.
I built the course to fix that for myself before Calculus I. Thirty-nine lessons, three mixed checkpoints and a readiness final, with a diagnostic on day one that you rematch at the end so the distance you moved is measurable rather than a feeling.
The interesting part is not the writing
The lessons were never files that only served a reader. They are my live course, wired into my own study system. Each one carries a line telling me to log my score into my database, self-report widgets, links into my private wiki, footers with repository paths, and vocabulary from my scheduler that means nothing to anyone else.
The same file had to be my working course and somebody else's book, and the difference between the two is not a copy and paste.
So the product is a build. A script assembles the lessons in reading order, strips everything that belongs to my system, renumbers the lessons by position so the reader never sees the counter jump, and prints to PDF. It produces two editions from one source: the full workbook and a free five-lesson preview, each with different offer copy.
The part I am most pleased with is not the transform. It is the check that runs after it. The build holds a dictionary of things that must never reach a reader, and it refuses to produce a file if any of them survive: study-log instructions, scheduler block types, raw wiki-link brackets, my local server address, repository paths, hard-coded dates from my own calendar, and phrases that address me personally.
The second course cost an evening
The pipeline was expensive to build, and the workbook is not the last course I will write. That is the whole argument for having built it, and it stopped being a theory on 4 August.
I sat the University of Utah's placement diagnostic for Calculus I. Six of its ten questions were on topics my course did not cover at all: reading the constants off a graph, vectors, polar coordinates, complex numbers, matrices, and one that was calculus proper rather than preparation for it. So the course was missing six lessons, and I found that out three weeks before term instead of during it.
Writing them took an evening. What that evening did not include is the interesting part.
- I did not decide where they go in the book. Reading order is a list in the build script, so inserting two lessons is editing two lines.
- I did not update the contents page. It is generated from the storefront's own lesson table, so the book cannot disagree with the page selling it.
- I did not renumber anything. Lesson numbers come from position, so the reader never sees the counter jump.
- I did not proofread the new lessons for my own study-system vocabulary. The build does that, and it refused to produce a file until they were clean.
- I did not check the layout by eye. Two scripts render every page and fail on a rule crossing text or a sideways overflow at phone width.
That last pair of guards did real work the same evening. The build rejected the book twice: once because I had left three links to my private wiki inside a sentence instead of the footer where the strip runs, and once because I had written five matrices as [[1, 2], [3, 4]], and a bare double bracket reads as one of those links. Then the layout checker failed the storefront, because my new lesson descriptions were the longest in the table and ran into one of the vertical rules.
Three classes of defect in one evening, none of which I noticed, none of which reached a file. The first time I wrote this course, the equivalent problems were found by reading all thirty-seven lessons by hand as if I were a stranger, and two of them had already shipped.
Writing the lessons was the work. The pipeline is the asset, and it is cheaper every time I use it.
The same shape produced this website. Settling the design was slow: whole pages built as competing variants and compared rendered rather than argued about, then a contrast audit that moved the accent color because ten of sixteen text styles failed at the size they were actually used. That cost is paid once. Every page since, including this one, is content dropped into a frame that already works, and the checks that protect it run in about the time it takes to read this paragraph.
Neither of those investments would survive being described as a feature. Nobody buys a reading-order list or a leak dictionary. They are the reason the second course, and the fourth, and the one after that, cost a fraction of the first.
What went wrong
The product was sitting in public the whole time
The paid PDF was in the same folder as the website. Nothing linked to it, which felt like enough. It is not. A static host serves every file in the published directory at a guessable URL whether or not anything points at it. Anyone who typed the obvious filename would have got the whole book for nothing.
It now lives outside the published directory entirely and is only ever handed over by the checkout platform after payment. The build throws an error if it ever finds the paid file in the deploy folder again, because I do not trust myself to remember.
The book contradicted the thing selling it
The workbook's own front matter told the reader it was free, and offered a $5 upgrade to an AI tutor. Meanwhile the store sold the workbook itself for $5, and the tutor did not exist and still does not. A buyer would have paid five dollars and then read a page telling them this was the free version of something else.
That happened because the front matter was written when the plan was different and never revisited when the plan changed. Now the offer copy is a separate file per edition, and the phrase "AI tutor" is in the leak dictionary, so the build fails if anything ever promises it again before it is real.
I was quoting myself back at the reader
I wrote the course one batch at a time, each batch as a reply to my last study session. That is a good way to write and it leaves a trail. A full read found 36 log-line instructions, 30 references to my scheduler, 143 wiki links, 37 footers with my repository path, hard-coded dates from my own calendar, and eight lesson openings that addressed me directly, including one that opened with what I had said the night before.
None of that is visible when you are the author, because all of it reads as normal. It took reading the first paragraph of all 37 lessons as a stranger to see it.
Three rebuilds lost to a single character
A separator in the lessons is a middle dot. I wrote that character literally into the build script, and the script silently stopped matching anything.
Windows PowerShell 5.1 reads a script file as the system's ANSI codepage unless the file carries a byte-order mark. My non-ASCII character arrived at the pattern matcher as different bytes than the ones in the lesson files, so it matched nothing and reported no error. It cost three full rebuild cycles before I stopped assuming the pattern was wrong and checked the encoding. The script is now ASCII only, and builds the character from its code point.
An escape sequence in the wrong language
A generator wrote CSS containing \2192, the escape for an arrow. Python read the leading \219 as an octal escape and emitted a control character instead, so the browser drew an empty box. The same bug shipped twice with two different characters before I caught the pattern.
Not a deep lesson, but a sharp one: when one language generates code in another, the escape sequences of both are in play.
What I would do differently
- Read the whole thing as the buyer before writing any pipeline. Every content failure above was findable in one careful read, and I built the machinery first.
- Write the leak dictionary at the start, not after the audit. It is the artifact that made the problem stop recurring, and it was the last thing I wrote.
- Treat the free preview as the real marketing asset. Five complete lessons prove more than any description, and it is the only honest way to sell something when nobody has heard of you.
- Expect the checks to catch you, not the other way round. Every guard here was written after something got past me. Each one has since caught a repeat that I would have shipped.
The workbook is on sale at store.ryanross.org, with a free five-lesson preview and no email required. The build script and its leak checks are the part I would rather talk about. Email me.