MishkaCmsCore.Runtime.PageBuilderEvent (Mishka CMS Core v0.0.1)

Copy Markdown View Source

One recorded change to a document in the visual editor — the durable half of the editor's history.

The undo stack in MishkaCmsCore.Runtime.PageBuilder.EditorSession is still what Ctrl+Z walks: in memory, fast, and gone when the session ends. This is what the history sheet reads, what a colleague sees, and what says who changed what — including the assistant.

What it anchors to

source_type + source_id name the LIVE record, always, for all four editable kinds. draft_id only says which branch the change happened on, and is nil for work done with no draft (the editor's published stage). Two consequences fall out of that shape rather than being coded:

  • "show me this draft's history" is a filter, not a second system;
  • publishing deletes the draft row, ON DELETE SET NULL clears draft_id, and the rows simply join the live record's own timeline. Nothing re-anchors them.

What it is not

Lean and append-only, deliberately unlike its neighbours: no archive, no unarchive, no version actions, no JSON:API routes. The house template is built for content a person edits through a form; this is a log written by the editor. Every action it does not have is one that cannot be misused.

The one mutable field is note — an annotation ABOUT a row rather than a claim about what happened, so editing it labels history instead of rewriting it. note_author_id travels with it, because otherwise a colleague's note on an assistant's row reads as the assistant's own words.

Attribution

author_id is never accepted from input: it is set from the actor, and it is ALWAYS the human — including on an :ai row, where the human is whoever's editor ran the prompt. author_type says who performed the change. Rendering the pair is MishkaCmsCoreWeb.Templates.HistoryTimeline's job, and it must branch on author_type BEFORE the email-masking ladder, or an assistant's row prints the operator's address.

Both author_id and note_author_id are released when their account is deleted (on_delete: :nilify): the row stays, and a :human row with no author reads as a deleted user.

See MishkaCmsCore.Runtime.PageBuilderSnapshot (the document a row restores) and MishkaCmsCore.Runtime.PageBuilder.History (how rows are written).

Summary

Functions

The three buckets the timeline's pills filter on.

Validates that the keys in the provided input are valid for at least one action on the resource.

Same as input/1, except restricts the keys to values accepted by the action provided.

The four editable kinds a change can be anchored to.

Types

t()

@type t() :: %MishkaCmsCore.Runtime.PageBuilderEvent{
  __lateral_join_source__: term(),
  __meta__: term(),
  __metadata__: term(),
  __order__: term(),
  aggregates: term(),
  author: term(),
  author_id: term(),
  author_master?: term(),
  author_type: term(),
  backend: term(),
  calculations: term(),
  detail: term(),
  draft_id: term(),
  id: term(),
  inserted_at: term(),
  kind: term(),
  label: term(),
  note: term(),
  note_author: term(),
  note_author_id: term(),
  note_author_master?: term(),
  note_edited_at: term(),
  op: term(),
  prompt: term(),
  refused: term(),
  site: term(),
  site_id: term(),
  snapshot: term(),
  snapshot_id: term(),
  source_id: term(),
  source_type: term(),
  target: term()
}

Functions

buckets()

@spec buckets() :: [atom()]

The three buckets the timeline's pills filter on.

default_short_name()

input(opts)

@spec input(values :: map() | Keyword.t()) :: map() | no_return()

Validates that the keys in the provided input are valid for at least one action on the resource.

Raises a KeyError error at compile time if not. This exists because generally a struct should only ever be created by Ash as a result of a successful action. You should not be creating records manually in code, e.g %MyResource{value: 1, value: 2}. Generally that is fine, but often with embedded resources it is nice to be able to validate the keys that are being provided, e.g

Resource
|> Ash.Changeset.for_create(:create, %{embedded: EmbeddedResource.input(foo: 1, bar: 2)})
|> Ash.create()

input(opts, action)

@spec input(values :: map() | Keyword.t(), action :: atom()) :: map() | no_return()

Same as input/1, except restricts the keys to values accepted by the action provided.

primary_key_matches?(left, right)

sources()

@spec sources() :: [atom()]

The four editable kinds a change can be anchored to.