# `MishkaCmsCore.Runtime.Authoring.Drafts`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.3/lib/mishka_cms_core/runtime/authoring/drafts.ex#L5)

The editor's autosave: the working document written to a **draft** row, not the live one.

## Why drafts and not the live record

Every write to a live runtime resource enqueues a compile job through `SendToOban`. An editor that
saved to the live row as you worked would therefore recompile the site on every change and publish
half-finished work to visitors. The draft resources exist precisely for this — they deliberately
carry no `SendToOban`, so writing one is cheap and invisible until someone publishes it.

A row belongs to the session that made it, and a session is one sitting, so the count grows with
use. `prune/3` caps it per document — `keep/0` is the number — dropping the oldest as a new one is
made.

That also makes the draft the shareable preview: `/_preview/:resource/:token` already serves a
draft by its `preview_token`, so "open in a new tab" needs no new route and no new auth story.

Component drafts do not exist as a resource, so a component's autosave is a no-op and its Save
button is the only persistence — reported honestly rather than silently doing nothing.

## Publishing what a draft holds

A draft is the whole document, its settings included: the layout, the SEO, the path, who may open
it. Its own `:publish` writes all of that over the original, and an editor that publishes its
working copy straight to the original writes `carried/2` beside its markup, so neither publish
leaves a setting behind in a draft it then throws away. The original keeps its own code either
way; the fingerprint `original_changed?/3` compares covers exactly what a publish writes over, and
`follow/5` keeps a draft in step with what its own editor saved to the original meanwhile.

# `autosave`

```elixir
@spec autosave(
  String.t(),
  map() | nil,
  String.t(),
  map(),
  String.t() | nil,
  String.t() | nil,
  integer() | nil
) :: {:ok, struct()} | :unsupported | {:error, term()}
```

Writes the current document to this session's draft, creating it on the first call and updating it
afterwards.

`label` is the name to keep. `nil` falls back to the autosave's own label, but when the editor has
a NAMED draft open it passes that name through — otherwise the automatic save would quietly rename
the author's draft to "Autosave" on the next keystroke.

It takes no source to copy from, unlike `save_as/8`: a sitting with no row yet is one that started
from the live document, and from its first write on it is the row itself that is read.

`status` says which of an error page's templates is being written, and is `nil` for every section
that keeps one. IT IS ASKED FOR RATHER THAN DEFAULTED because a caller who leaves it off is not
writing nothing — `Section.save_fields/3` falls back to the 404 and replaces THAT entry, so the
work lands on a template nobody opened and buries the one that was there. A default made that
silent, and the one caller that took it up was "Keep it as a draft" on the way out.

Returns `{:ok, draft}`, `:unsupported` for a section with no draft resource, or `{:error, reason}`.

# `autosave_label`

```elixir
@spec autosave_label() :: String.t()
```

The label the automatic save owns.

# `carried`

```elixir
@spec carried(String.t(), map()) :: map()
```

What publishing `draft` writes over the document it was made from — every field it carries but
the document's code — exactly as the draft's own `:publish` writes it. `%{}` for anything that is
not a draft of `section`.

For an editor that publishes its working copy straight to the original: written beside the
markup, in the same write, these are the draft's layout, SEO, path, title and permissions, which
would otherwise go with the draft when the publish discards it.

# `discard`

```elixir
@spec discard(String.t(), String.t(), map()) :: :ok | {:error, term()}
```

Deletes a draft — what "Discard" on the resume bar does to the autosave.

Deleting rather than ignoring is deliberate: a bar that offers to restore work you have already
said you do not want, every time you open the page, is a bar people learn to dismiss without
reading.

# `display_label`

```elixir
@spec display_label(String.t() | nil) :: String.t() | nil
```

The name to show for a draft: the automatic save's label in the caller's language, any other name as it was typed.

# `follow`

```elixir
@spec follow(String.t(), struct(), map(), map(), map()) ::
  {:ok, struct()} | {:error, term()}
```

Keeps `draft` in step with a change its own editor made to the original while it was open — a
setting saved to the page itself, say. `before` is the original as it stood, `now` as it is.

Every setting the change moved is written to the draft too, so publishing the draft does not put
the older one back; and the draft takes `now`'s fingerprint, so the change is not reported as
somebody else's by `original_changed?/3`. The draft's own markup is never replaced: when the
original's moved as well, the settings are carried and the fingerprint is kept, notice and all.

A draft that was already behind `before` — someone else changed the original first — is returned
as it is. Returns `{:ok, draft}` or `{:error, reason}`.

# `get`

```elixir
@spec get(String.t(), String.t(), map()) :: {:ok, struct()} | {:error, term()}
```

The draft `draft_id` of `section`, read as `actor`: `{:ok, draft}`, or `{:error, reason}` for a
draft that is gone, is not theirs to read, or a section that keeps none.

# `hash`

```elixir
@spec hash(String.t(), map()) :: non_neg_integer()
```

A fingerprint of `original`, a section's live document: `:erlang.phash2/1` of every field its draft
carries and publishing it writes over. A draft keeps the one it was made from (`source_hash`), so a
different one now means the original was changed since — see `original_changed?/3`.

The original's code is not in it. The editor's code drawer saves code to the original itself while
a draft is open, and no publish of the draft writes it, so a change to it is nothing the draft
would overwrite.

# `keep`

```elixir
@spec keep() :: pos_integer()
```

How many drafts a document keeps.

# `list`

```elixir
@spec list(String.t(), map() | nil, map()) :: [struct()]
```

Every draft of this record, newest first — what the editor's draft picker lists.

Returns `[]` for a section with no draft resource rather than raising, so a caller does not have to
ask `supported?/1` first.

# `named`

```elixir
@spec named(String.t(), map() | nil, map(), String.t()) :: struct() | nil
```

The draft this record already keeps under `label`, or `nil`.

What "a draft called X already exists" is asked with, so the editor can offer to update it instead
of quietly making a second one with the same name.

# `original_changed?`

```elixir
@spec original_changed?(struct()) :: boolean()
```

`original_changed?/3` for a draft on its own: the document it was made from is read by its
source id, as the system.

# `original_changed?`

```elixir
@spec original_changed?(String.t(), map() | nil, map() | nil) :: boolean()
```

Whether the document a pending draft was made from has changed since — a notice for its author,
nothing more. A draft that holds no fingerprint, or was published, answers `false`.

# `preview_segment`

```elixir
@spec preview_segment(String.t()) :: String.t() | nil
```

The `/_preview/:resource/:token` segment for a section.

# `prune`

```elixir
@spec prune(String.t(), map() | nil, map()) :: :ok
```

Drops everything past the newest `20` drafts of a record.

Runs where the count can grow and nowhere else — the moment a row is CREATED — so the realtime
save, which writes the same row over and over, costs one update and no extra read.

Newest is by `updated_at`, so a draft someone is working in right now cannot be the one dropped:
every change moves it back to the front of the list. A session whose row is pruned while it sits
idle is not broken by it either — its next write finds the id gone and starts a new row, which is
the same thing that happens when a colleague discards it from the picker.

A published draft is never touched: `list/3` reads pending drafts only, and a row with an
`applied_at` is the record of a publish rather than work in progress.

# `resource`

```elixir
@spec resource(String.t()) :: module() | nil
```

The Ash resource holding a section's drafts — what the editor points its drafts table at.

Returns `nil` for a section that keeps none, the same answer `supported?/1` gives.

# `save_as`

```elixir
@spec save_as(
  String.t(),
  map() | nil,
  map() | nil,
  String.t(),
  map(),
  String.t() | nil,
  String.t(),
  integer() | nil
) :: {:ok, struct()} | :unsupported | {:error, term()}
```

Saves the document under a NAME, creating that draft or updating it.

`draft_id` names the row to update — pass `nil` to create. Autosave has its own entry point,
`autosave/7`, and its own label, so naming a draft never collides with it.

`source` is the document a NEW row copies its fields from — the draft being forked or saved under
another name, and `nil` when the save starts from the live document, which is then the source. It
is asked for rather than defaulted for the reason `attrs/8` gives: a copy taken from the live
record hands the author today's published fields back with a success flash. Pass only a document
the author chose; the row the editor's own autosave made for a sitting is the live document's
working copy, not another document to copy from.

`status` says which of an error page's templates is being written, and is `nil` for every section
that keeps one template — see `autosave/7` for why it is asked for rather than defaulted.

# `source_key`

```elixir
@spec source_key(String.t()) :: atom() | nil
```

The draft attribute pointing back at the live record — `:source_page_id` and its siblings.

The drafts table scopes itself to one document by this key, so it has to be asked for by section
rather than assumed.

# `supported?`

```elixir
@spec supported?(String.t()) :: boolean()
```

Whether this section keeps drafts at all.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
