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_idset — a note on ONE element. The id is the editor's ownn<8>handle, which is minted byTree.parse/1and therefore changes every time the document is re-parsed. It is a soft reference on purpose and can never be a foreign key.node_idnil — 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
@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
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.
@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.
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()
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.
@spec types() :: [atom()]
Every comment type, in the order the form offers them.