From f845a2f68620d14fb72d4b7167ffe0305d591072 Mon Sep 17 00:00:00 2001 From: StyxX65 <150797939+StyxX65@users.noreply.github.com> Date: Mon, 8 Jun 2026 14:28:24 +0200 Subject: [PATCH] =?UTF-8?q?###=20Fixed=20=20-=20**Cards=20not=20shown=20af?= =?UTF-8?q?ter=20browser=20refresh**=20=E2=80=94=20when=20the=20browser=20?= =?UTF-8?q?reconnected=20to=20the=20SSE=20stream=20after=20a=20completed?= =?UTF-8?q?=20scan,=20the=20`scan=5Fphase`=20events=20in=20the=20replay=20?= =?UTF-8?q?buffer=20temporarily=20set=20`S.=5Fm365ScanRunning=20=3D=20true?= =?UTF-8?q?`=20(all=20running=20flags=20start=20at=20`false`=20after=20a?= =?UTF-8?q?=20page=20reload).=20The=20watchdog's=20`loadHistorySession`=20?= =?UTF-8?q?call=20fired=20in=20this=20window=20and=20bailed=20on=20the=20s?= =?UTF-8?q?tale=20flag;=20once=20`scan=5Fdone`=20cleared=20the=20flag,=20`?= =?UTF-8?q?=5FinitialStatusChecked`=20was=20already=20`true`=20so=20`loadH?= =?UTF-8?q?istorySession`=20was=20never=20retried.=20Fixed=20by=20having?= =?UTF-8?q?=20the=20`sse=5Freplay=5Fdone`=20handler=20retry=20`loadHistory?= =?UTF-8?q?Session(null)`=20when=20no=20scan=20is=20running=20and=20`S.=5F?= =?UTF-8?q?historyRefScanId`=20is=20still=20`null`=20after=20replay.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 ++ CLAUDE.md | 2 ++ static/js/scan.js | 8 +++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f11cc66..dcce28d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ Version numbers follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html ### Fixed +- **Cards not shown after browser refresh** — when the browser reconnected to the SSE stream after a completed scan, the `scan_phase` events in the replay buffer temporarily set `S._m365ScanRunning = true` (all running flags start at `false` after a page reload). The watchdog's `loadHistorySession` call fired in this window and bailed on the stale flag; once `scan_done` cleared the flag, `_initialStatusChecked` was already `true` so `loadHistorySession` was never retried. Fixed by having the `sse_replay_done` handler retry `loadHistorySession(null)` when no scan is running and `S._historyRefScanId` is still `null` after replay. + - **Settings modal too narrow for seven tabs** — widened from 640 px to 720 px so all tab labels fit on one line without wrapping. --- diff --git a/CLAUDE.md b/CLAUDE.md index c209383..4c6c163 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -155,6 +155,7 @@ Allows reviewing results from any past scan session without running a new scan. - **Cache invalidation** — `_sessions` and `_latestRefScanId` are module-level in `history.js`. `invalidateHistoryCache()` clears both. All three `*_done` SSE handlers in `scan.js` call `window.invalidateHistoryCache?.()` so the picker reflects the newest scan after completion. - **Re-scan diff** — `loadHistorySession` fetches the immediately preceding session's items after rendering the current session. Items present in the previous session but absent from the current one (compared by `id`) are tagged `_resolved: true` and appended after a `.resolved-divider` separator. `appendCard` in `results.js` adds `.card-resolved` (opacity 0.6), a green `✓ Resolved` badge, and hides the delete button for resolved items. `_setHistoryBanner` accepts an optional `resolvedCount` parameter and appends it to the banner label. Resolved items are NOT added to `S.flaggedData` — they are grid-only and cannot be bulk-selected or exported. - **Auto-load on page load** — `results.js` calls `window.loadHistorySession?.(null)` once when the SSE watchdog confirms `!status.running`. `null` resolves to the latest completed session via `_fetchSessions()[0].ref_scan_id`. The `_initialStatusChecked` guard ensures this fires at most once per page load. +- **`sse_replay_done` retry** — `scan_phase` events replayed from a *completed* scan temporarily set `S._m365ScanRunning = true` (because all flags start at `false` after a page refresh). This causes the watchdog's `loadHistorySession` call to bail before `scan_done` clears the flag. The `sse_replay_done` handler in `scan.js` retries `loadHistorySession(null)` if no scan is running and `S._historyRefScanId` is still `null` after replay. Do not remove this retry — it is the only way to guarantee DB items are loaded when the watchdog fires in the middle of a replay. - **Mode transitions** — `startScan()` calls `window.exitHistoryMode?.()` before clearing the grid, so any history banner is dismissed and `S._historyRefScanId` is reset before SSE events start arriving. ## CPR cross-referencing — gdpr_db.py + routes/database.py + static/js/results.js @@ -199,6 +200,7 @@ Allows reviewing results from any past scan session without running a new scan. - **Two separate abort events** — `state._scan_abort` (M365 + file) and `state._google_scan_abort` (Google). `POST /api/scan/stop` sets **both**. `_check_abort()` inside `_run_google_scan` must use the module-level `_scan_abort` alias (`= state._google_scan_abort`), not `gdpr_scanner._scan_abort` (which is the M365 event). Do not conflate them — a Google-only scan must react to Stop, and `gdpr_scanner._scan_abort` is not the right event for that path. - **`_check_abort()` emits `google_scan_done`, not `scan_cancelled`** — when the Google thread detects the abort signal it broadcasts `google_scan_done` (with `cancelled: True`). Do NOT change this back to `scan_cancelled`. The `scan_cancelled` handler in the frontend unconditionally closes the SSE connection; if a new M365/file scan started while the old Google thread was still winding down, that would drop all remaining events from the new scan. `google_scan_done` is safe because its handler checks `!S._m365ScanRunning && !S._fileScanRunning` before closing the SSE. - **Google Drive uses a lazy generator, not `list()`** — `iter_drive_files()` is iterated directly (no `list()` wrapping) in both the non-delta path and the delta-fallback path. Wrapping in `list()` would block the thread — and the abort check — for the entire file enumeration. The delta start token is recorded *before* the iteration loop so it captures state at the right moment regardless of when the loop is interrupted. +- **`scan_phase` replay sets running flags — handled by `sse_replay_done`** — the `scan_phase` handler sets `S._m365ScanRunning/S._googleScanRunning/S._fileScanRunning = true` whenever no flag is currently set and a matching source keyword is found in the phase text. On page refresh this fires during SSE replay of a completed scan (all flags start `false`), temporarily making the scan appear to still be running. This is intentional for live reconnection but harmless for the completed-scan case because `scan_done` clears the flag and `sse_replay_done` retries `loadHistorySession`. Do not remove the flag-setting logic from `scan_phase` — it is needed to detect reconnection to a genuinely running scan. ## Email sending — routes/email.py + m365_connector.py diff --git a/static/js/scan.js b/static/js/scan.js index 9f8a7d1..09bcc81 100644 --- a/static/js/scan.js +++ b/static/js/scan.js @@ -484,9 +484,15 @@ function _attachScanListeners(source) { window.invalidateHistoryCache?.(); }); // sse_replay_done marks end of buffer replay — log a note so the user knows - // earlier events above were replayed from an already-running scan + // earlier events above were replayed from an already-running scan. + // Also retry loadHistorySession if it bailed during replay: scan_phase events + // from a completed scan's replay temporarily set running flags to true, causing + // the watchdog's loadHistorySession call to bail before scan_done clears them. source.addEventListener('sse_replay_done', function() { log(t('m365_sse_replay_note', 'Live log resumed \u2014 earlier entries replayed from running scan.')); + if (!S._m365ScanRunning && !S._googleScanRunning && !S._fileScanRunning && !S._historyRefScanId) { + window.loadHistorySession?.(null); + } }); }