Loading…
Loading…
The new feature worked on the first try. Three weeks later it turned out it was missing from the sitemap, missing from search, and the save button quietly did nothing.

This site has seven content modules. Adding an eighth sounds like creating a route folder and writing a couple of pages.
That part took an afternoon. The rest was twelve files scattered across the codebase, and not one of them was going to remind me.
Here is the real list, copied out of the project's own documentation:
1 lib/validations.ts SAVE_MODULES
2 supabase/<module>.sql CHECK constraint and the IN list in toggle_save()
3 lib/tracking-shared.ts moduleFromPath()
4 components/layout/nav-shared SECTION_ICONS + EXPLORE_META
5 components/layout/navbar.tsx routeLinks
6 types/seo.ts SiteSection
7 lib/seo-verdict.ts SECTION_LABEL
8 lib/sitemap-entries.ts index route, detail routes, sectionOf()
9 lib/search/collect.ts search documents
10 lib/search/featured.ts MODULE_WEIGHT
11 components/search/command-palette.tsx icon, accent, label
12 services/… + profile/page.tsx slug resolution, module meta, catalog size
One thing jumps out of that list: only two or three entries have anything to do with showing the module. The rest are existing systems that quietly assume they know about every module on the site.
This is the part worth dwelling on, because the project runs TypeScript in strict mode. Surely the compiler catches this?
It catches some of it. SiteSection is a union, so adding a member turns every exhaustive switch over it red until you handle the new case. That is a type doing its job.
Most of the list has a different shape. MODULE_WEIGHT is a lookup table: a missing key gives you undefined, and undefined inside a sort comparison does not throw, it just orders things wrongly. collect.ts gathers search documents by calling each source in turn; forget to add a call and search still returns results, only fewer.
Entry 2 is beyond TypeScript's reach entirely. A CHECK constraint in Postgres is a list of strings inside the database. Somebody presses save, the request goes out, Postgres refuses it, and what they see is a button that does nothing.
That gap between where a rule is written and where it is enforced is what makes this whole category hard. A type union and a database constraint can encode the same fact, and one of them is checked while you type while the other is checked by a server in another country at the moment a stranger clicks something. Keeping them in agreement is manual work no matter how carefully the rest of the stack is typed, and it is why entry 2 sits so high on a list otherwise ordered by how easy each item is to forget.
The first instinct is to collapse all twelve into a single registry. I thought about it for a while and it does not hold up.
They need different things about the same module. The sitemap needs to know how to enumerate detail routes. The command palette needs an icon and a colour. The database constraint needs a string, and it lives in a system that cannot import anything from TypeScript at all. Force them together and you get an object every part of the system imports and nobody wants to touch.
What actually helped was much cheaper: write the list down, with a column for what breaks if you miss it.
A checklist that only lists steps rots, because the code moves and the document does not. What keeps its value is symptoms.
The troubleshooting table in the project's docs looks like this:
Save button does nothing → module missing from the CHECK constraint (2)
Item never appears in search → source not added to collect.ts (9)
Track colour renders unstyled → class literal missing, Tailwind never scanned it
The left column is what you actually observe at eleven at night. The right column is what you have no way to guess. Putting them side by side turns three hours of hunting into three minutes.
And if there is one thing to take from it: when adding something to an existing system, the useful question is not "what do I need to write", it is "where does this code already assume it knows the whole list". Those places are rarely near the thing you added, and they almost never fail loudly.
No comments yet — be the first!