feat: Pipeline Settings M4 — generation history list + per-step I/O drill-down (v0.26.0)

CEO ask #5 complete: browsable paginated run list + full input/output inspection
per pipeline stage.

## What was built

### Generations list (prautoblogger-gen-history)
Hidden admin page (options.php-parent, priority 14) showing all pipeline runs newest
first, 20 per page. Each row: article title (linked to dossier when a post exists),
start date, status chip, distinct model(s), settled cost, duration. Queries bounded
(LIMIT/OFFSET) — no unbounded SELECT *.

New files:
- includes/admin/class-gen-history-query.php — paginated run list + per-run I/O queries
- includes/admin/class-gen-history-page.php  — hidden admin page + asset enqueue
- assets/css/gen-history.css, assets/js/gen-history.js — table + drill-down UI
- templates/admin/gen-history-page.php       — HTML template

### Per-step input + output drill-down (Stage I/O)
'Stage I/O' toggle opens an AJAX-loaded inline panel. For each generation_log stage:
  - input_system: system-role message content from generation_log.request_json
  - input_user: last user-role message from request_json (rendered instruction)
  - output: model response from run_stages.meta_json.output
  - output_pruned: true when meta_json present but output key absent (retention prune)

New AJAX endpoint: prautoblogger_gen_run_io (manage_options + nonce)
New file: includes/ajax/class-gen-run-io-handler.php

### gen-log INPUT/OUTPUT coverage verified
- INPUT: generation_log.request_json stores the chat body WITHOUT the Authorization
  header (Request_Recorder captures body after build_body(), before headers are added).
  System + user message content extracted and surfaced.
- OUTPUT: run_stages.meta_json.output is the LLM response text (Run_Stage_State::done()).
  Image/log-only stages (image_a, image_b, llm_research, image_prompt_rewrite) have no
  run_stages row; output is null, NOT pruned. Pruned outputs are flagged honestly.

### Dossier as entry point
Runs with a linked post show a 'Dossier' button deep-linking into the existing dossier
(which renders full per-stage I/O with editorial context). Stage I/O is complementary —
works for failed/orphan runs and provides the same data in a lighter inline UI.

### Stage-list-driven
Stage labels via Stage_Display_Map::label() — Phase 2b curate/seo appear automatically.

### Dead prautoblogger_pipeline_history endpoint
Not dead — it IS used by pipeline-settings.js for prompt version history (M3 feature).
Left in place. No dangling endpoint.

## M3 P2 sweep
- Removed dead data-preview-nonce and data-diff-nonce HTML attrs from
  pipeline-settings-prompt-panel.php (JS uses prabPipeline.*Nonce from
  wp_localize_script, not data-* attrs — confirmed by grep).
- Removed 3 redundant wp_create_nonce() calls from class-pipeline-settings-renderer.php
  (preview_nonce, history_nonce, diff_nonce keys that fed the now-removed dead attrs).
- Updated CONTEXT.md: added M3 + M4 terminology tables.
- Updated ARCHITECTURE.md: added ajax/ section to file tree; M3/M4 design notes.
- Updated INFORMATION-ARCHITECTURE.md: prautoblogger-gen-history entry.

## Security
- manage_options cap + prautoblogger_gen_run_io nonce on the AJAX endpoint.
- All output esc_html()/esc_url() server-side before JSON-encoding.
- JS renders prompt/response text via textContent — never innerHTML.
- No writes from history UI; request_json never contains auth headers.

## Version: v0.25.0 → v0.26.0

Agent-Session: pipeline-settings-m4
This commit is contained in:
PRAutoBlogger Engineer
2026-06-23 06:57:54 +00:00
parent 2569b08862
commit baae383c76
16 changed files with 1497 additions and 16 deletions

View File

@@ -5,6 +5,66 @@ All notable changes to PRAutoBlogger will be documented in this file.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project uses [Semantic Versioning](https://semver.org/).
## [0.26.0] - 2026-06-23
### Added
- **Pipeline Settings M4 — Generation History: run list + per-step I/O drill-down (CEO ask #5 complete):**
- **Generations list (`prautoblogger-gen-history`):** A browsable, paginated admin page (20 runs
per page, newest first). Each row shows the linked article title (when the run produced a post),
start date/time, final run status chip, distinct model(s) used, settled cost, and duration.
Linked runs surface a "Dossier" button deep-linking to the existing Article Dossier; all runs
(including failed/orphan ones) have a "Stage I/O" toggle for the inline drill-down. Queries
are bounded (LIMIT/OFFSET) — no unbounded SELECT *.
- **Per-step input AND output drill-down:** "Stage I/O" opens an inline panel (loaded via AJAX
on first click, then toggled). For each generation_log row the panel shows:
- **Input — System Prompt:** the `system`-role message content extracted from
`generation_log.request_json` (the prompt template the LLM received after rendering).
- **Input — Assembled Instruction:** the `user`-role message content (the fully token-filled
instruction the LLM received).
- **Output — Model Response:** extracted from `run_stages.meta_json.output` (the LLM's raw
text response). When pruned by the retention policy or absent for log-only stages (image_a/
image_b, llm_research, etc.), the panel says so explicitly rather than leaving a blank.
- Per-stage model, token counts, estimated cost, and response status.
- **Stage-list-driven:** Stage labels come from `Stage_Display_Map::label()` so Phase 2b
`curate`/`seo` stages appear automatically without rework.
- **Dossier as primary per-step I/O surface:** For runs that produced a post the "Dossier"
button is the recommended entry point — the dossier already renders every stage's input and
output (request payload + stage snapshot) with the full editorial context. The inline Stage I/O
panel is complementary; it works for orphan/failed runs and shows the same data in a lighter UI.
- **gen-log INPUT/OUTPUT coverage confirmed:**
- INPUT: `generation_log.request_json` stores the full JSON chat body (messages[], model,
temperature, …) assembled by `build_body()` before the `Authorization` header is added.
The `Request_Recorder` captures only the body — auth headers are never stored. System and
user message content are extracted and surfaced.
- OUTPUT: `run_stages.meta_json` holds `{output: "..."}` — the LLM's raw response text
written by `Run_Stage_State::done()`. Image/log-only stages (image_a, image_b,
llm_research, image_prompt_rewrite) have no `run_stages` row; their output is null (not
pruned). When `meta_json` is present but lacks the `output` key (pruned by the
`prautoblogger_request_json_retention_days` setting), `output_pruned = true` is returned
and the UI explains the gap honestly.
- **New files:** `includes/admin/class-gen-history-query.php`,
`includes/admin/class-gen-history-page.php`,
`includes/ajax/class-gen-run-io-handler.php`,
`templates/admin/gen-history-page.php`,
`assets/css/gen-history.css`,
`assets/js/gen-history.js`.
### Fixed (M3 P2 sweep)
- **Removed dead `data-preview-nonce` and `data-diff-nonce` HTML attributes** from
`templates/admin/pipeline-settings-prompt-panel.php`. The JS has always read nonces from
`prabPipeline.previewNonce` / `prabPipeline.diffNonce` (via `wp_localize_script` in
`Pipeline_Settings_Page::on_enqueue_assets`), not from `data-*` attrs — confirmed by grep.
Removing them eliminates redundant server-side `wp_create_nonce()` calls in the renderer.
- **Removed 3 redundant `wp_create_nonce()` calls** from
`includes/admin/class-pipeline-settings-renderer.php` (`preview_nonce`, `history_nonce`,
`diff_nonce` keys) — they were only feeding the now-removed dead `data-*` attrs.
## [0.25.0] - 2026-06-23
### Added