2026-10-25

architecture · maintainability

The mess has an address

Physics does not get a window. The other half is where the window is allowed to go.

I used to put it next to the algorithm, because that is where I was sitting. A samples/ folder. A Program.cs with a button. A screenshot in the README that only builds if you have the same GPU stack I had that week. The library's build grows a UI reference. The test needs a window. Half a year later the algorithm has opinions about buttons, and every consumer of a math routine pays for a toolkit they will never open.

The behavior lives in a library. The host lives somewhere else, and the somewhere else has a name. I can find the mess. That is the whole maintainability theory. It is not a layering lecture. It is an address.

Four kinds of program

A library is the behavior. Algorithms, invariants, the types other code is allowed to take. Novolis.Manuscript knows chapters. Novolis.Physics knows a step. They do not know argv, and they do not know a window handle. If the library needs a disk or a console to do its job, I have already leaked a host into it. Callers in a studio and callers in a test should be able to hit the same method.

A tool is arguments and an exit code over that library. PackAsTool. It installs with dotnet tool install. It lives in novolis-tools, even when the library it wraps lives in another repo. Coverage, docs, the manuscript command. Flags map onto the library. They do not reimplement it. A tool that grows its own copy of the parser "because startup was awkward" is a second library with a worse test story.

A utility is one small technical program. A person who already knows what a file is. NDJSON explorer, an ADB utility, WireFish. Optional tiny UI. No installer. It lives in novolis-utilities. It is not a product, and I do not let it become one by accumulating a home screen. If it needs a release channel and an installer, it has outgrown the grain. Move it. Do not stretch the word.

A product is the thing a person actually sits in. Studios, readers, games, Presence Ledger, the private Hours app. Persistent session. A layout. An installer or an APK when the ship channel says so. Those live in novolis-apps. Multiple projects are allowed. A product is a composition. It may reference Avalonia, or MAUI, or Blazor, because it is the host. The libraries underneath still may not.

A lab is where I am still allowed to be unfinished. Smoke, benchmark, walkthrough, the first time I put Silk next to a simulation to see if the draw list is enough. novolis-lab is not a release catalog. Labs are not published. labs/shared can exist for an experiment, and it does not graduate with the experiment. When a lab grows up it moves into exactly one of the shipping homes. Tool, utility, or app. Not two. Not "the library, plus we left the demo where it was."

They rot at different speeds

A lab can be wrong for a month. I am the only user, and the user is also the author, and a broken camera rig on a Tuesday is information. A physics package cannot be wrong for a month. Other repos restore it. A sample "so the demo builds" inside that package makes every consumer build the demo's stack. I have had a unit test pull a windowing toolkit because the assertion and the visualization shared a project. The number was a double. The window was the entire cost.

A tool rots when the flags drift from the library. The library grew a parameter, the tool still has the old switch, and the README shows a command that 404s in the parser. That is a small rot, and it stays small if the tool is a thin map. It becomes a large rot if the tool inlined the behavior to avoid a package bump. Then I have two truths, and the one people run is the stale one.

A product rots in the session. Navigation, empty states, a button that commits the wrong violet because the app embedded its own palette. That rot is allowed to be product work. It is not allowed to leak down into Math because the screen was the place I noticed the bug. Fix the library. Publish. Let the product take the package. The screen is not the API.

The move I keep making

The move I keep making, and then undoing, is the demo that moves in with the library. It starts as examples/Hello. It is three files. It references the project next door, which is legal, because it is the same repo. Then Hello needs a texture, and a window, and a package that only Hello needed. The library's dependency graph gains a host. The pack includes it or the sample breaks. I delete Hello, or I try to. Something still references it from a test "for convenience."

So the demo goes to the lab repo. The shipped command goes with the other tools. The thing a person opens goes with the apps. The library keeps the API and loses the host.

Library repositories are allowed to keep private benchmarks/ and private tools/ that are not packed and not shipped. A profile executable I use while tuning a solver is not a product, and it is not a PackAsTool. The line is publishing. If it ships, it does not live beside the library. If it is a window a stranger is supposed to install, it is an app. If it is a command a Novolis developer installs from NuGet, it is a tool. If it is a technical exe with no installer, it is a utility. If it is how I learn whether the package works, it is a lab, and it can be ugly.

Finding it

Maintainable, for me, means I can find the mess. When every repo is a little bit of library and a little bit of app, I cannot, and neither can anyone I point at the tree. "Where is the PDF reader" has an answer. "Where is the half-finished artillery range" has an answer, and the answer is a lab, so I do not expect an installer. "Where is the presence inference" has an answer inside the app's core, not inside a maps SDK. The addresses are the design.

The fence between repos is what keeps me from collapsing the addresses back into one solution for an afternoon and forgetting to put the walls back. That half is sixty repos is a fence. The compile-time half is if it still compiles, it is not a rule. I need the names even when I am tired. Library, tool, utility, app, lab. If I cannot say which one I am editing, I am about to put a window in the wrong project.

All posts