diff --git a/AGENTS.md b/AGENTS.md index 805af39..04f6e21 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1288,6 +1288,11 @@ session with **no other memory of this project**. So: removes both `skatespots:last-location` and `skatespots:map-view` on account switch/logout and is exposed in Settings → Appearance; drafts are scoped by pubkey and `useFormDraft` debounces localStorage writes (400 ms). + **Coordinates are mandatory and explicit (2026-11-02):** Add Spot now always + shows `Coordinates *` with latitude/longitude inputs (`font-mono text-base`, + `required`, `aria-invalid`), below the pin summary/map; Edit Spot shows the same + larger fields and an inline `coordError`. Both still hard-block publish when + the pair is missing or out of range and always write `lat`/`lon`/`g` tags. ⚠️ **Remaining parity gap:** `EditSpotPage` still uses raw lat/lng + toast validation and does not yet share the map picker/draft/duplicate guard. - **Map Add entry points (2026-11-02):** every map Add Spot link now carries the diff --git a/distribution/README.md b/distribution/README.md index 0cdf773..3728fc5 100644 --- a/distribution/README.md +++ b/distribution/README.md @@ -7,10 +7,10 @@ account is required. - Forge: - Latest release: -- Signed APK (1.7.85): - +- Signed APK (1.7.86): + - SHA-256: - `58d642b792e6382b2a01159d66155c9cf60970b1cd9c5710200685e40b2a0c95` + `7890f29072f7df39241d99df94e9d17050752b22045579dee4190bbe005f0f0d` The APK is signed with the same key as the Zapstore build. Installing an F-Droid-signed build over it would require uninstalling SkateSpots and losing diff --git a/package-lock.json b/package-lock.json index baa38f0..c46ccaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "skate-spots", - "version": "1.7.85", + "version": "1.7.86", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "skate-spots", - "version": "1.7.85", + "version": "1.7.86", "license": "AGPL-3.0-only", "dependencies": { "@bitcoinerlab/secp256k1": "^1.2.0", diff --git a/package.json b/package.json index a3d03bc..3d8fca3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "skate-spots", "private": true, - "version": "1.7.85", + "version": "1.7.86", "license": "AGPL-3.0-only", "type": "module", "homepage": "https://skate-spots.org", diff --git a/public/download/index.html b/public/download/index.html index 6d696e2..5ca2677 100644 --- a/public/download/index.html +++ b/public/download/index.html @@ -98,10 +98,10 @@ - Download SkateSpots-1.7.85.apk · 60 MB + Download SkateSpots-1.7.86.apk · 60 MB diff --git a/release-notes.txt b/release-notes.txt index b16c4d9..d9ce158 100644 --- a/release-notes.txt +++ b/release-notes.txt @@ -1,3 +1,11 @@ +🛹 Spot coordinates are explicit and mandatory +- Add Spot now shows a permanent "Coordinates *" row with latitude and longitude fields, even when the map is collapsed. +- Edit Spot uses the same larger monospace coordinate fields, with an inline error if the pair is missing or invalid. +- Neither form can publish or save without a valid latitude/longitude pair; the event always carries lat, lon and a geohash. +- The pin summary shows the exact public coordinates in a larger, easier-to-check font. + +--- + 🛹 Navigation: map first, voice in your language - The Directions map now takes most of the phone screen: 56vh normally and 60vh while following. Route details and transit itineraries stay in the scrollable section below. - Voice guidance follows the app language instead of the phone/OS language, and prefers an installed local voice for that language. This covers all 24 app locales; the system default voice is used only when a matching one is not installed. diff --git a/src/pages/CreateSpotPage.tsx b/src/pages/CreateSpotPage.tsx index 92bb610..8f1f9f4 100644 --- a/src/pages/CreateSpotPage.tsx +++ b/src/pages/CreateSpotPage.tsx @@ -136,7 +136,6 @@ export default function CreateSpotPage() { const [errors, setErrors] = useState({}); const [showMore, setShowMore] = useState(false); const [showLocationMap, setShowLocationMap] = useState(!initial.pin); - const [showManualCoords, setShowManualCoords] = useState(false); // Duplicate guard: warn when a spot already exists right where you're pinning // one. This is the cheapest fix for clone spam and it points the contributor // at the existing pin instead. @@ -663,6 +662,9 @@ export default function CreateSpotPage() {

{location.trim() || `${lat.toFixed(5)}, ${lng.toFixed(5)}`}

+

+ {lat.toFixed(6)}, {lng.toFixed(6)} +

{t('spotUi.exactPinPublic')}

@@ -717,37 +719,37 @@ export default function CreateSpotPage() {

{t('spotUi.exactPinPublic')}

- - - - - - -
- { setLatitude(e.target.value); clearError('location'); }} - inputMode="decimal" - /> - { setLongitude(e.target.value); clearError('location'); }} - inputMode="decimal" - /> -
-
-
)} + {/* Coordinates are deliberately visible and editable on BOTH + add and edit. A spot cannot be published without a valid pair. */} +
+ +
+ { setLatitude(e.target.value); clearError('location'); }} + inputMode="decimal" + required + aria-invalid={!hasCoords} + className="font-mono text-base" + /> + { setLongitude(e.target.value); clearError('location'); }} + inputMode="decimal" + required + aria-invalid={!hasCoords} + className="font-mono text-base" + /> +
+
+ {errors.location &&

{errors.location}

} diff --git a/src/pages/EditSpotPage.tsx b/src/pages/EditSpotPage.tsx index 1af3a58..e49bccb 100644 --- a/src/pages/EditSpotPage.tsx +++ b/src/pages/EditSpotPage.tsx @@ -83,6 +83,7 @@ export default function EditSpotPage() { const [shelter, setShelter] = useState(DEFAULT_SHELTER); const [contactInfo, setContactInfo] = useState(''); const [images, setImages] = useState([]); + const [coordError, setCoordError] = useState(null); const [isGettingLocation, setIsGettingLocation] = useState(false); const [initialized, setInitialized] = useState(false); @@ -115,6 +116,7 @@ export default function EditSpotPage() { setLongitude(lng.toFixed(6)); } + setCoordError(null); setInitialized(true); } }, [spot, initialized]); @@ -138,6 +140,7 @@ export default function EditSpotPage() { (position) => { setLatitude(position.coords.latitude.toFixed(6)); setLongitude(position.coords.longitude.toFixed(6)); + setCoordError(null); setIsGettingLocation(false); toast({ title: t('spotUi.locationFound'), @@ -202,7 +205,8 @@ export default function EditSpotPage() { return; } - if (!latitude || !longitude) { + if (!latitude.trim() || !longitude.trim()) { + setCoordError(t('spotUi.locationRequiredDesc')); toast({ title: t('spotUi.locationRequired'), description: t('spotUi.locationRequiredDesc'), @@ -215,6 +219,7 @@ export default function EditSpotPage() { const lng = parseFloat(longitude); if (isNaN(lat) || isNaN(lng) || lat < -90 || lat > 90 || lng < -180 || lng > 180) { + setCoordError(t('spotUi.invalidCoordinatesDesc')); toast({ title: t('spotUi.invalidCoordinates'), description: t('spotUi.invalidCoordinatesDesc'), @@ -222,6 +227,7 @@ export default function EditSpotPage() { }); return; } + setCoordError(null); if (!selectedCategory) { toast({ @@ -417,27 +423,32 @@ export default function EditSpotPage() { /> - {/* Coordinates */} + {/* Coordinates — mandatory, visible and large enough to verify on a phone. */}
- -
-
- setLatitude(e.target.value)} - required - /> -
-
- setLongitude(e.target.value)} - required - /> -
+ +
+ { setLatitude(e.target.value); setCoordError(null); }} + inputMode="decimal" + required + aria-invalid={!!coordError} + className="font-mono text-base" + /> + { setLongitude(e.target.value); setCoordError(null); }} + inputMode="decimal" + required + aria-invalid={!!coordError} + className="font-mono text-base" + />
+ {coordError &&

{coordError}

}