Ask before the filter editors lose your changes #177

Closed
opened 2026-09-01 14:05:44 +00:00 by jcoffey-dev · 0 comments
Owner

Fixes #175.

The problem

Both editors on Filters & rules kept their edits in component state, so every way out of the page discarded them silently — a settings link, the app rail, even the Rules/Scripts switch. And as the issue points out, with a long rule list the Save button is scrolled off the bottom, so there was no persistent sign that anything was pending either.

The fix

Ask before leaving. Editors register what they have pending (web/src/lib/unsavedChanges.ts); navigation asks first. The question is Save / Discard / Stay here rather than a yes-or-no "leave without saving?", which would make losing the work the easy answer.

Wouter routes links, redirects and navigate() through a single aroundNav hook, so one guard on the router covers the app rail and the settings nav without either of them knowing an editor exists. The Rules/Scripts switch asks for itself, since it unmounts the editor without ever reaching the router. Reload and tab close fall to beforeunload and the browser's own generic prompt.

The back button is the known gap: by the time popstate arrives the history has already moved, and holding the page would mean pushing an entry back, which breaks the button for everyone with nothing pending.

Pin the save bar. It sticks to the foot of the settings pane with an "Unsaved changes" marker, so the indicator is on screen whether or not the rules fit in the window.

Two things fixed on the way past

Both in the raw script editor:

  • Saving cleared only sel, but the editor is shown whenever there is a name or a body — so it stayed open with the name field unlocked, and a second save created a duplicate script instead of updating the one just written.
  • The condition deciding whether the editor is open was a pair of identical nested ifs; it is now the one question it was asking twice.

Verification

npm run typecheck, npm test (492 pass, 6 new) and npm run build all green. Driven by hand against dev:mock as well:

  • 16 rules, one toggled → bar pinned at the foot showing Unsaved changes, rules scrolling behind it
  • settings nav link → dialog, URL unchanged; Save changes → "Filters saved", then the navigation completes
  • Rules → Scripts switch → same dialog; Discard changes → lands on Scripts
  • raw script edited, then Mail on the app rail → dialog with the script's own wording; saving leaves the script's active state exactly as it was, and the edit is there on reopening

One note: sticky bottom: 0 stops at the scroll container's padding box, which left the pane's 64px bottom padding as a strip for rules to scroll through below the bar. The offset is now minus that padding, via a variable so the narrow layout's 80px works too.

Not translated yet

Eight new strings fall back to English in all nine locales. Happy to add translations if you'd rather not wait for a native pass.

Merged 2026-09-01 as coffey-labs/ihasmail@1a29784848

Rebuilt from: git history, session transcript.

Fixes #175. ## The problem Both editors on **Filters & rules** kept their edits in component state, so every way out of the page discarded them silently — a settings link, the app rail, even the Rules/Scripts switch. And as the issue points out, with a long rule list the Save button is scrolled off the bottom, so there was no persistent sign that anything was pending either. ## The fix **Ask before leaving.** Editors register what they have pending (`web/src/lib/unsavedChanges.ts`); navigation asks first. The question is Save / Discard / Stay here rather than a yes-or-no "leave without saving?", which would make losing the work the easy answer. Wouter routes links, redirects and `navigate()` through a single `aroundNav` hook, so one guard on the router covers the app rail and the settings nav without either of them knowing an editor exists. The Rules/Scripts switch asks for itself, since it unmounts the editor without ever reaching the router. Reload and tab close fall to `beforeunload` and the browser's own generic prompt. The back button is the known gap: by the time `popstate` arrives the history has already moved, and holding the page would mean pushing an entry back, which breaks the button for everyone with nothing pending. **Pin the save bar.** It sticks to the foot of the settings pane with an "Unsaved changes" marker, so the indicator is on screen whether or not the rules fit in the window. ## Two things fixed on the way past Both in the raw script editor: - Saving cleared only `sel`, but the editor is shown whenever there is a name or a body — so it stayed open with the name field unlocked, and a second save created a duplicate script instead of updating the one just written. - The condition deciding whether the editor is open was a pair of identical nested `if`s; it is now the one question it was asking twice. ## Verification `npm run typecheck`, `npm test` (492 pass, 6 new) and `npm run build` all green. Driven by hand against `dev:mock` as well: - 16 rules, one toggled → bar pinned at the foot showing **Unsaved changes**, rules scrolling behind it - settings nav link → dialog, URL unchanged; **Save changes** → "Filters saved", then the navigation completes - Rules → Scripts switch → same dialog; **Discard changes** → lands on Scripts - raw script edited, then Mail on the app rail → dialog with the script's own wording; saving leaves the script's *active* state exactly as it was, and the edit is there on reopening One note: sticky `bottom: 0` stops at the scroll container's padding box, which left the pane's 64px bottom padding as a strip for rules to scroll through *below* the bar. The offset is now minus that padding, via a variable so the narrow layout's 80px works too. ## Not translated yet Eight new strings fall back to English in all nine locales. Happy to add translations if you'd rather not wait for a native pass. **Merged** 2026-09-01 as coffey-labs/ihasmail@1a29784848c7 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.