diff --git a/PLAN-spring-clean-2026-08-06.md b/PLAN-spring-clean-2026-08-06.md index 08f0b2c..d398ea3 100644 --- a/PLAN-spring-clean-2026-08-06.md +++ b/PLAN-spring-clean-2026-08-06.md @@ -171,3 +171,91 @@ start from the live state. visible`). The report/evidence commit follows this append; no GitHub push. - **Evidence:** `SPRING-MAPFEED-2026-08-06.png` shows the live map with a real message-first feed card and compact secondary metadata. + +## Wave 3 investigation and implementation design — items 10/12/14 (2026-08-06) + +### Item 10 — observation register: resolved by absence + +The frontend route table contains the public pages `/`, `/install`, `/stats`, +`/feed`, `/repeater`, `/companion`, `/docs`, `/open-source`, `/spam`, +`/topology`, and `/login`, plus redirects for `/regions`, `/about`, `/mqtt`, +and `/status`; it contains no `/register` or `/observations` page. The navigation +has no observation/register entry. A case-insensitive search of `frontend/src` +found only data/UI uses of “observations” in topology, packet detail, feed path +models, and map statistics, plus the observer-station registration form embedded +in `UKCompanionPage`; none is a dedicated observation register page. Stats, +repeater search, and owner portal are ordinary existing pages with no hidden +observation-register route. Per the item definition, this is resolved by absence; +no page was invented or changed. + +### Item 12 — Spam Watch: keep + +Live host baseline at `http://127.0.0.1:3000/api/spam/messages/status` returned +HTTP 200 JSON: +`{"ongoing":false,"activeIncidents":0,"totalIncidents":0,"messagesLast24h":0,"observersInvolved":0,"lastIncidentAt":null,"updatedAt":"2026-08-06T12:49:27.141Z"}`. +The companion request used by the page, +`/api/spam/messages/incidents?limit=200`, also returned HTTP 200 JSON with +`filters` (`status: all`, `minConfidence: 0.5`, `limit: 200`, `offset: 0`), +`returned: 0`, and an empty `incidents` array. A Googlebot Playwright run against +`https://ukmesh.com/spam` observed HTTP 200 JSON for both page API requests, +rendered the `Spam Watch` heading and `No ongoing spam detected`, and rendered +no alert. This is a useful, live transparency page even when the current result +is an empty/clean state, so it will remain unchanged unless final verification +finds a regression. + +### Item 14 — Health page removal: designed frontend-only + +Remove `frontend/src/pages/StatusPage.tsx`; remove its lazy import and element, +the `health` route component type, `/health` content route, and the obsolete +`/status -> /health` redirect. Remove `showHealth` from `SiteLayout` and its +`/health` navigation item, and remove the now-unused `showHealth` prop from +`UKLayout`. The separate `healthcheck.ukmesh.com` external link and the +`~/ukmesh/meshcore-health-check` application are explicitly out of scope and +will not be touched. Acceptance evidence is a zero-result `rg -F '/health' +frontend` search plus a public browser assertion that `/health` renders the +normal not-found state and no Health nav entry, while `/spam` retains its clean +live state. + +No backend change is planned. After implementation, run the required frontend +quality gates, build both real-tagged images, compare their shipped bundles, +deploy only the services whose bundle changed, and then perform the required +health/HopReach/API/browser checks before recording the screenshot, digests, and +local commit below. + +## Wave 3 completion — items 10/12/14 (2026-08-06) + +- **[DONE] Item 10 — observation register:** resolved by absence. The route + manifest and nav have no `/register` or `/observations` page; the frontend + search found only ordinary observation data fields and the embedded observer + station registration form. No page was invented. +- **[DONE] Item 12 — Spam Watch:** kept. Host curl returned HTTP 200 with + `ongoing: false`, zero active/total incidents, zero messages in the last 24h, + zero observers involved, and a current `updatedAt`; the incidents endpoint + returned HTTP 200 with a valid empty list. Googlebot Playwright against the + live public URL received HTTP 200 JSON for both requests and rendered the + heading plus `No ongoing spam detected` with no alert. +- **[DONE] Item 14 — Health page removal:** deleted `StatusPage.tsx`, its lazy + route wiring, `/health` manifest entry, `/status -> /health` redirect, nav + item/flag, unused status CSS, and stale E2E page checks. Direct `/health` + page-link searches in `frontend` are empty. The external Health Check link + and separate health-check app remain out of scope. +- **Quality gates:** frontend `npx tsc --noEmit`, `npm test` (77/77), + `npm run lint:css`, and `npm run build` all pass. No backend files changed. +- **Deployment:** built both `spring-pages` tags. The new not-found marker was + present in both asset sets, so `app-ukmesh` and `website-ukmesh` were both + deployed; no backend service was changed. Final app digest: + `sha256:5f278c9796c67402c644961555feb8a52c4ee6eb4cc1f41c94b9b79276c80b55`; + final website digest: + `sha256:56885f493cca824e2e7cb7ce6cda7b08748747762b00ffa84bdfb9432db9e67b`. + `.env` changed only `APP_IMAGE` and `WEBSITE_IMAGE`; `BACKEND_IMAGE` and + secret lines were untouched. +- **Live verification:** `/api/health` was healthy, HopReach returned 200, + `/health` and `/status` rendered the normal not-found state with no exact + Health nav link, and `/spam` rendered its live clean state with both API calls + at HTTP 200 and no alert. The SPA fallback returns document HTTP 200 for + unknown routes; the rendered not-found state proves the page route is gone. +- **Evidence:** `SPRING-PAGES-2026-08-06.png` contains the real Googlebot + captures for the removed `/health` state and retained `/spam` state. Summary: + `SPRING-PAGES-2026-08-06.md`. +- **Local commit:** to be recorded after the final scoped commit; no GitHub + push. diff --git a/SPRING-PAGES-2026-08-06.md b/SPRING-PAGES-2026-08-06.md new file mode 100644 index 0000000..2111b05 --- /dev/null +++ b/SPRING-PAGES-2026-08-06.md @@ -0,0 +1,82 @@ +# UKMesh Spring Pages — 2026-08-06 + +Scope: items 10, 12, and 14 on `/home/ben/ukmesh/meshcore-analytics`, branch +`main`. The separate `~/ukmesh/meshcore-health-check` application was not +modified. No GitHub push was performed. + +## Decisions + +### Item 10 — observation register: resolved by absence + +There is no dedicated observation-register page. The `PUBLIC_ROUTES` manifest +contains no `/register` or `/observations` route, and the site navigation has no +observation/register entry. A case-insensitive `rg` search across `frontend/src` +found only ordinary observation data labels and models in topology, stats, +packet detail, feed paths, and map statistics, plus the observer-station +registration form embedded in `UKCompanionPage`. Stats, repeater search, and +owner portal have no hidden register page. No page was invented or changed. + +### Item 12 — Spam Watch: kept + +The live host endpoint returned HTTP 200 and a valid page-shaped payload: + +```json +{"ongoing":false,"activeIncidents":0,"totalIncidents":0,"messagesLast24h":0,"observersInvolved":0,"lastIncidentAt":null,"updatedAt":"2026-08-06T12:49:27.141Z"} +``` + +The page’s incidents request, `/api/spam/messages/incidents?limit=200`, also +returned HTTP 200 with `minConfidence: 0.5`, `limit: 200`, `returned: 0`, and an +empty `incidents` array. A Googlebot Playwright check of the real public URL +observed HTTP 200 JSON for both requests, rendered `Spam Watch` and +`No ongoing spam detected`, and rendered no alert/error element. The page is a +useful transparency surface in this valid clean state, so no Spam code change +was necessary. + +### Item 14 — Health page: removed + +Removed the page module, lazy import, route component/type, `/health` content +route, obsolete `/status -> /health` redirect, `showHealth` prop, Health nav +entry, unused UK layout flag, page-specific status CSS, and stale E2E page +fixtures/assertions. Direct page-link searches for `/health` in `frontend` +return no results. The external `https://healthcheck.ukmesh.com` Health Check +link remains intentionally because it belongs to the separate application. + +## Quality and deployment + +- `cd frontend && npx tsc --noEmit` — passed. +- `cd frontend && npm test` — 77/77 passed. +- `cd frontend && npm run lint:css` — passed. +- `cd frontend && npm run build` — passed; only existing large-chunk warnings. +- Built both requested tags: `meshcore-analytics-app:spring-pages` and + `meshcore-analytics-website:spring-pages`. +- The new not-found marker was present in both shipped asset sets, so both + `app-ukmesh` and `website-ukmesh` bundles changed and both were deployed. + No backend image/service was changed. +- Final deployed image IDs: + - app: `sha256:5f278c9796c67402c644961555feb8a52c4ee6eb4cc1f41c94b9b79276c80b55` + - website: `sha256:56885f493cca824e2e7cb7ce6cda7b08748747762b00ffa84bdfb9432db9e67b` +- `.env` was updated only for `APP_IMAGE` and `WEBSITE_IMAGE`; `BACKEND_IMAGE` + and secret/configuration lines were left unchanged. + +## Live verification + +- `GET http://127.0.0.1:3000/api/health` — HTTP 200, `status: healthy`. +- `GET http://127.0.0.1:3000/hopreach/api/nodes` — HTTP 200. +- Googlebot Playwright against `https://ukmesh.com/health` — the SPA transport + returned 200, but the rendered page was the normal `That page is not on the + mesh` not-found state and the exact `Health` nav link count was zero. +- Googlebot Playwright against `https://ukmesh.com/status` — same normal + not-found state; no legacy redirect to the removed Health page. +- Googlebot Playwright against `https://ukmesh.com/spam` — document HTTP 200; + status and incidents API responses HTTP 200 with JSON content type; heading + and clean state visible; alert count zero. + +The browser emitted the same pre-existing unauthenticated owner-session and +service-worker MIME console noise seen in the pre-deploy baseline; neither +affected page data loading or rendered state. Evidence image: +[SPRING-PAGES-2026-08-06.png](./SPRING-PAGES-2026-08-06.png). + +## Local commit + +Implementation and evidence were committed locally after verification. No +remote push was attempted. diff --git a/SPRING-PAGES-2026-08-06.png b/SPRING-PAGES-2026-08-06.png new file mode 100644 index 0000000..6f7068f Binary files /dev/null and b/SPRING-PAGES-2026-08-06.png differ diff --git a/frontend/src/config/publicRoutes.ts b/frontend/src/config/publicRoutes.ts index 0e25787..0937f94 100644 --- a/frontend/src/config/publicRoutes.ts +++ b/frontend/src/config/publicRoutes.ts @@ -5,7 +5,6 @@ export type PublicRouteComponent = | 'companion' | 'install' | 'docs' - | 'health' | 'owner' | 'open-source' | 'stats' @@ -92,13 +91,6 @@ export const PUBLIC_ROUTES: readonly PublicRouteDefinition[] = [ description: 'Explore recent viable repeater relationships, highly connected relay hubs, and observed MeshCore network topology across the UK.', sitemap: true, }, - { - path: '/health', - component: 'health', - title: 'Platform Status — UK Mesh Network', - description: 'Public platform health, packet-ingest freshness, synthetic journeys, and background-worker status for UKMesh analytics.', - sitemap: true, - }, { path: '/login', component: 'owner', @@ -109,7 +101,6 @@ export const PUBLIC_ROUTES: readonly PublicRouteDefinition[] = [ { path: '/regions', redirectTo: '/', sitemap: false }, { path: '/about', redirectTo: '/', sitemap: false }, { path: '/mqtt', redirectTo: '/install', sitemap: false }, - { path: '/status', redirectTo: '/health', sitemap: false }, ] as const; export const PUBLIC_CONTENT_ROUTES = PUBLIC_ROUTES.filter( diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index d0cc2c2..d1b1882 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -29,7 +29,6 @@ const UKCompanionPage = lazy(() => import('./pages/ukmesh/UKCompanionPage.js').t const UKBestPracticePage = lazy(() => import('./pages/ukmesh/UKBestPracticePage.js').then(({ UKBestPracticePage: Component }) => ({ default: Component }))); const SpamPage = lazy(() => import('./pages/SpamTransparencyPage.js').then(({ SpamPage: Component }) => ({ default: Component }))); const TopologyPage = lazy(() => import('./pages/TopologyPage.js').then(({ TopologyPage: Component }) => ({ default: Component }))); -const StatusPage = lazy(() => import('./pages/StatusPage.js').then(({ StatusPage: Component }) => ({ default: Component }))); const NotFoundPage = lazy(() => import('./pages/NotFoundPage.js').then(({ NotFoundPage: Component }) => ({ default: Component }))); const PUBLIC_ROUTE_ELEMENTS: Record = { @@ -39,7 +38,6 @@ const PUBLIC_ROUTE_ELEMENTS: Record = companion: , install: , docs: , - health: , owner: , 'open-source': , stats: , diff --git a/frontend/src/pages/StatusPage.tsx b/frontend/src/pages/StatusPage.tsx deleted file mode 100644 index ea5a049..0000000 --- a/frontend/src/pages/StatusPage.tsx +++ /dev/null @@ -1,256 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { LoadingIndicator } from '../components/LoadingIndicator.js'; -import { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; -import { useRuntimeFeatures } from '../config/runtimeFeatures.js'; -import { getCurrentSite } from '../config/site.js'; -import { useVisibilityPoll } from '../hooks/useVisibilityPoll.js'; -import { fetchJson, withScopeParams } from '../utils/api.js'; -import './network-intelligence.css'; - -type PublicHealthPayload = { - status: 'healthy' | 'degraded' | 'critical'; - generatedAt: string; - maintenance: { active: boolean; message: string | null }; - incidents: Array<{ code: string; severity: 'warning' | 'critical' }>; - components: { - ingest: { status: string }; - workers: { status: string }; - storage: { status: string }; - }; -}; -type FirmwarePayload = { total: number; versions: Array<{ hardware_model: string; firmware_version: string; count: number }> }; -type FirmwareDistributionRow = { - firmware_version: string; - count: number; - hardware_models: string[]; -}; - -function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -function hasStringStatus(value: unknown): value is { status: string } { - return isRecord(value) && typeof value['status'] === 'string'; -} - -function isPublicHealthPayload(value: unknown): value is PublicHealthPayload { - if (!isRecord(value) || !isRecord(value['maintenance']) || !isRecord(value['components'])) return false; - const components = value['components']; - return isRecord(value) - && (value['status'] === 'healthy' || value['status'] === 'degraded' || value['status'] === 'critical') - && typeof value['generatedAt'] === 'string' - && typeof value['maintenance']['active'] === 'boolean' - && (typeof value['maintenance']['message'] === 'string' || value['maintenance']['message'] === null) - && Array.isArray(value['incidents']) - && value['incidents'].every((incident) => ( - isRecord(incident) - && typeof incident['code'] === 'string' - && (incident['severity'] === 'warning' || incident['severity'] === 'critical') - )) - && hasStringStatus(components['ingest']) - && hasStringStatus(components['workers']) - && hasStringStatus(components['storage']); -} - -function isFirmwarePayload(value: unknown): value is FirmwarePayload { - return isRecord(value) - && typeof value['total'] === 'number' - && Array.isArray(value['versions']); -} - -function aggregateFirmwareVersions(versions: FirmwarePayload['versions']): FirmwareDistributionRow[] { - const byVersion = new Map(); - for (const row of versions) { - const firmwareVersion = row.firmware_version || 'Unknown'; - const existing = byVersion.get(firmwareVersion); - if (existing) { - existing.count += row.count; - if (!existing.hardware_models.includes(row.hardware_model)) { - existing.hardware_models.push(row.hardware_model); - } - continue; - } - byVersion.set(firmwareVersion, { - firmware_version: firmwareVersion, - count: row.count, - hardware_models: [row.hardware_model], - }); - } - return Array.from(byVersion.values()); -} - -function isUnknownFirmwareVersion(version: string): boolean { - return version.toLowerCase() === 'unknown'; -} - -function statusPresentation(status: string): { dot: string; label: string } { - switch (status) { - case 'ok': return { dot: 'ok', label: 'Operational' }; - case 'running': return { dot: 'ok', label: 'Running' }; - case 'idle': return { dot: 'ok', label: 'Ready' }; - case 'warning': return { dot: 'warning', label: 'Needs attention' }; - case 'stale': return { dot: 'warning', label: 'Delayed' }; - case 'critical': return { dot: 'critical', label: 'Critical' }; - case 'failed': return { dot: 'critical', label: 'Failed' }; - default: return { dot: 'unknown', label: 'Checking' }; - } -} - -function reportTime(generatedAt: string): string { - const value = new Date(generatedAt); - return Number.isNaN(value.getTime()) - ? 'just now' - : value.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); -} - -export const StatusPage: React.FC = () => { - const site = getCurrentSite(); - const network = site.networkFilter ?? site.network; - const observer = site.observerId; - const { privacyGeneration } = useRuntimeFeatures(); - const [data, setData] = useState(null); - const [error, setError] = useState(false); - const [firmware, setFirmware] = useState(null); - const [includeUnknownFirmware, setIncludeUnknownFirmware] = useState(false); - - useEffect(() => { - setData(null); - setFirmware(null); - setError(false); - setIncludeUnknownFirmware(false); - }, [network, observer, privacyGeneration]); - - useVisibilityPoll(async (signal) => { - const next = await fetchJson( - withScopeParams('/api/health', { network, observer }), - { cache: 'no-store', signal }, - { timeoutMs: 15_000, maxBytes: 2 * 1024 * 1024, validate: isPublicHealthPayload }, - ); - if (signal.aborted) return; - setData(next); - setError(false); - }, { - scopeKey: `platform-health:${network}:${observer ?? 'all'}:${privacyGeneration}`, - intervalMs: 60_000, - timeoutMs: 15_000, - onError: () => setError(true), - }); - - useVisibilityPoll(async (signal) => { - const next = await fetchJson( - withScopeParams('/api/repeaters/firmware', { network, observer }), - { cache: 'no-store', signal }, - { timeoutMs: 15_000, maxBytes: 2 * 1024 * 1024, validate: isFirmwarePayload }, - ); - if (!signal.aborted) setFirmware(next); - }, { - scopeKey: `firmware-health:${network}:${observer ?? 'all'}:${privacyGeneration}`, - intervalMs: 10 * 60_000, - timeoutMs: 15_000, - }); - - const firmwareDistribution = firmware ? aggregateFirmwareVersions(firmware.versions) : []; - const unknownFirmware = firmwareDistribution.find((row) => isUnknownFirmwareVersion(row.firmware_version)); - const visibleFirmwareDistribution = includeUnknownFirmware - ? firmwareDistribution - : firmwareDistribution.filter((row) => !isUnknownFirmwareVersion(row.firmware_version)); - const xAxisInterval = Math.max(0, Math.ceil(visibleFirmwareDistribution.length / 8) - 1); - - return ( -
-

Public operations

Platform status

Service health, ingest freshness, and worker state without exposing private infrastructure details.

- {!data && !error && } - {error && !data &&
Status data is currently unavailable.
} - {data && ( - <> -
- {data.status === 'healthy' ? 'All monitored systems operational' : `Platform ${data.status}`} - Updated {reportTime(data.generatedAt)} -
- {data.maintenance.active &&
Planned maintenance{data.maintenance.message ?? 'Maintenance is currently in progress.'}
} - {data.incidents.length > 0 &&

Current notices

{data.incidents.map((incident) =>
{incident.code.replace(/_/g, ' ')}{incident.severity === 'critical' ? 'A monitored service is disrupted.' : 'A monitored service needs attention.'}
)}
} -
- {([ - ['Public ingest', 'Packet intake and freshness', data.components.ingest.status], - ['Background workers', 'Scheduled processing services', data.components.workers.status], - ['Storage', 'Durable platform storage', data.components.storage.status], - ] as const).map(([title, description, status]) => { - const presentation = statusPresentation(status); - return ( -
-

{title}

-
-
- - {presentation.label} - {description} -
-
-
- ); - })} -
-
-

Repeater firmware distribution

- {firmware && firmware.versions.length > 0 ? ( - <> - {unknownFirmware && ( -

- -

- )} - {visibleFirmwareDistribution.length > 0 ? ( - <> - - - - - - [value.toLocaleString(), 'Repeaters']} /> - - - -
- - - - - {visibleFirmwareDistribution.map((row) => ( - - - - - - ))} - -
Repeater firmware distribution{includeUnknownFirmware ? '' : ' (Unknown excluded)'}
FirmwareHardware modelsRepeaters
{row.firmware_version}{row.hardware_models.join(', ')}{row.count}
-
- - ) : ( -

Only repeaters with unknown firmware were reported. Select "Include Unknown" to show that bucket.

- )} - - ) :

Firmware telemetry is not yet available.

} -
-

Status values are deliberately aggregated. Hostnames, addresses, credentials, and private node identities are never included.

- - )} -
- ); -}; diff --git a/frontend/src/pages/network-intelligence.css b/frontend/src/pages/network-intelligence.css index 2966022..f6b8bf9 100644 --- a/frontend/src/pages/network-intelligence.css +++ b/frontend/src/pages/network-intelligence.css @@ -69,45 +69,6 @@ .topology-page__validation-table-head { color: var(--text-muted) !important; font: 11px var(--font-mono); letter-spacing: 0.06em; text-transform: uppercase; } .topology-page__validation-table strong { color: var(--amber); font: 11px var(--font-mono); text-transform: uppercase; } -.status-page { padding-top: 48px; padding-bottom: 64px; } -.status-page > header { margin-bottom: 22px; } -.status-page > header h1 { margin: 4px 0 8px; } -.status-page > header > p:last-child { color: var(--text-secondary); } -.status-page__banner { display: flex; justify-content: space-between; gap: 15px; padding: 14px 18px; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--bg-panel); } -.status-page__banner strong { color: var(--text-primary); } -.status-page__banner span { color: var(--text-muted); font: 10px var(--font-mono); } -.status-page__banner--healthy { border-color: rgba(0, 230, 118, 0.45); } -.status-page__banner--degraded { border-color: rgba(255, 179, 0, 0.55); } -.status-page__banner--critical { border-color: rgba(255, 23, 68, 0.6); color: #fca5a5; } -.status-page__maintenance { display: flex; gap: 16px; margin-top: 10px; padding: 11px 16px; border-radius: var(--radius); background: var(--amber-dim); color: var(--amber); } -.status-page__maintenance span { color: var(--text-secondary); } -.status-page__problems { margin-top: 20px; } -.status-page__problems h2 { margin-bottom: 8px; font-size: 15px; } -.status-page__problem { display: flex; gap: 14px; padding: 9px 12px; border-left: 3px solid var(--amber); background: var(--bg-panel); } -.status-page__problem + .status-page__problem { margin-top: 4px; } -.status-page__problem strong { min-width: 180px; color: var(--amber); font: 10px var(--font-mono); text-transform: uppercase; } -.status-page__problem span { color: var(--text-secondary); font-size: 11px; } -.status-page__problem--critical { border-left-color: var(--danger); } -.status-page__problem--critical strong { color: var(--danger); } -.status-page__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 20px; } -.status-page__grid > section { padding: 18px; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--bg-panel); } -.status-page__grid h2 { margin: 0 0 12px; font-size: 14px; } -.status-page__grid dl div { display: flex; justify-content: space-between; gap: 12px; padding: 7px 0; border-bottom: 1px solid var(--border); } -.status-page__grid dt { color: var(--text-muted); } -.status-page__grid dd { color: var(--text-primary); font-family: var(--font-mono); } -.status-page__checks > div { display: grid; grid-template-columns: 10px minmax(0, 1fr) auto; align-items: center; gap: 8px; padding: 7px 0; border-bottom: 1px solid var(--border); } -.status-page__checks strong { overflow: hidden; color: var(--text-secondary); font: 11px var(--font-mono); text-overflow: ellipsis; text-transform: uppercase; white-space: nowrap; } -.status-page__checks small { color: var(--text-muted); } -.status-page__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-muted); } -.status-page__dot--ok { background: var(--online); box-shadow: 0 0 8px rgba(0, 230, 118, 0.45); } -.status-page__dot--warning, -.status-page__dot--stale, -.status-page__dot--unknown { background: var(--amber); } -.status-page__dot--critical, -.status-page__dot--failed, -.status-page__dot--disabled { background: var(--danger); } -.status-page__privacy { margin-top: 16px; color: var(--text-muted); font-size: 11px; } - @media (max-width: 860px) { .topology-page__workspace { grid-template-columns: 1fr; } } @@ -122,16 +83,4 @@ .topology-page__validation-head { flex-direction: column; gap: 8px; } .topology-page__validation-stats { grid-template-columns: 1fr 1fr; } .topology-page__validation-table [role='row'] { grid-template-columns: 1fr 1fr; } - .status-page { padding-top: 28px; } - .status-page__banner, - .status-page__maintenance, - .status-page__problem { flex-direction: column; gap: 5px; } - .status-page__grid { grid-template-columns: 1fr; } -} -.status-page__firmware { - margin-top: 18px; - padding: 18px; - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: var(--bg-panel); } diff --git a/frontend/src/pages/shared/SiteLayout.tsx b/frontend/src/pages/shared/SiteLayout.tsx index 7fe5b94..e510df4 100644 --- a/frontend/src/pages/shared/SiteLayout.tsx +++ b/frontend/src/pages/shared/SiteLayout.tsx @@ -10,7 +10,6 @@ type SiteLayoutProps = { showAbout?: boolean; showInstall?: boolean; showMqtt?: boolean; - showHealth?: boolean; showOpenSource?: boolean; showBestPractice?: boolean; showPackets: boolean; @@ -48,7 +47,6 @@ export const SiteLayout: React.FC = ({ showAbout = true, showInstall = true, showMqtt = true, - showHealth = true, showOpenSource = true, showBestPractice = false, showPackets, @@ -81,7 +79,6 @@ export const SiteLayout: React.FC = ({ { to: '/about', label: 'What is MeshCore', enabled: showAbout }, { to: '/install', label: 'Install', enabled: showInstall }, { to: '/mqtt', label: 'MQTT', enabled: showMqtt }, - { to: '/health', label: 'Health', enabled: showHealth }, { to: '/packets', label: 'Packets', enabled: showPackets }, { to: '/open-source', label: 'Open Source', enabled: showOpenSource }, { to: '/stats', label: 'Stats', enabled: showStats }, diff --git a/frontend/src/pages/ukmesh/UKLayout.tsx b/frontend/src/pages/ukmesh/UKLayout.tsx index db9d729..30f9b6c 100644 --- a/frontend/src/pages/ukmesh/UKLayout.tsx +++ b/frontend/src/pages/ukmesh/UKLayout.tsx @@ -27,7 +27,6 @@ export const UKLayout: React.FC = () => { showInstall showBestPractice showMqtt={false} - showHealth showPackets={false} showOpenSource={false} showStats={false} diff --git a/frontend/test/e2e/accessibility.spec.ts b/frontend/test/e2e/accessibility.spec.ts index 28fceb7..019c261 100644 --- a/frontend/test/e2e/accessibility.spec.ts +++ b/frontend/test/e2e/accessibility.spec.ts @@ -91,21 +91,6 @@ async function fulfillApi(route: Route) { }, }); } - if (pathname === '/api/health') { - return route.fulfill({ - json: { - status: 'healthy', - generatedAt: '2026-07-18T12:00:00Z', - maintenance: { active: false, message: null }, - incidents: [], - components: { - ingest: { status: 'ok' }, - workers: { status: 'running' }, - storage: { status: 'ok' }, - }, - }, - }); - } if (pathname === '/api/topology') { return route.fulfill({ json: { @@ -229,7 +214,6 @@ const publicRoutes = [ '/companion', '/install', '/docs', - '/health', '/login', '/open-source', '/stats', diff --git a/frontend/test/e2e/mobile.spec.ts b/frontend/test/e2e/mobile.spec.ts index 33756d4..51d81c7 100644 --- a/frontend/test/e2e/mobile.spec.ts +++ b/frontend/test/e2e/mobile.spec.ts @@ -62,18 +62,6 @@ const chartStats = { }, }; -const health = { - status: 'healthy', - generatedAt: '2026-07-18T12:00:00Z', - maintenance: { active: false, message: null }, - incidents: [], - components: { - ingest: { status: 'ok' }, - workers: { status: 'running' }, - storage: { status: 'ok' }, - }, -}; - const repeaterNodes = Array.from({ length: 12 }, (_, index) => ({ node_id: `node-${index}`, name: `Repeater ${String(index + 1).padStart(2, '0')}`, @@ -109,7 +97,6 @@ async function installApiFixtures(page: Page) { }, }); } - if (pathname === '/api/health') return route.fulfill({ json: health }); if (pathname === '/api/repeaters/firmware') { return route.fulfill({ json: { @@ -168,7 +155,6 @@ const routedViews = [ ['UK install', 'http://127.0.0.1:4173/install'], ['UK open source', 'http://127.0.0.1:4173/open-source'], ['UK stats', 'http://127.0.0.1:4173/stats'], - ['UK health', 'http://127.0.0.1:4173/health'], ['UK login', 'http://127.0.0.1:4173/login'], ['UK docs', 'http://127.0.0.1:4173/docs'], ['test site home', 'http://127.0.0.1:4175/'], diff --git a/frontend/test/e2e/public.spec.ts b/frontend/test/e2e/public.spec.ts index e02c149..e499ff2 100644 --- a/frontend/test/e2e/public.spec.ts +++ b/frontend/test/e2e/public.spec.ts @@ -105,22 +105,6 @@ test('public site exposes its primary journeys', async ({ page }) => { }, }); }); - await page.route('**/api/health**', async (route) => { - await route.fulfill({ - json: { - status: 'healthy', - generatedAt: '2026-07-11T12:00:00Z', - maintenance: { active: false, message: null }, - incidents: [], - components: { - ingest: { status: 'ok' }, - workers: { status: 'running' }, - storage: { status: 'ok' }, - }, - }, - }); - }); - await page.goto('/'); await expect(page.getByRole('heading', { name: 'UK Mesh Network' })).toBeVisible(); await expect(page.getByRole('link', { name: 'Open live map' }).first()).toBeVisible(); @@ -142,15 +126,7 @@ test('public site exposes its primary journeys', async ({ page }) => { await expect(page.getByLabel('Geographic repeater topology graph')).toBeVisible(); await expect(page.getByRole('group', { name: '2 positioned repeaters and 1 links' })).toBeVisible(); - await page.getByRole('link', { name: 'Health', exact: true }).click(); - await expect(page.getByRole('heading', { name: 'Platform status' })).toBeVisible(); - await expect(page.getByText('All monitored systems operational')).toBeVisible(); - await expect(page.getByText('Status data is currently unavailable.')).toHaveCount(0); - await expect(page.getByRole('heading', { name: 'Public ingest' })).toBeVisible(); - const statusAccessibility = await new AxeBuilder({ page }).analyze(); - expect(statusAccessibility.violations - .filter((violation) => violation.impact === 'critical' || violation.impact === 'serious') - .map((violation) => ({ id: violation.id, targets: violation.nodes.map((node) => node.target.join(' ')) }))).toEqual([]); + await expect(page.getByRole('link', { name: 'Health', exact: true })).toHaveCount(0); }); // Guards issue #3 (contrast): the feed chrome renders the muted/secondary text