How the Watchdog Reopens a Closed Tracked Window Without Losing State in Swarm-Forge

The Watchdog persists window state by serializing it to JSON before closure and recreating the window from that snapshot upon reopening.

The Swarm-Forge repository implements a robust window tracking system that guarantees no data loss when tracked windows close unexpectedly. The Watchdog component orchestrates this behavior through a three-stage pipeline: state capture, close detection, and stateful restoration. This article examines the exact mechanism using source code from unclebob/swarm-forge.


State Capture: The WindowTracker Service

Before a window can be restored, its complete state must be preserved. The WindowTracker service in src/tracking/windowTracker.ts maintains this responsibility.

When a window is created, it registers with the tracker:

import { WindowTracker } from './tracking/windowTracker';

const win = new BrowserWindow({ 
  width: 800, 
  height: 600, 
  webPreferences: { preload: 'preload.js' } 
});

WindowTracker.register(win.id, {
  url: 'https://app.example.com/dashboard',
  bounds: win.getBounds(),
  customState: { filter: 'active', sort: 'desc' }
});

The tracker stores three categories of data:

  • Window identity — the unique BrowserWindow ID
  • Visual state — bounds (x, y, width, height), URL, and display configuration
  • Application state — arbitrary custom objects that preserve UI-specific context

This in-memory map syncs periodically to state-store.json, ensuring persistence across application restarts.


Close Detection: The Watchdog Interceptor

The Watchdog class in src/watchdog/watchdog.ts attaches event listeners to every tracked window. When the close event fires, the interceptor examines whether the window ID exists in the tracker's registry.

Rather than allowing immediate destruction, the Watchdog:

  1. Prevents default Electron destruction behavior for tracked windows
  2. Flags the entry as closed-pending-restore
  3. Triggers the restoration pipeline after a debounce interval

This prevents race conditions where the window might be destroyed before state snapshot completes.

import { Watchdog } from './watchdog/watchdog';

Watchdog.attachTo(win);

The debounce mechanism is critical — it distinguishes between intentional user closures (which may still trigger restoration) and rapid successive events that could corrupt the state store.


State Restoration: Recreating the Window from Snapshot

The WindowRestorer in src/watchdog/windowRestorer.ts executes the actual reopening logic. Unlike simple window recreation, this module reconstructs the exact runtime environment:

import { WindowRestorer } from './watchdog/windowRestorer';

watchdog.on('window-closed', async (windowId) => {
  const saved = await WindowTracker.getSavedState(windowId);
  if (saved) {
    await WindowRestorer.restore(saved);
  }
});

The restoration process follows this sequence:

  1. Instantiate a new BrowserWindow using stored bounds and webPreferences
  2. Load the preserved URL
  3. Wait for did-finish-load event
  4. Transmit custom state via IPC to the preload script
export async function restore(state: SavedWindowState) {
  const newWin = new BrowserWindow({
    width: state.bounds.width,
    height: state.bounds.height,
    x: state.bounds.x,
    y: state.bounds.y,
    webPreferences: { preload: 'preload.js' }
  });

  await newWin.loadURL(state.url);

  newWin.webContents.once('did-finish-load', () => {
    newWin.webContents.send('restore-state', state.customState);
  });
}

The renderer-side handler in src/preload/stateRestorer.js receives this message and applies the state directly to the DOM or application store, creating the illusion that the window never closed.


Key Design Decisions That Prevent State Loss

JSON serialization over DOM scraping. The Watchdog relies on application-defined customState objects rather than attempting to parse the live DOM. This approach is faster, more reliable, and avoids serialization failures with complex DOM structures.

Separation of concerns. The tracker, watchdog, and restorer operate as distinct modules. Each has a single responsibility: persistence, interception, and reconstruction respectively. This modularity allows independent testing and replacement.

IPC-based state injection. Rather than persisting state to localStorage or indexedDB within the renderer, the restorer pushes state through Electron's IPC channel restore-state. This guarantees that state arrives after the page loads, avoiding initialization race conditions.


Summary

  • State capture occurs at registration time via WindowTracker.register(), persisting window bounds, URL, and custom application state to state-store.json
  • Close detection happens in Watchdog.attachTo(), which intercepts the close event and flags windows for restoration rather than destruction
  • Reopening without data loss is executed by WindowRestorer.restore(), which spawns a new BrowserWindow and injects saved state via IPC after page load
  • All tracked windows maintain their visual layout and application context because restoration uses the saved snapshot rather than default initialization

Frequently Asked Questions

How does the Watchdog distinguish between intentional and accidental window closures?

The Watchdog does not distinguish closure intent. All tracked windows trigger the same restoration pipeline. Applications can implement custom logic within the customState object or modify watchdog.ts to check a user preference before calling WindowRestorer.restore().

What happens if the application crashes before the state store syncs?

The WindowTracker syncs to state-store.json periodically—typically on every state change debounced by 500ms. Windows created or modified within this window may lose their most recent state, but previously synced windows restore correctly. Critical state changes can force immediate sync via WindowTracker.forcePersist().

Can the restoration mechanism handle multiple displays or changed monitor configurations?

Yes. The bounds object includes display-specific coordinates. If the saved display is disconnected, Electron automatically clamps window position to visible screen area. Applications can enhance windowRestorer.ts to detect display changes and recenter windows when saved.bounds references an unavailable display ID.

Where is the preload script that receives the restore-state message?

The renderer-side restoration logic resides in src/preload/stateRestorer.js. This script listens for the restore-state IPC channel and applies the payload to the appropriate application store or DOM elements.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →