fix(client): stop widgets writing back settings they just read if unchanged or failed to read - #171
Conversation
Three effects in ChoreWidget and CalendarWidget persisted their settings whenever a load finished, rather than when a value changed. The loaded flag was a dependency, so completing the load was itself a trigger and the PATCH sent back exactly what the GET returned. Every mount was a write. On a display that rotates tabs this is continuous: a widget absent from the next tab unmounts and writes again on the way back. Measured on a kiosk at three writes per rotation cycle, unaffected by whether the dashboard was locked. The tab-specific effect in CalendarWidget is worse than the other two — it lists activeTab as a dependency, so it fires on every tab change even when the widget never unmounts. More serious than the churn: both widgets set their loaded flag in a finally block, so it flips to true even when the GET fails. The component is then holding its own defaults, and the persist effect wrote those over whatever the server had. A failed settings read silently replaced the user's stored settings with defaults. Both rules now live in one place. shouldPersistSettings refuses when the values match the ones loaded, and refuses outright when no load has succeeded — the snapshot stays null in that case, which is what distinguishes "unchanged" from "never read". Snapshots update only after a confirmed write; the tab-specific persist previously swallowed its error and resolved anyway, which would have marked the snapshot clean after a failed PATCH and suppressed the retry. The tab snapshot is keyed by tab number because that effect re-runs per tab, and comparing against another tab's values would keep writing on every rotation. Unit tests cover the helper, including the failed-load case. They are in client/src/utils/ because the client suite runs in node with no JSX renderer, so the decision is testable only once it is out of the component.
|
Reviewed critically and merging. I reproduced both the churn and the data loss on The bug is real, and the measurement is rightSet up a device with both widgets on one tab and counted writes to Three per mount, exactly as you measured on the kiosk. The data loss reproduces — and it is the serious oneThis took some care to trigger. Failing every GET to A user's stored preferences replaced by component defaults because a GET failed. Worth fixing on its own. No regression on real editsThe last line matters: the snapshot updates after the confirmed write, so the edit does not re-fire. On the codeCapturing I checked the things that could bite:
One thing worth a follow-up (not a blocker)The failed-load lockout is silent. When the GET fails, Server 224/224 and client 281/281 via CI, translation parity and build clean locally. |
Three effects in ChoreWidget and CalendarWidget persisted their settings whenever a load finished, rather than when a value changed. The loaded flag was a dependency, so completing the load was itself a trigger and the PATCH sent back exactly what the GET returned. Every mount was a write.
On a display that rotates tabs this is continuous: a widget absent from the next tab unmounts and writes again on the way back. Measured on a kiosk at three writes per rotation cycle, unaffected by whether the dashboard was locked.
The tab-specific effect in CalendarWidget is worse than the other two; it lists activeTab as a dependency, so it fires on every tab change even when the widget never unmounts.
More serious than the churn: both widgets set their loaded flag in a finally block, so it flips to true even when the GET fails. The component is then holding its own defaults, and the persist effect wrote those over whatever the server had. A failed settings read silently replaced the user's stored settings with defaults.
Both rules now live in one place. shouldPersistSettings refuses when the values match the ones loaded, and refuses outright when no load has succeeded - the snapshot stays null in that case, which is what distinguishes "unchanged" from "never read". Snapshots update only after a confirmed write; the tab-specific persist previously swallowed its error and resolved anyway, which would have marked the snapshot clean after a failed PATCH and suppressed the retry.
The tab snapshot is keyed by tab number because that effect re-runs per tab, and comparing against another tab's values would keep writing on every rotation.
Unit tests cover the helper, including the failed-load case. They are in client/src/utils/ because the client suite runs in node with no JSX renderer, so the decision is testable only once it is out of the component.