Dual-Snapshot Dirty Tracking

Dual-Snapshot Dirty Tracking

To know whether an editor has unsaved changes, keep two copies of the data — original (the loaded snapshot) and current (the working copy) — mutate only current in place, and compute dirtiness by comparing the two on demand. No per-edit change-counter, no delta log, no diff algorithm threaded through every mutation.

interface FileState { original: LocaleData; current: LocaleData }
// edit:  state.current[locale][key] = value
// dirty? walk original vs current, compare values

(localization-helper src/store/store.ts:11-21,47-62)

Why it wins: the dirty state is a pure function of two snapshots, so it is correct regardless of how the user got there — partial saves, undo, a crashed render, ten edits that cancel out to no net change all resolve correctly because nothing is accumulated incrementally. Edits stay dumb (just write current); the comparison is the single source of truth.

Where it breaks down: large datasets where a full walk per check is too expensive, or where you need the list of changes (not just yes/no) — then you need an explicit operation log alongside it (the same tool keeps one for structural ops; see Version Structure, Not Content). Cheap dirtiness ≠ cheap diff.

This is the same projection idea as Stateless Editor Panel as Pure Projection: derive state from data rather than maintaining it imperatively.