feat(spots+feed): tag spots in posts + any-user photo contributions - #6
Merged
Merged
Conversation
Feed spot-tagging: - Compose (media/upload.tsx): optional "Tag a spot" picker (debounced search) → sends spotId on the post. Posting without a spot is unchanged. - The feed list and single-post views show a tappable spot chip that opens the spot. (Backend now enriches post.spot on lists too.) Spot photo contributions (Google-Maps model): - Any logged-in user can add photos to any spot from the detail screen (gallery multi-select or camera) → uploads then refreshes the gallery. - Per-photo actions on user photos: Report (any user except the uploader) and Delete (uploader/admin). Google photos aren't reportable/deletable. - Photos are public immediately; the backend auto-hides one after 3 distinct reports (App-Store-compliant UGC moderation). API (src/lib/api): reportSpotPhoto / deleteSpotPhoto (photo key is encodeURIComponent'd — the S3 key contains a "/", verified against prod route matching); spotId on CreatePostData + spot on FeedPost. Fixes from code review: encoded photo-key (report/delete were 404-ing); gallery no longer double-renders the header photo; can't report your own photo; refresh after report so an auto-hidden photo disappears. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the hard crash on the spots map: NSInternalInconsistencyException: Inconsistency between local and UIKit touch registries (RCTSurfaceTouchHandler, New Architecture / iOS 26) Root cause (RN #53303): the projected-overlay marker Pressables were UNMOUNTED mid-gesture — projectToScreen returned null for off-screen points and the whole marker tree re-rendered on every onRegionChange frame — so a marker with a live UIKit touch disappeared and desynced Fabric's touch registry. "A lot of data" (many markers crossing the cull boundary) made it near-certain. Fixes: - projectToScreenXY: never returns null for off-screen points; markers stay MOUNTED and are hidden via opacity + pointerEvents instead of unmounting. - Stable, position-derived cluster keys (supercluster's cluster_id is reassigned across queries → key churn → remount). - Marker onPress defers its animateToRegion to the next frame so re-projection can't move/unmount the pressed marker while the touch is finalizing. - Safety net: patch-package neutralizes the fatal RCTAssert in RCTSurfaceTouchHandler (the guard right below it already skips the stray touch) — turns any residual desync into a no-op instead of a crash. Deep on-device testing still required (see PR notes): iOS 26 physical device, dense data, aggressive pan/pinch + tap-during-gesture. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Remove the +/- zoom controls (pinch-to-zoom covers it) and put a search (magnifying-glass) button in their place. - Tapping it opens a full-screen search: as you type it queries Google Places (biased to the current viewport) AND our spots in parallel, grouped as "Spots" and "Places". Tap a spot to fly there and open its card; tap a place to fly the map to that location. Reachable in fullscreen where the top search bar is hidden. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The map view now has its own Google-Maps-style in-map search (the magnifying-glass control), so the search bar above the content is redundant there. Gate it on viewMode === 'list' so it only appears in the list view. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Previously the bottom of the map always showed a horizontal preview of up to 5 spots when nothing was selected. Now the bottom card is hidden by default and appears only when you tap a pin; tapping elsewhere on the map clears the selection and hides it, and tapping another pin swaps it in. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
wbaxterh
added a commit
that referenced
this pull request
Jul 14, 2026
#5) * feat(spots): photos on add, one-tap save, flat My Spots, immersive map Mobile Spots UX overhaul (backend support already deployed). Add Spot (add.tsx): - Add photos from the gallery (multi-select) or camera before saving. On submit: createSpot -> upload each photo -> set the first as the main image. Photo upload failures are surfaced; if the spot deduped to one already owned by someone else, it's saved to My Spots and the user's photos are NOT attached to the foreign spot. My Spots (index.tsx): - Now a FLAT list of spots you CREATED ("Mine") + spots you SAVED ("Saved"), merged and de-duped (Mine wins). Named Collections preserved behind a secondary toggle. - Map selected-spot card: one-tap bookmark save/unsave; long-press opens the named-list picker (AddToSpotListModal). Saved state is seeded once from the server (getSavedSpots) and reconciled on focus — fixes the optimistic-unsave bounce and un-seeded preview cards. - Fullscreen map is now immersive: header/tabs/search/pills hide, controls (incl. a filter button) and the selected card stay, safe-area respected. Spot detail ([spotId].tsx): - Bookmark wired: tap = save/unsave to My Spots, long-press = named lists. - Photo gallery (userPhotos + googlePhotos) via expo-image. - Owner-only Edit (inline) + Delete. API (src/lib/api/spots.ts): - saveSpot / unsaveSpot / getSavedSpots / isSpotSaved / deleteSpot / uploadSpotPhoto (multipart via expo-file-system). No <Marker> added (New-Arch crash constraint respected). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(spots+feed): tag spots in posts + any-user photo contributions (#6) * feat(spots+feed): tag a spot in posts, any-user photo contributions Feed spot-tagging: - Compose (media/upload.tsx): optional "Tag a spot" picker (debounced search) → sends spotId on the post. Posting without a spot is unchanged. - The feed list and single-post views show a tappable spot chip that opens the spot. (Backend now enriches post.spot on lists too.) Spot photo contributions (Google-Maps model): - Any logged-in user can add photos to any spot from the detail screen (gallery multi-select or camera) → uploads then refreshes the gallery. - Per-photo actions on user photos: Report (any user except the uploader) and Delete (uploader/admin). Google photos aren't reportable/deletable. - Photos are public immediately; the backend auto-hides one after 3 distinct reports (App-Store-compliant UGC moderation). API (src/lib/api): reportSpotPhoto / deleteSpotPhoto (photo key is encodeURIComponent'd — the S3 key contains a "/", verified against prod route matching); spotId on CreatePostData + spot on FeedPost. Fixes from code review: encoded photo-key (report/delete were 404-ing); gallery no longer double-renders the header photo; can't report your own photo; refresh after report so an auto-hidden photo disappears. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(spots map): stop the Fabric touch-registry crash + steadier markers Fixes the hard crash on the spots map: NSInternalInconsistencyException: Inconsistency between local and UIKit touch registries (RCTSurfaceTouchHandler, New Architecture / iOS 26) Root cause (RN #53303): the projected-overlay marker Pressables were UNMOUNTED mid-gesture — projectToScreen returned null for off-screen points and the whole marker tree re-rendered on every onRegionChange frame — so a marker with a live UIKit touch disappeared and desynced Fabric's touch registry. "A lot of data" (many markers crossing the cull boundary) made it near-certain. Fixes: - projectToScreenXY: never returns null for off-screen points; markers stay MOUNTED and are hidden via opacity + pointerEvents instead of unmounting. - Stable, position-derived cluster keys (supercluster's cluster_id is reassigned across queries → key churn → remount). - Marker onPress defers its animateToRegion to the next frame so re-projection can't move/unmount the pressed marker while the touch is finalizing. - Safety net: patch-package neutralizes the fatal RCTAssert in RCTSurfaceTouchHandler (the guard right below it already skips the stray touch) — turns any residual desync into a no-op instead of a crash. Deep on-device testing still required (see PR notes): iOS 26 physical device, dense data, aggressive pan/pinch + tap-during-gesture. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(spots map): Google-Maps-style search, drop the +/- zoom buttons - Remove the +/- zoom controls (pinch-to-zoom covers it) and put a search (magnifying-glass) button in their place. - Tapping it opens a full-screen search: as you type it queries Google Places (biased to the current viewport) AND our spots in parallel, grouped as "Spots" and "Places". Tap a spot to fly there and open its card; tap a place to fly the map to that location. Reachable in fullscreen where the top search bar is hidden. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(spots): show the top search bar in list view only The map view now has its own Google-Maps-style in-map search (the magnifying-glass control), so the search bar above the content is redundant there. Gate it on viewMode === 'list' so it only appears in the list view. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(spots map): show the bottom spot card only when a pin is selected Previously the bottom of the map always showed a horizontal preview of up to 5 spots when nothing was selected. Now the bottom card is hidden by default and appears only when you tap a pin; tapping elsewhere on the map clears the selection and hides it, and tapping another pin swaps it in. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Adds two things you asked for: tagging a spot on a feed post and letting any user contribute photos to a spot (the Google Maps model). Backend support is already deployed to prod.
How Google Maps does it (and what we do)
On Google Maps any signed-in user can add photos to any place — the owner doesn't approve them; photos are attributed, appear immediately, and are moderated by reporting. We match that: any logged-in user adds photos to any spot, attributed to them, public immediately, with a Report action that auto-hides a photo after 3 distinct reports (uploader/admin can also delete).
Feed spot-tagging
media/upload.tsx): optional "Tag a spot" picker with debounced search. A post without a spot works exactly as before.populatePostUsersnow enrichespost.spoton lists (not just the single-post GET), so the chip appears in the feed itself.Spot photo contributions
Environment Variables
None.
Technical Impact
POST /spots/:id/photos/:photoKey/report+ auto-hide, hidden-photo filtering, and feed-listpost.spotenrichment.expo-image-picker, already installed with permissions)./; the clientencodeURIComponents it — verified against the live nginx+Express stack (encoded key → 401 auth, raw key → 404).react-native-maps<Marker>added.Testing Evidence
tsc --noEmit0 errors ·biome check .0 errors ·check-prod-readypass.Deployment Notes
Merge after #5. Recommend a dev-client/EAS build to exercise the picker + report/delete on-device.
Follow-ups (not blocking)
SpotChipcomponent (currently duplicated across screens).