Skip to content

fix(frontend): treat cancelled scans as non-success in finalizeScan (MCP-2755)#700

Merged
Dumbris merged 1 commit into
mainfrom
fix/scan-cancelled-non-success
Jun 17, 2026
Merged

fix(frontend): treat cancelled scans as non-success in finalizeScan (MCP-2755)#700
Dumbris merged 1 commit into
mainfrom
fix/scan-cancelled-non-success

Conversation

@Dumbris

@Dumbris Dumbris commented Jun 17, 2026

Copy link
Copy Markdown
Member

Summary

Low-priority polish from the Codex review of PR #698 (MCP-2740, merged).

In frontend/src/utils/scanState.ts, the scan-reconcile reducer marked cancelled/canceled as terminal but not an error, so finalizeScan (ServerDetail.vue) took the success path: it reloaded a report and showed the 'Scan Complete' toast. For a scan cancelled from another tab or during a cancel/status race this is misleading, and it could surface a stale previous report (a cancelled scan may not have produced one).

Change

  • decideScanReconcile now returns a new isCancelled flag (true for cancelled/canceled), distinct from isError. Cancellation is terminal but neither error nor success.
  • finalizeScan gains a non-success cancelled branch: suppresses the 'Scan Complete' success toast, skips the report reload (avoids surfacing a stale report), and surfaces a neutral 'Scan cancelled' info toast instead — only when a scan was actually in flight in this tab (mirrors the existing wasLoading gate).

Tests

  • New cases in frontend/tests/unit/scan-state-reconcile.spec.ts: cancelled/canceled → finalize:true, isError:false, isCancelled:true; and non-cancelled statuses → isCancelled:false.
  • Full frontend unit suite: 199 passed.
  • vue-tsc --noEmit: clean. make build: green.

Related #698
Related MCP-2755

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 17, 2026

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: a0ac9cf
Status: ✅  Deploy successful!
Preview URL: https://78576b3c.mcpproxy-docs.pages.dev
Branch Preview URL: https://fix-scan-cancelled-non-succe.mcpproxy-docs.pages.dev

View logs

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

📦 Build Artifacts

Workflow Run: View Run
Branch: fix/scan-cancelled-non-success

Available Artifacts

  • archive-darwin-amd64 (28 MB)
  • archive-darwin-arm64 (25 MB)
  • archive-linux-amd64 (16 MB)
  • archive-linux-arm64 (14 MB)
  • archive-windows-amd64 (28 MB)
  • archive-windows-arm64 (25 MB)
  • frontend-dist-pr (0 MB)
  • installer-dmg-darwin-amd64 (21 MB)
  • installer-dmg-darwin-arm64 (19 MB)

How to Download

Option 1: GitHub Web UI (easiest)

  1. Go to the workflow run page linked above
  2. Scroll to the bottom "Artifacts" section
  3. Click on the artifact you want to download

Option 2: GitHub CLI

gh run download 27684628976 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

@Dumbris

Dumbris commented Jun 17, 2026

Copy link
Copy Markdown
Member Author

🤖 Codex review — REQUEST_CHANGES (1×P2)

[P2] Refresh server state after cancelled scans — frontend/src/views/ServerDetail.vue:2827-2831

The cancellation branch suppresses the report reload (correct) but returns before serversStore.fetchServers(). When cancellation is observed via polling on a view opened mid-scan, server.value.security_scan.status is still scanning from the last fetch; after clearScanRunState() sets scanLoading=false, securityScanStatus falls back to that stale store value and the Security tab keeps showing the scanning spinner/status. Refresh the server store in this branch (without reloading the report).

Reviewed via codex exec review --base origin/main.

Dumbris added a commit that referenced this pull request Jun 17, 2026
Codex P2 on PR #700: the cancelled-scan branch in finalizeScan returned
before serversStore.fetchServers(), so after clearScanRunState() dropped
scanLoading the Security tab kept deriving securityScanStatus='scanning'
from the stale store and the spinner never cleared.

Extract a pure, table-driven scanFinalizeEffects() reducer (mirrors the
MCP-2740 decideScanReconcile extraction) that encodes per-outcome side
effects. Cancellation now refreshes the store (clearing the spinner) while
still skipping the report reload and the success toast. finalizeScan
consumes the reducer; new unit cases cover cancelled/success/error effects.

Related #700
Related MCP-2755
@Dumbris

Dumbris commented Jun 17, 2026

Copy link
Copy Markdown
Member Author

Addressed the P2 in bd8b05cc. Confirmed the bug: the cancelled branch returned before serversStore.fetchServers(), so after clearScanRunState() dropped scanLoading, securityScanStatus (computed at ServerDetail.vue:1430) fell back to the stale store security_scan.status (still scanning) and the Security-tab spinner stayed up.

Fix: extracted a pure, table-driven scanFinalizeEffects() reducer in scanState.ts (mirrors the MCP-2740 decideScanReconcile extraction) that encodes the per-outcome side effects. Cancellation now sets refreshServers: true (store refreshed → spinner clears) while still reloadReport: false (no stale report) and successToast: false. finalizeScan consumes the reducer instead of inline branches.

Test: new scanFinalizeEffects (MCP-2755) describe block in scan-state-reconcile.spec.ts — cancelled→refreshServers:true+reloadReport:false+no success toast (the spinner-clear assertion), plus success/error/no-in-flight cases. Full frontend unit suite 204 passed, vue-tsc --noEmit clean, make build green.

@Dumbris

Dumbris commented Jun 17, 2026

Copy link
Copy Markdown
Member Author

🤖 Codex re-review (round 2) — REQUEST_CHANGES (1×P2)

[P2] Clear stale report when finalizing cancellations — frontend/src/views/ServerDetail.vue:2830

The store refresh is added, but if the tab was opened mid-scan loadScanReport() may have populated scanReport with the previous completed report; on cancel this branch skips the reload but leaves that stale report in place, so the Security tab still renders it once scanLoading clears. The cancelled branch must also clear/suppress scanReport.

This is the 3rd cancel-path finding. To converge, please implement the full cancel-finalize contract in one pass (see issue).

Reviewed via codex exec review --base origin/main on bd8b05cc.

Dumbris added a commit that referenced this pull request Jun 17, 2026
… on cancel

Codex round-2 P2 on PR #700: a tab opened mid-scan loads a previous report,
and a cancelled scan may produce none — so finalizeScan must also CLEAR the
rendered report on cancel, not just skip the reload. Otherwise the Security
tab renders a stale report after a cancel.

Add a clearReport effect to scanFinalizeEffects (true for cancel) and extract
applyScanFinalize() — the ordered side-effect applier — out of the component
so the full contract is test-driven against fake handlers. finalizeScan now
wires its refs/stores into applyScanFinalize.

Full cancel contract now: stopScanPolling + clearScanRunState (spinner off),
clear scanReport (no stale report), refresh server store (status not
'scanning'), 'Scan cancelled' toast, NO success toast, NO report reload.

New behavioral test: tab-open-mid-scan stale report → cancelled via poll →
asserts scanLoading=false, scanReport cleared, store refreshed, no success
toast. plus success/error contract tests.

Related #700
Related MCP-2755
@Dumbris

Dumbris commented Jun 17, 2026

Copy link
Copy Markdown
Member Author

Round-2 P2 (stale scanReport) addressed in 28d883ca — full cancel-finalize contract implemented in one pass:

On a terminal cancelled/canceled status finalizeScan now:

  1. stopScanPolling() + clearScanRunState()scanLoading=false, activeScanJobId=null (clearScanRunState already calls stopScanPolling)
  2. shows 'Scan cancelled' (info), never 'Scan Complete'
  3. does NOT reload a report
  4. CLEARS scanReport.value = null ← the new fix, so a previously-loaded stale report (tab opened mid-scan) is dropped
  5. await serversStore.fetchServers() so security_scan.status isn't left stale at scanning

Net: after cancel the Security tab shows neither spinner nor stale report, and reflects the cancelled state.

Implementation: added a clearReport effect to scanFinalizeEffects (true for cancel) and extracted applyScanFinalize() — the ordered side-effect applier — out of the component, so the contract is test-driven, not just flag-asserted.

Test (as requested): applyScanFinalize — cancel-finalize contracttab opened mid-scan (stale report loaded) → cancelled via poll asserts scanLoading=false, scanReport cleared (null, not replaced), reloadReport not called, store refreshed, no success toast, not an error. Plus success/error contract cases.

Full frontend unit suite 209 passed, vue-tsc --noEmit clean, make build green.

@Dumbris

Dumbris commented Jun 17, 2026

Copy link
Copy Markdown
Member Author

🤖 Codex re-review (round 3) — REQUEST_CHANGES (1×P2)

[P2] Render a neutral cancelled state — frontend/src/utils/scanState.ts:147 + Security template

Clearing scanReport on cancel leaves no replacement state: the header score falls back to server.security_scan (misleading 0/100) and the detail section (gated on scanReport) goes blank. Add an explicit "Scan cancelled — no report" UI state in the Security tab for the cancelled branch instead of falling back to the score/blank.

Reviewed via codex exec review --base origin/main on 28d883ca.

Dumbris added a commit that referenced this pull request Jun 17, 2026
… no blank)

Codex round-3 P2 on PR #700: clearing scanReport on cancel exposed a TEMPLATE
gap — the Security tab header fell back to server.security_scan (a misleading
0/100) and the detail section (v-if scanReport) went blank.

Add a reactive scanCancelled flag (set on every cancel path, cleared on a new
scan / success / error) plus two pure, tested visibility helpers:
- cancelledPanelVisible() → renders a neutral 'Scan cancelled' detail panel
- riskScoreVisible() → suppresses the header score after a cancel

Wire scanCancelled across all cancel paths: finalizeScan (via a new setCancelled
handler + markCancelled effect in applyScanFinalize), the in-tab Cancel button,
the loadScanReport reconcile branch, and the terminal-status safety-net watcher.

Net: after a cancelled scan the Security tab shows neither the spinner, a stale
report, nor the stale stored score — it shows an explicit 'Scan cancelled' state.

Tests: cancelledPanelVisible + riskScoreVisible pure-helper cases, setCancelled
wiring in applyScanFinalize, and an end-to-end assertion that a cancelled scan
yields cancelled-panel=visible + risk-score=hidden (not 0/100, not blank).

Related #700
Related MCP-2755
@Dumbris

Dumbris commented Jun 17, 2026

Copy link
Copy Markdown
Member Author

Round-3 P2 (template gap) addressed in ff0af9a8 — explicit cancelled UI state landed (reducer + template + test together):

Template: added a reactive scanCancelled flag and two pure, tested visibility helpers wired into the Security tab:

  • riskScoreVisible() — header score panel now v-if="showRiskScorePanel", which returns false after a cancel even when a stale server.security_scan exists → no misleading 0/100.
  • cancelledPanelVisible() — new detail branch v-else-if="showCancelledScanPanel" renders a neutral "Scan cancelled — no report" panel (data-test="scan-cancelled-panel") → not blank.

Flag lifecycle (set on every cancel path, cleared on new scan/success/error):

  • finalizeScan → via a new setCancelled handler + markCancelled effect in applyScanFinalize
  • in-tab Cancel button (cancelSecurityScan) — bypasses finalizeScan, so it now clears the report, marks cancelled, and refreshes the store directly
  • loadScanReport reconcile branch + the terminal-status safety-net watcher
  • startSecurityScan clears it

Tests: cancelledPanelVisible + riskScoreVisible pure-helper cases (incl. "stale stored scan present → score hidden"), setCancelled wiring in applyScanFinalize, and the end-to-end assertion you asked for — cancelled scan → cancelled-panel visible, risk-score hidden (not 0/100, not blank).

Full frontend unit suite 219 passed, vue-tsc --noEmit clean, make build green.

@Dumbris

Dumbris commented Jun 17, 2026

Copy link
Copy Markdown
Member Author

🤖 Codex re-review (round 4) — REQUEST_CHANGES (2×P2)

Two more cancelled-panel state edges (fresh-load skip; scanCancelled leaking across /servers/foo/servers/bar). This is the 6th cancel-path finding.

Direction change (scope reduction): the cancelled-UI-panel approach keeps uncovering state-management edges and this is a low-priority polish PR. Please revert the scanCancelled ref + the cancelled <panel> + the report-clearing, and reduce this PR to the minimal fix for the original P3 only:

  • In the cancelled branch of finalizeScan: stop polling + clear scanLoading (already done), and suppress the "Scan Complete" success toast (optionally show a transient info "Scan cancelled" toast).
  • Do not clear scanReport, do not add a cancelled panel, do not add a scanCancelled ref. Leave report/score rendering exactly as main does it (the 0/100/blank/stale-report behaviors are pre-existing, out of scope here).

Net diff ≈ the toast change only. Keep the one unit test asserting a cancelled scan does not fire the success toast.

Reviewed via codex exec review --base origin/main on ff0af9a8.

…MCP-2755)

Codex P3 from #698: the scan-reconcile reducer marked cancelled/canceled as
terminal but not an error, so finalizeScan took the success path and could fire
the 'Scan Complete' toast for a scan cancelled from another tab or during a
cancel/status race.

Minimal fix (per PR #700 review direction to avoid scope creep):
- decideScanReconcile now flags cancelled/canceled as isCancelled (terminal,
  non-success), distinct from isError.
- finalizeScan suppresses the success toast for a cancelled scan and shows a
  neutral 'Scan cancelled' info notice instead, via a small pure finalizeToastKind
  helper. Report/score rendering is left exactly as before (pre-existing, out of
  scope) — no scanReport clearing, no cancelled panel, no extra ref.

Tests: reducer flags cancelled as terminal-non-success; finalizeToastKind — a
cancelled scan does NOT fire the success toast.

Related #698
Related MCP-2755
@Dumbris Dumbris force-pushed the fix/scan-cancelled-non-success branch from ff0af9a to a0ac9cf Compare June 17, 2026 11:17
@Dumbris

Dumbris commented Jun 17, 2026

Copy link
Copy Markdown
Member Author

Agreed — reduced to the minimal original-P3 fix and collapsed the branch to a single clean commit (a0ac9cf2, force-pushed). Reverted the rabbit-hole entirely.

Reverted: scanCancelled ref, the cancelled <panel>, riskScoreVisible/cancelledPanelVisible helpers, applyScanFinalize/scanFinalizeEffects machinery, and all scanReport-clearing on cancel. The 3 touched files are now diffed against main with net +69/-7.

Kept (the whole change):

  • decideScanReconcile flags cancelled/canceled as isCancelled (terminal, non-success), distinct from isError.
  • finalizeScan cancelled path: clearScanRunState() (stops polling + clears scanLoading, already there) + suppresses the 'Scan Complete' toast, showing a neutral 'Scan cancelled' info notice instead — via a tiny pure finalizeToastKind() helper.
  • Report/score rendering left exactly as main — no scanReport clearing, no panel, no ref. Every cancelled-panel state edge is gone.

Test: finalizeToastKinda cancelled scan does NOT fire the success toast (+ the reducer recognizing cancelled as terminal-non-success).

Full frontend unit suite 203 passed, vue-tsc --noEmit clean, make build green.

@Dumbris

Dumbris commented Jun 17, 2026

Copy link
Copy Markdown
Member Author

🤖 Codex review — ACCEPT ✅

The changes correctly distinguish cancelled terminal scans from successful completions for toast selection without introducing evident regressions in the reconciliation flow.

No P0–P2 findings after reducing to the minimal scope (suppress the "Scan Complete" success toast on cancel; cancelled-panel/scanReport-clearing reverted — report rendering unchanged from main). Resolves the original P3 cleanly.

Reviewed via codex exec review --base origin/main on a0ac9cf2.

@mcpproxy-gatekeeper mcpproxy-gatekeeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Claude Code review (Codex quota exhausted).

@Dumbris Dumbris enabled auto-merge (squash) June 17, 2026 11:19
@Dumbris Dumbris merged commit 5bac648 into main Jun 17, 2026
35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants