A repository viewer I built last summer has eight commits in its first week. The first four don't touch a single line of application code: they're the init from my template and three documentation commits. The fifth is the whole app.
It wasn't discipline. It's that writing the document showed me my mental model of the integration was wrong, and fixing it in a .md took twenty minutes instead of two days.
The mistake of starting with code in someone else's integration
When the logic is yours, reading the code is the best documentation there is. When it depends on someone else's API, it isn't: what you need to know is neither in your code nor in theirs. It's in the limits, the quotas and the deprecated parameters.
In that viewer I spent a while confusing two connections to the same provider. The login OAuth and the server token for reading the API look the same and aren't. One is for a human to log in, the other for the server to read. They fail differently. Writing it up in a document split into two sections is what made me see that a public-repo viewer only needed the second one.
If I'd started with the code, I'd have found that out around the third 401.
What exactly I document before the first line
I don't document my app's design. I document what I don't control. Specifically:
- The real limits, measured, not quoted. Copying "5,000 requests per hour" off the website isn't enough. I asked the quota endpoint: 60/h unauthenticated, 5,000/h authenticated, 30/min on search. And I measured that a
304 Not Modifieddoesn't burn quota — 4972, 4972, 4972 with conditionals, and 4971 on the same call withoutIf-None-Match. That repeating number is the core argument of the document, and without running it you don't have it. - What can't be automated. The OAuth App can't be created from the CLI: there's no endpoint to create it or to read the client secret, which is shown once in the browser. It's a manual step, and the day you set the environment up again you'll have forgotten it.
- The caps that surprise you in production. 2,000 token creations per hour, which is a ceiling on logins, not on requests. And forced reauthorisation if a user logs in ten times in an hour. Neither shows up when you read the happy path.
- What the decision does NOT solve. I noted that forcing login doesn't remove the need for conditional caching, because the user's bucket is shared with their PAT and with other apps acting on their behalf. If you don't write it down, the decision gives you false confidence.
None of this is a matter of opinion. These are facts you either check or make up.
Cookbooks: testing each piece in isolation before integrating it
In an AI service for document analysis I did the same thing a different way. Before any piece went into the service, each one had a cookbook: a script that runs it alone, writes its output to a folder and registers itself in a run_all_cookbooks. PDF parser, chunker, embeddings, number detector, verifiable extraction. One per piece.
The value isn't the test. It's that when the chunker's output changes shape, you see it in a text file instead of in a failure three layers up. When the pipeline went async, the cookbooks turned into runners with asyncio.run and the migration happened piece by piece, not all at once.
The cost is that you have to delete them when they stop being true. Months later there's a commit removing the deprecated cookbooks. A cookbook that no longer matches its piece is worse than none.
Documentation as the source of truth for auditing afterwards
In a gallery of UI concepts I first wrote a design guide for the piece — sizes, spacing scale, greyscale, which primitive is used for what — and then audited the code against it.
The interesting part is that the audit corrected the guide, not just the code. Three things the .md got short: that the accompanying grey steps through five levels and not two; that the spacing scale has four rungs and not three, because calling it three turned six legitimate uses into drift; and that the three dialogs are really the same dialog, with the same box and the same header, and because that wasn't stated the fix had only been applied to one.
Without the prior document those three findings don't exist. You have nothing to compare against and everything you find is "I reckon".
When this is a waste of time
The test I use:
- If the integration is yours end to end, don't document first. The code is the truth and the document ages.
- If it's a prototype you'll throw away, don't either. A game I wrote in a day has nothing written down and that's fine.
- If the document is going to repeat what the official reference says, don't write it. It only helps if it carries your measurements, your versions and the traps you personally hit.
And there's one failure that cost me: a document written in advance carries your assumptions inside it. In the viewer, the hosting section described a serverless platform because I took it for granted while drafting the brief, not because anyone had decided it. Correcting it to a long-lived container changed the whole conclusion: on serverless the agents SDK didn't fit; on a worker with a fifteen-minute request limit, it did.
That's why the next step isn't optional. There's a commit whose only job is verifying the document against live sources: run the query that appears in the guide, send an invalid API version so the API itself enumerates the valid ones, check that per_page=500 is silently clipped to 100, type the snippet that didn't compile in strict mode. That commit found that 18 of 20 returned "issues" were actually pull requests.
An unverified document is an assumption in nice formatting. A verified document is the only part of the integration that won't lie to you six months from now.
