# The design system anyone could build with

I turned our Figma library into code, then wrote the rules that let product people build internal tools without waiting for a designer.

Status: Shipped · Disciplines: Design systems, Design engineering, Front end · Built with: Figma, React, Radix UI, TanStack Table, MCP, Agent Skills, Claude Code · Year: 2026

Source: https://www.ahmedamr.com/work/tagaddod-mcp-design-system

<ProjectSummary label="TL;DR">

- **2024, in Figma.** I designed Tagaddod's design system: tokens that work in English and Arabic, and components built from the smallest part up to the table.
- **2025, in code.** I built the coded library myself with a coding agent, on headless components dressed in our designs, and handed it to front end.
- **2025 to 2026, for everyone.** Product people were building their own tools with AI, and no two looked alike. I wrote the house rules an agent needs to build our way, put an MCP server in front of them, and sat with people while they built.

</ProjectSummary>

<RoleScope
  role="Product Designer & Design Engineer"
  duration="The middle of 2024 to 2026, alongside my design work"
  scope={[
    "Figma tokens and components",
    "React component library",
    "Agent skills and screen guide",
    "MCP server",
    "Documentation, training and rollout",
  ]}
/>

<Figure
  id="tagaddod-system-hero"
  title="The system, rendered from itself"
  description="Token swatches, a component specimen, an agent session, and a production screen, captured from the running library rather than redrawn."
  expand
/>

*Chapter 01 · 2024*

## The handoff

> Why did every screen come out almost right?

When I joined Tagaddod, the company had one product designer, and I made it
two. Between us sat apps for customers and for the collectors in the field, a
spread of web forms, and a growing number of internal systems. Every one of them
was designed with care in Figma.

They came out of development looking similar, with the spacing a little off and
a button that was almost ours. Nobody was doing bad work. Front end was
rebuilding each design by eye, screen by screen, with no shared parts to start
from.

<DriftComparison />

### A deal with front end

The fix we agreed on was easy to say. I would design the system once, properly,
as a library. Front end would implement it once, as components. After that,
nobody would rebuild a button by eye again, because every product would start
from the same parts.

That put the weight on the library. It had to be exact enough that an engineer
could build from it without guessing, and complete enough that a designer never
needed to step outside it.

<SystemDeal
  labels={{
    library: "As a library",
    components: "As components",
    products: "Every product would start from the same parts",
  }}
  lettered="once"
  summary="The deal, drawn: the library on the drafting table it was designed on, carried across once to front end's rack, which is pegged out because its components were not yet built, and the products it was for, an app for customers, an app for the collectors in the field, a web form and an internal system, every one of them to start from the same parts."
  caption="In 2024 the library was ready in Figma and the components were still a plan."
/>

### Scoring set the build order

Before designing anything new, I took stock. I went through the products and
listed the components they used: which ones were being rebuilt a little
differently on every screen, and which ones had been made by hand for a single
place.

Then I scored each one on three things: how easy it would be to build, how often
it was used, and how much it shaped the experience. The scores set the order, so
the components that touched the most screens and cost the least to get right
came first.

### Colour you can predict

Then colour. Picking steps by eye in HSL gives uneven ramps, because HSL's
numbers do not follow how people see brightness: a yellow and a blue at the same
lightness look nothing alike. So we built the palette on HSLuv, a human-friendly
version of HSL that sits on a perceptually uniform colour space, the same
approach Shopify's Polaris uses for its palette.

In HSLuv, two colours with the same lightness look equally bright. Every ramp
could step the same way across hues, so we could predict how a step would
contrast without checking colours one at a time.

*An interactive figure sits at this point on the page: one lightness slider over six hues. In HSL the six colours come out unevenly light (at a lightness of 60, from 38 for blue to 97 for yellow on the CIE L\* scale); in HSLuv all six measure 60.*

Drag the lightness. In HSL the six colours drift apart in how light they look; in HSLuv they hold the number you asked for.

### Tokens before components

With the palette settled, I set up the tokens in the W3C design tokens format:
primitives first, then semantics. Primitive tokens hold the raw values, and
semantic tokens give those values a job. A component never asks for a
particular blue; it asks for the brand fill, or the border of a quiet section,
or the text that sits on a warning.

*A diagram sits at this point on the page: one card from the product, with every part labelled by the token that paints it.*

- `surface.primary` the card, `border.secondary` its outline and the rule across it.
- `fill.brand-secondary` the icon tile, `fill.brand` the filled button, `text.on-fill` the label inside it.
- `text.primary` the title, `text.secondary` the line beneath it, `text.link` the link.
- `fill.success-secondary` the status badge.

Each part of the card, labelled with the token it uses. The fill and the link are blue because the brand tier sets them. In the token file they are near-black.

The same split carried type and spacing, and from the first day it carried
language. Arabic and English do not behave the same way on a page. The same
weight reads lighter in Arabic, and a line height that is comfortable in English
clips it. So the tokens had to answer for both before any screen used them.

### Each tier may touch one thing

A structure like this stays small only if each tier is allowed one job and
refused the rest.

The core tier holds the raw values and belongs to nobody. Every step of every
ramp, every size and every radius lives here, named for what it is. A brand may
reach in, but rarely, and only when its own green is a different green.

The semantic tier is brand-neutral on purpose. The brand fill is near-black at
this tier and only becomes a colour one tier down. That is why a second brand
could arrive later without a rewrite.

The locale tier swaps the family and the weight ladder, and touches no colour.
One token, the regular weight, resolves to 400 in English and 500 in Arabic,
because Tajawal reads lighter at the same nominal number and its ladder has fewer
usable steps.

The direction tier changes no value at all. The properties are logical, so the
only thing that moves is the side they resolve to.

Two brands, two locales and two directions make eight versions of every screen,
and almost nothing is duplicated to get there.

The figure below runs on the tokens as they are today, so flip its switches to
see which tiers change. The brand switch came later, when the tokens moved into
code.

*An interactive specimen sits at this point on the page: switches for brand, locale and direction over a product card, and beneath it the five tiers that resolve into it.*

- **Core** holds the raw ramps, sizes and radii. GreenPan reaches in to move one primitive, `green.1200`, from `#007d44` to `#009f4d`.
- **Semantic** is brand-neutral: `fill.brand` is near-black here and only becomes a colour one tier down.
- **Brand** is where that happens. Tagaddod restates nineteen colour tokens; GreenPan overrides four, which is why GreenPan's link and icon tile fall through to grey.
- **Locale** swaps the type family and the whole weight ladder: `font.weight.regular` is 400 in English and 500 in Arabic.
- **Direction** changes no value at all. The properties are logical, so only the side they resolve to moves.

### From the smallest part to the table

Then the components, in the order they depend on each other. The smallest parts
came first: a button, a checkbox, a badge. Then the controls those parts make
together, like a select or pagination. Last, the component that leans on nearly
everything before it: the table, with its search, filters, sortable columns and
status badges.

<SystemPyramid
  courses={["The smallest parts", "The controls those parts make together", "The table"]}
  lettered="in the order they depend on each other"
  summary="The components raised in the order they depend on each other, like the courses of a pyramid. The smallest parts make the bottom course: a button, a checkbox and a badge among plain stones. The controls those parts make together, a select and pagination, are the course on top of them. The table, with its search, filters, sortable columns and status badges, has just been hauled up the ramp onto the top, last."
  caption="Each course rests on the one below it, which is why the table came last."
/>

Every component got its own documentation in the library: its states, its specs,
and how it should be used. By the time it was code, the library was grouped by
what each part does.

<SystemShelves
  layers={[
    {
      name: "Tokens",
      note: "Raw values, what they mean, and what changes on top",
      items: ["Core", "Semantic", "Extras", "Brands", "Locales and directions"],
    },
    {
      name: "Utilities",
      note: "Quiet helpers",
      items: ["AspectRatio", "ScrollArea", "Separator"],
    },
    {
      name: "Navigation & actions",
      note: "What people press",
      items: ["Button", "Tabs", "Pagination", "UserMenu"],
    },
    {
      name: "Forms",
      note: "What people fill in",
      items: ["TextInput", "Textarea", "Select", "Checkbox", "RadioButton", "Switch", "DatePicker", "Calendar", "Number", "RangeSlider"],
    },
    {
      name: "Display",
      note: "What people read",
      items: ["Badge", "Avatar", "Card", "Logo", "Table"],
    },
    {
      name: "Overlays",
      note: "What opens on top",
      items: ["Modal", "Drawer", "Popover", "Tooltip", "Listbox", "Toasts"],
    },
    {
      name: "Layout & shell",
      note: "Where a whole screen sits",
      items: ["Page", "Navigation", "ContentPanel", "ContentLayout"],
    },
  ]}
  lettered="the library"
  summary="The library drawn as a shelving unit, with a book for every component. The tokens are the plinth it stands on. Above them, three bays of two shelves: utilities, navigation and actions, and forms along the bottom shelf, then display, overlays, and layout and shell along the top one. The parts the article names stand face out."
  caption="The finished library, grouped the way its documentation groups it."
/>

### The code stopped at two components

Front end started with the button, then the text input. Both took longer than
anyone had planned. Meanwhile the roadmap kept arriving, and the product managers
had to decide where the engineer's time went. They moved it to other work, the
way a long-term fix usually loses to this quarter's features.

The library survived in Figma. Both of us designed with it every day, and it
kept our designs consistent with each other. In code, nothing changed. The
products went back to looking similar.

<SystemStopped
  labels={{
    figma: "In Figma",
    code: "In code",
    built: "The button, then the text input",
    products: "The products went back to looking similar",
  }}
  lettered="the roadmap"
  summary="The same drawing at the end of the chapter, with everything in the same place. In Figma, the library is still whole on the drafting table. In code, only the button and the text input stand built on the rack, and the rest is still pegged out. Where the library was carried across, the roadmap's crates arrive on a sledge, with the crew busy moving them, and the products are marked where they went back to looking similar."
  caption="The same drawing at the end of the chapter. Two components got built before the roadmap took the engineer's time."
/>

*Chapter 02 · 2025*

## The second try

> Could a coding agent put the code in a designer's hands?

Then coding agents became good enough to take seriously.

I was not starting from zero. I had written HTML, CSS and JavaScript, a little
React, and I knew my way around Git and a build setup. But I did not have the
nerve to start a system that size from nothing, on my own, next to a full design
job. A component library is the kind of project that looks finished long before
it is.

A coding agent made starting cheap enough that I stopped asking whether I could.

### Borrowing what was already solved

Before writing anything, I went looking for what I should not build, and found
headless components: libraries that solve behaviour and accessibility (focus,
keyboard control, what a screen reader hears) and leave every pixel to you.

That split suited me. Radix covered most of the library, and where it had
nothing, other libraries stepped in, like TanStack Table for the table. That
left the part I knew best: making each one look and behave like the component
we had already designed, state by state.

<Figure
  id="tagaddod-system-headless"
  title="Headless, then styled"
  description="Radix parts with only the browser's defaults, beside the same parts as the library ships them."
  expand
/>

**Borrow the behaviour, own the look**

Chose: Build on headless components, Radix first and libraries like TanStack Table where Radix had nothing, and restyle each one to the Figma library, state by state.

Trade-off: The library depends on primitives I do not control, and a component can only behave in the ways its primitive allows.

### Describing a design to something that cannot see it

The hard part was the handoff again. This time the one receiving it was an
agent.

Figma had no official MCP server yet. A community one existed, but too much of
the design got lost on the way through it. So every component started as
writing. Every space was described in relative units, so it would scale. Every
state was named: hover, pressed, focus, disabled, loading, error. Every place
Arabic behaves differently was spelled out. The agent could not see the design,
so the description had to be the design.

When a component came out wrong, it was usually a decision I had not written
down.

<SystemLost
  routes={{
    pipe: "Lost on the way through it",
    writing: "Every component started as writing",
  }}
  notes={[
    "Relative units",
    "Hover, pressed, focus, disabled, loading, error",
    "Every place Arabic behaves differently",
  ]}
  lettered="the description had to be the design"
  summary="A design on a drafting table reaching an agent, a screen on a desk, two ways. Through a community MCP server, drawn as a pipe with one joint parted, bits of the design fall out on the way. As writing, a sheet stood by the desk spells out relative units, the states hover, pressed, focus, disabled, loading and error, and every place Arabic behaves differently, while the next page is carried over. What the agent builds from it still has one small gap marked, because the description had to be the design."
  caption="Two ways the design reached the agent. The written one carried more, and still had gaps wherever a decision was never written down."
/>

### A second brand did not need a second library

The tokens moved into code alongside the components, and once there they picked
up something the Figma file had never carried: a second brand, GreenPan, with a
green of its own.

The obvious answer was a copy: duplicate the library, swap the palette, maintain
two. Every fix after that would have to be made twice, which is what the
brand-neutral tier had been built to avoid.

A brand overrides only the tokens it disagrees with, and everything else falls
through to the value underneath. Tagaddod restates nineteen colour tokens.
GreenPan overrides four, and reaches once into the core tier to move its green.
Nothing else in either brand is stated twice.

There is a catch. Where a brand overrides nothing, it inherits a decision it
never made. Flip the brand switch on the token figure in the first chapter and
GreenPan's link and icon tile stay grey, because nothing in its tier reaches
them. The system is doing what it was built to do, and it takes a person looking
at the screen to see that grey is wrong.

**One library for both brands**

Chose: Let a brand override only the semantic tokens it disagrees with, and fall through to the neutral value everywhere else.

Trade-off: Whatever a brand leaves alone falls back to the neutral value, so someone has to check each brand's screens.

### Components that passed alone fought when nested

Each component passed on its own. The trouble started when they met.

A drawer slides in over a page. From inside the drawer, someone opens a modal.
Inside the modal, a select opens its menu. Each had been tested alone, and
together they fought over the top of the screen: menus opening behind the
modal, popovers disappearing under the drawer.

<div className="bleed">
  <LayerSwitch
    hidePanelLabel
    layers={[
      {
        id: "bug",
        label: "Before the fix",
        body: (
          <Figure
            bleed={false}
            id="tagaddod-system-overlap-bug"
            title="The menu behind the modal"
            description="A select opened inside a modal inside a drawer, with its menu stuck under the modal."
            expand
          />
        ),
      },
      {
        id: "fixed",
        label: "After the fix",
        body: (
          <Figure
            bleed={false}
            id="tagaddod-system-overlap-fixed"
            title="The menu above the modal"
            description="The same screen, with the menu layered above the modal it was opened from."
            expand
          />
        ),
      },
    ]}
  />
</div>

Patching each case would have meant patching it again in every product.
Instead, the overlays learned to notice when they are nested and to rise above
whatever opened them, and the order they stack in became tokens.

### New work adopted it, existing products did not

I built a few small things with the library to find what was missing before
anyone else did, then handed it over to front end. It was partly adopted: new
work started from the real components, while the products that already existed
stayed as they were.

<Figure
  id="tagaddod-system-library"
  title="A specimen sheet from the shipped library"
  description="Actions, forms and data display rendered from the components themselves, at their real sizes."
  expand
/>

With the real components in hand, I found I was prototyping faster in code than I
ever had in Figma.

*Chapter 03 · 2025 to 2026*

## The intern

> How do you teach an agent taste?

I was not the only one who had noticed what an agent could build. Product people
across the company had started building tools for their own problems with AI. It
was useful work, and there was more of it every month.

It also looked like whatever the agent happened to produce that day. The drift
from the first chapter was back, and this time it ran from a prompt straight to
a product, with nobody in between.

<SystemDrift
  panels={[
    { name: "From Figma to code", note: "Screen by screen, by eye" },
    { name: "From a prompt straight to a product", note: "Whatever the agent made that day" },
  ]}
  lettered="nobody in between"
  caption="In the first chapter, front end at least checked the result against a design. Here there was no design to check it against."
/>

So I set a goal bigger than a component library. Anyone in the company who is
not a heavy developer should be able to build something close to what a designer
would make, without waiting for one.

### Given the whole repository, the agent still guessed

Back then there was no easy way to plug a design system into an agent, and
nothing like Claude Design existed. The only way was to hand the agent the
repository and tell it to design with it.

It read everything and decided nothing well. It imported components that did not
exist, invented props that sounded right, and picked a blue close enough to pass
a quick look.

*An interactive comparison plays at this point on the page. The same request, twice:*

- **Handed the repository.** The agent imports a DataGrid and a Tag from a package that does not exist, sets props that sound right, and hardcodes a blue close to the brand's.
- **Asks the MCP first.** It calls `list_components`, `get_component_docs("Table")` and `resolve_token("{color.fill.brand}")`, then imports Table and Badge from the real package and uses the brand fill by name.

The same request, first from an agent handed the repository, then from one that asks the server what is true.

### House rules for a very fast intern

I started to think of the agent as an intern who has read everything and knows
none of the house rules. An intern like that needs the rules written down.

So I stopped trying to write better prompts and started narrowing the decisions
the agent is allowed to make. The first layer is how each component is meant to
be used, with the do's and don'ts a designer carries around in their head:

- Never add a component to the shared library because one screen wanted it. Build it in the product.
- No literal values. Every colour, space, radius and duration is a token.
- Right to left is part of done, not a flip at the end.
- Navigation holds pages with their own address. Never filters, records or buttons.
- Never set a z-index. Nested overlays already know their place.

That last rule is the drawer from the second chapter, written the way you would
write it for an intern:

**Critical rules for AI agents** · `CLAUDE.md`

```md
❌ NEVER manually set z-index values or style props.
❌ NEVER calculate z-index in your code
❌ NEVER worry about nesting depth or stacking contexts

✅ ALWAYS use components as documented: they handle nesting automatically
✅ TRUST the system: any combination of overlays will layer correctly
```

The second layer is a ladder, the order the agent tries things in. When someone
asks for something, the agent looks in the library first, from the smallest
component up to the largest. If something fits, it uses it. If existing parts
can be combined to do the job, it composes them. Only when the library has
nothing does it look outside, for a headless or ready-made component, and even
then it dresses the result in our tokens and keeps it inside the product until
it earns a place in the library.

*An interactive agent session plays at this point on the page. Two requests:*

- **The library has it.** Asked for a date filter, the agent calls `search_components("date")`, finds DatePicker, reads `get_component_docs("DatePicker")`, and uses it as documented. Nothing new is built.
- **The library has nothing.** Asked for a trip's progress as steps, `search_components("step")` finds no match. The agent reads `get_implementation_workflow`, looks outside the library with a web search, keeps the markup and drops the styles, confirms each value with `resolve_token`, reads `get_rtl_guide`, and builds the component in the product, not in the shared library.

Two requests to the same agent, replayed from a script of the server's real answers.

Around those two layers sat the guides: one for building right to left, and one
for making a screen look like a designer laid it out.

### Where things go had to be written down too

Rules about components were still not enough. An agent could use the table, the
tabs and the cards correctly and still arrange them into a screen no designer
would ship. Knowing that a table exists does not tell anyone where its
filters go, which panel owns the scroll, or when a list should sit beside the
record it opens instead of covering it.

I wrote down how we make layout decisions. Our products run on nested
navigation: a rail of sections, and beside it a panel of pages whenever a
section has them. The work itself happens in panels, and
several can be open side by side. The skills apply that structure to every new
screen unless the person building asks for something else.

Those defaults came from a product I had built, the warehouse app further down
this page. The screen guide says so itself: it encodes that product's visual
taste.

<div className="bleed">
  <LayerSwitch
    hidePanelLabel
    layers={[
      {
        id: "en",
        label: "English (LTR)",
        body: (
          <Figure
            bleed={false}
            id="tagaddod-system-logs-en"
            title="Send and receive logs"
            description="Two tiers of navigation and a table, rendered left to right."
          />
        ),
      },
      {
        id: "ar",
        label: "العربية (RTL)",
        body: (
          <Figure
            bleed={false}
            id="tagaddod-system-logs-ar"
            title="سجلات الإرسال والاستلام"
            description="The same screen rendered right to left."
          />
        ),
      },
    ]}
  />
</div>

**Our layout by default**

Chose: Put every new screen in our navigation and panel structure unless the person building asks otherwise.

Trade-off: A request that needs another shape has to say so, or the agent pulls it back into ours.

### Everything went behind one door

All of this needed a single entrance, or it would depend on everyone remembering
which document to paste into which conversation. That entrance became an MCP
server.

The server is read-only. It answers what is true before the agent starts
guessing: which components exist, what their props are, what a token
resolves to. It also routes the request. Ask for a screen, and it points the
agent at the screen guide before anything else. Ask for a single component, and
it checks the library first and hands over the ladder when nothing fits. The
skills work through the same door: the screen assembly skill reads the guide,
picks the closest known pattern, and hands off to the skills for navigation, the
page shell, the panels and motion.

*An interactive router sits at this point on the page. The MCP server sends a request one of two ways:*

- **A screen** goes to the screen guide first (`get_design_guide`), then to the closest known pattern, then to the skills for navigation, the page shell, the panels and motion.
- **A single component** goes to the library first (`search_components`), then to composing existing parts, then to the ladder when nothing fits (`get_implementation_workflow`).

Pick a request to see where the server sends it.

It did not start that tidy. The first version, in the middle of 2025, was an
experiment in helping an agent find components in the library. Skills did not
exist as a format yet. Anthropic released them while I was still working this
out, and they turned out to be the shape I had been improvising: instructions an
agent picks up only when the task calls for them.

<SystemHouse
  layers={[
    {
      name: "The request",
      note: "What the person asks for",
      items: ["A receiving screen", "In Arabic"],
    },
    {
      name: "MCP",
      note: "One door in: what exists, and where the request goes",
      items: ["Components", "Tokens", "Screen guide", "Fallback workflow"],
    },
    {
      name: "Skills",
      note: "How we decide",
      items: ["Layout defaults", "Usage rules", "Do's and don'ts", "Right to left"],
    },
    {
      name: "The ladder",
      note: "The library first, then outside it",
      items: ["Smallest component first", "Compose", "Headless or ready-made", "Dressed in our tokens"],
    },
    {
      name: "The product",
      note: "Then a person reviews it",
      items: ["Runs", "Reads right to left", "Looks like ours"],
    },
  ]}
  lettered="one door"
  caption="A button and a whole dashboard go through the same door."
/>

**One set of house rules for every agent**

Chose: Write how we decide once, as skills and guides behind one read-only MCP server, so every person's agent starts from the same rules.

Trade-off: The rules are documents to maintain as the system changes, and they stop short of a finished screen.

None of this came out right the first time. It was the same loop, again and
again: ask for a screen, look at what came back, find the decision the agent had
to guess, and write that decision down. I kept going until what came back looked
like it came from our design team, not every time, but often enough to put in
other people's hands.

<SystemLoop
  steps={[
    "Ask for a screen",
    "Look at what came back",
    "Find the decision the agent had to guess",
    "Write that decision down",
  ]}
  lettered="again and again"
  summary="The loop that got the rules right, drawn as four stations along a path, with a line from the last back over the top to the first. Ask for a screen, down a string to the agent. Look at what came back. Find the decision the agent had to guess, here a list laid over the record it opens instead of beside it. Write that decision down on the house rules. Then again and again."
  caption="The loop behind the house rules. Each pass turned one guess into a written decision."
/>

### The warehouse app paid the system back

The biggest thing built this way was the Warehouse Center, where collected oil
is weighed, checked and turned into inventory. When it needed rebuilding, I
built it end to end on the system's tokens and rules, with an agent doing much
of the typing. It became one of the products the operation runs on, and it has
[a case study of its own](/work/warehouse-center).

Its real screens, dense data and live scale showed what an agent still did not
know about composing a product, and its navigation and panels became the layout
the screen guide now teaches.

<Figure
  id="tagaddod-system-warehouse"
  title="The receiving screen, right to left"
  description="A reconstruction of the Warehouse Center's scale workflow, built from library components with synthetic records."
  expand
/>

### Sitting next to people

The most useful week of the whole project was the one I spent sitting next to
people while they built, watching where they got stuck and fixing the rules that
had let them down.

Most of the people I wanted to reach were not developers, so the help could not
start at the design system. The documentation site grew a learning path that
starts at "What is AI?", walks through the tools, the setup, GitHub and MCPs,
and ends with building a prototype, adding a real backend and connecting to the
company's existing systems.

<Figure
  id="tagaddod-system-docs"
  title="Three levels of prior knowledge"
  description="Foundations, component pages, and a learning path that starts from what an AI model is."
  expand
/>

My sessions followed the same order, starting with how to build a product from
nothing and leaving our button for much later.

<SystemPath
  steps={{
    first: "What is AI?",
    flight: "The tools, the setup, GitHub and MCPs",
    big: ["Building a prototype", "Adding a real backend", "Connecting to the company's existing systems"],
  }}
  lettered="from nothing"
  summary="The learning path drawn as a climb from the ground: a low first step, what AI is, then a flight of smaller steps through the tools, the setup, GitHub and MCPs, then three big steps up, building a prototype, adding a real backend, and connecting to the company's existing systems."
  caption="It starts with what AI is, before any tool."
/>

It changed habits. Product people shipped internal tools built on the system,
and prototypes that used to start in Figma started in code. Then it went
somewhere I had not planned. Before I left the company, the Chief Product
Officer asked me to run a session for product managers and engineers, so they
could keep building through the design system after I had gone.

*Epilogue*

## Still open

> How much designer does a product still need?

I would not call this finished. What an agent builds inside these rules still
needs a designer's pass to be right, and I keep working on that gap most days.

How much of a designer's judgement can be written down before what is left is
taste nobody can write? How small can that final pass get?

My answer so far: less than it used to, more than none.

<Outcome
  items={[
    "Design goes straight to code: consistent products and rapid prototypes ship without a Figma step",
    "Product managers ship small internal tools fast, and they look like the rest of the product",
    {
      label:
        "I built the Warehouse Center on the system, and it became one of the products the operation runs on",
      link: { text: "the Warehouse Center", href: "/work/warehouse-center" },
    },
    "House rules for agents, from component usage to layout defaults, behind one MCP server",
    "A coded library for English and Arabic, grown out of the Figma system",
  ]}
/>

<RelatedWork
  items={[
    {
      href: "/work/warehouse-center",
      label: "The warehouse hub I built from A to Z to stop a seven-figure oil loss",
      note: "The backbone product built this way",
    },
  ]}
/>
