// components/tasks/index.js — the Tasks page as a feature module. // // window.tasksManager is a system-loader singleton (its "task-system" component // loads all task scripts, starts the shared SSE bus, then news TasksManager). // So mount() does NOT new it or load scripts — it renders the fragment and // re-inits the view, exactly like the old handleTasks(). unmount() releases // only this view's per-mount leaks (the 30s auto-refresh interval + open log // streams); it must never stop the shared taskEventBus or null the singleton. LP.features.register({ id: 'tasks', routes: ['/tasks', '/tasks*'], async mount(ctx) { const html = await ctx.loadFragment('/components/tasks/html/tasks-content.html'); ctx.setContent(html, 'Tasks'); if (window.tasksManager) { await window.tasksManager.init(); // Re-arm the global live-log poller (idempotent) — it's first started in // the constructor, which doesn't re-run, so a revisit after unmount cleared // it would otherwise have no poller. unmount() clears it again. if (typeof window.tasksManager.startGlobalLiveLogUpdater === 'function') window.tasksManager.startGlobalLiveLogUpdater(); } else { // Don't throw — matches handleTasks: the page still renders, task // functionality is just limited until the task-system component is ready. console.warn('TasksManager not available yet, task functionality will be limited'); } }, async unmount() { const tm = window.tasksManager; // The one per-view leak init() opens: the 30s auto-refresh interval stored // on the singleton. Each init() recreates it, so clearing here is idempotent. if (tm && tm.refreshInterval) { clearInterval(tm.refreshInterval); tm.refreshInterval = null; } // The global live-log poller (constructor-started, handle was discarded) and // any still-running per-task monitors — release them so they don't keep // firing on a torn-down page. if (tm && tm.globalLiveLogInterval) { clearInterval(tm.globalLiveLogInterval); tm.globalLiveLogInterval = null; } if (tm && tm.taskMonitors) { for (const stop of Array.from(tm.taskMonitors.values())) { try { stop(); } catch (_) {} } tm.taskMonitors.clear(); } // Stop any open per-task log streams this view started (each removes its own // SSE listeners + map entry). Snapshot keys first — stopLogStreaming mutates // the map. Does NOT touch the shared bus. if (tm && tm.activeLogStreams && typeof tm.stopLogStreaming === 'function') { for (const id of Array.from(tm.activeLogStreams.keys())) { try { tm.stopLogStreaming(id); } catch (_) {} } } // DO NOT: stop window.taskEventBus (shared SSE singleton), remove the // singleton's once-bound task listeners, or null window.tasksManager. }, });