The card badge only rendered when f.account_name was set, and the
group (role) badge was nested inside that same check. But save_item
never persisted account_name — only account_id (a GUID) and user_role.
Live SSE cards carried account_name so badges showed during a scan;
now that the grid loads finalized scans from the DB, the gap is exposed
and both badges vanish for earlier scans.
- Persist account_name (migration 11 + save_item) so future scans show
the user badge. Both M365 and Google cards already carry it.
- _accountPill() in results.js drives the group badge off user_role
alone (shows for legacy rows) and resolves a best-effort user label:
account_name → S._allUsers (id/email) → email-style account_id → omit.
Both card layouts share the one helper.
Legacy rows still lack account_name (never captured), but now show the
group badge and a resolved/email user label where possible.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
get_session_items / get_open_items / latest_scan_id all require
finished_at IS NOT NULL, but the M365 and Google engines return early
on abort (skipping finish_scan) and a process kill mid-scan (deploy,
OOM, crash) never reaches it either. Result on prod: 41/42 scans had
finished_at NULL, so 291 already-saved flagged items were invisible —
the grid showed nothing.
- finalize_orphan_scans(): finalises every finished_at-NULL scan; runs
once at startup before the scheduler (nothing is scanning at boot, so
any unfinished scan is dead). Recovers existing stranded items and
guards against future mid-scan restarts.
- run_scan: finalise the DB scan on the abort early-return too, so a
stopped scan's items stay visible without waiting for a restart.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>