Skip to content

Commit 6241d2d

Browse files
committed
update docs
1 parent 0f9b03d commit 6241d2d

8 files changed

Lines changed: 141 additions & 16 deletions

File tree

.cursor/rules/version-gated-feature-flags.mdc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ alwaysApply: false
66

77
# Version-Gated Feature Flags
88

9+
> Canonical human guide: [`docs/readme/version-gated-feature-flags.md`](../../docs/readme/version-gated-feature-flags.md)
10+
911
## Canonical source (SSOT)
1012

1113
All version-gated feature flag logic lives in `app/util/remoteFeatureFlag/index.ts`:

AGENTS.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,12 @@ scripts/ # Build and automation scripts
101101

102102
**Detailed guidelines are in `docs/testing/`** (canonical, in-repo) and the `mms-*` skill set installed via `yarn skills` (Cursor / Codex / Claude harnesses):
103103

104-
| Guide | Scope |
105-
| ------------------------------------------------------------------------------ | ------------------------------------------------- |
106-
| [`docs/testing/unit-testing.md`](docs/testing/unit-testing.md) | `*.test.*` files — test patterns, mocking, AAA |
107-
| [`docs/testing/e2e-testing.md`](docs/testing/e2e-testing.md) | Detox smoke/regression — Page Objects, gestures |
108-
| [`docs/testing/component-view-tests.md`](docs/testing/component-view-tests.md) | `*.view.test.tsx` — framework, presets, renderers |
104+
| Guide | Scope |
105+
| ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
106+
| [`docs/testing/unit-testing.md`](docs/testing/unit-testing.md) | `*.test.*` files — test patterns, mocking, AAA |
107+
| [`docs/testing/e2e-testing.md`](docs/testing/e2e-testing.md) | Detox smoke/regression — Page Objects, gestures |
108+
| [`docs/testing/component-view-tests.md`](docs/testing/component-view-tests.md) | `*.view.test.tsx` — framework, presets, renderers |
109+
| [`docs/readme/version-gated-feature-flags.md`](docs/readme/version-gated-feature-flags.md) | Version-gated remote flags — `validatedVersionGatedFeatureFlag` in selectors |
109110

110111
General coding, UI, deeplink-handler, and PR-creation guidance now lives in the centralized `mms-*` skill set installed via `yarn skills` (see `.agents/skills/mms-*` after sync).
111112

@@ -115,7 +116,7 @@ General coding, UI, deeplink-handler, and PR-creation guidance now lives in the
115116
- **Components**: Design system first → `component-library` second → custom last
116117
- **Styling**: Use `useTailwind()` hook, `Box`/`Text` components, design tokens
117118
- **Testing**: Mandatory for all code, AAA pattern, mock everything external
118-
- **Version-gated feature flags**: Use shared helpers from `app/util/remoteFeatureFlag` — see [`.cursor/rules/version-gated-feature-flags.mdc`](.cursor/rules/version-gated-feature-flags.mdc)
119+
- **Version-gated feature flags**: Use `validatedVersionGatedFeatureFlag` from `app/util/remoteFeatureFlag` in selectors — see [`docs/readme/version-gated-feature-flags.md`](docs/readme/version-gated-feature-flags.md) and [`.cursor/rules/version-gated-feature-flags.mdc`](.cursor/rules/version-gated-feature-flags.mdc)
119120
- **Commands**: ONLY use yarn (never npm/npx)
120121

121122
## Environment Setup

docs/compliance.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ To enable compliance:
5050
1. Set `complianceEnabled: true` in LaunchDarkly (or via the local feature flag override screen in dev builds).
5151
2. Compliance status will be fetched per-address whenever a gated action is triggered.
5252

53-
> **Version gating:** The feature flag uses `validatedVersionGatedFeatureFlag`, which compares against the **native binary version** reported by `react-native-device-info`'s `getVersion()` — not `package.json`. If your device has a stale build installed, the version check may fail even if the flag is enabled. Do a clean native rebuild to pick up the correct version.
53+
> **Version gating:** The feature flag uses `validatedVersionGatedFeatureFlag` in `selectComplianceEnabled`, which compares against the **native binary version** reported by `react-native-device-info`'s `getVersion()` — not `package.json`. If your device has a stale build installed, the version check may fail even if the flag is enabled. Do a clean native rebuild to pick up the correct version.
5454
5555
### Feature flag selector
5656

@@ -60,6 +60,8 @@ import { selectComplianceEnabled } from 'app/selectors/featureFlagController/com
6060
const isEnabled = useSelector(selectComplianceEnabled);
6161
```
6262

63+
The selector resolves the remote `{ enabled, minimumVersion }` payload via `validatedVersionGatedFeatureFlag`. See [`docs/readme/version-gated-feature-flags.md`](./readme/version-gated-feature-flags.md).
64+
6365
## Usage in Flows
6466

6567
### Option 1: `gate()` from `useComplianceGate` (recommended for action guards)

docs/musd/musd-convert-list.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,9 +461,14 @@ export const selectMusdConversionStatuses = createSelector(
461461

462462
**Path:** `app/components/UI/Earn/selectors/featureFlags/index.ts`
463463

464-
Add new version-gated flag with local fallback (following the same pattern as other mUSD flags):
464+
Add new version-gated flag with local fallback (see [`docs/readme/version-gated-feature-flags.md`](../../readme/version-gated-feature-flags.md)):
465465

466466
```typescript
467+
import {
468+
validatedVersionGatedFeatureFlag,
469+
type VersionGatedFeatureFlag,
470+
} from '../../../../util/remoteFeatureFlag';
471+
467472
export const selectMusdQuickConvertEnabledFlag = createSelector(
468473
selectRemoteFeatureFlags,
469474
(remoteFeatureFlags) => {

docs/perps/perps-feature-flags.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Framework for controlling Perps feature availability through LaunchDarkly with l
77
**Key Design Principles:**
88

99
- LaunchDarkly is the single source of truth for feature enablement
10-
- Version-gated flags ensure features only activate on compatible app versions
10+
- Version-gated flags use `validatedVersionGatedFeatureFlag` from `app/util/remoteFeatureFlag` (see [Version-gated feature flags](../readme/version-gated-feature-flags.md))
1111
- Local environment variables provide development/testing fallback
1212
- Graceful degradation when LaunchDarkly is unavailable
1313

@@ -74,6 +74,11 @@ See [Perps A/B Testing Framework](./perps-ab-testing.md) for variant-based flags
7474
**File:** `app/components/UI/Perps/selectors/featureFlags/index.ts`
7575

7676
```typescript
77+
import {
78+
validatedVersionGatedFeatureFlag,
79+
type VersionGatedFeatureFlag,
80+
} from '../../../../util/remoteFeatureFlag';
81+
7782
/**
7883
* Selector for My Feature flag
7984
* Controls visibility of My Feature in the UI
@@ -230,12 +235,14 @@ These flags are managed via `FeatureFlagConfigurationService` and control runtim
230235

231236
### Version Gating
232237

233-
The `minimumVersion` field ensures features only activate on compatible app versions:
238+
The `minimumVersion` field ensures features only activate on compatible app versions. **Do not** compare versions in selectors or components — use `validatedVersionGatedFeatureFlag`:
234239

235-
- **Format:** Semantic version string (e.g., `"7.60.0"`)
236-
- **Comparison:** Uses `compare-versions` library with `>=` operator
240+
- **Format:** Semantic version string (e.g. `"7.60.0"`)
241+
- **Evaluation:** `validatedVersionGatedFeatureFlag(remoteFlag)` returns `true` only when `enabled` is true **and** the native app version is `>= minimumVersion`
237242
- **Use case:** Prevent feature activation on older app versions that lack required code
238243

244+
See [`docs/readme/version-gated-feature-flags.md`](../readme/version-gated-feature-flags.md) for the full pattern and non-standard flag shapes.
245+
239246
---
240247

241248
## Troubleshooting
@@ -249,8 +256,8 @@ The `minimumVersion` field ensures features only activate on compatible app vers
249256
### Version Gating Not Working
250257

251258
1. **Verify `minimumVersion` format:** Must be valid semver string
252-
2. **Check app version:** `getVersion()` from `react-native-device-info`
253-
3. **Check comparison:** Uses `>=` operator
259+
2. **Check native app version:** `getVersion()` from `react-native-device-info` (used inside `validatedVersionGatedFeatureFlag`) — not `package.json`
260+
3. **Confirm selector uses the helper:** `validatedVersionGatedFeatureFlag(remoteFlag) ?? localFallback` — not inline `compare-versions`
254261

255262
### Local Flag Not Overriding
256263

@@ -265,7 +272,8 @@ The `minimumVersion` field ensures features only activate on compatible app vers
265272
- **Selectors:** `app/components/UI/Perps/selectors/featureFlags/index.ts`
266273
- **Mocks:** `app/components/UI/Perps/mocks/remoteFeatureFlagMocks.ts`
267274
- **Tests:** `app/components/UI/Perps/selectors/featureFlags/index.test.ts`
268-
- **Version validation:** `app/util/remoteFeatureFlag/index.ts`
275+
- **Version validation:** `app/util/remoteFeatureFlag/index.ts` (`validatedVersionGatedFeatureFlag`)
276+
- **Version-gated flags guide:** `docs/readme/version-gated-feature-flags.md`
269277
- **Controller init:** `app/core/Engine/controllers/remote-feature-flag-controller-init.ts`
270278
- **Configuration service:** `app/controllers/perps/services/FeatureFlagConfigurationService.ts`
271279

docs/predict/live-nfl-architecture.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@ The Live NFL feature introduces real-time sports betting functionality with WebS
88

99
## Feature Flag
1010

11-
The feature is gated behind `predictLiveNflEnabled`:
11+
The feature is gated behind `predictLiveNflEnabled` using `validatedVersionGatedFeatureFlag` (see [`docs/readme/version-gated-feature-flags.md`](../readme/version-gated-feature-flags.md)):
1212

1313
```typescript
1414
// app/components/UI/Predict/selectors/featureFlags/index.ts
15+
import {
16+
validatedVersionGatedFeatureFlag,
17+
type VersionGatedFeatureFlag,
18+
} from '../../../../util/remoteFeatureFlag';
19+
1520
export const selectPredictLiveNflEnabled = createSelector(
1621
selectRemoteFeatureFlags,
1722
(remoteFeatureFlags) => {

docs/predict/tasks/00-feature-flag-and-types.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ Set up the feature flag for Live NFL and define all necessary TypeScript types f
2727
Create/update `app/components/UI/Predict/selectors/featureFlags/index.ts`:
2828

2929
```typescript
30+
import {
31+
validatedVersionGatedFeatureFlag,
32+
type VersionGatedFeatureFlag,
33+
} from '../../../../util/remoteFeatureFlag';
34+
3035
/**
3136
* Selector for Predict Live NFL feature enablement
3237
*/
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Version-Gated Feature Flags
2+
3+
Canonical guide for evaluating remote feature flags that include a minimum app version. Applies to all teams (Ramp, Perps, Money, Predict, platform selectors, etc.).
4+
5+
## SSOT
6+
7+
| Concern | Location |
8+
| ------------------------------------ | -------------------------------------------------------------------------------------- |
9+
| Version comparison + flag validation | [`app/util/remoteFeatureFlag/index.ts`](../../app/util/remoteFeatureFlag/index.ts) |
10+
| Raw flag storage | `RemoteFeatureFlagController.remoteFeatureFlags` (no client version gating) |
11+
| Boolean for UI / hooks | Selectors under `app/selectors/featureFlagController/` or `**/selectors/featureFlags/` |
12+
13+
**Always use** `validatedVersionGatedFeatureFlag` in selectors. **Never** duplicate `hasMinimumRequiredVersion` or `validatedVersionGatedFeatureFlag` in feature modules.
14+
15+
`hasMinimumRequiredVersion` is a lower-level helper used inside `validatedVersionGatedFeatureFlag`. Import it from the util only when you need a standalone version check — do not reimplement it.
16+
17+
## Flag shapes
18+
19+
### Standard (preferred)
20+
21+
LaunchDarkly JSON:
22+
23+
```json
24+
{
25+
"enabled": true,
26+
"minimumVersion": "7.65.0"
27+
}
28+
```
29+
30+
Progressive-rollout wrappers (`{ name, value: { enabled, minimumVersion } }`) are normalized automatically.
31+
32+
### Non-standard configs
33+
34+
Some flags use different property names (e.g. `depositConfig` uses `active` instead of `enabled`). **Map** to the canonical shape before calling the helper:
35+
36+
```typescript
37+
validatedVersionGatedFeatureFlag({
38+
enabled: depositConfig.active ?? false,
39+
minimumVersion: depositConfig.minimumVersion ?? '',
40+
}) ?? false;
41+
```
42+
43+
### Multi-version flags (controller-processed)
44+
45+
`RemoteFeatureFlagController` resolves flags shaped as `{ versions: { "7.53.0": value, "7.60.0": value } }` at fetch time using `clientVersion`. Consumers read the **processed** value from state — no `validatedVersionGatedFeatureFlag` call needed for that shape.
46+
47+
## Implementation pattern
48+
49+
### 1. Selector (required)
50+
51+
```typescript
52+
import { createSelector } from 'reselect';
53+
import { selectRemoteFeatureFlags } from '../index'; // adjust path
54+
import {
55+
validatedVersionGatedFeatureFlag,
56+
type VersionGatedFeatureFlag,
57+
} from '../../../util/remoteFeatureFlag';
58+
59+
export const selectMyFeatureEnabled = createSelector(
60+
selectRemoteFeatureFlags,
61+
(remoteFeatureFlags) => {
62+
const localFlag = process.env.MM_MY_FEATURE_ENABLED === 'true';
63+
const remoteFlag =
64+
remoteFeatureFlags?.myFeature as unknown as VersionGatedFeatureFlag;
65+
66+
return validatedVersionGatedFeatureFlag(remoteFlag) ?? localFlag;
67+
},
68+
);
69+
```
70+
71+
- Remote flag wins when valid; `undefined` from the helper triggers env/local fallback.
72+
- When `OVERRIDE_REMOTE_FEATURE_FLAGS=true`, the helper returns `undefined` so local overrides apply.
73+
74+
### 2. Hook or component
75+
76+
```typescript
77+
const isEnabled = useSelector(selectMyFeatureEnabled);
78+
```
79+
80+
Do not call `getVersion()`, `compare-versions`, or local version helpers in hooks/components.
81+
82+
## Version source
83+
84+
`validatedVersionGatedFeatureFlag` compares against the **native binary version** from `react-native-device-info`'s `getVersion()` — not `package.json`. After a version bump, do a clean native rebuild if gating behaves unexpectedly.
85+
86+
## Forbidden
87+
88+
- Local `function hasMinimumRequiredVersion(...)` copies
89+
- Inline `compareVersions.compare(getVersion(), minimumVersion, '>=')` for feature flags
90+
- Duplicate util files under `app/components/UI/**/utils/` or `app/core/redux/slices/**/`
91+
92+
## Related
93+
94+
- [Perps feature flags](../perps/perps-feature-flags.md) — team-specific examples
95+
- [Compliance OFAC gating](../compliance.md#feature-flag) — real selector using this pattern
96+
- [`.cursor/rules/version-gated-feature-flags.mdc`](../../.cursor/rules/version-gated-feature-flags.mdc) — Cursor agent rule
97+
- External: [MetaMask remote feature flags](https://github.com/MetaMask/contributor-docs/blob/main/docs/remote-feature-flags.md)

0 commit comments

Comments
 (0)