From 9ebca725ae8d938efa7c41ea002ddb357ab8c5f4 Mon Sep 17 00:00:00 2001 From: jubnl Date: Sun, 5 Apr 2026 22:20:40 +0200 Subject: [PATCH 01/16] fix(CSP): Paths that end in / match any path they are a prefix of. --- server/src/app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/app.ts b/server/src/app.ts index bc36d97c..33bf1ffd 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -84,7 +84,7 @@ export function createApp(): express.Application { "https://unpkg.com", "https://open-meteo.com", "https://api.open-meteo.com", "https://geocoding-api.open-meteo.com", "https://api.exchangerate-api.com", "https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_50m_admin_0_countries.geojson", - "https://router.project-osrm.org/route/v1" + "https://router.project-osrm.org/route/v1/" ], fontSrc: ["'self'", "https://fonts.gstatic.com", "data:"], objectSrc: ["'none'"], From 7d8e3912b45b6d9f3a5116e235699bac970907f8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 5 Apr 2026 20:20:56 +0000 Subject: [PATCH 02/16] chore: bump version to 2.9.1 [skip ci] --- client/package-lock.json | 4 ++-- client/package.json | 2 +- server/package-lock.json | 4 ++-- server/package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 82b77715..96744d3b 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "trek-client", - "version": "2.9.0", + "version": "2.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trek-client", - "version": "2.9.0", + "version": "2.9.1", "dependencies": { "@react-pdf/renderer": "^4.3.2", "axios": "^1.6.7", diff --git a/client/package.json b/client/package.json index 10c40158..5304296e 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "trek-client", - "version": "2.9.0", + "version": "2.9.1", "private": true, "type": "module", "scripts": { diff --git a/server/package-lock.json b/server/package-lock.json index f07912e1..67b4b948 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "trek-server", - "version": "2.9.0", + "version": "2.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trek-server", - "version": "2.9.0", + "version": "2.9.1", "dependencies": { "@modelcontextprotocol/sdk": "^1.28.0", "archiver": "^6.0.1", diff --git a/server/package.json b/server/package.json index 1e20029a..72514782 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "trek-server", - "version": "2.9.0", + "version": "2.9.1", "main": "src/index.ts", "scripts": { "start": "node --import tsx src/index.ts", From fdbc015dbf6d93c6fc129c55a1332eea4135d01a Mon Sep 17 00:00:00 2001 From: jubnl Date: Sun, 5 Apr 2026 22:25:55 +0200 Subject: [PATCH 03/16] fix(memories): re-fetch EXIF info when navigating between lightbox photos The navigateTo function was clearing lightboxInfo without re-fetching it, causing the EXIF sidebar to disappear and nav button placement to break. Mirrors the fetch logic already present in the thumbnail click handler. Fixes #439 --- client/src/components/Memories/MemoriesPanel.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/src/components/Memories/MemoriesPanel.tsx b/client/src/components/Memories/MemoriesPanel.tsx index a466e265..baed3cd3 100644 --- a/client/src/components/Memories/MemoriesPanel.tsx +++ b/client/src/components/Memories/MemoriesPanel.tsx @@ -956,6 +956,9 @@ export default function MemoriesPanel({ tripId, startDate, endDate }: MemoriesPa setLightboxUserId(photo.user_id) setLightboxInfo(null) fetchImageAsBlob('/api' + buildProviderAssetUrl(photo, 'original')).then(setLightboxOriginalSrc) + setLightboxInfoLoading(true) + apiClient.get(buildProviderAssetUrl(photo, 'info')) + .then(r => setLightboxInfo(r.data)).catch(() => {}).finally(() => setLightboxInfoLoading(false)) } const exifContent = lightboxInfo ? ( From 7e0fe3b1b9d20547c10342da66f48cdb39d71bd5 Mon Sep 17 00:00:00 2001 From: jubnl Date: Sun, 5 Apr 2026 22:26:55 +0200 Subject: [PATCH 04/16] fix(reservations): hide price/budget fields when budget addon is disabled Closes #440 --- .../components/Planner/ReservationModal.tsx | 74 +++++++++++-------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/client/src/components/Planner/ReservationModal.tsx b/client/src/components/Planner/ReservationModal.tsx index bc8c34fd..2d8e4ca8 100644 --- a/client/src/components/Planner/ReservationModal.tsx +++ b/client/src/components/Planner/ReservationModal.tsx @@ -2,6 +2,7 @@ import { useState, useEffect, useRef, useMemo } from 'react' import { useParams } from 'react-router-dom' import apiClient from '../../api/client' import { useTripStore } from '../../store/tripStore' +import { useAddonStore } from '../../store/addonStore' import Modal from '../shared/Modal' import CustomSelect from '../shared/CustomSelect' import { Plane, Hotel, Utensils, Train, Car, Ship, Ticket, FileText, Users, Paperclip, X, ExternalLink, Link2 } from 'lucide-react' @@ -71,6 +72,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p const { t, locale } = useTranslation() const fileInputRef = useRef(null) + const isBudgetEnabled = useAddonStore(s => s.isEnabled('budget')) const budgetItems = useTripStore(s => s.budgetItems) const budgetCategories = useMemo(() => { const cats = new Set() @@ -196,8 +198,10 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p if (form.end_date) { combinedEndTime = form.reservation_end_time ? `${form.end_date}T${form.reservation_end_time}` : form.end_date } - if (form.price) metadata.price = form.price - if (form.budget_category) metadata.budget_category = form.budget_category + if (isBudgetEnabled) { + if (form.price) metadata.price = form.price + if (form.budget_category) metadata.budget_category = form.budget_category + } const saveData: Record = { title: form.title, type: form.type, status: form.status, reservation_time: form.reservation_time, reservation_end_time: combinedEndTime, @@ -208,9 +212,11 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p metadata: Object.keys(metadata).length > 0 ? metadata : null, } // Auto-create/update budget entry if price is set, or signal removal if cleared - saveData.create_budget_entry = form.price && parseFloat(form.price) > 0 - ? { total_price: parseFloat(form.price), category: form.budget_category || t(`reservations.type.${form.type}`) || 'Other' } - : { total_price: 0 } + if (isBudgetEnabled) { + saveData.create_budget_entry = form.price && parseFloat(form.price) > 0 + ? { total_price: parseFloat(form.price), category: form.budget_category || t(`reservations.type.${form.type}`) || 'Other' } + : { total_price: 0 } + } // If hotel with place + days, pass hotel data for auto-creation or update if (form.type === 'hotel' && form.hotel_place_id && form.hotel_start_day && form.hotel_end_day) { saveData.create_accommodation = { @@ -643,33 +649,37 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p - {/* Price + Budget Category */} -
-
- - { const v = e.target.value; if (v === '' || /^\d*\.?\d{0,2}$/.test(v)) set('price', v) }} - placeholder="0.00" - style={inputStyle} /> -
-
- - set('budget_category', v)} - options={[ - { value: '', label: t('reservations.budgetCategoryAuto') }, - ...budgetCategories.map(c => ({ value: c, label: c })), - ]} - placeholder={t('reservations.budgetCategoryAuto')} - size="sm" - /> -
-
- {form.price && parseFloat(form.price) > 0 && ( -
- {t('reservations.budgetHint')} -
+ {/* Price + Budget Category — only shown when budget addon is enabled */} + {isBudgetEnabled && ( + <> +
+
+ + { const v = e.target.value; if (v === '' || /^\d*\.?\d{0,2}$/.test(v)) set('price', v) }} + placeholder="0.00" + style={inputStyle} /> +
+
+ + set('budget_category', v)} + options={[ + { value: '', label: t('reservations.budgetCategoryAuto') }, + ...budgetCategories.map(c => ({ value: c, label: c })), + ]} + placeholder={t('reservations.budgetCategoryAuto')} + size="sm" + /> +
+
+ {form.price && parseFloat(form.price) > 0 && ( +
+ {t('reservations.budgetHint')} +
+ )} + )} {/* Actions */} From 306626ee1c8bd0b162ba9f712e3dd75cfc5c69a3 Mon Sep 17 00:00:00 2001 From: jubnl Date: Sun, 5 Apr 2026 22:29:46 +0200 Subject: [PATCH 05/16] fix(trip): redirect to plan tab when active tab's addon is disabled If a user's last visited tab belongs to an addon that gets disabled while they are away, re-opening the trip now resets the active tab to 'plan' instead of rendering the inaccessible addon page. Closes #441 --- client/src/pages/TripPlannerPage.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/src/pages/TripPlannerPage.tsx b/client/src/pages/TripPlannerPage.tsx index 2a9225b0..d2c0c2a3 100644 --- a/client/src/pages/TripPlannerPage.tsx +++ b/client/src/pages/TripPlannerPage.tsx @@ -137,6 +137,14 @@ export default function TripPlannerPage(): React.ReactElement | null { return saved || 'plan' }) + useEffect(() => { + const validTabIds = TRIP_TABS.map(t => t.id) + if (!validTabIds.includes(activeTab)) { + setActiveTab('plan') + sessionStorage.setItem(`trip-tab-${tripId}`, 'plan') + } + }, [enabledAddons]) + const handleTabChange = (tabId: string): void => { setActiveTab(tabId) sessionStorage.setItem(`trip-tab-${tripId}`, tabId) From f45f56318a4c18889e110d6d64949c8c1e27b8c9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 5 Apr 2026 20:36:00 +0000 Subject: [PATCH 06/16] chore: bump version to 2.9.2 [skip ci] --- client/package-lock.json | 4 ++-- client/package.json | 2 +- server/package-lock.json | 4 ++-- server/package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 96744d3b..1d0ea78f 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "trek-client", - "version": "2.9.1", + "version": "2.9.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trek-client", - "version": "2.9.1", + "version": "2.9.2", "dependencies": { "@react-pdf/renderer": "^4.3.2", "axios": "^1.6.7", diff --git a/client/package.json b/client/package.json index 5304296e..87ceef89 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "trek-client", - "version": "2.9.1", + "version": "2.9.2", "private": true, "type": "module", "scripts": { diff --git a/server/package-lock.json b/server/package-lock.json index 67b4b948..332fca75 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "trek-server", - "version": "2.9.1", + "version": "2.9.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trek-server", - "version": "2.9.1", + "version": "2.9.2", "dependencies": { "@modelcontextprotocol/sdk": "^1.28.0", "archiver": "^6.0.1", diff --git a/server/package.json b/server/package.json index 72514782..2f0e7f97 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "trek-server", - "version": "2.9.1", + "version": "2.9.2", "main": "src/index.ts", "scripts": { "start": "node --import tsx src/index.ts", From 411d8620bae8c298b3aad6ba9b4a6e9bb98ec834 Mon Sep 17 00:00:00 2001 From: jubnl Date: Sun, 5 Apr 2026 22:45:34 +0200 Subject: [PATCH 07/16] fix(reservations): reset stale budget category when it no longer exists If the budget category stored in reservation metadata was deleted, the form would re-submit it on next save, resurrecting the deleted category. Now validates against live budget items on form init and falls back to auto-generation when the stored category is gone. Closes #442 --- client/src/components/Planner/ReservationModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Planner/ReservationModal.tsx b/client/src/components/Planner/ReservationModal.tsx index 2d8e4ca8..d974d6a8 100644 --- a/client/src/components/Planner/ReservationModal.tsx +++ b/client/src/components/Planner/ReservationModal.tsx @@ -141,7 +141,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p hotel_start_day: (() => { const acc = accommodations.find(a => a.id == reservation.accommodation_id); return acc?.start_day_id || '' })(), hotel_end_day: (() => { const acc = accommodations.find(a => a.id == reservation.accommodation_id); return acc?.end_day_id || '' })(), price: meta.price || '', - budget_category: meta.budget_category || '', + budget_category: (meta.budget_category && budgetItems.some(i => i.category === meta.budget_category)) ? meta.budget_category : '', }) } else { setForm({ From a676dbe88168a08900a84e54db289a803dc4c7b4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 5 Apr 2026 20:46:34 +0000 Subject: [PATCH 08/16] chore: bump version to 2.9.3 [skip ci] --- client/package-lock.json | 4 ++-- client/package.json | 2 +- server/package-lock.json | 4 ++-- server/package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 1d0ea78f..6e1e2721 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "trek-client", - "version": "2.9.2", + "version": "2.9.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trek-client", - "version": "2.9.2", + "version": "2.9.3", "dependencies": { "@react-pdf/renderer": "^4.3.2", "axios": "^1.6.7", diff --git a/client/package.json b/client/package.json index 87ceef89..2662bca4 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "trek-client", - "version": "2.9.2", + "version": "2.9.3", "private": true, "type": "module", "scripts": { diff --git a/server/package-lock.json b/server/package-lock.json index 332fca75..b9884f54 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "trek-server", - "version": "2.9.2", + "version": "2.9.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trek-server", - "version": "2.9.2", + "version": "2.9.3", "dependencies": { "@modelcontextprotocol/sdk": "^1.28.0", "archiver": "^6.0.1", diff --git a/server/package.json b/server/package.json index 2f0e7f97..f2380d54 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "trek-server", - "version": "2.9.2", + "version": "2.9.3", "main": "src/index.ts", "scripts": { "start": "node --import tsx src/index.ts", From 03757ed0af8c24eb05a395c2a86c642fa78a8f67 Mon Sep 17 00:00:00 2001 From: Maurice Date: Sun, 5 Apr 2026 23:02:42 +0200 Subject: [PATCH 09/16] fix(dayplan): per-day transport positions for multi-day reservations Reordering places on one day of a multi-day reservation no longer affects the order on other days. Transport positions are now stored per-day in a new reservation_day_positions table instead of a single global day_plan_position on the reservation. --- client/src/api/client.ts | 2 +- .../src/components/Planner/DayPlanSidebar.tsx | 18 ++++-- server/src/db/migrations.ts | 21 +++++++ server/src/routes/reservations.ts | 5 +- server/src/services/reservationService.ts | 61 ++++++++++++++++--- 5 files changed, 89 insertions(+), 18 deletions(-) diff --git a/client/src/api/client.ts b/client/src/api/client.ts index eb3a236e..237d3e64 100644 --- a/client/src/api/client.ts +++ b/client/src/api/client.ts @@ -248,7 +248,7 @@ export const reservationsApi = { create: (tripId: number | string, data: Record) => apiClient.post(`/trips/${tripId}/reservations`, data).then(r => r.data), update: (tripId: number | string, id: number, data: Record) => apiClient.put(`/trips/${tripId}/reservations/${id}`, data).then(r => r.data), delete: (tripId: number | string, id: number) => apiClient.delete(`/trips/${tripId}/reservations/${id}`).then(r => r.data), - updatePositions: (tripId: number | string, positions: { id: number; day_plan_position: number }[]) => apiClient.put(`/trips/${tripId}/reservations/positions`, { positions }).then(r => r.data), + updatePositions: (tripId: number | string, positions: { id: number; day_plan_position: number }[], dayId?: number) => apiClient.put(`/trips/${tripId}/reservations/positions`, { positions, day_id: dayId }).then(r => r.data), } export const weatherApi = { diff --git a/client/src/components/Planner/DayPlanSidebar.tsx b/client/src/components/Planner/DayPlanSidebar.tsx index 58e8a751..9a575be6 100644 --- a/client/src/components/Planner/DayPlanSidebar.tsx +++ b/client/src/components/Planner/DayPlanSidebar.tsx @@ -366,9 +366,12 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({ const timed = timedTransports[ti] const minutes = timed.minutes - // Use persisted position if available - if (timed.data.day_plan_position != null) { - result.push({ type: timed.type, sortKey: timed.data.day_plan_position, data: timed.data }) + // Use per-day position if available, fallback to global position + const dayObj = days.find(d => d.id === dayId) + const perDayPos = timed.data.day_positions?.[dayId] ?? timed.data.day_positions?.[String(dayId)] + const effectivePos = perDayPos ?? timed.data.day_plan_position + if (effectivePos != null) { + result.push({ type: timed.type, sortKey: effectivePos, data: timed.data }) continue } @@ -500,10 +503,15 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({ if (transportUpdates.length) { for (const tu of transportUpdates) { const res = reservations.find(r => r.id === tu.id) - if (res) res.day_plan_position = tu.day_plan_position + if (res) { + res.day_plan_position = tu.day_plan_position + // Update per-day position for multi-day reservations + if (!res.day_positions) res.day_positions = {} + res.day_positions[dayId] = tu.day_plan_position + } } setTransportPosVersion(v => v + 1) - await reservationsApi.updatePositions(tripId, transportUpdates) + await reservationsApi.updatePositions(tripId, transportUpdates, dayId) } if (prevAssignmentIds.length) { const capturedDayId = dayId diff --git a/server/src/db/migrations.ts b/server/src/db/migrations.ts index a061d1c9..aa3a3e99 100644 --- a/server/src/db/migrations.ts +++ b/server/src/db/migrations.ts @@ -843,6 +843,27 @@ function runMigrations(db: Database.Database): void { const ins = db.prepare('INSERT OR IGNORE INTO packing_bag_members (bag_id, user_id) VALUES (?, ?)'); for (const b of bagsWithUser) ins.run(b.id, b.user_id); }, + // Migration: Per-day positions for multi-day reservations + () => { + db.exec(` + CREATE TABLE IF NOT EXISTS reservation_day_positions ( + reservation_id INTEGER NOT NULL REFERENCES reservations(id) ON DELETE CASCADE, + day_id INTEGER NOT NULL REFERENCES days(id) ON DELETE CASCADE, + position REAL NOT NULL, + PRIMARY KEY (reservation_id, day_id) + ); + `); + // Migrate existing global positions to per-day entries + const reservations = db.prepare('SELECT id, trip_id, reservation_time, reservation_end_time, day_plan_position FROM reservations WHERE day_plan_position IS NOT NULL').all() as any[]; + const ins = db.prepare('INSERT OR IGNORE INTO reservation_day_positions (reservation_id, day_id, position) VALUES (?, ?, ?)'); + for (const r of reservations) { + const startDate = r.reservation_time?.split('T')[0]; + const endDate = r.reservation_end_time?.split('T')[0] || startDate; + if (!startDate) continue; + const matchingDays = db.prepare('SELECT id FROM days WHERE trip_id = ? AND date >= ? AND date <= ?').all(r.trip_id, startDate, endDate) as { id: number }[]; + for (const d of matchingDays) ins.run(r.id, d.id, r.day_plan_position); + } + }, ]; if (currentVersion < migrations.length) { diff --git a/server/src/routes/reservations.ts b/server/src/routes/reservations.ts index 05b75038..b3e233d4 100644 --- a/server/src/routes/reservations.ts +++ b/server/src/routes/reservations.ts @@ -91,10 +91,11 @@ router.put('/positions', authenticate, (req: Request, res: Response) => { if (!Array.isArray(positions)) return res.status(400).json({ error: 'positions must be an array' }); - updatePositions(tripId, positions); + const { day_id } = req.body; + updatePositions(tripId, positions, day_id); res.json({ success: true }); - broadcast(tripId, 'reservation:positions', { positions }, req.headers['x-socket-id'] as string); + broadcast(tripId, 'reservation:positions', { positions, day_id }, req.headers['x-socket-id'] as string); }); router.put('/:id', authenticate, (req: Request, res: Response) => { diff --git a/server/src/services/reservationService.ts b/server/src/services/reservationService.ts index bbb5d183..177ea516 100644 --- a/server/src/services/reservationService.ts +++ b/server/src/services/reservationService.ts @@ -6,7 +6,7 @@ export function verifyTripAccess(tripId: string | number, userId: number) { } export function listReservations(tripId: string | number) { - return db.prepare(` + const reservations = db.prepare(` SELECT r.*, d.day_number, p.name as place_name, r.assignment_id, ap.place_id as accommodation_place_id, acc_p.name as accommodation_name FROM reservations r @@ -16,7 +16,27 @@ export function listReservations(tripId: string | number) { LEFT JOIN places acc_p ON ap.place_id = acc_p.id WHERE r.trip_id = ? ORDER BY r.reservation_time ASC, r.created_at ASC - `).all(tripId); + `).all(tripId) as any[]; + + // Attach per-day positions for multi-day reservations + const dayPositions = db.prepare(` + SELECT rdp.reservation_id, rdp.day_id, rdp.position + FROM reservation_day_positions rdp + JOIN reservations r ON rdp.reservation_id = r.id + WHERE r.trip_id = ? + `).all(tripId) as { reservation_id: number; day_id: number; position: number }[]; + + const posMap = new Map>(); + for (const dp of dayPositions) { + if (!posMap.has(dp.reservation_id)) posMap.set(dp.reservation_id, {}); + posMap.get(dp.reservation_id)![dp.day_id] = dp.position; + } + + for (const r of reservations) { + r.day_positions = posMap.get(r.id) || null; + } + + return reservations; } export function getReservationWithJoins(id: string | number) { @@ -117,14 +137,35 @@ export function createReservation(tripId: string | number, data: CreateReservati return { reservation, accommodationCreated }; } -export function updatePositions(tripId: string | number, positions: { id: number; day_plan_position: number }[]) { - const stmt = db.prepare('UPDATE reservations SET day_plan_position = ? WHERE id = ? AND trip_id = ?'); - const updateMany = db.transaction((items: { id: number; day_plan_position: number }[]) => { - for (const item of items) { - stmt.run(item.day_plan_position, item.id, tripId); - } - }); - updateMany(positions); +export function updatePositions(tripId: string | number, positions: { id: number; day_plan_position: number }[], dayId?: number | string) { + if (dayId) { + // Per-day positions for multi-day reservations + const stmt = db.prepare('INSERT OR REPLACE INTO reservation_day_positions (reservation_id, day_id, position) VALUES (?, ?, ?)'); + const updateMany = db.transaction((items: { id: number; day_plan_position: number }[]) => { + for (const item of items) { + stmt.run(item.id, dayId, item.day_plan_position); + } + }); + updateMany(positions); + } else { + // Legacy: update global position + const stmt = db.prepare('UPDATE reservations SET day_plan_position = ? WHERE id = ? AND trip_id = ?'); + const updateMany = db.transaction((items: { id: number; day_plan_position: number }[]) => { + for (const item of items) { + stmt.run(item.day_plan_position, item.id, tripId); + } + }); + updateMany(positions); + } +} + +export function getDayPositions(tripId: string | number, dayId: number | string) { + return db.prepare(` + SELECT rdp.reservation_id, rdp.position + FROM reservation_day_positions rdp + JOIN reservations r ON rdp.reservation_id = r.id + WHERE r.trip_id = ? AND rdp.day_id = ? + `).all(tripId, dayId) as { reservation_id: number; position: number }[]; } export function getReservation(id: string | number, tripId: string | number) { From 6491e1f9867a4836f8d4dad3ad16fd60acd9a02e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 5 Apr 2026 21:02:53 +0000 Subject: [PATCH 10/16] chore: bump version to 2.9.4 [skip ci] --- client/package-lock.json | 4 ++-- client/package.json | 2 +- server/package-lock.json | 4 ++-- server/package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 6e1e2721..91a6ce69 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "trek-client", - "version": "2.9.3", + "version": "2.9.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trek-client", - "version": "2.9.3", + "version": "2.9.4", "dependencies": { "@react-pdf/renderer": "^4.3.2", "axios": "^1.6.7", diff --git a/client/package.json b/client/package.json index 2662bca4..300eeeb2 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "trek-client", - "version": "2.9.3", + "version": "2.9.4", "private": true, "type": "module", "scripts": { diff --git a/server/package-lock.json b/server/package-lock.json index b9884f54..138879dc 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "trek-server", - "version": "2.9.3", + "version": "2.9.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trek-server", - "version": "2.9.3", + "version": "2.9.4", "dependencies": { "@modelcontextprotocol/sdk": "^1.28.0", "archiver": "^6.0.1", diff --git a/server/package.json b/server/package.json index f2380d54..23a2158b 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "trek-server", - "version": "2.9.3", + "version": "2.9.4", "main": "src/index.ts", "scripts": { "start": "node --import tsx src/index.ts", From c8250256a772d58420260017bac20851e345d3b1 Mon Sep 17 00:00:00 2001 From: jubnl Date: Sun, 5 Apr 2026 23:11:40 +0200 Subject: [PATCH 11/16] fix(streaming): end response on client disconnect during asset pipe When a client disconnects mid-stream, headers are already sent so the catch block now calls response.end() before returning, preventing the socket from being left open and crashing the server. Fixes #445. --- server/src/services/memories/helpersService.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/services/memories/helpersService.ts b/server/src/services/memories/helpersService.ts index 5ed382e5..9e3d32cf 100644 --- a/server/src/services/memories/helpersService.ts +++ b/server/src/services/memories/helpersService.ts @@ -178,7 +178,10 @@ export async function pipeAsset(url: string, response: Response, headers?: Recor await pipeline(Readable.fromWeb(resp.body as any), response); } } catch (error) { - if (response.headersSent) return; + if (response.headersSent) { + response.end(); + return; + } if (error instanceof SsrfBlockedError) { response.status(400).json({ error: error.message }); } else { From 48508b9df4bdc1f4d36aff273e81615974942adf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 5 Apr 2026 21:12:19 +0000 Subject: [PATCH 12/16] chore: bump version to 2.9.5 [skip ci] --- client/package-lock.json | 4 ++-- client/package.json | 2 +- server/package-lock.json | 4 ++-- server/package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 91a6ce69..b437fd81 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "trek-client", - "version": "2.9.4", + "version": "2.9.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trek-client", - "version": "2.9.4", + "version": "2.9.5", "dependencies": { "@react-pdf/renderer": "^4.3.2", "axios": "^1.6.7", diff --git a/client/package.json b/client/package.json index 300eeeb2..1f18b130 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "trek-client", - "version": "2.9.4", + "version": "2.9.5", "private": true, "type": "module", "scripts": { diff --git a/server/package-lock.json b/server/package-lock.json index 138879dc..144079f0 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "trek-server", - "version": "2.9.4", + "version": "2.9.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trek-server", - "version": "2.9.4", + "version": "2.9.5", "dependencies": { "@modelcontextprotocol/sdk": "^1.28.0", "archiver": "^6.0.1", diff --git a/server/package.json b/server/package.json index 23a2158b..25390d6a 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "trek-server", - "version": "2.9.4", + "version": "2.9.5", "main": "src/index.ts", "scripts": { "start": "node --import tsx src/index.ts", From 5c57116a68ce954a89bde7605b7beeb7127c8170 Mon Sep 17 00:00:00 2001 From: Maurice Date: Sun, 5 Apr 2026 23:26:35 +0200 Subject: [PATCH 13/16] fix(dayplan): restore time-based auto-sort for places and free reorder for untimed Timed places now auto-sort chronologically when a time is set. Untimed places can be freely dragged between timed items. Transports are inserted by time with per-day position override. Fixes regression from multi-day spanning PR that removed timed/untimed split. --- .../src/components/Planner/DayPlanSidebar.tsx | 17 +++++------ server/src/services/assignmentService.ts | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/client/src/components/Planner/DayPlanSidebar.tsx b/client/src/components/Planner/DayPlanSidebar.tsx index 9a575be6..5393ec9e 100644 --- a/client/src/components/Planner/DayPlanSidebar.tsx +++ b/client/src/components/Planner/DayPlanSidebar.tsx @@ -341,14 +341,13 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({ initTransportPositions(dayId) } - // Build base list: ALL places (timed and untimed) + notes sorted by order_index/sort_order - // Places keep their order_index ordering — only transports are inserted based on time. + // All places keep their order_index — untimed can be freely moved, timed auto-sort when time is set const baseItems = [ ...da.map(a => ({ type: 'place' as const, sortKey: a.order_index, data: a })), ...dn.map(n => ({ type: 'note' as const, sortKey: n.sort_order, data: n })), ].sort((a, b) => a.sortKey - b.sortKey) - // Only transports are inserted among base items based on time/position + // Transports are inserted among places based on time const timedTransports = transport.map(r => ({ type: 'transport' as const, data: r, @@ -360,22 +359,20 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({ return timedTransports.map((item, i) => ({ ...item, sortKey: i })) } - // Insert transports among base items using persisted position or time-to-position mapping. + // Insert transports among places based on per-day position or time const result = [...baseItems] for (let ti = 0; ti < timedTransports.length; ti++) { const timed = timedTransports[ti] const minutes = timed.minutes - // Use per-day position if available, fallback to global position - const dayObj = days.find(d => d.id === dayId) + // Use per-day position if explicitly set by user reorder const perDayPos = timed.data.day_positions?.[dayId] ?? timed.data.day_positions?.[String(dayId)] - const effectivePos = perDayPos ?? timed.data.day_plan_position - if (effectivePos != null) { - result.push({ type: timed.type, sortKey: effectivePos, data: timed.data }) + if (perDayPos != null) { + result.push({ type: timed.type, sortKey: perDayPos, data: timed.data }) continue } - // Find insertion position: after the last base item with time <= this transport's time + // Find insertion position: after the last place with time <= this transport's time let insertAfterKey = -Infinity for (const item of result) { if (item.type === 'place') { diff --git a/server/src/services/assignmentService.ts b/server/src/services/assignmentService.ts index 72ac7640..b7579ea9 100644 --- a/server/src/services/assignmentService.ts +++ b/server/src/services/assignmentService.ts @@ -168,6 +168,34 @@ export function getParticipants(assignmentId: string | number) { export function updateTime(id: string | number, placeTime: string | null, endTime: string | null) { db.prepare('UPDATE day_assignments SET assignment_time = ?, assignment_end_time = ? WHERE id = ?') .run(placeTime ?? null, endTime ?? null, id); + + // Auto-sort: reorder timed assignments chronologically within the day + if (placeTime) { + const assignment = db.prepare('SELECT day_id FROM day_assignments WHERE id = ?').get(id) as { day_id: number } | undefined; + if (assignment) { + const dayAssignments = db.prepare(` + SELECT da.id, COALESCE(da.assignment_time, p.place_time) as effective_time + FROM day_assignments da + JOIN places p ON da.place_id = p.id + WHERE da.day_id = ? + ORDER BY da.order_index ASC + `).all(assignment.day_id) as { id: number; effective_time: string | null }[]; + + // Separate timed and untimed, sort timed by time + const timed = dayAssignments.filter(a => a.effective_time).sort((a, b) => { + const ta = a.effective_time!.includes(':') ? a.effective_time! : '99:99'; + const tb = b.effective_time!.includes(':') ? b.effective_time! : '99:99'; + return ta.localeCompare(tb); + }); + const untimed = dayAssignments.filter(a => !a.effective_time); + + // Interleave: timed in chronological order, untimed keep relative position + const reordered = [...timed, ...untimed]; + const update = db.prepare('UPDATE day_assignments SET order_index = ? WHERE id = ?'); + reordered.forEach((a, i) => update.run(i, a.id)); + } + } + return getAssignmentWithPlace(Number(id)); } From 68a1f9683e47b22066cb082aebcc207e9c2fe339 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 5 Apr 2026 21:26:44 +0000 Subject: [PATCH 14/16] chore: bump version to 2.9.6 [skip ci] --- client/package-lock.json | 4 ++-- client/package.json | 2 +- server/package-lock.json | 4 ++-- server/package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index b437fd81..64629a5e 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "trek-client", - "version": "2.9.5", + "version": "2.9.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trek-client", - "version": "2.9.5", + "version": "2.9.6", "dependencies": { "@react-pdf/renderer": "^4.3.2", "axios": "^1.6.7", diff --git a/client/package.json b/client/package.json index 1f18b130..ca539b87 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "trek-client", - "version": "2.9.5", + "version": "2.9.6", "private": true, "type": "module", "scripts": { diff --git a/server/package-lock.json b/server/package-lock.json index 144079f0..d45c86ba 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "trek-server", - "version": "2.9.5", + "version": "2.9.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trek-server", - "version": "2.9.5", + "version": "2.9.6", "dependencies": { "@modelcontextprotocol/sdk": "^1.28.0", "archiver": "^6.0.1", diff --git a/server/package.json b/server/package.json index 25390d6a..cae859e3 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "trek-server", - "version": "2.9.5", + "version": "2.9.6", "main": "src/index.ts", "scripts": { "start": "node --import tsx src/index.ts", From e2be3ec1911dd3be17fbce0cd61bb4397bcbd6f1 Mon Sep 17 00:00:00 2001 From: jubnl Date: Sun, 5 Apr 2026 23:37:53 +0200 Subject: [PATCH 15/16] fix(atlas): replace fuzzy region matching with exact name_en check Bidirectional substring matching in isVisitedFeature caused unrelated regions to be highlighted as visited (e.g. selecting Nordrhein-Westfalen also marked Nord France due to "nord" being a substring match). Replace the fuzzy loop with an additional exact check against the Natural Earth name_en property to cover English-vs-native name mismatches. Also fix Nominatim field priority to prefer state over county so reverse-geocoded places resolve to the correct admin-1 level. Adds integration tests ATLAS-009 through ATLAS-011 covering mark/unmark region endpoints and user isolation. Fixes #446 --- client/src/pages/AtlasPage.tsx | 13 +- server/src/services/atlasService.ts | 2 +- server/tests/integration/atlas.test.ts | 181 +++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 8 deletions(-) diff --git a/client/src/pages/AtlasPage.tsx b/client/src/pages/AtlasPage.tsx index ddff4ce4..2365c8cf 100644 --- a/client/src/pages/AtlasPage.tsx +++ b/client/src/pages/AtlasPage.tsx @@ -480,15 +480,13 @@ export default function AtlasPage(): React.ReactElement { } } - // Match feature by ISO code OR region name + // Match feature by ISO code OR region name (native or English) const isVisitedFeature = (f: any) => { if (visitedRegionCodes.has(f.properties?.iso_3166_2)) return true const name = (f.properties?.name || '').toLowerCase() if (visitedRegionNames.has(name)) return true - // Fuzzy: check if any visited name is contained in feature name or vice versa - for (const vn of visitedRegionNames) { - if (name.includes(vn) || vn.includes(name)) return true - } + const nameEn = (f.properties?.name_en || '').toLowerCase() + if (nameEn && visitedRegionNames.has(nameEn)) return true return false } @@ -535,15 +533,16 @@ export default function AtlasPage(): React.ReactElement { }, onEachFeature: (feature, layer) => { const regionName = feature?.properties?.name || '' + const regionNameEn = feature?.properties?.name_en || '' const countryName = feature?.properties?.admin || '' const regionCode = feature?.properties?.iso_3166_2 || '' const countryA2 = (feature?.properties?.iso_a2 || '').toUpperCase() const visited = isVisitedFeature(feature) - const count = regionPlaceCounts[regionCode] || regionPlaceCounts[regionName.toLowerCase()] || 0 + const count = regionPlaceCounts[regionCode] || regionPlaceCounts[regionName.toLowerCase()] || regionPlaceCounts[regionNameEn.toLowerCase()] || 0 layer.on('click', () => { if (!countryA2) return if (visited) { - const regionEntry = visitedRegions[countryA2]?.find(r => r.code === regionCode) + const regionEntry = visitedRegions[countryA2]?.find(r => r.code === regionCode || r.name.toLowerCase() === regionNameEn.toLowerCase()) if (regionEntry?.manuallyMarked) { setConfirmActionRef.current({ type: 'unmark-region', diff --git a/server/src/services/atlasService.ts b/server/src/services/atlasService.ts index f79d082a..bb48347e 100644 --- a/server/src/services/atlasService.ts +++ b/server/src/services/atlasService.ts @@ -421,7 +421,7 @@ async function reverseGeocodeRegion(lat: number, lng: number): Promise { expect(res.status).toBe(404); }); }); + +describe('Mark/unmark region', () => { + it('ATLAS-009 — POST /region/:code/mark marks a region as visited', async () => { + const { user } = createUser(testDb); + + const res = await request(app) + .post('/api/addons/atlas/region/DE-NW/mark') + .set('Cookie', authCookie(user.id)) + .send({ name: 'Nordrhein-Westfalen', country_code: 'DE' }); + + expect(res.status).toBe(200); + expect(res.body.success).toBe(true); + }); + + it('ATLAS-009 — POST /region/:code/mark without name returns 400', async () => { + const { user } = createUser(testDb); + + const res = await request(app) + .post('/api/addons/atlas/region/DE-NW/mark') + .set('Cookie', authCookie(user.id)) + .send({ country_code: 'DE' }); + + expect(res.status).toBe(400); + }); + + it('ATLAS-009 — POST /region/:code/mark without country_code returns 400', async () => { + const { user } = createUser(testDb); + + const res = await request(app) + .post('/api/addons/atlas/region/DE-NW/mark') + .set('Cookie', authCookie(user.id)) + .send({ name: 'Nordrhein-Westfalen' }); + + expect(res.status).toBe(400); + }); + + it('ATLAS-009 — marking a region also auto-marks the parent country', async () => { + const { user } = createUser(testDb); + + await request(app) + .post('/api/addons/atlas/region/DE-NW/mark') + .set('Cookie', authCookie(user.id)) + .send({ name: 'Nordrhein-Westfalen', country_code: 'DE' }); + + const stats = await request(app) + .get('/api/addons/atlas/stats') + .set('Cookie', authCookie(user.id)); + + const codes = (stats.body.countries as any[]).map((c: any) => c.code); + expect(codes).toContain('DE'); + }); + + it('ATLAS-009 — marking the same region twice is idempotent', async () => { + const { user } = createUser(testDb); + + await request(app) + .post('/api/addons/atlas/region/DE-NW/mark') + .set('Cookie', authCookie(user.id)) + .send({ name: 'Nordrhein-Westfalen', country_code: 'DE' }); + + const res = await request(app) + .post('/api/addons/atlas/region/DE-NW/mark') + .set('Cookie', authCookie(user.id)) + .send({ name: 'Nordrhein-Westfalen', country_code: 'DE' }); + + expect(res.status).toBe(200); + }); + + it('ATLAS-010 — GET /regions returns marked regions grouped by country', async () => { + const { user } = createUser(testDb); + + await request(app) + .post('/api/addons/atlas/region/DE-NW/mark') + .set('Cookie', authCookie(user.id)) + .send({ name: 'Nordrhein-Westfalen', country_code: 'DE' }); + + await request(app) + .post('/api/addons/atlas/region/DE-BY/mark') + .set('Cookie', authCookie(user.id)) + .send({ name: 'Bayern', country_code: 'DE' }); + + const res = await request(app) + .get('/api/addons/atlas/regions') + .set('Cookie', authCookie(user.id)); + + expect(res.status).toBe(200); + expect(res.body).toHaveProperty('regions'); + const deRegions = res.body.regions['DE'] as any[]; + expect(deRegions).toBeDefined(); + const codes = deRegions.map((r: any) => r.code); + expect(codes).toContain('DE-NW'); + expect(codes).toContain('DE-BY'); + }); + + it('ATLAS-011 — DELETE /region/:code/mark unmarks a region', async () => { + const { user } = createUser(testDb); + + await request(app) + .post('/api/addons/atlas/region/DE-NW/mark') + .set('Cookie', authCookie(user.id)) + .send({ name: 'Nordrhein-Westfalen', country_code: 'DE' }); + + const del = await request(app) + .delete('/api/addons/atlas/region/DE-NW/mark') + .set('Cookie', authCookie(user.id)); + + expect(del.status).toBe(200); + expect(del.body.success).toBe(true); + + const res = await request(app) + .get('/api/addons/atlas/regions') + .set('Cookie', authCookie(user.id)); + + const deRegions = res.body.regions['DE'] as any[] | undefined; + const codes = (deRegions || []).map((r: any) => r.code); + expect(codes).not.toContain('DE-NW'); + }); + + it('ATLAS-011 — unmark last region in country also unmarks the parent country', async () => { + const { user } = createUser(testDb); + + await request(app) + .post('/api/addons/atlas/region/DE-NW/mark') + .set('Cookie', authCookie(user.id)) + .send({ name: 'Nordrhein-Westfalen', country_code: 'DE' }); + + await request(app) + .delete('/api/addons/atlas/region/DE-NW/mark') + .set('Cookie', authCookie(user.id)); + + const stats = await request(app) + .get('/api/addons/atlas/stats') + .set('Cookie', authCookie(user.id)); + + const codes = (stats.body.countries as any[]).map((c: any) => c.code); + expect(codes).not.toContain('DE'); + }); + + it('ATLAS-011 — unmark one region keeps country when another region remains', async () => { + const { user } = createUser(testDb); + + await request(app) + .post('/api/addons/atlas/region/DE-NW/mark') + .set('Cookie', authCookie(user.id)) + .send({ name: 'Nordrhein-Westfalen', country_code: 'DE' }); + + await request(app) + .post('/api/addons/atlas/region/DE-BY/mark') + .set('Cookie', authCookie(user.id)) + .send({ name: 'Bayern', country_code: 'DE' }); + + await request(app) + .delete('/api/addons/atlas/region/DE-NW/mark') + .set('Cookie', authCookie(user.id)); + + const stats = await request(app) + .get('/api/addons/atlas/stats') + .set('Cookie', authCookie(user.id)); + + const codes = (stats.body.countries as any[]).map((c: any) => c.code); + expect(codes).toContain('DE'); + }); + + it('ATLAS-011 — regions are isolated between users', async () => { + const { user: user1 } = createUser(testDb); + const { user: user2 } = createUser(testDb); + + await request(app) + .post('/api/addons/atlas/region/DE-NW/mark') + .set('Cookie', authCookie(user1.id)) + .send({ name: 'Nordrhein-Westfalen', country_code: 'DE' }); + + const res = await request(app) + .get('/api/addons/atlas/regions') + .set('Cookie', authCookie(user2.id)); + + expect(res.status).toBe(200); + const deRegions = res.body.regions['DE'] as any[] | undefined; + expect(deRegions).toBeUndefined(); + }); +}); From beb48af8ed24e600e21c8e4254fadff2aca3e4c3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 5 Apr 2026 21:38:56 +0000 Subject: [PATCH 16/16] chore: bump version to 2.9.7 [skip ci] --- client/package-lock.json | 4 ++-- client/package.json | 2 +- server/package-lock.json | 4 ++-- server/package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 64629a5e..18078911 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "trek-client", - "version": "2.9.6", + "version": "2.9.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trek-client", - "version": "2.9.6", + "version": "2.9.7", "dependencies": { "@react-pdf/renderer": "^4.3.2", "axios": "^1.6.7", diff --git a/client/package.json b/client/package.json index ca539b87..0462203b 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "trek-client", - "version": "2.9.6", + "version": "2.9.7", "private": true, "type": "module", "scripts": { diff --git a/server/package-lock.json b/server/package-lock.json index d45c86ba..062d1241 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "trek-server", - "version": "2.9.6", + "version": "2.9.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trek-server", - "version": "2.9.6", + "version": "2.9.7", "dependencies": { "@modelcontextprotocol/sdk": "^1.28.0", "archiver": "^6.0.1", diff --git a/server/package.json b/server/package.json index cae859e3..7fee6fce 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "trek-server", - "version": "2.9.6", + "version": "2.9.7", "main": "src/index.ts", "scripts": { "start": "node --import tsx src/index.ts",