Skip to content

review: re-land the eight fixes stranded on the closed 08-26 back-merge - #2894

Merged
innolope-dev merged 11 commits into
devfrom
reland/backmerge-0826-review-fixes
Aug 31, 2026
Merged

innolope-dev merged 11 commits into
devfrom
reland/backmerge-0826-review-fixes

Conversation

@innolope-dev

Copy link
Copy Markdown
Collaborator

Re-lands the review fixes that were reviewed on #2830 and lost when that back-merge was closed in favour of the 08-28 one (#2874). The replacement carried main's history but not these commits, so none of them are on dev or main today — verified per commit by grepping origin/dev for its added lines before opening this.

Cherry-picked with -x, so each commit keeps its original author, message and a pointer back to the original SHA.

What's in it

Commit Fix
1a3e020 Keep the ceremony guard on the lazy Sentry loader
80d2801 Guard the step-up WebAuthn ceremony against the shim race
29f0e06 es-AR voseo overrides for the new setup.passkey error keys
815c00c Render translated copy for login PasskeyError codes
fd90272 Drop a stale font mock
a8da766 Fail robots.ts closed with the shared production-domain check
7e33fb7 Edge-trigger the KYC_REJECTED capture, consume the deferral on submitted closes
514f23f Keep cross-region EU uplift multi-level (bridge-uplift)

Where dev had moved on

Five applied clean. The rest needed a decision, and one commit was only half-valid:

  • robots.ts — dev had already fixed the fail-closed bug inline, but left the derivation duplicated in layout.tsx. Kept the commit's isProductionDomain extraction so the two can't drift apart again — that drift is how the bug happened. Took the commit's test file: it is a strict superset of dev's (table-driven over unset/empty/staging/peanut.me.evil.example, plus a trailing-slash case), and dev's lazy require/resetModules dance is unnecessary now that the check reads the env at call time.
  • d953b345 split — that commit dropped getUnderlyingFetch as unimported and removed a stale font mock. The first half is no longer true: feat(native): restore the transport canary, failure-only, iOS included #2871 restored the transport canary and native-canary.ts imports it again (caught by typecheck). Only the font-mock half is re-landed, as fd90272; the export stays.
  • SumsubKycWrapper.test.tsx — purely additive. dev added multi-level resubmission tests, the commit added a post-submit-close test; both kept.
  • sumsub.types.ts — dev's KycActionType already had bridge-uplift and has since gained rails-unavailable. Kept dev's union; the commit's useSumsubKycFlow hunks applied clean.

Two mocks in the new useMultiPhaseKycFlow suite also needed updating for dev's current surface — @/utils/capacitor now needs isAndroidNative (the mascot module calls it), and the @/app/actions/sumsub mock has to spread requireActual so the hook's isTerminalActionCode branch gets the real helper instead of undefined. That is the same partial-mock pattern the sibling useSumsubKycFlow suite documents; without it every initiate silently bailed. Folded into 7e33fb7 rather than left as a follow-up.

Verification

pnpm typecheck clean; prettier --check clean on all touched files; the 8 touched suites pass (121 tests), plus the i18n parity suites (messages, locale-bridge, proxy-locale, friendly-error) for the es-AR change — 109 tests.

startAuthentication ran bare, so a step-up racing the native passkey shim
install could silently never settle (same class as login, TASK-21782). Route
it through guardPasskeyCeremony and map guard failures to StepUpError with
the curated classifyPasskeyError copy so callers render sane text.

(cherry picked from commit b74a5e4)
tookTooLong, notReady, deviceState and interrupted landed in es-419 without
es-AR counterparts, so Argentine users got tuteo forms for these errors.

(cherry picked from commit 9172826)
Login failures surfaced the hardcoded English PASSKEY_LOGIN_MESSAGES copy at
SetupPasskey and InvitesPage even though the same strings exist translated in
the setup.* catalog. The thrown PasskeyError already carries its code, so map
known codes to their catalog keys at the render sites and keep the English
message as the fallback for codes without a translated equivalent.

(cherry picked from commit 5c54f47)
The metadata-policy suite still stubbed Londrina_Solid, which layout no
longer imports.

Partial re-land of d953b34 (closed #2830): that commit also dropped
getUnderlyingFetch as unimported, but #2871 restored the transport canary
and native-canary.ts imports it again, so the export stays.
robots.ts derived IS_PRODUCTION_DOMAIN from BASE_URL, whose production
fallback opened indexing on any environment with the env unset. Extract the
raw-env fail-closed derivation layout.tsx already used into
seo-route-policy.js (isProductionDomain) and use it in both.

(cherry picked from commit 39f3336)
…l on submitted closes

The capture+fetchUser effect in useMultiPhaseKycFlow re-fired on every dep
change while the status sat on a stale REJECTED/ACTION_REQUIRED, duplicating
the posthog event and the user refetch on resubmit rounds. It now tracks the
previous status like the sibling transition effect in useSumsubKycFlow.

The multi-level ACTION_REQUIRED deferral also replayed on ANY close. A close
after the user submitted (level-2 done, SDK on "documents submitted") now
carries submitted: true from the wrapper and consumes the deferred transition
in both hooks, while an abandoned close still replays it once.

(cherry picked from commit 2c19373)
@vercel

vercel Bot commented Aug 31, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview Aug 31, 2026 8:56pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Essentials

Run ID: 2af279ad-4126-412b-9af9-98999ef3e32b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 7144.46 → 7147.28 (+2.82)
Findings: 0 net (+34 new, -34 resolved)

🆕 New findings (34)

  • critical complexity — src/hooks/useSumsubKycFlow.ts — CC 100, MI 51.2, SLOC 439
  • critical complexity — src/components/Invites/InvitesPage.tsx — CC 88, MI 54.93, SLOC 217
  • critical complexity — src/hooks/useMultiPhaseKycFlow.ts — CC 79, MI 56.12, SLOC 383
  • high hotspot — src/hooks/useSumsubKycFlow.ts — 53 commits, +766/-305 lines since 6 months ago
  • high complexity — src/utils/webauthn.utils.ts — CC 40, MI 54.03, SLOC 146
  • high hotspot — src/components/Invites/InvitesPage.tsx — 37 commits, +575/-364 lines since 6 months ago
  • high complexity — src/components/Setup/Views/SetupPasskey.tsx — CC 36, MI 55.38, SLOC 178
  • high complexity — src/components/Kyc/SumsubNativeSdk.tsx — CC 32, MI 59.32, SLOC 122
  • high complexity — src/app/layout.tsx — CC 11, MI 40.79, SLOC 105
  • medium high-mdd — src/hooks/useSumsubKycFlow.ts:94 — useSumsubKycFlow: MDD 245.7 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/useMultiPhaseKycFlow.ts:120 — useMultiPhaseKycFlow: MDD 143.7 (uses across many lines from declarations)
  • medium high-mdd — src/components/Invites/InvitesPage.tsx:40 — InvitePageContent: MDD 110.1 (uses across many lines from declarations)
  • medium high-mdd — src/components/Setup/Views/SetupPasskey.tsx:39 — SetupPasskey: MDD 69.7 (uses across many lines from declarations)
  • medium high-mdd — src/app/robots.ts:16 — robots: MDD 52.0 (uses across many lines from declarations)
  • medium high-dlt — src/components/Invites/InvitesPage.tsx:40 — InvitePageContent: DLT 50 (calls 50 distinct functions — high context load)
  • medium high-mdd — src/components/Kyc/SumsubNativeSdk.tsx:31 — SumsubNativeSdk: MDD 39.8 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/useSumsubKycFlow.ts:295 — : MDD 40.0 (uses across many lines from declarations)
  • medium high-dlt — src/components/Setup/Views/SetupPasskey.tsx:39 — SetupPasskey: DLT 33 (calls 33 distinct functions — high context load)
  • medium high-mdd — src/components/Kyc/SumsubNativeSdk.tsx:73 — : MDD 29.1 (uses across many lines from declarations)
  • medium method-complexity — src/hooks/useSumsubKycFlow.ts:295 — CC 25 SLOC 94

…and 14 more.

✅ Resolved (34)

  • src/hooks/useSumsubKycFlow.ts — CC 98, MI 51.24, SLOC 438
  • src/components/Invites/InvitesPage.tsx — CC 87, MI 55.1, SLOC 214
  • src/hooks/useMultiPhaseKycFlow.ts — CC 78, MI 56.31, SLOC 377
  • src/hooks/useSumsubKycFlow.ts — 50 commits, +726/-281 lines since 6 months ago
  • src/app/(mobile-ui)/layout.tsx — 36 commits, +243/-212 lines since 6 months ago
  • src/components/Setup/Views/SetupPasskey.tsx — CC 35, MI 55.61, SLOC 175
  • src/utils/webauthn.utils.ts — CC 32, MI 53.04, SLOC 131
  • src/components/Kyc/SumsubNativeSdk.tsx — CC 30, MI 60.38, SLOC 113
  • src/app/layout.tsx — CC 11, MI 40.76, SLOC 105
  • src/hooks/useSumsubKycFlow.ts:94 — useSumsubKycFlow: MDD 242.3 (uses across many lines from declarations)
  • src/hooks/useMultiPhaseKycFlow.ts:120 — useMultiPhaseKycFlow: MDD 140.0 (uses across many lines from declarations)
  • src/components/Invites/InvitesPage.tsx:39 — InvitePageContent: MDD 109.8 (uses across many lines from declarations)
  • src/components/Setup/Views/SetupPasskey.tsx:39 — SetupPasskey: MDD 69.6 (uses across many lines from declarations)
  • src/app/robots.ts:17 — robots: MDD 52.0 (uses across many lines from declarations)
  • src/components/Invites/InvitesPage.tsx:39 — InvitePageContent: DLT 49 (calls 49 distinct functions — high context load)
  • src/hooks/useSumsubKycFlow.ts:295 — : MDD 43.1 (uses across many lines from declarations)
  • src/components/Kyc/SumsubNativeSdk.tsx:31 — SumsubNativeSdk: MDD 34.6 (uses across many lines from declarations)
  • src/components/Setup/Views/SetupPasskey.tsx:39 — SetupPasskey: DLT 32 (calls 32 distinct functions — high context load)
  • src/components/Kyc/SumsubNativeSdk.tsx:67 — : MDD 26.1 (uses across many lines from declarations)
  • src/hooks/useSumsubKycFlow.ts:295 — CC 23 SLOC 93

…and 14 more.

@github-actions

github-actions Bot commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • ✅ unit: 4693 ran, 0 failed, 0 skipped, 1.7m

📊 Coverage (unit)

metric %
statements 73.4%
branches 58.7%
functions 64.5%
lines 74.4%
⏱ 10 slowest test cases
time test
4.2s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
2.1s src/components/Profile/views/__tests__/ResidenceChangeModal.test.tsx › prefers the server value over a stale device mirror
1.9s src/components/Profile/views/__tests__/ResidenceChangeModal.test.tsx › the change cooldown shows its date and blocks changing to another country, not re-saving
1.6s src/components/Profile/views/__tests__/ResidenceChangeModal.test.tsx › saves the declared residence, refetches, and closes
1.4s src/components/Profile/views/__tests__/ResidenceChangeModal.test.tsx › swaps from the server value with no device mirror at all
1.3s src/components/Profile/views/__tests__/ResidenceChangeModal.test.tsx › moving to a country in neither slot leaves the second document alone
1.1s src/components/Setup/Views/__tests__/Residence.test.tsx › reveals the second selector via the multi-doc link
1.0s src/hooks/query/__tests__/user.test.tsx › does NOT clear a token that rotated mid-request (stale 401 racing a fresh login)
1.0s src/components/Setup/Views/__tests__/Residence.test.tsx › shows the per-country availability comparison with the truth-first guidance
1.0s src/components/Setup/Views/__tests__/Residence.test.tsx › lists sanctioned countries in the selector so residents can answer truthfully
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@chip-peanut-bot chip-peanut-bot Bot 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.

Chip review — changes requested

Request changes: abandoning an unfinished follow-up level is misclassified as a completed submission, suppressing the real ACTION_REQUIRED state. The other reviewed fixes and CI are clean.

Findings

  • MAJOR · src/components/Kyc/SumsubKycWrapper.tsx:368 · Do not consume ACTION_REQUIRED after only level 1 was submitted
    In a multi-level session, the first level's submit sets hasSubmittedRef before Sumsub opens level 2. If ACTION_REQUIRED then arrives for that follow-up and the user closes without submitting it, this line sends {submitted: true}; both KYC hooks consume the deferred status, so the real action-required transition and user-store refresh are suppressed, and an open progress modal can remain stuck. Track submission for the currently required level (or forward and deduplicate every logical submit event, consuming ACTION_REQUIRED only when that event fires while the follow-up is required) so closing an unfinished follow-up remains an abandonment.

Checked clean

  • Exact detached head, supplied base SHA, merge base, trusted author, PR number, and dev base ref all matched.
  • CI at the exact head completed successfully, including unit, typecheck, eslint, format, analyze, and ds-shots.
  • SEO production-domain gating was checked for unset, empty, preview, spoofed-host, and trailing-slash inputs; the shared gate remains fail-closed.
  • Passkey lazy Sentry loading, translated PasskeyError mapping, and the step-up ceremony shim/timeout guard were checked without another actionable defect.
  • The backend bridge-uplift response and Bridge follow-up workflow were cross-checked; treating EU bridge-uplift as multi-level matches the API contract.
  • Security pass found no new secret exposure, injection path, privilege expansion, or unsafe domain trust.
  • No issue comments or review comments were read.

Second opinion skipped: daily-budget-reached.

Exact head: 514f23ff0945 · Context: repo, mono, github

Comment thread src/components/Kyc/SumsubKycWrapper.tsx Outdated
Chip flagged the `submitted: true` half of the previous commit: in a
multi-level session the first level's submit sets hasSubmittedRef before
Sumsub opens level 2, so a close that abandons an unfinished follow-up was
reported as a completed submission. Both hooks then consumed the deferred
ACTION_REQUIRED, suppressing the real transition and the user-store refresh
and leaving an open progress modal stuck.

The wrapper has no signal that separates the two cases: it subscribes to no
step/level event, and a second onApplicantSubmitted is deduped as the
idCheck twin rather than read as a new level. Rather than guess at the SDK's
per-level semantics, drop the manual-close consumption entirely and restore
the always-replay behaviour dev has today. handleSdkComplete keeps its
consumption — an SDK-signalled completion is unambiguous, and it is the path
the in-session resubmit actually takes.

The edge-triggered capture (prevCapturedStatusRef), which is the other half
of the previous commit and the one that stops duplicate KYC_REJECTED events
on resubmit rounds, is unaffected.

Telling an abandoned follow-up from a finished one needs a real level signal
from the SDK; that is left for a follow-up rather than inferred here.

@chip-peanut-bot chip-peanut-bot Bot 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.

Chip review — changes requested

The web manual-close regression is fixed, but the same deferred ACTION_REQUIRED is still consumed when a native multi-level session is manually closed after Level 1.

Findings

  • MAJOR · src/hooks/useMultiPhaseKycFlow.ts:325 · Do not consume native Level-1 exits
    On Capacitor, SumsubNativeSdk records Pending as submitted and then calls onComplete even when launch() later resolves Initial because the user manually closed; the existing native test pins exactly that Pending-to-Initial behavior. In an EU/LATAM multi-level session, Level 1 can reach Pending, the follow-up becomes ACTION_REQUIRED, and exiting before Level 2 finishes invokes this callback. This line consumes the deferred status, and originalHandleSdkComplete consumes it in the base hook too, so the rejection/store refresh never runs and the progress modal stays stale. The web onClose() fix does not cover this driver. Route ambiguous multi-level native exits through onClose, or consume only on a terminal/final-level signal, and add the corresponding native regression test.

Checked clean

  • Earlier finding: fixed for the cited web manual-close path, but still present through the native driver and re-reported at the current changed hook line.
  • Exact head and merge base matched the supplied SHAs; trusted PR metadata matched repository, author, head, and dev base.
  • Bridge-uplift multi-level routing was checked against the current backend initiate and follow-up status contracts.
  • Passkey ceremony guarding, localized PasskeyError rendering, and lazy Sentry loading were reviewed without another actionable defect.
  • Shared production-domain robots and root-metadata policy fail closed for unset, preview, and lookalike origins.
  • All exact-head CI checks completed successfully, including unit, typecheck, format, eslint, design-system, analysis, and preview checks.
  • Correctness, security, adversarial failure paths, and slop were reviewed across all 19 changed files.

Second opinion skipped: daily-budget-reached.

Exact head: 4e9d14e35e99 · Context: repo, product

Comment thread src/hooks/useMultiPhaseKycFlow.ts Outdated
Chip found the same defect class through the second driver. On Capacitor,
SumsubNativeSdk counts Pending as submitted and resolves launch() on close
whatever the user did, so in a multi-level session a Level-1 submit followed by
backing out of Level 2 fires onComplete exactly like a real completion — the
existing native test pins that Pending-then-Initial behaviour. The previous
commit's web-side onClose fix does not reach that path.

The consumption this drops is the one the re-landed commit ADDED: mirroring the
base hook's consume into this hook's capture effect. Without it the capture and
the user-store refresh run on that exit, which is what dev does today; with it
they were suppressed and the applicant sat on a stale progress modal.

The base hook's own consume in originalHandleSdkComplete is pre-existing on dev
and carries the same ambiguity — it suppresses the transition effect on a native
Level-1 exit. Left alone here rather than widened into a behaviour change this
PR cannot claim was reviewed; it wants a terminal/final-level signal from the
SDK, which is the same follow-up the web side needs.

The edge-triggered capture, the actual point of the original commit, is intact.

@chip-peanut-bot chip-peanut-bot Bot 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.

Chip review — changes requested

One repeated major KYC defect remains: the outer native-exit capture was fixed, but the inner status transition still consumes the same ambiguous Level-1 exit and can leave the progress modal stale.

Findings

  • MAJOR · src/hooks/useMultiPhaseKycFlow.ts:331 · Do not consume native Level-1 exits in the inner handler
    On native, SumsubNativeSdk records the Level-1 Pending event as submitted and calls this completion path even when launch closes as Initial after the user backs out of Level 2. This still invokes originalHandleSdkComplete(), whose base handler advances prevStatusRef to ACTION_REQUIRED and opens the progress modal. The new outer test passes because capture/fetchUser now run, but the inner transition that would close that modal is still consumed, so the applicant can remain on stale verifying UI. Propagate a final-level/ambiguous-native-exit signal and skip the base consumption for that case; add an assertion that the progress modal closes.

Checked clean

  • Earlier useMultiPhaseKycFlow finding: STILL PRESENT in the inner transition path; the outer capture consumption was removed, but originalHandleSdkComplete still consumes ACTION_REQUIRED.
  • Earlier SumsubKycWrapper finding: FIXED; post-submit manual closes no longer claim submission and both hooks replay the deferred ACTION_REQUIRED.
  • Exact-head CI: unit, typecheck, eslint, and format succeeded; ds-shots was still running when checked.
  • Passkey ceremony guard, translated PasskeyError rendering, SEO production-domain gate, and associated tests were inspected without another actionable defect.
  • A local targeted Jest run was unavailable because the detached worktree has no node_modules; exact-head unit CI succeeded instead.

Second opinion skipped: daily-budget-reached.

Exact head: 07844fc9b6a7 · Context: repo

Comment thread src/hooks/useMultiPhaseKycFlow.ts
Closes the native half of the deferral defect at its source rather than at the
consumer. SumsubNativeSdk trusted `hasSubmitted` alone, but in a multi-level
session that flag only proves SOME level was submitted: Pending fires after
Level 1 with the questionnaire still outstanding, so backing out of Level 2
resolved launch() as Initial and still reported a completion. Both flow hooks
then consumed the deferred ACTION_REQUIRED and left the applicant on a stale
verifying modal.

The plugin dismisses after the LAST level, so for multi-level a close whose own
status is not itself a submitted one is the user leaving mid-workflow. Single
level is unchanged and still trusts `hasSubmitted` — one submit IS the flow
there, and the closing status genuinely does read Initial after a real
submission, which the existing test pins.

The level the user did finish still reaches the funnel: an abandoned exit fires
onSubmitted before onClose, so dropping the bogus onComplete does not cost the
KYC_SUBMITTED signal. SumsubSdkProps carried isMultiLevel and onSubmitted
already and the wrapper spreads both, so no call site changes.

Why here and not in the hook: this is the driver that cannot tell the two exits
apart, and fixing it in the hook would have meant weakening a consume that is
correct for every unambiguous completion. It also settles the pre-existing base
hook consume for this path — the reason that one was left alone last round is
that only the native driver could distinguish the case, and now it does.

@chip-peanut-bot chip-peanut-bot Bot 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.

Chip review — no blocking findings — this is not an approval

Clean: the native multi-level Level-1 exit now routes through onClose, fixing both repeated findings; no other actionable defect remains.

Checked clean

  • Review of 07844fc: FIXED. A multi-level Pending event followed by an Initial launch result now emits onSubmitted and onClose, bypassing the inner completion handler that consumes deferred ACTION_REQUIRED.
  • Review of 4e9d14e: FIXED. The same native Level-1 abandonment path is now classified as a manual close, while final submitted statuses still complete.
  • KYC status deferral, rejection capture edge triggering, manual-close replay, native result routing, and EU bridge-uplift multi-level behavior were checked against the UI and backend contracts.
  • Passkey ceremony guarding, lazy Sentry loading, translated PasskeyError rendering, and setup locale parity were inspected without an actionable defect.
  • SEO production-domain sharing between root metadata and robots.ts fails closed for unset, preview, and lookalike origins.
  • Exact-head CI completed successfully: unit, typecheck, eslint, format, design-system checks, analysis, preview deployment, and aggregate ci-success.
  • A local targeted Jest run was unavailable because the detached worktree has no node_modules; exact-head unit CI succeeded instead.

Second opinion skipped: daily-budget-reached.

Exact head: 105963f03342 · Context: repo, peanut-api-ts, provider-docs

@innolope-dev
innolope-dev merged commit 55b210a into dev Aug 31, 2026
20 checks passed

This branch was successfully deployed

1 active deployment
Preview — 105963f0 Deployed Aug 31, 2026 by vercel[bot]
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.

1 participant