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 colours | across 152 uses in 11 files, all inline |
| 10 font sizes | 11, 12, 13, 14, 15, 16, 17, 22, 24, 26 |
| 11 spacing values | 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 32 |
| 2 primary buttons | defined 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:
ActionRowdecides 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 passingdestructiveas 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
pnpm --filter @tabletop/mobile testpnpm --filter @tabletop/mobile buildpnpm gateSeeded failures
| Seed | Bit |
|---|---|
| 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.