Promote: staging -> develop#656
Merged
Merged
Conversation
…padding fix(sell): zero-pad EIP-7702 authorization r/s to 32 bytes
…en (#618) ## Summary `KycViewManager`'s `KycSuccess` step switch had no `KycStep.dfxApproval` case. When `_continueKyc` emits `KycSuccess(currentStep: KycStep.dfxApproval)` (backend `processStatus: InProgress` with a `DfxApproval` current step), it fell through to `(_) => const Scaffold()` — a **blank white screen** with no refresh/back, stranding the user. Fix: - Route `dfxApproval` to the existing `KycPendingPage` (semantically "DFX is reviewing"; it already has a refresh action). - Remove the catch-all `(_)` arm so the inner switch is **exhaustive over `KycStep`** — a future enum value becomes a compile error (forced handling) rather than another silent blank. ## Test Adds the first `KycViewManager` widget test: drives the real `_continueKyc` path to `KycSuccess(dfxApproval)` and asserts `KycPendingPage` renders (not a blank `Scaffold`). ## Verification (local, CI-equivalent, Flutter 3.41.6) `generate_localization` → `generate_release_info` → `build_runner` → `analyze` → `test`: - `flutter analyze` on the changed files: **No issues found**. - Widget test **passes with the fix**; **fails when the routing change is reverted** (renders blank `Scaffold` → `KycPendingPage` not found), confirming it catches the regression. Fixes the K1 item of #613.
## Summary The user-facing **"Delete Wallet"** only cleared `walletAccountInfos` (`WalletStorage.deleteWallet`). The `walletInfos` row — which holds the **AES-GCM-encrypted seed** — and the **mnemonic encryption key** in `flutter_secure_storage` both survived. So after a user deletes their wallet, the full mnemonic remained recoverable on the device (resale / GDPR right-to-erasure risk); only the current-wallet pointer was cleared, masking the residue. Fix (non-breaking — the account-only `deleteWallet` is preserved for the onboarding-regenerate flow, which deliberately relies on the seed row surviving): - `WalletStorage.deleteWalletCompletely(id)` — deletes accounts **and** the `walletInfos` seed row in a transaction (FK-safe). - `SecureStorage.deleteMnemonicKey()` — removes the AES-GCM key. - `WalletRepository.purgeWallet(id)` = `deleteWalletCompletely` + `deleteMnemonicKey`. - `WalletService.deleteCurrentWallet` now calls `purgeWallet`. > Verified the onboarding-regenerate path does **not** call `deleteCurrentWallet`/`deleteWallet` at runtime (it defers the DB write), so it is unaffected. ## Test - `purgeWallet` removes the seed row **and** the mnemonic key. - `deleteWallet` (account-only) still leaves the row + key intact (regenerate contract guard). - The service delete now verifies `purgeWallet` is called and `deleteWallet` is not. ## Verification (local, CI-equivalent, Flutter 3.41.6) - `flutter analyze` on the 6 changed files: **No issues found**. - All tests **pass with the fix**; the service test **fails when `deleteCurrentWallet` is reverted** to `deleteWallet`, confirming the regression is caught. Fixes the S2 item of #612.
…oad (#630) ## What When a legal document's markdown asset fails to load, `LegalDocumentPage` caught the error and set `_markdownContent = ''`, which `build` treats as the *loaded* state → the user got a **blank page with no message and no way to retry**. This adds an explicit error state with a localized message and a **Retry** action. ## Why Part of #629 (audit finding **#19**). The failure was silent: `catch (_) { _markdownContent = ''; }` is indistinguishable from a successfully loaded empty document, so a missing/unreadable legal asset (e.g. a packaging slip-up or I/O error) leaves the user staring at an empty screen. ## Changes - `legal_document_page.dart`: track `_loadFailed`; on load failure render a centered error message + `Retry` button (`_retryLoad` re-runs the load) instead of an empty `Markdown`. The load is also now logged via `developer.log`. - New i18n key `legalDocumentLoadFailed` (EN + DE). - `legal_document_page_test.dart` (new): regression test — asset-load failure must surface an error + retry affordance (was red before the fix), plus a control for the loaded body and a retry-path test. `rootBundle.clear()` per test keeps the asset cache from leaking between cases. - `legal_document_golden_test.dart`: the existing default golden rendered `buildSubject()` with no content, which under the new behaviour would race into the error state. Switched it to render an empty document deterministically (`initialMarkdownContent: ''`) — same committed baseline, no async load. ## Test plan - `flutter analyze` → **0 issues** - `flutter test test/screens/legal/legal_document_page_test.dart` → red on `develop`, **green** with the fix - `flutter test test/goldens/screens/legal/legal_document_golden_test.dart` → **green** (baseline unchanged) - full `flutter test --coverage` run locally (only the known `home_golden` macOS pixel drift remains, which is green on CI) No production behaviour changes for the loaded/loading paths — only the failure path gains an error UI.
…export (#659) Implements [#658](#658). Adds a **"Rechtsdokumente — Downloads"** (nav `L`) section to the handbook that exposes RealUnit's three in-app legal documents as a **derived PDF + DOCX export** of the repo's Markdown — the same upstream/downstream model the store-listing and mail sections already use. `assets/legal/*.md` stays the single source of truth. ### In scope (exactly the 3 repo-local docs) | Document | Source | ARB title key | |---|---|---| | Datenschutzbestimmungen | `assets/legal/privacy_policy_<lang>.md` | `legalDisclaimerCheckboxPrivacyPolicy` | | Nutzungsbedingungen | `assets/legal/terms_of_use_<lang>.md` | `termsOfUse` | | Registrierungsvereinbarung | `assets/legal/registration_agreement_<lang>.md` | `legalDisclaimerCheckboxRegistrationAgreement` | Languages are **discovered by glob** (never hardcoded) → today `de` + `en` = 3 docs × 2 langs = **6 PDF + 6 DOCX**. A future `_fr.md` appears automatically. DFX, Aktionariat and the externally-hosted corporate PDFs are intentionally **out of scope** (no Markdown source in the repo). ### Per (document, language): three access paths Each card carries, consistent with the existing handbook `.test` cards (same `permalink` anchor + `🔗 Link` copy-button pattern): - a **PDF** download and a **DOCX** download, - a **direct link / permalink** to the entry (`id="legal-<base>-<lang>"`, copy-to-clipboard button), and - a `↗` link to the **Markdown source** on `develop`. > Direct-links per the follow-up request: each document now also has a Direktlink/permalink built exactly like the other handbook sections (`a.name.permalink` + generic `.copy-link[data-target]` JS), so the look stays consistent across the handbook. ### The critical determinism split - **HTML block** (download list) — produced by the pure-stdlib `scripts/assemble-handbook-legal.py`, **deterministic → committed → sync-gated** (CI re-runs the generator and fails on a non-empty `git diff` of `index.html`). - **PDF/DOCX binaries** — produced by `scripts/build-legal-downloads.sh` via pandoc (weasyprint PDF engine), **non-deterministic → git-ignored → built only inside the image → never committed, never sync-gated** (same treatment as the assembled screenshots). ### Changes - `scripts/assemble-handbook-legal.py` — deterministic generator (mirrors `assemble-handbook-store-listing.py`). - `scripts/templates/legal-downloads.html.tmpl` — section template. - `scripts/build-legal-downloads.sh` — pandoc PDF/DOCX builder (image-only). - `Dockerfile.handbook` — new `legal-docs-builder` stage chained on `store-listing-builder` (so both rewritten blocks survive); installs `font-dejavu` so weasyprint can render. - `handbook.nginx.conf` — `/legal/*.{pdf,docx}` served with `Content-Disposition: attachment`, behind the existing Basic-Auth gate. - `.github/workflows/handbook-build-check.yaml` — paths, the legal HTML sync gate, and download-presence smoke checks. - `.gitignore` — `docs/handbook/legal/`. - `docs/handbook/de/index.html` — markers, nav entry, CSS, and the committed (generated) block. ### Local verification - `docker build -f Dockerfile.handbook` is **green**; the `legal-docs-builder` stage produces **12 files**. - PDFs are valid (`%PDF-`); DOCX are valid OOXML (zip with `word/document.xml` + `word/styles.xml`) — i.e. real **editable Word documents**, the artifact requested for legal review. - DOCX content matches the Markdown (German text incl. umlauts intact, e.g. "Geltungsbereich", "RealUnit Schweiz AG"). - Generator is **idempotent** (re-run = byte-identical); the legal sync gate is in sync. - Container serves the downloads (200/401 behind auth) with the attachment disposition. Draft until CI is green.
…t instructions) (#661) Implements [#660](#660). **Pure i18n label fix.** The buy-payment button told customers to transfer the money *first* and click *afterwards*, but the click is exactly what **requests the payment instructions** (`PUT /v1/realunit/buy/{id}/confirm` → `requestPaymentInstructions` on the API side, sets `WAITING_FOR_PAYMENT`, returns a reference). The customer should click first, receive the payment slip by email, then pay. Tester feedback: Bojan Jankovic, 28.05.2026 — "Einzig den Text auf dem Button sollte man ändern." ### Change (label only) | Lang | Old | New | |------|-----|-----| | de | `Klicken Sie hier, sobald Sie die Überweisung getätigt haben` | `Zahlungsanweisungen per E-Mail anfordern` | | en | `Click here once you have made the transfer` | `Request payment instructions by email` | Only the `buyPaymentConfirm` value in `assets/languages/strings_de.arb` + `strings_en.arb` (key keeps its alphabetical position). `lib/generated/i18n.dart` is git-ignored and regenerated in CI (`dart run tool/generate_localization.dart`). ### Strictly out of scope (untouched) `confirmPayment` / `BuyConfirmCubit` logic, the `/confirm` endpoint and any API behaviour, `buyPaymentInformationDescription`, `buyExecutedDescription` / `PaymentExecutedSheet`, and the error strings `buyPaymentConfirmFailed` / `buyPaymentConfirmFailedAktionariat`. ### Tests & goldens - No widget test asserts the old literal or the `buyPaymentConfirm` key (verified across all of `test/`) — no test code change needed. The button consumer (`payment_information_details.dart`) uses the generated getter, so the new label takes effect and `onPressed` still calls `confirmPayment`. - The `buy_payment_info_loaded` golden renders this button and changes; **`golden-regenerate.yaml` was triggered on this branch immediately after push** (not waiting for Visual Regression to fail). The other 6 buy goldens don't render this button and are unaffected. 3-subagent review: Quality + Logic both PASS_CLEAN. Draft until CI (incl. regenerated golden) is green. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automatic Staging PR
This PR was automatically created after changes were pushed to staging.
Commits: 1 new commit(s)
Checklist