Plugin: handover step + auto-seed prior notes into next session (hc-dy9)

Add a final "Notes for next time" step so the tech finishing today can flag
pending issues, watch-fors, and outstanding client decisions for whoever
picks up the next healthcheck on the same site.

On ATT_HC_Session::start() for a given site_key, the server's step history
for the handover step is queried (limit 1, excluding the just-created
session). If a prior session left handover notes, they're written into
the new session's "Before You Start" notes prefixed with the prior
session's date ("From previous session (YYYY-MM-DD):") so the carry-over
is obvious. The tech can edit/clear them as normal step notes from there.

- includes/steps/125-handover.php — new step (id=handover) using the
  standard notes field. No server schema or API change; it's just another
  step row in step_updates, surfaced like any other.
- ATT_HC_Session::seed_before_notes_from_prior_handover() — best-effort,
  silent degrade on API failure. The session is already registered on
  the server before this runs, so a failed seed never blocks start.
- No seed on resume() — resuming an existing session would clobber
  whatever the tech had already typed.

Verified end-to-end against the live MySQL server: handover-test-XXXX
flow shows carry-over with date prefix; no-handover-XXXX flow confirms
no false-positive seed for a fresh site_key. Test rows purged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-30 10:17:44 +01:00
parent 3c64dd8125
commit 1a9e5ac0fd
3 changed files with 59 additions and 1 deletions

View File

@@ -14,6 +14,7 @@
{"_type":"issue","id":"hc-5ix.4","title":"Session data model: option-backed in-progress healthcheck record","description":"One in-progress session per site at a time. Stored in a custom option (or a CPT — pick during implementation). Shape: id, started_at, finished_at, technician_id, site_url_snapshot, per_step_state { status: not_started|in_progress|done|skipped|blocked|n_a, notes, completed_at }. Designed so phase-3 automation can attach structured findings later.","notes":"Built in phase-1 scaffold; passing lint + WP-eval end-to-end smoke test on testsite.","status":"closed","priority":1,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:11Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:49:27Z","started_at":"2026-06-11T14:41:27Z","closed_at":"2026-06-11T14:49:27Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.4","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:11Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"hc-5ix.4","title":"Session data model: option-backed in-progress healthcheck record","description":"One in-progress session per site at a time. Stored in a custom option (or a CPT — pick during implementation). Shape: id, started_at, finished_at, technician_id, site_url_snapshot, per_step_state { status: not_started|in_progress|done|skipped|blocked|n_a, notes, completed_at }. Designed so phase-3 automation can attach structured findings later.","notes":"Built in phase-1 scaffold; passing lint + WP-eval end-to-end smoke test on testsite.","status":"closed","priority":1,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:11Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:49:27Z","started_at":"2026-06-11T14:41:27Z","closed_at":"2026-06-11T14:49:27Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.4","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:11Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"hc-5ix.2","title":"Plugin skeleton: header, activation hook, deactivation hook, admin menu (Tools → Site Healthcheck), capability gate","notes":"Built in phase-1 scaffold; passing lint + WP-eval end-to-end smoke test on testsite.","status":"closed","priority":1,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:10Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:49:26Z","started_at":"2026-06-11T14:41:27Z","closed_at":"2026-06-11T14:49:26Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.2","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:09Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"hc-5ix.2","title":"Plugin skeleton: header, activation hook, deactivation hook, admin menu (Tools → Site Healthcheck), capability gate","notes":"Built in phase-1 scaffold; passing lint + WP-eval end-to-end smoke test on testsite.","status":"closed","priority":1,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:10Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:49:26Z","started_at":"2026-06-11T14:41:27Z","closed_at":"2026-06-11T14:49:26Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.2","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:09Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"hc-5ix.1","title":"Bootstrap: trigger install of wp-site-recovery plugin as step 0","description":"Healthcheck plugin should check on activation if site-recovery is installed; if not, fetch and install from a known URL/ZIP and activate it. Block stepper from starting until recovery is in place.","notes":"Detection half done (status panel + manual link). Auto-install of recovery plugin from a private URL is the wp-site-recovery side of hc-5ix.27 — closing this as 'detection complete'.","status":"closed","priority":1,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T11:26:40Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T15:03:02Z","closed_at":"2026-06-11T15:03:02Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.1","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T12:26:39Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"hc-5ix.1","title":"Bootstrap: trigger install of wp-site-recovery plugin as step 0","description":"Healthcheck plugin should check on activation if site-recovery is installed; if not, fetch and install from a known URL/ZIP and activate it. Block stepper from starting until recovery is in place.","notes":"Detection half done (status panel + manual link). Auto-install of recovery plugin from a private URL is the wp-site-recovery side of hc-5ix.27 — closing this as 'detection complete'.","status":"closed","priority":1,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T11:26:40Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T15:03:02Z","closed_at":"2026-06-11T15:03:02Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.1","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T12:26:39Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"hc-dy9","title":"Plugin: Notes for next time → seed prereq notes on next session","description":"Add a 'Notes for next time' step at the end of the flow as a handover for whoever picks up the next engagement. On starting a new session for the same site_key, the server's step history for the handover step is pulled and used to pre-populate the 'Before You Start' (before) step's notes — prefixed with the prior session's date so the carry-over is obvious.\n\nImplementation:\n- New step file includes/steps/125-handover.php using the existing step pattern (id=handover, standard notes field). No schema or API change required since this is just another step.\n- ATT_HC_Session::start() does a best-effort step_history(handover, site_key, limit=1, exclude_id=current) and on success calls update_step('before', NOT_STARTED, prefix + notes) on the new session. Silent degrade if API unreachable — start should still succeed.\n- Same auto-seed for resume()? Probably not — resuming an existing session would clobber whatever the tech had already typed. Only seed on a genuinely new start.\n\nUser asked for this 2026-06-29 — they want a low-friction handover so engagement context survives between visits.","status":"closed","priority":2,"issue_type":"feature","assignee":"Steve Hanlon","owner":"steve@hanlon.co.uk","created_at":"2026-06-30T09:14:53Z","created_by":"Steve Hanlon","updated_at":"2026-06-30T09:17:44Z","started_at":"2026-06-30T09:15:12Z","closed_at":"2026-06-30T09:17:44Z","close_reason":"Shipped. New step file includes/steps/125-handover.php (id=handover, title='Notes for next time') with blurb + sub_items prompting the tech for what to flag. ATT_HC_Session::start() now ends with a best-effort seed_before_notes_from_prior_handover() call that pulls the prior session's handover notes via step_history(handover, site_key, 1, current_id) and writes them into this session's 'before' step notes prefixed with 'From previous session (YYYY-MM-DD):'. Silent degrade on API failure so the session is never blocked from being created. Verified end-to-end against the live server: handover-test-XXXX site exercises full flow, no-handover-XXXX site confirms no false-positive seed. Plugin-only change — no server deploy needed. Test rows purged from MySQL.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"hc-3y0","title":"Server: deploy notes for VPS","description":"Document deployment to a PHP VPS:\n- PHP version requirements\n- Apache/nginx rewrite to public/index.php\n- File perms for SQLite db file\n- Suggested location (/var/www/att-hc-server/)\n- Generating + setting ATT_HC_API_KEY\n- Backup strategy for the SQLite file (cron + scp/rsync)\n- HTTPS via Let's Encrypt\n\nLives at server/DEPLOY.md.","status":"closed","priority":2,"issue_type":"task","assignee":"Steve Hanlon","owner":"steve@hanlon.co.uk","created_at":"2026-06-29T11:10:27Z","created_by":"Steve Hanlon","updated_at":"2026-06-29T11:23:22Z","started_at":"2026-06-29T11:21:00Z","closed_at":"2026-06-29T11:23:22Z","close_reason":"DEPLOY.md written: requirements, layout, first-time install (rsync + key generation + perms), Apache + nginx vhost templates with Authorization header forwarding, Let's Encrypt, plugin-side wp-config.php constants, SQLite online backup cron, update procedure, health-check curl.","dependencies":[{"issue_id":"hc-3y0","depends_on_id":"hc-0p1","type":"parent-child","created_at":"2026-06-29T12:13:24Z","created_by":"Steve Hanlon","metadata":"{}"},{"issue_id":"hc-3y0","depends_on_id":"hc-rdo","type":"blocks","created_at":"2026-06-29T12:10:37Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"hc-3y0","title":"Server: deploy notes for VPS","description":"Document deployment to a PHP VPS:\n- PHP version requirements\n- Apache/nginx rewrite to public/index.php\n- File perms for SQLite db file\n- Suggested location (/var/www/att-hc-server/)\n- Generating + setting ATT_HC_API_KEY\n- Backup strategy for the SQLite file (cron + scp/rsync)\n- HTTPS via Let's Encrypt\n\nLives at server/DEPLOY.md.","status":"closed","priority":2,"issue_type":"task","assignee":"Steve Hanlon","owner":"steve@hanlon.co.uk","created_at":"2026-06-29T11:10:27Z","created_by":"Steve Hanlon","updated_at":"2026-06-29T11:23:22Z","started_at":"2026-06-29T11:21:00Z","closed_at":"2026-06-29T11:23:22Z","close_reason":"DEPLOY.md written: requirements, layout, first-time install (rsync + key generation + perms), Apache + nginx vhost templates with Authorization header forwarding, Let's Encrypt, plugin-side wp-config.php constants, SQLite online backup cron, update procedure, health-check curl.","dependencies":[{"issue_id":"hc-3y0","depends_on_id":"hc-0p1","type":"parent-child","created_at":"2026-06-29T12:13:24Z","created_by":"Steve Hanlon","metadata":"{}"},{"issue_id":"hc-3y0","depends_on_id":"hc-rdo","type":"blocks","created_at":"2026-06-29T12:10:37Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"hc-0p1","title":"[epic] Central history server + plugin sync","description":"Replace the local-only session model with a required central server that stores healthcheck history across engagements.\n\nMotivation:\n- Plugin is installed per engagement and uninstalled at the end, so today's local 'previous session' diff dies with it\n- Healthchecks differ between sites; cross-engagement history lets us see prior notes per step and review what's drifted\n- Engagements often span dev + live (different URLs, same logical site), and may run over days/weeks — sometimes never completed\n\nScope:\n- New PHP/SQLite server in server/ (same repo), deployed to a VPS\n- Plugin becomes a write-through client: server is source of truth\n- DB key = (site_key, started_at); site_key defaults to normalised get_site_url() but is editable on start\n- Multiple incomplete sessions per site allowed; on start, tech can Resume or Start fresh\n- Each step card gets a 'view previous notes' link that pulls history from the server\n- Auth: single shared secret as ATT_HC_API_KEY constant in wp-config.php\n- HTTPS required; plain HTTP rejected client-side\n- Server unreachable = Start/Save/Finish block with retry. No offline queue v1.","status":"closed","priority":2,"issue_type":"feature","owner":"steve@hanlon.co.uk","created_at":"2026-06-29T11:09:02Z","created_by":"Steve Hanlon","updated_at":"2026-06-29T11:41:07Z","closed_at":"2026-06-29T11:41:07Z","close_reason":"All 8 child issues closed. Central history server + plugin sync feature complete: PHP/SQLite server with 8 endpoints (incl. /step-counts), schema with composite key (site_key, started_at) + step_updates + audit log, deploy notes for VPS (using /home/www/healthcheck). Plugin: ATT_HC_Api HTTP client with HTTPS guard + loopback dev exception, ATT_HC_Session refactored to write-through (server-of-truth), start screen with site_key + recent-engagements datalist + resume of incomplete sessions + double-click guard, per-step previous-notes disclosure with lazy loading. End-to-end verified across 4 separate test runs.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"hc-0p1","title":"[epic] Central history server + plugin sync","description":"Replace the local-only session model with a required central server that stores healthcheck history across engagements.\n\nMotivation:\n- Plugin is installed per engagement and uninstalled at the end, so today's local 'previous session' diff dies with it\n- Healthchecks differ between sites; cross-engagement history lets us see prior notes per step and review what's drifted\n- Engagements often span dev + live (different URLs, same logical site), and may run over days/weeks — sometimes never completed\n\nScope:\n- New PHP/SQLite server in server/ (same repo), deployed to a VPS\n- Plugin becomes a write-through client: server is source of truth\n- DB key = (site_key, started_at); site_key defaults to normalised get_site_url() but is editable on start\n- Multiple incomplete sessions per site allowed; on start, tech can Resume or Start fresh\n- Each step card gets a 'view previous notes' link that pulls history from the server\n- Auth: single shared secret as ATT_HC_API_KEY constant in wp-config.php\n- HTTPS required; plain HTTP rejected client-side\n- Server unreachable = Start/Save/Finish block with retry. No offline queue v1.","status":"closed","priority":2,"issue_type":"feature","owner":"steve@hanlon.co.uk","created_at":"2026-06-29T11:09:02Z","created_by":"Steve Hanlon","updated_at":"2026-06-29T11:41:07Z","closed_at":"2026-06-29T11:41:07Z","close_reason":"All 8 child issues closed. Central history server + plugin sync feature complete: PHP/SQLite server with 8 endpoints (incl. /step-counts), schema with composite key (site_key, started_at) + step_updates + audit log, deploy notes for VPS (using /home/www/healthcheck). Plugin: ATT_HC_Api HTTP client with HTTPS guard + loopback dev exception, ATT_HC_Session refactored to write-through (server-of-truth), start screen with site_key + recent-engagements datalist + resume of incomplete sessions + double-click guard, per-step previous-notes disclosure with lazy loading. End-to-end verified across 4 separate test runs.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"hc-5ix.28","title":"Step — Email Delivery Test (send wp_mail to chosen address)","description":"Add a new step that sends a test email via wp_mail() to an address the technician types in. Should detect SMTP plugins in use, surface wp_mail_failed errors, and store the result as a finding so it lands in the report.\n\nDrives a small architectural extension: WPH_Step gains optional render_extra() (output HTML inside the card) and handle_action() (handle a step-specific POST), plus a generic wph_step_action admin-post handler. Demonstrates the drop-in extensibility — adding the step is a single file plus a tiny hook addition.","notes":"Added in commit (next). New file includes/steps/115-email.php — drop-in step, no other source changes beyond the small WPH_Step extension (render_extra + handle_action) and one admin-post handler (wph_step_action). Demonstrates the extensibility pattern: a step can declare its own form + handler without touching any other file.\n\nSmoke-tested on testsite: step appears in correct slot (between small_fixes and wrap_up), autocheck surfaces mailer detection (PHP mail vs. 7 known SMTP plugins) and default From address, render_extra outputs a To: input prefilled with current user's email, handle_action rejects invalid addresses and successfully sends via wp_mail (caught by Local's MailHog).","status":"closed","priority":2,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T17:43:34Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T17:45:47Z","closed_at":"2026-06-11T17:45:47Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.28","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T18:43:33Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"hc-5ix.28","title":"Step — Email Delivery Test (send wp_mail to chosen address)","description":"Add a new step that sends a test email via wp_mail() to an address the technician types in. Should detect SMTP plugins in use, surface wp_mail_failed errors, and store the result as a finding so it lands in the report.\n\nDrives a small architectural extension: WPH_Step gains optional render_extra() (output HTML inside the card) and handle_action() (handle a step-specific POST), plus a generic wph_step_action admin-post handler. Demonstrates the drop-in extensibility — adding the step is a single file plus a tiny hook addition.","notes":"Added in commit (next). New file includes/steps/115-email.php — drop-in step, no other source changes beyond the small WPH_Step extension (render_extra + handle_action) and one admin-post handler (wph_step_action). Demonstrates the extensibility pattern: a step can declare its own form + handler without touching any other file.\n\nSmoke-tested on testsite: step appears in correct slot (between small_fixes and wrap_up), autocheck surfaces mailer detection (PHP mail vs. 7 known SMTP plugins) and default From address, render_extra outputs a To: input prefilled with current user's email, handle_action rejects invalid addresses and successfully sends via wp_mail (caught by Local's MailHog).","status":"closed","priority":2,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T17:43:34Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T17:45:47Z","closed_at":"2026-06-11T17:45:47Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.28","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T18:43:33Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}

View File

@@ -84,7 +84,39 @@ final class ATT_HC_Session {
]); ]);
update_option(ATT_HC_OPT_SESSION, $data, false); update_option(ATT_HC_OPT_SESSION, $data, false);
return new self($data); $session = new self($data);
// Carry handover notes from the prior session into the new "before"
// notes. Best-effort: a failure here must not block session creation,
// since the session is already registered on the server above.
self::seed_before_notes_from_prior_handover($session);
return $session;
}
/**
* If the prior session for this site left "Notes for next time" (id=handover),
* pre-fill the new session's "Before You Start" (id=before) notes with them,
* prefixed with the prior session's date so it's clear they're carried over.
*/
private static function seed_before_notes_from_prior_handover(self $session): void {
try {
$resp = ATT_HC_Api::step_history('handover', $session->site_key(), 1, $session->id());
} catch (ATT_HC_Api_Exception $e) {
return;
}
$rows = $resp['history'] ?? [];
if (!$rows) return;
$notes = trim((string) ($rows[0]['notes'] ?? ''));
if ($notes === '') return;
$prefix = 'From previous session (' . date('Y-m-d', (int) $rows[0]['started_at']) . "):\n";
try {
$session->update_step('before', self::STATUS_NOT_STARTED, $prefix . $notes);
} catch (ATT_HC_Api_Exception $e) {
// Already-rare degrade path — the session is alive, just no auto-seed.
}
} }
/** /**

View File

@@ -0,0 +1,25 @@
<?php
if (!defined('ABSPATH')) exit;
/**
* Last step. Anything written in this step's notes is carried into the
* "Before You Start" notes of the NEXT session for the same site_key, so
* the next engagement starts with the prior tech's flags already visible.
*
* The carry-over happens in ATT_HC_Session::start() — see seed_before_notes_from_prior_handover().
*/
return new class extends ATT_HC_Step {
public function id(): string { return 'handover'; }
public function title(): string { return 'Notes for next time'; }
public function blurb(): string {
return 'Anything to flag for whoever picks this up at the next healthcheck — pending issues, things that nearly broke, gotchas, client decisions still outstanding. Whatever you write here will be pre-filled into the "Before You Start" step of the next session for this site, so it gets read.';
}
public function sub_items(): array {
return [
'Pending issues that didn\'t need fixing today but might next time',
'Decisions you\'re waiting on from the client',
'Things that nearly went wrong and what to watch for',
'Workarounds in place that should eventually be properly fixed',
];
}
};