Editor has no beforeunload handler — user can refresh or close tab and lose unsaved path edits silently #161

Open
opened 2026-10-03 16:10:50 +00:00 by Mark · 0 comments
Owner

Finding

The Editor (src/pages/Editor.tsx) writes its path changes to sessionStorage on every modification (via the useEffect at line 188-210). The user can:

  • Refresh the page (Cmd-R or F5) — sessionStorage is preserved, edits are restored
  • Close the tab — sessionStorage is per-tab, but the session that wrote it ends; the next visit to /editor finds stale data (or nothing, depending on timing)
  • Navigate to a different route within the same tab — Back to Workspace carries edits, but a navigation to /gallery carries nothing (the Editor is unmounted, the sessionStorage entry is still there but is consumed by the next /workspace mount via #110)

The app has no beforeunload handler. The user can:

  • Edit a path in the Editor
  • Refresh the page (or close the tab)
  • Lose their edit silently (sessionStorage may or may not survive the refresh, depending on the browser)

The beforeunload event in modern browsers is restricted (you can prompt the user to confirm leaving, but you can't prevent navigation; you can only show a "you have unsaved changes" message and let the user decide).

Where

  • No beforeunload handler: grep -rn 'beforeunload' src/ returns no matches
  • Editor sessionStorage write: src/pages/Editor.tsx:188-210
  • Editor undo/redo (50-step history): src/pages/Editor.tsx:186-200

Suggested fix shape

Add a beforeunload handler in the Editor:

  1. Track an isDirty state that flips to true on any path mutation and false after a save (or after Editor handleExport).
  2. Add a useEffect that registers beforeunload when isDirty is true:
    useEffect(() => {
      if (!isDirty) return;
      const handler = (e: BeforeUnloadEvent) => {
        e.preventDefault();
        e.returnValue = ''; // required for Chrome to show the prompt
      };
      window.addEventListener('beforeunload', handler);
      return () => window.removeEventListener('beforeunload', handler);
    }, [isDirty]);
    
  3. Clear isDirty when the editor's sessionStorage write succeeds (or when handleExport runs).

The user sees the browser's "Leave site? Changes you made may not be saved." dialog and can choose to stay or leave. The current behavior is silent loss.

Severity

Medium. The user can lose work without warning. The fix is small.

Discovered by

Gap analysis (2026-10-02) of editor resilience.

  • #110 (closed) — Editor → Workspace round-trip. The persistence is in place; the unsaved-changes warning is the missing piece.
  • #150 (closed) — Editor Save button. The user can now save from the editor; combined with beforeunload, edits are protected on every leave path.
## Finding The Editor (`src/pages/Editor.tsx`) writes its path changes to `sessionStorage` on every modification (via the useEffect at line 188-210). The user can: - Refresh the page (Cmd-R or F5) — sessionStorage is preserved, edits are restored - Close the tab — sessionStorage is per-tab, but the session that wrote it ends; the next visit to `/editor` finds stale data (or nothing, depending on timing) - Navigate to a different route within the same tab — `Back to Workspace` carries edits, but a navigation to `/gallery` carries nothing (the Editor is unmounted, the sessionStorage entry is still there but is consumed by the next /workspace mount via #110) The app has **no `beforeunload` handler**. The user can: - Edit a path in the Editor - Refresh the page (or close the tab) - Lose their edit silently (sessionStorage may or may not survive the refresh, depending on the browser) The `beforeunload` event in modern browsers is restricted (you can prompt the user to confirm leaving, but you can't prevent navigation; you can only show a "you have unsaved changes" message and let the user decide). ## Where - No `beforeunload` handler: `grep -rn 'beforeunload' src/` returns no matches - Editor sessionStorage write: `src/pages/Editor.tsx:188-210` - Editor undo/redo (50-step history): `src/pages/Editor.tsx:186-200` ## Suggested fix shape Add a `beforeunload` handler in the Editor: 1. Track an `isDirty` state that flips to `true` on any path mutation and `false` after a save (or after Editor `handleExport`). 2. Add a `useEffect` that registers `beforeunload` when `isDirty` is true: ```ts useEffect(() => { if (!isDirty) return; const handler = (e: BeforeUnloadEvent) => { e.preventDefault(); e.returnValue = ''; // required for Chrome to show the prompt }; window.addEventListener('beforeunload', handler); return () => window.removeEventListener('beforeunload', handler); }, [isDirty]); ``` 3. Clear `isDirty` when the editor's sessionStorage write succeeds (or when handleExport runs). The user sees the browser's "Leave site? Changes you made may not be saved." dialog and can choose to stay or leave. The current behavior is silent loss. ## Severity Medium. The user can lose work without warning. The fix is small. ## Discovered by Gap analysis (2026-10-02) of editor resilience. ## Related - #110 (closed) — Editor → Workspace round-trip. The persistence is in place; the unsaved-changes warning is the missing piece. - #150 (closed) — Editor Save button. The user can now save from the editor; combined with beforeunload, edits are protected on every leave path.
Sign in to join this conversation.
No description provided.