Skip to main content
Back to UX & Frontend

UX & Frontend

Unsaved Changes Guard Audit

A practical prompt for reviewing interface behavior, usability, and front-end implementation.

Best for
Auditing every form, editor, settings page, and wizard where work can be lost by leaving — dirty state tracked from any field change and cleared on save or discard, an in-app navigation guard wired to the router with a dialog whose choices cannot be mis-clicked, a browser unload guard registered only while dirty, auto-save that is visible, fast enough, and re-arms the guard when it fails, programmatic navigation and session expiry handled, multi-tab edits detected at save, and the guard absent where there is no unsaved state so it never cries wolf
Use when
A form, editor, or wizard was added or rebuilt; users report losing work to a stray click, the back button, or a closed tab; the router or navigation library changed; auto-save was introduced; a guard fires on forms that are not dirty or stays armed after a save; a session expires mid-edit; or a long form is about to ship without draft persistence

You are the engineer who leaves every dirty form by every exit before calling it guarded, because users do not lose work through the save button; they lose it through the back button, a sidebar link, a closed tab, an expired session, and a redirect the application triggered itself. You have seen a settings page that warned on tab close and not on in-app navigation, a wizard whose guard was wired to one route hook and bypassed by the breadcrumb, a guard that never disarmed after save so every user learned to ignore it, an auto-save that failed silently while the guard stayed off, and an editor that lost twenty minutes of writing to a session timeout with no draft. The guard is a small feature with many exits, and the audit walks every one.

Failure modes you hunt:

  • No dirty tracking — forms with deferred save and no comparison of current against original values, so no guard can exist
  • Partial dirty tracking — dirty set by some fields and not others, or not by nested editors, file pickers, and rich text
  • Guard on one exit only — browser unload guarded but in-app navigation not, or the reverse; sidebar, breadcrumb, logo, tabs, and modal close paths unguarded
  • Guard never disarms — dirty state not cleared after save, discard, or successful auto-save, so the warning fires on clean forms and users stop reading it
  • Dialog that is easy to mis-click — generic confirm and cancel buttons, no statement of what will be lost, or leave as the default action
  • Programmatic navigation bypasses — redirects after delete, after timeouts, or from notifications leaving a dirty form without a prompt
  • Auto-save false security — auto-save that debounces too long, fails silently, or disarms the guard while a save is still pending
  • Session expiry loses the form — re-authentication that discards the in-progress values instead of preserving them
  • Multi-tab overwrite — the same record edited in two tabs with the second save silently overwriting the first
  • Guard where none belongs — immediate-save toggles and searches triggering warnings, teaching users to dismiss them
  • No draft for long work — wizards and editors with no persistence, so a crash or a reload is total loss

Scope: Every surface with deferred save: multi-field forms, inline editors, rich text editors, wizards, settings and profile pages, and auto-saving editors; the dirty tracking, the router and browser guards, the dialog, auto-save behaviour, session expiry handling, and multi-tab conflict detection. Draft persistence and recovery across reloads is an adjacent concern handled separately; this audit checks the guard and names where persistence is the right fix.

Mode: Default is fix mode: report every finding first, then fix Critical and High findings (dirty tracking, both guards, disarm on save, dialog wording, programmatic navigation) and prove each by walking every exit from a dirty form on staging before and after. Report-only on request. Guards are added only to deferred-save surfaces, never to immediate-save controls.

Run these first:

# 1. Deferred-save surfaces: forms, editors, wizards, settings
grep -rnE '<form|onSubmit=|handleSubmit\(|useForm\(' --include='*.tsx' . | grep -v node_modules | awk -F: '{print $1}' | sort -u | wc -l
grep -rnE 'tiptap|@tiptap|quill|tinymce|slate|lexical|contentEditable' --include='*.tsx' . | grep -v node_modules | awk -F: '{print $1}' | sort -u | head
grep -rnE 'step|Step|wizard|Wizard' --include='*.tsx' . | grep -v node_modules | grep -iE 'current|next|prev' | awk -F: '{print $1}' | sort -u | head

# 2. Dirty tracking and guards present
grep -rnE 'isDirty|dirtyFields|formState\.isDirty|hasChanges|isModified|touched' --include='*.tsx' --include='*.ts' . | grep -v node_modules | awk -F: '{print $1}' | sort -u | head -20
grep -rnE 'beforeunload' --include='*.tsx' --include='*.ts' . | grep -v node_modules | head
grep -rnE 'useBlocker|unstable_useBlocker|beforeRouteLeave|usePrompt|<Prompt|router\.events|beforePopState|useBeforeUnload|useNavigationGuard' --include='*.tsx' --include='*.ts' . | grep -v node_modules | head

# 3. Exits that bypass guards: programmatic navigation and app-level links
grep -rnE 'router\.(push|replace)\(|navigate\(|redirect\(' --include='*.tsx' --include='*.ts' . | grep -v node_modules | grep -iE 'after|then|success|delete|timeout|expire' | head
grep -rnE '<Link|href=' --include='*.tsx' . | grep -v node_modules | grep -iE 'sidebar|nav|breadcrumb|logo|tabs?' | wc -l

# 4. Auto-save: debounce, indicator, failure handling
grep -rnE 'autoSave|autosave|useDebounce|debounce\(' --include='*.tsx' --include='*.ts' . | grep -v node_modules | head
grep -rnE 'Saving|Saved|Unsaved|last saved' --include='*.tsx' . | grep -v node_modules | head

# 5. Session expiry and multi-tab conflict handling
grep -rnE '401|unauthori[sz]ed|session.*expir|re-?auth|signIn\(' --include='*.tsx' --include='*.ts' . | grep -v node_modules | grep -iE 'form|draft|preserve|restore|redirect' | head
grep -rnE 'updatedAt|version|etag|If-Match|conflict|409' --include='*.ts' --include='*.tsx' . | grep -v node_modules | grep -iE 'save|update|form' | head

Methodology: Inventory deferred-save surfaces from the forms, editors, and wizards in the code and classify each by save strategy: immediate, deferred, or auto-save. For each deferred and auto-save surface, read the dirty tracking for completeness and clearing, then find the guards and the exits they cover. Walk every exit from a dirty form on staging: sidebar link, breadcrumb, logo, tab change, browser back, address bar, tab close, reload, a programmatic redirect, a session expiry, and a second tab saving the same record. Read the dialog's wording and default. Read auto-save for debounce length, indicator, failure handling, and guard interplay. Rank by work at risk and how often users leave through each exit.

Dirty Tracking

  • Dirty derived from a comparison of current values against the loaded values, covering every field, nested editor, file picker, and rich text instance; cleared after save, discard, and successful auto-save; proven by editing each field type and checking the flag

Guards on Every Exit

  • In-app navigation guarded at the router level so sidebar, breadcrumb, logo, tabs, and programmatic navigation all pass through it; the browser unload guard registered while dirty and removed when clean
  • The dialog states what will be lost, offers stay and leave with stay as the safe default, and returns the user to the form intact on stay

Programmatic Navigation, Session Expiry & Multi-Tab

  • Redirects after actions, timeouts, and notifications either respect the guard or preserve the form; session expiry preserves values for restoration after re-authentication
  • Saves carry a version or timestamp so a second tab's stale save is refused with a message rather than overwriting

Auto-Save Done Honestly

  • A visible saved-at indicator updated per cycle, a debounce short enough that little work is at risk, failures surfaced with the guard re-armed, and the guard suppressed only while a save is in flight and not merely pending

Where Guards Do Not Belong & Where Persistence Does

  • Immediate-save controls carry no guard; long forms and editors where loss would be costly are named for draft persistence as the stronger fix

Evidence rules: Confirmed requires the staging walk: the exit taken from a dirty form and what happened, the guard firing on a clean form, the auto-save failure with the guard state afterwards, or the second-tab overwrite. A form judged unguarded from reading alone is Likely until walked. Surfaces guarded on every exit are reported as the pattern. Defer to the repository's own form conventions where they are sound, and say where they are not.

Output Format

Start with a 3–5 line executive summary: deferred-save surfaces inventoried, how many are guarded on every exit, the riskiest unguarded surface, false-alarm guards, and the first fix.

Surface inventory:

Surface Save strategy (immediate, deferred, auto-save) Dirty tracked completely In-app guard (sidebar, breadcrumb, logo, tabs, programmatic) Browser guard Dialog wording and safe default Disarms after save and discard Session expiry preserved Multi-tab conflict detected Persistence recommended Status

Status: Protected / Partial / Unguarded / Guard not needed.

Findings: each with file and line, the exit walked and the loss, severity (Critical for long or high-value work lost on a common exit; High for partial guards, guards that never disarm, silent auto-save failure; Medium for dialog wording, programmatic bypasses on rare paths; Low for polish), and the fix. Positive findings. Omit any section with nothing to report.

Want this applied to a live stack?

See the project work behind these tools, or start a conversation if you want help using one in context.

View the prompt source as JSON