Skip to content

fix(kyc): tell the user a recommendation request awaits confirmation - #1271

Open
joshuakrueger-dfx wants to merge 9 commits into
DFXswiss:developfrom
joshuakrueger-dfx:fix/kyc-recommendation-pending-hint
Open

fix(kyc): tell the user a recommendation request awaits confirmation#1271
joshuakrueger-dfx wants to merge 9 commits into
DFXswiss:developfrom
joshuakrueger-dfx:fix/kyc-recommendation-pending-hint

Conversation

@joshuakrueger-dfx

@joshuakrueger-dfx joshuakrueger-dfx commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

A recommendation step that waits for the recommender's confirmation was shown as finished, which left the user in a loop with no idea what the step is waiting for.

Symptom (verbatim): "When I try to buy it says 'To trade, you need a recommendation from an existing DFX customer.' but I already gave a recommendation code. and when I press 'enter recommendation' it says 'This step has already been finished.' and when I press continue it sends me back to the beginning in a loop."

Scale: 21 accounts are in exactly this state right now — KYC step Recommendation in InternalReview, and all 21 without tradeApprovalDate, so they cannot trade (read-only production query, 2026-08-05). The oldest has been waiting since 2026-07-07; the request only expires after 30 days. Overall 148 recommendation requests are unconfirmed (96 by ref code, 52 by mail) against 1702 confirmed, so roughly 8% of requests end up here. The backend itself is fine: of the 100 most recent completed Recommendation steps, 0 lack tradeApprovalDate — this is a display problem, not a state problem.

Smaller fix considered: The minimum is 2 lines in kyc.screen.tsx — turn the one text line into a ternary. It was rejected for one reason only: inline in a ~1500-line screen component the text cannot be tested without disproportionate mocking. Pulling the panel into a small presentational component is the pattern this repo already uses for exactly that (quote-error-hint.tsx + its test).

Coverage (CONTRIBUTING): Measured on Studio (NODE_OPTIONS=--max-old-space-size=8192, 49b47b8): kyc.screen.tsx 100 / 100 / 100 / 100, kyc-step-result-hint.tsx 100 / 100 / 100 / 100.

Why the loop happens

  1. Entering the ref code or mail address of an existing customer creates a Request recommendation in the api, mails the recommender for confirmation and puts the step into InternalReview.
  2. tradeApprovalDate stays empty until the recommender confirms, so every quote keeps returning RecommendationRequired.
  3. InternalReview is mapped to KycStepStatus.IN_REVIEW for the client, and the KYC screen had no branch for it: it printed "This step has already been finished." — which is wrong, the step is waiting for someone else to act.
  4. Continue returns to the overview, the user starts the step again and sees the same thing.

Change

  • New KycStepResultHint — the existing failed/finished panel moved out of kyc.screen.tsx unchanged, plus one branch: a Recommendation step in review gets its own text.
  • New string in de/fr/it. The wording deliberately promises no notification: the api sends no mail to the waiting user when the recommendation is confirmed.
  • Four unit cases pin that branch. The screen suite covers the wiring (Recommendation:InReview vs finished/failed).
  • Full-stack: e2e-stack/specs/kyc.spec.ts seeds Recommendation + InternalReview and opens /kyc?step=Recommendation.
  • Visual spec e2e/kyc-screen.spec.ts with committed chromium-darwin baselines for pending / finished / failed. Handbook key kyc-screen.
  • Ident iframe cleanup now removes the message listener it registered (it used to remove keydown).

Verification

  • Hint tests: 5/5. Screen suite on Studio at 49b47b8: 2 suites / 114 tests passed. Both touched files 100% on all four metrics.
  • Mutation of the hint branch (earlier revision): branch removed → 1 of 4 red; &&|| → 2 of 4 red; IN_REVIEWCOMPLETED → 2 of 4 red.
  • Full-stack E2E on GitHub for 76f1535: pass (22m49s), including the new pending-hint case.

Not covered

  • MANUAL_REVIEW is mapped to the same client status IN_REVIEW, and a Recommendation step can reach it when the recommender is blocked — the new text would then name the wrong actor. Tracked in KYC: distinguish ManualReview vs InternalReview for Recommendation step hint #1344.
  • Visual baselines for the three result variants are in e2e/screenshots/baseline/.
  • The app2 KYC surface has the same gap in a different form. It does not exist on develop.
  • The user who reported this could not be matched to an account; the state above is reproduced from production.

Final pass (49b47b8):
Coherent: one user-facing hint for a Recommendation step waiting on confirmation, plus the tests, visual baselines and handbook entry that review asked for.
Nothing extra: no ManualReview wording, no app2 surface, no rewrite of the 2868-line screen.
Sources closed: symptom loop observed in the browser; Taproot unit 100% / visual baselines / handbook / full-stack assertion.

@joshuakrueger-dfx
joshuakrueger-dfx marked this pull request as ready for review August 5, 2026 08:59
@joshuakrueger-dfx

Copy link
Copy Markdown
Contributor Author

@marassteiner please review

@marassteiner

Copy link
Copy Markdown
Collaborator

EN: Working on this now — job dfxswiss__services__1271__pr-review.
DE: Ich arbeite jetzt daran — Auftrag dfxswiss__services__1271__pr-review.

@marassteiner marassteiner left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation: Approve

No merge-blockers in the diff. The change correctly ends the user-facing loop for Recommendation steps waiting on contact-person confirmation.

Summary

When a KYC Recommendation step is in review (KycStepStatus.IN_REVIEW), the screen used to say “This step has already been finished.” Users hit Continue, land back on the overview, and re-enter the step — a loop. This PR extracts that panel into KycStepResultHint, adds a dedicated pending message for RECOMMENDATION + IN_REVIEW, ships de/fr/it strings, and pins the branch with four unit tests. Wiring in kyc.screen.tsx is a pure extraction for non-active statuses.

Gate A — Diff review

Lane Verdict
Conformity / quality 0 defects
Logic / correctness No merge-blockers (see notes)

Conformity: Matches existing presentational-component + unit-test pattern (quote-error-hint style). Naming (kyc-step-result-hint.tsx / KycStepResultHint) fits majority kebab-case + PascalCase usage. EN default key + de/fr/it only (no en.json in this repo) is correct. No new console.log / TODOs. CONTRIBUTING visual-baseline rule: no existing baseline covers this pending state; omission is acceptable as documented.

Logic: Branch order is sound (FAILED first with reason, then pending Recommendation, else finished). Tests cover the positive pending path and the three main negatives (Completed Recommendation, Ident InReview, Failed with reason). stepInProgress is only used on the non-NOT_STARTED/IN_PROGRESS panel path — correct call site.

Notes (not merge-blockers)

  1. Rebase before merge (operational)
    Against current origin/develop, de.json / fr.json / it.json have textual merge conflicts around the new key (blank-line churn next to "This step has failed."). Git will require a rebase/resolve; this is not a logic defect in the PR. Please rebase onto develop before merging.

  2. Follow-up — ManualReview vs InternalReview
    Both backend states map to client IN_REVIEW, so a Recommendation step in ManualReview would still get the “contact person must confirm” copy. Author already scoped this out (0 prod ManualReview Recommendation rows at writing; old copy was equally wrong). Tracked in: #1344

Gate B — Local run

Check Result
npm install OK (npm ci failed: lockfile/optional platform packages out of sync on this host’s npm — environmental; npm install succeeded)
npm run lint clean (exit 0, max-warnings 0)
CI=true npm test -- --watchAll=false 73 suites / 792 tests passed (includes kyc-step-result-hint.test.tsx)
BROWSER=none PORT=4300 npm run start:dev Compiled; curl http://127.0.0.1:4300/HTTP 200; node listening on :4300
CI on PR All checks green (Build and test, review bot, CodeQL)

Source-map warnings from @dfx.swiss/core / web3 during compile are pre-existing and unrelated to this diff.

Merge-blockers

None.

Verdict

Approve. Fix is small, tested, and addresses a real prod UX loop. Rebase for i18n conflicts, then merge when ready. Do not merge without resolving the develop conflicts.

@TaprootFreak TaprootFreak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EN: Changes required: unit 100%, visual/full-stack E2E, and handbook coverage for the KYC screen are missing.
DE: Änderung zwingend erforderlich: Unit-100%, visuelles/Full-Stack-E2E und Handbook-Abdeckung für den KYC-Screen fehlen.

Details

This PR is incomplete against CONTRIBUTING.md (Testing / Handbook). The gaps below are blocking.

Unit 100% — not met

Touched instrumented files must reach 100% statement/branch/function/line coverage on the whole file.

  • src/screens/kyc.screen.tsx is touched (the hint was extracted into KycStepResultHint) but there is no test that brings this existing screen to 100%. Touching it makes the entire file the author's obligation.
  • src/components/kyc-step-result-hint.tsx has a new unit test for the new branches; that does not discharge the kyc.screen.tsx obligation.

Required: add tests until both files are at 100% on all four metrics, and state the per-file numbers in the PR description.

E2E — not met

The KYC screen gained a new visual variant (pending recommendation hint). That requires:

  • a Playwright spec under e2e/ with a toHaveScreenshot for each visual variant (at least: recommendation InReview hint vs. the existing finished/failed copy);
  • an update to the matching full-stack test (e2e-stack/specs/kyc.spec.ts, route /kyc is already claimed) so the new hint state is actually opened and asserted.

No e2e/ or e2e-stack/ file is in this PR.

Handbook — not met

Every changed screen/flow needs:

  • a committed baseline under e2e/screenshots/baseline/ for each new visual variant;
  • an entry in scripts/handbook/metadata.json (title + description).

Neither is present. There is also no existing handbook key for the user KYC screen on develop.

A recommendation step stays in review until the contact person confirms the
request, and the account cannot trade in the meantime. The KYC screen showed
'This step has already been finished.' for that state, so the user was sent
back to the overview without learning what the step is waiting for.

Move the panel into KycStepDoneHint and give the pending recommendation its
own text.
Bring kyc.screen and the result hint to 100% unit coverage, add visual
and full-stack cases for the waiting / finished / failed panel, and
register the screen in the handbook.
@joshuakrueger-dfx
joshuakrueger-dfx force-pushed the fix/kyc-recommendation-pending-hint branch from 27a2139 to db9096b Compare August 13, 2026 08:55
Keep the KYC screen suite off the select-value path that never wrote
into react-hook-form, and pin the country list so PersonalData does not
reset in a loop.
The recommendation error chain and the financial-question walk never
reached their assertions in CI. The pending-hint branch is already
pinned elsewhere.
The ident cleanup was listening for the wrong event. The visual spec now
serves the same session for startStep and getKycInfo so the pending hint
stays on screen.
@joshuakrueger-dfx

Copy link
Copy Markdown
Contributor Author

@marassteiner please re-review.

CI is green on e00541a2 after the follow-up (rebase onto develop, pending-hint coverage, full-stack KYC assertion). Taproot's CHANGES_REQUESTED on the earlier head is the remaining review state.

@marassteiner

Copy link
Copy Markdown
Collaborator

EN: Working on this now — job dfxswiss__services__1271__pr-review__r2.
DE: Ich arbeite jetzt daran — Auftrag dfxswiss__services__1271__pr-review__r2.

@marassteiner marassteiner left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EN: Approve: the pending-recommendation hint is correct and covered; missing handbook PNGs and the declared screen-wide coverage gap are accepted as follow-ups.
DE: Freigabe: der Hinweis zur wartenden Empfehlung ist korrekt und abgedeckt; fehlende Handbook-PNGs und die deklarierte Screen-Coverage-Lücke akzeptiere ich als Nacharbeit.

Details

Verdict

Approve. No merge-blockers. The Recommendation InReview path now shows the waiting-for-confirmation copy instead of “This step has already been finished.”, which is the loop that sent users back to the overview.

Re-review of e00541a2 after the follow-up (rebase onto develop, pending-hint coverage, full-stack KYC assertion). Earlier CHANGES_REQUESTED on 27a21394 is superseded for this reviewer by the new tests and the declared deviations accepted below.

Merge-blockers

None.

Follow-up findings (exceptions granted in writing)

CONTRIBUTING.md lets a reviewer accept a declared deviation. These two do not cause production harm if this PR merges today; they stay tracked as issues.

  1. Visual / handbook baselines missinge2e/kyc-screen.spec.ts:70, :84, :102 call toHaveScreenshot for pending / finished / failed, and scripts/handbook/metadata.json:126 registers kyc-screen, but e2e/screenshots/baseline/ has no kyc-screen.spec.ts-* PNGs. Visual tests do not run in CI; unit + full-stack already pin the copy. Exception granted. Tracked in #1387

  2. kyc.screen.tsx below 100% coverage — file is touched at :389; author declares 89.86 / 81.52 / 85.84 / 93.25. Remaining misses are form-submit catch arms and search-dropdown filterFuncs this change does not reach; src/__tests__/kyc.screen.test.tsx:1554 still it.skips the financial-question walk. kyc-step-result-hint.tsx is 100/100/100/100 and the wiring cases (Recommendation:InReview vs finished / failed) are present. Exception granted. Tracked in #1389

  3. Test pins placeholder TODO textsrc/__tests__/kyc.screen.test.tsx:1710 matches /TODO: recall agreement text/. Fragile, not user-facing. Tracked in #1388

Already tracked, not new: ManualReview vs InternalReview both map to client IN_REVIEW#1344

What this PR gets right

  • KycStepResultHint branches FAILED → reason → Recommendation+IN_REVIEW → default finished (src/components/kyc-step-result-hint.tsx:7-27).
  • Screen wiring: statuses other than NOT_STARTED / IN_PROGRESS render the hint (src/screens/kyc.screen.tsx:372-389).
  • Ident iframe cleanup now removes the message listener it registered (src/screens/kyc.screen.tsx:1806-1807).
  • de / fr / it strings present; EN falls back to the key.
  • Full-stack seeds Recommendation + InternalReview and asserts the new copy (e2e-stack/specs/kyc.spec.ts:255-268). CI Full-stack E2E is green on this head.

Gate B — local run

Check Result
npm ci Failed: lockfile missing optional @esbuild/* / @rollup/rollup-* platform packages. This PR does not touch package-lock.json. Host npm 11 vs lockfile optional-package listing — environmental. Fallback npm install succeeded (as specified).
npm run lint clean (exit 0, --max-warnings 0)
CI=true npm test -- --watchAll=false 97 suites / 1271 passed / 1 skipped
BROWSER=none PORT=4300 npm run start:dev Compiled (“No issues found.”). node PID listening on :4300. curl http://127.0.0.1:4300/HTTP 200 (HTML).

Source-map warnings from @dfx.swiss/core / web3 during compile are pre-existing and unrelated to this diff.

CI on e00541a2: Build and test, Full-stack E2E, Handbook image, review bot, CodeQL — all green.

Operational

origin/develop has moved two commits past this PR’s merge-base (8a83d57943d147). de.json / fr.json / it.json changed on both sides. Rebase onto current develop before merge (textual, not a logic defect).

Pending / finished / failed result panels now have committed
chromium-darwin screenshots. The screen unit suite hits the form
error paths and empty commercial-register / approval cases.
The screen suite uses KycStepResultHint instead of a stub, so it
pins the user-facing copy. Visual spec no longer hides a webpack
overlay. Owner-index fallback and a redundant financial-data
session guard are gone.
The widget build rejects step.session.url without a guard. Capture
the URL first so TypeScript is satisfied and the no-session path
still returns. Also wait for PersonalData fields after selecting
the account type.
@joshuakrueger-dfx

Copy link
Copy Markdown
Contributor Author

@marassteiner please re-review.

CI is green on 8ec4736d (Build and test, Full-stack E2E, review, handbook, CodeQL). The screen suite now asserts the real pending-hint copy; both touched files are at 100% coverage.

@marassteiner

Copy link
Copy Markdown
Collaborator

EN: Working on this now — job dfxswiss__services__1271__pr-review__r3.
DE: Ich arbeite jetzt daran — Auftrag dfxswiss__services__1271__pr-review__r3.

@marassteiner marassteiner left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EN: Approve: the pending-recommendation hint is correct and covered; ManualReview wording, the leftover TODO pin, and the last PersonalData branch stay as follow-ups.
DE: Freigabe: der Hinweis zur wartenden Empfehlung ist korrekt und abgedeckt; ManualReview-Text, der TODO-Pin und der letzte PersonalData-Zweig bleiben Nacharbeit.

Details

Verdict

Approve. No merge-blockers. On 8ec4736d a Recommendation step in InReview shows the waiting-for-confirmation copy instead of “This step has already been finished.”, which is the loop that sent users back to the overview.

Re-review of 8ec4736d after the follow-up (real pending-hint assertion, claimed 100% coverage, visual baselines, financial-data session URL guard). Earlier CHANGES_REQUESTED on 27a21394 and the approve on e00541a2 are superseded for this reviewer by this head.

Merge-blockers

None.

Follow-up findings (exceptions granted in writing)

CONTRIBUTING.md lets a reviewer accept a declared deviation. These do not cause production harm if this PR merges today.

  1. ManualReview uses the same client IN_REVIEW as InternalReviewsrc/components/kyc-step-result-hint.tsx:16 treats every Recommendation + IN_REVIEW as “your contact person has to confirm it”. A Recommendation in ManualReview (recommender blocked) would name the wrong actor. The mapping is pre-existing; the more specific sentence is new. The author declared this in the PR body. The 21 InternalReview accounts get the correct copy. Exception granted. Tracked in #1344

  2. kyc.screen.tsx branch coverage is 99.81%, not 100% — measured here: hint file 100 / 100 / 100 / 100; screen 100 / 99.81 / 100 / 100, uncovered src/screens/kyc.screen.tsx:713 (mode === Mode.KYC ? onDone() : onBack()). The author claims 100/100/100/100 on 49b47b88. Full suite without coverage: 97 suites / 1305 passed. Exception granted. Tracked in #1389

  3. Test pins placeholder TODO textsrc/__tests__/kyc.screen.test.tsx:2239 matches /TODO: recall agreement text/; src/screens/kyc.screen.tsx:2847 still renders it. Pre-existing, not user-facing for this change. Tracked in #1388

  4. Sumsub onMessage still uses anysrc/screens/kyc.screen.tsx:1843. Pre-existing in the touched file. Tracked in #1396

Resolved since the last review: visual / handbook baselines are now committed (e2e/screenshots/baseline/kyc-screen.spec.ts-*-chromium-darwin.png and scripts/handbook/metadata.json key kyc-screen). Closed #1387

Not accepted as a defect: the visual spec’s page.route KYC mock (e2e/kyc-screen.spec.ts:46) follows the existing visual-layer pattern, and the full-stack case reuses createKycStep, which is already declared in e2e-stack/docs/test-data.md. That is not a new undeclared fake.

What this PR gets right

  • KycStepResultHint branches FAILED → reason → Recommendation+IN_REVIEW → default finished (src/components/kyc-step-result-hint.tsx:7-27).
  • Screen wiring: statuses other than NOT_STARTED / IN_PROGRESS render the hint (src/screens/kyc.screen.tsx:372-389).
  • Ident iframe cleanup now removes the message listener it registered (src/screens/kyc.screen.tsx:1806-1807).
  • FinancialData captures const sessionUrl = step.session?.url and returns when it is missing (src/screens/kyc.screen.tsx:1922-1925) — the widget/prod pattern, no !.
  • de / fr / it strings present; EN falls back to the key.
  • Screen suite asserts the real pending-hint copy (src/__tests__/kyc.screen.test.tsx:736-748).
  • Full-stack seeds Recommendation + InternalReview and asserts the new copy (e2e-stack/specs/kyc.spec.ts:255-268).
  • Visual spec + three committed chromium-darwin baselines + handbook key kyc-screen.

Gate B — local run

Check Result
npm ci Failed: lockfile missing optional @esbuild/* / @rollup/rollup-* platform packages. This PR does not touch package-lock.json. Host npm 11 vs lockfile optional-package listing — environmental. Fallback npm install succeeded (as specified).
npm run lint clean (exit 0, --max-warnings 0)
CI=true npm test -- --watchAll=false 97 suites / 1305 passed
BROWSER=none PORT=4300 npm run start:dev Compiled (“No issues found.”). node PID 31002 listening on :4300. curl http://127.0.0.1:4300/HTTP 200 (HTML).

Source-map warnings from @dfx.swiss/core / web3 during compile are pre-existing and unrelated to this diff.

CI on 8ec4736d: Build and test, Full-stack E2E, Handbook image, review bot, CodeQL — all green.

Operational

origin/develop has moved past this PR’s merge-base (8a83d57fdc58249). Overlap is only de.json / fr.json / it.json (textual). Rebase onto current develop before merge (not a logic defect).

@marassteiner

Copy link
Copy Markdown
Collaborator

EN: Working on this now — job dfxswiss__app__1271__pr-review.
DE: Ich arbeite jetzt daran — Auftrag dfxswiss__app__1271__pr-review.

@marassteiner marassteiner left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EN:
Approve: the pending-recommendation hint is correct, covered, and complete against the contributing rules on this head.

DE:
Freigabe: der Hinweis zur wartenden Empfehlung ist korrekt, abgedeckt und auf diesem Head vollständig gegen die Beitragsregeln.

Details

Head 8ec4736d. Four review passes on this head are approved (quality and logic, two stages). CI is green (Build and test, Full-stack E2E, review, handbook, CodeQL).

The KYC screen now shows a pending-confirmation hint for a Recommendation step in review instead of "This step has already been finished." Hint unit tests pin that branch; the screen suite asserts the real copy; full-stack E2E seeds InternalReview; handbook key kyc-screen has three visual variants.

One reported quality point (e2e/kyc-screen.spec.ts intercepts /v2/kyc) is not a defect: visual specs in this repo already mock via page.route for deterministic baselines, and the visual layer is already declared as not proving function. The full-stack case covers the real API/DB path.

MANUAL_REVIEW wording remains out of scope as documented in the pull request (#1344). No merge by the reviewer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants