|
| 1 | +--- |
| 2 | +description: Use shared version-gated feature flag helpers; never duplicate hasMinimumRequiredVersion or validatedVersionGatedFeatureFlag |
| 3 | +globs: app/**/*.{ts,tsx} |
| 4 | +alwaysApply: false |
| 5 | +--- |
| 6 | + |
| 7 | +# Version-Gated Feature Flags |
| 8 | + |
| 9 | +## Canonical source (SSOT) |
| 10 | + |
| 11 | +All version-gated feature flag logic lives in `app/util/remoteFeatureFlag/index.ts`: |
| 12 | + |
| 13 | +- `validatedVersionGatedFeatureFlag` — preferred for remote flags shaped as `{ enabled, minimumVersion }` (or progressive-rollout wrappers) |
| 14 | +- `hasMinimumRequiredVersion` — version comparison only; import when needed, never redefine |
| 15 | + |
| 16 | +## Required patterns |
| 17 | + |
| 18 | +### ✅ Use `validatedVersionGatedFeatureFlag` in selectors |
| 19 | + |
| 20 | +```typescript |
| 21 | +import { validatedVersionGatedFeatureFlag, VersionGatedFeatureFlag } from '../../../util/remoteFeatureFlag'; |
| 22 | + |
| 23 | +export const selectMyFeatureEnabled = createSelector( |
| 24 | + selectRemoteFeatureFlags, |
| 25 | + (remoteFeatureFlags) => { |
| 26 | + const remoteFlag = remoteFeatureFlags?.myFeature as unknown as VersionGatedFeatureFlag; |
| 27 | + return validatedVersionGatedFeatureFlag(remoteFlag) ?? false; |
| 28 | + }, |
| 29 | +); |
| 30 | +``` |
| 31 | + |
| 32 | +### ✅ Non-standard flag shapes — map, don't duplicate |
| 33 | + |
| 34 | +Configs that use `active` instead of `enabled` (e.g. `depositConfig`) must map to the canonical shape and call the shared helper: |
| 35 | + |
| 36 | +```typescript |
| 37 | +import { validatedVersionGatedFeatureFlag } from '../../../util/remoteFeatureFlag'; |
| 38 | + |
| 39 | +return ( |
| 40 | + validatedVersionGatedFeatureFlag({ |
| 41 | + enabled: depositConfig.active ?? false, |
| 42 | + minimumVersion: depositConfig.minimumVersion ?? '', |
| 43 | + }) ?? false |
| 44 | +); |
| 45 | +``` |
| 46 | + |
| 47 | +### ✅ Import `hasMinimumRequiredVersion` only from the util |
| 48 | + |
| 49 | +```typescript |
| 50 | +import { hasMinimumRequiredVersion } from '../../../util/remoteFeatureFlag'; |
| 51 | +``` |
| 52 | + |
| 53 | +Never copy the `compare-versions` + `getVersion` pattern into feature modules. |
| 54 | + |
| 55 | +## Forbidden patterns |
| 56 | + |
| 57 | +### ❌ Do not define local version-gating helpers |
| 58 | + |
| 59 | +```typescript |
| 60 | +// FORBIDDEN — duplicate of app/util/remoteFeatureFlag |
| 61 | +function hasMinimumRequiredVersion(minVersion: string) { ... } |
| 62 | +const validatedVersionGatedFeatureFlag = (flag: unknown) => { ... } |
| 63 | +``` |
| 64 | + |
| 65 | +### ❌ Do not inline version checks for feature flags |
| 66 | + |
| 67 | +```typescript |
| 68 | +// FORBIDDEN |
| 69 | +import { getVersion } from 'react-native-device-info'; |
| 70 | +import compareVersions from 'compare-versions'; |
| 71 | + |
| 72 | +const ok = compareVersions.compare(getVersion(), flag.minimumVersion, '>='); |
| 73 | +``` |
| 74 | + |
| 75 | +### ❌ Do not add duplicate utils under feature folders |
| 76 | + |
| 77 | +Do not create files like: |
| 78 | + |
| 79 | +- `app/components/UI/**/utils/hasMinimumRequiredVersion.ts` |
| 80 | +- `app/core/redux/slices/**/hasMinimumRequiredVersion.ts` |
| 81 | + |
| 82 | +## Where logic belongs |
| 83 | + |
| 84 | +| Layer | Responsibility | |
| 85 | +|-------|----------------| |
| 86 | +| `RemoteFeatureFlagController` | Fetch/store raw flags — **no** client version gating | |
| 87 | +| `app/util/remoteFeatureFlag` | Version comparison + `validatedVersionGatedFeatureFlag` | |
| 88 | +| Selectors (`featureFlagController`, `**/selectors/featureFlags`) | Call shared helpers; return `boolean` | |
| 89 | +| Hooks / components | `useSelector(selectXEnabled)` — not local version math | |
| 90 | + |
| 91 | +## Enforcement |
| 92 | + |
| 93 | +- **REJECT** new local definitions of `hasMinimumRequiredVersion` or `validatedVersionGatedFeatureFlag` |
| 94 | +- **REJECT** new `compare-versions` + `getVersion` usage for feature-flag gating outside `app/util/remoteFeatureFlag` |
| 95 | +- **REQUIRE** imports from `app/util/remoteFeatureFlag` for all version-gated flag checks |
0 commit comments