fix: scan button stuck, CPR dedup crash, role scope filter, profile race conditions; add auto-email toggle and route integration tests

This commit is contained in:
StyxX65 committed 2026-04-21 18:43:25 +02:00
1 parent 7c1afca80b
commit c350014b16
19 files changed
+1001 -17

No files matched your search

+7
View File
@@ -22,6 +22,13 @@ Never revert to `!!window._googleConnected` / `_fileSources.length > 0` — thos
`_PHASE_SOURCE_MAP` ordering matters — `Google Workspace` must appear before `Gmail` in the map. The email regex uses `/iu` flags — do not drop the `i`.
## Profile startup race conditions — profiles.js + users.js
`loadProfiles()` (fast, local file) resolves before `loadUsers()` (slow, Graph API). The user can select a profile before `S._allUsers` or the sources panel is populated.
- **`user_ids = "all"` must be deferred** — if `S._allUsers` is empty when `_applyProfile()` runs, set `window._pendingProfileAllUsers = true` instead of calling `.forEach()` on an empty array. `loadUsers()` checks this flag after populating `S._allUsers` and selects everyone. Do not remove this — reverting will silently leave all accounts unchecked whenever a profile is chosen on a fast machine before the user list loads.
- **Source checkboxes may not exist yet** — `_applyProfile()` calls `renderSourcesPanel()` first if `#sourcesPanel` contains no `input[data-source-id]` nodes. Same guard used in `loadUsers()`. Without it, `querySelectorAll` returns nothing and the profile's source selection is discarded; the next `renderSourcesPanel()` call re-renders all sources as checked (their default).
## Gotchas
- **Profile editor accounts** — default to unchecked. Only explicitly saved `user_ids` are checked.
+12 -2
View File
@@ -69,6 +69,11 @@ function _applyProfile(profile) {
// File sources may not be rendered yet (they load async), so store their IDs
// in S._pendingProfileSources for renderSourcesPanel() to apply after re-render.
const profileSources = profile.sources || [];
// Ensure at least M365 source checkboxes are present before reading the DOM.
// renderSourcesPanel() is idempotent and fast — safe to call here.
if (!document.querySelector('#sourcesPanel input[data-source-id]') && typeof renderSourcesPanel === 'function') {
renderSourcesPanel();
}
document.querySelectorAll('#sourcesPanel input[data-source-id]').forEach(function(cb) {
cb.checked = profileSources.includes(cb.dataset.sourceId);
});
@@ -181,8 +186,13 @@ function _applyProfile(profile) {
// ── User selection ────────────────────────────────────────────────────────
if (profile.user_ids === 'all') {
S._allUsers.forEach(u => { u.selected = true; });
if (S._allUsers.length) renderAccountList();
if (S._allUsers.length) {
S._allUsers.forEach(u => { u.selected = true; });
renderAccountList();
} else {
// Users not loaded yet — defer until loadUsers() resolves
window._pendingProfileAllUsers = true;
}
} else if (Array.isArray(profile.user_ids) && profile.user_ids.length) {
window._pendingProfileUserIds = profile.user_ids.map(u => u.id || u);
_applyPendingProfileUsers();
+4 -1
View File
@@ -300,6 +300,8 @@ function stLoadSmtp() {
if (tls) tls.checked = d.starttls !== false;
const pw = document.getElementById('st-smtpPw');
if (pw) pw.value = d.has_password ? '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022' : '';
const ae = document.getElementById('st-smtpAutoEmail');
if (ae) ae.checked = !!d.auto_email_manual;
}).catch(function(){});
}
@@ -313,7 +315,8 @@ async function stSmtpSave() {
user: document.getElementById('st-smtpUser').value.trim(),
from_addr: document.getElementById('st-smtpFrom').value.trim(),
recipients: document.getElementById('st-smtpTo').value.split(/[,;]/).map(function(s){return s.trim();}).filter(Boolean),
starttls: document.getElementById('st-smtpTls').checked,
starttls: document.getElementById('st-smtpTls').checked,
auto_email_manual: !!(document.getElementById('st-smtpAutoEmail') || {}).checked,
};
if (pw !== null) body.password = pw;
st.style.color = 'var(--muted)'; st.textContent = t('m365_smtp_saving','Saving...');
+5
View File
@@ -28,6 +28,11 @@ async function loadUsers() {
u.selected = prevSelected.has(u.id) ? prevSelected.get(u.id) : false;
});
S._allUsers = [...fetched, ...toAdd];
// Apply deferred "select all" from a profile chosen before users loaded
if (window._pendingProfileAllUsers) {
S._allUsers.forEach(u => { u.selected = true; });
window._pendingProfileAllUsers = false;
}
renderAccountList(fetched.length <= 1);
// Merge Google users separately so they're not blocked by M365 auth timing
_mergeGoogleUsers();