# Presentation

Import these from `@grafloria/renderer`.

## Functions

### `followPresenter`

Slave this host's camera to the presenter's, for as long as the handle is alive.

The follower keeps its OWN canvas size and reconstructs the camera rectangle from
the presenter's centre + zoom, so a follower on a different screen size sees the
same content correctly framed — and, critically, its `clientToWorld()` stays the
exact inverse of what it renders. Copying the presenter's rectangle wholesale
would break that (see viewport-channel.ts).

```ts
function followPresenter(
  host: PresentationHost,
  channel: ViewportChannel,
  options: FollowOptions = {}
): { stop: Unsubscribe }
```

### `isDocumentLocked`

Is this engine's document locked against edits?

```ts
function isDocumentLocked(engine: DiagramEngine): boolean
```

### `loadReadonlySnapshot`

Build a READ-ONLY document from a snapshot — the "share a link to a frozen copy"
case.

The load runs as a SYSTEM write, so it works even against an engine that is
already in presentation mode. Otherwise the order of two host calls would decide
whether the share link renders a diagram or an empty canvas: lock-then-load would
have the lock (correctly!) refuse every node the loader tried to add.

```ts
function loadReadonlySnapshot(
  engine: DiagramEngine,
  snapshot: SerializedDiagram
): DiagramModel
```

### `lockDocument`

Lock (or unlock) the document through the engine's own mode.

Thin on purpose: it drives `DiagramMode`, which this wave finally wired to real
enforcement in the model and the CommandManager. There is no second read-only
flag here, because a second flag is how you end up with two read-only modes that
disagree.

```ts
function lockDocument(engine: DiagramEngine, locked = true): void
```

### `presentTo`

Broadcast this host's camera to `channel` for as long as the returned handle is
alive. Returns a `stop()` that unsubscribes and cancels any pending trailing send.

```ts
function presentTo(
  host: PresentationHost,
  channel: ViewportChannel,
  options: PresentOptions = {}
): { stop: Unsubscribe }
```

## Classes

### `InMemoryViewportChannel`

A working, in-process implementation — the local/in-memory transport this card
ships so the feature is demonstrably complete end-to-end rather than an interface
with nothing behind it.

Genuinely useful beyond tests: it drives two `createDiagram()` instances on the
same page (a presenter canvas and a follower/minimap canvas), which is a real
product surface. Swap it for the network implementation and nothing else moves.

```ts
class InMemoryViewportChannel implements ViewportChannel
```

**Methods**

- `broadcastViewport(viewport: PresenterViewport): void` — Publish the presenter's current view to every follower.
- `onViewportBroadcast(callback: (viewport: PresenterViewport) => void): Unsubscribe` — Subscribe to presenter broadcasts. Returns an unsubscribe function.
- `getLast(): PresenterViewport | null` — The most recent broadcast, or null.
- `dispose(): void` — Drop all subscribers (teardown).

## Interfaces

### `FollowOptions`

```ts
interface FollowOptions
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `ignorePresenterId?` | `string` |  | Ignore broadcasts carrying this presenterId — i.e. my own. |
| `onFollow?` | `(viewport: PresenterViewport) => void` |  | Called whenever a presenter's viewport is applied (for a "following X" badge). |

### `PresentationHost`

The minimum a host must expose to be presented from / followed. `DiagramInstance` satisfies it.

```ts
interface PresentationHost
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `viewport` | `ViewportController` |  |  |

**Members**

- `render(): void` — Queue a repaint.

### `PresenterViewport`

What a presenter broadcasts. Centre + zoom, deliberately NOT a camera rect.

```ts
interface PresenterViewport
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `centerX` | `number` |  | World X of the centre of the presenter's view. |
| `centerY` | `number` |  | World Y of the centre of the presenter's view. |
| `zoom` | `number` |  | The presenter's magnification. |
| `presenterId?` | `string` |  | Who is presenting. Lets a follower ignore its own echo, and label the UI. |

### `PresentOptions`

```ts
interface PresentOptions
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `presenterId?` | `string` |  | Identifies this presenter on the wire; echoed back in the payload. |
| `throttleMs?` | `number` |  | Coalesce broadcasts to at most one per N ms. Default 50 (≈20/s). |

### `ViewportChannel`

The transport seam. Two methods, no lifecycle, no assumptions about the wire. Implement over WebSocket / WebRTC / Yjs awareness / BroadcastChannel / postMessage.

```ts
interface ViewportChannel
```

**Members**

- `broadcastViewport(viewport: PresenterViewport): void` — Publish the presenter's current view to every follower.
- `onViewportBroadcast(callback: (viewport: PresenterViewport) => void): Unsubscribe` — Subscribe to presenter broadcasts. Returns an unsubscribe function.
