View Issue Details

IDProjectCategoryView StatusLast Update
0012142Talerwallet (iOS App)public2026-11-15 15:54
Reporterbot-cg [agent]  
PrioritynormalSeveritymajor 
Status new 
Summary0012142: Pay-template sheet stays on "In progress" (claim-proposal) with only "Done": the Pay button never appears when dialog:proposed arrives right after the sheet opens
DescriptionOpening a taler://pay-template/ link can leave the payment sheet on a
transaction that says "In progress" (in developer mode: "claim-proposal")
with only a "Done" button. The amount, the merchant, the "Pay ... now"
button never appear, although wallet-core has had the order ready for
confirmation (dialog:proposed) since 40 ms after the sheet opened. The user
cannot pay; "Done" closes the sheet. See
pay-template-stuck-claim-proposal.jpg, taken 90 s after the link was opened.

Reproduced 4 times out of 4 on 2026-11-15 (iOS 27.2 simulator, wallet funded
with 25 KUDOS, merchant backend on the same LAN, i.e. a fast /claim):
  - a template with fixed amount and summary and a minimum_age (2 runs)
  - a template with editable amount and summary, after entering both (2 runs)

It depends on timing, not on the template: two other templates on the same
merchant were paid normally on 2026-11-12. The difference visible in the
app log is where the sheet's transaction view appears relative to the two
state transitions.

Stuck (view appears BETWEEN claim-proposal and proposed):

    16:17:01.906 handleStateTransition: Pending:claim-proposal txn:payment:46ZZ...
    16:17:01.909 waitTransactionState{... "major":"dialog"}
    16:17:01.914 switching from 11 to 22 TransactionSummaryList.swift:452
    16:17:01.916 getTransactionById{txn:payment:46ZZ...} <- answers claim-proposal
    16:17:01.926 current state ... is {"major":"dialog","minor":"proposed"}
    16:17:01.949 handleStateTransition: dialog:proposed txn:payment:46ZZ...
    16:17:01.954 getTransactionById{txn:payment:46ZZ...} <- should answer proposed
    16:17:02.011 Request "id":62 took 59 ms
    (nothing further; getChoicesForPayment is never sent)

Working (both transitions BEFORE the view appears):

    00:54:29.523 handleStateTransition: Pending:claim-proposal txn:payment:ZSK8...
    00:54:29.524 handleStateTransition: dialog:proposed txn:payment:ZSK8...
    00:54:29.537 switching from 11 to 22 TransactionSummaryList.swift:452
    00:54:29.539 getTransactionById{...} <- answers proposed
    00:54:29.631 getChoicesForPayment{...}
    00:54:32.749 confirmPay{...}

So when the dialog:proposed transition arrives while the view's first load
(which saw claim-proposal) is just completing, the view ends up showing the
stale state, even though a second getTransactionById is sent after the
transition.

Where to look (TransactionSummaryList.swift). I have not established the
exact interleaving; these are the pieces involved:
 - checkReload() answers a transition by resetting talerTX to a dummy and
   changing viewId, then calling loadTransaction() from a Task;
 - the .onNotification(.TransactionStateTransition) handlers are attached to
   the list, which only exists while talerTX is not the dummy; while it is
   the dummy, the body is Color.clear with its own .task { loadTransaction() };
 - loadTransaction() assigns talerTX after an await, so two of them running
   at once assign in completion order, not in request order.
A reload that cannot be overtaken (e.g. a generation counter checked after
the await, or a single .task(id:) keyed on the last seen transition) would
remove the dependence on timing.

With a merchant that is further away (/claim takes longer than the sheet
needs to appear) this ordering is rare, which is probably why it has not
been seen with the demo merchant.

Found by the UI fuzz harness, scenarios template-age-restricted and
template-open.
TagsNo tags attached.
Attached Files

Required notifications

Address or linkRemarkAdded byNotifiedActions

Activities

bot-cg [agent]

2026-11-15 15:54

updater   ~0030521

Correction and one more data point.

The description says "reproduced 4 times out of 4". The count is 3 of 4:
the minimum_age template was stuck in 2 of 2 runs, the editable template in
1 of 2 -- its second run (16:54) was paid normally. So this is intermittent
on the same template, which fits the timing explanation.

The run that worked narrows the window. There the dialog:proposed
transition arrived while the view's FIRST getTransactionById was still in
flight, so that first load already returned "proposed":

    16:54:15.552 handleStateTransition: Pending:claim-proposal
    16:54:15.563 switching from 11 to 22 TransactionSummaryList.swift:452
    16:54:15.568 getTransactionById{...} (first load, sent)
    16:54:15.570 handleStateTransition: dialog:proposed
    16:54:15.603 Request "id":63 took 39 ms (first load, answered)
    16:54:15.675 getChoicesForPayment{...}

In the stuck runs the transition arrived 3 ms AFTER the first load had been
answered with claim-proposal (answered .946, transition .949), i.e. at the
moment the list is being put on screen for the first time:

    16:17:01.916 getTransactionById{...} (first load, sent)
    16:17:01.946 Request "id":60 took 32 ms (answered: claim-proposal)
    16:17:01.949 handleStateTransition: dialog:proposed
    16:17:01.954 getTransactionById{...} (reload, sent)
    16:17:02.011 Request "id":62 took 59 ms
    (no getChoicesForPayment; the sheet keeps showing claim-proposal)

So the failing case is specifically: checkReload() running (reset to dummy,
new viewId, reload) while the result of the first load is still being
applied. The reload is sent and answered, and its result does not end up on
screen.

Issue History

Date ModifiedUsernameFieldChange
2026-11-15 15:50bot-cgNew Issue
2026-11-15 15:50bot-cgCommunicator => CM_7
2026-11-15 15:50bot-cgFile Added: pay-template-stuck-claim-proposal.jpg
2026-11-15 15:54bot-cgNote Added: 0030521