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

Copy Markdown View Source

A note left on a document in the visual editor — on one element, or on the page as a whole.

Why this exists

Review notes lived on the editing session: a list on the EditorSession struct, in ETS, swept after a day idle. MishkaCmsCore.Runtime.PageBuilder.Comment's own moduledoc said so and named the next step. A note that does not survive the afternoon is not a review; it is a sticky note on a screen someone is about to close.

The field shape is that struct's, deliberately, so the swap is storage and not a rewrite: the node it points at, the label captured beside it, who wrote it, whether it is resolved.

What a comment is attached to

Two scopes, and the difference is one nullable column:

  • node_id set — a note on ONE element. The id is the editor's own n<8> handle, which is minted by Tree.parse/1 and therefore changes every time the document is re-parsed. It is a soft reference on purpose and can never be a foreign key.
  • node_id nil — a note on the whole page.

node_label is captured at write time for exactly that reason. The element can be deleted, or the document re-parsed under it, and the note still reads "on the <h1>" instead of pointing at nothing. MishkaCmsCore.Runtime.PageBuilder.Review.locate/2 is what turns a stale id back into a selection, or into a plain "that element is gone".

Who wrote it

author_type is :human, :ai or :system, and author_id is nullable, because the other two have no user row. Nothing here implements AI review; what it does is make sure that when something does, it is a new value in an existing column rather than a second comment system beside this one. resolved_by_id is separate from author_id for the same reason — the thing that resolves a note is often not the thing that raised it.

Scope

global? true with a nullable site_id, matching MishkaCmsCore.Runtime.Snippet, because the builder edits both tenant documents (pages, layouts, error pages) and global ones (components). A comment on a global component belongs to no site.

Summary

Functions

Puts the editor's context onto the note the composer is about to save.

Puts the editor's own scoping onto a :for_editor query, from the table's path_params.

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.

Every comment type as a {label, value} option, in the order of types/0.

Every comment type, in the order the form offers them.

Types

t()

@type t() :: %MishkaCmsCore.Runtime.PageComment{
  __lateral_join_source__: term(),
  __meta__: term(),
  __metadata__: term(),
  __order__: term(),
  aggregates: term(),
  author: term(),
  author_id: term(),
  author_master?: term(),
  author_type: term(),
  body: term(),
  calculations: term(),
  document_id: term(),
  id: term(),
  inserted_at: term(),
  master_mentions: term(),
  node_id: term(),
  node_label: term(),
  node_path: term(),
  parent_comment: term(),
  parent_comment_id: term(),
  replies: term(),
  resolved_at: term(),
  resolved_by: term(),
  resolved_by_id: term(),
  resolved_by_master?: term(),
  section: term(),
  site: term(),
  site_id: term(),
  status: term(),
  type: term(),
  updated_at: term()
}

Functions

apply_scope(params, state)

@spec apply_scope(map(), map()) :: map()

Puts the editor's context onto the note the composer is about to save.

The author never restates which document they are on, which element is selected, or who they are — the editor already knows all three, and asking would be a form with five fields where two will do. It rides in on the form's defaults, which is also what makes a page-level note simply a note with no node_id.

site_id, author_id and author_type are written only for a master. A tenant's :create does not accept them: it takes site_id from the tenant and author_id from the actor.

default_short_name()

for_editor_arguments(query, state)

@spec for_editor_arguments(Ash.Query.t(), map()) :: Ash.Query.t()

Puts the editor's own scoping onto a :for_editor query, from the table's path_params.

PREFIXED keys, for the reason MishkaCmsCore.Runtime.Snippet records: Gervaz turns any path param whose name matches an attribute into an equality filter, and site_id == X would drop exactly the global rows this read exists to include.

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)

type_options()

@spec type_options() :: [{String.t(), String.t()}]

Every comment type as a {label, value} option, in the order of types/0.

types()

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

Every comment type, in the order the form offers them.