View Issue Details

IDProjectCategoryView StatusLast Update
0011986Talerwallet (iOS App)public2026-11-09 23:08
Reporterbot-cg [agent] Assigned ToResolution deadline
ImplementerMarcS Reviewervlada.svirsh  
Release signed byMarcS Deployed byMarcS  
Verifieravalos CommunicatorCM_7  
Data protection authority notification requiredNo  
CSIRT notification requiredNo  
Estimated effort (hours)Actual effort (hours)0 
Fast-trackNo
Work packagenone
PrioritynormalSeverityminorReproducibilityhave not tried
Status planningResolutionopen 
Summary0011986: Transaction detail names the payment service twice: a bare section header and the labelled "Using payment service:" row, 300pt apart
DescriptionA transaction's detail screen names the payment service twice: once as a
bare section header with nothing to say what it is, and once as the value
of a labelled row about 300 pt below it.

    staticText "taler-ops.ch" [16, 415, 370, 47] <- section header
    staticText "Using payment service:" [16, 560, 129, 23]
    staticText "taler-ops.ch" [275, 569, 91, 23] <- the labelled value

Both come from the same place. `ThreeAmountsSection.swift` ends its content
with

    let serviceURL = scope?.url ?? baseURL
    if let serviceURL {
        Text(minimalistic ? "Payment service:" : "Using payment service:")
        Text(serviceURL.trimURL)
    }

and then its header reads the same field again:

    } header: {
        let header = scope?.url?.trimURL ?? scope?.currency
                     ?? (summary != nil ? "Summary" : nil)

So whenever the scope has a URL -- which is the ordinary case for a
withdrawal, a deposit or a peer transfer -- the host is printed twice.
When it does not, the header already falls through to the currency, which
is what makes the fix small: the duplicate case can take the same path.

**How often.** Across every screen this harness has captured (1,048
episodes), **707 recorded screens** carry the pair, on `Withdrawal`,
`Wire Transfer`, `Authorize at Bank` and `Money Received`. It is not a
transient: the two labels are 300 pt apart, different widths, and present
together in every sample.

Screenshot attached (`11986-payment-service-twice.png`), cropped to the two
copies: the grey `taler-ops.ch` above the card, and `taler-ops.ch` again
inside it under "Using payment service:". iPhone 17 (402x874 pt),
iOS 27.2, GNU_Taler debug build.
Steps To Reproduce1. Withdraw anything, or open any transaction that has a payment service
   (Balances -> a currency -> a transaction).
2. Look at the top of the amounts card and at its last row.

Observed: the exchange host appears as a bare header and again as the value
of "Using payment service:".

Expected: once. The labelled copy is the one that explains itself.

The attached screenshot is from the harness's own capture
`c2h-3-e00003`, step 26, which is a CHF withdrawal at taler-ops.ch.
Additional InformationPatch attached: `redundant-payment-service.diff`.

It makes the header skip the host when the row below names it, falling
through to `scope?.currency` -- the alternative the header already uses
when the scope has no URL. Three lines plus the reasoning:

    let namedBelow = (scope?.url ?? baseURL) != nil
    let fromScope: String? = namedBelow ? nil : scope?.url?.trimURL
    let header = fromScope ?? scope?.currency ?? (summary != nil ? "Summary" : nil)

The behaviour changes in exactly one case -- scope has a URL -- and is
untouched in the other two.

**NOT YET COMPILED**, and I would rather say so than imply otherwise: the
one machine that builds this app is two hours into an unattended
measurement, and starting a build on it would perturb that run. I will
confirm the build in a follow-up note on this issue. (The last patch I
attached without building, on 11980, did not compile.)

An alternative worth considering, which I did not take: drop the header
entirely in that case rather than substituting the currency. That removes
a line of chrome instead of changing what it says, and it is a visual
judgment I did not want to make inside a bug fix.

Related but different: 11966 is two *overlapping* "Withdrawal" labels on
this same screen, whose cause is undetermined -- possibly a large-title
collapse. This one does not overlap, is 300 pt apart, and its cause is in
the source above.

**What I looked at and did not file**, from the same survey:

* The timestamp "8 Nov 2026 at 18:33" beside the status bar's "18:54" is
  NOT redundant -- it is the transaction's time, not the current time, and
  it stays informative when the screen is revisited.
* Pairs of identical labels at *identical* frames (e.g. "CHF 0.00" twice at
  [293,285,75,30] on Balances) are an accessibility-tree artifact rather
  than a double render: the same screens show "Vertical scroll bar" twice
  at one frame, and UIKit draws exactly one of those.
* The large "exchange-wait-reserve" state string on the attached screenshot
  is gated on `developerMode` (ThreeAmountsSection.swift:50), so it is a
  developer affordance, not something a user is shown.
TagsNo tags attached.
Attached Files
11986-payment-service-twice.png (43,542 bytes)   
11986-payment-service-twice.png (43,542 bytes)   
redundant-payment-service.diff (1,774 bytes)   
diff --git a/TalerWallet1/Views/Transactions/ThreeAmountsSection.swift b/TalerWallet1/Views/Transactions/ThreeAmountsSection.swift
index ab70daf8..4fe4fc50 100644
--- a/TalerWallet1/Views/Transactions/ThreeAmountsSection.swift
+++ b/TalerWallet1/Views/Transactions/ThreeAmountsSection.swift
@@ -186,7 +186,24 @@ struct ThreeAmountsSection: View {
                 }
             }
         } header: {
-            let header = scope?.url?.trimURL ?? scope?.currency ?? (summary != nil ? "Summary" : nil)
+            // The payment service is named ONCE on this screen.
+            //
+            // The section's content ends with "Using payment service:" and
+            // the host (:168-185), and this header took the same host from
+            // the same `scope?.url`. So a withdrawal's detail screen showed
+            // `taler-ops.ch` twice, about 300 pt apart: once as a bare
+            // header with nothing to say what it is, and once as a labelled
+            // value. Measured across the harness's captures: 707 recorded
+            // screens on `Withdrawal`, `Wire Transfer`, `Authorize at Bank`
+            // and `Money Received` carry the pair.
+            //
+            // The labelled copy is the one that explains itself, so the
+            // header falls through to its next alternative -- the currency
+            // -- exactly as it already does when the scope has no URL.
+            let namedBelow = (scope?.url ?? baseURL) != nil
+            let fromScope: String? = namedBelow ? nil : scope?.url?.trimURL
+            let header = fromScope ?? scope?.currency
+                         ?? (summary != nil ? "Summary" : nil)
             if let header {
                 Text(header)
                     .talerFont(.title3)
redundant-payment-service.diff (1,774 bytes)   

Required notifications

Address or linkRemarkAdded byNotifiedActions

The plan

IDSummaryStatusAssigned ToEstimated effort (hours)
This issue has no child issue. Work that is big enough to be divided is divided here, and each part is planned on its own.
This issue (hours)
Its children (hours)0
Together (hours)0
Nothing is missing: the plan can be finished.

Activities

bot-cg [agent]

2026-11-09 23:00

updater   ~0030362

Scope of the attached patch, checked rather than assumed: every screen that
shows this pair goes through the one section.

`ThreeAmountsSection` is reached through the `ThreeAmounts` wrapper in the
same file, and its callers are

    TalerWallet1/Views/Transactions/ (the transaction detail path)
    TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawAcceptView.swift
    TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift
    TalerWallet1/Views/Sheets/P2P_Sheets/P2pPayURIView.swift
    TalerWallet1/Views/Sheets/Payment/PaymentScan.swift

which is exactly the set of navigation titles the survey found the
duplicate under -- `Withdrawal`, `Wire Transfer`, `Authorize at Bank` and
`Money Received`. So the one header expression is the only place to change,
and no caller needs touching.

The survey itself, for the record: every `screens.jsonl` this harness has
written -- 1,048 episodes -- scanned for a string that appears twice as
separate text elements on one screen, with three exclusions, each there to
keep a known false positive out. Duplicates inside a navigation bar are
skipped (a large-title collapse legitimately has two, which is 11966's
subject); a pair must appear in at least two distinct episodes (one
sighting is a frame of an animation, the lesson the `render-collapsed`
analyzer taught); and pairs that are the same width and evenly spaced are
skipped, because a list of transactions repeats "Withdrawal" once per row
and that is a list, not a defect.

What came back, ranked by how many episodes carried it, was dominated by
the Settings rows -- 'About GNU Taler' in 400 episodes, 'Payment Services'
in 63, and so on. Those are NOT filed: the second copy is nested inside the
first, because `SettingsItem` uses
`.accessibilityElement(children: .combine)` and the snapshot lists the
combined row and its own title label. One label is drawn. Whether VoiceOver
reads it twice I cannot say from a snapshot, and there is no oracle in this
harness for what VoiceOver actually speaks.

Issue History

Date ModifiedUsernameFieldChange
2026-11-09 22:59bot-cgNew Issue
2026-11-09 22:59bot-cgFile Added: 11986-payment-service-twice.png
2026-11-09 22:59bot-cgFile Added: redundant-payment-service.diff
2026-11-09 23:00bot-cgNote Added: 0030362
2026-11-09 23:08Christian GrothoffImplementer => MarcS
2026-11-09 23:08Christian GrothoffReviewer => vlada.svirsh
2026-11-09 23:08Christian GrothoffRelease signed by => MarcS
2026-11-09 23:08Christian GrothoffDeployed by => MarcS
2026-11-09 23:08Christian GrothoffVerifier => avalos
2026-11-09 23:08Christian GrothoffStatusnew => planning