Markers: editable, manual x/y/z popup placement, drop heightmap auto-derive

Markers now carry a caller-supplied x/y/z instead of an auto-resolved
terrain height: clicking the map opens a real Leaflet popup pre-filled
with the clicked x/z, a default y of 60, and a random color, all
editable before saving. The same popup now also opens for editing an
existing marker (new updateMarker/PATCH /api/markers/:markerId, TDD'd
in markers.test.ts/index.test.ts). Removes the now-unused
resolveHeight/GET /api/height machinery. Linked-account markers
already synced server-side via the markers table, so cross-device
sync falls out of the existing loadMarkers()-on-init flow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tKdPZt78zbPUZMXWzKEKt
This commit is contained in:
2026-08-08 19:43:21 +02:00
parent 66fe2ffb7e
commit b2f9b6c0e9
10 changed files with 304 additions and 229 deletions
+21 -14
View File
@@ -22,6 +22,26 @@ html(lang="en")
span.text-sm.text-red-400(x-show="!loading && !server") No server registered yet — see backend README (bun run seed).
div.flex-1.flex.overflow-hidden
div#map.flex-1
div(style="display:none")
div(x-ref="markerFormEl")
div.bg-neutral-800.text-neutral-100.rounded.p-2.space-y-2(style="min-width: 220px;")
p.text-xs.text-neutral-400(x-text="markerForm.mode === 'edit' ? 'Edit marker' : 'New marker'")
div.grid.grid-cols-3.gap-1
input.bg-neutral-900.text-xs.px-1.py-1.rounded.border.border-neutral-700(
type="number" x-model.number="markerForm.x" placeholder="x")
input.bg-neutral-900.text-xs.px-1.py-1.rounded.border.border-neutral-700(
type="number" x-model.number="markerForm.y" placeholder="y")
input.bg-neutral-900.text-xs.px-1.py-1.rounded.border.border-neutral-700(
type="number" x-model.number="markerForm.z" placeholder="z")
div.flex.gap-1
input.flex-1.bg-neutral-900.text-sm.px-2.py-1.rounded.border.border-neutral-700(
type="text" placeholder="marker name" x-model="markerForm.name"
x-on:keydown.enter="saveMarkerForm")
input.w-10(type="color" x-model="markerForm.color")
div.flex.gap-1
button.flex-1.px-2.py-1.bg-emerald-700.rounded.text-xs(x-on:click="saveMarkerForm") Save
button.px-2.py-1.bg-neutral-700.rounded.text-xs(x-on:click="cancelMarkerForm") Cancel
p.text-xs.text-amber-400(x-show="markerStatus" x-text="markerStatus")
aside.w-80.flex.flex-col.border-l.border-neutral-700.bg-neutral-800(x-show="server")
div.border-b.border-neutral-700.p-2.space-y-2(style="max-height: 40%; overflow-y: auto;")
div.flex.items-center.justify-between
@@ -31,28 +51,15 @@ html(lang="en")
x-on:click="togglePlacingMarker"
x-text="placingMarker ? 'click the map…' : '+ place marker'")
template(x-if="pendingMarker")
div.space-y-1.bg-neutral-900.rounded.p-2
p.text-xs.text-neutral-400(x-text="'at ' + pendingMarker.x + ', ' + pendingMarker.z")
div.flex.gap-1
input.flex-1.bg-neutral-800.text-sm.px-2.py-1.rounded.border.border-neutral-700(
type="text" placeholder="marker name" x-model="markerNameInput"
x-on:keydown.enter="confirmMarker")
input.w-10(type="color" x-model="markerColorInput")
div.flex.gap-1
button.flex-1.px-2.py-1.bg-emerald-700.rounded.text-xs(x-on:click="confirmMarker") Save
button.px-2.py-1.bg-neutral-700.rounded.text-xs(x-on:click="cancelMarker") Cancel
template(x-for="marker in markers" x-bind:key="marker.id")
div.flex.items-center.gap-2.text-sm
span.inline-block.w-3.h-3.rounded-full.flex-shrink-0(x-bind:style="'background:' + marker.color")
span.flex-1.truncate(x-text="marker.name")
span.text-xs.text-neutral-500(x-text="marker.x + ', ' + marker.y + ', ' + marker.z")
button.text-xs.underline(x-on:click="editMarker(marker)") edit
button.text-xs.underline(x-show="account" x-on:click="shareMarker(marker)") share
button.text-xs.text-red-400(x-on:click="deleteMarker(marker)") ×
p.text-xs.text-amber-400(x-show="markerStatus" x-text="markerStatus")
div.flex-1.overflow-y-auto.p-2.space-y-1(x-ref="chatLog")
template(x-for="msg in chatMessages" x-bind:key="msg.id")
p.text-sm.break-words