Nink AI Engineering © Nink AI Engineering GmbH

Reuse Moved to Generation Time

I now have three prototype web apps, each built with Claude Code in its own repository: a confidential client decision tracker, an ontology authoring workspace, and an AI-refereed party word game. Three architectures, three UI concepts. The enterprise architect in me looked at that and reached for the word platform. This post is me arguing with that instinct.

Convergence without coordination

Under the surfaces, the apps agree on more than I expected: the same framework generation, the same database and auth provider, the same hosting, the same "no state library, the server is the source of truth" stance. The striking find is visual. Two of the three use the same palette to the hex digit: the same near-black text, the same border grey, the same accent blue. One arrived there through a component library's token preset, the other through hand-written CSS custom properties. Nobody synchronized them; they converged because the same person, with the same taste, answered the same question twice.

Convergence without a shared artifact is the best possible evidence that a shared artifact would actually get used. That is the honest case for harmonizing. But it says nothing about what kind of artifact it should be.

Motivated divergence is not debt

The tempting move is to treat every difference as drift to be cleaned up. Most of the differences here are load-bearing. The client tracker makes row-level security the only authorization gate, because confidentiality is its founding constraint. The game routes every write through a single server-side writer with optimistic version checks, because a realtime game loop needs one source of truth. The ontology workspace layers an application-side role matrix over its policies, because multi-role editing wants both. Three trust models, three correct answers. A platform that forced one pattern would be wrong in at least two products.

The accidental divergence is smaller and easy to name: three different answers to the same German-plus-English requirement (a proper i18n library, nothing, and hand-rolled copy objects), three styling stacks where the domains force none of them, and a half-finished rename in every repo. That list, not the architecture, is the actual cleanup backlog.

Four rungs, and where to stop climbing

Harmonization comes in tiers. A golden-path template that captures the winning choices (the auth wiring, the result-and-error contract, the i18n setup, the token sheet, the deploy checklist) costs almost nothing, because the content already exists in three places; you are choosing winners, not writing code. A shared design-token file for the professional tools, explicitly excluding the game's deliberately loud consumer identity, buys the visible coherence a platform promises at a fraction of the cost. Both of these are copy-time reuse: a new app starts right, existing apps stay untouched.

The next rung is a shared package or a monorepo, and here the economics have quietly changed. The classic argument assumes writing code is expensive, so duplicated code is waste. But my marginal cost of regenerating boilerplate with Claude Code is close to zero, while the costs of a shared dependency are what they always were: version coupling, breaking-change ceremony, and a forced decision between styling stacks that would mean rewriting a working app. For a solo maintainer, copying from a canonical reference beats depending on a package until there are many more apps or a second pair of hands.

The top rung, a shared runtime (one database project, shared accounts, one deployment umbrella), is not deferred; it is refused. Confidential client data whose security posture is "the database is the gate" must never share a blast radius with a game whose rooms are deliberately readable by anonymous players. Separate projects per app is not fragmentation. It is the security architecture.

What all three actually share

The uncomfortable finding is that the strongest commonality is what is missing everywhere: no CI in any repo, no error reporting in any repo, and those three stalled renames with no process that would ever sweep across repositories to finish them. A platform initiative that starts with a component library while the one commercial app has no error monitoring is optimizing the wrong layer. One workflow file with typecheck, lint, and tests, dropped into three repos, is worth more than any shared button.

The rule of three says that once you have three instances you may abstract. It does not say the abstraction must be a package. At three apps and one human, my platform already exists: the process layer, the conventions, the agent systems that scaffold and verify. The architecture decisions belong encoded there, applied at generation time, rather than compiled into a runtime dependency I would then version, migrate, and babysit alone. Reuse did not disappear when the apps stayed separate. It moved earlier in the pipeline.