feat(M3): dossier edit+rerun UI, F2/F3, run-list badges — v0.20.0

- Dossier_Actions (4 AJAX endpoints, manage_options+nonce): save fork /
  queue replay / queue rebuild / stage-status poll. Run identity always
  derived server-side from post meta; the AJAX layer validates + queues
  ONLY — all mutations happen in the cron handlers (chained-cron, CPO
  hard constraint). Fork saves lock message structure (count/roles) so
  every version stays replayable.
- Edit panel per editable stage: per-message textareas prefilled from
  the latest fork or the recorded request_json; explicit fork +
  downstream-stale + queued-execution copy; spend strip with ceiling
  warning (filterable fraction, default 0.8). Disabled stages always
  show WHY (policy reasons; missing-input retention copy).
- Stale / human-edited / attempt chips per stage + header badges (M2
  slot filled); dossier polls stage status while a re-run is active
  (board poll interval setting) and re-renders when the run settles.
- F2: sidebar Models & Prompts card (per-stage model/pv/role) + Run
  Spend card. F3: log-only stages (image_a/b, llm_research, ...) render
  from generation_log + the _prautoblogger_image_role attachment grid.
- M2 correctness: dossier is now item-scoped (idea-hash meta) and
  excludes gen_log rows linked to other posts of a multi-article run.
- Run-list visibility (product AC): board cards get a Human-modified
  chip; Review Queue rows get a stale-stages chip linking to the
  dossier. Both via single batched queries (no N+1).
- v0.20.0 + CHANGELOG + ARCHITECTURE #24 (+ file tree, schema tables,
  db-version note) in this commit per versioning discipline.

Agent-Session: engineer-prab-m3-20260612
This commit is contained in:
peptiderepo
2026-06-12 13:40:27 +00:00
parent 38500b2781
commit 6ed639d1db
30 changed files with 2606 additions and 22 deletions

View File

@@ -0,0 +1,109 @@
<?php
/**
* Partial: per-stage edit + replay panel (M3, v0.20.0).
*
* Rendered only for editable stages (writer chat stages with a
* persisted input). Included by dossier-stage-section.php; inherits its
* scope: $stage_name, $s_role, $section_id, $rerun (panel data),
* $dossier (spend/eligibility/post_id).
*
* UX contract (CPO guardrails): the copy states explicitly that saving
* forks a NEW version (original preserved), that re-running marks
* downstream stages stale, that execution is QUEUED (never implied
* synchronous), and what the run has already spent against its ceiling.
*
* Security: message contents via esc_textarea(); all attributes
* esc_attr(); spend numbers via number_format + esc_html.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$panel_prefill = is_array( $rerun['prefill'] ?? null ) ? $rerun['prefill'] : array(
'model' => '',
'messages' => array(),
);
$panel_fork = is_array( $rerun['fork'] ?? null ) ? $rerun['fork'] : null;
$panel_spend = is_array( $dossier['spend'] ?? null ) ? $dossier['spend'] : array(
'settled' => 0.0,
'reserved' => 0.0,
'ceiling' => 0.0,
'warn' => false,
);
$held_usd = (float) $panel_spend['settled'] + (float) $panel_spend['reserved'];
?>
<div class="prab-edit-panel" id="<?php echo $section_id; // Already escaped. ?>-edit" hidden
data-stage="<?php echo esc_attr( $stage_name ); ?>"
data-agent-role="<?php echo esc_attr( $s_role ); ?>">
<p class="prab-edit-copy">
<?php esc_html_e( 'You are editing a copy of this stage\'s input. Saving creates a new version — the original is preserved unchanged. Re-running this stage executes your latest saved version as a queued background job and marks all downstream stages stale; nothing downstream re-runs until you trigger it.', 'prautoblogger' ); ?>
</p>
<p class="prab-edit-spend<?php echo ! empty( $panel_spend['warn'] ) ? ' prab-edit-spend--warn' : ''; ?>">
<?php if ( (float) $panel_spend['ceiling'] > 0 ) : ?>
<?php
printf(
/* translators: 1: spent USD, 2: ceiling USD. */
esc_html__( 'Run spend so far: $%1$s of the $%2$s per-run ceiling. Re-runs are new spend on this run.', 'prautoblogger' ),
esc_html( number_format( $held_usd, 4 ) ),
esc_html( number_format( (float) $panel_spend['ceiling'], 2 ) )
);
if ( ! empty( $panel_spend['warn'] ) ) {
echo ' ';
esc_html_e( 'Warning: this run is approaching its ceiling — the re-run may halt. Raise the per-run ceiling setting first if needed.', 'prautoblogger' );
}
?>
<?php else : ?>
<?php esc_html_e( 'No per-run cost ceiling is configured for this run (monthly budget still applies).', 'prautoblogger' ); ?>
<?php endif; ?>
</p>
<p class="prab-edit-model">
<?php
printf(
/* translators: %s: model identifier. */
esc_html__( 'Model (fixed for replay): %s', 'prautoblogger' ),
esc_html( (string) $panel_prefill['model'] )
);
?>
</p>
<?php foreach ( $panel_prefill['messages'] as $i => $panel_message ) : ?>
<div class="prab-edit-message">
<label class="prab-edit-role" for="<?php echo $section_id; // Already escaped. ?>-msg-<?php echo (int) $i; ?>">
<?php echo esc_html( ucfirst( (string) ( $panel_message['role'] ?? '' ) ) ); ?>
</label>
<textarea class="prab-edit-textarea large-text"
id="<?php echo $section_id; // Already escaped. ?>-msg-<?php echo (int) $i; ?>"
rows="<?php echo 'system' === ( $panel_message['role'] ?? '' ) ? 4 : 10; ?>"
data-role="<?php echo esc_attr( (string) ( $panel_message['role'] ?? '' ) ); ?>"><?php echo esc_textarea( (string) ( $panel_message['content'] ?? '' ) ); ?></textarea>
</div>
<?php endforeach; ?>
<div class="prab-edit-actions">
<button type="button" class="button button-primary prab-edit-save">
<?php esc_html_e( 'Save as new version', 'prautoblogger' ); ?>
</button>
<button type="button" class="button prab-edit-rerun" <?php disabled( null === $panel_fork ); ?>>
<?php esc_html_e( 'Re-run this stage (queued)', 'prautoblogger' ); ?>
</button>
<span class="prab-edit-forkinfo" data-forkinfo>
<?php
if ( null !== $panel_fork ) {
printf(
/* translators: 1: version number, 2: author login, 3: datetime. */
esc_html__( 'Latest saved version: v%1$d by %2$s at %3$s', 'prautoblogger' ),
(int) $panel_fork['version'],
esc_html( (string) $panel_fork['author'] ),
esc_html( (string) $panel_fork['created_at'] )
);
} else {
esc_html_e( 'No edited version saved yet.', 'prautoblogger' );
}
?>
</span>
<span class="prab-edit-feedback" aria-live="polite" data-feedback></span>
</div>
</div>

View File

@@ -0,0 +1,118 @@
<?php
/**
* Partial: log-only stage section (M3/F3, v0.20.0).
*
* Renders stages that exist ONLY in generation_log — image_a/image_b,
* image_prompt_rewrite, llm_research, opik_eval_judge, ... — which the
* M2 dossier silently dropped because the image pipeline (and the
* research providers) never write run_stages rows (QA M2 F3). These
* sections show the call record (model, tokens, cost, status, raw
* trace) from their REAL data source; image stages additionally render
* the post's actual pipeline attachments ($dossier['images']).
*
* Variables injected by dossier-page.php:
* $stage_name (string) -- stage key
* $log_rows (array) -- generation_log rows for this stage
* $dossier (array) -- page view model (images)
*
* Security: identical escaping discipline to dossier-stage-section.php.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$lo_label = PRAutoBlogger_Stage_Display_Map::label( $stage_name );
$lo_is_image = in_array( $stage_name, array( 'image_a', 'image_b' ), true );
$lo_images = $lo_is_image && ! empty( $dossier['images'] ) ? (array) $dossier['images'] : array();
$lo_cost = 0.0;
$lo_status = '';
$lo_model = '—';
foreach ( $log_rows as $lo_row ) {
$lo_cost += (float) ( $lo_row['estimated_cost'] ?? 0 );
$lo_status = (string) ( $lo_row['response_status'] ?? '' );
if ( ! empty( $lo_row['model'] ) ) {
$lo_model = (string) $lo_row['model'];
}
}
$lo_section_id = 'prab-logstage-' . esc_attr( $stage_name );
?>
<div class="prab-dossier-section prab-dossier-section--stage prab-dossier-section--log-only"
data-stage="<?php echo esc_attr( $stage_name ); ?>"
id="<?php echo $lo_section_id; // Already escaped. ?>">
<div class="prab-stage-header">
<h2 class="prab-section-title"><?php echo esc_html( $lo_label ); ?></h2>
<div class="prab-stage-header-meta">
<?php if ( '' !== $lo_status ) : ?>
<span class="prab-trace-status prab-trace-status--<?php echo esc_attr( $lo_status ); ?>">
<?php echo esc_html( $lo_status ); ?>
</span>
<?php endif; ?>
<?php if ( $lo_cost > 0 ) : ?>
<span class="prab-stage-cost">$<?php echo esc_html( number_format( $lo_cost, 5 ) ); ?></span>
<?php endif; ?>
<span class="prab-stage-model"><?php echo esc_html( $lo_model ); ?></span>
</div>
</div>
<?php if ( ! empty( $lo_images ) ) : ?>
<div class="prab-image-grid">
<?php foreach ( $lo_images as $lo_image ) : ?>
<figure class="prab-image-card<?php echo ! empty( $lo_image['is_featured'] ) ? ' prab-image-card--chosen' : ''; ?>">
<img src="<?php echo esc_url( (string) $lo_image['url'] ); ?>" alt="<?php echo esc_attr( (string) $lo_image['role'] ); ?>" loading="lazy" />
<figcaption>
<?php echo esc_html( ucfirst( (string) $lo_image['role'] ) ); ?>
<?php if ( ! empty( $lo_image['is_featured'] ) ) : ?>
<span class="prab-chip prab-chip--chosen"><?php esc_html_e( 'Chosen (featured)', 'prautoblogger' ); ?></span>
<?php endif; ?>
</figcaption>
</figure>
<?php endforeach; ?>
</div>
<?php elseif ( $lo_is_image ) : ?>
<p class="prab-output-absent"><?php esc_html_e( 'No pipeline attachments recorded for this article (images attach on publish).', 'prautoblogger' ); ?></p>
<?php endif; ?>
<div class="prab-stage-trace">
<button type="button" class="prab-trace-toggle button-link"
aria-expanded="false"
aria-controls="<?php echo $lo_section_id; // Already escaped. ?>-trace">
<?php esc_html_e( 'Show raw trace', 'prautoblogger' ); ?>
</button>
<div id="<?php echo $lo_section_id; // Already escaped. ?>-trace" class="prab-trace-detail" hidden>
<?php foreach ( $log_rows as $log_row ) : ?>
<div class="prab-trace-entry">
<div class="prab-trace-meta">
<span><?php echo esc_html( (string) ( $log_row['model'] ?? '—' ) ); ?></span>
<span>
<?php
printf(
/* translators: 1: prompt tokens, 2: completion tokens. */
esc_html__( '%1$d prompt + %2$d completion tokens', 'prautoblogger' ),
(int) ( $log_row['prompt_tokens'] ?? 0 ),
(int) ( $log_row['completion_tokens'] ?? 0 )
);
?>
</span>
<span>$<?php echo esc_html( number_format( (float) ( $log_row['estimated_cost'] ?? 0 ), 6 ) ); ?></span>
<span class="prab-trace-status prab-trace-status--<?php echo esc_attr( (string) ( $log_row['response_status'] ?? '' ) ); ?>">
<?php echo esc_html( (string) ( $log_row['response_status'] ?? '' ) ); ?>
</span>
</div>
<?php if ( ! empty( $log_row['request_json'] ) ) : ?>
<details class="prab-trace-request">
<summary><?php esc_html_e( 'Request payload', 'prautoblogger' ); ?></summary>
<?php // SECURITY: esc_html() treats model output as untrusted HTML. ?>
<pre class="prab-trace-pre"><?php echo esc_html( (string) $log_row['request_json'] ); ?></pre>
</details>
<?php endif; ?>
<?php if ( ! empty( $log_row['error_message'] ) ) : ?>
<p class="prab-trace-error"><?php echo esc_html( (string) $log_row['error_message'] ); ?></p>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</div>
</div><!-- .prab-dossier-section--log-only -->

View File

@@ -101,6 +101,13 @@ $back_url = admin_url( 'admin.php?page=prautoblogger-board' );
<span class="prab-dossier-status prab-dossier-status--<?php echo esc_attr( $dossier['post_status'] ?? '' ); ?>">
<?php echo esc_html( ucfirst( $dossier['post_status'] ?? '' ) ); ?>
</span>
<?php // M3: run-summary audit badges -- visible, never tooltip-buried (CPO guardrail 2). ?>
<span class="prab-chip prab-chip--human prab-chip--header" data-header-chip="human" <?php echo ! empty( $dossier['human_modified_any'] ) ? '' : 'hidden'; ?>>
<?php esc_html_e( 'Human-modified', 'prautoblogger' ); ?>
</span>
<span class="prab-chip prab-chip--stale prab-chip--header" data-header-chip="stale" <?php echo ! empty( $dossier['stale_any'] ) ? '' : 'hidden'; ?>>
<?php esc_html_e( 'Has stale stages', 'prautoblogger' ); ?>
</span>
</div>
</div>
<?php if ( $post_id > 0 ) : ?>
@@ -142,6 +149,16 @@ $back_url = admin_url( 'admin.php?page=prautoblogger-board' );
require PRAUTOBLOGGER_PLUGIN_DIR . 'templates/admin/dossier-stage-section.php';
}
// M3/F3: stages that exist only in generation_log (images,
// llm_research, ...) render from their real data source.
foreach ( ( $dossier['log_only_stages'] ?? array() ) as $stage_name ) {
$log_rows = $log_index[ $stage_name ] ?? array();
if ( empty( $log_rows ) ) {
continue;
}
require PRAUTOBLOGGER_PLUGIN_DIR . 'templates/admin/dossier-log-stage-section.php';
}
if ( empty( $stages ) ) :
?>
<div class="prab-dossier-no-stages">
@@ -190,6 +207,8 @@ $back_url = admin_url( 'admin.php?page=prautoblogger-board' );
<?php endif; ?>
</div>
<?php require PRAUTOBLOGGER_PLUGIN_DIR . 'templates/admin/dossier-sidebar-cards.php'; ?>
<div class="prab-sidebar-card prab-cost-receipt">
<h3 class="prab-sidebar-heading"><?php esc_html_e( 'Cost Receipt', 'prautoblogger' ); ?></h3>
<table class="prab-receipt-table">

View File

@@ -0,0 +1,86 @@
<?php
/**
* Partial: M3 sidebar cards — run spend (guardrail 4 visibility) and
* the per-stage model/prompt-version consolidation (QA M2 F2).
*
* Included by dossier-page.php; inherits its scope: $dossier, $run,
* $log_index, $stages.
*
* Security: all db-sourced values esc_html(); numbers via number_format.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$sc_spend = is_array( $dossier['spend'] ?? null ) ? $dossier['spend'] : array(
'settled' => 0.0,
'reserved' => 0.0,
'ceiling' => 0.0,
'warn' => false,
);
$sc_held = (float) $sc_spend['settled'] + (float) $sc_spend['reserved'];
?>
<div class="prab-sidebar-card prab-run-spend<?php echo ! empty( $sc_spend['warn'] ) ? ' prab-run-spend--warn' : ''; ?>">
<h3 class="prab-sidebar-heading"><?php esc_html_e( 'Run Spend', 'prautoblogger' ); ?></h3>
<dl class="prab-sidebar-dl">
<dt><?php esc_html_e( 'Settled', 'prautoblogger' ); ?></dt>
<dd>$<?php echo esc_html( number_format( (float) $sc_spend['settled'], 4 ) ); ?></dd>
<?php if ( (float) $sc_spend['reserved'] > 0 ) : ?>
<dt><?php esc_html_e( 'Reserved', 'prautoblogger' ); ?></dt>
<dd>$<?php echo esc_html( number_format( (float) $sc_spend['reserved'], 4 ) ); ?></dd>
<?php endif; ?>
<dt><?php esc_html_e( 'Ceiling', 'prautoblogger' ); ?></dt>
<dd>
<?php if ( (float) $sc_spend['ceiling'] > 0 ) : ?>
$<?php echo esc_html( number_format( (float) $sc_spend['ceiling'], 2 ) ); ?>
<?php else : ?>
<?php esc_html_e( 'none configured', 'prautoblogger' ); ?>
<?php endif; ?>
</dd>
</dl>
<?php if ( ! empty( $sc_spend['warn'] ) ) : ?>
<p class="prab-spend-warning">
<?php
printf(
/* translators: 1: spent USD, 2: ceiling USD. */
esc_html__( 'This run has committed $%1$s of its $%2$s ceiling — further re-runs may halt.', 'prautoblogger' ),
esc_html( number_format( $sc_held, 4 ) ),
esc_html( number_format( (float) $sc_spend['ceiling'], 2 ) )
);
?>
</p>
<?php endif; ?>
</div>
<div class="prab-sidebar-card prab-models-card">
<h3 class="prab-sidebar-heading"><?php esc_html_e( 'Models & Prompts', 'prautoblogger' ); ?></h3>
<table class="prab-models-table">
<tbody>
<?php foreach ( $log_index as $sc_stage => $sc_rows ) : ?>
<?php
$sc_first = $sc_rows[0] ?? array();
$sc_model = ! empty( $sc_first['model'] ) ? (string) $sc_first['model'] : '—';
$sc_pv = ! empty( $sc_first['prompt_version'] ) ? (string) $sc_first['prompt_version'] : '—';
$sc_role = ! empty( $sc_first['agent_role'] ) ? (string) $sc_first['agent_role'] : '—';
?>
<tr>
<th scope="row"><?php echo esc_html( PRAutoBlogger_Stage_Display_Map::label( (string) $sc_stage ) ); ?></th>
<td>
<span class="prab-models-model"><?php echo esc_html( $sc_model ); ?></span>
<span class="prab-models-meta">
<?php
printf(
/* translators: 1: prompt version, 2: agent role. */
esc_html__( 'pv %1$s · %2$s', 'prautoblogger' ),
esc_html( $sc_pv ),
esc_html( $sc_role )
);
?>
</span>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>

View File

@@ -3,9 +3,10 @@
* Partial: single stage section for the Article Dossier.
*
* Variables injected by dossier-page.php:
* $stage_row (array) -- run_stages row incl. display_output
* $stage_row (array) -- run_stages row incl. display_output + ['rerun'] panel data (M3)
* $stage_name (string) -- stage key
* $log_rows (array) -- generation_log rows for this stage
* $dossier (array) -- page view model (spend/eligibility/post_id)
*
* Security: all model output escaped via esc_html() or wp_kses_post().
* request_json escaped via esc_html() -- treat as untrusted HTML.
@@ -30,9 +31,14 @@ $s_model = ( null !== $first_log && ! empty( $first_log['model'] ) ) ? $fi
$s_pv = ( null !== $first_log && ! empty( $first_log['prompt_version'] ) ) ? $first_log['prompt_version'] : '—';
$has_raw_trace = ! empty( $log_rows );
$section_id = 'prab-stage-' . esc_attr( $stage_name ) . '-' . esc_attr( $s_role );
$rerun = is_array( $stage_row['rerun'] ?? null ) ? $stage_row['rerun'] : array();
$is_stale = ! empty( $rerun['stale'] );
$is_human = ! empty( $rerun['human_modified'] );
$attempts = (int) ( $rerun['attempt'] ?? 1 );
?>
<div class="prab-dossier-section prab-dossier-section--stage prab-stage-status--<?php echo esc_attr( $s_status ); ?>"
<div class="prab-dossier-section prab-dossier-section--stage prab-stage-status--<?php echo esc_attr( $s_status ); ?><?php echo $is_stale ? ' prab-stage--stale' : ''; ?>"
data-stage="<?php echo esc_attr( $stage_name ); ?>"
data-agent-role="<?php echo esc_attr( $s_role ); ?>"
id="<?php echo $section_id; // Already escaped. ?>">
<div class="prab-stage-header">
@@ -46,6 +52,24 @@ $section_id = 'prab-stage-' . esc_attr( $stage_name ) . '-' . esc_attr( $s_ro
<span class="prab-stage-status-pill prab-stage-status-pill--<?php echo esc_attr( $s_status ); ?>">
<?php echo esc_html( ucfirst( $s_status ) ); ?>
</span>
<?php // M3 audit chips -- visible, never tooltip-buried (CPO guardrails 2+3). ?>
<span class="prab-chip prab-chip--stale" data-chip="stale" <?php echo $is_stale ? '' : 'hidden'; ?>>
<?php esc_html_e( 'Stale — upstream changed', 'prautoblogger' ); ?>
</span>
<span class="prab-chip prab-chip--human" data-chip="human" <?php echo $is_human ? '' : 'hidden'; ?>>
<?php esc_html_e( 'Human-edited input', 'prautoblogger' ); ?>
</span>
<?php if ( $attempts > 1 ) : ?>
<span class="prab-chip prab-chip--attempt">
<?php
printf(
/* translators: %d: attempt count. */
esc_html__( 'Attempt %d', 'prautoblogger' ),
(int) $attempts
);
?>
</span>
<?php endif; ?>
<?php if ( $s_cost > 0 ) : ?>
<span class="prab-stage-cost">$<?php echo esc_html( number_format( $s_cost, 5 ) ); ?></span>
<?php endif; ?>
@@ -123,4 +147,27 @@ $section_id = 'prab-stage-' . esc_attr( $stage_name ) . '-' . esc_attr( $s_ro
</div>
<?php endif; ?>
<?php // ── M3: edit + re-run affordances (chained-cron; nothing is synchronous) ── ?>
<div class="prab-stage-rerun-footer">
<?php if ( ! empty( $rerun['editable'] ) ) : ?>
<button type="button" class="button prab-edit-toggle"
aria-expanded="false" aria-controls="<?php echo $section_id; // Already escaped. ?>-edit">
<?php esc_html_e( 'Edit input', 'prautoblogger' ); ?>
</button>
<?php elseif ( ! empty( $rerun['edit_reason'] ) ) : ?>
<span class="prab-edit-unavailable"><?php echo esc_html( (string) $rerun['edit_reason'] ); ?></span>
<?php endif; ?>
<?php if ( ! empty( $rerun['rerun_from'] ) ) : ?>
<button type="button" class="button prab-rerun-from<?php echo $is_stale ? ' prab-rerun-from--stale' : ''; ?>"
data-stage="<?php echo esc_attr( $stage_name ); ?>">
<?php esc_html_e( 'Re-run from here', 'prautoblogger' ); ?>
</button>
<?php endif; ?>
</div>
<?php
if ( ! empty( $rerun['editable'] ) ) {
require PRAUTOBLOGGER_PLUGIN_DIR . 'templates/admin/dossier-edit-panel.php';
}
?>
</div><!-- .prab-dossier-section--stage -->

View File

@@ -103,6 +103,18 @@ $bulk_type = isset( $_GET['prautoblogger_bulk_type'] ) ? sanitize_text_field( wp
</span>
</div>
<?php
// v0.20.0: stale-stage warning chips (one batched query; CPO
// product AC -- the stale flag is visible at publish time).
$rq_post_runs = array();
foreach ( $query->posts as $rq_post ) {
$rq_run_id = (string) get_post_meta( $rq_post->ID, '_prautoblogger_run_id', true );
if ( '' !== $rq_run_id ) {
$rq_post_runs[ $rq_post->ID ] = $rq_run_id;
}
}
$rq_stale_runs = array_flip( PRAutoBlogger_Run_Stage_State::runs_with_stale_stages( array_values( $rq_post_runs ) ) );
?>
<table class="widefat striped prautoblogger-queue-table">
<thead>
<tr>
@@ -137,6 +149,11 @@ $bulk_type = isset( $_GET['prautoblogger_bulk_type'] ) ? sanitize_text_field( wp
</td>
<td>
<strong><?php the_title(); ?></strong>
<?php if ( isset( $rq_post_runs[ $post_id ], $rq_stale_runs[ $rq_post_runs[ $post_id ] ] ) ) : ?>
<a class="prab-review-stale-chip" href="<?php echo esc_url( PRAutoBlogger_Dossier_Page::url_for_post( (int) $post_id ) ); ?>">
<?php esc_html_e( 'Stale stages -- review dossier', 'prautoblogger' ); ?>
</a>
<?php endif; ?>
<?php if ( $editor_notes ) : ?>
<div class="prautoblogger-editor-notes"><?php echo esc_html( $editor_notes ); ?></div>
<?php endif; ?>