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

> Discover how Swarm-Forge's watchdog reopens closed windows, preserving state by serializing and recreating UI snapshots from JSON.

- Repository: [Robert C. Martin/swarm-forge](https://github.com/unclebob/swarm-forge)
- Tags: internals
- Published: 2026-08-30

---

**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`](https://github.com/unclebob/swarm-forge/blob/main/src/tracking/windowTracker.ts) maintains this responsibility.

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

```ts
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`](https://github.com/unclebob/swarm-forge/blob/main/state-store.json), ensuring persistence across application restarts.

---

## Close Detection: The Watchdog Interceptor

The `Watchdog` class in [`src/watchdog/watchdog.ts`](https://github.com/unclebob/swarm-forge/blob/main/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.

```ts
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`](https://github.com/unclebob/swarm-forge/blob/main/src/watchdog/windowRestorer.ts) executes the actual reopening logic. Unlike simple window recreation, this module reconstructs the *exact* runtime environment:

```ts
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

```ts
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`](https://github.com/unclebob/swarm-forge/blob/main/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`](https://github.com/unclebob/swarm-forge/blob/main/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`](https://github.com/unclebob/swarm-forge/blob/main/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`](https://github.com/unclebob/swarm-forge/blob/main/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`](https://github.com/unclebob/swarm-forge/blob/main/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`](https://github.com/unclebob/swarm-forge/blob/main/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.