# History & replay

Because every sequenced entry is persisted to an append-only log (with
periodic snapshots), any past state of a document can be reconstructed and
its evolution replayed. The server exposes this through one REST endpoint.

## `GET /collab/docs/:docId/history`

(The `/collab` prefix follows `ROUTE_PREFIX`; see
[Configuration](configuration.md).)

Query parameters:

| Parameter | Type        | Default                   | Meaning                                                                                                           |
| --------- | ----------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `from`    | integer ≥ 0 | `0`                       | Start of the replay window. The response's snapshot is the **nearest stored snapshot with `snapshotSeq ≤ from`**. |
| `to`      | integer     | `Number.MAX_SAFE_INTEGER` | End of the window: entries are returned up to and including this seq.                                             |

Response (`200`):

```json
{
	"success": true,
	"snapshotSeq": 200,
	"snapshot": {
		"id": "root",
		"type": "element",
		"tag": "div",
		"attrs": {},
		"children": []
	},
	"entries": [
		{
			"seq": 201,
			"clientId": "p9",
			"userId": "alice",
			"patches": [
				{
					"op": "text",
					"nodeId": "d1",
					"index": 0,
					"remove": 0,
					"insert": "H"
				}
			]
		}
	],
	"headSeq": 245
}
```

Semantics (this is exactly the `CollabStorage.history` contract):

- `snapshot` is the greatest stored snapshot with `seq ≤ from`: the caller
  starts from it and applies `entries` in order;
- `entries` covers `(snapshotSeq, to]`, ordered by `seq`. Note the range
  starts at the _snapshot_, not at `from`: the caller gets everything needed
  to reach `from` and then continue to `to`;
- `headSeq` is the latest seq of the document; use it for pagination
  ("fetch the next window") and to render a progress bar;
- each entry carries `clientId` (the connection) and, when the user was
  authenticated, the stable `userId` for attribution.

The document state at any `seq` is:

```text
doc@seq = applyPatches(snapshot, entries where s ≤ seq)
```

## Authentication

The endpoint uses **the same auth rules as the WS handshake**
(`resolveIdentity` + the `read` action):

- token via `Authorization: Bearer <token>` header **or** `?token=<token>`
  query parameter;
- guests are allowed only when `allowAnonymous` is on;
- an invalid token is rejected even in anonymous mode.

Error responses:

| Status | Body                                                 | When                                                   |
| ------ | ---------------------------------------------------- | ------------------------------------------------------ |
| `401`  | `{"success":false,"error":{"code":"auth_required"}}` | No token and anonymous mode is off.                    |
| `401`  | `{"success":false,"error":{"code":"auth_failed"}}`   | Token presented but rejected by `checkAuthentication`. |
| `403`  | `{"success":false,"error":{"code":"forbidden"}}`     | `authorize(identity, docId, 'read')` denied.           |
| `404`  | `{"success":false,"error":{"code":"doc_not_found"}}` | Unknown document.                                      |
| `500`  | `{"success":false,"error":{"code":"internal"}}`      | Storage failure.                                       |

Example:

```bash
curl -H "Authorization: Bearer $TOKEN" \
  'https://example.com/collab/docs/demo/history?from=0&to=500'
```

## How the demo replay player uses it

The demo page (and the `@jodit/collab-plugin` plugin demo) rebuilds the document
**purely from the patch log**:

1. take a base snapshot (`snapshot` at `snapshotSeq`) and materialize it into
   a detached DOM tree;
2. step through `entries` with `seq > snapshotSeq` on a timer, applying each
   entry's `patches` to that DOM via the same apply function the live editor
   uses;
3. drive a progress bar from `index / entries.length` (or from `headSeq` when
   paginating).

Since entries record the author, the player can also attribute every step
("who typed this") using the `userId`/`clientId` fields. The live demo
colors its patch log entries per peer this way.

> **Tip: Time travel**
>
> To show the document as of seq `N`, request
> `?from=N&to=N` and apply all returned entries: the nearest-snapshot rule
> means you never replay more than `snapshotEvery` entries (default 200) to
> reach any point in history.

## Export the current document as HTML

For server-side rendering, archival, or "save to my CMS" flows you often want
the document as an HTML string without opening a browser. The server
reconstructs it from storage (nearest snapshot + entries) and serializes it:

```
GET {routePrefix}/docs/{docId}/html
Authorization: Bearer <token>     # or ?token=<token>
```

The response is raw `text/html`: the document **body** markup (the internal
`root` wrapper is unwrapped), with text and attributes HTML-escaped and void
elements (`<br>`, `<img>`, `<hr>`) emitted self-closing:

```bash
curl -H 'Authorization: Bearer <token>' \
  https://collab.example.com/collab/docs/report-42/html > report-42.html
```

Same read authorization as the history endpoint: a valid token with the
`read` action, `404` for an unknown document, `401`/`403` on auth failure.

> **Note: Comment anchors in exported HTML**
>
> Comment anchors are ordinary `data-jodit-comment` spans, so they appear in
> the export. If you are exporting for publishing rather than round-tripping,
> strip `[data-jodit-comment]` attributes on your side (the comment threads
> themselves live server-side and are not part of the HTML).

The serializer is also exported from `@jodit/collab-protocol` as
`serializeToHtml(node)` if you need it in your own code.
