Kelli Novotny · Product designer · New York

Machines check.
I judge.

I design and build, with a model in the loop every day. This page was made the same way, on the same token rule as the apps I work on. Below: how the work gets made, two design systems, and where I draw the line.

01 · AI workflow · The three gates

The builder is a model.

On my current client engagement I design and the founder builds in Flutter with a coding agent, so the thing that turns my design into a screen isn't a person. A colleague asks when something is missing. An agent invents. Three gates keep the build from drifting off the design.

Gate A · Figma

Token names only.

Before a design leaves Figma, every element must be describable in token names. Primitives hold values, semantic tokens hold roles, and what Gate A checks is which layer a thing is bound to. I run it through the Figma MCP and get a violation list back.

Gate B · Handoff

The spec is for the model.

A per-screen work order: exact token names per element plus the exported frame, committed to the repo, because the model can read the repo and can't read my Figma. The prompt ends: if the tokens don't cover it, stop and tell me.

Gate C · Build

The script counts first.

A script tallies token violations in the built screen before I look. Exemptions are logged, not hidden. Retired tokens are deprecated with the lint switched on, and never mirrored into Figma, because a dead token in the library is an invitation to use it.

Gate A's last run on one screen found 13 violations, and none of them needed a new token. The values they should have used already existed. The design had drifted off the system, which is what the gate is for. The gates also catch their own blind spots: Gate A once passed a border bound to a primitive, which cleared "bound to a variable" and failed the only question that matters. The check is now "bound to the right layer."

The principle

Design the pipeline for the builder you actually have.

02 · Design systems · Rexx

One token, two homes.

Rexx is an iOS app for keeping the recommendations people give you, designed, built, and shipped solo with a model in the loop. Every color, radius, and spacing value lives twice: a Figma variable and a Swift enum the components consume. Here is one of them. Flip the toggle in the corner.

Figma

color/status/inProgress

#F28C40
Lightcolor/statusOrange#F28C40
Darkcolor/statusOrangeDark#F5A55E

Swift

static let inProgress =
  Color(UIColor { traits in
    traits.userInterfaceStyle == .dark
      ? UIColor(...)  // #F5A55E
      : UIColor(...)  // #F28C40
  })
105 variables64 primitives41 aliaseslight + darkmirrored in RexxStyles.swift

Ten of the 41 aliases, read from the file: three whites in light become three different darks

background/base#FFFFFF#000000
background/grouped#F1F1F0#000000
background/elevated#FEFEFE#1C1C1E
background/tile#FFFFFF#2C2C2E
background/placeholder#F4F4F4#2C2C2E
stroke/divider#E6E6E6#1C1C1E
text/base#000000#FFFFFF
text/heading#303030#E6E6E6
text/label#1C1C1E#E6E6E6
status/inProgress#F28C40#F5A55E

A sync skill I wrote checks the two homes for drift and won't change either side until I say yes. The footer numbers and the sheet above were read live from the Rexx Figma file on September 21, 2026. One honest note: I shipped Rexx function-first, so the UI is a release behind my own bar, and I know exactly where.

Rexx app icon RexxLive on the App Store since February 2026 therexxapp.com

The principle

If a screen can't be described in token names, either the screen drifted or the system is short a token.

03 · Semantic tokens · SafeMom

The rules can't live in my head.

SafeMom is a pregnancy ingredient checker, and my current engagement. Someone scans a product barcode and gets one of three answers: safe, caution, avoid. That answer has to land without raising its voice, in both themes, in a build I don't own. So the rule lives next to the value, where the thing building the screens can read it.

SafeverdictSafe
fill · onFill · border
CautionverdictCaution
fill · onFill · border
AvoidverdictAvoid
fill · onFill · border

Each verdict is a set of semantic tokens, not a color: the dot, the fill behind the card, the label on that fill, and a border that is decorative on purpose. Next to each value in DESIGN.md I record the rule that governs it. For accent: always the brand blue, never a verdict. Nothing in the name tells you that. The file does.

SafeMom result card, safe verdict SafeMom result card, caution verdict SafeMom result card, avoid verdict

The rules hold in both themes, every pair measured rather than eyeballed, and the verdict never rides on color alone: dot, label, and fill agree.

Dark mode found the drift.

Rules only mean something if something checks them. To make these captures follow the toggle in the corner, I pulled dark-mode frames out of my Figma file, and four things broke. Each had been wrong all along, and light mode never showed it. Two of them:

01Coach-mark text sat on a scrim that doesn't invert.The text token flips for dark. The scrim is a constant. They met in the middle.
02The viewfinder dissolved into the surface.Its edge was the surface color, one step off. In dark mode, no edge.

Six semantic tokens minted, nineteen nodes rebound, light mode pixel-identical. SafeMom's dark theme isn't reachable in the app yet, so all four were caught before any user could see them. A page demo turned into a fix for a paying client, the same afternoon.

The principle

The machine can tell you a button is bound to the wrong layer. It can't tell you the verdict reads calm. That's where its job ends and mine starts.

04 · Product judgment · SafeMom

Credibility is the entire product.

The same engagement, the part that isn't tokens. The founder vibe-coded a v1 that found real traction and still looked like a prototype. Here that costs more than it would elsewhere: someone is deciding whether to believe an app about their pregnancy, and a tool that looks unfinished doesn't get the benefit of the doubt.

Before: the founder's v1

SafeMom home screen before the rebrand

After: the brand it earned

SafeMom home screen after the rebrand

The brand: warm on top, clinical underneath. The structure: the old home screen was a lobby in front of the app's one job, so I cut it.

A verdict is a claim.

A scanner gets judged on the scans that work. I drew the three that don't: still loading, a barcode that matched a product but not an ingredient list, and no connection at all. None of them shows a verdict color, because a verdict is a claim and the app hasn't earned one until it has actually checked something. The "based on the label we could read" line comes off those screens too, since no label was read.

Drawing the loading state turned up a gap: the skeleton bars had no token to stand on, so a divider token was doing a surface job. That is the system being short a token, not the screen drifting. surfaceSkeleton was minted with its rule attached: loading placeholders only, never a divider.

The principle

Trust is built from what you leave out.

05 · Context engineering

I design what the model knows.

Most of an AI product's behavior is settled before anyone touches a screen: what the model knows, what it remembers between sessions, what it's allowed to do on your behalf. That's design work. Two receipts.

Rexx · What it's allowed to do

The model that must not recommend.

Rexx saves recommendations other people give you. A model that starts making its own would ruin that, so the first line of the system prompt forbids it. It classifies into whatever categories exist and can't invent one: you can add a category, the model can't. I wrote the prompt, the extraction schema, and the failure modes.

My system · What it remembers

The index is the memory.

My notes live in one Obsidian folder, and Claude reads the same files I do. The index points at notes instead of pasting them in, so a session loads only what it needs, and per-project rules send it to the right corner. Every turn resends the whole context, so the structure that makes retrieval accurate is also the one that makes it cheap.

The principle

The model is a constant. What separates two AI products is what you put in front of it and what you leave out.

06 · Design engineering

I work in the repo.

Three things I built that aren't screens: the theme layer inside a client's Flutter app, the tooling that checks my own work, and this page. None of it is heroic engineering. It's the part of the job that used to be out of reach and isn't anymore.

In the client's repo

  • A primitives file, nineteen semantic tokens, both theme maps.
  • A preview screen, and a CLAUDE.md telling his coding agent the rules.
  • Six files, 1,136 additions, zero deletions. Nothing a user could see changed.

Skills I wrote

  • A Figma-to-Swift parity check that won't change either side until I say yes.
  • One that turns a working prototype into annotated Figma frames.
  • Both run on my own work, before anyone else sees it.

This page's tokens, read from the stylesheet right now

Flip the toggle. Values and counts are read off the live stylesheet, not typed in.

Claude Code daily, with the Figma MCP for the token audits and the captures. therexxapp.com is Next.js on shadcn and Tailwind, also built in Claude Code, repo public on my GitHub.

The principle

Scripts check the mechanical truths: tokens bound, nothing drifting, nothing missing. Taste is the part I keep.

07 · Point of view · Granola

Hardest year of my career. Best one too.

Most of how I work now is new, and I learned it by getting it wrong first. The tools worth keeping turned out to be the ones that added to what I was doing instead of taking it over. I don't keep allegiances, I go back and retest, and Granola is the one I've liked most this year.

Granola: the note that stays mine

I type half-thoughts while I listen, and Granola hears the whole room. The enhanced note merges the two: the workshop's full context, organized around what I chose to write down. It adds what the room said without replacing what I wrote. Same note, both layers: mine on top, enhanced beneath.

Granola note, My notes view: raw half-typed notes from a talk on context design Granola note, Enhanced view: the same note expanded into structured sections

The principle

Add what the machine knows without replacing what I decided. That's the rule for Granola, and it's the rule for this page.

The short version

That's the loop.

Everything here got better because someone questioned it. I'd like more of that.

Kelli Novotny · kellinovotny.com · therexxapp.com

Light