DEV Community

Cover image for When your GUI isn't the whole input language
Isamu Arimoto
Isamu Arimoto

Posted on

When your GUI isn't the whole input language

I spent a week of my life on where a button should go. I don't think I'll do that again, and it took porting a benchmark tool to work out why.

Let me concede the obvious thing first, because otherwise it's the only thing anyone will want to talk about: GUIs are still input devices. Direct manipulation, menus, palettes, keyboard shortcuts, drag targets, accessibility affordances — all input, all irreplaceable. I'm not arguing for chat-instead-of-UI. Command palettes, scripting and macro systems have absorbed open-ended commands inside GUIs for decades, and they work.

The argument is narrower, and it's about a specific failure: designing controls first, for a system whose set of valid operations won't hold still.

Where I noticed

I'd built a benchmark tool. It evaluates combinations of LLM/SLM models × skills, and scores each combination on four things: accuracy, runtime, turn count, and total cost.

It also tracks the moving parts. New models arrive constantly. Prices change constantly. Both are part of the tool's state.

It ran inside Claude Code. I decided to port it to a proper web tool, sat down to design the interface, and got about as far as two dropdowns before it fell apart.

The two dropdowns were fine. Pick a model. Pick a skill set. Both are finite, both need to be shown to you — you can't ask for a model if you don't know it exists — and both are cheap to render.

Everything else was the problem.

What the work actually is

The tool isn't really "run a benchmark." It's maintenance. Every time a model or a skill is added or updated, work appears:

  • A model was added. Re-run it against which skills? All of them, or the ones where the previous best is close? Compare against which baseline?
  • A price changed. Recompute historical cost, or only from here? Are last month's cost comparisons still meaningful?
  • A skill was updated. Which stored results are now invalid? Re-run the whole matrix, or the affected column?

I kept trying to design controls for these and kept discovering the same thing: each one is a slightly different task. Not a variant of a task — a different task, shaped by what changed and what I already knew.

To put that in a UI I'd have to enumerate them. Every one, in advance, before knowing what the next model release would make me want. And each new axis creates more cross-combinations with all the others.

In chat it's a sentence. "gpt-5-mini is in — run it against every skill and put it next to haiku." The agent works out which runs are actually needed. Nothing was designed for that request specifically, and nothing had to be.

That's the whole of it. Not "chat is better." Just: when the set of valid operations keeps changing, a control surface has to keep growing, and it's always one release behind.

Where the line fell

Not "finite versus infinite." I had it wrong at first. The line is:

UI keeps it Things that must be enumerated and shown — available models, available skills, the price table, the four scores
Chat takes it Things where the task is different every time — what to re-run, against which baseline, which results to invalidate

The model list has to be a UI, and not because it's short. It's because you cannot ask for something you don't know exists. Enumeration is the whole value.

The four scores are the same. Accuracy, runtime, turns, cost — you read them. They update when a price moves. There is nothing to click, and clicking would not help.

The part I didn't expect

When a user gets stuck in a GUI, you often get nothing usable. They can't find the control, conclude the thing can't be done, and leave. You may see that they dropped off; you rarely learn what they were trying to do.

When a user gets stuck in chat, they type what they were trying to do. Now you have a sentence describing a task your product doesn't support, in their words.

I want to be careful here, because there's an obvious objection and it's correct: chat users also leave silently. Plenty do. And GUIs have telemetry, funnels, session recordings — this isn't a categorical difference, and I'd be overselling it if I said it was.

What it is, I think, is a difference in recovery. A failed GUI interaction usually leaves you an event, not an intention. A failed chat interaction leaves a sentence, which means someone — you, a support person, or the agent itself — has something to answer while the user is still there. Not always. But it's the difference between a signal you have to interpret and one that states itself.

For a tool whose task list changes every week, that mattered more than I expected. The unsupported requests are the roadmap.

A second data point, honestly labelled

I also maintain a browser terminal grid for supervising several coding agents at once. Different product, different problem, built for different reasons.

Counting one cell's header: roughly 11 buttons and 9 status chips. The chips are directory, git branch and change count, model and context percentage, token counts, which PR or issue the cell is on, a diff badge, usage, and a one-line summary of what the agent is currently doing. None of them is clickable in that configuration. They are read.

And every button that survived does the same kind of thing: expand, close, show files, open the canvas, open the timeline. They change where you look. Not one of them makes the agent do anything — that gets typed.

The same boundary appeared in another product, one I wasn't thinking about the benchmark tool while building.

But I should not call that convergence. Same author, same period, same tools, same assumptions. It's a personal design pattern showing up twice, not independent validation. Take it as evidence that the boundary isn't specific to one benchmark workflow, and no more.

Where this stops being true

Precisely, because a claim that never fails isn't saying much:

  • Precision and spatial feedback. You do not describe a crop, a curve, or a fillet. Photo editors and CAD tools aren't going to move that to a prompt, and shouldn't.
  • Repetition and muscle memory. An action you take fifty times a day should be one keystroke. Typing it is a downgrade every single time.
  • Discovery. A new user doesn't know what to ask for. This is the strongest argument for keeping the control surface, and it's why the model list stayed a list.
  • Safety. "Delete the failed runs" should require pointing at something, not a sentence that could be parsed generously.

Spreadsheets, IDEs and design tools stay hybrids. They should. Chat is an orchestration layer over stable primitives, not a replacement for them.

The thesis, as narrowly as I can put it

A visual interface is excellent at exposing stable objects, state, constraints, and high-frequency actions. It becomes expensive and brittle when the valid operations are numerous, compositional, state-dependent, and continuously changing.

In those systems: let the UI enumerate what has to be chosen and inspected, and let a conversational layer absorb the requests that would otherwise need a new control every week.

The gain isn't that chat replaces the interface. It's that changing intent becomes observable, recoverable and extensible without shipping a button for every new combination.

So: stop agonising over button placement — in the parts of your product where the verbs won't hold still. Everywhere else, the button is still the right answer — as it has been the whole time.


If you've shipped a control surface for a workflow that kept changing underneath it, I'd like to hear how that went. My honest position is that I found the boundary in two of my own products and I don't yet know how far it generalises.

Top comments (0)