The Four Themes of ktsu.dev

ktsu.dev is the open-source .NET project I've been building since 2023. The catalog has grown to something like thirty NuGet packages (type-safety libraries, data-management libraries, build-tooling libraries, UI libraries), and the temptation, when describing it, is to enumerate the packages and let the catalog speak for itself.

That framing misses what actually matters. The libraries vary substantially in size and scope, but they share a small number of design commitments that determine which libraries I'll build, which problems I'll address, and which compromises I won't make. The commitments come first. The catalog follows from them.

This post is the four themes underneath ktsu.dev, what each one means in practice, and what the project deliberately is not trying to be.

Theme 1: Semantic Clarity Through Strong Typing

The first commitment is that code should communicate intent at compile time, and the type system is the main tool for that.

The bug class this is aimed at is primitive obsession, the failure mode where everything in an application is a string, a double, or an int, and the compiler can do nothing to distinguish between user IDs and email addresses, between distances and durations, between absolute paths and relative paths. The compiler reduces to a syntax checker, and every distinction that matters becomes a code-review or a runtime-check responsibility.

The ktsu position is that the cost of defining types is paid once, and the value of those types being unmistakable is collected across every function signature, every parameter, every assignment, every refactor. The most explicit expression of this is ktsu.Semantics, which provides semantic-string types, path types, and physics quantities with built-in validation and zero runtime cost. But the theme runs through other libraries too (typed handles instead of opaque tokens, typed identifiers instead of strings, typed configurations instead of dictionaries).

The plain version of the principle is this. If the domain has a concept, give it a type, and let the compiler do the work. Most projects underuse this. The libraries make it cheap.

Theme 2: Simplified and Streamlined APIs

The second commitment is that the right amount of boilerplate is the smallest amount that still leaves the design comprehensible, and the library's job is to remove the boilerplate without obscuring the model.

This shows up most visibly in libraries like AppDataStorage, where the entire API for persisting a settings type is "inherit from AppData<T>, call LoadOrCreate()." The library handles the parts that would otherwise be ceremonial (directory creation, JSON serialization, atomic writes, backup management) without exposing them to the consumer. The consumer's code stays focused on the domain.

The theme also shows up in Extensions, the utility library where small, frequently-needed operations get a single short name instead of a multi-line idiom. Checking whether a collection is null or empty should be one short method, not three lines of guard clauses scattered through every function that uses a collection.

There's a line I'm conscious of here. Simplified isn't the same as magic. A library that's "easy to use because it does everything behind the scenes" is the wrong kind of easy. The consumer ends up not understanding what the library is doing, and the first time something breaks they have no model to repair it with. The ktsu approach is to make the common case short while keeping the uncommon case possible. Sensible defaults plus visible escape hatches.

The test is simple. Every library should make the new user productive in their first hour, and should still make the same library productive for the user who's been with it for three years and wants to do something it didn't anticipate.

Theme 3: Designed for Maintainability

The third commitment is to libraries that are nice to live with, not just nice to introduce.

This is the theme that's easiest to articulate as principles and hardest to evaluate from the outside. It looks like consistent patterns across the library catalog, thorough documentation that doesn't lie about edge cases, comprehensive test coverage so refactors don't break consumers, clear deprecation paths when something has to change, and semantic-version discipline so consumers can predict the impact of an upgrade.

A few specific properties this produces.

Predictable upgrade behavior. A 1.x to 1.y upgrade is non-breaking for consumers. A 2.x upgrade may require code changes, but those changes are documented in a CHANGELOG entry that names the breaking change and the migration path. Consumers can decide whether to upgrade based on a real cost estimate.

Self-explanatory exception messages. When a ktsu library throws, the message names the input that caused the throw, the constraint that was violated, and where to look for documentation. The most expensive part of debugging library-thrown exceptions is working out what the library was trying to say, and that shouldn't be expensive.

Tests that double as examples. The test projects for most ktsu libraries are written readably enough that a consumer can use them as a usage reference. The intent is that someone reading the tests learns how the library is supposed to be used, not just whether it works.

A single shared SDK (ktsu.Sdk) that handles cross-cutting build, packaging, and metadata concerns across the catalog. Adding a new library starts from a known-good baseline, and bug fixes to the SDK propagate to every library. The downside is that the libraries are tightly coupled to the SDK. The upside is that they're consistent in ways that hand-rolled project files never are.

This theme matters because most libraries a project depends on aren't libraries it will only use once. The team will upgrade them, hit edge cases, and need to read the source the first time something doesn't behave as expected. A library designed for maintenance over years is a substantially different thing to live with than a library that was built for an initial release and then frozen.

Theme 4: Leveraging Modern .NET Tooling

The fourth commitment is to take advantage of where .NET is now, not pretend it's still 2015.

This shows up in obvious ways (multi-targeting from netstandard2.1 through net10.0, async/await everywhere it's appropriate, nullable reference types enabled and honored, source generators where they buy real value, span-based APIs in performance-sensitive code). It also shows up in less obvious ways, like using the new Lock type on .NET 9+ for synchronization, using generic math features where they reduce code duplication, and using analyzer packages to keep code consistent across the catalog.

The principle that drives it is that modern .NET is a meaningfully better platform than .NET Framework, .NET Core 3.1, or .NET 5. Pretending otherwise, sticking to lowest-common-denominator APIs to maximize compatibility, costs the consumer more in clunky code than they save in marginal compatibility.

The pragmatic compromise is to multi-target enough to be usable in older codebases (netstandard2.1 is broadly compatible) while letting the implementation be modern. Consumers on older runtimes get the API, and consumers on newer runtimes get the performance benefits of the newer-API implementations under the hood.

There's a related point about polyfills. ktsu libraries use the Polyfill package liberally to make newer-.NET idioms available in older targets. The library author writes the modern idiom, and the polyfill makes it work on older runtimes. The alternative, #if NET6_0_OR_GREATER conditional compilation scattered through the code, produces source that's hard to read and harder to maintain. Polyfills push that complexity out of the source and into a dedicated layer that's easier to reason about.

What the Themes Are Not About

Three things worth being explicit about because they're conspicuously absent from the list.

This isn't a framework. ktsu is a catalog of small, focused libraries that compose with whatever else is in the project. There's no opinionated overall structure a consumer has to adopt to use any one library. Pull in ktsu.Semantics for type-safe paths, pull in ktsu.AppDataStorage for config persistence, pull in ktsu.Extensions for utility methods. They work together, but they don't require each other, and they don't require anything about the application's architecture.

It's not about performance optimization as the primary value. Some of the libraries are performance-conscious where it matters (the source-generator-backed paths in Semantics, the span-based APIs in performance-sensitive code), but performance isn't the lead. The lead is expressiveness and safety. Performance is the table-stakes baseline that lets the libraries be used in real code without apologizing.

It's not trying to compete with the BCL. When the .NET base class library has a thing, ktsu uses the BCL's thing. The libraries fill gaps the BCL doesn't address (semantic types, opinionated persistence, specific utilities). They don't reimplement collections, threading primitives, networking, or anything else the platform already does well.

Why the Themes Hold Together

The four themes aren't independent. They reinforce each other in specific ways:

A library that picked any one of these themes and ignored the others would be a worse library. The themes are linked because the underlying philosophy is linked. Make .NET development pleasant by making the things that should be easy actually easy, and making the things that should be unmistakable actually unmistakable.

Advice for Coming to ktsu Fresh

Three pieces of advice for anyone considering using any of the libraries.

Start narrow. Pick the library that addresses the most acute problem in the current project and try it on that. Semantics for a domain with too many string-typed identifiers, AppDataStorage for a desktop app with config to persist, Extensions for a project that's been accumulating utility functions. One library, one project, one evaluation cycle.

Read the README, then the tests. Both are written to be readable, and the tests show the library in normal use. An hour is usually enough to tell whether the library fits the problem at hand.

Don't feel obligated to adopt the whole catalog. Each library is independent. Using one doesn't commit a project to using others, and the libraries are designed not to be sticky. If a library stops being useful, removing it is a one-line dotnet remove package and a localized refactor, not a project-wide migration.

The themes are what make me build the libraries. They're also what should help a consumer decide whether the libraries are right for them. If "make domain types unmistakable, keep APIs short, design for years not months, use modern .NET well" sounds like the right set of priorities, the catalog is going to feel coherent. If it doesn't, the libraries probably aren't the right fit, and that's fine. There are other catalogs with different commitments, and the right one for a given team is the one whose commitments match its priorities.