with one click
logger-messages
Enforces active voice for logger messages and the Event Details API. Use when writing a new logger class or modifying message arrays in getInfo().
Menu
Enforces active voice for logger messages and the Event Details API. Use when writing a new logger class or modifying message arrays in getInfo().
Adds changelog entries to readme.txt following keepachangelog format. Use when updating the Unreleased section or documenting changes for a release.
Reproducibly capture in-product UI screenshots (admin popovers, settings teasers, dashboard widgets) used as embedded images in premium upsell teasers. Use whenever a teaser image needs refreshing after the underlying UI changes.
Guides implementation of structured action links on log events. Use when adding get_action_links() to a logger or migrating from get_log_row_details_output().
How to design and regenerate marketing screenshots for the wordpress.org plugin page (banner-1544x500.png, screenshot-1.png, etc). Covers the event mix that converts, the reproducible WordPress Playground pipeline that bakes it, and every non-obvious gotcha from previous shoots. Use when refreshing screenshot-1.png, banners, or any image showing the Simple History event log.
Guidance for writing and running tests in Simple History. Covers which framework to use, how to run existing tests, and how to create new ones (including the codegen recording workflow).
Surface and triage local issues that are safe and quick for an AI to implement and easy for a human to verify. Use when the user wants to "knock out issues", asks for "quick wins", "low-hanging fruit", "what's easy to do right now", or wants a batch of small tasks to work through in one session.
| name | logger-messages |
| description | Enforces active voice for logger messages and the Event Details API. Use when writing a new logger class or modifying message arrays in getInfo(). |
| allowed-tools | Read, Grep, Glob |
Write clear, user-friendly messages for Simple History event logs.
Write as if someone is telling you what they just did.
✅ DO ❌ DON'T
─────────────────────────────────────────────
Activated plugin Plugin was activated
Created menu Menu has been created
Updated settings Settings were updated
Published post Post has been published
public function getInfo() {
return [
'messages' => [
'plugin_activated' => __( 'Activated plugin', 'simple-history' ),
'plugin_deactivated' => __( 'Deactivated plugin', 'simple-history' ),
'post_updated' => __( 'Updated post "{post_title}"', 'simple-history' ),
],
];
}
Keys must be globally unique across all loggers (used as RFC 5424 MSGID).
// ✅ Good - descriptive prefix
'plugin_activated', 'theme_switched', 'user_logged_in'
// ❌ Bad - too generic
'activated', 'updated', 'deleted'
Verify uniqueness: grep -r "'your_key'" loggers/
The message body is a declarative sentence ("what happened?"). Action links (rendered below the message, see the action-links skill) are the canonical "what can I do?" affordance. Wrapping {post_title} (or any other interpolated token) in an <a> tag inside the template puts a CTA mid-sentence and competes with the action row.
Rule: Inline links inside message templates are permitted only when they point somewhere the action row cannot reach. If get_action_links() already covers the destination (Edit, View, Revisions, the overview page, …), the message must be plain text.
// ❌ Don't — Edit/View action links already point to the post.
'post_updated' => __( 'Updated post "<a href="...">{post_title}</a>"', 'simple-history' ),
// ✅ Do — plain title, action row handles navigation.
'post_updated' => __( 'Updated post "{post_title}"', 'simple-history' ),
Deleted items: still plain text. A dead link is worse than no link — the overview action link (All pages, All plugins) is the right hand-off.
Legitimate exception: the link goes somewhere no action link can express (e.g. an arbitrary external reference). Then an inline link is additive, not redundant.
Not an urgent migration — apply opportunistically when touching a logger for other reasons.
Prefix all context keys with the entity name to avoid collisions and keep keys self-documenting.
// ✅ Good - prefixed with entity
'plugin_name', 'plugin_current_version', 'theme_new_version'
'site_health_status', 'site_health_label', 'site_health_badge_label'
// ❌ Bad - too generic
'test', 'label', 'status', 'name', 'version'
Use the Event Details API for get_log_row_details_output(). Never build raw HTML with SimpleHistoryLogitem__keyValueTable.
use Simple_History\Event_Details\Event_Details_Group;
use Simple_History\Event_Details\Event_Details_Group_Table_Formatter;
use Simple_History\Event_Details\Event_Details_Item;
public function get_log_row_details_output( $row ) {
$group = new Event_Details_Group();
$group->set_formatter( new Event_Details_Group_Table_Formatter() );
$group->add_items(
array(
// Read value directly from context key.
new Event_Details_Item( 'status', __( 'Status', 'simple-history' ) ),
// Read new/prev pair from context (looks for key_new and key_prev).
new Event_Details_Item( array( 'setting_name' ), __( 'Setting', 'simple-history' ) ),
)
);
return $group;
}
Formatters:
Event_Details_Group_Table_Formatter — key-value table (default)Event_Details_Group_Diff_Table_Formatter — before/after with diffsEvent_Details_Group_Inline_Formatter — compact inline textManual values (when context keys don't match conventions):
( new Event_Details_Item( null, __( 'Label', 'simple-history' ) ) )
->set_new_value( $value )
See docs/architecture/event-details.md for full API reference.
When the structured API can't express your output (images, HTML content, color swatches):
Item_RAW_Formatter — Full custom HTML/JSON for an item (no name column)Item_Table_Row_RAW_Formatter — Table row with escaped name + raw HTML valueuse Simple_History\Event_Details\Event_Details_Item_Table_Row_RAW_Formatter;
$raw_formatter = ( new Event_Details_Item_Table_Row_RAW_Formatter() )
->set_html_output( sprintf( '<a href="%1$s">%2$s</a>', esc_url( $url ), esc_html( $url ) ) )
->set_json_output( [ 'url' => $url ] );
$item = ( new Event_Details_Item( null, __( 'URL', 'simple-history' ) ) )
->set_formatter( $raw_formatter );
Use RAW formatters sparingly — only when no structured formatter fits.
Navigational links (Edit, View, Preview) belong in get_action_links(), not inside get_log_row_details_output(). See the action-links skill.
Old loggers often embed <a> tags in the details table (e.g., "View/Edit" comment link, "View plugin info" thickbox). When migrating these loggers:
get_action_links()The only case for a link inside details is when the value itself is a URL (e.g., a plugin's homepage URL displayed as data). Use Item_Table_Row_RAW_Formatter for that.
Many older loggers build HTML manually with SimpleHistoryLogitem__keyValueTable tables. When migrating:
| Old pattern | New approach |
|---|---|
<table class='SimpleHistoryLogitem__keyValueTable'> with <tr>/<td> | Event_Details_Group + Group_Table_Formatter |
<ins> / <del> for changed values | Event_Details_Item with set_values() (auto-generates ins/del) |
<span class='SimpleHistoryLogitem__inlineDivided'> | Event_Details_Group + Group_Inline_Formatter |
Inline <a href> links to edit/view | Move to get_action_links() |
| Images, color swatches, shortcode output | Item_RAW_Formatter or Item_Table_Row_RAW_Formatter |
Standalone <p> text blocks (not key-value) | Group_Inline_Formatter with a single item, or RAW formatter |
Value transforms (e.g., true → "Enabled", locale → display name): Transform in PHP, then pass to set_new_value() / set_prev_value().
Conditional rows: Don't set values for items you want hidden — the container auto-removes empty items.