### 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.

This commit is contained in:
StyxX65 2026-06-08 14:28:24 +02:00
parent 79e589b525
commit f845a2f686
3 changed files with 11 additions and 1 deletions

View File

@ -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.
---

View File

@ -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

View File

@ -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);
}
});
}