Feature: Node Edit Review Side Panel
# Functional Requirements
## **1. Purpose**
When an editor is working on one piece of content, they need a focused, in-page surface that shows every configured review criterion individually — with its pass / warn / fail status and the score relative to its thresholds. This document describes the **side panel** on the Node Edit screen as depicted in the comps (collapsed default, expanded review panel, and not-yet-run states).
The panel is a **diagnostic and trigger surface**: it summarizes the state of the content's review, lets the editor trigger reviews, and links out to the surfaces where reviews are acted on. Drilling into suggestions, accepting / rejecting them, and applying them are handled by the two workflow tickets named in §12.
## **2. Scope**
* A side panel on the Node Edit screen with collapsed and expanded states.
* Summary pill widget at the top (counts of pass / warn / fail, plus optional aggregate score).
* Per-category rows, each with a threshold thermometer, status icon, and a brief feedback excerpt + "View details" affordance.
* Two run actions the editor can initiate: full run (when no prior review exists) and per-category re-run (after a full run has completed at least once).
* Empty, in-progress, partial, completed, and stale states.
###
## **3. Entry workflows**
The panel must handle two distinct entry points. Behavior diverges primarily in the initial state and the available actions.
### **Workflow A — Existing entity with a prior review**
The editor opens the Node Edit screen for an entity that has stored results from a prior review.
<figure>
| **Phase** | **Panel state** |
|-----------|-----------------|
| Panel mount | Hydrates from the latest stored review for the entity. Pill widget reflects last tally. Category rows show last per-category result with thermometer and status icon. Panel renders collapsed by default; the editor expands it to see per-category rows. |
| Content unchanged since last review | All controls available. Per-category re-run is enabled on every row. Full re-run is available as a secondary action. |
| Content changed since last review | Panel surfaces a "Results may be out of date" indicator at the panel header. Last results remain visible (greyed or annotated, not removed). Both full and per-category re-runs are available. |
| Editor re-runs | See 5 (states). |
</figure>### **Workflow B — New entity, created from scratch**
The editor is creating a new content entity. No prior review results exist for it.
<figure>
<table>
<tr>
<th>
**Phase**
</th>
<th>
**Panel state**
</th>
</tr>
<tr>
<td>Before the entity is saved for the first time</td>
<td>
Panel is visible but **inert**. Empty state copy explains that the entity must be saved as a draft before a review can run. No "Run review" button.
Question... Can we run a review without a saved draft?
</td>
</tr>
<tr>
<td>After the first draft save</td>
<td>Empty "not yet run" state with a single primary "Run review" affordance. Pill widget renders in a neutral / unrun appearance. Category rows render placeholders for each configured category.</td>
</tr>
<tr>
<td>First full run completes</td>
<td>Behavior matches Workflow A's "Content unchanged" phase. Per-category re-run is now unlocked.</td>
</tr>
</table>
</figure>> **Why the panel is inert before save:** running a review requires a stable entity ID and field values that match what the review prompt will see. Reviewing un-persisted form state risks scoring content that doesn't match what will be stored. (See open question on pre-save behavior in §11.)
## **4. Layout and components**
### **4.1 Collapsed vs. expanded states**
* **Collapsed (default):** the panel header is visible (pill widget + last-run timestamp + staleness indicator if applicable). Per-category rows are hidden.
* **Expanded:** the per-category rows are revealed below the pill. State persistence (per-user, per-session, per-entity) is an open question — see §11.
### **4.2 Panel header — summary pill widget**
* Segmented pill showing counts of categories in each state: **pass / warn / fail** plus, where present, **not-yet-run** as a fourth grey segment.
* Above the pill: panel title, last-run timestamp, staleness indicator (when applicable).
### **4.3 Per-category rows**
* One row per configured review criterion.
* **Layout:** single column when ≤ 5 categories, two columns when ≥ 6. Threshold for switching is configurable; not a user choice.
* Each row contains:
* **Category label** (left).
* **Threshold thermometer** (center): horizontal bar showing the score's position relative to fail and (optional) warn / pass thresholds. Two-zone (red / green) when no warn threshold is configured; three-zone (red / yellow / green) when warn is present.
* **Status icon** (pass / warn / fail / not-run / running / stale).
* **Re-run this category** action (visible only after the first full run has completed for this entity).
## **5. States**
<figure>
| **ID** | **State** | **Trigger** | **Visible appearance** |
|--------|-----------|-------------|------------------------|
| S1 | Empty (not yet run) | New entity, never reviewed | Neutral pill, empty category rows, single "Run review" primary action |
| S2 | Running (full) | Editor clicks "Run review" | Pill shows in-flight indicator; each category row enters its own loading state and resolves independently as results arrive |
| S3 | Partial | A run in progress, some categories complete, others still running, OR a run was interrupted | Completed rows show results; in-flight rows show loaders; failed rows show a retry affordance |
| S4 | Completed | All categories returned | Pill widget reflects final tally; rows show results; "Re-run this category" available on each row |
| S5 | Stale | Entity content changed since last completed run | Same as S4 plus a staleness indicator at the panel header and on individual rows whose underlying fields changed (granular staleness is an open question — §11) |
| S6 | Pre-save (Workflow B only) | New entity, not yet saved | Inert panel, explanatory empty state, no run affordance |
</figure>**Reviews complete independently.** A category that takes 60 seconds must not block a category that completes in 5. Rows update progressively as their results arrive.
## **6. Interactions**
### **6.1 Run review (full)**
* **Visibility:** primary CTA in S1; secondary action in S4 / S5. Per the ticket comment thread, re-running is treated as a secondary action — the prominent bottom-of-panel "Run all" button has been removed in favor of per-row re-runs.
* **Behavior:** initiates one review job per configured category, in parallel. Pill widget moves to S2. Each row independently transitions to its loading state and then to its result.
* **Result replacement:** a new full run replaces prior results for **all** categories. No history is surfaced in v1.
### **6.2 Re-run a single category**
* **Visibility:** appears on each category row only after at least one full run has completed for this entity (i.e., never available in S1; always available in S4 / S5).
* **Behavior:** runs only the selected category. Other rows remain at their last result. Pill widget recomputes when the row resolves.
* **Rationale:** from the working-session discussion — expensive reviews shouldn't be repeated wholesale when the editor is iterating on a single criterion.
### **6.3 View details**
* Each category row exposes a "View details" affordance that leaves the panel and opens the accept / reject surface on the rendered node view (Ticket 3).
* This document does not specify what that surface looks like or what actions are available there. The panel's only obligation is to provide a working link with the right context (entity id, category id, current result reference).
### **6.4 Expand / collapse the panel**
* The panel can be collapsed to just its header (pill + timestamp) or expanded to show per-category rows.
* A user-initiated affordance toggles between the two.
### **6.5 Dismiss staleness indicator**
* Editor can dismiss the panel-header staleness banner without re-running. Dismissal is per-session, not persisted.
## **7. Threshold model**
* Every category declares a **fail threshold** (required)
* A **warn threshold** is optional. Categories without a warn threshold render as a two-zone bar (red / green) and never produce a yellow status — neither in the row nor in the pill.
* Three tiers is the maximum supported. This constraint keeps the shared pill widget legible.
## **8. Edge cases (panel-specific)**
* **One category configured.** Panel renders a single row. Pill widget is technically redundant but is kept for consistency with multi-category cases.
* **Category with only fail / pass thresholds.** No yellow zone rendered on that row's thermometer; that category contributes only to pass or fail segments of the pill.
* **A category was added after the last full run.** That row renders in S1 ("not yet run") state with a "Run this category" affordance, while other rows continue to show their last results. The pill shows a not-run segment for that category. The "reset all on add" alternative is **not** v1 behavior.
* **A category was removed since the configuration last changed.** Its row is hidden from the panel. Whether the underlying storage retains its prior results is an implementation decision; the panel does not render them.
* **A run is interrupted (provider failure, page reload).** Category rows that completed before the interruption show their results; the rest revert to S1 with a "Retry" affordance per row.
* **All categories fail at the provider level.** Pill shows an error indicator; panel header explains; full re-run remains available.
## **9. Data and API surface (referenced, not specified here)**
This functional doc does **not** name or assume specific entity types, classes, fields, or method signatures. The data model that backs the panel is owned by the implementation spec that follows.
What the panel needs from whatever data layer is built:
* Read access to the most recent set of per-category results for an entity (score, status, brief feedback excerpt, run timestamp).
* Read access to the configuration for each criterion (label, fail / pass / optional warn thresholds, direction, rule description).
* A way to initiate a full run and a per-category re-run.
* A way for in-progress runs to deliver results per category as they complete, rather than as a single bulk response.
Also out of scope for this functional doc:
* Provider-side prompt design per criterion.
* Queue worker behavior.
### **9.1 HTMX as a candidate progressive-update mechanism**
The panel's progressive update behavior (rows resolving independently as their reviews complete) requires a transport that can swap individual row fragments into the page without coordinating a single bulk response.
In the ticket discussion, Bruno proposed **HTMX** for this. The argument:
* HTMX is now in Drupal core, so adopting it adds no new frontend dependency.
* The panel's needs map cleanly onto HTMX's strengths: per-row polling or server-sent swaps for the loading-then-final state of each category, with no client-side state machine to maintain.
* React would be heavier than the interaction demands here. The panel is largely server-rendered with discrete row updates — exactly the shape HTMX targets.
This functional doc does not mandate HTMX. It records HTMX as the leading candidate from the working-session discussion; the implementation issue should decide between HTMX, polled Ajax, server-sent events, or a small React island. Whatever mechanism wins, it must satisfy:
* Per-category result delivery (no bulk-only response).
* Independent row updates that don't re-render the rest of the panel.
* A graceful path for a row that fails (per-row retry surface — see S3).
## **10. Acceptance criteria**
The panel is functionally complete for v1 when an editor can, on a Node Edit screen:
1. Open a brand-new entity, save a draft, and see S1 with a single "Run review" CTA.
2. Click "Run review" and watch rows resolve independently (S2 → S4) without one slow category blocking others.
3. Open an entity with prior results and see S4 hydrated from storage, including the pill tally and per-row results.
4. Edit the entity, see S5 with a staleness indicator, and choose between full re-run or per-category re-run.
5. Re-run a single category and see only that row change.
6. Collapse and expand the panel.
7. Follow the "View details" affordance on any row through to the accept / reject surface (link target validated here; the destination is verified in Ticket 3).
8. See a sensible rendering with 1 category, 5 categories, and 10 categories
## **11. Open questions**
* Granular staleness detection — does the panel know _which_ fields changed and therefore which categories' results are stale? Or is staleness always panel-wide?
* Pre-save behavior for Workflow B — is the panel truly inert until first save, or do we provide a "Run review on the form state without saving" path? The latter is more flexible but breaks the "review what's stored" guarantee.
* Collapsed-vs-expanded state persistence — should the panel remember whether it was last opened expanded or collapsed, per user / per entity / per session, or always default to collapsed on page load?
* Where exactly the panel sits on Node Edit — sidebar region, dedicated tab, or full-width below the form.
* How re-running a single category interacts with the aggregate score (recompute incrementally vs. recompute from all current per-category scores).
* How "View details" navigates to the accept / reject surface (Ticket 3) — same page, modal, separate route.
## Video / Screenshots

{width="801" height="600"}
{width=900 height=596}
{width=900 height=593}
## Claude Design - Stand-alone HTML Export File
[edit_blog_unscored_v02.standalone.html](/uploads/18a1698c105e8925caaf18ce3ab07122/edit_blog_unscored_v02.standalone.html)
issue