Resizing a column: every step renders the whole list page in the background and queues Columns saved. #521

Closed
opened 2026-10-01 10:47:21 +00:00 by tiagoagueda · 1 comment
Owner

Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading the column-width save in static/js/app.js and the view it posts to.

Checked again on 2026-10-01 against main at 0a5cad587: still present. The code is now at static/js/app.js:2079-2102 (saveWidth) and static/js/app.js:2190-2236 (the saves on pointerup, pointercancel and the arrow keys); core/views_tables.py and core/tables.py are unchanged.

A column width is saved by posting to the same view as the Columns form, and that view answers every save with a success message and a redirect. fetch follows the redirect, so each save is a profile write plus a full render of the list page whose result is thrown away, and that discarded render is what consumes the message. The keyboard path saves on every arrow press, with no debounce and no ordering.

  • Where: static/js/app.js:1846-1869 (saveWidth: fetch(head.dataset.colSettings, {method: "POST", …}) with no redirect option; the X-Requested-With header it sends is read nowhere on the server); static/js/app.js:1976-2003 (a save on pointerup, on pointercancel and on every ArrowLeft or ArrowRight keydown); core/views_tables.py:34-40 (table.clean_settings(request.POST, current), messages.success(request, _("Columns saved.")) unless move is posted, redirect(safe_next(request, "/"))); core/tables.py:759-770 (save_settings reads the profile's whole table_settings, changes one key and writes it back).
  • What happens: holding ArrowRight on a column's handle auto-repeats, and each repeat is a POST that writes the profile and then a GET of the applications or companies list with its query, filters and layout. Overlapping saves are not serialised, so the last write to land can be an intermediate width, and the column comes back narrower after a reload. A save still in flight when the person moves on can leave its Columns saved. for their next page.
  • Why it matters: wasted server work proportional to how long a key is held (on SQLite each write takes the database's write lock), an occasional wrong stored width, and an occasional stray banner.
  • Fix: answer a background width save with 204 No Content and no message (for example when width is in the POST, or on an explicit flag the script sends), and keep the redirect and message for the form. In saveWidth, debounce the keyboard saves (save once the arrows have been still for about 400 ms, or on keyup); pointer saves are already one per drag.

Done when

  • A test in tests/test_tables.py posts width, px, order and show the way the script does and asserts a 204 with no message stored, while the Columns form's post still redirects with Columns saved. It fails today and passes after the fix.
Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading the column-width save in `static/js/app.js` and the view it posts to. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. The code is now at `static/js/app.js:2079-2102` (`saveWidth`) and `static/js/app.js:2190-2236` (the saves on `pointerup`, `pointercancel` and the arrow keys); `core/views_tables.py` and `core/tables.py` are unchanged. A column width is saved by posting to the same view as the *Columns* form, and that view answers every save with a success message and a redirect. `fetch` follows the redirect, so each save is a profile write plus a full render of the list page whose result is thrown away, and that discarded render is what consumes the message. The keyboard path saves on every arrow press, with no debounce and no ordering. - **Where:** `static/js/app.js:1846-1869` (`saveWidth`: `fetch(head.dataset.colSettings, {method: "POST", …})` with no `redirect` option; the `X-Requested-With` header it sends is read nowhere on the server); `static/js/app.js:1976-2003` (a save on `pointerup`, on `pointercancel` and on every ArrowLeft or ArrowRight keydown); `core/views_tables.py:34-40` (`table.clean_settings(request.POST, current)`, `messages.success(request, _("Columns saved."))` unless `move` is posted, `redirect(safe_next(request, "/"))`); `core/tables.py:759-770` (`save_settings` reads the profile's whole `table_settings`, changes one key and writes it back). - **What happens:** holding ArrowRight on a column's handle auto-repeats, and each repeat is a POST that writes the profile and then a GET of the applications or companies list with its query, filters and layout. Overlapping saves are not serialised, so the last write to land can be an intermediate width, and the column comes back narrower after a reload. A save still in flight when the person moves on can leave its *Columns saved.* for their next page. - **Why it matters:** wasted server work proportional to how long a key is held (on SQLite each write takes the database's write lock), an occasional wrong stored width, and an occasional stray banner. - **Fix:** answer a background width save with `204 No Content` and no message (for example when `width` is in the POST, or on an explicit flag the script sends), and keep the redirect and message for the form. In `saveWidth`, debounce the keyboard saves (save once the arrows have been still for about 400 ms, or on keyup); pointer saves are already one per drag. ## Done when - A test in `tests/test_tables.py` posts `width`, `px`, `order` and `show` the way the script does and asserts a 204 with no message stored, while the *Columns* form's post still redirects with *Columns saved.* It fails today and passes after the fix.
tiagoagueda added this to the 0.6.0 milestone 2026-10-01 10:47:21 +00:00
Author
Owner

Landed on main with a green Forgejo CI run. Its entry in the CHANGELOG's Unreleased section ends in (#521).

Landed on main with a green Forgejo CI run. Its entry in the CHANGELOG's Unreleased section ends in (#521).
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#521
No description provided.