The visual editor's history, drawn as a MishkaGervaz custom table template: a vertical rail with a bucket-tinted node per change, the assistant's own words where it has them, and a Revert on every row whose document was kept.
Backed by MishkaCmsCore.Runtime.PageBuilderEvent and opened from the builder's Record-history
sheet. It replaced a hand-rolled <ol> over the in-memory undo stack, which meant the sheet showed
one session's ops, called every author "You", and lost everything on reload.
Why a second timeline template
MishkaCmsCoreWeb.Templates.ActivityTimeline draws a vertical timeline too, and is deliberately NOT
extended: it binds row content by column POSITION, and its dot_class / node_class helpers are
public API for MishkaCmsCore.Activity.ActivityEntry and MishkaDocument.EventLog. Reusing it
would couple three unrelated tables through a shared private vocabulary. This follows
MishkaCmsCoreWeb.Templates.ReviewComments instead — filter pills, per-row buttons, one static
column the template unpacks itself.
A template owns the whole table, so the filter bar and the footer are here. No UI adapter is
involved: everything this draws is either the template's own markup or a column's render.
Two rules that are easy to get wrong
filter_values is keyed by the filter's ATOM name — gervaz builds it with
Map.put(acc, filter.name, value). Reading it with a string key answers nil silently, and the
table then renders unfiltered with every pill unchecked, which looks like the pills are unwired.
Author branches on author_type BEFORE the masking ladder. Every row carries a human
author_id, including an assistant's — it is whoever's editor ran the prompt — so a ladder that
matches an email-bearing author first would print the operator's address under the assistant's
work. See MishkaCmsCore.Runtime.PageBuilderEvent.
Summary
Functions
Callback implementation for MishkaGervaz.Table.Behaviours.Template.render_bulk_actions/1.
Callback implementation for MishkaGervaz.Table.Behaviours.Template.render_empty/1.
Callback implementation for MishkaGervaz.Table.Behaviours.Template.render_error/1.
The four pills — All / Content / Style / Publish — as radios rather than a dropdown.
Callback implementation for MishkaGervaz.Table.Behaviours.Template.render_header/1.
The timeline, before it knows what happened.
Callback implementation for MishkaGervaz.Table.Behaviours.Template.render_template_switcher/1.
Functions
Callback implementation for MishkaGervaz.Table.Behaviours.Template.render_bulk_actions/1.
Callback implementation for MishkaGervaz.Table.Behaviours.Template.render_empty/1.
Callback implementation for MishkaGervaz.Table.Behaviours.Template.render_error/1.
The four pills — All / Content / Style / Publish — as radios rather than a dropdown.
"All" is the empty value of the one :select filter, so switching to it clears rather than
filtering on a fourth thing.
Callback implementation for MishkaGervaz.Table.Behaviours.Template.render_header/1.
The timeline, before it knows what happened.
Three rows the shape of a real one — dot, connector, a title and a line of detail — so the sheet settles into its content instead of replacing a centred spinner with a list.
Callback implementation for MishkaGervaz.Table.Behaviours.Template.render_template_switcher/1.