feat: Pipeline Settings M3 — assembled-instructions preview + prompt version history/diff (#173, v0.25.0)

Read-only assembled-instructions preview (last-run rendered prompt) with Template-vs-Preview guardrail; per-prompt version history + LCS diff. Stage-list-driven. QA APPROVE df92ed3; CI green.

Agent-Session: pipeline-settings-m3-merge
This commit is contained in:
peptiderepo
2026-06-23 13:33:18 +07:00
committed by GitHub
parent 8b789b3f1b
commit 2569b08862
16 changed files with 1634 additions and 107 deletions

View File

@@ -1,24 +1,30 @@
<?php
/**
* Pipeline Settings page template.
* Pipeline Settings page template (M3).
*
* Variables injected by PRAutoBlogger_Pipeline_Settings_Renderer::render():
* array $view['steps'] — ordered step definitions from Step_Map.
* array $view['active_step'] — the currently selected step definition.
* array $view['save_result'] — {status, message} from the save handler.
* string $view['nonce_field'] — nonce field name.
* string $view['nonce_action'] — nonce action string.
* string $view['page_slug'] — pipeline page slug.
* array $view['step_data'] — assembled view data for the active step.
* float $view['monthly_spend']— current month's LLM spend in USD.
* float $view['budget'] — configured monthly budget cap in USD.
* array $view['global_fields']— field defs + current values for global context.
* string $view['step_context'] — context id matching the active step, or null.
* array $view['step_fields'] — field defs + current values for active step context.
* array $view['steps'] -- ordered step definitions from Step_Map.
* array $view['active_step'] -- the currently selected step definition.
* array $view['save_result'] -- {status, message} from the save handler.
* string $view['nonce_field'] -- nonce field name.
* string $view['nonce_action'] -- nonce action string.
* string $view['page_slug'] -- pipeline page slug.
* array $view['step_data'] -- assembled view data for the active step.
* float $view['monthly_spend'] -- current month's LLM spend in USD.
* float $view['budget'] -- configured monthly budget cap in USD.
* array $view['global_fields'] -- field defs + current values for global context.
* string $view['step_context'] -- context id matching the active step, or null.
* array $view['step_fields'] -- field defs + current values for active step context.
* string $view['preview_nonce'] -- (M3) nonce for preview AJAX.
* string $view['history_nonce'] -- (M3) nonce for history AJAX.
* string $view['diff_nonce'] -- (M3) nonce for diff AJAX.
* string $view['preview_action'] -- (M3) AJAX action name for preview.
* string $view['history_action'] -- (M3) AJAX action name for history.
* string $view['diff_action'] -- (M3) AJAX action name for diff.
*
* @see admin/class-pipeline-settings-renderer.php — Provides $view.
* @see templates/admin/pipeline-settings-prompt-panel.php — Prompt editor partial.
* @see templates/admin/pipeline-settings-step-options.php — Step option fields partial.
* @see admin/class-pipeline-settings-renderer.php -- Provides $view.
* @see templates/admin/pipeline-settings-prompt-panel.php -- Prompt editor partial.
* @see templates/admin/pipeline-settings-step-options.php -- Step option fields partial.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
@@ -84,7 +90,7 @@ $base_url = admin_url( 'admin.php?page=' . rawurlencode( $page_slug ) );
<?php endif; ?>
<?php
// ── Global Content Context block (editable — M2) ─────────────────────────
// -- Global Content Context block (editable -- M2) -----------------
$context = 'global';
$section_title = __( 'Global Content Context', 'prautoblogger' );
$step_id = 'global';
@@ -117,7 +123,7 @@ $base_url = admin_url( 'admin.php?page=' . rawurlencode( $page_slug ) );
</div>
<?php
// ── Step option fields (M2) ───────────────────────────────────────
// -- Step option fields (M2) ----------------------------------------
if ( null !== $step_context && ! empty( $step_fields ) ) :
$context = $step_context;
$section_title = __( 'Step Settings', 'prautoblogger' );

View File

@@ -1,26 +1,29 @@
<?php
/**
* Partial: Pipeline Settings — single prompt editor panel.
* Partial: Pipeline Settings — single prompt editor panel (M3).
*
* Variables passed by pipeline-settings-page.php:
* array $panel — prompt panel data from Renderer::build_prompt_panel_data().
* string $nonce_action — nonce action string.
* string $nonce_field — nonce field name.
* string $step_id — active step id (for the form action URL).
* string $base_url — admin.php?page=prautoblogger-pipeline base URL.
* string $panel_title — human-readable title for this panel's h3.
* string $writing_mode — (optional) current writing pipeline mode for Writer step.
* bool $is_writer — whether this is the Writer step.
* array $panel -- prompt panel data from Renderer::build_prompt_panel_data().
* string $nonce_action -- nonce action string (save form).
* string $nonce_field -- nonce field name (save form).
* string $step_id -- active step id (for the form action URL).
* string $base_url -- admin.php?page=prautoblogger-pipeline base URL.
* string $panel_title -- human-readable title for this panel's h3.
* string $writing_mode -- (optional) current writing pipeline mode for Writer step.
* bool $is_writer -- whether this is the Writer step.
* array $view -- full view array (carries preview/history nonces).
*
* @see templates/admin/pipeline-settings-page.php — Includes this partial.
* @see templates/admin/pipeline-settings-page.php -- Includes this partial.
* @see ajax/class-pipeline-preview-handler.php -- Serves preview AJAX.
* @see ajax/class-pipeline-history-handler.php -- Serves history/diff AJAX.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$key = $panel['key'];
$css_key = str_replace( '.', '-', $key );
$form_key = str_replace( '.', '-', $key );
$key = $panel['key'];
$css_key = str_replace( '.', '-', $key );
$form_key = str_replace( '.', '-', $key );
// Writer-step inactive detection.
$is_inactive = false;
@@ -31,6 +34,14 @@ if ( isset( $is_writer ) && $is_writer ) {
$is_inactive = ( 'single_pass' === $writing_mode && in_array( $key, $multi_step_keys, true ) )
|| ( 'multi_step' === $writing_mode && in_array( $key, $single_pass_keys, true ) );
}
$versions = $panel['versions'] ?? array();
$has_history = count( $versions ) > 1;
$history_id = 'pab-history-' . $css_key;
$diff_id = 'pab-diff-' . $css_key;
$preview_id = 'pab-preview-' . $css_key;
$template_id = 'pab-template-' . $css_key;
$toggle_hint_id = 'pab-toggle-hint-' . $css_key;
?>
<div class="pab-section">
<h3 class="pab-section-title">
@@ -76,32 +87,159 @@ if ( isset( $is_writer ) && $is_writer ) {
</p>
<?php endif; ?>
<form method="post"
action="<?php echo esc_url( add_query_arg( 'step', $step_id, $base_url ) ); ?>"
class="pab-prompt-form">
<?php wp_nonce_field( $nonce_action, $nonce_field ); ?>
<input type="hidden" name="pipeline_action" value="save_prompt" />
<input type="hidden" name="prompt_key" value="<?php echo esc_attr( $form_key ); ?>" />
<textarea name="prompt_body"
id="pab-prompt-<?php echo esc_attr( $css_key ); ?>"
class="pab-prompt-editor"
rows="10"
spellcheck="false"><?php echo esc_textarea( $panel['body'] ); ?></textarea>
<div class="pab-prompt-actions">
<button type="submit" class="ab-btn ab-btn-secondary ab-btn-sm">
<?php esc_html_e( 'Save (creates new version)', 'prautoblogger' ); ?>
<!-- M3: Template / Preview toggle -->
<div class="pab-tp-toggle-row">
<div class="pab-tp-toggle" role="group"
aria-label="<?php esc_attr_e( 'Template or preview mode', 'prautoblogger' ); ?>"
data-prompt-key="<?php echo esc_attr( $form_key ); ?>"
data-preview-id="<?php echo esc_attr( $preview_id ); ?>"
data-template-id="<?php echo esc_attr( $template_id ); ?>"
data-toggle-hint-id="<?php echo esc_attr( $toggle_hint_id ); ?>"
data-ajax-url="<?php echo esc_attr( admin_url( 'admin-ajax.php' ) ); ?>"
data-preview-action="<?php echo esc_attr( $view['preview_action'] ); ?>"
data-preview-nonce="<?php echo esc_attr( $view['preview_nonce'] ); ?>">
<button type="button"
class="pab-tp-btn pab-tp-btn--active"
data-mode="template"
aria-pressed="true">
<?php esc_html_e( 'Template', 'prautoblogger' ); ?>
</button>
<?php if ( ! $panel['is_default'] ) : ?>
<button type="submit"
name="pipeline_action"
value="reset_prompt"
class="ab-btn ab-btn-outline ab-btn-sm pab-btn-reset"
onclick="return confirm('<?php esc_attr_e( 'Reset to factory default? This creates a new version.', 'prautoblogger' ); ?>');">
<?php esc_html_e( 'Reset to default', 'prautoblogger' ); ?>
<button type="button"
class="pab-tp-btn"
data-mode="preview"
aria-pressed="false">
<?php esc_html_e( 'Preview assembled instructions', 'prautoblogger' ); ?>
</button>
<?php endif; ?>
</div>
</form>
<span id="<?php echo esc_attr( $toggle_hint_id ); ?>" class="pab-tp-hint">
<?php esc_html_e( 'Editing the template affects all future runs.', 'prautoblogger' ); ?>
</span>
</div>
<!-- Template view (editable) -->
<div id="<?php echo esc_attr( $template_id ); ?>" class="pab-tp-view pab-tp-view--template">
<form method="post"
action="<?php echo esc_url( add_query_arg( 'step', $step_id, $base_url ) ); ?>"
class="pab-prompt-form">
<?php wp_nonce_field( $nonce_action, $nonce_field ); ?>
<input type="hidden" name="pipeline_action" value="save_prompt" />
<input type="hidden" name="prompt_key" value="<?php echo esc_attr( $form_key ); ?>" />
<div class="pab-prompt-editor-wrap">
<div class="pab-prompt-editor-toolbar">
<span class="pab-prompt-editor-title">
<?php
printf(
/* translators: %s = prompt registry key */
esc_html__( 'Template — %s', 'prautoblogger' ),
esc_html( $key )
);
?>
</span>
</div>
<textarea name="prompt_body"
id="pab-prompt-<?php echo esc_attr( $css_key ); ?>"
class="pab-prompt-editor"
rows="10"
spellcheck="false"><?php echo esc_textarea( $panel['body'] ); ?></textarea>
</div>
<div class="pab-prompt-actions">
<button type="submit" class="ab-btn ab-btn-secondary ab-btn-sm">
<?php esc_html_e( 'Save (creates new version)', 'prautoblogger' ); ?>
</button>
<?php if ( ! $panel['is_default'] ) : ?>
<button type="submit"
name="pipeline_action"
value="reset_prompt"
class="ab-btn ab-btn-outline ab-btn-sm pab-btn-reset"
onclick="return confirm('<?php esc_attr_e( 'Reset to factory default? This creates a new version.', 'prautoblogger' ); ?>');">
<?php esc_html_e( 'Reset to default', 'prautoblogger' ); ?>
</button>
<?php endif; ?>
</div>
</form>
</div>
<!-- Preview view (read-only, M3) -->
<div id="<?php echo esc_attr( $preview_id ); ?>"
class="pab-tp-view pab-tp-view--preview"
style="display:none;">
<div class="pab-preview-block">
<div class="pab-preview-header">
<span class="pab-preview-label"><?php esc_html_e( 'Preview — assembled instructions', 'prautoblogger' ); ?></span>
<span class="pab-preview-readonly-badge"><?php esc_html_e( 'Read-only', 'prautoblogger' ); ?></span>
<span class="pab-preview-source-note js-preview-source"></span>
</div>
<pre class="pab-preview-body js-preview-body"><span class="pab-preview-loading"><?php esc_html_e( 'Loading preview…', 'prautoblogger' ); ?></span></pre>
<div class="pab-preview-notice">
<span class="dashicons dashicons-info-outline"></span>
<span><?php echo wp_kses( __( '<strong>Preview only — not editable.</strong> This is the fully-rendered prompt the LLM will receive, with tokens filled from the last run. To change the template body, switch to the Template view.', 'prautoblogger' ), array( 'strong' => array() ) ); ?></span>
</div>
</div>
</div>
<!-- M3: Version history accordion -->
<?php if ( count( $versions ) > 0 ) : ?>
<div class="pab-history-accordion">
<button type="button"
class="pab-history-trigger"
aria-expanded="false"
aria-controls="<?php echo esc_attr( $history_id ); ?>">
<span class="dashicons dashicons-arrow-down-alt2 pab-history-arrow"></span>
<?php
printf(
/* translators: %s = prompt registry key */
esc_html__( 'Version history — %s', 'prautoblogger' ),
esc_html( $key )
);
?>
</button>
<div id="<?php echo esc_attr( $history_id ); ?>"
class="pab-history-body"
style="display:none;">
<?php foreach ( $versions as $ver ) : ?>
<?php
$is_current = (bool) $ver['active'];
$ver_num = (int) $ver['version'];
?>
<div class="pab-version-row">
<span class="pab-version-num">v<?php echo esc_html( (string) $ver_num ); ?></span>
<span class="pab-version-author"><?php echo esc_html( (string) $ver['author'] ); ?></span>
<span class="pab-version-date"><?php echo esc_html( (string) $ver['created_at'] ); ?></span>
<?php if ( $is_current ) : ?>
<span class="pab-badge pab-badge-ok pab-version-current"><?php esc_html_e( 'current', 'prautoblogger' ); ?></span>
<?php endif; ?>
<?php if ( $ver_num > 1 ) : ?>
<button type="button"
class="ab-btn ab-btn-outline ab-btn-sm pab-diff-btn"
data-prompt-key="<?php echo esc_attr( $form_key ); ?>"
data-version-a="<?php echo esc_attr( (string) ( $ver_num - 1 ) ); ?>"
data-version-b="<?php echo esc_attr( (string) $ver_num ); ?>"
data-diff-target="<?php echo esc_attr( $diff_id ); ?>"
data-ajax-url="<?php echo esc_attr( admin_url( 'admin-ajax.php' ) ); ?>"
data-diff-action="<?php echo esc_attr( $view['diff_action'] ); ?>"
data-diff-nonce="<?php echo esc_attr( $view['diff_nonce'] ); ?>">
<?php esc_html_e( 'Diff', 'prautoblogger' ); ?>
</button>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- M3: Diff display area (hidden until a Diff button is clicked) -->
<div id="<?php echo esc_attr( $diff_id ); ?>"
class="pab-diff-panel"
style="display:none;">
<div class="pab-diff-header js-diff-header"></div>
<div class="pab-diff-block js-diff-lines"></div>
<button type="button"
class="ab-btn ab-btn-outline ab-btn-sm pab-diff-close"
data-diff-id="<?php echo esc_attr( $diff_id ); ?>">
<?php esc_html_e( 'Close diff', 'prautoblogger' ); ?>
</button>
</div>
<?php endif; ?>
</div>