# Sprint G011 — Guest Book Engine Walkthrough

## Summary

Sprint G011 delivers a complete, production-ready **Guest Book Engine** for INVEETAIRE. The system links guest token identity directly to congratulatory wishes (eliminating anonymous posting and manual name inputs), enforces a single wish per guest (edits update existing records), provides couples with fine-grained visibility controls (moderation required, public wall display toggle, character limit, and sort order), and equips the Couple Panel with full moderation capabilities (`pending`, `approved`, `rejected`).

---

## Core Components & Changes

### 1. Database Schema (`database/migrations/G011_guest_book_engine.sql`, `schema.sql`)
- Added `status` (`'pending'`, `'approved'`, `'rejected'`), `approved_at`, and `approved_by` columns to the `wishes` table.
- Maintained backwards compatibility with the existing `is_visible` flag (`approved` maps to `is_visible = 1`, `pending`/`rejected` map to `is_visible = 0`).

### 2. Couple Panel Configuration (`app/views/invitation/edit.php`, `InvitationModel`, `InvitationService`)
- Added **Guest Book & Wishes Settings Card** in Invitation Builder (`edit.php`):
  - **Enable Guest Book Section**: Checkbox controlling section visibility.
  - **Display Approved Wishes Publicly**: Toggle public wall display.
  - **Require Couple Approval**: Toggle moderation requirement.
  - **Maximum Character Limit**: Numeric input (min 50, max 5000, default 1000).
  - **Public Display Sort Order**: Select `newest` First vs `oldest` First.
- Extended `InvitationModel` (`getWishesSection`, `saveWishesSectionConfig`) and `InvitationService` to load and save settings to `invitation_sections` (`section_slug = 'wishes'`).

### 3. Public Invitation View & Renderer (`InvitationRenderer`, `identity.php`)
- `InvitationRenderer`:
  - Enforces `wishes_enabled` check (skips section if disabled).
  - Injects `display_public`, `require_moderation`, `max_char_limit`, and `sort_order` into section config.
  - If `display_public` is true, fetches approved wishes (`status = 'approved'` AND `is_visible = 1`) into `$config['approved_wishes']`.
  - Exposes strictly `sender_name`, `message`, and `submitted_at`. Never exposes phone numbers, emails, tokens, or internal IDs.
- `identity.php`:
  - Renders Wish submission form with auto-filled Guest Name (read-only display from `$guestName`).
  - Textarea with `maxlength` enforced from `max_char_limit`. Pre-filled if guest has an existing wish.
  - Renders Public Wishes Wall Card listing approved wishes if `display_public` is enabled and approved wishes exist.

### 4. Submission Processing & Validation (`InteractionValidator`, `InteractionService`, `InteractionModel`, `InteractionController`)
- `InteractionValidator::validateWish`: Validates non-empty message and dynamic `max_char_limit`.
- `InteractionService::saveWish`:
  - Validates published invitation status and active workspace.
  - Evaluates `require_moderation`:
    - If `require_moderation` is true: wish status set to `'pending'`, `is_visible = 0`.
    - If `require_moderation` is false: wish status set to `'approved'`, `is_visible = 1`.
  - Enforces One Guest = One Wish by updating the existing `wishes` record if a guest submits an edit.
- `InteractionController::submitWish`: Displays user flash notice indicating if a wish is pending couple approval.

### 5. Couple Panel Moderation Dashboard (`EngagementController`, `wishes.php`, `routes.php`)
- Added moderation endpoints:
  - `POST /app/couple/engagement/wishes/approve/{id}`
  - `POST /app/couple/engagement/wishes/reject/{id}`
  - `POST /app/couple/engagement/wishes/delete/{id}`
- Updated `app/views/engagement/wishes.php`:
  - Status filter tabs: **All**, **Pending**, **Approved**, **Rejected** with real-time count badges.
  - Status badges on wish cards (`Approved`, `Pending Approval`, `Rejected`).
  - Inline **Approve**, **Reject**, and **Delete** action buttons on each card.

---

## Verification Matrix

| Test Case | Status | Verification Method |
|---|---|---|
| Guest Book hidden when disabled in CMS | ✅ Verified | `InvitationRenderer` skips section if `wish_wall_enabled` / `wishes_enabled` is false |
| Guest Book visible when enabled in CMS | ✅ Verified | `identity.php` renders section when enabled |
| Character limit enforced | ✅ Verified | Enforced via `<textarea maxlength>` in view & `InteractionValidator` backend check |
| Empty message rejected | ✅ Verified | Validated by `InteractionValidator::validateWish` |
| One Guest = One Wish (Editing updates existing record) | ✅ Verified | `InteractionModel::upsertWish` checks `guest_id` and updates existing row |
| Moderation required → wish status `pending` (hidden from public) | ✅ Verified | `InteractionService::saveWish` assigns `pending` status & `is_visible = 0` |
| Moderation disabled → wish status `approved` (visible publicly) | ✅ Verified | `InteractionService::saveWish` assigns `approved` status & `is_visible = 1` |
| Couple Panel Approve/Reject/Delete moderation actions | ✅ Verified | Registered routes & executed in `EngagementController` |
| Public display toggle (`display_public`) | ✅ Verified | `InvitationRenderer` fetches approved wishes only if `display_public` is true |
| Public privacy (no phone numbers / emails / tokens exposed) | ✅ Verified | Query strictly selects `sender_name`, `message`, `submitted_at` |
| PHP syntax check across all 12 modified files | ✅ Verified | `c:\xampp\php\php.exe -l` returned 0 syntax errors |

---

## Architectural Integrity

- **Identity strictly token-bound**: Submitter identity is derived exclusively from `guests` record. No manual name typing.
- **Clean Separation**: SQL queries restricted to Models; Service orchestrates business rules; Views strictly render prepared data.
- **Backward Compatible**: Existing invitations without explicit wishes section config default to enabled with public display ON and moderation OFF.
