# Feature Specification: View Event Landing Page

**Feature**: `007-view-event`
**Created**: 2026-03-06
**Status**: Draft
**Source**: Migrated from spec/userstories.md

## User Scenarios & Testing

### User Story 1 - View event details as guest (Priority: P1)

A guest receives a shared event link, opens it, and sees all relevant event information: title, description (if provided), date and time, location (if provided), and the count of confirmed attendees.

**Why this priority**: Core value of the feature — without this, no other part of the event page is meaningful.

**Independent Test**: Can be fully tested by navigating to a valid event URL and verifying all event fields are displayed correctly, including attendee count.

**Acceptance Scenarios**:

1. **Given** a valid event link, **When** a guest opens the URL, **Then** the page displays the event title, date and time, and attendee count.
2. **Given** a valid event link for an event with optional fields set, **When** a guest opens the URL, **Then** the description and location are also displayed.
3. **Given** a valid event link for an event with optional fields absent, **When** a guest opens the URL, **Then** only the required fields are shown — no placeholder text for missing optional fields.
4. **Given** a valid event with RSVPs, **When** a guest opens the event page, **Then** only the total count of confirmed attendees is shown — individual names are NOT displayed to guests (names are only visible to the organizer via the organizer view).
5. **Given** an event page, **When** it is rendered, **Then** no external resources (CDNs, fonts, tracking scripts) are loaded — all assets are served from the app's own domain.
6. **Given** a guest with no account, **When** they open the event URL, **Then** the page loads without any login, account, or access code required.

---

### User Story 2 - View expired event (Priority: P2)

A guest opens a shared event link after the event's expiry date has passed. The event details are still accessible but the page clearly communicates that the event has ended and RSVP actions are not available.

**Why this priority**: The expired state is a required UI behavior that derives directly from the mandatory expiry date in US-1. Displaying expired events incorrectly would mislead guests.

**Independent Test**: Can be tested by creating an event with a past expiry date (or advancing the system clock) and verifying the "event has ended" state renders without RSVP controls.

**Acceptance Scenarios**:

1. **Given** an event whose expiry date has passed, **When** a guest opens the event URL, **Then** the page displays a clear "this event has ended" state and no RSVP actions are shown.

---

### User Story 3 - View cancelled event (Priority: P2)

A guest opens a shared event link for an event that has been cancelled (US-18). The page clearly communicates the cancellation and optionally displays the organizer's cancellation message. No RSVP actions are shown. [Deferred until US-18 is implemented]

**Why this priority**: Cancellation is a distinct state from expiry; guests must not be misled into thinking they can still RSVP.

**Independent Test**: Can be tested once US-18 is implemented by cancelling an event and verifying the cancelled state renders correctly.

**Acceptance Scenarios**:

1. **Given** a cancelled event (US-18), **When** a guest opens the event URL, **Then** the page displays a clear "cancelled" state with the cancellation message if provided, and no RSVP actions are shown. [Deferred until US-18 is implemented]

---

### User Story 4 - Event not found (Priority: P2)

A guest navigates to an event URL that no longer resolves — the event was deleted by the organizer (US-19) or automatically removed after expiry (US-12). The page displays a clear "event not found" message with no partial data or error traces.

**Why this priority**: Correct handling of deleted events is a privacy requirement — no partial data may be served.

**Independent Test**: Can be tested by navigating to a URL with an unknown event token and verifying the "event not found" message renders.

**Acceptance Scenarios**:

1. **Given** an event token that does not match any event on the server, **When** a guest opens the URL, **Then** the page displays a clear "event not found" message — no partial data, no error traces, no stack dumps.

---

### Edge Cases

- What happens when the event has no attendees yet? — Count shows 0.
- What happens when the event has been cancelled after US-18 is implemented? — Renders cancelled state with optional message; RSVP hidden. [Deferred]
- What happens when the server is temporarily unavailable? — The page displays a generic, friendly error message with a manual "Retry" button. No automatic retry.
- How does the page behave when JavaScript is disabled? — Per Q-3 resolution: the app is a SPA; JavaScript-dependent rendering is acceptable.

## Requirements

### Functional Requirements

- **FR-001**: The event page MUST display: title, date and time, and attendee count for any valid event.
- **FR-002**: The event page MUST display description and location when those optional fields are set on the event.
- **FR-003**: The public event page MUST display only the count of confirmed attendees. Individual attendee names MUST NOT be shown to guests — names are only visible to the organizer (organizer view, separate user story).
- **FR-004**: If the event's expiry date has passed, the page MUST render a clear "this event has ended" state and MUST NOT show any RSVP actions.
- **FR-005**: If the event has been cancelled (US-18), the page MUST display a "cancelled" state with the cancellation message (if provided) and MUST NOT show any RSVP actions. [Deferred until US-18 is implemented]
- **FR-006**: If the event token does not match any event on the server, the page MUST display a clear "event not found" message — no partial data or error traces.
- **FR-007**: The event page MUST be accessible without any login, account, or access code — only the event token in the URL is required.
- **FR-008**: The event page MUST NOT load any external resources (no CDNs, no Google Fonts, no tracking scripts).

### Key Entities

- **Event**: Has a public event token (UUID in URL), title, optional description, date/time (OffsetDateTime — displayed in the organizer's original timezone, no conversion to viewer timezone), IANA timezone name (e.g. `Europe/Berlin`, stored as separate field — required for human-readable timezone display), optional location, expiry date (LocalDate), and optionally a cancelled state with message. See `.specify/memory/research/datetime-best-practices.md` for full stack type mapping.
  - **Note**: The IANA timezone requires a new `timezone` field on the Event entity and API schema. This impacts US-1 (Create Event) — the frontend must send the organizer's IANA zone ID alongside the OffsetDateTime.
- **RSVP**: Has a guest name and binary attending status (attending / not attending — no "maybe"). Only the count of confirmed attendees (status = attending) is exposed on the public event page. Individual names are visible only in the organizer view, sorted alphabetically by name.

## Success Criteria

### Measurable Outcomes

- **SC-001**: A guest who opens a valid event URL can see all set event fields (title, date/time, and any optional fields) without logging in.
- **SC-002**: The attendee count reflects all current server-side RSVPs with attending status. No individual names are exposed on the public event page.
- **SC-003**: An expired event URL renders the "ended" state — RSVP controls are absent from the DOM, not merely hidden via CSS.
- **SC-004**: An unknown event token URL renders a "not found" message — no event data, no server error details.
- **SC-005**: No network requests to external domains are made when loading the event page.

## Clarifications

### Session 2026-03-06

- Q: What should the event page display when the server is temporarily unavailable? → A: Generic friendly error state with a manual "Retry" button; no automatic retry.
- Q: How should date/time be displayed regarding timezones? → A: Organizer timezone preserved — display the time exactly as entered by the organizer (OffsetDateTime), no conversion to viewer's local timezone. The IANA timezone name (e.g. "Europe/Berlin") MUST be displayed alongside the time. Requires a new `timezone` field on Event entity/API (impacts US-1).
- Q: What is the URL pattern for event pages? → A: `/events/:token` (e.g. `/events/a1b2c3d4-...`). Plural, matching the RESTful API resource name.
- Q: Should guest names be visible to other guests on the public event page? → A: No. Only the attendee count is shown to guests. Individual names are exclusively visible to the organizer, sorted alphabetically.
- Q: How should the loading state look while the API call is in progress? → A: Skeleton-shimmer (placeholder blocks in field shape that shimmer until data arrives).
- Q: Should the event page include OpenGraph meta tags for link previews? → A: Out of scope for US-007. Separate user story — generic app-branding OG-tags only, no event data exposed to crawlers. Noted in `.specify/memory/ideen.md`.
- Q: Should date/time formatting adapt to the viewer's browser locale? → A: Yes, browser-locale-based via `Intl.DateTimeFormat` (e.g. DE: "15. März 2026, 20:00" / EN: "March 15, 2026, 8:00 PM").
- Q: Is RSVP status binary or are there more states (e.g. "maybe")? → A: Binary — attending or not attending. No "maybe" status. Count reflects only confirmed attendees.
