stackchain-dashboard/frontend/workspace-bootstrap.js
timmy 5d9cec1dd7
Some checks failed
CI / lint (pull_request) Successful in 3m49s
CI / build-release (pull_request) Successful in 6s
CI / browser-journey (pull_request) Failing after 9m26s
CI / release-candidate (pull_request) Has been skipped
perf: demand-load mobile workspace features (Closes #1468)
2026-08-27 11:47:09 +00:00

153 lines
5.0 KiB
JavaScript

async function loadWorkspace({
document,
window = null,
createLoader = createFeatureLoader,
schedule = callback => setTimeout(callback, 750),
}) {
let cameOnline = false;
let replayed = false;
const status = document.querySelector('#my-work-action-status');
const retryButton = document.querySelector('#retry-workspace');
const names = ['work-core', 'today-timer', 'planning'];
const urls = Object.fromEntries(names.map(name => [name,
document.querySelector(`meta[name="stackchain-feature-${name}"]`)?.content || ''
]));
const originalUrls = {...urls};
const loader = createLoader({document, urls});
const attempts = Object.fromEntries(names.map(name => [name, 0]));
const failed = new Set();
const recoveries = new Map();
let retryInFlight = null;
let resolveWorkspaceReady;
const workspaceReady = new Promise(resolve => { resolveWorkspaceReady = resolve; });
let workspaceMarkedReady = false;
const markWorkspaceReady = () => {
if (workspaceMarkedReady) return false;
workspaceMarkedReady = true;
resolveWorkspaceReady(true);
return true;
};
const loadFeature = name => {
attempts[name] += 1;
urls[name] = originalUrls[name] + (attempts[name] > 1 ? '?retry=' + attempts[name] : '');
return loader.load(name);
};
const retryOnce = async name => {
try { return await loadFeature(name); }
catch (_error) {
await new Promise(resolve => schedule(resolve));
return loadFeature(name);
}
};
const showRecovery = () => {
if (status) status.textContent = failed.has('work-core') ?
'Workspace unavailable. Reconnect or retry.' :
'Today or planning tools unavailable. My Work is ready; reconnect or retry.';
if (retryButton) retryButton.hidden = retryButton.disabled = false;
};
const hideRecovery = () => {
if (failed.size) return;
if (retryButton) retryButton.hidden = true;
if (status) status.textContent = '';
};
const retryFailed = () => {
if (retryInFlight) return retryInFlight;
if (retryButton) retryButton.disabled = true;
const pending = Array.from(failed);
retryInFlight = Promise.all(pending.map(async name => {
try {
await loadFeature(name);
failed.delete(name);
recoveries.get(name)?.resolve(true);
recoveries.delete(name);
} catch (_error) {}
})).then(() => {
if (failed.size) showRecovery();
else hideRecovery();
}).finally(() => { retryInFlight = null; });
return retryInFlight;
};
retryButton?.addEventListener?.('click', retryFailed);
const handleOnline = () => { cameOnline = true; void retryFailed(); };
window?.addEventListener('online', handleOnline);
try {
await retryOnce('work-core');
} catch (_error) {
failed.add('work-core');
showRecovery();
await new Promise(resolve => recoveries.set('work-core', {resolve}));
}
hideRecovery();
let optionalReady = null;
const hydrateWorkspace = () => {
if (optionalReady) return optionalReady;
let resolveOptional;
optionalReady = new Promise(resolve => { resolveOptional = resolve; });
const optional = ['today-timer', 'planning'].map(async name => {
try {
await retryOnce(name);
return true;
} catch (_error) {
failed.add(name);
showRecovery();
return new Promise(resolve => recoveries.set(name, {resolve}));
}
});
Promise.all(optional).then(() => {
hideRecovery();
resolveOptional(true);
});
return optionalReady;
};
const hydrationSelector = [
'[data-mobile-task]:not([data-mobile-task="work"]):not([data-mobile-task="queues"])',
'[data-progressive-loading="true"]',
'#app-menu-toggle',
'#work-settings-toggle',
].join(',');
const hydrateForAction = async event => {
const target = event.target?.closest?.(hydrationSelector);
if (!target) return;
event.preventDefault();
event.stopImmediatePropagation();
await hydrateWorkspace();
await workspaceReady;
document.removeEventListener?.('click', hydrateForAction, true);
target.click?.();
};
document.addEventListener?.('click', hydrateForAction, true);
const hash = window?.location?.hash || '';
const deepLinkReady = hash.startsWith('#/') && hash !== '#/my-work' ?
hydrateWorkspace() : Promise.resolve(false);
return {
hydrateWorkspace,
deepLinkReady,
workspaceReady,
markWorkspaceReady,
get optionalReady() { return hydrateWorkspace(); },
retryFeature(name) {
if (!failed.has(name)) return Promise.resolve(true);
return retryFailed().then(() => !failed.has(name));
},
replayOnline(callback) {
if (replayed) return;
replayed = true;
window?.removeEventListener('online', handleOnline);
if (cameOnline) callback();
},
};
}
if (typeof window !== 'undefined' && typeof document !== 'undefined' &&
typeof createFeatureLoader === 'function') {
window.stackchainWorkspaceLifecycle = loadWorkspace({document, window});
}
if (typeof module !== 'undefined' && module.exports) module.exports = loadWorkspace;