Skip to content

Product design, leadership and the Harness Design series

Harness Design seriesArticle no. 06

What the token foundation hid: closing Milestone 1 of the Harness Design System

John Morais, .

A gouache letterpress workshop: hands over a wooden type case with many compartments, among ink rollers, plants on the window, and a hanging apron

Continuing the first week of dedicated focus on the project, the work was all on Milestone 1, which we called the technical token foundation. I already explained in the previous articles why we start there. Tokens are the smallest unit of the system. In an Atomic Design reading, they are the atoms: they come before any component, and everything we build on top inherits what they define. Fixing this base first is not perfectionism, it's the only way to avoid multiplying the problem once the AI comes in on top.

The estimate was to close tokens in one week. It didn't close in one week. And the reason it didn't close is what's worth telling.

Tokens looked like the simple part

Creating and organizing tokens, in the abstract, looks like the easy part of the project. Color, typography, spacing, border radius. Small stuff.

What we kept discovering is that the problem isn't creating the token. It's migrating the system to it safely and updating that in production without breaking what's already running. That stops being a design task and becomes a systems task. Each small token decision opens up a range of infrastructure, code, and process decisions that we hadn't sized. That's why Milestone 1 ended up becoming a sequence of activities much bigger than the initial list, and we realigned almost every day to deal with what kept showing up.

Three of those surprises deserve a record, because they are exactly the kind of thing nobody tells you.

Typography, which got stuck more than any color

The most emblematic case was typography. And it was the most annoying precisely because it looked trivial.

In Figma, italic is not a separate property. Figma fuses weight and style into a single string, "regular italic", "medium italic", so the weight you pick already carries the italic built in. But on export, when the code is going to consume it, that needs to come decomposed: the weight on one side, and on the other whether it's normal or italic. We had modeled italic inside the weight field, and that was generating a wrong token on output.

We spent a good while trying to solve this inside the variables. Creating a type for normal and italic, mapping weight by weight, size by size. I kept running it, Glauber kept testing, and with each attempt the organization got more confusing instead of cleaner. At some point we stopped to look at how other design systems handle this (Ant Design, Nitro), and the answer was a little humbling: they don't tokenize typography in variables. They solve it in the styles.

The decision that closed the matter was to drop the typography variables and consume directly from Figma's text styles. The style already carries everything together (font, weight, italic, line height, letter spacing), and that is what React and Flutter now read. I want to say that this is not a pretty win. We changed it, undid it, redid it, and in the end removed something we had built. That broke the export plugin, which had been built on top of the document with all the variables, and forced us to update it.

There was even a naming detail in the middle of this. We used "medium" as the name of a font size, but "medium" is also the name of a weight. For an LLM reading it, that ambiguity is an invitation to error. We swapped it for "Base", in the Tailwind style (text-base). It sounds silly. Token naming matters more than it seems when the reader is the machine, and that had already come up earlier in the project.

The bind, which we couldn't automate from outside

In the last article I left the bind prompt running via Figma Agents, partially working. The continuation of that story is that we tried several paths to automate the bind from outside (Claude Code via MCP, scripts through Scripter, Figma Agents itself) and none delivered enough for scale. Figma Agents got to about 80%, which helps but doesn't close it.

What unblocked it was moving the bind into the plugin we had already built internally, instead of leaving it as a loose prompt. There we could put in rules that the prompt had trouble guaranteeing: fallback to the nearest value when the exact value doesn't exist (a border radius of 9 becomes 8, and doesn't become invention) and, for color, comparison by distance in RGB space to find the correct semantic variable.

The concrete result on the design side is what puts me most at ease about Milestone 1: with this plugin, we managed to bind almost 100% of the components in the Figma Design System file to the correct tokens. It is not the definitive bind architecture of the universe. It is the one that works for our file, with our team, right now.

Where the JSON lives, and why that became a discussion

The last surprise was about infrastructure, and it's the one that ties most to what I wrote in "Before AI-first, human-first".

When we exported the tokens JSON, it came out close to 40 thousand tokens in size, too big for an LLM to read directly. Rodolfo added a script to reduce that, and Gabriel proposed minifying the file, removing the spacing: for the machine to parse it makes no difference, the readability problem is only ours.

And there was a debate about where that JSON should live. The easy option was to leave everything in Prolog Cloud, the repository that centralizes the product context, because any PD could run the skill and commit without understanding infra. The problem is that dumping a lot of token files there can get in the way of the LLM's indexing, which already has plenty to handle.

The path we settled on was to keep Prolog Cloud as the source of truth for the tokens and, from a commit there, mirror the file to a separate Design System infrastructure repository, which then cascades the updates to React, Flutter, and Glorp. We accepted raising the pipeline's complexity to lower the complexity of use. Glauber, or any designer, runs the skill and commits, with no need to clone the infra or understand how it works. It's the same kind of trade we had already made with governance: the ideal solution on paper loses to the solution the team can operate without drowning.

What we tested, and what showed up

With the foundation up, we tested applying harness to the tokens. The idea was to ask Claude to show visually how it understood each token from the documentation, and to ask it to build test interfaces (an alert component, for example) just to see how it applied the tokens in practice.

The results were positive. It became reasonably clear that Claude was understanding the intent of each token: when to use it, when not to use it, what goes with what. I won't inflate this beyond what it was. It was an initial test, on a small slice, and the goal was only to get a signal that the schema we wrote produces the right understanding on the other side. It did.

The way of testing was simple, I took all the harness documentation for the tokens and asked Claude to tell me what it understood, lol, a bit more complex than that, but that was the idea, what I generated was a report with the result.

Next is a summary of the report, and one detail, Glorp is the name of Prolog's Design System.

The honest scope of all this

Here it's worth being direct about what Milestone 1 delivers and what it doesn't deliver yet.

What it delivers is a scalable, semi-automated token foundation, with governance in both Figma and code, tokens consumable by an LLM, and almost all the components in the design file tied to the right tokens. It's a solid base.

What it doesn't deliver yet is the result that gives the project its name: an LLM assembling screens with direction. That return doesn't come from token harness. As had already been made clear before, token harness is different from component harness, and most interface decisions will come from the components, not from loose tokens. The token weighs in when Claude needs to create something from scratch, without a ready-made component to reuse. The real production gain lives in component harness, which is a later stage. This first part is the one that makes that part possible, not the one that harvests its fruit.

What's next

We closed Milestone 1 and Milestone 2, which was the harness of the tokens themselves, for Prolog Cloud, React, and Flutter. Milestone 3, applying the tokens to the components, is already underway.

I can't say what the results will be. There's the migration of the tokens in the React and Flutter components, which we'll tackle with an AI-assisted old-to-new mapping. There's an idea of also creating harness for the old tokens, to give Claude a comparison reference and increase the accuracy of that migration. There's the open doubt about whether that minified JSON will be consumed well at scale. And there's the rollout in production, with the new and old tokens coexisting for a while. Each of these fronts can work out, go wrong, or reveal a problem I haven't seen yet. It's the part of the project I can predict the least, and probably the most interesting to document when it happens.