Skip to content

Hot-Reload System

Overview

The hot-reload system enables rapid development by automatically detecting file changes and notifying connected browsers to reload. This eliminates the need to manually refresh the browser after editing frontend code.

Architecture

Components

  1. File Watcher (internal/hotreload/watcher.go)
  2. Uses fsnotify to monitor file system changes
  3. Watches specified directories and file patterns
  4. Debounces rapid changes (e.g., multiple saves)
  5. Notifies subscribers of file changes

  6. WebSocket Server (internal/hotreload/server.go)

  7. Manages WebSocket connections from browsers
  8. Broadcasts file change events to all connected clients
  9. Handles client connection/disconnection

  10. Manager (internal/hotreload/manager.go)

  11. Coordinates watcher and WebSocket server
  12. Configurable via config.yaml

  13. Client (web/static/js/hotreload.js)

  14. Connects to WebSocket endpoint
  15. Listens for file change events
  16. Reloads CSS without full page refresh
  17. Triggers full page reload for JS/HTML changes

Configuration

config.yaml

hot_reload:
  enabled: true  # Enable hot-reload (development only)
  watch_dirs:
    - "./web/static"  # Watch frontend files
    - "./personas"    # Watch persona definitions
  patterns:
    - "*.html"  # HTML files
    - "*.css"   # Stylesheets
    - "*.js"    # JavaScript
    - "*.md"    # Markdown (personas)
    - "PERSONA.md"

Settings

  • enabled: Turn hot-reload on/off (disable in production)
  • watch_dirs: List of directories to monitor
  • patterns: File patterns to watch (glob format)

Integration (To Complete)

Step 1: Initialize in main.go

import (
    "github.com/jordanhubbard/loom/internal/hotreload"
)

func main() {
    // ... existing config loading ...

    // Initialize hot-reload
    var hrManager *hotreload.Manager
    if cfg.HotReload.Enabled {
        var err error
        hrManager, err = hotreload.NewManager(
            cfg.HotReload.Enabled,
            cfg.HotReload.WatchDirs,
            cfg.HotReload.Patterns,
        )
        if err != nil {
            log.Printf("Hot-reload initialization failed: %v", err)
        } else {
            defer hrManager.Close()
        }
    }

    // ... rest of initialization ...
}

Step 2: Register WebSocket Route

// In main.go after apiServer.SetupRoutes()
handler := apiServer.SetupRoutes()

// Add hot-reload WebSocket endpoint
if hrManager != nil && hrManager.IsEnabled() {
    http.HandleFunc("/ws/hotreload", hrManager.GetServer().HandleWebSocket)
    http.HandleFunc("/api/v1/hotreload/status", hrManager.GetServer().HandleStatus)
    log.Println("[HotReload] WebSocket endpoint registered at /ws/hotreload")
}

Step 3: Include Client Script

Add to web/static/index.html before closing </body> tag:

<!-- Hot-reload client (development only) -->
<script src="/static/js/hotreload.js"></script>

Workflow

┌─────────────────────┐
│  Developer Edits    │
│  File (app.js)      │
└──────────┬──────────┘
┌─────────────────────┐
│  fsnotify Detects   │
│  Change Event       │
└──────────┬──────────┘
┌─────────────────────┐
│  Watcher Debounces  │
│  (100ms delay)      │
└──────────┬──────────┘
┌─────────────────────┐
│  Watcher Notifies   │
│  WebSocket Server   │
└──────────┬──────────┘
┌─────────────────────┐
│  Server Broadcasts  │
│  to All Clients     │
└──────────┬──────────┘
┌─────────────────────┐
│  Browser Receives   │
│  Message            │
└──────────┬──────────┘
┌─────────────────────┐
│  Client Reloads     │
│  ├─ CSS: Hot reload │
│  └─ JS/HTML: Full   │
└─────────────────────┘

Client Behavior

CSS Changes

  • Reload stylesheets without full page refresh
  • Add cache-busting timestamp to <link> tags
  • Page state preserved

JavaScript Changes

  • Full page reload required
  • Show notification before reload
  • 500ms delay to show message

HTML Changes

  • Full page reload required
  • Immediate reload

Configuration Changes

  • Full page reload
  • Useful for persona definition updates

Browser Compatibility

  • Requires WebSocket support
  • Modern browsers (Chrome, Firefox, Safari, Edge)
  • Falls back gracefully if WebSocket unavailable

Development Usage

Enable Hot-Reload

# In config.yaml
hot_reload:
  enabled: true

Check Status

curl http://localhost:8080/api/v1/hotreload/status

Response:

{
  "enabled": true,
  "connected_clients": 2
}

Debug in Browser Console

// Check connection status
window.hotReload.status()
// { connected: true, attempts: 0 }

// Manual reload test
window.hotReload.test()

// Reconnect manually
window.hotReload.reconnect()

Production Deployment

IMPORTANT: Disable hot-reload in production:

hot_reload:
  enabled: false

Hot-reload introduces: - Additional file I/O overhead - WebSocket connections - Potential security considerations

It's designed for development environments only.

Troubleshooting

Hot-Reload Not Working

  1. Check configuration

    grep -A 10 "hot_reload" config.yaml
    

  2. Verify WebSocket connection

  3. Open browser DevTools → Network → WS tab
  4. Should see connection to ws://localhost:8080/ws/hotreload
  5. Status should be "101 Switching Protocols"

  6. Check server logs

    grep "HotReload" logs/loom.log
    

Should see:

[HotReload] Watching ./web/static
[HotReload] Client connected (total: 1)

Changes Not Detected

  1. Verify watch directories exist

    ls -la ./web/static
    

  2. Check file patterns match

  3. Patterns use filepath.Match (not regex)
  4. Example: *.js matches app.js but not js/app.js
  5. Use glob patterns: **/*.js for recursive

  6. Look for watcher errors

    grep "Watcher error" logs/loom.log
    

Browser Not Reloading

  1. Check hotreload.js is loaded
  2. Open DevTools → Sources
  3. Verify /static/js/hotreload.js is present

  4. Check for console errors

    // Should see:
    [HotReload] Connected
    [HotReload] Client initialized
    

  5. Test WebSocket manually

    const ws = new WebSocket('ws://localhost:8080/ws/hotreload');
    ws.onopen = () => console.log('Connected');
    ws.onmessage = (e) => console.log('Message:', e.data);
    

Too Many Reconnect Attempts

If hot-reload repeatedly tries to reconnect: - Server might not be running - WebSocket endpoint not registered - Check max reconnect attempts (default: 5)

Performance Considerations

File System Overhead

  • Debouncing: Changes are debounced (100ms) to avoid excessive events
  • Recursive watching: Automatically watches subdirectories
  • Pattern filtering: Only processes matching file types

Memory Usage

  • Each connected browser = 1 WebSocket connection
  • Minimal memory per connection (~10KB)
  • Event buffer size: 10 events per client
  • Watch directories: 1-5 directories
  • File patterns: 3-10 patterns
  • Connected clients: 1-10 browsers

For large codebases (>10,000 files), consider: - Limiting watch patterns to specific extensions - Watching only actively developed directories

Future Enhancements

Phase 1 (Current)

  • ✅ File watching with fsnotify
  • ✅ WebSocket server for notifications
  • ✅ Browser client with hot CSS reload
  • ✅ Configuration support

Phase 2 (Planned)

  • ⏳ Hot module replacement for JavaScript
  • ⏳ Persona reload without page refresh
  • ⏳ Backend code hot-reload (partial - Go)
  • ⏳ Selective component refresh

Phase 3 (Future)

  • 📋 Browser sync across multiple devices
  • 📋 File change history/timeline
  • 📋 Conditional reload (e.g., only if no console errors)
  • 📋 Integration with test runner

See Also