From 1d674808bb2dc5034344c4ca576ad2e824a5c32e Mon Sep 17 00:00:00 2001 From: Steve Hanlon Date: Mon, 24 Aug 2026 12:14:26 +0100 Subject: [PATCH] Dashboard: hide archived sessions from the All-sites list (hc-7vo) Follow-up to the archive feature: archived healthchecks showed in both the main All-sites list and the Archived section. allSitesSummary() now excludes archived sessions from the aggregate (and from the per-site "latest session" detail), so a site whose sessions are all archived drops off the main list and appears only under "Archived". A site with a mix keeps its row, counting only its live sessions. The per-site page still lists every session. Also bumps the server version baseline in config.php.example to 0.1.8. 2 new tests (51 total). Co-Authored-By: Claude Opus 4.8 (1M context) --- .beads/issues.jsonl | 1 + server/config.php.example | 2 +- server/src/Store.php | 21 +++++++++++++++------ tests/server_test.php | 36 +++++++++++++++++++++++++++++++++--- 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 3389176..0148344 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -16,6 +16,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.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-7vo","title":"Dashboard: hide archived sites from the All-sites list","description":"Follow-up to hc-b8r: archived healthchecks appeared in both the main All-sites list and the Archived section. Exclude archived sessions from allSitesSummary so a site with only archived sessions drops off the main list and shows only under Archived.","status":"open","priority":2,"issue_type":"feature","owner":"steve@hanlon.co.uk","created_at":"2026-08-24T11:14:10Z","created_by":"Steve Hanlon","updated_at":"2026-08-24T11:14:10Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"hc-b8r","title":"Dashboard: archive healthchecks + Archived section on homepage","description":"Some audits are no longer needed. Add an archive toggle on the per-site dashboard page (/dashboard?site=X); archived healthchecks are also listed on the homepage under an 'Archived' heading at the bottom. Server/dashboard only — not exposed to the plugin. No hiding of archived items elsewhere for now.","status":"closed","priority":2,"issue_type":"feature","owner":"steve@hanlon.co.uk","created_at":"2026-08-24T10:57:26Z","created_by":"Steve Hanlon","updated_at":"2026-08-24T11:02:58Z","closed_at":"2026-08-24T11:02:58Z","close_reason":"Implemented: archive toggle on the per-site dashboard page + Archived section on the homepage. Migration 0003, Store::setArchived/archivedHealthchecks, CSRF-guarded POST /dashboard/healthchecks/{id}/archive. 7 new tests.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"hc-66c","title":"AJAX step saving on the healthcheck reporting page","description":"The step cards currently save via a full-page POST, which reloads the page and breaks the technician's flow. Switch to AJAX: notes save on blur, status saves on change, with inline save feedback and no reload.","status":"closed","priority":2,"issue_type":"feature","owner":"steve@hanlon.co.uk","created_at":"2026-07-27T07:21:00Z","created_by":"Steve Hanlon","updated_at":"2026-07-27T07:27:23Z","closed_at":"2026-07-27T07:27:23Z","close_reason":"Implemented in 0.1.5: AJAX auto-save on the step cards — status saves on change, notes on blur, inline feedback, no reload. Progressive enhancement; no-JS form POST retained as fallback.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"hc-fbd","title":"Next healthcheck due date per site","description":"Technician can set 'next healthcheck due' while working a session; stored on the server against the healthcheck row, surfaced in the dashboard site list and session views so it is visible when planning work.","acceptance_criteria":"Plugin UI sets/clears a date on the active session; server persists + validates YYYY-MM-DD; dashboard shows next-due per site (derived from latest session) and per session; report includes it; tests pass.","status":"closed","priority":2,"issue_type":"feature","owner":"steve@hanlon.co.uk","created_at":"2026-07-23T10:50:25Z","created_by":"Steve Hanlon","updated_at":"2026-07-23T11:04:08Z","closed_at":"2026-07-23T11:04:08Z","close_reason":"Implemented and released in 0.1.4: server column + validation + dashboard views, plugin date control, 42-test suite. Commits 6313857, 55990be.","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/server/config.php.example b/server/config.php.example index 5b1dc25..632adba 100644 --- a/server/config.php.example +++ b/server/config.php.example @@ -28,5 +28,5 @@ return [ 'db_pass' => 'REPLACE_WITH_DB_PASSWORD', // Server version, surfaced on GET /. Bump on deploy. - 'version' => '0.1.0', + 'version' => '0.1.8', ]; diff --git a/server/src/Store.php b/server/src/Store.php index 0931c61..0664fbc 100644 --- a/server/src/Store.php +++ b/server/src/Store.php @@ -222,10 +222,17 @@ final class Store { /** * Summary of all sites for the dashboard: one row per site_key, * ordered by most-recently-active first. - * `next_due` is taken from the most recent session, not from the most recent - * session that happens to have one set. If the latest visit didn't schedule a - * return, the site genuinely has nothing scheduled — carrying an older date - * forward would show a visit as overdue immediately after it was completed. + * + * Archived sessions are excluded from the aggregate, so a site whose sessions + * are all archived drops off this list entirely (it still shows, per session, + * under the dashboard's "Archived" heading). Every column — counts, latest + * dates, next_due — reflects only the site's non-archived sessions. + * + * `next_due` is taken from the most recent (non-archived) session, not from + * the most recent one that happens to have one set. If the latest visit didn't + * schedule a return, the site genuinely has nothing scheduled — carrying an + * older date forward would show a visit as overdue immediately after it was + * completed. * * @return array */ @@ -237,16 +244,18 @@ final class Store { SUM(CASE WHEN finished_at IS NOT NULL THEN 1 ELSE 0 END) AS finished, MAX(started_at) AS last_started_at FROM healthchecks + WHERE archived = 0 GROUP BY site_key ORDER BY MAX(updated_at) DESC' ); $rows = $stmt->fetchAll(); - // Fetch the reporting_url and finished_at from the most recent session per site. + // Fetch the reporting_url and finished_at from the most recent NON-archived + // session per site (the site is only here because it has at least one). $out = []; foreach ($rows as $r) { $detail = Db::pdo()->prepare( 'SELECT reporting_url, finished_at, next_due FROM healthchecks - WHERE site_key = ? ORDER BY started_at DESC LIMIT 1' + WHERE site_key = ? AND archived = 0 ORDER BY started_at DESC LIMIT 1' ); $detail->execute([$r['site_key']]); $last = $detail->fetch() ?: []; diff --git a/tests/server_test.php b/tests/server_test.php index 2385f95..34f708b 100644 --- a/tests/server_test.php +++ b/tests/server_test.php @@ -125,6 +125,12 @@ function archived_section(string $html): string { return $pos === false ? '' : substr($html, $pos); } +/** The home page up to (not including) the Archived section — i.e. the All-sites list. */ +function main_site_list(string $html): string { + $pos = strpos($html, '

from a dashboard table that mentions $needle — for precise assertions. */ function row_containing(string $html, string $needle): string { if (preg_match_all('#.*?#s', $html, $m)) { @@ -475,9 +481,10 @@ test('the per-site page marks an archived session and offers Unarchive', functio assert_contains('status-archived', $page, 'the row should carry the archived badge'); }); -test('an unarchived session still shows the normal listing (not hidden)', function () { - // "No need to hide them" — an archived session stays in its site's session list. - $site = unique_site('archive-not-hidden'); +test('an archived session still shows on its own per-site page', function () { + // Hiding is only from the home page's All-sites list — a site's own page + // still lists every session (archived ones just carry the badge). + $site = unique_site('archive-persite-visible'); $hc = create_hc($site, ['next_due' => '2027-01-01']); $token = dash_csrf('/dashboard?site=' . rawurlencode($site)); archive_hc($hc['id'], true, $token); @@ -486,6 +493,29 @@ test('an unarchived session still shows the normal listing (not hidden)', functi assert_contains('2027-01-01', $page, 'the archived session is still in the per-site table'); }); +test('a site with only archived sessions drops off the All sites list', function () { + $site = unique_site('archive-hidden-main'); + $hc = create_hc($site); + $token = dash_csrf('/dashboard?site=' . rawurlencode($site)); + archive_hc($hc['id'], true, $token); + + $home = dash('/dashboard')['body']; + assert_not_contains($site, main_site_list($home), 'gone from the All sites list'); + assert_contains($site, archived_section($home), 'but still under Archived'); +}); + +test('a site keeps its All sites row while it still has a live session', function () { + $site = unique_site('archive-mixed'); + create_hc($site); // stays active + $stale = create_hc($site); // gets archived + $token = dash_csrf('/dashboard?site=' . rawurlencode($site)); + archive_hc($stale['id'], true, $token); + + $home = dash('/dashboard')['body']; + assert_contains($site, main_site_list($home), 'still has a live session, so still listed'); + assert_contains($site, archived_section($home), 'and its archived session shows under Archived'); +}); + test('archiving an unknown healthcheck reports not found', function () { $site = unique_site('archive-unknown'); create_hc($site); // gives us a page to read a CSRF token from