# How DrawDB Tracks History for Table and Field Updates: Inside the Undo/Redo System

> Discover how DrawDB tracks history for table and field updates with its client-side undo/redo system and efficient diff engine. Learn the mechanics behind saving your diagram changes.

- Repository: [drawDB/drawdb](https://github.com/drawdb-io/drawdb)
- Tags: internals
- Published: 2026-08-14

---

**DrawDB tracks history for table and field updates using a client-side undo/redo system built on two stacks (undo and redo) and a minimal diff engine that captures only the changes between diagram snapshots.**

DrawDB is an open-source database diagramming tool that provides robust version control for schema modifications directly in the browser. Understanding how DrawDB tracks history for table and field updates reveals a lightweight yet powerful architecture that enables seamless undo and redo functionality across the entire diagram editor. The implementation relies on React hooks and context providers to maintain immutable state snapshots without server dependencies.

## The Core Architecture: Stacks and Diffs

At the heart of DrawDB's history tracking lies a dual-stack system implemented in [`src/hooks/useUndoRedo.js`](https://github.com/drawdb-io/drawdb/blob/main/src/hooks/useUndoRedo.js). This hook maintains an **undo stack** for past actions, a **redo stack** for reversed actions, and a pointer to the **present state** representing the current diagram.

The `UndoRedoContext` provider in [`src/context/UndoRedoContext.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/context/UndoRedoContext.jsx) wraps the application, exposing history functions to any component that modifies tables or fields. When a user renames a column or moves a table, the component calls `addHistoryEntry()` with the new diagram state.

To minimize memory footprint, DrawDB does not store complete state snapshots. Instead, the `diff` utility in [`src/utils/diff.js`](https://github.com/drawdb-io/drawdb/blob/main/src/utils/diff.js) generates a minimal diff object between the previous and new states. This diff represents precisely what changed—whether a field type update, table deletion, or relationship modification—making history entries compact and fast to apply.

## Creating History Entries with addHistoryEntry

When a table or field update occurs, the application invokes `addHistoryEntry(newState)` to record the change. This function calculates the difference between the current and new states, pushes that diff onto the undo stack via a reducer dispatch, and clears the redo stack since a new action invalidates the forward history.

```javascript
function addHistoryEntry(newState) {
  const diffEntry = createDiff(presentState, newState);   // src/utils/diff.js
  dispatch({ type: "PUSH", payload: diffEntry });        // pushes onto undo stack
  setPresentState(newState);                             // updates the current diagram
}

```

Components throughout the editor integrate this hook to track modifications. For example, when renaming a column:

```javascript
import { useUndoRedo } from '@/hooks/useUndoRedo';

function onColumnRename(tableId, columnId, newName) {
  const { diagram, addHistoryEntry } = useUndoRedo();

  const updatedDiagram = updateColumn(diagram, tableId, columnId, { name: newName });
  addHistoryEntry(updatedDiagram);           // <-- history recorded
}

```

## Executing Undo and Redo Operations

The **undo** operation reverses the most recent change by popping the latest diff from the undo stack, applying its inverse transformation using `diff.applyInverse`, and moving that diff to the redo stack. The **redo** operation performs the inverse, re-applying a stored diff to restore a previously undone state.

```javascript
import { useUndoRedo } from '@/hooks/useUndoRedo';

function handleUndo() {
  const { undo } = useUndoRedo();
  undo();                                    // reverts the most recent diff
}

```

For redo functionality:

```javascript
import { useUndoRedo } from '@/hooks/useUndoRedo';

function handleRedo() {
  const { redo } = useUndoRedo();
  redo();                                    // reapplies the last undone diff
}

```

The system also provides `clearHistory()` to reset both stacks when loading a new diagram or performing bulk operations that should not be tracked.

## Persistence and Session Recovery

Because the history consists of immutable diff objects, DrawDB can serialize the entire undo/redo stack for storage. The `SaveStateContext` provider in [`src/context/SaveStateContext.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/context/SaveStateContext.jsx) persists these stacks to `localStorage`, allowing users to restore their session—including the complete history of table and field updates—after closing and reopening the browser.

This architecture also supports export and import functionality. Users can serialize their diagram along with its history, enabling version control workflows where previous schema iterations remain accessible even after file transfers.

## Summary

- DrawDB implements history tracking through a custom `useUndoRedo` hook in [`src/hooks/useUndoRedo.js`](https://github.com/drawdb-io/drawdb/blob/main/src/hooks/useUndoRedo.js) that maintains separate undo and redo stacks.
- The system uses a minimal diff engine in [`src/utils/diff.js`](https://github.com/drawdb-io/drawdb/blob/main/src/utils/diff.js) to record changes between diagram states rather than storing full snapshots.
- Components call `addHistoryEntry()` to record table and field modifications, pushing compressed diff objects onto the history stack.
- Undo operations apply inverse diffs using `diff.applyInverse` while redo operations re-apply stored diffs, enabling arbitrary navigation through the edit history.
- History persistence is handled by `SaveStateContext`, which stores the stack state in `localStorage` for session recovery and supports serialization for export/import workflows.

## Frequently Asked Questions

### How does DrawDB store history without a backend database?

DrawDB uses a client-side architecture where the `useUndoRedo` hook maintains two in-memory stacks containing minimal diff objects. These diffs represent only the changes between states rather than complete diagram copies. The `SaveStateContext` provider serializes these stacks to `localStorage`, enabling history persistence across page reloads without requiring server-side storage.

### What happens to the redo stack when I make a new edit after undoing?

When you perform a new action after undoing, the `addHistoryEntry()` function automatically clears the redo stack. This behavior follows standard undo/redo conventions: once you diverge from the historical timeline by creating new changes, the previously undone actions are permanently discarded from the forward history to prevent branching state conflicts.

### Can DrawDB history track individual field properties like data types or constraints?

Yes, the diff engine in [`src/utils/diff.js`](https://github.com/drawdb-io/drawdb/blob/main/src/utils/diff.js) captures granular changes at the field level. When you modify a column's data type, name, or constraints, the system generates a diff object representing that specific property change. The `updateColumn` utility creates a new diagram state with the modified field, and `addHistoryEntry()` records the precise difference between the old and new column definitions.

### Is there a limit to how many undo steps DrawDB retains?

According to the source implementation in [`src/hooks/useUndoRedo.js`](https://github.com/drawdb-io/drawdb/blob/main/src/hooks/useUndoRedo.js), there is no explicit hard limit on the undo stack size. Because each entry stores only a minimal diff rather than a full state snapshot, the memory footprint remains lightweight even with extensive histories. The system can theoretically track hundreds of table and field updates while maintaining responsive performance, limited only by available browser memory.