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:
Bas van Rossem
2026-02-19 16:44:15 +01:00
parent 275137cdbb
commit aceef65002
2 changed files with 43 additions and 20 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect, useRef, useCallback } from 'react';
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import TopBar from '../components/layout/TopBar';
import PageContainer from '../components/layout/PageContainer';
@@ -7,29 +7,16 @@ import MobilitySection from '../components/dashboard/MobilitySection';
import WeaponsSection from '../components/dashboard/WeaponsSection';
import NotesSection from '../components/dashboard/NotesSection';
import { useShipStore } from '../store/use-ship-store';
import type { Ship } from '../types/ship';
export default function ShipDashboardPage() {
const { id } = useParams<{ id: string }>();
const { ship, weapons, loading, joinShip, leaveShip, updateShip, createWeapon, updateWeapon, deleteWeapon } = useShipStore();
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
if (id) joinShip(id);
return () => leaveShip();
}, [id, joinShip, leaveShip]);
const handleUpdate = useCallback(
(patch: Partial<Ship>) => {
// Debounce rapid changes (e.g. stepper clicks)
if (debounceRef.current) clearTimeout(debounceRef.current);
debounceRef.current = setTimeout(() => {
updateShip(patch);
}, 300);
},
[updateShip],
);
if (loading || !ship) {
return (
<>
@@ -45,15 +32,15 @@ export default function ShipDashboardPage() {
<>
<TopBar title={ship.name} />
<PageContainer>
<VitalsSection ship={ship} onUpdate={handleUpdate} />
<MobilitySection ship={ship} onUpdate={handleUpdate} />
<VitalsSection ship={ship} onUpdate={updateShip} />
<MobilitySection ship={ship} onUpdate={updateShip} />
<WeaponsSection
weapons={weapons}
onCreateWeapon={createWeapon}
onUpdateWeapon={updateWeapon}
onDeleteWeapon={deleteWeapon}
/>
<NotesSection notes={ship.notes} onUpdate={handleUpdate} />
<NotesSection notes={ship.notes} onUpdate={updateShip} />
</PageContainer>
</>
);