Files
webstudio/apps/builder/app/shared/code-editor.tsx
Oleg IsonenandClaude Fable 5 071a4d9a85 feat: MCP update 3 (#5864)
## Summary

This update adds new MCP and CLI capabilities for project auditing,
structured data, project-specific agent instructions, focused project
search, safer bulk editing, image descriptions, font discovery, visual
verification, and external registry inspection.

## New Capabilities

### Audit projects

- Run a single `audit` command across accessibility, SEO, security,
assets, styles, and performance.
- Limit an audit to selected scopes, pages, or severity levels.
- Receive stable findings with locations and suggested fixes.
- Run rendered audits across desktop, mobile, and Builder breakpoint
widths.
- Detect horizontal overflow, broken images, oversized images, eager
below-fold images, render-blocking resources, and legacy font formats.
- Identify unused assets, tokens, CSS variables, local styles, and
breakpoints.
- Identify duplicate token declarations.
- Detect accessibility issues involving names, labels, headings,
landmarks, IDs, keyboard focus, ARIA, iframes, and autoplaying media.
- Detect SEO issues involving titles, descriptions, language, social
images, and structured data.
- Detect unsafe new-tab links and write resources exposed during page
rendering.

### Search an entire project

- Search project text, labels, props, resources, assets, and styles
through `search-project`.
- Find relevant records without downloading and reparsing the full
project snapshot.
- Scope searches to the relevant project-data areas.

### Add validated JSON-LD

- Add structured data with a dedicated JSON-LD component.
- Place JSON-LD inside the page Head Slot.
- Edit JSON in a purpose-built code editor.
- Prevent structurally invalid JSON-LD from being saved.
- Validate Schema.org types, properties, and values.
- Find invalid, missing, unknown, deprecated, or incompatible structured
data through the SEO audit.
- Let agents discover the correct JSON-LD workflow without using page
custom metadata or a generic HTML embed.

### Configure project-specific agent instructions

- Add AI-agent instructions from the new **Agents** section in Project
Settings.
- Synchronize those instructions into a managed root `AGENTS.md`.
- Update or remove the managed file automatically during project sync.
- Protect existing user-owned `AGENTS.md` files from being overwritten.
- Share the instructions with authenticated CLI and MCP agents.
- Keep agent instructions out of publicly readable production bundles.

### Generate and manage image descriptions

- Audit image assets for missing descriptions.
- Let a vision-capable agent inspect each image in its rendered context.
- Save generated descriptions to multiple image assets in one operation.
- Mark images as intentionally decorative.
- Reuse the saved asset description wherever the image is used.
- Re-run the audit to verify that missing-description findings are
resolved.

### Perform bounded bulk replacements

- Replace matching literal text across selected pages with
`replace-text`.
- Replace matching fixed prop text with `replace-prop-text`.
- Replace matching resource names or URLs with `replace-resource-text`.
- Restrict replacements by page, instance, prop, resource, field, match
mode, and maximum change count.
- Leave dynamic expressions unchanged.

### Inspect element ancestry

- Return an element’s direct parent in instance listings.
- Request the complete ancestor chain when inspecting an element.
- Understand tree placement without rebuilding the instance hierarchy
from raw project data.

### Discover available fonts

- List uploaded font families through `list-fonts`.
- Discover available system font stacks.
- Inspect font family, style, weight, format, and associated asset
information before applying typography.

### Inspect external component registries

- Inspect a local or remote shadcn-style registry item.
- Read its description, files, dependencies, targets, and documentation.
- Receive an explicit Webstudio compatibility report.
- Inspect registry metadata without installing or executing external
code.

### Capture generated project routes directly

- Run `webstudio screenshot --path /route` against the configured
project.
- Generate a temporary production-style preview automatically.
- Capture the requested route.
- Stop the temporary preview after capture.
- Avoid manually building and starting a preview for a single
screenshot.

### Assert visual changes

- Add expected text to screenshot comparisons.
- Set maximum pixel mismatch limits.
- Set minimum or maximum changed-region counts.
- Assert whether overall brightness or a color channel moved in the
expected direction.
- Use these assertions as pass/fail evidence for visual changes.

### Require compatible CLI versions

- Send the project-bundle contract version with CLI synchronization
requests.
- Stop incompatible CLI versions before they receive incomplete project
data.
- Tell users exactly how to update the CLI or run the latest version
once.
- Prevent older clients from silently dropping project settings.

## Upgrade Note

This branch introduces a new synchronized project-bundle contract. Older
CLI versions must update before synchronizing against the new API.

```sh
npm install -g webstudio@latest
```

Or run the latest version once:

```sh
npx webstudio@latest <command>
```

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 11:27:58 +02:00

213 lines
5.2 KiB
TypeScript

import {
forwardRef,
useMemo,
type ComponentProps,
useEffect,
type ReactNode,
} from "react";
import { styleTags, tags } from "@lezer/highlight";
import {
keymap,
tooltips,
highlightSpecialChars,
highlightActiveLine,
} from "@codemirror/view";
import {
bracketMatching,
indentOnInput,
LanguageSupport,
LRLanguage,
} from "@codemirror/language";
import {
autocompletion,
closeBrackets,
closeBracketsKeymap,
completionKeymap,
} from "@codemirror/autocomplete";
import { html } from "@codemirror/lang-html";
import { javascript } from "@codemirror/lang-javascript";
import { markdown } from "@codemirror/lang-markdown";
import { css } from "@webstudio-is/design-system";
import {
EditorContent,
EditorDialog,
EditorDialogButton,
EditorDialogControl,
foldGutterExtension,
getCodeEditorCssVars,
} from "~/shared/code-editor-base";
import { cssCompletionSource, cssLanguage } from "@codemirror/lang-css";
const wrapperStyle = css({
position: "relative",
variants: {
size: {
default: getCodeEditorCssVars({ minHeight: "160px", maxHeight: "320px" }),
small: getCodeEditorCssVars({ minHeight: "16px", maxHeight: "120px" }),
full: {
...getCodeEditorCssVars({ minHeight: "100%", maxHeight: "100%" }),
height: "100%",
"& > div": { height: "100%" },
"& > div > div:first-child": { height: "100%" },
"& .cm-editor, & .cm-scroller": { height: "100%" },
},
},
},
defaultVariants: {
size: "default",
},
});
const getHtmlExtensions = () => [
highlightActiveLine(),
highlightSpecialChars(),
indentOnInput(),
html({}),
bracketMatching(),
closeBrackets(),
// render autocomplete in body
// to prevent popover scroll overflow
tooltips({ parent: document.body }),
autocompletion({ icons: false }),
keymap.of([...closeBracketsKeymap, ...completionKeymap]),
];
const getMarkdownExtensions = () => [
highlightActiveLine(),
highlightSpecialChars(),
indentOnInput(),
markdown({
extensions: [
{
props: [
styleTags({
HorizontalRule: tags.separator,
HeaderMark: tags.annotation,
QuoteMark: tags.annotation,
ListMark: tags.annotation,
LinkMark: tags.annotation,
EmphasisMark: tags.annotation,
CodeMark: tags.annotation,
InlineCode: tags.string,
URL: tags.url,
}),
],
},
],
}),
bracketMatching(),
closeBrackets(),
keymap.of(closeBracketsKeymap),
];
const getJsonExtensions = () => [
highlightActiveLine(),
highlightSpecialChars(),
indentOnInput(),
javascript(),
bracketMatching(),
closeBrackets(),
keymap.of(closeBracketsKeymap),
];
const cssPropertiesLanguage = LRLanguage.define({
name: "css",
parser: cssLanguage.configure({ top: "Styles" }).parser,
});
const cssProperties = new LanguageSupport(
cssPropertiesLanguage,
cssPropertiesLanguage.data.of({
autocomplete: cssCompletionSource,
})
);
const getCssPropertiesExtensions = () => [
highlightActiveLine(),
highlightSpecialChars(),
indentOnInput(),
cssProperties,
// render autocomplete in body
// to prevent popover scroll overflow
tooltips({ parent: document.body }),
autocompletion({ icons: false }),
];
export const CodeEditor = forwardRef<
HTMLDivElement,
Omit<ComponentProps<typeof EditorContent>, "extensions"> & {
lang?: "html" | "json" | "markdown" | "css-properties";
title?: ReactNode;
size?: "default" | "small" | "full";
}
>(({ lang, title, size, ...editorContentProps }, ref) => {
const extensions = useMemo(() => {
if (lang === "html") {
return getHtmlExtensions();
}
if (lang === "markdown") {
return getMarkdownExtensions();
}
if (lang === "json") {
return getJsonExtensions();
}
if (lang === "css-properties") {
return getCssPropertiesExtensions();
}
if (lang === undefined) {
return [];
}
lang satisfies never;
return [];
}, [lang]);
const dialogExtensions = useMemo(
() => [...extensions, foldGutterExtension],
[extensions]
);
// prevent clicking on autocomplete options propagating to body
// and closing dialogs and popovers
useEffect(() => {
const handlePointerDown = (event: PointerEvent) => {
if (
event.target instanceof HTMLElement &&
event.target.closest(".cm-tooltip-autocomplete")
) {
event.stopPropagation();
}
};
const options = { capture: true };
document.addEventListener("pointerdown", handlePointerDown, options);
return () => {
document.removeEventListener("pointerdown", handlePointerDown, options);
};
}, []);
return (
<div className={wrapperStyle({ size })} ref={ref}>
<EditorDialogControl>
<EditorContent {...editorContentProps} extensions={extensions} />
<EditorDialog
title={title}
content={
<EditorContent
{...editorContentProps}
extensions={dialogExtensions}
/>
}
>
<EditorDialogButton />
</EditorDialog>
</EditorDialogControl>
</div>
);
});
CodeEditor.displayName = "CodeEditor";