From bd2bdebc3318cb88aa130cab1f1481635d7eb059 Mon Sep 17 00:00:00 2001 From: Maurice Date: Sat, 18 Apr 2026 01:43:55 +0200 Subject: [PATCH 1/4] feat(map): auto-fit the planner map to the trip's places on load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the annoyance from discussion #510 — the planner opened every trip centered on the global default, even when the trip's places were on the other side of the world. We already have a BoundsController that fits the map on the current places when fitKey changes, so nudging fitKey once per trip (after the places have loaded) gives each trip its own starting view without any new settings or UI. If a trip has no places with coordinates yet, the global default still applies. --- client/src/pages/TripPlannerPage.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/client/src/pages/TripPlannerPage.tsx b/client/src/pages/TripPlannerPage.tsx index b8b5dc50..abecfdc5 100644 --- a/client/src/pages/TripPlannerPage.tsx +++ b/client/src/pages/TripPlannerPage.tsx @@ -233,9 +233,19 @@ export default function TripPlannerPage(): React.ReactElement | null { const [showReservationModal, setShowReservationModal] = useState(false) const [editingReservation, setEditingReservation] = useState(null) const [fitKey, setFitKey] = useState(0) + const initialFitTripId = useRef(null) const [mobileSidebarOpen, setMobileSidebarOpen] = useState<'left' | 'right' | null>(null) const [deletePlaceId, setDeletePlaceId] = useState(null) + useEffect(() => { + if (!trip) return + if (initialFitTripId.current === trip.id) return + const hasGeoPlaces = places.some(p => p.lat != null && p.lng != null) + if (!hasGeoPlaces) return + initialFitTripId.current = trip.id + setFitKey(k => k + 1) + }, [trip, places]) + const connectionsStorageKey = tripId ? `trek:visible-connections:${tripId}` : null const [visibleConnections, setVisibleConnections] = useState(() => { if (typeof window === 'undefined' || !connectionsStorageKey) return [] From 2c0894b33021ebdb90bcd1b9adae8492c7f3e787 Mon Sep 17 00:00:00 2001 From: Maurice Date: Sat, 18 Apr 2026 01:48:53 +0200 Subject: [PATCH 2/4] fix(types): add missing map_booking_labels to Settings interface The booking-labels toggle from the transport-routes-on-map change was reading and writing settings.map_booking_labels without the key being declared on the Settings type, so the store typing was inconsistent. Adds it as an optional boolean to match the other display toggles. --- client/src/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/types.ts b/client/src/types.ts index 4bcfa22a..5d841c64 100644 --- a/client/src/types.ts +++ b/client/src/types.ts @@ -212,6 +212,7 @@ export interface Settings { show_place_description: boolean route_calculation?: boolean blur_booking_codes?: boolean + map_booking_labels?: boolean } export interface AssignmentsMap { From ec4aaa628f708e3db44b1a91d094488708931459 Mon Sep 17 00:00:00 2001 From: Maurice Date: Sat, 18 Apr 2026 01:57:01 +0200 Subject: [PATCH 3/4] fix(docker): include server/data/airports.json in the image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing 'data' entry in .dockerignore hid the committed airports.json snapshot from the build context, so every Docker deployment ended up without it. The airport service then logged the "missing" warning and the autocomplete silently returned no results — the dropdown flashed a loading spinner and disappeared. Add an exception that keeps the SQLite DB, logs and tmp excluded but lets the airports snapshot through. --- .dockerignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.dockerignore b/.dockerignore index c0defbdb..dcd9575b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,6 +3,7 @@ client/node_modules server/node_modules client/dist data +!server/data/airports.json uploads .git .github From 68a30369098522e1abe86ab4ee1c4fd9f34a195e Mon Sep 17 00:00:00 2001 From: Maurice Date: Sat, 18 Apr 2026 02:02:09 +0200 Subject: [PATCH 4/4] refactor: move airports.json out of server/data into server/assets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit server/data is for runtime state (SQLite, backups, logs, tmp) — the airports snapshot is a shipped dataset, not user data, and it being in there forced us to poke a hole in both .dockerignore and .gitignore. Move it to server/assets/ and drop the exceptions; service and build script point at the new path. --- .dockerignore | 1 - .gitignore | 1 - server/{data => assets}/airports.json | 0 server/scripts/build-airports.mjs | 4 ++-- server/src/services/airportService.ts | 2 +- 5 files changed, 3 insertions(+), 5 deletions(-) rename server/{data => assets}/airports.json (100%) diff --git a/.dockerignore b/.dockerignore index dcd9575b..c0defbdb 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,7 +3,6 @@ client/node_modules server/node_modules client/dist data -!server/data/airports.json uploads .git .github diff --git a/.gitignore b/.gitignore index bfaa629a..14638f00 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,6 @@ client/public/icons/*.png # User data server/data/* -!server/data/airports.json server/uploads/ # Environment diff --git a/server/data/airports.json b/server/assets/airports.json similarity index 100% rename from server/data/airports.json rename to server/assets/airports.json diff --git a/server/scripts/build-airports.mjs b/server/scripts/build-airports.mjs index d7c00465..153fe88f 100644 --- a/server/scripts/build-airports.mjs +++ b/server/scripts/build-airports.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// Build server/data/airports.json from OurAirports (davidmegginson.github.io/ourairports-data). +// Build server/assets/airports.json from OurAirports (davidmegginson.github.io/ourairports-data). // License: Public Domain. Keeps large/medium airports with an IATA code; timezone derived from coords via tz-lookup. import fs from 'node:fs' @@ -9,7 +9,7 @@ import { fileURLToPath } from 'node:url' import tzLookup from 'tz-lookup' const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const OUT = path.join(__dirname, '..', 'data', 'airports.json') +const OUT = path.join(__dirname, '..', 'assets', 'airports.json') const SRC = 'https://davidmegginson.github.io/ourairports-data/airports.csv' function fetchText(url) { diff --git a/server/src/services/airportService.ts b/server/src/services/airportService.ts index c3250b11..a5745d31 100644 --- a/server/src/services/airportService.ts +++ b/server/src/services/airportService.ts @@ -18,7 +18,7 @@ let byIata: Map | null = null; function load(): Airport[] { if (cache) return cache; - const file = path.join(__dirname, '..', '..', 'data', 'airports.json'); + const file = path.join(__dirname, '..', '..', 'assets', 'airports.json'); if (!fs.existsSync(file)) { console.warn('[airports] airports.json missing — run `node scripts/build-airports.mjs`'); cache = [];