A deliberate departure from the mockup

Redesigning PvP invitations and Settings meant deciding, up front, where the request diverged from the just-locked design-system mockup rather than treating the gap as an oversight.

PvP’s create-invitation page, its accept-invitation page, and Settings all shared the same complaint: bare radio buttons, unstyled checkboxes, a slider for turn duration that duplicated a setting already offered per-invite. The request asked for a dropdown, a segmented theme picker, and toggle switches. The design-system mockup finished only weeks earlier specifies something else entirely for forms: flat radio buttons, plain checkboxes, a plain slider, the same shape already sitting in the app. Before any component changed, the work had to establish which of those two specs was wrong, or whether neither was, and the redesign was simply going to diverge from the locked reference on purpose.

It was the latter. The mockup’s reference treatment for Forms & Settings was never meant to cover a panel wrapper, a dropdown, or a toggle switch, so building those wasn’t a gap the design-system work had missed, it was new ground the mockup had never claimed. Four decision tickets settled the shapes before implementation started: language becomes a native dropdown, theme becomes a segmented pill with sun, moon, and monitor icons, and the accessibility checkboxes become toggle switches, one family of controls decided together rather than three separate redesigns argued in isolation. A prototype branch validated each shape live before it touched real code, and a panel wrapper, a badge style for read-only language and duration on the accept-invitation page, and a footer nav row all came from precedent already established elsewhere in the app, needing no new decision at all.

Implementation surfaced two real bugs neither the design pass nor a first read of the code had caught. A pre-existing contrast bug on the accept-invitation page’s primary button rendered dark text on the accent fill, a color pairing swapped in one line. The other took an end-to-end test to find: the toggle switch’s decorative track sat in front of its real checkbox in normal document flow, silently absorbing every click a user or a test runner sent its way.

/* pointer-events: none -- purely decorative; the real input sits on top
   (see .switch-input) and must be what actually receives the click, both
   for real users and for Playwright's role-targeted .click(). */
.switch-track {
	pointer-events: none;
}

The fifth piece, a toast telling the host when a guest rejects or abandons an invitation, needed no new plumbing at all. It reuses the presence-detection approach already built for the instant same-opponent rematch feature: a one-shot toast, no schema changes, no new invalidation of the invite itself. All five pieces shipped as one branch and one pull request, the same umbrella shape used for past multi-ticket rollouts, closing the redesign request and its planning ticket together. A console error on the create-invitation page, spotted during the same review but unrelated to any of this, was split off to its own ticket rather than folded in here.