diff --git a/ui-electrobun/src/mainview/CommsTab.svelte b/ui-electrobun/src/mainview/CommsTab.svelte index 2e676f7..069437f 100644 --- a/ui-electrobun/src/mainview/CommsTab.svelte +++ b/ui-electrobun/src/mainview/CommsTab.svelte @@ -186,11 +186,12 @@ } } - $effect(() => { + // Use onMount instead of $effect — loadChannels/loadAgents write to $state + import { onMount } from 'svelte'; + onMount(() => { loadChannels(); loadAgents(); appRpc.addMessageListener('btmsg.newMessage', onNewMessage); - // Feature 4: Fallback 30s poll for missed events pollTimer = setInterval(() => { if (mode === 'channels' && activeChannelId) { loadChannelMessages(activeChannelId); @@ -198,7 +199,6 @@ loadDmMessages(activeDmAgentId); } }, 30000); - return () => { if (pollTimer) clearInterval(pollTimer); appRpc.removeMessageListener?.('btmsg.newMessage', onNewMessage); diff --git a/ui-electrobun/src/mainview/FileBrowser.svelte b/ui-electrobun/src/mainview/FileBrowser.svelte index 6a6452e..cc0c2d1 100644 --- a/ui-electrobun/src/mainview/FileBrowser.svelte +++ b/ui-electrobun/src/mainview/FileBrowser.svelte @@ -263,13 +263,13 @@ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } - // Load root directory on mount - $effect(() => { + // Load root directory on mount — use onMount, NOT $effect + // $effect reads openDirs (via new Set(openDirs)) and writes to it → infinite loop + import { onMount } from 'svelte'; + onMount(() => { if (cwd) { loadDir(cwd); - const s = new Set(openDirs); - s.add(cwd); - openDirs = s; + openDirs = new Set([cwd]); } }); diff --git a/ui-electrobun/src/mainview/TaskBoardTab.svelte b/ui-electrobun/src/mainview/TaskBoardTab.svelte index 5ab7b99..97248c1 100644 --- a/ui-electrobun/src/mainview/TaskBoardTab.svelte +++ b/ui-electrobun/src/mainview/TaskBoardTab.svelte @@ -51,12 +51,13 @@ // ── Derived ────────────────────────────────────────────────────────── - let tasksByCol = $derived( - COLUMNS.reduce((acc, col) => { + // NO $derived — .reduce creates new objects every evaluation → infinite loop + function getTasksByCol(): Record { + return COLUMNS.reduce((acc, col) => { acc[col] = tasks.filter(t => t.status === col); return acc; - }, {} as Record) - ); + }, {} as Record); + } // ── Data fetching ──────────────────────────────────────────────────── @@ -177,10 +178,12 @@ if (!payload.groupId || payload.groupId === groupId) loadTasks(); } - $effect(() => { + // Use onMount instead of $effect — loadTasks() writes to $state (pollToken++) + // which would trigger $effect re-run → infinite loop + import { onMount } from 'svelte'; + onMount(() => { loadTasks(); appRpc.addMessageListener('bttask.changed', onTaskChanged); - // Feature 4: Fallback 30s poll for missed events pollTimer = setInterval(loadTasks, 30000); return () => { if (pollTimer) clearInterval(pollTimer); @@ -243,11 +246,11 @@ >
{COL_LABELS[col]} - {tasksByCol[col]?.length ?? 0} + {getTasksByCol()[col]?.length ?? 0}
- {#each tasksByCol[col] ?? [] as task (task.id)} + {#each getTasksByCol()[col] ?? [] as task (task.id)}