One word at a time

Two long words typed at once could break the whole duel layout. The fix drops the side-by-side grid for a single panel that follows whoever is acting.

Both duel modes showed the player’s word and the opponent’s word side by side, each rendered as fixed-width letter cells in its own column. Type or receive two long words at once and the columns pushed past the viewport, breaking the page layout rather than just looking cramped. The root cause traced to a 1fr 1fr grid whose items default to min-width: auto, holding a no-wrap flex row of WordChars cells that never had permission to shrink. A wayfinder map took the fix past patching that one grid, toward a design where two long words could never collide on screen again, on any viewport.

The map resolved into a spec built on one rule: exactly one word renders as letter cells at a time. The other player’s word collapses to a small text chip instead of a second column. Which word gets the cells is called ownership, and it has to survive things a naive “whoever’s turn it is” check would miss, mid-animation state and a PvP client resyncing from the server after a dropped connection among them:

export function selectPanelOwner(inputs: WordPanelInputs): WordPanelOwner {
  if (inputs.playerAnimating) return { owner: 'player', mode: 'animating' };
  if (inputs.opponentAnimating) return { owner: 'opponent', mode: 'animating' };
  if (!inputs.isPlayerTurn) return { owner: 'opponent', mode: 'draft' };
  return { owner: 'player', mode: 'input' };
}

Animation always outranks turn state, since a word already playing across the board is the thing worth watching regardless of whose turn comes next, and the whole function reads only from authoritative state, never from animation events themselves, so a PvP client that resyncs mid-animation always lands on the same answer the server would give. Two deviations from the written spec came out of the same session that built it: a crashed word shows as an error-styled chip once the turn passes rather than lingering in the panel, and the PvE opponent’s last word now shows as a chip while the player types theirs, matching the rule PvP already followed of keeping the move you’re answering in view.

The old two-column markup, duplicated across both duel pages, condensed into one shared DuelWordPanel component built on the same WordChars letter renderer the lives system had already unified. New tests target the panel deliberately at the width where the bug used to happen: a 390px viewport typing a fifteen-letter word while the opponent’s word animates, in both PvE and a phone-sized PvP guest view, with zero horizontal overflow in either.