// 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(); } 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; } // 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. }, });