Fix step status+notes save (forms were nested in HTML)

The step card's save form wrapped the entire card, including the
autocheck 'Run checks' button and per-step extras like the email
'Send' form — those are also <form> elements. Nested forms are
invalid HTML; browsers silently drop the outer form's submit when
they see an inner one.

So clicking 'Save step' was effectively a no-op: the POST to
wph_save_step never reached the server, and the status reverted to
'not started' on reload. The bug had been there since the autocheck
panel was introduced in v0.3 of the healthcheck.

Restructured the step card as a <div> wrapper containing three
siblings: (1) static content (title, blurb, sub-items, watch-outs,
escalation), (2) the autocheck panel + step render_extra (each with
their own forms), (3) the save form. No nesting anywhere.

Verified end-to-end on testsite: step status and notes now persist
across page loads. The email step (which has 3 sibling forms — refresh
checks, send test, save) also renders without nesting.
This commit is contained in:
2026-06-12 10:29:33 +01:00
parent b9dad0163b
commit 8ff3a6ec31

View File

@@ -272,10 +272,7 @@ function wph_render_step_card(WPH_Session $session, WPH_Step $step): void {
$status = $state['status']; $status = $state['status'];
$notes = $state['notes']; $notes = $state['notes'];
?> ?>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" class="wph-step" id="step-<?php echo esc_attr($step->id()); ?>"> <div class="wph-step" id="step-<?php echo esc_attr($step->id()); ?>">
<?php wp_nonce_field('wph_save_step_' . $step->id()); ?>
<input type="hidden" name="action" value="wph_save_step">
<input type="hidden" name="step" value="<?php echo esc_attr($step->id()); ?>">
<header> <header>
<h2><?php echo esc_html($step->title()); ?></h2> <h2><?php echo esc_html($step->title()); ?></h2>
<span class="wph-step-status wph-status-<?php echo esc_attr($status); ?>"><?php echo esc_html(str_replace('_', ' ', $status)); ?></span> <span class="wph-step-status wph-status-<?php echo esc_attr($status); ?>"><?php echo esc_html(str_replace('_', ' ', $status)); ?></span>
@@ -303,8 +300,19 @@ function wph_render_step_card(WPH_Session $session, WPH_Step $step): void {
<?php if ($status === WPH_Session::STATUS_BLOCKED && ($esc = $step->escalation())): ?> <?php if ($status === WPH_Session::STATUS_BLOCKED && ($esc = $step->escalation())): ?>
<div class="wph-escalation"><?php echo esc_html($esc); ?></div> <div class="wph-escalation"><?php echo esc_html($esc); ?></div>
<?php endif; ?> <?php endif; ?>
<?php wph_render_autocheck($session, $step); ?>
<?php $step->render_extra($session->data()); ?> <?php
// Inner forms (autocheck refresh, step-specific extras like email send)
// are rendered as siblings of the save form — never nested. Nested forms
// are invalid HTML; browsers drop the outer form's submit silently.
wph_render_autocheck($session, $step);
$step->render_extra($session->data());
?>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" class="wph-step-save">
<?php wp_nonce_field('wph_save_step_' . $step->id()); ?>
<input type="hidden" name="action" value="wph_save_step">
<input type="hidden" name="step" value="<?php echo esc_attr($step->id()); ?>">
<p> <p>
<label> <label>
<strong>Status:</strong> <strong>Status:</strong>
@@ -320,12 +328,13 @@ function wph_render_step_card(WPH_Session $session, WPH_Step $step): void {
<textarea name="notes" placeholder="What did you check, find, fix, or flag?"><?php echo esc_textarea($notes); ?></textarea> <textarea name="notes" placeholder="What did you check, find, fix, or flag?"><?php echo esc_textarea($notes); ?></textarea>
</p> </p>
<p> <p>
<button class="button">Save step</button> <button class="button button-primary">Save step</button>
<?php if ($state['updated_at']): ?> <?php if ($state['updated_at']): ?>
<span class="description">Last saved <?php echo esc_html(human_time_diff($state['updated_at'], time())); ?> ago</span> <span class="description">Last saved <?php echo esc_html(human_time_diff($state['updated_at'], time())); ?> ago</span>
<?php endif; ?> <?php endif; ?>
</p> </p>
</form> </form>
</div>
<?php <?php
} }