Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
cb5c590
feat(buy): let customers cancel a pending RealUnit buy quote
TaprootFreak Aug 14, 2026
c83d16f
fix(buy): block parallel confirm and deactivate on a quote
TaprootFreak Aug 14, 2026
3baaba7
fix(buy): space the dual CTA and keep pending list on reload errors
TaprootFreak Aug 14, 2026
509030b
fix(buy): drop a cancelled quote immediately and serialize deactivate
TaprootFreak Aug 14, 2026
37215f1
fix(buy): only cancel WaitingForPayment quotes and lock per id
TaprootFreak Aug 14, 2026
0ac6331
fix(buy): separate deactivate loading and remove rows by id
TaprootFreak Aug 14, 2026
e3b7096
fix(buy): do not emit deactivate result after the cubit closes
TaprootFreak Aug 14, 2026
d80667a
fix(buy): keep pending list when reload fails or is stale
TaprootFreak Aug 14, 2026
626399a
test(goldens): regenerate baselines on the self-hosted runner
github-actions[bot] Aug 14, 2026
c1b6749
fix(realunit): cancel pending buys only after a binding purchase
TaprootFreak Aug 17, 2026
cfb7c95
fix(realunit): read cancel-dialog strings from the page context
TaprootFreak Aug 17, 2026
37dea77
test(goldens): regenerate baselines on the self-hosted runner
github-actions[bot] Aug 17, 2026
08b5f79
fix(realunit): golden the cancel-quote dialog as its own widget
TaprootFreak Aug 17, 2026
7a2aaed
test(goldens): regenerate baselines on the self-hosted runner
github-actions[bot] Aug 17, 2026
e666ab7
fix(realunit): drop cancelled pending rows before reload
TaprootFreak Aug 17, 2026
abfc424
fix(handbook): bump screenshot count pin to 282
TaprootFreak Aug 17, 2026
533f1a7
fix(realunit): silence prefer_const_constructors on coverage ctor
TaprootFreak Aug 17, 2026
1ed1e1f
fix(handbook): probe new detail cards in the smoke sample
TaprootFreak Aug 17, 2026
e8e2131
docs(screens): inventory pending-transaction detail route
TaprootFreak Aug 17, 2026
7bf0c1b
fix(ci): deepen shallow checkout for touched-UI coverage
TaprootFreak Aug 17, 2026
7caeda2
test(realunit): cover empty pop and unmount on pending list
TaprootFreak Aug 17, 2026
c6eae8e
test(realunit): resolve pushNamed after unmounting the list
TaprootFreak Aug 17, 2026
426070a
fix(realunit): drop cancelled rows even if the list view unmounts
TaprootFreak Aug 17, 2026
0187b82
test(realunit): hit isClosed after the detail route returns
TaprootFreak Aug 17, 2026
249aa7c
fix(realunit): apply detail return on the pending cubit
TaprootFreak Aug 17, 2026
fa014f4
fix(realunit): drop unused pending-transactions wrapper
TaprootFreak Aug 17, 2026
ee6f809
fix(realunit): pending detail sheet, title, and back-during-cancel
TaprootFreak Aug 17, 2026
58d1d99
test(goldens): regenerate baselines on the self-hosted runner
github-actions[bot] Aug 17, 2026
5d881b7
fix(realunit): scroll the cancel sheet at large text scale
TaprootFreak Aug 17, 2026
57e8ca1
test(realunit): gate cancel-quote sheet on the responsive matrix
TaprootFreak Aug 17, 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
10 changes: 5 additions & 5 deletions .github/workflows/handbook-build-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,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" != "279" ]; then
echo "expected 279 screenshots, got $count" >&2
if [ "$count" != "282" ]; then
echo "expected 282 screenshots, got $count" >&2
exit 1
fi

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

# Screenshots dir must contain all 279 PNGs assembled from Goldens.
# Screenshots dir must contain all 282 PNGs assembled from Goldens.
# Hit one of them through the auth gate to verify wiring end-to-end.
# Mix of the original 01-61 range and the 62-269 batch (every Golden
# Mix of the original 01-61 range and the 62-272 batch (every Golden
# baseline) so a regression in either half 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; do
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-pending-transaction-detail-buy-waiting 272-pending-transaction-detail-not-cancellable; do
code=$(curl -s -o /dev/null -w '%{http_code}' -u "${HANDBOOK_USER:-x}:${HANDBOOK_PASS:-x}" "http://127.0.0.1:8080/screenshots/${name}.png")
# 200 (auth happens to match) or 401 (auth fails but file exists)
# both prove the file is on disk. 404 means it was not assembled.
Expand Down
46 changes: 41 additions & 5 deletions .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,51 @@ jobs:
# baselines.
- run: flutter test --coverage --exclude-tags golden

# Per-file 100% line-coverage gate for every UI file this PR touches
# under lib/screens/** or lib/widgets/** (pages, views, widgets — not
# cubits/blocs, not *.g.dart). Runs against the FULL lcov.info before
# the packages/cubits/blocs extract below, so widget coverage is not
# stripped away first. The floor on packages/cubits/blocs is unchanged.
- name: Enforce 100% line coverage on touched UI files
run: |
set -euo pipefail
if [ ! -f coverage/lcov.info ]; then
echo "::error::coverage/lcov.info not found — cannot enforce touched UI coverage"
exit 1
fi
# PRs use github.base_ref (e.g. staging/develop). Push/dispatch have
# no base_ref — fall back to develop so the step stays defined.
# Prefer the PR base SHA (always present on pull_request). Fetch that
# single commit so a shallow checkout can still diff touched files.
BASE="${{ github.event.pull_request.base.sha }}"
if [ -z "$BASE" ]; then
BASE_REF="${{ github.base_ref }}"
if [ -z "${BASE_REF}" ]; then
BASE_REF="develop"
fi
git fetch --no-tags --depth=50 origin "$BASE_REF"
BASE="origin/${BASE_REF}"
else
git fetch --no-tags --depth=1 origin "$BASE"
fi
# actions/checkout is shallow. A depth-1 fetch of the base SHA
# does not connect histories, so `git diff BASE...HEAD` dies
# with "no merge base". Deepen until the two tips meet.
if ! git merge-base "$BASE" HEAD >/dev/null 2>&1; then
git fetch --no-tags --deepen=200 origin
fi
if ! git merge-base "$BASE" HEAD >/dev/null 2>&1; then
git fetch --unshallow --no-tags origin || git fetch --no-tags --deepen=1000 origin
fi
bash scripts/check-touched-ui-coverage.sh coverage/lcov.info "$BASE"

# Narrow the coverage report to the README-defined activated surface:
# lib/packages/** — services, repositories, signers, utils
# lib/screens/**/cubit(s)/** — Cubit logic per feature
# lib/screens/**/bloc/** — Bloc logic per feature
# Widget files (lib/screens/**/*_page.dart, lib/widgets/**) are covered
# by `testWidgets` specs and excluded from the line-coverage scope by
# design — see README "Coverage scope". Threshold enforcement is the
# next step (see README "Coverage infrastructure roadmap"); this step
# only produces the scoped baseline + a human-readable summary.
# Widget files (lib/screens/**/*_page.dart, lib/widgets/**) stay outside
# this floor extract; when a PR touches them they are gated by the
# touched-UI check above instead — see README "Coverage scope".
#
# The extract patterns use lcov's fnmatch matcher, where `*` is greedy
# across `/` — so `lib/packages/*` matches arbitrarily deep paths under
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ The app supports three wallet modes (`software`, `bitbox`, `debug`) with differe
- Uses `flutter_test`, `bloc_test`, and `mocktail` (NOT mockito).
- Test structure mirrors `lib/` structure.
- Test helper at `test/helper/` (provides `pumpApp`).
- **Touched UI files require 100% line coverage.** Every file under `lib/screens/**` or `lib/widgets/**` that a PR changes (pages, views, widgets — not only cubits) must have 100% line coverage, measured per file against the full `lcov.info` before the packages/cubits/blocs scope extract. Generated `*.g.dart` is excluded. Cubit/bloc folders under screens stay under the existing 100% packages/cubits/blocs floor and are not part of this UI gate. CI enforces the rule via [`scripts/check-touched-ui-coverage.sh`](scripts/check-touched-ui-coverage.sh). A neglected file that is touched must be brought fully to 100% in the same PR.
- For BitBox-related code, the layered test strategy (Tier 0–4) is documented in [`docs/testing.md`](docs/testing.md), with concrete patterns for cubit tests, widget tests, service + HTTP tests, and `FakeBitboxCredentials`-backed integration tests.
- [`docs/testing.md`](docs/testing.md) also lists the surface that needs an infra PR first (Drift repositories, `getIt`-coupled pages, `path_provider`-coupled cubits, the Sumsub SDK, plugin-coupled widgets). Don't try to mock around those without changing the injection point.
- Service-lifecycle tests are mandatory for any service with a `Timer`, observer/subscription loop, or platform/MethodChannel dependency: instantiate the real class (no mock of the service itself), swap `BitboxUsbPlatform.instance` in `setUp` and restore in `tearDown`. Tests with periodic-timer or observer behaviour MUST drive time via `package:fake_async` (`fakeAsync` zone + `async.elapse(...)`). Wall-clock `Future.delayed` is not acceptable for time-bound assertions.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ A Flutter wallet for Real Unit investors. Multi-chain, BitBox-ready, KYC-aware.
- Defensive code that genuinely cannot be reached in `flutter_test` (platform channels without a test override, native plugin entry points, BLE callbacks) is exempted by an inline `// coverage:ignore-line` annotation with a one-line reason.
- The branch is protected on GitHub: a PR cannot be merged while CI is red.

**Coverage scope:** `lib/packages/**` (services, repositories, signers, utils) and the `cubits/` + `bloc/` directories under each `lib/screens/<feature>/`. Widget files (`lib/screens/<feature>/<feature>_page.dart` and `lib/widgets/**`) are exercised via `testWidgets` specs and excluded from the line-coverage gate — widget tests count as `widget` coverage in the feature matrix, not as line %. Generated files (`*.g.dart` from `build_runner` / Drift) are also stripped after the scope extract — they are tool output, not developer code, and would otherwise drag the scoped line % down for free.
**Coverage scope:** `lib/packages/**` (services, repositories, signers, utils) and the `cubits/` + `bloc/` directories under each `lib/screens/<feature>/` stay under the repo-wide floor gate (100% lines on that surface). **Touched UI files are no longer exempt from the line-coverage gate:** every PR-touched file under `lib/screens/**` or `lib/widgets/**` (pages, views, widgets — excluding cubit/bloc folders and `*.g.dart`) must reach 100% line coverage per file. CI runs [`scripts/check-touched-ui-coverage.sh`](scripts/check-touched-ui-coverage.sh) against the full `lcov.info` **before** the packages/cubits/blocs extract, so widget lines are still present when the check runs. The floor on packages/cubits/blocs is **not** widened to the whole widget tree — only files the PR actually touches are gated. Generated files (`*.g.dart` from `build_runner` / Drift) are stripped after the scope extract — they are tool output, not developer code.

The five-tier testing model (Tier 0 Cubit unit · Tier 1 FakeBitbox integration · Tier 2 firmware simulator · Tier 3 Maestro flows (handbook simulator + deferred BitBox02 hardware) · Tier 4 BLE VCR/replay stretch) is tracked in [#314](https://github.com/RealUnitCH/app/issues/314). See [`docs/testing.md`](docs/testing.md) for the full tier picker. New BitBox-touching PRs are expected to add tests at the appropriate tier(s).

Expand Down Expand Up @@ -130,9 +130,9 @@ The transport is USB on Android and Bluetooth on iOS; the original BitBox 02 has

The activated surface (see "Coverage scope" above) is at **100 % scoped line coverage**. Every file under `lib/packages/**`, `lib/screens/**/cubit(s)/**`, and `lib/screens/**/bloc/**` either ships with tests or carries an `// coverage:ignore-*` annotation with a documented reason. The previous bullet list of partially-covered services, KYC cubits, biometric unlock, and DFX backend services has been retired — those gaps are closed.

Out of scope of the gate and tracked elsewhere:
Out of scope of the floor gate and tracked elsewhere:

- **Widget render paths** — measured separately via `testWidgets` specs, not in the line-coverage gate (deliberate; see `docs/testing.md` "Tier 0" rationale).
- **Untouched widget render paths** — widget files the PR does not change are still outside the packages/cubits/blocs floor extract. **Touched** UI files under `lib/screens/**` / `lib/widgets/**` are gated to 100% line coverage per file by `scripts/check-touched-ui-coverage.sh` (see Coverage scope above).
- **Visual regression (goldens)** — every `lib/screens/**/*_page.dart` has a `test/goldens/**/*_golden_test.dart` companion, validated pixel-exact on the self-hosted runner by the `Visual Regression` CI job. Not folded into the line-coverage gate. The one exception is `lib/screens/web_view/web_view_page.dart` — `InAppWebView` is a platform-view that has no headless render in `flutter_test`, the spec is committed with `skip: true`. See [`docs/visual-regression-tests.md`](docs/visual-regression-tests.md).
- **Tier 2 (firmware simulator)** — runs in `bitbox-simulator.yml`, not folded into the scoped coverage number.
- **Tier 3 (Maestro handbook flows)** — runs in `tier3-handbook.yaml`, not folded in.
Expand Down
4 changes: 4 additions & 0 deletions assets/languages/strings_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@
"paySwapping": "REALU wird in ZCHF getauscht",
"payWaitingForEth": "Netzwerkgebühren werden angefordert",
"pdf": "PDF",
"pendingTransactionDeactivate": "Angebot stornieren",
"pendingTransactionDeactivateConfirm": "Dieses ausstehende Kaufangebot stornieren?",
"pendingTransactionDeactivateFailed": "Das Angebot konnte nicht storniert werden. Bitte versuchen Sie es erneut.",
"pendingTransactionDetailTitle": "Ausstehende Transaktion",
"pendingTransactions": "Ausstehende Transaktionen",
"personalData": "Persönliche Daten",
"phone": "Telefon",
Expand Down
4 changes: 4 additions & 0 deletions assets/languages/strings_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@
"paySwapping": "Swapping REALU to ZCHF",
"payWaitingForEth": "Requesting network fees",
"pdf": "PDF",
"pendingTransactionDeactivate": "Cancel quote",
"pendingTransactionDeactivateConfirm": "Cancel this pending buy quote?",
"pendingTransactionDeactivateFailed": "The quote could not be cancelled. Please try again.",
"pendingTransactionDetailTitle": "Pending transaction",
"pendingTransactions": "Pending transactions",
"personalData": "Personal data",
"phone": "Phone",
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 279 Handbook-Screenshots
Es gibt keinen separaten Regeneration-Schritt: Die 282 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
50 changes: 49 additions & 1 deletion docs/handbook/de/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3833,7 +3833,55 @@ <h2><span class="num">31</span>Dashboard — Transaktionen</h2>
/>
</div>
<div class="desc">
Unter dem Kurschart erscheint die Karte <b>Ausstehende Transaktionen</b> mit noch laufenden DFX-Aufträgen: <b>Kauf / In Bearbeitung / 500 CHF</b> und <b>Verkauf / Warte auf Zahlung / 30 REALU</b>, jeweils mit Datum und drehendem Ladeindikator. Der Nutzer sieht dies, solange ein Kauf oder Verkauf beim Zahlungsdienst noch nicht abgeschlossen ist; da hier noch kein Guthaben vorliegt, folgen darunter Token-Illustration und Kaufen-Button.
Unter dem Kurschart erscheint die Karte <b>Ausstehende Transaktionen</b> mit noch laufenden DFX-Aufträgen: <b>Kauf / Warte auf Zahlung / 500 CHF</b> (verbindlicher Kauf) und <b>Verkauf / Warte auf Zahlung / 30 REALU</b>, jeweils mit Datum und drehendem Ladeindikator. Die Zeilen sind tippbar und öffnen die Detailseite der jeweiligen Transaktion; es gibt kein X-Icon zum Stornieren auf der Zeile. Der Nutzer sieht dies, solange ein Kauf oder Verkauf beim Zahlungsdienst noch nicht abgeschlossen ist; da hier noch kein Guthaben vorliegt, folgen darunter Token-Illustration und Kaufen-Button.
</div>
</div>
<div class="test" id="270-pending-transaction-detail-buy-waiting">
<div class="head">
<a class="name permalink" href="#270-pending-transaction-detail-buy-waiting">270-pending-transaction-detail-buy-waiting</a>
<button class="copy-link" type="button" data-target="270-pending-transaction-detail-buy-waiting" title="Direkt-Link kopieren" aria-label="Direkt-Link kopieren">🔗 Link</button>
<span class="src">screens/dashboard/goldens/macos/pending_transaction_detail_buy_waiting.png</span>
</div>
<div class="img">
<img
src="../screenshots/270-pending-transaction-detail-buy-waiting.png"
alt="Detailseite eines ausstehenden Kaufs mit Storno-Button"
/>
</div>
<div class="desc">
Detailseite eines verbindlichen Kaufs im Status <b>Warte auf Zahlung</b>: Typ, Status, Betrag, Asset und ID der Transaktion. Unten sticky der sekundäre Button <b>Angebot stornieren</b> — nur hier, und nur nach dem verbindlichen Kauf, ist Storno erlaubt.
</div>
</div>
<div class="test" id="271-pending-transaction-detail-deactivate-overlay">
<div class="head">
<a class="name permalink" href="#271-pending-transaction-detail-deactivate-overlay">271-pending-transaction-detail-deactivate-overlay</a>
<button class="copy-link" type="button" data-target="271-pending-transaction-detail-deactivate-overlay" title="Direkt-Link kopieren" aria-label="Direkt-Link kopieren">🔗 Link</button>
<span class="src">screens/dashboard/goldens/macos/pending_transaction_detail_deactivate_overlay.png</span>
</div>
<div class="img">
<img
src="../screenshots/271-pending-transaction-detail-deactivate-overlay.png"
alt="Bestätigungsdialog zum Stornieren des Kaufangebots"
/>
</div>
<div class="desc">
Nach Tippen auf <b>Angebot stornieren</b> erscheint der Bestätigungsdialog mit Abbrechen und erneuter Storno-Aktion. Erst nach Bestätigung wird der Deactivate-Request gesendet.
</div>
</div>
<div class="test" id="272-pending-transaction-detail-not-cancellable">
<div class="head">
<a class="name permalink" href="#272-pending-transaction-detail-not-cancellable">272-pending-transaction-detail-not-cancellable</a>
<button class="copy-link" type="button" data-target="272-pending-transaction-detail-not-cancellable" title="Direkt-Link kopieren" aria-label="Direkt-Link kopieren">🔗 Link</button>
<span class="src">screens/dashboard/goldens/macos/pending_transaction_detail_not_cancellable.png</span>
</div>
<div class="img">
<img
src="../screenshots/272-pending-transaction-detail-not-cancellable.png"
alt="Detailseite ohne Storno-Button (Verkauf oder in Bearbeitung)"
/>
</div>
<div class="desc">
Detailseite einer ausstehenden Transaktion, die nicht stornierbar ist (z. B. Verkauf oder Kauf in Bearbeitung): dieselben Felder wie beim Kauf, aber ohne den Button <b>Angebot stornieren</b>.
</div>
</div>
<div class="test" id="93-dashboard-recent-transactions">
Expand Down
7 changes: 4 additions & 3 deletions docs/screens.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Column meaning:
screen, or `—` if the screen has no Golden baseline. Each slot is a
Visual-Regression Golden under `test/goldens/`, mapped to its handbook
position by `scripts/assemble-handbook-screenshots.sh`. The handbook now
covers **all 279 Golden baselines** — every screen **plus every tested
covers **all 282 Golden baselines** — every screen **plus every tested
state variant** (Default / Loading / Error / Snackbar / Dropdown /
Validation / Confirm / Success / Failure …), including the areas that were
previously absent: Support (email capture, tickets, chat), Settings
Expand Down Expand Up @@ -50,6 +50,7 @@ Column meaning:
| PIN & lock | `VerifyPinPage` | `verifyPin` | `/verifyPin` | `86`, `87` |
| Dashboard & trading | `DashboardPage` | `dashboard` | `/dashboard` | `35`, `89`, `90`, `91`, `92`, `93`, `94` |
| Dashboard & trading | `TransactionHistoryPage` | `transactionHistory` | `/dashboard/transactionHistory` | `36`, `95`, `96`, `97`, `98`, `99`, `100` |
| Dashboard & trading | `PendingTransactionDetailPage` | `pendingTransaction` | `/dashboard/pendingTransaction` | `270`, `271`, `272` |
| Dashboard & trading | `BuyPage` | `buy` | `/buy` | `44`, `45`, `46`, `47`, `48`, `103`, `104`, `105`, `106`, `107`, `108`, `109`, `110`, `111`, `112`, `113` |
| Dashboard & trading | `BuyPaymentDetailsPage` | `buyPaymentDetails` | `/buyPaymentDetails` | `53`, `114`, `115`, `116`, `117` |
| Dashboard & trading | `SellPage` | `sell` | `/sell` | `49`, `50`, `51`, `52`, `118`, `119`, `120`, `123`, `124`, `125` |
Expand Down Expand Up @@ -116,7 +117,7 @@ Column meaning:
| KYC | `KycMergeProcessingPage` | — | — | `210` |
| Shared widgets | `PhoneNumberField` | — | — | `268` |

76 screens — 44 routed (`GoRoute`) + 32 non-routed. The table also carries
77 screens — 45 routed (`GoRoute`) + 32 non-routed. The table also carries
one shared form-widget baseline (`PhoneNumberField`), which is not a screen.

## Notes
Expand All @@ -136,7 +137,7 @@ one shared form-widget baseline (`PhoneNumberField`), which is not a screen.
`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 279 handbook slots is a Visual-Regression
- **Handbook numbering.** Each of the 282 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
Loading
Loading