}
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) {