Recording what happened reloads the whole page, on the one screen somebody uses all day #257

Closed
opened 2026-09-17 15:07:05 +00:00 by tiagoagueda · 0 comments
Owner

An application's page is where the job search actually gets recorded: the status moves, an
event is added, a reminder is ticked off. Every one of those is a plain form post that
redirects to get_absolute_url(), so the answer to "I heard back" is a fresh render of a
347-line template with its prefetches, and the reader is returned to the top of a page they
were reading the middle of.

The forms are at application_detail.html lines 55 (status), 72 (add an event) and 333
(complete a reminder), with the interview and document actions beside them.

The server already does this

StatusChangeView.post (applications/views.py:476) branches on request.htmx and returns
applications/partials/application_row.html instead of redirecting. The list view asks for
that and gets a swapped row. The detail page posts the same form to the same view and does
not ask, so it takes the redirect.

There are four request.htmx branches in the whole application — two in
applications/views.py, one in jobs/views.py, one line of settings. htmx is a dependency
this project already pays for and it appears in seven of 182 templates. This screen is what
it was bought for.

What it should be

The status band, a new timeline entry and a reminder's completion swap in place. The page
does not move, the timeline grows where the eye already is, and the round trip carries a
fragment instead of a record with everything joined to it.

What comes free, and should be stated so it is not rebuilt:

  • Pending and failure are already handled. #226 puts aria-busy on whatever is being
    replaced and renders one role="alert" region whose words come from the page, so a swap
    that answers 500 or never arrives already says so. A new swap inherits that.
  • Focus is already a solved rule. #227: a control that swaps itself away has to hand
    focus to what replaces it, or the next Tab starts at the skip link. The reminder tick is
    exactly that shape — it removes itself.

What has to be built rather than borrowed: the partials. The one that exists is a row for
the list
, which is not what the detail page shows. A timeline entry, the status band and a
reminder row are three small templates, and the views already have the objects.

The forms must still post. Without a script the page has to behave exactly as it does
today — that is the same rule as #134 and #136, and here it costs nothing, because an
hx-post on a real form with a real action is progressive by construction.

Related: #247 wants the slow work moved onto django-tasks-db with a "working…" state.
These meet at the same place — what a swap shows while the server is still thinking.

An application's page is where the job search actually gets recorded: the status moves, an event is added, a reminder is ticked off. Every one of those is a plain form post that redirects to `get_absolute_url()`, so the answer to "I heard back" is a fresh render of a 347-line template with its prefetches, and the reader is returned to the top of a page they were reading the middle of. The forms are at `application_detail.html` lines 55 (status), 72 (add an event) and 333 (complete a reminder), with the interview and document actions beside them. ## The server already does this `StatusChangeView.post` (`applications/views.py:476`) branches on `request.htmx` and returns `applications/partials/application_row.html` instead of redirecting. The list view asks for that and gets a swapped row. The detail page posts the same form to the same view and does not ask, so it takes the redirect. There are four `request.htmx` branches in the whole application — two in `applications/views.py`, one in `jobs/views.py`, one line of settings. htmx is a dependency this project already pays for and it appears in seven of 182 templates. This screen is what it was bought for. ## What it should be The status band, a new timeline entry and a reminder's completion swap in place. The page does not move, the timeline grows where the eye already is, and the round trip carries a fragment instead of a record with everything joined to it. What comes free, and should be stated so it is not rebuilt: - **Pending and failure are already handled.** #226 puts `aria-busy` on whatever is being replaced and renders one `role="alert"` region whose words come from the page, so a swap that answers 500 or never arrives already says so. A new swap inherits that. - **Focus is already a solved rule.** #227: a control that swaps itself away has to hand focus to what replaces it, or the next Tab starts at the skip link. The reminder tick is exactly that shape — it removes itself. What has to be built rather than borrowed: the partials. The one that exists is a *row for the list*, which is not what the detail page shows. A timeline entry, the status band and a reminder row are three small templates, and the views already have the objects. **The forms must still post.** Without a script the page has to behave exactly as it does today — that is the same rule as #134 and #136, and here it costs nothing, because an `hx-post` on a real form with a real action is progressive by construction. Related: #247 wants the slow work moved onto `django-tasks-db` with a "working…" state. These meet at the same place — what a swap shows while the server is still thinking.
tiagoagueda added this to the 0.4.0 milestone 2026-09-17 15:07:05 +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#257
No description provided.