## ADDED Requirements

### Requirement: Invitation routes declare link-preview metadata (P1)

Both the personalised invitation route and the open event route SHALL declare link-preview
metadata in their server-rendered HTML, in the form third-party applications read when a link
is pasted into them. The metadata SHALL declare a title, a description, an image with its
pixel dimensions when one exists, a canonical URL, a site name and a type.

The metadata SHALL be present in the initial server-rendered HTML, because the applications
that read it execute no client-side code.

#### Scenario: Open link unfurls

- **WHEN** the open event link is pasted into an application that renders link previews
- **THEN** the preview shows the event's title, description and image

#### Scenario: Present without scripting

- **WHEN** the invitation HTML is fetched with no JavaScript executed
- **THEN** the link-preview metadata is complete in that response

#### Scenario: Image dimensions declared

- **WHEN** an event has a preview image
- **THEN** the metadata declares that image's width and height alongside its absolute URL

#### Scenario: Absolute URLs

- **WHEN** link-preview metadata is emitted
- **THEN** every URL in it is absolute, including the image

### Requirement: Tokenized route previews are guest-free (P0)

The personalised invitation route's link-preview metadata SHALL be identical for every guest
invited to that event and SHALL be derivable from public event information alone. It SHALL
NOT contain a guest name, party size, contact detail, RSVP status, or the invitation token in
any field, including the image URL.

The canonical URL declared by the personalised route SHALL be the open event link, not the
tokenized link.

#### Scenario: Two guests, one preview

- **WHEN** the invitation links of two different guests to the same event are fetched
- **THEN** their link-preview metadata is byte-identical

#### Scenario: Token absent from the preview

- **WHEN** a personalised invitation page is rendered
- **THEN** no field of its link-preview metadata contains the invitation token

#### Scenario: Image URL is shared

- **WHEN** two guests' invitation links are unfurled
- **THEN** both reference the same image URL, which contains no per-guest component

#### Scenario: Canonical points to the public page

- **WHEN** a personalised invitation page declares its canonical preview URL
- **THEN** that URL is the event's open link

### Requirement: Preview metadata does not weaken token handling (P0)

Declaring link-preview metadata SHALL NOT change how the personalised route is cached,
indexed, or referred from. The route SHALL remain uncached and privately scoped, SHALL
continue to instruct search engines not to index or follow it, and SHALL continue to suppress
the referrer. A request that carries no valid token SHALL continue to produce the same
response as one carrying an unknown token.

#### Scenario: Still not indexable

- **WHEN** a personalised invitation page is fetched
- **THEN** it instructs search engines not to index it, exactly as before this change

#### Scenario: Still not cached

- **WHEN** a personalised invitation page is fetched
- **THEN** its caching directives keep it private and unstored, exactly as before this change

#### Scenario: Unknown token unchanged

- **WHEN** a link-preview crawler fetches a personalised invitation URL whose token does not exist
- **THEN** it receives the same generic not-found response any other client receives

### Requirement: A recognised crawler's visit is not a guest's visit (P1)

Fetching a personalised invitation page is what records that a guest opened their
invitation. A link-preview crawler fetches the same page for a different reason, so the
system SHALL recognise the crawlers of the major platforms invitations are shared through
and SHALL NOT record an open, a status change, or a history entry for them.

Recognition is by declared client identity and is therefore approximate. An unrecognised
crawler SHALL be treated as a guest, which is the behaviour that already existed — this
requirement narrows a known inaccuracy rather than eliminating it.

#### Scenario: Pasting a link does not mark it opened

- **WHEN** an admin copies a guest's invitation link and pastes it into a chat application, whose crawler then fetches the page
- **THEN** that invitation's open count, status and history are unchanged
- **AND** the preview still renders

#### Scenario: The guest opening it does count

- **WHEN** the guest then opens the same link in a browser
- **THEN** the open is recorded exactly as it was before this change

#### Scenario: An unrecognised crawler is counted as a guest

- **WHEN** a preview service the system does not recognise fetches the page
- **THEN** an open is recorded, as it would have been before this change
