The One-Man Company Part III · Chapter 11 of 18

Part III · Chapter 11 of 18

Tool UX

The widget conventions that turn brand and engine into one product. Inputs, outputs, errors, mode-switching, and the shared primitives Bob inherits.

The rule that bounds every widget in the catalogue is the 30-second test.

A first-time visitor must be able to use the tool in under thirty seconds, with zero documentation, on a phone they're holding while waiting for coffee. Not a target. A pass-fail gate. The test is run, weekly, on a fresh incognito tab with a stopwatch. The widget either gets the user to an answer within the budget or the widget isn't done.

The rule has consequences that disqualify most of the patterns developer teams reach for instinctively. No onboarding modals. No "Get started" buttons. No tutorials, walkthroughs, or coachmarks. No welcome screens, splash screens, or interstitials. No "Sign in to continue" before the first calculation. If the widget needs to teach the user how to use it, the input is wrong — and the answer is to redesign the input, not to annotate it.

Thirty seconds. No tutorial. No coachmark. No welcome.

The boundary is the design constraint that produces every other UX decision in this chapter. Why is the main result the largest text on the page? Because the user came for that number, and the 30-second test fails if they have to scan for it. Why is the Copy button always to the right of the result? Because the second they have the number, they're moving on, and the 30-second test fails if they have to find the button. Why are there no settings drawers? Because a setting is a thing the user has to decide about first, and the 30-second test fails the second the user has to make a meta-decision instead of the primary one.

The transferable why: a tight bound on the user's first interaction is the design constraint that produces a good widget. Open-ended budgets produce open-ended widgets — they add settings, options, advanced modes, all the things a product manager can argue make the product more powerful. Power and clarity trade off against each other at the widget level. Pick a clarity bound the team can't violate, and the widget keeps its shape across every redesign.

Locked 2026-05-11 · 30-second test · run weekly on a fresh incognito tab


The second decision was the one I disagreed with myself about for a month. How to switch modes inside a widget.

The widget patterns the rest of the web has settled on for mode-switching are dropdowns and gear-icon menus. "Pick a calculation type from the dropdown." "Click the gear icon to change units." The patterns are everywhere. They also fail the 30-second test in a way I kept rationalizing past until I watched a real first-time visitor abandon the OvertimeCalculator because the jurisdiction picker was a dropdown they didn't open.

A dropdown reads as a unit picker — "choose meters or feet." A gear icon reads as "the boring settings live here, you don't need them yet." Neither pattern reads as "this is the primary interaction that reshapes what you see." Modes are not settings; they're a primary interaction. They have to be visible on first render.

Modes are tabs. Dropdowns are for units.

Mode-switching that reshapes inputs uses the <ModeTabs> primitive — top tabs, always visible, optionally URL-shareable via the hashRoute prop. Mode-switching among two or three same-input choices uses <SegmentedControl>, a pill-style selector with smaller visual weight. A single binary on-off uses <Toggle>. Dropdowns survive — but only for what they read as: unit pickers, locale selectors, choices among many same-shape options. Anything that reshapes the widget is banned from dropdowns.

The transferable why: the affordances on a page have inherited meanings from every other page the user has ever seen. A dropdown means "pick from a list of equivalents." A gear means "settings you might want later." A tab means "this is a different mode of the same thing." When you reach for the wrong affordance, you've fought the user's mental model — and the mental model wins, because there are more pages on the web than there are on your site.

Locked 2026-05-11 · ModeTabs / SegmentedControl / Toggle · dropdowns demoted to unit picking


The third decision sounds boring and was load-bearing for the catalogue's consistency. One precision rule, all the tools.

Each tool used to make its own precision decision. The unit converter showed eight decimal places. The percentage calculator showed two. The interest calculator showed six. Each one was defensible in isolation, all of them together made the site look like five different teams had built it. The number a user saw in one tool didn't have the same precision shape as the number they saw in another, and the catalogue read as a mosaic rather than a single product.

The fix was a single rule that survived as the default: four significant figures. Not six. Not eight. Not "as many as JavaScript gives us." Four. Enough for engineering, finance, and stats homework. More is visual noise. Money is the one carve-out — two decimals, always. Percentages are two decimals or four sig figs, whichever is fewer. Per-tool overrides exist but require justification in the spec, signed off by Kai, checked by Ben.

Four sig figs. Money: two decimals. The rest is opinion, not justification.

The rule lives in a single primitive — <NumberOutput format="sigfig" | "money" | "percent" | "integer">. Every widget routes its results through it. No widget formats numbers ad hoc; if it tries, the precision rule drifts the moment the spec leaves Kai's hands. The primitive does the rounding, the locale-aware separators, the negative-in-coral, the dash-instead-of-NaN. One source of truth for what a number looks like across the site.

The transferable why: small consistency rules compound into a brand the user trusts without being able to explain why. Inconsistent precision reads as inconsistent care. Inconsistent error messages read as inconsistent quality. The user can't articulate the inconsistency, but they feel it — and the feeling shows up later as "this site feels a little off" in a survey. Pick the small rules; enforce them in primitives; let the brand benefit from the boring uniformity.

Locked 2026-05-11 · four sig figs default · enforced in NumberOutput primitive


The fourth decision was where the brand voice meets the widget. What an error looks like.

The default browser error pattern is the red box with the exclamation mark. The default JavaScript error pattern is the thrown exception that bubbles up to a stack trace. The default UX-library error pattern is the toast at the top of the screen that disappears in three seconds. None of those passes the AmEx test — the rule from the Voice chapter that says the brand sounds like a person who cares the thing broke, not a corporation reading from a script.

The version that survives is the one written from the user's seat. "That's frustrating — try this instead." The error sits under the input that caused it, in a coral surface with role="alert", with one sentence on what went wrong and one suggestion on how to fix it. "Amount must be positive — try a number greater than zero." "This format isn't recognized — try a 24-hour time like 14:30." Empty states get a sentence too: "Paste some numbers above — comma, space, or newline separated — to see the median."

"That broke. Here's the fix." Never "Error: invalid input."

The shape lives in one primitive — <ToolError message=… />. Coral background, accessible markup, optional icon. The primitive renders only when message is non-empty, so widgets don't need to gate it themselves. Every widget that does input validation routes its messages through the primitive. Generic alerts and thrown exceptions are banned from the public surface; if one slips out, the QA pass catches it.

The transferable why: the error surface is where users decide whether they trust the product. A user reading a clear apology and a concrete suggestion feels treated like a person; a user reading "Validation failed" feels treated like an obstacle. The amount of effort to write the better message is one sentence. The compounding payoff is every error message reading like the brand voice rather than reading like a stack trace that wandered into the UI.

Locked 2026-05-11 · ToolError primitive · AmEx voice in every failure surface


The last decision is the one that lets the catalogue stay consistent as it grows. Shared primitives, imported by name.

Without shared primitives, every new widget reinvents — the Copy button, the result card, the precision-formatted number, the destructive confirmation modal. Each agent or human writing a new tool has to make the same dozen micro-decisions, and the micro-decisions drift. The catalogue ages into mosaic; the conventions in this chapter become aspirational rather than enforced.

The set is small on purpose. <ToolError>, <CopyButton>, <ModeTabs>, <SegmentedControl>, <ResultCard>, <NumberOutput>, <PrimaryButton>, <SecondaryButton>, <DestructiveButton> + <ConfirmModal>. Nine primitives. Every widget Bob writes imports from this set. Adding a tenth primitive requires a PR adding it to src/components/ui/, documenting its signature, listing it in this chapter — and only then using it in a tool. The chapter is the contract.

Nine primitives. Bob imports by name. Ben blocks the merge if a tool reinvents one.

The drift is honest. Roughly a dozen tools shipped before this chapter existed, each with bespoke versions of the patterns above — gear-icon settings drawers, hero-sized Copy buttons, dropdowns where tabs belong, ad-hoc number formatting. The drift gets fixed opportunistically — whenever a widget gets touched for any other reason, the touch includes a primitive retrofit. The schedule isn't a sprint; it's the same migrate-on-touch pattern from the engines chapter. When the drift table reads zero rows, the catalogue is consistent.

The transferable why: shared primitives are the only sustainable way to keep visual conventions intact as a codebase grows. Documentation alone drifts; reviews alone miss things; conventions encoded as primitives don't. The cost of building primitives upfront is real — a week of work, a small amount of indirection — and the payoff is every widget the catalogue ships afterward inheriting the conventions for free. Pay it once; collect forever.

Locked 2026-05-11 · src/components/ui/ · nine primitives · the contract is the chapter

That's the widget grammar. Thirty-second test as the bound, tabs over dropdowns, one precision rule, errors that teach, nine shared primitives. Every Microapp the catalogue ships from here lives inside those choices.