View Issue Details

IDProjectCategoryView StatusLast Update
0012054Talerwallet (WebExtension)public2026-11-10 23:43
Reporterbot-cg [agent] Assigned ToResolution deadline
ImplementerReviewer 
Release signed byDeployed by 
VerifierCommunicatorCM_7  
Data protection authority notification requiredNo  
CSIRT notification requiredNo  
Estimated effort (hours)Actual effort (hours)0 
Fast-trackNo
Work packagenone
PrioritynormalSeveritytweakReproducibilityhave not tried
Status newResolutionopen 
Summary0012054: Wallet: shared ConfirmationDialog focuses an extra body action instead of Cancel (durability prompt)
DescriptionWhat happens:
ConfirmationDialog focused the first focusable element in the dialog. For the PWA durability prompt ('Protect wallet data before continuing', src/platform/durability.tsx, shown before money actions when storage is not persistent) the children contain 'Install PWA' and 'Database export and import' buttons before the actions, so focus landed on 'Install PWA' (or on 'Database export and import', which cancels the pending action and navigates away, when install is unavailable). Rendered with the same children: activeElement BUTTON 'Install PWA'.

Expected:
A field the dialog asks for if any (e.g. the 'I understand' acknowledgement checkbox), otherwise the safe Cancel button; never the confirming action or an extra action.

Where:
  - packages/wallet-webui/src/ui/ConfirmationDialog.tsx:41

Measured in headless Chromium (document.activeElement):
  before: BUTTON 'Install PWA'
  after: BUTTON 'Cancel'

Proposed fix:
On open, focus the first enabled input/select/textarea in the dialog, else the cancel button (first button of the actions row), else the dialog container. Tab trapping is unchanged.

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 (0 errors in wallet-webui with workspace sources; plain `tsc --noEmit -p .` output identical to baseline, whose errors come from stale taler-util/wallet-core lib/); eslint + prettier clean on changed files; package tests screens/exchange-removal/payment-entry/routing bundled from src: 89/95 pass, the same 6 App-level demo tests fail before and after; scratch test: plain -> Cancel, extra button -> Cancel, checkbox -> checkbox; exchange-delete story still focuses its acknowledgement checkbox (wallet--exchange-delete-dialog--*.png); demo reset dialog still Cancel. Call sites: durability prompt (changed), TransactionDetailScreen x2, SettingsScreen x3, ExchangeDetailScreen x2, BankAccountsScreen, PaymentScreen, DemoBanner (unchanged result).
TagsNo tags attached.
Attached Files
wallet--confirmation-dialog.diff (1,883 bytes)   
diff --git a/packages/wallet-webui/src/ui/ConfirmationDialog.tsx b/packages/wallet-webui/src/ui/ConfirmationDialog.tsx
index 32844defa..72ce55961 100644
--- a/packages/wallet-webui/src/ui/ConfirmationDialog.tsx
+++ b/packages/wallet-webui/src/ui/ConfirmationDialog.tsx
@@ -25,6 +25,7 @@ export function ConfirmationDialog(props: {
   onConfirm: () => void;
 }) {
   const dialogRef = useRef<HTMLDivElement>(null);
+  const actionsRef = useRef<HTMLDivElement>(null);
   const previousFocus = useRef<HTMLElement | null>(null);
   const onCancel = useRef(props.onCancel);
   const working = useRef(props.working);
@@ -38,8 +39,15 @@ export function ConfirmationDialog(props: {
         ? document.activeElement
         : null;
     const dialog = dialogRef.current;
-    const first = dialog ? focusableElements(dialog)[0] : undefined;
-    first?.focus();
+    // Start on the first field the dialog asks for, otherwise on the cancel
+    // button: neither the confirming action nor an extra action placed in
+    // the body should be what Enter triggers by default.
+    const cancel = actionsRef.current?.querySelector("button");
+    const initial =
+      dialog?.querySelector<HTMLElement>(
+        "input:not([disabled]), select:not([disabled]), textarea:not([disabled])",
+      ) ?? (cancel && !cancel.disabled ? cancel : dialog);
+    initial?.focus();
     const onKeyDown = (event: KeyboardEvent) => {
       if (event.key === "Escape" && !working.current) {
         event.preventDefault();
@@ -98,7 +106,7 @@ export function ConfirmationDialog(props: {
             {props.description}
           </div>
           {props.children}
-          <div class="mt-6 flex flex-wrap justify-end gap-3">
+          <div ref={actionsRef} class="mt-6 flex flex-wrap justify-end gap-3">
             <Button
               tone="secondary"
               onClick={props.onCancel}

Required notifications

Address or linkRemarkAdded byNotifiedActions

Relationships

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 

Activities

Issue History

Date ModifiedUsernameFieldChange
2026-11-10 23:42bot-cgNew Issue
2026-11-10 23:42bot-cgRelationship addedchild of 0012024
2026-11-10 23:43bot-cgFile Added: wallet--confirmation-dialog.diff
2026-11-10 23:43bot-cgFile Added: wallet--confirmation-dialog--before.png
2026-11-10 23:43bot-cgFile Added: wallet--confirmation-dialog--after.png