mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
feat(roadtrip): show every change to a road trip live to everyone on it
Two people shaping one drive were each editing their own copy: a via dragged on one screen reached the other only on the next reload, and the route it changed looked settled until then. Every write in the domain now announces itself over the websocket the rest of the trip already uses — the five REST routes and the matching MCP tools alike, so an assistant moving a stop is as visible as a person doing it. Two new trip-scoped events carry it: roadtripVia:changed with the day's whole via list, and roadtripTrack:changed. The whole list rather than a delta, because a via is identified by its position in a chain and a delta protocol would need an ordering guarantee to be worth anything. The originating socket is excluded through the X-Socket-Id header the other domains already forward, so nobody watches their own change arrive twice.
This commit is contained in:
@@ -28,6 +28,8 @@ const FIXTURES: Record<TrekWsEventName, Record<string, unknown>> = {
|
||||
'day:updated': { day: { id: 2, date: '2026-06-11' } },
|
||||
'day:deleted': { dayId: 2 },
|
||||
'day:reordered': { orderedIds: [2, 1] },
|
||||
'roadtripVia:changed': { dayId: 2, vias: [{ id: 9, lat: 53.5, lng: 9.9 }] },
|
||||
'roadtripTrack:changed': { dayId: 2, track: null },
|
||||
'dayNote:created': { dayId: 2, note: { id: 5 } },
|
||||
'dayNote:updated': { dayId: 2, note: { id: 5 } },
|
||||
'dayNote:deleted': { noteId: 5, dayId: 2 },
|
||||
@@ -138,11 +140,12 @@ const DRIFT_VARIANTS: Partial<Record<TrekWsEventName, Record<string, unknown>[]>
|
||||
};
|
||||
|
||||
describe('@trek/shared realtime event registry', () => {
|
||||
it('WSEVT-REG-001: pins the authoritative inventory counts (69 trip + 32 user = 101)', () => {
|
||||
it('WSEVT-REG-001: pins the authoritative inventory counts (71 trip + 32 user = 103)', () => {
|
||||
// 67th to 69th trip event: the three collab:link:* a shared link emits.
|
||||
expect(TREK_WS_TRIP_EVENT_NAMES).toHaveLength(69);
|
||||
// 70th and 71st: the road trip's vias and tracks, which used to be written silently.
|
||||
expect(TREK_WS_TRIP_EVENT_NAMES).toHaveLength(71);
|
||||
expect(TREK_WS_USER_EVENT_NAMES).toHaveLength(32);
|
||||
expect(TREK_WS_EVENT_NAMES).toHaveLength(101);
|
||||
expect(TREK_WS_EVENT_NAMES).toHaveLength(103);
|
||||
});
|
||||
|
||||
it('WSEVT-REG-002: every name is domain:action shaped and outside the reserved plugin: namespace', () => {
|
||||
|
||||
@@ -92,6 +92,25 @@ export const TREK_WS_EVENTS = {
|
||||
payload: z.union([z.object({ orderedIds: idList }), z.object({ day: entity })]),
|
||||
},
|
||||
|
||||
// ── Road trip ────────────────────────────────────────────────────────────
|
||||
// The points a drive is routed through, and the tracks a day follows. Broadcast in one
|
||||
// event carrying the day's whole list rather than one event per point: a via has no
|
||||
// identity anyone reads, the client keeps them per day anyway, and a drag is a rapid
|
||||
// burst of writes whose only interesting state is the one that lands last.
|
||||
//
|
||||
// These used to be silent, on the reasoning that a via is how one person draws a route
|
||||
// rather than a change to the itinerary everybody reads. That does not hold: two people
|
||||
// planning a road trip see the same line on the same map, and a reshaped drive changes
|
||||
// every arrival time after it.
|
||||
'roadtripVia:changed': {
|
||||
scope: 'trip',
|
||||
payload: z.object({ dayId: id, vias: z.array(entity) }),
|
||||
},
|
||||
'roadtripTrack:changed': {
|
||||
scope: 'trip',
|
||||
payload: z.object({ dayId: id, track: entity.nullable() }),
|
||||
},
|
||||
|
||||
// ── Day notes ────────────────────────────────────────────────────────────
|
||||
'dayNote:created': { scope: 'trip', payload: z.object({ dayId: id, note: entity }) },
|
||||
'dayNote:updated': { scope: 'trip', payload: z.object({ dayId: id, note: entity }) },
|
||||
|
||||
Reference in New Issue
Block a user