Skip to content

refactor(settings/contact): consume createSupportTicket capability#594

Merged
TaprootFreak merged 6 commits into
integration/realunit-registrationfrom
refactor/consume-support-capability
May 28, 2026
Merged

refactor(settings/contact): consume createSupportTicket capability#594
TaprootFreak merged 6 commits into
integration/realunit-registrationfrom
refactor/consume-support-capability

Conversation

@TaprootFreak
Copy link
Copy Markdown
Contributor

Companion app PR to DFXswiss/api#3772 (merged 2026-05-26). Closes V9 in `docs/api-authority-audit.md`.

The Support tile now reads the new `user.capabilities.createSupportTicket` field for its tap decision. No more local `mail != null` reconstruction; the rule lives on the backend, the app maps a typed enum value to a UI step.

Architecture

Per the eight consumer rules in `CONTRIBUTING.md` → "Consuming API capabilities — eight rules" (documented in #593):

State Tap action
`capability == null` (legacy backend pre-#3772) Direct push to Support — API is the authority
`capability.available == true` Direct push to Support
`capability.available == false, missingPrerequisite == email` Push email capture page; on `pop(true)` re-init the cubit and push Support if the refreshed capability is now available (or null — symmetric to branch 1)
`missingPrerequisite == unknown` or `null` Defensive direct push — let the API render the error

`MissingPrerequisite` is an open enum with `email` + `unknown`. Additive backend values degrade to `unknown` so a future prerequisite type never breaks `/v2/user` parsing for unrelated callers (KYC, settings, etc.).

What changes

`lib/`

  • `packages/service/dfx/models/user/dto/user_dto.dart` — `UserCapabilitiesDto.createSupportTicket` optional field + `CreateSupportTicketCapabilityDto` + open enum `MissingPrerequisite`.
  • `screens/settings_contact/settings_contact_page.dart` — BlocProvider-wrapped; Support tile `onTap` dispatches through `_onSupportTap` (4 branches above). Tile layout unchanged.
  • `screens/settings_contact/cubit/...` — new `SettingsContactCubit` + state (`part of` pattern, States extend Equatable).
  • `screens/support/cubits/support_email_capture/...` — new cubit + state for the standalone email capture flow.
  • `screens/support/subpages/support_email_capture_page.dart` — standalone page (no KYC coupling) calling `RealUnitRegistrationService.registerEmail`. `mergeRequested` status surfaces a dedicated message — the multi-step verification flow is deliberately not dragged into this minimal page.
  • `setup/routing/routes/support_routes.dart` + `setup/routing/router_config.dart` — new `SupportRoutes.emailCapture` under `/support/email`.
  • `assets/languages/strings_{en,de}.arb` — 4 new keys (alphabetically sorted, both languages).
  • `lib/generated/i18n.dart` — regenerated via `dart run tool/generate_localization.dart`.

`test/`

File Tests
`packages/service/dfx/models/user/dto/user_dto_test.dart` +14 cases for createSupportTicket parsing, incl. `unknown` degradation and JSON-null handling
`screens/settings_contact/cubit/settings_contact_cubit_test.dart` 6 cases
`screens/settings_contact/cubit/settings_contact_state_test.dart` 8 cases
`screens/settings_contact/settings_contact_page_test.dart` 15 widget tests covering tile visibility + 11 routing branches incl. `unknown` and pop(null|false)
`screens/support/cubits/support_email_capture/support_email_capture_cubit_test.dart` 5 cases (success, mergeRequested, ApiException, generic throw)
`screens/support/cubits/support_email_capture/support_email_capture_state_test.dart` 7 Equatable cases
`screens/support/subpages/support_email_capture_page_test.dart` 9 widget tests
`goldens/screens/support/support_email_capture_golden_test.dart` 2 goldens (default + submitting)
`goldens/screens/settings_contact/settings_contact_golden_test.dart` Re-baselined for BlocProvider wrap; visual surface unchanged

Local verification

  • `dart format` clean on all touched files
  • `dart analyze lib/ test/` — no issues found
  • `flutter test` on touched scope → 140/140 green

Review history

Implemented + reviewed via internal subagent loop, three iterations:

  1. First implementation off `develop` → had to rebase onto `chore/post-580-followups` (PR refactor(settings/contact): make Support tile unconditional #588 base mismatch).
  2. Reviewer found 2 SHOULD-FIX (Branch-1 asymmetry with `?? false` violating the no-fallback rule; `MissingPrerequisite.fromString` throw breaking unrelated `/v2/user` callers on additive backend changes). Both addressed: explicit null-check symmetry, open-enum `unknown` degradation.
  3. Final reviewer pass found a `dart format` issue on the enum block — fixed; added an explicit `unknown`-routing widget test as a NICE-TO-HAVE.

Targeting `chore/post-580-followups`

Per request — this is a post-#580 follow-up that consumes a new API capability rather than introducing one in isolation. PR base set accordingly.

Companion app PR to DFXswiss/api#3772 (merged). Closes V9 in
docs/api-authority-audit.md.

Per the eight consumer rules in CONTRIBUTING.md "Consuming API
capabilities — eight rules", the Support tile reads the new
`user.capabilities.createSupportTicket` field for its tap decision.
No more local `mail != null` reconstruction; the rule is on the
backend, the app maps a typed enum value to a UI step.

## What changes

- `UserCapabilitiesDto` gains an optional `createSupportTicket`
  field (`CreateSupportTicketCapabilityDto { available,
  missingPrerequisite? }`). `MissingPrerequisite` is an open enum
  with members `email` + `unknown` — additive backend values
  degrade to `unknown` so a future prerequisite type never breaks
  /v2/user parsing for unrelated callers.
- `SettingsContactPage` keeps the post-#588 tile layout unchanged
  (tiles unconditional). The Support tile's `onTap` now dispatches
  through `SettingsContactCubit`, which hydrates the capability:
    1. `capability == null` (legacy backend pre-#3772) → direct push
    2. `capability.available == true` → direct push
    3. `capability.available == false, missingPrerequisite == email`
       → push email capture; on pop(true), re-init the cubit and push
       Support if the refreshed capability is now available (or
       `null` — symmetric to branch 1).
    4. `missingPrerequisite == unknown` or `null` → defensive direct
       push, let the API render the error.
- New standalone `SupportEmailCapturePage` (not KYC-coupled) calls
  `RealUnitRegistrationService.registerEmail`. `mergeRequested`
  status surfaces a dedicated i18n string telling the user to pick
  a different address — the multi-step verification flow is
  deliberately not dragged into this minimal capture page.
- New route `SupportRoutes.emailCapture` under `/support/email`.
- 4 new i18n keys (EN + DE).

## Notes on the eight consumer rules

- Rule 1 (read shape, don't reconstruct): the cubit reads
  `user.capabilities.createSupportTicket` verbatim; no `mail` reads.
- Rule 2 (unconditional visibility): tile rendered in every state.
- Rule 3 (type → UI dispatch): the switch on `MissingPrerequisite`
  is pure routing, no business rule comments.
- Rule 4 (legacy backend): null capability → direct push, tested.
- Rule 5 (no reactive 400): tap branches pre-emptively on capability;
  no try/catch redirect path.
- Rule 6 (pair-PR): replaces the deleted `emailSet` cubit field
  from PR #588 in the same flow.
- Rule 7 (tests pin contract): cubit + page tests assert on
  capability state shape, not on `mail != null`.
- Rule 8 (no over-engineering): 1 DTO + 1 closed-ish enum, no
  endpoint hierarchy.

## Tests

- `user_dto_test.dart`: 14 new cases for createSupportTicket parsing,
  including `unknown` degradation and JSON-null handling
- `settings_contact_cubit_test.dart`: 6 cases (init paths,
  capability shapes, failure)
- `settings_contact_state_test.dart`: 8 cases (Equatable props)
- `settings_contact_page_test.dart`: 15 widget tests covering tile
  visibility (4 states) + 11 routing branches incl. `unknown` →
  defensive direct push and pop(null|false) variants
- `support_email_capture_cubit_test.dart`: 5 cases (success,
  mergeRequested, ApiException, generic throw)
- `support_email_capture_state_test.dart`: 7 Equatable props cases
- `support_email_capture_page_test.dart`: 9 widget tests (render,
  validation, submit states, snackbar variants)
- 2 new goldens for the email capture page (default + submitting);
  settings_contact_page_default.png re-baselined for BlocProvider
  wrap (visual surface unchanged)

## Local verification

- `dart format` clean on touched files
- `dart analyze lib/ test/` no issues
- `flutter test test/screens/settings_contact/ test/screens/support/
  test/packages/service/dfx/models/user/` → 140/140 green
@TaprootFreak TaprootFreak marked this pull request as ready for review May 26, 2026 23:36
@TaprootFreak TaprootFreak added the tier3:full Opt-in: run Tier 3 Maestro handbook flows on this PR label May 26, 2026
Base automatically changed from chore/post-580-followups to develop May 27, 2026 08:01
@TaprootFreak TaprootFreak changed the base branch from develop to integration/realunit-registration May 28, 2026 08:16
TaprootFreak and others added 5 commits May 28, 2026 10:16
Two new keys (supportEmailCaptureDescription, supportEmailMergeRequiresVerification)
duzed the user ("Deine", "wähle", "kontaktiere") while the rest of
strings_de.arb uniformly siezt ("Verwenden Sie", "Klicken Sie", "Bitte
überweisen Sie"). Align the new strings with the file-wide convention.
Restore Dart 3.10 dot-shorthand for EdgeInsets / CrossAxisAlignment /
FilledButtonState in the two touched files. Matches `home_page.dart`,
`sell_page.dart`, `connect_bitbox_view.dart` etc. — all other widgets
in lib/screens drop the type when it is inferable from the parameter
position. `TextInputType` is intentionally kept long-form to match
the nearby KYC pages (kyc_registration_address_step etc.).
@TaprootFreak TaprootFreak merged commit 45480e0 into integration/realunit-registration May 28, 2026
5 checks passed
@TaprootFreak TaprootFreak deleted the refactor/consume-support-capability branch May 28, 2026 09:57
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.

1 participant