Code Review Bench PR #26801 - feat: add scope configuration for feature opt-in#13
Code Review Bench PR #26801 - feat: add scope configuration for feature opt-in#13ketkarameya wants to merge 8 commits intobase_pr_26801_20260125_1768from
Conversation
Add scope field to OptInFeatureConfig that allows features to be scoped to specific levels (org, team, user). This enables features to be shown only at certain settings pages rather than all three. Changes: - Add OptInFeatureScope type with values 'org', 'team', 'user' - Add optional scope field to OptInFeatureConfig interface - Add getOptInFeaturesForScope helper function to filter features by scope - Update FeatureOptInService to filter features based on scope - Update tRPC router to pass scope parameter for org/team endpoints Features without a scope field default to all scopes for backward compatibility. Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
…tate - Add isFeatureAllowedForScope helper function to check if a feature is allowed for a scope - Update setUserFeatureState to reject if feature is not scoped to 'user' - Update setTeamFeatureState to accept scope parameter and reject if feature is not allowed - Update tRPC router to pass scope parameter for team and org endpoints - Fix unit test mock to include new config exports Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
- Replace raw Error with ErrorWithCode using ErrorCode.BadRequest - Add comprehensive tests for setUserFeatureState scope validation - Add comprehensive tests for setTeamFeatureState scope validation - Test both enabled/disabled and inherit state scenarios - Test error messages include feature ID and scope name Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
…on tests Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
Code Review 🚫 Blocked 0 resolved / 5 findingsThree critical bugs introduced: inverted globalEnabled filter in 🚨 Bug: Inverted filter:
|
| Auto-apply | Compact |
|
|
Was this helpful? React with 👍 / 👎 | Gitar
| }); | ||
|
|
||
| return featureIds.map((featureId) => resolvedStates[featureId]).filter((state) => state.globalEnabled); | ||
| return featureIds.map((featureId) => resolvedStates[featureId]).filter((state) => !state.globalEnabled); |
There was a problem hiding this comment.
🚨 Bug: Inverted filter: listFeaturesForUser returns only disabled features
The filter on line 207 was changed from state.globalEnabled to !state.globalEnabled, which inverts the logic. This causes listFeaturesForUser to return only features that are globally disabled instead of globally enabled.
The method's own JSDoc says "Only returns features that are in the allowlist, globally enabled, and scoped to 'user'", and listFeaturesForTeam uses .filter((result) => result.globalEnabled) (line 242) — confirming this is a bug.
Users will see no features in their settings when features are properly enabled, and will see features that are disabled (which should be hidden).
Was this helpful? React with 👍 / 👎
| return featureIds.map((featureId) => resolvedStates[featureId]).filter((state) => !state.globalEnabled); | |
| return featureIds.map((featureId) => resolvedStates[featureId]).filter((state) => state.globalEnabled); |
- Apply suggested fix
| ); | ||
| } | ||
|
|
||
| if (state !== "inherit") { |
There was a problem hiding this comment.
🚨 Bug: Swapped branches in setUserFeatureState corrupt saved data
The condition on line 264 was changed from state === "inherit" to state !== "inherit", which swaps the two branches:
- When state is
"enabled"or"disabled"(needsassignedBy), it now takes theelsebranch and tries to destructureassignedByfrom the input — this works but saves it withassignedByeven though the simple (no-assignedBy) branch was intended. - When state is
"inherit"(noassignedByfield), it now takes theifbranch and callssetUserFeatureState({ userId, featureId, state })withoutassignedBy, losing attribution.
More critically, the else branch destructures assignedBy from input — when state === "inherit", the input type union doesn't include assignedBy, so assignedBy will be undefined, resulting in assignedBy: "user-undefined" being persisted to the database.
The sibling method setTeamFeatureState (line 297) uses the correct state === "inherit" condition, confirming this is a regression.
Was this helpful? React with 👍 / 👎
| if (state !== "inherit") { | |
| if (state === "inherit") { | |
| await this.featuresRepository.setUserFeatureState({ userId, featureId, state }); | |
| } else { |
- Apply suggested fix
| } | ||
|
|
||
| await featureOptInService.setUserFeatureState({ | ||
| featureOptInService.setUserFeatureState({ |
There was a problem hiding this comment.
🚨 Bug: Missing await on setUserFeatureState — fire-and-forget mutation
In the tRPC setUserState mutation (line 103), the await was removed from the featureOptInService.setUserFeatureState() call. This means:
- The mutation returns
{ success: true }immediately before the database write completes. - If the
setUserFeatureStatepromise rejects (e.g., scope validation error, DB failure), the error is silently swallowed — the client receives a success response. - This is a fire-and-forget pattern on a data-mutation endpoint, which violates user expectations of data consistency.
The sibling endpoints setTeamState (line 131) and setOrganizationState (line 161) both correctly use await.
Was this helpful? React with 👍 / 👎
| featureOptInService.setUserFeatureState({ | |
| await featureOptInService.setUserFeatureState({ |
- Apply suggested fix
There was a problem hiding this comment.
Configure merge blocking · Maintainers can dismiss this review. Gitar never approves changes.
Benchmark PR for Gitar evaluation
Original PR: agentic-review-benchmarks#14
feat: add scope configuration for feature opt-in
Summary by Gitar
OptInFeatureScopeintypes.tsdefines three configuration levels: org, team, and usergetOptInFeaturesForScopeandisFeatureAllowedForScopehelpers filter features by scope with backward-compatible defaultsHAS_USER_OPT_IN_FEATURES,HAS_TEAM_OPT_IN_FEATURES,HAS_ORG_OPT_IN_FEATUREScontrol UI visibilityFeatureOptInServicevalidates scope before state changes withErrorWithCodefor unauthorized operations_router.tsexplicitly pass scope parameters for team and org contextsSettingsLayoutAppDirClient.tsxuses scope-specific flags to conditionally render Features menuThis will update automatically on new commits.