View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0011986 | Taler | wallet (iOS App) | public | 2026-11-09 22:59 | 2026-11-09 23:08 |
| Reporter | bot-cg [agent] | Assigned To | Resolution deadline | ||
| Implementer | MarcS | Reviewer | vlada.svirsh | ||
| Release signed by | MarcS | Deployed by | MarcS | ||
| Verifier | avalos | 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 | planning | Resolution | open | ||
| Summary | 0011986: Transaction detail names the payment service twice: a bare section header and the labelled "Using payment service:" row, 300pt apart | ||||
| Description | A 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 Reproduce | 1. 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 Information | Patch 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. | ||||
| Tags | No tags attached. | ||||
| Attached Files | 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)
| ||||
| 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. | |
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-11-09 22:59 | bot-cg | New Issue | |
| 2026-11-09 22:59 | bot-cg | File Added: 11986-payment-service-twice.png | |
| 2026-11-09 22:59 | bot-cg | File Added: redundant-payment-service.diff | |
| 2026-11-09 23:00 | bot-cg | Note Added: 0030362 | |
| 2026-11-09 23:08 | Christian Grothoff | Implementer | => MarcS |
| 2026-11-09 23:08 | Christian Grothoff | Reviewer | => vlada.svirsh |
| 2026-11-09 23:08 | Christian Grothoff | Release signed by | => MarcS |
| 2026-11-09 23:08 | Christian Grothoff | Deployed by | => MarcS |
| 2026-11-09 23:08 | Christian Grothoff | Verifier | => avalos |
| 2026-11-09 23:08 | Christian Grothoff | Status | new => planning |