Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
b329dc3
fix(kyc): correct emailConfirmed contract docs, fail-loud date parse,…
TaprootFreak Jul 12, 2026
fd739bd
docs(screens): update the inventory tally for the confirm-email page
TaprootFreak Jul 12, 2026
9c0e0cb
test(goldens): support chat page state baselines (#816)
TaprootFreak Jul 12, 2026
fa33455
test(goldens): support create-ticket page state baselines (#816)
TaprootFreak Jul 12, 2026
dead128
test(goldens): support email-capture buy-flow variant baseline (#816)
TaprootFreak Jul 12, 2026
c7189ae
test(goldens): support tickets page state baselines (#816)
TaprootFreak Jul 12, 2026
b0d18f0
test(goldens): verify-pin app-lock, lockout and biometric-hint state …
TaprootFreak Jul 12, 2026
35c8069
test(goldens): setup-pin confirm-mismatch and store-failure baselines…
TaprootFreak Jul 12, 2026
e2a8017
test(goldens): create-wallet loading state baseline (#816)
TaprootFreak Jul 12, 2026
6c413ac
test(goldens): verify-seed error, verifying, verified and commit-fail…
TaprootFreak Jul 12, 2026
af95176
test(goldens): restore-wallet complete, in-flight, success and failur…
TaprootFreak Jul 12, 2026
4b0ea9c
fix(support): render message time in the message's local zone, not th…
TaprootFreak Jul 12, 2026
70cc484
Merge pull request #821 from RealUnitCH/test/goldens-pin-onboarding
TaprootFreak Jul 12, 2026
a50f7ae
Merge pull request #820 from RealUnitCH/test/goldens-support
TaprootFreak Jul 12, 2026
36a3b89
Merge pull request #819 from RealUnitCH/fix/kyc-confirm-email-review-…
TaprootFreak Jul 12, 2026
c1fc48e
test(goldens): settings screens state baselines (#816) (#822)
TaprootFreak Jul 12, 2026
97b18b6
test(kyc): cover registration form-widget interaction + confirm-email…
TaprootFreak Jul 12, 2026
165d5f1
fix(kyc): return to the in-flight route after resume/PIN re-lock inst…
TaprootFreak Jul 12, 2026
5640879
test(goldens): KYC email + financial-data state baselines (#816) (#830)
TaprootFreak Jul 12, 2026
9c147b1
test(goldens): KYC registration wizard state baselines (#816) (#829)
TaprootFreak Jul 12, 2026
c5c2425
test(goldens): KYC status/verification state baselines (#816) (#828)
TaprootFreak Jul 12, 2026
fc2e217
docs(store): add App Store promotional text (#826)
TaprootFreak Jul 12, 2026
3c09457
fix(kyc): clear stale country-field error on selection and neutralize…
TaprootFreak Jul 12, 2026
3e7011f
test(goldens): regenerate country-field state goldens for the neutral…
TaprootFreak Jul 12, 2026
766792a
feat(kyc): drive the legal-disclaimer gate from the API (#831)
TaprootFreak Jul 12, 2026
9054b55
test(kyc): restore 100% coverage for the legal-acceptance feature (#837)
TaprootFreak Jul 12, 2026
e596aed
test(goldens): buy flow state baselines (#816) (#834)
TaprootFreak Jul 12, 2026
5843f4b
test(goldens): dashboard / transaction-history / receive state baseli…
TaprootFreak Jul 12, 2026
cfadd40
test(goldens): connect-bitbox / debug-auth / legal-document state bas…
TaprootFreak Jul 12, 2026
5637315
feat(kyc): show a manual-review screen when the RealUnit registration…
TaprootFreak Jul 12, 2026
0b56954
chore: bump version floor to 1.2.0 (#839)
TaprootFreak Jul 12, 2026
010b0a6
Merge pull request #824 from RealUnitCH/staging
TaprootFreak Jul 12, 2026
a4dba40
fix(kyc): sign the server-provided registration date, not the device …
TaprootFreak Jul 13, 2026
577467b
docs(handbook): cover all 268 golden baselines (#843)
TaprootFreak Jul 13, 2026
e3a5354
fix(kyc): surface structured API rejections on the registration submi…
TaprootFreak Jul 13, 2026
ab786dd
fix(ci): give the cold-start welcome assert head-room for iOS 26 hier…
TaprootFreak Jul 13, 2026
ce48858
test: cover hidden activated-surface code and gate against blind spot…
TaprootFreak Jul 13, 2026
88cf78b
test: cover the boot-time security-flag migration (#846)
TaprootFreak Jul 13, 2026
8977699
feat(kyc): hard-wire tax residences to include the address country (#…
TaprootFreak Jul 14, 2026
3150edd
fix(bitbox): keep pairing CTAs tappable on small screens and large te…
TaprootFreak Jul 14, 2026
8610155
fix(kyc): hide dead remove button on the primary tax residence row (#…
TaprootFreak Jul 14, 2026
0f4544e
ci: disable Actions cache for flutter-action on the self-hosted runne…
TaprootFreak Jul 14, 2026
5c490b6
ci: lower self-hosted job timeout from 30 to 15 minutes (#853)
TaprootFreak Jul 15, 2026
b5d59fc
fix(responsive): keep CTAs tappable across 25 surfaces at any device …
TaprootFreak Jul 15, 2026
a6275ba
fix(kyc): seed the tax step from the stored declaration and bound it …
TaprootFreak Jul 15, 2026
765f2a1
ci(golden-regenerate): checkout live branch head instead of pinned di…
TaprootFreak Jul 15, 2026
047b6ac
Merge pull request #841 from RealUnitCH/staging
TaprootFreak Jul 15, 2026
4ce94fc
ci: dedupe staging-lane runs via shared concurrency group (#857)
TaprootFreak Jul 15, 2026
29c3b5d
fix(responsive): keep bottom-sheet CTAs tappable at large text scale …
TaprootFreak Jul 15, 2026
389a866
fix(buy): typed handling for primary-email confirm errors and unconfi…
TaprootFreak Jul 15, 2026
8e2ab79
fix(ci): route staging checks without cancelled required checks (#861)
TaprootFreak Jul 15, 2026
3d8ff8c
docs(handbook): stage the four no-registration web baselines (18 → 22…
TaprootFreak Jul 15, 2026
87f79b1
Merge pull request #859 from RealUnitCH/staging
TaprootFreak Jul 15, 2026
7ac5551
chore(sell/buy): format beneficiary IBAN and guard the sell sheet cal…
TaprootFreak Jul 16, 2026
308df99
test: cover the boot-time SQLCipher encryption-key lifecycle (#868)
TaprootFreak Jul 16, 2026
b89f189
test(goldens): support create-ticket SnackBars (error + success) (#863)
TaprootFreak Jul 16, 2026
0af48fe
fix(support): route a known email to the merge-verification flow (#869)
TaprootFreak Jul 16, 2026
a893a3c
Merge pull request #865 from RealUnitCH/staging
TaprootFreak Jul 17, 2026
1b9999e
fix: gate-flow PIN lockout copy + local ticket date (#870)
TaprootFreak Jul 17, 2026
91b9301
fix(transactions): show transaction dates/times in local time (#872)
TaprootFreak Jul 18, 2026
1579367
feat(error-handling): catch unhandled async errors via PlatformDispat…
TaprootFreak Jul 18, 2026
c454897
feat: OCP pay-flow (RealU→ZCHF→Open CryptoPay) (#674)
TaprootFreak Jul 22, 2026
a59b989
feat: RealUnit wallet-to-wallet (W2W) gasless transfer flow (#687)
TaprootFreak Jul 22, 2026
f6bfcd1
fix: upgrade mobile_scanner to 7.4.0 for arm64 iOS-26 simulator suppo…
TaprootFreak Jul 23, 2026
98ce01f
Add a payment-deeplink entry point for OpenCryptoPay (#876)
TaprootFreak Jul 24, 2026
af2f1b2
Wire Sentry into native release builds (#880)
TaprootFreak Jul 24, 2026
35447f6
fix(bitbox): sign the registration with a chainId-extended EIP-712 do…
Blume1977 Jul 31, 2026
2d0d454
Merge pull request #871 from RealUnitCH/staging
TaprootFreak Jul 31, 2026
0c74fcf
fix(ci): run the iOS config-only build outside the bundler environmen…
Danswar Jul 31, 2026
1ad0909
Merge pull request #882 from RealUnitCH/staging
TaprootFreak Jul 31, 2026
ba5e029
c23c74f4 - Gate Pay and Send behind a hidden insider unlock (#885)
TaprootFreak Aug 3, 2026
9fb121d
feat(kyc): add the PersonalData step page (#883)
Danswar Aug 3, 2026
f1c1ea7
feat(crash-reporting): opt-in crash reporting behind a build-time SEN…
TaprootFreak Aug 3, 2026
98484cd
chore(l10n): remove unused `logout` string key (#748)
TaprootFreak Aug 3, 2026
bb855f4
Merge pull request #888 from RealUnitCH/staging
TaprootFreak Aug 3, 2026
5820c34
c23c74f4 - Render the handbook insider section single-column (#889)
TaprootFreak Aug 3, 2026
b746377
feat(support): attach an image to support tickets and chat messages (…
joshuakrueger-dfx Aug 4, 2026
8c51dd9
Merge pull request #891 from RealUnitCH/staging
TaprootFreak Aug 4, 2026
11d3760
c23c74f4 - Handbook: instructions-first walkthrough and staging-to-pr…
TaprootFreak Aug 4, 2026
eaba98d
fix(support): review follow-ups for the ticket attachment field (#893)
joshuakrueger-dfx Aug 7, 2026
73a6ea2
Merge pull request #895 from RealUnitCH/staging
TaprootFreak Aug 7, 2026
eb536e9
chore(ios): raise the deployment target from 13.0 to 15.0 (#899)
TaprootFreak Aug 7, 2026
69fce83
Merge pull request #900 from RealUnitCH/staging
TaprootFreak Aug 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
30 changes: 30 additions & 0 deletions .coverage-visibility-allowlist
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Coverage visibility allowlist — see scripts/check-coverage-visibility.sh.
#
# In-scope .dart files that legitimately produce NO coverable lines, so they
# never emit an `SF:` record in the scoped tracefile and would otherwise trip
# the visibility gate. Every entry here is a pure abstract interface / port, an
# enum, const-only data, or a Drift table schema — there is no executable body
# to cover.
#
# This is a ratchet: a NEW in-scope file that carries no coverage fails the
# build. Fix it by adding a test that exercises it (preferred). Only add a line
# here if the file genuinely has no coverable lines, and say why in a comment.
# Blank lines and `#` comments are ignored. Paths are repo-relative.

# Abstract interfaces / ports (implementations live elsewhere and are tested):
lib/packages/io/backup_exclusion_port.dart
lib/packages/io/documents_directory_port.dart
lib/packages/service/biometric/biometric_port.dart
lib/packages/service/price_service.dart
lib/screens/kyc/steps/ident/cubits/kyc_ident/sumsub_ident_port.dart

# Enums (no bodies):
lib/packages/service/biometric/biometric_auth_outcome.dart
lib/packages/service/dfx/models/payment/payment_info_error.dart
lib/screens/restore_wallet/cubit/validate_seed/validate_seed_state.dart

# Const-only data (covered as values by default_assets_test.dart, no lines):
lib/packages/utils/default_assets.dart

# Drift table schema (column getters carry coverage:ignore-line):
lib/packages/storage/node_storage.dart
33 changes: 28 additions & 5 deletions .github/workflows/golden-regenerate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,38 @@ jobs:
regenerate:
name: Regenerate golden baselines
runs-on: [self-hosted, macOS, ARM64, m3-ultra, realunit-app]
timeout-minutes: 30
# Same reasoning as `golden-tests` in `pull-request.yaml`: the old 30 was
# sized for the cache era (~18 minutes of stalled Actions-cache overhead).
# Regeneration is that same workload plus a commit+push, so 15 minutes
# still leaves room for a cold Flutter SDK download, and it frees this
# repo's single self-hosted slot far sooner when a run wedges. Keep the
# two jobs' timeouts in sync, like the rest of their setup.
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
# Explicit token so the later `git push` is authenticated. Without
# this, checkout still works but the remote is configured with no
# credential helper and the push fails with HTTP 403.
token: ${{ secrets.GITHUB_TOKEN }}
# Single-slot runner can start minutes after dispatch. Default
# checkout pins github.sha (stale by then); render against that
# tree and the later push is rejected because the branch tip has
# moved. Checkout the live head instead (github.ref_name — the
# dispatched branch; workflow_dispatch always runs with --ref
# <branch>) so baselines match the tree they get committed to.
ref: ${{ github.ref_name }}
- uses: subosito/flutter-action@v2
with:
flutter-version: "3.41.6"
channel: "stable"
cache: true
# Self-hosted runner already keeps the Flutter SDK persistently
# under `_work/_tool` across runs. The Actions cache restore on
# this runner stalls at 0.5-0.8 MB/s, times out after the 10
# minute segment timeout, falls back to a cache miss anyway,
# then burns ~8 minutes on a cache-save — ~18 minutes of pure
# overhead that returns nothing useful here.
cache: false
- run: flutter pub get
- run: dart run tool/generate_localization.dart
- run: dart run tool/generate_release_info.dart
Expand All @@ -75,9 +94,13 @@ jobs:
exit 0
fi
git commit -m "test(goldens): regenerate baselines on the self-hosted runner"
# On protected branches (develop/main) this fails by design.
# The next step uploads the PNGs as an artifact so the user can
# still recover the regen output.
# Protected branches (develop/main): fails by design — no force-push,
# no bypass. Feature branches: after the ref: fix above this should
# normally succeed. If the branch still moves between checkout and
# push (now a seconds-wide window), fail loud; the fallback artifact
# step below still uploads the PNGs. Recovery: re-dispatch (cheap).
# Do NOT rebase or force-push — that would publish baselines that no
# longer match the tree head they were rendered against.
git push

# Runs only if the push step failed. Uploads the regenerated PNGs so
Expand Down
42 changes: 33 additions & 9 deletions .github/workflows/handbook-build-check.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,32 @@
name: Handbook Build Check

# PR-only build verification for Dockerfile.handbook + the Goldens-assembled
# screenshots. Does NOT push to Docker Hub and does NOT deploy — that
# remains the job of handbook-deploy.yaml (staging push → DEV, develop push → PRD).
# PR-only build verification for the handbook CONTENT. Gates four things
# before a staging merge, all pure content checks with no live deploy:
# - screenshot-assembly: scripts/assemble-handbook-screenshots.sh runs and
# produces the expected PNG count
# - store-listing sync: scripts/assemble-handbook-store-listing.py output
# matches the committed handbook (+ its HTML
# sanitizer strips injection payloads)
# - legal-sync: scripts/assemble-handbook-legal.py output
# matches the committed handbook
# - image build + container smoke:
# `docker build` (no push) + a container smoke
# test: /healthz answers and /de/ sits behind the
# auth wall. The HTTP probes do not establish
# per-file existence (the auth gate answers 401
# before nginx ever looks a file up): screenshots
# are proven by their explicit count guard above,
# legal downloads by build-legal-downloads.sh's
# fail-fast per-base existence check during the
# image build
#
# Does NOT check out DFXswiss/api or RealUnitCH/web (those only happen in
# the real build inside handbook.yaml) and does NOT push to Docker Hub or
# deploy — that remains the job of handbook-deploy.yaml (staging push →
# PRD). The paths filter below mirrors the CONTENT inputs of the handbook
# image plus this workflow itself; the deploy WIRING (SSH, secrets,
# server rollout) are exercised exclusively by the deploy run, not by this
# check.
#
# Path filter covers everything that goes into the handbook image:
# - docs/handbook/** handbook HTML, README, en/de subtrees
Expand Down Expand Up @@ -68,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" != "61" ]; then
echo "expected 61 screenshots, got $count" >&2
if [ "$count" != "279" ]; then
echo "expected 279 screenshots, got $count" >&2
exit 1
fi

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

# Screenshots dir must contain all 61 PNGs assembled from Goldens.
# Screenshots dir must contain all 279 PNGs assembled from Goldens.
# Hit one of them through the auth gate to verify wiring end-to-end.
# Mix of original 01-26 range + new 27-61 range 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; do
# Mix of the original 01-61 range and the 62-269 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
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
89 changes: 26 additions & 63 deletions .github/workflows/handbook-deploy.yaml
Original file line number Diff line number Diff line change
@@ -1,88 +1,51 @@
name: Handbook CI/CD

# Per-branch handbook deploy pipeline, one environment per branch:
# push to `staging` → build from staging → deploy DEV (dev-handbook.realunit.app)
# push to `develop` → build from develop → deploy PRD (handbook.realunit.app)
# Handbook deploy pipeline: a push to `staging` deploys directly to PRD
# (handbook.realunit.app). There is no separate DEV environment anymore —
# the former dev-handbook.realunit.app instance is deliberately
# decommissioned and no longer served.
#
# DEV and PRD are independent runs keyed by the pushed branch; they no
# longer share a single run that fans out to both. The "DEV is green before
# PRD ships" guarantee is now provided by the branch-promotion flow itself:
# handbook content reaches `develop` only after it has been on `staging`
# (auto-staging-pr.yaml opens the staging → develop PR), i.e. after it was
# built and smoke-tested on DEV. Promoting to develop is what triggers PRD.
#
# This is a deliberate dev/prd-coupling exception scoped to the handbook
# alone — the wallet app itself still follows the strict develop→main
# separation.
# This also means the previous "DEV is green before PRD ships" guarantee is
# gone by design. The safety net for handbook CONTENT is now the PR check
# (handbook-build-check.yaml), which runs on every handbook-relevant PR —
# it gates screenshot-assembly, store-listing sync, legal-sync, and an
# image build + container smoke test, not the deploy WIRING itself (SSH,
# secrets, server rollout), which only the actual deploy run here
# exercises. There is no intermediate staging-only deploy left to catch
# problems before PRD sees them.
#
# All build/deploy/smoke logic lives in the reusable workflow
# .github/workflows/handbook.yaml. This file only wires the per-env
# parameters (source ref, image tag, smoke URL, deploy secrets) and routes
# the pushed branch to its environment via the per-job `if:` guards below.
# .github/workflows/handbook.yaml. This file only wires the PRD parameters
# (source ref, image tag, smoke URL, deploy secrets) and gates the single
# deploy job on the pushed branch via its `if:` guard below.

on:
push:
branches: [staging, develop]
paths:
- "docs/handbook/**"
- "test/goldens/screens/**"
- "scripts/assemble-handbook-screenshots.sh"
- "scripts/assemble-handbook-store-listing.py"
- "scripts/templates/store-listing.html.tmpl"
# Store-listing section is baked into the image from the Fastlane
# metadata; a metadata- or screenshot-only change must redeploy too.
- "ios/fastlane/metadata/**"
- "ios/fastlane/screenshots/**"
- "android/fastlane/metadata/**"
- "Dockerfile.handbook"
- "handbook.nginx.conf"
- "handbook.htpasswd"
- ".github/workflows/handbook-deploy.yaml"
- ".github/workflows/handbook.yaml"
branches: [staging]
workflow_dispatch:

permissions:
contents: read

# One concurrency lane PER BRANCH so a staging (DEV) deploy and a develop
# (PRD) deploy never serialize against — or cancel — each other. Within a
# single lane a manual workflow_dispatch still can't race a push: both would
# build the same env's tag in parallel, the later build would win on Docker
# Hub, and the target server could pull either depending on timing.
# cancel-in-progress is false so a mid-rollout deploy is never killed (we'd
# otherwise risk recreating the container halfway through).
# One concurrency lane (keyed by branch) so a push and a manual
# workflow_dispatch on the same branch never race — or cancel — each other:
# both would otherwise build the same :latest tag in parallel, the later
# build would win on Docker Hub, and the target server could pull either
# depending on timing. cancel-in-progress is false so a mid-rollout deploy is
# never killed (we'd otherwise risk recreating the container halfway
# through).
concurrency:
group: handbook-deploy-${{ github.ref_name }}
cancel-in-progress: false

jobs:
# Push to staging → DEV. Distinct image tag (:beta) from PRD (:latest) so a
# staging build can never clobber the develop build on Docker Hub (both
# used to push :beta, which raced whenever the two branches were pushed
# close together).
deploy-dev:
if: github.ref_name == 'staging'
uses: ./.github/workflows/handbook.yaml
with:
environment: DEV
ref: staging
docker_tag: dfxswiss/realunit-app-handbook:beta
smoke_url: https://dev-handbook.realunit.app/healthz
secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_DEV_SSH_KEY }}
DEPLOY_SSH_KNOWN_HOSTS: ${{ secrets.DEPLOY_DEV_SSH_KNOWN_HOSTS }}
DEPLOY_USER: ${{ secrets.DEPLOY_DEV_USER }}
DEPLOY_HOST: ${{ secrets.DEPLOY_DEV_HOST }}

# Push to develop → PRD.
# Push to staging → PRD.
deploy-prd:
if: github.ref_name == 'develop'
if: github.ref_name == 'staging'
uses: ./.github/workflows/handbook.yaml
with:
environment: PRD
ref: develop
ref: staging
docker_tag: dfxswiss/realunit-app-handbook:latest
smoke_url: https://handbook.realunit.app/healthz
secrets:
Expand Down
Loading
Loading