mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-09 22:14:29 +09:00
## Summary This improves how coding agents discover, author, verify, and audit Webstudio projects through the CLI and MCP. The work focuses on making agents more reliable with less context, while keeping edits semantic and Builder-editable. ## What changes for users - Agents can verify dynamic bindings after authoring and receive actionable diagnostics when expressions, resources, scopes, or component props do not line up. - Agents can create complete resource-backed runtime UI through a semantic operation instead of coordinating fragile low-level edits. - MCP discovery and command output are compact by default, with pagination and focused verbose detail when an agent needs more context. - Asset, style, token, font, page, and component inspection provides stronger usage context so agents can preserve the existing design system. - Rendered audits provide better evidence for responsive behavior, geometry, contrast, generated-build size, and large-project runs. - Cross-project MCP workflows can run from a manifest with explicit mutation approval, isolated failures, resumable progress, and persistent checkpoints. - Project-specific agent onboarding is clearer and does not add telemetry. - Packaged CLI releases gain stronger smoke verification for install, MCP startup, authoring, preview, screenshots, and audits. ## Reliability and architecture - Runtime operation output schemas are generated from the runtime registry and remain client-compatible. - Project-build exports are consolidated around architectural boundaries instead of feature-by-feature convenience entry points. - HTTP, preview, screenshot, and project-session paths are hardened for longer autonomous workflows. - Craft compatibility work is read-only and provenance-aware; it does not silently opt ordinary projects into Craft behavior. - A minimal-context evaluation harness exercises key workflows with a weaker model and stores bounded, privacy-safe evidence. ## Validation - Rebased on the latest `origin/main`. - `pnpm lint` - Repository-wide `oxfmt --check` - CLI typecheck and full test suite: 773 tests passed. - Project-build typecheck and full test suite: 1,866 tests passed. - Protocol typecheck and full test suite: 83 tests passed. - HTTP client tests: 23 passed. - CSS data tests: 3,620 passed, 1 skipped. - Focused Builder tests: 67 passed. - Package-boundary, generated API, and generated documentation checks passed. - Minimal-context agent evaluations passed for authenticated resource-backed UI and design-system-aware responsive authoring.
196 lines
5.1 KiB
TypeScript
196 lines
5.1 KiB
TypeScript
// Data utilities own access to Webstudio's instance-related stores and
|
|
// transaction boundaries. Put generic store reads/writes and content-mode data
|
|
// guards here, not tree-shape mutations.
|
|
import { toast } from "@webstudio-is/design-system";
|
|
import { type WebstudioData, isPageTemplate } from "@webstudio-is/sdk";
|
|
import {
|
|
executeBuilderRuntimeOperation,
|
|
createRuntimeMutationAccumulator,
|
|
type BuilderRuntimeOperationInput,
|
|
type BuilderRuntimeMutationOperationId,
|
|
type BuilderRuntimeOperationResult,
|
|
} from "@webstudio-is/project-build/runtime";
|
|
import { builderRuntimeContext } from "@webstudio-is/project-build/runtime";
|
|
import { type BuilderRuntimeMutation } from "@webstudio-is/project-build/runtime";
|
|
import { $canOpenPageTemplates, $selectedPage } from "../nano-states";
|
|
import { createTransactionFromBuilderPatchPayload } from "../sync/builder-patch";
|
|
import {
|
|
$assets,
|
|
$breakpoints,
|
|
$dataSources,
|
|
$instances,
|
|
$marketplaceProduct,
|
|
$pages,
|
|
$projectSettings,
|
|
$project,
|
|
$props,
|
|
$resources,
|
|
$styles,
|
|
$styleSourceSelections,
|
|
$styleSources,
|
|
} from "../sync/data-stores";
|
|
|
|
type RuntimeMutationResult<Id extends BuilderRuntimeMutationOperationId> =
|
|
Extract<BuilderRuntimeOperationResult<Id>, BuilderRuntimeMutation>;
|
|
|
|
export type RuntimeMutationOperation = {
|
|
[Id in BuilderRuntimeMutationOperationId]: {
|
|
id: Id;
|
|
input: BuilderRuntimeOperationInput<Id>;
|
|
};
|
|
}[BuilderRuntimeMutationOperationId];
|
|
|
|
export type WebstudioInstanceData = Pick<
|
|
WebstudioData,
|
|
| "instances"
|
|
| "props"
|
|
| "styleSourceSelections"
|
|
| "styleSources"
|
|
| "styles"
|
|
| "dataSources"
|
|
| "resources"
|
|
>;
|
|
|
|
const canCommitWebstudioData = () => {
|
|
const selectedPage = $selectedPage.get();
|
|
return (
|
|
isPageTemplate(selectedPage) === false ||
|
|
$canOpenPageTemplates.get() === true
|
|
);
|
|
};
|
|
|
|
export const migrateLoadedWebstudioData = () => {
|
|
const result = executeRuntimeMutation({
|
|
id: "system.migrateLoadedData",
|
|
input: {},
|
|
});
|
|
if (result?.result.didBreakCycles === true) {
|
|
toast.info("Detected and fixed cycles in the instance tree.");
|
|
}
|
|
};
|
|
|
|
const getRuntimeMutationContext = () => ({
|
|
createId: builderRuntimeContext.createId,
|
|
projectId: $project.get()?.id,
|
|
});
|
|
|
|
const requireSynchronousResult = <Result>(
|
|
id: BuilderRuntimeMutationOperationId,
|
|
result: Result | Promise<Result>
|
|
): Result => {
|
|
if (result instanceof Promise) {
|
|
throw Error(`Builder runtime operation "${id}" must be synchronous.`);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
const createRuntimeMutationArgs = <
|
|
Id extends BuilderRuntimeMutationOperationId,
|
|
>({
|
|
id,
|
|
input,
|
|
}: {
|
|
id: Id;
|
|
input: BuilderRuntimeOperationInput<Id>;
|
|
}) => ({
|
|
id,
|
|
state: getWebstudioData(),
|
|
input,
|
|
context: getRuntimeMutationContext(),
|
|
});
|
|
|
|
const commitRuntimeMutation = <Mutation extends BuilderRuntimeMutation>(
|
|
result: Mutation
|
|
): Mutation => {
|
|
createTransactionFromBuilderPatchPayload({
|
|
data: getWebstudioData(),
|
|
payload: result.payload,
|
|
});
|
|
return result;
|
|
};
|
|
|
|
export const executeRuntimeMutation = <
|
|
Id extends BuilderRuntimeMutationOperationId,
|
|
>({
|
|
id,
|
|
input,
|
|
}: {
|
|
id: Id;
|
|
input: BuilderRuntimeOperationInput<Id>;
|
|
}): RuntimeMutationResult<Id> | undefined => {
|
|
if (canCommitWebstudioData() === false) {
|
|
return;
|
|
}
|
|
return commitRuntimeMutation(
|
|
requireSynchronousResult(
|
|
id,
|
|
executeBuilderRuntimeOperation<RuntimeMutationResult<Id>>(
|
|
createRuntimeMutationArgs({ id, input })
|
|
)
|
|
)
|
|
);
|
|
};
|
|
|
|
export const executeRuntimeMutationSequence = (
|
|
operations: readonly RuntimeMutationOperation[]
|
|
): void => {
|
|
if (canCommitWebstudioData() === false) {
|
|
return;
|
|
}
|
|
const accumulator = createRuntimeMutationAccumulator(getWebstudioData());
|
|
const results = operations.map(({ id, input }) => {
|
|
return accumulator.stage(
|
|
requireSynchronousResult(
|
|
id,
|
|
executeBuilderRuntimeOperation<BuilderRuntimeMutation>({
|
|
id,
|
|
state: accumulator.state,
|
|
input,
|
|
context: getRuntimeMutationContext(),
|
|
})
|
|
)
|
|
);
|
|
});
|
|
commitRuntimeMutation(accumulator.complete({ results }));
|
|
};
|
|
|
|
export const executeRuntimeMutationAsync = async <
|
|
Id extends BuilderRuntimeMutationOperationId,
|
|
>({
|
|
id,
|
|
input,
|
|
}: {
|
|
id: Id;
|
|
input: BuilderRuntimeOperationInput<Id>;
|
|
}): Promise<RuntimeMutationResult<Id> | undefined> => {
|
|
if (canCommitWebstudioData() === false) {
|
|
return;
|
|
}
|
|
const result = await executeBuilderRuntimeOperation<
|
|
RuntimeMutationResult<Id>
|
|
>(createRuntimeMutationArgs({ id, input }));
|
|
return commitRuntimeMutation(result);
|
|
};
|
|
|
|
export const getWebstudioData = () => {
|
|
const pages = $pages.get();
|
|
if (pages === undefined) {
|
|
throw Error(`Cannot get webstudio data with empty pages`);
|
|
}
|
|
const projectSettings = $projectSettings.get() ?? { meta: {}, compiler: {} };
|
|
return {
|
|
pages,
|
|
instances: $instances.get(),
|
|
props: $props.get(),
|
|
dataSources: $dataSources.get(),
|
|
resources: $resources.get(),
|
|
breakpoints: $breakpoints.get(),
|
|
styleSourceSelections: $styleSourceSelections.get(),
|
|
styleSources: $styleSources.get(),
|
|
styles: $styles.get(),
|
|
assets: $assets.get(),
|
|
marketplaceProduct: $marketplaceProduct.get(),
|
|
projectSettings,
|
|
};
|
|
};
|