← back

On designing systems: changelogs, pipelines, content engines, design tokens, before they were needed. And why that instinct, annoying as it felt at the time, was completely right.

Praise Ofumaduadike
Nigeria·Mar 2026·~8 min read
SystemsTokensDesign SystemsProcess

Every project I have shipped in the last two years has eventually needed a changelog, a token system, a readable git history, and some way of talking about what I built without starting from scratch each time. None of that was planned. All of it arrived as an emergency.

With this portfolio, I tried something different: I built the infrastructure first, even when there was nothing to log, no tokens to reference, and no shipped work to tweet about. This is an account of what I built, what I was thinking, and genuinely, what I got wrong.

4

Systems built

v1.x

Platform version

v0.x

Project version

0

Regrets so far

The Four Systems

System #01Changelogs

Two surfaces, not one: because two things are happening.

The portfolio itself changes: new pages, new features, design overhauls, infrastructure fixes. But each project I document also has its own arc: design decisions, pivots, things I tried that didn't work. These are not the same story. Stuffing them into one changelog would be like having a single thread for both your journal and your product release notes.

So I built /changelog for the platform and /projects/[slug]/changelog for the work itself. Platform versions start at v1.x. Project versions start at v0.x. Platform tags describe engineering systems: [infra], [changelog]. Project tags describe thinking: [decision], [design], [ux].

The distinction felt fussy when I made it. It already feels necessary.

Platform

v1.x
[infra]
[changelog]
[design]
[perf]

Project

v0.x
[decision]
[design]
[ux]
[pivot]
two surfaces, one build
System #02Design Tokens

One source of truth, enforced like a rule rather than a preference.

Every color, every spacing value, every shadow: defined once as a CSS custom property, referenced everywhere else. The decision that made this non-negotiable was the contact section: it needed completely different behavior in light and dark mode, separate from the rest of the site.

Without tokens, you're writing overrides on top of overrides and hoping they hold. With tokens, it's two component-specific variables that flip with the theme, and everything downstream just works.

The discipline wasn't the technical part. I knew how to write custom properties. The discipline was the commitment: no hardcoded values in components, ever, even when a hardcoded value would be faster in the moment. The moment usually lies about what's fast.

--fg
body text
--accent
interactive
--surface
cards
--border
dividers
System #03Git Pipeline

Commit messages as a record you'd actually want to read.

I know developers who treat git history like a search index: it's there if you need it, but no one reads it for pleasure. I decided early that my history should tell the story of the build in plain English. Feature commits, design system commits, and changelog commits stay semantically separate.

This costs about thirty extra seconds per commit and, depending on the day, some mild internal resistance. What it buys is a history I can actually reconstruct from, and that the changelog scripts can parse without me needing to remember what I was thinking three weeks ago.

Future-me is always slightly worse at context than present-me thinks.

featadd contact section with copy-email interaction
dsadd --contact-header-bg token for header band
changelogv1.4.0: contact section + token additions
featreading progress bar on writing page
dsadd --radius-xl to global token set
System #04Twitter Content Engine

Consistency as a design problem, not a willpower problem.

Building in public only works if it's consistent. Consistency is the first casualty of creative work, because the same energy that makes you build also makes you forget to talk about it. So I treated the content engine as a design problem with constraints: categories of content, a generation pattern tied to what the project actually produces, a scheduling window in the early morning to avoid competing with other Claude usage.

The engine runs on commits, decisions, and shipped components, not on whatever I feel like writing that day. The content is supposed to be a byproduct of the work, not a second job that I'm also trying to hold down.

Commit
Decision
Component
Tweet

content is a byproduct of the work, not a second job

The Actual Argument

The argument isn't that you should build systems. Every designer has heard that. The argument is that systems are the work: not scaffolding around it, not investment in future productivity, not best practices hygiene. The work itself.

Every hour I spent designing tokens saved ten hours of override archaeology later. Every disciplined commit message made the changelog write itself. Every constraint I put on the content engine meant one less decision to make at 7am when I'd rather be building.

The overhead is real. The investment is real. So is the compound return, and it compounds faster than you expect, usually right around the third time you don't have to think about something that used to require thinking.

There is also something worth saying about the relationship between constraints and speed. Every system I built felt like friction at the time of building it. A commit type schema. A version numbering rule. A token naming convention. All friction, all the time.

And then I shipped the contact section in an afternoon, added dark mode support in two token overrides, and wrote a changelog entry that described the decision as well as the change, and I understood that the friction had been paying interest the whole time. I just wasn't looking at the account.

The rule

If you're making the same decision twice, you haven't built the system yet. Build the system. The moment is the worst time to be making architectural decisions.

What I Got Wrong, Too

This is not a retrospective that pretends everything worked on the first try. It didn't. Three specific mistakes are worth naming.

I overbuilt the changelog schema.

Four fields became eight became twelve before I remembered that I don't know what I'll need yet. The schema has since been cut in half. The entries are clearer for it.

I built the content engine before I had things to tweet about.

Consistency requires content. Content requires shipping. The right order is: ship first, systematize second. I had this backwards for about three weeks.

I named my design tokens too generically at first.

--color-blue-500 is not a token. It's a variable that doesn't know what it's for. Semantic names like --accent and --fg-muted carry meaning. Generic names don't.

Why This Matters for Design

Design is the discipline of making decisions under constraint, at scale, consistently. Systems are how you extend your capacity to make those decisions without being present for every single one of them.

Tokens let you design in the abstract

When a color has a name that describes its function rather than its value, you can change the value without changing the meaning. That's not a technical affordance, it's a design one.

Changelogs are design documentation

Every release note is a design decision recorded in public. If you can't write a changelog entry for a change, you may not have finished making the decision yet.

Git history is a design artifact

A readable commit history is a design artifact in the same way a Figma file is. It communicates intent. It can be read by someone who wasn't in the room.

Consistency is a design output

A content engine that runs on constraints is a designed system. The constraint is the creative act. Willpower is not a design strategy, and it's never been a reliable one.

The instinct to build structure before it's obviously needed is uncomfortable. It looks like over-engineering. It feels like delay. It is, in the short term, slower than not doing it.

But slowness is not the right frame. The right frame is: what does the work cost when I have to reconstruct it from memory, from a messy git log, from a cascade of hardcoded values that I now have to touch one by one?

Building a system before you need it isn't premature optimization. Premature optimization is solving a performance problem that doesn't exist yet. Building a system is making sure you can think clearly when things are moving fast.

That's the whole argument. I'm glad I listened to the instinct, even when it annoyed me.