The value was in nine places and one enum, and the enum was the only one that ran
You add a value to a feature. It goes into the type union, into the metadata map the create form renders from, into the access table, into the shelf that decides what appears on the landing pages, into the sitemap. You write sample content for it. You run the suite. Green. Then someone opens the site, picks the new option, and the request comes back 400 Invalid input - every time, for everybody, all day. The value was birthday , a seventh theme for a picture-book generator. The thing that rejected it was one hand-written schema inside the route: // POST /api/generate theme: z.enum(["bedtime", "adventure", "fantasy", "friendship", "wisdom", "learning"]), Six literals, typed out by hand months earlier. The selector offered a seventh. The gate had never heard of it. Why a full day of work never touched it Every book generated that day came out of a script that called insertStory directly and pushed the job onto the queue. Six books, six good renders, six sets of illustrations we sat and reviewed. None of them went through POST /api/generate , because the script did not need to. The unit suite did not go through it either - by design it runs with no database and no network, so it never loads that route. So the pipeline was fine. Generation was fine. The prompts, the wardrobe rules, the visual notes, all fine. The one edge nobody exercised was the door the product actually opens. The gap has a name worth keeping: the pipeline works and the product works are two different claims, and testing the back half proves only the first. It surfaced from the site, not from the suite. Grep found five more copies of the same six strings The literal list had been copy-pasted around: - api/internal/stories/seed : same six, same omission - admin/stories : the back-office filter could not find a birthday story at all - a dev-only loading preview: harmless, drifting anyway - lib/seo/landingCatalog : the comment above it read "themes that ship today", and it listed six. Every generated landing page had been silently skipping the new theme - tests/landingCombos.test.ts : the worst one That last file asserts that every landing combination uses a valid theme, and it carries its own definition of "valid": the stale six. So while the theme was broken, the test was green. Fix the omission and add birthday combos, and the test goes red. A test that passes because reality has not reached it yet, and fails the moment it does. The pattern was already in the same file, two lines down theme: z.enum([...six literals...]), language: z.enum(STORY_INPUT_LANGUAGES), // derived, so the API gate cannot drift from the create form Somebody had already hit this, written the fix, written the comment explaining it, and applied it to exactly one field. The repair was to derive STORY_THEMES from the metadata map the create form already renders, import it everywhere, and add a test that scans src/ and fails any file quoting three or more theme literals in a row. Curated subsets are the awkward part. The age-band recommendations and the random default pool on the create page genuinely should not grow every time a theme ships. Those now carry a THEME-SUBSET: marker saying why they are a subset - because without it, deliberate and forgot are byte-for-byte identical in a diff, which is the whole reason this shipped broken. The check that would have caught it takes three requests Not one. Three, sent through the front door with a control group: theme=birthday -> 403 premium_theme the thing being fixed theme=wisdom -> 403 premium_theme positive control: a theme known to work theme=nonsense -> 400 Invalid input negative control: the gate still rejects junk Read alone, the first line cannot tell you whether the theme got fixed or the validation got deleted. The second says the response is the normal one for a working theme. The third says the schema is still doing its job. Any new user-facing value gets these three before it counts as shipped. What the theme was for There is a moment about an hour into a child's birthday party. The wrapping paper is on the floor, the guests have moved outside, and two or three presents are still on the table that nobody has gone back to. At the parties we have been to, almost everything left there had the child's name printed on it: the mug, the T-shirt, the wooden puzzle cut into the letters. The child read the name once, confirmed it was theirs, and left. A name is a check, and checks finish. That is the hour the theme was written for, and we should be exact about what it is: something we have watched, not something we counted. No sample, no control group, none of the discipline of the section above. It stays an observation, and it costs nothing to run at the next party you go to: wait an hour, then read what is still on the table. Disclosure, because it decides what counts as a bug here: we build Lumora, which takes one photo of a child and writes and illustrates a story with that child inside it rather than printed on the cover. What we are aiming at is a birthday gift a child is still reading after the guests have gone home. The theme built for that hour was, for one day, the only one the API refused to accept - and the six sample books we spent the day reviewing all rendered beautifully, which is exactly why nobody noticed. Top comments (0)
Comments
No comments yet. Start the discussion.