Skip to content

feat(spots+feed): tag spots in posts + any-user photo contributions - #6

Merged
wbaxterh merged 5 commits into
feature/spots-ux-overhaulfrom
feature/spots-feed-photos
Jul 14, 2026
Merged

feat(spots+feed): tag spots in posts + any-user photo contributions#6
wbaxterh merged 5 commits into
feature/spots-ux-overhaulfrom
feature/spots-feed-photos

Conversation

@wbaxterh

Copy link
Copy Markdown
Owner

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.

Stacked on #5 (feature/spots-ux-overhaul) — it builds on that PR's uploadSpotPhoto + spot-detail gallery. Merge #5 first; GitHub will retarget this to v2-rebuild.

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

  • Compose (media/upload.tsx): optional "Tag a spot" picker with debounced search. A post without a spot works exactly as before.
  • The feed list and single-post views show a tappable spot chip → opens the spot. Backend populatePostUsers now enriches post.spot on lists (not just the single-post GET), so the chip appears in the feed itself.

Spot photo contributions

  • Add Photo on the spot detail for any logged-in user (gallery multi-select or camera); refreshes the gallery after upload.
  • Per-photo menu on user photos: Report (anyone but the uploader) and Delete (uploader/admin). Google photos aren't reportable/deletable.

Environment Variables

None.

Technical Impact

  • Backend (already deployed): POST /spots/:id/photos/:photoKey/report + auto-hide, hidden-photo filtering, and feed-list post.spot enrichment.
  • No new native modules (reuses expo-image-picker, already installed with permissions).
  • The S3 photo key contains a /; the client encodeURIComponents it — verified against the live nginx+Express stack (encoded key → 401 auth, raw key → 404).
  • No react-native-maps <Marker> added.

Testing Evidence

  • tsc --noEmit 0 errors · biome check . 0 errors · check-prod-ready pass.
  • High-effort multi-agent code review; the confirmed bugs it found were fixed here (404-ing photo key, gallery double-render, self-report, refresh-after-report).
  • Not device-tested yet — needs a run on hardware (camera/gallery, feed compose, report/delete).

Deployment Notes

Merge after #5. Recommend a dev-client/EAS build to exercise the picker + report/delete on-device.

Follow-ups (not blocking)

  • DRY: shared photo-picker + debounce hook + a SpotChip component (currently duplicated across screens).
  • Optional: show photo attribution ("added by @user") in the gallery.

wbaxterh and others added 5 commits July 11, 2026 16:57
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
wbaxterh merged commit 5d8f68c into feature/spots-ux-overhaul Jul 14, 2026
1 check passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant