Skip to content

fix: Multi-image mosaic positioning and POI type unification#185

Merged
fatherlinux merged 7 commits into
masterfrom
fix/multi-image-mosaic-positioning
Apr 5, 2026
Merged

fix: Multi-image mosaic positioning and POI type unification#185
fatherlinux merged 7 commits into
masterfrom
fix/multi-image-mosaic-positioning

Conversation

@fatherlinux
Copy link
Copy Markdown
Member

Summary

Fixes critical bugs introduced in PR #182 where the multi-image mosaic was rendering in the wrong location and media handling wasn't unified across POI types.

Fixes from PR #182:

  • Mosaic was appearing inside the Info tab instead of at the top of the sidebar
  • Linear features had no media support (only showed default thumbnail)
  • Virtual POIs had different media handling code path
  • Pending badges weren't updating after approval
  • Moderation count wasn't updating after upload
  • Primary image indicators were missing
  • Button positioning was inconsistent
  • Caption length limited to 200 characters

Changes

Core Fixes

  • Mosaic Position: Moved from ReadOnlyView (Info tab) to Sidebar top-level (between header and tabs)
  • POI Type Unification: All POI types (destination, linear feature, virtual) now use identical media handling code
  • Media State: Centralized at Sidebar component level, removed duplicate state in ReadOnlyView
  • Empty State: Added "+ Add Photo/Video" button when no media exists

UI Improvements

  • Primary image indicators: grey star in mosaic, gold badge in lightbox
  • Consistent button positioning and sizing (48px height, 180px width)
  • Delete button: bottom-left, Add/Set Primary: bottom-right
  • Proper spacing between stacked buttons (60px gap for set-primary above add)

Event System

  • poi-media-updated: Refreshes media in sidebar when approved/uploaded
  • poi-updated: Refreshes map markers when primary image changes
  • moderation-count-changed: Updates badge count on upload
  • Fixed stale closure issues by inlining refresh logic in event handlers

Database

  • Migration 017: Increased caption length from 200 to 2000 characters

Bug Fixes

  • Lightbox now stays on same image when setting it as primary (was jumping to different index)
  • Moderation queue now shows poi_media items (was querying old photo_submissions table)
  • Fixed navigation when deleting media (moves to next/prev instead of closing)
  • Fixed has_primary_image flag synchronization on delete/set-primary

Related Issues

Test Plan

  • ✅ Tested with destinations, linear features, and virtual POIs
  • ✅ Verified mosaic appears at top of sidebar for all types
  • ✅ Verified empty state with upload button
  • ✅ Verified primary image indicators display correctly
  • ✅ Verified event-driven updates work (pending badges, count, map markers)
  • ✅ Verified lightbox navigation and delete functionality
  • ✅ Verified caption length increase (2000 chars)

Technical Debt

This PR unifies the media handling across POI types but does not address the root architectural issue (3800+ line Sidebar with duplicate code paths). Issue #184 created to track proper inheritance/composition refactor.

Deployment Notes

  • Must apply migration 017 to production database
  • No breaking changes to existing media
  • Event system is backward compatible

🤖 Generated with Claude Code

Fixes critical issues introduced in PR #182 where the multi-image mosaic
was incorrectly rendering inside the Info tab instead of at the top of
the sidebar, and media handling was not unified across POI types.

**Fixed Issues:**
- Mosaic now renders at sidebar top (between header and tabs) for all POIs
- Removed duplicate media rendering from ReadOnlyView component
- Unified media handling: destinations, linear features, and virtual POIs
  now use identical Mosaic/Lightbox code path
- Added primary image indicators (grey star in mosaic, gold badge in lightbox)
- Fixed lightbox navigation when setting primary image (stays on same image)
- Added event emission for map marker thumbnail updates
- Fixed pending badge async updates with event-driven system
- Fixed moderation count badge updates on upload
- Standardized button positioning (delete: bottom-left, add/set-primary: bottom-right)
- Increased caption length limit from 200 to 2000 characters (migration 017)
- Fixed moderation queue to show poi_media table items
- Fixed event listener thrashing with stable dependencies

**Database:**
- Migration 017: Increase poi_media.caption length to 2000 chars

**Technical Changes:**
- Removed showImage parameter from ReadOnlyView/EditView
- Media state management now at Sidebar component level only
- Linear features now use same Mosaic component as destinations
- Event system: poi-media-updated, poi-updated, moderation-count-changed
- Inline refresh logic in event handlers to prevent stale closures

**Related:**
- Addresses issues discovered during PR #182 review
- Creates issue #184 for future POI type unification refactor

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a robust media moderation workflow and a dual-strategy Google OAuth implementation to handle conditional Google Drive access for administrators. Key updates include the addition of media management endpoints (delete, set-primary), the integration of photo submissions into the moderation service, and a refactored frontend featuring a mosaic gallery and an enhanced lightbox with administrative actions. Feedback highlights critical security and stability improvements: specifically, the removal of a hardcoded admin email in favor of environment variables, implementing error handling for JSON parsing of OAuth credentials, and masking internal error details in API responses. Furthermore, it is advised to wrap media deletion logic in a database transaction to maintain referential integrity and to re-enable React.StrictMode to ensure frontend code quality.

I am having trouble creating individual review comments. Click here to see my feedback.

backend/routes/auth.js (7)

security-high high

Hardcoding a personal email address as a fallback for administrative access is a security risk and violates the repository's configuration standards. This should be strictly loaded from environment variables without a default that points to a specific user. Additionally, relying on email strings for authorization is brittle; consider using the role field from the user record if available.

const ADMIN_EMAIL = process.env.ADMIN_EMAIL || '';
References
  1. No hardcoded credentials — use environment files or mounted secrets (link)

backend/routes/auth.js (23-24)

medium

Parsing oauth_credentials without a try-catch block is risky. If the database contains an invalid JSON string or an empty string, JSON.parse will throw an exception and crash the request handler. Consider wrapping this in a safe utility function or a try-catch block to ensure application stability.

backend/routes/admin.js (3935)

security-medium medium

Exposing error.message directly to the client in a 500 response can leak sensitive information about the backend implementation or database structure. It is safer to log the detailed error on the server and return a generic message to the user.

      res.status(500).json({ error: 'Failed to save edits' });

backend/server.js (1273-1299)

medium

The database operations for deleting a media record and updating the POI's has_primary_image flag are not wrapped in a transaction. If the second query fails after the deletion, the POI's state will be inconsistent with its actual media content. Consider using BEGIN and COMMIT to ensure atomicity, similar to the implementation in the set-primary route.

frontend/src/main.jsx (8-12)

medium

The removal of React.StrictMode reduces the ability of the development environment to catch potential issues like side effects or deprecated API usage. Unless there is a specific technical reason for its removal (such as a conflict with a third-party library), it is recommended to keep the application wrapped in StrictMode.

The image navigation buttons (grey chevrons on mobile) were accidentally
removed when unifying media handling across POI types. These buttons allow
users to navigate between POIs in the list on mobile devices.

Fixes failing UI tests:
- should navigate POIs using grey chevron buttons
- should prevent double navigation on rapid button clicks
Removed redundant comments that restate what the code obviously does.
Gourmand correctly flagged these as adding no information beyond the code itself.
Deleted 12 AI-generated status report files that Gourmand correctly identified
as providing zero value. These files clutter the repository root and duplicate
information already available in git history and the issue tracker.

Per Gourmand guidance:
- Progress history → git commit messages
- Current status → issue tracker
- Documentation → README.md and docs/

Files removed:
- DEPLOYMENT_GUIDE.md
- DEPLOYMENT_VERIFICATION_CHECKLIST.md
- EXEC_SUMMARY.md
- HANDOFF_SUMMARY.md
- NEXT_STEPS.md
- PACKAGE_STRUCTURE.md
- PROD_FIX_QUICKREF.md
- PROD_ISSUE_FLOWCHART.md
- PROD_TROUBLESHOOT.md
- PRODUCTION_INCIDENT_README.md
- README_PRODUCTION.md
- TROUBLESHOOTING_PACKAGE_INDEX.md
Fixes all 5 issues identified by Gemini code review:

1. Remove hardcoded admin email fallback (security)
   - Changed ADMIN_EMAIL fallback from personal email to empty string
   - Forces proper environment variable configuration

2. Add try-catch for JSON.parse of oauth_credentials (stability)
   - Prevents crash on invalid JSON in database
   - Logs error and continues with null credentials

3. Remove internal error details from 500 responses (security)
   - Prevents leaking implementation details to clients
   - Still logs full error server-side for debugging

4. Wrap media delete in transaction (data integrity)
   - Uses BEGIN/COMMIT/ROLLBACK like set-primary endpoint
   - Ensures has_primary_image flag stays consistent with media state
   - Prevents orphaned POI state if UPDATE fails after DELETE

5. Re-enable React.StrictMode (code quality)
   - Restores development-time checks for side effects
   - Helps catch deprecated API usage and other issues
Addresses Gatehouse advisory about image server deletion happening after
DB commit. Now returns honest status when partial success occurs:

- 200 OK: Both database and image server deletion succeeded
- 202 Accepted: Database updated, image server cleanup pending

Changes:
- Track image server deletion success/failure
- Return 202 with warning when image server delete fails
- Log orphaned asset IDs for manual cleanup
- Add TODO comment for background cleanup job

This accepts eventual consistency as a design decision:
- Database (source of truth) is always consistent
- Image server failures don't block user operations
- Orphaned images can be cleaned up later (manual or automated)
- Clients are informed about partial success via 202 status
Replaced TODO comment with reference to issue #186 for background cleanup job.

Gourmand doesn't allow TODOs - they must be either implemented immediately,
tracked in a proper issue, or deleted. Since the background cleanup job is
out of scope for this PR, created issue #186 to track it properly.
@fatherlinux fatherlinux merged commit 23ccf34 into master Apr 5, 2026
3 checks passed
@fatherlinux fatherlinux deleted the fix/multi-image-mosaic-positioning branch April 5, 2026 05:23
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