From 70f99f5fdb575078f1aaac7913ea0ade3f13f3f4 Mon Sep 17 00:00:00 2001 From: Carl Worth Date: Sun, 8 Mar 2026 21:20:20 -0400 Subject: [PATCH] anagrams: Again, follow the rule that server events are the source of truth Prior code here was returning letters from the rack (claim area) to the center of the board, but then server events would also return the letters, resulting in duplicate letters appearing in the center as ghosts. Meanwhile, there was an opposite problem in response to the user pressing backspace in quick succession. Because the client was waiting for the server to remove the tile from the rack, addiitional presses of backspace would all apply to the same last tile, ad would fail. So, now, as of this commit, we remove tiles from the rack with backspace, (allowing subsequent presses to target subsequent letters) but wait for the server to tell us the tile has been returned to the center area. This should keep state in sync and keep things humming along. --- anagrams/anagrams.jsx | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/anagrams/anagrams.jsx b/anagrams/anagrams.jsx index 8056fe2..4afbfd4 100644 --- a/anagrams/anagrams.jsx +++ b/anagrams/anagrams.jsx @@ -532,23 +532,31 @@ class Game extends React.Component { } async _return_center_letter(tile, rack_index) { + /* Optimistically remove from rack so that rapid backspaces + * target different entries instead of repeating the same one. */ + this.setState(prev => ({ + claim_rack: prev.claim_rack.filter((_, i) => i !== rack_index), + claim_center_tiles: prev.claim_center_tiles.filter( + t => t.id !== tile.id), + claim_error: null + })); + const response = await fetch_post_json("return-letter", { letter_id: tile.id }); - if (response.ok) { - const positions = { ...this.state.tile_positions }; - if (!positions[tile.id]) { - positions[tile.id] = this._random_position(); - } + if (!response.ok) { + /* Revert: put the entry back in the rack. */ this.setState(prev => ({ - claim_rack: prev.claim_rack.filter((_, i) => i !== rack_index), - claim_center_tiles: prev.claim_center_tiles.filter( - t => t.id !== tile.id), - center: [...prev.center, tile], - tile_positions: positions, - claim_error: null + claim_rack: [...prev.claim_rack, { + letter: tile.letter, source: "center", tile + }], + claim_center_tiles: [...prev.claim_center_tiles, tile] })); } + /* Don't add tile back to center here. The SSE letter-returned + * event (receive_letter_returned) is the single source of truth + * for center updates, avoiding the double-add that created + * ghost tiles. */ } async steal_word(owner_session, word_obj) { -- 2.45.2