feat: taller route tiles (h-46), labels overlay above route line for more place names, renamed auto routes become custom (rename clears auto_detected + startup backfill)
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 6s
Build and push images / build-worker (push) Successful in 5s
Build and push images / build-frontend (push) Successful in 9s

This commit is contained in:
2026-06-23 12:08:33 +01:00
parent 16dff88564
commit 62cea59ef8
4 changed files with 29 additions and 4 deletions
+3
View File
@@ -179,6 +179,9 @@ async def update_route(
raise HTTPException(status_code=404, detail="Route not found") raise HTTPException(status_code=404, detail="Route not found")
if body.name is not None and body.name.strip(): if body.name is not None and body.name.strip():
route.name = body.name.strip() route.name = body.name.strip()
# A user-given name makes this a custom route (moves it out of the
# auto-detected group on the routes page).
route.auto_detected = False
if body.sport_type is not None: if body.sport_type is not None:
route.sport_type = body.sport_type route.sport_type = body.sport_type
await db.commit() await db.commit()
+14
View File
@@ -189,6 +189,20 @@ async def init_db():
except Exception as e: except Exception as e:
print(f"avg_speed_ms fix skipped: {e}") print(f"avg_speed_ms fix skipped: {e}")
# An auto-detected route the user has renamed should count as a custom route.
# Auto names are generated as "<Sport> route <DD Mon YYYY>"; any auto_detected
# route whose name no longer matches that pattern has been renamed → mark it
# custom so it groups with user-named routes.
try:
async with engine.begin() as conn:
await conn.execute(text(
"UPDATE named_routes SET auto_detected = false "
"WHERE auto_detected = true "
"AND name !~ '^.+ route [0-9]{1,2} [A-Za-z]{3} [0-9]{4}$'"
))
except Exception as e:
print(f"route auto_detected backfill skipped: {e}")
# Seed admin user (only if password is configured) # Seed admin user (only if password is configured)
if not settings.admin_password: if not settings.admin_password:
print("ADMIN_PASSWORD not set - skipping admin user seed") print("ADMIN_PASSWORD not set - skipping admin user seed")
+11 -3
View File
@@ -2,8 +2,11 @@ import { useEffect, useRef } from 'react'
import L from 'leaflet' import L from 'leaflet'
import { sportColor } from '../../utils/format' import { sportColor } from '../../utils/format'
// Voyager raster tiles — same street style used on the activity map. // Voyager raster tiles — same street style used on the activity map. We split
const TILE_URL = 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png' // the basemap (no labels) from a labels-only overlay so place names can be drawn
// *above* the route line instead of being hidden underneath it.
const BASE_URL = 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}{r}.png'
const LABELS_URL = 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager_only_labels/{z}/{x}/{y}{r}.png'
function decodePolyline(encoded) { function decodePolyline(encoded) {
if (!encoded) return [] if (!encoded) return []
@@ -38,7 +41,12 @@ export default function RouteTileMap({ polyline, sportType, className = '' }) {
zoomSnap: 0, zoomSnap: 0,
}) })
mapRef.current = map mapRef.current = map
L.tileLayer(TILE_URL, { maxZoom: 19 }).addTo(map) L.tileLayer(BASE_URL, { maxZoom: 19 }).addTo(map)
// A dedicated high-z pane keeps the labels overlay on top of the route line.
map.createPane('labels')
map.getPane('labels').style.zIndex = 650
map.getPane('labels').style.pointerEvents = 'none'
L.tileLayer(LABELS_URL, { maxZoom: 19, pane: 'labels' }).addTo(map)
return () => { map.remove(); mapRef.current = null } return () => { map.remove(); mapRef.current = null }
}, []) }, [])
+1 -1
View File
@@ -268,7 +268,7 @@ export default function RoutesPage() {
isSelected ? '' : 'bg-gray-900 border-gray-800 hover:border-gray-600' isSelected ? '' : 'bg-gray-900 border-gray-800 hover:border-gray-600'
}`}> }`}>
<RouteTileMap polyline={route.reference_polyline} sportType={route.sport_type} <RouteTileMap polyline={route.reference_polyline} sportType={route.sport_type}
className="w-full h-36 rounded-lg overflow-hidden" /> className="w-full h-[11.5rem] rounded-lg overflow-hidden" />
<p className="text-xs font-medium text-white mt-2 truncate">{route.name}</p> <p className="text-xs font-medium text-white mt-2 truncate">{route.name}</p>
<div className="flex items-center justify-between mt-0.5 gap-1"> <div className="flex items-center justify-between mt-0.5 gap-1">
<span className="text-xs text-gray-500">{formatDistance(route.distance_m, unit)}</span> <span className="text-xs text-gray-500">{formatDistance(route.distance_m, unit)}</span>