Settings: a failed save can be retried, and says what is happening #275

Merged
robbertbos merged 1 commit from settings-retry into main 2026-08-09 21:05:58 +00:00
Owner

Closes the last open item in #208, plus three things found next to it while
testing.

The item from #208

Voorkeuren and Automatisch opruimen save on change, with no submit button. So a
failed save left nothing to press: picking the same value again fires no change
event, and reloading was the only way back. The banner said "Probeer het
opnieuw" without offering the means.

Each panel now keeps the last patch it sent, and the banner offers it back
through nldd-banner's own actions slot.

Found while testing, not in #208

Automatisch opruimen showed nothing at all when a save failed - the same
silence #201 fixed for Voorkeuren. It was never passed failed. It also had no
live region, so a screen reader heard none of this either. Both fixed, with the
tests it never had.

Both panels shared one mutation, so they shared its state: changing a theme
put "Opslaan..." under Automatisch opruimen as well, and a failure would have
shown the notice in a panel the user never touched. One saver per panel now, so
the state cannot leak rather than being filtered afterwards. Settings.vue gets
the test it never had; it fails on the shared-mutation version.

Offline said "Opslaan..." forever. TanStack pauses a mutation instead of
running it (networkMode: 'online'), so nothing was being saved and no error
would ever arrive - the retry button could not appear by construction. That
state now says "Wachten op verbinding...".

The save notice

  • It flashed on every change. It now waits a second - the point where a delay
    stops feeling instant, and the same hold-back nldd-activity-indicator uses.
  • It sat at the foot of the card, so it belonged to whichever of the three
    fields you last touched. Each field carries its own now, and only the one
    being saved speaks.
  • It kept the meta font size and secondary colour, so it whispered. It appears
    only when something is slow or stuck, so it takes the content tier - which in
    this repo means no size rule at all.
  • The spinner is nldd-activity-indicator, which needs a box of its own:
    measured at 518x518 when left to fill its parent, 20x20 once given one. Its
    own 1000ms hold-back is off, since the delay is already ours.

The banner drops its full stop: "Opslaan mislukt" is a fragment, and the other
fragment-shaped notice in the app has none either.

Verification

  • 764 frontend unit tests, vue-tsc, eslint 0 errors, the full e2e suite
    (67 passed, 3 skipped), uvx pre-commit run --all-files including the
    generated-API-types gate.
  • Driven in a browser with a delayed PUT /api/settings: exactly one live
    region speaks, and it is the field that was changed.
  • No backend change.
Closes the last open item in #208, plus three things found next to it while testing. ## The item from #208 Voorkeuren and Automatisch opruimen save on change, with no submit button. So a failed save left nothing to press: picking the same value again fires no change event, and reloading was the only way back. The banner said "Probeer het opnieuw" without offering the means. Each panel now keeps the last patch it sent, and the banner offers it back through `nldd-banner`'s own `actions` slot. ## Found while testing, not in #208 **Automatisch opruimen showed nothing at all when a save failed** - the same silence #201 fixed for Voorkeuren. It was never passed `failed`. It also had no live region, so a screen reader heard none of this either. Both fixed, with the tests it never had. **Both panels shared one mutation**, so they shared its state: changing a theme put "Opslaan..." under Automatisch opruimen as well, and a failure would have shown the notice in a panel the user never touched. One saver per panel now, so the state cannot leak rather than being filtered afterwards. `Settings.vue` gets the test it never had; it fails on the shared-mutation version. **Offline said "Opslaan..." forever.** TanStack pauses a mutation instead of running it (`networkMode: 'online'`), so nothing was being saved and no error would ever arrive - the retry button could not appear by construction. That state now says "Wachten op verbinding...". ## The save notice - It flashed on every change. It now waits a second - the point where a delay stops feeling instant, and the same hold-back `nldd-activity-indicator` uses. - It sat at the foot of the card, so it belonged to whichever of the three fields you last touched. Each field carries its own now, and only the one being saved speaks. - It kept the meta font size and secondary colour, so it whispered. It appears only when something is slow or stuck, so it takes the content tier - which in this repo means no size rule at all. - The spinner is `nldd-activity-indicator`, which needs a box of its own: measured at 518x518 when left to fill its parent, 20x20 once given one. Its own 1000ms hold-back is off, since the delay is already ours. The banner drops its full stop: "Opslaan mislukt" is a fragment, and the other fragment-shaped notice in the app has none either. ## Verification - 764 frontend unit tests, `vue-tsc`, eslint 0 errors, the full e2e suite (67 passed, 3 skipped), `uvx pre-commit run --all-files` including the generated-API-types gate. - Driven in a browser with a delayed `PUT /api/settings`: exactly one live region speaks, and it is the field that was changed. - No backend change.
Give a failed settings save something to press
All checks were successful
CI / release-scripts (pull_request) Successful in 5s
security-scan / SBOM (trivy) (pull_request) Successful in 9s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 15s
security-scan / JS SCA (npm audit) (pull_request) Successful in 17s
security-scan / Python SAST (bandit) (pull_request) Successful in 20s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 38s
test-build / build (frontend) (pull_request) Successful in 59s
CI / pre-commit (pull_request) Successful in 53s
test-build / build (backend) (pull_request) Successful in 1m1s
test-build / build (pull_request) Successful in 0s
CI / frontend-test (pull_request) Successful in 1m11s
CI / backend-test (pull_request) Successful in 2m12s
CI / e2e (pull_request) Successful in 2m23s
8ad652f081
Voorkeuren and Automatisch opruimen both save on change, so a failure
left nothing to retry: picking the same value again fires no change
event, and reloading was the only way out. The banner said "Probeer het
opnieuw" without offering the means. Settings.vue now keeps the last
patch and the banner offers it back, through nldd-banner's own actions
slot.

Automatisch opruimen showed nothing at all when a save failed - the
silence that was fixed for Voorkeuren in #201, still here because this
panel came later. It gets the same notice.
Give each settings panel its own save state
All checks were successful
CI / release-scripts (pull_request) Successful in 6s
security-scan / SBOM (trivy) (pull_request) Successful in 9s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 16s
security-scan / JS SCA (npm audit) (pull_request) Successful in 17s
security-scan / Python SAST (bandit) (pull_request) Successful in 20s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 38s
CI / pre-commit (pull_request) Successful in 53s
test-build / build (frontend) (pull_request) Successful in 58s
test-build / build (backend) (pull_request) Successful in 1m2s
test-build / build (pull_request) Successful in 0s
CI / frontend-test (pull_request) Successful in 1m10s
CI / backend-test (pull_request) Successful in 2m6s
CI / e2e (pull_request) Successful in 2m18s
d82aa058f1
Voorkeuren and Automatisch opruimen shared one mutation, so they shared
its flags: changing a theme put "Opslaan..." under both panels, and a
failure would have shown the notice in a panel the user never touched.

One saver per panel instead of one filtered by which panel started it -
the state cannot leak rather than being sorted out afterwards. The route
gets the test it never had; it fails on the shared-mutation version.
Say what is actually happening while a setting saves
Some checks failed
CI / release-scripts (pull_request) Successful in 6s
security-scan / SBOM (trivy) (pull_request) Successful in 11s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 15s
security-scan / JS SCA (npm audit) (pull_request) Successful in 18s
security-scan / Python SAST (bandit) (pull_request) Successful in 20s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 38s
CI / pre-commit (pull_request) Successful in 53s
test-build / build (frontend) (pull_request) Successful in 58s
test-build / build (backend) (pull_request) Successful in 1m1s
test-build / build (pull_request) Successful in 0s
CI / frontend-test (pull_request) Failing after 1m7s
CI / backend-test (pull_request) Successful in 2m18s
CI / e2e (pull_request) Successful in 2m28s
915f86c53a
Three things the status line got wrong. It flashed "Opslaan..." on every
change, where the control had already moved and the flash was noise - it
now waits 600ms, so only a slow save says anything. Offline it said
"Opslaan..." indefinitely, but TanStack pauses a mutation instead of
running it (networkMode 'online'), so nothing was being saved and no
error would ever arrive; that state now says "Wachten op verbinding...".
And because the line appears only when something is slow or stuck, it
reads in the content colour rather than the secondary one, which this
repo reserves for text that labels other text.

Automatisch opruimen also had no live region at all, so a screen reader
heard none of this. It has the same one Voorkeuren already had.

The banner drops its full stop: "Opslaan mislukt" is a fragment, and the
other fragment-shaped notice in the app has none either.
Let the slow-save notice reach the reading size, and wait a full second
Some checks failed
CI / release-scripts (pull_request) Successful in 5s
security-scan / SBOM (trivy) (pull_request) Successful in 11s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 15s
security-scan / JS SCA (npm audit) (pull_request) Successful in 17s
security-scan / Python SAST (bandit) (pull_request) Successful in 21s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 40s
CI / pre-commit (pull_request) Successful in 50s
test-build / build (frontend) (pull_request) Successful in 58s
test-build / build (backend) (pull_request) Successful in 1m1s
test-build / build (pull_request) Successful in 0s
CI / frontend-test (pull_request) Failing after 1m6s
CI / backend-test (pull_request) Successful in 2m10s
CI / e2e (pull_request) Successful in 2m19s
13b854eb6a
Two corrections. The notice kept the meta font size, so removing only
its colour changed nothing visible; it is a sentence the reader reads,
which in this repo means no size rule at all.

And the threshold moves from 600ms to 1000ms. That is where a delay
stops feeling instant, and it is the same number nldd-activity-indicator
uses to keep brief loading states from flashing.
Put the save notice next to the setting it is about
Some checks failed
CI / release-scripts (pull_request) Successful in 6s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 15s
security-scan / SBOM (trivy) (pull_request) Successful in 11s
security-scan / JS SCA (npm audit) (pull_request) Successful in 17s
security-scan / Python SAST (bandit) (pull_request) Successful in 22s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 42s
CI / pre-commit (pull_request) Successful in 50s
test-build / build (frontend) (pull_request) Successful in 58s
test-build / build (backend) (pull_request) Successful in 1m1s
test-build / build (pull_request) Successful in 0s
CI / frontend-test (pull_request) Failing after 1m6s
CI / e2e (pull_request) Successful in 2m17s
CI / backend-test (pull_request) Successful in 2m9s
cc8261564a
It sat at the foot of the card, so it belonged to whichever of the three
fields you last touched - which the reader cannot tell, and far enough
from the control that they may not see it at all. Each field now carries
its own, and only the one being saved speaks.

The spinner is nldd-activity-indicator, which needs a box of its own:
measured at 518x518 when left to fill its parent, 20x20 once given one.
Its own 1000ms hold-back is switched off, since the delay is already
ours - otherwise the two would stack.
robbertbos changed title from Settings: a failed save can be retried to Settings: a failed save can be retried, and says what is happening 2026-08-09 20:55:39 +00:00
robbertbos force-pushed settings-retry from cc8261564a
Some checks failed
CI / release-scripts (pull_request) Successful in 6s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 15s
security-scan / SBOM (trivy) (pull_request) Successful in 11s
security-scan / JS SCA (npm audit) (pull_request) Successful in 17s
security-scan / Python SAST (bandit) (pull_request) Successful in 22s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 42s
CI / pre-commit (pull_request) Successful in 50s
test-build / build (frontend) (pull_request) Successful in 58s
test-build / build (backend) (pull_request) Successful in 1m1s
test-build / build (pull_request) Successful in 0s
CI / frontend-test (pull_request) Failing after 1m6s
CI / e2e (pull_request) Successful in 2m17s
CI / backend-test (pull_request) Successful in 2m9s
to cfa8249bd0
All checks were successful
CI / release-scripts (pull_request) Successful in 6s
security-scan / SBOM (trivy) (pull_request) Successful in 11s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 14s
security-scan / JS SCA (npm audit) (pull_request) Successful in 19s
security-scan / Python SAST (bandit) (pull_request) Successful in 20s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 39s
CI / pre-commit (pull_request) Successful in 53s
test-build / build (frontend) (pull_request) Successful in 1m5s
test-build / build (backend) (pull_request) Successful in 1m7s
CI / frontend-test (pull_request) Successful in 1m8s
test-build / build (pull_request) Successful in 0s
CI / backend-test (pull_request) Successful in 2m8s
CI / e2e (pull_request) Successful in 2m19s
CI / release-scripts (push) Successful in 7s
security-scan / SBOM (trivy) (push) Successful in 12s
security-scan / Filesystem scan (trivy fs) (push) Successful in 15s
security-scan / JS SCA (npm audit) (push) Successful in 17s
security-scan / Python SAST (bandit) (push) Successful in 19s
security-scan / Python SCA (pip-audit) (push) Successful in 40s
CI / pre-commit (push) Successful in 1m1s
publish-main / build (frontend) (push) Successful in 1m1s
publish-main / build (backend) (push) Successful in 1m7s
publish-main / build (push) Successful in 0s
CI / frontend-test (push) Successful in 1m12s
CI / backend-test (push) Successful in 2m17s
CI / e2e (push) Successful in 2m26s
2026-08-09 21:01:56 +00:00
Compare
robbertbos deleted branch settings-retry 2026-08-09 21:05:58 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
robbertbos/waggle!275
No description provided.