Skip to content

feat(cells): Remove cross-org feature gating from notification settings#115829

Merged
lynnagara merged 3 commits into
masterfrom
org-list-features
May 20, 2026
Merged

feat(cells): Remove cross-org feature gating from notification settings#115829
lynnagara merged 3 commits into
masterfrom
org-list-features

Conversation

@lynnagara
Copy link
Copy Markdown
Member

@lynnagara lynnagara commented May 19, 2026

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 in saas deployments. They are not shown in self-hosted and dev.

…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.
@lynnagara lynnagara requested a review from a team as a code owner May 19, 2026 18:26
@github-actions github-actions Bot added the Scope: Frontend Automatically applied to PRs that change frontend components label May 19, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 19, 2026

📊 Type Coverage Diff

✅ No new type safety issues introduced. Coverage: 93.56%

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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".

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 45f9370. Configure here.

@lynnagara lynnagara requested a review from a team May 19, 2026 18:38
@lynnagara lynnagara changed the title feat(cells): Remove cross-org featuring gating from notification settings feat(cells): Remove cross-org feature gating from notification settings May 19, 2026
Comment on lines -61 to -64
if (type === 'quota' && checkFeatureFlag('spend-visibility-notifications')) {
field.label = t('Spend');
field.help = t('Notifications that help avoid surprise invoices.');
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not all orgs will have access to spend-visibility. Would we be able to use the org details to make a decision here?

Copy link
Copy Markdown
Member Author

@lynnagara lynnagara May 19, 2026

Choose a reason for hiding this comment

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

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

@lynnagara lynnagara merged commit 958fab1 into master May 20, 2026
71 checks passed
@lynnagara lynnagara deleted the org-list-features branch May 20, 2026 17:46
JonasBa pushed a commit that referenced this pull request May 21, 2026
…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.
lynnagara added a commit that referenced this pull request May 27, 2026
…#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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants