Lindsay Edwards

Scaffolding that compiles is not a product

On this page

I opened a project I had built and, on paper, it looked done. A thorough database schema. Complete encryption. Every publishing adapter written. Auth configured. A whole billing package sitting there ready.

Then I tried to actually use it, and almost nothing joined up. A demo, in other words, that would fall over the moment a real person tried to log in securely, save a preference, or pay for anything.

The layers were real, the wiring was not#

The pieces were not fake. The schema had the right tables. The crypto module did real authenticated encryption. The adapters could talk to the vendors they were built for.

What was missing was the connective tissue between the layers. The auth existed, but it was not threaded through every route. The settings page had a form, but saving it did nothing durable. The integrations page knew about OAuth, but did not run it.

None of that shows up when the thing compiles. A green build tells you the types line up. It says nothing about whether a click reaches the database.

The codebase confessed in its own comments#

Here is the part I have grown to respect. The gaps were not hidden. The code named them against itself.

The API routes read a user from a header, and when it was absent, fell back to a literal demo-user. So every route “had auth” and every route also had a trapdoor around it.

The settings page popped an alert instead of persisting anything:

function onSave() {
// TODO: persist to the settings API
alert("Saved");
}

The integrations page was blunter. It alerted OAuth not implemented and stopped there. The billing page rendered its plans and totals from a constants file, not from any account state. And there was a test, passing happily, that asserted the placeholder default was in fact the placeholder.

A test that asserts the mock is still the mock is not a bug. It is a bookmark telling you exactly where the real work has not started.

That honesty is worth more than a fake that pretends. A stub that lies costs you a debugging session later. A stub that says not implemented costs you nothing and points at the next task.

The last mile is the actual work#

The lesson landed as a budgeting error, not a coding one. I had treated the hard, interesting parts, the crypto and the schema, as the bulk of the job. They were not.

The bulk was the last mile. Thread the authenticated user through every single route so there is no demo-user path left. Make the settings form write and read back. Run the OAuth flow the integrations page only talked about. Replace the billing constants with real account state. Delete the test that guards the placeholder.

That work is unglamorous and easy to under-cost, because each item is small and there are a lot of them. Scaffolding that compiles feels like ninety percent done. It is closer to half.

The lesson#

Building the layers is not the same as connecting them, and the connecting is where the schedule quietly goes. Compiling proves the shapes fit. It does not prove a request survives the trip from click to column.

So I do the mature thing now, which is to name the gap out loud instead of letting a green build imply it is closed. And I root the plan at one invariant: every route gets the authenticated user, no fallback, no exceptions. Once that single thread runs end to end, the settings, the integrations, and the billing have something real to hang off.

Name the gap. Wire the last mile. Then call it a product.

Keep reading