Files
prautoblogger/templates/admin/dossier-edit-panel.php
peptiderepo 6ed639d1db 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
2026-06-12 14:28:38 +00:00

110 lines
4.5 KiB
PHP

<?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>