# Sprint G009 — Walkthrough: Couple Panel Integration & Content Binding

## Summary

Full audit of all Couple Panel configurable fields vs. Public Invitation rendering. Found and resolved **5 issues** across 2 files.

---

## Changes Made

### [`InvitationRenderer.php`](file:///c:/xampp/htdocs/inveetaire/app/modules/PublicInvitation/InvitationRenderer.php)

**The architectural fix.** The `media` section is the single store for all uploaded file paths (hero, gallery, story, music, etc.). But the gallery and story sections have their own `invitation_sections` DB rows with empty configs. The renderer now bridges the gap:

1. **Pre-scan** — Before iterating visible sections, the renderer does a first pass to extract the `media` section's JSON metadata.
2. **Gallery bridge** — When processing the `gallery` section, injects `$mediaMeta['gallery']` (the array of relative paths) as `$config['images']`. If the array is empty, the section is hidden.
3. **Story bridge** — When processing the `story` section, injects `$mediaMeta['story']` as `$config['story_images']`. Empty array is gracefully handled.
4. **Media section excluded** — The `media` section row itself is always skipped from visible sections (it has no template in identity.php).

---

### [`identity.php`](file:///c:/xampp/htdocs/inveetaire/app/views/public/identity.php)

**4 rendering fixes:**

| Fix | Section | Change |
|---|---|---|
| Fix 1 | Hero | `cover_image` path → `url('/i/media/serve') . '?path=' . urlencode($path)` |
| Fix 2 | Gallery | Each path in `$config['images']` → serve URL via `array_map` |
| Fix 3 | Story | New: reads `$config['story_images']`, builds serve URLs, renders a photo grid below the narrative |
| Fix 4 | Wishes | Removed dead `\|\| $slug === 'wish'` check (canonical slug is `wishes`) |

---

## Root Cause Analysis

The two critical bugs (hero cover, gallery images) had the same root cause: **storage paths are not web-accessible**. The `storage/` directory is intentionally protected. All files must be served via `GET /i/media/serve?path=...` which performs ownership validation before streaming. Using raw paths as `<img src="">` always resulted in broken images.

The gallery architecture gap was a second separate issue: the Media Engine stores all uploaded paths in a single `media` section row (by design — clean separation), but the renderer needed to bridge that data into the per-section configs that identity.php expects.

---

## Verification Checklist

Test these manually after the fix:

- [ ] Upload a hero cover image via Media Manager → hero background renders correctly on public invitation
- [ ] Upload gallery images → gallery section appears with correct images on public invitation
- [ ] Upload story images → story section renders both text narrative AND images
- [ ] Empty gallery (no uploads) → gallery section hidden correctly
- [ ] Story with only text (no images) → story section renders text only, no broken image grid
- [ ] Music player still functions normally (unchanged)
- [ ] RSVP form still submits and shows status (unchanged)
- [ ] Wishes form still submits and shows status (unchanged)
- [ ] Gift section shows/hides based on bank details (unchanged)
- [ ] QR Code renders correctly (unchanged)
- [ ] Events section shows venue, date, time, maps link (unchanged)

---

## Architecture Invariants Preserved

- ✅ Couple Panel remains the single source of truth
- ✅ Public Invitation has no hardcoded content
- ✅ No new DB tables introduced
- ✅ Backward compatible — existing invitations without media uploads will gracefully hide those sections
- ✅ `InvitationRenderer` remains SQL-free and HTML-free
- ✅ `identity.php` remains reactive — no business logic added, only URL-building helpers
