feat: pause Today during mobile Security Center (Closes #1086)
CI / lint (pull_request) Successful in 3m27s
CI / build-release (pull_request) Successful in 8s
CI / browser-journey (pull_request) Successful in 2m59s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-18 16:41:59 +00:00
1 parent aba4b55ca0
commit 473f592615
11 files changed
+174 -14

No files matched your search

+1
View File
@@ -1853,6 +1853,7 @@
},
onCapture:() => openCreateIssueSheet(),
});
window.stackchainTodayTimerView = timerView;
mobileInsights.start();
todaySessionSync = attachTodaySessionHandoff({
fetchJson:fetchReviewJson, storage:localStorage, timer, qs,
+4
View File
@@ -51,6 +51,10 @@
<div><h2>Security Center</h2><p class="small muted">Investigate activity, remove untrusted access, and verify the result.</p></div>
<button id="close-active-devices" type="button" aria-label="Close active devices">Close</button>
</div>
<aside class="today-detour-interruption" id="security-center-today-detour" data-today-detour role="status" aria-live="polite" hidden>
<strong data-today-detour-label>Today paused</strong>
<button id="return-from-security-center" data-return-from-detour type="button">Return to Today</button>
</aside>
<nav class="security-section-nav" aria-label="Security Center sections">
<button id="security-section-activity" type="button">Activity</button>
<button id="security-section-devices" type="button">Devices</button>
+4 -2
View File
@@ -1,7 +1,7 @@
(function (root, factory) {
if (typeof module !== 'undefined' && module.exports) module.exports = factory;
else root.attachSecurityCenter = boundary => factory({ root, boundary });
})(typeof window !== 'undefined' ? window : this, function attachSecurityCenter({ root, boundary }) {
else root.attachSecurityCenter = (boundary, options = {}) => factory({ root, boundary, ...options });
})(typeof window !== 'undefined' ? window : this, function attachSecurityCenter({ root, boundary, onOpen = () => {}, onClose = () => {} }) {
const devicesButton = root.document.getElementById('active-devices');
const devicesSheet = root.document.getElementById('active-devices-sheet');
const devicesList = root.document.getElementById('active-devices-list');
@@ -52,6 +52,7 @@
if (!devicesSheet.hidden) {
devicesSheet.hidden = true;
devicesButton?.focus();
onClose();
}
});
@@ -222,6 +223,7 @@
const open = () => {
if (!devicesSheet) return;
onOpen();
devicesSheet.hidden = false;
root.document.getElementById('close-active-devices')?.focus();
navigate('activity', { history: root.history?.state?.stackchainSecuritySection !== 'activity' });
+16 -1
View File
@@ -45,6 +45,12 @@
const devicesSheet = root.document.getElementById('active-devices-sheet');
const devicesStatus = root.document.getElementById('active-devices-status');
const closeDevices = root.document.getElementById('close-active-devices');
const returnFromSecurity = root.document.getElementById('return-from-security-center');
const beginSecurityDetour = () => {
if (root.matchMedia?.('(max-width: 600px)')?.matches) {
root.stackchainTodayTimerView?.beginDetour?.('security-center');
}
};
const securityFeatures = root.createFeatureLoader({
document: root.document,
urls: {
@@ -57,6 +63,7 @@
const openSecurityCenter = async () => {
if (loadingSecurityCenter) return;
loadingSecurityCenter = true;
beginSecurityDetour();
devicesSheet.hidden = false;
closeDevices?.focus();
try {
@@ -66,7 +73,10 @@
retryLabel: 'Tap Active devices to retry.',
}, () => {
devicesButton.removeEventListener('click', openSecurityCenter);
root.attachSecurityCenter(boundary).open();
root.attachSecurityCenter(boundary, {
onOpen: beginSecurityDetour,
onClose: () => root.stackchainTodayTimerView?.finishDetour?.('security-center'),
}).open();
});
} finally {
loadingSecurityCenter = false;
@@ -78,6 +88,11 @@
if (closeDevices && devicesSheet) closeDevices.addEventListener('click', () => {
devicesSheet.hidden = true;
devicesButton?.focus();
root.stackchainTodayTimerView?.finishDetour?.('security-center');
});
if (returnFromSecurity && devicesSheet) returnFromSecurity.addEventListener('click', () => {
if (root.history?.state?.stackchainSecuritySection) root.history.back();
else devicesSheet.hidden = true;
});
boundary.refreshOfflineLease().then(valid => {
if (valid) boundary.resumeQueuedWork();
+8 -7
View File
@@ -57,7 +57,7 @@ function createTodayTimer({ storage, getLogin, now = () => Date.now(), onChange
const validDetour = state => {
const pending = state.detour_interruption;
return pending && typeof pending.identity === 'string' && pending.identity &&
typeof pending.resume === 'boolean' && ['find', 'queues', 'insights', 'device-setup'].includes(pending.reason) ?
typeof pending.resume === 'boolean' && ['find', 'queues', 'insights', 'device-setup', 'security-center'].includes(pending.reason) ?
{ identity:pending.identity, resume:pending.resume, reason:pending.reason } : null;
};
const validBreak = state => {
@@ -289,7 +289,7 @@ function createTodayTimer({ storage, getLogin, now = () => Date.now(), onChange
return write(state) ? { identity:pending.identity, resumed } : null;
},
beginDetour(reason) {
if (!['find', 'queues', 'insights', 'device-setup'].includes(reason)) return null;
if (!['find', 'queues', 'insights', 'device-setup', 'security-center'].includes(reason)) return null;
const state = read();
const existing = validDetour(state);
if (existing) return existing;
@@ -304,9 +304,10 @@ function createTodayTimer({ storage, getLogin, now = () => Date.now(), onChange
detourInterruption() {
return validDetour(read());
},
returnFromDetour() {
returnFromDetour(reason = '') {
const state = read();
const pending = validDetour(state);
if (reason && pending?.reason !== reason) return null;
const entry = pending && state.entries[pending.identity];
if (!pending || !entry || state.active_identity !== pending.identity) return null;
const resumed = pending.resume && !entry.running;
@@ -521,7 +522,7 @@ function createTodayTimerView({ timer, isActive, queryAll, formatEstimate, getRu
reopen(identity) { onReopen?.(identity); },
search(action, ...args) { return searchView[action]?.(...args); },
beginDetour(reason) { return detourView.open(reason); },
finishDetour() { return detourView.finish(); },
finishDetour(reason) { return detourView.finish(reason); },
finish() { timer.stop(); progress = null; runway = null; render(); },
update(nextProgress, nextRunway) { progress = nextProgress; runway = nextRunway; render(); },
reset() { progress = null; runway = null; render(); },
@@ -701,9 +702,9 @@ function createTodayDetourInterruption({ timer, queryAll, getItemLabel, onChange
return pending;
},
restore() { return render(); },
finish() {
const result = timer.returnFromDetour?.();
render(null);
finish(reason) {
const result = timer.returnFromDetour?.(reason);
if (result || !reason) render(null);
return result;
},
};