All work

Side build · Design engineer · Oct 2026

agent-clarifier

I wanted one component with lots of states, variants and a personality, to try out the design engineering ideas I’d been collecting. I picked a favourite: the card Claude shows when it asks before it acts. shadcn/ui already has one. This was about what I’d learn, not what I’d beat.

Role
Solo build, paired with Claude
Status
v0.1.0, live on npm. Open source (MIT)
Link
github.com/Marigameo/agent-clarifier

At a glance

The aim

Not a perfect component. I wanted a fairly complex one, with variants, states and a personality, to practise design engineering on. Then I wanted to see how much of it I could teach an agent for next time.

My part

  • The homework. Claude’s card taken apart, how others ask, and the research on when to.
  • Every call. What each way out means, what got cut, the colours, the spacing, the size target.
  • The audits. One skill at a time, from Emil Kowalski and Jakub Krehel, plus my own doubts from using it.

Claude researched, drafted, built and tested. I set the direction and made the calls.

Considered

Every state, one question or many, keyboard and screen readers, right-to-left languages. All of it kept as light as I could: about 21 KB gzipped, with no dependencies. Live on npm and GitHub.

Have a go first. It’s the real component, not a picture of it.

Everyday

Ways out

Edge cases

I’m planning a trip to London, can you give me some hotel recommendations?

Happy to. A few quick questions first, so I search for the right places.

The playground needs JavaScript to run the real component.

The real agent-clarifier, v0.1.0 straight from npm, in a pretend chat. The details further down link back here.

Try it in a full chat app, on your phone too

Backstory

A favourite, not a flagship

  • I first wanted to build an artifact editor like Claude’s, with collaborative editing. It’s a huge build, and I’d already built something like it at work. So I picked something smaller and more personal.
  • Claude’s question card is one of my favourites. It rescues a lazy, vague prompt, and lazy people like me love it. I’d used it a lot, and was always curious when Claude decides to show it, and when it doesn’t.
Complex enough to learn from. Small enough for a weekend.

Research

Knowing when is the real work

  • I started with a vague Claude research about the card itself: how other frontier tools do it, and what’s public to learn from. That’s where I met its name, an ambiguity resolver. Sounds wild, right? I asked Claude to write the design tensions down in a learnings file as we went.
  • That’s when it clicked. The real moat isn’t the card. It’s teaching the agent when to use it, and when not to. And the card has many personalities: several questions or one decision, all at once or strictly one by one.

Homework

Borrow the feel, not the pixels

  • Reading done, I took the card itself apart. I lined it up against the AskUserQuestion docs, then against how Cursor, ChatGPT, MCP and LangGraph ask.
  • Three things stuck. Multi-select must never move on by itself. Skip and close mean different things. And when to ask matters as much as how, so the README ships a prompt that tells the model when.
Learn why it works first. Then the parts you keep are on purpose.

Design call · meaning

Every way out means something

  • I wanted each exit to tell the agent something different, so it can say what it did about it. Skip means “say what you assumed”. Decide for me means “say what you chose”.
  • So Decide for me takes Skip’s place instead of sitting next to it, to avoid the overload. And × became minimise, so nothing is lost. A message typed while it’s folded is the answer.
Same outcome for the agent’s next step, different intent from the user. So, a different reply.

Simplify

Ask what each control is for

  • After v1 I asked for a draft mode: pick, get the answer prefilled, add context, then send. It came back as three levels of opt-in, an Edit first button and a prefill strip. Reviewing it, I couldn’t say what Edit first was for.
  • So we cut it: one way to answer, the card, and one way out to your own words, the composer. Later Claude designed branching, and I said no to that too, to build a top-notch core first.
The real design board after round 3: a long page of frames for the question card, covering its anatomy, both flows, every input, the placements, the recovery states, the records it leaves in the conversation, and the new In your own words section that replaced draft mode.
The real board after round 3. It was frozen in round 6, when the playground took over.

Process

From a board to a playground

  • Plan and visuals came first: research, a written plan, a board of every state, then a build plan we agreed on before any code. Each round was snapshotted into process/, and never edited after.
  • Then I hit a wall: I couldn’t judge a shadow, a grey or a 120ms confirm from a drawing. So the board was frozen in round 6, and every call after it was made on the real component, in a playground like the one above.

On paper · round 0 to 5

  1. Round 0 · 10 Oct

    Homework

    Claude’s card taken apart, five other systems, and the research on when models should ask

  2. Rounds 1 to 3

    A plan and a board

    30+ frames in 8 sections, every state. Draft mode added in round 2, cut in round 3

  3. Naming

    clarify-card → agent-clarifier

    A noun, named after the job, not the look. It’s also a pill, a docked tray and a record

  4. Rounds 4 and 5

    Scope lock

    Branching said no to. A build plan agreed, and DESIGN.md started: a reason for every value

On the real component · round 6 onwards

  1. Round 6

    The playground

    The board frozen. Every call after this is made on the real thing, in a fake chat

  2. M1 to M7

    The build

    Model, element, both flows, keyboard, screen readers, converters for Claude and MCP, README

  3. Rounds 9 to 21 · 11 Oct

    Audits and details

    One skill at a time, then my own doubts from using it, one per round

  4. Rounds 22 to 26 · 12 Oct

    Weight, release and a chat demo

    Size measured against 25 packages, a review that read it like a stranger, then npm, Svelte 5 and a phone-first chat demo

Grey is paper. Coral is the real component, where most of the decisions got made.

With AI

I make the calls, Claude does the legwork

  • We worked as a pair in Claude Code. Claude researched, drafted, built and tested. I set the direction and made the calls, from options with a recommendation, one round at a time. When I delegated a batch of calls in round 8, I audited them after.
  • For the audits I brought in skills from people whose craft I trust: Emil Kowalski’s for motion, and Jakub Krehel’s for the interface. Each ran read-only first. I picked. Then Claude built it, and broke each fix on purpose to prove its test could fail.

One round

  1. Me A brief, or a doubt from using it
  2. Claude + a skill A read-only audit, frozen before any fix
  3. Me Pick from options, with a recommendation
  4. Claude Build it, test it, break the fix on purpose
  5. Both Snapshot, journal, commit

The skills

RoundSkillWhat it foundTurned down
Round 9Motion find-animation-opportunities Emil Kowalski FoundA toast that swapped in unnoticed, and the doc saying 200ms while the code ran 160ms. Turned downFive ideas: fills on rows, a pulse on the pill, an error shake and more.
Round 10Craft emil-design-eng Emil Kowalski FoundButtons that didn’t answer the hover, and an Undo timer that kept running in a hidden tab. Turned downEase-in-out for the height. It would lag behind the content.
Round 11Interface better-interface Jakub Krehel FoundSix reviewers in parallel, 10 high: contrast themes lost every ring, Send dropped focus, the toast read “Und” at 320px. Turned downIts own fix for one finding. It would have broken Undo.
Round 13Stress break Jakub Krehel Found34 worst cases on one page. 6 broke, and the worst was made by round 11’s own fix. Turned downStacked combining marks. Abusive input, not a real case.
Round 15Checkbox animate + review-animations Emil Kowalski FoundA 0.8px press and a 120ms tick, for the mouse only. Turned downMotion on keyboard toggles, on unticking, and a bounce.
Round 20Hover 7 practical animation tips Emil Kowalski FoundHover was already instant. But an arrow stayed on a row the mouse had left. Turned downHiding the keyboard’s arrow while a mouse rests on the card.
Round 23Release code review Claude Code FoundFour reviewers read the package as a stranger would: 44 findings, 9 serious, three README recipes broken. Turned downLong MCP lists. Documented instead, outside the scope lock.
Round 23Cleanup /simplify Claude Code FoundA regression in one of the review’s own fixes, caught before it shipped. Turned downTwo cleanups that would have changed behaviour.
The skills brought the checklists. The calls stayed mine.

Design call · colour

The user’s decision is the loudest thing

  • I asked why the card mixed black and blue. The answer found a real problem: blue had four jobs on one card, and the agent’s Recommended badge looked like my pick.
  • So we prototyped a monochrome card, and I kept the blue anyway. The badge went grey instead. The same habit, see the options on the real thing, then pick, settled the row spacing and the pencil too.

Blue had four jobs on one card, and the agent’s Recommended badge sat right next to my pick. Claude prototyped two monochrome cards from a shadcn reference I liked.

Look at the picked key, the Recommended badge and the focus ring.

The callKept the blue: it says “your pick” best. The badge and the “Asked by” dot went grey.

Prototype it on the real thing, flip between the options, then pick.

Details

Small things, on purpose

  • Most rounds started with a doubt from using it: an arrow that lingered, a header that lost its padding, a checkbox that felt plain. Each got a measurement before a fix, a test that fails without it, and a before and after.
  • I wanted it to feel calm, and calm is a long list of small things. Every default now has a written reason in DESIGN.md. If a value has no reason, it’s a guess, and guesses get fixed.

Round 17

A limit that informs, not scolds

“Up to 2 picks” was a black toast, louder than the picks themselves. Black is for toasts you can press, so now it’s quiet ink text.

pick four

Look at the footer, left of Skip.

Round 21

Back goes where the card doesn’t move

The card sits at the bottom, so its header is the edge that moves between steps. Back moved to the footer, and the dashes now show what’s answered.

Look at the top-left corner, then the bottom-left.

Round 14

“Something else”, typed in place

It used to open a second box under the row. Now the row becomes the field. On single choice, once you type, the other options fade to grey, never see-through.

press 4

Look at the last row.

Round 21

The number key answers the hover

The key was 2% away from the hover fill, so it vanished into it. Now it steps toward ink, mixed from the tokens, so a theme’s key follows.

hover a row

Look at the 1 key on the hovered row.

Round 18

A stuck header keeps its padding

A margin was collapsing out of the sticky header, so “Asked by” touched the card’s edge once you scrolled. The fix was one line: display: flow-root.

scroll the options

Look above “Asked by”.

Round 20

The arrow travels with its partner

Moving the mouse across rows left an arrow on a row it had already left. Now the arrow shows only with its partner: the fill for a mouse, the ring for the keyboard.

sweep the mouse

Look at the right end of row 1, in the second and third frames.

Round 9

The pill that turned into an oval

Slowed down 10 times, the fold showed the minimised pill stretched into an oval mid-way. No test was looking for that. Now the pill only fades in.

press Esc

Round 15

The checkbox animation that didn’t exist

I wanted the feel of Claude’s checkbox. Frame by frame, it fills in one frame: the feel was my screen recorder’s click circle. We animated ours anyway, by Emil’s rules: a 0.8px press and a 120ms tick, mouse only.

click, then press Space

Watch the tick draw in, left to right, over the first 60ms.

None of these is something you’d point at. Together, they’re why it feels calm.

Weight

Weigh it before you diet

  • I wanted it light, so nobody thinks twice about installing it. Claude measured first: 25 comparable packages, built with the same settings. Every safe saving added up to about 0.6 KB.
  • So the honest result was a better target, not a smaller number: about 21 KB gzipped, among single-purpose widgets that do less, and nothing at page load if it loads on first use. The playground above does exactly that.
Gzipped, each built the same way. React adds ~66 KB where it says so.

Misses & lessons

What I’d tell day-zero me

  • MissTrusted 100% test coverage. Two tests checked either side of a limit, never the limit itself.

    LessonTest at the boundary. Then break the code on purpose, and watch a test fail.

  • MissTrusted an “after” screenshot. The gap looked right only because a focus ring drew it.

    LessonA screenshot can agree with you for the wrong reason. Measure the thing itself.

  • MissAssumed the audits still held. Two rounds of my own changes later, an accessibility fix had quietly gone.

    LessonAudits expire. Re-run them after every change to the parts they judged.

  • MissTested against the source for 22 rounds. Three README recipes didn’t work, and some TypeScript users got no types.

    LessonRead it as a stranger. Install the package, and follow your own README.

What’s next

From a weekend to a day?

  • The end goal is bigger than one card: a skill and a knowledge base for crafting UX elements like this, faster. This one took a whole weekend of learning and care. Can the next one take a day?
  • The first two are out: a skill for the clarifier itself, when to use it, when not to, and which variant fits, and one for the design engineering habits from this build. Now anyone can check their agent is using it right.

The skills

  • craft-componentBuild a component the careful way. Brief, plan, milestones, audit, release on a go.
  • ask-clarifierWhen to ask, when not to, and which question type fits.

See all the skills

For the card itselfA VoiceOver pass, and a real phone with its keyboard.

Ask only when it matters, and make asking feel calm. That’s the card. Learn it once, and craft the next one faster. That’s the exercise.
npm install @marigameo/agent-clarifier npm GitHub Chat demo Playground

Every round, with my brief, the calls and the renders, is in the journal on GitHub

Back to all work