Enforce the date-sync invariant in commit/restore, not just the flight tool

redateStaysAndPlaces() now runs in commit() (before the snapshot, so
history stores consistent pairs) and in restore(). Any present or future
edit that changes day dates keeps stays/places/booking strings in sync
for free; consistent plans are an idempotent no-op. apply_flight_anchors
no longer calls it directly — commit is the single enforcement point.
enterTrip's load-time self-heal stays for plans persisted by older code.
This commit is contained in:
Greg Pomerantz 2026-09-09 16:43:47 -04:00
parent 6c90a656bd
commit 54f049eecc

View File

@ -413,12 +413,10 @@ const TOOLS = {
} }
last.wakingHours = Math.max(1, Math.round((depMin - last.startMin) / 60)); last.wakingHours = Math.max(1, Math.round((depMin - last.startMin) / 60));
// 5) sync the date-carrying neighbours (stays / places / hotel bookings) // 5) commit (persisted immediately — this is the edit we must never lose;
redateStaysAndPlaces(); // commit() also re-syncs stays/places/booking dates to the new day
// dates) + re-render every date-bearing UI piece (renderAll alone only
// 6) commit (persisted immediately — this is the edit we must never lose) // covers rail + map, which left tabs, chips and title showing old dates)
// + re-render every date-bearing UI piece (renderAll alone only covers
// rail + map, which left tabs, chips and title showing old dates)
ids.forEach(id => rebuildLegs(days[id])); ids.forEach(id => rebuildLegs(days[id]));
commit('[flight] re-anchored around booked arrival + departure'); commit('[flight] re-anchored around booked arrival + departure');
persistTrip(true); persistTrip(true);
@ -1659,6 +1657,11 @@ function recentHistory() {
return out; return out;
} }
const commit = (label) => { const commit = (label) => {
// invariant for every edit, not just flight re-anchoring: the date-carrying
// neighbours (stays, places, hotel booking strings) must track the day
// dates. Idempotent — a consistent plan is a no-op. Runs before the
// snapshot so history stores consistent pairs.
redateStaysAndPlaces();
history = history.slice(0, hIdx + 1); // drop any redo branch history = history.slice(0, hIdx + 1); // drop any redo branch
history.push({ v: ++version, label, at: Date.now(), days: deep(days), stays: deep(stays), bookings: deep(M.trip.bookings), title: M.trip.title }); history.push({ v: ++version, label, at: Date.now(), days: deep(days), stays: deep(stays), bookings: deep(M.trip.bookings), title: M.trip.title });
hIdx = history.length - 1; hIdx = history.length - 1;
@ -1674,6 +1677,7 @@ function restore(idx, silent) {
staySeq = stays.reduce((m, o) => Math.max(m, +String(o.id).replace(/\D/g, '') || 0), 1); staySeq = stays.reduce((m, o) => Math.max(m, +String(o.id).replace(/\D/g, '') || 0), 1);
if (s.bookings) M.trip.bookings = deep(s.bookings); if (s.bookings) M.trip.bookings = deep(s.bookings);
if (s.title != null) M.trip.title = s.title; if (s.title != null) M.trip.title = s.title;
redateStaysAndPlaces(); // old-era snapshots may hold inconsistent pairs
closeDiscover(); closeL3(); closeDiscover(); closeL3();
rebuildLegs(day); // snapshots may predate route enrichment — re-upgrade legs rebuildLegs(day); // snapshots may predate route enrichment — re-upgrade legs
renderHotelMks(); renderBaseChip(); renderHotelMks(); renderBaseChip();