# Tracked changes (suggestions)

A **suggestion** is a proposed edit that does not change the document until a
reviewer accepts it. Insertions show up as green-underlined text; deletions
keep the text visible but struck through. Every suggestion carries its
author and time, and a reviewer resolves it with one click: accept applies
it, reject discards it. This is the same "suggesting mode" reviewers expect
from Google Docs and CKEditor, built on the same primitives as comments.

Like comments, the **anchor lives in the document HTML** and the metadata lives
on the server, so suggestions survive reloads, sync live to every participant,
and appear in the HTML export until resolved.

## How it works

A suggestion is marked content plus a server-side record:

- an **insertion** is real content wrapped in
  `<span data-jodit-suggestion="<id>" data-suggestion-type="insert">…</span>`
  (rendered as a redline; it is not yet "accepted" text, just proposed);
- a **deletion** wraps the text the author proposes to remove with
  `data-suggestion-type="delete"` (the text stays visible, struck through,
  until the deletion is accepted).

The span travels through the ordinary patch OT, exactly like a comment anchor,
so it converges across clients with no special casing. The server stores only
the metadata (`Suggestion`): `id`, `type`, `authorId/authorName/authorColor`,
the captured `quote`, and `createdAt`.

**Resolving is server-authoritative.** When a reviewer accepts or rejects, the
server derives patches from its own copy of the document and submits them
through the normal sequencer as a `system` edit:

| Action | Insertion suggestion        | Deletion suggestion         |
| ------ | --------------------------- | --------------------------- |
| Accept | unwrap span → keep the text | remove span → drop the text |
| Reject | remove span → drop the text | unwrap span → keep the text |

Because the resolution is just patches, it converges on every client live and
lands in history like any other change; there is no separate "reset" opcode.

> **Note: Roles**
>
> Proposing a change requires the `comment` action; accepting or rejecting
> requires `write`. So a `commenter`-role reviewer can suggest edits that a
> `writer`/`owner` later applies. See
> [Authentication & identity](authentication.md).

## In the editor

The `@jodit/collab-plugin` plugin ships the whole flow; no wiring beyond enabling
collaboration:

- toolbar buttons **Suggest insertion** and **Suggest deletion** wrap the
  current selection in a suggestion anchor and register it;
- a **Suggesting mode** toolbar toggle turns every edit into a suggestion
  (see below);
- a **Suggestions** review panel (Jodit's left workplace slot, opposite the
  comments panel) lists every open suggestion with author, time, an
  insertion/deletion chip and the affected text;
- **Accept** / **Reject** buttons on each card resolve it (disabled for a role
  that cannot `write`); clicking a card scrolls to and flashes its anchor.

When there are open threads or pending suggestions, the comment and review
toolbar buttons carry a small count badge, so a closed panel still signals
waiting items. A panel that starts closed (`commentsHidden` /
`suggestionsHidden`) auto-opens the first time items arrive; set
`commentsAutoReveal: false` / `suggestionsAutoReveal: false` to keep it closed
until the user clicks the toolbar button.

Turn it off with `collab: { suggestions: false }`. The lower-level pieces
(`SuggestionsPanel`, `CollabClient.createSuggestion/acceptSuggestion/rejectSuggestion`,
`attachCollab().markSuggestion`) are exported for custom clients.

## Suggesting mode

Beyond marking a selection by hand, the plugin has a full **Suggesting mode**
(the `collabSuggestMode` toolbar toggle, or
`editor.collab.setSuggesting(true)`). While it is on, local edits stop changing
the document for real:

- **typing** lands inside insert-suggestion spans — one suggestion per typing
  run (a pause closes the run and registers it);
- **Backspace and Delete never remove anything** — the would-be-deleted
  character, or the whole selection, is wrapped into a delete-suggestion span
  and the caret steps over it; consecutive presses grow the same suggestion;
- **typing over a selection** produces a pair: the selection becomes a
  deletion suggestion and the typed text an insertion suggestion right after
  it.

Deletion interception is active only in this mode; with the toggle off the
editor deletes normally. The suggestions produced this way are ordinary
suggestions — the review panel, accept/reject flow, REST listing and webhooks
apply unchanged.

Current limits: Enter, paste and IME composition still edit the document for
real, and Backspace at the very start of a block does nothing rather than
proposing a structural merge.

## Protocol messages

Client → server (author fields are never sent; the server assigns them):

```jsonc
// propose (the anchor span was already inserted via a normal submit)
{ "type": "suggestion.create", "suggestionId": "s1", "suggestionType": "insert", "quote": "the new text" }
// apply / discard
{ "type": "suggestion.accept", "suggestionId": "s1" }
{ "type": "suggestion.reject", "suggestionId": "s1" }
```

Server → client:

```jsonc
{ "type": "suggestion.updated", "suggestion": { "id": "s1", "type": "insert", "authorName": "Bob", "createdAt": 1751835600000, "quote": "…", "authorId": "bob", "authorColor": "#3f51b5" } }
{ "type": "suggestion.deleted", "suggestionId": "s1", "resolution": "accepted" } // or "rejected"
```

The `welcome` message includes the open set as `suggestions: Suggestion[]`, so a
late joiner sees pending review items immediately. Errors use the
`suggestion` context and the `suggestion_not_found` code; see the
[protocol spec](protocol.md).

## Listing over REST

For dashboards or a "N changes awaiting review" badge, the open suggestions of a
document are available over REST (same read auth as
[history](history.md)):

```bash
curl -H "Authorization: Bearer $TOKEN" \
  https://collab.example.com/collab/docs/report-42/suggestions
# → { "success": true, "suggestions": [ { "id": "s1", "type": "insert", … } ] }
```

Create/accept/reject are intentionally **not** REST endpoints: they must be
sequenced against the live document, so they go over the WS session. Webhooks
fire `suggestion.created` and `suggestion.resolved` (with `{ suggestionId,
resolution }`) if you need to react server-side; see [Webhooks](webhooks.md).

> **Warning: A resolved suggestion is a normal edit**
>
> Accepting a deletion permanently removes that text from the live document
> (it remains recoverable through [history](history.md) and
> [revisions](revisions.md), like any other edit). If the anchored text was
> already deleted by hand before review, resolving the suggestion is a safe
> no-op on the document and simply clears the review card.
