Skip to content

feat: Authentication bypass via biometric enrollment change#7351

Open
OtavioStasiak wants to merge 38 commits into
developfrom
feat.authentication-bypass-via-biometric-enrollment-change
Open

feat: Authentication bypass via biometric enrollment change#7351
OtavioStasiak wants to merge 38 commits into
developfrom
feat.authentication-bypass-via-biometric-enrollment-change

Conversation

@OtavioStasiak
Copy link
Copy Markdown
Contributor

@OtavioStasiak OtavioStasiak commented May 27, 2026

Proposed changes

Fix authentication bypass, where an attacker who knows the device passcode can enrol a new biometric in OS Settings and use it to unlock the app, the previous implementation never verified that the enrolment set hadn't changed since biometry was enabled in-app.

This PR also fixes the bug that freezes the app when you try to change the passcode. Additionally, we added E2E tests for the passcode feature.

Issue(s)

https://rocketchat.atlassian.net/browse/VLN-216

How to test or reproduce

Reproducing the original vulnerability (on develop, before this PR):

  1. Install the app, log in, go to Settings → Screen lock, enable auto-lock, set a passcode, enable biometry (Touch ID / Face ID / fingerprint).
  2. Background the app long enough to trigger auto-lock.
  3. In OS Settings (not the app), add a new fingerprint / re-enrol Face ID using the device passcode.
  4. Reopen the app → the newly enrolled biometric unlocks it. Bug confirmed.

Verifying the fix (with this PR):

  1. Repeat steps 1–3 above.
  2. Reopen the app → biometry prompt is not auto-fired; the passcode modal appears with the subtitle "Biometric enrollment changed, please use your passcode".
  3. Open Settings → Screen lock → the biometry toggle is now off.
  4. Re-enable biometry from Settings → fresh enrol() runs, sentinel is rebound to the new enrolment set, normal biometric unlock works again on the next
    auto-lock cycle.

Other paths to sanity-check:

  • Cancel the biometric prompt: unlock flow should fall back to the passcode modal with the biometry button still visible (no auto re-prompt). Tap the biometry
    button → prompt re-fires; cancel again → button stays, flag untouched.
  • Disable biometry from Settings: sentinel is removed (disenrol()); re-enabling runs a fresh enrol().
  • Force_Screen_Lock, MAX_ATTEMPTS, lockout timer: unchanged — verify by exhausting passcode attempts.
  • Upgrade path (silent-bind migration): install a build from develop with biometry enabled, then update to this branch without changing OS biometric
    enrolment. First unlock should be biometric as usual — no passcode interstitial. Migration runs once on app init and is idempotent on subsequent launches.
  • Auto-lock unlock with no enrolment change: passcode modal shows no subtitle (the subtitle is strictly tied to the invalidation event).

Upgrade-path (silent-bind migration) — step by step:

  1. Check out develop (or any commit before this PR), pnpm install && pnpm pod-install, run pnpm ios / pnpm android on a device with biometry enrolled.
  2. Log in, go to Settings → Screen lock, enable auto-lock + set a passcode + turn biometry on. Background the app, foreground it once, confirm biometric
    unlock works. Quit the app.
  3. Without changing OS biometric enrolment and without uninstalling, check out this branch and rebuild on top of the same install (so
    BIOMETRY_ENABLED_KEY=true survives but the new sentinel doesn't exist yet).
  4. Launch the app → first unlock should be biometric, with no passcode interstitial. Behind the scenes runBiometricTrustMigration() ran on init, saw flag=true
    && !sentinel && !migrated, called enrol() silently, and set BIOMETRIC_TRUST_MIGRATION_V1_DONE=true.
  5. Background → foreground again → still biometric, still no interstitial (migration is now a no-op because the sentinel exists).
  6. Fully quit and relaunch → still biometric (idempotency check; migration short-circuits on sentinel exists).
  7. Verify the upgrade didn't weaken the enrolment-change protection: after step 6, go into OS Settings and add a new fingerprint / re-enrol Face ID. Reopen
    the app → passcode modal with the "Biometric enrollment changed" subtitle, Screen Lock toggle now off. This proves the migration grandfathered the user in and
    the sentinel is bound to the current enrolment set.
  8. Reconciliation branch (harder to repro without dev tooling — optional): with the marker set and biometry on, manually clear the keychain item (e.g. via
    Xcode's "Erase All Content" simulator option or by toggling biometry off-then-on at the OS level on Android in a way that wipes Keystore). Relaunch →
    migration sees flag=true && !sentinel && migrated, clears BIOMETRY_ENABLED_KEY without re-enrolling; Screen Lock toggle shows off; next unlock is
    passcode-only. Re-enabling biometry from Settings works normally.
  9. Fresh-install control: uninstall, reinstall this branch, set up biometry from scratch → migration is a no-op (no flag at boot); normal enrol() runs from
    the Settings toggle. Confirms the migration helper doesn't run on installs that never had pre-fix biometry.

Screenshots

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Summary by CodeRabbit

  • New Features
    • Biometric trust: detects enrollment changes, falls back to passcode, and runs a one‑shot migration; settings and flows now use the trust store and surface an enrollment-change subtitle only when applicable.
    • Deferred modal confirmations: passcode/change-passcode modals defer callbacks until after hide animations.
  • Localization
    • Added biometric-enrollment-change translations in 20+ languages.
  • Tests
    • Expanded unit and E2E coverage for biometric trust, migration, modal settling, and screen-lock flows.
  • Documentation
    • Added architecture, flows, and platform docs for the biometric trust subsystem.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a keychain-backed biometric trust store, migration, and resolve flow; replaces expo-local-authentication wiring with trust-store APIs; surfaces enrollment-change reasons through lock modals; defers modal callbacks until hide; and adds tests, translations, docs, and E2E coverage.

Changes

Biometric Trust Store & Passcode Authentication

Layer / File(s) Summary
Core types and constants
app/definitions/IBiometricTrustStore.ts, app/definitions/index.ts, app/lib/constants/localAuthentication.ts
Adds TrustResult and IBiometricTrustStore, re-exports the new types, and introduces migration/sentinel and E2E timing constants.
Keychain trust store implementation & tests
app/lib/biometricTrustStore/index.ts, app/lib/biometricTrustStore/index.test.ts, jest.setup.js, package.json
Implements biometricTrustStore using react-native-keychain, classifyError, enroll/verify/disenrol, enablement APIs and setBiometryEnabled; adds unit tests and a Jest keychain mock; adds react-native-keychain dependency.
Resolve biometric trust
app/lib/biometricTrustStore/resolveBiometricTrust.ts, app/lib/biometricTrustStore/resolveBiometricTrust.test.ts
Maps TrustResult to { unlocked } or { unlocked:false, modal }, runs disenrol() before disabling on invalidation, and tests outcome kinds and ordering.
Biometric trust migration & restore
app/lib/biometricTrustStore/migration.ts, app/lib/biometricTrustStore/migration.test.ts, app/sagas/init.js
One-shot migration that probes sentinel, enrols when needed, sets migration marker, or reconciles by disabling trust; wired to app restore and covered by tests for upgrade/reconcile/idempotency/errors.
Local authentication helpers
app/lib/methods/helpers/localAuthentication.ts, app/lib/methods/helpers/localAuthentication.test.ts, app/lib/methods/helpers/events.ts
Replaces expo-local-authentication flow with biometricTrustStore.enrol()/verify()/setEnabled(), returns TrustResult from biometryAuth, extends emitted modal payloads to include reason, and updates tests and auto-lock E2E override.
Passcode entry & lock modal UI
app/containers/Passcode/PasscodeEnter.tsx, app/containers/Passcode/PasscodeEnter.test.tsx
Passcode modal accepts and mirrors reason and hasBiometry into state, resolves resolveBiometricTrust outcomes to either finish or update modal state, and shows enrollment-change subtitle only for that reason (tests added).
Screen locked / change-passcode modal deferral
app/views/ScreenLockedView.tsx, app/views/ChangePasscodeView.tsx, app/lib/hooks/useDeferredModalSettle.ts
Defers submit/cancel callbacks by storing pending resolvers and invoking them on onModalHide; flushes pending resolvers when new requests arrive to avoid leaving callers unresolved.
Screen lock settings
app/views/ScreenLockConfigView.tsx
Switches biometry state to biometricTrustStore.isEnabled() and uses setBiometryEnabled() when toggled; forces UI off on non-success results and adds testIDs for E2E.
Deep-linking & server selection guards
app/sagas/deepLinking.js, app/views/RoomsListView/components/ServersList.tsx, app/views/SecurityPrivacyView.tsx
Guards navigation/selection flows by catching local-auth failures and aborting navigation where appropriate.
Maestro E2E & helpers
.maestro/scripts/data-setup.js, .maestro/tests/assorted/screen-lock.yaml
Exports sleep helper; adds an end-to-end screen-lock spec validating passcode setup, auto-lock, change-passcode flow, and relaunch unlocks.
Translations & documentation
app/i18n/locales/*.json, app/lib/biometricTrustStore/docs/*
Adds Local_authentication_biometric_enrollment_changed across locales and adds architecture/flows/platform docs for the trust-store subsystem.

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • diegolmello

Suggested labels

type: bug

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'feat: Authentication bypass via biometric enrollment change' is vague and potentially misleading, describing a security vulnerability rather than the actual fix being implemented. Rephrase the title to clearly indicate the feature being added, such as 'feat: Add biometric trust sentinel validation to prevent enrollment-change bypass' or 'feat: Implement biometric enrollment verification via keychain sentinel'.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • VLN-216: Request failed with status code 401

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

OtavioStasiak and others added 3 commits May 27, 2026 15:58
…plumbing

Introduce app/lib/biometricTrustStore: a keychain-backed sentinel bound to
ACCESS_CONTROL.BIOMETRY_CURRENT_SET so the OS invalidates it when the device's
enrolment set changes (iOS errSecItemNotFound, Android
KeyPermanentlyInvalidatedException). The store exposes enrol/disenrol/verify/
probeExists and classifies platform errors into a TrustResult union.

Wire the store into handleLocalAuthentication via the Option C pattern: the
upstream verify() runs before the modal opens and its outcome decides whether
to unlock (success), open the passcode modal with biometry available but
auto-prompt suppressed (canceled/error), or fall back to passcode-only
(unavailable / enrollmentChanged — slice 02 will add the disenrol + flag-clear
side effects). PasscodeEnter and ScreenLockedView take a new skipAutoBiometry
prop carried over LOCAL_AUTHENTICATE_EMITTER so the biometry button stays
visible without re-firing the prompt the user just dismissed.

Screen-lock toggle now enrols/disenrols the sentinel alongside flipping
BIOMETRY_ENABLED_KEY so the keychain item and the flag stay in lockstep.

Part of VLN-216.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add handleBiometricTrustResult, a shared helper that maps a TrustResult into
an (unlocked, modal-config) outcome. Both call sites — handleLocalAuthentication's
upstream verify() preflight and PasscodeEnter's biometry-button retry — route
through it so the invalidation policy lives in one place.

On {kind: 'enrollmentChanged'} the helper runs disenrol() BEFORE clearing
BIOMETRY_ENABLED_KEY, so a crash between the two leaves a state slice 04's
reconciliation can clean up (a flipped flag with a live sentinel would
otherwise look like a healthy enrolment). The resulting modal carries
reason: 'enrollmentChanged' over LOCAL_AUTHENTICATE_EMITTER so slice 03 can
render an explanatory subtitle.

Cancel/error keep biometry available with skipAutoBiometry; unavailable is
passcode-only; success unlocks without a modal.

In PasscodeEnter the biometry button now mirrors hasBiometry/reason in local
state so an enrolment-change triggered from the button hides the button
within the same modal session without re-emitting the event (which would
orphan the upstream openModal promise).

Closes VLN-216.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… unlock

When handleLocalAuthentication invalidates biometric trust because the device
enrolment set changed, the passcode modal now displays an explanatory
subtitle reading "Biometric enrollment changed, please use your passcode".

The signal travels over LOCAL_AUTHENTICATE_EMITTER's existing reason payload
(added in the previous commit). PasscodeEnter reads reason from props,
mirrors it into local state so a button-triggered invalidation can update it
without re-emitting, and renders Base's subtitle slot only when
reason === 'enrollmentChanged'. The subtitle clears naturally on the next
modal open because reason is reinitialised from props each session.

Normal auto-lock unlocks, cancel/error fallbacks, and re-opens after a
successful unlock leave the subtitle hidden — it is strictly tied to the
invalidation event.

Part of VLN-216.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ers on upgrade

Existing installs that had biometry enabled before the trust store existed
have BIOMETRY_ENABLED_KEY=true but no keychain sentinel, which would force
them through the passcode-only modal on first launch after upgrade. Run a
one-shot migration on app init that grandfathers them with a silent enrol().

The marker BIOMETRIC_TRUST_MIGRATION_V1_DONE makes this idempotent and lets
the helper distinguish two superficially identical states:

  !migrated && flag && !sentinel → silent enrol(), set marker.    upgrade path
   migrated && flag && !sentinel → clear flag, no enrol().        reconciliation

Without the marker, post-invalidation state (flag=true && !sentinel after a
crash between disenrol() and the flag-clear in the enrollmentChanged
handler) would silently re-bind and undo the enrollment-change protection.
With the marker, that state instead clears the flag — the user re-enables
biometry from Settings, which runs a fresh enrol() that observes the new
enrolment set.

enrol() failure leaves the marker unset so the next boot retries, and
leaves the flag alone so the next unlock falls into the unavailable branch
and asks for the passcode. probeExists() rejection is swallowed and logged.

The trade-off (silent bind vs. theoretical pre-fix compromise) follows the
product decision in DECISIONS.md / ADR 0006.

Part of VLN-216.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@OtavioStasiak OtavioStasiak force-pushed the feat.authentication-bypass-via-biometric-enrollment-change branch from 49e4acd to 7ec65a4 Compare May 27, 2026 19:10
@OtavioStasiak
Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/containers/Passcode/PasscodeEnter.tsx`:
- Around line 43-56: The biometry() async function can reject and is currently
invoked as a floating promise from readStorage(); wrap the internal async calls
in biometry() (calls to biometryAuth() and handleBiometricTrustResult()) in a
try/catch and surface/log/handle errors (e.g., set UI state or clear modal)
before returning, and also ensure every call site (e.g., where readStorage()
calls biometry()) either awaits biometry() or attaches .catch(...) to handle
rejections so no unhandled promise rejections occur; update symbols involved:
biometry(), biometryAuth, handleBiometricTrustResult, finishProcess,
setHasBiometry, setReason, and the readStorage() call sites to explicitly handle
errors.

In `@app/views/ScreenLockConfigView.tsx`:
- Around line 165-173: The async setState callback that calls
biometricTrustStore.enrol()/disenrol() lacks error handling and unconditionally
persists userPreferences.setBool(BIOMETRY_ENABLED_KEY, biometry), which can
desync UI and the trust store; wrap the enrol/disenrol calls in a try/catch
inside the callback, only call userPreferences.setBool(BIOMETRY_ENABLED_KEY,
biometry) after the operation succeeds, and on failure revert the UI toggle
(reset this.state.biometry or call setState to the previous value) and
surface/log the error (e.g., show an error toast or processLogger.error) so the
preference and keychain remain consistent with biometricTrustStore state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d18c30d1-9315-4a3e-b18c-b8ff74efb8f9

📥 Commits

Reviewing files that changed from the base of the PR and between 41444f4 and f091568.

⛔ Files ignored due to path filters (2)
  • ios/Podfile.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (38)
  • app/containers/Passcode/PasscodeEnter.test.tsx
  • app/containers/Passcode/PasscodeEnter.tsx
  • app/i18n/locales/ar.json
  • app/i18n/locales/bn-IN.json
  • app/i18n/locales/cs.json
  • app/i18n/locales/de.json
  • app/i18n/locales/en.json
  • app/i18n/locales/fi.json
  • app/i18n/locales/fr.json
  • app/i18n/locales/hi-IN.json
  • app/i18n/locales/hu.json
  • app/i18n/locales/it.json
  • app/i18n/locales/nl.json
  • app/i18n/locales/no.json
  • app/i18n/locales/pt-BR.json
  • app/i18n/locales/ru.json
  • app/i18n/locales/sl-SI.json
  • app/i18n/locales/sv.json
  • app/i18n/locales/ta-IN.json
  • app/i18n/locales/te-IN.json
  • app/i18n/locales/tr.json
  • app/i18n/locales/zh-CN.json
  • app/i18n/locales/zh-TW.json
  • app/lib/biometricTrustStore/handleResult.test.ts
  • app/lib/biometricTrustStore/handleResult.ts
  • app/lib/biometricTrustStore/index.test.ts
  • app/lib/biometricTrustStore/index.ts
  • app/lib/biometricTrustStore/migration.test.ts
  • app/lib/biometricTrustStore/migration.ts
  • app/lib/constants/localAuthentication.ts
  • app/lib/methods/helpers/events.ts
  • app/lib/methods/helpers/localAuthentication.test.ts
  • app/lib/methods/helpers/localAuthentication.ts
  • app/sagas/init.js
  • app/views/ScreenLockConfigView.tsx
  • app/views/ScreenLockedView.tsx
  • jest.setup.js
  • package.json
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/lib/constants/localAuthentication.ts
  • app/lib/methods/helpers/events.ts
  • jest.setup.js
  • app/views/ScreenLockConfigView.tsx
  • app/sagas/init.js
  • app/lib/biometricTrustStore/migration.ts
  • app/views/ScreenLockedView.tsx
  • app/lib/biometricTrustStore/index.ts
  • app/lib/biometricTrustStore/index.test.ts
  • app/lib/biometricTrustStore/handleResult.ts
  • app/containers/Passcode/PasscodeEnter.test.tsx
  • app/lib/methods/helpers/localAuthentication.ts
  • app/lib/biometricTrustStore/handleResult.test.ts
  • app/lib/methods/helpers/localAuthentication.test.ts
  • app/containers/Passcode/PasscodeEnter.tsx
  • app/lib/biometricTrustStore/migration.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Use TypeScript with strict mode and baseUrl set to app/ for import resolution

Files:

  • app/lib/constants/localAuthentication.ts
  • app/lib/methods/helpers/events.ts
  • app/views/ScreenLockConfigView.tsx
  • app/lib/biometricTrustStore/migration.ts
  • app/views/ScreenLockedView.tsx
  • app/lib/biometricTrustStore/index.ts
  • app/lib/biometricTrustStore/index.test.ts
  • app/lib/biometricTrustStore/handleResult.ts
  • app/containers/Passcode/PasscodeEnter.test.tsx
  • app/lib/methods/helpers/localAuthentication.ts
  • app/lib/biometricTrustStore/handleResult.test.ts
  • app/lib/methods/helpers/localAuthentication.test.ts
  • app/containers/Passcode/PasscodeEnter.tsx
  • app/lib/biometricTrustStore/migration.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use Prettier with tabs, single quotes, 130 char width, no trailing commas, arrow parens avoid, bracket same line
Use @rocket.chat/eslint-config base with React, React Native, TypeScript, Jest plugins

Files:

  • app/lib/constants/localAuthentication.ts
  • app/lib/methods/helpers/events.ts
  • jest.setup.js
  • app/views/ScreenLockConfigView.tsx
  • app/sagas/init.js
  • app/lib/biometricTrustStore/migration.ts
  • app/views/ScreenLockedView.tsx
  • app/lib/biometricTrustStore/index.ts
  • app/lib/biometricTrustStore/index.test.ts
  • app/lib/biometricTrustStore/handleResult.ts
  • app/containers/Passcode/PasscodeEnter.test.tsx
  • app/lib/methods/helpers/localAuthentication.ts
  • app/lib/biometricTrustStore/handleResult.test.ts
  • app/lib/methods/helpers/localAuthentication.test.ts
  • app/containers/Passcode/PasscodeEnter.tsx
  • app/lib/biometricTrustStore/migration.test.ts
app/views/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

View components (70+ screen components) should be placed in app/views/ directory

Files:

  • app/views/ScreenLockConfigView.tsx
  • app/views/ScreenLockedView.tsx
app/containers/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Reusable UI components should be placed in app/containers/ directory

Files:

  • app/containers/Passcode/PasscodeEnter.test.tsx
  • app/containers/Passcode/PasscodeEnter.tsx
🧠 Learnings (5)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/lib/constants/localAuthentication.ts
  • app/lib/methods/helpers/events.ts
  • app/views/ScreenLockConfigView.tsx
  • app/lib/biometricTrustStore/migration.ts
  • app/views/ScreenLockedView.tsx
  • app/lib/biometricTrustStore/index.ts
  • app/lib/biometricTrustStore/index.test.ts
  • app/lib/biometricTrustStore/handleResult.ts
  • app/containers/Passcode/PasscodeEnter.test.tsx
  • app/lib/methods/helpers/localAuthentication.ts
  • app/lib/biometricTrustStore/handleResult.test.ts
  • app/lib/methods/helpers/localAuthentication.test.ts
  • app/containers/Passcode/PasscodeEnter.tsx
  • app/lib/biometricTrustStore/migration.test.ts
📚 Learning: 2026-03-30T15:49:26.708Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6875
File: app/containers/RoomItem/Actions.tsx:12-12
Timestamp: 2026-03-30T15:49:26.708Z
Learning: In Rocket.Chat.ReactNative, do not rely on `react-native-worklets` v0.6.1 exporting a built-in Jest mock (e.g., `react-native-worklets/lib/module/mock` does not exist for this version). Instead, add the Jest manual mock in your repo’s `jest.setup.js`/`jest.setup.ts`, mocking `react-native-worklets` to provide `scheduleOnRN: jest.fn((fn, ...args) => fn(...args))`. This ensures Jest can import the module and that `scheduleOnRN` executes the passed function during tests.

Applied to files:

  • jest.setup.js
📚 Learning: 2026-05-07T13:19:52.152Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7304
File: app/sagas/deepLinking.js:237-243
Timestamp: 2026-05-07T13:19:52.152Z
Learning: In this codebase’s Redux-Saga usage, remember that `yield put(action)` dispatches through the Redux store synchronously, and any saga(s) that synchronously react via action listeners (and synchronous `put` chains) will run to completion before the calling saga resumes at its next `yield`. As a result, within a single saga there is no scheduler interleaving between a `yield select(...)` and a subsequent `yield take(...)` at the next `yield` point, so a check-then-take pattern like `const state = yield select(...); if (state !== TARGET) { yield take(a => a.type === TARGET); }` is safe from TOCTOU races under the synchronous `put`/take model described above.

Applied to files:

  • app/sagas/init.js
📚 Learning: 2026-02-05T13:55:00.974Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6930
File: package.json:101-101
Timestamp: 2026-02-05T13:55:00.974Z
Learning: In this repository, the dependency on react-native-image-crop-picker should reference the RocketChat fork (RocketChat/react-native-image-crop-picker) with explicit commit pins, not the upstream ivpusic/react-native-image-crop-picker. Update package.json dependencies (and any lockfile) to point to the fork URL and a specific commit, ensuring edge-to-edge Android fixes are included. This pattern should apply to all package.json files in the repo that declare this dependency.

Applied to files:

  • package.json
📚 Learning: 2026-05-07T17:47:14.516Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7303
File: package.json:5-5
Timestamp: 2026-05-07T17:47:14.516Z
Learning: When reviewing pnpm `packageManager` version pins in any `package.json` (e.g., `"packageManager": "pnpm@<version>"`), don’t rely solely on web-search results to determine whether a version exists. For very recently published versions, cross-check the target version against the official pnpm release page (https://github.com/pnpm/pnpm/releases) and the npm registry page for pnpm (https://www.npmjs.com/package/pnpm) before flagging the pinned version as non-existent.

Applied to files:

  • package.json
🔇 Additional comments (36)
app/lib/biometricTrustStore/index.ts (1)

1-107: LGTM!

app/lib/biometricTrustStore/index.test.ts (1)

1-153: LGTM!

jest.setup.js (1)

319-327: LGTM!

package.json (1)

106-106: LGTM!

app/lib/biometricTrustStore/handleResult.ts (1)

1-40: LGTM!

app/lib/biometricTrustStore/handleResult.test.ts (1)

1-89: LGTM!

app/lib/biometricTrustStore/migration.ts (1)

1-48: LGTM!

app/lib/biometricTrustStore/migration.test.ts (1)

1-135: LGTM!

app/lib/constants/localAuthentication.ts (1)

5-5: LGTM!

app/lib/methods/helpers/localAuthentication.ts (1)

11-13: LGTM!

Also applies to: 55-63, 81-87, 94-97, 119-134

app/lib/methods/helpers/localAuthentication.test.ts (1)

1-149: LGTM!

app/lib/methods/helpers/events.ts (1)

13-14: LGTM!

app/sagas/init.js (1)

13-13: LGTM!

Also applies to: 27-27

app/containers/Passcode/PasscodeEnter.test.tsx (1)

47-61: LGTM!

Also applies to: 63-76, 78-93, 101-113

app/views/ScreenLockedView.tsx (1)

20-21: LGTM!

Also applies to: 83-88

app/i18n/locales/ar.json (1)

327-327: LGTM!

app/i18n/locales/bn-IN.json (1)

454-454: LGTM!

app/i18n/locales/cs.json (1)

486-486: LGTM!

app/i18n/locales/de.json (1)

448-448: LGTM!

app/i18n/locales/en.json (1)

501-501: LGTM!

app/i18n/locales/fi.json (1)

429-429: LGTM!

app/i18n/locales/fr.json (1)

394-394: LGTM!

app/i18n/locales/hi-IN.json (1)

454-454: LGTM!

app/i18n/locales/hu.json (1)

455-455: LGTM!

app/i18n/locales/it.json (1)

356-356: LGTM!

app/i18n/locales/nl.json (1)

394-394: LGTM!

app/i18n/locales/no.json (1)

480-480: LGTM!

app/i18n/locales/pt-BR.json (1)

496-496: LGTM!

app/i18n/locales/ru.json (1)

419-419: LGTM!

app/i18n/locales/sl-SI.json (1)

404-404: LGTM!

app/i18n/locales/sv.json (1)

428-428: LGTM!

app/i18n/locales/ta-IN.json (1)

454-454: LGTM!

app/i18n/locales/te-IN.json (1)

453-453: LGTM!

app/i18n/locales/tr.json (1)

341-341: LGTM!

app/i18n/locales/zh-CN.json (1)

326-326: LGTM!

app/i18n/locales/zh-TW.json (1)

342-342: LGTM!

Comment thread app/containers/Passcode/PasscodeEnter.tsx Outdated
Comment thread app/views/ScreenLockConfigView.tsx
@github-actions
Copy link
Copy Markdown

iOS Build Available

Rocket.Chat 4.73.0.108982

@OtavioStasiak OtavioStasiak temporarily deployed to approve_e2e_testing June 2, 2026 22:11 — with GitHub Actions Inactive
@OtavioStasiak
Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 3, 2026

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
app/lib/biometricTrustStore/docs/README.md (1)

17-23: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language identifier to the fenced block.

Line 17 opens a code fence without a language, which still triggers MD040 markdownlint. Please change it to ```text for the directory listing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/lib/biometricTrustStore/docs/README.md` around lines 17 - 23, The code
fence in the README directory listing is missing a language identifier causing
MD040; update the fenced block opening from ``` to ```text so the directory
listing is marked as plain text. Locate the fenced block in the
biometricTrustStore README (the directory listing showing index.ts,
migration.ts, resolveBiometricTrust.ts, docs/) and change the opening fence to
```text, leaving the content and closing fence unchanged.
🧹 Nitpick comments (1)
app/views/SecurityPrivacyView.tsx (1)

62-67: ⚡ Quick win

Consider inspecting the error type for more robust error handling.

The empty catch block will silently swallow any error from handleLocalAuthentication, not just UserCanceledError. While the current behavior (staying on screen) is safe, catching all errors without inspection could hide unexpected failures (e.g., storage errors, system-level issues) and make debugging harder.

🛡️ Suggested improvement to check error type
 		try {
 			await handleLocalAuthentication(true);
-		} catch {
-			// User dismissed the unlock modal — stay on this screen.
+		} catch (error) {
+			// User dismissed the unlock modal — stay on this screen.
+			if (!(error instanceof UserCanceledError)) {
+				console.warn('Unexpected error in handleLocalAuthentication:', error);
+			}
 			return;
 		}

Note: You'll need to import UserCanceledError from app/lib/methods/helpers/localAuthentication.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/SecurityPrivacyView.tsx` around lines 62 - 67, The catch is
currently swallowing all errors from handleLocalAuthentication; change it to
inspect the thrown error (import UserCanceledError from
app/lib/methods/helpers/localAuthentication) and only suppress it (return) when
error is an instance of UserCanceledError, otherwise rethrow or log and rethrow
so unexpected errors (e.g., storage/system errors) surface; update the try/catch
around handleLocalAuthentication(true) accordingly and ensure UserCanceledError
is imported and referenced by name.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/containers/Passcode/Base/Locked.tsx`:
- Around line 34-41: The timer callback used in setTimeout can produce an
unhandled rejection if resetAttempts() fails, preventing setStatus(TYPE.ENTER)
from running; wrap the await resetAttempts() call in a try/catch inside the
async callback (the function that calls calcTimeLeft(), resetAttempts(), and
setStatus) and on error handle/log it (e.g., processLogger/error handling or
console.error) but ensure setStatus(TYPE.ENTER) is executed in a finally-like
path so the UI transitions regardless of resetAttempts() outcome; update the
callback around resetAttempts(), calcTimeLeft(), and setStatus to guarantee the
status flip even when resetAttempts throws.

In `@app/containers/Passcode/PasscodeEnter.tsx`:
- Around line 35-36: PasscodeEnter seeds mirrored biometry state only once
(hasBiometry/reason) so when a new lock request mounts the already-mounted
component it doesn't re-enable biometry or update the subtitle; add a useEffect
inside the PasscodeEnter component that watches the incoming props
initialHasBiometry and initialReason (or the lock-request identifier prop if
present) and calls setHasBiometry(initialHasBiometry) and
setReason(initialReason) to sync local state whenever those props change.

In `@app/sagas/deepLinking.js`:
- Around line 206-211: The existing-user deep-link branch in handleOpen must
mirror the early-return behavior: wrap the call to localAuthenticate(host)
inside that branch in a try/catch and on catch do an immediate return (same as
the other branch) so a canceled or superseded unlock drops the deep link instead
of falling through; locate the existing if (user && serverRecord) branch in
handleOpen, find the spot calling localAuthenticate(host) there, and change it
to try { yield localAuthenticate(host); } catch { return; } to prevent
subsequent getServerInfo(host)/server-switch logic from running.

---

Duplicate comments:
In `@app/lib/biometricTrustStore/docs/README.md`:
- Around line 17-23: The code fence in the README directory listing is missing a
language identifier causing MD040; update the fenced block opening from ``` to
```text so the directory listing is marked as plain text. Locate the fenced
block in the biometricTrustStore README (the directory listing showing index.ts,
migration.ts, resolveBiometricTrust.ts, docs/) and change the opening fence to
```text, leaving the content and closing fence unchanged.

---

Nitpick comments:
In `@app/views/SecurityPrivacyView.tsx`:
- Around line 62-67: The catch is currently swallowing all errors from
handleLocalAuthentication; change it to inspect the thrown error (import
UserCanceledError from app/lib/methods/helpers/localAuthentication) and only
suppress it (return) when error is an instance of UserCanceledError, otherwise
rethrow or log and rethrow so unexpected errors (e.g., storage/system errors)
surface; update the try/catch around handleLocalAuthentication(true) accordingly
and ensure UserCanceledError is imported and referenced by name.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1820d278-4294-4894-a1a0-de13070730d5

📥 Commits

Reviewing files that changed from the base of the PR and between b7e21c0 and 410d8b0.

📒 Files selected for processing (25)
  • app/containers/Passcode/Base/Locked.tsx
  • app/containers/Passcode/PasscodeEnter.test.tsx
  • app/containers/Passcode/PasscodeEnter.tsx
  • app/definitions/IBiometricTrustStore.ts
  • app/lib/biometricTrustStore/docs/ARCHITECTURE.md
  • app/lib/biometricTrustStore/docs/FLOWS.md
  • app/lib/biometricTrustStore/docs/PLATFORMS.md
  • app/lib/biometricTrustStore/docs/README.md
  • app/lib/biometricTrustStore/index.test.ts
  • app/lib/biometricTrustStore/index.ts
  • app/lib/biometricTrustStore/migration.test.ts
  • app/lib/biometricTrustStore/migration.ts
  • app/lib/biometricTrustStore/resolveBiometricTrust.test.ts
  • app/lib/biometricTrustStore/resolveBiometricTrust.ts
  • app/lib/hooks/useDeferredModalSettle.test.ts
  • app/lib/hooks/useDeferredModalSettle.ts
  • app/lib/methods/helpers/events.ts
  • app/lib/methods/helpers/localAuthentication.test.ts
  • app/lib/methods/helpers/localAuthentication.ts
  • app/sagas/deepLinking.js
  • app/views/ChangePasscodeView.tsx
  • app/views/RoomsListView/components/ServersList.tsx
  • app/views/ScreenLockConfigView.tsx
  • app/views/ScreenLockedView.tsx
  • app/views/SecurityPrivacyView.tsx
✅ Files skipped from review due to trivial changes (1)
  • app/lib/biometricTrustStore/docs/FLOWS.md
🚧 Files skipped from review as they are similar to previous changes (11)
  • app/lib/methods/helpers/events.ts
  • app/definitions/IBiometricTrustStore.ts
  • app/lib/biometricTrustStore/resolveBiometricTrust.test.ts
  • app/lib/methods/helpers/localAuthentication.test.ts
  • app/lib/biometricTrustStore/migration.ts
  • app/lib/biometricTrustStore/migration.test.ts
  • app/lib/methods/helpers/localAuthentication.ts
  • app/views/ScreenLockConfigView.tsx
  • app/lib/biometricTrustStore/index.ts
  • app/containers/Passcode/PasscodeEnter.test.tsx
  • app/lib/biometricTrustStore/index.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/views/SecurityPrivacyView.tsx
  • app/views/RoomsListView/components/ServersList.tsx
  • app/lib/hooks/useDeferredModalSettle.test.ts
  • app/lib/hooks/useDeferredModalSettle.ts
  • app/containers/Passcode/Base/Locked.tsx
  • app/sagas/deepLinking.js
  • app/views/ChangePasscodeView.tsx
  • app/views/ScreenLockedView.tsx
  • app/lib/biometricTrustStore/resolveBiometricTrust.ts
  • app/containers/Passcode/PasscodeEnter.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Use TypeScript with strict mode enabled

Files:

  • app/views/SecurityPrivacyView.tsx
  • app/views/RoomsListView/components/ServersList.tsx
  • app/lib/hooks/useDeferredModalSettle.test.ts
  • app/lib/hooks/useDeferredModalSettle.ts
  • app/containers/Passcode/Base/Locked.tsx
  • app/views/ChangePasscodeView.tsx
  • app/views/ScreenLockedView.tsx
  • app/lib/biometricTrustStore/resolveBiometricTrust.ts
  • app/containers/Passcode/PasscodeEnter.tsx
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier formatting with tabs, single quotes, 130 character line width, no trailing commas, and avoid arrow function parentheses

Files:

  • app/views/SecurityPrivacyView.tsx
  • app/views/RoomsListView/components/ServersList.tsx
  • app/lib/hooks/useDeferredModalSettle.test.ts
  • app/lib/hooks/useDeferredModalSettle.ts
  • app/containers/Passcode/Base/Locked.tsx
  • app/sagas/deepLinking.js
  • app/views/ChangePasscodeView.tsx
  • app/views/ScreenLockedView.tsx
  • app/lib/biometricTrustStore/resolveBiometricTrust.ts
  • app/containers/Passcode/PasscodeEnter.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Enforce ESLint rules from @rocket.chat/eslint-config with React, React Native, TypeScript, and Jest plugins

Files:

  • app/views/SecurityPrivacyView.tsx
  • app/views/RoomsListView/components/ServersList.tsx
  • app/lib/hooks/useDeferredModalSettle.test.ts
  • app/lib/hooks/useDeferredModalSettle.ts
  • app/containers/Passcode/Base/Locked.tsx
  • app/sagas/deepLinking.js
  • app/views/ChangePasscodeView.tsx
  • app/views/ScreenLockedView.tsx
  • app/lib/biometricTrustStore/resolveBiometricTrust.ts
  • app/containers/Passcode/PasscodeEnter.tsx
app/views/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place screen components in 'app/views/' directory

Files:

  • app/views/SecurityPrivacyView.tsx
  • app/views/RoomsListView/components/ServersList.tsx
  • app/views/ChangePasscodeView.tsx
  • app/views/ScreenLockedView.tsx
app/containers/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place reusable UI components in 'app/containers/' directory

Files:

  • app/containers/Passcode/Base/Locked.tsx
  • app/containers/Passcode/PasscodeEnter.tsx
🧠 Learnings (5)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/views/SecurityPrivacyView.tsx
  • app/views/RoomsListView/components/ServersList.tsx
  • app/lib/hooks/useDeferredModalSettle.test.ts
  • app/lib/hooks/useDeferredModalSettle.ts
  • app/containers/Passcode/Base/Locked.tsx
  • app/views/ChangePasscodeView.tsx
  • app/views/ScreenLockedView.tsx
  • app/lib/biometricTrustStore/resolveBiometricTrust.ts
  • app/containers/Passcode/PasscodeEnter.tsx
📚 Learning: 2026-05-07T13:19:52.152Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7304
File: app/sagas/deepLinking.js:237-243
Timestamp: 2026-05-07T13:19:52.152Z
Learning: In this codebase’s Redux-Saga usage, remember that `yield put(action)` dispatches through the Redux store synchronously, and any saga(s) that synchronously react via action listeners (and synchronous `put` chains) will run to completion before the calling saga resumes at its next `yield`. As a result, within a single saga there is no scheduler interleaving between a `yield select(...)` and a subsequent `yield take(...)` at the next `yield` point, so a check-then-take pattern like `const state = yield select(...); if (state !== TARGET) { yield take(a => a.type === TARGET); }` is safe from TOCTOU races under the synchronous `put`/take model described above.

Applied to files:

  • app/sagas/deepLinking.js
📚 Learning: 2026-04-07T17:49:25.836Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-07T17:49:25.836Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Use explicit error handling with try/catch blocks for async operations

Applied to files:

  • app/containers/Passcode/PasscodeEnter.tsx
📚 Learning: 2026-04-04T21:34:30.268Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6808
File: app/containers/MessageComposer/components/ComposerInput.tsx:337-341
Timestamp: 2026-04-04T21:34:30.268Z
Learning: In Rocket.Chat React Native, the markdown composer's autocomplete insertion (ComposerInput.tsx onAutocompleteItemSelected) does NOT need to add a space between an underscore italic delimiter `_` and a `@` or `#` mention sigil. The web platform (using the same rocket.chat/message-parser) does not add such a space either, so parity with web is the correct behavior. The previous learning about "space between `_` and mention sigil" applies only to test/story file content strings, not to the composer's runtime autocomplete behavior.

Applied to files:

  • app/containers/Passcode/PasscodeEnter.tsx
📚 Learning: 2026-04-07T17:49:25.836Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-07T17:49:25.836Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript for type safety; add explicit type annotations to function parameters and return types

Applied to files:

  • app/lib/biometricTrustStore/docs/README.md
🪛 LanguageTool
app/lib/biometricTrustStore/docs/ARCHITECTURE.md

[style] ~109-~109: Consider an alternative for the overused word “exactly”.
Context: ...ng the sentinel doesn't prompt. That is exactly the bypass this subsystem exists to clo...

(EXACTLY_PRECISELY)

app/lib/biometricTrustStore/docs/PLATFORMS.md

[grammar] ~35-~35: Ensure spelling is correct
Context: ...llmentChangedkind (and its subtitle), where iOS more often reachesunavailable`. ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 markdownlint-cli2 (0.22.1)
app/lib/biometricTrustStore/docs/README.md

[warning] 17-17: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (6)
app/lib/biometricTrustStore/docs/ARCHITECTURE.md (1)

1-147: LGTM!

app/lib/biometricTrustStore/docs/PLATFORMS.md (1)

1-54: LGTM!

app/lib/biometricTrustStore/resolveBiometricTrust.ts (2)

24-27: Confirm setEnabled is synchronous; otherwise it floats a promise and breaks the documented ordering.

The invalidation branches await biometricTrustStore.disenroll() but call biometricTrustStore.setEnabled(false) without awaiting (lines 26 and 36). The unit-test mock returns void, so this is only safe if the real setEnabled is synchronous (e.g. MMKV-backed). If it returns a Promise, the function resolves before the persisted disable completes — violating the disenroll-before-disable invariant documented on lines 17–19 — and leaves an unhandled rejection (the no-void/floating-promise constraint from learnings).

The intended modal shape (hasBiometry: false + reason) is confirmed by the resolver tests and PasscodeEnter, so no change is needed there — only the setEnabled await needs verifying.

#!/bin/bash
# Inspect setEnabled's declared return type in the store + interface.
fd -t f 'index.ts' app/lib/biometricTrustStore --exec rg -nP -C2 'setEnabled' {}
fd -t f 'IBiometricTrustStore.ts' --exec rg -nP -C2 'setEnabled' {}

Based on learnings, floating promises should be handled explicitly (e.g. await) rather than left unawaited.


28-37: LGTM!

app/lib/hooks/useDeferredModalSettle.ts (1)

1-44: LGTM!

app/lib/hooks/useDeferredModalSettle.test.ts (1)

1-67: LGTM!

Comment thread app/containers/Passcode/Base/Locked.tsx Outdated
Comment thread app/containers/Passcode/PasscodeEnter.tsx
Comment thread app/sagas/deepLinking.js
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants