Skip to content

Release: develop -> main - #928

Open
github-actions[bot] wants to merge 32 commits into
mainfrom
develop
Open

Release: develop -> main#928
github-actions[bot] wants to merge 32 commits into
mainfrom
develop

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automatic Release PR

This PR was automatically created after changes were pushed to develop.

Commits: 2 new commit(s)

Checklist

  • Review all changes
  • Verify CI passes
  • Approve and merge when ready for production

TaprootFreak and others added 26 commits August 21, 2026 12:40
EN:
The buy flow now shows and quotes exact CHF with rappen instead of
rounding to whole francs.

DE:
Der Kauf zeigt und quotet exakte CHF mit Rappen, statt auf ganze Franken
zu runden.

<details>
<summary>Details</summary>

Typing 10000 CHF for REALU at 1.37 yields 7299 shares. The field now
snaps to 9999.63 (shares times list in Rappen), the quote request keeps
that amount, and payment details render two decimal places.

`chargedFiatAmount` snaps to Rappen only. Sell still sends an integer
share count.

</details>
Promote: staging -> develop
EN:
Adds a live RealUnit share-token geo-filter table to the handbook.
handbook.realunit.app loads GET /v1/country at runtime and does not copy
country rows into the repo.
Until DFXswiss/backend is deployed, the realunit columns stay empty and
the page says so.

DE:
Ergänzt die Live-Tabelle des RealUnit-Aktientoken-Geo-Filters im
Handbook.
handbook.realunit.app lädt GET /v1/country zur Laufzeit und kopiert
keine Länderzeilen ins Repo.
Solange DFXswiss/backend nicht deployed ist, bleiben die
realunit-Spalten leer; die Seite weist darauf hin.

<details>
<summary>Details</summary>

Section `#spec-geo` in `docs/handbook/de/index.html`. Client
`docs/handbook/de/geo-filter.js` tries same-origin `/v1/country` (nginx
proxy in `handbook.nginx.conf`) then `https://api.dfx.swiss/v1/country`.
Screenshot count is unchanged. No Dart/UI change, no goldens.

The geo-filter columns only appear after the merged backend
`CountryDto.realunit` is live on that API.

</details>
EN:
The geo-filter table now shows German and English country names.
CSV, Excel, and PDF downloads export the currently visible rows.
Native-script API foreignName is no longer used.

DE:
Die Geo-Filter-Tabelle zeigt Ländernamen auf Deutsch und Englisch.
CSV, Excel und PDF laden die aktuell angezeigten Zeilen herunter.
Die native foreignName der API wird nicht mehr verwendet.

<details>
<summary>Details</summary>

Names come from `Intl.DisplayNames` (ISO 3166-1) so
Arabic/Japanese/Chinese script in `foreignName` is not shown. Downloads
are generated in the browser from the live `GET /v1/country` payload; no
country rows are copied into the repo.

</details>
…able from state (#934)

EN:
The buy amount field keeps the typed value instead of being overwritten
by the conversion. Quotes still use the Rappen-exact payable. Tests pin
currency-switch and quote wiring so that regression cannot return
silently.

DE:
Das Betragsfeld behält die Eingabe statt sie mit dem Umrechnungsergebnis
zu überschreiben. Quotes laufen weiter über den rappengenauen Betrag.
Tests pinnen Währungswechsel und Quote-Verdrahtung, damit die Regression
nicht still zurückkommt.

<details>
<summary>Details</summary>

Since #926 the conversion result was written back into `fiatText`, and
the BuyView listener synced it into the amount TextField after every
conversion. Editing became impossible: every keystroke or backspace was
overwritten with shares × list price 250ms later (prefill 300 → 299.46;
deleting digits snapped back to the next whole-share multiple and got
stuck). Reported by a customer via RealUnit (only the default amount
worked) and reproduced on-device.

Fix:
- Conversion results land in a new `payableText` only; `fiatText` always
keeps the user's own text (typing in the shares field still converts
into the fiat field, that path is unchanged output).
- All quote requests (initial, listener re-fetch, gate retries in
`PaymentActionButton`) use `quoteAmountText`: the Rappen-exact payable
when a conversion is live, otherwise the typed amount. The #926 goal
(quote and details in Rappen, no whole-franc rounding) stays intact.
- The details page keeps rendering the charge echoed by the quote,
unchanged.
- Tests pin: deleting digits keeps the typed text; currency switch
preserves `fiatText`; `getPaymentInfo` is called with the live payable
rather than the field text.

The exact charge is visible on the confirmation/details page. An
optional display line under the field (`payableText` is already in
state) is a product choice and is not part of this PR.

`flutter analyze` clean on the touched files; `flutter test` of the
three buy test files: 31 passed.

</details>

---------

Co-authored-by: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com>
Promote: staging -> develop
…935)

EN:
The buy screen shows the Rappen-exact charge under the amount field
without overwriting what the user typed. Labelling a draft for handbook
flows and later marking it ready no longer starts a second Maestro run.

DE:
Die Kaufseite zeigt den rappengenauen Betrag unter dem Feld, ohne die
Eingabe zu überschreiben. Ein gelabelter Draft startet beim Ready keinen
zweiten Maestro-Lauf.

<details>
<summary>Details</summary>

Buy:
- `payableText` already holds the conversion result. A read-only line
under the amount field renders it when it is non-empty and differs from
the typed amount (e.g. field `300`, line `299.46 CHF`).
- Comparison is numeric, so `300` and `300.00` stay hidden. Punctuation
lives in the widget template, not the ARB value.
- Tests pin visibility of `299.46` and hide when payable is empty,
string-equal, or numerically equal.
- Visual regression: `buy_charged_amount` golden (field `300`, exact
charge `299.46 CHF` under it). Baseline generated on the self-hosted
runner.

CI:
- Observed on #934: `tier3:full` started Maestro; `ready_for_review`
started a second macos-latest run on the same SHA because the labeled
concurrency group is unique.
- `ready_for_review` is removed from Tier 3 `pull_request` types.
`labeled` starts a run only when the event label is `tier3:full`;
`unlabeled` does not start a run. `pull-request.yaml` is unchanged
(draft-skip means ready is the first real Analyze/Visual run).

</details>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
EN:
These tests pin that a EUR buy sends currency EUR and shows the EUR
settlement IBAN from the quote, not a leftover CHF IBAN.
Production buy code is unchanged: it already forwards the selected
currency and renders the IBAN the API returns.
The confirm button is not locked; an EUR quote can still be tapped to
confirm.

DE:
Diese Tests stellen sicher, dass ein EUR-Kauf die Währung EUR sendet und
die EUR-IBAN aus der Quote zeigt, nicht eine übrig gebliebene CHF-IBAN.
Der Produktionscode bleibt unverändert: er leitet die gewählte Währung
bereits weiter und zeigt die IBAN der API.
Der Bestätigen-Button wird nicht gesperrt; eine EUR-Quote lässt sich
weiterhin bestätigen.

<details>
<summary>Details</summary>

Pins the EUR vs CHF buy-quote IBAN contract in existing test files:

- Request body for `PUT /v1/realunit/buy` with `Currency.eur` is
`currency: EUR` and maps the EUR IBAN from the mock response.
- Default/CHF path maps the CHF settlement IBAN.
- `RealUnitBuyPaymentInfoDto.fromJson` parses EUR IBAN + EUR currency.
- `BuyPaymentInfoCubit` success for EUR keeps the EUR IBAN.
- After converter settlement in EUR, `BuyView` refetches the quote with
`Currency.eur`.
- On an EUR `BuyPaymentInfoSuccess`, `BuyConfirmButton` receives the EUR
IBAN (not the CHF leftover).
- Tapping confirm on an EUR quote still calls `confirmPayment`.
- Payment details (after confirm) render the formatted EUR IBAN and
amount-in EUR.

IBANs used as distinct fixtures:

- CHF: `CH2208307000560946309` → `CH22 0830 7000 5609 4630 9`
- EUR: `CH9708307000560946317` → `CH97 0830 7000 5609 4631 7`

No production `lib/` changes.

</details>
…olden (#939)

EN:
A 502 with a plain-text body no longer crashes JSON parsing into a
FormatException on the buy quote screen. The quote retry button remains;
if the API sent no JSON message, only Retry is shown. A visual baseline
pins that retry-only state.

DE:
Ein 502 mit Klartext-Body erscheint auf dem Kauf-Quote-Screen nicht mehr
als FormatException. Der Wiederholen-Button bleibt; ohne JSON-Message
zeigt die UI nur Wiederholen. Eine visuelle Baseline pinnt diesen
Zustand.

<details>
<summary>Details</summary>

Gateway 502 responses with a non-JSON body (e.g. `error code: 502`) were
passed through `jsonDecode`, which threw `FormatException`. That string
was stored on `BuyPaymentInfoFailure.message` and rendered as the info
title.

`ApiException.fromBody` now decodes JSON objects via `fromJson` and
otherwise yields an empty user-facing message with the HTTP status. The
buy payment-info service uses it on error paths. HTTP 502 is mapped like
503 (`priceSourceUnavailable`). The catch-all no longer copies
`Object.toString()` into the UI.

A golden (`buy_price_source_unavailable_empty`) pins the empty-message
retry-only buy screen. The PNG is generated on the self-hosted runner
via `golden-regenerate.yaml`, not locally.

</details>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
EN:
Buying a large CHF amount no longer dies on Retry. The app now shows the
quote cap when the order is above inventory, and it reads 105.000 /
90'000 as one hundred five thousand / ninety thousand instead of
rejecting the input.

DE:
Ein grosser CHF-Betrag stirbt nicht mehr am Retry. Die App zeigt die
Quote-Obergrenze, wenn der Auftrag über dem Bestand liegt, und liest
105.000 / 90'000 als einhundertfünftausend / neunzigtausend statt die
Eingabe zu verwerfen.

<details>
<summary>Details</summary>

Two buy-CTA failures:

1. Quote `AmountTooHigh` / `LimitExceeded` with `maxVolume` used to map
to unknown + Retry, which re-quoted the same over-limit amount.
2. CHF/EUR have at most two decimal places, so `105.000` / `90,000` /
`90'000` are thousands groups. The parser treated them as invalid (or,
on the raw quote API, `105.000` is 105 francs). The field now accepts
the apostrophe and the parser keeps the thousands.

Screenshots of min/max and other quote outcomes now show the typed
amount in the fields, as on the live screen. The max-amount hint still
appears only after an over-inventory quote.

</details>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
#941)

EN:
The app no longer infers a default currency from the phone locale.
Until the user picks one, the display currency is EUR, then the account
currency from GET /v2/user (set from residence on the API: CH/LI → CHF,
otherwise EUR).
A late apply is dropped if the wallet closed or was replaced.

DE:
Die App rät die Default-Währung nicht mehr aus der Telefon-Locale.
Ohne gespeicherte Wahl gilt EUR, danach die Account-Währung von GET
/v2/user (API setzt sie aus dem Wohnsitz: CH/LI → CHF, sonst EUR).
Eine späte Apply wird verworfen, wenn die Wallet geschlossen oder
ersetzt wurde.

<details>
<summary>Details</summary>

Local prefs still win. `ApplyAccountCurrencyEvent` is ignored when a
currency is already stored and is not written to prefs.

Wallet open fetches GET /v2/user and applies that currency. Wallet close
restores the unset default (EUR). Replacing the open wallet instance
clears then re-applies. KYC applies the field after `getUser` only while
that same wallet instance is still open.

Buy reads the settings currency from context. The quote is cleared when
the converter currency changes, and Confirm stays hidden while the
success quote currency lags the picker. Dashboard drops in-flight
price/chart/history for the previous currency.

Sell is unchanged.

</details>
Promote: staging -> develop
)

EN:
The send-process screen waits for its incoming page animation to finish
before starting the transfer or opening the result sheet. A fast prepare
failure no longer paints over the still-sliding confirm page. The
transfer error mapping itself is unchanged.

DE:
Der Sende-Prozess-Screen wartet, bis die eingehende Seitenanimation
fertig ist, bevor Transfer und Ergebnisdialog starten. Ein schneller
Prepare-Fehler übermalt nicht mehr die noch rüberschiebende Prüfseite.
Die Fehlerzuordnung des Transfers selbst bleibt unverändert.

<details>
<summary>Details</summary>

`SendConfirmPage` still pushes `SendProcessPage` with a
`MaterialPageRoute`. `SendProcessCubit.start()` is no longer called from
`BlocProvider.create`. A private route gate starts the cubit once the
real route animation completes.

`ModalRoute.animation` is a `ProxyAnimation` whose parent is
`kAlwaysCompleteAnimation` on the first frame of a push. The gate treats
that placeholder as unresolved so it does not start during the slide.
The result sheet also waits for the same completion.

Widget test: mid-slide the preparing label is shown and the failure
sheet is not; after the animation the failure sheet is shown.

</details>

---------

Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
EN:
Pay, quote load, and sell-bitbox now wait for the incoming route slide
to finish before starting work or opening a result sheet. The
send-process gate is extracted into a shared widget so the same handling
is not duplicated. Fast failures no longer overlay the previous screen
mid-slide.

DE:
Pay, Quote-Laden und Sell-BitBox warten, bis der eingehende Slide fertig
ist, bevor Arbeit startet oder ein Ergebnisdialog aufgeht. Der
Send-Prozess-Gate liegt in einem gemeinsamen Widget, damit dasselbe
Handling nicht dupliziert wird. Schnelle Fehler übermalen nicht mehr die
vorige Seite mitten im Slide.

<details>
<summary>Details</summary>

`RouteAnimationGate` ignores the first-frame `kAlwaysCompleteAnimation`
placeholder. `onSettled` runs from the context below `BlocProvider`.
Result/reconnect sheets still call `waitForIncomingRouteAnimation` as a
second wait.

Widget tests cover the shared gate and the pay-process 300 ms slide.
SellBitbox cubit tests call `start()` explicitly; the constructor no
longer auto-starts.

</details>

Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
Promote: staging -> develop
EN:
If a software-wallet REALU send already broadcast but the receipt wait
timed out, the app now treats that as success when the HTTP status is
500 and the error names the tx hash. A 400 or 409 whose JSON body
happens to look like a timeout stays a failure. The failure sheet never
shows raw viem or exception text for generic errors. The in-progress
copy no longer asks the user to confirm in a wallet.

DE:
Wenn ein REALU-Send mit Software-Wallet schon broadcastet ist und der
Receipt-Wait ausläuft, zeigt die App Erfolg nur bei HTTP 500 und einem
Fehler, der den tx-Hash nennt. Ein 400 oder 409, dessen JSON-Body wie
ein Timeout aussieht, bleibt ein Fehler. Das Fehler-Sheet zeigt bei
generic keinen viem- oder Exception-Rohtext. Der Fortschrittstext
verlangt keine Wallet-Bestätigung mehr.

<details>
<summary>Details</summary>

- The transfer service maps HTTP 500 plus a receipt-wait timeout phrase
(`timed out while waiting for transaction` and `0x` + 64 hex) to
`TransferReceiptTimeoutException`. The cubit maps only that type to
success.
- Body `statusCode` must not override the HTTP status: HTTP 400/409 with
a timeout-like JSON body stays `ApiException`.
- Generic failures always use localized copy; viem/timeout strings are
never shown.
- `sendSigning`: EN `Sending the transfer` / DE `Überweisung wird
gesendet`.
- The signing-state visual baseline was refreshed from the self-hosted
Visual Regression runner for that copy.
- Retry still reuses the same prepare id.
- Tests on the build host: exception surface, transfer service, send
cubit, send page — 113 passed.

The matching API change is a separate public pull request.

</details>

---------

Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
EN:
Send and Pay no longer stack a second amount or quote page when the
camera keeps seeing the same QR. Both scanner pages re-arm only after
that route pops. A catalog, real-cubit double-capture tests, and a
CONTRIBUTING rule make the same mistake a blocking review finding.

DE:
Senden und Bezahlen stapeln keine zweite Betrags- oder Quote-Seite mehr,
wenn die Kamera denselben QR weiter sieht. Beide Scanner-Seiten rüsten
erst wieder auf, nachdem diese Route geschlossen wurde. Katalog, Tests
mit echtem Cubit und eine CONTRIBUTING-Regel machen denselben Fehler zum
blockierenden Review-Fund.

<details>
<summary>Details</summary>

`QrScannerView` forwards every camera frame. The send recipient page and
the pay scan page used to `Navigator.push` the next step and `reset()`
the cubit in the same listener turn. That dropped the decoded/valid
guard, so the next frame pushed a second copy of the same page.

Both consumers now use `pushThenRearm`: push first, call `reset` only
after the route pops. Invalid-scan snackbars that do not push still
reset immediately.

Tests:

- Catalog `test/helper/scanner_navigation_catalog.dart` plus a discovery
self-test that fails if any `QrScannerView(` under `lib/` is missing
from the catalog.
- Real-cubit widget tests fire two `BarcodeCapture`s and expect the
destination `findsOne`; after pop, a third capture is accepted.
- Existing mock page tests now assert `reset` is not called until pop.

CONTRIBUTING.md has a new **Scanner navigation — CRITICAL** section;
`docs/testing.md` has the matching test contract.

</details>

---------

Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
Promote: staging -> develop
EN:
Adds a Tier-1 integration test for the BitBox sell path that starts with
too little ETH.
The cubit posts `/v1/faucet`, polls `/v1/blockchain/balances`, then
signs and broadcasts the unsigned swap and deposit through the DFX API.
The faucet response is held behind a Completer so polling cannot start
until the faucet call returns.
Existing tests that skip the faucet hop are unchanged. No production
code change.

DE:
Ergänzt einen Tier-1-Integrationstest für den BitBox-Sell-Pfad, der mit
zu wenig ETH startet.
Der Cubit sendet POST `/v1/faucet`, pollt `/v1/blockchain/balances` und
signiert dann Swap und Deposit über die DFX-API.
Die Faucet-Antwort liegt hinter einem Completer, damit das Polling erst
nach Rückkehr des Faucet-Calls startet.
Die bestehenden Tests, die den Faucet-Schritt überspringen, bleiben
unverändert. Kein Production-Code.

<details>
<summary>Details</summary>

The previous happy path in `test/integration/sell_bitbox_flow_test.dart`
set `ethBalance` above `requiredGasEth`, so `POST /v1/faucet` never ran.

The new case uses `_info(ethBalance: 0)` and wires the real
`DfxFaucetService` and `DfxBlockchainApiService` (plus the already-real
sell service) to the same `MockClient`. Sequence:

1. `POST /v1/faucet` is entered; the 200 is held on a Completer
2. a 5s elapse must not call `/v1/blockchain/balances` (timer not
installed yet)
3. Completer completes; cubit is `WaitingForEth`
4. after the 5s poll timer, `POST /v1/blockchain/balances` with
`blockchain=Ethereum` and ETH asset id 111
5. unsigned swap + deposit, two FakeBitbox signs, two broadcasts, one
confirm
6. `SellBitboxSuccess`

Unsigned/broadcast/confirm before the faucet POST fail the test. No
BitBox hardware and no live node. Local checks on the build host:
analyze clean, format unchanged, `flutter test
test/integration/sell_bitbox_flow_test.dart` +6 passed.

</details>

---------

Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
EN:
This PR reproduces the customer chart bug as goldens, without a product
fix.
MAX shows the old holdings curve, then a drop to zero. 1W, 1M, 3M and 1J
show the empty plot.

DE:
Dieser PR bildet den Kunden-Chart-Bug als Goldens ab, ohne Produkt-Fix.
MAX zeigt die alte Bestandskurve, dann den Abfall auf 0. 1W, 1M, 3M und
1J zeigen die leere Fläche.

<details>
<summary>Details</summary>

Fixtures: non-zero points older than one year (MAX curve), then zeros
through now (1W–1J collapse with `minY == maxY == 0`). Same sequence the
customer reported. PNGs from the self-hosted visual-regression runner.

</details>

---------

Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
EN:
The portfolio development chart disappeared for time windows where every
holding is zero.
The cubit now keeps a Y-range from 0 so the line stays drawn, and holds
the last value before the window so the stroke spans the selected
period.
A dashboard golden records the all-zero MAX view; the customer-report
1W–1J goldens show that full-width zero line on a 0…10 axis.
Unit tests cover an all-zero series, a one-week window of zeros, and
carrying a pre-window sample to minX.

DE:
Die Bestandsentwicklung verschwand in Zeitfenstern, in denen jeder Punkt
0 ist.
Der Cubit hält jetzt eine Y-Spanne ab 0, damit die Linie gezeichnet
bleibt, und übernimmt den letzten Wert vor dem Fenster, damit der Strich
die gewählte Periode ausfüllt.
Ein Dashboard-Golden dokumentiert die Null-MAX-Ansicht; die
Kunden-Goldens 1W–1J zeigen diese volle Null-Linie auf einer Achse 0…10.
Unit-Tests decken eine Null-Serie, ein 1W-Null-Fenster und das
Nachziehen eines Werts vor minX ab.

<details>
<summary>Details</summary>

When every visible point is 0, `average * 0.05` was 0, so `minY == maxY
== 0` and `LineChart` drew no curve. The cubit now falls back to a 5.0
currency-unit floor so the chart has a Y-range. Holdings cannot go
negative: the bottom line is always clamped to 0 (scale 0…10 for an
all-zero series), not centered through negative labels.

A clipped period (1W/1M/3M/1J) previously started the stroke at the
first interior sample. If that sample sat months into the window, the
line began mid-chart. The last sample before `minX` is now held at the
window edge so the stroke spans the selected period. An empty window
(every sample older than the period) stays empty.

`dashboard_portfolio_chart_zero.png` is the all-zero MAX view. After
#953, the mixed-history 1W/1M/3M/1J goldens pin the full-width zero line
on the 0-axis.

</details>

---------

Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
EN:
Five synthetic test customers document different buying patterns on the
holdings chart.
Each history has at least ten trades inside one year; the diagrams are
golden baselines, not live accounts.
Handbook spec 80 lists every trade and maps six screenshots (MAX for all
five, plus 1J after a sell-to-zero).
The screenshot count guard moves from 278 to 284.

DE:
Fünf synthetische Testkunden dokumentieren unterschiedliches
Kaufverhalten auf der Bestandsentwicklung.
Jede Historie hat mindestens zehn Trades in einem Jahr; die Diagramme
sind Golden-Baselines, keine Live-Konten.
Handbook-Spec 80 listet alle Trades und sechs Screenshots (MAX für alle
fünf, plus 1J nach Verkauf auf 0).
Der Screenshot-Count-Guard steigt von 278 auf 284.

<details>
<summary>Details</summary>

Personas: monthly DCA (12×80), lump then top-ups, eight buys then four
sells to zero, interleaved mix, rising scale. Fixtures are
local-midnight offsets so MAX and 1J stay stable. No addresses or
personal data.

</details>

---------

Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
EN:
Handbook PRD deploys have been failing because the mail-preview
generator now writes 28 HTML files, not 25.
This updates the exact-count guard so a push to staging can ship the
handbook again, including spec 80.
No product UI change.

DE:
Die Handbook-PRD-Deploys schlagen fehl, weil der Mail-Preview-Generator
jetzt 28 HTML-Dateien schreibt, nicht 25.
Dieser Guard-Stand lässt den Staging-Push wieder nach PRD, inklusive
Spec 80.
Keine Produkt-UI-Änderung.

<details>
<summary>Details</summary>

Count is 24 standalone `add()` mails + 3 `pendingTypes` expansions + 1
index = 28. The loop `add()` template is not counted in the 24.
Confirmed against the failed deploy log on staging (got 28).

The three extra previews come from DFXswiss/backend#5221: `black-squad`,
`kyc-payment-data`, `recommendation-confirmation`. The handbook page
`#spec-mails` only links to the generated `/mails/` index, so no extra
cards.

This unblocks #952 (`Promote: staging -> develop`), whose
only red check is `deploy-prd / Build and deploy to PRD`.

</details>

---------

Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
Promote: staging -> develop
…955)

EN:
Send is gone from the dashboard. The settings wallet-address screen
shows the QR, the stock-token hint, and Send. Tapping Send first shows
that transfers only reach registered shareholders and update the share
register. Tapping Pay first shows that payment is a two-step
REALU-to-ZCHF swap usable at OpenCryptoPay locations, because REALU is a
security.

DE:
Send ist vom Dashboard weg. Die Wallet-Adresse in den Einstellungen
zeigt den QR, den Hinweis zu den Aktientoken und Senden. Vor dem Senden
erscheint der Hinweis: nur an registrierte Aktionäre, das Aktienbuch
wird aktualisiert. Vor dem Bezahlen: Zwei-Schritt-Tausch REALU zu ZCHF
an OpenCryptoPay-Standorten, weil REALU ein Wertpapier ist.

<details>
<summary>Details</summary>

Dashboard keeps Buy, Sell, and insider-gated Pay. `/send` and `/pay`
land on info screens; Continue opens the existing recipient scanner or
OpenCryptoPay scanner. The QR address row shrink-wraps so the cluster
stays centered. Shareholder enforcement for the transfer itself lives in
the companion backend change.

</details>

---------

Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
Promote: staging -> develop
@TaprootFreakAI

Copy link
Copy Markdown
Collaborator

EN:
Maestro handbook flows was cancelled after the XCUITest driver returned HTTP 500 and the JVM hung until the 60-minute timeout; the required checks on this SHA are already green.

DE:
Maestro handbook flows wurde abgebrochen, nachdem der XCUITest-Treiber HTTP 500 lieferte und die JVM bis zum 60-Minuten-Timeout hing; die Pflicht-Checks auf diesem SHA sind bereits grün.

Details

Flow 01-welcome died with UnknownFailure on http://127.0.0.1:7001/deviceInfo (HTTP 500), then sat until GitHub cancelled the job. That check is not in the required-status set (Analyze & Test, Visual Regression, Coverage Floor Gate).

This repo does not grant workflow re-run or workflow_dispatch to this account. The retry/timeout fix is in #958 (targets staging).

TaprootFreakAI and others added 2 commits August 29, 2026 13:26
…es (#958)

EN:
Maestro retries XCUITest driver HTTP 500 hangs instead of sitting until
the job timeout. Handbook persona goldens keep trade dates on 2026-08-28
so Visual Regression does not go red at midnight.

DE:
Maestro retried bei XCUITest-Driver-HTTP-500, statt bis zum Job-Timeout
zu warten. Die Handbook-Persona-Goldens halten die Trade-Daten auf dem
28.8.2026, damit Visual Regression nicht um Mitternacht rot wird.

<details>
<summary>Details</summary>

The cancelled Maestro check on the release PR is not a required status
check. Analyze & Test, Visual Regression, and Coverage Floor Gate
already passed on that SHA.

`is_driver_hang_or_death` matches `deviceInfo failed, code: 500`. Each
`maestro test` is killed after `MAESTRO_ATTEMPT_TIMEOUT_SEC` (default
480) and retried. Handbook persona `_today()` is a fixed date, not
`DateTime.now()`.

</details>

---------

Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
Promote: staging -> develop
@TaprootFreakAI

Copy link
Copy Markdown
Collaborator

EN:
android-deploy failed on Sentry native-symbol assemble (libapp.so / app.so: not all chunks available) after the CLI said the files were already on the server. iOS deploy succeeded. Play Store never got this AAB because symbol upload runs before upload_to_play_store.

DE:
android-deploy ist am Sentry-Native-Symbol-Assemble gescheitert (libapp.so / app.so: nicht alle Chunks da), nachdem die CLI die Dateien schon auf dem Server gemeldet hatte. iOS-Deploy ist durch. Der Play Store hat dieses AAB nicht bekommen, weil der Symbol-Upload vor upload_to_play_store läuft.

Details

Retry helper: #960 (targets staging). A re-run of the failed Release job still uses the tagged Fastfile without the retry; the change applies on the next develop tag after that PR merges. This account cannot re-run GitHub Actions jobs.

TaprootFreakAI and others added 2 commits August 29, 2026 17:59
EN:
Android deploy on the v1.2.20 tag failed while uploading native symbols:
Sentry reported the files were already on the server, then failed
assembling libapp.so chunks. This retries that upload three times so a
flake does not block the Play Store upload.

DE:
Der Android-Deploy auf Tag v1.2.20 ist beim Native-Symbol-Upload
gescheitert: Sentry meldete die Dateien schon auf dem Server und
scheiterte dann am Zusammensetzen der libapp.so-Chunks. Dieser Stand
retried den Upload dreimal, damit ein Flake den Play-Store-Upload nicht
blockiert.

<details>
<summary>Details</summary>

iOS deploy on the same Release run succeeded. Analyze / Visual on the
release SHA were still running at the time of this PR. The Play Store
lane never reached `upload_to_play_store` because symbol upload runs
first.

Re-running the failed Release job still uses the tagged Fastfile without
this retry. This change takes effect on the next develop tag after
merge.

</details>

Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
Promote: staging -> develop
@TaprootFreakAI

Copy link
Copy Markdown
Collaborator

EN:
v1.2.21 android-deploy failed again: Sentry retried 3/3, files already on the server, app.so assemble still stuck. iOS succeeded. Play Store still did not get this AAB.

DE:
v1.2.21 android-deploy ist wieder gescheitert: Sentry 3/3 retried, Dateien schon auf dem Server, app.so-Assemble weiter hängend. iOS ist durch. Der Play Store hat dieses AAB weiter nicht.

Details

Follow-up: #962 — Play Store upload first, continue when assemble is stuck on already-uploaded files.

TaprootFreakAI and others added 2 commits August 30, 2026 06:00
EN:
v1.2.21 android-deploy retried Sentry symbol upload three times and
still failed: the CLI said the files were already on the server, then
assemble of app.so chunks failed. This uploads the Play Store AAB first
and continues when that stuck assemble is the only error.

DE:
v1.2.21 android-deploy hat den Sentry-Symbol-Upload dreimal retried und
ist trotzdem gescheitert: die CLI meldete die Dateien schon auf dem
Server, danach scheiterte das Assemble der app.so-Chunks. Dieser Stand
lädt das AAB zuerst in den Play Store und macht weiter, wenn nur dieses
hängende Assemble fehlschlägt.

<details>
<summary>Details</summary>

Missing symbol files still fail the lane immediately. Other Sentry
errors still retry three times and then fail. iOS gets the same
continue-on-stuck-assemble path.

A re-run of the v1.2.21 Release job still uses the tagged Fastfile
without this change. It applies on the next develop tag after merge.

</details>

---------

Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
Promote: staging -> develop
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants