Root cause: several Cartagena old-city landmarks in the data had latitudes
~4 km too high (≈10.46°N instead of ≈10.42°N), dropping them out into the
Caribbean bay — that's why Day 2 stops 2–5 rendered in the water. The chat
model couldn't fix them because its web search couldn't surface valid
coordinates for those specific buildings, and it (correctly) refused to move
them to arbitrary guessed locations.
Also found the CTG (Rafael Núñez) airport was misplaced ~8 km east onto the
Bocagrande area ([10.4622,-75.4385]); the real airport is [10.4425,-75.5131].
This inflated the Day 1 arrival drop-off to 313 min.
Changes:
- data.js: re-anchor 6 old-city stops (S5, S7–S10, S20) + the C2 candidate to
verified on-land coordinates (all snap <21 m to the OSRM extract); fix the
CTG airport in the CTG→SMR flight stations and the S22 stop.
- app.js: correct AIRPORTS.ctg (used by the arrival transfer) to the real
airport. Add healCoordinates() so a plan saved before this fix (still holding
the old in-the-bay points in the browser's localStorage) is re-anchored to
the corrected source on load — stops re-sync by id, arrival/departure airports
re-derived from the AIRPORTS table. Idempotent; user-swapped stops (candidate
ids) are left alone. Verified with a CDP test that corrupts the saved plan
(6 stops + airport) and confirms a reload self-heals them back.
All regression suites pass: coords 6/6, drop-off leg 17/17, smoke 0 errors,
rail structure 13/13, dates 7/7.
The mock can now work several trips at once: a trip picker in the
topbar, per-trip state in localStorage (edits + version history survive
switching), and a per-trip router key so each trip routes on its own
OSRM extract.
Colombia (Cartagena -> Santa Marta, 8 days) becomes the default working
trip, inside a new walking-profile OSRM instance:
- scripts/crop_pbf.py: streaming 3-pass PBF cropper (no PBF library;
the country file is too big for osmium extract -s complete_ways on
this box). Framing validated against osmium test fixtures and
osrm-extract. Drops relations (foot profile ignores restrictions).
- scripts/setup-osrm-colombia.sh: download -> crop (~4.6 MB) ->
extract/partition/customize with the official foot.lua -> serve
:5003. Verified: Getsemaní -> Castillo San Felipe 7.98 km / 96 min.
- server.js: ROUTERS map, ?router=colombia|northeast on /route,
/table, /router-status (per-router probe points).
- app.js: legs touching a transit stop are fixed (flight/taxi/bus via
M.multimodal), never re-routed; distance-0 router answers (both
points on one node) read as adjacent stops, not out-of-coverage.
- multi-hotel fix: day-scope map fit uses the stays covering THAT day,
so a two-city trip no longer fits the whole country per day.
Also: pre-cache-tiles.sh (Cartagena+Santa Marta z14-16, 2560 tiles),
tile cache write fix (Node rejects flags:'x'; buffer-then-write),
favicon, renderHotelMks load-order guard, tests updated for the new
default trip (all four puppeteer suites green, zero console errors).
- server.js: proxy /route, /table, /router-status to the local OSRM on :5000
- app.js: routeClient (polyline-5 decode, ordered route fetch); legs start as
straight-line estimates (conf 1) and are upgraded in the background to real
road geometry + duration (conf 3); out-of-coverage trips (Florence) fall
back gracefully with a 'estimate — outside router coverage' source
- L3 leg editor: dragging a waypoint now genuinely re-routes through the
router (was silently no-op — #app covered the map with pointer-events:auto,
so no map drag ever landed; #app is now pointer-events:none, children opt in)
- data.js: second demo city (Boston 14–16 Sep, inside the router's NE-US
extract) via ?city=boston; nudges + multimodal legs are now dataset-driven
- L0: Day/Trip scope toggle — per-day summary cards (pacing bar, stops,
end time, price, base) with click-to-jump; map fits the whole trip
- version history: commit() snapshots days+stays on every mutation; topbar
vN pill with undo/redo + dropdown timeline showing a diff label per version;
Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y; restore() re-enriches legs (snapshots may
predate route enrichment)
- offline: staged 'prepare for offline' card in the trip rail (itinerary →
POI details → vector tiles) that flips the topbar badge to '📦 offline ready'
- chat/scripts/nudges: Florence-specific strings generalized (stay name/dates,
vibes, pacing summary) so both demo cities read naturally