From ac5bb71b61a1aa817a6b798f140d8e805a654457 Mon Sep 17 00:00:00 2001 From: shankar0123 Date: Thu, 14 May 2026 19:43:14 +0000 Subject: [PATCH] =?UTF-8?q?feat(discovery):=20close=20P-M1=20=E2=80=94=20i?= =?UTF-8?q?n-flight=20scan=20progress=20panel=20on=20DiscoveryPage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes frontend-design-audit finding P-M1 (Med): DiscoveryPage doesn't show real-time scan progress — operator who just kicked off a scan must navigate to NetworkScanPage to see if it's running Operator choice (2026-05-14): poll-and-render over SSE / WebSocket. Rationale recorded in the source comment: zero new transport infrastructure to maintain; reuses the existing TanStack Query plumbing. SSE / WebSocket were the alternative paths but neither is currently used anywhere else in the codebase (grep -rn "text/event-stream|EventSource|websocket" returned zero hits), so adopting one for a single Medium finding would be disproportionate. ═══════════════════════════ CHANGES ═══════════════════════════════ web/src/pages/DiscoveryPage.tsx: • Dropped the `enabled: showScans` gate on the ['discovery-scans'] query. The query is now always-on, so the new in-flight panel has data to render without operator interaction. • Refetch cadence flips between 2.5s and 30s via a function-shape refetchInterval that introspects the query's most-recent data: anyInFlight = scans.some(s => !s.completed_at) return anyInFlight ? 2500 : 30000 domain.DiscoveryScan.CompletedAt is *time.Time (nullable pointer) — nil while the agent is still scanning, set when the agent posts its DiscoveryReport. When the last running scan finishes, the next 2.5s tick sees no in-flight rows and the interval flips back to 30s automatically. • Derived `inFlightScans = scans.data.filter(!completed_at)` — drives both the visibility gate (panel doesn't render when empty) and the row count badge. • New panel renders ABOVE the existing summary tiles: - Amber background, animated ping dot, role=status + aria-live= polite so screen readers announce status changes. - "{N} scan(s) in progress" header + per-scan row showing agent_id, directories count, started_at (formatDateTime), and certificates_found-so-far. - data-testid hooks: discovery-inflight-panel + discovery-inflight-row- for QA + future Playwright. No backend changes — getDiscoveryScans() endpoint already returns the complete DiscoveryScan shape including the nullable completed_at field. The closure is pure frontend. ═══════════════════════════ AUDIT FRAMING ════════════════════════ The audit said "real-time scan progress" but the operator chose the practical interpretation — sub-3-second update latency for an operator visiting the page, not push-based streaming. The poll cadence is high enough that an operator clicking from NetworkScanPage to DiscoveryPage sees in-flight signal within the first refetch tick (the dashboard's pre-existing 30s polling drops to 2.5s the moment the first in-flight scan is observed). ═══════════════════════════ VERIFICATION ═══════════════════════════ • npx tsc --noEmit — exit 0 • npx vitest run DiscoveryPage AuditPage — 7/7 pass • npx vite build — built in 3.31s • CI guards: no-raw-table baseline 17/17, no-unbound-label 134/134, no-raw-toLocaleString clean (the new