feat(cells): Remove cross-org feature gating from notification settings#115829
Conversation
…ings NotificationSettings is a per-user account page, but it was deciding which entries to show by reading feature flags off every org in OrganizationsStore — "does *any* of my orgs have feature X?" — to toggle the quota and spikeProtection entries and to relabel "Quota" as "Spend". There are two main reasons to move away from this model: - Features are org-scoped state, not user-scoped. Folding them across all of a user's orgs via .some() produces gates that don't reflect what any specific org can do. A single privileged membership flips the gate for all orgs the user belongs to. - The store is fed from the /organizations/ listing, which is moving to the control silo as part of cells. Features are not available through that endpoint without an expensive cross-cell fan-out so this data will not be available in control. This change now shows the "quota" and "spike protection" options unconditionally in the UI regardless of org membership.
📊 Type Coverage Diff✅ No new type safety issues introduced. Coverage: 93.56% |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 45f9370. Configure here.
| type NotificationFields = z.infer<typeof notificationSchema>; | ||
|
|
||
| export function NotificationSettings() { | ||
| const {organizations} = useLegacyStore(OrganizationsStore); |
There was a problem hiding this comment.
Quota/Spend label inconsistency between listing and detail pages
Medium Severity
The removed conditional relabeling of "Quota" → "Spend" creates a UX inconsistency. The main notification settings page now always shows the label "Quota" (from NOTIFICATION_SETTING_FIELDS), but notificationSettingsByType.tsx still conditionally relabels the detail page title to "Spend Notifications" and switches to SPEND_FIELDS when spend-visibility-notifications is present. Users with that flag see "Quota" on the listing, then "Spend Notifications" after clicking "Manage".
Reviewed by Cursor Bugbot for commit 45f9370. Configure here.
| if (type === 'quota' && checkFeatureFlag('spend-visibility-notifications')) { | ||
| field.label = t('Spend'); | ||
| field.help = t('Notifications that help avoid surprise invoices.'); | ||
| } |
There was a problem hiding this comment.
Not all orgs will have access to spend-visibility. Would we be able to use the org details to make a decision here?
There was a problem hiding this comment.
I updated to filter this out for self-hosted/dev environments where spend visibility isn't relevant
In saas, i believe we never want to hide this:
- the field isn't available in OrganizationMapping or anywhere else in the control silo
- this UI is not org scoped so even if a user belongs to some organizations with the flag and some without it is kind of inconsistent anyway
- the only users in prod where this wouldn't be hidden are users with only organizations from pre-AM1 plan days and no new ones -- an increasingly tiny set
…gs (#115829) NotificationSettings is a per-user account page, but it was deciding which entries to show by reading feature flags off every org in OrganizationsStore — "does *any* of my orgs have feature X?" — to toggle the quota and spikeProtection entries and to relabel "Quota" as "Spend". There are two main reasons to move away from this model: - Features are org-scoped state, not user-scoped. Folding them across all of a user's orgs via .some() produces gates that don't reflect what any specific org can do. A single privileged membership flips the gate for all orgs the user belongs to. - The store is fed from the /organizations/ listing, which is moving to the control silo as part of the cellularization work. The `features` list is not available through that endpoint without an expensive cross-cell fan-out so this data will not be available in control. This change now shows the "quota" and "spike protection" options unconditionally in the UI regardless of org membership.
…#115937) The org listing endpoint returns the smaller OrganizationSummary type not the full Organization. OrganizationSummary is a strict subset of organization and does not include a number of fields, such as `features` (as these can no longer be returned from the control silo). Since `features` is not present on OrganizationSummary, this change unconditionally shows every category on the notifications list page, and doesn't hide any. This seems the safer direction to go, as the notificationSettingsByType component is primarily just a list of links -- the actual orgs that the change would be applied to is configured on the linked view. The exception to this pattern is self-hosted. This follows the pattern in #115829 which hid the `quota` entry from the notification settings index on self-hosted. This PR adds the same `isSelfHosted` gate so a direct link to the quota page returns null on self-hosted instead of rendering categories that don't apply.


NotificationSettings is a per-user account page, but it was deciding which entries to show by reading feature flags off every org in OrganizationsStore — "does any of my orgs have feature X?" — to toggle the quota and spikeProtection entries and to relabel "Quota" as "Spend".
There are two main reasons to move away from this model:
featureslist is not available through that endpoint without an expensive cross-cell fan-out so this data will not be available in control.This change now shows the "quota" and "spike protection" options unconditionally in the UI regardless of org membership in saas deployments. They are not shown in self-hosted and dev.