Kelli Novotny · Product designer · New York

I design and build.

On my current work an AI writes the code that turns my designs into screens. Nobody walks over to ask what I meant, which changed what a spec has to carry and what has to check the result afterwards. It will also tell me the result is great whether it is or not, so working that out is still my job. This page is what came of that.

Two design systems, the checks that keep them honest, and the calls I keep for myself. The case studies live at kellinovotny.com.

01 · Why the work changed · The three gates

The builder is a model.

On my current engagement I design and the founder builds in Flutter with a coding agent. So the thing turning my designs into screens isn't a person, which changes what the spec has to carry. A colleague asks when something is missing. An agent invents. Three gates keep the build from drifting off the design. The system they check against is in his repo, written by me: six files, 1,136 lines, zero deletions, merged in September.

Gate A · Figma

Token names only.

Nothing leaves Figma unless every element can be named in tokens. Primitives hold values, semantics hold roles, and what I'm checking is which layer a thing is bound to, not just whether it's bound to anything at all. I run the check against the Figma file and get the violations back.

Gate B · Handoff

The spec is for the model.

A work order per screen: exact token names per element, plus the exported frame, committed to the repo, since the repo is what the model can actually read. The last line of the prompt is the one that matters. If the tokens don't cover it, stop and tell me.

Gate C · Build

The script counts first.

A script counts violations before I look. Exemptions get logged with the reason attached (one sheet runs 132pt of bottom padding, off-scale on purpose, for safe-area clearance). Retired tokens get deprecated with the lint on and never mirrored into Figma, because a dead token in a library is an invitation.

The last run on one screen found thirteen violations, none of which needed a new token: the design had drifted, not the system.

02 · One system, two places · Rexx

When Figma and the code disagree, I find out.

Rexx keeps the recommendations people give you. I designed it, built it and shipped it solo, with a model in the loop the whole way. Every color, radius and spacing value lives twice: once as a Figma variable, once as a Swift enum the components read. Here's 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 both sides for drift and won't change either until I say yes. Rexx's own UI is a release behind my bar, and I know exactly where.

RexxLive on the App Store since February 2026 therexxapp.com

03 · Rules a machine can follow · SafeMom

The rules can't live in my head.

SafeMom is a pregnancy ingredient checker, and my current engagement. You scan a product barcode and get back 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, which rules out holding the standard in my head and applying it by eye. So it lives in a file instead, next to the values, 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 tokens, not one color, and next to each value in DESIGN.md I write the rule that governs it. accent is 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

04 · What it can't decide · SafeMom

Credibility is the entire product.

Same product, the part that isn't tokens. The founder vibe-coded a v1 that found real traction and still looked like a prototype. That costs more here than it would elsewhere, because 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.

05 · What it's allowed to know

I design what the model knows.

Most of an AI product's behavior is settled before anyone touches a screen: what it knows, what it remembers between sessions, what it's allowed to do on your behalf. All design decisions.

Rexx · What it's allowed to do

The model that must not recommend.

Rexx saves the recommendations other people give you, so a model adding its own would ruin it. The first line of the system prompt forbids that. I wrote the prompt, the 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. Every turn resends everything, so loading less is both more accurate and cheaper to run.

06 · What I build myself

I write some of the code myself.

Not everything I make is a screen. None of it is heroic engineering either. It's the part of the job that used to be out of reach and isn't anymore, including this page, which is checked the same way the apps are.

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.

07 · What the year taught me · 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 already doing instead of taking it over. I don't keep allegiances. Claude Design got written off once and earned its way back. 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 room's full context, organized around what I chose to write down. Same note, both layers, mine on top.

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

Where this stands

I'm still figuring it out.

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

Kelli Novotny · kellinovotny.com · therexxapp.com

Light