An AI agent built to do Ralph loops - plan mode for planning and ralph mode for implementing.

fix(web): resolve Svelte 5 mount and effect_orphan errors

Wrap router $effect in initRouter() called from App.svelte component
context to fix effect_orphan. Add resolve.conditions=['browser'] to
vite.config.ts so Vite picks the client entry point instead of the
server one (Svelte 5.50 changed package.json exports). Also add
justfile dev recipe for running daemon + web dev server together.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+37 -21
+8
justfile
··· 22 22 web-build: 23 23 cd web && bun run build 24 24 25 + # Run daemon and web dev server together 26 + dev: 27 + #!/usr/bin/env bash 28 + trap 'kill 0' EXIT 29 + cargo run -- daemon start & 30 + cd web && bun run dev & 31 + wait 32 + 25 33 # Run the web UI dev server 26 34 web-dev: 27 35 cd web && bun run dev
+4 -1
web/src/App.svelte
··· 5 5 * Manages WebSocket connection and dispatches events to stores. 6 6 */ 7 7 8 - import { getCurrentRoute, routerState } from './router.svelte'; 8 + import { getCurrentRoute, initRouter, routerState } from './router.svelte'; 9 9 import { createWsConnection } from './api/websocket'; 10 10 import { handleWsEvent as handleGraphWsEvent } from './stores/graph.svelte'; 11 11 import { handleWsEvent as handleAgentsWsEvent } from './stores/agents.svelte'; ··· 20 20 import SessionHistory from './views/SessionHistory.svelte'; 21 21 import GraphSearch from './views/GraphSearch.svelte'; 22 22 import Placeholder from './views/Placeholder.svelte'; 23 + 24 + // Initialize hash-based router (must run in component context) 25 + initRouter(); 23 26 24 27 let wsConnection: ReturnType<typeof createWsConnection> | null = null; 25 28
+22 -20
web/src/router.svelte.ts
··· 120 120 121 121 /** 122 122 * Initialize the router and listen for hash changes. 123 - * The $effect handles setup and cleanup automatically. 123 + * Must be called from within a component's reactive context (e.g., App.svelte). 124 124 */ 125 - $effect(() => { 126 - const handler = () => { 127 - // Parse the hash and update routerState 128 - let hash = window.location.hash; 129 - // Remove leading '#' 130 - if (hash.startsWith('#')) { 131 - hash = hash.slice(1); 132 - } 133 - routerState.path = hash === '' ? '/' : hash; 134 - }; 125 + export function initRouter(): void { 126 + $effect(() => { 127 + const handler = () => { 128 + // Parse the hash and update routerState 129 + let hash = window.location.hash; 130 + // Remove leading '#' 131 + if (hash.startsWith('#')) { 132 + hash = hash.slice(1); 133 + } 134 + routerState.path = hash === '' ? '/' : hash; 135 + }; 135 136 136 - // Parse current hash on init 137 - handler(); 137 + // Parse current hash on init 138 + handler(); 138 139 139 - // Listen for hash changes 140 - window.addEventListener('hashchange', handler); 140 + // Listen for hash changes 141 + window.addEventListener('hashchange', handler); 141 142 142 - // Return cleanup function 143 - return () => { 144 - window.removeEventListener('hashchange', handler); 145 - }; 146 - }); 143 + // Return cleanup function 144 + return () => { 145 + window.removeEventListener('hashchange', handler); 146 + }; 147 + }); 148 + }
+3
web/vite.config.ts
··· 3 3 4 4 export default defineConfig({ 5 5 plugins: [svelte()], 6 + resolve: { 7 + conditions: ['browser'], 8 + }, 6 9 build: { 7 10 outDir: 'dist', 8 11 },