feat(feature-flags): feature flags as primary authority with remoteComponents flag - #5559
Merged
Conversation
… next Re-applied remote-components and sdk-nextjs changes on top of the latest next branch. Resolved renames (handler-graphql -> api-graphql, project-template-base extraction) and fixed lint/dependency issues.
… components - Add remoteComponents?: boolean to IFeatureFlagsDto and isRemoteComponentsEnabled() to FeatureFlags class - Create FeatureFlagsContext with module-level store, FeatureFlagsProvider, and useProjectFeatureFlags hook - Create LicenseDecoratedFeatureFlags extending FeatureFlags with WCP license intersection - Create FeatureFlag.CanUse* components in @webiny/project (CanUseRemoteComponents, CanUseMultiTenancy, etc.) - Wire FeatureFlagsProvider into renderConfigWorker.tsx - Update FeatureFlags extension to populate FeatureFlagsContext during render - Gate RemoteComponents with FeatureFlag.CanUseRemoteComponents - Restructure webiny.config.base.tsx: WebinyConfigTsx renders before RemoteComponents - Migrate config-level Wcp.CanUse* to FeatureFlag.CanUse* (TenantManager, Languages, AiPowerups, webiny.config.tsx) - Deprecate config-level Wcp.CanUse* components in @webiny/project - Wire remoteComponents through WCP layer (ILicense, License, NullLicense, WcpContext, decorator, GraphQL, ReactLicense) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lags migration Phase 2: API feature flags manifest - Create FeatureFlagsSchemaFactory exposing featureFlags GraphQL query - Create FeatureFlagsWithLicenseDecorator on API FeatureFlags abstraction - Register decorator in WcpFeature, schema factory in ApiCoreFeature Phase 3: Admin loads feature flags - Create FeatureFlagsGateway, FeatureFlagsService, FeatureFlagsFeature - Create FeatureFlagsProvider and useFeatureFlags hook - Register in createRootContainer, add provider in Admin.tsx - Migrate 13 consumer files from useWcp() to useFeatureFlags() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…icenseDecorator Match build-level behavior: without a license, license-governed flags are forced to false (NullLicense.canUse*() returns false). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lags - app-workflows: Wcp.CanUseWorkflows → useFeatureFlags().isWorkflowsEnabled() - app-website-builder-workflows: same - app-headless-cms-workflows: same - app-file-manager: Wcp.CanUsePrivateFiles → extracted PrivateFilesBulkEditField - app-admin AccessManagementExtension: Wcp.CanUseTeams → extracted TeamsRoute/TeamsMenuItem - app-website-builder LanguageCodeTag: Wcp.CanUseMultiTenancy → useFeatureFlags() Zero Wcp.CanUse* usages remain outside backward-compat definition. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Delete packages/app-admin/src/components/Wcp.tsx (no consumers) - Delete packages/project/src/components/Wcp.tsx (no consumers) - Remove Wcp export from app-admin components/index.ts - Remove Wcp export from project index.ts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rom admin - Remove WcpProvider from Admin.tsx (no longer wraps the app) - Remove WcpFeature registration from createRootContainer - Comment out useWcp() in WebinyVersion.tsx (2 files) and Menus.tsx - Remove useWcp export from app-admin index - Remove useWcp re-export from app-serverless-cms The admin app no longer fires the GetWcpProject GraphQL query. Feature availability is now driven entirely by the featureFlags query. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…icenseDecorator Use WcpLicenseProvider directly instead of WcpContext to avoid the cycle: FeatureFlags → FeatureFlagsWithLicenseDecorator → WcpContext → WcpContextWithFeatureFlagsDecorator → FeatureFlags Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…reFlags Migrate 14 files from WcpContext.canUse*() to FeatureFlags.get().is*Enabled(): - api-record-locking, api-workflows, api-website-builder-workflows - api-audit-logs, api-file-manager, api-file-manager-s3 - api-core (IdentityContext, GetPermissionsFromIdentity) - api-aco (FolderLevelPermissions) - ai-powerups (AiImageEnrichment, BaseGraphQLSchema) - app-aco (GetFolderLevelPermissionWithFlpDecorator) Fix structuredClone failure on MobX proxies in FeatureFlags.toDto() by using JSON.parse(JSON.stringify()) instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add toResolvedDto() to FeatureFlags that returns a complete IFeatureFlagsDto with all flags explicitly set (using is*Enabled() methods), not just the user overrides. The featureFlags GraphQL query now returns the full picture. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All flags in toResolvedDto() are now flat boolean keys — no nested objects for aiPowerups, fileManager, or advancedAccessControlLayer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ptions envelope Structure preserved (aiPowerups, fileManager, advancedAccessControlLayer are nested objects) but the enabled/options wrapper is removed — children are direct boolean values. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…olvedDto When aiPowerups or advancedAccessControlLayer is disabled, return false instead of an object with all children set to false. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… aiPowerups Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… at config level Add CanUseAiPowerups to FeatureFlag components. Wrap AiPowerups extension with it in AiPowerups.tsx so the entire extension (API + Admin) doesn't mount when aiPowerups is disabled. Individual sub-feature checks happen inside the extension when the parent flag passes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ons)
Change aiPowerups from { enabled, options } envelope to boolean | IAiPowerupsOptions,
matching the advancedAccessControlLayer pattern. Now supports:
- aiPowerups: false (disable entirely)
- aiPowerups: { websiteBuilder: { pageGeneration: false } } (sub-feature control)
- omit (enabled by default)
Remove unused IAiPowerupsFeatureFlags interface.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e config render
The renderConfigWorker now checks for webiny.features.ts at the project
root and loads it before rendering the config tree. This ensures feature
flags are set before any FeatureFlag.CanUse* component reads them,
regardless of render order.
The file exports a typed IFeatureFlagsDto object with full autocomplete:
export default { aiPowerups: false } satisfies IFeatureFlagsDto;
Move feature flags from webiny.config.tsx <Project.FeatureFlags> to
webiny.features.ts. Fix AiPowerups to use CanUseAiPowerups (not
CanUseMultiTenancy).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The feature flags file exports a React component that renders <Project.FeatureFlags>. The render worker mounts it before the main config tree so setProjectFeatureFlags() fires during render, before any FeatureFlag.CanUse* reads the flags. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nstead of separate file
The template imports { FeatureFlags } from webiny.config.tsx and renders
it before DefaultExtensions. If FeatureFlags is not exported, null is
rendered. No separate webiny.features.tsx file needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ags are set FeatureFlagsGate renders children only after setProjectFeatureFlags() has been called. In webiny.config.base.tsx, <FeatureFlags /> renders first (setting the flags), then FeatureFlagsGate opens and extensions mount with the correct flag values. If no FeatureFlags component is exported, skip=true bypasses the gate. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace module-level variable + notification callback with a MobX observable store. FeatureFlagsProvider and FeatureFlagsGate are MobX observers — they re-render automatically when setProjectFeatureFlags() updates the store. Remove debug logs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…decorator - toDto() now returns the fully resolved state (all flags explicitly set) - Remove toResolvedDto() — toDto() is the single method - API FeatureFlagsWithLicenseDecorator uses class extension pattern (overrides is*Enabled methods) instead of DTO mutation + fromDto() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… isEnabled(name)
Single method: featureFlags.isEnabled('recordLocking') replaces
isRecordLockingEnabled(). Dot-path for nested flags:
isEnabled('aiPowerups.cms.entryGeneration').
KnownFeatureFlag union type provides autocomplete for known flag names.
FeatureFlagName = KnownFeatureFlag | (string & {}) allows custom flags.
Decorators override one method (isEnabled) with a LICENSE_CHECKS map.
Adding a new flag: update IFeatureFlagsDto + KnownFeatureFlag + Zod.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… link with feature flags
Delete both WebinyVersion.tsx files (unused).
Restore Upgrade menu link in Menus.tsx using featureFlags.isEnabled('multiTenancy')
instead of wcp.getProject() — show Upgrade when multiTenancy is disabled.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The bulk action extension depends on CmsGenerateEntryContentUseCase from ai-powerups. When aiPowerups is disabled, the use case is not registered, causing a DI resolution error on /cms/manage. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…witch - Base isEnabled() returns false for undefined flags (disabled by default) - License decorator: no license → all false; license blocks → false; license allows → config decides; not license-governed → needs license to exist, then config decides - Config can disable what license allows, but cannot enable what license blocks Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use namespace import to check if FeatureFlags exists in webiny.config.tsx instead of named import that throws when the export is missing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Same pattern as AWS template: optional FeatureFlags import, FeatureFlagsGate wrapping all extensions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add isExplicitlyDisabled() to distinguish 'not set' from 'set to false'. When license allows a flag, it's enabled unless config explicitly disables it. Config undefined = license decides. Config false = disabled even if licensed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Not license-governed yet — WCP backend doesn't know about it. Without the entry, it falls through to 'license exists + config decides'. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts: # .github/workflows/wac/utils/runNodeScripts/slopCop.js
|
🚓 Slop Cop ✅ Nothing worth flagging. The diff looks consistent with the PR's stated intent and the code-style rules. Automated, non-blocking heads-up from an LLM. It can be wrong — use your judgment. Regenerates on every push. |
IdentityContext now depends on FeatureFlags after migration from WcpContext.canUse*(). Add BuildParamsFeature + FeatureFlagsFeature to the test container setup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sService Replace WcpService mock with FeatureFlagsService mock after migration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The bundler produces IIFE format (consumed via new Function() eval), but the test used RemoteComponentLoader which does import() (expects ESM). Replace with eval pattern matching how GraphQLRemoteComponentLoader and the admin sandbox actually consume bundles. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
webiny.config.tsx) is the switch within the gate. Admin loads afeatureFlagsGraphQL query instead ofwcp.getProject.isEnabled("name")API with dot-path support (isEnabled("aiPowerups.cms.entryGeneration")).KnownFeatureFlagunion provides autocomplete. No per-flag methods or decorator overrides needed.remoteComponentsfeature flag added — gates the entire Remote Components extension (API + Admin) via<FeatureFlag.CanUseRemoteComponents>.aiPowerupssimplified toboolean | optionsunion (no moreenabled/optionsenvelope).Remote Components
Remote Components is a new enterprise feature that allows developers to create, edit, and bundle React components directly from the Webiny Admin UI, then load and render them in a Next.js frontend at runtime.
Key capabilities:
GraphQLRemoteComponentLoaderfetches and evaluates bundled components via the Webiny SDKThe
remoteComponentsfeature flag gates the entire extension — when disabled, no GraphQL schema is registered and no admin UI (menu, routes, editor) is mounted.Feature flags decision flow
Key changes
featureFlagsGraphQL query returns fully resolved flagsuseFeatureFlags()replacesuseWcp()in admin (~35 files migrated)FeatureFlag.CanUse*components replaceWcp.CanUse*at config levelFeatureFlagsGate+ MobX store ensures correct render timingFeatureFlagsexport fromwebiny.config.tsxrenders before extensionsWcp.CanUse*components andWcpProviderfrom adminWcpContext.canUse*()consumers migrated toFeatureFlagsLICENSE_CHECKSmap — single override point for license decorationskills/repo-skills/add-feature-flag/SKILL.mdTest plan
recordLocking: falsein config → disabled despite license allowing itremoteComponents: false→ Components menu and API schema absentFeatureFlagsexport fromwebiny.config.tsx→ no error, license decidesyarn checkpasses (149 packages, only pre-existing@webiny/iconsfailure)🤖 Generated with Claude Code