Save as you go: a field that failed to save is not sent again when the connection returns, and a server error is reported as no connection #1157

Closed
opened 2026-10-31 03:04:24 +00:00 by tiagoagueda · 0 comments
Owner

What exists today

static/js/app.js:2436-2477 sendField() posts one field with fetch and reads the answer as JSON. On any failure the .catch (2473-2476) writes the region's data-offline sentence, "Not saved: Postulo could not be reached. Press Save when it can.", and stops. The field keeps data-unsaved, so the leave guard still asks (settleGuard, 2430-2434): nothing is lost silently, and the person is told what to do. That part is sound, and why the scout's claim is narrowed.

What is missing:

  • No retry. The field is sent again only if it changes again or Save is pressed. Coming back online (window online event) or to the tab does nothing.
  • A server error reads as no connection. response.json() (2449-2451) is called whatever the status; a 500 or a proxy's 502 answers HTML, json() throws, and the person is told Postulo could not be reached when it could and failed. The status is never checked.

Only two pages use it: templates/settings/appearance.html:14 and settings/accessibility.html:14, when the person has switched the feature on.

What would change

  • Keep a per-form set of fields whose last send failed for want of a connection. On online, and on visibilitychange to visible, queue them again through queueSave (which already serialises saves and carries the stamp). Clear the set on success. No timer back-off is needed: the browser's online event is the signal, and the page can always be saved with Save.
  • Check response.ok before json(). A non-OK answer that is not the JSON refusal the view sends becomes a second sentence in the template, for example data-failed="Not saved: Postulo answered with an error. Press Save to try again.", translated with the page.
  • Write the status text only when it differs from what the region holds, so a retry that fails the same way is not announced again.

What it saves or protects

small: on the two settings pages that save as they go, a change made while offline is saved when the connection returns instead of waiting for Save; a 5xx is described as what it is

Accessibility

must be checked: the role="status" region (templates/settings/_save_status.html:9) must announce each state once (Saving…, Saved at…, Not saved…), and a retry that fails again must not rewrite the same sentence and announce it again; the guard must keep asking while a field is unsaved. Guarded by tests/e2e/test_save_as_you_go.py (live region present from the start, line 66; guard asks while refused, 164; scripts off, 212); no test covers a failed request

Decisions

  • Retry automatically, or keep "Press Save" as the only path? Recommended: retry on online and on return to the tab only, never on a timer; the sentence still says Save works.

Found by the efficiency and reliability audit of 2026-10-30 (finding A12; index postulo/postulo#1108). Checked against main at daee30b15 (2026-10-30).

## What exists today `static/js/app.js:2436-2477` `sendField()` posts one field with `fetch` and reads the answer as JSON. On any failure the `.catch` (2473-2476) writes the region's `data-offline` sentence, "Not saved: Postulo could not be reached. Press Save when it can.", and stops. The field keeps `data-unsaved`, so the leave guard still asks (`settleGuard`, 2430-2434): nothing is lost silently, and the person is told what to do. That part is sound, and why the scout's claim is narrowed. What is missing: - **No retry.** The field is sent again only if it changes again or *Save* is pressed. Coming back online (`window` `online` event) or to the tab does nothing. - **A server error reads as no connection.** `response.json()` (2449-2451) is called whatever the status; a 500 or a proxy's 502 answers HTML, `json()` throws, and the person is told Postulo could not be reached when it could and failed. The status is never checked. Only two pages use it: `templates/settings/appearance.html:14` and `settings/accessibility.html:14`, when the person has switched the feature on. ## What would change - Keep a per-form set of fields whose last send failed for want of a connection. On `online`, and on `visibilitychange` to visible, queue them again through `queueSave` (which already serialises saves and carries the stamp). Clear the set on success. No timer back-off is needed: the browser's `online` event is the signal, and the page can always be saved with *Save*. - Check `response.ok` before `json()`. A non-OK answer that is not the JSON refusal the view sends becomes a second sentence in the template, for example `data-failed="Not saved: Postulo answered with an error. Press Save to try again."`, translated with the page. - Write the status text only when it differs from what the region holds, so a retry that fails the same way is not announced again. ## What it saves or protects small: on the two settings pages that save as they go, a change made while offline is saved when the connection returns instead of waiting for *Save*; a 5xx is described as what it is ## Accessibility must be checked: the `role="status"` region (`templates/settings/_save_status.html:9`) must announce each state once (*Saving…*, *Saved at…*, *Not saved…*), and a retry that fails again must not rewrite the same sentence and announce it again; the guard must keep asking while a field is unsaved. Guarded by `tests/e2e/test_save_as_you_go.py` (live region present from the start, line 66; guard asks while refused, 164; scripts off, 212); no test covers a failed request ## Decisions - **Retry automatically, or keep "Press Save" as the only path?** Recommended: retry on `online` and on return to the tab only, never on a timer; the sentence still says *Save* works. --- Found by the efficiency and reliability audit of 2026-10-30 (finding A12; index postulo/postulo#1108). Checked against `main` at `daee30b15` (2026-10-30).
tiagoagueda added this to the 0.6.0 milestone 2026-10-31 03:04:24 +00:00
Sign in to join this conversation.
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
Postulo/postulo#1157
No description provided.