Repository navigation
chore: backmerge main into dev (2026-08-26) - #2830
innolope-dev wants to merge 48 commits into
Conversation
Sumsub's `bridge-requirements` workflow now branches every EEA applicant to the `bridge-eea-uplift` questionnaire as soon as the documents go GREEN. The frontend closed the SDK on the first submit for every non-LATAM flow, so the questionnaire never showed. About 3 minutes later the backend poller read the follow-up level's `init` state, and the KYC drawer fell back to "Start verification". The user could not reach the questionnaire at all. `isMultiLevel` is the flag that holds the SDK open past the first submit. It was derived from the `regionIntent` hook prop. Almost every entry point withholds that prop on purpose: the bank, claim and add-money flows pass the intent to handleInitiateKyc at call time, so that mounting a page does not create a backend record. The flag was false for all of them. It now comes from the intent the SDK was actually opened with, and it covers the Bridge intents (EU, NA) as well as LATAM. TASK-21510
CodeRabbit flagged the render-time write to isMultiLevelRef: React can discard an interrupted render, but a ref write survives it, so the guard could act on a value that never committed. Drop the ref and read showWrapper / isMultiLevel as state, with both in the effect's dependency array. A re-run with an unchanged status is a no-op, because prevStatusRef already holds that status.
…tion Review follow-ups on the EEA uplift fix. NA shares the `bridge-requirements` workflow with EU, but only EEA applicants branch to the uplift questionnaire. Holding the SDK open for NA would park every US applicant on Sumsub's "documents submitted" screen until approval, and suppress the submit handler that fires KYC_SUBMITTED. NA is now single-level. The held ACTION_REQUIRED transition was consumed, not deferred: prevStatusRef advanced before the guard returned, so the transition could never fire again. A user who abandoned mid-questionnaire was left on a stale "verifying" modal with nothing to close it. The guard now returns before the ref advances, so closing the SDK re-runs the effect and evaluates the same transition for real. isMultiLevel is now cleared in the two close handlers instead of being reset at each single-level open, so a closed SDK reads false by construction. The drawer CTA said "Re-submit verification" under copy that asks the user to continue. The generic branch now says "Continue", matching the sibling modal. The reject-labels branch keeps the re-submit label.
CodeRabbit (Major): the APPROVED branch hides the SDK without clearing the flag, so a later self-heal, restart-identity or start-action open inherited a stale true. SumsubKycWrapper then suppressed that session's completion close and stranded the user in the SDK. Approval is the third and last path that closes the SDK. Clearing here is what makes "closed implies single-level" hold everywhere, so the per-open resets stay deleted rather than being reinstated as belt and braces. Also drops the stale NA references from two comments: NA shares the bridge-requirements workflow but never branches to the EEA uplift.
…restart flag Review round (jjramirezn): - The store-refresh effect in useMultiPhaseKycFlow fired KYC_REJECTED and fetchUser on every happy-path ACTION_REQUIRED once api#1370 makes it a routine mid-questionnaire status. Same defer guard as the transition effect: hold while a multi-level SDK session is open, act on close. - A deferred ACTION_REQUIRED replayed on EVERY close, so an in-session resubmit (handleSdkComplete) closed the progress modal it had just opened. A submission close now consumes the held transition; the manual close keeps the replay — abandoning really does leave the action required, and the SDK can sit on top of an open progress modal. - Multi-level sessions emitted no KYC_SUBMITTED at all (submit handlers early-return, the APPROVED close skips onComplete). New optional onSubmitted prop reports the Level-1 submit; useMultiPhaseKycFlow captures the event with the same region_intent resolution. - handleRestartIdentity reopened the same workflow with isMultiLevel false, closing a restarted LATAM session before the questionnaire. It now re-derives the flag from regionIntentRef. - The NA exclusion comments claimed NA has no second level; the backend registry documents organic source-of-funds / proof-of-address branches. Comments corrected — NA stays excluded because the branches are rare and the hold would apply to every US applicant.
The deposit screen told every user a ~0.1% bridging fee applies. No such fee exists: deposits ledger 1:1 and the Rhino fee is booked on withdraw only. The copy drifted in during the i18n string extraction (0cffa3e) and contradicted both the same screen's tooltips (variance, not fee) and the fees help page (deposits free). Reworded as bridging variance and made the EVM variant say USDC on Arbitrum arrives exactly, since no bridging happens there at all. Reported by a user via support.
zerodev's toWebAuthnKey has no timeout; on native Android a tap that races the async autoShimWebAuthn install (or a Credential Manager callback that never fires) pends forever — isLoggingIn stays true and the Log In button is dead until the app is killed (TASK-21782, Konrad's 2026-08-24 recording). - race the ceremony against a 60s timeout; late results are discarded - gate native ceremonies on the capgo shim actually being installed - Sentry tags login_ceremony_timeout / login_shim_not_ready (+shimInstalled, elapsedMs) — the discriminating telemetry the RCA lacked - disable Sign Up while a login ceremony is pending (waitlist-step flash) - stop labeling the native app isPwa:true in the login capture
…honest failure copy /code-review high findings applied: - guardPasskeyCeremony() is now the one wrapper both login and register route through (register had the identical hang with zero telemetry — the tightest shim race is a fresh install's first Sign Up tap) - timeout race is native-only: web hybrid/QR and security-key ceremonies legitimately run minutes; the silent hang is a Capacitor defect - verify-token capture now requires an active ceremony window, so a verify response landing after the timeout cannot leave a half-authenticated session - shim install failures (incl. the previously-uncaught dynamic import) mark a known-dead flag: login fails immediately with restart copy instead of polling 3s toward advice that can never come true - PasskeyErrorCode is a closed union of the copy map keys — a future code can't silently fall into the clearAuthState branch - isPwa telemetry uses isNativeBridge() (capacitor-flavored web builds keep real PWA semantics); comment now states usePWAStatus's deliberate behavior - shim-flag reads unified on isPasskeyShimInstalled(); waitForPasskeyShim tests moved to fake timers
A bare any-ceremony-active check re-opened the capture window: ceremony A times out, the user retries (B), and A's late verify token would be stored while B runs. Each verify request now records the ceremony id it was issued under, and its token persists only if that SAME ceremony is still active at response time. Test covers exactly the A-times-out/B-active/A-lands-late path.
fix(i18n): deposit note claimed a 0.1% bridging fee that does not exist
…open fix(kyc): keep the Sumsub SDK open through the EEA uplift questionnaire
…ces-v2 fix(seo): noindex app surfaces at response layer
Manteca outage 2026-08-24. disabledPaymentProviders: ['MANTECA'] blocks QR payments (MP AR + PIX BR); disabledMantecaCurrencies: ['ARS','BRL'] blocks add-money and withdraw for both geos. Revert by emptying both arrays.
hotfix: disable all Manteca surfaces (maintenance kill-switch)
Reverts the kill-switch flip from #2807: QR payments (MP AR + PIX BR) and ARS/BRL add-money + withdraw are live again.
…remaining WebAuthn paths - guardPasskeyCeremony now rejects a second concurrent ceremony (CeremonyConflictError -> PASSKEY_INTERRUPTED) instead of evicting the first window, and the fetch wrapper only STASHES a verify token: it is persisted when the owning ceremony resolves and discarded on failure. Token persisted <=> a ceremony the user saw succeed - closes both the stale-token-under-retry and evicted-window half-auth holes. - native sign callback and app-lock presence check get the same shim gate + ceremony timeout (same silent-hang class as login); app-lock maps PasskeyShimFailedError to 'unsupported' so a lock that can never be lifted does not engage. - SetupPasskey surfaces guard errors with curated copy (no generic UnknownError modal, no double Sentry capture); a timed-out register re-checks the username and routes to login instead of re-registering. - the 'pending' bail in the register catch releases isRegistering. - web ceremonies get a generous 5-min bound so an abandoned hybrid/QR login cannot dead-end the landing page now that Sign Up is disabled while isLoggingIn. - shim install retries once before markPasskeyShimFailed; guard-error captures unified in captureCeremonyGuardError with per-class tags (shim_failed no longer reported as shim_not_ready). - login analytics use real display-mode detection instead of usePWAStatus's Capacitor short-circuit.
…l resolved-but-unpatched shim install - stashCeremonyVerifyToken takes the issue-time ceremony id and accepts only while that ceremony is still active: a request issued outside the current window (a timed-out ceremony's late verify) cannot enter the retry's stash. A late request issued DURING the retry's window remains unattributable at the fetch layer; commit-on-resolve still bounds it to ceremonies the user saw succeed. - installPasskeyShim throws when autoShimWebAuthn resolves without patching navigator.credentials, routing the resolved-but-unpatched state through the retry/markPasskeyShimFailed path. - isStandaloneDisplayMode reads mode=pwa via URLSearchParams so the parameter is detected regardless of query order; regression test.
… hatch - SetupPasskey guard-error branches use setup.passkey.* i18n keys (tookTooLong / notReady / deviceState / interrupted) added to en, es-419 and pt-BR (es-AR inherits via the delta overlay) instead of the hardcoded-English classifyPasskeyError map. - Landing disables Sign Up during a pending login on native only: the waitlist-step flash was native, while on web an abandoned hybrid/QR ceremony can pend minutes and Sign Up must stay the way out; a mid-ceremony register now fails cleanly as CeremonyConflictError.
…emony-hang fix(login): unwedge a never-settling native passkey ceremony
hotfix: re-enable Manteca (revert 2026-08-24 kill-switch)
… OTA shipped env-less The OTA lane ran native-build.js without writing .env.production.local, so every NEXT_PUBLIC_* was inlined as undefined: recognizeQr threw on BASE_URL for every scan, and Sentry/PostHog never initialised, which is why nothing reported it. The store lanes wrote the file all along. Four months, two applied bundles. native-build.js now fails the build in CI when the file is missing or a required key has no value, so a lane that forgets it cannot ship a dark bundle again. Ref: mono ops/native-ota-envless-bundle-rca.md
…n env-less bundle DirectSendQR/utils re-declared BASE_URL from process.env with a non-null assertion, and read it on the first line of recognizeQr. @/constants/general.consts already exports the same value with a peanut.me fallback; nothing imported the local copy. The test reproduces the production TypeError against the old code.
…log lines; require NEXT_PUBLIC_ONESIGNAL_APP_ID The hand-rolled RP_ID / OneSignal regexes were unanchored (a commented-out key read as set) and duplicated missingNativeEnv(); OneSignal was only a warning although the native adapter throws at init without it.
…g it, not by assigning undefined
…into-dev-20260826 # Conflicts: # src/config/peanut.config.tsx
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-authored-by: Aleks Balinda <aleks@peanut.me> Co-authored-by: chip-bot[bot] <2915219+chip-bot[bot]@users.noreply.github.com>
Code-analysis diffPainscore total: 7451.59 → 7495.57 (+43.98) 🆕 New findings (183)
…and 163 more. ✅ Resolved (166)
…and 146 more. 📈 Painscore deltas (top movers)
|
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
fix(ci): bake NEXT_PUBLIC_* into Capgo OTA bundles (native QR scan/paste dead on ota-1.0.54/1.0.55)
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
- require published prose for ownership so resolved links never target routes that notFound() on published: false - decode percent-encoded path segments before content lookups - localize en-authored absolute peanut.me URLs with no content owner, matching the relative-href behavior; non-en absolute URLs stay pinned - derive legal slugs from the content mirror instead of a hand-kept set - resolve landing content hrefs inside the server components via a shared memoized contentHrefsFor, and require the href props where they remain so a missing map can no longer silently ship /en/ links - build help/stories hub links from the locale that served the prose, removing the second fallback walk per slug - drop the now-unused localizeContentHref and derive LandingContentHrefs from the href map
|
Hey @innolope-dev — one more Its delta doesn't touch Why it matters for |
There was a problem hiding this comment.
Chip review: Changes requested
Request changes: cross-region EU uplift is treated as single-level, so the web SDK closes before the required EEA follow-up questionnaire.
Findings
- MAJOR · src/hooks/useSumsubKycFlow.ts:391 · Keep cross-region EU uplift multi-level
An already-approved EEA user choosing a locked EU region calls handleInitiateKyc with crossRegion=true. The backend moves them to bridge-requirements and returns a token with actionType "bridge-uplift"; that workflow then branches to bridge-eea-uplift. This truthiness check sets isMultiLevel=false, so the web SDK closes on the first submission before showing the required follow-up questionnaire. Treat bridge-uplift as multi-level (and add it to KycActionType), or return the effective level/isMultiLevel from the API, with a cross-region EU regression test.
Checked clean
- Validated the exact head/base pair and the PeanutProvider merge-conflict semantic union.
- Reviewed passkey shim gating, ceremony serialization and timeouts, late-token attribution, native signing guards, and error handling.
- Reviewed Sumsub multi-level transitions, deferred ACTION_REQUIRED handling, web/native SDK close paths, and the backend actionType contract.
- Reviewed response-level noindex headers, route metadata and canonical ownership, robots crawl exceptions, and preview fail-closed behavior.
- Reviewed Manteca kill-switch restoration and crypto-deposit bridging copy.
- Exact-head CI passed typecheck, unit, eslint, e2e, format, analyze, and deployment checks; local focused tests could not start because the detached worktree has no node_modules.
Exact head: 5b20452d088d · Context: repo, peanut-api-ts
RegulatedRails, SecurityBuiltIn and YourMoney now resolve content hrefs from the filesystem, and the barrel is imported by client components (LandingPageClient, ShhhhhLandingPage, MerchantLandingPage), which pulled fs into the client bundle and broke the production build. Drop the three server sections from the barrel and import them directly in /quests.
…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.
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.
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.
tookTooLong, notReady, deviceState and interrupted landed in es-419 without es-AR counterparts, so Argentine users got tuteo forms for these errors.
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.
getUnderlyingFetch (and its canary-era doc comment) has zero importers; the metadata-policy font mock still stubbed Londrina_Solid, which layout no longer imports.
…ale-links-v2 fix(seo): link localized pages to content owners
…into-dev-20260826 # Conflicts: # src/components/LandingPage/Footer.tsx # src/components/LandingPage/LandingPageClient.tsx # src/components/LandingPage/LandingPageContent.tsx # src/components/LandingPage/RegulatedRails.tsx
Backmerge of
mainintodev.One conflict, in
src/config/peanut.config.tsx— both sides rewrote the same provideruseEffect. Resolved as the semantic union:AppStateProvidersimport, the marketing-route split, and the lazy country-locale loadinstallPasskeyShimwith the patched-flag verification throw, the bounded 1.5s retry, andmarkPasskeyShimFailed()on double failurescheduleDirectFetchCanary()call and import from main's side were not carried over: dev retired the native transport-migration canary in chore(native): retire the transport-migration probes #2737 and thenative-canary.tsmodule no longer exists on this branch — this merge is where main's last reference to it diesTypecheck clean; passkeyCeremony.utils, ClientProviders, user-query and usePWAStatus test suites all green (35 tests).
Brings in: