The page script fails silently: htmx errors show nothing, an expired session fills the table, Back breaks controls, export buttons lock #226

Closed
opened 2026-09-15 21:23:26 +00:00 by tiagoagueda · 0 comments
Owner

Found in the 2026-09-15 code audit.

1. A failed htmx request shows nothing

  • app.js has no listener for htmx:responseError or htmx:sendError; the only htmx config is base.html:12.
  • No template uses hx-indicator, so the .htmx-indicator rules (assets/css/app.css:689-696) never apply.
  • Every live filter, sort and page link swaps a table (partials/table/head.html, pagination.html, applications/partials/filters.html, jobs/company_list.html). On a 500, or offline, nothing changes and the filter just looks broken.

2. An expired session swaps the sign-in page into the table

HtmxMiddleware is loaded but nothing sends HX-Redirect, so htmx follows the login 302 and swaps the whole login page into #…-table.

3. Back restores controls that no longer work, and keeps personal data in sessionStorage

  • Table swaps use hx-push-url, and htmx 2 caches page bodies in sessionStorage (historyCacheSize: 10).
  • On Back it restores the column-resize handles (app.js:1148-1191), Select all (:508) and label chips (:816, 881-913) without their listeners.
  • The setup functions then skip them because their markers are present, and nothing listens for htmx:historyRestore.
  • The cached copy of personal data stays in sessionStorage after signing out in the same tab.

4. Export buttons stay locked after the first download

The submit-once guard (app.js:212-242) marks every non-htmx POST form and clears the mark only on pageshow. An attachment download never leaves the page, so after the first click these stay aria-disabled:

  • core/export.html:34 → export_download (FileResponse(as_attachment=True));
  • documents/cv_detail.html:18 → CVExportView → redirect to a download.

5. Duplication that makes this harder to fix

  • Two parallel sets of document-level drag listeners (app.js:96-177 and 1285-1336).
  • The "is somebody typing" check written twice (399-405, 449-456).
  • tokenFor(head) ignores its argument.
  • Three separate afterSwap registrations and no historyRestore.
  • A live region nothing updates (server/logs.html:71).

Proposal

  • One translated role="alert" region in base.html, with its words in data attributes, filled by a delegated responseError/sendError handler.
  • Set aria-busy on the target, and show an indicator while a request runs.
  • A small middleware that turns a login redirect for an htmx request into HX-Redirect.
  • Either delegated listeners plus an onContentReady(fn) helper covering DOMContentLoaded, afterSwap and historyRestore, or historyCacheSize: 0, refreshOnHistoryMiss: true (the views already handle history_restore_request).
  • Let a form opt out of the submit guard (data-download), or clear the mark on visibilitychange or after a few seconds. Add an e2e test that exports twice.
  • Extract isTyping() and a single drag controller; delete the dead pieces.
Found in the 2026-09-15 code audit. ## 1. A failed htmx request shows nothing - `app.js` has no listener for `htmx:responseError` or `htmx:sendError`; the only htmx config is `base.html:12`. - No template uses `hx-indicator`, so the `.htmx-indicator` rules (`assets/css/app.css:689-696`) never apply. - Every live filter, sort and page link swaps a table (`partials/table/head.html`, `pagination.html`, `applications/partials/filters.html`, `jobs/company_list.html`). On a 500, or offline, nothing changes and the filter just looks broken. ## 2. An expired session swaps the sign-in page into the table `HtmxMiddleware` is loaded but nothing sends `HX-Redirect`, so htmx follows the login 302 and swaps the whole login page into `#…-table`. ## 3. Back restores controls that no longer work, and keeps personal data in sessionStorage - Table swaps use `hx-push-url`, and htmx 2 caches page bodies in `sessionStorage` (`historyCacheSize: 10`). - On Back it restores the column-resize handles (`app.js:1148-1191`), *Select all* (`:508`) and label chips (`:816, 881-913`) without their listeners. - The setup functions then skip them because their markers are present, and nothing listens for `htmx:historyRestore`. - The cached copy of personal data stays in `sessionStorage` after signing out in the same tab. ## 4. Export buttons stay locked after the first download The submit-once guard (`app.js:212-242`) marks every non-htmx POST form and clears the mark only on `pageshow`. An attachment download never leaves the page, so after the first click these stay `aria-disabled`: - `core/export.html:34` → `export_download` (`FileResponse(as_attachment=True)`); - `documents/cv_detail.html:18` → `CVExportView` → redirect to a download. ## 5. Duplication that makes this harder to fix - Two parallel sets of document-level drag listeners (`app.js:96-177` and `1285-1336`). - The "is somebody typing" check written twice (`399-405`, `449-456`). - `tokenFor(head)` ignores its argument. - Three separate `afterSwap` registrations and no `historyRestore`. - A live region nothing updates (`server/logs.html:71`). ## Proposal - One translated `role="alert"` region in `base.html`, with its words in data attributes, filled by a delegated `responseError`/`sendError` handler. - Set `aria-busy` on the target, and show an indicator while a request runs. - A small middleware that turns a login redirect for an htmx request into `HX-Redirect`. - Either delegated listeners plus an `onContentReady(fn)` helper covering `DOMContentLoaded`, `afterSwap` and `historyRestore`, or `historyCacheSize: 0, refreshOnHistoryMiss: true` (the views already handle `history_restore_request`). - Let a form opt out of the submit guard (`data-download`), or clear the mark on `visibilitychange` or after a few seconds. Add an e2e test that exports twice. - Extract `isTyping()` and a single drag controller; delete the dead pieces.
tiagoagueda added this to the 0.4.0 milestone 2026-09-15 21:33: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#226
No description provided.