Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
485c468
Disclose whole-REALU rounding on OpenCryptoPay pay
TaprootFreakAI Sep 15, 2026
1375626
test(pay): pop the process sheet to re-enable Pay
TaprootFreakAI Sep 15, 2026
f36ad8f
test(goldens): regenerate baselines on the self-hosted runner
github-actions[bot] Sep 15, 2026
4b77b42
Keep pay-quote copy out of the cubit
TaprootFreakAI Sep 15, 2026
dee3ea1
docs(handbook): match pay slot labels to 293-295
TaprootFreakAI Sep 15, 2026
6afa26e
Drop unused i18n from the pay-quote cubit test
TaprootFreakAI Sep 15, 2026
a46b7af
Leave the pay quote after a completed REALU swap
TaprootFreakAI Sep 15, 2026
d640544
Leave the pay quote unless the swap is known not to have run
TaprootFreakAI Sep 15, 2026
8d05abe
Treat a signed swap as completed before the broadcast round-trip
TaprootFreakAI Sep 15, 2026
1a34464
Show invalid swap-quote API errors 1:1 instead of as missing holdings
TaprootFreakAI Sep 15, 2026
efa6402
Retry the pay leg when swap broadcast fails after the tx is signed
TaprootFreakAI Sep 15, 2026
f7775a0
Drop the unused insufficient-holdings pay-quote error variant
TaprootFreakAI Sep 15, 2026
b0f15a9
Surface invalid process-swap API errors 1:1 instead of as missing REALU
TaprootFreakAI Sep 15, 2026
aad0ca7
Say leftover ZCHF from OpenCryptoPay pay is kept by DFX
TaprootFreakAI Sep 16, 2026
469cd2a
test(goldens): regenerate baselines on the self-hosted runner
github-actions[bot] Sep 16, 2026
cd5ec4f
Add OCP pay integration tests for the 1 EUR whole-share quote
TaprootFreakAI Sep 16, 2026
285ac8d
Show localized pay-retry copy for non-API broadcast failures
TaprootFreakAI Sep 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions .github/workflows/handbook-build-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ jobs:
set -euo pipefail
bash scripts/assemble-handbook-screenshots.sh /tmp/handbook-shots
count=$(ls -1 /tmp/handbook-shots/*.png | wc -l | tr -d ' ')
if [ "$count" != "301" ]; then
echo "expected 301 screenshots, got $count" >&2
if [ "$count" != "304" ]; then
echo "expected 304 screenshots, got $count" >&2
exit 1
fi

Expand Down Expand Up @@ -177,10 +177,11 @@ jobs:
exit 1
fi

# Spot-check handbook screenshots assembled from Goldens (301 PNGs).
# Mix of original 01-61 slots, later Golden batches, and referral
# baselines so a regression in any range surfaces here.
for name in 01-welcome 11-dashboard 26-terms 35-dashboard-with-balance 46-buy-kyc-required 52-sell-unknown-error 53-buy-payment-details 61-kyc-registration-tax-tin-error 62-welcome-page-android 219-settings-security-page-default 268-phone-number-field-default 269-dashboard-insider-unlocked 270-handbook-persona-dca 275-handbook-persona-exit-1j 276-referral-overview 282-referral-payout-row 283-referral-terms-readonly 287-kyc-registration-referral-recognized 288-transaction-history-referral-payout 289-dashboard-referral-entry-and-payout 290-kyc-registration-referral-promo 291-referral-bind-error-invalid 292-referral-bind-error-already-registered; do
# Spot-check handbook screenshots assembled from Goldens (304 PNGs).
# Mix of original 01-61 slots, later Golden batches, referral
# baselines, bind-error overlays, and pay whole-share rounding so a
# regression in any range surfaces here.
for name in 01-welcome 11-dashboard 26-terms 35-dashboard-with-balance 46-buy-kyc-required 52-sell-unknown-error 53-buy-payment-details 61-kyc-registration-tax-tin-error 62-welcome-page-android 219-settings-security-page-default 268-phone-number-field-default 269-dashboard-insider-unlocked 270-handbook-persona-dca 275-handbook-persona-exit-1j 276-referral-overview 282-referral-payout-row 283-referral-terms-readonly 287-kyc-registration-referral-recognized 288-transaction-history-referral-payout 289-dashboard-referral-entry-and-payout 290-kyc-registration-referral-promo 291-referral-bind-error-invalid 292-referral-bind-error-already-registered 293-pay-info 294-pay-quote-ready 295-pay-quote-ready-merchant; do
# Auth is checked before the file exists: a missing PNG also
# returns 401, so HTTP status cannot prove assembly. Look on disk.
if ! docker exec handbook test -f "/usr/share/nginx/html/screenshots/${name}.png"; then
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ The app supports three wallet modes (`software`, `bitbox`, `debug`) with differe
```bash
rg "^//\s*@no-integration-test:" lib/
```
- Visual-regression Goldens under `test/goldens/screens/` and `test/goldens/widgets/` are also the source of the 301 screenshots served at `handbook.realunit.app`. When you add a handbook page, you MUST add a matching Golden test AND a row in the mapping table at `scripts/assemble-handbook-screenshots.sh` — the handbook will not pick up a Maestro-captured PNG anymore. The `Handbook Build Check` workflow on every PR runs the assembly script and fails loudly if a mapped Golden is missing.
- Visual-regression Goldens under `test/goldens/screens/` and `test/goldens/widgets/` are also the source of the 304 screenshots served at `handbook.realunit.app`. When you add a handbook page, you MUST add a matching Golden test AND a row in the mapping table at `scripts/assemble-handbook-screenshots.sh` — the handbook will not pick up a Maestro-captured PNG anymore. The `Handbook Build Check` workflow on every PR runs the assembly script and fails loudly if a mapped Golden is missing.
- Why: single source of truth — a UI regression that breaks a Golden also breaks the handbook image before either ships; eliminates the previous "two pipelines, two truths" problem.
- See: [`docs/visual-regression-tests.md`](docs/visual-regression-tests.md) section "Handbook screenshots are sourced from Goldens".

Expand Down
3 changes: 2 additions & 1 deletion assets/languages/strings_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
"payFailureQuoteExpired": "Das Zahlungsangebot ist abgelaufen. Bitte scannen Sie den Code erneut.",
"payFailureSignatureUnsupported": "Diese Wallet kann keine Transaktionen signieren. Wechseln Sie zu einer Software- oder BitBox-Wallet.",
"payFailureTitle": "Zahlung fehlgeschlagen",
"payInfoBody": "Die Bezahlfunktion kann an allen OpenCryptoPay-Standorten genutzt werden.\n\nDer Tausch erfolgt in zwei Schritten. Zuerst wird REALU in ZCHF getauscht. ZCHF ist ein CHF-Stablecoin, der anschliessend an den Zahlungsempfänger übertragen wird.\n\nREALU ist ein Wertpapier und wird nicht für die Bezahlung selbst verwendet.",
"payInfoBody": "Die Bezahlfunktion kann an allen OpenCryptoPay-Standorten genutzt werden.\n\nDer Tausch erfolgt in zwei Schritten. Zuerst wird REALU in ZCHF getauscht. ZCHF ist ein CHF-Stablecoin, der anschliessend an den Zahlungsempfänger übertragen wird.\n\nREALU ist ein Wertpapier und wird nicht für die Bezahlung selbst verwendet.\n\nBei jeder Zahlung wird auf ganze REALU-Anteile aufgerundet, weil es keine halben REALU gibt. Restliches ZCHF behält DFX.",
"payInfoTitle": "Bevor Sie bezahlen",
"paymentInformationFailed": "Beim Abrufen der Zahlungsinformationen ist ein Fehler aufgetreten.",
"paymentInformationFailedDescription": "Bitte versuchen Sie es später erneut. Wenn der Fehler weiterhin besteht, wenden Sie sich an unseren Support.",
Expand All @@ -199,6 +199,7 @@
"payQuoteRealuEstimated": "Erwarteter Erlös",
"payQuoteRealuFees": "Gebühren",
"payQuoteRequested": "Geforderter Betrag",
"payQuoteRoundingNotice": "Bei jeder Zahlung wird auf ganze REALU-Anteile aufgerundet, weil es keine halben REALU gibt. Restliches ZCHF behält DFX.",
"payQuoteSummary": "Sie bezahlen ${amount} ${asset}",
"payQuoteTitle": "Zahlung bestätigen",
"payQuoteUnavailable": "Für diesen Zahlungscode ist keine ZCHF-Zahlung verfügbar.",
Expand Down
3 changes: 2 additions & 1 deletion assets/languages/strings_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
"payFailureQuoteExpired": "The payment quote expired. Please scan the code again.",
"payFailureSignatureUnsupported": "This wallet cannot sign transactions. Switch to a software or BitBox wallet.",
"payFailureTitle": "Payment failed",
"payInfoBody": "You can use the payment function at all OpenCryptoPay locations.\n\nThe exchange happens in two steps. First, REALU is swapped for ZCHF. ZCHF is a Swiss franc stablecoin, which is then transferred to the payment recipient.\n\nREALU is a security and is not used for the payment itself.",
"payInfoBody": "You can use the payment function at all OpenCryptoPay locations.\n\nThe exchange happens in two steps. First, REALU is swapped for ZCHF. ZCHF is a Swiss franc stablecoin, which is then transferred to the payment recipient.\n\nREALU is a security and is not used for the payment itself.\n\nEvery payment is rounded up to whole REALU shares, because there are no fractional REALU. Leftover ZCHF is kept by DFX.",
"payInfoTitle": "Before you pay",
"paymentInformationFailed": "An error occurred while getting the payment information.",
"paymentInformationFailedDescription": "Please try again later. If the error persists, contact our support team.",
Expand All @@ -199,6 +199,7 @@
"payQuoteRealuEstimated": "Estimated proceeds",
"payQuoteRealuFees": "Fees",
"payQuoteRequested": "Requested amount",
"payQuoteRoundingNotice": "Every payment is rounded up to whole REALU shares, because there are no fractional REALU. Leftover ZCHF is kept by DFX.",
"payQuoteSummary": "You pay ${amount} ${asset}",
"payQuoteTitle": "Confirm payment",
"payQuoteUnavailable": "No ZCHF payment is available for this payment code.",
Expand Down
2 changes: 1 addition & 1 deletion docs/handbook/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ deployten Image (`handbook.realunit.app`).

## Screenshots regenerieren

Es gibt keinen separaten Regeneration-Schritt: Die 301 Handbook-Screenshots
Es gibt keinen separaten Regeneration-Schritt: Die 304 Handbook-Screenshots
sind direkt die Golden-Baselines unter `test/goldens/` (gemappt in
`scripts/assemble-handbook-screenshots.sh`). Eine UI-Änderung an einer der
gemappten Pages produziert beim `flutter test test/goldens` einen Diff —
Expand Down
98 changes: 95 additions & 3 deletions docs/handbook/de/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,9 @@
<li>
<a href="#spec-80"><span class="spec-num">80</span>Testkunden — Bestandsentwicklung</a>
</li>
<li>
<a href="#spec-81"><span class="spec-num">81</span>Bezahlen — ganze REALU</a>
</li>
<li>
<a href="#spec-geo"><span class="spec-num">G</span>Aktientoken — Geo-Filter</a>
</li>
Expand Down Expand Up @@ -1186,14 +1189,14 @@ <h1>RealUnit Wallet — Handbook</h1>
<span>📱 <b>App:</b> RealUnit Wallet (Flutter)</span>
<span>📂 <b>Code:</b>
<a href="https://github.com/RealUnitCH/app">RealUnitCH/app</a></span>
<span>🧪 <b>Tests:</b> 355 Visual-Regression Goldens (301 davon im Handbook) · 26 Tier-3 Smoke-Flows</span>
<span>🧪 <b>Tests:</b> 355 Visual-Regression Goldens (304 davon im Handbook) · 26 Tier-3 Smoke-Flows</span>
</div>
</section>

<section id="architecture" style="margin-top: 40px">
<h3 style="margin-top: 0">Single Source of Truth</h3>
<p>
Die 301 App-Screenshots dieser Seite sind Goldens aus
Die 304 App-Screenshots dieser Seite sind Goldens aus
<code>test/goldens/{screens,widgets}/&lt;feature&gt;/goldens/macos/&lt;file&gt;.png</code>.
Welcher Golden welcher Handbook-Slot ist, definiert die Mapping-Tabelle in
<code>scripts/assemble-handbook-screenshots.sh</code>. Der Docker-Build
Expand Down Expand Up @@ -1233,7 +1236,7 @@ <h3 style="margin-top: 0">Single Source of Truth</h3>
<b>Pipeline:</b> Page-Edit → <code>flutter test --update-goldens test/goldens/</code>
auf dem self-hosted Runner (Hardware-Determinismus) → neue Baseline committen →
<code>handbook-deploy.yaml</code> baut das Docker-Image aus dem gepushten Branch
und assembliert die 301 Slots aus <code>test/goldens/</code> → Push auf
und assembliert die 304 Slots aus <code>test/goldens/</code> → Push auf
<code>staging</code> deployt das Handbook-Image nach Produktion, via
Cloudflare Tunnel.
</p>
Expand Down Expand Up @@ -8419,6 +8422,95 @@ <h2><span class="num">80</span>Testkunden — Bestandsentwicklung</h2>

<hr class="sep" />

<details id="spec-81" class="spec" open>
<summary>
<div class="spec-head">
<div class="lhs">
<h2><span class="num">81</span>Bezahlen — ganze REALU</h2>
<div class="file">test/goldens/screens/pay/</div>
</div>
<div class="rhs">
<b>3 Screens</b>
<div class="links">
<a
href="https://github.com/RealUnitCH/app/tree/develop/test/goldens/screens/pay"
>Goldens ↗</a
>
</div>
</div>
</div>
</summary>
<p class="spec-intro">
OpenCryptoPay-Zahlungen verkaufen immer <b>ganze REALU-Anteile</b> —
halbe Anteile gibt es nicht. Die Bestätigungsseite («Zahlung bestätigen»)
sagt das ausdrücklich. Ein allfälliger ZCHF-Rest geht an DFX.
Die Zahlungs-API rundet den Tausch auf ganze Anteile auf; die App rechnet
die Stückzahl nicht selbst.
</p>

<div class="tests cols-3">
<div class="test" id="293-pay-info">
<div class="head">
<a class="name permalink" href="#293-pay-info">293-pay-info</a>
<button class="copy-link" type="button" data-target="293-pay-info" title="Direkt-Link kopieren" aria-label="Direkt-Link kopieren">🔗 Link</button>
<span class="src">screens/pay/goldens/macos/pay_info_page.png</span>
</div>
<div class="img">
<img
src="../screenshots/293-pay-info.png"
alt="Bevor Sie bezahlen — Hinweis auf ganze REALU-Anteile"
/>
</div>
<div class="desc">
Intro-Seite <b>Bevor Sie bezahlen</b> mit dem zusätzlichen Absatz:
bei jeder Zahlung wird auf ganze REALU-Anteile aufgerundet, weil es
keine halben REALU gibt; ein allfälliger ZCHF-Rest geht an DFX.
Golden-Quelle: <code>pay_info_page.png</code>.
</div>
</div>
<div class="test" id="294-pay-quote-ready">
<div class="head">
<a class="name permalink" href="#294-pay-quote-ready">294-pay-quote-ready</a>
<button class="copy-link" type="button" data-target="294-pay-quote-ready" title="Direkt-Link kopieren" aria-label="Direkt-Link kopieren">🔗 Link</button>
<span class="src">screens/pay/goldens/macos/pay_quote_page_ready.png</span>
</div>
<div class="img">
<img
src="../screenshots/294-pay-quote-ready.png"
alt="Zahlung bestätigen — Aufrundungshinweis"
/>
</div>
<div class="desc">
Bestätigungsseite mit Betrag, benötigten ZCHF und dem sichtbaren
Hinweis, dass jede Zahlung auf ganze REALU-Anteile aufgerundet wird.
Restliches ZCHF behält DFX. Button <b>Bezahlen</b> bleibt
die sticky CTA.
Golden-Quelle: <code>pay_quote_page_ready.png</code>.
</div>
</div>
<div class="test" id="295-pay-quote-ready-merchant">
<div class="head">
<a class="name permalink" href="#295-pay-quote-ready-merchant">295-pay-quote-ready-merchant</a>
<button class="copy-link" type="button" data-target="295-pay-quote-ready-merchant" title="Direkt-Link kopieren" aria-label="Direkt-Link kopieren">🔗 Link</button>
<span class="src">screens/pay/goldens/macos/pay_quote_page_ready_with_merchant.png</span>
</div>
<div class="img">
<img
src="../screenshots/295-pay-quote-ready-merchant.png"
alt="Zahlung bestätigen — Empfänger, REALU-Zeilen und Aufrundungshinweis"
/>
</div>
<div class="desc">
Dieselbe Bestätigungsseite mit Empfänger, REALU-Anteilzeilen und
dem Aufrundungshinweis unter den Beträgen.
Golden-Quelle: <code>pay_quote_page_ready_with_merchant.png</code>.
</div>
</div>
</div>
</details>

<hr class="sep" />

<details id="spec-geo" class="spec" open>
<summary>
<div class="spec-head">
Expand Down
9 changes: 6 additions & 3 deletions docs/screens.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ Column meaning:
| Dashboard & trading | `SellBitboxPage` | `sellBitbox` | `/sellBitbox` | `126`, `127`, `128`, `129`, `130`, `131`, `132`, `133`, `134`, `135`, `136` |
| Dashboard & trading | `SellBankAccountSelectionPage` | — | — | `121`, `122` |
| Dashboard & trading | `ReceivePage` | `receive` | `/receive` | `101`, `102` |
| Dashboard & trading | `PayInfoPage` | `pay` | `/pay` | `293` |
| Dashboard & trading | `PayQuotePage` | — | — | `294`, `295` |
| Dashboard & trading | `ConnectBitboxPage` | — | — | `137`, `138`, `139`, `140`, `141`, `142`, `143`, `144`, `145`, `146` |
| Dashboard & trading | `BitboxAddressRecoveryPage` | `bitboxAddressRecovery` | `/bitboxAddressRecovery` | `147` |
| Dashboard & trading | `WebViewPage` | `webView` | `/webView` | — |
Expand Down Expand Up @@ -122,13 +124,14 @@ Column meaning:
| Shared widgets | `PhoneNumberField` | — | — | `268` |
| Shared widgets | `ReferralPayoutTransactionRow` | — | — | `282` |

81 screens — 47 routed (`GoRoute`) + 34 non-routed. The table also carries
83 screens — 48 routed (`GoRoute`) + 35 non-routed. The table also carries
two shared-widget baselines (`PhoneNumberField`, `ReferralPayoutTransactionRow`),
which are not screens.
Referral surfaces have Golden baselines (`276`–`292`); the handbook
documents the programme in `#spec-referral`. Overlay slots `291` and `292`
are listed on `DashboardPage` (the host screen), not as a second shared-widget
row.
row. OpenCryptoPay pay rounding cards `293`–`295` are documented in
`#spec-81`.

## Notes

Expand All @@ -147,7 +150,7 @@ row.
`243`–`245`. `SetupPinPage` also backs the `settingsChangePin` route
(`/settings/security/changePin`) via a second constructor; that reuse has no
separate Golden and is not given its own row.
- **Handbook numbering.** Each of the 301 handbook slots is a Visual-Regression
- **Handbook numbering.** Each of the 304 handbook slots is a Visual-Regression
Golden under `test/goldens/`, mapped to its handbook position by
`scripts/assemble-handbook-screenshots.sh`. A parallel Tier-3 Maestro flow
(`.maestro/handbook/NN-*.yaml`) covers navigation/tap-routing smoke for the
Expand Down
4 changes: 2 additions & 2 deletions docs/visual-regression-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ public repos are free even for macOS minutes.

## Handbook screenshots are sourced from Goldens

The 301 PNGs the handbook serves at `handbook.realunit.app/screenshots/`
The 304 PNGs the handbook serves at `handbook.realunit.app/screenshots/`
are assembled from the Golden baselines at docker-build time. One
Golden → one handbook page, via the explicit mapping in
`scripts/assemble-handbook-screenshots.sh`. The handbook does **not**
Expand Down Expand Up @@ -231,7 +231,7 @@ that directory into `/usr/share/nginx/html/screenshots/`.
Golden file.
4. Open the PR. The `Handbook Build Check` workflow runs
`docker build` and a container smoke (`/healthz` + auth gate +
spot-checks selected mapped screenshots via `docker exec test -f`; the full set is gated by the assemble step (`expected 301`)). A missing Golden surfaces here
spot-checks selected mapped screenshots via `docker exec test -f`; the full set is gated by the assemble step (`expected 304`)). A missing Golden surfaces here
as a missing-source error from the assembly script before docker
even spins up.

Expand Down
Loading