feat(web): optimistic UI updates with debounced server sync
Ship and weapon updates now apply locally immediately so stepper clicks feel instant. Server sends are debounced (300ms) and batched so rapid clicks produce a single network call. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -108,8 +108,24 @@ export const useShipStore = create<ShipStore>((set, get) => ({
|
||||
|
||||
updateShip: (patch) => {
|
||||
const id = get().currentShipId;
|
||||
if (!id) return;
|
||||
socket.emit('ship:update', { shipId: id, patch });
|
||||
const ship = get().ship;
|
||||
if (!id || !ship) return;
|
||||
|
||||
// Optimistic: apply locally immediately
|
||||
set({ ship: { ...ship, ...patch } });
|
||||
|
||||
// Accumulate patches and debounce the server send
|
||||
const pending = (socket as any).__pendingPatch ?? {};
|
||||
Object.assign(pending, patch);
|
||||
(socket as any).__pendingPatch = pending;
|
||||
|
||||
if ((socket as any).__patchTimer) clearTimeout((socket as any).__patchTimer);
|
||||
(socket as any).__patchTimer = setTimeout(() => {
|
||||
const merged = (socket as any).__pendingPatch;
|
||||
delete (socket as any).__pendingPatch;
|
||||
delete (socket as any).__patchTimer;
|
||||
if (merged) socket.emit('ship:update', { shipId: id, patch: merged });
|
||||
}, 300);
|
||||
},
|
||||
|
||||
createWeapon: (weapon) => {
|
||||
@@ -119,7 +135,27 @@ export const useShipStore = create<ShipStore>((set, get) => ({
|
||||
},
|
||||
|
||||
updateWeapon: (weaponId, patch) => {
|
||||
socket.emit('weapon:update', { weaponId, patch });
|
||||
// Optimistic: apply locally immediately
|
||||
set({
|
||||
weapons: get().weapons.map((w) =>
|
||||
w.id === weaponId ? { ...w, ...patch } : w,
|
||||
),
|
||||
});
|
||||
|
||||
// Debounce per-weapon server sends
|
||||
const timerKey = `__weaponTimer_${weaponId}`;
|
||||
const patchKey = `__weaponPatch_${weaponId}`;
|
||||
const pending = (socket as any)[patchKey] ?? {};
|
||||
Object.assign(pending, patch);
|
||||
(socket as any)[patchKey] = pending;
|
||||
|
||||
if ((socket as any)[timerKey]) clearTimeout((socket as any)[timerKey]);
|
||||
(socket as any)[timerKey] = setTimeout(() => {
|
||||
const merged = (socket as any)[patchKey];
|
||||
delete (socket as any)[patchKey];
|
||||
delete (socket as any)[timerKey];
|
||||
if (merged) socket.emit('weapon:update', { weaponId, patch: merged });
|
||||
}, 300);
|
||||
},
|
||||
|
||||
deleteWeapon: (weaponId) => {
|
||||
|
||||
Reference in New Issue
Block a user