Files
Oleg Isonen d186d24501 feat: Improve MCP project audits, visual verification, and dynamic content authoring (#5874)
This update helps AI agents understand, audit, and visually verify
Webstudio projects more reliably. It also adds clearer guidance for
expressions, Collections, screenshots, and responsive rendered audits.

### Audit complete projects

- Run one `audit` command across accessibility, SEO, security, assets,
styles, and performance.
- Audit the whole project or focus on a specific page, scope, or
severity.
- Receive stable findings with locations, explanations, and recommended
fixes.
- Continue large result sets through pagination without repeating
general guidance.

### Audit the rendered website

- Build and inspect the generated website, rather than relying only on
stored project data.
- Check every static page at representative mobile, tablet, and desktop
widths.
- Use one suitable viewport inside each configured breakpoint range.
- Reuse the same browser and resize pages between captures for
substantially faster audits.
- Process multiple pages concurrently while keeping resource use
bounded.
- Supply concrete examples for dynamic routes such as blog posts.
- Review and confirm unusually large screenshot plans before they run.

### Find visual and performance problems

Rendered audits can detect:

- Horizontal page overflow.
- Broken images.
- Images loaded eagerly below the fold.
- Images that are much larger than their rendered size.
- Render-blocking resources.
- Legacy font formats.
- Pages that fail to load or redirect somewhere unexpected.
- Pages captured before their layout becomes stable.

Detailed screenshots, measurements, failures, and timing evidence are
saved locally for further inspection.

### Capture generated pages directly

- Capture a project route with `webstudio screenshot --path /route`.
- Automatically generate and start a temporary production-style preview.
- Stop the temporary preview after the screenshot is captured.
- Return navigation, responsive layout, and page-size evidence with JSON
output.
- Allow external image domains needed by the generated preview.

### Reuse generated previews

- Cache generated preview builds when the project has not changed.
- Reuse preview dependencies across regenerations.
- Keep preview dependencies isolated from the user’s project
dependencies.
- Surface preview build and startup failures with useful diagnostic
output.
- Include local project assets in generated previews.

### Author Collections correctly

- Explain when Collection is required for arrays and objects.
- Guide agents to bind the complete iterable instead of one item or the
response wrapper.
- Explain how Collection repeats its children for every entry.
- Explain current-item and current-key scope.
- Preserve Webstudio’s generated Collection parameters.
- Guide agents to use stable values for repeated interactive components.

### Write expressions with fewer mistakes

- Add a dedicated `webstudio://project/expressions` reference.
- Document expression scope, supported syntax, and supported methods.
- Explain how resources, variables, Collections, actions, and `system`
values are accessed.
- Distinguish fixed values from runtime expressions.
- Provide valid examples for text, props, resource URLs, headers, query
parameters, and bodies.
- Warn agents that valid syntax does not guarantee that runtime data has
the expected shape.

### Improve agent discovery

- Make Collection and expression workflows discoverable through
`meta.guide`.
- Return Collection-specific instructions through `components.get`.
- Point relevant command schemas to the expression reference.
- Keep detailed guidance available without placing it in every tool
response.

### Improve audit reliability

- Report which rendered captures passed, found issues, failed, or were
skipped.
- Distinguish planning, preview, capture, and cleanup failures.
- Include actionable retry instructions with rendered failures.
- Preserve completed evidence when part of an audit fails.
- Detect stale or conflicting preview servers more clearly.
- Support cancellation and bounded capture timeouts.

### Improve packaged CLI verification

- Add a release-shaped CLI smoke test.
- Install the packed CLI in an isolated temporary project.
- Fail on peer-dependency warning floods.
- Verify preview, screenshots, MCP calls, rendered audits, and generated
assets.
- Confirm that generic React Router previews do not include Docker-only
image dependencies.
- Keep CLI and MCP stdout machine-readable while progress remains on
stderr.

## User Impact

Agents can now audit a real rendered Webstudio site, inspect responsive
behavior, find concrete visual and performance problems, and provide
evidence for their conclusions. They also receive much clearer
instructions when turning API data into repeated content or writing
dynamic expressions.
2026-07-14 13:52:09 +02:00

75 lines
1.9 KiB
TypeScript

import type { ChildProcess } from "node:child_process";
const delay = (milliseconds: number) =>
new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
const hasExited = (child: ChildProcess) =>
child.exitCode !== null || child.signalCode !== null;
const isProcessGroupRunning = (child: ChildProcess, pid: number) => {
try {
process.kill(-pid, 0);
return true;
} catch (error) {
const code = (error as NodeJS.ErrnoException).code;
if (code === "ESRCH") {
return false;
}
if (code === "EPERM") {
return hasExited(child) === false;
}
throw error;
}
};
export const stopChildProcess = async (
child: ChildProcess,
{
killGroup = false,
timeoutMs = 5_000,
}: { killGroup?: boolean; timeoutMs?: number } = {}
) => {
const usesProcessGroup =
killGroup && process.platform !== "win32" && child.pid !== undefined;
const isRunning = () =>
usesProcessGroup
? isProcessGroupRunning(child, child.pid as number)
: hasExited(child) === false;
const waitUntilStopped = async () => {
const deadline = Date.now() + timeoutMs;
while (isRunning() && Date.now() < deadline) {
await delay(Math.min(25, Math.max(1, deadline - Date.now())));
}
return isRunning() === false;
};
if (isRunning() === false) {
return;
}
const sendSignal = (signal: NodeJS.Signals) => {
if (usesProcessGroup) {
try {
process.kill(-(child.pid as number), signal);
return;
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ESRCH") {
return;
}
}
}
child.kill(signal);
};
sendSignal("SIGTERM");
if (await waitUntilStopped()) {
return;
}
sendSignal("SIGKILL");
if ((await waitUntilStopped()) === false) {
throw new Error(
`Child process${child.pid === undefined ? "" : ` ${child.pid}`} did not exit after SIGKILL`
);
}
};