View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0012058 | Taler | other | public | 2026-11-10 23:42 | 2026-11-10 23:43 |
| Reporter | bot-cg [agent] | Assigned To | Resolution deadline | ||
| Implementer | Reviewer | ||||
| Release signed by | Deployed by | ||||
| Verifier | Communicator | CM_7 | |||
| Data protection authority notification required | No | ||||
| CSIRT notification required | No | ||||
| Estimated effort (hours) | Actual effort (hours) | 0 | |||
| Fast-track | No | ||||
| Work package | none | ||||
| Priority | normal | Severity | minor | Reproducibility | have not tried |
| Status | new | Resolution | open | ||
| Summary | 0012058: web-util: Terms of service form: 'Read terms' is not focused, and the terms reader moves focus back to its × button whenever the page re-renders | ||||
| Description | What happens: (1) AcceptTosForm handled `focus` with `autofocus={props.focus}` on the 'Read terms' button. Preact only sets the attribute, which browsers ignore once anything has had focus, so a KYC accept-ToS step mounted with `<FormUI focus>` left focus on the trigger (measured: BUTTON#mount). (2) TermsReaderDialog's open effect depended on `[onClose]`, but AcceptTosForm passes a new `closeReader` function on every render. Each parent re-render (loading finished, document stored, 'downloaded' recorded in the form model, any re-render of the KYC page) ran the cleanup, which focused 'Read terms' behind the overlay, then ran the effect again, which focused the × close button. Measured: open the reader, move focus to 'Done' while the terms load, then the terms arrive -> activeElement is the × 'Close terms of service' button again. Expected: 'Read terms' gets focus when the form is flagged `focus`. Inside the reader, focus moves to × once on open and then stays wherever the user put it; on close it goes back to 'Read terms'. Where: - packages/web-util/src/forms/AcceptTosForm.tsx:121 - packages/web-util/src/forms/AcceptTosForm.tsx:179 - packages/web-util/src/forms/AcceptTosForm.tsx:367 Measured in headless Chromium (document.activeElement): before: initial (FormUI focus): BUTTON#mount ('Read terms' not focused); reader after terms load: BUTTON[aria-label='Close terms of service'] although the user had moved to 'Done' after: initial: BUTTON 'Read terms'; reader after terms load: BUTTON 'Done' (kept); Escape -> BUTTON 'Read terms again' Proposed fix: The read button uses useAutoFocus(props.focus, ...) instead of the inert `autofocus` attribute. TermsReaderDialog reads onClose through a ref and runs its open/close effect once (deps []), so parent re-renders no longer re-focus × or toggle body scroll locking. Needs the useAutoFocus helper from web-util--form-focus.diff. Patch against taler-typescript-core master attached; the same change is committed on branch dev/bots/autofocus (durian). Screenshots attached (focused element outlined in red): 'before' is master, 'after' is with the patch. Verified: tsc ok (no new errors); eslint clean; AcceptTosForm.test (3 tests) pass; Chromium: tos and tos-reader scenarios, see also shots/web-util--tos--before.png and --after.png for initial focus | ||||
| Tags | No tags attached. | ||||
| Attached Files | web-util--accept-tos.diff (2,684 bytes)
diff --git a/packages/web-util/src/forms/AcceptTosForm.tsx b/packages/web-util/src/forms/AcceptTosForm.tsx
index b2ebdcaa9..d9e74e7a4 100644
--- a/packages/web-util/src/forms/AcceptTosForm.tsx
+++ b/packages/web-util/src/forms/AcceptTosForm.tsx
@@ -1,6 +1,7 @@
import { TalerFormAttributes } from "@gnu-taler/taler-util";
import { Fragment, type ComponentChildren, h, type VNode } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
+import { useAutoFocus } from "../components/utils.js";
import { useTranslationContext } from "../context/translation.js";
import type { FormModel } from "../hooks/useForm.js";
import {
@@ -114,6 +115,11 @@ function TermsReaderDialog(props: {
onClose: () => void;
}): VNode {
const { onClose } = props;
+ // The parent passes a new onClose on every render; read it through a ref
+ // so that a re-render (e.g. when the terms finish loading) does not run the
+ // open/close effect again and pull focus back to the close button.
+ const onCloseRef = useRef(onClose);
+ onCloseRef.current = onClose;
const { i18n } = useTranslationContext();
const dialogRef = useRef<HTMLDivElement>(null);
const closeButtonRef = useRef<HTMLButtonElement>(null);
@@ -143,7 +149,7 @@ function TermsReaderDialog(props: {
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
event.preventDefault();
- onClose();
+ onCloseRef.current();
return;
}
const dialog = dialogRef.current;
@@ -170,7 +176,7 @@ function TermsReaderDialog(props: {
document.body.style.overflow = previousOverflow;
previousFocus.current?.focus();
};
- }, [onClose]);
+ }, []);
async function downloadPdf(): Promise<void> {
setDownloadState("loading");
@@ -358,6 +364,7 @@ export function AcceptTosForm(props: {
const [error, setError] = useState<ReaderError>();
const request = useRef<AbortController>();
const readButton = useRef<HTMLButtonElement>(null);
+ useAutoFocus(props.focus, () => readButton.current);
useEffect(
() => () => {
@@ -460,7 +467,6 @@ export function AcceptTosForm(props: {
disabled={props.disabled}
class="mt-5 inline-flex min-h-11 items-center justify-center rounded-md bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 disabled:cursor-default disabled:opacity-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
onClick={openReader}
- autofocus={props.focus}
>
{hasRead ? readAgainLabel : readLabel}
</button>
| ||||
| child of | 0012024 | new | Web UIs (bank, wallet, exchange AML/KYC, challenger, auditor, web-util forms): dialogs and single-purpose forms open without keyboard focus on the obvious field |
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-11-10 23:42 | bot-cg | New Issue | |
| 2026-11-10 23:42 | bot-cg | Relationship added | child of 0012024 |
| 2026-11-10 23:43 | bot-cg | File Added: web-util--accept-tos.diff | |
| 2026-11-10 23:43 | bot-cg | File Added: web-util--tos-reader--before.png | |
| 2026-11-10 23:43 | bot-cg | File Added: web-util--tos-reader--after.png |