webstudio 0.297.0 → 0.298.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cli.js +239 -103
- package/lib/content-runtime.js +28 -28
- package/package.json +20 -20
- package/templates/cloudflare/package.json +2 -2
- package/templates/defaults/package.json +8 -8
- package/templates/react-router/package.json +8 -8
- package/templates/react-router-cloudflare/package.json +1 -1
- package/templates/saas-helpers/wrangler.jsonc +7 -0
- package/templates/ssg/package.json +6 -6
package/lib/cli.js
CHANGED
|
@@ -17167,16 +17167,9 @@ const validateAssetQuery$1 = ({
|
|
|
17167
17167
|
}
|
|
17168
17168
|
}
|
|
17169
17169
|
if (query.output.mode === "fields") {
|
|
17170
|
-
for (const
|
|
17170
|
+
for (const fieldPath of query.output.fields) {
|
|
17171
17171
|
const catalogPath = getCatalogPath(fieldPath);
|
|
17172
17172
|
referencedFieldPaths.set(catalogPath, fieldPath);
|
|
17173
|
-
if (fieldPath[0] === "properties" && catalog !== void 0 && getCatalogField(catalog, catalogPath) === void 0) {
|
|
17174
|
-
addWarning(
|
|
17175
|
-
"UNOBSERVED_FIELD",
|
|
17176
|
-
["query", "output", "fields", String(index2)],
|
|
17177
|
-
`Asset field ${catalogPath} is not currently observed`
|
|
17178
|
-
);
|
|
17179
|
-
}
|
|
17180
17173
|
}
|
|
17181
17174
|
}
|
|
17182
17175
|
const uniqueIssues = [
|
|
@@ -52714,7 +52707,8 @@ object({
|
|
|
52714
52707
|
template: string$5().min(1),
|
|
52715
52708
|
entries: array(string$5().min(1).max(256)).min(1).max(64).default(["*.mdx"]),
|
|
52716
52709
|
slugField: string$5().min(1).optional(),
|
|
52717
|
-
generateSlugFrom: string$5().min(1).optional()
|
|
52710
|
+
generateSlugFrom: string$5().min(1).optional(),
|
|
52711
|
+
entryPageId: string$5().min(1).optional()
|
|
52718
52712
|
});
|
|
52719
52713
|
const assetQueryLiteral = (value2) => strictObject({ type: literal$2("literal"), value: value2 });
|
|
52720
52714
|
const assetQueryExpression = string$5().refine(isQueryExpression, "Asset query expression is invalid");
|
|
@@ -100762,7 +100756,7 @@ const runtimeOperationContractData = [
|
|
|
100762
100756
|
type: "object",
|
|
100763
100757
|
properties: {
|
|
100764
100758
|
scopes: {
|
|
100765
|
-
description: "Audit scopes. Omit to run all standard scopes; Craft remains opt-in. accessibility errors: missing-alt, missing-image-input-alt, missing-iframe-title, missing-accessible-name, missing-form-label, invalid-aria-role, missing-required-aria-role-property, role-interactive-not-focusable, aria-hidden-focusable, invalid-aria-state, invalid-aria-number, autoplay-media-with-sound, invalid-label-reference, duplicate-id, and missing-aria-reference; warnings: missing-image-description, unsupported-aria-role-property, positive-tabindex, missing-page-heading, skipped-heading-level, missing-main-landmark, and multiple-main-landmarks. Documentation: https://www.w3.org/WAI/WCAG22/understanding/. security errors: non-get-resource-exposed-as-data-source; warning: target-blank-without-noopener. Documentation: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/noopener. seo errors: empty-page-title, invalid-json-ld, and json-ld-in-custom-metadata; warnings: missing-page-description, empty-page-description, invalid-page-language, missing-social-image-asset, duplicate-page-title, duplicate-page-description, missing-json-ld-context, unknown-schema-org-type, deprecated-schema-org-type, unknown-schema-org-property, deprecated-schema-org-property, unsupported-schema-org-property, and incompatible-schema-org-value. Documentation: https://developers.google.com/search/docs/fundamentals/seo-starter-guide. assets info: unused-asset. Documentation: https://docs.webstudio.is/university/foundations/anatomy-of-the-webstudio-builder. styles warnings: style-on-dom-transparent-component, invalid-style-state-selector, and orphan-style-breakpoint; info: unused-design-token, unused-css-variable, unused-local-style-source, unused-breakpoint, and duplicate-design-token-declarations. Documentation: https://docs.webstudio.is/university/foundations/design-tokens. performance info: atomic-css-disabled. Rendered checks add image loading/sizing, render-blocking resource, and legacy font-format evidence. Documentation: https://docs.webstudio.is/university/foundations/project-settings#atomic-css. craft is an opt-in, read-only compatibility check and is excluded when scopes are omitted. Documentation: https://docs.webstudio.is/university/craft.",
|
|
100759
|
+
description: "Audit scopes. Omit to run all standard scopes; Craft remains opt-in. accessibility errors: missing-alt, missing-image-input-alt, missing-iframe-title, missing-accessible-name, missing-form-label, invalid-aria-role, missing-required-aria-role-property, role-interactive-not-focusable, aria-hidden-focusable, invalid-aria-state, invalid-aria-number, autoplay-media-with-sound, invalid-label-reference, duplicate-id, and missing-aria-reference; warnings: missing-image-description, unsupported-aria-role-property, positive-tabindex, missing-page-heading, skipped-heading-level, missing-main-landmark, and multiple-main-landmarks. Documentation: https://www.w3.org/WAI/WCAG22/understanding/. security errors: non-get-resource-exposed-as-data-source; warning: target-blank-without-noopener. Documentation: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/noopener. seo errors: empty-page-title, invalid-json-ld, and json-ld-in-custom-metadata; warnings: missing-page-description, empty-page-description, invalid-page-language, missing-social-image-asset, duplicate-page-title, duplicate-page-description, missing-json-ld-context, unknown-schema-org-type, deprecated-schema-org-type, unknown-schema-org-property, deprecated-schema-org-property, unsupported-schema-org-property, and incompatible-schema-org-value. Documentation: https://developers.google.com/search/docs/fundamentals/seo-starter-guide. assets info: unused-asset. When connected Content Blocks may hide MDX and nested Asset dependencies, unused-asset is skipped instead of returning unsafe false positives. Documentation: https://docs.webstudio.is/university/foundations/anatomy-of-the-webstudio-builder. styles warnings: style-on-dom-transparent-component, invalid-style-state-selector, and orphan-style-breakpoint; info: unused-design-token, unused-css-variable, unused-local-style-source, unused-breakpoint, and duplicate-design-token-declarations. Documentation: https://docs.webstudio.is/university/foundations/design-tokens. performance info: atomic-css-disabled. Rendered checks add image loading/sizing, render-blocking resource, and legacy font-format evidence. Documentation: https://docs.webstudio.is/university/foundations/project-settings#atomic-css. craft is an opt-in, read-only compatibility check and is excluded when scopes are omitted. Documentation: https://docs.webstudio.is/university/craft.",
|
|
100766
100760
|
minItems: 1,
|
|
100767
100761
|
type: "array",
|
|
100768
100762
|
items: {
|
|
@@ -107132,7 +107126,7 @@ const runtimeOperationContractData = [
|
|
|
107132
107126
|
conflictResolution: {
|
|
107133
107127
|
type: "string",
|
|
107134
107128
|
enum: ["ours", "theirs", "merge"],
|
|
107135
|
-
description: '
|
|
107129
|
+
description: 'Token conflicts: "ours" keeps, "theirs" replaces, and "merge" combines styles.'
|
|
107136
107130
|
},
|
|
107137
107131
|
contentMode: {
|
|
107138
107132
|
description: "Apply content-mode copy restrictions to props and styles while reusing existing tokens and breakpoints.",
|
|
@@ -117268,6 +117262,11 @@ const runtimeOperationContractData = [
|
|
|
117268
117262
|
],
|
|
117269
117263
|
description: "One structured repeated-item fragment. Descendant expressions may reference collectionItem and collectionItemKey."
|
|
117270
117264
|
},
|
|
117265
|
+
conflictResolution: {
|
|
117266
|
+
type: "string",
|
|
117267
|
+
enum: ["ours", "theirs", "merge"],
|
|
117268
|
+
description: 'Token conflicts: "ours" keeps, "theirs" replaces, and "merge" combines styles.'
|
|
117269
|
+
},
|
|
117271
117270
|
mode: {
|
|
117272
117271
|
type: "string",
|
|
117273
117272
|
enum: ["append", "prepend", "replace"]
|
|
@@ -123165,7 +123164,7 @@ const runtimeOperationContractData = [
|
|
|
123165
123164
|
conflictResolution: {
|
|
123166
123165
|
type: "string",
|
|
123167
123166
|
enum: ["ours", "theirs", "merge"],
|
|
123168
|
-
description: '
|
|
123167
|
+
description: 'Token conflicts: "ours" keeps, "theirs" replaces, and "merge" combines styles.'
|
|
123169
123168
|
},
|
|
123170
123169
|
contentMode: {
|
|
123171
123170
|
description: "Apply content-mode copy restrictions to props and styles while reusing existing tokens and breakpoints.",
|
|
@@ -135153,7 +135152,8 @@ const runtimeOperationContractData = [
|
|
|
135153
135152
|
type: "object",
|
|
135154
135153
|
properties: {
|
|
135155
135154
|
scopeInstanceId: {
|
|
135156
|
-
type: "string"
|
|
135155
|
+
type: "string",
|
|
135156
|
+
description: 'Instance ID or ":root" for Global Root.'
|
|
135157
135157
|
},
|
|
135158
135158
|
name: {
|
|
135159
135159
|
type: "string",
|
|
@@ -135258,6 +135258,7 @@ const runtimeOperationContractData = [
|
|
|
135258
135258
|
type: "object",
|
|
135259
135259
|
properties: {
|
|
135260
135260
|
scopeInstanceId: {
|
|
135261
|
+
description: 'Instance ID or ":root" for Global Root.',
|
|
135261
135262
|
type: "string"
|
|
135262
135263
|
},
|
|
135263
135264
|
name: {
|
|
@@ -136652,7 +136653,8 @@ const runtimeOperationContractData = [
|
|
|
136652
136653
|
required: []
|
|
136653
136654
|
},
|
|
136654
136655
|
scopeInstanceId: {
|
|
136655
|
-
type: "string"
|
|
136656
|
+
type: "string",
|
|
136657
|
+
description: 'Instance ID or ":root" for Global Root.'
|
|
136656
136658
|
},
|
|
136657
136659
|
dataSourceName: {
|
|
136658
136660
|
type: "string"
|
|
@@ -137218,6 +137220,7 @@ const runtimeOperationContractData = [
|
|
|
137218
137220
|
required: []
|
|
137219
137221
|
},
|
|
137220
137222
|
scopeInstanceId: {
|
|
137223
|
+
description: 'Instance ID or ":root" for Global Root.',
|
|
137221
137224
|
type: "string"
|
|
137222
137225
|
},
|
|
137223
137226
|
dataSourceName: {
|
|
@@ -137584,6 +137587,7 @@ const runtimeOperationContractData = [
|
|
|
137584
137587
|
required: ["name", "method", "url", "headers"]
|
|
137585
137588
|
},
|
|
137586
137589
|
scopeInstanceId: {
|
|
137590
|
+
description: 'Instance ID or ":root" for Global Root.',
|
|
137587
137591
|
type: "string"
|
|
137588
137592
|
},
|
|
137589
137593
|
dataSourceName: {
|
|
@@ -137839,6 +137843,7 @@ const runtimeOperationContractData = [
|
|
|
137839
137843
|
type: "string"
|
|
137840
137844
|
},
|
|
137841
137845
|
scopeInstanceId: {
|
|
137846
|
+
description: 'Instance ID or ":root" for Global Root.',
|
|
137842
137847
|
type: "string"
|
|
137843
137848
|
},
|
|
137844
137849
|
exposeAsDataSource: {
|
|
@@ -164969,6 +164974,20 @@ const splitByOperator = (node2, operator) => {
|
|
|
164969
164974
|
}
|
|
164970
164975
|
return lists.filter((list2) => list2.length > 0).map((list2) => createValueNode(list2));
|
|
164971
164976
|
};
|
|
164977
|
+
const excludedGridLineIdentifiers = /* @__PURE__ */ new Set([
|
|
164978
|
+
...cssWideKeywords$1,
|
|
164979
|
+
"auto",
|
|
164980
|
+
"span",
|
|
164981
|
+
"default"
|
|
164982
|
+
]);
|
|
164983
|
+
const isGridLineCustomIdentifier = (value2) => {
|
|
164984
|
+
if (value2 === void 0) {
|
|
164985
|
+
return false;
|
|
164986
|
+
}
|
|
164987
|
+
const children = getValueList(value2);
|
|
164988
|
+
const child = children[0];
|
|
164989
|
+
return children.length === 1 && child?.type === "Identifier" && excludedGridLineIdentifiers.has(child.name.toLowerCase()) === false;
|
|
164990
|
+
};
|
|
164972
164991
|
const joinByOperator = (list, operator) => {
|
|
164973
164992
|
const joined = [];
|
|
164974
164993
|
for (const node2 of list) {
|
|
@@ -165893,13 +165912,15 @@ const expandShorthand = function* (property2, value2) {
|
|
|
165893
165912
|
value2,
|
|
165894
165913
|
"/"
|
|
165895
165914
|
);
|
|
165896
|
-
|
|
165897
|
-
|
|
165898
|
-
|
|
165899
|
-
|
|
165900
|
-
|
|
165901
|
-
yield ["grid-row-
|
|
165902
|
-
yield ["grid-column-
|
|
165915
|
+
const auto = createIdentifier("auto");
|
|
165916
|
+
const resolvedRowStart = rowStart ?? auto;
|
|
165917
|
+
const resolvedColumnStart = columnStart ?? (isGridLineCustomIdentifier(rowStart) ? rowStart : auto);
|
|
165918
|
+
const resolvedRowEnd = rowEnd ?? (isGridLineCustomIdentifier(rowStart) ? rowStart : auto);
|
|
165919
|
+
const resolvedColumnEnd = columnEnd ?? (isGridLineCustomIdentifier(resolvedColumnStart) ? resolvedColumnStart : auto);
|
|
165920
|
+
yield ["grid-row-start", resolvedRowStart];
|
|
165921
|
+
yield ["grid-column-start", resolvedColumnStart];
|
|
165922
|
+
yield ["grid-row-end", resolvedRowEnd];
|
|
165923
|
+
yield ["grid-column-end", resolvedColumnEnd];
|
|
165903
165924
|
break;
|
|
165904
165925
|
}
|
|
165905
165926
|
case "grid-row": {
|
|
@@ -187319,16 +187340,19 @@ const listDataVariables = (state, input2 = {}) => {
|
|
|
187319
187340
|
return { variables: items, ...pagination };
|
|
187320
187341
|
};
|
|
187321
187342
|
const dataVariableValueInput = dataSourceVariableValue;
|
|
187343
|
+
const resourceScopeInstanceIdDescription = `Instance ID or ${JSON.stringify(ROOT_INSTANCE_ID)} for Global Root.`;
|
|
187344
|
+
const resourceScopeInstanceIdInput = string$5().describe(resourceScopeInstanceIdDescription);
|
|
187345
|
+
const optionalResourceScopeInstanceIdInput = string$5().optional().describe(resourceScopeInstanceIdDescription);
|
|
187322
187346
|
const dataVariableCreateInput = object({
|
|
187323
187347
|
dataSourceId: runtimeGeneratedIdInput,
|
|
187324
|
-
scopeInstanceId:
|
|
187348
|
+
scopeInstanceId: resourceScopeInstanceIdInput,
|
|
187325
187349
|
name: string$5().min(1),
|
|
187326
187350
|
value: dataVariableValueInput
|
|
187327
187351
|
});
|
|
187328
187352
|
const dataVariableUpdateInput = object({
|
|
187329
187353
|
dataSourceId: string$5(),
|
|
187330
187354
|
values: object({
|
|
187331
|
-
scopeInstanceId:
|
|
187355
|
+
scopeInstanceId: optionalResourceScopeInstanceIdInput,
|
|
187332
187356
|
name: string$5().min(1).optional(),
|
|
187333
187357
|
value: dataVariableValueInput.optional()
|
|
187334
187358
|
})
|
|
@@ -188420,7 +188444,7 @@ const resourceCreateInput = object({
|
|
|
188420
188444
|
resourceId: runtimeGeneratedIdInput,
|
|
188421
188445
|
resource: resourceFieldsInput,
|
|
188422
188446
|
dataSourceId: runtimeGeneratedIdInput,
|
|
188423
|
-
scopeInstanceId:
|
|
188447
|
+
scopeInstanceId: optionalResourceScopeInstanceIdInput,
|
|
188424
188448
|
dataSourceName: string$5().optional(),
|
|
188425
188449
|
exposeAsDataSource: exposeAsDataSourceInput
|
|
188426
188450
|
}).superRefine((input2, context) => {
|
|
@@ -188438,7 +188462,7 @@ const resourceUpdateInput = object({
|
|
|
188438
188462
|
resourceId: string$5(),
|
|
188439
188463
|
values: resourceFieldsUpdateInput,
|
|
188440
188464
|
dataSourceName: string$5().optional(),
|
|
188441
|
-
scopeInstanceId:
|
|
188465
|
+
scopeInstanceId: optionalResourceScopeInstanceIdInput,
|
|
188442
188466
|
exposeAsDataSource: exposeAsDataSourceInput
|
|
188443
188467
|
});
|
|
188444
188468
|
const replaceResourceTextInput = object({
|
|
@@ -188886,7 +188910,7 @@ const createResource$1 = (state, input2, context) => {
|
|
|
188886
188910
|
const build2 = getRequiredBuildData(state);
|
|
188887
188911
|
const resourceId2 = context.createId();
|
|
188888
188912
|
const exposeAsDataSource = input2.exposeAsDataSource ?? (resourceInput2.method === "get" && input2.scopeInstanceId !== void 0);
|
|
188889
|
-
if (exposeAsDataSource && build2.instances.some(
|
|
188913
|
+
if (exposeAsDataSource && input2.scopeInstanceId !== ROOT_INSTANCE_ID && build2.instances.some(
|
|
188890
188914
|
(instance2) => instance2.id === input2.scopeInstanceId
|
|
188891
188915
|
) === false) {
|
|
188892
188916
|
return throwBuilderRuntimeError("NOT_FOUND", "Scope instance not found");
|
|
@@ -188988,7 +189012,7 @@ const updateResource$1 = (state, input2, context, options) => {
|
|
|
188988
189012
|
"scopeInstanceId is required when exposeAsDataSource is true."
|
|
188989
189013
|
);
|
|
188990
189014
|
}
|
|
188991
|
-
if (exposeAsDataSource && build2.instances.some((instance2) => instance2.id === scopeInstanceId) === false) {
|
|
189015
|
+
if (exposeAsDataSource && scopeInstanceId !== ROOT_INSTANCE_ID && build2.instances.some((instance2) => instance2.id === scopeInstanceId) === false) {
|
|
188992
189016
|
return throwBuilderRuntimeError("NOT_FOUND", "Scope instance not found");
|
|
188993
189017
|
}
|
|
188994
189018
|
const dataSourceId2 = exposeAsDataSource ? dataSource2?.id ?? context.createId() : dataSource2?.id;
|
|
@@ -189263,7 +189287,7 @@ const assetsResourceGetInput = object({ resourceId: string$5() });
|
|
|
189263
189287
|
const assetsResourceCreateInput = object({
|
|
189264
189288
|
name: string$5().min(1),
|
|
189265
189289
|
query: assetQueryResourceConfigurationInput.optional(),
|
|
189266
|
-
scopeInstanceId:
|
|
189290
|
+
scopeInstanceId: resourceScopeInstanceIdInput,
|
|
189267
189291
|
dataSourceName: string$5().optional()
|
|
189268
189292
|
});
|
|
189269
189293
|
const assetsResourceUpdateInput = object({
|
|
@@ -189274,7 +189298,7 @@ const assetsResourceUpdateInput = object({
|
|
|
189274
189298
|
}).refine((values) => Object.keys(values).length > 0, {
|
|
189275
189299
|
error: "At least one Assets resource value is required."
|
|
189276
189300
|
}),
|
|
189277
|
-
scopeInstanceId:
|
|
189301
|
+
scopeInstanceId: optionalResourceScopeInstanceIdInput,
|
|
189278
189302
|
dataSourceName: string$5().optional()
|
|
189279
189303
|
});
|
|
189280
189304
|
const normalizeWhere = (where) => mapQueryWhere(where, (condition) => ({
|
|
@@ -191871,6 +191895,12 @@ const props$i = {
|
|
|
191871
191895
|
control: "text",
|
|
191872
191896
|
type: "string"
|
|
191873
191897
|
},
|
|
191898
|
+
preconnect: {
|
|
191899
|
+
description: "Opens a connection to the YouTube player before playback.\nDisable this for consent-based click-to-load embeds.\nDefaults to false in Privacy Enhanced Mode and true otherwise.",
|
|
191900
|
+
required: false,
|
|
191901
|
+
control: "boolean",
|
|
191902
|
+
type: "boolean"
|
|
191903
|
+
},
|
|
191874
191904
|
privacyEnhancedMode: {
|
|
191875
191905
|
description: "The Privacy Enhanced Mode of the YouTube embedded player prevents the use of views of embedded YouTube content from influencing the viewer’s browsing experience on YouTube.\nhttps://support.google.com/youtube/answer/171780?hl=en#zippy=%2Cturn-on-privacy-enhanced-mode",
|
|
191876
191906
|
required: false,
|
|
@@ -191931,6 +191961,7 @@ const initialProps = [
|
|
|
191931
191961
|
"className",
|
|
191932
191962
|
"url",
|
|
191933
191963
|
"privacyEnhancedMode",
|
|
191964
|
+
"preconnect",
|
|
191934
191965
|
"title",
|
|
191935
191966
|
"loading",
|
|
191936
191967
|
"showPreview",
|
|
@@ -195681,6 +195712,18 @@ const assertValidSlotPlacement = ({
|
|
|
195681
195712
|
);
|
|
195682
195713
|
}
|
|
195683
195714
|
};
|
|
195715
|
+
const isInsideContentBlockTemplates = (instances, instanceId2) => {
|
|
195716
|
+
const visited = /* @__PURE__ */ new Set();
|
|
195717
|
+
let instance2 = instances.get(instanceId2);
|
|
195718
|
+
while (instance2 !== void 0 && visited.has(instance2.id) === false) {
|
|
195719
|
+
if (instance2.component === blockTemplateComponent) {
|
|
195720
|
+
return true;
|
|
195721
|
+
}
|
|
195722
|
+
visited.add(instance2.id);
|
|
195723
|
+
instance2 = findParentInstanceReference(instances, instance2.id)?.instance;
|
|
195724
|
+
}
|
|
195725
|
+
return false;
|
|
195726
|
+
};
|
|
195684
195727
|
const attachSharedSlot$1 = (state, input2, context) => {
|
|
195685
195728
|
const before = getRequiredSlotState(state);
|
|
195686
195729
|
const slotId = context.createId();
|
|
@@ -195716,6 +195759,12 @@ const attachSharedSlot$1 = (state, input2, context) => {
|
|
|
195716
195759
|
if (parent === void 0) {
|
|
195717
195760
|
return throwBuilderRuntimeError("NOT_FOUND", "Target parent not found");
|
|
195718
195761
|
}
|
|
195762
|
+
if (isInsideContentBlockTemplates(draft.instances, parent.id)) {
|
|
195763
|
+
return throwBuilderRuntimeError(
|
|
195764
|
+
"BAD_REQUEST",
|
|
195765
|
+
"Shared Slots cannot be used inside Content Block Templates. Duplicate the Slot content into a regular template instead."
|
|
195766
|
+
);
|
|
195767
|
+
}
|
|
195719
195768
|
if (findTreeInstanceIds(draft.instances, fragmentId).has(parent.id)) {
|
|
195720
195769
|
return throwBuilderRuntimeError(
|
|
195721
195770
|
"BAD_REQUEST",
|
|
@@ -241597,7 +241646,7 @@ function requireReactDom_development() {
|
|
|
241597
241646
|
}
|
|
241598
241647
|
var ReactDOMClientDispatcher = {
|
|
241599
241648
|
prefetchDNS: prefetchDNS$1,
|
|
241600
|
-
preconnect: preconnect$
|
|
241649
|
+
preconnect: preconnect$1,
|
|
241601
241650
|
preload: preload$1,
|
|
241602
241651
|
preloadModule: preloadModule$1,
|
|
241603
241652
|
preinitStyle,
|
|
@@ -241634,7 +241683,7 @@ function requireReactDom_development() {
|
|
|
241634
241683
|
function prefetchDNS$1(href) {
|
|
241635
241684
|
preconnectAs("dns-prefetch", href, null);
|
|
241636
241685
|
}
|
|
241637
|
-
function preconnect$
|
|
241686
|
+
function preconnect$1(href, crossOrigin) {
|
|
241638
241687
|
preconnectAs("preconnect", href, crossOrigin);
|
|
241639
241688
|
}
|
|
241640
241689
|
function preload$1(href, as, options2) {
|
|
@@ -256876,6 +256925,18 @@ var r$3 = { grad: 0.9, turn: 360, rad: 360 / (2 * Math.PI) }, t = function(r2) {
|
|
|
256876
256925
|
}(), w$2 = function(r2) {
|
|
256877
256926
|
return r2 instanceof j$1 ? r2 : new j$1(r2);
|
|
256878
256927
|
};
|
|
256928
|
+
const preconnectedOrigins = /* @__PURE__ */ new Set();
|
|
256929
|
+
const preconnect = (url2) => {
|
|
256930
|
+
if (preconnectedOrigins.has(url2)) {
|
|
256931
|
+
return;
|
|
256932
|
+
}
|
|
256933
|
+
const link2 = document.createElement("link");
|
|
256934
|
+
link2.rel = "preconnect";
|
|
256935
|
+
link2.href = url2;
|
|
256936
|
+
link2.crossOrigin = "true";
|
|
256937
|
+
document.head.appendChild(link2);
|
|
256938
|
+
preconnectedOrigins.add(url2);
|
|
256939
|
+
};
|
|
256879
256940
|
const requestFullscreen = (element2) => {
|
|
256880
256941
|
const isTouchDevice = "ontouchstart" in window;
|
|
256881
256942
|
const isMobileResolution = window.matchMedia("(max-width: 1024px)").matches;
|
|
@@ -256935,28 +256996,16 @@ const getVideoUrl$1 = (options) => {
|
|
|
256935
256996
|
}
|
|
256936
256997
|
return url2.toString();
|
|
256937
256998
|
};
|
|
256938
|
-
const preconnect$1 = (url2) => {
|
|
256939
|
-
const link2 = document.createElement("link");
|
|
256940
|
-
link2.rel = "preconnect";
|
|
256941
|
-
link2.href = url2;
|
|
256942
|
-
link2.crossOrigin = "true";
|
|
256943
|
-
document.head.appendChild(link2);
|
|
256944
|
-
};
|
|
256945
|
-
let warmed$1 = false;
|
|
256946
256999
|
const PLAYER_CDN = "https://f.vimeocdn.com";
|
|
256947
257000
|
const IFRAME_CDN = "https://player.vimeo.com";
|
|
256948
257001
|
const IMAGE_CDN$1 = "https://i.vimeocdn.com";
|
|
256949
257002
|
const warmConnections$1 = () => {
|
|
256950
|
-
if (warmed$1) {
|
|
256951
|
-
return;
|
|
256952
|
-
}
|
|
256953
257003
|
if (window.matchMedia("(hover: none)").matches) {
|
|
256954
257004
|
return;
|
|
256955
257005
|
}
|
|
256956
|
-
preconnect
|
|
256957
|
-
preconnect
|
|
256958
|
-
preconnect
|
|
256959
|
-
warmed$1 = true;
|
|
257006
|
+
preconnect(PLAYER_CDN);
|
|
257007
|
+
preconnect(IFRAME_CDN);
|
|
257008
|
+
preconnect(IMAGE_CDN$1);
|
|
256960
257009
|
};
|
|
256961
257010
|
const getVideoId$1 = (url2) => {
|
|
256962
257011
|
try {
|
|
@@ -257289,16 +257338,8 @@ const getVideoUrl = (options, videoUrlOrigin) => {
|
|
|
257289
257338
|
});
|
|
257290
257339
|
return url2.toString();
|
|
257291
257340
|
};
|
|
257292
|
-
const preconnect = (url2) => {
|
|
257293
|
-
const link2 = document.createElement("link");
|
|
257294
|
-
link2.rel = "preconnect";
|
|
257295
|
-
link2.href = url2;
|
|
257296
|
-
link2.crossOrigin = "true";
|
|
257297
|
-
document.head.appendChild(link2);
|
|
257298
|
-
};
|
|
257299
|
-
let warmed = false;
|
|
257300
257341
|
const warmConnections = (videoUrl) => {
|
|
257301
|
-
if (
|
|
257342
|
+
if (window.matchMedia("(hover: none)").matches) {
|
|
257302
257343
|
return;
|
|
257303
257344
|
}
|
|
257304
257345
|
try {
|
|
@@ -257306,8 +257347,6 @@ const warmConnections = (videoUrl) => {
|
|
|
257306
257347
|
preconnect(videoUrlObject.origin);
|
|
257307
257348
|
} catch {
|
|
257308
257349
|
}
|
|
257309
|
-
preconnect(IMAGE_CDN);
|
|
257310
|
-
warmed = true;
|
|
257311
257350
|
};
|
|
257312
257351
|
const getPreviewImageUrl = (videoId) => {
|
|
257313
257352
|
return new URL(`${IMAGE_CDN}/vi/${videoId}/maxresdefault.jpg`);
|
|
@@ -257335,11 +257374,6 @@ const Player = ({
|
|
|
257335
257374
|
onStatusChange("loading");
|
|
257336
257375
|
}
|
|
257337
257376
|
}, [autoplay, status, renderer, onStatusChange]);
|
|
257338
|
-
reactExports.useEffect(() => {
|
|
257339
|
-
if (renderer !== "canvas") {
|
|
257340
|
-
warmConnections(videoUrl);
|
|
257341
|
-
}
|
|
257342
|
-
}, [renderer, videoUrl]);
|
|
257343
257377
|
reactExports.useEffect(() => {
|
|
257344
257378
|
const videoId = getVideoId(videoUrl);
|
|
257345
257379
|
if (!videoId || !showPreview) {
|
|
@@ -257386,6 +257420,7 @@ const YouTube = reactExports.forwardRef(
|
|
|
257386
257420
|
loading = "lazy",
|
|
257387
257421
|
autoplay,
|
|
257388
257422
|
showPreview,
|
|
257423
|
+
preconnect: preconnectConnections,
|
|
257389
257424
|
showAnnotations,
|
|
257390
257425
|
showCaptions,
|
|
257391
257426
|
showControls,
|
|
@@ -257399,6 +257434,7 @@ const YouTube = reactExports.forwardRef(
|
|
|
257399
257434
|
const [status, setStatus] = reactExports.useState("initial");
|
|
257400
257435
|
const [previewImageUrl, setPreviewImageUrl] = reactExports.useState();
|
|
257401
257436
|
const { renderer } = reactExports.useContext(ReactSdkContext);
|
|
257437
|
+
const shouldPreconnect = preconnectConnections ?? privacyEnhancedMode === false;
|
|
257402
257438
|
const videoUrlOrigin = privacyEnhancedMode ?? true ? PLAYER_PRIVACY_ENHANVED_MODE_CDN : PLAYER_ORIGINAL_CDN;
|
|
257403
257439
|
const videoUrl = getVideoUrl(
|
|
257404
257440
|
{
|
|
@@ -257415,6 +257451,11 @@ const YouTube = reactExports.forwardRef(
|
|
|
257415
257451
|
},
|
|
257416
257452
|
videoUrlOrigin
|
|
257417
257453
|
);
|
|
257454
|
+
reactExports.useEffect(() => {
|
|
257455
|
+
if (renderer !== "canvas" && shouldPreconnect && videoUrl) {
|
|
257456
|
+
warmConnections(videoUrl);
|
|
257457
|
+
}
|
|
257458
|
+
}, [renderer, shouldPreconnect, videoUrl]);
|
|
257418
257459
|
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
257419
257460
|
VideoContext.Provider,
|
|
257420
257461
|
{
|
|
@@ -282099,6 +282140,9 @@ const collectionDataInput = discriminatedUnion("type", [
|
|
|
282099
282140
|
)
|
|
282100
282141
|
})
|
|
282101
282142
|
]);
|
|
282143
|
+
const conflictResolutionInput = _enum(["ours", "theirs", "merge"]).describe(
|
|
282144
|
+
'Token conflicts: "ours" keeps, "theirs" replaces, and "merge" combines styles.'
|
|
282145
|
+
);
|
|
282102
282146
|
const insertCollectionInput = object({
|
|
282103
282147
|
parentInstanceId: string$5(),
|
|
282104
282148
|
data: collectionDataInput.describe(
|
|
@@ -282107,6 +282151,7 @@ const insertCollectionInput = object({
|
|
|
282107
282151
|
itemFragment: webstudioFragmentMutationInput.describe(
|
|
282108
282152
|
"One structured repeated-item fragment. Descendant expressions may reference collectionItem and collectionItemKey."
|
|
282109
282153
|
),
|
|
282154
|
+
conflictResolution: conflictResolutionInput.optional(),
|
|
282110
282155
|
mode: instanceInsertModeInput.optional(),
|
|
282111
282156
|
insertIndex: insertIndexInput.optional()
|
|
282112
282157
|
});
|
|
@@ -282921,9 +282966,6 @@ const materializeMdxComponent = (node2) => {
|
|
|
282921
282966
|
}
|
|
282922
282967
|
}
|
|
282923
282968
|
};
|
|
282924
|
-
const conflictResolutionInput = _enum(["ours", "theirs", "merge"]).describe(
|
|
282925
|
-
'How to resolve incoming design tokens that share a name with an existing token. "ours" keeps the existing token styles and id, "theirs" uses incoming styles, and "merge" combines both.'
|
|
282926
|
-
);
|
|
282927
282969
|
const insertComponentInput = object({
|
|
282928
282970
|
parentInstanceId: string$5(),
|
|
282929
282971
|
component: instanceComponent.describe(
|
|
@@ -283652,6 +283694,22 @@ const createInsertFragmentMutation = ({
|
|
|
283652
283694
|
invalidatesNamespaces: componentInsertNamespaces
|
|
283653
283695
|
});
|
|
283654
283696
|
};
|
|
283697
|
+
const requireFragmentTokenConflictResolution = ({
|
|
283698
|
+
fragment,
|
|
283699
|
+
targetData,
|
|
283700
|
+
conflictResolution
|
|
283701
|
+
}) => {
|
|
283702
|
+
if (conflictResolution !== void 0) {
|
|
283703
|
+
return;
|
|
283704
|
+
}
|
|
283705
|
+
const conflicts = detectFragmentTokenConflicts({ fragment, targetData });
|
|
283706
|
+
if (conflicts.length > 0) {
|
|
283707
|
+
return throwBuilderRuntimeError(
|
|
283708
|
+
"CONFLICT",
|
|
283709
|
+
`Design token conflicts require an explicit conflictResolution (ours, theirs, or merge): ${conflicts.map(({ tokenName }) => tokenName).join(", ")}`
|
|
283710
|
+
);
|
|
283711
|
+
}
|
|
283712
|
+
};
|
|
283655
283713
|
const insertComponent$1 = (state, input2, context) => {
|
|
283656
283714
|
const mutationState = getRequiredComponentInsertState(state);
|
|
283657
283715
|
const parent = mutationState.instances.get(input2.parentInstanceId);
|
|
@@ -283782,6 +283840,11 @@ const insertCollection$1 = (state, input2, context) => {
|
|
|
283782
283840
|
input: input2,
|
|
283783
283841
|
templates: templates2
|
|
283784
283842
|
});
|
|
283843
|
+
requireFragmentTokenConflictResolution({
|
|
283844
|
+
fragment: collection.fragment,
|
|
283845
|
+
targetData: mutationState,
|
|
283846
|
+
conflictResolution: input2.conflictResolution
|
|
283847
|
+
});
|
|
283785
283848
|
const getMappedId = (ids, sourceId) => {
|
|
283786
283849
|
const id2 = ids.get(sourceId);
|
|
283787
283850
|
if (id2 === void 0) {
|
|
@@ -283799,6 +283862,7 @@ const insertCollection$1 = (state, input2, context) => {
|
|
|
283799
283862
|
templates: templates2,
|
|
283800
283863
|
mode: input2.mode,
|
|
283801
283864
|
insertIndex: input2.insertIndex,
|
|
283865
|
+
conflictResolution: input2.conflictResolution,
|
|
283802
283866
|
additionalAvailableVariables: collection.parameterDataSources,
|
|
283803
283867
|
getResultDetails: ({ newInstanceIds, newDataSourceIds }) => ({
|
|
283804
283868
|
collectionInstanceId: getMappedId(
|
|
@@ -283856,18 +283920,11 @@ const insertFragment$1 = (state, input2, context) => {
|
|
|
283856
283920
|
if (htmlEmbedError !== void 0) {
|
|
283857
283921
|
return throwBuilderRuntimeError("BAD_REQUEST", htmlEmbedError.message);
|
|
283858
283922
|
}
|
|
283859
|
-
|
|
283860
|
-
|
|
283861
|
-
|
|
283862
|
-
|
|
283863
|
-
|
|
283864
|
-
if (conflicts.length > 0) {
|
|
283865
|
-
return throwBuilderRuntimeError(
|
|
283866
|
-
"CONFLICT",
|
|
283867
|
-
`Design token conflicts require an explicit conflictResolution (ours, theirs, or merge): ${conflicts.map(({ tokenName }) => tokenName).join(", ")}`
|
|
283868
|
-
);
|
|
283869
|
-
}
|
|
283870
|
-
}
|
|
283923
|
+
requireFragmentTokenConflictResolution({
|
|
283924
|
+
fragment: input2.fragment,
|
|
283925
|
+
targetData: getRequiredComponentInsertState(state),
|
|
283926
|
+
conflictResolution: input2.conflictResolution
|
|
283927
|
+
});
|
|
283871
283928
|
if (input2.fragment.children.length === 0) {
|
|
283872
283929
|
return createInsertTokenFragmentMutation({
|
|
283873
283930
|
state,
|
|
@@ -299810,7 +299867,7 @@ const auditScopeDescription = [
|
|
|
299810
299867
|
"accessibility errors: missing-alt, missing-image-input-alt, missing-iframe-title, missing-accessible-name, missing-form-label, invalid-aria-role, missing-required-aria-role-property, role-interactive-not-focusable, aria-hidden-focusable, invalid-aria-state, invalid-aria-number, autoplay-media-with-sound, invalid-label-reference, duplicate-id, and missing-aria-reference; warnings: missing-image-description, unsupported-aria-role-property, positive-tabindex, missing-page-heading, skipped-heading-level, missing-main-landmark, and multiple-main-landmarks. Documentation: https://www.w3.org/WAI/WCAG22/understanding/.",
|
|
299811
299868
|
"security errors: non-get-resource-exposed-as-data-source; warning: target-blank-without-noopener. Documentation: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/noopener.",
|
|
299812
299869
|
"seo errors: empty-page-title, invalid-json-ld, and json-ld-in-custom-metadata; warnings: missing-page-description, empty-page-description, invalid-page-language, missing-social-image-asset, duplicate-page-title, duplicate-page-description, missing-json-ld-context, unknown-schema-org-type, deprecated-schema-org-type, unknown-schema-org-property, deprecated-schema-org-property, unsupported-schema-org-property, and incompatible-schema-org-value. Documentation: https://developers.google.com/search/docs/fundamentals/seo-starter-guide.",
|
|
299813
|
-
"assets info: unused-asset. Documentation: https://docs.webstudio.is/university/foundations/anatomy-of-the-webstudio-builder.",
|
|
299870
|
+
"assets info: unused-asset. When connected Content Blocks may hide MDX and nested Asset dependencies, unused-asset is skipped instead of returning unsafe false positives. Documentation: https://docs.webstudio.is/university/foundations/anatomy-of-the-webstudio-builder.",
|
|
299814
299871
|
"styles warnings: style-on-dom-transparent-component, invalid-style-state-selector, and orphan-style-breakpoint; info: unused-design-token, unused-css-variable, unused-local-style-source, unused-breakpoint, and duplicate-design-token-declarations. Documentation: https://docs.webstudio.is/university/foundations/design-tokens.",
|
|
299815
299872
|
"performance info: atomic-css-disabled. Rendered checks add image loading/sizing, render-blocking resource, and legacy font-format evidence. Documentation: https://docs.webstudio.is/university/foundations/project-settings#atomic-css.",
|
|
299816
299873
|
"craft is an opt-in, read-only compatibility check and is excluded when scopes are omitted. Documentation: https://docs.webstudio.is/university/craft."
|
|
@@ -300760,6 +300817,20 @@ const getSkippedChecks = (state, input2, scopes) => {
|
|
|
300760
300817
|
}
|
|
300761
300818
|
}
|
|
300762
300819
|
}
|
|
300820
|
+
if (scopes.includes("assets")) {
|
|
300821
|
+
if (getContentBlockSources({
|
|
300822
|
+
instances: state.instances?.values() ?? [],
|
|
300823
|
+
props: state.props?.values() ?? []
|
|
300824
|
+
}).size > 0) {
|
|
300825
|
+
checks2.push({
|
|
300826
|
+
scope: "assets",
|
|
300827
|
+
checkId: "content-asset-dependencies",
|
|
300828
|
+
reason: "missing-build-data",
|
|
300829
|
+
message: "Unused Asset findings are skipped because connected Content Blocks can reference MDX files and nested Assets that are not visible in editable project data.",
|
|
300830
|
+
location: {}
|
|
300831
|
+
});
|
|
300832
|
+
}
|
|
300833
|
+
}
|
|
300763
300834
|
return checks2.sort(
|
|
300764
300835
|
(left, right) => left.scope.localeCompare(right.scope) || left.checkId.localeCompare(right.checkId) || (left.location.pagePath ?? "").localeCompare(
|
|
300765
300836
|
right.location.pagePath ?? ""
|
|
@@ -300799,6 +300870,7 @@ function audit$2(state, input2, context = {}) {
|
|
|
300799
300870
|
pageId: selectedPage?.id,
|
|
300800
300871
|
verbose: input2.verbose === true
|
|
300801
300872
|
});
|
|
300873
|
+
const skippedChecks = getSkippedChecks(state, normalizedInput, scopes);
|
|
300802
300874
|
const standardScopes = scopes.filter((scope2) => scope2 !== "craft");
|
|
300803
300875
|
const raw2 = standardScopes.length === 0 ? { matches: [] } : analyzeProject(state, {
|
|
300804
300876
|
scopes: standardScopes,
|
|
@@ -300806,8 +300878,13 @@ function audit$2(state, input2, context = {}) {
|
|
|
300806
300878
|
pagePath: normalizedInput.pagePath,
|
|
300807
300879
|
limit: Number.MAX_SAFE_INTEGER
|
|
300808
300880
|
});
|
|
300881
|
+
const skipsUnusedAssetFindings = skippedChecks.some(
|
|
300882
|
+
({ checkId }) => checkId === "content-asset-dependencies"
|
|
300883
|
+
);
|
|
300809
300884
|
const craftAnalysis = scopes.includes("craft") ? analyzeCraftProfile(state) : void 0;
|
|
300810
|
-
const normalizedFindings = [...raw2.matches, ...craftAnalysis?.matches ?? []].
|
|
300885
|
+
const normalizedFindings = [...raw2.matches, ...craftAnalysis?.matches ?? []].filter(
|
|
300886
|
+
(match) => skipsUnusedAssetFindings === false || match.issue !== "unused-asset"
|
|
300887
|
+
).map(canonicalizeAuditMatchPagePaths).map(normalizeAuditFinding).sort(
|
|
300811
300888
|
(left, right) => severityOrder[left.severity] - severityOrder[right.severity] || left.scope.localeCompare(right.scope) || left.ruleId.localeCompare(right.ruleId) || (left.location.pagePath ?? "").localeCompare(
|
|
300812
300889
|
right.location.pagePath ?? ""
|
|
300813
300890
|
) || left.id.localeCompare(right.id)
|
|
@@ -300868,7 +300945,6 @@ function audit$2(state, input2, context = {}) {
|
|
|
300868
300945
|
)
|
|
300869
300946
|
};
|
|
300870
300947
|
}
|
|
300871
|
-
const skippedChecks = getSkippedChecks(state, normalizedInput, scopes);
|
|
300872
300948
|
const manualChecks = [
|
|
300873
300949
|
...scopes.some(
|
|
300874
300950
|
(scope2) => ["accessibility", "styles", "performance"].includes(scope2)
|
|
@@ -311509,6 +311585,18 @@ const stopBrowserProcess = async ({
|
|
|
311509
311585
|
2e3
|
|
311510
311586
|
).catch(() => void 0);
|
|
311511
311587
|
};
|
|
311588
|
+
const removeBrowserProfile = async (userDataDir, dependencies2) => {
|
|
311589
|
+
try {
|
|
311590
|
+
await dependencies2.rm(userDataDir, { recursive: true, force: true });
|
|
311591
|
+
} catch {
|
|
311592
|
+
await dependencies2.rm(userDataDir, {
|
|
311593
|
+
recursive: true,
|
|
311594
|
+
force: true,
|
|
311595
|
+
maxRetries: 3,
|
|
311596
|
+
retryDelay: 50
|
|
311597
|
+
}).catch(() => void 0);
|
|
311598
|
+
}
|
|
311599
|
+
};
|
|
311512
311600
|
const startBrowserRuntimeOnce = async (options, dependencies2) => {
|
|
311513
311601
|
const startupTimeout = options.startupTimeout ?? options.timeout;
|
|
311514
311602
|
const userDataDir = await dependencies2.mkdtemp(
|
|
@@ -311527,7 +311615,7 @@ const startBrowserRuntimeOnce = async (options, dependencies2) => {
|
|
|
311527
311615
|
})
|
|
311528
311616
|
);
|
|
311529
311617
|
} catch (error) {
|
|
311530
|
-
await
|
|
311618
|
+
await removeBrowserProfile(userDataDir, dependencies2);
|
|
311531
311619
|
throw error;
|
|
311532
311620
|
}
|
|
311533
311621
|
let startupOutput = "";
|
|
@@ -311588,10 +311676,7 @@ const startBrowserRuntimeOnce = async (options, dependencies2) => {
|
|
|
311588
311676
|
running,
|
|
311589
311677
|
gracePeriodMs: 2e3
|
|
311590
311678
|
});
|
|
311591
|
-
await
|
|
311592
|
-
recursive: true,
|
|
311593
|
-
force: true
|
|
311594
|
-
});
|
|
311679
|
+
await removeBrowserProfile(userDataDir, dependencies2);
|
|
311595
311680
|
})();
|
|
311596
311681
|
await closePromise;
|
|
311597
311682
|
}
|
|
@@ -311604,7 +311689,7 @@ const startBrowserRuntimeOnce = async (options, dependencies2) => {
|
|
|
311604
311689
|
running,
|
|
311605
311690
|
gracePeriodMs: Math.min(startupTimeout, 2e3)
|
|
311606
311691
|
});
|
|
311607
|
-
await
|
|
311692
|
+
await removeBrowserProfile(userDataDir, dependencies2);
|
|
311608
311693
|
throw error;
|
|
311609
311694
|
}
|
|
311610
311695
|
};
|
|
@@ -322322,6 +322407,8 @@ const getMetaIndex = (tools, guidance) => {
|
|
|
322322
322407
|
"Operate on the configured project only.",
|
|
322323
322408
|
"Read ids before writing.",
|
|
322324
322409
|
"Prefer semantic tools over apply-patch.",
|
|
322410
|
+
"For every collection.json frontmatter property, add a JSON Schema description that tells editors what to enter instead of repeating its label.",
|
|
322411
|
+
"When a content collection has a dynamic entry page, read that page's id and store it as x-webstudio.entryPageId in collection.json. The page must have exactly one URL parameter. Webstudio does not infer this link from the page path or Assets query. Before handoff, verify Open on canvas from both an entry menu and Entry settings.",
|
|
322325
322412
|
valuesVsBindingsRule,
|
|
322326
322413
|
"Use status/refresh when cached data may be stale.",
|
|
322327
322414
|
guidance?.visualVerificationRule
|
|
@@ -328185,6 +328272,9 @@ You can find your config at ${GLOBAL_CONFIG_FILE}`);
|
|
|
328185
328272
|
);
|
|
328186
328273
|
};
|
|
328187
328274
|
const assetContentDescriptorHeader = "x-webstudio-asset-content-descriptor";
|
|
328275
|
+
const assetDescriptionHeader = "x-webstudio-asset-description";
|
|
328276
|
+
const assetDescriptionEncodingHeader = "x-webstudio-asset-description-encoding";
|
|
328277
|
+
const assetDescriptionEncoding = "base64url";
|
|
328188
328278
|
const assetContentDescriptor = object({
|
|
328189
328279
|
id: string$5(),
|
|
328190
328280
|
projectId: string$5(),
|
|
@@ -328499,15 +328589,37 @@ const projectConfirmedMutationInput = (command) => {
|
|
|
328499
328589
|
const runtimeProjectMutation = (command) => projectMutationInput(command);
|
|
328500
328590
|
const stagedUploadChunkSize = 3 * 1024 * 1024;
|
|
328501
328591
|
const formatMebibytes = (bytes) => `${Math.round(bytes / 1024 / 1024)} MiB`;
|
|
328592
|
+
const encodeAssetDescriptionHeader = (value2) => btoa(String.fromCharCode(...new TextEncoder().encode(value2))).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
328502
328593
|
const formatError = (error) => error instanceof Error ? error.message : String(error);
|
|
328503
328594
|
const getErrorStatus = (error) => typeof error === "object" && error !== null && "status" in error && typeof error.status === "number" ? error.status : void 0;
|
|
328504
|
-
const retryOnce = async (task) => {
|
|
328595
|
+
const retryOnce = async (task, delayMs = 0) => {
|
|
328505
328596
|
try {
|
|
328506
328597
|
return await task();
|
|
328507
328598
|
} catch {
|
|
328599
|
+
if (delayMs > 0) {
|
|
328600
|
+
await new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
328601
|
+
}
|
|
328508
328602
|
return await task();
|
|
328509
328603
|
}
|
|
328510
328604
|
};
|
|
328605
|
+
const isRetryableAssetUploadError = (error) => {
|
|
328606
|
+
const status = getErrorStatus(error);
|
|
328607
|
+
return status === void 0 || status === 408 || status === 429 || status >= 500;
|
|
328608
|
+
};
|
|
328609
|
+
const assetUploadRetryExhaustedCode = "ASSET_UPLOAD_RETRY_EXHAUSTED";
|
|
328610
|
+
class AssetUploadRetryError extends Error {
|
|
328611
|
+
webstudioCode = assetUploadRetryExhaustedCode;
|
|
328612
|
+
retryable = true;
|
|
328613
|
+
status;
|
|
328614
|
+
constructor(assetName, cause) {
|
|
328615
|
+
const status = getErrorStatus(cause);
|
|
328616
|
+
super(
|
|
328617
|
+
`Asset "${assetName}" failed after 2 attempts${status === void 0 ? "" : ` (HTTP ${status})`}. Retry this file by itself. If it still fails, verify that it opens and matches its declared format, then report ${assetUploadRetryExhaustedCode} and the HTTP status.`,
|
|
328618
|
+
{ cause }
|
|
328619
|
+
);
|
|
328620
|
+
this.status = status;
|
|
328621
|
+
}
|
|
328622
|
+
}
|
|
328511
328623
|
const getBinaryAssetDataHash = async (data2) => {
|
|
328512
328624
|
return getAssetContentHash(
|
|
328513
328625
|
data2 instanceof Blob ? await data2.arrayBuffer() : data2
|
|
@@ -328540,6 +328652,7 @@ const getAssetUploadUrl = ({
|
|
|
328540
328652
|
};
|
|
328541
328653
|
const uploadAsset = async (params) => {
|
|
328542
328654
|
const { authToken, headers, origin, projectId, upload } = params;
|
|
328655
|
+
const description2 = upload.asset.description ?? void 0;
|
|
328543
328656
|
const result2 = await requestAssetRestJson(
|
|
328544
328657
|
fetchJsonResponse,
|
|
328545
328658
|
getAssetUploadUrl({
|
|
@@ -328554,7 +328667,8 @@ const uploadAsset = async (params) => {
|
|
|
328554
328667
|
headers: createHeaders({
|
|
328555
328668
|
...headers,
|
|
328556
328669
|
"x-auth-token": authToken,
|
|
328557
|
-
|
|
328670
|
+
[assetDescriptionHeader]: description2 === void 0 ? void 0 : encodeAssetDescriptionHeader(description2),
|
|
328671
|
+
[assetDescriptionEncodingHeader]: description2 === void 0 ? void 0 : assetDescriptionEncoding,
|
|
328558
328672
|
"x-webstudio-asset-meta": JSON.stringify(upload.asset.meta),
|
|
328559
328673
|
"content-type": "application/octet-stream"
|
|
328560
328674
|
})
|
|
@@ -328613,9 +328727,10 @@ const uploadAssetsSettled = async (params) => {
|
|
|
328613
328727
|
}
|
|
328614
328728
|
});
|
|
328615
328729
|
};
|
|
328616
|
-
const uploadedAssets = force === true ? await upload() : await retryOnce(upload);
|
|
328730
|
+
const uploadedAssets = force === true ? await upload() : await retryOnce(upload, 100);
|
|
328617
328731
|
results[index2] = { status: "fulfilled", uploadedAssets };
|
|
328618
|
-
} catch (
|
|
328732
|
+
} catch (cause) {
|
|
328733
|
+
const error = force !== true && isRetryableAssetUploadError(cause) ? new AssetUploadRetryError(asset2.name, cause) : cause;
|
|
328619
328734
|
const status = getErrorStatus(error);
|
|
328620
328735
|
results[index2] = {
|
|
328621
328736
|
status: force === true && (status === void 0 || status >= 500) ? "ambiguous" : "rejected",
|
|
@@ -328643,6 +328758,9 @@ const uploadAssets = async (params) => {
|
|
|
328643
328758
|
(result2) => result2.status === "rejected" ? [{ asset: result2.asset, error: result2.error }] : []
|
|
328644
328759
|
);
|
|
328645
328760
|
if (failed.length > 0) {
|
|
328761
|
+
if (failed.length === 1 && failed[0]?.error instanceof AssetUploadRetryError) {
|
|
328762
|
+
throw failed[0].error;
|
|
328763
|
+
}
|
|
328646
328764
|
throw new Error(
|
|
328647
328765
|
`Failed to upload assets: ${failed.map(({ asset: asset2, error }) => `${asset2.name}: ${formatError(error)}`).join("; ")}`
|
|
328648
328766
|
);
|
|
@@ -328742,7 +328860,12 @@ const uploadProjectAssets = async (params) => {
|
|
|
328742
328860
|
{
|
|
328743
328861
|
index: result2.index,
|
|
328744
328862
|
name: result2.asset.name,
|
|
328745
|
-
error: formatError(result2.error)
|
|
328863
|
+
error: formatError(result2.error),
|
|
328864
|
+
...result2.error instanceof AssetUploadRetryError ? {
|
|
328865
|
+
code: result2.error.webstudioCode,
|
|
328866
|
+
retryable: result2.error.retryable,
|
|
328867
|
+
...result2.error.status === void 0 ? {} : { status: result2.error.status }
|
|
328868
|
+
} : {}
|
|
328746
328869
|
}
|
|
328747
328870
|
] : []
|
|
328748
328871
|
),
|
|
@@ -329683,7 +329806,7 @@ class HandledCliError extends Error {
|
|
|
329683
329806
|
}
|
|
329684
329807
|
const isHandledCliError = (error) => error instanceof HandledCliError;
|
|
329685
329808
|
const name = "webstudio";
|
|
329686
|
-
const version$1 = "0.
|
|
329809
|
+
const version$1 = "0.298.0";
|
|
329687
329810
|
const description = "Webstudio CLI";
|
|
329688
329811
|
const author = "Webstudio <github@webstudio.is>";
|
|
329689
329812
|
const homepage = "https://webstudio.is";
|
|
@@ -329695,7 +329818,7 @@ const scripts = { "typecheck": "tsc --noEmit", "generate-docs": "tsx scripts/gen
|
|
|
329695
329818
|
const license = "AGPL-3.0-or-later";
|
|
329696
329819
|
const engines = { "node": ">=22.12.0" };
|
|
329697
329820
|
const dependencies = { "@clack/prompts": "^0.10.0", "@emotion/hash": "^0.9.2", "@trpc/client": "^10.45.2", "@webstudio-is/http-client": "workspace:*", "@webstudio-is/project-migrations": "workspace:*", "@webstudio-is/protocol": "workspace:*", "@webstudio-is/sync-client": "workspace:*", "acorn": "^8.14.1", "acorn-jsx": "^5.3.2", "acorn-walk": "^8.3.4", "change-case": "^5.4.4", "chrome-launcher": "^1.2.1", "deepmerge": "^4.3.1", "decode-named-character-reference": "^1.0.2", "detect-port": "^2.1.0", "env-paths": "^3.0.0", "esbuild": "^0.25.3", "execa": "^10.0.1", "fast-deep-equal": "^3.1.3", "immer": "^10.1.1", "p-limit": "^6.2.0", "parse5": "7.3.0", "path-key": "^4.0.0", "picocolors": "^1.1.1", "reserved-identifiers": "^1.0.0", "semver": "7.7.1", "tinyexec": "^0.3.2", "tus-js-client": "^4.3.1", "warn-once": "^0.1.1", "which": "^5.0.0", "yargs": "^17.7.2", "zod": "^4.4.3" };
|
|
329698
|
-
const devDependencies = { "@cloudflare/vite-plugin": "^1.1.0", "@netlify/vite-plugin-react-router": "^1.0.1", "@react-router/dev": "^7.5.3", "@react-router/fs-routes": "^7.5.3", "@react-router/node": "^7.5.3", "@react-router/serve": "^7.5.3", "@remix-run/cloudflare": "^2.16.5", "@remix-run/cloudflare-pages": "^2.16.5", "@remix-run/dev": "^2.16.5", "@remix-run/node": "^2.16.5", "@remix-run/react": "^2.16.5", "@remix-run/server-runtime": "^2.16.5", "@shikijs/langs": "4.4.1", "@shikijs/themes": "4.4.1", "@types/mdast": "^4.0.4", "@types/react": "^18.2.70", "@types/react-dom": "^18.2.25", "@types/semver": "^7.7.0", "@types/which": "^3.0.4", "@types/yargs": "^17.0.33", "@vercel/react-router": "^1.1.0", "@vitejs/plugin-react": "^4.4.1", "@webstudio-is/css-engine": "workspace:*", "@webstudio-is/content-engine": "workspace:*", "@webstudio-is/expression": "workspace:*", "@webstudio-is/fonts": "workspace:*", "@webstudio-is/image": "workspace:*", "@webstudio-is/project-build": "workspace:*", "@webstudio-is/query-builder": "workspace:*", "@webstudio-is/react-sdk": "workspace:*", "@webstudio-is/sdk": "workspace:*", "@webstudio-is/sdk-components-animation": "workspace:*", "@webstudio-is/sdk-components-react": "workspace:*", "@webstudio-is/sdk-components-react-radix": "workspace:*", "@webstudio-is/sdk-components-react-remix": "workspace:*", "@webstudio-is/sdk-components-react-router": "workspace:*", "@webstudio-is/sdk-components-registry": "workspace:*", "@webstudio-is/tsconfig": "workspace:*", "@webstudio-is/vision": "workspace:*", "@webstudio-is/wsauth": "workspace:*", "h3": "^1.15.1", "ipx": "^3.0.3", "isbot": "^5.1.25", "mdast-util-directive": "^3.1.0", "mdast-util-from-markdown": "^2.0.3", "mdast-util-to-string": "^4.0.0", "micromark-extension-directive": "^4.0.0", "oxfmt": "0.58.0", "react": "18.3.0-canary-14898b6a9-20240318", "react-dom": "18.3.0-canary-14898b6a9-20240318", "react-router": "^7.5.3", "shiki": "4.4.1", "ts-expect": "^1.3.0", "typescript": "7.0.2", "vike": "^0.4.229", "vite": "^6.3.4", "vitest": "^3.1.2", "wrangler": "
|
|
329821
|
+
const devDependencies = { "@cloudflare/vite-plugin": "^1.1.0", "@netlify/vite-plugin-react-router": "^1.0.1", "@react-router/dev": "^7.5.3", "@react-router/fs-routes": "^7.5.3", "@react-router/node": "^7.5.3", "@react-router/serve": "^7.5.3", "@remix-run/cloudflare": "^2.16.5", "@remix-run/cloudflare-pages": "^2.16.5", "@remix-run/dev": "^2.16.5", "@remix-run/node": "^2.16.5", "@remix-run/react": "^2.16.5", "@remix-run/server-runtime": "^2.16.5", "@shikijs/langs": "4.4.1", "@shikijs/themes": "4.4.1", "@types/mdast": "^4.0.4", "@types/react": "^18.2.70", "@types/react-dom": "^18.2.25", "@types/semver": "^7.7.0", "@types/which": "^3.0.4", "@types/yargs": "^17.0.33", "@vercel/react-router": "^1.1.0", "@vitejs/plugin-react": "^4.4.1", "@webstudio-is/css-engine": "workspace:*", "@webstudio-is/content-engine": "workspace:*", "@webstudio-is/expression": "workspace:*", "@webstudio-is/fonts": "workspace:*", "@webstudio-is/image": "workspace:*", "@webstudio-is/project-build": "workspace:*", "@webstudio-is/query-builder": "workspace:*", "@webstudio-is/react-sdk": "workspace:*", "@webstudio-is/sdk": "workspace:*", "@webstudio-is/sdk-components-animation": "workspace:*", "@webstudio-is/sdk-components-react": "workspace:*", "@webstudio-is/sdk-components-react-radix": "workspace:*", "@webstudio-is/sdk-components-react-remix": "workspace:*", "@webstudio-is/sdk-components-react-router": "workspace:*", "@webstudio-is/sdk-components-registry": "workspace:*", "@webstudio-is/tsconfig": "workspace:*", "@webstudio-is/vision": "workspace:*", "@webstudio-is/wsauth": "workspace:*", "h3": "^1.15.1", "ipx": "^3.0.3", "isbot": "^5.1.25", "mdast-util-directive": "^3.1.0", "mdast-util-from-markdown": "^2.0.3", "mdast-util-to-string": "^4.0.0", "micromark-extension-directive": "^4.0.0", "oxfmt": "0.58.0", "react": "18.3.0-canary-14898b6a9-20240318", "react-dom": "18.3.0-canary-14898b6a9-20240318", "react-router": "^7.5.3", "shiki": "4.4.1", "ts-expect": "^1.3.0", "typescript": "7.0.2", "vike": "^0.4.229", "vite": "^6.3.4", "vitest": "^3.1.2", "wrangler": "4.130.0" };
|
|
329699
329822
|
const packageJson = {
|
|
329700
329823
|
name,
|
|
329701
329824
|
version: version$1,
|
|
@@ -336642,6 +336765,10 @@ const getData = (state) => ({
|
|
|
336642
336765
|
...getRequiredComponentInsertData(state),
|
|
336643
336766
|
...state.assetFolders === void 0 ? {} : { assetFolders: state.assetFolders }
|
|
336644
336767
|
});
|
|
336768
|
+
const bindContentBlockSource = (state, blockInstanceId, source) => source.type === "expression" ? {
|
|
336769
|
+
...source,
|
|
336770
|
+
value: bindExpressionInput(state, blockInstanceId, source.value)
|
|
336771
|
+
} : source;
|
|
336645
336772
|
const getMdxAssetSourceBlockInstanceIds = ({
|
|
336646
336773
|
assetId: assetId2,
|
|
336647
336774
|
state
|
|
@@ -336650,6 +336777,7 @@ const getMdxAssetSourceBlockInstanceIds = ({
|
|
|
336650
336777
|
for (const dataSource2 of state.dataSources?.values() ?? []) {
|
|
336651
336778
|
if (dataSource2.type === "variable") {
|
|
336652
336779
|
values.set(dataSource2.name, dataSource2.value.value);
|
|
336780
|
+
values.set(dataSource2.id, dataSource2.value.value);
|
|
336653
336781
|
}
|
|
336654
336782
|
}
|
|
336655
336783
|
return Array.from(
|
|
@@ -336756,6 +336884,7 @@ const createContentBlockApplication = ({
|
|
|
336756
336884
|
const resolveSource = ({
|
|
336757
336885
|
source,
|
|
336758
336886
|
state,
|
|
336887
|
+
blockInstanceId,
|
|
336759
336888
|
variables
|
|
336760
336889
|
}) => {
|
|
336761
336890
|
const resolved = resolveSourceAssetId?.({ source, state, variables });
|
|
@@ -336765,15 +336894,21 @@ const createContentBlockApplication = ({
|
|
|
336765
336894
|
}
|
|
336766
336895
|
return resolved;
|
|
336767
336896
|
}
|
|
336768
|
-
const values =
|
|
336769
|
-
|
|
336770
|
-
|
|
336897
|
+
const values = new Map(Object.entries(variables ?? {}));
|
|
336898
|
+
const availableVariables = state.instances === void 0 || state.dataSources === void 0 ? [] : findAvailableVariables({
|
|
336899
|
+
startingInstanceId: blockInstanceId,
|
|
336900
|
+
instances: state.instances,
|
|
336901
|
+
dataSources: state.dataSources
|
|
336902
|
+
});
|
|
336903
|
+
for (const dataSource2 of availableVariables) {
|
|
336904
|
+
const supplied = variables !== void 0 && Object.prototype.hasOwnProperty.call(variables, dataSource2.name);
|
|
336905
|
+
if (supplied) {
|
|
336906
|
+
values.set(dataSource2.id, variables?.[dataSource2.name]);
|
|
336907
|
+
} else if (dataSource2.type === "variable") {
|
|
336771
336908
|
values.set(dataSource2.name, dataSource2.value.value);
|
|
336909
|
+
values.set(dataSource2.id, dataSource2.value.value);
|
|
336772
336910
|
}
|
|
336773
336911
|
}
|
|
336774
|
-
for (const [name2, value2] of Object.entries(variables ?? {})) {
|
|
336775
|
-
values.set(name2, value2);
|
|
336776
|
-
}
|
|
336777
336912
|
const assetId2 = resolveContentBlockSourceAssetId({ source, values });
|
|
336778
336913
|
if (assetId2 !== void 0) {
|
|
336779
336914
|
return assetId2;
|
|
@@ -336812,6 +336947,7 @@ const createContentBlockApplication = ({
|
|
|
336812
336947
|
const assetId2 = resolveSource({
|
|
336813
336948
|
source,
|
|
336814
336949
|
state,
|
|
336950
|
+
blockInstanceId,
|
|
336815
336951
|
variables
|
|
336816
336952
|
});
|
|
336817
336953
|
const sessionState = await session.open(assetId2);
|
|
@@ -336886,7 +337022,7 @@ const createContentBlockApplication = ({
|
|
|
336886
337022
|
...prepareContentBlockConnect({
|
|
336887
337023
|
state,
|
|
336888
337024
|
blockInstanceId,
|
|
336889
|
-
source
|
|
337025
|
+
source: bindContentBlockSource(state, blockInstanceId, source)
|
|
336890
337026
|
}),
|
|
336891
337027
|
inspection
|
|
336892
337028
|
};
|
|
@@ -336909,7 +337045,7 @@ const createContentBlockApplication = ({
|
|
|
336909
337045
|
...prepareContentBlockSwitch({
|
|
336910
337046
|
state,
|
|
336911
337047
|
blockInstanceId,
|
|
336912
|
-
source
|
|
337048
|
+
source: bindContentBlockSource(state, blockInstanceId, source)
|
|
336913
337049
|
}),
|
|
336914
337050
|
inspection
|
|
336915
337051
|
};
|
|
@@ -344539,8 +344675,8 @@ const build = async (options) => {
|
|
|
344539
344675
|
const cliDocs = {
|
|
344540
344676
|
"api-use-cases": '# CLI API Use Cases\n\n## Link/configure one project\n\nCommands:\n\n- webstudio init --link <api-share-link> --json\n\nNotes:\n\n- Writes local project id and global origin/token config.\n\n## Import synced project bundle into another project\n\nCommands:\n\n- webstudio sync\n- webstudio import --to <destination-share-link>\n- MCP tool: import {"to":"<destination-share-link>"}\n\nNotes:\n\n- Imports local `.webstudio/data.json` into the destination project.\n- Destination share link must allow build/import access.\n- Use `--skip-assets` only when asset rows and files should not be imported.\n\n## Identify current token\n\nCommands:\n\n- MCP tool: whoami {}\n\n## Check token permissions\n\nCommands:\n\n- webstudio permissions --json\n\n## Inspect project/build/version\n\nCommands:\n\n- MCP tool: inspect {}\n- MCP tool: snapshot {"include":["pages","instances","styles"]}\n\n## Discover CLI/API capabilities\n\nCommands:\n\n- webstudio schema api\n- webstudio schema mcp\n- webstudio man --json\n- webstudio man llm --json\n- MCP tool: meta.index {}\n- MCP tool: meta.guide {"brief":"Create a pricing page"}\n- MCP tool: meta.get-more-tools {"brief":"update-styles"}\n- webstudio mcp list-resources\n- webstudio mcp read-resource webstudio://project/guide\n- webstudio mcp read-resource webstudio://project/expressions\n\nNotes:\n\n- Use `webstudio schema mcp` for a compact machine-readable MCP tool overview. Add `--verbose` or use focused `meta.get-more-tools` calls only when exact input schemas are needed.\n- Use focused MCP tools for discovery first: `meta.index`, `meta.guide`, `meta.get-more-tools`, `components.list`, `components.summary`, `components.search`, `components.get`, `templates.list`, and `templates.get`. Protocol clients can use `resources/list` and `resources/read`; shell agents can use `webstudio mcp list-resources` and `webstudio mcp read-resource <uri>`. Read longer resources such as `webstudio://project/tools` and `webstudio://project/components` only when focused tools are insufficient.\n- `components.summary` returns counts by default; request `{"detail":"components","limit":20}` for paginated entries. Registry list tools return compact metadata, while `components.get` and `templates.get` return focused full details.\n- Read `webstudio://project/expressions` before authoring unfamiliar computed text, prop bindings, resource expressions, actions, or Collection item bindings.\n- From a shell, call one MCP tool with the shortcut form `webstudio <tool> \'<json>\'`, for example `webstudio components.summary`. The explicit equivalent is `webstudio mcp single-op-call <tool> \'<json>\'`. Use `--input-file` for large payloads.\n\n## Inspect external shadcn registry items\n\nCommands:\n\n- webstudio registry inspect --source https://example.com/r/registry.json --item button --json\n- webstudio registry inspect --source ./registry.json --item dialog --json\n- webstudio registry inspect --source https://example.com/r/button.json --json\n\nNotes:\n\n- Reads a local or remote registry item without installing files or changing the configured Webstudio project.\n- Returns the item name, description, package and registry dependencies, file paths/targets, available docs, and a read-only compatibility report.\n- The report explicitly says whether installation or editable-component conversion is supported, lists declared requirements and manual steps, and says when arbitrary source code was not analyzed.\n- This is an inspection step only. It does not install files or change the configured project.\n\n## Understand what MCP can do\n\nCommands:\n\n- webstudio man mcp\n- MCP tool: meta.index {}\n- MCP tool: meta.guide {"brief":"What can Webstudio MCP do?"}\n\nMCP lets agents work on one configured Webstudio project. Agents can:\n\n- Inspect the linked project, token permissions, and latest editable build.\n- Read selected project data for audits, migrations, and repair.\n- Search values and ids across every Builder namespace without sending full project data to the model.\n- Audit accessibility, security, SEO, performance settings, unused assets, ineffective Collection styles, and unused or duplicate style data.\n- Create and edit pages, folders, redirects, breakpoints, and page templates.\n- Create pages from reusable templates.\n- Update page metadata, SEO fields, auth settings, and marketplace metadata.\n- Insert components and styled JSX sections.\n- Create data-driven lists, grids, cards, and similar repeated UI from array or object data in one Collection operation.\n- Move, copy, wrap, unwrap, convert, rename, retag, and delete elements.\n- Update text, rich text, props, bindings, and actions.\n- Create and update local styles, design tokens, style sources, and CSS variables.\n- Create static data variables and JSON variables.\n- Create HTTP, GraphQL, and system resources.\n- Use system resources for sitemap, current date, and assets.\n- Bind resources to rendered data or form/action props.\n- Manage nested asset folders and upload, inspect, move, duplicate, download, replace, delete, and inspect usage for assets.\n- Publish, unpublish, inspect publish jobs, and manage custom domains.\n- Start preview, capture screenshots, compare screenshot diffs, and use OCR when installed.\n\n## Inspect and refresh MCP session cache\n\nCommands:\n\n- MCP tool: status {}\n- MCP tool: status {"verbose":true}\n- MCP tool: refresh {"namespaces":["pages","instances","styles"]}\n- MCP tool: reset-session {}\n\nNotes:\n\n- Use status before a task to understand the cached ProjectSession state.\n- Use status with `{"verbose":true}` only when debugging full namespaces, freshness, compatibility, or diagnostics.\n- Use refresh when project data may have changed outside the current MCP session.\n- Use reset-session when local cached state is corrupt or incompatible.\n\n## Visually verify rendered work with AI vision\n\nCommands:\n\n- MCP tool: preview.start {}\n- MCP tool: preview.status {}\n- MCP tool: preview.stop {}\n- MCP tool: screenshot {"path":"/","output":".webstudio/screenshots/home-current.png","viewport":{"width":1440,"height":900},"waitUntil":"load","waitForTimeout":250}\n- MCP tool: screenshot {"path":"/pricing","output":".webstudio/screenshots/pricing-current.png","viewport":{"width":1440,"height":900},"waitUntil":"load","waitForTimeout":250}\n- MCP tool: screenshot {"baseUrl":"http://127.0.0.1:5177","path":"/pricing","output":".webstudio/screenshots/pricing-current.png","viewport":{"width":1440,"height":900},"waitUntil":"load","waitForTimeout":250}\n- MCP tool: screenshot.diff {"baselinePath":".webstudio/screenshots/home-before.png","currentPath":".webstudio/screenshots/home-current.png","outputDir":".webstudio/screenshots/diff"}\n- MCP tool: screenshot.diff {"baselinePath":".webstudio/screenshots/home-before.png","currentPath":".webstudio/screenshots/home-current.png","outputDir":".webstudio/screenshots/diff","expectedText":["Pricing","Start free"]}\n- MCP tool: screenshot.diff {"baselinePath":".webstudio/screenshots/home-before.png","currentPath":".webstudio/screenshots/home-current.png","outputDir":".webstudio/screenshots/diff","expectedVisual":{"maxMismatchPercentage":2,"maxChangedRegions":3,"dominantColorChange":{"channel":"luminance","direction":"increase","minMagnitude":10}}}\n- MCP tool: screenshot.diff {"baselinePath":".webstudio/screenshots/pricing-before.png","currentPath":".webstudio/screenshots/pricing-current.png","outputDir":".webstudio/screenshots/diff"}\n- MCP tool: vision.install-ocr {"confirm":true}\n\nNotes:\n\n- Enter this workflow only when the user explicitly requests visual verification or opts in after being asked. Do not start preview, screenshots, diffs, OCR, or rendered audits automatically after a mutation.\n- `preview.status` reports whether generated output is `stale`. When no preview is running, `url`, `pid`, and `mode` are omitted. When present, `renderedProjectVersion` is the last project version materialized into the preview.\n- MCP preview and path-based screenshot tools select an available loopback port and return the preview URL. Do not pass `host` or `port`.\n- A managed `screenshot` or another `preview.start` refreshes stale generated output before capture.\n\n- After opt-in, use this so a vision-capable AI can see the generated site from the current MCP session. Use `path`; never pass a Webstudio Builder/share URL or capture Builder chrome.\n- For multi-page work, capture every changed page by `path` through the same preview server; no click navigation is required.\n- Iterative mode is the default: after MCP mutations, path screenshots ensure generated project files are current, wait for the exact session version, and perform an ordinary page reload without Vite HMR. The preview server and browser stay alive. Use `{"mode":"production"}` only for release-like verification; rendered audit does this automatically.\n- Calling `preview.start` after a committed mutation restarts a stale iterative server so external browsers and HTTP clients receive the newly generated project.\n- Do not call `preview.start` through one-shot `webstudio mcp single-op-call`: it is long-lived. From a shell, use `webstudio mcp run` with preview.start, screenshot, and preview.stop in one shared process, or use a real long-running MCP client.\n- From one-shot shell calls or another process, pass `baseUrl` with `path` to capture an already-running preview/site without generating, building, starting, or restarting preview.\n- Use preview.stop only in the same long-running MCP server or `webstudio mcp run` process that started preview. A separate one-shot `single-op-call` process does not own another process\'s preview controller.\n- Use waitForSelector when the rendered app has a reliable ready marker, waitUntil:"networkidle" for network-heavy pages, and waitForTimeout only for final visual settling.\n- The screenshot timeout bounds browser capture after the preview is ready. A timeout returns `SCREENSHOT_TIMEOUT`, resets the reusable browser session, and releases the shared preview lifecycle for cleanup.\n- Preview installs generated app dependencies under `.webstudio/preview` and reuses them across regenerations.\n- Do not add generated-preview dependencies to the repository root `package.json` or `pnpm-lock.yaml`.\n- When launcher metadata is available, Preview reuses a supported npm or pnpm launcher. Without launcher metadata, it defaults to npm.\n- Unless `npm_config_cache` is already configured, npm uses the writable `.webstudio/preview/.npm-cache` directory.\n- For npm cache permission errors, unset `npm_config_cache` to use the preview-local cache, then retry. No cache deletion is required.\n- If dependency installation fails, check the reported package-manager path and network configuration, then reinstall or update the Webstudio CLI if the problem persists.\n- When a baseline exists, use screenshot.diff once per baseline/current page or viewport pair to get changed regions, OCR textAnalysis, and diff artifact paths before deciding whether the result matches. Pass expectedText for explicit pass/fail current-screen text assertions with found and missing text. Pass expectedVisual for pass/fail limits on pixel mismatch percentage, changed-region count, or the overall dominant color/brightness direction.\n- If screenshot.diff reports OCR unavailable and the user agrees to install it, call vision.install-ocr {"confirm":true}; otherwise continue with pixel diff and visual inspection.\n- Compare the PNG, OCR text evidence, and diff artifacts against the user\'s intent for layout, typography, colors, spacing, imagery, and responsive framing; then iterate with focused mutations.\n- Root CLI equivalent: `webstudio screenshot --path /pricing --output pricing.png` generates a temporary production preview, captures that route, and stops the server. For repeated captures, keep `webstudio preview` running and pass its absolute URL to `webstudio screenshot`.\n\n## List pages\n\nCommands:\n\n- MCP tool: list-pages {}\n- MCP tool: list-folders {}\n\n## Read page by id\n\nCommands:\n\n- MCP tool: get-page {"pageId":"<pageId>"}\n\n## Read page by path\n\nCommands:\n\n- MCP tool: get-page-by-path {"path":"/pricing"}\n\n## Create page\n\nCommands:\n\n- MCP tool: create-page {"name":"Pricing","path":"/pricing"}\n- MCP tool: create-page {"name":"Pricing","path":"/pricing","title":"Pricing","meta":{"description":"Plans for teams"}}\n\nNotes:\n\n- `name`, `path`, page `title`, and metadata text fields accept plain fixed values.\n- For computed page titles or metadata, send JavaScript expression code such as `pageTitle ?? "Pricing"`.\n\n## Update page settings/metadata\n\nCommands:\n\n- MCP tool: update-page {"pageId":"<pageId>","values":{"title":"Pricing","meta":{"description":"Plans","status":200}}}\n- MCP tool: update-page {"pageId":"<pageId>","values":{"meta":{"auth":{"method":"basic","login":"<login>","password":"<password>"}}}}\n\nNotes:\n\n- Page `title` and metadata text fields accept plain fixed values.\n- For computed page titles or metadata, send JavaScript expression code such as `pageTitle ?? "Pricing"`.\n- Page `status` accepts a fixed HTTP status code as a number from 200 through 599 or a JavaScript expression string for a dynamic status.\n\n## Read project settings\n\nCommands:\n\n- MCP tool: get-project-settings {}\n\nNotes:\n\n- Read `meta.agentInstructions` before making project changes. It contains the project\'s own guidance for AI agents.\n- Agent instructions are shared project guidance. Do not store credentials or other secrets there.\n\n## Update project settings\n\nCommands:\n\n- MCP tool: update-project-settings {"meta":{"siteName":"Acme"}}\n- MCP tool: update-project-settings {"meta":{"agentInstructions":"Use existing design tokens and keep product copy concise."}}\n\n## Read marketplace product\n\nCommands:\n\n- MCP tool: get-marketplace-product {}\n\n## Update marketplace product\n\nCommands:\n\n- MCP tool: update-marketplace-product {"category":"pageTemplates","name":"Acme Template","thumbnailAssetId":"asset-id","author":"Acme Studio","email":"hello@example.com","website":"https://example.com","issues":"","description":"Reusable template project for Acme landing pages."}\n\n## Submit marketplace product\n\nCommands:\n\n- MCP tool: upload-asset {"asset":{"name":"marketplace-thumbnail.png","type":"image","format":"png","meta":{"width":1200,"height":630}},"assetsDir":".webstudio/assets"}\n- MCP tool: update-marketplace-product {"category":"pageTemplates","name":"Acme Template","thumbnailAssetId":"<uploadedAssetId>","author":"Acme Studio","email":"hello@example.com","website":"https://example.com","issues":"","description":"Reusable template project for Acme landing pages."}\n- MCP tool: publish {"target":"production"}\n- MCP tool: submit-marketplace-product {"acknowledgePublicSubmission":true}\n\nNotes:\n\n- Wait for the production publish to complete before submitting the product for review.\n- Submission requires complete, valid marketplace metadata.\n\n## List redirects\n\nCommands:\n\n- MCP tool: list-redirects {}\n\n## Create redirect\n\nCommands:\n\n- MCP tool: create-redirect {"old":"/old","new":"/new","status":301}\n\n## Update redirect\n\nCommands:\n\n- MCP tool: update-redirect {"old":"/old","values":{"new":"/newer","status":302}}\n- MCP tool: update-redirect {"old":"/old","values":{"status":null}}\n\n## Delete redirect\n\nCommands:\n\n- MCP tool: delete-redirect {"old":"/old"}\n\n## Set redirects\n\nCommands:\n\n- MCP tool: set-redirects {"redirects":[{"old":"/old","new":"/new","status":"301"}]}\n\n## List breakpoints\n\nCommands:\n\n- MCP tool: list-breakpoints {}\n\n## Create breakpoint\n\nCommands:\n\n- MCP tool: create-breakpoint {"label":"Tablet","maxWidth":991}\n\n## Update breakpoint\n\nCommands:\n\n- MCP tool: update-breakpoint {"breakpointId":"tablet","values":{"label":"Tablet","maxWidth":1023}}\n- MCP tool: update-breakpoint {"breakpointId":"tablet","values":{"condition":null,"minWidth":768}}\n- MCP tool: update-breakpoint {"breakpointId":"tablet","values":{"minWidth":null,"maxWidth":null,"condition":"(hover: hover)"}}\n\n## Delete breakpoint\n\nCommands:\n\n- MCP tool: delete-breakpoint {"breakpointId":"tablet"}\n\n## Duplicate page\n\nCommands:\n\n- MCP tool: duplicate-page {"pageId":"<pageId>","name":"Pricing Copy","path":"/pricing-copy"}\n- MCP tool: duplicate-page {"pageId":"<pageId>","name":"Paris","path":"/paris","substitutions":{"text":{"London":"Paris"},"variables":{"city":{"type":"string","value":"Paris"}}}}\n- webstudio duplicate-page --page <pageId> --name Paris --path /paris --substitutions \'{"text":{"London":"Paris"},"variables":{"city":{"type":"string","value":"Paris"}}}\' --json\n\nNotes:\n\n- Text substitutions replace exact fixed text only in the duplicated page\'s text children, string props, title, and metadata.\n- Variable substitutions are keyed by copied source-variable name and use typed variable values. The operation rejects missing or ambiguous names without committing a partial duplicate.\n- Existing expressions and cloned variable/resource references keep their remapped ids.\n\n## List page templates\n\nCommands:\n\n- MCP tool: list-page-templates {}\n\n## Create page template\n\nCommands:\n\n- MCP tool: create-page-template {"name":"Landing Template","title":"Landing"}\n\n## Update page template\n\nCommands:\n\n- MCP tool: update-page-template {"templateId":"<templateId>","values":{"name":"Article Template","meta":{"description":"Reusable article layout"}}}\n\n## Delete page template\n\nCommands:\n\n- MCP tool: delete-page-template {"templateId":"<templateId>"}\n\n## Duplicate page template\n\nCommands:\n\n- MCP tool: duplicate-page-template {"templateId":"<templateId>"}\n\n## Reorder page template\n\nCommands:\n\n- MCP tool: reorder-page-template {"sourceTemplateId":"<sourceTemplateId>","targetTemplateId":"<targetTemplateId>","position":"before"}\n\n## Create page from template\n\nCommands:\n\n- MCP tool: create-page-from-template {"templateId":"<templateId>","name":"Landing","path":"/landing"}\n\n## Delete page\n\nCommands:\n\n- MCP tool: delete-page {"pageId":"<pageId>"}\n\n## List folders\n\nCommands:\n\n- MCP tool: list-folders {}\n- MCP tool: list-pages {}\n\n## Create folder\n\nCommands:\n\n- MCP tool: create-folder {"name":"Blog","slug":"blog"}\n\n## Update folder\n\nCommands:\n\n- MCP tool: update-folder {"folderId":"<folderId>","values":{"name":"Blog","slug":"blog"}}\n\n## Delete folder\n\nCommands:\n\n- MCP tool: delete-folder {"folderId":"<folderId>"}\n\n## List element instances\n\nCommands:\n\n- MCP tool: list-instances {"pagePath":"/","maxDepth":3}\n\n## Inspect one element instance\n\nCommands:\n\n- MCP tool: inspect-instance {"instanceId":"<instanceId>","include":["props","styles","children"]}\n\n## Insert authored JSX or one component template\n\nCommands:\n\n- MCP tool: insert-fragment {"parentInstanceId":"<instanceId>","fragment":"<section ws:style={css`padding: 32px;`}><h2>Product OS</h2><Switch><SwitchThumb /></Switch></section>"}\n- MCP tool: insert-component {"parentInstanceId":"<instanceId>","component":"@webstudio-is/sdk-components-react-radix:Switch"}\n- MCP tool: insert-component {"parentInstanceId":"<instanceId>","component":"Form"}\n\nNotes:\n\n- Use MCP `insert-fragment` as the default way to author styled component trees. It converts JSX to a structured fragment before mutation.\n- Use only exact component ids returned by `components.search`, `components.get`, or `templates.get`. Never derive or guess component ids.\n- Use lowercase HTML JSX such as `<div>` and `<form>`. The internal `ws:` namespace is not HTML-tag shorthand.\n- For Webstudio\'s complete form structure, discover the Form component and insert its automatic template with `insert-component` using component `"Form"`.\n- MCP receives JSX as a JSON string because MCP arguments are JSON. The CLI converts it locally before the runtime mutation, so the project session receives structured Webstudio data, not JSX source.\n- In `insert-fragment` JSX, use ``ws:style={css`...`}`` for Webstudio-native CSS, or use React-style object syntax such as `style={{ padding: 24 }}` when that is simpler. Both forms create editable Webstudio style data.\n- Do not access host globals or dynamic code APIs in JSX fragments, including `process`, `globalThis`, `eval`, `Function`, or `constructor`.\n- Use Webstudio prop names such as `class` and `for`; do not use React aliases `className` or `htmlFor`.\n- Use Webstudio actions for event/action props, for example `onClick={new ActionValue(["event"], expression\\`console.log(event)\\`)}`. Do not pass JavaScript functions such as `onClick={() => ...}`.\n- Plain prop values must be JSON-compatible: `null`, strings, booleans, finite numbers, arrays, and plain objects. Do not pass `undefined`, `Symbol`, `BigInt`, `NaN`, `Infinity`, `Date`, `Map`, `Set`, class instances, or circular objects; omit the prop, use plain data, or use `expression`/`ActionValue` when the value is dynamic.\n- Template-backed components used in JSX must include required child/part components explicitly under the same parent structure as the template, for example `<Switch><SwitchThumb /></Switch>`.\n- Webstudio applies a registered template automatically when using `insert-component`, so composed components such as Switch include required child parts and styles.\n- Use `components.list`, `components.summary`, `components.search`, `components.get`, `templates.list`, and `templates.get` to discover known registry items, component ids, props, templates, insertability, and content model. Read `webstudio://project/components` only when those focused tools are insufficient.\n- Component/template registry items use a shadcn-compatible top-level shape plus Webstudio-specific superset metadata in `meta`. They are for Builder/MCP discovery, not a published shadcn install registry yet.\n- Known components with `contentModel.category: "none"` are not standalone-insertable; insert their root component template instead so required providers/parents are included.\n- Unknown custom component ids are a low-level extension mechanism, not a discovery fallback. Agents must not synthesize them.\n\n## Make a region editable in Content mode\n\nCommands:\n\n- MCP tool: insert-component {"parentInstanceId":"<instanceId>","component":"ws:block"}\n- MCP tool: inspect-instance {"instanceId":"<instanceId>","include":["children"]}\n\nNotes:\n\n- When a page will be handed to a Content-mode editor, wrap every region they should be able to edit in a Content Block (`ws:block`). Content-mode editors can edit text and supported props only in Content Block descendants. Content outside those blocks remains read-only, even when it looks like ordinary editable text.\n- Put reusable insertable options inside exactly one direct `ws:block-template` child. A missing or second template container makes the Content Block invalid. A template is source material, not editor content: editors cannot edit or delete it directly. When an editor inserts a template, its copy becomes a direct child of the Content Block and is editable.\n- Before implementation, inventory every editor-owned field and its intended control and write destination, whether or not the block uses MDX. Check that templates contain the required styling, but do not treat containment or a successful text edit as proof that the whole block is editable. Test every field through Content mode, inspect its saved project data or source file, reload, and restore the original value. Report passed, failed, or not tested for each field; missing or read-only required controls are unfinished requirements.\n- Use controls that match the content: a calendar for dates and an image picker for image replacement. Keep fixed punctuation and units out of directly bound value elements. Preserve stored types and wording: when reading time is already a complete string such as `4 min read`, bind that string directly rather than assuming it is a number.\n- For MDX-backed blocks, containment alone is not enough: the MDX body is editable through its source mapping, but the designed shell needs writable document-frontmatter bindings. Follow the binding recipe below rather than binding editable article fields to query results.\n\n## Store Content Block content in MDX\n\nCommands:\n\n- MCP tool: list-assets {"type":"file"}\n- MCP tool: upload-asset {"asset":{"name":"article.mdx","type":"file","format":"mdx","meta":{}},"assetsDir":".webstudio/assets"}\n- MCP tool: connect-content-block-source {"blockInstanceId":"<contentBlockInstanceId>","renderScope":"page:/articles/example","source":{"type":"asset","assetId":"<mdxAssetId>"}}\n- MCP tool: switch-content-block-source {"blockInstanceId":"<contentBlockInstanceId>","renderScope":"page:/articles/example","source":{"type":"asset","assetId":"<otherMdxAssetId>"}}\n- MCP tool: inspect-content-block-source {"blockInstanceId":"<contentBlockInstanceId>","renderScope":"page:/articles/example"}\n- MCP tool: edit-content-block-source {"blockInstanceId":"<contentBlockInstanceId>","renderScope":"page:/articles/example","source":"# Article title\\n\\nArticle body."}\n- MCP tool: update-content-block-frontmatter {"blockInstanceId":"<contentBlockInstanceId>","renderScope":"page:/articles/example","properties":{"title":"Article title","draft":false}}\n- MCP tool: reload-content-block-source {"blockInstanceId":"<contentBlockInstanceId>","renderScope":"page:/articles/example"}\n- MCP tool: migrate-content-block-template-references {"assetIds":["<mdxAssetId>"],"migration":{"type":"rename","from":"Old template name","to":"New template name"}}\n- MCP tool: disconnect-content-block-source {"blockInstanceId":"<contentBlockInstanceId>","renderScope":"page:/articles/example"}\n\nNotes:\n\n- Create the local `.mdx` file under `.webstudio/assets` before calling `upload-asset`. Use the returned Asset ID when connecting it.\n- Use the Content Block source operations for connecting, switching, inspecting, editing, reloading, and disconnecting. Do not create or delete the `src` prop with generic prop tools; the source operations preserve the Body outlet and Content Block lifecycle.\n- `renderScope` accepts any non-empty stable key for one rendered occurrence. Use a page-based key for a direct Content Block and a distinct key for each repeated Collection occurrence. It does not load a page, route parameters, or resource results. Supply the concrete scoped values needed to resolve an expression through `variables`.\n- For a result-one Assets resource, preview the concrete query, then connect its persisted expression with the previewed item supplied as `variables`, for example `source:{"type":"expression","value":"post.data.id"}` and `variables:{"post":{"data":{"id":"<mdxAssetId>"}}}`.\n- Connecting replaces existing Body content. If the result returns `requiresConfirmation:true`, report that replacement to the user and repeat the same call with `confirmReplacement:true` only after approval.\n- Prefer Markdown for standard document content; it automatically uses a unique matching semantic template when one exists. Use lowercase JSX such as `<section>` or `<svg>` for HTML or SVG with authored properties that Markdown cannot express.\n- Verify that the Content Block has exactly one direct Templates container before connecting or editing MDX. Zero or multiple containers block materialization and publication.\n- Read the Content Block\'s Templates children before writing a custom reference. Use capitalized JSX such as `<PromotionCard />` for its stable **Name**, which is independent from the display label. A matching template wins over a built-in MDX adapter. Other registered components are unavailable until added to this Content Block\'s Templates. JSX attributes accept quoted static values and bare booleans; expressions such as `{false}` are unsupported. Legacy `ws.element` and `ws:name` forms are compatibility input only. Component namespaces such as `$.*`, `radix.*`, and `animation.*` are unsupported.\n- When explicit JSX children match the designed template structure, their text and supported props overlay the cloned descendants and retain template styles. A mismatched child structure replaces the root defaults, while each authored child still resolves through a matching template when possible. An explicit empty pair clears defaults, and a self-closing reference keeps them. Editing inherited default content writes it back as explicit JSX children. Template resolution is live, so adding a matching semantic or named template later updates existing MDX. Preserve unresolved names and report their diagnostics instead of deleting them.\n- When a template is renamed or deleted, use `migrate-content-block-template-references` to update a selected set of affected MDX files. The first call returns a plan. Report its changed-file, update, omission, and diagnostic counts, then repeat the exact request with its `confirmationToken` only after approval. Renames and removals update named JSX and legacy `ws:name` references, including names such as `Image` and `CodeText` when they identify templates. Rename targets must be valid PascalCase JSX identifiers. Removing a paired reference unwraps and preserves its authored children; removing a self-closing reference removes the node because it has no authored children. Invalid files remain unchanged and are reported in diagnostics.\n- `edit-content-block-source` replaces the complete MDX source. Preserve frontmatter and unrelated source when making a bounded edit.\n- `update-content-block-frontmatter` replaces the complete frontmatter mapping. Inspect the current source first and include every property that must remain.\n- Store frontmatter images as exact `$ref` objects. Bind an editable Image source to the resolved `.src` with explicit `binding.mode:"readwrite"`; use `mode:"read"` for its alt binding to `.description`. Omitting the source mode leaves the image visible but not replaceable in Content mode.\n- Use `update-content-block-frontmatter` for MCP frontmatter edits. MDX-rendered elements are not persistent instance targets for generic `bind-props` or `update-text` calls. Preserve existing `mode:"readwrite"` bindings when encountered; they are valid only for exact direct paths into the connected document\'s frontmatter. Direct bindings through a loaded Markdown or MDX `$ref` ending in `#frontmatter` save to the referenced file, with its write permissions enforced. Shared-record edits affect every document using that record. Computed expressions and JSON/body references remain read-only. Image replacement is supported: a direct Image source binding with `mode:"readwrite"` lets the picker replace the frontmatter `$ref`, while shared Asset metadata is edited in Asset settings.\n- Inspect every returned diagnostic. Invalid MDX is saved rather than silently repaired; preserve the source, report the source range, and fix only the requested or invalid part.\n- If an edit in a long-lived MCP session reports a conflict after another client saved the Asset, call `reload-content-block-source`, inspect the latest source, reapply the requested change, and retry. One-shot CLI calls refresh before each operation and normally cannot reproduce a stale session. Never overwrite the newer revision blindly.\n\n## Make an MDX article header editable\n\nUse the resource only to select the connected source, for example `post.data.id`.\nBind article values inside the Content Block to its document parameter instead\nof `post.data.properties.*`. Inspect the block\'s variables first; `document` is\nthe default name, not a name to assume.\n\nThese commands target persistent designed instances inside the connected block.\n\nCommands:\n\n- MCP tool: update-text {"instanceId":"<headingInstanceId>","childIndex":0,"text":"document.frontmatter.title","mode":"expression","expressionBindingMode":"readwrite"}\n- MCP tool: update-text {"instanceId":"<readingTimeValueInstanceId>","childIndex":0,"text":"document.frontmatter.readingTime","mode":"expression","expressionBindingMode":"readwrite"}\n- MCP tool: bind-props {"bindings":[{"instanceId":"<dateInstanceId>","name":"datetime","binding":{"type":"expression","value":"document.frontmatter.publishedAt","mode":"readwrite"}}]}\n\nNotes:\n\n- Create writable bindings explicitly; the default is read-only. Merely moving a heading or Link inside the Content Block does not make its query-bound text editable.\n- The MDX body uses its source mapping. Static designed content outside that body remains protected unless its text or supported props bind to writable frontmatter fields. Do not apply these generic instance tools to MDX-generated instances.\n- Use direct static paths. Property access is already safe. Fallbacks and formatted expressions, such as `document.frontmatter.title ?? "Untitled"`, remain read-only.\n- Keep every intended editable value in its own text element with a single direct read-write binding. For reading time, use three inline siblings: static `— `, the bound reading-time value, and static ` min read`. Preserve whitespace and the stored field type. Do not combine them into a template literal or concatenate strings, and do not put literal siblings inside the value element itself. Keep fixed wording protected in the designed shell.\n- Prefer component formatting controls, such as Date Time formatting with a directly bound date prop, over transforming the expression. Do not silently sacrifice editability for formatting or a fallback; explain unsupported cases and ask before making an intended editable field read-only.\n- A direct writable binding such as `document.frontmatter.author.name` can edit a shared author loaded through `../authors/oleg.md#frontmatter`. The edit saves to the author file and affects every article using it; preserve the article\'s `$ref` marker.\n- Before handoff, inventory every article-owned field, including header text, author details, dates, reading time, categories, hero and inline image sources, alternative text, captions, links, and custom-component content. For each field, inspect its binding and source, edit it through the Content-mode UI, verify the saved MDX or referenced file, reload, and restore the test value. Record passed, failed, or not tested for each field. A correct preview, a successful MCP write, and one representative text edit do not prove the whole article is editable. Do not claim completion while required fields fail or remain untested.\n- Bind the Image source directly to `document.frontmatter.featureImage.src` with `binding.mode:"readwrite"` using `bind-props`. Content mode\'s **Choose source** replaces the article\'s frontmatter image `$ref`; the resolved URL stays read-only. Verify selection, the saved reference, and reload instead of treating a missing write mode as a platform limitation. For alternative text bound to `.description`, use **Choose source → asset actions → Settings → Description** to edit shared Asset metadata. This affects every use of the Asset and is separate from replacing an article\'s image.\n\n## Make an article image replaceable in Content mode\n\nUse this for a persistent designed Image inside a connected Content Block,\nwith `featureImage: { $ref: "./images/hero.png" }` in its MDX frontmatter.\nRead the instance ID and document variable name before adapting the example.\n\nCommands:\n\n- MCP tool: bind-props {"bindings":[{"instanceId":"<imageInstanceId>","name":"src","binding":{"type":"expression","value":"document.frontmatter.featureImage.src","mode":"readwrite"}},{"instanceId":"<imageInstanceId>","name":"alt","binding":{"type":"expression","value":"document.frontmatter.featureImage.description","mode":"read"}}]}\n\nNotes:\n\n- The source mode must be explicit: the default `read` mode displays the image but prevents Content-mode replacement. Do not bind to query-result properties or add a fallback expression.\n- **Choose source** replaces the article\'s `featureImage.$ref`; the disabled resolved-URL input is expected. Do not store a resolved URL string or replace the shared Asset itself.\n- Shared alternative text is a separate action: **Choose source → asset actions → Settings → Description**. Its read-only binding does not require the image source binding to be read-only.\n- If the picker is missing or disabled, inspect the source mode, document scope, loaded reference, and permissions before claiming a product limitation.\n- Verify through Content mode with an approved temporary replacement. Check the saved `$ref`, unchanged unrelated MDX and original shared Asset, reload persistence, and restoration. If that test is not authorized, report it as not tested, not passed.\n\n## Move elements\n\nCommands:\n\n- MCP tool: move-instance {"moves":"moves.json contents"}\n\nNotes:\n\n- Use `position: "end"` to append an instance. Repeating this for A and then B preserves the final order A, B.\n- A numeric `insertIndex` addresses the target parent\'s children before the moved instance is removed. Use it for exact placement; do not calculate the last index to append.\n- Moves in one `moves` array are applied sequentially in array order.\n\n## Clone element subtree\n\nCommands:\n\n- MCP tool: clone-instance {"sourceInstanceId":"<instanceId>","targetParentInstanceId":"<targetParentId>"}\n\n## Delete element subtree\n\nCommands:\n\n- MCP tool: delete-instance {"instanceIds":["<instanceId>"]}\n\n## List text/expression children\n\nCommands:\n\n- MCP tool: list-texts {"pagePath":"/"}\n\n## Update text child\n\nCommands:\n\n- MCP tool: update-text {"instanceId":"<instanceId>","childIndex":0,"text":"Launch faster"}\n\n## Replace bounded literal text\n\nCommands:\n\n- MCP tool: replace-text {"find":"Start free","replace":"Get started","match":"exact","pagePath":"/pricing","limit":20}\n\nNotes:\n\n- This changes only literal text children, never expression children. Scope it to pagePath or pageId and set a limit before a broad replacement.\n\n## Replace bounded static prop text\n\nCommands:\n\n- MCP tool: replace-prop-text {"find":"old.example.com","replace":"www.example.com","match":"substring","names":["href","code"],"limit":20}\n\nNotes:\n\n- This changes only static string props such as href, alt, aria-label, title, and HTML embed code. It never changes expressions, resources, actions, assets, or other dynamic bindings. Use names or instanceIds and a limit to narrow the change.\n\n## Replace bounded resource text\n\nCommands:\n\n- MCP tool: replace-resource-text {"find":"api.old.example.com","replace":"api.example.com","fields":["url"],"limit":20}\n\nNotes:\n\n- This changes resource names and fixed URL literals only. It skips dynamic URL expressions, headers, search parameters, request bodies, and GraphQL query code.\n\n## Update props\n\nCommands:\n\n- MCP tool: update-props {"updates":"props.json contents"}\n- MCP tool: replace-prop-text {"find":"Old label","replace":"New label","names":["aria-label","title"],"limit":20}\n\nNotes:\n\n- Use this for fixed prop values such as `aria-label`, `alt`, `id`, static `href`, and other direct string/number/boolean/json prop values.\n\n## Add JSON-LD structured data\n\nCommands:\n\n- MCP tool: components.get {"component":"JsonLd"}\n- MCP tool: insert-component {"parentInstanceId":"<headSlotInstanceId>","component":"JsonLd"}\n- MCP tool: update-props {"updates":[{"instanceId":"<jsonLdInstanceId>","name":"code","type":"string","value":"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Organization\\",\\"name\\":\\"Acme\\"}"}]}\n- MCP tool: bind-props {"bindings":[{"instanceId":"<jsonLdInstanceId>","name":"code","binding":{"type":"expression","value":"({ \'@context\': \'https://schema.org\', \'@type\': \'Article\', headline: post.title })"}}]}\n- MCP tool: audit {"scopes":["seo"],"pagePath":"/"}\n\nNotes:\n\n- Prefer placing `JsonLd` inside `HeadSlot`.\n- For fixed structured data, store `code` as a JSON object or array encoded as a compact string. The Builder formats it for editing.\n- For structured data containing runtime values, use `bind-props` with an expression that evaluates directly to an object or array. Do not call `JSON.stringify` or assemble JSON with string concatenation. Webstudio stores the expression as source text, evaluates it at runtime, and the `JsonLd` component validates and serializes the resulting value.\n- The semantic prop update rejects malformed JSON and structurally invalid fixed JSON-LD with a precise JSON path.\n- The SEO audit also warns about a missing top-level `@context`, unknown or superseded Schema.org terms, properties unsupported by the supplied type, and incompatible primitive value types.\n- Schema.org vocabulary findings are warnings because custom vocabularies and extensions remain valid. Dynamic JSON-LD is marked as skipped for rendered validation.\n- Do not use bindings just to set static text.\n\n## Delete props\n\nCommands:\n\n- MCP tool: delete-props {"deletions":"props.json contents"}\n\n## Bind props to expressions/resources/actions\n\nCommands:\n\n- MCP tool: bind-props {"bindings":"bindings.json contents"}\n\nNotes:\n\n- Use this only when the prop should remain dynamic: expression, resource, action, or an existing scoped runtime context value such as `system`.\n- For a fixed string value, use `update-props` with `type:"string"` and a direct `value` instead.\n\n## Read styles\n\nCommands:\n\n- MCP tool: get-styles {"instanceIds":["<instanceId>"],"includeTokens":true}\n\n## Update local styles\n\nCommands:\n\n- MCP tool: update-styles {"updates":"styles.json contents"}\n\n## Delete local styles\n\nCommands:\n\n- MCP tool: delete-styles {"deletions":"styles.json contents"}\n\n## Replace matching style values\n\nCommands:\n\n- MCP tool: replace-styles {"property":"color","fromValue":{"type":"keyword","value":"red"},"toValue":{"type":"keyword","value":"blue"}}\n\n## List design tokens\n\nCommands:\n\n- MCP tool: list-design-tokens {}\n- MCP tool: list-design-tokens {"withUsage":true}\n- MCP tool: list-design-tokens {"verbose":true}\n\nNotes:\n\n- The default response is compact and includes token id, name, declaration count, and optional usage count.\n- Use `verbose:true` only when you need the full inline style declarations.\n\n## Create design tokens\n\nCommands:\n\n- MCP tool: create-design-token {"tokens":"tokens.json contents"}\n\n## Update design token styles\n\nCommands:\n\n- MCP tool: update-design-token-styles {"designTokenId":"<tokenId>","updates":"styles.json contents"}\n\n## Delete design token styles\n\nCommands:\n\n- MCP tool: delete-design-token-styles {"designTokenId":"<tokenId>","deletions":"styles.json contents"}\n\n## Attach design token to instances\n\nCommands:\n\n- MCP tool: attach-design-token {"designTokenId":"<tokenId>","instanceIds":"instances.json contents"}\n\n## Detach design token from instances\n\nCommands:\n\n- MCP tool: detach-design-token {"designTokenId":"<tokenId>","instanceIds":"instances.json contents"}\n\n## Extract design token from local styles\n\nCommands:\n\n- MCP tool: extract-design-token {"instanceIds":["<instanceId>"],"name":"Brand Primary","removeLocalProps":["color"]}\n\n## List CSS variables\n\nCommands:\n\n- MCP tool: list-css-variables {"withUsage":true}\n\n## Define CSS variables\n\nCommands:\n\n- MCP tool: define-css-variable {"vars":{"--color-primary":"#2d3748","--color-accent":"#e53e3e","--space-card":"1.5rem"},"overwrite":true}\n\nNotes:\n\n- Define or overwrite multiple CSS variables atomically by including every name and value in one `vars` object.\n- Pass colors as CSS strings. Structured `hex` color components use normalized values from `0` to `1`, not `0` to `255`.\n\n## Delete CSS variables\n\nCommands:\n\n- MCP tool: delete-css-variable {"names":["--color-primary","--color-accent","--space-card"],"force":true}\n\nNotes:\n\n- Delete multiple CSS variables atomically by including every name in one `names` array. Destructive MCP calls still require the returned confirmation token before they commit.\n\n## Rewrite CSS variable references\n\nCommands:\n\n- MCP tool: rewrite-css-variable-refs {"map":"variables.json contents"}\n\n## List data variables\n\nCommands:\n\n- MCP tool: list-variables {}\n- MCP tool: list-variables {"scopeInstanceId":"<instanceId>"}\n\nNotes:\n\n- Data variables live in the internal `dataSources` namespace.\n- For raw `snapshot`, request the public `variables` namespace rather than the internal `dataSources` name. Raw patch payloads still use `dataSources` when applying direct changes.\n- Scope variables to the instance where they should become available. Descendants can use them in expressions, and nested variables with the same name mask outer variables.\n\n## Create data variable\n\nCommands:\n\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"title","value":{"type":"string","value":"Hello"}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"count","value":{"type":"number","value":3}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"featured","value":{"type":"boolean","value":true}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"tags","value":{"type":"json","value":["news","product"]}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"filters","value":{"type":"json","value":{"tag":"news"}}}\n\nNotes:\n\n- Data variable values support `string`, `number`, `boolean`, and `json`. Use `json` for all arrays and objects.\n- Parameters are internal scoped runtime values provided by pages, collections, or components. They are not a public authoring surface: do not create, update, or delete parameter records. Use data variables/resources for user-authored data, and reference documented context values such as `system` only where they are already in scope.\n\n## Update data variable\n\nCommands:\n\n- MCP tool: update-variable {"dataSourceId":"<variableId>","values":{"value":{"type":"json","value":{"count":1}}}}\n\n## Delete data variable\n\nCommands:\n\n- MCP tool: delete-variable {"dataSourceId":"<variableId>"}\n\n## List resources\n\nCommands:\n\n- MCP tool: list-resources {}\n- MCP tool: list-resources {"scopeInstanceId":"<instanceId>"}\n\n## Create resource\n\nCommands:\n\n- MCP tool: create-resource {"resource":{"name":"Posts","method":"get","url":"https://api.example.com/posts","headers":[]}}\n- MCP tool: create-resource {"resource":{"name":"Posts","method":"get","url":"\'https://api.example.com/posts?tag=\' + filters.tag","headers":[]},"scopeInstanceId":"<instanceId>","dataSourceName":"posts"}\n- MCP tool: create-resource {"resource":{"name":"Filtered Posts","method":"get","url":"https://api.example.com/posts","searchParams":[{"name":"tag","value":"filters.tag"},{"name":"source","value":{"type":"literal","value":"website"}}],"headers":[{"name":"Authorization","value":"\'Bearer \' + auth.token"}]},"scopeInstanceId":"<instanceId>","dataSourceName":"posts"}\n- MCP tool: create-resource {"resource":{"name":"Post GraphQL","control":"graphql","method":"post","url":"https://api.example.com/graphql","headers":[{"name":"Content-Type","value":{"type":"literal","value":"application/json"}}],"body":"{ query: \'query Post($slug: String!) { post(slug: $slug) { title } }\', variables: { slug: system.params.slug } }"},"scopeInstanceId":"<instanceId>","dataSourceName":"post"}\n- MCP tool: create-resource {"resource":{"name":"Current Date","control":"system","method":"get","url":"/$resources/current-date","headers":[]},"scopeInstanceId":"<instanceId>","dataSourceName":"currentDate"}\n\nNotes:\n\n- Resource `url` accepts plain fixed URLs and paths such as `https://api.example.com/posts` and `/$resources/current-date`.\n- Resource `url` can also be a JavaScript expression when it is computed, such as `"https://api.example.com/posts?tag=" + filters.tag`.\n- Header values, search parameter values, and body accept expressions for dynamic values. For fixed text, use `{"type":"literal","value":"application/json"}`; Webstudio stores the required string expression for you.\n- Search parameter values, header values, and body expressions can read scoped variables and documented runtime context values such as `system` when they are available at the resource scope.\n- Add `scopeInstanceId` and `dataSourceName` when the resource result should be exposed as a scoped read data variable. Scoped resources are generated into the page resource `data` map and may be loaded during page rendering. Use this for read-oriented resources such as GET CMS/API data.\n- For submit/write/action resources, create the resource without `scopeInstanceId`, then bind a component prop such as a Form `action` with `bind-props` and `binding.type: "resource"`. Prop-bound resources are generated into the page resource `action` map instead of the read `data` map. Use this for POST, PUT, DELETE, webhooks, GraphQL submissions, and other explicit action flows.\n- Resource `method` can be `get`, `post`, `put`, or `delete`. Use GET for read data, POST for creates/GraphQL/webhooks/form submissions, PUT for full updates or replacements, and DELETE for deletion actions.\n- Optional `control` values are `graphql` and `system`. Use `graphql` for GraphQL-style requests, usually POST with a query body. Use `system` for built-in resources such as `"/$resources/sitemap.xml"`, `"/$resources/current-date"`, and `"/$resources/assets"` and when the resource should use the built-in `system` parameter. System fields are `system.origin`, `system.pathname`, `system.params`, and `system.search`.\n\n## Update resource\n\nCommands:\n\n- MCP tool: update-resource {"resourceId":"<resourceId>","values":{"url":"https://api.example.com/posts"}}\n- MCP tool: replace-resource-text {"find":"api.old.example.com","replace":"api.example.com","fields":["url"],"limit":20}\n\n## Query Markdown assets\n\nCommands:\n\n- MCP tool: get-asset-field-catalog {}\n- MCP tool: validate-asset-query {"query":{"where":{"all":[{"field":["extension"],"operator":"eq","value":"md"},{"field":["properties","draft"],"operator":"ne","value":true}]},"limit":20}}\n- MCP tool: create-assets-resource {"name":"All assets","scopeInstanceId":"<instanceId>","dataSourceName":"assets"}\n- MCP tool: create-assets-resource {"name":"Published posts","scopeInstanceId":"<instanceId>","dataSourceName":"posts","query":{"result":"many","where":{"all":[{"field":["extension"],"operator":"eq","value":{"type":"literal","value":"md"}},{"field":["properties","draft"],"operator":"ne","value":{"type":"literal","value":true}}]},"sort":[{"field":["properties","publishedAt"],"direction":"desc"}],"limit":{"type":"literal","value":20},"output":{"mode":"fields","includeMetadata":false,"fields":[["properties","title"],["properties","slug"],["properties","publishedAt"],["properties","excerpt"]]},"content":{"mode":"none"}}}\n- MCP tool: create-assets-resource {"name":"Post by slug or ID","scopeInstanceId":"<instanceId>","dataSourceName":"post","query":{"result":"one","where":{"all":[{"field":["extension"],"operator":"eq","value":{"type":"literal","value":"md"}},{"field":["properties","slug"],"operator":"eq","value":"system.params.slug"}]},"output":{"mode":"fields","includeMetadata":false,"fields":[["properties","title"],["properties","publishedAt"]]},"content":{"mode":"markdown-body-ref"}}}\n- MCP tool: list-assets-resources {}\n- MCP tool: get-assets-resource {"resourceId":"<resourceId>"}\n- MCP tool: update-assets-resource {"resourceId":"<resourceId>","values":{"query":null}}\n- MCP tool: preview-asset-query {"query":{"result":"one","where":{"all":[{"field":["extension"],"operator":"eq","value":"md"},{"field":["properties","slug"],"operator":"eq","value":"hello-world"}]},"output":{"mode":"fields","includeMetadata":false,"fields":[["properties","title"]]},"content":{"mode":"markdown-body-ref"}}}\n\nNotes:\n\n- Read the field catalog before authoring unfamiliar queries. It includes dynamic schema-less frontmatter paths such as `properties.author.name`, observed types, optionality, and mixed-type state without downloading the source files.\n- Minimize the deployed content database by using `output.mode:"fields"` and selecting only fields the rendered page needs. Keep `includeMetadata:false` unless the rendered value needs file metadata such as name, path, MIME type, or creation date, and avoid `output.mode:"all"` as a convenience default. Query diagnostics are returned separately and do not require metadata output. Filters and sorting may still require their referenced fields in the database.\n- Every reachable Assets data source contributes to the shared published database. Keep one final resource per rendered query. Update an existing scoped resource rather than creating a placeholder, preview copy, or repair replacement, and remove obsolete duplicates.\n- Keep bounded overview filters, limits, and offsets literal and add a deterministic ID tie-breaker to the sort. With `content.mode:"none"`, compilation can materialize the small overview result instead of retaining its output fields across every candidate document. Use runtime expressions only for genuinely dynamic values such as the detail slug.\n- Combine filters with `where.all` (AND) and `where.any` (OR), including nested groups. Filter values, limit, and offset on a saved resource may be Webstudio expressions evaluated at render time. Preview queries use concrete JSON values.\n- Query Markdown or MDX files directly and use `content.mode:"markdown-body-ref"` when rendering their bodies. The published database retains metadata and a document reference, then fetches only the selected files from Asset storage at runtime; it does not embed their bodies. Filter and paginate before content is loaded.\n- `full` and bounded `range` continue to request embedded file bytes. Use them only when the caller requires the complete source or a byte range.\n- In Markdown or MDX, reference sibling Assets with conventional relative URLs such as `../images/hero.png`. Deferred `markdown-body-ref` content resolves matching files against the document\'s folder and emits the correct Builder or published Asset URL. Keep external URLs absolute.\n- Markdown Embed renders sanitized authored HTML for figures, captions, audio, video, and iframes. Scripts, inline event handlers, `srcdoc`, and unsafe URL protocols are removed. It does not render Webstudio MDX elements; connect the `.mdx` file to a Content Block for that workflow.\n- Preview each query with concrete values before saving it. Inspect `__diagnostics__.query` for the temporary query-only footprint and `__diagnostics__.database` for the merged database built from all reachable Assets queries. Only `database.usedBytes` counts toward `database.maxBytes`; query sizes must not be summed and are not separate allowances. Compare `usedBytes`, `unboundedBytes`, and `truncated` within both scopes. A completed Markdown blog should include every source document without truncation, contain no embedded Markdown bodies, and retain only its intended materialized overview query. When merged usage approaches the limit, remove duplicate reachable resources first, then unused output fields, then narrow candidate files.\n- Use `result:"many"` for listings. It returns the ID-keyed map at `<dataSourceName>.data`, with `totalCount` and `hasMore` at `<dataSourceName>.meta`, and remains the default for existing queries. Bind a listing Collection to `posts.data`.\n- Use `result:"one"` for a unique detail route. It returns the selected item or `null` directly at `post.data`, always includes its `id`, and omits pagination. Bind components and page settings directly with expressions such as `post.data.properties.title`, `post.data.content.text`, and `post.data ? 200 : 404`. `result:"first"` and `result:"last"` also return a direct item or `null` but require explicit sorting.\n- Assets always executes a structured query. Omit `query` to use the default many-result query, which selects URL and optional image dimensions. Set `values.query:null` to restore it.\n- `create-assets-resource` and `update-assets-resource` are the semantic authoring path. Do not construct the internal query URL, headers, or body expression manually.\n- The shared metadata index is maintained automatically and is emitted only when a configured Assets resource is reachable.\n\n## Delete resource\n\nCommands:\n\n- MCP tool: delete-resource {"resourceId":"<resourceId>"}\n\n## List assets\n\nCommands:\n\n- MCP tool: list-assets {"withUsage":true}\n- MCP tool: list-assets {"verbose":true}\n\nNotes:\n\n- Compact results include each asset\'s folder id. Use `verbose:true` to include complete records for a page of assets, or `get-asset` to read one complete record including description, folder, creation time, and image/font metadata.\n- Image asset descriptions are the default alt text for asset-backed Image components.\n- To generate missing descriptions, inspect the image in its rendered page or asset source, write a concise description of its purpose, and save it on the asset rather than duplicating it on each Image instance.\n\n## Get asset\n\nCommands:\n\n- MCP tool: get-asset {"assetId":"<assetId>"}\n\n## List asset folders\n\nCommands:\n\n- MCP tool: list-asset-folders {}\n\n## Create asset folder\n\nCommands:\n\n- MCP tool: create-asset-folder {"name":"Marketing"}\n- MCP tool: create-asset-folder {"name":"Photos","parentId":"<parentFolderId>"}\n\n## Update asset folder\n\nCommands:\n\n- MCP tool: update-asset-folder {"folderId":"<folderId>","values":{"name":"Brand"}}\n- MCP tool: update-asset-folder {"folderId":"<folderId>","values":{"parentId":"<parentFolderId>"}}\n- MCP tool: update-asset-folder {"folderId":"<folderId>","values":{"parentId":null}}\n\nNotes:\n\n- Updating `parentId` is the folder equivalent of cut and paste. Use `null` to move a folder to Root.\n\n## Duplicate asset folder\n\nCommands:\n\n- MCP tool: duplicate-asset-folder {"folderId":"<folderId>"}\n- MCP tool: duplicate-asset-folder {"folderId":"<folderId>","parentId":"<targetFolderId>"}\n\nNotes:\n\n- Duplication recursively copies descendant folders and assets. This is the folder equivalent of copy and paste.\n\n## Delete asset folder\n\nCommands:\n\n- MCP tool: delete-asset-folder {"folderId":"<folderId>"}\n\nNotes:\n\n- Deleting a folder recursively deletes its descendant folders and assets.\n\n## Update asset metadata\n\nCommands:\n\n- MCP tool: update-asset {"assetId":"<assetId>","values":{"description":"Team collaborating around a whiteboard"}}\n- MCP tool: update-asset {"assetId":"<fontAssetId>","values":{"meta":{"family":"Rajdhani","style":"normal","weight":600}}}\n\nNotes:\n\n- Use an empty description only when the image is intentionally decorative.\n- Updating an image asset description updates the default alt text wherever that asset is used with an asset-backed alt prop.\n- Font metadata updates merge with the detected metadata and are validated before committing; use this to correct a family, style, or weight after upload.\n\n## Generate missing image descriptions with an agent\n\nCommands:\n\n- MCP tool: audit {"scopes":["accessibility"],"verbose":true}\n- MCP tool: set-image-descriptions {"updates":[{"assetId":"hero-id","description":"Team collaborating around a whiteboard"},{"assetId":"texture-id","decorative":true}]}\n- MCP tool: audit {"scopes":["accessibility"]}\n\nNotes:\n\n- Start from `missing-image-description` findings. Inspect each image in its rendered page context before writing text.\n- The vision-capable agent generates the wording; the CLI validates and stores it but does not contain its own vision model.\n- Use `decorative:true` only when the image adds no information. This intentionally stores an empty description so later audits do not report it as missing.\n- Re-run the accessibility audit after the update. Asset-backed Image components use the saved asset description as their default alt text.\n\n## Manage fonts\n\nCommands:\n\n- MCP tool: list-fonts {"includeSystem":true}\n- MCP tool: list-assets {"type":"font"}\n- MCP tool: upload-asset {"asset":{"name":"acme-sans.woff2","type":"font","format":"woff2","meta":{"family":"Acme Sans","style":"normal","weight":400}},"assetsDir":".webstudio/assets"}\n- MCP tool: update-styles {"updates":"styles.json contents"}\n\nNotes:\n\n- Use `list-fonts` to discover uploaded families and system stacks. Upload/delete fonts through the existing asset tools, then apply a family with a `font-family` style declaration.\n\n## Upload one asset\n\nCommands:\n\n- MCP tool: upload-asset {"asset":{"name":"image.png","type":"image","format":"png","meta":{"width":1200,"height":630}},"assetsDir":".webstudio/assets"}\n- MCP tool: upload-asset {"asset":{"name":"image.png","type":"image","format":"png","folderId":"<folderId>","meta":{"width":1200,"height":630}},"assetsDir":".webstudio/assets"}\n\n## Upload asset batch\n\nCommands:\n\n- MCP tool: upload-assets {"assets":[{"name":"image.png","type":"image","format":"png","meta":{"width":1200,"height":630}}],"assetsDir":".webstudio/assets"}\n\nNotes:\n\n- Multi-file uploads return `uploaded`, `failed`, and `ambiguous` lists with the\n original input index. Retry only `failed` files. A forced upload in\n `ambiguous` may already be committed; inspect the Assets list before deciding\n whether to upload it again. Successful uploads remain committed.\n\n## Duplicate asset\n\nCommands:\n\n- MCP tool: duplicate-asset {"assetId":"<assetId>"}\n- MCP tool: duplicate-asset {"assetId":"<assetId>","folderId":"<targetFolderId>"}\n- MCP tool: duplicate-asset {"assetId":"<assetId>","folderId":null}\n\nNotes:\n\n- Duplication is the asset equivalent of copy and paste. Updating `folderId` is the equivalent of cut and paste; use `null` for Root.\n\n## Download asset\n\nCommands:\n\n- MCP tool: download-asset {"assetId":"<assetId>"}\n\n## Find asset usage\n\nCommands:\n\n- MCP tool: find-asset-usage {"assetId":"<assetId>"}\n\n## Replace asset references\n\nCommands:\n\n- MCP tool: replace-asset {"fromAssetId":"<oldAssetId>","toAssetId":"<newAssetId>"}\n\n## Delete assets\n\nCommands:\n\n- MCP tool: delete-asset {"assetIdsOrPrefixes":["<assetId>"],"force":true}\n\n## Publish project\n\nCommands:\n\n- webstudio publish deploy --target production --json\n\n## List publishes\n\nCommands:\n\n- webstudio publish list --json\n\n## Check publish job\n\nCommands:\n\n- webstudio publish status --job <buildId> --json\n\n## Unpublish\n\nCommands:\n\n- webstudio publish unpublish --target production --confirm --json\n\n## List domains\n\nCommands:\n\n- webstudio domains list --json\n\n## Create domain\n\nCommands:\n\n- webstudio domains create --domain example.com --json\n\n## Update domain\n\nCommands:\n\n- webstudio domains update --domain-id <domainId> --domain www.example.com --json\n\n## Delete domain\n\nCommands:\n\n- webstudio domains delete --domain-id <domainId> --confirm --json\n\n## Verify domain\n\nCommands:\n\n- webstudio domains verify --domain-id <domainId> --json\n\n## Make arbitrary store-level changes\n\nCommands:\n\n- MCP tool: inspect {}\n- MCP tool: snapshot {"include":["<namespace>"]}\n- MCP tool: apply-patch {"baseVersion":"<version>","transactions":"patch.json contents"}\n\nNotes:\n\n- Use only when no semantic command exists.\n\n## Manage marketplace metadata\n\nCommands:\n\n- MCP tool: get-marketplace-product {}\n- MCP tool: update-marketplace-product {"category":"pageTemplates","name":"Acme Template","thumbnailAssetId":"asset-id","author":"Acme Studio","email":"hello@example.com","website":"https://example.com","issues":"","description":"Reusable template project for Acme landing pages."}\n- MCP tool: submit-marketplace-product {"acknowledgePublicSubmission":true}\n\nPatch namespaces:\n\n- marketplaceProduct\n\n## Search and inspect safely\n\nCommands:\n\n- webstudio search-project \'{"query":"pricing"}\'\n- MCP tool: search-project {"query":"pricing"}\n- MCP tool: search-project {"query":"api.example.com","namespaces":["resources"]}\n- MCP tool: search-project {"query":"Brand","namespaces":["styles","styleSources","styleSourceSelections"]}\n- MCP tool: list-instances {"pagePath":"/","maxDepth":5}\n- MCP tool: inspect-instance {"instanceId":"<instanceId>","include":["props","styles","children"]}\n- MCP tool: list-texts {"pagePath":"/"}\n- MCP tool: list-assets {"withUsage":true}\n- MCP tool: find-asset-usage {"assetId":"<assetId>"}\n- MCP tool: snapshot {"include":["pages","instances","props","resources","assets"]}\n\nNotes:\n\n- `search-project` is a standalone local-session command over every Builder namespace. It returns matching values with stable match ids, namespace paths, owning entities, references, and affected pages.\n- Use it when a value or id is known. Use list/get tools when the project structure or target is not known.\n- The command synchronizes its required Builder namespaces, then searches locally and returns only matches. Namespace filters limit values matched; related namespaces may still supply route and reference context, and synchronization is unchanged. This reduces data passed to the model, not the initial project-data synchronization.\n- Asset records and file metadata are searchable. Asset binary or document contents are not Builder namespace data and are not searched.\n- Values in recognized credential fields are excluded and counted in `excludedSensitiveValueCount`.\n- Use `audit` for project health findings.\n\n## Audit project quality\n\nCommands:\n\n- webstudio audit --json\n- webstudio audit --scopes accessibility --scopes seo --json\n- webstudio audit --page-path /pricing --json\n- webstudio audit --scopes accessibility --verbose --json\n- webstudio audit --rendered --page-path /pricing --json\n- webstudio audit --rendered --route-example post=/blog/hello --json\n- webstudio audit --rendered --image-domain images.example.com --json\n- MCP tool: audit {}\n- MCP tool: audit {"scopes":["accessibility","security"],"severities":["error","warning"]}\n- MCP tool: audit {"scopes":["accessibility"],"verbose":true}\n- MCP tool: audit {"scopes":["craft"],"verbose":true}\n- MCP tool: audit {"rendered":true,"verbose":true}\n\nNotes:\n\n- With no scopes, `audit` checks accessibility, security, SEO, performance settings, unused assets, ineffective Collection styles, non-GET resources exposed as render-time data, and unused or duplicate style data.\n- Craft is opt-in and read-only. Run `audit` with `scopes:["craft"]` to detect whether the project is not using Craft, partially compatible, or compatible with the versioned Craft 1.2 profile. `profileStatuses` includes the University-doc provenance and the smallest safe next action. The audit never installs Craft or changes a non-Craft project.\n- The `performance` scope reports disabled atomic CSS generation. A rendered audit also measures broken, eager below-fold, and oversized images, browser-marked render-blocking resources, and legacy font formats.\n- Rendered image and resource metrics run only when the selected scopes include `performance`; responsive layout dimensions remain available whenever `rendered:true` is requested.\n- Compact findings include stable ids, severity, message, and location. Use `--verbose` or `{"verbose":true}` for evidence, explanation, suggested remediation, skipped-check details, and manual-check workflows.\n- `summary` counts all findings before severity filtering and pagination.\n- `contractVersion` identifies the audit response contract. Handle a new value before assuming existing fields retain the same meaning.\n- Expression-, resource-, and parameter-backed values that cannot be checked reliably appear in `skippedChecks`; they are not treated as passing or failing.\n- Page filters apply to page-owned accessibility, security, and SEO checks. Asset and style usage remain project-wide to avoid false unused findings.\n- Continue paginated results with `cursor`. Restart the audit if the project version changes.\n- Verbose skipped-check and manual-check details are included on the first findings page only; their total counts remain available on every page.\n- `manualChecks` describes responsive, hierarchy, and contrast checks that require preview screenshots and vision.\n- Focused audits return only manual checks relevant to their selected scopes.\n- In a long-lived MCP session, `{"rendered":true}` reuses preview and screenshot\n tools to capture every static page at mobile, desktop, and Builder breakpoint\n edges. Compact output reports rendered check/issue/failure counts; verbose\n output includes screenshot paths and measured layout dimensions.\n- Dynamic route templates are skipped unless `--route-example <pageId>=<path>`\n (or MCP `routeExamples`) supplies a concrete path. The path must not contain\n unresolved `:` or `*` parameters.\n- Plans above 120 captures return a short-lived confirmation token. Review the\n unchanged plan, then rerun with `--confirm-large-run` and\n `--confirmation-token`.\n- Detailed rendered evidence is stored in a versioned manifest under\n `.webstudio/audits`; compact output includes its path and screenshot count.\n- Rendered checks also report broken images, eager images below the fold, and\n image sources more than 2x their rendered dimensions in both axes, including\n Webstudio instance ids and measured dimensions when available.\n- Rendered checks include sanitized Resource Timing evidence and report\n browser-marked render-blocking resources plus legacy `.ttf`, `.otf`, and\n `.woff` fonts without applying a universal transfer-size threshold.\n- Fix findings through semantic mutation commands, then rerun `audit` to confirm their deterministic finding ids disappeared.\n\n## Verify dynamic bindings\n\nCommands:\n\n- webstudio verify-bindings --json\n- MCP tool: verify-bindings {"pagePath":"/pricing"}\n- MCP tool: verify-bindings {"instanceId":"<instanceId>","limit":50}\n\nNotes:\n\n- Statically checks persisted text expressions, expression/action/resource/parameter props, resource expressions, and page metadata.\n- Findings distinguish invalid syntax, unknown or out-of-scope variables, stale internal data-source ids, and missing resource or parameter references.\n- Page and instance filters can be combined when the instance belongs to the selected page. Continue findings with `cursor`.\n- This operation does not resolve rendered values or execute external resources. Preview representative loading, empty, error, and populated states after static findings are fixed.\n\n## Refactor targeted content\n\nCommands:\n\n- MCP tool: list-instances {"pagePath":"/"}\n- MCP tool: list-texts {"pagePath":"/"}\n- MCP tool: update-text {"instanceId":"<instanceId>","childIndex":0,"text":"Launch faster"}\n- MCP tool: replace-text {"find":"Old headline","replace":"New headline","match":"exact","pagePath":"/pricing","limit":20}\n- MCP tool: update-props {"updates":"props.json contents"}\n- MCP tool: update-page {"pageId":"<pageId>","values":{"title":"Pricing","meta":{"description":"Plans"}}}\n- MCP tool: update-resource {"resourceId":"<resourceId>","values":{"url":"https://api.example.com/posts"}}\n- MCP tool: replace-asset {"fromAssetId":"<oldAssetId>","toAssetId":"<newAssetId>"}\n- MCP tool: replace-styles {"property":"color","fromValue":{"type":"keyword","value":"red"},"toValue":{"type":"keyword","value":"blue"}}\n- MCP tool: rewrite-css-variable-refs {"map":"variables.json contents"}\n\nNotes:\n\n- Use focused reads first, then mutate only matching instances, props, metadata, resource URLs, assets, or style references. Use `replace-text` for bounded literal text changes, `replace-prop-text` for bounded static prop text, `replace-resource-text` for fixed resource names/URLs, and `update-text` for one known child or expressions.\n\n## Optimize existing project\n\nCommands:\n\n- MCP tool: list-pages {}\n- MCP tool: list-folders {}\n- MCP tool: update-page {"pageId":"<pageId>","values":{"title":"Pricing","meta":{"description":"Plans"}}}\n- MCP tool: update-props {"updates":"props.json contents"}\n- MCP tool: list-breakpoints {}\n- MCP tool: update-breakpoint {"breakpointId":"tablet","values":{"maxWidth":1023}}\n- MCP tool: get-styles {"instanceIds":["<instanceId>"],"includeTokens":true}\n- MCP tool: update-styles {"updates":"styles.json contents"}\n- MCP tool: attach-design-token {"designTokenId":"<tokenId>","instanceIds":"instances.json contents"}\n- MCP tool: update-project-settings {"meta":{"siteName":"Acme"}}\n\nNotes:\n\n- Use this for SEO metadata, accessibility labels, responsive behavior, token consistency, and project settings.\n\n## Connect external data\n\nCommands:\n\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"title","value":{"type":"string","value":"Hello"}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"tags","value":{"type":"json","value":["news","product"]}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"filters","value":{"type":"json","value":{"tag":"news"}}}\n- MCP tool: create-resource {"resource":{"name":"Posts","method":"get","url":"https://api.example.com/posts","searchParams":[{"name":"tag","value":"filters.tag"},{"name":"source","value":{"type":"literal","value":"website"}}],"headers":[]},"scopeInstanceId":"<instanceId>","dataSourceName":"posts"}\n- MCP tool: create-resource {"resource":{"name":"Post GraphQL","control":"graphql","method":"post","url":"https://api.example.com/graphql","headers":[{"name":"Content-Type","value":{"type":"literal","value":"application/json"}}],"body":"{ query: \'query Post($slug: String!) { post(slug: $slug) { title } }\', variables: { slug: system.params.slug } }"},"scopeInstanceId":"<instanceId>","dataSourceName":"post"}\n- MCP tool: create-resource {"resource":{"name":"Current Date","control":"system","method":"get","url":"/$resources/current-date","headers":[]},"scopeInstanceId":"<instanceId>","dataSourceName":"currentDate"}\n- MCP tool: update-resource {"resourceId":"<resourceId>","values":{"url":"https://api.example.com/posts"}}\n- MCP tool: bind-props {"bindings":"bindings.json contents"}\n- MCP tool: insert-fragment {"parentInstanceId":"<instanceId>","fragment":"<ws.collection>{/_ collection content _/}</ws.collection>"}\n\nNotes:\n\n- Use this for CMS sections, blog listings, Ghost/headless CMS pages, n8n-style integrations, and API URLs built from variables.\n- For read data, expose GET resources as scoped data variables with `scopeInstanceId`/`dataSourceName` and read the loaded result from the resource result wrapper, usually `.data`.\n- For writes, webhooks, GraphQL submissions, and deletes, prefer unscoped resources bound to Form `action` props so they become action resources instead of auto-loaded read resources.\n- Use direct props for fixed values and prop bindings only when a prop must read a data variable, resource, action, or documented runtime context value such as `system`.\n\n## Render an array or object as repeated content\n\nCommands:\n\n- MCP tool: insert-collection {"parentInstanceId":"<instanceId>","data":{"type":"expression","value":"posts.data.items"},"itemFragment":"<article><h2>{expression`collectionItem.title`}</h2></article>"}\n- MCP tool: inspect-instance {"instanceId":"<collectionId>","include":["props","bindings","children"]}\n\nNotes:\n\n- Use Collection whenever an array or object from a resource or data variable should render a list, grid, cards, table rows, options, tabs, or other repeated UI.\n- Pass `insert-collection` the complete array or object. Do not pass the resource response wrapper or one indexed item. External resource arrays are commonly nested under the scoped resource result\'s `data` field or deeper.\n- Pass one repeated-item Webstudio JSX root. The command creates the Collection, its private current-item/current-key parameters, the iterable binding, and descendant item bindings atomically.\n- Collection renders the item root once for every entry. Use `expression` values such as `collectionItem.title` in descendant text and props. Object iteration also exposes `collectionItemKey`.\n- Wrap multiple repeated sibling instances in one Element inside Collection.\n- For repeated Radix items such as accordion items, tabs, or menu options, bind a stable unique id or slug to every required `value` prop.\n- See the [Collection documentation](https://docs.webstudio.is/university/core-components/collection) for the equivalent Builder workflow.\n\n## Support dynamic runtime behavior\n\nCommands:\n\n- MCP tool: integrate-runtime-ui {"parentInstanceId":"<instanceId>","resources":[{"resource":{"name":"Seats","method":"get","url":"https://api.example.com/seats","headers":[]},"dataSourceName":"Seats","exposeAsDataSource":true}],"structure":{"type":"collection","data":{"type":"expression","value":"Seats.data"},"itemFragment":{"children":[{"type":"id","value":"seat"}],"instances":[{"type":"instance","id":"seat","component":"Text","children":[{"type":"expression","value":"collectionItem.label"}]}],"props":[],"dataSources":[],"resources":[],"styleSources":[],"styleSourceSelections":[],"styles":[],"breakpoints":[],"assets":[]}},"retainedBehavior":[{"instanceId":"<scriptInstanceId>","responsibility":"Seat selection behavior"}]}\n- MCP tool: update-props {"updates":"props.json contents"}\n- MCP tool: bind-props {"bindings":"bindings.json contents"}\n- MCP tool: create-resource {"resource":{"name":"Seats","method":"get","url":"https://api.example.com/seats","headers":[]}}\n- MCP tool: snapshot {"include":["instances","props","resources"]}\n- MCP tool: apply-patch {"baseVersion":"<version>","transactions":"patch.json contents"}\n\nNotes:\n\n- Use `integrate-runtime-ui` to create variables/resources, insert one editable fragment or Collection, and add safe data bindings in one transaction.\n- List existing script-owned responsibilities under `retainedBehavior`. The operation preserves those instances and never evaluates or accepts replacement script bodies.\n- `unsupportedConversions` records behavior that cannot be represented safely. Dry-run returns the complete transaction and the same retained/unsupported report without changing the project.\n- New actions and HtmlEmbed scripts are intentionally rejected. Create normal editable components and data bindings; keep opaque runtime behavior in existing script instances.\n\n## Build authenticated pages\n\nCommands:\n\n- MCP tool: meta.guide {"brief":"Build a Supabase-authenticated account page","taskScope":"structural-project-change","workflow":"authenticated-page"}\n- MCP tool: create-page {"name":"Account","path":"/account"}\n- MCP tool: update-page {"pageId":"<pageId>","values":{"meta":{"auth":{"method":"basic","login":"<login>","password":"<password>"}}}}\n- MCP tool: create-resource {"resource":{"name":"Session","method":"get","url":"https://api.example.com/session","headers":[]}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"user","value":{"type":"json","value":{}}}\n- MCP tool: update-props {"updates":"props.json contents"}\n- MCP tool: bind-props {"bindings":"bindings.json contents"}\n\nNotes:\n\n- Inspect and reuse the project\'s existing auth convention before authoring. Do\n not add a second provider or session model implicitly.\n- Model signed-out, loading, signed-in, and failed-auth states explicitly.\n- Never store credentials, service-role keys, refresh tokens, private session\n values, or authenticated response bodies in project data, command output,\n screenshots, agent instructions, or error reports. Privileged provider calls\n and authorization enforcement belong server-side.\n- Basic auth is semantic today. Provider-specific Supabase/Firebase setup still\n uses the existing resource, variable, prop, binding, and embed tools; there is\n no provider-specific installer.\n\n## Generate from design input\n\nCommands:\n\n- MCP tool: meta.guide {"brief":"Recreate this Figma design as a responsive page","taskScope":"visual-change","workflow":"design-input"}\n- MCP tool: create-page {"name":"Landing","path":"/landing"}\n- MCP tool: create-design-token {"tokens":"tokens.json contents"}\n- MCP tool: define-css-variable {"vars":"vars.json contents"}\n- MCP tool: list-breakpoints {}\n- MCP tool: insert-fragment {"parentInstanceId":"<instanceId>","fragment":"<section><p>Section copy</p></section>"}\n- MCP tool: update-styles {"updates":[{"instanceId":"<instanceId>","breakpoint":"<breakpointId-from-list-breakpoints>","property":"padding-left","value":{"type":"unit","unit":"px","value":24}}]}\n- MCP tool: preview.start {}\n- MCP tool: screenshot {"path":"/landing","output":"landing-desktop.png","viewport":{"width":1440,"height":900},"waitUntil":"load","waitForTimeout":250}\n- MCP tool: screenshot {"path":"/landing","output":"landing-mobile.png","viewport":{"width":390,"height":844},"waitUntil":"load","waitForTimeout":250}\n- MCP tool: screenshot {"baseUrl":"http://127.0.0.1:5177","path":"/landing","output":"landing-desktop.png","viewport":{"width":1440,"height":900},"waitUntil":"load","waitForTimeout":250}\n\nNotes:\n\n- Use this after the agent can inspect the supplied design. There is no direct\n Figma, screenshot, Inception, or `design.md` import command.\n- Inspect and reuse existing variables, tokens, styles, components, assets, and\n page patterns before authoring. Build semantic editable structure rather than\n flattening the design into an image or absolute-positioned approximation.\n- Ask whether the user wants visual verification unless they explicitly requested\n it. If they decline, stop after focused reads and a static audit. If they opt\n in, verify one familiar viewport inside every distinct Builder breakpoint\n range, then run rendered audit and inspect the screenshots before completion.\n\n## Cross-project maintenance\n\nCommands:\n\n- webstudio mcp run .temp/projects.json\n- webstudio mcp run .temp/projects.json --dry-run\n- webstudio mcp run .temp/projects.json --approve-mutations --concurrency 2\n\nNotes:\n\n- Put shared `calls` and a `projects` array of independently linked project roots in the existing `mcp run` manifest. Project roots are relative to the manifest file.\n- Each project uses its own config, authentication, ProjectSession storage, checkpoint, and failure boundary. Confirmed successful calls are checkpointed. Reads can resume automatically; a mutation interrupted after dispatch is reported as ambiguous and is not replayed automatically, preventing silent duplicate writes.\n- Focus the manifest on bounded reads or audits first. Use per-call `dryRun`, global `--dry-run`, or explicitly approve committed mutations with `--approve-mutations` after reviewing the manifest.\n\n# Known CLI Gaps\n\n## Provider-specific authenticated pages\n\nMissing:\nCLI supports page basic auth and generic resources/props/embeds, but not guided Supabase/Firebase auth setup.\n\nCurrent fallback:\nCall `meta.guide` with `workflow:"authenticated-page"`, then create the\npage, resources, variables, props, bindings, and embeds with existing semantic\ntools.\n\nSuggested commands:\n\n- setup-auth-page\n\n## Generate from design input\n\nMissing:\nNo command imports Figma, screenshots, Inception output, or design.md and turns it into pages/tokens/layout.\n\nCurrent fallback:\nCall `meta.guide` with `workflow:"design-input"`, let the agent inspect the supplied\ndesign, then use semantic page, token, asset, fragment, style, preview,\nscreenshot, and audit tools. Use `apply-patch` only when no semantic operation\nfits.\n\nSuggested commands:\n\n- generate-from-design\n\n## Built-in cross-project maintenance\n\nMissing:\nPublic API and CLI intentionally operate on one configured project at a time; there is no built-in multi-project discovery or loop runner.\n\nCurrent fallback:\nRun the CLI from an external script that reconfigures one project/session at a time.\n\nSuggested commands:\n\n- none\n',
|
|
344541
344677
|
"manual-api": '# Webstudio API CLI Manual\n\nThe API commands operate on the single project configured by:\n\n- .webstudio/config.json: projectId\n- global Webstudio config: origin and token\n\n- Pass --json to API/discovery commands that support it. Do not add --json to top-level commands unless their help/schema documents it.\n- Never pass a project id. Commands use configured project only.\n- Read ids before writing. Do not invent ids for existing records.\n- stdout is one JSON object. stderr is diagnostics.\n- Prefer MCP semantic tools for detailed project edits. Use MCP apply-patch only when no semantic tool exists.\n\n## Start\n\n{{start}}\n\n## Read First\n\n{{readFirst}}\n\n## Project Session Cache\n\n- CLI commands use one local ProjectSession snapshot for the configured project.\n- Local-capable reads use cached namespaces when compatible and fetch only missing or stale namespaces.\n- Local-capable mutations build patches from the local snapshot, then commit with the cached build version.\n- Successful mutation commits update the local snapshot only after the remote commit succeeds.\n- Server-only commands run remotely and invalidate/refetch namespaces declared by the operation catalog.\n- Use --refresh on local-capable commands to refresh required namespaces before running.\n- Successful JSON responses include compact meta.session with operationId, buildId, version, source, committed, namespaceCounts, diagnosticCount, non-empty diagnostic summaries, and optional compatibilityVersion.\n\n## Search project data\n\nRun `webstudio search-project \'{"query":"known value"}\'` to search all Builder\nnamespaces in the local project session. Narrow it with\n`{"query":"Brand","namespaces":["styleSources","styles"]}` when only specific\nnamespaces are relevant. Results contain the current value, stable match id,\nnamespace path, owning entity, resolved references, and affected pages.\n\nValues in recognized credential fields are excluded and reported in\n`excludedSensitiveValueCount`.\n\nThe command synchronizes its required Builder namespaces, then searches locally\nand returns only matches. Namespace filters limit values matched; related\nnamespaces may still supply route and reference context, and synchronization is\nunchanged. This reduces data sent to the model, not project synchronization. It\nsearches asset metadata, not asset binaries or document contents.\n\n## CLI Capability Inventory\n\nFor a short end-consumer summary of what MCP can do, see\n`manual mcp` / `webstudio man mcp`. The MCP inventory describes the same\nproject, page, element, style, data, asset, publish, domain, and visual\nverification capabilities without internal command names.\n\n### Top-Level Commands\n\n{{topLevelCapabilityIndex}}\n\n### High-Level API Commands By Area\n\n{{apiCapabilityIndex}}\n\n### MCP Tool Operations\n\nThese are MCP tools. From a shell, call them with the shortcut form `webstudio <tool> \'<json>\'` or with the explicit form `webstudio mcp single-op-call <tool> \'<json>\'`:\n\n{{mcpOnlyCommandIndex}}\n\n## Task Recipes\n\n{{taskRecipeIndex}}\n\n## Use Case Index\n\n{{useCaseIndex}}\n\n## Known CLI Gaps\n\n{{knownCliGapIndex}}\n\n## Input File Shapes\n\n{{inputFileShapeIndex}}\n\n## Raw Patch Fallback\n\napply-patch accepts either BuildPatchTransaction[] or { "transactions": BuildPatchTransaction[] }.\n\nEach transaction has:\n\n{\n"id": "patch-transaction-label",\n"payload": [\n{\n"namespace": "projectSettings",\n"patches": [\n{ "op": "replace", "path": ["meta", "siteName"], "value": "New Site" }\n]\n}\n]\n}\n\nThe transaction id is a patch label used for optimistic synchronization. It is\nnot a Builder record id. Do not invent ids for pages, instances, props,\nbreakpoints, resources, variables, folders, assets, or other project records.\n\nPatch paths are JSON-patch-like paths into Builder store data. Map-like namespaces use ids as the first path item.\n\nSupported namespaces:\n\n- pages: redirects, page records, and folders\n- projectSettings: project-wide metadata and compiler settings\n- instances: element instances and children, including text/expression children\n- props: element props, bindings, page references, resource bindings\n- styles: CSS declarations keyed by style declaration key\n- styleSources: local style sources and reusable design tokens\n- styleSourceSelections: instance-to-style-source connections\n- dataSources: data variables, parameters, and resource data sources\n- resources: data resource definitions\n- assets: project asset records handled by the existing asset patch path\n- breakpoints: responsive breakpoints\n- marketplaceProduct: marketplace metadata\n\n## Data Sources\n\n`dataSources` is the internal Builder namespace for variables. Public API, CLI,\nand MCP tools expose it through two user-facing groups:\n\n- data variables: `list-variables`, `create-variable`, `update-variable`, and\n `delete-variable`\n- data resources: `list-resources`, `create-resource`, `update-resource`, and\n `delete-resource`\n\nFor raw `snapshot`, request the public `variables` namespace rather than the\ninternal `dataSources` name. Raw patch payloads still use `dataSources` when\napplying direct changes.\n\nVariables can be scoped to an instance. Expressions under that instance can use\nthe variable by name; nested variables with the same name mask outer variables.\nVariable values support `string`, `number`, `boolean`, and `json`. Use `json`\nfor all arrays and objects, including tags, selected categories, and nested API\nfilter state.\nParameters are internal scoped runtime values provided by pages, collections,\nor components. They are not a public authoring surface: do not create, update,\nor delete parameter records. Public tools should preserve existing parameter\nrecords and may reference documented context values such as `system` in\nexpressions where they are already in scope.\n\nResource `url` accepts plain fixed URLs and paths, for example\n`https://api.example.com/posts` or `/$resources/current-date`. Dynamic URLs can\ncombine strings and variables, for example\n`"https://api.example.com/posts?tag=" + filters.tag`. Prefer `searchParams` for\nquery parameters that should be encoded separately:\n`[{ "name": "tag", "value": "filters.tag" }]`. Header values, search parameter\nvalues, and bodies are expressions for dynamic content. For fixed text, use\n`{ "type": "literal", "value": "application/json" }`; Webstudio stores the\nrequired string expression. Headers can still read variables such as\n`"Bearer " + auth.token`, and GraphQL bodies can return objects such as\n`{ query: "...", variables: { slug: system.params.slug } }`.\n\nCreate a GET resource with `scopeInstanceId` when the fetched resource result\nshould be available as a read data variable. Scoped GET resources default to\n`exposeAsDataSource: true`, are generated into the page resource `data` map,\nand may be loaded during page rendering. Use `dataSourceName` to choose the\nvariable name.\n\nFor submit/write/action resources, create the resource without\n`scopeInstanceId`, then bind a component prop such as a Form `action` to the\nresource with `bind-props` and `binding.type: "resource"`. Prop-bound resources\nare generated into the page resource `action` map instead of the read `data`\nmap. Use this shape for POST, PUT, DELETE, webhook, and other resources that\nshould run only from an explicit form/action flow, not merely because the page\nrendered.\n\nPOST, PUT, and DELETE resources default to `exposeAsDataSource: false` even\nwhen a scope is supplied. Set `exposeAsDataSource: true` only when a write-method\nresource intentionally provides render-time data, such as a read-only GraphQL\nPOST query. A scope is required, and the result includes a warning because the\nrequest may execute during page rendering. Set `exposeAsDataSource: false` on\n`update-resource` to detach an existing render-time data source.\n\nResource `method` can be `get`, `post`, `put`, or `delete`. Use GET for read\ndata. Use POST for creates, GraphQL requests, webhooks, and form submissions.\nUse PUT for full updates/replacements. Use DELETE for deletion actions.\nOptional `control` values are `graphql` and `system`: `graphql` marks a\nGraphQL-style resource, usually POST with a query body; `system` marks a\nresource intended to use the built-in `system` parameter or one of the built-in\nlocal resource URLs: `"/$resources/sitemap.xml"`,\n`"/$resources/current-date"`, and `"/$resources/assets"`. The system parameter\nfields are `system.origin`, `system.pathname`, `system.params`, and\n`system.search`.\n\nUse prop bindings for dynamic values that read variables or resources; use\ndirect props for static values.\n\nCommit raw patch:\n\nMCP tool: apply-patch\n\n## Raw Patch Examples\n\nRename the site:\n\n[\n{\n"id": "patch-site-name",\n"payload": [\n{\n"namespace": "projectSettings",\n"patches": [\n{ "op": "add", "path": ["meta", "siteName"], "value": "Acme Studio" }\n]\n}\n]\n}\n]\n\nUpdate page title metadata:\n\n[\n{\n"id": "patch-page-title",\n"payload": [\n{\n"namespace": "pages",\n"patches": [\n{ "op": "replace", "path": ["pages", "page-id", "meta", "title"], "value": "Pricing" }\n]\n}\n]\n}\n]\n\nUpdate a text child on an element:\n\n[\n{\n"id": "patch-text",\n"payload": [\n{\n"namespace": "instances",\n"patches": [\n{ "op": "replace", "path": ["instance-id", "children", 0, "value"], "value": "Launch faster" }\n]\n}\n]\n}\n]\n\nCreate records with semantic operations such as create-variable,\ncreate-resource, create-design-token, create-page, create-folder,\nand create-breakpoint. Raw patch rejects generated record\ncreation, collection replacement, record replacement with a different `id`, and\nrecord id field mutations in id-keyed namespaces because Webstudio must generate\nand preserve record ids.\n\n## Safety Rules\n\n- For MCP apply-patch, read the latest version with MCP snapshot before writing.\n- Reuse ids from MCP snapshot output when updating existing records.\n- Do not create generated records, replace generated record collections, replace records with different ids, or mutate record id fields with raw patch. Use semantic create operations so Webstudio generates ids.\n- If apply-patch reports a version conflict, read the latest build and regenerate the patch.\n- Prefer semantic MCP read tools for discovery, then use MCP snapshot for exact patch paths.\n\n## Command Index\n\n{{commandIndex}}\n',
|
|
344542
|
-
"manual-llm": '# Webstudio CLI Manual for LLMs\n\nUse this order. Stop only when a command returns ok:false.\n\nIf you are inside the Webstudio monorepo, the first command discovery should use\nthe local CLI exactly as `node packages/cli/local.js ...` from the repo root. Do\nnot use `packages/cli/bin.js` for local source-tree work; it is the packaged\nbuild entry and may use stale built output. Do not use `pnpm exec webstudio`,\n`pnpm --filter webstudio exec webstudio`, or a global `webstudio`: they can\nresolve an older binary.\n\nFor delegated design-system or “use every component” tasks, skip the generic warm-up sequence and start with exactly one MCP command: `webstudio workflow.next \'{"goal":"design-system-page"}\'`. Report that returned checkpoint to the parent/user and stop until continued.\n\n## Use MCP locally or optionally connect a client\n\nDo not install, register, or connect an MCP server merely because the user asks\nyou to edit a Webstudio project. If Webstudio MCP tools are already available,\nuse them. If you have shell access, use the local CLI shortcuts such as\n`webstudio meta.index` and `webstudio list-pages`; they expose the same project\noperations without changing client configuration or restarting the app.\n\nOnly when the user explicitly asks for persistent native MCP integration, run\nthe command for their client:\n\n- Claude Code: `webstudio connect claude`\n- Codex: `webstudio connect codex`\n- Cursor: `webstudio connect cursor`\n- VS Code or GitHub Copilot: `webstudio connect vscode`\n\nRun project operations from the linked project root. If the folder is not\nlinked, ask for an editable Builder share link and run\n`webstudio init --link <share-link> --json`. You can then use local CLI\nshortcuts immediately. Do not run `webstudio sync`, `webstudio connect`, or\nrestart the app unless the user specifically wants native MCP registration:\nMCP reads and edits the latest editable Builder build directly, including for\nprojects that have never been published. Treat the share link as a credential\nand do not include it in committed files, logs, screenshots, or issue reports.\n\nThe optional `connect` command verifies project access before changing client configuration. For\nClaude Code, Cursor, and VS Code it safely merges the `webstudio` server into\nthe client\'s project configuration. For Codex it runs both `codex mcp add` and\n`codex mcp get webstudio`; do not repeat those commands separately. Follow the\nreload, restart, or approval instruction printed by `connect`, then verify the\nloaded MCP connection by asking the client to use Webstudio MCP and list the\nproject pages. Use `--print` only to inspect the generated setup without\nchanging configuration or requiring project access.\n\n## Always\n\n1. webstudio permissions --json\n2. For bounded shell workflows, call MCP tools directly through the CLI shortcut, for example `webstudio meta.index` or `webstudio insert-fragment \'<json>\' --dry-run`. The explicit form `webstudio mcp single-op-call <tool> \'<json>\'` is equivalent and useful when you need to make the MCP boundary obvious. Use `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'` for multiple calls in one shared CLI session. Use a normal JSON file path for large batches. Use long-running `webstudio mcp` only when your environment is a real MCP client. Do not manually send raw JSON-RPC to `webstudio mcp` from a shell or PTY.\n3. Read MCP `meta.index`, for example `webstudio meta.index`.\n4. Use focused MCP calls with concrete JSON: `webstudio meta.guide \'{"brief":"Create a design system page using every component"}\'`, `webstudio meta.get-more-tools \'{"tools":["insert-fragment"]}\'`, `webstudio components.list \'{"source":"all"}\'`, `webstudio components.coverage-plan`, `webstudio components.search \'{"brief":"radix select"}\'`, `webstudio components.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'`, `webstudio templates.list`, and `webstudio templates.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'`.\n5. Read overview resources `webstudio://project/tools-overview` or `webstudio://project/components-overview` when useful. Read full resources `webstudio://project/tools` or `webstudio://project/components` only when focused tools are insufficient.\n6. Pick focused MCP read tool.\n7. Pick semantic MCP write tool.\n\nUse `webstudio schema mcp` for a compact MCP tool overview. Add `--verbose` only when exact input schemas for all tools are truly needed; otherwise prefer focused `meta.get-more-tools` and `components.*` calls.\n\nRun these commands from the linked project root. Use the MCP startup status line\'s absolute root for local files; write temporary scripts and artifacts under `<project root>/.temp`, not under a parent workspace.\n\nMonorepo quick path for a simple styled section:\n\n```sh\nnode packages/cli/local.js mcp single-op-call meta.index\nnode packages/cli/local.js mcp single-op-call meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nnode packages/cli/local.js mcp single-op-call insert-fragment --input-file .temp/insert-fragment.json --dry-run\n```\n\nSave the readable payload in `.temp/insert-fragment.json`:\n\n```json\n{\n "parentInstanceId": "parent-id",\n "fragment": "<section ws:style={css`padding: 32px; display: grid; gap: 12px;`}><h2>Launch Kit</h2><p>A focused section created with Webstudio JSX.</p><button>Get started</button></section>"\n}\n```\n\nSingle quotes inside the JSX keep the JSON valid without backslash-escaped attributes. The same local shortcut form is shorter and preferred for simple shell steps:\n\n```sh\nnode packages/cli/local.js meta.index\nnode packages/cli/local.js meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nnode packages/cli/local.js insert-fragment --input-file .temp/insert-fragment.json --dry-run\n```\n\nFor this simple path, do not grep source files, dump full MCP resources, or write parser scripts first. Use `list-pages`, `get-page-by-path`, or `list-instances` only to get the target `parentInstanceId`.\n\nWhen authoring JSX for `insert-fragment`, use Webstudio component helpers and Webstudio style syntax. Use `ws:style={css\\`...\\`}`for Webstudio-native CSS. For simpler cases, use React-style object syntax such as`style={{ padding: 24 }}`. Both forms create editable Webstudio style data.\n\nWhen the task says another user will edit a page in Content mode, use a Content Block (`ws:block`) around every editable region. Content-mode users can edit text and supported props only in descendants of that block; content outside it is read-only. Put reusable insertable options in exactly one direct `ws:block-template` child. Do not put intended editor content inside that template container: templates are protected source material, while an inserted template copy becomes editable Content Block body content. A missing or second template container makes the block invalid. Verify this structure before handoff.\n\nWhen the Content Block body must be stored in an `.mdx` Asset, use the dedicated `connect-content-block-source`, `switch-content-block-source`, `inspect-content-block-source`, `edit-content-block-source`, `update-content-block-frontmatter`, `reload-content-block-source`, and `disconnect-content-block-source` tools. Do not manipulate its `src` with generic prop tools. Connecting replaces existing Body content, so report `requiresConfirmation:true` and retry with `confirmReplacement:true` only after user approval. Prefer Markdown for standard document content; it automatically uses unique matching semantic templates. Use lowercase JSX such as `<section>` or `<svg>` for HTML or SVG that Markdown cannot express. Capitalized JSX resolves a unique stable template **Name** or a built-in MDX adapter. Add custom components to this Content Block\'s Templates before referencing them in MDX. The display label is independent. JSX attributes accept quoted values and bare booleans; expressions such as `{false}` are unsupported. Legacy `ws.element` and `ws:name` forms are compatibility input only. Component namespaces such as `$.*`, `radix.*`, and `animation.*` are unsupported; use direct component identifiers. Matching explicit children overlay designed descendants and keep their template styles. A mismatched child structure replaces defaults, while an explicit empty pair clears defaults and a self-closing reference keeps them. Editing inherited default content writes it back as explicit JSX children. Template resolution is live, including when a matching template is added after the MDX element. Preserve invalid MDX and unresolved template names, inspect all source-located diagnostics, and resolve revision conflicts by reloading before reapplying the change. After a template rename or deletion, use `migrate-content-block-template-references` to preview and confirm custom-template JSX and legacy updates across selected MDX files. A confirmed removal unwraps paired references and preserves their authored children; a self-closing reference disappears because it has none.\n\nFrontmatter is part of the same MDX source. `update-content-block-frontmatter` receives the complete replacement property map, so inspect and preserve properties the user did not ask to remove. Store frontmatter images as exact `$ref` objects. Bind editable Image sources to their resolved `.src` with explicit `binding.mode:"readwrite"`; omitting the mode leaves the image visible but not replaceable in Content mode. Bind alt properties to their Asset `.description` with `mode:"read"`. Use `update-content-block-frontmatter` for MCP frontmatter edits; MDX-rendered elements are not persistent targets for generic prop or text mutations. Preserve existing `mode:"readwrite"` bindings, which are valid only for exact direct frontmatter paths. Direct bindings through a loaded Markdown or MDX `$ref` ending in `#frontmatter` save to the referenced file, with its write permissions enforced. Shared-record edits affect every document using that record. Computed expressions and JSON/body references remain read-only. Image replacement is supported: a direct Image source binding with `mode:"readwrite"` lets the picker replace the frontmatter `$ref`, while shared Asset metadata is edited in Asset settings. For an expression-bound source inside a Collection, pass the occurrence\'s scoped values and a distinct stable `renderScope`; for example, resolve `post.assetId` with `variables:{"post":{"assetId":"<mdxAssetId>"}}`.\n\nFor an editable MDX article, use the resource to select the Content Block\'s source Asset (`post.data.id`), then bind article fields inside the block to its document parameter (`document.frontmatter.title`), not query-result properties. Inspect the actual document variable name first. Create writable bindings explicitly: `update-text` uses `expressionBindingMode:"readwrite"`; `bind-props` uses `binding.mode:"readwrite"`. These generic tools target persistent designed instances, such as a header outside the MDX body, not MDX-generated instances. The MDX body is editable through its source mapping; merely placing static or query-bound content inside the block does not make it editable. Use exact direct paths; property access is already safe. Direct bindings through a loaded Markdown or MDX `$ref` ending in `#frontmatter` save to the referenced file, with its write permissions enforced. Shared-record edits affect every document using that record. Computed expressions and JSON/body references remain read-only. Image replacement is supported: a direct Image source binding with `mode:"readwrite"` lets the picker replace the frontmatter `$ref`, while shared Asset metadata is edited in Asset settings. Follow the complete article editability checklist below before handoff.\n\n### Content Block completion checklist\n\nFor every Content Block creation, migration, or repair—with or without MDX—treat Content-mode editability as a delivery requirement for all content the user expects editors to change. Before implementation, inventory each value, its intended UI control, and its write destination. Choose controls by meaning: date/calendar controls for dates, image pickers for image replacement, and direct text or number controls for reading time. Keep fixed punctuation and units outside a directly bound value element; never mix literal and expression children there. Preserve the existing stored type and wording: if `readTime` already stores `4 min read`, bind that whole string directly instead of assuming it stores only a number. Distinguish protected layout/templates from editable content explicitly. Test each field through Content mode, check the saved project data or source file, reload, restore test values, and record passed/failed/not tested. Testing one heading, inspecting bindings, or successfully writing through MCP does not cover the remaining fields. Report missing controls and unsupported writes as unfinished requirements, not successful delivery.\n\n### Complete article editability is required\n\nWhen the user asks for the whole article to be editable in Content mode, this includes every article-owned value: title, excerpt, author details and links, dates, reading time, categories, hero and inline image sources, alternative text, captions, body text, links, and custom-component content and media. Exclude only designer-owned layout, styling, templates, and shared navigation/footer unless the user asks otherwise. A correct preview, a successful MCP write, or an editable body does not prove this requirement is met.\n\nBefore handoff:\n\n1. Inventory every article field, its UI control, binding, and source file/field. Header fields outside **MDX content** are still part of the article and must be editable through the Content Block\'s document bindings.\n2. In Content mode, edit every inventoried field through the UI, including choosing a different hero image and editing link destinations and custom-component props. Do not substitute a raw MDX/YAML edit or an MCP write for this check.\n3. Read the saved source after each edit. Confirm the intended MDX body/frontmatter or referenced author file changed, unrelated content stayed intact, and the edit survives reload. Restore test values and verify restoration.\n4. Mark each field passed, failed, or not tested. Do not claim the article is fully editable while any required field is failed or untested.\n\n**Image replacement is not shared metadata editing.** For frontmatter such as `featureImage: { $ref: "./images/hero.png" }`, bind the Image source directly to `document.frontmatter.featureImage.src` with `binding.mode:"readwrite"` using `bind-props`. In Content mode, **Choose source** replaces the article’s `featureImage.$ref`; it does not overwrite the shared Asset’s resolved `.src`. The resolved URL field stays read-only. Verify the picker, saved reference, and reload—do not infer support from the displayed image alone. Bind alternative text to `.description` to use the shared Asset description; edit it through **Choose source → asset actions → Settings → Description**, which affects every use of that Asset. Do not treat a missing write mode as a platform limitation, and do not defer image replacement over a separate shared-description question.\n\nKeep intended editable values separate from display formatting. For reading time, use three inline sibling text elements: static `— `, one value element bound directly to `document.frontmatter.readingTime` with `expressionBindingMode:"readwrite"`, and static ` min read`. The value element must contain only that binding, not mixed literal and expression children. Preserve whitespace and the stored field type. Do not concatenate labels or units, use template literals, or add fallbacks/formatting calls to an editable binding. Prefer component formatting controls with a directly bound value. Explain unsupported cases and ask before making an intended editable field read-only; do not trade away editability just to match the display.\n\nDo not access host globals or dynamic code APIs in JSX fragments, including `process`, `globalThis`, `eval`, `Function`, or `constructor`. JSX fragments are declarative project data; use the built-in Webstudio helpers instead.\n\nUse Webstudio prop names in JSX: `class`, `for`, `aria-label`, and other HTML/Webstudio names. Do not use React-only aliases such as `className` or `htmlFor`; the runtime rejects them with the Webstudio prop name to use.\n\nUse Webstudio actions for event/action props. Do not pass JavaScript functions such as `onClick={() => ...}`; the runtime rejects them because functions cannot be persisted as Webstudio project data.\n\n```tsx\n<button onClick={new ActionValue(["event"], expression`console.log(event)`)}>\n Open\n</button>\n```\n\nPlain JSX prop values must be JSON-compatible: `null`, strings, booleans, finite numbers, arrays, and plain objects. Do not pass `undefined`, `Symbol`, `BigInt`, `NaN`, `Infinity`, `Date`, `Map`, `Set`, class instances, or circular objects; omit the prop, use plain data, or use `expression`/`ActionValue` when the value is dynamic.\n\nIf a component has a registered template with required parts, JSX must include those parts explicitly under the same parent structure as the template, for example `<Switch><SwitchThumb /></Switch>`. Use `insert-component` when you want Webstudio to apply one component template automatically.\n\n## Animation Components\n\nBefore creating animation examples, inspect the exact components with focused discovery:\n\n```sh\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-animation:AnimateChildren"}\'\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-animation:AnimateText"}\'\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-animation:StaggerAnimation"}\'\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-animation:VideoAnimation"}\'\n```\n\nUse Animation Group (`AnimateChildren`) as the controller. Put normal instances directly inside it, or put Text Animation, Stagger Animation, or Video Animation directly inside it. Text, Stagger, and Video are helper components with `contentModel.category: "none"` and should not be used as standalone section roots.\n\nDefine timing and CSS changes on the Animation Group `action` prop. Use `type:"view"` for viewport entry/exit progress and `type:"scroll"` for scroll-progress timelines. For in animations, keep the canvas styles as the final state and use `fill:"backwards"` with keyframes that describe the starting state. For out animations, use `fill:"forwards"` with keyframes that describe the ending state.\n\nText Animation settings: `slidingWindow` defaults to `5`, `easing` defaults to `linear`, and `splitBy` defaults to `char`. Use `splitBy:"space"` for word-by-word animation. The parent Animation Group keyframes provide the actual opacity, translate, scale, or other styles.\n\nStagger Animation settings: `slidingWindow` defaults to `1` and `easing` defaults to `linear`. It applies parent Animation Group progress across its direct children. Use `slidingWindow:0` for instant sequential steps, `1` for one child at a time, and values above `1` for overlapping waves.\n\nVideo Animation settings: `timeline` is a boolean. Prefer `insert-component` for Video Animation so the Video child template is inserted, then configure the Video child asset/source. Use short, seek-friendly videos for smooth scroll-linked playback.\n\nUse JSX fragments for authored animation structures when you need styled, editable examples. Put the final visual state in `ws:style` and put the starting or ending animated state in the Animation Group `action` keyframes. Include an explicit `offset` on every keyframe: use `offset: 0` for starting-state keyframes with `fill:"backwards"` and `offset: 1` for ending-state keyframes with `fill:"forwards"`.\n\n```tsx\n<AnimateChildren\n action={{\n type: "view",\n axis: "block",\n animations: [\n {\n name: "Fade up on entry",\n timing: {\n fill: "backwards",\n rangeStart: ["entry", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["entry", { type: "unit", value: 100, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n opacity: { type: "unit", value: 0, unit: "number" },\n translate: {\n type: "tuple",\n value: [\n { type: "unit", value: 0, unit: "number" },\n { type: "unit", value: 24, unit: "px" },\n ],\n },\n },\n },\n ],\n },\n ],\n }}\n>\n <section\n ws:style={css`\n display: grid;\n gap: 16px;\n padding: 48px;\n border-radius: 24px;\n background: #111827;\n color: white;\n `}\n >\n <h2>Launch metrics</h2>\n <p>A polished card that fades up as it enters the viewport.</p>\n </section>\n</AnimateChildren>\n```\n\nFor Text Animation, keep `AnimateText` as the direct child of Animation Group and place the text-containing element inside it:\n\n```tsx\n<AnimateChildren\n action={{\n type: "view",\n animations: [\n {\n name: "Parallax In",\n timing: {\n fill: "backwards",\n rangeStart: ["cover", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 70, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n translate: {\n type: "tuple",\n value: [\n { type: "unit", value: 0, unit: "number" },\n { type: "unit", value: 100, unit: "px" },\n ],\n },\n },\n },\n ],\n },\n {\n name: "Opacity In",\n timing: {\n fill: "backwards",\n rangeStart: ["cover", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 70, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n opacity: { type: "unit", value: 0, unit: "number" },\n },\n },\n ],\n },\n {\n name: "Scale In",\n timing: {\n fill: "backwards",\n rangeStart: ["cover", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 70, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n scale: {\n type: "tuple",\n value: [\n { type: "unit", value: 5, unit: "number" },\n { type: "unit", value: 5, unit: "number" },\n ],\n },\n },\n },\n ],\n },\n {\n name: "Parallax Out",\n timing: {\n fill: "forwards",\n rangeStart: ["cover", { type: "unit", value: 50, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 100, unit: "%" }],\n },\n keyframes: [\n {\n offset: 1,\n styles: {\n translate: {\n type: "tuple",\n value: [\n { type: "unit", value: 0, unit: "number" },\n { type: "unit", value: -100, unit: "px" },\n ],\n },\n scale: {\n type: "tuple",\n value: [\n { type: "unit", value: 5, unit: "number" },\n { type: "unit", value: 5, unit: "number" },\n ],\n },\n opacity: { type: "unit", value: 0, unit: "number" },\n },\n },\n ],\n },\n ],\n insetStart: { type: "unit", value: 5, unit: "%" },\n insetEnd: { type: "unit", value: 5, unit: "%" },\n isPinned: true,\n }}\n>\n <AnimateText splitBy="space" slidingWindow={5} easing="easeOutQuart">\n <h2>Animate words with controlled rhythm</h2>\n </AnimateText>\n</AnimateChildren>\n```\n\nFor Stagger Animation, put the repeated cards or rows directly inside `StaggerAnimation`:\n\n```tsx\n<AnimateChildren\n action={{\n type: "view",\n animations: [\n {\n timing: {\n fill: "backwards",\n rangeStart: ["contain", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["contain", { type: "unit", value: 30, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n opacity: { type: "unit", value: 0, unit: "number" },\n },\n },\n ],\n },\n ],\n }}\n>\n <StaggerAnimation>\n <article\n ws:style={css`\n padding: 20px;\n border: 1px solid #d1d5db;\n border-radius: 16px;\n `}\n >\n Plan\n </article>\n <article\n ws:style={css`\n padding: 20px;\n border: 1px solid #d1d5db;\n border-radius: 16px;\n `}\n >\n Build\n </article>\n <article\n ws:style={css`\n padding: 20px;\n border: 1px solid #d1d5db;\n border-radius: 16px;\n `}\n >\n Launch\n </article>\n </StaggerAnimation>\n</AnimateChildren>\n```\n\nFor Video Animation, use the registered template via `insert-component` when possible. If you author JSX, include the Video child explicitly:\n\n```tsx\n<AnimateChildren\n action={{\n type: "view",\n animations: [\n {\n name: "Video progress",\n timing: {\n fill: "both",\n rangeStart: ["cover", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 100, unit: "%" }],\n },\n keyframes: [{ offset: 0, styles: {} }],\n },\n ],\n }}\n>\n <VideoAnimation timeline={true}>\n <Video\n preload="auto"\n autoPlay={true}\n muted={true}\n playsInline={true}\n crossOrigin="anonymous"\n />\n </VideoAnimation>\n</AnimateChildren>\n```\n\n## Command Surface Boundary\n\n- Use top-level `webstudio ...` shell commands for setup, sync/import/build/preview/screenshot, permissions, publish/domains, schema, registry inspection, man, and starting MCP.\n- Use MCP tools for Builder project data manipulation: pages, instances/components, props, text, styles, tokens, variables, resources, assets, breakpoints, redirects, and raw patches.\n- From a shell, call MCP tools with the shortcut form `webstudio <tool> \'<json>\'`, for example `webstudio insert-fragment \'<json>\' --dry-run`. The explicit equivalent is `webstudio mcp single-op-call <tool> \'<json>\'`. Use `--input-file` for large payloads.\n- Inside the Webstudio monorepo, call the local CLI as its own command: `node packages/cli/local.js ...`. Do not wrap the CLI call in `pwd && ...`, command substitution, `pnpm exec webstudio`, `pnpm --filter webstudio exec webstudio`, or a global `webstudio`.\n- For experiments, pass `--dry-run` to local-capable mutation calls. Read the computed transaction from `meta.session.transaction` and its base build version from `meta.session.version`. Copying a `.webstudio` folder is not an isolated project clone; `.webstudio/config.json` still points to the same remote project, so non-dry-run mutations can commit to that project.\n- Read `meta.session.commitStatus` before interpreting durability. Read-only results report `not-applicable` and retain `committed:false` for compatibility; dry-run plans report `planned`; failed mutations report `failed`; no-op mutations report `unchanged`; durable mutations report `committed` with `meta.session.committed:true`.\n- For bounded multi-step shell work, run inline JSON with `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'`; this reuses one CLI session without raw JSON-RPC. For large batches, write `{ "calls": [{ "tool": "..." }] }` to a normal JSON file and run `webstudio mcp run .temp/mcp-calls.json`.\n- Use JSON strings for `brief` fields. Never pass boolean flags such as `{"brief":true}`.\n- Treat `webstudio mcp single-op-call` and `webstudio mcp run` stderr lines as progress checkpoints; stdout remains JSON on both success and failure. On failure, parse stdout for `{ "ok": false, "error": { "code": "...", "message": "..." } }` before deciding what to fix.\n- If a CLI/MCP tool crashes, hangs, gives a confusing error, needs an undocumented workaround, or forces source-code inspection for normal usage, ask the user to report it in Discord `#help` at https://wstd.us/community. Give them a complete copy-paste report with the goal, expected behavior, actual error, exact command/tool call, stdout JSON, stderr/lifecycle logs, environment, workaround, and secrets redacted.\n- Run one-shot `webstudio mcp single-op-call` commands sequentially against a linked `.webstudio` folder. If a command returns `PROJECT_SESSION_BUSY`, another CLI/MCP process is updating the local session; wait a moment and retry sequentially.\n- In delegated or non-streaming agent environments, do not batch many MCP calls silently and do not wrap many shortcut or `webstudio mcp single-op-call` commands in a shell loop. Treat each parent-visible checkpoint as the unit of work. If the parent asks for status within 30 seconds, run exactly one shortcut command such as `webstudio meta.index` or one explicit `webstudio mcp single-op-call` command, report that command/result, then wait for the parent to continue. Do not take a broad task such as creating a full design-system page as one execution unit. Call `workflow.next {"goal":"design-system-page"}`, report the returned phase/checkpoint, wait until the parent continues, call `checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}`, complete exactly that bounded phase, and return: discovery, page creation, one dry-run JSX section, one committed JSX section, one `components.coverage-insert-next` call, or one presentation pass. Phase commands do not include nextPhase in their own output. After the parent continues, acknowledge the previous checkpoint first, then call `workflow.next` with the next phase. For all-component design-system pages, checkpoint after workflow planning, discovery, page creation, call `components.coverage-insert-next` once before checkpointing again, then finish with `workflow.next {"goal":"design-system-page","phase":"presentation-pass"}`. Coverage 72/72 is necessary but not sufficient: the page must be organized into styled, real-world examples, not raw unstyled component dumps.\n- For design-system or “use every component” tasks, start with compact `webstudio components.coverage-plan`, checkpoint, then request component coverage details with `webstudio components.coverage-plan \'{"detail":"roots","offset":20}\'` or `{"detail":"parts"}` only when needed. Do not pass `detail` to `list-pages`; use `list-pages {}` or `get-page-by-path` for page lookup.\n- MCP tool shortcuts are only for MCP tools. If a shortcut is ambiguous with a real top-level command, the real top-level command wins; use `webstudio mcp single-op-call <tool> \'<json>\'` to force the MCP path.\n\n## LLM Implementation Process\n\nUse this process for user requests that change Webstudio content, layout, styles, assets, pages, redirects, resources, or publishing state:\n\n1. Discover capabilities with `webstudio man --json`, `webstudio schema api`, `webstudio schema mcp`, MCP `meta.index`, `meta.guide`, `meta.get-more-tools`, `components.list`, `components.summary`, `components.coverage-plan`, `components.search`, `components.get`, `templates.list`, and `templates.get`. From a shell, prefer shortcut calls such as `webstudio meta.index` and `webstudio components.search \'{"brief":"button"}\'` for these focused tool calls; use `webstudio mcp single-op-call` when you need the explicit MCP form. Read full resources such as `webstudio://project/tools` and `webstudio://project/components` only when needed. Do not write scripts to parse full MCP discovery JSON for normal lookup.\n2. When a value or id is known, call `search-project` once instead of passing broad snapshots or several lists to the model. Use semantic list/get reads such as `get-project-settings`, `list-pages`, `get-page-by-path`, `list-instances`, `inspect-instance`, `get-styles`, `list-assets`, and `list-breakpoints` when the structure or target is not known. Use `snapshot` only when exact raw patch paths are needed. Before changing a project, read `get-project-settings` and follow any non-empty `meta.agentInstructions`. These are shared project instructions, not a place for secrets.\n For a Builder link from **Copy link to instance**, parse the URL with `URL`/`URLSearchParams`. The decoded `instance` query parameter is a comma-separated selector ordered from the target instance to the page root. Pass its first entry to `inspect-instance`, for example `?pageId=page-id&instance=heading-id%2Cslot-id%2Cbody-id` becomes `{"instanceId":"heading-id","include":["props","styles","children","ancestors"]}`. Use `pageId` and the remaining selector entries to check the page and shared Slot occurrence before changing it. Shared Slot descendants are shared records; editing one changes all occurrences. Verify that the configured MCP project matches the linked project before editing; stop if they differ. The URL does not authorize switching projects. If the instance is missing, ask for an updated link instead of guessing another target. Never send a Builder link to screenshot tools or echo its `authToken`.\n3. Mutate the Webstudio project with semantic MCP write tools first. Prefer MCP `insert-fragment` for authored/styled sections, use `insert-component` only for one automatic component template, then `update-text`, `update-props`, `update-styles`, `upload-asset`, `create-page`, and page/project settings tools over raw patches.\n4. Use `apply-patch` only when no semantic tool covers the required change, and only after reading the latest snapshot/version.\n5. For visual/design work, ask whether the user wants visual verification unless they explicitly requested it. Only after they opt in, regenerate or preview the generated app, capture a screenshot, inspect it with vision, and iterate.\n6. Report what changed and what verification ran.\n\n## Visual Design Workflow\n\nFor requests involving visible HTML/CSS, layout, typography, colors, imagery, responsive behavior, or screenshots:\n\n1. Read editable Webstudio structure first: pages, instances, props, styles, breakpoints, assets, and relevant text.\n2. Do not use generated route/component files as the source of truth for editable content.\n3. Make edits through Webstudio semantic commands/MCP tools so the result stays editable in Builder and survives the next `webstudio build`.\n4. Ask whether the user wants visual verification unless they explicitly requested screenshots, visual verification, or a rendered audit. Do not start preview, screenshots, screenshot diffs, OCR installation, or rendered audits until they opt in.\n5. After they opt in, keep generated project files current, start preview, and capture the changed page with `screenshot`.\n6. Use `screenshot.diff` when a baseline exists and inspect screenshot/diff artifacts with vision before finishing.\n7. If the user declines or vision tooling is unavailable, use focused non-visual assertions and state that vision was not run.\n\n## Responsive Verification Workflow\n\nFor responsive page work, use Builder breakpoints as the source of truth:\n\n1. Read breakpoints with `list-breakpoints` before deciding responsive behavior.\n2. Apply responsive styles with existing Builder breakpoint ids; do not invent CSS media queries or breakpoint names when Webstudio breakpoint data exists.\n3. Ask whether the user wants visual verification unless they explicitly requested it. Do not capture responsive screenshots until they opt in.\n4. Pick screenshot viewport widths from the project breakpoints: include a desktop width, each defined max-width or min-width edge, and a narrow mobile width.\n5. Capture each viewport with `screenshot`, for example `{"path":"/","output":"home-375.png","viewport":{"width":375,"height":812}}` and `{"path":"/","output":"home-1440.png","viewport":{"width":1440,"height":900}}`.\n6. Inspect every viewport screenshot with vision before finishing, checking layout, overflow, hidden content, text wrapping, and breakpoint-specific style changes.\n7. If any viewport fails, update styles through semantic Webstudio tools and repeat screenshots for the affected breakpoints.\n\n## Generated Files Guardrails\n\n- Do not edit `app/__generated__`, generated route files, generated page files, generated CSS, or build output for normal Webstudio content/design requests.\n- Do not replace generated page components with handcrafted app code unless the user explicitly asks for code-only export customization.\n- Generated files are build artifacts and may be overwritten by `webstudio build`.\n- If a task truly requires generated app customization, keep it outside `app/__generated__` where possible and explain that it is not editable Webstudio content.\n\n## Values vs Bindings\n\nBefore authoring unfamiliar expressions, read `webstudio://project/expressions` with MCP `resources/read` or `webstudio mcp read-resource webstudio://project/expressions`. It documents the supported expression subset, method allowlist, scope, Collection context, and validation limits.\n\n- Use direct value tools for fixed content. For one visible text child, use `update-text` with plain `text`. For a bounded multi-instance literal replacement, use `replace-text` with `find`, `replace`, `pagePath` or `pageId`, and `limit`; it does not change expression children. Use `replace-prop-text` for bounded changes inside static string props, optionally limited to prop names or instance ids; it never changes dynamic bindings. For static props such as `aria-label`, `alt`, `id`, `class`, `href`, or button labels stored as props, use `update-props` with the prop\'s direct type/value.\n- Use `bind-props` only when the prop must stay dynamic: an expression, resource result, action, or existing scoped runtime context such as `system`. Do not use `bind-props` just to set a fixed string.\n- Direct prop string example: `{"updates":[{"instanceId":"button-id","name":"aria-label","type":"string","value":"Open menu"}]}`.\n- Expression binding example: `{"bindings":[{"instanceId":"link-id","name":"href","binding":{"type":"expression","value":"currentPost.url"}}]}`.\n- Page metadata fields such as `title`, `description`, `language`, `redirect`, and custom meta content accept plain fixed text. For computed values, pass JavaScript expression code such as `pageTitle ?? "Pricing | Acme"`.\n- Page `status` accepts a fixed HTTP status code as a number from 200 through 599, for example `302`. For a dynamic status, pass JavaScript expression code such as `system.status`.\n- Page metadata update example: use `update-page` with `{"pageId":"page-id","values":{"title":"Pricing | Acme","meta":{"description":"Plans for teams"}}}`.\n- Draft a page with `update-page` and `{"pageId":"page-id","values":{"isDraft":true}}`. It remains editable and previewable but is omitted from every publish target, including staging, and from sitemap output.\n- Stage a draft page for a future publish with `{"pageId":"page-id","values":{"isDraft":false}}`. This clears draft state but does not deploy the site. The home page and `/*` catch-all page cannot be drafts.\n- Resource `url` accepts plain fixed URLs and paths. For computed URLs, pass JavaScript expression code such as `"https://api.example.com/items?tag=" + filters.tag`. Resource header values, search parameter values, and text bodies accept expressions for dynamic values; for fixed text, use `{ "type": "literal", "value": "application/json" }`.\n- Resource update example: use `update-resource` with `{"resourceId":"resource-id","values":{"url":"https://api.example.com/items"}}`.\n- Assets is one system resource that always executes a structured query. `result:"many"` returns the existing ID-keyed collection shape and is the backward-compatible default. `result:"one"`, `result:"first"`, and `result:"last"` return one direct item or `null`; first and last require explicit sorting. `create-assets-resource` without `query` uses the default URL and optional image-dimensions output.\n- For a Markdown-backed blog, create exactly two Builder page definitions: a fixed `/blog` overview and one `/blog/:slug` detail page. Both pages load content through Assets resources. Never create one Builder page per post or duplicate Markdown content into static page structures. Read `get-asset-field-catalog`, validate each structured query with `validate-asset-query`, then call `create-assets-resource` or `update-assets-resource`. Set `values.query:null` to restore the default query.\n- Optimize every explicit Assets query for the deployed content-database size. Use `output.mode:"fields"` and select only fields that are actually rendered or otherwise required by the query. Keep `includeMetadata:false` unless the rendered value needs file metadata; diagnostics are returned separately. Do not use `output.mode:"all"` as a convenience default.\n- Every reachable Assets data source contributes to the shared database. Keep one final resource per rendered query. Update an existing scoped resource instead of creating a placeholder, preview copy, or repair replacement, and remove obsolete duplicate resources and data sources.\n- Make a bounded overview fully static: use literal values for its filters, limit, and offset, add a deterministic ID tie-breaker to its sort, and use `content.mode:"none"`. This lets compilation materialize the small overview result instead of retaining overview-only fields across every candidate article. Reserve runtime expressions for values that are truly dynamic, such as `system.params.slug` on the detail route.\n- Query Markdown or MDX files directly and use `content.mode:"markdown-body-ref"` when rendering their bodies. The published database keeps only metadata and document references, filters and paginates first, and fetches the selected files from Asset storage at runtime. Do not create companion JSON descriptors merely to avoid embedding the document.\n- `full` and bounded `range` request embedded file bytes. Use them only when the caller explicitly requires the complete source or a byte range.\n- Deferred Markdown or MDX bodies exclude frontmatter and resolve conventional relative links and images such as `../images/hero.png` to matching Assets. Markdown Embed permits sanitized figures, audio, video, and iframes, but removes scripts, inline event handlers, and unsafe URLs. It does not render Webstudio MDX elements; connect the `.mdx` file to a Content Block for that workflow.\n- For `result:"many"`, Assets expose an ID-keyed map at `<dataSourceName>.data` and `totalCount`/`hasMore` at `<dataSourceName>.meta`; bind a listing Collection to `posts.data`. Single-result modes expose the selected item or `null` directly at `<dataSourceName>.data`, always include its `id`, and expose `totalCount` in meta. Bind detail components and page settings directly from expressions such as `post.data.properties.title`, `post.data.content.text`, and `post.data ? 200 : 404` without a Collection.\n- Use `preview-asset-query` with concrete values before binding expressions in the saved resource. Inspect `__diagnostics__.query` for the temporary query-only footprint and `__diagnostics__.database` for the merged database built from all reachable Assets queries. Only `database.usedBytes` counts toward `database.maxBytes`; query sizes are not separate allowances and must not be summed. Compare `usedBytes`, `unboundedBytes`, and `truncated` within both scopes. A finished Markdown blog must include every source document without truncation, contain no embedded Markdown bodies, and retain only the intended materialized overview query. When merged usage approaches the limit, remove duplicate reachable resources first, then unused output fields, then narrow candidate files. Inspect saved mode and configuration with `list-assets-resources` or `get-assets-resource`; shared index maintenance is automatic.\n- Data variable values support `string`, `number`, `boolean`, and `json`. Use `json` for all arrays, objects, filters, and nested data.\n- Parameters are internal scoped runtime values from pages, collections, or components. They are not a public authoring surface: do not create, update, or delete parameter records. Public tools should preserve existing parameter records and may reference documented context values such as `system` in expressions where they are already in scope.\n- Use scoped resources for read data. A GET resource created with `scopeInstanceId`/`dataSourceName` defaults to `exposeAsDataSource:true`, becomes a scoped resource data variable, is generated into the page resource `data` map, and may be loaded while rendering the page. Read the loaded resource result from its wrapper, usually `.data`.\n- Use prop-bound resources for actions. A resource created without `scopeInstanceId` and bound to a component prop such as Form `action` with `bind-props` and `binding.type: "resource"` becomes an action resource in the page resource `action` map. Use this for POST, PUT, DELETE, webhooks, GraphQL submissions, and anything that should run only from an explicit form/action flow.\n- POST, PUT, and DELETE resources default to `exposeAsDataSource:false`, even with a scope. Set `exposeAsDataSource:true` only for an intentional render-time read such as a GraphQL POST query; provide `scopeInstanceId` and inspect the returned warning. Set it to `false` during `update-resource` to detach existing render-time exposure.\n- For dynamic resource query parameters prefer `searchParams`, for example `{"name":"tag","value":"filters.tag"}`. Use `{"type":"literal","value":"website"}` for fixed request text. Header values can use an expression such as `"Bearer " + auth.token`. Body can be an object expression, including GraphQL payloads such as `{ query: "...", variables: { slug: system.params.slug } }`.\n- Resource methods are `get`, `post`, `put`, and `delete`. Optional resource controls are `graphql` and `system`. Use `control:"graphql"` for GraphQL POST resources with query bodies. Use `control:"system"` for built-in local resource URLs such as `"/$resources/current-date"` and for resources reading the built-in `system` parameter. The built-in system fields are `system.origin`, `system.pathname`, `system.params`, and `system.search`; do not use `system.path`.\n- Whenever an array or object from a resource or data variable should render repeated UI, call `insert-collection` with the complete iterable and one repeated-item JSX root. The command creates the Collection, private item parameters, iterable binding, and descendant item bindings atomically. Use `collectionItem` and `collectionItemKey` expressions in the item JSX. Wrap multiple repeated siblings in one Element, and give repeated Radix items stable unique `value` bindings.\n- Expressions are single JavaScript expressions, not statements or functions. Functions, arrow functions, classes, `new`, `this`, `await`, imports, arbitrary calls, increment/decrement, and assignment outside actions are unsupported. Property and index access are made safe automatically, so write direct access. Use nullish coalescing when a fallback is required.\n\n## Pick Read Command\n\n{{readFirst}}\n\n## Pick Write Command\n\n{{taskRecipeIndex}}\n\n## Raw Patch Only If Needed\n\n1. Use MCP tool: snapshot.\n2. Write BuildPatchTransaction[].\n3. Use MCP tool: apply-patch.\n\n## MCP Argument Examples\n\nMCP tools receive JSON argument objects, not CLI flags. Use these shapes:\n\n{{mcpArgumentExampleIndex}}\n\n## Rules\n\n- Never guess ids for existing records. Read them first.\n- Never use project ids from user input. Commands use the configured project.\n- Use --refresh before a local-capable command when cached data may be stale.\n- Pass --json only to commands whose help/schema documents it. Do not add --json to top-level commands such as sync unless supported.\n- On VERSION_CONFLICT, read MCP snapshot again, regenerate the patch, then retry.\n- Treat stdout JSON as the API contract and stderr as diagnostics.\n- Never run visual verification automatically. Ask first unless the user explicitly requested screenshots, visual verification, or a rendered audit; if they do not opt in, use focused non-visual assertions.\n- Do not edit generated files for normal Webstudio content/design requests.\n- Use direct values for static strings and bindings only for dynamic expressions/resources/actions.\n- Use plain fixed text where documented. Only encode a quoted JavaScript string literal when a field is explicitly documented as an expression-only value.\n- Confirm destructive commands with --confirm only when user requested deletion/unpublish/replacement.\n- Use webstudio schema api for machine-readable top-level command metadata and webstudio schema mcp for MCP tool schemas.\n\n## Known Gaps\n\n{{knownCliGapIndex}}\n',
|
|
344543
|
-
"manual-mcp": '# Webstudio MCP Manual\n\n`webstudio mcp` starts a stdio MCP server for real MCP clients. Shell users can call MCP tools with the shortcut form `webstudio <tool> \'<json>\'`, for example `webstudio meta.index` or `webstudio insert-fragment \'<json>\' --dry-run`. `webstudio mcp single-op-call` is the explicit equivalent and prints the structured JSON result. `webstudio mcp run` runs multiple MCP tool calls from inline JSON or a normal JSON file in one shared CLI session. Do not manually type or pipe raw JSON-RPC frames into `webstudio mcp` from an interactive shell or PTY.\n\n## Startup\n\nIf you are already working with a shell-capable agent, it can use the local CLI\ndirectly. Native MCP client registration is optional. Give the editable Builder\nshare link only when the trusted agent asks for it. Treat the share link as a\ncredential: do not include it in committed files, screenshots, logs, or issue\nreports.\n\n1. Configure a project with `webstudio init --link <api-share-link> --json`.\n2. Check capabilities with `webstudio permissions --json`.\n3. Use shortcut calls such as `webstudio meta.index` and `webstudio insert-fragment \'<json>\' --dry-run` for individual MCP tool calls. Use the explicit equivalent `webstudio mcp single-op-call <tool> \'<json>\'` when you need to force the MCP path, or `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'` for bounded multi-call workflows. Use `webstudio mcp run .temp/mcp-calls.json` for large batches.\n4. Start discovery with `meta.index`, then call focused tools with concrete JSON, for example `webstudio mcp single-op-call meta.guide \'{"brief":"Create a design system page using every component"}\'`.\n\nDo not run `webstudio sync`, install an MCP server, change client configuration,\nor restart the app for this local CLI workflow.\n\nWhen the user explicitly wants persistent native MCP integration, run\n`webstudio connect claude`, `webstudio connect codex`, `webstudio connect\ncursor`, or `webstudio connect vscode`. This optional command changes client\nconfiguration, so follow its client-specific reload or restart instruction.\nUse `--print` to inspect the generated setup without changing configuration or\nrequiring project access. For Codex, `connect` registers and verifies the server\nthrough the Codex CLI. Before changing client configuration, `connect` verifies\nthat the saved project endpoint is reachable and its credential is accepted.\n\nStart MCP from the linked Webstudio project root. The lifecycle status line prints that absolute root; create local scripts, screenshots, and temporary artifacts under that root, for example `<project root>/.temp/script.mjs`. If the shell starts in a parent workspace, `cd` into the project root first or use absolute paths.\n\nWhen developing inside the Webstudio monorepo, start the local CLI exactly as `node packages/cli/local.js mcp` from the repo root. Do not use `pnpm exec webstudio`, `pnpm --filter webstudio exec webstudio`, or a global `webstudio`: they can resolve an older binary.\n\nWhile the server is running, stdout is reserved for MCP JSON-RPC messages. Do not print human text from the server process. The server advertises MCP `logging` capability and emits sparse `notifications/message` logs for ready state and tool lifecycle checkpoints such as `tool preview.start started`, `tool preview.start still running after 10000ms`, and `tool preview.start succeeded in 1234ms`; stderr also mirrors these sparse lifecycle fallback lines prefixed with `[webstudio mcp]`.\n\n## One-Shot Tool Calls\n\nUse the shortcut `webstudio <tool> \'<json>\'` when you are operating from a shell and need one MCP tool result. The explicit form `webstudio mcp single-op-call <tool> \'<json>\'` is equivalent and avoids writing temporary Node.js stdio client scripts.\n\nExamples:\n\n```sh\nwebstudio mcp single-op-call meta.index\nwebstudio mcp single-op-call meta.guide \'{"brief":"Create a design system page using every component"}\'\nwebstudio mcp single-op-call meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nwebstudio mcp single-op-call components.list \'{"source":"all"}\'\nwebstudio mcp single-op-call components.coverage-plan\nwebstudio mcp single-op-call components.search \'{"brief":"radix select"}\'\nwebstudio mcp single-op-call components.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'\nwebstudio mcp single-op-call templates.list\nwebstudio mcp single-op-call templates.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'\nwebstudio mcp single-op-call insert-fragment --input-file .temp/insert-fragment.json\n```\n\nShortcut equivalents:\n\n```sh\nwebstudio meta.index\nwebstudio meta.guide \'{"brief":"Create a design system page using every component"}\'\nwebstudio meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nwebstudio components.list \'{"source":"all"}\'\nwebstudio components.coverage-plan\nwebstudio components.search \'{"brief":"radix select"}\'\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'\nwebstudio templates.list\nwebstudio templates.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'\nwebstudio insert-fragment --input-file .temp/insert-fragment.json\n```\n\n### Tool name convention\n\nMCP tool names are opaque strings, not JavaScript property access. A dot separates a namespace from its tool name, and every segment uses lowercase kebab-case. For example, `components.coverage-insert-next` is the `coverage-insert-next` tool in the `components` namespace. Pass the complete name as one CLI argument: `webstudio components.coverage-insert-next`. Batch `mcp run` calls also accept the underscore form advertised by MCP protocol discovery, such as `components_coverage_insert_next`. Unknown names return near matches and direct you to `meta.index`.\n\n### Readable fragment inputs\n\nPrefer `--input-file` for JSX so JSON and shell quoting do not obscure the fragment. For example, save this as `.temp/insert-fragment.json`:\n\n```json\n{\n "parentInstanceId": "root-id",\n "fragment": "<section ws:style={css`padding: 32px; display: grid; gap: 16px;`}><h2>Northstar Product OS</h2><p>Reusable patterns for teams.</p></section>"\n}\n```\n\nThen run `webstudio insert-fragment --input-file .temp/insert-fragment.json`. Single quotes inside the JSX keep the JSON valid and readable without backslash-escaped attributes.\n\nWrite and review larger fragments as JSX before placing them in the `fragment` field. Common patterns:\n\n```tsx\n<section style={{ padding: 32, borderRadius: 16 }}>\n <h2>Operations Console</h2>\n <p>\n React-style object styles become editable Webstudio styles.\n </p>\n</section>\n\n<section ws:tokens={[token("accent", css`color: #0f766e;`)]}>\n <button\n onClick={new ActionValue(["event"], expression`console.log(event)`)}\n >\n Track launch\n </button>\n</section>\n\n<section>\n <Switch>\n <SwitchThumb />\n </Switch>\n</section>\n```\n\nRules:\n\n- Inside the Webstudio monorepo, replace `webstudio` in the examples above with `node packages/cli/local.js`, for example `node packages/cli/local.js meta.index`.\n- For a simple authored/styled section, run `meta.index`, then `meta.get-more-tools \'{"tools":["insert-fragment"]}\'`, then `insert-fragment`. Do not grep source files, dump full MCP resources, or write parser scripts first.\n- In `insert-fragment` JSX, use ``ws:style={css`...`}`` for Webstudio-native CSS, or use React-style object syntax such as `style={{ padding: 24 }}` when that is simpler. Both forms create editable Webstudio style data.\n- `css` templates accept declarations and `@media` rules. Do not put selectors or unsupported at-rules such as `@keyframes` inside them. Use direct animation component identifiers such as `<AnimateChildren>`; `animation` is accepted only as legacy input and is not a callable CSS keyframes helper.\n- Do not access host globals or dynamic code APIs in JSX fragments, including `process`, `globalThis`, `eval`, `Function`, or `constructor`.\n- Use Webstudio prop names such as `class` and `for`; do not use React aliases `className` or `htmlFor`.\n- Use Webstudio actions for event/action props, for example `onClick={new ActionValue(["event"], expression\\`console.log(event)\\`)}`. Do not pass JavaScript functions such as `onClick={() => ...}`.\n- Plain prop values must be JSON-compatible: `null`, strings, booleans, finite numbers, arrays, and plain objects. Do not pass `undefined`, `Symbol`, `BigInt`, `NaN`, `Infinity`, `Date`, `Map`, `Set`, class instances, or circular objects; omit the prop, use plain data, or use `expression`/`ActionValue` when the value is dynamic.\n- Template-backed components used in JSX must include required child/part components explicitly under the same parent structure as the template, for example `<Switch><SwitchThumb /></Switch>`. Use `insert-component` when you want one automatic registered component template.\n- The positional input is JSON and defaults to `{}`.\n- Use `--input-file` for large mutation payloads.\n- Use `--dry-run` with local-capable mutation tools when you need a patch plan without committing. The computed transaction is returned in `meta.session.transaction`, and `meta.session.version` is its base build version. Copying a `.webstudio` folder is not an isolated project clone; `.webstudio/config.json` still points to the same remote project, so non-dry-run mutations can commit to that project.\n- The command prints JSON to stdout for both success and failure. Success uses the same `structuredContent` shape MCP tools return: `{ "ok": true, "data": ..., "meta": ... }`. Failure prints `{ "ok": false, "error": { "code": "...", "message": "..." }, "meta": ... }` and exits nonzero.\n- The command writes sparse progress to stderr, including start, success/failure, elapsed time, and committed status when the tool returns session metadata.\n- Invalid argument types fail loudly with path-specific messages, for example `meta.guide input.brief must be a string when provided`.\n- Run one-shot shortcut or `mcp single-op-call` commands sequentially against the same linked `.webstudio` folder. If you receive `PROJECT_SESSION_BUSY`, another CLI/MCP process is updating the local session; wait a moment and retry sequentially.\n- To work with another previously linked project without changing the directory\'s default link, start MCP or a shell call with `--project <projectId>`, for example `webstudio mcp --project <projectId>` or `webstudio mcp single-op-call list-pages --project <projectId>`. Selected projects use isolated local session and checkpoint files.\n- If you are a delegated agent and your parent cannot see live stderr/stdout, do not run a long sequence of shortcut or `mcp single-op-call` commands silently and do not wrap many calls in a shell loop. Treat each parent-visible checkpoint as the unit of work. If the parent asks for status within 30 seconds, run exactly one `webstudio <tool>` or `webstudio mcp single-op-call` command, report that command/result, then wait before the next MCP command. For all-component design-system pages, checkpoint after discovery, checkpoint after page creation, call `components.coverage-insert-next` once before checkpointing again, then finish with the `presentation-pass` workflow phase. Coverage alone is not completion; organize examples into styled sections/cards.\n\n## MDX-backed Content Blocks\n\nUse a connected `.mdx` Asset when editors should change a Content Block body visually while the document remains stored as a file.\n\n1. Create the `.mdx` file under `.webstudio/assets`, then upload it and keep the returned Asset ID.\n2. Inspect the Content Block and verify that it has exactly one direct Templates container. Every custom template referenced from MDX needs a unique top-level instance name. A missing or second Templates container blocks connected MDX materialization and publication.\n3. Connect the Asset with `connect-content-block-source`. Use a stable page-based `renderScope` for a direct occurrence.\n4. If the result returns `requiresConfirmation:true`, tell the user that connecting will replace the existing Body content. Repeat the same call with `confirmReplacement:true` only after approval.\n5. Edit the complete source with `edit-content-block-source`, or replace the complete frontmatter map with `update-content-block-frontmatter`.\n6. Inspect every returned diagnostic. Invalid MDX is preserved, not silently repaired.\n\n```sh\nwebstudio upload-asset \'{"asset":{"name":"article.mdx","type":"file","format":"mdx","meta":{}},"assetsDir":".webstudio/assets"}\'\nwebstudio connect-content-block-source \'{"blockInstanceId":"<contentBlockInstanceId>","renderScope":"page:/articles/example","source":{"type":"asset","assetId":"<mdxAssetId>"}}\'\nwebstudio inspect-content-block-source \'{"blockInstanceId":"<contentBlockInstanceId>","renderScope":"page:/articles/example"}\'\nwebstudio edit-content-block-source --input-file .temp/edit-content-block-source.json\nwebstudio reload-content-block-source \'{"blockInstanceId":"<contentBlockInstanceId>","renderScope":"page:/articles/example"}\'\nwebstudio migrate-content-block-template-references \'{"assetIds":["<mdxAssetId>"],"migration":{"type":"rename","from":"Old template name","to":"New template name"}}\'\n```\n\nPrefer Markdown for standard document content. Markdown nodes automatically use a unique matching semantic template when one exists and keep their normal semantic fallback otherwise. Use lowercase JSX such as `<section>` or `<svg>` for a standard HTML or SVG element with authored properties Markdown cannot express.\n\nReference a uniquely named top-level custom template with capitalized JSX such as `<PromotionCard tone="featured">Content</PromotionCard>`. The template\'s stable **Name** must be a valid JSX component identifier and is independent from its display label. A template name wins over a built-in MDX adapter. Other registered components are unavailable until added to this Content Block\'s Templates. Attributes accept quoted static values and bare booleans. Expressions such as `{false}` are unsupported, as are imports, spreads, functions, and executable JavaScript. Legacy `ws.element` and `ws:name` forms are read for compatibility but are not emitted or recommended. Component namespaces such as `$.*`, `radix.*`, and `animation.*` are unsupported; use the direct component identifier.\n\nWhen explicit JSX children match the designed template structure, their text and supported props overlay the cloned descendants so template styles stay intact. A mismatched child structure replaces the root defaults, and each authored child still resolves through a matching template when possible. An explicit empty pair such as `<PromotionCard></PromotionCard>` clears the defaults. A self-closing reference such as `<PromotionCard />` keeps them. Editing inherited default content writes it back as explicit JSX children. Template resolution is live: adding a missing semantic or named template later also updates existing MDX without rewriting it. Preserve unresolved template names and report their diagnostics.\n\nWhen a template is renamed or deleted, use `migrate-content-block-template-references` to update the affected MDX files. Its first call returns a plan with changed-file, update, omission, and diagnostic counts. Report the plan and repeat the exact request with its `confirmationToken` only after approval. Renames and removals update named JSX and legacy `ws:name` references, including names such as `Image` and `CodeText` when they identify templates. Rename targets must be valid PascalCase JSX identifiers. Removing a paired reference unwraps and preserves its explicit authored children; removing a self-closing reference removes that node because it has no authored children. Invalid files remain unchanged and are reported in diagnostics.\n\nUse the dedicated connect, switch, inspect, edit, update-frontmatter, reload, and disconnect operations instead of creating or deleting the Content Block\'s `src` with generic prop tools. An expression-bound source inside a Collection also needs the occurrence\'s scoped values and a distinct stable `renderScope`. For example, use `source:{"type":"expression","value":"post.assetId"}` with `variables:{"post":{"assetId":"<mdxAssetId>"}}`.\n\n`renderScope` is any non-empty stable identity for one rendered occurrence, such as `page:/articles/example`. It does not load that page, its route parameters, or its resource results. To connect a result-one Assets resource, run `preview-asset-query` with concrete values, keep the saved source expression such as `post.data.id`, and supply the previewed item for this occurrence with `variables:{"post":{"data":{"id":"<mdxAssetId>"}}}`. The operation validates that concrete Asset while persisting the dynamic expression.\n\nA source edit replaces the complete MDX document. A frontmatter update replaces the complete frontmatter property map. Inspect the current source first and preserve everything the user did not ask to change. Store frontmatter images as exact `$ref` objects. Bind editable Image sources to their resolved `.src` with explicit `binding.mode:"readwrite"`; omitting the mode leaves the image visible but not replaceable in Content mode. Bind alt properties to their Asset `.description` with `mode:"read"`. Use `update-content-block-frontmatter` for MCP frontmatter edits. MDX-rendered elements are not persistent instance targets for generic `bind-props` or `update-text` calls. Preserve existing `mode:"readwrite"` bindings when encountered; they are valid only for exact direct paths into the connected document\'s frontmatter. Direct bindings through a loaded Markdown or MDX `$ref` ending in `#frontmatter` save to the referenced file, with its write permissions enforced. Shared-record edits affect every document using that record. Computed expressions and JSON/body references remain read-only. Image replacement is supported: a direct Image source binding with `mode:"readwrite"` lets the picker replace the frontmatter `$ref`, while shared Asset metadata is edited in Asset settings.\n\nFor an editable MDX article, use the resource to select the Content Block\'s source Asset (`post.data.id`), then bind article fields inside the block to its document parameter (`document.frontmatter.title`), not query-result properties. Inspect the actual document variable name first. Create writable bindings explicitly: `update-text` uses `expressionBindingMode:"readwrite"`; `bind-props` uses `binding.mode:"readwrite"`. These generic tools target persistent designed instances, such as a header outside the MDX body, not MDX-generated instances. The MDX body is editable through its source mapping; merely placing static or query-bound content inside the block does not make it editable. Use exact direct paths; property access is already safe. Direct bindings through a loaded Markdown or MDX `$ref` ending in `#frontmatter` save to the referenced file, with its write permissions enforced. Shared-record edits affect every document using that record. Computed expressions and JSON/body references remain read-only. Image replacement is supported: a direct Image source binding with `mode:"readwrite"` lets the picker replace the frontmatter `$ref`, while shared Asset metadata is edited in Asset settings. Follow the complete article editability checklist below before handoff.\n\n### Content Block completion checklist\n\nFor every Content Block creation, migration, or repair—with or without MDX—treat Content-mode editability as a delivery requirement for all content the user expects editors to change. Before implementation, inventory each value, its intended UI control, and its write destination. Choose controls by meaning: date/calendar controls for dates, image pickers for image replacement, and direct text or number controls for reading time. Keep fixed punctuation and units outside a directly bound value element; never mix literal and expression children there. Preserve the existing stored type and wording: if `readTime` already stores `4 min read`, bind that whole string directly instead of assuming it stores only a number. Distinguish protected layout/templates from editable content explicitly. Test each field through Content mode, check the saved project data or source file, reload, restore test values, and record passed/failed/not tested. Testing one heading, inspecting bindings, or successfully writing through MCP does not cover the remaining fields. Report missing controls and unsupported writes as unfinished requirements, not successful delivery.\n\n### Complete article editability is required\n\nWhen the user asks for the whole article to be editable in Content mode, this includes every article-owned value: title, excerpt, author details and links, dates, reading time, categories, hero and inline image sources, alternative text, captions, body text, links, and custom-component content and media. Exclude only designer-owned layout, styling, templates, and shared navigation/footer unless the user asks otherwise. A correct preview, a successful MCP write, or an editable body does not prove this requirement is met.\n\nBefore handoff:\n\n1. Inventory every article field, its UI control, binding, and source file/field. Header fields outside **MDX content** are still part of the article and must be editable through the Content Block\'s document bindings.\n2. In Content mode, edit every inventoried field through the UI, including choosing a different hero image and editing link destinations and custom-component props. Do not substitute a raw MDX/YAML edit or an MCP write for this check.\n3. Read the saved source after each edit. Confirm the intended MDX body/frontmatter or referenced author file changed, unrelated content stayed intact, and the edit survives reload. Restore test values and verify restoration.\n4. Mark each field passed, failed, or not tested. Do not claim the article is fully editable while any required field is failed or untested.\n\n**Image replacement is not shared metadata editing.** For frontmatter such as `featureImage: { $ref: "./images/hero.png" }`, bind the Image source directly to `document.frontmatter.featureImage.src` with `binding.mode:"readwrite"` using `bind-props`. In Content mode, **Choose source** replaces the article’s `featureImage.$ref`; it does not overwrite the shared Asset’s resolved `.src`. The resolved URL field stays read-only. Verify the picker, saved reference, and reload—do not infer support from the displayed image alone. Bind alternative text to `.description` to use the shared Asset description; edit it through **Choose source → asset actions → Settings → Description**, which affects every use of that Asset. Do not treat a missing write mode as a platform limitation, and do not defer image replacement over a separate shared-description question.\n\nKeep intended editable values separate from display formatting. For reading time, use three inline sibling text elements: static `— `, one value element bound directly to `document.frontmatter.readingTime` with `expressionBindingMode:"readwrite"`, and static ` min read`. The value element must contain only that binding, not mixed literal and expression children. Preserve whitespace and the stored field type. Do not concatenate labels or units, use template literals, or add fallbacks/formatting calls to an editable binding. Prefer component formatting controls with a directly bound value. Explain unsupported cases and ask before making an intended editable field read-only; do not trade away editability just to match the display.\n\nIf an edit in a long-lived MCP session reports a revision conflict after another client saved the Asset, call `reload-content-block-source`, inspect the latest source, reapply the requested change, and retry. One-shot CLI calls refresh before each operation and normally cannot reproduce a stale session. Never overwrite the newer revision blindly. Use `disconnect-content-block-source` to remove the connection while leaving the Asset unchanged.\n\n## Reporting CLI/MCP Issues\n\nIf a CLI/MCP tool gives a confusing error, crashes, hangs, produces invalid output, requires an undocumented workaround, or makes you inspect source code to understand normal usage, ask the user to report it in the Webstudio Discord `#help` channel: https://wstd.us/community.\n\nGive the user a complete copy-paste report. Include only non-secret values: never include auth tokens, private URLs, cookies, API keys, passwords, or proprietary project data. Redact them as `<redacted>`.\n\nCopy-paste template:\n\n````md\nWebstudio CLI/MCP issue report\n\nWhat I was trying to do:\n<short user goal, for example "Create a resource from an external API and render it in a collection">\n\nWhat I expected:\n<what should have happened>\n\nWhat happened instead:\n<exact error, confusing behavior, hang, missing docs, or workaround required>\n\nCommand/tool used:\n\n```sh\n<exact command or MCP tool call, with tokens/secrets redacted>\n```\n\nStructured output / error:\n\n```json\n<stdout JSON or MCP structuredContent, if available, with secrets redacted>\n```\n\nStderr / lifecycle logs:\n\n```txt\n<stderr lines, timings, checkpoint messages, or stack trace, with secrets redacted>\n```\n\nEnvironment:\n\n- CLI command path: <webstudio / node packages/cli/local.js / other>\n- Webstudio CLI version: <from command output if known>\n- OS: <macOS / Windows / Linux / unknown>\n- Node version: <node -v if known>\n- Project/session state: <linked project, local .webstudio session, preview, MCP server, or unknown>\n\nWorkaround tried:\n<what the agent/user tried next, and whether it worked>\n\nWhy this should be improved:\n<one sentence: better error message, docs, schema, tool behavior, etc.>\n````\n\n## Shared-Session Shell Runs\n\nUse `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'` when you are operating from a shell and need several MCP tool calls to share one CLI session without hand-writing JSON-RPC. For large batches, pass a normal JSON file path such as `.temp/mcp-calls.json`. Do not use shell process substitution like `<(...)`; use inline JSON or a real file.\n\nUse `mcp run` for long-lived tools such as `preview.start`. A one-shot `mcp single-op-call preview.start` cannot keep ownership of a preview server for a later screenshot or stop call. Put `preview.start`, `screenshot`, and `preview.stop` in one shared `mcp run` process, or use a real long-running MCP client.\n\nInput shape:\n\n```json\n{\n "calls": [\n { "tool": "meta.index" },\n { "tool": "components.find", "input": { "brief": "radix select" } }\n ]\n}\n```\n\nRules:\n\n- The command prints JSON to stdout for both success and failure. It stops at the first failed call and prints partial results in `{ "ok": false, "error": ..., "data": { "completedCalls": ..., "results": [...] }, "meta": ... }`, then exits nonzero.\n- If a call returns `checkpoint.required`, read-only discovery and inspection remain available, but mutations and state-changing session tools return `CHECKPOINT_REQUIRED`. Stop and report the checkpoint to the parent/user. Only after the parent/user continues, call `checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}` before continuing mutations.\n- For `mcp single-op-call`, checkpoint requirements persist across later one-shot CLI processes until you call `checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}`.\n- Use this instead of manually sending JSON-RPC frames to `webstudio mcp` from a shell.\n\n### Cross-project batches\n\nAdd `projects` to the same `mcp run` manifest to run focused reads, audits, or dry runs across independently linked project roots:\n\n```json\n{\n "concurrency": 2,\n "calls": [\n { "tool": "status" },\n { "tool": "audit", "input": {} },\n {\n "tool": "update-project-settings",\n "input": { "meta": { "siteName": "Reviewed" } },\n "dryRun": true\n }\n ],\n "projects": [\n { "id": "site-a", "root": "../site-a" },\n { "id": "site-b", "root": "../site-b" }\n ]\n}\n```\n\nProject roots and an optional `progressFile` are resolved relative to the manifest file. Each project may provide its own `calls` instead of using the top-level calls. Each root must already be linked with its own `.webstudio/config.json`; the runner creates an independently authenticated ProjectSession and uses root-scoped session, audit, preview-data, and checkpoint paths without changing the process working directory.\n\nConcurrency defaults to 2, is capped at 16, and can be set in the manifest or overridden with `--concurrency`. A failure is reported for that project while other projects continue. Progress is saved after every successful call; rerunning with the default `--resume` skips completed projects and starts failed projects after their last confirmed successful call. Reads and dry runs may be retried. A committed mutation interrupted after dispatch is marked `AMBIGUOUS_MUTATION_RESULT` and is never replayed automatically; inspect that project before deciding how to continue. Use `--no-resume` only to intentionally start the complete manifest over.\n\nCommitted mutation tools are rejected in a projects batch unless the command includes `--approve-mutations`. Review the complete manifest before granting approval. `--dry-run` applies to every call and does not require mutation approval. The final stdout object is compact: project counts, one status/error record per project, elapsed time, and the progress-file path rather than every tool result.\n\n## Discovery\n\nUse MCP itself after startup, or call the same tools with `webstudio mcp single-op-call`:\n\n- `tools/list`: machine-readable available tools\n- `resources/list`: available overview and full JSON resources\n- `meta.index`: concise capability catalog\n- `meta.guide`: workflow for a user goal; call with a string brief such as `{"brief":"Create a pricing page"}`\n- `meta.get-more-tools`: detailed params, examples, namespaces, and local/server behavior; prefer exact names such as `{"tools":["insert-fragment"]}` when you know them\n- `components.list`: compact registry metadata for visible components and templates; use a focused get tool for complete details\n- `components.summary`: component counts by default; use `{"detail":"components","limit":20}` for paginated entries\n- `components.coverage-plan`: compact paged plan for design-system coverage tasks that need every component; default returns counts plus the first root page, use `{"detail":"roots"}`, `{"detail":"parts"}`, or `{"detail":"full"}` for more\n- `components.coverage-status`: page-specific covered/missing component report with `missingRoots` and `missingParts`\n- `components.search`: focused component/template search by id, namespace, label, category, or content model\n- `components.find`: compatibility alias for focused component search\n- `components.get`: full metadata for one component id\n- `templates.list`: compact metadata for template-backed insertions only\n- `templates.get`: full registry item and payload metadata for one template\n- `search-project`: find a known value or id with `webstudio search-project \'{"query":"pricing"}\'` or MCP `search-project {"query":"pricing"}`; use focused list/get tools when the target structure is unknown\n\n`meta.guide` returns structured `routing` with the selected workflow and any broad context bundle it recommends. Authentication and design context bundles appear only when their specialized workflow is selected. Set `authoredFragment` when using an authored fragment and `reuseDesignSystem` when its recommendations should retain design-system discovery tools.\n\nSet `taskScope` and `workflow` explicitly; `meta.guide` does not infer them or the authored-fragment flags from the brief. Specialized workflows are `markdown-blog`, `json-ld`, `collection`, `expression`, `authenticated-page`, `font-assets`, `design-input`, and `craft`; otherwise use `general`. For work that must not change project or local state, pass `{"brief":"Inventory custom code","taskScope":"read-only-audit","workflow":"general"}`. The resulting `read-only-discovery` workflow excludes mutation and side-effecting session tools and uses focused search, list, get, inspect, and snapshot tools.\n\n`search-project` follows normal ProjectSession synchronization, then searches in the CLI process. Namespace filters limit values matched; related namespaces may still supply route and reference context, and synchronization is unchanged. Only paged matches enter model context. Recognized credential fields and asset binary or document bodies are excluded.\n\nComponent and template registry items use a shadcn-compatible top-level shape plus Webstudio-specific superset metadata in `meta`. Use `meta.runtime` for component ids, props, states, content model, and source identity; `meta.authoring` for composition and accessibility guidance; and `meta.builder` for template insertion details and expected project-data namespaces. These items are for Builder/MCP discovery and are not a published shadcn install registry yet.\n\nPrefer the focused `components.*` tools over dumping `webstudio://project/components`. Do not write local scripts to parse full MCP discovery JSON for common component lookup.\nFor “use every component” or design-system pages, start with compact `components.coverage-plan`, checkpoint, then page through roots/parts instead of dumping the full catalog.\n\n## Consumer Capabilities\n\nMCP lets agents work on one configured Webstudio project at a time. In consumer\nterms, agents can:\n\n- Check which project they are connected to.\n- Check what the share link is allowed to do.\n- Inspect project metadata and the latest editable build.\n- Read selected project data for audits and repair.\n- Search all Builder namespaces for a known value or id without putting complete namespace data in model context.\n- Apply precise project changes against a known version.\n- List, inspect, create, update, delete, duplicate, copy, and reorder pages.\n- Set the home page.\n- Preserve old page paths for redirects or history.\n- Read and update page titles, descriptions, metadata, auth settings, and SEO fields.\n- List, create, update, duplicate, move, and delete page folders.\n- List, create, update, delete, duplicate, reorder, and reuse page templates.\n- Create pages from reusable templates.\n- Read and update project site settings.\n- Read and update marketplace product metadata.\n- List, create, update, delete, and replace redirects.\n- List, create, update, and delete responsive breakpoints.\n- List and inspect page elements.\n- Insert registered components.\n- Insert styled JSX fragments.\n- Move, reparent, clone, duplicate, wrap, unwrap, convert, rename, retag, and delete elements.\n- Fill grid cells.\n- List and update text children.\n- Update plain text and expression text.\n- Update structured rich text.\n- Add, update, delete, and bind element props.\n- Bind props to expressions, resources, actions, and runtime system values.\n- Read, add, update, delete, and replace local styles.\n- Update selected style-source styles.\n- List, create, update, attach, detach, extract, duplicate, rename, lock, unlock, reorder, clear, and delete design tokens and style sources.\n- List, define, rename, delete, and rewrite CSS variables.\n- List, create, update, and delete static data variables.\n- Create string, number, boolean, and JSON variables. Arrays use JSON.\n- Delete unused data variables.\n- List, create, update, upsert, bind, and delete resources.\n- Create HTTP resources.\n- Create GraphQL resources.\n- Create system resources.\n- Use built-in system resources for sitemap, current date, and assets.\n- List and inspect complete asset metadata; upload, download, update, move, duplicate, find usage for, replace, and delete assets.\n- List, create, rename, move, recursively duplicate, and recursively delete nested asset folders.\n- Publish to staging or production.\n- Publish to selected domains.\n- List publish builds.\n- Check publish job status.\n- Unpublish staging or production deployments.\n- List, create, update, delete, and verify custom domains.\n- Start and stop preview.\n- Capture screenshots of generated pages.\n- Compare screenshots against baselines.\n- Install OCR support for richer visual checks.\n\nUseful resources:\n\n- `webstudio://project/status`: compact current ProjectSession status\n- `webstudio://project/tools-overview`: small operation overview by capability area\n- `webstudio://project/components-overview`: small component overview with ids, labels, namespaces, and categories\n- `webstudio://project/tools`: full operation catalog; read only when focused metadata is insufficient\n- `webstudio://project/components`: full component catalog with props, states, and content model composition constraints; read only when `components.summary`, `components.find`, and `components.get` are insufficient\n- `webstudio://project/guide`: concise discovery guide\n- `webstudio://project/expressions`: expression syntax, scope, supported methods, bindings, Collection iteration context, and verification\n- `webstudio://project/accessibility-review`: evidence-based LLM accessibility-review workflow using project checks, preview, and screenshots\n\n## MCP SDK Client Imports\n\nWhen writing a local Node.js MCP client script, use the official MCP SDK package and these exact ESM imports:\n\nInside the Webstudio monorepo this package is available at the repo root. In another project, install it first with `pnpm add -D @modelcontextprotocol/sdk`.\n\n```js\nimport { Client } from "@modelcontextprotocol/sdk/client/index.js";\nimport { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";\nimport { LoggingMessageNotificationSchema } from "@modelcontextprotocol/sdk/types.js";\n```\n\nMinimal stdio client for the local Webstudio CLI:\n\n```js\nconst client = new Client({ name: "webstudio-agent", version: "1.0.0" });\n\nclient.setNotificationHandler(\n LoggingMessageNotificationSchema,\n (notification) => {\n console.error(`[mcp] ${notification.params.data}`);\n }\n);\n\nconst transport = new StdioClientTransport({\n command: "node",\n args: ["packages/cli/local.js", "mcp"],\n cwd: process.cwd(),\n stderr: "inherit",\n});\n\nawait client.connect(transport);\n\nconst index = await client.callTool({\n name: "meta.index",\n arguments: {},\n});\nconsole.log(JSON.stringify(index.structuredContent, null, 2));\n\nawait client.close();\n```\n\nUse `node packages/cli/local.js mcp` from the Webstudio monorepo root for local development, or `webstudio mcp` from a linked project where the CLI is installed. Keep stdout for JSON-RPC/structured results and surface MCP logging notifications or stderr lifecycle lines as progress.\n\n## Core Rules\n\n- stdout is reserved for MCP JSON-RPC while the server is running.\n- Operate on the configured project only.\n- Read ids before writing.\n- Prefer semantic tools over `apply-patch`.\n- Use `status` and `refresh` when cached namespaces may be stale. Pass `status {"verbose":true}` only when debugging full namespace arrays, freshness, compatibility, or diagnostic details.\n- Read `meta.session.commitStatus` before interpreting durability. Read-only results report `not-applicable` and retain `committed:false` for compatibility; dry-run plans report `planned`; failed mutations report `failed`; no-op mutations report `unchanged`; durable mutations report `committed` with `meta.session.committed:true`.\n- Never run visual verification automatically. Ask first unless the user explicitly requested screenshots, visual verification, or a rendered audit; if they do not opt in, use focused non-visual assertions.\n\n## Vision Verification Loop\n\nVision-capable AI can use MCP to see what it is building:\n\n{{mcpVisionVerificationLoopMarkdown}}\n\nGenerated app setup:\n\n{{mcpGeneratedAppDependencyNotes}}\n\n## MCP argument examples\n\nExamples below show meaningful argument combinations. Tool schemas are the\nsource of truth. For tools with no required arguments, pass `{}`.\n\n{{mcpArgumentExampleIndex}}\n\n{{contentEngineReferenceMarkdown}}\n\n## Screenshot Verification\n\n{{screenshotVerificationSummary}}\n',
|
|
344678
|
+
"manual-llm": '# Webstudio CLI Manual for LLMs\n\nUse this order. Stop only when a command returns ok:false.\n\nIf you are inside the Webstudio monorepo, the first command discovery should use\nthe local CLI exactly as `node packages/cli/local.js ...` from the repo root. Do\nnot use `packages/cli/bin.js` for local source-tree work; it is the packaged\nbuild entry and may use stale built output. Do not use `pnpm exec webstudio`,\n`pnpm --filter webstudio exec webstudio`, or a global `webstudio`: they can\nresolve an older binary.\n\nFor delegated design-system or “use every component” tasks, skip the generic warm-up sequence and start with exactly one MCP command: `webstudio workflow.next \'{"goal":"design-system-page"}\'`. Report that returned checkpoint to the parent/user and stop until continued.\n\n## Use MCP locally or optionally connect a client\n\nDo not install, register, or connect an MCP server merely because the user asks\nyou to edit a Webstudio project. If Webstudio MCP tools are already available,\nuse them. If you have shell access, use the local CLI shortcuts such as\n`webstudio meta.index` and `webstudio list-pages`; they expose the same project\noperations without changing client configuration or restarting the app.\n\nOnly when the user explicitly asks for persistent native MCP integration, run\nthe command for their client:\n\n- Claude Code: `webstudio connect claude`\n- Codex: `webstudio connect codex`\n- Cursor: `webstudio connect cursor`\n- VS Code or GitHub Copilot: `webstudio connect vscode`\n\nRun project operations from the linked project root. If the folder is not\nlinked, ask for an editable Builder share link and run\n`webstudio init --link <share-link> --json`. You can then use local CLI\nshortcuts immediately. Do not run `webstudio sync`, `webstudio connect`, or\nrestart the app unless the user specifically wants native MCP registration:\nMCP reads and edits the latest editable Builder build directly, including for\nprojects that have never been published. Treat the share link as a credential\nand do not include it in committed files, logs, screenshots, or issue reports.\n\nThe optional `connect` command verifies project access before changing client configuration. For\nClaude Code, Cursor, and VS Code it safely merges the `webstudio` server into\nthe client\'s project configuration. For Codex it runs both `codex mcp add` and\n`codex mcp get webstudio`; do not repeat those commands separately. Follow the\nreload, restart, or approval instruction printed by `connect`, then verify the\nloaded MCP connection by asking the client to use Webstudio MCP and list the\nproject pages. Use `--print` only to inspect the generated setup without\nchanging configuration or requiring project access.\n\n## Always\n\n1. webstudio permissions --json\n2. For bounded shell workflows, call MCP tools directly through the CLI shortcut, for example `webstudio meta.index` or `webstudio insert-fragment \'<json>\' --dry-run`. The explicit form `webstudio mcp single-op-call <tool> \'<json>\'` is equivalent and useful when you need to make the MCP boundary obvious. Use `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'` for multiple calls in one shared CLI session. Use a normal JSON file path for large batches. Use long-running `webstudio mcp` only when your environment is a real MCP client. Do not manually send raw JSON-RPC to `webstudio mcp` from a shell or PTY.\n3. Read MCP `meta.index`, for example `webstudio meta.index`.\n4. Use focused MCP calls with concrete JSON: `webstudio meta.guide \'{"brief":"Create a design system page using every component"}\'`, `webstudio meta.get-more-tools \'{"tools":["insert-fragment"]}\'`, `webstudio components.list \'{"source":"all"}\'`, `webstudio components.coverage-plan`, `webstudio components.search \'{"brief":"radix select"}\'`, `webstudio components.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'`, `webstudio templates.list`, and `webstudio templates.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'`.\n5. Read overview resources `webstudio://project/tools-overview` or `webstudio://project/components-overview` when useful. Read full resources `webstudio://project/tools` or `webstudio://project/components` only when focused tools are insufficient.\n6. Pick focused MCP read tool.\n7. Pick semantic MCP write tool.\n\nUse `webstudio schema mcp` for a compact MCP tool overview. Add `--verbose` only when exact input schemas for all tools are truly needed; otherwise prefer focused `meta.get-more-tools` and `components.*` calls.\n\nRun these commands from the linked project root. Use the MCP startup status line\'s absolute root for local files; write temporary scripts and artifacts under `<project root>/.temp`, not under a parent workspace.\n\nMonorepo quick path for a simple styled section:\n\n```sh\nnode packages/cli/local.js mcp single-op-call meta.index\nnode packages/cli/local.js mcp single-op-call meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nnode packages/cli/local.js mcp single-op-call insert-fragment --input-file .temp/insert-fragment.json --dry-run\n```\n\nSave the readable payload in `.temp/insert-fragment.json`:\n\n```json\n{\n "parentInstanceId": "parent-id",\n "fragment": "<section ws:style={css`padding: 32px; display: grid; gap: 12px;`}><h2>Launch Kit</h2><p>A focused section created with Webstudio JSX.</p><button>Get started</button></section>"\n}\n```\n\nSingle quotes inside the JSX keep the JSON valid without backslash-escaped attributes. The same local shortcut form is shorter and preferred for simple shell steps:\n\n```sh\nnode packages/cli/local.js meta.index\nnode packages/cli/local.js meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nnode packages/cli/local.js insert-fragment --input-file .temp/insert-fragment.json --dry-run\n```\n\nFor this simple path, do not grep source files, dump full MCP resources, or write parser scripts first. Use `list-pages`, `get-page-by-path`, or `list-instances` only to get the target `parentInstanceId`.\n\nWhen authoring JSX for `insert-fragment`, use Webstudio component helpers and Webstudio style syntax. Use `ws:style={css\\`...\\`}`for Webstudio-native CSS. For simpler cases, use React-style object syntax such as`style={{ padding: 24 }}`. Both forms create editable Webstudio style data.\n\nWhen the task says another user will edit a page in Content mode, use a Content Block (`ws:block`) around every editable region. Content-mode users can edit text and supported props only in descendants of that block; content outside it is read-only. Put reusable insertable options in exactly one direct `ws:block-template` child. Do not put intended editor content inside that template container: templates are protected source material, while an inserted template copy becomes editable Content Block body content. A missing or second template container makes the block invalid. Verify this structure before handoff.\n\nWhen MDX files belong to a content collection, connect the collection to its dynamic entry page explicitly. Read the page ID after finding or creating the page, verify that its path has exactly one URL parameter, and store the ID at `x-webstudio.entryPageId` in `collection.json`. Do not assume Webstudio infers the page from its path, Assets query, or Content Block source. Preserve the rest of `collection.json`, and verify **Open on canvas** from both an entry asset menu and **Entry settings** before handoff. If no compatible page exists, leave the field unset and report that entry navigation still needs configuration.\n\nWhen the Content Block body must be stored in an `.mdx` Asset, use the dedicated `connect-content-block-source`, `switch-content-block-source`, `inspect-content-block-source`, `edit-content-block-source`, `update-content-block-frontmatter`, `reload-content-block-source`, and `disconnect-content-block-source` tools. Do not manipulate its `src` with generic prop tools. Connecting replaces existing Body content, so report `requiresConfirmation:true` and retry with `confirmReplacement:true` only after user approval. Prefer Markdown for standard document content; it automatically uses unique matching semantic templates. Use lowercase JSX such as `<section>` or `<svg>` for HTML or SVG that Markdown cannot express. Capitalized JSX resolves a unique stable template **Name** or a built-in MDX adapter. Add custom components to this Content Block\'s Templates before referencing them in MDX. The display label is independent. JSX attributes accept quoted values and bare booleans; expressions such as `{false}` are unsupported. Legacy `ws.element` and `ws:name` forms are compatibility input only. Component namespaces such as `$.*`, `radix.*`, and `animation.*` are unsupported; use direct component identifiers. Matching explicit children overlay designed descendants and keep their template styles. A mismatched child structure replaces defaults, while an explicit empty pair clears defaults and a self-closing reference keeps them. Editing inherited default content writes it back as explicit JSX children. Template resolution is live, including when a matching template is added after the MDX element. Preserve invalid MDX and unresolved template names, inspect all source-located diagnostics, and resolve revision conflicts by reloading before reapplying the change. After a template rename or deletion, use `migrate-content-block-template-references` to preview and confirm custom-template JSX and legacy updates across selected MDX files. A confirmed removal unwraps paired references and preserves their authored children; a self-closing reference disappears because it has none.\n\nFrontmatter is part of the same MDX source. `update-content-block-frontmatter` receives the complete replacement property map, so inspect and preserve properties the user did not ask to remove. Store frontmatter images as exact `$ref` objects. Bind editable Image sources to their resolved `.src` with explicit `binding.mode:"readwrite"`; omitting the mode leaves the image visible but not replaceable in Content mode. Bind alt properties to their Asset `.description` with `mode:"read"`. Use `update-content-block-frontmatter` for MCP frontmatter edits; MDX-rendered elements are not persistent targets for generic prop or text mutations. Preserve existing `mode:"readwrite"` bindings, which are valid only for exact direct frontmatter paths. Direct bindings through a loaded Markdown or MDX `$ref` ending in `#frontmatter` save to the referenced file, with its write permissions enforced. Shared-record edits affect every document using that record. Computed expressions and JSON/body references remain read-only. Image replacement is supported: a direct Image source binding with `mode:"readwrite"` lets the picker replace the frontmatter `$ref`, while shared Asset metadata is edited in Asset settings. For an expression-bound source inside a Collection, pass the occurrence\'s scoped values and a distinct stable `renderScope`; for example, resolve `post.assetId` with `variables:{"post":{"assetId":"<mdxAssetId>"}}`.\n\nFor an editable MDX article, use the resource to select the Content Block\'s source Asset (`post.data.id`), then bind article fields inside the block to its document parameter (`document.frontmatter.title`), not query-result properties. Inspect the actual document variable name first. Create writable bindings explicitly: `update-text` uses `expressionBindingMode:"readwrite"`; `bind-props` uses `binding.mode:"readwrite"`. These generic tools target persistent designed instances, such as a header outside the MDX body, not MDX-generated instances. The MDX body is editable through its source mapping; merely placing static or query-bound content inside the block does not make it editable. Use exact direct paths; property access is already safe. Direct bindings through a loaded Markdown or MDX `$ref` ending in `#frontmatter` save to the referenced file, with its write permissions enforced. Shared-record edits affect every document using that record. Computed expressions and JSON/body references remain read-only. Image replacement is supported: a direct Image source binding with `mode:"readwrite"` lets the picker replace the frontmatter `$ref`, while shared Asset metadata is edited in Asset settings. Follow the complete article editability checklist below before handoff.\n\n### Content Block completion checklist\n\nFor every Content Block creation, migration, or repair—with or without MDX—treat Content-mode editability as a delivery requirement for all content the user expects editors to change. Before implementation, inventory each value, its intended UI control, and its write destination. Choose controls by meaning: date/calendar controls for dates, image pickers for image replacement, and direct text or number controls for reading time. Keep fixed punctuation and units outside a directly bound value element; never mix literal and expression children there. Preserve the existing stored type and wording: if `readTime` already stores `4 min read`, bind that whole string directly instead of assuming it stores only a number. Distinguish protected layout/templates from editable content explicitly. Test each field through Content mode, check the saved project data or source file, reload, restore test values, and record passed/failed/not tested. Testing one heading, inspecting bindings, or successfully writing through MCP does not cover the remaining fields. Report missing controls and unsupported writes as unfinished requirements, not successful delivery.\n\n### Complete article editability is required\n\nWhen the user asks for the whole article to be editable in Content mode, this includes every article-owned value: title, excerpt, author details and links, dates, reading time, categories, hero and inline image sources, alternative text, captions, body text, links, and custom-component content and media. Exclude only designer-owned layout, styling, templates, and shared navigation/footer unless the user asks otherwise. A correct preview, a successful MCP write, or an editable body does not prove this requirement is met.\n\nBefore handoff:\n\n1. Inventory every article field, its UI control, binding, and source file/field. Header fields outside **MDX content** are still part of the article and must be editable through the Content Block\'s document bindings.\n2. In Content mode, edit every inventoried field through the UI, including choosing a different hero image and editing link destinations and custom-component props. Do not substitute a raw MDX/YAML edit or an MCP write for this check.\n3. Read the saved source after each edit. Confirm the intended MDX body/frontmatter or referenced author file changed, unrelated content stayed intact, and the edit survives reload. Restore test values and verify restoration.\n4. Mark each field passed, failed, or not tested. Do not claim the article is fully editable while any required field is failed or untested.\n\n**Image replacement is not shared metadata editing.** For frontmatter such as `featureImage: { $ref: "./images/hero.png" }`, bind the Image source directly to `document.frontmatter.featureImage.src` with `binding.mode:"readwrite"` using `bind-props`. In Content mode, **Choose source** replaces the article’s `featureImage.$ref`; it does not overwrite the shared Asset’s resolved `.src`. The resolved URL field stays read-only. Verify the picker, saved reference, and reload—do not infer support from the displayed image alone. Bind alternative text to `.description` to use the shared Asset description; edit it through **Choose source → asset actions → Settings → Description**, which affects every use of that Asset. Do not treat a missing write mode as a platform limitation, and do not defer image replacement over a separate shared-description question.\n\nKeep intended editable values separate from display formatting. For reading time, use three inline sibling text elements: static `— `, one value element bound directly to `document.frontmatter.readingTime` with `expressionBindingMode:"readwrite"`, and static ` min read`. The value element must contain only that binding, not mixed literal and expression children. Preserve whitespace and the stored field type. Do not concatenate labels or units, use template literals, or add fallbacks/formatting calls to an editable binding. Prefer component formatting controls with a directly bound value. Explain unsupported cases and ask before making an intended editable field read-only; do not trade away editability just to match the display.\n\nDo not access host globals or dynamic code APIs in JSX fragments, including `process`, `globalThis`, `eval`, `Function`, or `constructor`. JSX fragments are declarative project data; use the built-in Webstudio helpers instead.\n\nUse Webstudio prop names in JSX: `class`, `for`, `aria-label`, and other HTML/Webstudio names. Do not use React-only aliases such as `className` or `htmlFor`; the runtime rejects them with the Webstudio prop name to use.\n\nUse Webstudio actions for event/action props. Do not pass JavaScript functions such as `onClick={() => ...}`; the runtime rejects them because functions cannot be persisted as Webstudio project data.\n\n```tsx\n<button onClick={new ActionValue(["event"], expression`console.log(event)`)}>\n Open\n</button>\n```\n\nPlain JSX prop values must be JSON-compatible: `null`, strings, booleans, finite numbers, arrays, and plain objects. Do not pass `undefined`, `Symbol`, `BigInt`, `NaN`, `Infinity`, `Date`, `Map`, `Set`, class instances, or circular objects; omit the prop, use plain data, or use `expression`/`ActionValue` when the value is dynamic.\n\nIf a component has a registered template with required parts, JSX must include those parts explicitly under the same parent structure as the template, for example `<Switch><SwitchThumb /></Switch>`. Use `insert-component` when you want Webstudio to apply one component template automatically.\n\n## Animation Components\n\nBefore creating animation examples, inspect the exact components with focused discovery:\n\n```sh\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-animation:AnimateChildren"}\'\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-animation:AnimateText"}\'\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-animation:StaggerAnimation"}\'\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-animation:VideoAnimation"}\'\n```\n\nUse Animation Group (`AnimateChildren`) as the controller. Put normal instances directly inside it, or put Text Animation, Stagger Animation, or Video Animation directly inside it. Text, Stagger, and Video are helper components with `contentModel.category: "none"` and should not be used as standalone section roots.\n\nDefine timing and CSS changes on the Animation Group `action` prop. Use `type:"view"` for viewport entry/exit progress and `type:"scroll"` for scroll-progress timelines. For in animations, keep the canvas styles as the final state and use `fill:"backwards"` with keyframes that describe the starting state. For out animations, use `fill:"forwards"` with keyframes that describe the ending state.\n\nText Animation settings: `slidingWindow` defaults to `5`, `easing` defaults to `linear`, and `splitBy` defaults to `char`. Use `splitBy:"space"` for word-by-word animation. The parent Animation Group keyframes provide the actual opacity, translate, scale, or other styles.\n\nStagger Animation settings: `slidingWindow` defaults to `1` and `easing` defaults to `linear`. It applies parent Animation Group progress across its direct children. Use `slidingWindow:0` for instant sequential steps, `1` for one child at a time, and values above `1` for overlapping waves.\n\nVideo Animation settings: `timeline` is a boolean. Prefer `insert-component` for Video Animation so the Video child template is inserted, then configure the Video child asset/source. Use short, seek-friendly videos for smooth scroll-linked playback.\n\nUse JSX fragments for authored animation structures when you need styled, editable examples. Put the final visual state in `ws:style` and put the starting or ending animated state in the Animation Group `action` keyframes. Include an explicit `offset` on every keyframe: use `offset: 0` for starting-state keyframes with `fill:"backwards"` and `offset: 1` for ending-state keyframes with `fill:"forwards"`.\n\n```tsx\n<AnimateChildren\n action={{\n type: "view",\n axis: "block",\n animations: [\n {\n name: "Fade up on entry",\n timing: {\n fill: "backwards",\n rangeStart: ["entry", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["entry", { type: "unit", value: 100, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n opacity: { type: "unit", value: 0, unit: "number" },\n translate: {\n type: "tuple",\n value: [\n { type: "unit", value: 0, unit: "number" },\n { type: "unit", value: 24, unit: "px" },\n ],\n },\n },\n },\n ],\n },\n ],\n }}\n>\n <section\n ws:style={css`\n display: grid;\n gap: 16px;\n padding: 48px;\n border-radius: 24px;\n background: #111827;\n color: white;\n `}\n >\n <h2>Launch metrics</h2>\n <p>A polished card that fades up as it enters the viewport.</p>\n </section>\n</AnimateChildren>\n```\n\nFor Text Animation, keep `AnimateText` as the direct child of Animation Group and place the text-containing element inside it:\n\n```tsx\n<AnimateChildren\n action={{\n type: "view",\n animations: [\n {\n name: "Parallax In",\n timing: {\n fill: "backwards",\n rangeStart: ["cover", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 70, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n translate: {\n type: "tuple",\n value: [\n { type: "unit", value: 0, unit: "number" },\n { type: "unit", value: 100, unit: "px" },\n ],\n },\n },\n },\n ],\n },\n {\n name: "Opacity In",\n timing: {\n fill: "backwards",\n rangeStart: ["cover", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 70, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n opacity: { type: "unit", value: 0, unit: "number" },\n },\n },\n ],\n },\n {\n name: "Scale In",\n timing: {\n fill: "backwards",\n rangeStart: ["cover", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 70, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n scale: {\n type: "tuple",\n value: [\n { type: "unit", value: 5, unit: "number" },\n { type: "unit", value: 5, unit: "number" },\n ],\n },\n },\n },\n ],\n },\n {\n name: "Parallax Out",\n timing: {\n fill: "forwards",\n rangeStart: ["cover", { type: "unit", value: 50, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 100, unit: "%" }],\n },\n keyframes: [\n {\n offset: 1,\n styles: {\n translate: {\n type: "tuple",\n value: [\n { type: "unit", value: 0, unit: "number" },\n { type: "unit", value: -100, unit: "px" },\n ],\n },\n scale: {\n type: "tuple",\n value: [\n { type: "unit", value: 5, unit: "number" },\n { type: "unit", value: 5, unit: "number" },\n ],\n },\n opacity: { type: "unit", value: 0, unit: "number" },\n },\n },\n ],\n },\n ],\n insetStart: { type: "unit", value: 5, unit: "%" },\n insetEnd: { type: "unit", value: 5, unit: "%" },\n isPinned: true,\n }}\n>\n <AnimateText splitBy="space" slidingWindow={5} easing="easeOutQuart">\n <h2>Animate words with controlled rhythm</h2>\n </AnimateText>\n</AnimateChildren>\n```\n\nFor Stagger Animation, put the repeated cards or rows directly inside `StaggerAnimation`:\n\n```tsx\n<AnimateChildren\n action={{\n type: "view",\n animations: [\n {\n timing: {\n fill: "backwards",\n rangeStart: ["contain", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["contain", { type: "unit", value: 30, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n opacity: { type: "unit", value: 0, unit: "number" },\n },\n },\n ],\n },\n ],\n }}\n>\n <StaggerAnimation>\n <article\n ws:style={css`\n padding: 20px;\n border: 1px solid #d1d5db;\n border-radius: 16px;\n `}\n >\n Plan\n </article>\n <article\n ws:style={css`\n padding: 20px;\n border: 1px solid #d1d5db;\n border-radius: 16px;\n `}\n >\n Build\n </article>\n <article\n ws:style={css`\n padding: 20px;\n border: 1px solid #d1d5db;\n border-radius: 16px;\n `}\n >\n Launch\n </article>\n </StaggerAnimation>\n</AnimateChildren>\n```\n\nFor Video Animation, use the registered template via `insert-component` when possible. If you author JSX, include the Video child explicitly:\n\n```tsx\n<AnimateChildren\n action={{\n type: "view",\n animations: [\n {\n name: "Video progress",\n timing: {\n fill: "both",\n rangeStart: ["cover", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 100, unit: "%" }],\n },\n keyframes: [{ offset: 0, styles: {} }],\n },\n ],\n }}\n>\n <VideoAnimation timeline={true}>\n <Video\n preload="auto"\n autoPlay={true}\n muted={true}\n playsInline={true}\n crossOrigin="anonymous"\n />\n </VideoAnimation>\n</AnimateChildren>\n```\n\n## Command Surface Boundary\n\n- Use top-level `webstudio ...` shell commands for setup, sync/import/build/preview/screenshot, permissions, publish/domains, schema, registry inspection, man, and starting MCP.\n- Use MCP tools for Builder project data manipulation: pages, instances/components, props, text, styles, tokens, variables, resources, assets, breakpoints, redirects, and raw patches.\n- From a shell, call MCP tools with the shortcut form `webstudio <tool> \'<json>\'`, for example `webstudio insert-fragment \'<json>\' --dry-run`. The explicit equivalent is `webstudio mcp single-op-call <tool> \'<json>\'`. Use `--input-file` for large payloads.\n- Inside the Webstudio monorepo, call the local CLI as its own command: `node packages/cli/local.js ...`. Do not wrap the CLI call in `pwd && ...`, command substitution, `pnpm exec webstudio`, `pnpm --filter webstudio exec webstudio`, or a global `webstudio`.\n- For experiments, pass `--dry-run` to local-capable mutation calls. Read the computed transaction from `meta.session.transaction` and its base build version from `meta.session.version`. Copying a `.webstudio` folder is not an isolated project clone; `.webstudio/config.json` still points to the same remote project, so non-dry-run mutations can commit to that project.\n- Read `meta.session.commitStatus` before interpreting durability. Read-only results report `not-applicable` and retain `committed:false` for compatibility; dry-run plans report `planned`; failed mutations report `failed`; no-op mutations report `unchanged`; durable mutations report `committed` with `meta.session.committed:true`.\n- For bounded multi-step shell work, run inline JSON with `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'`; this reuses one CLI session without raw JSON-RPC. For large batches, write `{ "calls": [{ "tool": "..." }] }` to a normal JSON file and run `webstudio mcp run .temp/mcp-calls.json`.\n- Use JSON strings for `brief` fields. Never pass boolean flags such as `{"brief":true}`.\n- Treat `webstudio mcp single-op-call` and `webstudio mcp run` stderr lines as progress checkpoints; stdout remains JSON on both success and failure. On failure, parse stdout for `{ "ok": false, "error": { "code": "...", "message": "..." } }` before deciding what to fix.\n- If a CLI/MCP tool crashes, hangs, gives a confusing error, needs an undocumented workaround, or forces source-code inspection for normal usage, ask the user to report it in Discord `#help` at https://wstd.us/community. Give them a complete copy-paste report with the goal, expected behavior, actual error, exact command/tool call, stdout JSON, stderr/lifecycle logs, environment, workaround, and secrets redacted.\n- Run one-shot `webstudio mcp single-op-call` commands sequentially against a linked `.webstudio` folder. If a command returns `PROJECT_SESSION_BUSY`, another CLI/MCP process is updating the local session; wait a moment and retry sequentially.\n- In delegated or non-streaming agent environments, do not batch many MCP calls silently and do not wrap many shortcut or `webstudio mcp single-op-call` commands in a shell loop. Treat each parent-visible checkpoint as the unit of work. If the parent asks for status within 30 seconds, run exactly one shortcut command such as `webstudio meta.index` or one explicit `webstudio mcp single-op-call` command, report that command/result, then wait for the parent to continue. Do not take a broad task such as creating a full design-system page as one execution unit. Call `workflow.next {"goal":"design-system-page"}`, report the returned phase/checkpoint, wait until the parent continues, call `checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}`, complete exactly that bounded phase, and return: discovery, page creation, one dry-run JSX section, one committed JSX section, one `components.coverage-insert-next` call, or one presentation pass. Phase commands do not include nextPhase in their own output. After the parent continues, acknowledge the previous checkpoint first, then call `workflow.next` with the next phase. For all-component design-system pages, checkpoint after workflow planning, discovery, page creation, call `components.coverage-insert-next` once before checkpointing again, then finish with `workflow.next {"goal":"design-system-page","phase":"presentation-pass"}`. Coverage 72/72 is necessary but not sufficient: the page must be organized into styled, real-world examples, not raw unstyled component dumps.\n- For design-system or “use every component” tasks, start with compact `webstudio components.coverage-plan`, checkpoint, then request component coverage details with `webstudio components.coverage-plan \'{"detail":"roots","offset":20}\'` or `{"detail":"parts"}` only when needed. Do not pass `detail` to `list-pages`; use `list-pages {}` or `get-page-by-path` for page lookup.\n- MCP tool shortcuts are only for MCP tools. If a shortcut is ambiguous with a real top-level command, the real top-level command wins; use `webstudio mcp single-op-call <tool> \'<json>\'` to force the MCP path.\n\n## LLM Implementation Process\n\nUse this process for user requests that change Webstudio content, layout, styles, assets, pages, redirects, resources, or publishing state:\n\n1. Discover capabilities with `webstudio man --json`, `webstudio schema api`, `webstudio schema mcp`, MCP `meta.index`, `meta.guide`, `meta.get-more-tools`, `components.list`, `components.summary`, `components.coverage-plan`, `components.search`, `components.get`, `templates.list`, and `templates.get`. From a shell, prefer shortcut calls such as `webstudio meta.index` and `webstudio components.search \'{"brief":"button"}\'` for these focused tool calls; use `webstudio mcp single-op-call` when you need the explicit MCP form. Read full resources such as `webstudio://project/tools` and `webstudio://project/components` only when needed. Do not write scripts to parse full MCP discovery JSON for normal lookup.\n2. When a value or id is known, call `search-project` once instead of passing broad snapshots or several lists to the model. Use semantic list/get reads such as `get-project-settings`, `list-pages`, `get-page-by-path`, `list-instances`, `inspect-instance`, `get-styles`, `list-assets`, and `list-breakpoints` when the structure or target is not known. Use `snapshot` only when exact raw patch paths are needed. Before changing a project, read `get-project-settings` and follow any non-empty `meta.agentInstructions`. These are shared project instructions, not a place for secrets.\n For a Builder link from **Copy link to instance**, parse the URL with `URL`/`URLSearchParams`. The decoded `instance` query parameter is a comma-separated selector ordered from the target instance to the page root. Pass its first entry to `inspect-instance`, for example `?pageId=page-id&instance=heading-id%2Cslot-id%2Cbody-id` becomes `{"instanceId":"heading-id","include":["props","styles","children","ancestors"]}`. Use `pageId` and the remaining selector entries to check the page and shared Slot occurrence before changing it. Shared Slot descendants are shared records; editing one changes all occurrences. Verify that the configured MCP project matches the linked project before editing; stop if they differ. The URL does not authorize switching projects. If the instance is missing, ask for an updated link instead of guessing another target. Never send a Builder link to screenshot tools or echo its `authToken`.\n3. Mutate the Webstudio project with semantic MCP write tools first. Prefer MCP `insert-fragment` for authored/styled sections, use `insert-component` only for one automatic component template, then `update-text`, `update-props`, `update-styles`, `upload-asset`, `create-page`, and page/project settings tools over raw patches.\n4. Use `apply-patch` only when no semantic tool covers the required change, and only after reading the latest snapshot/version.\n5. For visual/design work, ask whether the user wants visual verification unless they explicitly requested it. Only after they opt in, regenerate or preview the generated app, capture a screenshot, inspect it with vision, and iterate.\n6. Report what changed and what verification ran.\n\n## Visual Design Workflow\n\nFor requests involving visible HTML/CSS, layout, typography, colors, imagery, responsive behavior, or screenshots:\n\n1. Read editable Webstudio structure first: pages, instances, props, styles, breakpoints, assets, and relevant text.\n2. Do not use generated route/component files as the source of truth for editable content.\n3. Make edits through Webstudio semantic commands/MCP tools so the result stays editable in Builder and survives the next `webstudio build`.\n4. Ask whether the user wants visual verification unless they explicitly requested screenshots, visual verification, or a rendered audit. Do not start preview, screenshots, screenshot diffs, OCR installation, or rendered audits until they opt in.\n5. After they opt in, keep generated project files current, start preview, and capture the changed page with `screenshot`.\n6. Use `screenshot.diff` when a baseline exists and inspect screenshot/diff artifacts with vision before finishing.\n7. If the user declines or vision tooling is unavailable, use focused non-visual assertions and state that vision was not run.\n\n## Responsive Verification Workflow\n\nFor responsive page work, use Builder breakpoints as the source of truth:\n\n1. Read breakpoints with `list-breakpoints` before deciding responsive behavior.\n2. Apply responsive styles with existing Builder breakpoint ids; do not invent CSS media queries or breakpoint names when Webstudio breakpoint data exists.\n3. Ask whether the user wants visual verification unless they explicitly requested it. Do not capture responsive screenshots until they opt in.\n4. Pick screenshot viewport widths from the project breakpoints: include a desktop width, each defined max-width or min-width edge, and a narrow mobile width.\n5. Capture each viewport with `screenshot`, for example `{"path":"/","output":"home-375.png","viewport":{"width":375,"height":812}}` and `{"path":"/","output":"home-1440.png","viewport":{"width":1440,"height":900}}`.\n6. Inspect every viewport screenshot with vision before finishing, checking layout, overflow, hidden content, text wrapping, and breakpoint-specific style changes.\n7. If any viewport fails, update styles through semantic Webstudio tools and repeat screenshots for the affected breakpoints.\n\n## Generated Files Guardrails\n\n- Do not edit `app/__generated__`, generated route files, generated page files, generated CSS, or build output for normal Webstudio content/design requests.\n- Do not replace generated page components with handcrafted app code unless the user explicitly asks for code-only export customization.\n- Generated files are build artifacts and may be overwritten by `webstudio build`.\n- If a task truly requires generated app customization, keep it outside `app/__generated__` where possible and explain that it is not editable Webstudio content.\n\n## Values vs Bindings\n\nBefore authoring unfamiliar expressions, read `webstudio://project/expressions` with MCP `resources/read` or `webstudio mcp read-resource webstudio://project/expressions`. It documents the supported expression subset, method allowlist, scope, Collection context, and validation limits.\n\n- Use direct value tools for fixed content. For one visible text child, use `update-text` with plain `text`. For a bounded multi-instance literal replacement, use `replace-text` with `find`, `replace`, `pagePath` or `pageId`, and `limit`; it does not change expression children. Use `replace-prop-text` for bounded changes inside static string props, optionally limited to prop names or instance ids; it never changes dynamic bindings. For static props such as `aria-label`, `alt`, `id`, `class`, `href`, or button labels stored as props, use `update-props` with the prop\'s direct type/value.\n- Use `bind-props` only when the prop must stay dynamic: an expression, resource result, action, or existing scoped runtime context such as `system`. Do not use `bind-props` just to set a fixed string.\n- Direct prop string example: `{"updates":[{"instanceId":"button-id","name":"aria-label","type":"string","value":"Open menu"}]}`.\n- Expression binding example: `{"bindings":[{"instanceId":"link-id","name":"href","binding":{"type":"expression","value":"currentPost.url"}}]}`.\n- Page metadata fields such as `title`, `description`, `language`, `redirect`, and custom meta content accept plain fixed text. For computed values, pass JavaScript expression code such as `pageTitle ?? "Pricing | Acme"`.\n- Page `status` accepts a fixed HTTP status code as a number from 200 through 599, for example `302`. For a dynamic status, pass JavaScript expression code such as `system.status`.\n- Page metadata update example: use `update-page` with `{"pageId":"page-id","values":{"title":"Pricing | Acme","meta":{"description":"Plans for teams"}}}`.\n- Draft a page with `update-page` and `{"pageId":"page-id","values":{"isDraft":true}}`. It remains editable and previewable but is omitted from every publish target, including staging, and from sitemap output.\n- Stage a draft page for a future publish with `{"pageId":"page-id","values":{"isDraft":false}}`. This clears draft state but does not deploy the site. The home page and `/*` catch-all page cannot be drafts.\n- Resource `url` accepts plain fixed URLs and paths. For computed URLs, pass JavaScript expression code such as `"https://api.example.com/items?tag=" + filters.tag`. Resource header values, search parameter values, and text bodies accept expressions for dynamic values; for fixed text, use `{ "type": "literal", "value": "application/json" }`.\n- Resource update example: use `update-resource` with `{"resourceId":"resource-id","values":{"url":"https://api.example.com/items"}}`.\n- Assets is one system resource that always executes a structured query. `result:"many"` returns the existing ID-keyed collection shape and is the backward-compatible default. `result:"one"`, `result:"first"`, and `result:"last"` return one direct item or `null`; first and last require explicit sorting. `create-assets-resource` without `query` uses the default URL and optional image-dimensions output.\n- For a Markdown-backed blog, create exactly two Builder page definitions: a fixed `/blog` overview and one `/blog/:slug` detail page. Both pages load content through Assets resources. Never create one Builder page per post or duplicate Markdown content into static page structures. Read `get-asset-field-catalog`, validate each structured query with `validate-asset-query`, then call `create-assets-resource` or `update-assets-resource`. Set `values.query:null` to restore the default query.\n- Optimize every explicit Assets query for the deployed content-database size. Use `output.mode:"fields"` and select only fields that are actually rendered or otherwise required by the query. Keep `includeMetadata:false` unless the rendered value needs file metadata; diagnostics are returned separately. Do not use `output.mode:"all"` as a convenience default.\n- Every reachable Assets data source contributes to the shared database. Keep one final resource per rendered query. Update an existing scoped resource instead of creating a placeholder, preview copy, or repair replacement, and remove obsolete duplicate resources and data sources.\n- Make a bounded overview fully static: use literal values for its filters, limit, and offset, add a deterministic ID tie-breaker to its sort, and use `content.mode:"none"`. This lets compilation materialize the small overview result instead of retaining overview-only fields across every candidate article. Reserve runtime expressions for values that are truly dynamic, such as `system.params.slug` on the detail route.\n- Query Markdown or MDX files directly and use `content.mode:"markdown-body-ref"` when rendering their bodies. The published database keeps only metadata and document references, filters and paginates first, and fetches the selected files from Asset storage at runtime. Do not create companion JSON descriptors merely to avoid embedding the document.\n- `full` and bounded `range` request embedded file bytes. Use them only when the caller explicitly requires the complete source or a byte range.\n- Deferred Markdown or MDX bodies exclude frontmatter and resolve conventional relative links and images such as `../images/hero.png` to matching Assets. Markdown Embed permits sanitized figures, audio, video, and iframes, but removes scripts, inline event handlers, and unsafe URLs. It does not render Webstudio MDX elements; connect the `.mdx` file to a Content Block for that workflow.\n- For `result:"many"`, Assets expose an ID-keyed map at `<dataSourceName>.data` and `totalCount`/`hasMore` at `<dataSourceName>.meta`; bind a listing Collection to `posts.data`. Single-result modes expose the selected item or `null` directly at `<dataSourceName>.data`, always include its `id`, and expose `totalCount` in meta. Bind detail components and page settings directly from expressions such as `post.data.properties.title`, `post.data.content.text`, and `post.data ? 200 : 404` without a Collection.\n- Use `preview-asset-query` with concrete values before binding expressions in the saved resource. Inspect `__diagnostics__.query` for the temporary query-only footprint and `__diagnostics__.database` for the merged database built from all reachable Assets queries. Only `database.usedBytes` counts toward `database.maxBytes`; query sizes are not separate allowances and must not be summed. Compare `usedBytes`, `unboundedBytes`, and `truncated` within both scopes. A finished Markdown blog must include every source document without truncation, contain no embedded Markdown bodies, and retain only the intended materialized overview query. When merged usage approaches the limit, remove duplicate reachable resources first, then unused output fields, then narrow candidate files. Inspect saved mode and configuration with `list-assets-resources` or `get-assets-resource`; shared index maintenance is automatic.\n- Data variable values support `string`, `number`, `boolean`, and `json`. Use `json` for all arrays, objects, filters, and nested data.\n- Parameters are internal scoped runtime values from pages, collections, or components. They are not a public authoring surface: do not create, update, or delete parameter records. Public tools should preserve existing parameter records and may reference documented context values such as `system` in expressions where they are already in scope.\n- Use scoped resources for read data. A GET resource created with `scopeInstanceId`/`dataSourceName` defaults to `exposeAsDataSource:true`, becomes a scoped resource data variable, is generated into the page resource `data` map, and may be loaded while rendering the page. Read the loaded resource result from its wrapper, usually `.data`.\n- Use prop-bound resources for actions. A resource created without `scopeInstanceId` and bound to a component prop such as Form `action` with `bind-props` and `binding.type: "resource"` becomes an action resource in the page resource `action` map. Use this for POST, PUT, DELETE, webhooks, GraphQL submissions, and anything that should run only from an explicit form/action flow.\n- POST, PUT, and DELETE resources default to `exposeAsDataSource:false`, even with a scope. Set `exposeAsDataSource:true` only for an intentional render-time read such as a GraphQL POST query; provide `scopeInstanceId` and inspect the returned warning. Set it to `false` during `update-resource` to detach existing render-time exposure.\n- For dynamic resource query parameters prefer `searchParams`, for example `{"name":"tag","value":"filters.tag"}`. Use `{"type":"literal","value":"website"}` for fixed request text. Header values can use an expression such as `"Bearer " + auth.token`. Body can be an object expression, including GraphQL payloads such as `{ query: "...", variables: { slug: system.params.slug } }`.\n- Resource methods are `get`, `post`, `put`, and `delete`. Optional resource controls are `graphql` and `system`. Use `control:"graphql"` for GraphQL POST resources with query bodies. Use `control:"system"` for built-in local resource URLs such as `"/$resources/current-date"` and for resources reading the built-in `system` parameter. The built-in system fields are `system.origin`, `system.pathname`, `system.params`, and `system.search`; do not use `system.path`.\n- Whenever an array or object from a resource or data variable should render repeated UI, call `insert-collection` with the complete iterable and one repeated-item JSX root. The command creates the Collection, private item parameters, iterable binding, and descendant item bindings atomically. Use `collectionItem` and `collectionItemKey` expressions in the item JSX. Wrap multiple repeated siblings in one Element, and give repeated Radix items stable unique `value` bindings.\n- Expressions are single JavaScript expressions, not statements or functions. Functions, arrow functions, classes, `new`, `this`, `await`, imports, arbitrary calls, increment/decrement, and assignment outside actions are unsupported. Property and index access are made safe automatically, so write direct access. Use nullish coalescing when a fallback is required.\n\n## Pick Read Command\n\n{{readFirst}}\n\n## Pick Write Command\n\n{{taskRecipeIndex}}\n\n## Raw Patch Only If Needed\n\n1. Use MCP tool: snapshot.\n2. Write BuildPatchTransaction[].\n3. Use MCP tool: apply-patch.\n\n## MCP Argument Examples\n\nMCP tools receive JSON argument objects, not CLI flags. Use these shapes:\n\n{{mcpArgumentExampleIndex}}\n\n## Rules\n\n- Never guess ids for existing records. Read them first.\n- Never use project ids from user input. Commands use the configured project.\n- Use --refresh before a local-capable command when cached data may be stale.\n- Pass --json only to commands whose help/schema documents it. Do not add --json to top-level commands such as sync unless supported.\n- On VERSION_CONFLICT, read MCP snapshot again, regenerate the patch, then retry.\n- Treat stdout JSON as the API contract and stderr as diagnostics.\n- Never run visual verification automatically. Ask first unless the user explicitly requested screenshots, visual verification, or a rendered audit; if they do not opt in, use focused non-visual assertions.\n- Do not edit generated files for normal Webstudio content/design requests.\n- Use direct values for static strings and bindings only for dynamic expressions/resources/actions.\n- Use plain fixed text where documented. Only encode a quoted JavaScript string literal when a field is explicitly documented as an expression-only value.\n- Confirm destructive commands with --confirm only when user requested deletion/unpublish/replacement.\n- Use webstudio schema api for machine-readable top-level command metadata and webstudio schema mcp for MCP tool schemas.\n\n## Known Gaps\n\n{{knownCliGapIndex}}\n',
|
|
344679
|
+
"manual-mcp": '# Webstudio MCP Manual\n\n`webstudio mcp` starts a stdio MCP server for real MCP clients. Shell users can call MCP tools with the shortcut form `webstudio <tool> \'<json>\'`, for example `webstudio meta.index` or `webstudio insert-fragment \'<json>\' --dry-run`. `webstudio mcp single-op-call` is the explicit equivalent and prints the structured JSON result. `webstudio mcp run` runs multiple MCP tool calls from inline JSON or a normal JSON file in one shared CLI session. Do not manually type or pipe raw JSON-RPC frames into `webstudio mcp` from an interactive shell or PTY.\n\n## Startup\n\nIf you are already working with a shell-capable agent, it can use the local CLI\ndirectly. Native MCP client registration is optional. Give the editable Builder\nshare link only when the trusted agent asks for it. Treat the share link as a\ncredential: do not include it in committed files, screenshots, logs, or issue\nreports.\n\n1. Configure a project with `webstudio init --link <api-share-link> --json`.\n2. Check capabilities with `webstudio permissions --json`.\n3. Use shortcut calls such as `webstudio meta.index` and `webstudio insert-fragment \'<json>\' --dry-run` for individual MCP tool calls. Use the explicit equivalent `webstudio mcp single-op-call <tool> \'<json>\'` when you need to force the MCP path, or `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'` for bounded multi-call workflows. Use `webstudio mcp run .temp/mcp-calls.json` for large batches.\n4. Start discovery with `meta.index`, then call focused tools with concrete JSON, for example `webstudio mcp single-op-call meta.guide \'{"brief":"Create a design system page using every component"}\'`.\n\nDo not run `webstudio sync`, install an MCP server, change client configuration,\nor restart the app for this local CLI workflow.\n\nWhen the user explicitly wants persistent native MCP integration, run\n`webstudio connect claude`, `webstudio connect codex`, `webstudio connect\ncursor`, or `webstudio connect vscode`. This optional command changes client\nconfiguration, so follow its client-specific reload or restart instruction.\nUse `--print` to inspect the generated setup without changing configuration or\nrequiring project access. For Codex, `connect` registers and verifies the server\nthrough the Codex CLI. Before changing client configuration, `connect` verifies\nthat the saved project endpoint is reachable and its credential is accepted.\n\nStart MCP from the linked Webstudio project root. The lifecycle status line prints that absolute root; create local scripts, screenshots, and temporary artifacts under that root, for example `<project root>/.temp/script.mjs`. If the shell starts in a parent workspace, `cd` into the project root first or use absolute paths.\n\nWhen developing inside the Webstudio monorepo, start the local CLI exactly as `node packages/cli/local.js mcp` from the repo root. Do not use `pnpm exec webstudio`, `pnpm --filter webstudio exec webstudio`, or a global `webstudio`: they can resolve an older binary.\n\nWhile the server is running, stdout is reserved for MCP JSON-RPC messages. Do not print human text from the server process. The server advertises MCP `logging` capability and emits sparse `notifications/message` logs for ready state and tool lifecycle checkpoints such as `tool preview.start started`, `tool preview.start still running after 10000ms`, and `tool preview.start succeeded in 1234ms`; stderr also mirrors these sparse lifecycle fallback lines prefixed with `[webstudio mcp]`.\n\n## One-Shot Tool Calls\n\nUse the shortcut `webstudio <tool> \'<json>\'` when you are operating from a shell and need one MCP tool result. The explicit form `webstudio mcp single-op-call <tool> \'<json>\'` is equivalent and avoids writing temporary Node.js stdio client scripts.\n\nExamples:\n\n```sh\nwebstudio mcp single-op-call meta.index\nwebstudio mcp single-op-call meta.guide \'{"brief":"Create a design system page using every component"}\'\nwebstudio mcp single-op-call meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nwebstudio mcp single-op-call components.list \'{"source":"all"}\'\nwebstudio mcp single-op-call components.coverage-plan\nwebstudio mcp single-op-call components.search \'{"brief":"radix select"}\'\nwebstudio mcp single-op-call components.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'\nwebstudio mcp single-op-call templates.list\nwebstudio mcp single-op-call templates.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'\nwebstudio mcp single-op-call insert-fragment --input-file .temp/insert-fragment.json\n```\n\nShortcut equivalents:\n\n```sh\nwebstudio meta.index\nwebstudio meta.guide \'{"brief":"Create a design system page using every component"}\'\nwebstudio meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nwebstudio components.list \'{"source":"all"}\'\nwebstudio components.coverage-plan\nwebstudio components.search \'{"brief":"radix select"}\'\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'\nwebstudio templates.list\nwebstudio templates.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'\nwebstudio insert-fragment --input-file .temp/insert-fragment.json\n```\n\n### Tool name convention\n\nMCP tool names are opaque strings, not JavaScript property access. A dot separates a namespace from its tool name, and every segment uses lowercase kebab-case. For example, `components.coverage-insert-next` is the `coverage-insert-next` tool in the `components` namespace. Pass the complete name as one CLI argument: `webstudio components.coverage-insert-next`. Batch `mcp run` calls also accept the underscore form advertised by MCP protocol discovery, such as `components_coverage_insert_next`. Unknown names return near matches and direct you to `meta.index`.\n\n### Readable fragment inputs\n\nPrefer `--input-file` for JSX so JSON and shell quoting do not obscure the fragment. For example, save this as `.temp/insert-fragment.json`:\n\n```json\n{\n "parentInstanceId": "root-id",\n "fragment": "<section ws:style={css`padding: 32px; display: grid; gap: 16px;`}><h2>Northstar Product OS</h2><p>Reusable patterns for teams.</p></section>"\n}\n```\n\nThen run `webstudio insert-fragment --input-file .temp/insert-fragment.json`. Single quotes inside the JSX keep the JSON valid and readable without backslash-escaped attributes.\n\nWrite and review larger fragments as JSX before placing them in the `fragment` field. Common patterns:\n\n```tsx\n<section style={{ padding: 32, borderRadius: 16 }}>\n <h2>Operations Console</h2>\n <p>\n React-style object styles become editable Webstudio styles.\n </p>\n</section>\n\n<section ws:tokens={[token("accent", css`color: #0f766e;`)]}>\n <button\n onClick={new ActionValue(["event"], expression`console.log(event)`)}\n >\n Track launch\n </button>\n</section>\n\n<section>\n <Switch>\n <SwitchThumb />\n </Switch>\n</section>\n```\n\nRules:\n\n- Inside the Webstudio monorepo, replace `webstudio` in the examples above with `node packages/cli/local.js`, for example `node packages/cli/local.js meta.index`.\n- For a simple authored/styled section, run `meta.index`, then `meta.get-more-tools \'{"tools":["insert-fragment"]}\'`, then `insert-fragment`. Do not grep source files, dump full MCP resources, or write parser scripts first.\n- In `insert-fragment` JSX, use ``ws:style={css`...`}`` for Webstudio-native CSS, or use React-style object syntax such as `style={{ padding: 24 }}` when that is simpler. Both forms create editable Webstudio style data.\n- `css` templates accept declarations and `@media` rules. Do not put selectors or unsupported at-rules such as `@keyframes` inside them. Use direct animation component identifiers such as `<AnimateChildren>`; `animation` is accepted only as legacy input and is not a callable CSS keyframes helper.\n- Do not access host globals or dynamic code APIs in JSX fragments, including `process`, `globalThis`, `eval`, `Function`, or `constructor`.\n- Use Webstudio prop names such as `class` and `for`; do not use React aliases `className` or `htmlFor`.\n- Use Webstudio actions for event/action props, for example `onClick={new ActionValue(["event"], expression\\`console.log(event)\\`)}`. Do not pass JavaScript functions such as `onClick={() => ...}`.\n- Plain prop values must be JSON-compatible: `null`, strings, booleans, finite numbers, arrays, and plain objects. Do not pass `undefined`, `Symbol`, `BigInt`, `NaN`, `Infinity`, `Date`, `Map`, `Set`, class instances, or circular objects; omit the prop, use plain data, or use `expression`/`ActionValue` when the value is dynamic.\n- Template-backed components used in JSX must include required child/part components explicitly under the same parent structure as the template, for example `<Switch><SwitchThumb /></Switch>`. Use `insert-component` when you want one automatic registered component template.\n- The positional input is JSON and defaults to `{}`.\n- Use `--input-file` for large mutation payloads.\n- Use `--dry-run` with local-capable mutation tools when you need a patch plan without committing. The computed transaction is returned in `meta.session.transaction`, and `meta.session.version` is its base build version. Copying a `.webstudio` folder is not an isolated project clone; `.webstudio/config.json` still points to the same remote project, so non-dry-run mutations can commit to that project.\n- The command prints JSON to stdout for both success and failure. Success uses the same `structuredContent` shape MCP tools return: `{ "ok": true, "data": ..., "meta": ... }`. Failure prints `{ "ok": false, "error": { "code": "...", "message": "..." }, "meta": ... }` and exits nonzero.\n- The command writes sparse progress to stderr, including start, success/failure, elapsed time, and committed status when the tool returns session metadata.\n- Invalid argument types fail loudly with path-specific messages, for example `meta.guide input.brief must be a string when provided`.\n- Run one-shot shortcut or `mcp single-op-call` commands sequentially against the same linked `.webstudio` folder. If you receive `PROJECT_SESSION_BUSY`, another CLI/MCP process is updating the local session; wait a moment and retry sequentially.\n- To work with another previously linked project without changing the directory\'s default link, start MCP or a shell call with `--project <projectId>`, for example `webstudio mcp --project <projectId>` or `webstudio mcp single-op-call list-pages --project <projectId>`. Selected projects use isolated local session and checkpoint files.\n- If you are a delegated agent and your parent cannot see live stderr/stdout, do not run a long sequence of shortcut or `mcp single-op-call` commands silently and do not wrap many calls in a shell loop. Treat each parent-visible checkpoint as the unit of work. If the parent asks for status within 30 seconds, run exactly one `webstudio <tool>` or `webstudio mcp single-op-call` command, report that command/result, then wait before the next MCP command. For all-component design-system pages, checkpoint after discovery, checkpoint after page creation, call `components.coverage-insert-next` once before checkpointing again, then finish with the `presentation-pass` workflow phase. Coverage alone is not completion; organize examples into styled sections/cards.\n\n## MDX-backed Content Blocks\n\nUse a connected `.mdx` Asset when editors should change a Content Block body visually while the document remains stored as a file.\n\n### Connect a collection to its entry page\n\nWhen MDX files belong to a content collection, connect the collection to its dynamic entry page explicitly. Read the page ID after finding or creating the page, verify that its path has exactly one URL parameter, and store the ID at `x-webstudio.entryPageId` in `collection.json`. Do not assume Webstudio infers the page from its path, Assets query, or Content Block source. Preserve the rest of `collection.json`, and verify **Open on canvas** from both an entry asset menu and **Entry settings** before handoff. If no compatible page exists, leave the field unset and report that entry navigation still needs configuration.\n\n```json\n{\n "x-webstudio": {\n "template": "template.mdx",\n "entries": ["*.mdx"],\n "entryPageId": "<dynamic-entry-page-id>"\n }\n}\n```\n\n1. Create the `.mdx` file under `.webstudio/assets`, then upload it and keep the returned Asset ID.\n2. Inspect the Content Block and verify that it has exactly one direct Templates container. Every custom template referenced from MDX needs a unique top-level instance name. A missing or second Templates container blocks connected MDX materialization and publication.\n3. Connect the Asset with `connect-content-block-source`. Use a stable page-based `renderScope` for a direct occurrence.\n4. If the result returns `requiresConfirmation:true`, tell the user that connecting will replace the existing Body content. Repeat the same call with `confirmReplacement:true` only after approval.\n5. Edit the complete source with `edit-content-block-source`, or replace the complete frontmatter map with `update-content-block-frontmatter`.\n6. Inspect every returned diagnostic. Invalid MDX is preserved, not silently repaired.\n\n```sh\nwebstudio upload-asset \'{"asset":{"name":"article.mdx","type":"file","format":"mdx","meta":{}},"assetsDir":".webstudio/assets"}\'\nwebstudio connect-content-block-source \'{"blockInstanceId":"<contentBlockInstanceId>","renderScope":"page:/articles/example","source":{"type":"asset","assetId":"<mdxAssetId>"}}\'\nwebstudio inspect-content-block-source \'{"blockInstanceId":"<contentBlockInstanceId>","renderScope":"page:/articles/example"}\'\nwebstudio edit-content-block-source --input-file .temp/edit-content-block-source.json\nwebstudio reload-content-block-source \'{"blockInstanceId":"<contentBlockInstanceId>","renderScope":"page:/articles/example"}\'\nwebstudio migrate-content-block-template-references \'{"assetIds":["<mdxAssetId>"],"migration":{"type":"rename","from":"Old template name","to":"New template name"}}\'\n```\n\nPrefer Markdown for standard document content. Markdown nodes automatically use a unique matching semantic template when one exists and keep their normal semantic fallback otherwise. Use lowercase JSX such as `<section>` or `<svg>` for a standard HTML or SVG element with authored properties Markdown cannot express.\n\nReference a uniquely named top-level custom template with capitalized JSX such as `<PromotionCard tone="featured">Content</PromotionCard>`. The template\'s stable **Name** must be a valid JSX component identifier and is independent from its display label. A template name wins over a built-in MDX adapter. Other registered components are unavailable until added to this Content Block\'s Templates. Attributes accept quoted static values and bare booleans. Expressions such as `{false}` are unsupported, as are imports, spreads, functions, and executable JavaScript. Legacy `ws.element` and `ws:name` forms are read for compatibility but are not emitted or recommended. Component namespaces such as `$.*`, `radix.*`, and `animation.*` are unsupported; use the direct component identifier.\n\nWhen explicit JSX children match the designed template structure, their text and supported props overlay the cloned descendants so template styles stay intact. A mismatched child structure replaces the root defaults, and each authored child still resolves through a matching template when possible. An explicit empty pair such as `<PromotionCard></PromotionCard>` clears the defaults. A self-closing reference such as `<PromotionCard />` keeps them. Editing inherited default content writes it back as explicit JSX children. Template resolution is live: adding a missing semantic or named template later also updates existing MDX without rewriting it. Preserve unresolved template names and report their diagnostics.\n\nWhen a template is renamed or deleted, use `migrate-content-block-template-references` to update the affected MDX files. Its first call returns a plan with changed-file, update, omission, and diagnostic counts. Report the plan and repeat the exact request with its `confirmationToken` only after approval. Renames and removals update named JSX and legacy `ws:name` references, including names such as `Image` and `CodeText` when they identify templates. Rename targets must be valid PascalCase JSX identifiers. Removing a paired reference unwraps and preserves its explicit authored children; removing a self-closing reference removes that node because it has no authored children. Invalid files remain unchanged and are reported in diagnostics.\n\nUse the dedicated connect, switch, inspect, edit, update-frontmatter, reload, and disconnect operations instead of creating or deleting the Content Block\'s `src` with generic prop tools. An expression-bound source inside a Collection also needs the occurrence\'s scoped values and a distinct stable `renderScope`. For example, use `source:{"type":"expression","value":"post.assetId"}` with `variables:{"post":{"assetId":"<mdxAssetId>"}}`.\n\n`renderScope` is any non-empty stable identity for one rendered occurrence, such as `page:/articles/example`. It does not load that page, its route parameters, or its resource results. To connect a result-one Assets resource, run `preview-asset-query` with concrete values, keep the saved source expression such as `post.data.id`, and supply the previewed item for this occurrence with `variables:{"post":{"data":{"id":"<mdxAssetId>"}}}`. The operation validates that concrete Asset while persisting the dynamic expression.\n\nA source edit replaces the complete MDX document. A frontmatter update replaces the complete frontmatter property map. Inspect the current source first and preserve everything the user did not ask to change. Store frontmatter images as exact `$ref` objects. Bind editable Image sources to their resolved `.src` with explicit `binding.mode:"readwrite"`; omitting the mode leaves the image visible but not replaceable in Content mode. Bind alt properties to their Asset `.description` with `mode:"read"`. Use `update-content-block-frontmatter` for MCP frontmatter edits. MDX-rendered elements are not persistent instance targets for generic `bind-props` or `update-text` calls. Preserve existing `mode:"readwrite"` bindings when encountered; they are valid only for exact direct paths into the connected document\'s frontmatter. Direct bindings through a loaded Markdown or MDX `$ref` ending in `#frontmatter` save to the referenced file, with its write permissions enforced. Shared-record edits affect every document using that record. Computed expressions and JSON/body references remain read-only. Image replacement is supported: a direct Image source binding with `mode:"readwrite"` lets the picker replace the frontmatter `$ref`, while shared Asset metadata is edited in Asset settings.\n\nFor an editable MDX article, use the resource to select the Content Block\'s source Asset (`post.data.id`), then bind article fields inside the block to its document parameter (`document.frontmatter.title`), not query-result properties. Inspect the actual document variable name first. Create writable bindings explicitly: `update-text` uses `expressionBindingMode:"readwrite"`; `bind-props` uses `binding.mode:"readwrite"`. These generic tools target persistent designed instances, such as a header outside the MDX body, not MDX-generated instances. The MDX body is editable through its source mapping; merely placing static or query-bound content inside the block does not make it editable. Use exact direct paths; property access is already safe. Direct bindings through a loaded Markdown or MDX `$ref` ending in `#frontmatter` save to the referenced file, with its write permissions enforced. Shared-record edits affect every document using that record. Computed expressions and JSON/body references remain read-only. Image replacement is supported: a direct Image source binding with `mode:"readwrite"` lets the picker replace the frontmatter `$ref`, while shared Asset metadata is edited in Asset settings. Follow the complete article editability checklist below before handoff.\n\n### Content Block completion checklist\n\nFor every Content Block creation, migration, or repair—with or without MDX—treat Content-mode editability as a delivery requirement for all content the user expects editors to change. Before implementation, inventory each value, its intended UI control, and its write destination. Choose controls by meaning: date/calendar controls for dates, image pickers for image replacement, and direct text or number controls for reading time. Keep fixed punctuation and units outside a directly bound value element; never mix literal and expression children there. Preserve the existing stored type and wording: if `readTime` already stores `4 min read`, bind that whole string directly instead of assuming it stores only a number. Distinguish protected layout/templates from editable content explicitly. Test each field through Content mode, check the saved project data or source file, reload, restore test values, and record passed/failed/not tested. Testing one heading, inspecting bindings, or successfully writing through MCP does not cover the remaining fields. Report missing controls and unsupported writes as unfinished requirements, not successful delivery.\n\n### Complete article editability is required\n\nWhen the user asks for the whole article to be editable in Content mode, this includes every article-owned value: title, excerpt, author details and links, dates, reading time, categories, hero and inline image sources, alternative text, captions, body text, links, and custom-component content and media. Exclude only designer-owned layout, styling, templates, and shared navigation/footer unless the user asks otherwise. A correct preview, a successful MCP write, or an editable body does not prove this requirement is met.\n\nBefore handoff:\n\n1. Inventory every article field, its UI control, binding, and source file/field. Header fields outside **MDX content** are still part of the article and must be editable through the Content Block\'s document bindings.\n2. In Content mode, edit every inventoried field through the UI, including choosing a different hero image and editing link destinations and custom-component props. Do not substitute a raw MDX/YAML edit or an MCP write for this check.\n3. Read the saved source after each edit. Confirm the intended MDX body/frontmatter or referenced author file changed, unrelated content stayed intact, and the edit survives reload. Restore test values and verify restoration.\n4. Mark each field passed, failed, or not tested. Do not claim the article is fully editable while any required field is failed or untested.\n\n**Image replacement is not shared metadata editing.** For frontmatter such as `featureImage: { $ref: "./images/hero.png" }`, bind the Image source directly to `document.frontmatter.featureImage.src` with `binding.mode:"readwrite"` using `bind-props`. In Content mode, **Choose source** replaces the article’s `featureImage.$ref`; it does not overwrite the shared Asset’s resolved `.src`. The resolved URL field stays read-only. Verify the picker, saved reference, and reload—do not infer support from the displayed image alone. Bind alternative text to `.description` to use the shared Asset description; edit it through **Choose source → asset actions → Settings → Description**, which affects every use of that Asset. Do not treat a missing write mode as a platform limitation, and do not defer image replacement over a separate shared-description question.\n\nKeep intended editable values separate from display formatting. For reading time, use three inline sibling text elements: static `— `, one value element bound directly to `document.frontmatter.readingTime` with `expressionBindingMode:"readwrite"`, and static ` min read`. The value element must contain only that binding, not mixed literal and expression children. Preserve whitespace and the stored field type. Do not concatenate labels or units, use template literals, or add fallbacks/formatting calls to an editable binding. Prefer component formatting controls with a directly bound value. Explain unsupported cases and ask before making an intended editable field read-only; do not trade away editability just to match the display.\n\nIf an edit in a long-lived MCP session reports a revision conflict after another client saved the Asset, call `reload-content-block-source`, inspect the latest source, reapply the requested change, and retry. One-shot CLI calls refresh before each operation and normally cannot reproduce a stale session. Never overwrite the newer revision blindly. Use `disconnect-content-block-source` to remove the connection while leaving the Asset unchanged.\n\n## Reporting CLI/MCP Issues\n\nIf a CLI/MCP tool gives a confusing error, crashes, hangs, produces invalid output, requires an undocumented workaround, or makes you inspect source code to understand normal usage, ask the user to report it in the Webstudio Discord `#help` channel: https://wstd.us/community.\n\nGive the user a complete copy-paste report. Include only non-secret values: never include auth tokens, private URLs, cookies, API keys, passwords, or proprietary project data. Redact them as `<redacted>`.\n\nCopy-paste template:\n\n````md\nWebstudio CLI/MCP issue report\n\nWhat I was trying to do:\n<short user goal, for example "Create a resource from an external API and render it in a collection">\n\nWhat I expected:\n<what should have happened>\n\nWhat happened instead:\n<exact error, confusing behavior, hang, missing docs, or workaround required>\n\nCommand/tool used:\n\n```sh\n<exact command or MCP tool call, with tokens/secrets redacted>\n```\n\nStructured output / error:\n\n```json\n<stdout JSON or MCP structuredContent, if available, with secrets redacted>\n```\n\nStderr / lifecycle logs:\n\n```txt\n<stderr lines, timings, checkpoint messages, or stack trace, with secrets redacted>\n```\n\nEnvironment:\n\n- CLI command path: <webstudio / node packages/cli/local.js / other>\n- Webstudio CLI version: <from command output if known>\n- OS: <macOS / Windows / Linux / unknown>\n- Node version: <node -v if known>\n- Project/session state: <linked project, local .webstudio session, preview, MCP server, or unknown>\n\nWorkaround tried:\n<what the agent/user tried next, and whether it worked>\n\nWhy this should be improved:\n<one sentence: better error message, docs, schema, tool behavior, etc.>\n````\n\n## Shared-Session Shell Runs\n\nUse `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'` when you are operating from a shell and need several MCP tool calls to share one CLI session without hand-writing JSON-RPC. For large batches, pass a normal JSON file path such as `.temp/mcp-calls.json`. Do not use shell process substitution like `<(...)`; use inline JSON or a real file.\n\nUse `mcp run` for long-lived tools such as `preview.start`. A one-shot `mcp single-op-call preview.start` cannot keep ownership of a preview server for a later screenshot or stop call. Put `preview.start`, `screenshot`, and `preview.stop` in one shared `mcp run` process, or use a real long-running MCP client.\n\nInput shape:\n\n```json\n{\n "calls": [\n { "tool": "meta.index" },\n { "tool": "components.find", "input": { "brief": "radix select" } }\n ]\n}\n```\n\nRules:\n\n- The command prints JSON to stdout for both success and failure. It stops at the first failed call and prints partial results in `{ "ok": false, "error": ..., "data": { "completedCalls": ..., "results": [...] }, "meta": ... }`, then exits nonzero.\n- If a call returns `checkpoint.required`, read-only discovery and inspection remain available, but mutations and state-changing session tools return `CHECKPOINT_REQUIRED`. Stop and report the checkpoint to the parent/user. Only after the parent/user continues, call `checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}` before continuing mutations.\n- For `mcp single-op-call`, checkpoint requirements persist across later one-shot CLI processes until you call `checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}`.\n- Use this instead of manually sending JSON-RPC frames to `webstudio mcp` from a shell.\n\n### Cross-project batches\n\nAdd `projects` to the same `mcp run` manifest to run focused reads, audits, or dry runs across independently linked project roots:\n\n```json\n{\n "concurrency": 2,\n "calls": [\n { "tool": "status" },\n { "tool": "audit", "input": {} },\n {\n "tool": "update-project-settings",\n "input": { "meta": { "siteName": "Reviewed" } },\n "dryRun": true\n }\n ],\n "projects": [\n { "id": "site-a", "root": "../site-a" },\n { "id": "site-b", "root": "../site-b" }\n ]\n}\n```\n\nProject roots and an optional `progressFile` are resolved relative to the manifest file. Each project may provide its own `calls` instead of using the top-level calls. Each root must already be linked with its own `.webstudio/config.json`; the runner creates an independently authenticated ProjectSession and uses root-scoped session, audit, preview-data, and checkpoint paths without changing the process working directory.\n\nConcurrency defaults to 2, is capped at 16, and can be set in the manifest or overridden with `--concurrency`. A failure is reported for that project while other projects continue. Progress is saved after every successful call; rerunning with the default `--resume` skips completed projects and starts failed projects after their last confirmed successful call. Reads and dry runs may be retried. A committed mutation interrupted after dispatch is marked `AMBIGUOUS_MUTATION_RESULT` and is never replayed automatically; inspect that project before deciding how to continue. Use `--no-resume` only to intentionally start the complete manifest over.\n\nCommitted mutation tools are rejected in a projects batch unless the command includes `--approve-mutations`. Review the complete manifest before granting approval. `--dry-run` applies to every call and does not require mutation approval. The final stdout object is compact: project counts, one status/error record per project, elapsed time, and the progress-file path rather than every tool result.\n\n## Discovery\n\nUse MCP itself after startup, or call the same tools with `webstudio mcp single-op-call`:\n\n- `tools/list`: machine-readable available tools\n- `resources/list`: available overview and full JSON resources\n- `meta.index`: concise capability catalog\n- `meta.guide`: workflow for a user goal; call with a string brief such as `{"brief":"Create a pricing page"}`\n- `meta.get-more-tools`: detailed params, examples, namespaces, and local/server behavior; prefer exact names such as `{"tools":["insert-fragment"]}` when you know them\n- `components.list`: compact registry metadata for visible components and templates; use a focused get tool for complete details\n- `components.summary`: component counts by default; use `{"detail":"components","limit":20}` for paginated entries\n- `components.coverage-plan`: compact paged plan for design-system coverage tasks that need every component; default returns counts plus the first root page, use `{"detail":"roots"}`, `{"detail":"parts"}`, or `{"detail":"full"}` for more\n- `components.coverage-status`: page-specific covered/missing component report with `missingRoots` and `missingParts`\n- `components.search`: focused component/template search by id, namespace, label, category, or content model\n- `components.find`: compatibility alias for focused component search\n- `components.get`: full metadata for one component id\n- `templates.list`: compact metadata for template-backed insertions only\n- `templates.get`: full registry item and payload metadata for one template\n- `search-project`: find a known value or id with `webstudio search-project \'{"query":"pricing"}\'` or MCP `search-project {"query":"pricing"}`; use focused list/get tools when the target structure is unknown\n\n`meta.guide` returns structured `routing` with the selected workflow and any broad context bundle it recommends. Authentication and design context bundles appear only when their specialized workflow is selected. Set `authoredFragment` when using an authored fragment and `reuseDesignSystem` when its recommendations should retain design-system discovery tools.\n\nSet `taskScope` and `workflow` explicitly; `meta.guide` does not infer them or the authored-fragment flags from the brief. Specialized workflows are `markdown-blog`, `json-ld`, `collection`, `expression`, `authenticated-page`, `font-assets`, `design-input`, and `craft`; otherwise use `general`. For work that must not change project or local state, pass `{"brief":"Inventory custom code","taskScope":"read-only-audit","workflow":"general"}`. The resulting `read-only-discovery` workflow excludes mutation and side-effecting session tools and uses focused search, list, get, inspect, and snapshot tools.\n\n`search-project` follows normal ProjectSession synchronization, then searches in the CLI process. Namespace filters limit values matched; related namespaces may still supply route and reference context, and synchronization is unchanged. Only paged matches enter model context. Recognized credential fields and asset binary or document bodies are excluded.\n\nComponent and template registry items use a shadcn-compatible top-level shape plus Webstudio-specific superset metadata in `meta`. Use `meta.runtime` for component ids, props, states, content model, and source identity; `meta.authoring` for composition and accessibility guidance; and `meta.builder` for template insertion details and expected project-data namespaces. These items are for Builder/MCP discovery and are not a published shadcn install registry yet.\n\nPrefer the focused `components.*` tools over dumping `webstudio://project/components`. Do not write local scripts to parse full MCP discovery JSON for common component lookup.\nFor “use every component” or design-system pages, start with compact `components.coverage-plan`, checkpoint, then page through roots/parts instead of dumping the full catalog.\n\n## Consumer Capabilities\n\nMCP lets agents work on one configured Webstudio project at a time. In consumer\nterms, agents can:\n\n- Check which project they are connected to.\n- Check what the share link is allowed to do.\n- Inspect project metadata and the latest editable build.\n- Read selected project data for audits and repair.\n- Search all Builder namespaces for a known value or id without putting complete namespace data in model context.\n- Apply precise project changes against a known version.\n- List, inspect, create, update, delete, duplicate, copy, and reorder pages.\n- Set the home page.\n- Preserve old page paths for redirects or history.\n- Read and update page titles, descriptions, metadata, auth settings, and SEO fields.\n- List, create, update, duplicate, move, and delete page folders.\n- List, create, update, delete, duplicate, reorder, and reuse page templates.\n- Create pages from reusable templates.\n- Read and update project site settings.\n- Read and update marketplace product metadata.\n- List, create, update, delete, and replace redirects.\n- List, create, update, and delete responsive breakpoints.\n- List and inspect page elements.\n- Insert registered components.\n- Insert styled JSX fragments.\n- Move, reparent, clone, duplicate, wrap, unwrap, convert, rename, retag, and delete elements.\n- Fill grid cells.\n- List and update text children.\n- Update plain text and expression text.\n- Update structured rich text.\n- Add, update, delete, and bind element props.\n- Bind props to expressions, resources, actions, and runtime system values.\n- Read, add, update, delete, and replace local styles.\n- Update selected style-source styles.\n- List, create, update, attach, detach, extract, duplicate, rename, lock, unlock, reorder, clear, and delete design tokens and style sources.\n- List, define, rename, delete, and rewrite CSS variables.\n- List, create, update, and delete static data variables.\n- Create string, number, boolean, and JSON variables. Arrays use JSON.\n- Delete unused data variables.\n- List, create, update, upsert, bind, and delete resources.\n- Create HTTP resources.\n- Create GraphQL resources.\n- Create system resources.\n- Use built-in system resources for sitemap, current date, and assets.\n- List and inspect complete asset metadata; upload, download, update, move, duplicate, find usage for, replace, and delete assets.\n- List, create, rename, move, recursively duplicate, and recursively delete nested asset folders.\n- Publish to staging or production.\n- Publish to selected domains.\n- List publish builds.\n- Check publish job status.\n- Unpublish staging or production deployments.\n- List, create, update, delete, and verify custom domains.\n- Start and stop preview.\n- Capture screenshots of generated pages.\n- Compare screenshots against baselines.\n- Install OCR support for richer visual checks.\n\nUseful resources:\n\n- `webstudio://project/status`: compact current ProjectSession status\n- `webstudio://project/tools-overview`: small operation overview by capability area\n- `webstudio://project/components-overview`: small component overview with ids, labels, namespaces, and categories\n- `webstudio://project/tools`: full operation catalog; read only when focused metadata is insufficient\n- `webstudio://project/components`: full component catalog with props, states, and content model composition constraints; read only when `components.summary`, `components.find`, and `components.get` are insufficient\n- `webstudio://project/guide`: concise discovery guide\n- `webstudio://project/expressions`: expression syntax, scope, supported methods, bindings, Collection iteration context, and verification\n- `webstudio://project/accessibility-review`: evidence-based LLM accessibility-review workflow using project checks, preview, and screenshots\n\n## MCP SDK Client Imports\n\nWhen writing a local Node.js MCP client script, use the official MCP SDK package and these exact ESM imports:\n\nInside the Webstudio monorepo this package is available at the repo root. In another project, install it first with `pnpm add -D @modelcontextprotocol/sdk`.\n\n```js\nimport { Client } from "@modelcontextprotocol/sdk/client/index.js";\nimport { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";\nimport { LoggingMessageNotificationSchema } from "@modelcontextprotocol/sdk/types.js";\n```\n\nMinimal stdio client for the local Webstudio CLI:\n\n```js\nconst client = new Client({ name: "webstudio-agent", version: "1.0.0" });\n\nclient.setNotificationHandler(\n LoggingMessageNotificationSchema,\n (notification) => {\n console.error(`[mcp] ${notification.params.data}`);\n }\n);\n\nconst transport = new StdioClientTransport({\n command: "node",\n args: ["packages/cli/local.js", "mcp"],\n cwd: process.cwd(),\n stderr: "inherit",\n});\n\nawait client.connect(transport);\n\nconst index = await client.callTool({\n name: "meta.index",\n arguments: {},\n});\nconsole.log(JSON.stringify(index.structuredContent, null, 2));\n\nawait client.close();\n```\n\nUse `node packages/cli/local.js mcp` from the Webstudio monorepo root for local development, or `webstudio mcp` from a linked project where the CLI is installed. Keep stdout for JSON-RPC/structured results and surface MCP logging notifications or stderr lifecycle lines as progress.\n\n## Core Rules\n\n- stdout is reserved for MCP JSON-RPC while the server is running.\n- Operate on the configured project only.\n- Read ids before writing.\n- Prefer semantic tools over `apply-patch`.\n- Use `status` and `refresh` when cached namespaces may be stale. Pass `status {"verbose":true}` only when debugging full namespace arrays, freshness, compatibility, or diagnostic details.\n- Read `meta.session.commitStatus` before interpreting durability. Read-only results report `not-applicable` and retain `committed:false` for compatibility; dry-run plans report `planned`; failed mutations report `failed`; no-op mutations report `unchanged`; durable mutations report `committed` with `meta.session.committed:true`.\n- Never run visual verification automatically. Ask first unless the user explicitly requested screenshots, visual verification, or a rendered audit; if they do not opt in, use focused non-visual assertions.\n\n## Vision Verification Loop\n\nVision-capable AI can use MCP to see what it is building:\n\n{{mcpVisionVerificationLoopMarkdown}}\n\nGenerated app setup:\n\n{{mcpGeneratedAppDependencyNotes}}\n\n## MCP argument examples\n\nExamples below show meaningful argument combinations. Tool schemas are the\nsource of truth. For tools with no required arguments, pass `{}`.\n\n{{mcpArgumentExampleIndex}}\n\n{{contentEngineReferenceMarkdown}}\n\n## Screenshot Verification\n\n{{screenshotVerificationSummary}}\n',
|
|
344544
344680
|
"mcp-startup-epilogue": 'If you are inside the Webstudio monorepo, use the local CLI exactly as `node packages/cli/local.js ...` as your first command path. Do not use `packages/cli/bin.js` for local source-tree work; it is the packaged build entry and may use stale built output.\n\nPlain `webstudio mcp` starts the stdio MCP server for real MCP clients. Do not manually type or pipe raw JSON-RPC frames into it from an interactive shell or PTY. From a shell, use shortcut calls such as `webstudio meta.index` and `webstudio insert-fragment \'<json>\' --dry-run` for one bounded tool call. The explicit equivalent is `webstudio mcp single-op-call <tool> \'<json>\'`. Use `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'` for small multi-call batches in one shared CLI session. For large batches, pass a normal JSON file path such as `.temp/mcp-calls.json`. Do not use shell process substitution like `<(...)`; use inline JSON or a real file.\n\nPut each local CLI call in its own shell command; do not chain helper commands with `&&`, `;`, command substitution, or shell wrappers around the CLI when reporting a CLI step. Do not use `pnpm exec webstudio`, `pnpm --filter webstudio exec webstudio`, or a global `webstudio`; those can resolve an older binary. For example:\n\n```sh\nnode packages/cli/local.js mcp single-op-call meta.index\nnode packages/cli/local.js mcp single-op-call meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nnode packages/cli/local.js mcp single-op-call insert-fragment --input-file .temp/insert-fragment.json --dry-run\n```\n\nSave the input as `.temp/insert-fragment.json`; use single quotes for JSX attributes so the JSON needs no backslash escaping:\n\n```json\n{\n "parentInstanceId": "parent-id",\n "fragment": "<section ws:style={css`padding: 32px; display: grid; gap: 12px;`}><h2>Launch Kit</h2><p>A focused section created with Webstudio JSX.</p><button>Get started</button></section>"\n}\n```\n\nThe shorter local shortcut form is preferred for simple shell steps:\n\n```sh\nnode packages/cli/local.js meta.index\nnode packages/cli/local.js meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nnode packages/cli/local.js insert-fragment --input-file .temp/insert-fragment.json --dry-run\n```\n\nFor simple authored/styled sections, run the three commands above. Do not grep source files, dump full MCP resources, or write parser scripts first. Use `list-pages`, `get-page-by-path`, or `list-instances` only when you still need the target `parentInstanceId`.\n\nWhen building for a Content-mode editor, use a Content Block (`ws:block`) around every region that editor must change. Content-mode text and supported props are editable only in Content Block descendants; content outside is read-only. Keep reusable insertable source options in exactly one direct `ws:block-template` child. A missing or second template container makes the block invalid. Templates themselves are protected; an editor\'s inserted copy becomes editable Content Block body content. Verify this structure before handoff.\n\nFor an MDX-backed Content Block, use the dedicated content-source tools instead of generic prop mutations: connect, switch, inspect, edit, update frontmatter, reload, or disconnect the source. Confirm before replacing existing Body content. Prefer Markdown for standard document content; it automatically uses unique matching semantic templates. Use lowercase JSX such as `<section>` or `<svg>` for HTML or SVG that Markdown cannot express. Capitalized JSX resolves a unique stable template **Name** or a built-in MDX adapter. Add custom components to this Content Block\'s Templates before referencing them in MDX. The display label is independent. JSX attributes accept quoted values and bare booleans; expressions such as `{false}` are unsupported. Legacy `ws.element` and `ws:name` forms are compatibility input only. Component namespaces such as `$.*`, `radix.*`, and `animation.*` are unsupported; use direct component identifiers. Matching explicit children overlay designed descendants and keep template styles. A mismatched child structure replaces defaults, an explicit empty pair clears defaults, and a self-closing reference keeps them. Editing inherited default content writes it back as explicit JSX children. Template resolution is live when matching templates are added later. Preserve invalid source and unresolved names, inspect all diagnostics, and reload before retrying a revision conflict. A frontmatter update replaces the complete property map; preserve fields the user did not ask to remove. Store frontmatter images as exact `$ref` objects; bind editable Image sources to `.src` with explicit `binding.mode:"readwrite"` and alt properties to the Asset `.description` with `mode:"read"`. Omitting the source mode leaves the image visible but not replaceable in Content mode. Only exact direct frontmatter bindings may use `mode:"readwrite"`. After a template rename or deletion, use `migrate-content-block-template-references` to preview and confirm custom-template JSX and legacy changes across selected MDX files. A confirmed removal unwraps paired references and preserves their authored children; a self-closing reference disappears because it has none.\n\nFor an editable MDX article, use the resource to select the Content Block\'s source Asset (`post.data.id`), then bind article fields inside the block to its document parameter (`document.frontmatter.title`), not query-result properties. Inspect the actual document variable name first. Create writable bindings explicitly: `update-text` uses `expressionBindingMode:"readwrite"`; `bind-props` uses `binding.mode:"readwrite"`. These generic tools target persistent designed instances, such as a header outside the MDX body, not MDX-generated instances. The MDX body is editable through its source mapping; merely placing static or query-bound content inside the block does not make it editable. Use exact direct paths; property access is already safe. Direct bindings through a loaded Markdown or MDX `$ref` ending in `#frontmatter` save to the referenced file, with its write permissions enforced. Shared-record edits affect every document using that record. Computed expressions and JSON/body references remain read-only. Image replacement is supported: a direct Image source binding with `mode:"readwrite"` lets the picker replace the frontmatter `$ref`, while shared Asset metadata is edited in Asset settings. Follow the complete article editability checklist below before handoff.\n\n### Content Block completion checklist\n\nFor every Content Block creation, migration, or repair—with or without MDX—treat Content-mode editability as a delivery requirement for all content the user expects editors to change. Before implementation, inventory each value, its intended UI control, and its write destination. Choose controls by meaning: date/calendar controls for dates, image pickers for image replacement, and direct text or number controls for reading time. Keep fixed punctuation and units outside a directly bound value element; never mix literal and expression children there. Preserve the existing stored type and wording: if `readTime` already stores `4 min read`, bind that whole string directly instead of assuming it stores only a number. Distinguish protected layout/templates from editable content explicitly. Test each field through Content mode, check the saved project data or source file, reload, restore test values, and record passed/failed/not tested. Testing one heading, inspecting bindings, or successfully writing through MCP does not cover the remaining fields. Report missing controls and unsupported writes as unfinished requirements, not successful delivery.\n\n### Complete article editability is required\n\nWhen the user asks for the whole article to be editable in Content mode, this includes every article-owned value: title, excerpt, author details and links, dates, reading time, categories, hero and inline image sources, alternative text, captions, body text, links, and custom-component content and media. Exclude only designer-owned layout, styling, templates, and shared navigation/footer unless the user asks otherwise. A correct preview, a successful MCP write, or an editable body does not prove this requirement is met.\n\nBefore handoff:\n\n1. Inventory every article field, its UI control, binding, and source file/field. Header fields outside **MDX content** are still part of the article and must be editable through the Content Block\'s document bindings.\n2. In Content mode, edit every inventoried field through the UI, including choosing a different hero image and editing link destinations and custom-component props. Do not substitute a raw MDX/YAML edit or an MCP write for this check.\n3. Read the saved source after each edit. Confirm the intended MDX body/frontmatter or referenced author file changed, unrelated content stayed intact, and the edit survives reload. Restore test values and verify restoration.\n4. Mark each field passed, failed, or not tested. Do not claim the article is fully editable while any required field is failed or untested.\n\n**Image replacement is not shared metadata editing.** For frontmatter such as `featureImage: { $ref: "./images/hero.png" }`, bind the Image source directly to `document.frontmatter.featureImage.src` with `binding.mode:"readwrite"` using `bind-props`. In Content mode, **Choose source** replaces the article’s `featureImage.$ref`; it does not overwrite the shared Asset’s resolved `.src`. The resolved URL field stays read-only. Verify the picker, saved reference, and reload—do not infer support from the displayed image alone. Bind alternative text to `.description` to use the shared Asset description; edit it through **Choose source → asset actions → Settings → Description**, which affects every use of that Asset. Do not treat a missing write mode as a platform limitation, and do not defer image replacement over a separate shared-description question.\n\nKeep intended editable values separate from display formatting. For reading time, use three inline sibling text elements: static `— `, one value element bound directly to `document.frontmatter.readingTime` with `expressionBindingMode:"readwrite"`, and static ` min read`. The value element must contain only that binding, not mixed literal and expression children. Preserve whitespace and the stored field type. Do not concatenate labels or units, use template literals, or add fallbacks/formatting calls to an editable binding. Prefer component formatting controls with a directly bound value. Explain unsupported cases and ask before making an intended editable field read-only; do not trade away editability just to match the display.\n\nRun it from the linked Webstudio project root. The startup status line prints that absolute root; use it for local files such as `<project root>/.temp/script.mjs`, screenshots, and generated artifacts.\n\nFor experiments, pass `--dry-run` to local-capable mutation calls. Read the computed transaction from `meta.session.transaction` and its base build version from `meta.session.version`. Copying a `.webstudio` folder is not an isolated project clone; `.webstudio/config.json` still points to the same remote project, so non-dry-run mutations can commit to that project.\n\nStartup marks cached ProjectSession data stale so MCP tools read the current Builder dev build.\n\nAfter startup, use focused discovery tools with concrete JSON. For delegated design-system or “use every component” tasks, start with exactly one command: `webstudio workflow.next \'{"goal":"design-system-page"}\'`; report the returned checkpoint to the parent/user and stop until continued. For other tasks, read `meta.index` first. From a shell, `webstudio mcp list-tools` is a concise alias for the initial tool catalog. Then call shortcuts such as `webstudio meta.guide \'{"brief":"Create a design system page using every component"}\'`, `webstudio workflow.next \'{"goal":"design-system-page"}\'`, `webstudio meta.get-more-tools \'{"tools":["insert-fragment"]}\'`, `webstudio components.list \'{"source":"all"}\'`, `webstudio components.summary`, `webstudio components.search \'{"brief":"radix select"}\'`, `webstudio components.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'`, `webstudio templates.list`, and `webstudio templates.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'`. Use `webstudio mcp single-op-call` when you need the explicit MCP form. Prefer `insert-fragment` for authored/styled sections; use `insert-component` only when inserting one automatic component template. In JSX, use `ws:style={css\\`...\\`}`for styles,`class`/`for`instead of`className`/`htmlFor`, `ActionValue`for actions instead of JavaScript functions such as`onClick={() => ...}`, only JSON-compatible plain prop values, and no host globals or dynamic code APIs. For bounded multi-step shell work, run inline JSON like `webstudio mcp run \'[{"tool":"meta.index"},{"tool":"components.search","input":{"brief":"button"}}]\'`; this reuses one CLI session without raw JSON-RPC. For larger batches, write `{ "calls": [{ "tool": "..." }] }`to`.temp/mcp-calls.json`and run`webstudio mcp run .temp/mcp-calls.json`. If any call returns `checkpoint.required`, `mcp run`stops immediately before later calls; report the checkpoint and call`checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}`before continuing. For design-system or “use every component” tasks, call`workflow.next {"goal":"design-system-page"}`for the next bounded phase, report its checkpoint, wait until the parent/user continues, call`checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}`, then run only that phase. Start coverage phases with compact`webstudio components.coverage-plan`, checkpoint, then request component coverage details with `components.coverage-plan {"detail":"roots"}`or`components.coverage-plan {"detail":"parts"}`. Do not pass `detail`to`list-pages`; use `list-pages {}`or`get-page-by-path`for page lookup. Read MCP overview resources`webstudio://project/guide`, `webstudio://project/tools-overview`, and `webstudio://project/components-overview`when useful. Read full resources`webstudio://project/tools`and`webstudio://project/components` only when focused tools are insufficient; do not dump or parse full discovery JSON for common lookup.\n\nAfter startup, MCP clients discover capabilities with `tools/list`, `resources/list`, `meta.index`, `meta.guide`, and `meta.get-more-tools`.\n\nstdout is reserved for MCP JSON-RPC messages while the server is running.\n\nThe server advertises MCP `logging` capability and sends sparse `notifications/message` logs for ready state and tool lifecycle checkpoints such as `tool preview.start started`, `tool preview.start still running after 10000ms`, and `tool preview.start succeeded in 1234ms`. It also writes the same sparse lifecycle status lines to stderr, prefixed with `[webstudio mcp]`, including a final ready line. Treat the process as healthy and long-running after either ready signal; surface tool lifecycle logs as progress checkpoints and interact through MCP JSON-RPC on stdin/stdout.\n\n`webstudio mcp single-op-call` also writes sparse lifecycle lines to stderr for each one-shot call. Use those stderr lines as progress checkpoints and parse stdout as JSON on both success and failure. Failed one-shot calls print `{ "ok": false, "error": { "code": "...", "message": "..." }, "meta": ... }` to stdout and exit nonzero. If a one-shot call returns checkpoint.required, read-only discovery remains available, while later mutations fail with `CHECKPOINT_REQUIRED` until you report the checkpoint and call `webstudio mcp single-op-call checkpoint.ack \'{"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}\'`.\n\n`webstudio mcp run` also writes sparse lifecycle lines to stderr and prints one JSON result on success or failure. It stops at the first failed call or checkpoint requirement, including `CHECKPOINT_REQUIRED`, and preserves partial results so delegated agents can report exactly where they stopped.\n\nWhen `report-issue` is available, use it when the user explicitly asks to report a problem. Also use it automatically after a Webstudio-controlled tool crash, hang, malformed result, misleading success, repeated failure of the same documented operation, failed documented recovery, or required undocumented workaround. Do not automatically report expected confirmation/checkpoint flow, a clearly explained invalid input, missing permissions, `PROJECT_SESSION_BUSY`, a recovered version conflict, rate limiting, cancellation, or an offline environment. Write a complete anonymous report with the generalized user story, attempted workflow, expected behavior, actual result, recovery attempts, impact, technical context, acceptance criteria, exact model identifier, and reasoning effort. Never include names, usernames, emails, phone numbers, organizations, project or resource ids, domains, URLs, IP addresses, local paths, credentials, tokens, customer content, exact unique values, or raw contextual tool data.\n\nIf you are running as a delegated or non-streaming agent whose parent cannot see live stderr/stdout, do not batch many MCP calls silently and do not run long shell loops of shortcut or `webstudio mcp single-op-call` commands. Treat each parent-visible checkpoint as the unit of work. If the parent asks for status within 30 seconds, run exactly one shortcut command such as `webstudio meta.index` or one explicit `webstudio mcp single-op-call` command, return a concise checkpoint with that command/result, and wait for the parent to continue. Do not take a broad task such as creating a full design-system page as one execution unit. Call `workflow.next {"goal":"design-system-page"}`, report the returned phase/checkpoint, wait until the parent/user continues, call `checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}`, complete exactly that bounded phase, and return: discovery, page creation, one dry-run JSX section, one committed JSX section, one `components.coverage-insert-next` call, or one presentation pass. Phase commands do not include nextPhase in their own output. After the parent continues, acknowledge the previous checkpoint first, then call `workflow.next` with the next phase. For all-component design-system pages, checkpoint after workflow planning, discovery, page creation, call `components.coverage-insert-next` once before checkpointing again, then finish with the `presentation-pass` workflow phase.\n',
|
|
344545
344681
|
"mcp-vision": '# MCP Vision Verification\n\n## Generated App Dependency Notes\n\n- `preview.start` and `webstudio preview` install generated app dependencies under `.webstudio/preview` and reuse them across regenerations.\n- Session previews download missing project assets into `.webstudio/assets`. If `PREVIEW_ASSET_DOWNLOAD_FAILED` occurs, restore network and project asset access, then retry `preview.start`.\n- When launcher metadata is available, dependency installation reuses a supported npm or pnpm launcher. Without launcher metadata, it defaults to npm.\n- When npm is selected, dependency installation honors `npm_config_cache`. If it is unset, preview uses the writable `.webstudio/preview/.npm-cache` directory instead of the user\'s default npm cache.\n- For npm cache permission errors, unset `npm_config_cache` to use the preview-local cache, then retry. No cache deletion is required.\n- Do not add generated-preview dependencies to the repository root `package.json` or `pnpm-lock.yaml`.\n- If dependency installation fails, the error includes the selected package-manager path and sanitized diagnostics. Check that path and the network configuration, then reinstall or update the Webstudio CLI if the problem persists.\n\n## Visual Verification Rule\n\nAsk the user whether they want visual verification before starting it, unless the user explicitly requested screenshots, visual verification, or a rendered audit. Never start `preview.start`, `screenshot`, `screenshot.diff`, `vision.install-ocr`, or a rendered audit automatically. If the user does not opt in, use focused non-visual assertions and report that vision was not run. After the user opts in, use `preview.start` and `screenshot({ path })` so vision can inspect the current MCP session and verify that generated project files are current. Iterative preview is the default: it keeps one generated-site server and browser alive, regenerates changed files, and performs an ordinary page reload without Vite HMR. Use `mode: "production"` only for release-like verification; rendered audit selects it automatically. `preview.start` is long-lived and cannot be used through one-shot `mcp single-op-call`; from a shell, use `webstudio mcp run` for preview.start/screenshot/preview.stop in one shared process, or use a long-running MCP server. When a baseline exists, use screenshot.diff to get pixel regions, OCR text changes, and diff PNG artifacts.\n\nAn authenticated project share URL is used with `webstudio init --link`; it is\nnot a generated-site preview URL. Project screenshots and rendered audits use\nthe generated local preview owned by the current CLI/MCP process. Do not pass a\nBuilder/share URL to `screenshot`, even without query parameters. Use `path`\nafter starting preview, or use `baseUrl` only for an intentional generated site\nthat is already running. Path captures verify the generated-site root marker\nand fail instead of returning a screenshot of Builder chrome.\n\n## Vision Verification Loop\n\n- Ask the user whether they want visual verification unless they explicitly requested it. Stop before calling preview, capture, comparison, OCR, or rendered-audit tools until the user opts in.\n- Make focused page/content/style changes with semantic MCP tools.\n- Call preview.start once to keep the iterative generated site running. In shell-driven workflows, run preview.start, screenshot, and preview.stop inside one `webstudio mcp run` call so they share the same preview owner.\n- Read `preview.status.stale` before relying on generated output. When present, `renderedProjectVersion` identifies the last project version materialized into the preview; a stale preview refreshes automatically on the next managed screenshot or `preview.start` call.\n- {{dependency-notes}}\n- After MCP mutations, path-based screenshots regenerate the current session in place, wait for its exact project version, and normally reload the route. The server and browser remain alive. From one-shot shell calls or another process, pass `baseUrl` with `path` to capture an already-running generated site without starting it. Use preview.stop only in the same long-running MCP server or `webstudio mcp run` process that started preview; a separate one-shot `single-op-call` process does not own another process\'s preview controller.\n- For multi-page work, capture each changed page by path through the same preview server, for example screenshot({ path: "/" }), screenshot({ path: "/pricing" }), and screenshot({ path: "/about" }). The screenshot tool navigates directly to the requested route; no browser click navigation is required.\n- For responsive work, call list-breakpoints first, then capture screenshots at viewport widths based on the Builder breakpoints plus a narrow mobile and desktop width.\n- Call screenshot with { path: "/" } or the changed page path and viewport such as { width: 375, height: 812 } and { width: 1440, height: 900 }. For an existing preview in another process, call screenshot with { baseUrl: "http://127.0.0.1:5177", path: "/" }. Use waitForSelector when the page has a reliable ready marker, waitUntil:"networkidle" for network-heavy pages, and waitForTimeout only for final visual settling.\n- The MCP runner selects an available loopback port and returns the preview URL. Do not pass `host` or `port` to MCP preview or screenshot tools. To capture a generated site already running in another process, pass its `baseUrl` with `path`.\n- Automatic browser discovery checks system installations, configured browser paths, and Chromium installations in the Playwright browser cache.\n- The screenshot timeout bounds browser capture after the preview is ready. A timeout returns `SCREENSHOT_TIMEOUT`, resets the reusable browser session, and releases the shared preview lifecycle for cleanup.\n- {{diff}} When a baseline PNG exists, call screenshot.diff with baselinePath, currentPath, and outputDir for each page/viewport pair. Add expectedText when a specific visible phrase must be present; its assertions report pass/fail plus found and missing text. Add expectedVisual to set pass/fail limits for mismatch percentage, the number of changed regions, or an overall dominant color/brightness direction.\n- {{diff}} Read screenshot.diff textAnalysis: it reports OCR status plus text that appeared, disappeared, moved, changed content, or changed font/style geometry. If OCR is unavailable, expectedText assertions fail and textAnalysis reports why; ask the user for permission to install Tesseract, then call vision.install-ocr with { "confirm": true }, or rely on visual inspection.\n- Inspect every viewport PNG and any diff artifacts with vision, then compare layout, OCR text evidence, color, spacing, imagery, and responsive framing against the user intent.\n- If the screenshot does not match, apply another focused mutation and repeat screenshot verification.\n\n## Workflow Summary With Diff\n\nAfter the user explicitly requests or approves visual verification, call preview.start once to start the iterative generated-site preview, then screenshot({ path, viewport }) after focused mutations; path screenshots regenerate changed files and reload the route in the existing server and browser. For responsive work, use list-breakpoints and capture each changed page at Builder breakpoint widths plus mobile and desktop widths. Use screenshot.diff on each baseline/current page or viewport pair when a baseline exists, then inspect pixel regions, OCR textAnalysis, and PNG/diff artifacts with vision before finishing. Use mode: "production" only for release-like verification.\n\n## Workflow Summary Without Diff\n\nAfter the user explicitly requests or approves visual verification, call preview.start once to start the iterative generated-site preview, then screenshot({ path, viewport }) after focused mutations; path screenshots regenerate changed files and reload the route in the existing server and browser. For responsive work, use list-breakpoints and capture each changed page at Builder breakpoint widths plus mobile and desktop widths. Inspect every PNG with vision before finishing. Use mode: "production" only for release-like verification.\n\nEach screenshot result includes rendered `layout` metrics when the local browser\nprovides them. `layout.horizontalOverflow: true` is deterministic evidence that\nthe rendered document exceeds the requested viewport width. Use vision for\nclipping, wrapping, hierarchy, and other judgments that layout dimensions alone\ncannot establish.\n\nPass `includeImageMetrics: true` when an ordinary screenshot needs\n`layout.images`; rendered audit enables it automatically. The array includes\neach rendered image\'s Webstudio instance id when available, loading mode,\ncompletion state, natural dimensions, rendered dimensions, and document\nposition. Rendered audits use this evidence to report broken images, eager\nloading below the fold, and sources more than 2x the rendered dimensions in\nboth axes. Oversized-source results are optimization evidence, not universal\nperformance conformance.\n\nPass `includeResourceMetrics: true` when an ordinary screenshot needs sanitized\nResource Timing evidence; rendered audit enables it automatically. Resource\nmetrics contain only the URL pathname, initiator type, transfer/body sizes,\nduration, and browser-provided render-blocking status. Origins and query strings\nare omitted. Rendered audits report explicitly blocking resources and legacy\n`.ttf`, `.otf`, or `.woff` font files without applying a universal byte-size\nbudget.\n\n## Screenshot Verification Summary\n\nAfter the user explicitly requests or approves visual verification, call preview.start once inside a long-running MCP server, then use screenshot({ path, viewport }) for fast repeated checks across multiple pages. Iterative mode is the default: after MCP mutations, path screenshots regenerate changed files and reload the requested route while keeping the server and browser alive. Use mode: "production" only for release-like verification. From one-shot shell calls or another process, use screenshot({ baseUrl, path, viewport }) to capture an already-running preview/site without generating, building, starting, or restarting preview. Use path values such as "/", "/pricing", or "/about" to capture specific generated routes. For responsive work, read list-breakpoints and capture one familiar device viewport inside each Builder breakpoint range before using vision. Screenshot waits for load by default, then fonts and two layout frames; pass waitForSelector for app readiness, waitUntil:"networkidle" for network-heavy pages, and waitForTimeout for final settling. When a baseline exists, use screenshot.diff for changed regions, OCR textAnalysis, and diff artifacts on each baseline/current screenshot pair. Outside MCP, use `webstudio screenshot --path /pricing --output pricing.png` for one temporary generated preview capture, or keep `webstudio preview` running and pass its absolute URL to `webstudio screenshot` for repeated captures.\n\n## Screenshot Diff Evidence\n\n- Pixel evidence: total mismatch, changed regions, dominant color/luminance direction, diffPath, and contextDiffPath.\n- OCR evidence: textAnalysis.status, provider, and changes for appeared/disappeared/content_changed/moved/font_changed text. expectedText adds pass/fail assertions plus found and missing current-screen text. expectedVisual adds pass/fail quantitative checks for mismatch percentage, changed-region count, and the overall dominant color/brightness direction.\n- OCR dependency: screenshot.diff uses the system tesseract binary when available. If missing, it returns ocr_unavailable_tesseract_not_found_or_failed and still returns pixel evidence.\n- OCR install: MCP cannot prompt. Ask the user first; if they agree, call vision.install-ocr with { "confirm": true }. If automatic install is unavailable, follow the returned installUrl.\n- Final judgment: OCR and pixel diff are evidence. A vision-capable model must still inspect screenshots/diff artifacts and compare the rendered result to user intent.\n'
|
|
344546
344682
|
};
|