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); const serviceWorkerReady = window?.navigator?.serviceWorker?.register ? window.navigator.serviceWorker.register('service-worker.js') : Promise.resolve(null); 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="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, serviceWorkerReady, 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;