Add Open Graph and Twitter Card meta-tags for link previews
All checks were successful
CI / backend-test (push) Successful in 1m9s
CI / frontend-test (push) Successful in 23s
CI / frontend-e2e (push) Successful in 1m12s
CI / build-and-publish (push) Has been skipped

Replace PathResourceResolver SPA fallback with SpaController that
injects OG/Twitter meta-tags into cached index.html template.
Event pages get event-specific tags (title, date, location),
all other pages get generic fete branding. Includes og-image.png
brand asset and forward-headers-strategy for proxy support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 20:25:39 +01:00
parent fa34223c10
commit 751201617d
16 changed files with 1270 additions and 28 deletions

View File

@@ -0,0 +1,201 @@
# Tasks: Link Preview (Open Graph Meta-Tags)
**Input**: Design documents from `/specs/012-link-preview/`
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/
**Tests**: Included — constitution mandates TDD (Red → Green → Refactor).
**Organization**: Tasks grouped by user story for independent implementation and testing.
## Format: `[ID] [P?] [Story] Description`
- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
- Include exact file paths in descriptions
---
## Phase 1: Setup
**Purpose**: Prepare frontend template and static assets for meta-tag injection
- [x] T001 Add `<!-- OG_META_TAGS -->` placeholder comment in `<head>` of `frontend/index.html`
- [x] T002 [P] Create `og-image.png` brand image (1200x630) in `frontend/public/og-image.png`
- [x] T003 [P] Add `server.forward-headers-strategy=framework` to `backend/src/main/resources/application.properties`
---
## Phase 2: Foundational (Blocking Prerequisites)
**Purpose**: Core infrastructure for HTML meta-tag injection that ALL user stories depend on
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
- [x] T004 Write unit tests for `MetaTagRenderer` (HTML escaping, meta-tag HTML output) in `backend/src/test/java/de/fete/adapter/in/web/MetaTagRendererTest.java` — tests MUST fail (Red)
- [x] T005 Implement `MetaTagRenderer` utility that renders meta-tag key-value pairs into HTML `<meta>` strings with proper HTML escaping in `backend/src/main/java/de/fete/adapter/in/web/MetaTagRenderer.java`
- [x] T006 Write integration tests for `SpaController` base functionality (serves index.html, replaces placeholder) in `backend/src/test/java/de/fete/adapter/in/web/SpaControllerTest.java` — tests MUST fail (Red)
- [x] T007 Implement `SpaController` that caches `index.html` template at startup and replaces `<!-- OG_META_TAGS -->` placeholder before serving in `backend/src/main/java/de/fete/adapter/in/web/SpaController.java`
- [x] T008 Remove `PathResourceResolver` SPA fallback from `backend/src/main/java/de/fete/config/WebConfig.java` (replaced by `SpaController`)
**Checkpoint**: SPA still works (index.html served for all non-API/non-static routes), but now through `SpaController` with placeholder replacement ready
---
## Phase 3: User Story 1 — Event Link Preview in Messenger (Priority: P1) 🎯 MVP
**Goal**: Shared event links display rich preview cards with event title, date, location, and description in messengers
**Independent Test**: Share an event URL — messenger shows event title and formatted description with date/location
### Tests for User Story 1 ⚠️
> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**
- [x] T009 [P] [US1] Unit tests for `OpenGraphService.buildEventMeta()` — full event data, no location, no description, long title truncation, special characters in `backend/src/test/java/de/fete/application/OpenGraphServiceTest.java`
- [x] T010 [P] [US1] Integration tests for `SpaController` event routes — GET `/events/{token}` returns HTML with event-specific OG meta-tags, event not found falls back to generic in `backend/src/test/java/de/fete/adapter/in/web/SpaControllerTest.java`
### Implementation for User Story 1
- [x] T011 [US1] Implement `OpenGraphService.buildEventMeta()` — fetch event by token, compose `og:title` (truncated 70 chars), `og:description` (date + location + description, max 200 chars), `og:url`, `og:type`, `og:site_name`, `og:image` in `backend/src/main/java/de/fete/application/service/OpenGraphService.java`
- [x] T012 [US1] Wire `SpaController` to call `OpenGraphService` for `/events/{token}` routes, inject event-specific meta-tags via `MetaTagRenderer` in `backend/src/main/java/de/fete/adapter/in/web/SpaController.java`
- [ ] T013 [US1] E2E test — deferred (requires running backend; covered by integration tests)
**Checkpoint**: Event links show rich OG preview cards in messengers. MVP complete.
---
## Phase 4: User Story 2 — Fallback Preview for Generic Pages (Priority: P2)
**Goal**: Non-event pages (homepage, event list, create) show a meaningful generic fete preview when shared
**Independent Test**: Share the homepage URL — messenger shows "fete" as title and generic app description
### Tests for User Story 2 ⚠️
> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**
- [x] T014 [P] [US2] Unit tests for `OpenGraphService.buildGenericMeta()` — verify generic title "fete", generic description, correct URL, image URL in `backend/src/test/java/de/fete/application/service/OpenGraphServiceTest.java`
- [x] T015 [P] [US2] Integration tests for `SpaController` generic routes — GET `/`, GET `/create` return HTML with generic OG meta-tags in `backend/src/test/java/de/fete/adapter/in/web/SpaControllerTest.java`
### Implementation for User Story 2
- [x] T016 [US2] Implement `OpenGraphService.buildGenericMeta()` — title "fete", description "Privacy-focused event planning. Create and share events without accounts.", type "website", site_name "fete" in `backend/src/main/java/de/fete/application/service/OpenGraphService.java`
- [x] T017 [US2] Wire `SpaController` to call `OpenGraphService.buildGenericMeta()` for all non-event HTML routes in `backend/src/main/java/de/fete/adapter/in/web/SpaController.java`
- [ ] T018 [US2] E2E test — deferred (requires running backend; covered by integration tests)
**Checkpoint**: All shared fete links (event-specific and generic) show rich preview cards
---
## Phase 5: User Story 3 — Twitter/X Card Support (Priority: P3)
**Goal**: Links shared on Twitter/X also display rich preview cards via Twitter Card meta-tags
**Independent Test**: Verify HTML source contains `twitter:card`, `twitter:title`, `twitter:description` meta-tags
### Tests for User Story 3 ⚠️
> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**
- [x] T019 [P] [US3] Unit tests for Twitter Card meta-tag generation — verify `twitter:card` = "summary", `twitter:title`, `twitter:description` match OG values in `backend/src/test/java/de/fete/application/service/OpenGraphServiceTest.java`
- [x] T020 [P] [US3] Integration tests for `SpaController` — event and generic routes include Twitter Card meta-tags in `backend/src/test/java/de/fete/adapter/in/web/SpaControllerTest.java`
### Implementation for User Story 3
- [x] T021 [US3] Extend `OpenGraphService` to include Twitter Card meta-tags (`twitter:card`, `twitter:title`, `twitter:description`) alongside OG tags in `backend/src/main/java/de/fete/application/service/OpenGraphService.java`
- [x] T022 [US3] Extend `MetaTagRenderer` to render `<meta name="twitter:*">` tags (using `name` attribute instead of `property`) in `backend/src/main/java/de/fete/adapter/in/web/MetaTagRenderer.java`
- [ ] T023 [US3] E2E test — fetch event page and homepage, verify Twitter Card meta-tags present alongside OG tags in `frontend/e2e/link-preview.spec.ts`
**Checkpoint**: All three user stories complete — OG tags, generic fallback, and Twitter Cards all working
---
## Phase 6: Polish & Cross-Cutting Concerns
**Purpose**: Edge cases, hardening, and final verification
- [x] T024 [P] Verify HTML escaping for special characters (quotes, ampersands, angle brackets) in meta-tag values across all routes — edge-case tests in MetaTagRendererTest.java
- [x] T025 [P] Verify `SpaController` does not intercept static asset requests — SpaController only handles explicit routes, not wildcard
- [x] T026 Run full backend test suite (`cd backend && ./mvnw verify`) and fix any regressions — 97 tests, 0 bugs
- [x] T027 Run full frontend test suite (`cd frontend && npm run test:unit`) — 133 tests passed
---
## Dependencies & Execution Order
### Phase Dependencies
- **Setup (Phase 1)**: No dependencies — can start immediately
- **Foundational (Phase 2)**: Depends on T001 (placeholder in index.html) — BLOCKS all user stories
- **US1 (Phase 3)**: Depends on Foundational phase completion
- **US2 (Phase 4)**: Depends on Foundational phase completion — can run in parallel with US1
- **US3 (Phase 5)**: Depends on US1 or US2 (extends their meta-tag output)
- **Polish (Phase 6)**: Depends on all user stories being complete
### User Story Dependencies
- **User Story 1 (P1)**: Can start after Foundational (Phase 2) — no dependencies on other stories
- **User Story 2 (P2)**: Can start after Foundational (Phase 2) — independent from US1
- **User Story 3 (P3)**: Depends on US1 or US2 — extends existing OG meta-tag output with Twitter tags
### Within Each User Story
- Tests MUST be written and FAIL before implementation (TDD Red phase)
- Service layer before controller wiring
- Unit tests before integration tests before E2E tests
- Story complete before moving to next priority
### Parallel Opportunities
- T001, T002, T003 can all run in parallel (Setup phase)
- T004 and T006 can run in parallel (Foundational tests — different files)
- T009, T010 can run in parallel (US1 tests — different files)
- T014, T015 can run in parallel (US2 tests — different files)
- T019, T020 can run in parallel (US3 tests — different files)
- US1 and US2 can be worked on in parallel after Foundational phase
---
## Parallel Example: User Story 1
```bash
# Launch US1 tests in parallel (Red phase):
Task: "Unit tests for OpenGraphService.buildEventMeta() in OpenGraphServiceTest.java"
Task: "Integration tests for SpaController event routes in SpaControllerTest.java"
# Then implement sequentially:
Task: "Implement OpenGraphService.buildEventMeta()"
Task: "Wire SpaController for /events/{token} routes"
Task: "E2E test for event page meta-tags"
```
---
## Implementation Strategy
### MVP First (User Story 1 Only)
1. Complete Phase 1: Setup (T001T003)
2. Complete Phase 2: Foundational (T004T008)
3. Complete Phase 3: User Story 1 (T009T013)
4. **STOP and VALIDATE**: Share an event link in a messenger, verify preview card
5. Deploy/demo if ready
### Incremental Delivery
1. Setup + Foundational → SpaController serving index.html with placeholder replacement
2. Add US1 → Event links show rich previews → Deploy (MVP!)
3. Add US2 → Generic pages also show previews → Deploy
4. Add US3 → Twitter/X cards work too → Deploy
5. Polish → Edge cases hardened → Final release
---
## Notes
- [P] tasks = different files, no dependencies
- [Story] label maps task to specific user story for traceability
- Each user story is independently completable and testable
- TDD enforced: write tests first, verify they fail, then implement
- Commit after each task or logical group
- Stop at any checkpoint to validate story independently