# Contract: HTML Meta-Tags

**Feature**: 012-link-preview | **Date**: 2026-03-09

## Overview

This feature does not add new REST API endpoints. The contract is the HTML meta-tag structure injected into the server-rendered `index.html`.

## Meta-Tag Contract: Event Pages

For requests to `/events/{eventToken}` where the event exists:

```html
<!-- Open Graph -->
<meta property="og:title" content="{event title, max 70 chars}">
<meta property="og:description" content="{formatted description, max 200 chars}">
<meta property="og:url" content="{absolute canonical URL}">
<meta property="og:type" content="website">
<meta property="og:site_name" content="fete">
<meta property="og:image" content="{absolute URL}/og-image.png">

<!-- Twitter Card -->
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="{event title, max 70 chars}">
<meta name="twitter:description" content="{formatted description, max 200 chars}">
```

### Description Format

Full event data:
```
📅 Saturday, March 15, 2026 at 7:00 PM · 📍 Berlin — First 200 chars of description...
```

No location:
```
📅 Saturday, March 15, 2026 at 7:00 PM — First 200 chars of description...
```

No description:
```
📅 Saturday, March 15, 2026 at 7:00 PM · 📍 Berlin
```

No location, no description:
```
📅 Saturday, March 15, 2026 at 7:00 PM
```

### Title Truncation

- Titles ≤ 70 characters: used as-is.
- Titles > 70 characters: truncated to 67 characters + "..."

### HTML Escaping

All meta-tag `content` values MUST be HTML-escaped:
- `"` → `&quot;`
- `&` → `&amp;`
- `<` → `&lt;`
- `>` → `&gt;`

## Meta-Tag Contract: Non-Event Pages

For requests to `/`, `/create`, or any other non-event, non-API, non-static route:

```html
<!-- Open Graph -->
<meta property="og:title" content="fete">
<meta property="og:description" content="Privacy-focused event planning. Create and share events without accounts.">
<meta property="og:url" content="{absolute canonical URL}">
<meta property="og:type" content="website">
<meta property="og:site_name" content="fete">
<meta property="og:image" content="{absolute URL}/og-image.png">

<!-- Twitter Card -->
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="fete">
<meta name="twitter:description" content="Privacy-focused event planning. Create and share events without accounts.">
```

## Meta-Tag Contract: Event Not Found

For requests to `/events/{eventToken}` where the event does NOT exist, fall back to generic meta-tags (same as non-event pages). The Vue SPA will handle the 404 UI client-side.

## Injection Mechanism

The `index.html` template contains a placeholder:

```html
<head>
  ...
  <!-- OG_META_TAGS -->
  ...
</head>
```

The server replaces `<!-- OG_META_TAGS -->` with the generated meta-tag block before sending the response.
