Migrate modules between shared `src/opensource/` code and enterprise overlays under `enterprise/src/opensource/`, while preserving the `src/opensource` boundary and existing component override points.
src/opensource, enterprise/, Vite overlay, or edition-specific behaviorsrc/opensource/..., but enterprise behavior must override itsrc/variant/... or src/opensource/variant/... facades need to be removedsrc/opensource import boundarysrc/opensource is the shared source of truthsrc/opensource/ is the default implementationsrc/opensource/ must not import commercial codesrc/opensource/ or imports a module outside the tree, move that dependency into the current closure instead of keeping a shimenterprise/src/opensource/opensourceopensourcekeewoodClient or teamshareClient into src/opensourceenterprise/ is an overlay, not a second appenterprise/ as the shared file under src/enterprise/** first, then src/**enterprise/... when it preserves behavior without duplication@/opensource/... and the shim would import from outside the opensource treeenterprise/...src just because enterprise files import them@/opensource/... path it overrides@/opensource/..., the enterprise implementation must live under enterprise/src/opensource/...enterprise/components/... or another non-mirrored path unless the user explicitly wants a commercial-only modulesrc/opensource/ should import @/opensource/... directly@/variant/... in open-source codeenterprise/src/opensource/...Use this rule first:
| Artifact | Preferred mechanism |
|---|---|
| React view/component rendered through factory | ComponentFactory default in src/opensource plus enterprise or premium override where still needed |
| Hook / service / preload helper imported from open-source code | Direct @/opensource/... import plus enterprise/src/opensource/... overlay when behavior differs |
| Shared implementation used by both editions | Move to src/opensource/...; if callers already target @/opensource/..., migrate the real code and delete the old file instead of leaving a compatibility shim |
Legacy src/components/... module becoming dual-edition | Migrate to src/opensource/... plus mirrored enterprise/src/opensource/...; then switch callers to @/opensource/... |
| Runtime-only indirection | src/runtime/<domain>/ only when direct imports and overlay cannot express the binding |
Use this for hooks, services, helpers, and other non-visual modules.
src/opensource/...@/opensource/... path directlyenterprise/src/opensource/...variant facade after callers are switchedMinimal example:
// src/opensource/components/business/RecordingSummary/hooks/useCancelRecord.tsx
export default function useCancelRecord() {
// shared implementation
}
// enterprise/src/opensource/components/business/RecordingSummary/hooks/useCancelRecord.tsx
export default function useCancelRecord() {
// enterprise implementation
}
import useCancelRecord from "@/opensource/components/business/RecordingSummary/hooks/useCancelRecord"
Use this when a UI component needs edition-specific rendering.
src/opensource/...src/opensource/components/ComponentRender/config/defaultComponents.tsxsrc/... only when it owns real delta@/opensource/... imports plus enterprise overlayThis keeps render-time switching and non-render imports aligned.
Use this when the only edition difference is a small injected block inside a much larger shared page or layout.
Rules:
src/opensource/...null, false, no-op callback, or another tiny safe fallbackenterprise/src/opensource/...@/opensource/... facade instead of importing commercial modules directlyWhy:
Example use cases:
EmptyWorkspacePanelMainLayoutSSOLayoutuseFreePointsTriggerUse this only as a short-lived transition when a large enterprise implementation already exists under src/... and the user has not required a hard opensource-only closure.
enterprise/src/opensource/<same-path>.ts(x)src/...@/opensource/...variant filesDo not use this pattern when the user requires src/opensource/... and enterprise/src/opensource/... to avoid importing any dependency outside the opensource tree.
Use this when a module and its dependencies only exist for enterprise or App-native behavior.
Rules:
enterprise/src/opensource/...src/opensource stubs for enterprise-only stores or services unless the user explicitly wants a temporary bridge@/opensource/... path, so overlay resolves the real filesrc/... files once imports are switchedExample closure:
stores/recordingSummary/appNative.tsservices/recordSummaryAppNativeService/**services/AppAIRecordingService/**Why:
src recreates the old boundary violationUse when a hook or component is moved entirely to enterprise/src/opensource/..., but src/opensource callers still import it (e.g. overlay disabled, or callers exist in both editions).
Rules:
src/opensource/... at the same logical pathfalse for flags, () => {} for callbacks, null for ReactNodeStub minimization rules:
src/opensource/... if the stub does not use themExample:
// src/opensource/.../useCollaboratorUpdatePanel.tsx (stub)
function useCollaboratorUpdatePanel({ selectedProject: _sp, onClose: _onClose }) {
return {
collaborators: [],
collaborationInfo: { is_collaboration_enabled: false, default_join_permission: "viewer" },
openManageModal: () => {},
CollaboratorUpdatePanel: null,
}
}
// enterprise/src/opensource/.../useCollaboratorUpdatePanel.tsx (full impl)
// Full implementation with CollaborationManageModal, useCollaborationManageData, etc.
Use only when direct imports plus overlay are still not enough.
Rules:
src/runtime/<domain>/src/opensource/ off that runtime facade unless the user explicitly wants the extra indirectionWhen creating an enterprise overlay file (e.g. enterprise/src/opensource/.../ProjectCardContainer), relative imports like ../ProjectCard may fail TypeScript resolution if the target has no enterprise overlay (only partial overlay like ProjectCardShareSection).
Rule: use @/opensource/... absolute paths for modules that exist only in src or have no full enterprise overlay, so resolution falls through to src correctly.
Before moving or stubbing a module, run rg "ModuleName|useModuleName" (or equivalent) to find every caller.
Rule: each importer must either (a) get the stub when overlay is disabled, or (b) be updated to conditionally use the feature. Missing an importer causes build failures such as EISDIR or "module not found".
Before creating files, decide which import path should survive after migration.
Ask:
@/opensource/...?@/components/...?src/opensource/... under enterprise/src/opensource/...?Rule: do not start by copying code into enterprise/components/... when the long-term target is a dual-edition @/opensource/... path.
Before creating any overlay file, ask:
null or no-op facade?Rule: if the delta is only a small block inside a large shared file, first extract that block into a shared src/opensource/... facade and override only that facade in enterprise/src/opensource/....
Split the target into:
variant facades that can be removedAsk:
src/opensource still point at indirection instead of the real shared file?src/opensource?src/opensourcesrc/opensource/...@/opensource/...opensource, migrate that dependency into the same closure or inline the needed code before proceedingenterprise/src/opensource/... only for real behavior differencesopensource boundarysrc/... but callers must stay inside opensource, migrate the real implementation or the required subset into the mirrored enterprise/src/opensource/... pathsrc and enterprisevariantWhen old code uses @/variant/...:
@/opensource/... importenterprise/src/opensource/... overlays for any enterprise-only behaviorsrc/variant/...src/opensource/variant/...For the migrated domain, check:
src/opensource/... only imports shared-safe dependenciessrc/opensource file imports @/variant/...enterprise/src/opensource/...src/opensource still avoids private clients and private infrastructure@/opensource/... path that runtime code conceptually depends onsrcvariant pathssrc/opensource does not import commercial modules directlysrc/opensource does not import @/variant/...src/opensourceenterprise/src/opensource/...srckeewoodClient and teamshareClient are not introduced into src/opensourcesrc/opensource and enterprise/src/opensource do not import modules outside the opensource tree unless the user explicitly approves a temporary exceptionrg before migration@/variant/... importssrc@/components/business/<module>variant after the overlay existsThis preserves dead indirection and makes the import graph harder to reason about.
Rule: if direct @/opensource/... plus mirrored enterprise/... works, delete variant.
src/opensource import commercial codeThis breaks the hard boundary.
Rule: only enterprise/... may point back to commercial implementations as migration shims.
Render-time code may be correct while preload or utility code still points at old facades.
Rule: migrate preload helpers and utility imports together with the main implementation.
Large duplicate files increase drift immediately.
Rule: prefer a thin enterprise re-export shim first, then refactor if needed.
This invites regressions because new code can accidentally import the pre-migration path again.
Rule: once callers are updated to @/opensource/..., delete the old src/components/... source files for that migrated closure.
If a feature depends on private infrastructure, the shared layer should not import it.
Rule: degrade or stub the open-source path instead of copying the client.
srcThis makes the import graph look shared when the implementation is not actually shared.
Rule: if the module only exists for enterprise/App-native behavior, move the full chain into enterprise/src/opensource/....
A shared initializer may still dynamically import files that were intentionally removed from src.
Rule: keep the shared initializer web-safe, and add an enterprise overlay initializer when native restore logic must remain.
If enterprise/src/opensource/foo.ts re-exports from @/opensource/foo, overlay resolution points back to the same enterprise file.
Rule: put the real enterprise implementation in the overlay file, or use a temporary non-overlay source path only during an explicit short-lived migration step.
vitePluginEnterpriseOverlay may be commented out; the build then uses only src. Enterprise overlay files are never loaded.
Rule: when moving hooks/components to enterprise/src/opensource/... only, keep open-source stubs in src/opensource/... that satisfy all imports. Otherwise the build fails (e.g. EISDIR: illegal operation on a directory when importing a deleted module).
Open-source stubs must return the exact same interface as the enterprise implementation.
Rule: hooks must return the same shape (e.g. collaborators: [], collaborationInfo: { ... }, openManageModal: () => {}, CollaboratorUpdatePanel: null). Components must export the same default and accept the same props.
If callers are meant to import @/opensource/..., placing the real file under enterprise/components/... breaks the mirrored overlay model and leaves the import graph inconsistent.
Rule: dual-edition overlays for shared modules belong under enterprise/src/opensource/....
Copying all enterprise files, types, and helpers into src/opensource/... increases drift and makes the stub look like a real shared implementation when it is not.
Rule: keep the stub minimal. Export only what open-source callers need right now.
A local helper such as utils/env.ts may duplicate an existing global utility and create extra cleanup work during migration.
Rule: prefer shared utilities like @/opensource/utils/env when they already express the same behavior.
src/opensource by mistakeThis usually happens when the user says "enterprise only" or "open-source should not include this", but the migration starts from caller paths instead of feature ownership.
Rule:
src/opensource/... may contain only the shared facade or no-op stub for that featureenterprise/src/opensource/... pathsrc/opensource/...Copying an entire page or layout just to swap one banner or modal creates unnecessary drift immediately.
Rule: extract the variable block into a dedicated shared child component or hook first, then override only that child in enterprise/src/opensource/....
Use RecordingSummary as the reference implementation:
src/opensource/... is the shared baseline@/opensource/... directlyuseCancelRecord and useIsCurrentRecording behavior is exposed through enterprise/src/opensource/...enterprise/src/opensource/...initRecordSummaryService is split so shared code keeps only web restore logic and enterprise overlay restores native logicpreloadRecordSummaryEditorPanel no longer needs a variant facadesrc/variant/... and src/opensource/variant/... files are deleted after migrationWhen using this skill, produce:
@/opensource/... imports over extra facades