fix(sync): Editier-Lock-Versionierung + Zeichnen-Race + Mess-Race (v1.6.4)

- handleStopEditing speichert jetzt MIT baseVersion und aktualisiert
  featuresVersionRef aus der Antwort (409 → Server-Stand übernehmen). Verhindert
  Fehl-Konflikte/Konflikt-Schleifen beim erneuten Bearbeiten nach dem Freigeben.
- Lock-Ablehnung (zwei zeichnen bei freier Karte gleichzeitig): der abgelehnte
  Client lädt den echten Sperr-Halter nach → Karte wird lokal read-only, keine
  weiteren nicht-speicherbaren Änderungen mehr.
- Mess-Höhenabfrage: Generation-Zähler bricht veraltete asynchrone Abfragen ab
  (Neue Messung/Verlassen) → keine falschen Höhen-/Druckwerte der alten Messung.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Pepe Ziberi
2026-07-22 21:36:00 +02:00
parent d2a1e7fc1e
commit 0e65721a1c
4 changed files with 37 additions and 5 deletions

View File

@@ -126,6 +126,9 @@ export function MapView({
const markerCleanupsRef = useRef<(() => void)[]>([])
const measureMarkersRef = useRef<maplibregl.Marker[]>([])
const measureCoordsRef = useRef<number[][]>([])
// Generation-Zähler: bricht veraltete (asynchrone) Höhen-Abfragen ab, wenn
// zwischenzeitlich eine neue Messung gestartet/geleert wurde.
const measureGenRef = useRef(0)
const [isMapLoaded, setIsMapLoaded] = useState(false)
const [activeBaseLayer, setActiveBaseLayer] = useState<'osm' | 'satellite' | 'swisstopo'>('osm')
const [layerDropdownOpen, setLayerDropdownOpen] = useState(false)
@@ -454,6 +457,8 @@ export function MapView({
const fetchElevationsAndUpdate = useCallback(async () => {
const coords = measureCoordsRef.current
if (coords.length < 2 || !map.current) return
// Diese Abfrage gehört zur aktuellen Messung; wird sie überholt, verwerfen wir das Ergebnis.
const gen = measureGenRef.current
// Interpolate extra points along the line (every ~30m) for better elevation accuracy
const sampledCoords: number[][] = [coords[0]]
@@ -560,6 +565,10 @@ export function MapView({
} catch { /* ignore */ }
if (hoseTypes.length === 0) hoseTypes = FALLBACK_HOSE_TYPES
// Überholt? (neue/geleerte Messung während der async Abfragen) → Ergebnis verwerfen,
// damit keine Höhen-Labels/Druckwerte der ALTEN Messung erscheinen.
if (gen !== measureGenRef.current) return
let totalDist = 0
for (let i = 1; i < coords.length; i++) {
totalDist += haversineDistance(coords[i - 1], coords[i])
@@ -1906,6 +1915,7 @@ export function MapView({
// Reset measurement state when leaving measure mode (but keep info panel — user closes it)
useEffect(() => {
if (drawMode !== 'measure') {
measureGenRef.current++ // pendende Höhen-Abfrage beim Verlassen entwerten
measureMarkersRef.current.forEach(m => m.remove())
measureMarkersRef.current = []
measureCoordsRef.current = []
@@ -2156,6 +2166,7 @@ export function MapView({
// Messung komplett entfernen (Linie, Marker, Höhen-Labels, Panel, Zustand)
const clearMeasurement = useCallback(() => {
measureGenRef.current++ // laufende Höhen-Abfragen der alten Messung entwerten
measureMarkersRef.current.forEach(m => m.remove())
measureMarkersRef.current = []
measureCoordsRef.current = []