An application's reminders: Put off until tomorrow or next week drops focus to the start of the page #520

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 reminder actions and the focus handover after htmx swaps.

Checked again on 2026-10-01 against main at 0a5cad587: still present. The code is now at templates/cotton/reminder_actions.html:55-66 (the Later form; Done at :39-40) and static/js/app.js:1894-1916 (the focus handover); templates/applications/partials/reminders.html is unchanged. Since #310 the menu's trigger is a <button popovertarget> with no id, not a <summary>, and since #316 the reminders page is the calendar.

On an application's page the two Put off menu items post through htmx and replace the whole #reminders region, as Done does. Done says where focus goes afterwards; the Put off items do not, and htmx 2.0.10 restores focus only to an element with an id, which they lack. The focused menu item is replaced and focus falls to <body>. The template's comment says nothing needs handing over for Later, which is true of the content and not of the DOM.

  • Where: templates/cotton/reminder_actions.html:41-52 (the Later form: hx-target="{{ target }}" hx-swap="outerHTML", and two role="menuitem" buttons with no id and no data-focus-after; only Done at :32-33 carries data-focus-after); templates/applications/partials/reminders.html:1-16 (the comment, and target="#reminders" at :27-28); static/js/app.js:1659-1683 (htmx:beforeRequest looks for [data-focus-after] in or around the requesting form and finds none, so htmx:afterSettle does nothing); static/js/vendor/htmx.min.js (focus is restored only when ee(t.elt,"id")).
  • What happens: a keyboard user opens a reminder's actions menu on an application and chooses Put off until tomorrow. The reminder's date changes, the region is redrawn, and focus is on the body. The next Tab starts from the skip link; a screen reader gives no position. The calendar, where reminders are listed since #316, is unaffected: there the form posts without htmx and the page reloads.
  • Why it matters: WCAG 2.2 SC 2.4.3, Focus Order, the failure #257 fixed for Done. tests/e2e/test_application_swaps.py covers only Done.
  • Fix: give each reminder's menu trigger a stable id (for example reminder-{{ reminder.pk }}-actions on the trigger <button popovertarget> drawn by cotton/dropdown_menu.html, through a new attribute) and have the Later buttons carry data-focus-after pointing at it, so focus returns to the same reminder's trigger once its new date is drawn; fall back to the region when the trigger is gone. Correct the comment in reminders.html.

Done when

  • A browser test in tests/e2e/test_application_swaps.py opens a reminder's menu with the keyboard on an application's page, chooses Put off until tomorrow, and asserts that focus is on that reminder's actions trigger (not on <body>). It fails today and passes after the fix.
Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading the reminder actions and the focus handover after htmx swaps. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. The code is now at `templates/cotton/reminder_actions.html:55-66` (the *Later* form; *Done* at `:39-40`) and `static/js/app.js:1894-1916` (the focus handover); `templates/applications/partials/reminders.html` is unchanged. Since #310 the menu's trigger is a `<button popovertarget>` with no `id`, not a `<summary>`, and since #316 the reminders page is the calendar. On an application's page the two *Put off* menu items post through htmx and replace the whole `#reminders` region, as *Done* does. *Done* says where focus goes afterwards; the *Put off* items do not, and htmx 2.0.10 restores focus only to an element with an `id`, which they lack. The focused menu item is replaced and focus falls to `<body>`. The template's comment says nothing needs handing over for *Later*, which is true of the content and not of the DOM. - **Where:** `templates/cotton/reminder_actions.html:41-52` (the *Later* form: `hx-target="{{ target }}" hx-swap="outerHTML"`, and two `role="menuitem"` buttons with no `id` and no `data-focus-after`; only *Done* at `:32-33` carries `data-focus-after`); `templates/applications/partials/reminders.html:1-16` (the comment, and `target="#reminders"` at `:27-28`); `static/js/app.js:1659-1683` (`htmx:beforeRequest` looks for `[data-focus-after]` in or around the requesting form and finds none, so `htmx:afterSettle` does nothing); `static/js/vendor/htmx.min.js` (focus is restored only when `ee(t.elt,"id")`). - **What happens:** a keyboard user opens a reminder's actions menu on an application and chooses *Put off until tomorrow*. The reminder's date changes, the region is redrawn, and focus is on the body. The next Tab starts from the skip link; a screen reader gives no position. The calendar, where reminders are listed since #316, is unaffected: there the form posts without htmx and the page reloads. - **Why it matters:** WCAG 2.2 SC 2.4.3, Focus Order, the failure #257 fixed for *Done*. `tests/e2e/test_application_swaps.py` covers only *Done*. - **Fix:** give each reminder's menu trigger a stable id (for example `reminder-{{ reminder.pk }}-actions` on the trigger `<button popovertarget>` drawn by `cotton/dropdown_menu.html`, through a new attribute) and have the *Later* buttons carry `data-focus-after` pointing at it, so focus returns to the same reminder's trigger once its new date is drawn; fall back to the region when the trigger is gone. Correct the comment in `reminders.html`. ## Done when - A browser test in `tests/e2e/test_application_swaps.py` opens a reminder's menu with the keyboard on an application's page, chooses *Put off until tomorrow*, and asserts that focus is on that reminder's actions trigger (not on `<body>`). 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 (#520).

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