5 min read

Who gets to score the match? Keeping every court in sync on PlayTab

In a two-court padel session, each court runs its own independent round at its own pace. When someone on one court logs a score, it needs to sync instantly and correctly to everyone's phones on the other court too — no manual refresh, no uncertainty about whether it saved.

That's a small requirement to state and a specific one to actually build. It splits into two separate questions that look like one: how does everyone's phone find out something changed, and who's allowed to be the one who changes it.

Realtime, not "check again later"

PlayTab doesn't poll. Each session screen opens a Supabase Realtime channel scoped to that session's ID, subscribed to any change on its matches and its own session row. The moment someone submits a score anywhere, every other phone still looking at that session gets told to reload — not next time they happen to refresh, immediately, mid-glance.

That part was the easy half. It answers "how does a change get everywhere." It says nothing about who should be allowed to cause one.

Deciding who's allowed to press Confirm

For a regular casual session, the answer is deliberately loose: anyone in the group can enter a score. It's four friends at a court: there's no referee, and adding one would be solving a problem nobody has.

A tournament raises the stakes enough that the loose answer stops being the right one. There's a bracket, there are forfeits, there's a winner that gets declared and stays declared. So tournaments get an actual gate: a stored list of who's allowed to score, not a convention everyone's trusted to respect. The creator is always on it; they can add co-organizers at setup. Every match card in the tournament — group stage, semifinal, final — checks that same list before it'll let anyone open the scoring sheet.

The reason it's one list and not a check repeated on each screen is ordinary and still worth saying out loud: a check that has to be remembered on every new screen is a check that eventually gets forgotten on one of them. A knockout bracket has group-stage cards, semifinal cards, and a final card — three separate places a "can this person score?" check could have been pasted in, and three separate places it could have been pasted in wrong. One list that every card reads from can't drift out of sync with itself.

The bug that taught me "unique enough" wasn't

Multi-court sessions store which match belongs to which court using the same database column that single-court sessions use to mean something else entirely — for one mode it's "this match's position within the round," for the other it's "which court this match belongs to." Reasonable on its own; the trouble starts once you sort by it.

A single court's round can hold more matches than a small single-court round ever would, which means plenty of rows legitimately share the exact same round-number-and-court-identifier pair. Sort only by those two columns and the database makes no promise at all about which of the tied rows comes first — and that promise is allowed to change on every single fetch. So a list that looked stable could silently reorder itself the instant anyone entered a score, purely because the row UPDATE gave the database's tie-breaking a new excuse to pick differently. Nobody moved anything. Match 2 just wasn't where it was a second ago.

The fix was one more .order() clause — sort by the match's own id last, since an id is assigned once and never changes. It's a small addition for a specific, general lesson: whenever a column that used to mean one clean thing gets reused to mean two things depending on context, any sort built on the old assumption needs a second look. "Unique enough for the cases I tested" isn't the same claim as "unique," and only one of those is safe to sort by.

The takeaway

Realtime answers "does everyone find out." The organizers list answers "who's allowed to cause it." A stable sort answers "does it land in the same place every time." Three different questions, and conflating any two of them is exactly how a genuinely live, multi-court, multi-phone session either shows the wrong thing to the wrong person or reshuffles under someone's thumb mid-tap.

Next up: how PlayTab has stayed visually and structurally consistent across roughly forty separate AI coding sessions — including the one rule that got reverted specifically because a "more correct" version looked worse.