Specifying an AI composer, frame by frame
One bar carried every AI feature in an investment platform and offered everything at once. I respecified it as twenty-six frames across six state families, each with the one line an engineer can pass or fail.
Senior Product Designer. Interaction design, working prototypes, and the delivery spec.
Institutional allocators and fund managers. Sophisticated, time-poor, sceptical of automation.
Ongoing. AS-IS audit through specified states and prototypes.
Designed in Claude Design on the current design system, and shared with engineering through Claude Code.
01 — Context & my role
The platform serves private capital markets: allocators and fund managers running diligence, tracking managers and preparing for investment committee. The composer is the way into every AI feature in it — one bar on the dashboard, and the same component inside reports, documents and meeting notes.
I was the designer on it: the audit of what existed, the interaction patterns, the prototypes in code, and the delivery board engineering built from. The work is still shipping, so this case is illustrated with the wireframes from that board rather than product screenshots. Labels are invented; structure, states and behaviour are as specified.
02 — The problem
The first layer offered everything at once, and once you chose something it disappeared into the input.
I audited the as-is composer and thread. The first layer of the composer carried too much: a shortcuts row and an all-shortcuts grid, a context picker, a source selector, research, web, history, workflows and send — all exposed before the user had typed anything. Choosing was harder than asking. And once something was selected — a document, a team, a generated output — it was rendered inside the composer itself, mixed into the text the user was still writing, so the question and its context were indistinguishable.
Ask anything…
The as-is composer. Nine controls on the first layer, and selected context rendered inside the input. Drawn in grey throughout; every redesigned bar further down is drawn in ink.
03 — Constraints
04 — The decision
Two moves. The composer's first layer comes down to a prompt, a plus, a model selector and send — commands live behind / and context behind +. And everything the user has attached moves out of the input into a row of context tags below it, where it stays visible without competing with the question being typed.
One prompt, a plus for context, the model, send. Commands sit behind the slash the placeholder names and context behind the plus — the figure opens that second level and closes it again.
Why — the dashboard is where open questions start, and it was the one surface with no room for a new column, so the composer had to sit in the existing composition.
05 — The states
The composer was specified as twenty-six frames across six families, each carrying the one line an engineer can pass or fail.
The first layer is a field, a plus, a model read-out and send. Everything behind it has states of its own, and most of them had never been drawn: an attachment that fails to upload, a picker on first run with nothing to pick, a microphone that is not there, a model control that disappears when a flag is off.
Where the machine already had numbers, the spec uses them rather than approximations: a 120-second recording cap, a countdown that appears with fifteen seconds left, a 44-bar waveform. Seven keyboard bindings are written out as a contract, and Escape is still an open question — two panels can be open at once, and only the innermost should close.
Seven states between an empty bar and a failed send. Only the first and the last could be inferred from the frames that existed.
Send turns live on the first character and the placeholder is gone, not faded.
The field grows to its cap and then scrolls; the toolbar row never leaves the bottom of the bar, and the attachment bar stays attached beneath it.
The field is inert but still legible, the mic is gone, and the only live control is Stop.
Could not send. Check your connection and try again.
The message stays in the field, the reason sits below the bar rather than in a toast, and send is live for the retry.
The whole composer drops back and nothing inside it takes focus. Used while a run owns the surface.
The bar under the composer is the only place an attached thing appears. Four of its five states were unbuilt, and the overflow rule was not decided at all.
A single badge sits on a bar that shares the composer border and rounds off the bottom.
Past the bar width the remainder collapses into one count that opens the full list. The badge reads +N with no trailing word.
Clicking the count opens the bar to a second row and no further. Whatever still does not fit stays behind a +N badge on that second row.
The uploading file takes the muted badge with a spinner in the leading slot; send stays live and queues behind the upload.
board-pack-final.pdf could not be read. Remove it or try a different file.
The failed badge is destructive and keeps its ×, and the reason is spelled out below the bar rather than in a toast.
The design-system Picker already had these states. They were missing here because nothing had been drawn with anything other than a full list.
The + opens the attach menu in the same slot the picker later occupies — bottom-left, 8px above the composer edge, 360px wide. Escape closes it and focus returns to the field.
Choosing Workspaces swaps the attach menu for the picker in the same anchored slot, so nothing jumps. Multi-select keeps it open so several can be attached.
Typing narrows both groups live. A group with no surviving row disappears rather than showing an empty heading.
The empty case quotes the term back and the search field keeps focus, so the user can correct it without reaching for the mouse.
Distinct from no results: nothing exists rather than nothing matched. The route out needs an action the Picker does not have, so either the component gains one or this panel is composed by hand.
Five phases in the machine, a 120-second cap, and a countdown that appears with fifteen seconds left. The board drew the settings popover and nothing else.
Voice transcription is starting…
The control takes a wash and a spinner, is disabled, and reads that transcription is starting.
The field gives way to the waveform — 44 bars, 2px minimum — with elapsed time on the right. The mic is a filled square.
Transcription stops in 0:13
The countdown appears at 105 seconds elapsed and not before; the announcement fires at fifteen, ten and five seconds to screen readers only.
Transcribing audio…
Same wash as starting. The mic cannot be pressed again and the settings chevron is disabled with it.
No microphone detected
One of five reasons shows, in order: unsupported browser, no microphone, another composer already transcribing, starting, transcribing. The control is inert, not hidden.
The Source dropdown is not going away — its job moved inside the plus, and the move is not as clean as the removal list implied. Source is exclusive today, attachments are additive, and one of its options has no row in the new menu at all.
Meetings appears as an attachable source alongside Workspaces. Today the menu has no row for it, so a user who could ask across their meetings before cannot after the change.
Modes moved into the bar in this proposal, so their off state had never been drawn — and off is the state a user sees first. The model menu had been drawn from memory and was wrong on three counts: a deprecated model, one that is not in the registry, and the wrong default.
With no mode on, the toolbar is the plus, the model read-out, the mic and send. Nothing occupies the space the badges would take.
A mode reads as one badge with an × that turns it off. The plus-menu switch and the badge are the same state seen from two places.
Ten active models across four providers, deprecated ones filtered out, and the default is GPT-5.5 rather than a 5.6 variant. Open question: whether the menu groups by provider as drawn, or runs flat as the component does today.
With multi-model off the list is the OpenAI subset and nothing else. No provider headings, because there is only one provider.
The selector returns nothing when the flag is off for a surface, so the read-out is absent and the mic sits directly beside send. The composer must not leave a gap where it was.
A target-state board is only half a spec. Eight parts of the composer exist today, and each one carries a disposition. Two of them are marked decide because they are genuinely open: whether the plus keeps sources mutually exclusive the way the old dropdown did, and what happens to the two sources that are written into the spec but have no endpoint behind them.
06 — Impact
The composer is in build, so what can be claimed today is the specification itself.
Adoption and time-to-answer are being measured as the composer rolls out. I would rather publish those once than estimate them here.
07 — What I learned
08 — What this case doesn't cover
The composer is live and unfinished. Four things are outside what is shown here.
Let’s build AI into a product with the structure to grow it.
AI changes how a product behaves, not just how it looks. I design the surfaces, specify the states behind them, and leave a structure the team can extend rather than rebuild.
Let’s talk