fix(sync): re-hydrate active trip store on reconnect/online (H1) (#1181)

setRefetchCallback was dead code, so on reconnect the queue flushed and Dexie
re-seeded but the open trip's Zustand store was never refreshed — a
collaborator's edits made while we were offline didn't appear until navigating
away and back.

- new tripStore.hydrateActiveTrip(): silent refresh of the active trip's
  collaborative state (days/places/packing/todo/budget/reservations/files),
  no resetTrip and no isLoading toggle so there's no splash on reconnect
- syncTriggers wires setRefetchCallback to it (WS layer awaits the flush hook
  first) and re-hydrates open trips after the online-event syncAll; cleared on
  unregister
- websocket exposes getActiveTrips() for the online-event path
- tests: refetch wiring + ordering, silent hydrate without reset/splash
This commit is contained in:
jubnl
2026-06-15 09:32:28 +02:00
committed by GitHub
parent 1eb2cb8eb2
commit 39b5af790e
5 changed files with 160 additions and 4 deletions
+34
View File
@@ -259,6 +259,40 @@ describe('tripStore', () => {
});
});
describe('hydrateActiveTrip', () => {
const loadHandlers = (places: unknown[] = [], budget: unknown[] = []) => [
http.get('/api/trips/1', () => HttpResponse.json({ trip: buildTrip({ id: 1 }) })),
http.get('/api/trips/1/days', () => HttpResponse.json({ days: [] })),
http.get('/api/trips/1/places', () => HttpResponse.json({ places })),
http.get('/api/trips/1/packing', () => HttpResponse.json({ items: [] })),
http.get('/api/trips/1/todo', () => HttpResponse.json({ items: [] })),
http.get('/api/trips/1/budget', () => HttpResponse.json({ items: budget })),
http.get('/api/trips/1/reservations', () => HttpResponse.json({ reservations: [] })),
http.get('/api/trips/1/files', () => HttpResponse.json({ files: [] })),
http.get('/api/tags', () => HttpResponse.json({ tags: [] })),
http.get('/api/categories', () => HttpResponse.json({ categories: [] })),
];
it('FE-TRIP-H1: silently refreshes resources without resetting or splashing', async () => {
server.use(...loadHandlers());
await useTripStore.getState().loadTrip(1);
expect(useTripStore.getState().trip!.id).toBe(1);
// New collaborative state arrives (as if edited by someone while we were offline).
const place = buildPlace({ trip_id: 1 });
const budgetItem = buildBudgetItem({ trip_id: 1 });
server.use(...loadHandlers([place], [budgetItem]));
await useTripStore.getState().hydrateActiveTrip(1);
const state = useTripStore.getState();
expect(state.places).toEqual([place]);
expect(state.budgetItems).toEqual([budgetItem]);
expect(state.trip!.id).toBe(1); // trip not reset
expect(state.isLoading).toBe(false); // no splash toggled
});
});
describe('refreshDays', () => {
it('FE-TRIP-007: refreshDays re-fetches days and rebuilds assignments/dayNotes maps', async () => {
const assignment = buildAssignment({ day_id: 20, order_index: 0 });