From 8b3e3eb839a4659b592c39c9ccf763fdaf5e9668 Mon Sep 17 00:00:00 2001 From: Trey T Date: Sun, 7 Jun 2026 10:46:43 -0500 Subject: [PATCH] Make server the single source of truth for active location MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two devices on the same account each kept their own localStorage location and re-asserted it onto Feeld's single shared account-location slot on every Discover mount — so loading the site on device B would overwrite the location device A had just set (San Antonio → Dallas). - useLocation: on server sync, always adopt the server's current/custom location, overriding this device's local cache (was local-first via a `!location` guard). Devices now converge to the server value on load. Expose `locationReady`. - Discover: wait for `locationReady` before pushing DeviceLocationUpdate, and push whenever the authoritative location changes (deduped by coords) instead of a one-shot stale-cache assert. Device B now stamps the server's value onto Feeld, not its own old location. Co-Authored-By: Claude Opus 4.7 --- web/src/hooks/useLocation.tsx | 28 ++++++++++++++++------------ web/src/pages/Discover.tsx | 33 +++++++++++++++++++-------------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/web/src/hooks/useLocation.tsx b/web/src/hooks/useLocation.tsx index fca73f9..4ce0ca6 100755 --- a/web/src/hooks/useLocation.tsx +++ b/web/src/hooks/useLocation.tsx @@ -43,6 +43,10 @@ export interface LocationState { interface LocationContextType { location: LocationState | null; savedLocations: SavedLocation[]; + // True once the initial sync from the server (the source of truth) has completed. + // Consumers that push the location onward (e.g. Discover → Feeld) should wait for this + // so they assert the server's authoritative value, not this device's stale cache. + locationReady: boolean; // Throws BannedCountryError if the LocationState has a banned country/countryCode. setLocation: (location: LocationState | null) => void; // Throws BannedCountryError if the country is on the Feeld-restricted list. @@ -106,18 +110,17 @@ export function LocationProvider({ children }: { children: ReactNode }) { }); } - // Restore current location if we don't have one - const cleanCurrent = sanitizeCurrent(serverData.currentLocation); - if (cleanCurrent && !location) { - setLocationState(cleanCurrent); - localStorage.setItem(CURRENT_LOCATION_KEY, JSON.stringify(cleanCurrent)); - } - - // Restore custom location if we don't have one - const cleanCustom = sanitizeCurrent(serverData.customLocation); - if (cleanCustom && !location) { - setLocationState(cleanCustom); - localStorage.setItem(CURRENT_LOCATION_KEY, JSON.stringify(cleanCustom)); + // The server is the single source of truth for the active location. + // Adopt whatever it currently holds (prefer currentLocation, fall back to + // customLocation), OVERRIDING this device's local cache — so two devices on + // the same account always converge to the same place instead of each + // re-asserting its own stale copy. Only keep local if the server has none. + const serverActive = + sanitizeCurrent(serverData.currentLocation) || + sanitizeCurrent(serverData.customLocation); + if (serverActive) { + setLocationState(serverActive); + localStorage.setItem(CURRENT_LOCATION_KEY, JSON.stringify(serverActive)); } } } catch (e) { @@ -199,6 +202,7 @@ export function LocationProvider({ children }: { children: ReactNode }) { value={{ location, savedLocations, + locationReady: hasSyncedFromServer, setLocation, saveLocation, deleteLocation, diff --git a/web/src/pages/Discover.tsx b/web/src/pages/Discover.tsx index 31d4b42..d474e0c 100755 --- a/web/src/pages/Discover.tsx +++ b/web/src/pages/Discover.tsx @@ -291,24 +291,29 @@ export function DiscoverPage() { }); const initialLoadDone = useRef(false); const savedLikedMeProfiles = useRef>(new Set()); - const { location } = useLocation(); + const { location, locationReady } = useLocation(); const { isFavorite, toggleFavorite } = useFavorites(); - // Set device location on Feeld when Discover page loads (emulates app open GPS) + // Push the location to Feeld whenever the authoritative location changes. + // Gate on `locationReady` so we wait for the server (the source of truth) to load + // first — otherwise this device would stamp its own stale localStorage location onto + // Feeld's single shared account-location slot, overriding what another device just set. + // Dedupe by coordinates so we don't re-push the same place on every re-render. const [updateDeviceLocation] = useMutation(DEVICE_LOCATION_UPDATE_MUTATION); - const locationSetRef = useRef(false); + const lastPushedRef = useRef(null); useEffect(() => { - if (location && !locationSetRef.current) { - locationSetRef.current = true; - updateDeviceLocation({ - variables: { input: { latitude: location.latitude, longitude: location.longitude } }, - }).then(() => { - console.log(`[Discover] Location set to ${location.name || `${location.latitude},${location.longitude}`}`); - }).catch((err) => { - console.error('[Discover] Failed to set location:', err); - }); - } - }, [location, updateDeviceLocation]); + if (!locationReady || !location) return; + const coordKey = `${location.latitude},${location.longitude}`; + if (lastPushedRef.current === coordKey) return; + lastPushedRef.current = coordKey; + updateDeviceLocation({ + variables: { input: { latitude: location.latitude, longitude: location.longitude } }, + }).then(() => { + console.log(`[Discover] Location set to ${location.name || coordKey}`); + }).catch((err) => { + console.error('[Discover] Failed to set location:', err); + }); + }, [locationReady, location, updateDeviceLocation]); // Fetch rotation location (what the cron actually set on Feeld) const [rotationLocation, setRotationLocation] = useState(null);