fix: Preserve existing variables when inserting Marketplace pages

This commit is contained in:
Oleg Isonen committed 2026-11-03 20:35:35 +00:00
1 parent f992d370f0
commit 18d79a608f
3 files changed
+48 -6

No files matched your search

@@ -3,6 +3,7 @@ import { insertWebstudioFragmentAt } from "~/shared/instance-utils/insert";
import {
detectPageTokenConflicts,
extractWebstudioFragment,
preserveExistingRootVariables,
} from "@webstudio-is/project-build/runtime";
import { executeRuntimeMutation } from "~/shared/instance-utils/data";
import { useMemo } from "react";
@@ -101,9 +102,12 @@ const insertPage = async ({
return;
}
const rootStyleTargetData = getWebstudioData();
const rootFragment = extractWebstudioFragment(sourceData, ROOT_INSTANCE_ID);
const safeSourceData = preserveExistingRootVariables({
sourceData,
targetData: rootStyleTargetData,
});
const rootStyleConflictResolution = await resolveRootStyleConflicts({
fragment: rootFragment,
fragment: extractWebstudioFragment(safeSourceData, ROOT_INSTANCE_ID),
targetData: rootStyleTargetData,
});
if (rootStyleConflictResolution === "cancel") {
@@ -116,7 +120,8 @@ const insertPage = async ({
const result = executeRuntimeMutation({
id: "pages.copy",
input: {
sourceData,
sourceData: safeSourceData,
preserveExistingRootVariables: true,
pageId,
parentFolderId: ROOT_FOLDER_ID,
projectId,
@@ -6843,6 +6843,9 @@ export const runtimeOperationContractData = [
type: "string",
enum: ["ours", "theirs"],
},
preserveExistingRootVariables: {
type: "boolean",
},
},
required: ["sourceData", "pageId"],
},
@@ -35,6 +35,7 @@ import {
type WebstudioFragment,
type WebstudioData,
ROOT_INSTANCE_ID,
getStyleDeclKey,
} from "@webstudio-is/sdk";
import type {
@@ -119,6 +120,32 @@ const sourceWebstudioDataInput = z
"Required hydrated WebstudioData from the source project. This is a low-level cross-project transfer command; use duplicate-page to copy a page within the configured project."
);
export const preserveExistingRootVariables = ({
sourceData,
targetData,
}: {
sourceData: WebstudioData;
targetData: WebstudioData;
}): WebstudioData => {
const fragment = extractWebstudioFragment(sourceData, ROOT_INSTANCE_ID);
const conflictingVariables = new Set(
detectFragmentRootStyleConflicts({ fragment, targetData })
.filter(({ incomingStyle }) => incomingStyle.property.startsWith("--"))
.map(({ incomingStyle }) => getStyleDeclKey(incomingStyle))
);
if (conflictingVariables.size === 0) {
return sourceData;
}
return {
...sourceData,
styles: new Map(
Array.from(sourceData.styles).filter(
([key]) => conflictingVariables.has(key) === false
)
),
};
};
export const pageCopyInput = z.object({
projectId: z.string(),
sourceData: sourceWebstudioDataInput,
@@ -133,6 +160,7 @@ export const pageCopyInput = z.object({
.describe(
'How to resolve conflicting global root-local declarations: "ours" keeps the target project values; "theirs" uses the incoming source values. Required when conflicts exist.'
),
preserveExistingRootVariables: z.boolean().optional(),
});
export const folderDuplicateInput = z.object({
@@ -926,19 +954,25 @@ export const copyPage = (
build: data,
assets: Array.from(data.assets.values()),
});
const sourcePage = findPageByIdOrPath(input.pageId, input.sourceData.pages);
const sourceData = input.preserveExistingRootVariables
? preserveExistingRootVariables({
sourceData: input.sourceData,
targetData: before,
})
: input.sourceData;
const sourcePage = findPageByIdOrPath(input.pageId, sourceData.pages);
if (sourcePage === undefined) {
return throwBuilderRuntimeError("BAD_REQUEST", "Page could not be copied");
}
requireRootStyleConflictResolution({
rootFragment: extractWebstudioFragment(input.sourceData, ROOT_INSTANCE_ID),
rootFragment: extractWebstudioFragment(sourceData, ROOT_INSTANCE_ID),
targetData: before,
resolution: input.rootStyleConflictResolution,
});
let pageId: Page["id"] | undefined;
const { payload } = produceWebstudioDataMutation(before, (draft) => {
pageId = insertPageCopyMutable({
source: { data: input.sourceData, pageId: input.pageId },
source: { data: sourceData, pageId: input.pageId },
target: { data: draft, folderId: parentFolderId },
projectId: input.projectId,
conflictResolution: input.conflictResolution,