Skip to content

WI-045: One set of controls

WI-045: One set of controls

Reported: “the UI is painful to look at, and inconsistent in terms of controls and where they are.”

The second half is the real complaint, and the audit is blunt about why:

13 hex coloursacross 152 uses in 11 files, all inline
10 font sizes11, 12, 13, 14, 15, 16, 17, 22, 24, 26
11 spacing values2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 32
2 primary buttonsdefined independently, in two files

Nothing was wrong individually. Together they made every screen look like it had been built by somebody who had not seen the others.

The fix is not paint

src/theme.ts holds the values and src/ui.tsx holds the controls. The scales are deliberately short — a palette with four greys gets used consistently; one with nine gets used at random.

What actually changed for somebody using it:

  • ActionRow decides where actions go. Primary first, destructive last, always. A Delete that is second on one screen and fourth on another is how somebody taps it by accident, and passing destructive as a separate prop means a screen cannot quietly put it elsewhere.
  • One Button. A destructive action is never the same weight as a safe one, and a primary action never looks like a link. Both were true somewhere before.
  • One RatingPicker. There were two, byte-for-byte identical in different files — two chances for the rating scale to become different things in different places.
  • One ErrorText, rendering nothing for no error, so screens stop inventing their own {error && …} in their own colour and position.

The long-press is gone

WI-043 replaced the session editor’s long-press winner picker with a visible row and noted the log form still used the gesture. That left one idea with two gestures, one screen apart — precisely the inconsistency reported. The log form now uses the same row.

Definition of done

  • Every colour comes from src/theme.ts.
  • Primary, secondary and destructive actions are visually distinct, from one component.
  • Actions appear in the same order on every screen.
  • One rating picker, one error style, one empty state.
  • The winner picker is the same control in both places.
  • A one-off colour fails the build.
  • Each check proven able to fail.
  • Gate green.

Verification

Terminal window
pnpm --filter @tabletop/mobile test
pnpm --filter @tabletop/mobile build
pnpm gate

Seeded failures

SeedBit
A one-off grey in a screen✅
theme.ts itself (must stay quiet)✅ quiet
The winner picker reverts to a long-press✅ 3 tests

Only colour is enforced. Spacing and type are scales somebody can step outside when a layout genuinely needs it; a one-off grey never is.

git checkout -- ate the work again

A seed used it to undo an edit to plays.tsx, and the file was not yet committed — so it reverted the whole conversion, and the check then reported four real colour literals as though the pass had never run. This is written in AGENTS.md and it has now happened twice.

The rule that would have prevented it is already there: commit before seeding. It is not enough to know it; the seed scripts need to do it, which is why this one now does.

Not done here

apps/web has its own inline styles in a single Base.astro. One file, one place, and no inconsistency to remove yet — worth revisiting when it has more than three pages.