Skip to content

Promote: staging -> develop#656

Merged
TaprootFreak merged 6 commits into
developfrom
staging
Jun 3, 2026
Merged

Promote: staging -> develop#656
TaprootFreak merged 6 commits into
developfrom
staging

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot commented Jun 3, 2026

Automatic Staging PR

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

Commits: 1 new commit(s)

Checklist

  • Review all changes
  • Verify CI passes
  • Approve and merge to promote into develop

…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.
@TaprootFreak TaprootFreak added the tier3:full Opt-in: run Tier 3 Maestro handbook flows on this PR label Jun 3, 2026
TaprootFreak and others added 2 commits June 3, 2026 18:52
…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>
@TaprootFreak TaprootFreak merged commit b76f139 into develop Jun 3, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tier3:full Opt-in: run Tier 3 Maestro handbook flows on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants